DESKTOP-O04BTUJ\muzen 2 年之前
父节点
当前提交
5df45d8e96
共有 36 个文件被更改,包括 2736 次插入180 次删除
  1. 103 95
      package-lock.json
  2. 2 0
      package.json
  3. 19 19
      public/index.html
  4. 18 1
      src/App.vue
  5. 19 0
      src/api/article.js
  6. 1 1
      src/api/guide.js
  7. 29 0
      src/api/lottery.js
  8. 19 0
      src/common/main.scss
  9. 25 8
      src/components/open-app/open-app.vue
  10. 45 0
      src/components/uni-popup-dialog/keypress.js
  11. 271 0
      src/components/uni-popup-dialog/uni-popup-dialog.vue
  12. 143 0
      src/components/uni-popup-message/uni-popup-message.vue
  13. 187 0
      src/components/uni-popup-share/uni-popup-share.vue
  14. 7 0
      src/components/uni-popup/i18n/en.json
  15. 8 0
      src/components/uni-popup/i18n/index.js
  16. 7 0
      src/components/uni-popup/i18n/zh-Hans.json
  17. 7 0
      src/components/uni-popup/i18n/zh-Hant.json
  18. 45 0
      src/components/uni-popup/keypress.js
  19. 26 0
      src/components/uni-popup/popup.js
  20. 474 0
      src/components/uni-popup/uni-popup.vue
  21. 128 0
      src/components/uni-transition/createAnimation.js
  22. 277 0
      src/components/uni-transition/uni-transition.vue
  23. 2 1
      src/main.js
  24. 33 25
      src/manifest.json
  25. 19 0
      src/pages.json
  26. 154 0
      src/pages/article/index.vue
  27. 190 0
      src/pages/lottery/detail.vue
  28. 324 0
      src/pages/lottery/index.vue
  29. 5 3
      src/pages/museum/index.vue
  30. 143 12
      src/pages/user/agreement.vue
  31. 1 11
      src/pages/vip/index.vue
  32. 二进制
      src/static/lottery/bg-1.png
  33. 二进制
      src/static/lottery/bg.png
  34. 二进制
      src/static/lottery/btn.png
  35. 二进制
      src/static/lottery/noChance.png
  36. 5 4
      src/utils/request.js

+ 103 - 95
package-lock.json

@@ -26,6 +26,8 @@
         "@dcloudio/uni-quickapp-native": "^2.0.1-34920220630001",
         "@dcloudio/uni-quickapp-webview": "^2.0.1-34920220630001",
         "@dcloudio/uni-stat": "^2.0.1-34920220630001",
+        "@lucky-canvas/uni": "^0.0.11",
+        "@lucky-canvas/vue": "^0.1.11",
         "@vue/shared": "^3.0.0",
         "@zebra-ui/swiper": "^2.2.2",
         "core-js": "^3.6.5",
@@ -3355,6 +3357,32 @@
         "@jridgewell/sourcemap-codec": "^1.4.10"
       }
     },
+    "node_modules/@lucky-canvas/uni": {
+      "version": "0.0.11",
+      "resolved": "https://registry.npmmirror.com/@lucky-canvas/uni/-/uni-0.0.11.tgz",
+      "integrity": "sha512-QbT+ve1B0bc7TS7WrbDrB3EMOy2HcGYORbyEmjzIy9zp29yu2UYVwg/P2xfcwXpZLOJJd+cOlDEY4GgAreiDPA==",
+      "dependencies": {
+        "lucky-canvas": "~1.7.19"
+      }
+    },
+    "node_modules/@lucky-canvas/vue": {
+      "version": "0.1.11",
+      "resolved": "https://registry.npmmirror.com/@lucky-canvas/vue/-/vue-0.1.11.tgz",
+      "integrity": "sha512-5vm0txSKRBtMgrE/HZEvw1joSTx9NTdAkc8tBp/aX0LxyhQtiTVBLsRgdYUK/OiURCL8bo+046BTGnV+Q4JFlg==",
+      "dependencies": {
+        "@vue/composition-api": "^1.0.0",
+        "lucky-canvas": "^1.7.23",
+        "vue-demi": "^0.7.4"
+      },
+      "peerDependencies": {
+        "vue": "^2.0.0 || >=3.0.0-rc.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@mrmlnc/readdir-enhanced": {
       "version": "2.2.1",
       "resolved": "https://registry.npmmirror.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@@ -4217,34 +4245,6 @@
       "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
       "dev": true
     },
-    "node_modules/@vue/compiler-sfc": {
-      "version": "2.7.3",
-      "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-2.7.3.tgz",
-      "integrity": "sha512-/9SO6KeR1ljo+j4Tdkl9zsFG2yY4NQ9p3GKdPVCU99bmkNkTW8g9Tq8SMEvhOfo+RhBC0PdDAOmibl+N37f5YA==",
-      "dependencies": {
-        "@babel/parser": "^7.18.4",
-        "postcss": "^8.4.14",
-        "source-map": "^0.6.1"
-      }
-    },
-    "node_modules/@vue/compiler-sfc/node_modules/picocolors": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz",
-      "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
-    },
-    "node_modules/@vue/compiler-sfc/node_modules/postcss": {
-      "version": "8.4.14",
-      "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.14.tgz",
-      "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
-      "dependencies": {
-        "nanoid": "^3.3.4",
-        "picocolors": "^1.0.0",
-        "source-map-js": "^1.0.2"
-      },
-      "engines": {
-        "node": "^10 || ^12 || >=14"
-      }
-    },
     "node_modules/@vue/component-compiler-utils": {
       "version": "3.3.0",
       "resolved": "https://registry.npmmirror.com/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz",
@@ -4275,6 +4275,14 @@
         "node": ">=4"
       }
     },
+    "node_modules/@vue/composition-api": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmmirror.com/@vue/composition-api/-/composition-api-1.7.0.tgz",
+      "integrity": "sha512-hxOgLYR+wjuPX9bkP2pAPlqUs98XxBoa9DSLyp1z6+YR92wC42PZcZKs4d+VRtcv4udOv041Kss+F6ap5nj8YA==",
+      "peerDependencies": {
+        "vue": ">= 2.5 < 2.7"
+      }
+    },
     "node_modules/@vue/preload-webpack-plugin": {
       "version": "1.1.2",
       "resolved": "https://registry.npmmirror.com/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz",
@@ -7233,11 +7241,6 @@
       "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
       "dev": true
     },
-    "node_modules/csstype": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.0.tgz",
-      "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
-    },
     "node_modules/cyclist": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/cyclist/-/cyclist-1.0.1.tgz",
@@ -13348,6 +13351,11 @@
         "yallist": "^2.1.2"
       }
     },
+    "node_modules/lucky-canvas": {
+      "version": "1.7.27",
+      "resolved": "https://registry.npmmirror.com/lucky-canvas/-/lucky-canvas-1.7.27.tgz",
+      "integrity": "sha512-Ftz6qD+863bI7xijBmZg3dw3cNEc7odPr70EZQcGA14y3TgTAzH65HPosOCd6kKUlMwhntBaHMx3onoj9MtJRQ=="
+    },
     "node_modules/make-dir": {
       "version": "3.1.0",
       "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz",
@@ -13813,17 +13821,6 @@
       "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==",
       "optional": true
     },
-    "node_modules/nanoid": {
-      "version": "3.3.4",
-      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz",
-      "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
-      "bin": {
-        "nanoid": "bin/nanoid.cjs"
-      },
-      "engines": {
-        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
-      }
-    },
     "node_modules/nanomatch": {
       "version": "1.2.13",
       "resolved": "https://registry.npmmirror.com/nanomatch/-/nanomatch-1.2.13.tgz",
@@ -17155,6 +17152,7 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz",
       "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -18766,12 +18764,27 @@
       "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
     },
     "node_modules/vue": {
-      "version": "2.7.3",
-      "resolved": "https://registry.npmmirror.com/vue/-/vue-2.7.3.tgz",
-      "integrity": "sha512-7MTirXG7LYJi3r2jeYy7EiXIhEE85uP3kBwSxqwDsvIfy/l7+qR4U6ajw8ji1KoP+wYYg7ZY28TBTf3P3Fa4Nw==",
-      "dependencies": {
-        "@vue/compiler-sfc": "2.7.3",
-        "csstype": "^3.1.0"
+      "version": "2.6.14",
+      "resolved": "https://registry.npmmirror.com/vue/-/vue-2.6.14.tgz",
+      "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
+    },
+    "node_modules/vue-demi": {
+      "version": "0.7.5",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.7.5.tgz",
+      "integrity": "sha512-eFSQSvbQdY7C9ujOzvM6tn7XxwLjn0VQDXQsiYBLBwf28Na+2nTQR4BBBcomhmdP6mmHlBKAwarq6a0BPG87hQ==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-beta.1",
+        "vue": "^2.6.0 || >=3.0.0-rc.1"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
       }
     },
     "node_modules/vue-hot-reload-api": {
@@ -22510,6 +22523,24 @@
         "@jridgewell/sourcemap-codec": "^1.4.10"
       }
     },
+    "@lucky-canvas/uni": {
+      "version": "0.0.11",
+      "resolved": "https://registry.npmmirror.com/@lucky-canvas/uni/-/uni-0.0.11.tgz",
+      "integrity": "sha512-QbT+ve1B0bc7TS7WrbDrB3EMOy2HcGYORbyEmjzIy9zp29yu2UYVwg/P2xfcwXpZLOJJd+cOlDEY4GgAreiDPA==",
+      "requires": {
+        "lucky-canvas": "~1.7.19"
+      }
+    },
+    "@lucky-canvas/vue": {
+      "version": "0.1.11",
+      "resolved": "https://registry.npmmirror.com/@lucky-canvas/vue/-/vue-0.1.11.tgz",
+      "integrity": "sha512-5vm0txSKRBtMgrE/HZEvw1joSTx9NTdAkc8tBp/aX0LxyhQtiTVBLsRgdYUK/OiURCL8bo+046BTGnV+Q4JFlg==",
+      "requires": {
+        "@vue/composition-api": "^1.0.0",
+        "lucky-canvas": "^1.7.23",
+        "vue-demi": "^0.7.4"
+      }
+    },
     "@mrmlnc/readdir-enhanced": {
       "version": "2.2.1",
       "resolved": "https://registry.npmmirror.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@@ -23249,33 +23280,6 @@
         }
       }
     },
-    "@vue/compiler-sfc": {
-      "version": "2.7.3",
-      "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-2.7.3.tgz",
-      "integrity": "sha512-/9SO6KeR1ljo+j4Tdkl9zsFG2yY4NQ9p3GKdPVCU99bmkNkTW8g9Tq8SMEvhOfo+RhBC0PdDAOmibl+N37f5YA==",
-      "requires": {
-        "@babel/parser": "^7.18.4",
-        "postcss": "^8.4.14",
-        "source-map": "^0.6.1"
-      },
-      "dependencies": {
-        "picocolors": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz",
-          "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
-        },
-        "postcss": {
-          "version": "8.4.14",
-          "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.14.tgz",
-          "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
-          "requires": {
-            "nanoid": "^3.3.4",
-            "picocolors": "^1.0.0",
-            "source-map-js": "^1.0.2"
-          }
-        }
-      }
-    },
     "@vue/component-compiler-utils": {
       "version": "3.3.0",
       "resolved": "https://registry.npmmirror.com/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz",
@@ -23303,6 +23307,12 @@
         }
       }
     },
+    "@vue/composition-api": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmmirror.com/@vue/composition-api/-/composition-api-1.7.0.tgz",
+      "integrity": "sha512-hxOgLYR+wjuPX9bkP2pAPlqUs98XxBoa9DSLyp1z6+YR92wC42PZcZKs4d+VRtcv4udOv041Kss+F6ap5nj8YA==",
+      "requires": {}
+    },
     "@vue/preload-webpack-plugin": {
       "version": "1.1.2",
       "resolved": "https://registry.npmmirror.com/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz",
@@ -25796,11 +25806,6 @@
         }
       }
     },
-    "csstype": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.0.tgz",
-      "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
-    },
     "cyclist": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/cyclist/-/cyclist-1.0.1.tgz",
@@ -30698,6 +30703,11 @@
         "yallist": "^2.1.2"
       }
     },
+    "lucky-canvas": {
+      "version": "1.7.27",
+      "resolved": "https://registry.npmmirror.com/lucky-canvas/-/lucky-canvas-1.7.27.tgz",
+      "integrity": "sha512-Ftz6qD+863bI7xijBmZg3dw3cNEc7odPr70EZQcGA14y3TgTAzH65HPosOCd6kKUlMwhntBaHMx3onoj9MtJRQ=="
+    },
     "make-dir": {
       "version": "3.1.0",
       "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz",
@@ -31089,11 +31099,6 @@
       "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==",
       "optional": true
     },
-    "nanoid": {
-      "version": "3.3.4",
-      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz",
-      "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
-    },
     "nanomatch": {
       "version": "1.2.13",
       "resolved": "https://registry.npmmirror.com/nanomatch/-/nanomatch-1.2.13.tgz",
@@ -33866,7 +33871,8 @@
     "source-map-js": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz",
-      "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
+      "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+      "dev": true
     },
     "source-map-resolve": {
       "version": "0.5.3",
@@ -35206,13 +35212,15 @@
       "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
     },
     "vue": {
-      "version": "2.7.3",
-      "resolved": "https://registry.npmmirror.com/vue/-/vue-2.7.3.tgz",
-      "integrity": "sha512-7MTirXG7LYJi3r2jeYy7EiXIhEE85uP3kBwSxqwDsvIfy/l7+qR4U6ajw8ji1KoP+wYYg7ZY28TBTf3P3Fa4Nw==",
-      "requires": {
-        "@vue/compiler-sfc": "2.7.3",
-        "csstype": "^3.1.0"
-      }
+      "version": "2.6.14",
+      "resolved": "https://registry.npmmirror.com/vue/-/vue-2.6.14.tgz",
+      "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
+    },
+    "vue-demi": {
+      "version": "0.7.5",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.7.5.tgz",
+      "integrity": "sha512-eFSQSvbQdY7C9ujOzvM6tn7XxwLjn0VQDXQsiYBLBwf28Na+2nTQR4BBBcomhmdP6mmHlBKAwarq6a0BPG87hQ==",
+      "requires": {}
     },
     "vue-hot-reload-api": {
       "version": "2.3.4",

+ 2 - 0
package.json

@@ -66,6 +66,8 @@
     "@dcloudio/uni-quickapp-native": "^2.0.1-34920220630001",
     "@dcloudio/uni-quickapp-webview": "^2.0.1-34920220630001",
     "@dcloudio/uni-stat": "^2.0.1-34920220630001",
+    "@lucky-canvas/uni": "^0.0.11",
+    "@lucky-canvas/vue": "^0.1.11",
     "@vue/shared": "^3.0.0",
     "@zebra-ui/swiper": "^2.2.2",
     "core-js": "^3.6.5",

+ 19 - 19
public/index.html

@@ -1,25 +1,25 @@
 <!DOCTYPE html>
 <html lang="zh-CN">
 
-    <head>
-        <meta charset="utf-8">
-        <meta http-equiv="X-UA-Compatible" content="IE=edge">
-        <title>
-            <%= htmlWebpackPlugin.options.title %>
-        </title>
-        <script>
-            var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
-            document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')
-        </script>
-        <link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
-    </head>
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <title>
+        <%= htmlWebpackPlugin.options.title %>
+    </title>
+    <script>
+        var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
+        document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+    </script>
+    <link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
+</head>
 
-    <body>
-        <noscript>
-            <strong>Please enable JavaScript to continue.</strong>
-        </noscript>
-        <div id="app"></div>
-        <!-- built files will be auto injected -->
-    </body>
+<body>
+    <noscript>
+        <strong>Please enable JavaScript to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+</body>
 
 </html>

+ 18 - 1
src/App.vue

@@ -1,6 +1,23 @@
 <script>
 export default {
-
+  globalData: {
+    // 用户信息
+    userInfo: {},
+    // 状态栏
+    statusBarHeight: 20
+  },
+  onLaunch() {
+    // 获取App方法
+    getUserInfo.postMessage('获取用户信息')
+    getDeviceInfo.postMessage('获取设备信息')
+    // 暴露setUserInfo方法给APP
+    window['setUserInfo'] = res => {
+      this.globalData.userInfo = JSON.parse(res)
+    }
+    window['setDeviceInfo'] = res => {
+      this.globalData.statusBarHeight = JSON.parse(res).statusHeight
+    }
+  }
 }
 </script>
 

+ 19 - 0
src/api/article.js

@@ -0,0 +1,19 @@
+import request from "../utils/request";
+
+// 设备文章
+export function detail(data){
+  return request({
+    url: `/device/article/detail`,
+    method: 'get',
+    data
+  })
+}
+
+// 评论
+export function list(data){
+  return request({
+    url: `/device/articleComment/list`,
+    method: 'get',
+    data
+  })
+}

+ 1 - 1
src/api/guide.js

@@ -4,6 +4,6 @@ export function getContent(data) {
   return request({
     url: `/device/getGuidePageContent`,
     method: 'get',
-    data: data
+    data
   })
 }

+ 29 - 0
src/api/lottery.js

@@ -0,0 +1,29 @@
+import request from '@/utils/request'
+
+// 抽奖页面信息
+export function page(header) {
+  return request({
+    url: `/point/lottery/lotteryPage`,
+    header,
+    method: 'get'
+  })
+}
+
+// 抽奖结果
+export function result(header) {
+  return request({
+    url: `/point/lottery/getLotteryResult`,
+    header,
+    method: 'get'
+  })
+}
+
+// 领奖
+export function receive(header, data) {
+  return request({
+    url: `/point/lottery/doLottery`,
+    header,
+    method: 'post',
+    data
+  })
+}

+ 19 - 0
src/common/main.scss

@@ -42,4 +42,23 @@ img[mini]{
 
 .app-container{
   padding: 32rpx;
+}
+
+input{
+  height: 40px;
+  line-height: 40px;
+}
+
+input,
+textarea{
+  border: 1px solid #dcdfe6;
+  padding: 0 15px;
+  border-radius: 4px;
+  margin: 16rpx 0 0 0;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+textarea{
+  padding: 30rpx;
 }

+ 25 - 8
src/components/open-app/open-app.vue

@@ -7,23 +7,40 @@
       </view>
       <uni-button type="submit" @click="open">打开App</uni-button>
     </view>
-    <view style="height: 88rpx" />
+    <view style="height: 88rpx" v-if="isShow" />
   </view>
 </template>
 
 <script>
 export default {
-  props:{
+  props: {
     // 界面类型
-    page: Number,
+    page: {
+      type: Number,
+      default: null
+    },
     // 频道类型
-    channel: Number,
+    channel: {
+      type: Number,
+      default: null
+    },
     // 音频类型
-    audioType: Number,
+    audioType: {
+      type: Number,
+      default: null
+    },
     // 音频Id
-    audioId: Number
+    audioId: {
+      type: Number,
+      default: null
+    },
+    // 是否覆盖
+    isShow: {
+      type: Boolean,
+      default: true
+    }
   },
-  watch:{
+  watch: {
     channel(val) {
       return val
     },
@@ -36,7 +53,7 @@ export default {
   },
   methods: {
     // 打开app
-    open(){
+    open() {
       this.openApp(this.page, this.channel, this.audioType, this.audioId)
     }
   }

+ 45 - 0
src/components/uni-popup-dialog/keypress.js

@@ -0,0 +1,45 @@
+// #ifdef H5
+export default {
+  name: 'Keypress',
+  props: {
+    disable: {
+      type: Boolean,
+      default: false
+    }
+  },
+  mounted () {
+    const keyNames = {
+      esc: ['Esc', 'Escape'],
+      tab: 'Tab',
+      enter: 'Enter',
+      space: [' ', 'Spacebar'],
+      up: ['Up', 'ArrowUp'],
+      left: ['Left', 'ArrowLeft'],
+      right: ['Right', 'ArrowRight'],
+      down: ['Down', 'ArrowDown'],
+      delete: ['Backspace', 'Delete', 'Del']
+    }
+    const listener = ($event) => {
+      if (this.disable) {
+        return
+      }
+      const keyName = Object.keys(keyNames).find(key => {
+        const keyName = $event.key
+        const value = keyNames[key]
+        return value === keyName || (Array.isArray(value) && value.includes(keyName))
+      })
+      if (keyName) {
+        // 避免和其他按键事件冲突
+        setTimeout(() => {
+          this.$emit(keyName, {})
+        }, 0)
+      }
+    }
+    document.addEventListener('keyup', listener)
+    this.$once('hook:beforeDestroy', () => {
+      document.removeEventListener('keyup', listener)
+    })
+  },
+	render: () => {}
+}
+// #endif

+ 271 - 0
src/components/uni-popup-dialog/uni-popup-dialog.vue

@@ -0,0 +1,271 @@
+<template>
+	<view class="uni-popup-dialog">
+		<view class="uni-dialog-title">
+			<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{titleText}}</text>
+		</view>
+		<view v-if="mode === 'base'" class="uni-dialog-content">
+			<slot>
+				<text class="uni-dialog-content-text">{{content}}</text>
+			</slot>
+		</view>
+		<view v-else class="uni-dialog-content">
+			<slot>
+				<input class="uni-dialog-input" v-model="val" type="text" :placeholder="placeholderText" :focus="focus" >
+			</slot>
+		</view>
+		<view class="uni-dialog-button-group">
+			<view class="uni-dialog-button" @click="closeDialog">
+				<text class="uni-dialog-button-text">{{closeText}}</text>
+			</view>
+			<view class="uni-dialog-button uni-border-left" @click="onOk">
+				<text class="uni-dialog-button-text uni-button-color">{{okText}}</text>
+			</view>
+		</view>
+
+	</view>
+</template>
+
+<script>
+	import popup from '../uni-popup/popup.js'
+	import {
+	initVueI18n
+	} from '@dcloudio/uni-i18n'
+	import messages from '../uni-popup/i18n/index.js'
+	const {	t } = initVueI18n(messages)
+	/**
+	 * PopUp 弹出层-对话框样式
+	 * @description 弹出层-对话框样式
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
+	 * @property {String} value input 模式下的默认值
+	 * @property {String} placeholder input 模式下输入提示
+	 * @property {String} type = [success|warning|info|error] 主题样式
+	 *  @value success 成功
+	 * 	@value warning 提示
+	 * 	@value info 消息
+	 * 	@value error 错误
+	 * @property {String} mode = [base|input] 模式、
+	 * 	@value base 基础对话框
+	 * 	@value input 可输入对话框
+	 * @property {String} content 对话框内容
+	 * @property {Boolean} beforeClose 是否拦截取消事件
+	 * @event {Function} confirm 点击确认按钮触发
+	 * @event {Function} close 点击取消按钮触发
+	 */
+
+	export default {
+		name: "uniPopupDialog",
+		mixins: [popup],
+		emits:['confirm','close'],
+		props: {
+			value: {
+				type: [String, Number],
+				default: ''
+			},
+			placeholder: {
+				type: [String, Number],
+				default: ''
+			},
+			type: {
+				type: String,
+				default: 'error'
+			},
+			mode: {
+				type: String,
+				default: 'base'
+			},
+			title: {
+				type: String,
+				default: ''
+			},
+			content: {
+				type: String,
+				default: ''
+			},
+			beforeClose: {
+				type: Boolean,
+				default: false
+			},
+			cancelText:{
+				type: String,
+				default: ''
+			},
+			confirmText:{
+				type: String,
+				default: ''
+			}
+		},
+		data() {
+			return {
+				dialogType: 'error',
+				focus: false,
+				val: ""
+			}
+		},
+		computed: {
+			okText() {
+				return this.confirmText || t("uni-popup.ok")
+			},
+			closeText() {
+				return this.cancelText || t("uni-popup.cancel")
+			},
+			placeholderText() {
+				return this.placeholder || t("uni-popup.placeholder")
+			},
+			titleText() {
+				return this.title || t("uni-popup.title")
+			}
+		},
+		watch: {
+			type(val) {
+				this.dialogType = val
+			},
+			mode(val) {
+				if (val === 'input') {
+					this.dialogType = 'info'
+				}
+			},
+			value(val) {
+				this.val = val
+			}
+		},
+		created() {
+			// 对话框遮罩不可点击
+			this.popup.disableMask()
+			// this.popup.closeMask()
+			if (this.mode === 'input') {
+				this.dialogType = 'info'
+				this.val = this.value
+			} else {
+				this.dialogType = this.type
+			}
+		},
+		mounted() {
+			this.focus = true
+		},
+		methods: {
+			/**
+			 * 点击确认按钮
+			 */
+			onOk() {
+				if (this.mode === 'input'){
+					this.$emit('confirm', this.val)
+				}else{
+					this.$emit('confirm')
+				}
+				if(this.beforeClose) return
+				this.popup.close()
+			},
+			/**
+			 * 点击取消按钮
+			 */
+			closeDialog() {
+				this.$emit('close')
+				if(this.beforeClose) return
+				this.popup.close()
+			},
+			close(){
+				this.popup.close()
+			}
+		}
+	}
+</script>
+
+<style lang="scss" >
+	.uni-popup-dialog {
+		width: 300px;
+		border-radius: 11px;
+		background-color: #fff;
+	}
+
+	.uni-dialog-title {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+		padding-top: 25px;
+	}
+
+	.uni-dialog-title-text {
+		font-size: 16px;
+		font-weight: 500;
+	}
+
+	.uni-dialog-content {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		padding: 20px;
+	}
+
+	.uni-dialog-content-text {
+		font-size: 14px;
+		color: #6C6C6C;
+	}
+
+	.uni-dialog-button-group {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		border-top-color: #f5f5f5;
+		border-top-style: solid;
+		border-top-width: 1px;
+	}
+
+	.uni-dialog-button {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+
+		flex: 1;
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		height: 45px;
+	}
+
+	.uni-border-left {
+		border-left-color: #f0f0f0;
+		border-left-style: solid;
+		border-left-width: 1px;
+	}
+
+	.uni-dialog-button-text {
+		font-size: 16px;
+		color: #333;
+	}
+
+	.uni-button-color {
+		color: #007aff;
+	}
+
+	.uni-dialog-input {
+		flex: 1;
+		font-size: 14px;
+		border: 1px #eee solid;
+		height: 40px;
+		padding: 0 10px;
+		border-radius: 5px;
+		color: #555;
+	}
+
+	.uni-popup__success {
+		color: #4cd964;
+	}
+
+	.uni-popup__warn {
+		color: #f0ad4e;
+	}
+
+	.uni-popup__error {
+		color: #dd524d;
+	}
+
+	.uni-popup__info {
+		color: #909399;
+	}
+</style>

+ 143 - 0
src/components/uni-popup-message/uni-popup-message.vue

@@ -0,0 +1,143 @@
+<template>
+	<view class="uni-popup-message">
+		<view class="uni-popup-message__box fixforpc-width" :class="'uni-popup__'+type">
+			<slot>
+				<text class="uni-popup-message-text" :class="'uni-popup__'+type+'-text'">{{message}}</text>
+			</slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	import popup from '../uni-popup/popup.js'
+	/**
+	 * PopUp 弹出层-消息提示
+	 * @description 弹出层-消息提示
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
+	 * @property {String} type = [success|warning|info|error] 主题样式
+	 *  @value success 成功
+	 * 	@value warning 提示
+	 * 	@value info 消息
+	 * 	@value error 错误
+	 * @property {String} message 消息提示文字
+	 * @property {String} duration 显示时间,设置为 0 则不会自动关闭
+	 */
+
+	export default {
+		name: 'uniPopupMessage',
+		mixins:[popup],
+		props: {
+			/**
+			 * 主题 success/warning/info/error	  默认 success
+			 */
+			type: {
+				type: String,
+				default: 'success'
+			},
+			/**
+			 * 消息文字
+			 */
+			message: {
+				type: String,
+				default: ''
+			},
+			/**
+			 * 显示时间,设置为 0 则不会自动关闭
+			 */
+			duration: {
+				type: Number,
+				default: 3000
+			},
+			maskShow:{
+				type:Boolean,
+				default:false
+			}
+		},
+		data() {
+			return {}
+		},
+		created() {
+			this.popup.maskShow = this.maskShow
+			this.popup.messageChild = this
+		},
+		methods: {
+			timerClose(){
+				if(this.duration === 0) return
+				clearTimeout(this.timer) 
+				this.timer = setTimeout(()=>{
+					this.popup.close()
+				},this.duration)
+			}
+		}
+	}
+</script>
+<style lang="scss" >
+	.uni-popup-message {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+	}
+
+	.uni-popup-message__box {
+		background-color: #e1f3d8;
+		padding: 10px 15px;
+		border-color: #eee;
+		border-style: solid;
+		border-width: 1px;
+		flex: 1;
+	}
+
+	@media screen and (min-width: 500px) {
+		.fixforpc-width {
+			margin-top: 20px;
+			border-radius: 4px;
+			flex: none;
+			min-width: 380px;
+			/* #ifndef APP-NVUE */
+			max-width: 50%;
+			/* #endif */
+			/* #ifdef APP-NVUE */
+			max-width: 500px;
+			/* #endif */
+		}
+	}
+
+	.uni-popup-message-text {
+		font-size: 14px;
+		padding: 0;
+	}
+
+	.uni-popup__success {
+		background-color: #e1f3d8;
+	}
+
+	.uni-popup__success-text {
+		color: #67C23A;
+	}
+
+	.uni-popup__warn {
+		background-color: #faecd8;
+	}
+
+	.uni-popup__warn-text {
+		color: #E6A23C;
+	}
+
+	.uni-popup__error {
+		background-color: #fde2e2;
+	}
+
+	.uni-popup__error-text {
+		color: #F56C6C;
+	}
+
+	.uni-popup__info {
+		background-color: #F2F6FC;
+	}
+
+	.uni-popup__info-text {
+		color: #909399;
+	}
+</style>

+ 187 - 0
src/components/uni-popup-share/uni-popup-share.vue

@@ -0,0 +1,187 @@
+<template>
+	<view class="uni-popup-share">
+		<view class="uni-share-title"><text class="uni-share-title-text">{{shareTitleText}}</text></view>
+		<view class="uni-share-content">
+			<view class="uni-share-content-box">
+				<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
+					<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
+					<text class="uni-share-text">{{item.text}}</text>
+				</view>
+
+			</view>
+		</view>
+		<view class="uni-share-button-box">
+			<button class="uni-share-button" @click="close">{{cancelText}}</button>
+		</view>
+	</view>
+</template>
+
+<script>
+	import popup from '../uni-popup/popup.js'
+	import {
+	initVueI18n
+	} from '@dcloudio/uni-i18n'
+	import messages from '../uni-popup/i18n/index.js'
+	const {	t	} = initVueI18n(messages)
+	export default {
+		name: 'UniPopupShare',
+		mixins:[popup],
+		emits:['select'],
+		props: {
+			title: {
+				type: String,
+				default: ''
+			},
+			beforeClose: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				bottomData: [{
+						text: '微信',
+						icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c2b17470-50be-11eb-b680-7980c8a877b8.png',
+						name: 'wx'
+					},
+					{
+						text: '支付宝',
+						icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/d684ae40-50be-11eb-8ff1-d5dcf8779628.png',
+						name: 'wx'
+					},
+					{
+						text: 'QQ',
+						icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png',
+						name: 'qq'
+					},
+					{
+						text: '新浪',
+						icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png',
+						name: 'sina'
+					},
+					// {
+					// 	text: '百度',
+					// 	icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/1ec6e920-50bf-11eb-8a36-ebb87efcf8c0.png',
+					// 	name: 'copy'
+					// },
+					// {
+					// 	text: '其他',
+					// 	icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/2e0fdfe0-50bf-11eb-b997-9918a5dda011.png',
+					// 	name: 'more'
+					// }
+				]
+			}
+		},
+		created() {},
+		computed: {
+			cancelText() {
+				return t("uni-popup.cancel")
+			},
+		shareTitleText() {
+				return this.title || t("uni-popup.shareTitle")
+			}
+		},
+		methods: {
+			/**
+			 * 选择内容
+			 */
+			select(item, index) {
+				this.$emit('select', {
+					item,
+					index
+				})
+				this.close()
+
+			},
+			/**
+			 * 关闭窗口
+			 */
+			close() {
+				if(this.beforeClose) return
+				this.popup.close()
+			}
+		}
+	}
+</script>
+<style lang="scss" >
+	.uni-popup-share {
+		background-color: #fff;
+		border-top-left-radius: 11px;
+		border-top-right-radius: 11px;
+	}
+	.uni-share-title {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		height: 40px;
+	}
+	.uni-share-title-text {
+		font-size: 14px;
+		color: #666;
+	}
+	.uni-share-content {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+		padding-top: 10px;
+	}
+
+	.uni-share-content-box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		flex-wrap: wrap;
+		width: 360px;
+	}
+
+	.uni-share-content-item {
+		width: 90px;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		justify-content: center;
+		padding: 10px 0;
+		align-items: center;
+	}
+
+	.uni-share-content-item:active {
+		background-color: #f5f5f5;
+	}
+
+	.uni-share-image {
+		width: 30px;
+		height: 30px;
+	}
+
+	.uni-share-text {
+		margin-top: 10px;
+		font-size: 14px;
+		color: #3B4144;
+	}
+
+	.uni-share-button-box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		padding: 10px 15px;
+	}
+
+	.uni-share-button {
+		flex: 1;
+		border-radius: 50px;
+		color: #666;
+		font-size: 16px;
+	}
+
+	.uni-share-button::after {
+		border-radius: 50px;
+	}
+</style>

+ 7 - 0
src/components/uni-popup/i18n/en.json

@@ -0,0 +1,7 @@
+{
+	"uni-popup.cancel": "cancel",
+	"uni-popup.ok": "ok",
+	"uni-popup.placeholder": "pleace enter",
+	"uni-popup.title": "Hint",
+	"uni-popup.shareTitle": "Share to"
+}

+ 8 - 0
src/components/uni-popup/i18n/index.js

@@ -0,0 +1,8 @@
+import en from './en.json'
+import zhHans from './zh-Hans.json'
+import zhHant from './zh-Hant.json'
+export default {
+	en,
+	'zh-Hans': zhHans,
+	'zh-Hant': zhHant
+}

+ 7 - 0
src/components/uni-popup/i18n/zh-Hans.json

@@ -0,0 +1,7 @@
+{
+	"uni-popup.cancel": "取消",
+	"uni-popup.ok": "确定",
+	"uni-popup.placeholder": "请输入",
+		"uni-popup.title": "提示",
+		"uni-popup.shareTitle": "分享到"
+}

+ 7 - 0
src/components/uni-popup/i18n/zh-Hant.json

@@ -0,0 +1,7 @@
+{
+	"uni-popup.cancel": "取消",
+	"uni-popup.ok": "確定",
+	"uni-popup.placeholder": "請輸入",
+	"uni-popup.title": "提示",
+	"uni-popup.shareTitle": "分享到"
+}

+ 45 - 0
src/components/uni-popup/keypress.js

@@ -0,0 +1,45 @@
+// #ifdef H5
+export default {
+  name: 'Keypress',
+  props: {
+    disable: {
+      type: Boolean,
+      default: false
+    }
+  },
+  mounted () {
+    const keyNames = {
+      esc: ['Esc', 'Escape'],
+      tab: 'Tab',
+      enter: 'Enter',
+      space: [' ', 'Spacebar'],
+      up: ['Up', 'ArrowUp'],
+      left: ['Left', 'ArrowLeft'],
+      right: ['Right', 'ArrowRight'],
+      down: ['Down', 'ArrowDown'],
+      delete: ['Backspace', 'Delete', 'Del']
+    }
+    const listener = ($event) => {
+      if (this.disable) {
+        return
+      }
+      const keyName = Object.keys(keyNames).find(key => {
+        const keyName = $event.key
+        const value = keyNames[key]
+        return value === keyName || (Array.isArray(value) && value.includes(keyName))
+      })
+      if (keyName) {
+        // 避免和其他按键事件冲突
+        setTimeout(() => {
+          this.$emit(keyName, {})
+        }, 0)
+      }
+    }
+    document.addEventListener('keyup', listener)
+    // this.$once('hook:beforeDestroy', () => {
+    //   document.removeEventListener('keyup', listener)
+    // })
+  },
+	render: () => {}
+}
+// #endif

+ 26 - 0
src/components/uni-popup/popup.js

@@ -0,0 +1,26 @@
+
+export default {
+	data() {
+		return {
+			
+		}
+	},
+	created(){
+		this.popup = this.getParent()
+	},
+	methods:{
+		/**
+		 * 获取父元素实例
+		 */
+		getParent(name = 'uniPopup') {
+			let parent = this.$parent;
+			let parentName = parent.$options.name;
+			while (parentName !== name) {
+				parent = parent.$parent;
+				if (!parent) return false
+				parentName = parent.$options.name;
+			}
+			return parent;
+		},
+	}
+}

+ 474 - 0
src/components/uni-popup/uni-popup.vue

@@ -0,0 +1,474 @@
+<template>
+	<view v-if="showPopup" class="uni-popup" :class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']">
+		<view @touchstart="touchstart">
+			<uni-transition key="1" v-if="maskShow" name="mask" mode-class="fade" :styles="maskClass"
+				:duration="duration" :show="showTrans" @click="onTap" />
+			<uni-transition key="2" :mode-class="ani" name="content" :styles="transClass" :duration="duration"
+				:show="showTrans" @click="onTap">
+				<view class="uni-popup__wrapper" :style="{ backgroundColor: bg }" :class="[popupstyle]" @click="clear">
+					<slot />
+				</view>
+			</uni-transition>
+		</view>
+		<!-- #ifdef H5 -->
+		<keypress v-if="maskShow" @esc="onTap" />
+		<!-- #endif -->
+	</view>
+</template>
+
+<script>
+	// #ifdef H5
+	import keypress from './keypress.js'
+	// #endif
+
+	/**
+	 * PopUp 弹出层
+	 * @description 弹出层组件,为了解决遮罩弹层的问题
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
+	 * @property {String} type = [top|center|bottom|left|right|message|dialog|share] 弹出方式
+	 * 	@value top 顶部弹出
+	 * 	@value center 中间弹出
+	 * 	@value bottom 底部弹出
+	 * 	@value left		左侧弹出
+	 * 	@value right  右侧弹出
+	 * 	@value message 消息提示
+	 * 	@value dialog 对话框
+	 * 	@value share 底部分享示例
+	 * @property {Boolean} animation = [true|false] 是否开启动画
+	 * @property {Boolean} maskClick = [true|false] 蒙版点击是否关闭弹窗(废弃)
+	 * @property {Boolean} isMaskClick = [true|false] 蒙版点击是否关闭弹窗
+	 * @property {String}  backgroundColor 主窗口背景色
+	 * @property {String}  maskBackgroundColor 蒙版颜色
+	 * @property {Boolean} safeArea		   是否适配底部安全区
+	 * @event {Function} change 打开关闭弹窗触发,e={show: false}
+	 * @event {Function} maskClick 点击遮罩触发
+	 */
+
+	export default {
+		name: 'uniPopup',
+		components: {
+			// #ifdef H5
+			keypress
+			// #endif
+		},
+		emits: ['change', 'maskClick'],
+		props: {
+			// 开启动画
+			animation: {
+				type: Boolean,
+				default: true
+			},
+			// 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
+			// message: 消息提示 ; dialog : 对话框
+			type: {
+				type: String,
+				default: 'center'
+			},
+			// maskClick
+			isMaskClick: {
+				type: Boolean,
+				default: null
+			},
+			// TODO 2 个版本后废弃属性 ,使用 isMaskClick
+			maskClick: {
+				type: Boolean,
+				default: null
+			},
+			backgroundColor: {
+				type: String,
+				default: 'none'
+			},
+			safeArea: {
+				type: Boolean,
+				default: true
+			},
+			maskBackgroundColor: {
+				type: String,
+				default: 'rgba(0, 0, 0, 0.4)'
+			},
+		},
+
+		watch: {
+			/**
+			 * 监听type类型
+			 */
+			type: {
+				handler: function(type) {
+					if (!this.config[type]) return
+					this[this.config[type]](true)
+				},
+				immediate: true
+			},
+			isDesktop: {
+				handler: function(newVal) {
+					if (!this.config[newVal]) return
+					this[this.config[this.type]](true)
+				},
+				immediate: true
+			},
+			/**
+			 * 监听遮罩是否可点击
+			 * @param {Object} val
+			 */
+			maskClick: {
+				handler: function(val) {
+					this.mkclick = val
+				},
+				immediate: true
+			},
+			isMaskClick: {
+				handler: function(val) {
+					this.mkclick = val
+				},
+				immediate: true
+			},
+			// H5 下禁止底部滚动
+			showPopup(show) {
+				// #ifdef H5
+				// fix by mehaotian 处理 h5 滚动穿透的问题
+				document.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible'
+				// #endif
+			}
+		},
+		data() {
+			return {
+				duration: 300,
+				ani: [],
+				showPopup: false,
+				showTrans: false,
+				popupWidth: 0,
+				popupHeight: 0,
+				config: {
+					top: 'top',
+					bottom: 'bottom',
+					center: 'center',
+					left: 'left',
+					right: 'right',
+					message: 'top',
+					dialog: 'center',
+					share: 'bottom'
+				},
+				maskClass: {
+					position: 'fixed',
+					bottom: 0,
+					top: 0,
+					left: 0,
+					right: 0,
+					backgroundColor: 'rgba(0, 0, 0, 0.4)'
+				},
+				transClass: {
+					position: 'fixed',
+					left: 0,
+					right: 0
+				},
+				maskShow: true,
+				mkclick: true,
+				popupstyle: this.isDesktop ? 'fixforpc-top' : 'top'
+			}
+		},
+		computed: {
+			isDesktop() {
+				return this.popupWidth >= 500 && this.popupHeight >= 500
+			},
+			bg() {
+				if (this.backgroundColor === '' || this.backgroundColor === 'none') {
+					return 'transparent'
+				}
+				return this.backgroundColor
+			}
+		},
+		mounted() {
+			const fixSize = () => {
+				const {
+					windowWidth,
+					windowHeight,
+					windowTop,
+					safeArea,
+					screenHeight,
+					safeAreaInsets
+				} = uni.getSystemInfoSync()
+				this.popupWidth = windowWidth
+				this.popupHeight = windowHeight + (windowTop || 0)
+				// TODO fix by mehaotian 是否适配底部安全区 ,目前微信ios 、和 app ios 计算有差异,需要框架修复
+				if (safeArea && this.safeArea) {
+					// #ifdef MP-WEIXIN
+					this.safeAreaInsets = screenHeight - safeArea.bottom
+					// #endif
+					// #ifndef MP-WEIXIN
+					this.safeAreaInsets = safeAreaInsets.bottom
+					// #endif
+				} else {
+					this.safeAreaInsets = 0
+				}
+			}
+			fixSize()
+			// #ifdef H5
+			// window.addEventListener('resize', fixSize)
+			// this.$once('hook:beforeDestroy', () => {
+			// 	window.removeEventListener('resize', fixSize)
+			// })
+			// #endif
+		},
+		// #ifndef VUE3
+		// TODO vue2
+		destroyed() {
+			this.setH5Visible()
+		},
+		// #endif
+		// #ifdef VUE3
+		// TODO vue3
+		unmounted() {
+			this.setH5Visible()
+		},
+		// #endif
+		created() {
+			// this.mkclick =  this.isMaskClick || this.maskClick
+			if (this.isMaskClick === null && this.maskClick === null) {
+				this.mkclick = true
+			} else {
+				this.mkclick = this.isMaskClick !== null ? this.isMaskClick : this.maskClick
+			}
+			if (this.animation) {
+				this.duration = 300
+			} else {
+				this.duration = 0
+			}
+			// TODO 处理 message 组件生命周期异常的问题
+			this.messageChild = null
+			// TODO 解决头条冒泡的问题
+			this.clearPropagation = false
+			this.maskClass.backgroundColor = this.maskBackgroundColor
+		},
+		methods: {
+			setH5Visible() {
+				// #ifdef H5
+				// fix by mehaotian 处理 h5 滚动穿透的问题
+				document.getElementsByTagName('body')[0].style.overflow = 'visible'
+				// #endif
+			},
+			/**
+			 * 公用方法,不显示遮罩层
+			 */
+			closeMask() {
+				this.maskShow = false
+			},
+			/**
+			 * 公用方法,遮罩层禁止点击
+			 */
+			disableMask() {
+				this.mkclick = false
+			},
+			// TODO nvue 取消冒泡
+			clear(e) {
+				// #ifndef APP-NVUE
+				e.stopPropagation()
+				// #endif
+				this.clearPropagation = true
+			},
+
+			open(direction) {
+				// fix by mehaotian 处理快速打开关闭的情况
+				if (this.showPopup) {
+					clearTimeout(this.timer)
+					this.showPopup = false
+				}
+				let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share']
+				if (!(direction && innerType.indexOf(direction) !== -1)) {
+					direction = this.type
+				}
+				if (!this.config[direction]) {
+					console.error('缺少类型:', direction)
+					return
+				}
+				this[this.config[direction]]()
+				this.$emit('change', {
+					show: true,
+					type: direction
+				})
+			},
+			close(type) {
+				this.showTrans = false
+				this.$emit('change', {
+					show: false,
+					type: this.type
+				})
+				clearTimeout(this.timer)
+				// // 自定义关闭事件
+				// this.customOpen && this.customClose()
+				this.timer = setTimeout(() => {
+					this.showPopup = false
+				}, 300)
+			},
+			// TODO 处理冒泡事件,头条的冒泡事件有问题 ,先这样兼容
+			touchstart() {
+				this.clearPropagation = false
+			},
+
+			onTap() {
+				if (this.clearPropagation) {
+					// fix by mehaotian 兼容 nvue
+					this.clearPropagation = false
+					return
+				}
+				this.$emit('maskClick')
+				if (!this.mkclick) return
+				this.close()
+			},
+			/**
+			 * 顶部弹出样式处理
+			 */
+			top(type) {
+				this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top'
+				this.ani = ['slide-top']
+				this.transClass = {
+					position: 'fixed',
+					left: 0,
+					right: 0,
+					backgroundColor: this.bg
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+				this.$nextTick(() => {
+					if (this.messageChild && this.type === 'message') {
+						this.messageChild.timerClose()
+					}
+				})
+			},
+			/**
+			 * 底部弹出样式处理
+			 */
+			bottom(type) {
+				this.popupstyle = 'bottom'
+				this.ani = ['slide-bottom']
+				this.transClass = {
+					position: 'fixed',
+					left: 0,
+					right: 0,
+					bottom: 0,
+					paddingBottom: this.safeAreaInsets + 'px',
+					backgroundColor: this.bg
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+			},
+			/**
+			 * 中间弹出样式处理
+			 */
+			center(type) {
+				this.popupstyle = 'center'
+				this.ani = ['zoom-out', 'fade']
+				this.transClass = {
+					position: 'fixed',
+					/* #ifndef APP-NVUE */
+					display: 'flex',
+					flexDirection: 'column',
+					/* #endif */
+					bottom: 0,
+					left: 0,
+					right: 0,
+					top: 0,
+					justifyContent: 'center',
+					alignItems: 'center'
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+			},
+			left(type) {
+				this.popupstyle = 'left'
+				this.ani = ['slide-left']
+				this.transClass = {
+					position: 'fixed',
+					left: 0,
+					bottom: 0,
+					top: 0,
+					backgroundColor: this.bg,
+					/* #ifndef APP-NVUE */
+					display: 'flex',
+					flexDirection: 'column'
+					/* #endif */
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+			},
+			right(type) {
+				this.popupstyle = 'right'
+				this.ani = ['slide-right']
+				this.transClass = {
+					position: 'fixed',
+					bottom: 0,
+					right: 0,
+					top: 0,
+					backgroundColor: this.bg,
+					/* #ifndef APP-NVUE */
+					display: 'flex',
+					flexDirection: 'column'
+					/* #endif */
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+			}
+		}
+	}
+</script>
+<style lang="scss">
+	.uni-popup {
+		position: fixed;
+		/* #ifndef APP-NVUE */
+		z-index: 99;
+
+		/* #endif */
+		&.top,
+		&.left,
+		&.right {
+			/* #ifdef H5 */
+			top: var(--window-top);
+			/* #endif */
+			/* #ifndef H5 */
+			top: 0;
+			/* #endif */
+		}
+
+		.uni-popup__wrapper {
+			/* #ifndef APP-NVUE */
+			display: block;
+			/* #endif */
+			position: relative;
+
+			/* iphonex 等安全区设置,底部安全区适配 */
+			/* #ifndef APP-NVUE */
+			// padding-bottom: constant(safe-area-inset-bottom);
+			// padding-bottom: env(safe-area-inset-bottom);
+			/* #endif */
+			&.left,
+			&.right {
+				/* #ifdef H5 */
+				padding-top: var(--window-top);
+				/* #endif */
+				/* #ifndef H5 */
+				padding-top: 0;
+				/* #endif */
+				flex: 1;
+			}
+		}
+	}
+
+	.fixforpc-z-index {
+		/* #ifndef APP-NVUE */
+		z-index: 999;
+		/* #endif */
+	}
+
+	.fixforpc-top {
+		top: 0;
+	}
+</style>

+ 128 - 0
src/components/uni-transition/createAnimation.js

@@ -0,0 +1,128 @@
+// const defaultOption = {
+// 	duration: 300,
+// 	timingFunction: 'linear',
+// 	delay: 0,
+// 	transformOrigin: '50% 50% 0'
+// }
+// #ifdef APP-NVUE
+const nvueAnimation = uni.requireNativePlugin('animation')
+// #endif
+class MPAnimation {
+	constructor(options, _this) {
+		this.options = options
+		this.animation = uni.createAnimation(options)
+		this.currentStepAnimates = {}
+		this.next = 0
+		this.$ = _this
+
+	}
+
+	_nvuePushAnimates(type, args) {
+		let aniObj = this.currentStepAnimates[this.next]
+		let styles = {}
+		if (!aniObj) {
+			styles = {
+				styles: {},
+				config: {}
+			}
+		} else {
+			styles = aniObj
+		}
+		if (animateTypes1.includes(type)) {
+			if (!styles.styles.transform) {
+				styles.styles.transform = ''
+			}
+			let unit = ''
+			if(type === 'rotate'){
+				unit = 'deg'
+			}
+			styles.styles.transform += `${type}(${args+unit}) `
+		} else {
+			styles.styles[type] = `${args}`
+		}
+		this.currentStepAnimates[this.next] = styles
+	}
+	_animateRun(styles = {}, config = {}) {
+		let ref = this.$.$refs['ani'].ref
+		if (!ref) return
+		return new Promise((resolve, reject) => {
+			nvueAnimation.transition(ref, {
+				styles,
+				...config
+			}, res => {
+				resolve()
+			})
+		})
+	}
+
+	_nvueNextAnimate(animates, step = 0, fn) {
+		let obj = animates[step]
+		if (obj) {
+			let {
+				styles,
+				config
+			} = obj
+			this._animateRun(styles, config).then(() => {
+				step += 1
+				this._nvueNextAnimate(animates, step, fn)
+			})
+		} else {
+			this.currentStepAnimates = {}
+			typeof fn === 'function' && fn()
+			this.isEnd = true
+		}
+	}
+
+	step(config = {}) {
+		// #ifndef APP-NVUE
+		this.animation.step(config)
+		// #endif
+		// #ifdef APP-NVUE
+		this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
+		this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
+		this.next++
+		// #endif
+		return this
+	}
+
+	run(fn) {
+		// #ifndef APP-NVUE
+		this.$.animationData = this.animation.export()
+		this.$.timer = setTimeout(() => {
+			typeof fn === 'function' && fn()
+		}, this.$.durationTime)
+		// #endif
+		// #ifdef APP-NVUE
+		this.isEnd = false
+		let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref
+		if(!ref) return
+		this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
+		this.next = 0
+		// #endif
+	}
+}
+
+
+const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
+	'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
+	'translateZ'
+]
+const animateTypes2 = ['opacity', 'backgroundColor']
+const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
+animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
+	MPAnimation.prototype[type] = function(...args) {
+		// #ifndef APP-NVUE
+		this.animation[type](...args)
+		// #endif
+		// #ifdef APP-NVUE
+		this._nvuePushAnimates(type, args)
+		// #endif
+		return this
+	}
+})
+
+export function createAnimation(option, _this) {
+	if(!_this) return
+	clearTimeout(_this.timer)
+	return new MPAnimation(option, _this)
+}

+ 277 - 0
src/components/uni-transition/uni-transition.vue

@@ -0,0 +1,277 @@
+<template>
+	<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
+</template>
+
+<script>
+import { createAnimation } from './createAnimation'
+
+/**
+ * Transition 过渡动画
+ * @description 简单过渡动画组件
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=985
+ * @property {Boolean} show = [false|true] 控制组件显示或隐藏
+ * @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
+ *  @value fade 渐隐渐出过渡
+ *  @value slide-top 由上至下过渡
+ *  @value slide-right 由右至左过渡
+ *  @value slide-bottom 由下至上过渡
+ *  @value slide-left 由左至右过渡
+ *  @value zoom-in 由小到大过渡
+ *  @value zoom-out 由大到小过渡
+ * @property {Number} duration 过渡动画持续时间
+ * @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red`
+ */
+export default {
+	name: 'uniTransition',
+	emits:['click','change'],
+	props: {
+		show: {
+			type: Boolean,
+			default: false
+		},
+		modeClass: {
+			type: [Array, String],
+			default() {
+				return 'fade'
+			}
+		},
+		duration: {
+			type: Number,
+			default: 300
+		},
+		styles: {
+			type: Object,
+			default() {
+				return {}
+			}
+		},
+		customClass:{
+			type: String,
+			default: ''
+		}
+	},
+	data() {
+		return {
+			isShow: false,
+			transform: '',
+			opacity: 1,
+			animationData: {},
+			durationTime: 300,
+			config: {}
+		}
+	},
+	watch: {
+		show: {
+			handler(newVal) {
+				if (newVal) {
+					this.open()
+				} else {
+					// 避免上来就执行 close,导致动画错乱
+					if (this.isShow) {
+						this.close()
+					}
+				}
+			},
+			immediate: true
+		}
+	},
+	computed: {
+		// 生成样式数据
+		stylesObject() {
+			let styles = {
+				...this.styles,
+				'transition-duration': this.duration / 1000 + 's'
+			}
+			let transform = ''
+			for (let i in styles) {
+				let line = this.toLine(i)
+				transform += line + ':' + styles[i] + ';'
+			}
+			return transform
+		},
+		// 初始化动画条件
+		transformStyles() {
+			return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
+		}
+	},
+	created() {
+		// 动画默认配置
+		this.config = {
+			duration: this.duration,
+			timingFunction: 'ease',
+			transformOrigin: '50% 50%',
+			delay: 0
+		}
+		this.durationTime = this.duration
+	},
+	methods: {
+		/**
+		 *  ref 触发 初始化动画
+		 */
+		init(obj = {}) {
+			if (obj.duration) {
+				this.durationTime = obj.duration
+			}
+			this.animation = createAnimation(Object.assign(this.config, obj),this)
+		},
+		/**
+		 * 点击组件触发回调
+		 */
+		onClick() {
+			this.$emit('click', {
+				detail: this.isShow
+			})
+		},
+		/**
+		 * ref 触发 动画分组
+		 * @param {Object} obj
+		 */
+		step(obj, config = {}) {
+			if (!this.animation) return
+			for (let i in obj) {
+				try {
+					if(typeof obj[i] === 'object'){
+						this.animation[i](...obj[i])
+					}else{
+						this.animation[i](obj[i])
+					}
+				} catch (e) {
+					console.error(`方法 ${i} 不存在`)
+				}
+			}
+			this.animation.step(config)
+			return this
+		},
+		/**
+		 *  ref 触发 执行动画
+		 */
+		run(fn) {
+			if (!this.animation) return
+			this.animation.run(fn)
+		},
+		// 开始过度动画
+		open() {
+			clearTimeout(this.timer)
+			this.transform = ''
+			this.isShow = true
+			let { opacity, transform } = this.styleInit(false)
+			if (typeof opacity !== 'undefined') {
+				this.opacity = opacity
+			}
+			this.transform = transform
+			// 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常
+			this.$nextTick(() => {
+				// TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器
+				this.timer = setTimeout(() => {
+					this.animation = createAnimation(this.config, this)
+					this.tranfromInit(false).step()
+					this.animation.run()
+					this.$emit('change', {
+						detail: this.isShow
+					})
+				}, 20)
+			})
+		},
+		// 关闭过度动画
+		close(type) {
+			if (!this.animation) return
+			this.tranfromInit(true)
+				.step()
+				.run(() => {
+					this.isShow = false
+					this.animationData = null
+					this.animation = null
+					let { opacity, transform } = this.styleInit(false)
+					this.opacity = opacity || 1
+					this.transform = transform
+					this.$emit('change', {
+						detail: this.isShow
+					})
+				})
+		},
+		// 处理动画开始前的默认样式
+		styleInit(type) {
+			let styles = {
+				transform: ''
+			}
+			let buildStyle = (type, mode) => {
+				if (mode === 'fade') {
+					styles.opacity = this.animationType(type)[mode]
+				} else {
+					styles.transform += this.animationType(type)[mode] + ' '
+				}
+			}
+			if (typeof this.modeClass === 'string') {
+				buildStyle(type, this.modeClass)
+			} else {
+				this.modeClass.forEach(mode => {
+					buildStyle(type, mode)
+				})
+			}
+			return styles
+		},
+		// 处理内置组合动画
+		tranfromInit(type) {
+			let buildTranfrom = (type, mode) => {
+				let aniNum = null
+				if (mode === 'fade') {
+					aniNum = type ? 0 : 1
+				} else {
+					aniNum = type ? '-100%' : '0'
+					if (mode === 'zoom-in') {
+						aniNum = type ? 0.8 : 1
+					}
+					if (mode === 'zoom-out') {
+						aniNum = type ? 1.2 : 1
+					}
+					if (mode === 'slide-right') {
+						aniNum = type ? '100%' : '0'
+					}
+					if (mode === 'slide-bottom') {
+						aniNum = type ? '100%' : '0'
+					}
+				}
+				this.animation[this.animationMode()[mode]](aniNum)
+			}
+			if (typeof this.modeClass === 'string') {
+				buildTranfrom(type, this.modeClass)
+			} else {
+				this.modeClass.forEach(mode => {
+					buildTranfrom(type, mode)
+				})
+			}
+
+			return this.animation
+		},
+		animationType(type) {
+			return {
+				fade: type ? 1 : 0,
+				'slide-top': `translateY(${type ? '0' : '-100%'})`,
+				'slide-right': `translateX(${type ? '0' : '100%'})`,
+				'slide-bottom': `translateY(${type ? '0' : '100%'})`,
+				'slide-left': `translateX(${type ? '0' : '-100%'})`,
+				'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
+				'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
+			}
+		},
+		// 内置动画类型与实际动画对应字典
+		animationMode() {
+			return {
+				fade: 'opacity',
+				'slide-top': 'translateY',
+				'slide-right': 'translateX',
+				'slide-bottom': 'translateY',
+				'slide-left': 'translateX',
+				'zoom-in': 'scale',
+				'zoom-out': 'scale'
+			}
+		},
+		// 驼峰转中横线
+		toLine(name) {
+			return name.replace(/([A-Z])/g, '-$1').toLowerCase()
+		}
+	}
+}
+</script>
+
+<style></style>

+ 2 - 1
src/main.js

@@ -5,6 +5,7 @@ Vue.config.productionTip = false
 
 // 打开App
 Vue.prototype.openApp = function (page, channel, audioType, audioId) {
+  // 
   const res = uni.getSystemInfoSync()
   var ua = window.navigator.userAgent.toLowerCase();
   if (ua.match(/MicroMessenger/i) == 'micromessenger' || ua.match(/WeiBo/i) == "weibo" || ua.match(/QQ/i) == "qq") {
@@ -15,7 +16,7 @@ Vue.prototype.openApp = function (page, channel, audioType, audioId) {
     })
   } else {
     if (res.platform === 'ios') {
-      window.location.href = 'airsmart://?page=${page}&channel=${channel}&audioType=${audioType}&audioId=${audioId}'
+      window.location.href = `airsmart://?page=${page}&channel=${channel}&audioType=${audioType}&audioId=${audioId}`
     } else {
       window.location.href = `airsmart://com.muzen.radioplayer:8888/main?page=${page}&channel=${channel}&audioType=${audioType}&audioId=${audioId}`
     }

+ 33 - 25
src/manifest.json

@@ -5,7 +5,8 @@
 	"versionName": "1.0.0",
 	"versionCode": "100",
 	"transformPx": false,
-	"app-plus": { /* 5+App特有相关 */
+	"app-plus": {
+		/* 5+App特有相关 */
 		"usingComponents": true,
 		"splashscreen": {
 			"alwaysShowBeforeRender": true,
@@ -13,11 +14,14 @@
 			"autoclose": true,
 			"delay": 0
 		},
-		"modules": { /* 模块配置 */
+		"modules": {
+			/* 模块配置 */
 
 		},
-		"distribute": { /* 应用发布信息 */
-			"android": { /* android打包配置 */
+		"distribute": {
+			/* 应用发布信息 */
+			"android": {
+				/* android打包配置 */
 				"permissions": ["<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
 					"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
 					"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
@@ -42,39 +46,43 @@
 					"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
 				]
 			},
-			"ios": { /* ios打包配置 */
+			"ios": {
+				/* ios打包配置 */
 
 			},
-			"sdkConfigs": { /* SDK配置 */
+			"sdkConfigs": {
+				/* SDK配置 */
 
 			}
 		}
 	},
-	"quickapp": { /* 快应用特有相关 */
+	"quickapp": {
+		/* 快应用特有相关 */
 
 	},
-	"mp-weixin": { /* 微信小程序特有相关 */
+	"mp-weixin": {
+		/* 微信小程序特有相关 */
 		"appid": "",
 		"setting": {
 			"urlCheck": false
 		},
 		"usingComponents": true
 	},
-	"mp-alipay" : {
-        "usingComponents" : true
-    },
-    "mp-baidu" : {
-        "usingComponents" : true
-    },
-    "mp-toutiao" : {
-        "usingComponents" : true
-    },
-    "mp-qq" : {
-        "usingComponents" : true
-    },
-		"h5": {
-			"router": {
-				"mode": "history"
-			}
+	"mp-alipay": {
+		"usingComponents": true
+	},
+	"mp-baidu": {
+		"usingComponents": true
+	},
+	"mp-toutiao": {
+		"usingComponents": true
+	},
+	"mp-qq": {
+		"usingComponents": true
+	},
+	"h5": {
+		"router": {
+			"mode": "history"
 		}
-}
+	}
+}

+ 19 - 0
src/pages.json

@@ -9,6 +9,13 @@
 			"navigationStyle": "custom"
 		}
 	}, {
+		// 设备文章
+		"path": "pages/article/index",
+		"style": {
+			"navigationStyle": "custom",
+			"onReachBottomDistance": 50
+		}
+	}, {
 		// 耳机操作手势
 		"path": "pages/operation/index",
 		"style": {
@@ -88,5 +95,17 @@
 		"style": {
 			"navigationStyle": "custom"
 		}
+	}, {
+		// 抽奖
+		"path": "pages/lottery/index",
+		"style": {
+			"navigationStyle": "custom"
+		}
+	}, {
+		// 填写收货信息
+		"path": "pages/lottery/detail",
+		"style": {
+			"navigationStyle": "custom"
+		}
 	}]
 }

+ 154 - 0
src/pages/article/index.vue

@@ -0,0 +1,154 @@
+<template>
+  <view class="article">
+    <image class="header-bg" :src="url" />
+    <view class="content">
+      <h3 style="text-align:center; margin-bottom: 20rpx" >{{ title }}</h3>
+      <rich-text :nodes="content" />
+    </view>
+    <view class="list">
+      <view class="title" v-if="tableData.length > 0">全部评论</view>
+      <view v-for="item in tableData" :key="item.commentId" style="margin-bottom: 48rpx">
+        <view flex between>
+          <view flex center>
+            <img class="avatar" :src="item.userAvatar" />
+            <view style="opacity: 0.7; margin-left: 20rpx">
+              <view style="fontSize: 26rpx">{{ item.userName }}</view>
+              <view style="fontSize: 22rpx">{{ item.createTime }}</view>
+            </view>
+          </view>
+          <view flex center style="opacity: 0.7">
+            <uni-icons type="hand-up" size="20" color="#FFF" />
+            <view>{{ item.likeNum }}</view>
+          </view>
+        </view>
+        <view class="item-content">
+          <view style="fontSize:28rpx">
+            {{ item.content }}
+          </view>
+          <view class="sub-content" v-if="item.replyList.length > 0">
+            <view v-for="i in item.replyList.slice(0, 2)" :key="i.replyId" style="margin-bottom: 10rpx">
+              <view v-if="i.replyType === 0">
+                <span style="color:#A4D099">
+                  {{ i.fromUserName }}:
+                </span>{{ i.content }}
+              </view>
+              <view v-else>
+                <span style="color:#A4D099">
+                  {{ i.fromUserName }}</span>回复
+                <span style="color:#A4D099">
+                  {{ i.toUserName }}:</span>
+                {{ i.content }}
+              </view>
+            </view>
+            <view v-if="item.replyList.length > 2" style="color:#A4D099">共{{ item.replyTotal }}条回复 ></view>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { detail, list } from '@/api/article.js'
+export default {
+  data() {
+    return {
+      // 表单
+      form: {
+        pageNum: 1,
+        pageSize: 10
+      },
+      // 背景图
+      url: '',
+      // 标题
+      title: '',
+      // 内容
+      content: '',
+      // 列表
+      tableData: [],
+      // 是否还有下一页
+      hasMore: false,
+      // 只触发一次
+      only: true
+    }
+  },
+  onLoad(e) {
+    if (e) {
+      this.form.articleId = e.articleId
+      detail({
+        articleId: e.articleId
+      }).then(res => {
+        if (res.data.code === 0) {
+          this.url = res.data.data.pic
+          this.title = res.data.data.title
+          this.content = res.data.data.content
+        }
+      })
+      this.getList()
+    }
+  },
+  // 页面触底
+  onReachBottom() {
+    if (this.hasMore) {
+      this.form.pageNum++
+      this.getList()
+    } else {
+      if (this.only) {
+        uni.showToast({
+          icon: 'none',
+          title: '暂无更多评论'
+        })
+        this.only = false
+      }
+    }
+  },
+  methods: {
+    getList() {
+      list(this.form).then(res => {
+        if (res.data.code === 0) {
+          res.data.data.records.map(i => {
+            this.tableData.push(i)
+          })
+          this.hasMore = res.data.data.hasMore
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.header-bg {
+  width: 100%;
+}
+
+.content {
+  padding: 48rpx 32rpx;
+}
+
+.title {
+  margin-bottom: 32rpx;
+}
+
+.list {
+  padding: 32rpx;
+}
+
+.avatar {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 50%;
+}
+
+.item-content {
+  margin-left: 100rpx;
+  margin-top: 22rpx;
+}
+
+.sub-content {
+  background: #232323;
+  padding: 16rpx;
+  font-size: 26rpx;
+  margin-top: 10rpx;
+}
+</style>

+ 190 - 0
src/pages/lottery/detail.vue

@@ -0,0 +1,190 @@
+<template>
+  <view class="app-container">
+    <view class="nav" :style="{'padding-top': statusBarHeight + 'px'}">
+      <uni-icons type="back" style="font-size: 48rpx" @click="getBack" />
+      <text class="title">积分抽奖</text>
+    </view>
+    <uni-forms class="form" ref="form" :modelValue="form" :rules="rules" label-width="100px"
+      label-position="top" validateTrigger="bind">
+      <view class="tips">请检查您的信息填写正确,确认后无法更改哦</view>
+      <uni-forms-item label="收货人:" name="receiveName" required>
+        <input type="text" v-model="form.receiveName" placeholder="请输入收货人姓名" />
+      </uni-forms-item>
+      <uni-forms-item label="手机号:" name="receivePhone" required>
+        <input type="number" v-model="form.receivePhone" placeholder="请输入手机号" />
+      </uni-forms-item>
+      <uni-forms-item label="收货地址:" name="receiveAddress" required>
+        <textarea v-model="form.receiveAddress" placeholder="请输入收货地址" />
+      </uni-forms-item>
+      <button @click="getSubmit" :disabled="disabled">确认</button>
+    </uni-forms>
+  </view>
+</template>
+
+<script>
+import { receive } from '@/api/lottery.js'
+export default {
+  data() {
+    return {
+      // 状态栏
+      statusBarHeight: getApp().globalData.statusBarHeight,
+      // 表单
+      form: {},
+      // 防重复点击
+      disabled: false,
+      // 校验
+      rules: {
+        receiveName: {
+          rules: [{
+            required: true, errorMessage: '请输入收货人姓名'
+          }]
+        },
+        receivePhone: {
+          rules: [{
+            required: true, errorMessage: '请输入手机号'
+          }, {
+            pattern: /^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\d{8}$/, errorMessage: '请输入正确的手机号'
+          }]
+        },
+        receiveAddress: {
+          rules: [{
+            required: true, errorMessage: '请输入收货地址'
+          }]
+        }
+      }
+    }
+  },
+  onLoad(e) {
+    this.form = {
+      prizeId: e.prizeId,
+      lotteryCode: decodeURIComponent(e.lotteryCode)
+    }
+  },
+  methods: {
+    // 返回
+    getBack() {
+      uni.redirectTo({
+        url: `/pages/lottery/index`
+      })
+    },
+
+    // 确定
+    getSubmit() {
+      this.$refs.form.validate((valid) => {
+        if (!valid) {
+          this.disabled = true
+          receive(getApp().globalData.userInfo, this.form).then(res => {
+            if (res.data.code === 0) {
+              uni.showToast({
+                title: '提交成功!',
+                duration: 3000
+              })
+              setTimeout(() => {
+                this.getBack()
+              }, 3000)
+            } else {
+              uni.showToast({
+                icon: 'error',
+                title: res.data.message,
+              })
+              this.disabled = false
+            }
+          })
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.app-container {
+  position: relative;
+  background: url('@/static/lottery/bg-1.png');
+  background-size: cover;
+  background-repeat: no-repeat;
+  width: 100%;
+  height: 100%;
+  color: #000;
+  padding: 0 32rpx 32rpx;
+
+  .nav {
+    height: 88rpx;
+    display: flex;
+    align-items: center;
+    color: #333333;
+    font-weight: 600;
+    font-size: 36rpx;
+    position: relative;
+    box-sizing: content-box;
+
+    .title {
+      position: absolute;
+      left: 50%;
+      transform: translate(-50%);
+    }
+  }
+
+  .tips {
+    color: #999;
+    font-size: 24rpx;
+    margin-bottom: 32rpx;
+  }
+
+  .form {
+    margin-top: 32rpx;
+    height: 1122rpx;
+    background: #fff;
+    border-radius: 32rpx;
+    padding: 32rpx;
+    position: relative;
+
+    .tips {
+      color: #999;
+      font-size: 24rpx;
+      text-align: center;
+    }
+
+    .form-item {
+      margin: 32rpx 0 0 0;
+    }
+
+    ::v-deep .uni-forms-item__label {
+      font-size: 32rpx;
+      font-weight: bold;
+      padding: 0;
+    }
+
+    input {
+      border-radius: 16rpx;
+      height: 88rpx;
+      line-height: 88rpx;
+      color: #333333;
+      margin: 0;
+    }
+
+    uni-textarea {
+      border-radius: 16rpx;
+      margin: 0;
+    }
+
+    ::v-deep .input-placeholder, .textarea-placeholder{
+      opacity: 0.6;
+    }
+
+    button {
+      background: linear-gradient(180deg, #72cdae 0%, #599f82 100%);
+      color: #fff;
+      width: 480rpx;
+      height: 80rpx;
+      line-height: 80rpx;
+      border-radius: 50rpx;
+      font-size: 32rpx;
+      position: absolute;
+      left: 50%;
+      bottom: 60rpx;
+      transform: translate(-50%);
+    }
+  }
+}
+</style>

+ 324 - 0
src/pages/lottery/index.vue

@@ -0,0 +1,324 @@
+<template>
+  <view class="app-container">
+    <view class="nav" :style="{'padding-top': statusBarHeight + 'px'}">
+      <uni-icons type="back" style="font-size: 48rpx" @click="close" />
+      <text class="title">积分抽奖</text>
+    </view>
+
+    <!-- 每次消耗多少积分 -->
+    <view class="every">{{lotteryConsumePoint}}积分/次</view>
+
+    <!-- 转盘 -->
+    <LuckyWheel class="lucky" ref="lucky" width="568rpx" height="568rpx" :prizes="prizes" :buttons="buttons"
+      :defaultStyle="defaultStyle" @start="startCallBack" @end="endCallBack" />
+
+    <!-- 剩余抽奖次数和积分 -->
+    <view class="tips">
+      <view>今日剩余抽奖机会:{{ hasLotteryCount }}</view>
+      <view>可用积分:{{ maySignPoint }}</view>
+    </view>
+
+    <!-- 活动规则 -->
+    <view class="rules">
+      <view class="title">活动规则</view>
+      <view v-for="(item, index) in rule" :key="index">{{ item }}</view>
+    </view>
+
+    <!-- 弹窗 -->
+    <uni-popup ref="popup" type="center" :is-mask-click="false">
+      <view v-if="this.hasLotteryCount <= 0" class="popup" style="justify-content: space-around">
+        <view style="fontSize: 36rpx">
+          <view>很遗憾!</view>
+          <view>今日抽奖次数已用光</view>
+        </view>
+        <img src="../../static/lottery/noChance.png" />
+      </view>
+      <view v-else class="popup">
+        <view style="fontSize: 36rpx">{{ form.resultGoodName }}</view>
+        <img :src="form.resultGoodPic" />
+        <button v-if="form.resultGoodType === 4" @click="getSubmit">领取</button>
+        <button v-if="form.resultGoodType === 5" @click="getAgain">再抽一次</button>
+        <button v-if="form.resultGoodType === 3" @click="getDetail">填写收货信息</button>
+      </view>
+      <uni-icons class="close" type="close" size="45" color="#FFF" @click="closePopup" />
+    </uni-popup>
+  </view>
+</template>
+
+<script>
+import { page, result, receive } from '@/api/lottery.js'
+import LuckyWheel from '@lucky-canvas/uni/lucky-wheel'
+export default {
+  components: {
+    LuckyWheel
+  },
+  data() {
+    return {
+      // 状态栏
+      statusBarHeight: getApp().globalData.statusBarHeight,
+      // 转盘
+      prizes: [],
+      buttons: [{
+        radius: '45%',
+        background: '#ffffff40',
+        imgs: [{
+          src: '../../static/lottery/btn.png',
+          top: '-75px',
+          width: '96px',
+          height: '124px'
+        }]
+      }],
+      defaultStyle: {
+        fontSize: '26rpx'
+      },
+      // 可用积分
+      maySignPoint: 0,
+      // 每次消耗积分
+      lotteryConsumePoint: 0,
+      // 今日剩余抽奖机会
+      hasLotteryCount: 0,
+      // 活动规则
+      rule: '',
+      // 表单
+      form: {},
+      // 防止连点
+      disabled: true
+    }
+  },
+  onLoad() {
+    this.getList()
+    console.log(getApp());
+  },
+  methods: {
+    // 获取页面信息
+    getList() {
+      this.prizes = []
+      page(getApp().globalData.userInfo).then(res => {
+        if (res.data.code === 0) {
+          const j = res.data.data
+          // 可用积分
+          this.maySignPoint = j.maySignPoint
+          // 每次消耗积分
+          this.lotteryConsumePoint = j.lotteryConsumePoint
+          // 今日剩余抽奖机会
+          this.hasLotteryCount = j.hasLotteryCount
+          // 奖品列表
+          j.userLotteryDatas.map(i => {
+            this.prizes.push({
+              background: i.sort % 2 === 0 ? '#fff7a6' : '#9cc6a5',
+              fonts: [{
+                text: i.name,
+                fontColor: i.sort % 2 === 0 ? '#ff8831' : '#fff',
+                top: '10%'
+              }],
+              imgs: [{
+                src: i.pic,
+                top: '30%',
+                width: '108rpx',
+                height: '108rpx'
+              }]
+            })
+          })
+          // 活动规则
+          this.rule = j.rule.split('\n')
+        }
+      })
+    },
+
+    close() {
+      closePage.postMessage('关闭页面')
+    },
+
+    // 点击抽奖按钮触发回调
+    startCallBack() {
+      if (this.hasLotteryCount > 0) {
+        if (this.maySignPoint >= this.lotteryConsumePoint) {
+          if (this.disabled) {
+            this.$refs.lucky.play()
+            this.disabled = false
+            result(getApp().globalData.userInfo).then(res => {
+              if (res.data.code === 0) {
+                setTimeout(() => {
+                  this.$refs.lucky.stop(res.data.data.resultGoodSort - 1)
+                  this.form = res.data.data
+                }, 3000)
+              } else {
+                this.$refs.lucky.stop()
+                uni.showToast({
+                  icon: 'error',
+                  title: res.data.message
+                })
+              }
+            })
+          }
+        } else {
+          uni.showToast({
+            icon: 'error',
+            title: '可用积分不足'
+          })
+        }
+      } else {
+        this.$refs.popup.open()
+      }
+    },
+    // 抽奖结束触发回调
+    endCallBack() {
+      this.$nextTick(() => {
+        this.$refs.popup.open()
+        this.disabled = true
+        this.getList()
+      })
+    },
+
+    // 再抽一次
+    getAgain() {
+      this.$refs.popup.close()
+      this.startCallBack()
+    },
+
+    // 领取
+    getSubmit() {
+      if (this.disabled) {
+        this.disabled = false
+        receive(getApp().globalData.userInfo, {
+          prizeId: this.form.resultGoodId,
+          lotteryCode: this.form.lotteryCode
+        }).then(res => {
+          if (res.data.code === 0) {
+            this.$refs.popup.close()
+            this.getList()
+            setTimeout(() => {
+              this.disabled = true
+              uni.showToast({
+                title: '领取成功,已累积到积分中!'
+              })
+            }, 500)
+          }
+        })
+      }
+    },
+
+    // 填写收货信息
+    getDetail() {
+      uni.redirectTo({
+        url: `/pages/lottery/detail?prizeId=${this.form.resultGoodId}&lotteryCode=` + encodeURIComponent(this.form.lotteryCode)
+      })
+    },
+
+    // 关闭弹窗
+    closePopup() {
+      this.$refs.popup.close()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.app-container {
+  position: relative;
+  background: url('@/static/lottery/bg.png');
+  background-size: cover;
+  background-repeat: no-repeat;
+  width: 100%;
+  height: 1880rpx;
+  padding: 0 32rpx 32rpx;
+
+  .nav {
+    height: 88rpx;
+    display: flex;
+    align-items: center;
+    color: #333333;
+    font-weight: bold;
+    font-size: 36rpx;
+    position: relative;
+    box-sizing: content-box;
+
+    .title {
+      position: absolute;
+      left: 50%;
+      transform: translate(-50%);
+    }
+  }
+
+  .every {
+    position: absolute;
+    left: 50%;
+    top: 380rpx;
+    transform: translate(-50%);
+    color: #1a5509;
+    font-weight: bold;
+  }
+
+  .lucky {
+    position: absolute;
+    top: 496rpx;
+    left: 50%;
+    transform: translate(-50%);
+  }
+
+  .tips {
+    position: absolute;
+    top: 62%;
+    left: 50%;
+    transform: translate(-50%);
+    text-align: center;
+    line-height: 50rpx;
+  }
+
+  .rules {
+    width: 100%;
+    position: absolute;
+    left: 0;
+    bottom: 40rpx;
+    color: #192b24;
+    padding: 0 64rpx;
+    font-size: 28rpx;
+    font-weight: bold;
+
+    .title {
+      font-weight: bold;
+      font-size: 38rpx;
+      text-align: center;
+      margin-bottom: 32rpx;
+    }
+    view {
+      margin-bottom: 20rpx;
+    }
+  }
+
+  .popup {
+    width: 576rpx;
+    height: 600rpx;
+    background: #fff;
+    border-radius: 32rpx;
+    color: #000;
+    font-weight: bold;
+    text-align: center;
+    padding: 48rpx 0;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    align-items: center;
+
+    img {
+      width: 328rpx;
+    }
+
+    button {
+      background: linear-gradient(180deg, #72cdae 0%, #599f82 100%);
+      color: #fff;
+      width: 320rpx;
+      height: 80rpx;
+      line-height: 80rpx;
+      border-radius: 50rpx;
+      font-size: 32rpx;
+    }
+  }
+  .close {
+    position: absolute;
+    left: 50%;
+    transform: translate(-50%);
+    margin-top: 20rpx;
+  }
+}
+</style>

+ 5 - 3
src/pages/museum/index.vue

@@ -12,10 +12,12 @@
           <text>{{ item.title }}</text>
         </view>
         <view flex wrap :center="index === 1 || index === 2 ? true : false">
-          <view :style="{width: index === 1 || index === 2 ? '50%' : '33.33333%'}" v-for="(i, ind) in item.img"
-            :key="i.name" @click="getDetail(i)">
+          <view :style="{width: index === 1 || index === 2 ? '50%' : '33.33333%'}"
+            v-for="(i, ind) in item.img" :key="i.name" @click="getDetail(i)">
             <img :src="i.url" style="width: 100%" />
-            <view :class="['name', item.img.length === 2 ? ind === 0 ? 'padding-left' : 'padding-right' : '']">{{ i.name }}</view>
+            <view
+              :class="['name', item.img.length === 2 ? ind === 0 ? 'padding-left' : 'padding-right' : '']">
+              {{ i.name }}</view>
           </view>
         </view>
       </view>

文件差异内容过多而无法显示
+ 143 - 12
src/pages/user/agreement.vue


+ 1 - 11
src/pages/vip/index.vue

@@ -320,23 +320,13 @@
         如果您对本条款内容有任何疑问,可通过如下方式与我们联系:
       </p>
       <p>客户服务中心电话:400-850-8199</p>
-      <p>客户服务邮箱:cs@radio1964.com</p>
+      <p>客户服务邮箱:xz@radio1964.com</p>
     </div>
 
     <h4 class="float-right">猫王公司</h4>
   </view>
 </template>
 
-<script>
-export default {
-  data() {
-    return {
-
-    }
-  }
-}
-</script>
-
 <style lang="scss" scoped>
 .text-indent {
   text-indent: 2em;

二进制
src/static/lottery/bg-1.png


二进制
src/static/lottery/bg.png


二进制
src/static/lottery/btn.png


二进制
src/static/lottery/noChance.png


+ 5 - 4
src/utils/request.js

@@ -1,9 +1,9 @@
-const baseUrl = 'https://o3tapi.radio1964.com/web' // 测试
-// const baseUrl = 'https://client.ohplay.radio1964.net/web' //生产
-// const baseUrl = 'http://192.168.0.217/web' // 付新保本地'
+// const baseUrl = 'https://o3tapi.radio1964.com/web' // 测试
+const baseUrl = 'https://client.ohplay.radio1964.net/web' //生产
+// const baseUrl = 'http://192.168.0.217:8011/web' // 付新保本地'
 
 function request(e) {
-  if(!e.data){
+  if (!e.data) {
     e.data = {}
   }
 
@@ -14,6 +14,7 @@ function request(e) {
   const promise = new Promise((resolve, reject) => {
     uni.request({
       url: baseUrl + e.url,
+      header: e.header,
       method: e.method,
       data: e.data,
       success: (res) => {