Browse Source

首次更新

王大坤 1 year ago
commit
e3db69ca94
100 changed files with 10371 additions and 0 deletions
  1. BIN
      .DS_Store
  2. 14 0
      mini/.editorconfig
  3. 201 0
      mini/.eslintrc.js
  4. 5 0
      mini/.gitignore
  5. 45 0
      mini/App.vue
  6. 84 0
      mini/api/episode.js
  7. 17 0
      mini/api/index.js
  8. 11 0
      mini/api/pay.js
  9. 46 0
      mini/api/setting.js
  10. 42 0
      mini/api/share/index.js
  11. 16 0
      mini/api/share/order.js
  12. 16 0
      mini/api/share/team.js
  13. 24 0
      mini/api/share/withdraw.js
  14. 18 0
      mini/api/sign.js
  15. 37 0
      mini/api/user/collect.js
  16. 16 0
      mini/api/user/consume.js
  17. 50 0
      mini/api/user/episode.js
  18. 29 0
      mini/api/user/favorite.js
  19. 101 0
      mini/api/user/index.js
  20. 24 0
      mini/api/user/recharge.js
  21. 30 0
      mini/api/user/vip.js
  22. 30 0
      mini/components/CheckLogin/index.vue
  23. 147 0
      mini/components/Episode/index.vue
  24. 104 0
      mini/components/NavBar/index.vue
  25. 437 0
      mini/components/Recharge/index.vue
  26. 124 0
      mini/components/SwiperBox/index.vue
  27. 151 0
      mini/components/TabBar/index.vue
  28. 20 0
      mini/index.html
  29. 32 0
      mini/main.js
  30. 90 0
      mini/manifest.json
  31. 2602 0
      mini/package-lock.json
  32. 7 0
      mini/package.json
  33. 240 0
      mini/pages.json
  34. 242 0
      mini/pages/episode/components/EpisodeButtons.vue
  35. 260 0
      mini/pages/episode/components/EpisodePart.vue
  36. 517 0
      mini/pages/episode/play.vue
  37. 131 0
      mini/pages/index/components/EpisodeBox.vue
  38. 123 0
      mini/pages/index/components/Recent.vue
  39. 154 0
      mini/pages/index/index.vue
  40. 56 0
      mini/pages/index/news.vue
  41. 57 0
      mini/pages/index/rank.vue
  42. 58 0
      mini/pages/index/search.vue
  43. 99 0
      mini/pages/login.vue
  44. 56 0
      mini/pages/member/free.vue
  45. 310 0
      mini/pages/member/index.vue
  46. 168 0
      mini/pages/my/consume.vue
  47. 168 0
      mini/pages/my/history.vue
  48. 497 0
      mini/pages/my/index.vue
  49. 34 0
      mini/pages/my/protocol.vue
  50. 155 0
      mini/pages/my/recharge.vue
  51. 130 0
      mini/pages/share/components/Qrcode.vue
  52. 145 0
      mini/pages/share/income.vue
  53. 243 0
      mini/pages/share/index.vue
  54. 166 0
      mini/pages/share/order.vue
  55. 134 0
      mini/pages/share/team.vue
  56. 34 0
      mini/pages/share/tips.vue
  57. 294 0
      mini/pages/share/withdraw.vue
  58. 151 0
      mini/pages/share/withdrawDetail.vue
  59. 205 0
      mini/pages/sign/index.vue
  60. 160 0
      mini/pages/trace/collect.vue
  61. 161 0
      mini/pages/trace/index.vue
  62. 59 0
      mini/pages/trace/list.vue
  63. 44 0
      mini/project.config.json
  64. 7 0
      mini/project.private.config.json
  65. 15 0
      mini/setting.js
  66. 17 0
      mini/static/css/common.scss
  67. 266 0
      mini/static/css/flex.scss
  68. 3 0
      mini/static/css/iconfont.css
  69. 12 0
      mini/static/css/mixin.scss
  70. 172 0
      mini/static/css/store-buy.scss
  71. 12 0
      mini/static/css/variable.scss
  72. BIN
      mini/static/font/iconfont.eot
  73. 46 0
      mini/static/font/iconfont.svg
  74. BIN
      mini/static/font/iconfont.ttf
  75. BIN
      mini/static/font/iconfont.woff
  76. BIN
      mini/static/font/iconfont.woff2
  77. BIN
      mini/static/image/default-head-img.png
  78. BIN
      mini/static/image/default-movie.png
  79. BIN
      mini/static/image/gold-bag.png
  80. BIN
      mini/static/image/gold.png
  81. BIN
      mini/static/image/member-line-bg.png
  82. BIN
      mini/static/image/member-selected-bg.png
  83. BIN
      mini/static/image/my-page/contact.png
  84. BIN
      mini/static/image/my-page/order.png
  85. BIN
      mini/static/image/my-page/protocol.png
  86. BIN
      mini/static/image/my-page/recharge.png
  87. BIN
      mini/static/image/my-page/share.png
  88. BIN
      mini/static/image/my-recharge-bg.png
  89. BIN
      mini/static/image/playing.png
  90. BIN
      mini/static/image/share-qrcode-bg.png
  91. BIN
      mini/static/image/share/alipay.png
  92. BIN
      mini/static/image/share/bank.png
  93. BIN
      mini/static/image/share/detail.png
  94. BIN
      mini/static/image/share/income.png
  95. BIN
      mini/static/image/share/order.png
  96. BIN
      mini/static/image/share/qrcode.png
  97. BIN
      mini/static/image/share/team.png
  98. BIN
      mini/static/image/share/wechat.png
  99. BIN
      mini/static/image/tab/home-HL.png
  100. BIN
      mini/static/image/tab/home.png

BIN
.DS_Store


+ 14 - 0
mini/.editorconfig

xqd
@@ -0,0 +1,14 @@
+# https://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+insert_final_newline = false
+trim_trailing_whitespace = false

+ 201 - 0
mini/.eslintrc.js

xqd
@@ -0,0 +1,201 @@
+module.exports = {
+  root: true,
+  "env": {
+    browser: true,
+    node: true,
+    es6: true,
+  },
+  extends: ['plugin:vue/recommended', 'eslint:recommended'],
+  parserOptions: {
+    parser: 'babel-eslint',
+    sourceType: 'module'
+  },
+  globals: {
+    uni: true,
+    wx: true,
+    tt: true,
+    ks: true
+  },
+  rules: {
+    "vue/max-attributes-per-line": [2, {
+      "singleline": 10,
+      "multiline": {
+        "max": 1,
+        "allowFirstLine": false
+      }
+    }],
+    "vue/singleline-html-element-content-newline": "off",
+    "vue/multiline-html-element-content-newline":"off",
+    "vue/name-property-casing": ["error", "PascalCase"],
+    "vue/no-v-html": "off",
+    'accessor-pairs': 2,
+    'arrow-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'block-spacing': [2, 'always'],
+    'brace-style': [2, '1tbs', {
+      'allowSingleLine': true
+    }],
+    'camelcase': [0, {
+      'properties': 'always'
+    }],
+    'comma-dangle': [2, 'never'],
+    'comma-spacing': [2, {
+      'before': false,
+      'after': true
+    }],
+    'comma-style': [2, 'last'],
+    'constructor-super': 2,
+    'curly': [2, 'multi-line'],
+    'dot-location': [2, 'property'],
+    'eol-last': 2,
+    'eqeqeq': ["error", "always", {"null": "ignore"}],
+    'generator-star-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'handle-callback-err': [2, '^(err|error)$'],
+    'indent': [2, 2, {
+      'SwitchCase': 1
+    }],
+    'jsx-quotes': [2, 'prefer-single'],
+    'key-spacing': [2, {
+      'beforeColon': false,
+      'afterColon': true
+    }],
+    'keyword-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'new-cap': [2, {
+      'newIsCap': true,
+      'capIsNew': false
+    }],
+    'new-parens': 2,
+    'no-array-constructor': 2,
+    'no-caller': 2,
+    'no-console': 'off',
+    'no-class-assign': 2,
+    'no-cond-assign': 2,
+    'no-const-assign': 2,
+    'no-control-regex': 0,
+    'no-delete-var': 2,
+    'no-dupe-args': 2,
+    'no-dupe-class-members': 2,
+    'no-dupe-keys': 2,
+    'no-duplicate-case': 2,
+    'no-empty-character-class': 2,
+    'no-empty-pattern': 2,
+    'no-eval': 2,
+    'no-ex-assign': 2,
+    'no-extend-native': 2,
+    'no-extra-bind': 2,
+    'no-extra-boolean-cast': 2,
+    'no-extra-parens': [2, 'functions'],
+    'no-fallthrough': 2,
+    'no-floating-decimal': 2,
+    'no-func-assign': 2,
+    'no-implied-eval': 2,
+    'no-inner-declarations': [2, 'functions'],
+    'no-invalid-regexp': 2,
+    'no-irregular-whitespace': 2,
+    'no-iterator': 2,
+    'no-label-var': 2,
+    'no-labels': [2, {
+      'allowLoop': false,
+      'allowSwitch': false
+    }],
+    'no-lone-blocks': 2,
+    'no-mixed-spaces-and-tabs': 2,
+    'no-multi-spaces': 2,
+    'no-multi-str': 2,
+    'no-multiple-empty-lines': [2, {
+      'max': 1
+    }],
+    'no-native-reassign': 2,
+    'no-negated-in-lhs': 2,
+    'no-new-object': 2,
+    'no-new-require': 2,
+    'no-new-symbol': 2,
+    'no-new-wrappers': 2,
+    'no-obj-calls': 2,
+    'no-octal': 2,
+    'no-octal-escape': 2,
+    'no-path-concat': 2,
+    'no-proto': 2,
+    'no-redeclare': 2,
+    'no-regex-spaces': 2,
+    'no-return-assign': [2, 'except-parens'],
+    'no-self-assign': 2,
+    'no-self-compare': 2,
+    'no-sequences': 2,
+    'no-shadow-restricted-names': 2,
+    'no-spaced-func': 2,
+    'no-sparse-arrays': 2,
+    'no-this-before-super': 2,
+    'no-throw-literal': 2,
+    'no-trailing-spaces': 2,
+    'no-undef': 2,
+    'no-undef-init': 2,
+    'no-unexpected-multiline': 2,
+    'no-unmodified-loop-condition': 2,
+    'no-unneeded-ternary': [2, {
+      'defaultAssignment': false
+    }],
+    'no-unreachable': 2,
+    'no-unsafe-finally': 2,
+    'no-unused-vars': [2, {
+      'vars': 'all',
+      'args': 'none'
+    }],
+    'no-useless-call': 2,
+    'no-useless-computed-key': 2,
+    'no-useless-constructor': 2,
+    'no-useless-escape': 0,
+    'no-whitespace-before-property': 2,
+    'no-with': 2,
+    'one-var': [2, {
+      'initialized': 'never'
+    }],
+    'operator-linebreak': [2, 'after', {
+      'overrides': {
+        '?': 'before',
+        ':': 'before'
+      }
+    }],
+    'padded-blocks': [2, 'never'],
+    'quotes': [2, 'single', {
+      'avoidEscape': true,
+      'allowTemplateLiterals': true
+    }],
+    'semi': [2, 'never'],
+    'semi-spacing': [2, {
+      'before': false,
+      'after': true
+    }],
+    'space-before-blocks': [2, 'always'],
+    'space-before-function-paren': [2, 'never'],
+    'space-in-parens': [2, 'never'],
+    'space-infix-ops': 2,
+    'space-unary-ops': [2, {
+      'words': true,
+      'nonwords': false
+    }],
+    'spaced-comment': [2, 'always', {
+      'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
+    }],
+    'template-curly-spacing': [2, 'never'],
+    'use-isnan': 2,
+    'valid-typeof': 2,
+    'wrap-iife': [2, 'any'],
+    'yield-star-spacing': [2, 'both'],
+    'yoda': [2, 'never'],
+    'prefer-const': 2,
+    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
+    'object-curly-spacing': [2, 'always', {
+      objectsInObjects: false
+    }],
+    'array-bracket-spacing': [2, 'never']
+  }
+}

+ 5 - 0
mini/.gitignore

xqd
@@ -0,0 +1,5 @@
+/node_modules
+/unpackage
+/.hbuilderx
+/.idea
+

+ 45 - 0
mini/App.vue

xqd
@@ -0,0 +1,45 @@
+<script>
+import Cache from './utils/cache'
+export default {
+  globalData: {
+    isLogin: false
+  },
+  async onLaunch(options) {
+    console.log('-->data', options)
+    console.log('App Launch')
+    const path = options.path ? '/' + options.path : '/pages/index/index'
+    const query = options.query
+    // 直接传递user_id
+    if (typeof query.user_id !== 'undefined' && query.user_id) {
+      Cache.set('parent_id', query.user_id)
+    }
+    // 微信小程序 对应的二维码是 scene_code
+    if (typeof query.scene !== 'undefined' && query.scene) {
+      Cache.set('parent_id', query.scene)
+    }
+    if (this.$api.user.isLogin()) {
+      await this.$api.user.info().then(res => {
+        this.$store.dispatch('user/info', res.data)
+      })
+    } else {
+      uni.reLaunch({
+        url: '/pages/login?path=' + path + '&query=' + encodeURI(JSON.stringify(query))
+      })
+    }
+  },
+  onShow() {
+    console.log('App Show')
+  },
+  onHide() {
+    console.log('App Hide')
+  }
+}
+</script>
+
+<style lang="scss">
+	/*每个页面公共css */
+  @import "@/uni_modules/uview-ui/index.scss";
+  @import '@/static/css/flex.scss';
+  @import '@/static/css/common.scss';
+  @import '@/static/css/iconfont.css';
+</style>

+ 84 - 0
mini/api/episode.js

xqd
@@ -0,0 +1,84 @@
+const request = uni.$u.http
+
+export function recommend() {
+  return request.get(
+    'episode/recommend'
+  )
+}
+
+export function news() {
+  return request.get(
+    'episode/news'
+  )
+}
+
+export function rank() {
+  return request.get(
+    'episode/rank'
+  )
+}
+
+export function todayRecommend() {
+  return request.get(
+    'episode/today/recommend'
+  )
+}
+
+export function detail(id) {
+  return request.get(
+    `episode/${id}/detail`
+  )
+}
+
+export function trace() {
+  return request.get(
+    'episode/trace'
+  )
+}
+
+export function list(params) {
+  return request.get(
+    'episode/list',
+    { params }
+  )
+}
+
+export function shared(id) {
+  return request.post(
+    `episode/${id}/shared`
+  )
+}
+
+export function search(params) {
+  return request.get(
+    `episode/search`,
+    { params }
+  )
+}
+
+export function vipFree(params) {
+  return request.get(
+    `episode/vip/free`,
+    { params }
+  )
+}
+
+export function listBuyNum(list_id) {
+  return request.get(
+    `episode/list/${list_id}/buyNum`
+  )
+}
+
+export default {
+  recommend,
+  news,
+  rank,
+  todayRecommend,
+  detail,
+  trace,
+  list,
+  shared,
+  search,
+  vipFree,
+  listBuyNum
+}

+ 17 - 0
mini/api/index.js

xqd
@@ -0,0 +1,17 @@
+import user from './user/index'
+import setting from './setting'
+import episode from './episode'
+import sign from './sign'
+import pay from './pay'
+import share from './share'
+
+const api = {
+  user,
+  setting,
+  episode,
+  sign,
+  pay,
+  share
+}
+
+export default api

+ 11 - 0
mini/api/pay.js

xqd
@@ -0,0 +1,11 @@
+const request = uni.$u.http
+
+export function query(pay_id) {
+  return request.get(
+    `pay/${pay_id}/query`
+  )
+}
+
+export default {
+  query
+}

+ 46 - 0
mini/api/setting.js

xqd
@@ -0,0 +1,46 @@
+const request = uni.$u.http
+
+export function tabBar() {
+  return request.get(
+    'setting/tabBar'
+  )
+}
+
+export function navBar() {
+  return request.get(
+    'setting/navBar'
+  )
+}
+
+export function banner() {
+  return request.get(
+    'setting/banner'
+  )
+}
+
+export function rechargeCombo() {
+  return request.get(
+    'setting/rechargeCombo'
+  )
+}
+
+export function homeColumn() {
+  return request.get(
+    'setting/homeColumn'
+  )
+}
+
+export function config() {
+  return request.get(
+    'setting/config'
+  )
+}
+
+export default {
+  tabBar,
+  navBar,
+  banner,
+  rechargeCombo,
+  homeColumn,
+  config
+}

+ 42 - 0
mini/api/share/index.js

xqd
@@ -0,0 +1,42 @@
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/8/14.
+ */
+const request = uni.$u.http
+
+import order from './order'
+import team from './team'
+import withdraw from './withdraw'
+
+export function income() {
+  return request.get(
+    'share/income'
+  )
+}
+
+export function tips() {
+  return request.get(
+    'share/tips'
+  )
+}
+
+export function setting() {
+  return request.get(
+    'share/setting'
+  )
+}
+
+export function generateQrcode() {
+  return request.post(
+    'share/generate/qrcode'
+  )
+}
+
+export default {
+  order,
+  withdraw,
+  team,
+  income,
+  tips,
+  setting,
+  generateQrcode
+}

+ 16 - 0
mini/api/share/order.js

xqd
@@ -0,0 +1,16 @@
+
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/11/5.
+ */
+const request = uni.$u.http
+
+export function lists(params) {
+  return request.get(
+    'share/order/lists',
+    { params }
+  )
+}
+
+export default {
+  lists
+}

+ 16 - 0
mini/api/share/team.js

xqd
@@ -0,0 +1,16 @@
+
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/11/5.
+ */
+const request = uni.$u.http
+
+export function lists(params) {
+  return request.get(
+    'share/team/lists',
+    { params }
+  )
+}
+
+export default {
+  lists
+}

+ 24 - 0
mini/api/share/withdraw.js

xqd
@@ -0,0 +1,24 @@
+
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/8/22.
+ */
+const request = uni.$u.http
+
+export function lists(params) {
+  return request.get(
+    `share/withdraw/lists`,
+    { params }
+  )
+}
+
+export function create(data) {
+  return request.post(
+    `share/withdraw/create`,
+    data
+  )
+}
+
+export default {
+  lists,
+  create
+}

+ 18 - 0
mini/api/sign.js

xqd
@@ -0,0 +1,18 @@
+const request = uni.$u.http
+
+export function setting() {
+  return request.get(
+    'user/sign/setting'
+  )
+}
+
+export function handle() {
+  return request.post(
+    `user/sign/handle`
+  )
+}
+
+export default {
+  setting,
+  handle
+}

+ 37 - 0
mini/api/user/collect.js

xqd
@@ -0,0 +1,37 @@
+
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/8/22.
+ */
+const request = uni.$u.http
+
+export function record(params) {
+  return request.get(
+    `user/collect/record`,
+    { params }
+  )
+}
+
+export function check(id) {
+  return request.post(
+    `user/collect/${id}/check`
+  )
+}
+
+export function add(id) {
+  return request.post(
+    `user/collect/${id}/add`
+  )
+}
+
+export function destroy(id) {
+  return request.post(
+    `user/collect/${id}/destroy`
+  )
+}
+
+export default {
+  record,
+  check,
+  add,
+  destroy
+}

+ 16 - 0
mini/api/user/consume.js

xqd
@@ -0,0 +1,16 @@
+
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/8/22.
+ */
+const request = uni.$u.http
+
+export function record(params) {
+  return request.get(
+    'user/consume/record',
+    { params }
+  )
+}
+
+export default {
+  record
+}

+ 50 - 0
mini/api/user/episode.js

xqd
@@ -0,0 +1,50 @@
+
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/8/22.
+ */
+const request = uni.$u.http
+
+export function record(params) {
+  return request.get(
+    'user/watch/record',
+    { params }
+  )
+}
+export function deleteRecord(id) {
+  return request.post(
+    `user/watch/${id}/destroy`
+  )
+}
+
+export function recent() {
+  return request.get(
+    'user/watch/recent'
+  )
+}
+
+export function watched(id, list_id) {
+  return request.post(
+    'user/watch/episode',
+    { id, list_id }
+  )
+}
+
+export async function buyRecord(episode_id) {
+  return request.get(
+    `user/episode/buy/${episode_id}/record`
+  )
+}
+
+export async function buyHandle(episode_id, list_id) {
+  return request.post(
+    `user/episode/${episode_id}/${list_id}/buy`
+  )
+}
+export default {
+  recent,
+  record,
+  deleteRecord,
+  watched,
+  buyRecord,
+  buyHandle
+}

+ 29 - 0
mini/api/user/favorite.js

xqd
@@ -0,0 +1,29 @@
+
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/8/22.
+ */
+const request = uni.$u.http
+
+export function check(id) {
+  return request.post(
+    `user/favorite/${id}/check`
+  )
+}
+
+export function add(id) {
+  return request.post(
+    `user/favorite/${id}/add`
+  )
+}
+
+export function destroy(id) {
+  return request.post(
+    `user/favorite/${id}/destroy`
+  )
+}
+
+export default {
+  check,
+  add,
+  destroy
+}

+ 101 - 0
mini/api/user/index.js

xqd
@@ -0,0 +1,101 @@
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/8/14.
+ */
+import { getToken } from '../../utils/auth'
+
+const request = uni.$u.http
+
+import episode from './episode'
+import consume from './consume'
+import recharge from './recharge'
+import collect from './collect'
+import favorite from './favorite'
+import vip from './vip'
+import Cache from "../../utils/cache";
+
+export async function login() {
+  if(Cache.get('isLogin')) return ;
+  Cache.set('isLogin', 1)
+  return new Promise((resolve, reject) => {
+    uni.showLoading({
+      title: '数据加载中...',
+      mask: true
+    })
+    uni.login({
+      provider: uni.$u.platform,
+      success: loginRes => {
+        console.log('-->login auth', loginRes)
+        uni.hideLoading()
+        // #ifdef  MP-KUAISHOU
+        const url = '/auth/kuaishou'
+        // #endif
+        // #ifdef  MP-TOUTIAO
+        const url = '/auth/bytedance'
+        // #endif
+        // #ifdef  MP-WEIXIN
+        const url = '/auth/wechat'
+        // #endif
+
+        return request.post(
+          url,
+          { code: loginRes.code, anonymousCode: loginRes.anonymousCode }
+        ).then(res => {
+          Cache.set('isLogin', 0)
+          const parentId = Cache.get('parent_id')
+          resolve(res)
+        }).catch(err => {
+          Cache.set('isLogin', 0)
+          reject(err)
+        })
+      },
+      fail: err => {
+        console.error('-->微信授权登陆错误', err)
+        reject(err)
+      }
+    })
+  })
+}
+
+export function update(data) {
+  return request.post(
+    'user/update',
+    data
+  )
+}
+
+export async function info() {
+  return request.get(
+    'user/info'
+  )
+}
+
+export async function bind(id) {
+  return request.post(
+    `user/${id}/bind`
+  )
+}
+
+export async function parent() {
+  return request.get(
+    `user/parent`
+  )
+}
+
+export function isLogin() {
+  return !!getToken()
+}
+
+export default {
+  login,
+  update,
+  bind,
+  info,
+  isLogin,
+  episode,
+  consume,
+  recharge,
+  collect,
+  favorite,
+  vip,
+  parent
+}

+ 24 - 0
mini/api/user/recharge.js

xqd
@@ -0,0 +1,24 @@
+
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/8/22.
+ */
+const request = uni.$u.http
+
+export function record(params) {
+  return request.get(
+    'user/recharge/record',
+    { params }
+  )
+}
+
+export function create(data) {
+  return request.post(
+    'user/recharge/create/order',
+    data
+  )
+}
+
+export default {
+  record,
+  create
+}

+ 30 - 0
mini/api/user/vip.js

xqd
@@ -0,0 +1,30 @@
+
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/8/22.
+ */
+const request = uni.$u.http
+
+export function setting() {
+  return request.get(
+    'user/vip/setting'
+  )
+}
+
+export function last() {
+  return request.get(
+    'user/vip/last'
+  )
+}
+
+export function create(data) {
+  return request.post(
+    'user/vip/create/order',
+    data
+  )
+}
+
+export default {
+  setting,
+  last,
+  create
+}

+ 30 - 0
mini/components/CheckLogin/index.vue

xqd
@@ -0,0 +1,30 @@
+<template>
+  <view class="check-login">
+    <u-loading-page
+      :loading="!isLogin"
+      :bg-color="$colors.bgColor"
+      :color="$colors.primaryColor"
+      :loading-color="$colors.primaryColor"
+    />
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'CheckLogin',
+  data() {
+    return {}
+  },
+  computed: {
+    isLogin() {
+      return this.$api.user.isLogin()
+    }
+  },
+  methods: {}
+}
+</script>
+
+<style lang="scss" scoped>
+    .check-login {
+    }
+</style>

+ 147 - 0
mini/components/Episode/index.vue

xqd
@@ -0,0 +1,147 @@
+<template>
+  <view class="episode" :style="[customStyle]" @click="handlePlay">
+    <view class="cover-image" :style="[imageStyle]">
+      <view v-if="rank" class="rank" :class="{first: rank === 1}">
+        <text>{{ rank }}</text>
+      </view>
+      <image :src="episode.cover_img" :style="[imageStyle]" />
+      <view v-if="recent" class="special">最近播放 </view>
+      <view v-if="guess" class="special guess">猜你喜欢 </view>
+    </view>
+    <u-text
+      :text="episode.name"
+      :lines="1"
+      size="32rpx"
+      margin="20rpx 0 10rpx"
+      :color="$colors.defaultColor"
+    />
+    <view class="status-box dir-left-nowrap">
+      <u-text
+        :text="episode.status_text"
+        :lines="1"
+        size="24rpx"
+        :custom-style="{flex: 'unset'}"
+        :color="$colors.primaryColor"
+      />
+      <u-text
+        :text="`共${episode.total}集`"
+        :lines="1"
+        size="24rpx"
+        margin="0 0 0 10rpx"
+        :color="$colors.infoColor"
+      />
+    </view>
+  </view></template>
+
+<script>
+import UText from '../../uni_modules/uview-ui/components/u--text/u--text'
+export default {
+  name: 'Episode',
+  components: { UText },
+  props: {
+    episode: {
+      type: Object,
+      required: true
+    },
+    customStyle: {
+      type: Object,
+      default() {
+        return {}
+      }
+    },
+    imageStyle: {
+      type: Object,
+      default() {
+        return {}
+      }
+    },
+    recent: {
+      type: Boolean,
+      default: false
+    },
+    guess: {
+      type: Boolean,
+      default: false
+    },
+    rank: {
+      type: Number,
+      default: 0
+    },
+    redirect: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data() {
+    return {}
+  },
+  computed: {},
+  methods: {
+    handlePlay() {
+      if (!this.redirect) return
+      this.$u.route({
+        url: '/pages/episode/play',
+        params: {
+          id: this.episode.id
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+    .episode {
+      width: calc((#{750rpx} - #{80rpx}) / 3);
+      margin-bottom: 30rpx;
+      overflow: hidden;
+      .cover-image{
+        position: relative;
+        height: 330rpx;
+        image{
+          width: 100%;
+          height: inherit;
+          border-radius: 20rpx;
+        }
+        .special{
+          background: rgba(48, 98, 97, 0.65);
+          text-align: center;
+          color: #fff;
+          padding: 10rpx 0;
+          position: absolute;
+          bottom: 0;
+          width: 100%;
+          font-size: 26rpx;
+          border-bottom-left-radius: 20rpx;
+          border-bottom-right-radius: 20rpx;
+          &.guess{
+            background: rgba(124, 78, 112, 0.65);
+          }
+        }
+        .rank{
+          position: absolute;
+          top: -35rpx;
+          left: -35rpx;
+          width: 70rpx;
+          height: 70rpx;
+          transform: rotate(45deg);
+          background: rgba(251, 54, 81, 0.6);
+          color: #fff;
+          font-size: 26rpx;
+          z-index: 0;
+          &.first{
+            background: #FE3552;
+          }
+          text{
+            position: absolute;
+            right: 5px;
+            top: 10px;
+            transform: rotate(-45deg);
+          }
+        }
+      }
+      .status-box{
+
+      }
+    }
+</style>

+ 104 - 0
mini/components/NavBar/index.vue

xqd
@@ -0,0 +1,104 @@
+<template>
+  <view class="nav-bar main-center cross-center">
+    <view
+      v-for="(item,index) in navItem"
+      :key="index"
+      class="item dir-top-wrap cross-center"
+      @click="handleRedirect(item)"
+    >
+      <view
+        class="icon"
+        :class="item.class"
+        :style="{
+          backgroundImage: `url(${item.icon})`
+        }"
+      />
+      <text>{{ item.name }}</text>
+    </view>
+    <!--充值-->
+    <recharge :show.sync="recharge.show" />
+  </view>
+</template>
+
+<script>
+
+import Recharge from '../Recharge/index'
+export default {
+  name: 'NavBar',
+  components: { Recharge },
+  data() {
+    return {
+      types: {
+        1: { href: '/pages/index/rank', class: 'rank' },
+        2: { href: '/pages/index/news', class: 'news' },
+        3: { href: '/pages/member/index', class: 'member' },
+        4: { href: '/pages/sign/index', class: 'sign' },
+        5: { type: 'recharge', class: 'recharge' }
+      },
+      navItem: [
+        { icon: '', name: '排行榜', href: '/pages/index/rank', class: 'rank' },
+        { icon: '', name: '最新', href: '/pages/index/news', class: 'news' },
+        { icon: '', name: '会员', href: '/pages/member/index', class: 'member' },
+        { icon: '', name: '签到', href: '/pages/sign/index', class: 'sign' },
+        { icon: '', name: '充值', type: 'recharge', class: 'recharge' }
+      ],
+      recharge: { show: false }
+    }
+  },
+  computed: {},
+  async created() {
+    await this.getData()
+  },
+  methods: {
+    handleRedirect(item) {
+      if (item.type === 'recharge') {
+        this.recharge.show = true
+      } else if (item.href) {
+        this.$u.route(item.href)
+      }
+    },
+    async getData() {
+      /**
+       * type 1 -排行榜 2 -最新  3 -会员 4 -签到 5 -充值
+       */
+      await this.$api.setting.navBar().then(res => {
+        const data = res.data
+        data.forEach(obj => {
+          Object.assign(obj, this.types[obj.type])
+        })
+        this.navItem = data
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .nav-bar {
+    .item{
+      flex: 1;
+      font-size: 32rpx;
+      .icon{
+        width: 90rpx;
+        height: 90rpx;
+        background-repeat: no-repeat;
+        background-size: 100%;
+        background-position: center;
+        &.rank{}
+        &.news{
+          width: 80rpx;
+        }
+        &.member{
+          width: 82rpx;
+        }
+        &.sign{}
+        &.recharge{
+          width: 70rpx;
+        }
+      }
+      text{
+        color: #fff;
+      }
+    }
+  }
+</style>

+ 437 - 0
mini/components/Recharge/index.vue

xqd
@@ -0,0 +1,437 @@
+<template>
+  <view class="recharge">
+    <u-popup
+      :show="show"
+      :mode="mode"
+      round="20rpx"
+      :close-on-click-overlay="false"
+      @close="close"
+    >
+      <view class="container" :class="{bottom: mode === 'bottom'}">
+        <template v-if="type === 'play'">
+          <view class="play-container">
+            <view class="header main-between cross-center">
+              <text>感谢您的支持,本集解锁后可继续观看</text>
+              <u-icon name="arrow-down" size="28rpx" @click="close" />
+            </view>
+            <view class="episode main-between cross-center">
+              <view class="detail">
+                <view class="name">{{ episode.name }} 第{{ list.sort }}集</view>
+                <view class="sale-box main-left cross-center">
+                  <view class="sale-price cross-center">专享价{{ list.sale_price }}金币</view>
+                  <view v-if="list.origin_price" class="origin-price cross-center">原价{{ list.origin_price }}金币</view>
+                </view>
+              </view>
+              <view class="buy-num">{{ $util.tranNumber(buyNum) }}人购买</view>
+            </view>
+          </view>
+          <view class="static-text main-between cross-center">
+            <text>充值金币</text>
+            <view class="overage">账户余额:<text>{{ userInfo.info.integral }}金币</text></view>
+          </view>
+        </template>
+        <template v-else>
+          <view class="static-text main-between cross-center">
+            <text>充值金币</text>
+            <u-icon name="close-circle" size="52rpx" color="#BEBDBB" @click="close" />
+          </view>
+          <view class="overage">账户余额:<text>{{ userInfo.info.integral }}金币</text></view>
+        </template>
+
+        <scroll-view
+          class="recharge-group-view dir-left-wrap"
+          scroll-y
+          scroll-with-animation
+          :enable-flex="true"
+        >
+          <view class="recharge-group dir-left-wrap">
+            <view
+              v-for="(combo,index) in combos"
+              :key="index"
+              class="recharge-item dir-top-wrap main-center cross-center"
+              :class="{active: rechargeActive === index}"
+              @click="rechargeActive = index"
+            >
+              <template v-if="!combo.vip_combo">
+                <text class="price">{{ combo.price }}元</text>
+                <text class="gold">{{ combo.gold }}+{{ combo.gift }}金币</text>
+                <text class="gift">多送{{ combo.gift }}金币</text>
+              </template>
+              <template v-else>
+                <text class="price">{{ combo.price }}元</text>
+                <text class="gift">{{ combo.name }}</text>
+              </template>
+            </view>
+          </view>
+        </scroll-view>
+
+        <view class="btn" @click="handleRecharge">充值</view>
+      </view>
+    </u-popup>
+  </view>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+export default {
+  name: 'Recharge',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    mode: {
+      type: String,
+      default: 'center'
+    },
+    type: {
+      type: String,
+      default: 'normal'
+    },
+    episode: {
+      type: Object,
+      default() {
+        return {}
+      }
+    },
+    list: {
+      type: Object,
+      default() {
+        return {}
+      }
+    }
+  },
+  data() {
+    return {
+      modal: {
+        show: false
+      },
+      combos: [],
+      rechargeActive: 0,
+      buyNum: 0,
+      payId: 0
+    }
+  },
+  computed: {
+    ...mapState({
+      userInfo: seate => seate.user.info
+    })
+  },
+  watch: {
+    show(val) {
+      this.modal.show = val
+      if (val) {
+        this.getCombo()
+        this.getBuyNum()
+      }
+    }
+  },
+  created() {
+    // this.getCombo()
+    // this.getBuyNum()
+  },
+  methods: {
+    getCombo() {
+      this.$api.setting.rechargeCombo().then(res => {
+        this.combos = res.data
+      })
+    },
+    close() {
+      this.$emit('update:show', false)
+    },
+    getBuyNum() {
+      if (this.type === 'play') {
+        this.$api.episode.listBuyNum(this.list.id).then(res => {
+          this.buyNum = res.data
+        })
+      }
+    },
+    handleRecharge() {
+      // IOS 不允许购买
+      // #ifdef MP-TOUTIAO
+      if (!this.$util.checkOS()) return
+      // #endif
+      const item = this.combos[this.rechargeActive]
+      console.log('-->data', item)
+      let promise = null
+      // 选择的是会员套餐
+      if (typeof item.vip_combo !== 'undefined' && item.vip_combo) {
+        promise = this.$api.user.vip.create({ id: item.id })
+      } else {
+        promise = this.$api.user.recharge.create({ id: item.id })
+      }
+      this.$loading('请稍后...')
+      promise.then(res => {
+        this.payId = res.pay_id
+        delete res.pay_id
+        console.log('-->data', res)
+        // #ifdef MP-TOUTIAO
+        tt.pay({
+          service: 5,
+          orderInfo: {
+            order_id: res.data.order_id,
+            order_token: res.data.order_token
+          },
+          success: payRes => {
+            if (payRes.code === 0) {
+              this.$loading('支付结果查询中...')
+              this.query()
+            } else {
+              this.$u.toast('支付失败')
+            }
+          },
+          fail: err => {
+            console.log('-->data', err)
+            clearInterval(this.interval)
+            // 调起收银台失败处理逻辑
+          }
+        })
+        // #endif
+        // #ifdef MP-KUAISHOU
+        ks.pay({
+          serviceId: '1',
+          orderInfo: {
+            order_no: res.data.order_id,
+            order_info_token: res.data.order_token
+          },
+          success: payRes => {
+            this.$loading('支付结果查询中...')
+            this.query()
+          },
+          fail: err => {
+            console.log('-->data', err)
+            clearInterval(this.interval)
+            // 调起收银台失败处理逻辑
+          }
+        })
+        // #endif
+        // #ifdef MP-WEIXIN
+        uni.requestPayment({
+          ...res.data,
+          provider: 'wxpay',
+          success: res => {
+            console.log('success:' + JSON.stringify(res))
+            // _this.$u.toast("支付成功")
+            this.$loading('支付结果查询中...')
+            this.query()
+          },
+          fail: err => {
+            console.log('fail:' + JSON.stringify(err))
+            // _this.$u.toast("支付失败")
+            clearInterval(this.interval)
+          }
+        })
+        // #endif
+        this.$hideLoading()
+      }).catch(() => {
+        this.$hideLoading()
+      })
+    },
+    handleBuyVip() {
+      // #ifdef MP-TOUTIAO | MP-WEIXIN
+      if (!this.$util.checkOS()) return
+      // #endif
+      const item = this.settings[this.activeIndex]
+      this.$loading('购买中...')
+      this.$api.user.vip.create({ id: item.id }).then(res => {
+        this.$hideLoading()
+        this.payId = res.pay_id
+        delete res.pay_id
+        this.modal.show = false
+        // #ifdef MP-TOUTIAO
+        tt.pay({
+          service: 5,
+          orderInfo: {
+            order_id: res.data.order_id,
+            order_token: res.data.order_token
+          },
+          success: payRes => {
+            if (payRes.code === 0) {
+              this.$loading('支付结果查询中...')
+              this.query()
+            } else {
+              this.$u.toast('支付失败')
+            }
+          },
+          fail: err => {
+            console.log('-->data', err)
+            // 调起收银台失败处理逻辑
+          }
+        })
+        // #endif
+        // #ifdef MP-KUAISHOU
+        ks.pay({
+          serviceId: '1',
+          orderInfo: {
+            order_no: res.data.order_id,
+            order_info_token: res.data.order_token
+          },
+          success: payRes => {
+            this.$loading('支付结果查询中...')
+            this.query()
+          },
+          fail: err => {
+            console.log('-->data', err)
+            // 调起收银台失败处理逻辑
+          }
+        })
+        // #endif
+        // #ifdef MP-WEIXIN
+        uni.requestPayment({
+          ...res.data,
+          provider: 'wxpay',
+          success: res => {
+            console.log('success:' + JSON.stringify(res))
+            // _this.$u.toast("支付成功")
+            this.$loading('支付结果查询中...')
+            this.query()
+          },
+          fail: err => {
+            console.log('fail:' + JSON.stringify(err))
+            // _this.$u.toast("支付失败")
+            clearInterval(this.interval)
+          }
+        })
+        // #endif
+      }).catch(() => {
+        this.$hideLoading()
+      })
+    },
+    query() {
+      if (this.interval) return
+      this.interval = setInterval(() => {
+        this.$api.pay.query(this.payId).then(res => {
+          this.$hideLoading()
+          this.$u.toast('支付成功')
+          clearInterval(this.interval)
+          // 获取用户信息
+          this.$api.user.info().then(res => {
+            this.$store.dispatch('user/info', res.data)
+          })
+        }).catch(err => {
+          this.$hideLoading()
+        })
+      }, 1000)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+    .recharge {
+      font-size: 28rpx;
+      .container{
+        width: 700rpx;
+        border: 2rpx solid;
+        padding: 30rpx;
+        &.bottom{
+          width: 750rpx;
+          border: unset;
+          .recharge-group{
+            .recharge-item{
+              width: calc(#{670rpx} / 2) !important;
+            }
+          }
+        }
+        .static-text{
+          font-size: 36rpx;
+          font-weight: 600;
+          margin-bottom: 40rpx;
+          .overage{
+            font-size: 28rpx;
+            color: $info-color;
+          }
+        }
+        .overage{
+          color: $info-color;
+          text{
+            color: #FB3651 ;
+          }
+        }
+        // 播放充值
+        .play-container{
+          .episode{
+            margin: 40rpx 0 ;
+            background: linear-gradient(270deg, #6EEBE8, #FF74B9);
+            border-radius: 20rpx;
+            height: 180rpx;
+            padding: 20rpx;
+            .detail{
+              .name{
+                color: $default-color;
+                font-weight: 600;
+              }
+              .sale-box{
+                margin-top: 40rpx;
+                .sale-price{
+                  color: $primary-color;
+                  font-size: 28rpx;
+                }
+                .origin-price{
+                  color: $default-color;
+                  font-size: 24rpx;
+                  margin-left: 20rpx;
+                  text-decoration: line-through;
+                  opacity: .6;
+                  margin-top: 6rpx;
+                }
+              }
+            }
+            .buy-num{
+              color: $default-color;
+              min-width: 80px;
+              text-align: center;
+            }
+          }
+        }
+        // 充值套餐
+        .recharge-group-view{
+          .recharge-group{
+            margin-top: 30rpx;
+            height: 50vh;
+            display: flex;
+            .recharge-item{
+              border: 4rpx solid $primary-color;
+              width: calc(#{600rpx} / 2);
+              margin-right: 20rpx;
+              margin-bottom: 20rpx;
+              border-radius: 20rpx;
+              padding: 40rpx 20rpx;
+              transition: .3s;
+              &:nth-child(2n){
+                margin-right: 0;
+              }
+              &.active{
+                background: #1b1e32;
+                border: 4rpx solid #1b1e32;
+                .price{
+                  color: $default-color;
+                }
+              }
+              .price{
+                margin-bottom: 40rpx;
+                font-size: 38rpx;
+              }
+              .gold{
+                color: $primary-color;
+                margin-bottom: 10rpx;
+              }
+              .gift{
+                color: $info-color;
+              }
+            }
+          }
+        }
+        .btn{
+          background: linear-gradient(270deg, #6EEBE8 0%, #FF74B9 100%);
+          width: 90%;
+          margin: 40rpx auto;
+          padding: 20rpx 0;
+          text-align: center;
+          border-radius: 40rpx;
+          color: $default-color;
+          letter-spacing: .1rem;
+        }
+      }
+    }
+</style>

+ 124 - 0
mini/components/SwiperBox/index.vue

xqd
@@ -0,0 +1,124 @@
+<template>
+  <view
+    class="swiper-box "
+    :class="{
+      loading:loading,
+      'main-center':loading,
+      'cross-center': loading
+    }"
+    :style="{height: height}"
+  >
+    <u-loading-icon :show="loading" vertical />
+    <u-swiper
+      v-if="list.length"
+      :list="list"
+      :height="height"
+      :radius="radius"
+      style="width: 100%;"
+      :bg-color="$colors.bgColor"
+      :indicator="true"
+      :show-title="true"
+      indicator-mode="dot"
+      :indicator-style="{bottom: '24rpx'}"
+      img-mode=""
+      @click="handleClick"
+      @change="handleChange"
+    >
+      <view
+        slot="indicator"
+        class="indicator"
+      >
+        <view
+          v-for="(item, index) in list"
+          :key="index"
+          class="indicator__dot"
+          :class="[index === currentNum && 'indicator__dot--active']"
+        />
+      </view>
+    </u-swiper>
+  </view>
+</template>
+
+<script>
+
+export default {
+  name: 'SwiperBox',
+  props: {
+    height: {
+      type: [Number, String],
+      default: '330rpx'
+    },
+    radius: {
+      type: [Number, String],
+      default: '10rpx'
+    }
+  },
+  data() {
+    return {
+      loading: true,
+      list: [],
+      currentNum: 0,
+      data: []
+    }
+  },
+  computed: {
+  },
+  async created() {
+    await this.getSwiper()
+  },
+  methods: {
+    handleClick(index) {
+      const item = this.data[index]
+      console.log('-->data', item)
+      if (item.episode_id) {
+        this.$u.route({
+          url: 'pages/episode/play',
+          params: {
+            id: item.episode_id
+          }
+        })
+      }
+    },
+    handleChange(e) {
+      this.currentNum = e.current
+    },
+    async getSwiper() {
+      await this.$api.setting.banner().then(res => {
+        this.loading = false
+        this.data = res.data
+        res.data.forEach(obj => {
+          this.list.push(obj.image)
+        })
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .swiper-box{
+    margin: 20rpx 0;
+    border-radius: 8rpx;
+    &.loading{
+      background-color: $bg-color;
+    }
+    .indicator {
+      display: flex;
+      flex-direction: row;
+      justify-content: center;
+
+      &__dot {
+        height: 20rpx;
+        width: 20rpx;
+        border-radius: 50%;
+        background-color: rgba(255, 255, 255, 0.35);
+        margin: 0 10px;
+        transition: background-color 0.3s;
+
+        &--active {
+          background-color: #6EEBE8;
+        }
+      }
+    }
+  }
+</style>

+ 151 - 0
mini/components/TabBar/index.vue

xqd
@@ -0,0 +1,151 @@
+<template>
+  <view class="tab-bar">
+    <view class="content main-between cross-center">
+      <view
+        v-for="(item,index) in list"
+        :key="index"
+        class="tab-item dir-top-wrap cross-center main-center"
+        :class="{active: active === index}"
+        @click="handleSwitch(index)"
+      >
+        <view class="icon">
+          <image :src="active === index ? (item.selected_icon ? item.selected_icon : item.selectedIconPath) : (item.icon ? item.icon : item.iconPath)" mode="aspectFit" />
+        </view>
+        <text>{{ item.text }}</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+
+export default {
+  name: 'TabBar',
+  data() {
+    return {
+      color: '#8a8a8a',
+      selectedColor: '#6EEBE8',
+      types: {
+        1: {
+          'pagePath': '/pages/index/index',
+          'iconPath': '/static/image/tab/home.png',
+          'selectedIconPath': '/static/image/tab/home-HL.png'
+        },
+        2: {
+          'pagePath': '/pages/trace/index',
+          'iconPath': '/static/image/tab/trace.png',
+          'selectedIconPath': '/static/image/tab/trace-HL.png'
+        },
+        3: {
+          'pagePath': '/pages/my/index',
+          'iconPath': '/static/image/tab/my.png',
+          'selectedIconPath': '/static/image/tab/my-HL.png'
+        }
+      },
+      list: [
+        {
+          'pagePath': '/pages/index/index',
+          'iconPath': '/static/image/tab/home.png',
+          'selectedIconPath': '/static/image/tab/home-HL.png',
+          'text': '首页'
+        },
+        {
+          'pagePath': '/pages/trace/index',
+          'iconPath': '/static/image/tab/trace.png',
+          'selectedIconPath': '/static/image/tab/trace-HL.png',
+          'text': '追剧'
+        },
+        {
+          'pagePath': '/pages/my/index',
+          'iconPath': '/static/image/tab/my.png',
+          'selectedIconPath': '/static/image/tab/my-HL.png',
+          'text': '我的'
+        }
+      ]
+    }
+  },
+  computed: {
+    ...mapState({
+      active: seate => seate.tab.index
+    })
+  },
+  async created() {
+    this.calc()
+    uni.hideTabBar()
+    await this.getData()
+  },
+  methods: {
+    handleSwitch(index) {
+      if (index === this.active) {
+        return
+      }
+      this.$store.dispatch('tab/index', index)
+      const item = this.list[index]
+      uni.switchTab({
+        url: item.pagePath
+      })
+    },
+    calc() {
+      let active = 1
+      const page = uni.$u.page().replace('//', '/')
+      this.list.forEach((obj, index) => {
+        if (obj.pagePath === page) {
+          active = index
+        }
+      })
+      if (active !== this.active) {
+        this.$store.dispatch('tab/index', active)
+      }
+    },
+    async getData() {
+      await this.$api.setting.tabBar().then(res => {
+        const data = res.data
+        data.forEach(obj => {
+          Object.assign(obj, this.types[obj.type])
+        })
+
+        console.log('-->data', data)
+
+        this.list = data
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .tab-bar{
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    .content{
+      background: $bg-color;
+      position: absolute;
+      bottom: 0;
+      width: 100%;
+      height: 120rpx;
+      background-size: 110% 100%;
+
+      .tab-item{
+        flex: 1;
+        color: #8a8a8a;
+        transition: .3s;
+        font-size: 24rpx;
+        position: relative;
+        &.active{
+          color: #6EEBE8;
+        }
+        .icon{
+          width: 42rpx;
+          height: 42rpx;
+          image{
+            height: 100%;
+            width: 100%;
+          }
+        }
+      }
+    }
+  }
+</style>

+ 20 - 0
mini/index.html

xqd
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <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>
+    <title></title>
+    <!--preload-links-->
+    <!--app-context-->
+  </head>
+  <body>
+    <div id="app"><!--app-html--></div>
+    <script type="module" src="/main.js"></script>
+  </body>
+</html>

+ 32 - 0
mini/main.js

xqd
@@ -0,0 +1,32 @@
+import Vue from 'vue'
+import App from './App'
+import uView from '@/uni_modules/uview-ui'
+
+// vuex store
+import store from './store'
+import mixin from './utils/mixin'
+
+Vue.prototype.$store = store
+
+Vue.config.productionTip = false
+App.mpType = 'app'
+
+// #ifdef MP
+// 引入uView对小程序分享的mixin封装
+const mpShare = require('@/uni_modules/uview-ui/libs/mixin/mpShare.js')
+Vue.mixin(mpShare)
+// #endif
+
+// uView
+Vue.use(uView)
+
+Vue.mixin(mixin)
+
+const app = new Vue({
+  store,
+  ...App
+})
+
+require('utils/request/index')(app)
+
+app.$mount()

+ 90 - 0
mini/manifest.json

xqd
@@ -0,0 +1,90 @@
+{
+    "name" : "user",
+    "appid" : "__UNI__B2F00A6",
+    "description" : "",
+    "versionName" : "1.0.0",
+    "versionCode" : "100",
+    "transformPx" : false,
+    /* 5+App特有相关 */
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueStyleCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        /* 模块配置 */
+        "modules" : {},
+        /* 应用发布信息 */
+        "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.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ]
+            },
+            /* ios打包配置 */
+            "ios" : {},
+            /* SDK配置 */
+            "sdkConfigs" : {}
+        }
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "wxc614ff7099796009",
+        "setting" : {
+            "urlCheck" : false,
+            "minified" : true,
+            "postcss" : false,
+            "es6" : false
+        },
+        "usingComponents" : true,
+        "permission" : {
+            "scope.userLocation" : {
+                "desc" : "你的位置信息将用于小程序定位"
+            }
+        },
+        "uniStatistics" : {
+            "enable" : false
+        }
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true,
+        "appid" : "tta9e8df5536d6b48b01",
+        "setting" : {
+            "urlCheck" : false
+        }
+    },
+    "uniStatistics" : {
+        "enable" : false
+    },
+    "vueVersion" : "2",
+    "mp-kuaishou" : {
+        "appid" : "ks658369973403037900"
+    }
+}

+ 2602 - 0
mini/package-lock.json

xqd
@@ -0,0 +1,2602 @@
+{
+  "name": "mini",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "devDependencies": {
+        "babel-eslint": "10.1.0",
+        "eslint": "6.7.2",
+        "eslint-plugin-vue": "6.2.2"
+      }
+    },
+    "node_modules/@babel/code-frame": {
+      "version": "7.16.7",
+      "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.16.7.tgz",
+      "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
+      "dev": true,
+      "dependencies": {
+        "@babel/highlight": "^7.16.7"
+      }
+    },
+    "node_modules/@babel/generator": {
+      "version": "7.17.7",
+      "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.17.7.tgz",
+      "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.17.0",
+        "jsesc": "^2.5.1",
+        "source-map": "^0.5.0"
+      }
+    },
+    "node_modules/@babel/helper-environment-visitor": {
+      "version": "7.16.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz",
+      "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.16.7"
+      }
+    },
+    "node_modules/@babel/helper-function-name": {
+      "version": "7.16.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz",
+      "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-get-function-arity": "^7.16.7",
+        "@babel/template": "^7.16.7",
+        "@babel/types": "^7.16.7"
+      }
+    },
+    "node_modules/@babel/helper-get-function-arity": {
+      "version": "7.16.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz",
+      "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.16.7"
+      }
+    },
+    "node_modules/@babel/helper-hoist-variables": {
+      "version": "7.16.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz",
+      "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.16.7"
+      }
+    },
+    "node_modules/@babel/helper-split-export-declaration": {
+      "version": "7.16.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz",
+      "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.16.7"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.16.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
+      "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==",
+      "dev": true
+    },
+    "node_modules/@babel/highlight": {
+      "version": "7.16.10",
+      "resolved": "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.16.10.tgz",
+      "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-validator-identifier": "^7.16.7",
+        "chalk": "^2.0.0",
+        "js-tokens": "^4.0.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.17.8",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.17.8.tgz",
+      "integrity": "sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ==",
+      "dev": true
+    },
+    "node_modules/@babel/template": {
+      "version": "7.16.7",
+      "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.16.7.tgz",
+      "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==",
+      "dev": true,
+      "dependencies": {
+        "@babel/code-frame": "^7.16.7",
+        "@babel/parser": "^7.16.7",
+        "@babel/types": "^7.16.7"
+      }
+    },
+    "node_modules/@babel/traverse": {
+      "version": "7.17.3",
+      "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.17.3.tgz",
+      "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/code-frame": "^7.16.7",
+        "@babel/generator": "^7.17.3",
+        "@babel/helper-environment-visitor": "^7.16.7",
+        "@babel/helper-function-name": "^7.16.7",
+        "@babel/helper-hoist-variables": "^7.16.7",
+        "@babel/helper-split-export-declaration": "^7.16.7",
+        "@babel/parser": "^7.17.3",
+        "@babel/types": "^7.17.0",
+        "debug": "^4.1.0",
+        "globals": "^11.1.0"
+      }
+    },
+    "node_modules/@babel/traverse/node_modules/globals": {
+      "version": "11.12.0",
+      "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz",
+      "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+      "dev": true
+    },
+    "node_modules/@babel/types": {
+      "version": "7.17.0",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.17.0.tgz",
+      "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-validator-identifier": "^7.16.7",
+        "to-fast-properties": "^2.0.0"
+      }
+    },
+    "node_modules/acorn": {
+      "version": "7.4.1",
+      "resolved": "https://registry.npmmirror.com/acorn/-/acorn-7.4.1.tgz",
+      "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+      "dev": true
+    },
+    "node_modules/acorn-jsx": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+      "dev": true
+    },
+    "node_modules/ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "node_modules/ansi-escapes": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+      "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+      "dev": true,
+      "dependencies": {
+        "type-fest": "^0.21.3"
+      }
+    },
+    "node_modules/ansi-escapes/node_modules/type-fest": {
+      "version": "0.21.3",
+      "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.21.3.tgz",
+      "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+      "dev": true
+    },
+    "node_modules/ansi-regex": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz",
+      "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+      "dev": true
+    },
+    "node_modules/ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^1.9.0"
+      }
+    },
+    "node_modules/argparse": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "dev": true,
+      "dependencies": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
+    "node_modules/astral-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/astral-regex/-/astral-regex-1.0.0.tgz",
+      "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+      "dev": true
+    },
+    "node_modules/babel-eslint": {
+      "version": "10.1.0",
+      "resolved": "https://registry.npmmirror.com/babel-eslint/-/babel-eslint-10.1.0.tgz",
+      "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==",
+      "dev": true,
+      "dependencies": {
+        "@babel/code-frame": "^7.0.0",
+        "@babel/parser": "^7.7.0",
+        "@babel/traverse": "^7.7.0",
+        "@babel/types": "^7.7.0",
+        "eslint-visitor-keys": "^1.0.0",
+        "resolve": "^1.12.0"
+      }
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true
+    },
+    "node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true
+    },
+    "node_modules/chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      }
+    },
+    "node_modules/chardet": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmmirror.com/chardet/-/chardet-0.7.0.tgz",
+      "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+      "dev": true
+    },
+    "node_modules/cli-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-3.1.0.tgz",
+      "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+      "dev": true,
+      "dependencies": {
+        "restore-cursor": "^3.1.0"
+      }
+    },
+    "node_modules/cli-width": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/cli-width/-/cli-width-3.0.0.tgz",
+      "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
+      "dev": true
+    },
+    "node_modules/color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "1.1.3"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+      "dev": true
+    },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+      "dev": true
+    },
+    "node_modules/cross-spawn": {
+      "version": "6.0.5",
+      "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-6.0.5.tgz",
+      "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+      "dev": true,
+      "dependencies": {
+        "nice-try": "^1.0.4",
+        "path-key": "^2.0.1",
+        "semver": "^5.5.0",
+        "shebang-command": "^1.2.0",
+        "which": "^1.2.9"
+      }
+    },
+    "node_modules/cross-spawn/node_modules/semver": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz",
+      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+      "dev": true
+    },
+    "node_modules/debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "2.1.2"
+      }
+    },
+    "node_modules/deep-is": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz",
+      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+      "dev": true
+    },
+    "node_modules/doctrine": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz",
+      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+      "dev": true,
+      "dependencies": {
+        "esutils": "^2.0.2"
+      }
+    },
+    "node_modules/emoji-regex": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-7.0.3.tgz",
+      "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+      "dev": true
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+      "dev": true
+    },
+    "node_modules/eslint": {
+      "version": "6.7.2",
+      "resolved": "https://registry.npmmirror.com/eslint/-/eslint-6.7.2.tgz",
+      "integrity": "sha512-qMlSWJaCSxDFr8fBPvJM9kJwbazrhNcBU3+DszDW1OlEwKBBRWsJc7NJFelvwQpanHCR14cOLD41x8Eqvo3Nng==",
+      "dev": true,
+      "dependencies": {
+        "@babel/code-frame": "^7.0.0",
+        "ajv": "^6.10.0",
+        "chalk": "^2.1.0",
+        "cross-spawn": "^6.0.5",
+        "debug": "^4.0.1",
+        "doctrine": "^3.0.0",
+        "eslint-scope": "^5.0.0",
+        "eslint-utils": "^1.4.3",
+        "eslint-visitor-keys": "^1.1.0",
+        "espree": "^6.1.2",
+        "esquery": "^1.0.1",
+        "esutils": "^2.0.2",
+        "file-entry-cache": "^5.0.1",
+        "functional-red-black-tree": "^1.0.1",
+        "glob-parent": "^5.0.0",
+        "globals": "^12.1.0",
+        "ignore": "^4.0.6",
+        "import-fresh": "^3.0.0",
+        "imurmurhash": "^0.1.4",
+        "inquirer": "^7.0.0",
+        "is-glob": "^4.0.0",
+        "js-yaml": "^3.13.1",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "levn": "^0.3.0",
+        "lodash": "^4.17.14",
+        "minimatch": "^3.0.4",
+        "mkdirp": "^0.5.1",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.8.3",
+        "progress": "^2.0.0",
+        "regexpp": "^2.0.1",
+        "semver": "^6.1.2",
+        "strip-ansi": "^5.2.0",
+        "strip-json-comments": "^3.0.1",
+        "table": "^5.2.3",
+        "text-table": "^0.2.0",
+        "v8-compile-cache": "^2.0.3"
+      }
+    },
+    "node_modules/eslint-plugin-vue": {
+      "version": "6.2.2",
+      "resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-6.2.2.tgz",
+      "integrity": "sha512-Nhc+oVAHm0uz/PkJAWscwIT4ijTrK5fqNqz9QB1D35SbbuMG1uB6Yr5AJpvPSWg+WOw7nYNswerYh0kOk64gqQ==",
+      "dev": true,
+      "dependencies": {
+        "natural-compare": "^1.4.0",
+        "semver": "^5.6.0",
+        "vue-eslint-parser": "^7.0.0"
+      }
+    },
+    "node_modules/eslint-plugin-vue/node_modules/semver": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz",
+      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+      "dev": true
+    },
+    "node_modules/eslint-scope": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz",
+      "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+      "dev": true,
+      "dependencies": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^4.1.1"
+      }
+    },
+    "node_modules/eslint-scope/node_modules/estraverse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz",
+      "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+      "dev": true
+    },
+    "node_modules/eslint-visitor-keys": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+      "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+      "dev": true
+    },
+    "node_modules/eslint/node_modules/eslint-utils": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmmirror.com/eslint-utils/-/eslint-utils-1.4.3.tgz",
+      "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
+      "dev": true,
+      "dependencies": {
+        "eslint-visitor-keys": "^1.1.0"
+      }
+    },
+    "node_modules/espree": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmmirror.com/espree/-/espree-6.2.1.tgz",
+      "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
+      "dev": true,
+      "dependencies": {
+        "acorn": "^7.1.1",
+        "acorn-jsx": "^5.2.0",
+        "eslint-visitor-keys": "^1.1.0"
+      }
+    },
+    "node_modules/esprima": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz",
+      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+      "dev": true
+    },
+    "node_modules/esquery": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.4.0.tgz",
+      "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.1.0"
+      }
+    },
+    "node_modules/esrecurse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz",
+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.2.0"
+      }
+    },
+    "node_modules/estraverse": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz",
+      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+      "dev": true
+    },
+    "node_modules/esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true
+    },
+    "node_modules/external-editor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/external-editor/-/external-editor-3.1.0.tgz",
+      "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+      "dev": true,
+      "dependencies": {
+        "chardet": "^0.7.0",
+        "iconv-lite": "^0.4.24",
+        "tmp": "^0.0.33"
+      }
+    },
+    "node_modules/fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "dev": true
+    },
+    "node_modules/fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "node_modules/fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+      "dev": true
+    },
+    "node_modules/figures": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmmirror.com/figures/-/figures-3.2.0.tgz",
+      "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
+      "dev": true,
+      "dependencies": {
+        "escape-string-regexp": "^1.0.5"
+      }
+    },
+    "node_modules/file-entry-cache": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+      "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
+      "dev": true,
+      "dependencies": {
+        "flat-cache": "^2.0.1"
+      }
+    },
+    "node_modules/flat-cache": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-2.0.1.tgz",
+      "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
+      "dev": true,
+      "dependencies": {
+        "flatted": "^2.0.0",
+        "rimraf": "2.6.3",
+        "write": "1.0.3"
+      }
+    },
+    "node_modules/flatted": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/flatted/-/flatted-2.0.2.tgz",
+      "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
+      "dev": true
+    },
+    "node_modules/fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+      "dev": true
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+      "dev": true
+    },
+    "node_modules/functional-red-black-tree": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+      "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
+      "dev": true
+    },
+    "node_modules/glob": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.0.tgz",
+      "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+      "dev": true,
+      "dependencies": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.0.4",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      }
+    },
+    "node_modules/globals": {
+      "version": "12.4.0",
+      "resolved": "https://registry.npmmirror.com/globals/-/globals-12.4.0.tgz",
+      "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
+      "dev": true,
+      "dependencies": {
+        "type-fest": "^0.8.1"
+      }
+    },
+    "node_modules/has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "dev": true,
+      "dependencies": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "node_modules/has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+      "dev": true
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "dev": true,
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "node_modules/ignore": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmmirror.com/ignore/-/ignore-4.0.6.tgz",
+      "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+      "dev": true
+    },
+    "node_modules/import-fresh": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz",
+      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+      "dev": true,
+      "dependencies": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      }
+    },
+    "node_modules/imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+      "dev": true
+    },
+    "node_modules/inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+      "dev": true,
+      "dependencies": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "dev": true
+    },
+    "node_modules/inquirer": {
+      "version": "7.3.3",
+      "resolved": "https://registry.npmmirror.com/inquirer/-/inquirer-7.3.3.tgz",
+      "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-escapes": "^4.2.1",
+        "chalk": "^4.1.0",
+        "cli-cursor": "^3.1.0",
+        "cli-width": "^3.0.0",
+        "external-editor": "^3.0.3",
+        "figures": "^3.0.0",
+        "lodash": "^4.17.19",
+        "mute-stream": "0.0.8",
+        "run-async": "^2.4.0",
+        "rxjs": "^6.6.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0",
+        "through": "^2.3.6"
+      }
+    },
+    "node_modules/inquirer/node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true
+    },
+    "node_modules/inquirer/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      }
+    },
+    "node_modules/inquirer/node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      }
+    },
+    "node_modules/inquirer/node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "~1.1.4"
+      }
+    },
+    "node_modules/inquirer/node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true
+    },
+    "node_modules/inquirer/node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true
+    },
+    "node_modules/inquirer/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      }
+    },
+    "node_modules/inquirer/node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      }
+    },
+    "node_modules/is-core-module": {
+      "version": "2.8.1",
+      "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.8.1.tgz",
+      "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==",
+      "dev": true,
+      "dependencies": {
+        "has": "^1.0.3"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true
+    },
+    "node_modules/is-fullwidth-code-point": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+      "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
+      "dev": true
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      }
+    },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "dev": true
+    },
+    "node_modules/js-yaml": {
+      "version": "3.14.1",
+      "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.1.tgz",
+      "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+      "dev": true,
+      "dependencies": {
+        "argparse": "^1.0.7",
+        "esprima": "^4.0.0"
+      }
+    },
+    "node_modules/jsesc": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz",
+      "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+      "dev": true
+    },
+    "node_modules/json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "node_modules/json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+      "dev": true
+    },
+    "node_modules/levn": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmmirror.com/levn/-/levn-0.3.0.tgz",
+      "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "~1.1.2",
+        "type-check": "~0.3.2"
+      }
+    },
+    "node_modules/lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "dev": true
+    },
+    "node_modules/mimic-fn": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz",
+      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+      "dev": true
+    },
+    "node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "node_modules/minimist": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.5.tgz",
+      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+      "dev": true
+    },
+    "node_modules/mkdirp": {
+      "version": "0.5.5",
+      "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.5.tgz",
+      "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+      "dev": true,
+      "dependencies": {
+        "minimist": "^1.2.5"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "node_modules/mute-stream": {
+      "version": "0.0.8",
+      "resolved": "https://registry.npmmirror.com/mute-stream/-/mute-stream-0.0.8.tgz",
+      "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
+      "dev": true
+    },
+    "node_modules/natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+      "dev": true
+    },
+    "node_modules/nice-try": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/nice-try/-/nice-try-1.0.5.tgz",
+      "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+      "dev": true
+    },
+    "node_modules/once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "dev": true,
+      "dependencies": {
+        "wrappy": "1"
+      }
+    },
+    "node_modules/onetime": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz",
+      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+      "dev": true,
+      "dependencies": {
+        "mimic-fn": "^2.1.0"
+      }
+    },
+    "node_modules/optionator": {
+      "version": "0.8.3",
+      "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.8.3.tgz",
+      "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+      "dev": true,
+      "dependencies": {
+        "deep-is": "~0.1.3",
+        "fast-levenshtein": "~2.0.6",
+        "levn": "~0.3.0",
+        "prelude-ls": "~1.1.2",
+        "type-check": "~0.3.2",
+        "word-wrap": "~1.2.3"
+      }
+    },
+    "node_modules/os-tmpdir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
+      "dev": true
+    },
+    "node_modules/parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
+      "dependencies": {
+        "callsites": "^3.0.0"
+      }
+    },
+    "node_modules/path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+      "dev": true
+    },
+    "node_modules/path-key": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/path-key/-/path-key-2.0.1.tgz",
+      "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==",
+      "dev": true
+    },
+    "node_modules/path-parse": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+      "dev": true
+    },
+    "node_modules/prelude-ls": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.1.2.tgz",
+      "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
+      "dev": true
+    },
+    "node_modules/progress": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz",
+      "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+      "dev": true
+    },
+    "node_modules/punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+      "dev": true
+    },
+    "node_modules/regexpp": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/regexpp/-/regexpp-2.0.1.tgz",
+      "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
+      "dev": true
+    },
+    "node_modules/resolve": {
+      "version": "1.22.0",
+      "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.0.tgz",
+      "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
+      "dev": true,
+      "dependencies": {
+        "is-core-module": "^2.8.1",
+        "path-parse": "^1.0.7",
+        "supports-preserve-symlinks-flag": "^1.0.0"
+      }
+    },
+    "node_modules/resolve-from": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz",
+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "dev": true
+    },
+    "node_modules/restore-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-3.1.0.tgz",
+      "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+      "dev": true,
+      "dependencies": {
+        "onetime": "^5.1.0",
+        "signal-exit": "^3.0.2"
+      }
+    },
+    "node_modules/rimraf": {
+      "version": "2.6.3",
+      "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.6.3.tgz",
+      "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+      "dev": true,
+      "dependencies": {
+        "glob": "^7.1.3"
+      }
+    },
+    "node_modules/run-async": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmmirror.com/run-async/-/run-async-2.4.1.tgz",
+      "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
+      "dev": true
+    },
+    "node_modules/rxjs": {
+      "version": "6.6.7",
+      "resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-6.6.7.tgz",
+      "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+      "dev": true,
+      "dependencies": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "dev": true
+    },
+    "node_modules/semver": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.0.tgz",
+      "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+      "dev": true
+    },
+    "node_modules/shebang-command": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-1.2.0.tgz",
+      "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==",
+      "dev": true,
+      "dependencies": {
+        "shebang-regex": "^1.0.0"
+      }
+    },
+    "node_modules/shebang-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-1.0.0.tgz",
+      "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==",
+      "dev": true
+    },
+    "node_modules/signal-exit": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz",
+      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+      "dev": true
+    },
+    "node_modules/slice-ansi": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-2.1.0.tgz",
+      "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^3.2.0",
+        "astral-regex": "^1.0.0",
+        "is-fullwidth-code-point": "^2.0.0"
+      }
+    },
+    "node_modules/source-map": {
+      "version": "0.5.7",
+      "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz",
+      "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+      "dev": true
+    },
+    "node_modules/sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+      "dev": true
+    },
+    "node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "dev": true,
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      }
+    },
+    "node_modules/string-width/node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true
+    },
+    "node_modules/string-width/node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "dev": true
+    },
+    "node_modules/string-width/node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "dev": true
+    },
+    "node_modules/string-width/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      }
+    },
+    "node_modules/strip-ansi": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz",
+      "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^4.1.0"
+      }
+    },
+    "node_modules/strip-json-comments": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "dev": true
+    },
+    "node_modules/supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^3.0.0"
+      }
+    },
+    "node_modules/supports-preserve-symlinks-flag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+      "dev": true
+    },
+    "node_modules/table": {
+      "version": "5.4.6",
+      "resolved": "https://registry.npmmirror.com/table/-/table-5.4.6.tgz",
+      "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
+      "dev": true,
+      "dependencies": {
+        "ajv": "^6.10.2",
+        "lodash": "^4.17.14",
+        "slice-ansi": "^2.1.0",
+        "string-width": "^3.0.0"
+      }
+    },
+    "node_modules/table/node_modules/string-width": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/string-width/-/string-width-3.1.0.tgz",
+      "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+      "dev": true,
+      "dependencies": {
+        "emoji-regex": "^7.0.1",
+        "is-fullwidth-code-point": "^2.0.0",
+        "strip-ansi": "^5.1.0"
+      }
+    },
+    "node_modules/text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+      "dev": true
+    },
+    "node_modules/through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmmirror.com/through/-/through-2.3.8.tgz",
+      "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
+      "dev": true
+    },
+    "node_modules/tmp": {
+      "version": "0.0.33",
+      "resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.0.33.tgz",
+      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+      "dev": true,
+      "dependencies": {
+        "os-tmpdir": "~1.0.2"
+      }
+    },
+    "node_modules/to-fast-properties": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+      "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+      "dev": true
+    },
+    "node_modules/tslib": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz",
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+      "dev": true
+    },
+    "node_modules/type-check": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.3.2.tgz",
+      "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "~1.1.2"
+      }
+    },
+    "node_modules/type-fest": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.8.1.tgz",
+      "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+      "dev": true
+    },
+    "node_modules/uri-js": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz",
+      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+      "dev": true,
+      "dependencies": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "node_modules/v8-compile-cache": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
+      "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
+      "dev": true
+    },
+    "node_modules/vue-eslint-parser": {
+      "version": "7.11.0",
+      "resolved": "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-7.11.0.tgz",
+      "integrity": "sha512-qh3VhDLeh773wjgNTl7ss0VejY9bMMa0GoDG2fQVyDzRFdiU3L7fw74tWZDHNQXdZqxO3EveQroa9ct39D2nqg==",
+      "dev": true,
+      "dependencies": {
+        "debug": "^4.1.1",
+        "eslint-scope": "^5.1.1",
+        "eslint-visitor-keys": "^1.1.0",
+        "espree": "^6.2.1",
+        "esquery": "^1.4.0",
+        "lodash": "^4.17.21",
+        "semver": "^6.3.0"
+      }
+    },
+    "node_modules/which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmmirror.com/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "dev": true,
+      "dependencies": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "node_modules/word-wrap": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.3.tgz",
+      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+      "dev": true
+    },
+    "node_modules/wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "dev": true
+    },
+    "node_modules/write": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/write/-/write-1.0.3.tgz",
+      "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
+      "dev": true,
+      "dependencies": {
+        "mkdirp": "^0.5.1"
+      }
+    }
+  },
+  "dependencies": {
+    "@babel/code-frame": {
+      "version": "7.16.7",
+      "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.16.7.tgz",
+      "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
+      "dev": true,
+      "requires": {
+        "@babel/highlight": "^7.16.7"
+      }
+    },
+    "@babel/generator": {
+      "version": "7.17.7",
+      "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.17.7.tgz",
+      "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.17.0",
+        "jsesc": "^2.5.1",
+        "source-map": "^0.5.0"
+      }
+    },
+    "@babel/helper-environment-visitor": {
+      "version": "7.16.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz",
+      "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.16.7"
+      }
+    },
+    "@babel/helper-function-name": {
+      "version": "7.16.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz",
+      "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-get-function-arity": "^7.16.7",
+        "@babel/template": "^7.16.7",
+        "@babel/types": "^7.16.7"
+      }
+    },
+    "@babel/helper-get-function-arity": {
+      "version": "7.16.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz",
+      "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.16.7"
+      }
+    },
+    "@babel/helper-hoist-variables": {
+      "version": "7.16.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz",
+      "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.16.7"
+      }
+    },
+    "@babel/helper-split-export-declaration": {
+      "version": "7.16.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz",
+      "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.16.7"
+      }
+    },
+    "@babel/helper-validator-identifier": {
+      "version": "7.16.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
+      "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==",
+      "dev": true
+    },
+    "@babel/highlight": {
+      "version": "7.16.10",
+      "resolved": "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.16.10.tgz",
+      "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-validator-identifier": "^7.16.7",
+        "chalk": "^2.0.0",
+        "js-tokens": "^4.0.0"
+      }
+    },
+    "@babel/parser": {
+      "version": "7.17.8",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.17.8.tgz",
+      "integrity": "sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ==",
+      "dev": true
+    },
+    "@babel/template": {
+      "version": "7.16.7",
+      "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.16.7.tgz",
+      "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.16.7",
+        "@babel/parser": "^7.16.7",
+        "@babel/types": "^7.16.7"
+      }
+    },
+    "@babel/traverse": {
+      "version": "7.17.3",
+      "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.17.3.tgz",
+      "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.16.7",
+        "@babel/generator": "^7.17.3",
+        "@babel/helper-environment-visitor": "^7.16.7",
+        "@babel/helper-function-name": "^7.16.7",
+        "@babel/helper-hoist-variables": "^7.16.7",
+        "@babel/helper-split-export-declaration": "^7.16.7",
+        "@babel/parser": "^7.17.3",
+        "@babel/types": "^7.17.0",
+        "debug": "^4.1.0",
+        "globals": "^11.1.0"
+      },
+      "dependencies": {
+        "globals": {
+          "version": "11.12.0",
+          "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz",
+          "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+          "dev": true
+        }
+      }
+    },
+    "@babel/types": {
+      "version": "7.17.0",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.17.0.tgz",
+      "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-validator-identifier": "^7.16.7",
+        "to-fast-properties": "^2.0.0"
+      }
+    },
+    "acorn": {
+      "version": "7.4.1",
+      "resolved": "https://registry.npmmirror.com/acorn/-/acorn-7.4.1.tgz",
+      "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+      "dev": true
+    },
+    "acorn-jsx": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+      "dev": true
+    },
+    "ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "requires": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "ansi-escapes": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+      "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+      "dev": true,
+      "requires": {
+        "type-fest": "^0.21.3"
+      },
+      "dependencies": {
+        "type-fest": {
+          "version": "0.21.3",
+          "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.21.3.tgz",
+          "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+          "dev": true
+        }
+      }
+    },
+    "ansi-regex": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz",
+      "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+      "dev": true
+    },
+    "ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "dev": true,
+      "requires": {
+        "color-convert": "^1.9.0"
+      }
+    },
+    "argparse": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "dev": true,
+      "requires": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
+    "astral-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/astral-regex/-/astral-regex-1.0.0.tgz",
+      "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+      "dev": true
+    },
+    "babel-eslint": {
+      "version": "10.1.0",
+      "resolved": "https://registry.npmmirror.com/babel-eslint/-/babel-eslint-10.1.0.tgz",
+      "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.0.0",
+        "@babel/parser": "^7.7.0",
+        "@babel/traverse": "^7.7.0",
+        "@babel/types": "^7.7.0",
+        "eslint-visitor-keys": "^1.0.0",
+        "resolve": "^1.12.0"
+      }
+    },
+    "balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true
+    },
+    "chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      }
+    },
+    "chardet": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmmirror.com/chardet/-/chardet-0.7.0.tgz",
+      "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+      "dev": true
+    },
+    "cli-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-3.1.0.tgz",
+      "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+      "dev": true,
+      "requires": {
+        "restore-cursor": "^3.1.0"
+      }
+    },
+    "cli-width": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/cli-width/-/cli-width-3.0.0.tgz",
+      "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
+      "dev": true
+    },
+    "color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "dev": true,
+      "requires": {
+        "color-name": "1.1.3"
+      }
+    },
+    "color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+      "dev": true
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+      "dev": true
+    },
+    "cross-spawn": {
+      "version": "6.0.5",
+      "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-6.0.5.tgz",
+      "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+      "dev": true,
+      "requires": {
+        "nice-try": "^1.0.4",
+        "path-key": "^2.0.1",
+        "semver": "^5.5.0",
+        "shebang-command": "^1.2.0",
+        "which": "^1.2.9"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dev": true,
+      "requires": {
+        "ms": "2.1.2"
+      }
+    },
+    "deep-is": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz",
+      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+      "dev": true
+    },
+    "doctrine": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz",
+      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+      "dev": true,
+      "requires": {
+        "esutils": "^2.0.2"
+      }
+    },
+    "emoji-regex": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-7.0.3.tgz",
+      "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+      "dev": true
+    },
+    "escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+      "dev": true
+    },
+    "eslint": {
+      "version": "6.7.2",
+      "resolved": "https://registry.npmmirror.com/eslint/-/eslint-6.7.2.tgz",
+      "integrity": "sha512-qMlSWJaCSxDFr8fBPvJM9kJwbazrhNcBU3+DszDW1OlEwKBBRWsJc7NJFelvwQpanHCR14cOLD41x8Eqvo3Nng==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.0.0",
+        "ajv": "^6.10.0",
+        "chalk": "^2.1.0",
+        "cross-spawn": "^6.0.5",
+        "debug": "^4.0.1",
+        "doctrine": "^3.0.0",
+        "eslint-scope": "^5.0.0",
+        "eslint-utils": "^1.4.3",
+        "eslint-visitor-keys": "^1.1.0",
+        "espree": "^6.1.2",
+        "esquery": "^1.0.1",
+        "esutils": "^2.0.2",
+        "file-entry-cache": "^5.0.1",
+        "functional-red-black-tree": "^1.0.1",
+        "glob-parent": "^5.0.0",
+        "globals": "^12.1.0",
+        "ignore": "^4.0.6",
+        "import-fresh": "^3.0.0",
+        "imurmurhash": "^0.1.4",
+        "inquirer": "^7.0.0",
+        "is-glob": "^4.0.0",
+        "js-yaml": "^3.13.1",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "levn": "^0.3.0",
+        "lodash": "^4.17.14",
+        "minimatch": "^3.0.4",
+        "mkdirp": "^0.5.1",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.8.3",
+        "progress": "^2.0.0",
+        "regexpp": "^2.0.1",
+        "semver": "^6.1.2",
+        "strip-ansi": "^5.2.0",
+        "strip-json-comments": "^3.0.1",
+        "table": "^5.2.3",
+        "text-table": "^0.2.0",
+        "v8-compile-cache": "^2.0.3"
+      },
+      "dependencies": {
+        "eslint-utils": {
+          "version": "1.4.3",
+          "resolved": "https://registry.npmmirror.com/eslint-utils/-/eslint-utils-1.4.3.tgz",
+          "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
+          "dev": true,
+          "requires": {
+            "eslint-visitor-keys": "^1.1.0"
+          }
+        }
+      }
+    },
+    "eslint-plugin-vue": {
+      "version": "6.2.2",
+      "resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-6.2.2.tgz",
+      "integrity": "sha512-Nhc+oVAHm0uz/PkJAWscwIT4ijTrK5fqNqz9QB1D35SbbuMG1uB6Yr5AJpvPSWg+WOw7nYNswerYh0kOk64gqQ==",
+      "dev": true,
+      "requires": {
+        "natural-compare": "^1.4.0",
+        "semver": "^5.6.0",
+        "vue-eslint-parser": "^7.0.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "eslint-scope": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz",
+      "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+      "dev": true,
+      "requires": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^4.1.1"
+      },
+      "dependencies": {
+        "estraverse": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz",
+          "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+          "dev": true
+        }
+      }
+    },
+    "eslint-visitor-keys": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+      "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+      "dev": true
+    },
+    "espree": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmmirror.com/espree/-/espree-6.2.1.tgz",
+      "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
+      "dev": true,
+      "requires": {
+        "acorn": "^7.1.1",
+        "acorn-jsx": "^5.2.0",
+        "eslint-visitor-keys": "^1.1.0"
+      }
+    },
+    "esprima": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz",
+      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+      "dev": true
+    },
+    "esquery": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.4.0.tgz",
+      "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^5.1.0"
+      }
+    },
+    "esrecurse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz",
+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^5.2.0"
+      }
+    },
+    "estraverse": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz",
+      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+      "dev": true
+    },
+    "esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true
+    },
+    "external-editor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/external-editor/-/external-editor-3.1.0.tgz",
+      "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+      "dev": true,
+      "requires": {
+        "chardet": "^0.7.0",
+        "iconv-lite": "^0.4.24",
+        "tmp": "^0.0.33"
+      }
+    },
+    "fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "dev": true
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+      "dev": true
+    },
+    "figures": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmmirror.com/figures/-/figures-3.2.0.tgz",
+      "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
+      "dev": true,
+      "requires": {
+        "escape-string-regexp": "^1.0.5"
+      }
+    },
+    "file-entry-cache": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+      "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
+      "dev": true,
+      "requires": {
+        "flat-cache": "^2.0.1"
+      }
+    },
+    "flat-cache": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-2.0.1.tgz",
+      "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
+      "dev": true,
+      "requires": {
+        "flatted": "^2.0.0",
+        "rimraf": "2.6.3",
+        "write": "1.0.3"
+      }
+    },
+    "flatted": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/flatted/-/flatted-2.0.2.tgz",
+      "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
+      "dev": true
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+      "dev": true
+    },
+    "function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+      "dev": true
+    },
+    "functional-red-black-tree": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+      "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
+      "dev": true
+    },
+    "glob": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.0.tgz",
+      "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+      "dev": true,
+      "requires": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.0.4",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      }
+    },
+    "glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "requires": {
+        "is-glob": "^4.0.1"
+      }
+    },
+    "globals": {
+      "version": "12.4.0",
+      "resolved": "https://registry.npmmirror.com/globals/-/globals-12.4.0.tgz",
+      "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
+      "dev": true,
+      "requires": {
+        "type-fest": "^0.8.1"
+      }
+    },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "dev": true,
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+      "dev": true
+    },
+    "iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "dev": true,
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "ignore": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmmirror.com/ignore/-/ignore-4.0.6.tgz",
+      "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+      "dev": true
+    },
+    "import-fresh": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz",
+      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+      "dev": true,
+      "requires": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      }
+    },
+    "imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+      "dev": true
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+      "dev": true,
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "dev": true
+    },
+    "inquirer": {
+      "version": "7.3.3",
+      "resolved": "https://registry.npmmirror.com/inquirer/-/inquirer-7.3.3.tgz",
+      "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==",
+      "dev": true,
+      "requires": {
+        "ansi-escapes": "^4.2.1",
+        "chalk": "^4.1.0",
+        "cli-cursor": "^3.1.0",
+        "cli-width": "^3.0.0",
+        "external-editor": "^3.0.3",
+        "figures": "^3.0.0",
+        "lodash": "^4.17.19",
+        "mute-stream": "0.0.8",
+        "run-async": "^2.4.0",
+        "rxjs": "^6.6.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0",
+        "through": "^2.3.6"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
+          "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+          "dev": true
+        },
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "dev": true,
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
+        "chalk": {
+          "version": "4.1.2",
+          "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
+          "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+          "dev": true
+        },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "6.0.1",
+          "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+          "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^5.0.1"
+          }
+        },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
+          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        }
+      }
+    },
+    "is-core-module": {
+      "version": "2.8.1",
+      "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.8.1.tgz",
+      "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==",
+      "dev": true,
+      "requires": {
+        "has": "^1.0.3"
+      }
+    },
+    "is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true
+    },
+    "is-fullwidth-code-point": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+      "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
+      "dev": true
+    },
+    "is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "requires": {
+        "is-extglob": "^2.1.1"
+      }
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true
+    },
+    "js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "dev": true
+    },
+    "js-yaml": {
+      "version": "3.14.1",
+      "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.1.tgz",
+      "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+      "dev": true,
+      "requires": {
+        "argparse": "^1.0.7",
+        "esprima": "^4.0.0"
+      }
+    },
+    "jsesc": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz",
+      "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+      "dev": true
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+      "dev": true
+    },
+    "levn": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmmirror.com/levn/-/levn-0.3.0.tgz",
+      "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "~1.1.2",
+        "type-check": "~0.3.2"
+      }
+    },
+    "lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "dev": true
+    },
+    "mimic-fn": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz",
+      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+      "dev": true
+    },
+    "minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "minimist": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.5.tgz",
+      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+      "dev": true
+    },
+    "mkdirp": {
+      "version": "0.5.5",
+      "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.5.tgz",
+      "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+      "dev": true,
+      "requires": {
+        "minimist": "^1.2.5"
+      }
+    },
+    "ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "mute-stream": {
+      "version": "0.0.8",
+      "resolved": "https://registry.npmmirror.com/mute-stream/-/mute-stream-0.0.8.tgz",
+      "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
+      "dev": true
+    },
+    "natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+      "dev": true
+    },
+    "nice-try": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/nice-try/-/nice-try-1.0.5.tgz",
+      "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+      "dev": true
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "dev": true,
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "onetime": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz",
+      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+      "dev": true,
+      "requires": {
+        "mimic-fn": "^2.1.0"
+      }
+    },
+    "optionator": {
+      "version": "0.8.3",
+      "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.8.3.tgz",
+      "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+      "dev": true,
+      "requires": {
+        "deep-is": "~0.1.3",
+        "fast-levenshtein": "~2.0.6",
+        "levn": "~0.3.0",
+        "prelude-ls": "~1.1.2",
+        "type-check": "~0.3.2",
+        "word-wrap": "~1.2.3"
+      }
+    },
+    "os-tmpdir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
+      "dev": true
+    },
+    "parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
+      "requires": {
+        "callsites": "^3.0.0"
+      }
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+      "dev": true
+    },
+    "path-key": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/path-key/-/path-key-2.0.1.tgz",
+      "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==",
+      "dev": true
+    },
+    "path-parse": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+      "dev": true
+    },
+    "prelude-ls": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.1.2.tgz",
+      "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
+      "dev": true
+    },
+    "progress": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz",
+      "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+      "dev": true
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+      "dev": true
+    },
+    "regexpp": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/regexpp/-/regexpp-2.0.1.tgz",
+      "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
+      "dev": true
+    },
+    "resolve": {
+      "version": "1.22.0",
+      "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.0.tgz",
+      "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
+      "dev": true,
+      "requires": {
+        "is-core-module": "^2.8.1",
+        "path-parse": "^1.0.7",
+        "supports-preserve-symlinks-flag": "^1.0.0"
+      }
+    },
+    "resolve-from": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz",
+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "dev": true
+    },
+    "restore-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-3.1.0.tgz",
+      "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+      "dev": true,
+      "requires": {
+        "onetime": "^5.1.0",
+        "signal-exit": "^3.0.2"
+      }
+    },
+    "rimraf": {
+      "version": "2.6.3",
+      "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.6.3.tgz",
+      "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+      "dev": true,
+      "requires": {
+        "glob": "^7.1.3"
+      }
+    },
+    "run-async": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmmirror.com/run-async/-/run-async-2.4.1.tgz",
+      "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
+      "dev": true
+    },
+    "rxjs": {
+      "version": "6.6.7",
+      "resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-6.6.7.tgz",
+      "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "dev": true
+    },
+    "semver": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.0.tgz",
+      "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+      "dev": true
+    },
+    "shebang-command": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-1.2.0.tgz",
+      "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==",
+      "dev": true,
+      "requires": {
+        "shebang-regex": "^1.0.0"
+      }
+    },
+    "shebang-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-1.0.0.tgz",
+      "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==",
+      "dev": true
+    },
+    "signal-exit": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz",
+      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+      "dev": true
+    },
+    "slice-ansi": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-2.1.0.tgz",
+      "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.0",
+        "astral-regex": "^1.0.0",
+        "is-fullwidth-code-point": "^2.0.0"
+      }
+    },
+    "source-map": {
+      "version": "0.5.7",
+      "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz",
+      "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+      "dev": true
+    },
+    "sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+      "dev": true
+    },
+    "string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "dev": true,
+      "requires": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
+          "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+          "dev": true
+        },
+        "emoji-regex": {
+          "version": "8.0.0",
+          "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
+          "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "6.0.1",
+          "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+          "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^5.0.1"
+          }
+        }
+      }
+    },
+    "strip-ansi": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz",
+      "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+      "dev": true,
+      "requires": {
+        "ansi-regex": "^4.1.0"
+      }
+    },
+    "strip-json-comments": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "dev": true
+    },
+    "supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "dev": true,
+      "requires": {
+        "has-flag": "^3.0.0"
+      }
+    },
+    "supports-preserve-symlinks-flag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+      "dev": true
+    },
+    "table": {
+      "version": "5.4.6",
+      "resolved": "https://registry.npmmirror.com/table/-/table-5.4.6.tgz",
+      "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
+      "dev": true,
+      "requires": {
+        "ajv": "^6.10.2",
+        "lodash": "^4.17.14",
+        "slice-ansi": "^2.1.0",
+        "string-width": "^3.0.0"
+      },
+      "dependencies": {
+        "string-width": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmmirror.com/string-width/-/string-width-3.1.0.tgz",
+          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^7.0.1",
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^5.1.0"
+          }
+        }
+      }
+    },
+    "text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+      "dev": true
+    },
+    "through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmmirror.com/through/-/through-2.3.8.tgz",
+      "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
+      "dev": true
+    },
+    "tmp": {
+      "version": "0.0.33",
+      "resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.0.33.tgz",
+      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+      "dev": true,
+      "requires": {
+        "os-tmpdir": "~1.0.2"
+      }
+    },
+    "to-fast-properties": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+      "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+      "dev": true
+    },
+    "tslib": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz",
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+      "dev": true
+    },
+    "type-check": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.3.2.tgz",
+      "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "~1.1.2"
+      }
+    },
+    "type-fest": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.8.1.tgz",
+      "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+      "dev": true
+    },
+    "uri-js": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz",
+      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+      "dev": true,
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "v8-compile-cache": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
+      "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
+      "dev": true
+    },
+    "vue-eslint-parser": {
+      "version": "7.11.0",
+      "resolved": "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-7.11.0.tgz",
+      "integrity": "sha512-qh3VhDLeh773wjgNTl7ss0VejY9bMMa0GoDG2fQVyDzRFdiU3L7fw74tWZDHNQXdZqxO3EveQroa9ct39D2nqg==",
+      "dev": true,
+      "requires": {
+        "debug": "^4.1.1",
+        "eslint-scope": "^5.1.1",
+        "eslint-visitor-keys": "^1.1.0",
+        "espree": "^6.2.1",
+        "esquery": "^1.4.0",
+        "lodash": "^4.17.21",
+        "semver": "^6.3.0"
+      }
+    },
+    "which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmmirror.com/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "dev": true,
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "word-wrap": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.3.tgz",
+      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+      "dev": true
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "dev": true
+    },
+    "write": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/write/-/write-1.0.3.tgz",
+      "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
+      "dev": true,
+      "requires": {
+        "mkdirp": "^0.5.1"
+      }
+    }
+  }
+}

+ 7 - 0
mini/package.json

xqd
@@ -0,0 +1,7 @@
+{
+  "devDependencies": {
+    "babel-eslint": "10.1.0",
+    "eslint": "6.7.2",
+    "eslint-plugin-vue": "6.2.2"
+  }
+}

+ 240 - 0
mini/pages.json

xqd
@@ -0,0 +1,240 @@
+{
+  "easycom": {
+    "^u-(.*)": "@/uni_modules/uview-ui/components/u-$1/u-$1.vue",
+    "layout": "@/layout/index.vue"
+  },
+  "pages": [
+    //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+    {
+      "path": "pages/index/index",
+      "style": {
+        // #ifdef  MP-KUAISHOU
+        "navigationBarTitleText": "四海剧场",
+        // #endif
+        // #ifdef  MP-TOUTIAO | MP-WEIXIN
+        "navigationBarTitleText": "张四爷剧场",
+        // #endif
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/login",
+      "style": {
+        // #ifdef  MP-KUAISHOU
+        "navigationBarTitleText": "四海剧场|login",
+        // #endif
+        // #ifdef  MP-TOUTIAO | MP-WEIXIN
+        "navigationBarTitleText": "张四爷剧场|login",
+        // #endif
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/index/rank",
+      "style": {
+        "navigationBarTitleText": "排行榜",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/index/news",
+      "style": {
+        "navigationBarTitleText": "最新",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/index/search",
+      "style": {
+        "navigationBarTitleText": "搜索结果",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/trace/index",
+      "style": {
+        // #ifdef  MP-KUAISHOU
+        "navigationBarTitleText": "四海剧场",
+        // #endif
+        // #ifdef  MP-TOUTIAO | MP-WEIXIN
+        "navigationBarTitleText": "张四爷剧场",
+        // #endif
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/my/index",
+      "style": {
+        // #ifdef  MP-KUAISHOU
+        "navigationBarTitleText": "四海剧场",
+        // #endif
+        // #ifdef  MP-TOUTIAO | MP-WEIXIN
+        "navigationBarTitleText": "张四爷剧场",
+        // #endif
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/trace/collect",
+      "style": {
+        "navigationBarTitleText": "我的收藏",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/trace/list",
+      "style": {
+        // #ifdef  MP-KUAISHOU
+        "navigationBarTitleText": "四海剧场",
+        // #endif
+        // #ifdef  MP-TOUTIAO | MP-WEIXIN
+        "navigationBarTitleText": "张四爷剧场",
+        // #endif
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/my/consume",
+      "style": {
+        "navigationBarTitleText": "消费记录",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/my/history",
+      "style": {
+        "navigationBarTitleText": "历史记录",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/my/recharge",
+      "style": {
+        "navigationBarTitleText": "充值记录",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/my/protocol",
+      "style": {
+        "navigationBarTitleText": "用户协议",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/sign/index",
+      "style": {
+        "navigationBarTitleText": "签到",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/member/index",
+      "style": {
+        "navigationBarTitleText": "会员",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/member/free",
+      "style": {
+        "navigationBarTitleText": "会员免费片单",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/episode/play",
+      "style": {
+        "navigationBarTitleText": "播放",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/share/index",
+      "style": {
+        "navigationBarTitleText": "分享中心",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/share/income",
+      "style": {
+        "navigationBarTitleText": "分享佣金",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/share/order",
+      "style": {
+        "navigationBarTitleText": "分享订单",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/share/team",
+      "style": {
+        "navigationBarTitleText": "我的团队",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/share/withdraw",
+      "style": {
+        "navigationBarTitleText": "提现",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/share/withdrawDetail",
+      "style": {
+        "navigationBarTitleText": "提现明细",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/share/tips",
+      "style": {
+        "navigationBarTitleText": "用户须知",
+        "enablePullDownRefresh": false
+      }
+    }
+  ],
+  "globalStyle": {
+    "navigationBarTextStyle": "white", // black or white
+    // #ifdef  MP-KUAISHOU
+    "navigationBarTitleText": "四海剧场",
+    // #endif
+    // #ifdef  MP-TOUTIAO | MP-WEIXIN
+    "navigationBarTitleText": "张四爷剧场",
+    // #endif
+    "navigationBarBackgroundColor": "#151728",
+    "backgroundColor": "#151728"
+  },
+  "tabBar": {
+    "custom": true,
+    "color": "#8a8a8a",
+    "selectedColor": "#6EEBE8",
+    "backgroundColor": "#17162B",
+    "borderStyle": "black",
+    "list": [
+      {
+        "pagePath": "pages/index/index",
+        "iconPath": "static/image/tab/home.png",
+        "selectedIconPath": "static/image/tab/home-HL.png",
+        "text": "首页"
+      },
+      {
+        "pagePath": "pages/trace/index",
+        "iconPath": "static/image/tab/trace.png",
+        "selectedIconPath": "static/image/tab/trace-HL.png",
+        "text": "追剧"
+      },
+      {
+        "pagePath": "pages/my/index",
+        "iconPath": "static/image/tab/my.png",
+        "selectedIconPath": "static/image/tab/my-HL.png",
+        "text": "我的"
+      }
+    ]
+  }
+}

+ 242 - 0
mini/pages/episode/components/EpisodeButtons.vue

xqd
@@ -0,0 +1,242 @@
+<template>
+  <view v-if="Object.keys(episode).length" class="episode-buttons">
+    <!--播放数据-->
+    <view class="view-num main-left cross-center">
+      <u-icon name="eye-fill" :color="isCollect?$colors.primaryColor:$colors.defaultColor" size="26rpx" />
+      <text>{{ $util.tranNumber(episode.user_watch_record_count) }}</text>
+    </view>
+    <!--按钮-->
+    <view class="status-bar dir-top-wrap main-center">
+      <!--喜欢-->
+      <view
+        class="item fav dir-top-wrap main-center cross-center"
+        :class="{active: isFav}"
+        @click="handleFavorite"
+      >
+        <u-loading-icon v-if="favLoading" size="58rpx" :color="$colors.primaryColor" />
+        <u-icon v-else name="heart-fill" size="58rpx" :color="isFav?$colors.primaryColor:$colors.defaultColor" />
+        <text>{{ $util.tranNumber(favNumber) }}</text>
+      </view>
+      <!--收藏-->
+      <view
+        class="item collect dir-top-wrap main-center cross-center"
+        :class="{active: isCollect}"
+        @click="handleCollect"
+      >
+        <u-loading-icon v-if="collectLoading" size="58rpx" :color="$colors.primaryColor" />
+        <u-icon v-else name="star-fill" size="58rpx" :color="isCollect?$colors.primaryColor:$colors.defaultColor" />
+        <text>{{ $util.tranNumber(episode.user_collect_count) }}</text>
+      </view>
+      <!--分享-->
+      <view class="item share dir-top-wrap main-center cross-center">
+        <button open-type="share" />
+        <u-icon name="share-fill" size="58rpx" :color="$colors.defaultColor" />
+        <text>{{ $util.tranNumber(episode.share_count) }}</text>
+      </view>
+    </view>
+    <!--toast-->
+    <view
+      v-if="toast.show"
+      class="toast dir-top-wrap main-center cross-center"
+      :class="toast.status"
+    >
+      <u-icon
+        :name="toast.status === 'success' ? 'checkmark-circle' : 'close-circle'"
+        :color="toast.status === 'success' ? $colors.primaryColor : $colors.defaultColor"
+        size="80rpx"
+      />
+      <text>{{ toast.text }}</text>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'EpisodeButtons',
+  props: {
+    episode: {
+      type: Object,
+      required: true,
+      default() {
+        return {}
+      }
+    },
+    currentEpisode: {
+      type: Object,
+      required: true
+    }
+  },
+  data() {
+    return {
+      isCollect: false,
+      isFav: false,
+      favLoading: false,
+      collectLoading: false,
+      toast: { // 收藏/喜欢 toast
+        status: 'success',
+        text: '收藏成功',
+        show: false
+      },
+      btnLock: false,
+      favNumber: 0,
+      listId: 0
+    }
+  },
+  watch: {
+    'toast.show'(val) {
+      if (val) {
+        setTimeout(() => {
+          this.toast.show = false
+        }, 1000)
+      }
+    },
+    currentEpisode(val) {
+      if (Object.keys(val).length) {
+        this.listId = val.id
+      }
+    },
+    listId(val) {
+      this.checkFavorite()
+    }
+  },
+  created() {
+    if (Object.keys(this.episode).length) {
+      this.checkCollect()
+      // this.checkFavorite()
+    }
+  },
+  methods: {
+    // 检查是否收藏当前剧集
+    checkCollect() {
+      this.$api.user.collect.check(this.episode.id).then(res => {
+        this.isCollect = res.data
+      })
+    },
+    // 收藏相关 处理
+    handleCollect() {
+      if (this.btnLock) return
+      this.btnLock = true
+      this.collectLoading = true
+      const method = this.isCollect ? 'destroy' : 'add'
+      const num = this.isCollect ? -1 : 1
+      this.$api.user.collect[method](this.episode.id).then(res => {
+        this.btnLock = false
+        this.collectLoading = false
+        if (res.data) {
+          this.toast.show = true
+          this.toast.status = this.isCollect ? 'cancel' : 'success'
+          this.toast.text = this.isCollect ? '取消收藏' : '收藏成功'
+          this.isCollect = !this.isCollect
+          this.$emit('change', { type: 'collect', num: num })
+        }
+      }).catch(() => {
+        this.collectLoading = false
+        this.btnLock = false
+      })
+    },
+    // 检查是否喜欢当前短剧
+    checkFavorite() {
+      this.$api.user.favorite.check(this.currentEpisode.id).then(res => {
+        this.isFav = res.data.isFav
+        this.favNumber = res.data.number
+        console.log('-->data', this.isFav)
+      })
+    },
+    // 处理 喜欢剧集
+    handleFavorite() {
+      console.log('-->handleFavorite', this.btnLock)
+      if (this.btnLock) return
+      console.log('-->handleFavorite', this.btnLock)
+      this.btnLock = true
+      this.favLoading = true
+      const method = this.isFav ? 'destroy' : 'add'
+      const num = this.isFav ? -1 : 1
+      this.$api.user.favorite[method](this.currentEpisode.id).then(res => {
+        this.btnLock = false
+        this.favLoading = false
+        if (res.data) {
+          this.toast.show = true
+          this.toast.status = this.isFav ? 'cancel' : 'success'
+          this.toast.text = this.isFav ? '取消喜欢' : '喜欢成功'
+          // this.$emit('change', { type: 'fav', num: num })
+          this.isFav = !this.isFav
+          this.favNumber += num
+        }
+      }).catch(() => {
+        this.favLoading = false
+        this.btnLock = false
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.episode-buttons {
+  .view-num{
+    position: fixed;
+    right: 20rpx;
+    top: 40rpx;
+    color: #fff;
+    font-size: 26rpx;
+    z-index: 100;
+    text{
+      margin-left: 10rpx;
+    }
+  }
+
+  .status-bar{
+    position: fixed;
+    bottom: 300rpx;
+    right: 40rpx;
+    color: $default-color;
+    z-index: 999;
+    .item{
+      margin-bottom: 40rpx;
+      &.share{
+        position: relative;
+        button{
+          position: absolute;
+          background: transparent;
+          top: 0;
+          left: 0;
+          right: 0;
+          bottom: 0;
+          z-index: 1;
+          border: unset;
+          box-shadow: unset;
+          &:after{
+            content: unset;
+          }
+        }
+      }
+      &.active{
+        color: $primary-color;
+      }
+      text{
+        margin-top: 10rpx;
+      }
+    }
+  }
+
+  .toast{
+    position: fixed;
+    width: 60vw;
+    background: rgba(0,0,0,.5);
+    height: 300rpx;
+    top: 30%;
+    left: 50%;
+    transform: translate(-50%,-50%);
+    border-radius: 20rpx;
+    font-size: 36rpx;
+    color: $default-color;
+    z-index: 999;
+    &.success{
+      color: $primary-color;
+    }
+    text{
+      margin-top: 20rpx;
+    }
+  }
+}
+</style>

+ 260 - 0
mini/pages/episode/components/EpisodePart.vue

xqd
@@ -0,0 +1,260 @@
+<template>
+  <view v-if="Object.keys(episode).length" class="episode-part">
+    <view class="footer" :class="{episode: footerShow}">
+      <view class="bar main-between cross-center" @click="footerShow = !footerShow">
+        <view class="icon" />
+        <view class="name">
+          <u-text :text="episode.name" :color="$colors.infoColor" :lines="1" />
+        </view>
+        <view class="arrow">
+          <u-icon name="arrow-up" :color="$colors.infoColor" size="32rpx" />
+        </view>
+      </view>
+      <view class="episode-container dir-top-wrap">
+        <!--分集 横向滚动-->
+        <scroll-view
+          class="header-box dir-left-nowrap cross-center"
+          scroll-x
+          scroll-with-animation
+        >
+          <view
+            v-for="(item,index) in episodeTabData"
+            :key="index"
+            class="header-item"
+            :class="{active: episodesTabIndex === index}"
+            @click="episodesTabIndex = index"
+          >{{ item.title }}</view>
+        </scroll-view>
+        <!--几集选择-->
+        <swiper
+          class="content"
+          :current="episodesTabIndex"
+          @change="handleSwiperChange"
+        >
+          <swiper-item
+            v-for="(row, index) in episodeTabData"
+            :key="index"
+            class="dir-left-wrap main-left"
+          >
+            <view
+              v-for="(item, rowIndex) in row.lists"
+              :key="rowIndex"
+              class="episode-item"
+              @click="handleSelectEpisode(item.index)"
+            >
+              <image :src="episode.cover_img" />
+              <text>第{{ item.sort }}集</text>
+              <view v-if="parseInt(currentEpisode.sort) === parseInt(item.sort) && isPlaying" class="playing" />
+              <view
+                v-if="!item.is_free && buyRecord.indexOf(item.id) === -1 && !(episode.is_vip_watch && userInfo.info.is_vip)"
+                class="lock main-center cross-center"
+              >
+                <u-icon name="lock-fill" :color="$colors.defaultColor" size="46rpx" />
+              </view>
+            </view>
+          </swiper-item>
+        </swiper>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+export default {
+  name: 'EpisodePart',
+  props: {
+    episode: {
+      type: Object,
+      required: true,
+      default() {
+        return {}
+      }
+    },
+    currentEpisode: {
+      type: Object,
+      required: true
+    },
+    isPlaying: {
+      type: Boolean,
+      required: true
+    },
+    buyRecord: {
+      type: Array,
+      required: true
+    }
+  },
+  data() {
+    return {
+      footerShow: false, // 底部是否弹出
+      episodesTabIndex: 0 // 分集 Tab 选中
+    }
+  },
+  computed: {
+    ...mapState({
+      userInfo: seate => seate.user.info
+    }),
+    episodeTabData() {
+      const list = []
+      if (Object.keys(this.episode).length) {
+        let temp = []
+        this.episode.lists.forEach((obj, index) => {
+          temp.push(obj)
+          if (temp.length === 6 || index === (this.episode.lists.length - 1)) {
+            const start = list.length ? (list.length * 6) + 1 : 1
+            const end = (start - 1) + temp.length
+            list.push({ title: `${start}集-${end}集`, lists: temp })
+            temp = []
+          }
+        })
+      }
+      return list
+    }
+  },
+  watch: {
+    currentEpisode(val) {
+      const index = this.episode.lists.findIndex(obj => {
+        return obj.sort === this.currentEpisode.sort
+      })
+      console.log('-->currentEpisode index', index)
+    }
+  },
+  methods: {
+    handleSelectEpisode(index) {
+      this.$emit('selectEpisode', index)
+      this.footerShow = false
+    },
+    handleSwiperChange(e) {
+      this.episodesTabIndex = e.detail.current
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.episode-part {
+  .footer{
+    position: fixed;
+    width: 95vw;
+    height: 76rpx;
+    background: rgba(24, 28, 47, 0.8);
+    bottom: 50rpx;
+    left: 50%;
+    transform: translateX(-50%);
+    font-size: 26rpx;
+    color: $info-color;
+    border-radius: 20rpx;
+    z-index: 999;
+    transition: .3s;
+    &.episode{
+      bottom: 700rpx;
+      margin-top: -4rpx;
+      border-bottom-left-radius: 0;
+      border-bottom-right-radius: 0;
+      .episode-container{
+        display: flex;
+        margin-top: -4rpx;
+        border-bottom-left-radius: 20px;
+        border-bottom-right-radius: 20px;
+      }
+      .bar{
+        .arrow{
+          transform: rotate(180deg);
+        }
+      }
+    }
+    .bar{
+      padding: 0 20rpx;
+      .icon{
+        background: url("/static/image/video.png") no-repeat center;
+        background-size: 70%;
+        width: 80rpx;
+        height: 80rpx;
+      }
+      .name{
+        text-align: left;
+        flex: 1;
+        padding: 0 30rpx;
+      }
+      .arrow{
+        transition: .3s;
+      }
+    }
+
+    .episode-container{
+      display: none;
+      background: inherit;
+      min-height: 620rpx;
+      .header-box{
+        white-space: nowrap;
+        margin: 20rpx 0;
+        .header-item{
+          margin-right: 20rpx;
+          border-radius: 20rpx;
+          display: inline-block;
+          width: 200rpx;
+          border: 1rpx solid $default-color;
+          text-align: center;
+          padding: 10rpx 0;
+          color: $default-color;
+          &.active{
+            border-color: $primary-color;
+            color: $primary-color;
+          }
+        }
+      }
+      .content{
+        margin-top: 20rpx;
+        height: 580rpx;
+        .episode-item{
+          position: relative;
+          width: calc((100% - #{40rpx}) / 3);
+          margin-right: 20rpx;
+          margin-bottom: 20rpx;
+          overflow: hidden;
+          border-radius: 18rpx;
+          height: 270rpx;
+          &:nth-child(3n){
+            margin-right: 0;
+          }
+          .playing{
+            position: absolute;
+            top: 0;
+            left: 0;
+            bottom: 0;
+            right: 0;
+            background: rgba(0,0,0,.5) url("/static/image/playing.png") no-repeat center;
+            background-size: 40rpx;
+            z-index: 2;
+          }
+          image{
+            width: 100%;
+            height: 270rpx;
+          }
+          text{
+            position: absolute;
+            left: 0;
+            bottom: 0;
+            right: 0;
+            color: $default-color;
+            padding: 20rpx 0;
+            text-align: center;
+            background: rgba(0,0,0,.3);
+            z-index: 1;
+          }
+          .lock{
+            position: absolute;
+            top: 0;
+            left: 0;
+            bottom: 0;
+            right: 0;
+            background: rgba(0,0,0,.5);
+            z-index: 2;
+          }
+        }
+      }
+    }
+
+  }
+}
+</style>

+ 517 - 0
mini/pages/episode/play.vue

xqd
@@ -0,0 +1,517 @@
+<template>
+  <view class="play-container">
+    <u-loading-page
+      :loading="loading"
+      :bg-color="$colors.bgColor"
+      :color="$colors.primaryColor"
+      :loading-color="$colors.primaryColor"
+    />
+    <template v-if="!loading">
+      <!-- 剧集按钮-->
+      <episode-buttons :episode="episode" :current-episode="currentEpisode" @change="handleCollectAndFavChange" />
+      <!--视频播放-->
+      <view class="video-box main-center cross-center" :style="{zIndex: isPlaying ? 0 : 998}">
+        <!--视频容器-->
+        <swiper
+          class="swiper"
+          circular
+          :vertical="true"
+          :current="swiperCurrent"
+          @change="handleSwiperChancge"
+          @animationfinish="handleSwiperAnimationFinish"
+        >
+          <swiper-item v-for="(item, index) in swiperEpisode" :key="index" class="swiper-item">
+            <!-- #ifdef  MP-TOUTIAO | MP-WEIXIN-->
+            <!-- 控制按钮 - 播放 -->
+            <view v-if="!item.isPlaying" class="play-layer main-center cross-center" @tap="handlePlay(item)">
+              <view class="icon">
+                <u-icon name="play-right-fill" size="100rpx" :color="$colors.defaultColor" />
+              </view>
+            </view>
+            <!-- 控制按钮 - 暂停 -->
+            <view v-if="item.isPlaying" class="pause-layer" @tap="handlePause(item, true)" />
+            <!--   #endif-->
+            <video
+              v-if="Object.keys(item).length"
+              :id="`video${index}`"
+              :autoplay="(index==='current'&&isFirstLoad)?'autoplay':false"
+              :poster="episode.cover_img"
+              :src="item.url"
+              :style="{
+                width:'100%',
+                height: 'calc(100vh - 130rpx)',
+                zIndex: 0
+              }"
+              :show-play-btn="video.playBtn"
+              :show-center-play-btn="video.playBtn"
+              :show-fullscreen-btn="video.fullscreenBtn"
+              :controls="video.controls"
+              :show-progress="video.progress"
+              object-fit="contain"
+              @timeupdate="timeupdate"
+              @ended="ended"
+              @play="play($event,item)"
+            />
+          </swiper-item>
+        </swiper>
+      </view>
+      <!--底部-->
+      <episode-part
+        :episode="episode"
+        :is-playing="isPlaying"
+        :buy-record="buyRecord"
+        :current-episode="currentEpisode"
+        @selectEpisode="handleSelectEpisode"
+      />
+      <!--充值-->
+      <recharge :show.sync="rechargeShow" type="play" mode="bottom" :episode="episode" :list="currentEpisode" />
+    </template>
+  </view>
+</template>
+
+<script>
+import {
+  mapState
+} from 'vuex'
+import Recharge from '../../components/Recharge/index'
+import EpisodeButtons from './components/EpisodeButtons'
+import EpisodePart from './components/EpisodePart'
+export default {
+  name: 'Play',
+  components: {
+    EpisodePart,
+    EpisodeButtons,
+    Recharge
+  },
+  data() {
+    return {
+      isFirstLoad: true, // 是否首次播放
+      id: null, // 短剧ID
+      listId: null, // 剧集ID
+      isPlaying: false, // 是否播放
+      progress: 0, // 进度条
+      episode: {}, // 短剧信息
+      lists: [], // 剧集信息
+      loading: false, // 数据加载
+      video: { // 视频配置
+        controls: true,
+        progress: true,
+        fullscreenBtn: false,
+        playBtn: false,
+        // #ifdef  MP-KUAISHOU | MP-TOUTIAO
+        duration: 450,
+        // #endif
+        // #ifdef  MP-WEIXIN
+        duration: 500
+        // #endif
+      },
+      buyRecord: [], // 购买记录
+      rechargeShow: false, // 显示充值
+      swiperCurrent: 1, // 当前滚动
+      currentEpisode: {}, // 当前播放剧集
+      swiperEpisode: { // swiper 剧集
+        prev: {},
+        current: {},
+        next: {}
+      },
+      indexArr: {
+        'prev': 0,
+        'current': 1,
+        'next': 2
+      }
+    }
+  },
+  computed: {
+    ...mapState({
+      userInfo: seate => seate.user.info
+    }),
+    videoContext() {
+      const indexArr = ['prev', 'current', 'next']
+      const swiperKey = indexArr[this.swiperCurrent]
+      return uni.createVideoContext(`video${swiperKey}`, this)
+    }
+  },
+  watch: {
+    // 进度条
+    progress(val) {
+      if (val >= 100) {
+        const indexArr = ['prev', 'current', 'next']
+        const len = this.lists.length
+        const swiperKey = indexArr[this.swiperCurrent]
+        if (this.swiperEpisode[swiperKey].sort === this.lists[len - 1].sort) {
+          this.$u.toast('已全部播放完成')
+          return 100
+        }
+        // 切换
+        switch (this.swiperCurrent) {
+          case 0:
+            this.swiperCurrent = 1
+            break
+          case 1:
+            this.swiperCurrent = 2
+            break
+          case 2:
+            this.swiperCurrent = 0
+            break
+        }
+        this.$forceUpdate()
+      }
+    },
+    swiperCurrent(val) {
+      const indexArr = ['prev', 'current', 'next']
+      const swiperKey = indexArr[val]
+      const dataIndex = this.lists.findIndex(val => {
+        return this.swiperEpisode[swiperKey].sort === val.sort
+      })
+      const len = this.lists.length
+      let prevIndex, currentIndex, nextIndex
+      switch (swiperKey) {
+        case 'prev':
+          currentIndex = dataIndex + 1 >= len ? 0 : dataIndex + 1
+          nextIndex = dataIndex - 1 < 0 ? len - 1 : dataIndex - 1
+          this.swiperEpisode.current = this.lists[currentIndex]
+          this.swiperEpisode.next = this.lists[nextIndex]
+          break
+        case 'current':
+          prevIndex = dataIndex - 1 < 0 ? len - 1 : dataIndex - 1
+          nextIndex = dataIndex + 1 >= len ? 0 : dataIndex + 1
+          this.swiperEpisode.prev = this.lists[prevIndex]
+          this.swiperEpisode.next = this.lists[nextIndex]
+          break
+        case 'next':
+          prevIndex = dataIndex + 1 >= len ? 0 : dataIndex + 1
+          currentIndex = dataIndex - 1 < 0 ? len - 1 : dataIndex - 1
+          this.swiperEpisode.current = this.lists[currentIndex]
+          this.swiperEpisode.prev = this.lists[prevIndex]
+          break
+      }
+
+      // 暂停其他
+      this.handlePause(this.currentEpisode, false)
+
+      // 当前播放剧集
+      this.currentEpisode = this.swiperEpisode[swiperKey]
+      // 播放
+      this.handlePlay(this.currentEpisode)
+    },
+    currentEpisode(val) {
+      console.log('-->当前剧集', val.sort)
+    }
+  },
+  methods: {
+    // 播放进度
+    timeupdate({ detail }) {
+      // currentTime, duration
+      if (detail.duration) {
+        this.progress = (detail.currentTime / detail.duration * 100).toFixed(2)
+      }
+    },
+    ended() {
+      // #ifdef  MP-KUAISHOU
+      this.progress = 100
+      // #endif
+    },
+    play(e, item) {
+      console.log('-->data', e, item)
+      // #ifdef  MP-KUAISHOU
+      this.handlePlay(item)
+      // #endif
+    },
+    // 播放
+    handlePlay(item) {
+      this.currentEpisode = item
+      // 检查是否购买
+      if (!this.checkBeforePlay(item)) {
+        // 余额是否购买
+        if (!this.checkOverage(item)) {
+          // #ifdef  MP-KUAISHOU
+          // 如果没有购买,那把视频链接设置为空 停止播放,使用 pause 和 stop 在真机上不生效 只能采用这种方式
+          // item.url = ''//让对应的视频停止播放==========》
+          this.videoContext.stop()
+          // #endif
+          this.rechargeShow = true
+          return
+        }
+        // 余额足够 直接购买
+        this.handleBuy()
+        return
+      }
+      this.isPlaying = true
+      item.isPlaying = true
+      // #ifdef MP-KUAISHOU
+      item.url = item.src
+      this.$forceUpdate()
+      setTimeout(() => {
+        this.isFirstLoad || this.videoContext.play() // 当滑动的时候自动播放
+        // const query = uni.createSelectorQuery().in(this);
+        // let videoDom=query.select('#videocurrent');
+        // console.log('视频播放器:',videoDom.node())
+        // videoDom.node().play();
+      }, 1000)
+      // #endif
+      // #ifdef MP-TOUTIAO | MP-WEIXIN
+      this.videoContext.play()
+      // #endif
+      this.progress = 0
+      this.watched(this.id, this.currentEpisode.id)
+    },
+    // 暂停
+    handlePause(item, isAll = false) {
+      if (!this.isPlaying) return
+      item.isPlaying = false
+      this.isPlaying = false
+      // 展厅其他的
+      const indexArr = ['prev', 'current', 'next']
+      const swiperKey = indexArr[this.swiperCurrent]
+      indexArr.forEach(obj => {
+        if (swiperKey !== obj || isAll) {
+          const videoContext = uni.createVideoContext(`video${obj}`, this)
+          videoContext.pause()
+        }
+      })
+    },
+    // 选择剧集
+    handleSelectEpisode(index) {
+      // 暂停上一个
+      this.handlePause(this.currentEpisode, true)
+
+      const item = this.lists[index]
+
+      // 重置SwiperEpisode数据 切换播放
+      this.swiperCurrent = 1
+      this.initSwiperEpisode(item.id)
+    },
+    // 当前剧集购买记录
+    async getBuyRecord() {
+      await this.$api.user.episode.buyRecord(this.id).then(res => {
+        this.buyRecord = res.data
+      })
+    },
+    // 购买剧集
+    async handleBuy() {
+      await this.$api.user.episode.buyHandle(this.id, this.currentEpisode.id).then(async res => {
+        this.$hideLoading()
+        if (typeof res.data.overage !== 'undefined') {
+          this.rechargeShow = true
+        } else {
+          this.$u.toast('购买成功')
+          await this.getBuyRecord()
+          this.handlePlay(this.currentEpisode)
+          this.$api.user.info().then(res => {
+            this.$store.dispatch('user/info', res.data)
+          })
+        }
+      }).catch(() => {
+        this.$hideLoading()
+      })
+    },
+    // 滚动 Swiper
+    handleSwiperChancge({
+      detail
+    }) {
+      this.isFirstLoad = false
+      // this.swiperCurrent = detail.current
+    },
+    handleSwiperAnimationFinish({ detail }) {
+      this.swiperCurrent = detail.current // 注释掉尝试-------》
+    },
+    // 播放前检查剧集是否购买/免费
+    checkBeforePlay(item) {
+      // 剧集免费 不免费已购买 VIP观看是VIP
+      if (item.is_free) {
+        return true
+      }
+
+      if (!item.is_free && this.buyRecord.indexOf(item.id) !== -1) {
+        return true
+      }
+
+      if (this.episode.is_vip_watch && this.userInfo.info.is_vip) {
+        return true
+      }
+
+      return false
+    },
+    // 检查余额是否足够支付
+    checkOverage(item) {
+      return this.userInfo.info.integral >= item.sale_price
+    },
+    // 记录观看记录
+    watched(id, list_id) {
+      this.$api.user.episode.watched(id, list_id).then(res => {
+
+      })
+    },
+    // 分享
+    handleShared(id) {
+      console.log('-->handleShared success')
+      this.$api.episode.shared(id).then(res => {
+        this.episode.share_count += 1
+      })
+    },
+    handleCollectAndFavChange(data) {
+      if (data.type === 'collect') {
+        this.episode.user_collect_count += data.num
+      } else {
+        // this.episode.user_favorite_count += data.num
+      }
+    },
+    // // 初始化 Swiper 剧集
+    initSwiperEpisode(listId) {
+      let currentIndex = 0
+      if (listId) {
+        currentIndex = this.lists.findIndex(obj => {
+          return parseInt(listId) === parseInt(obj.id)
+        })
+      }
+      let prevIndex = currentIndex - 1
+      let nextIndex = currentIndex + 1
+      const len = this.lists.length
+      if (parseInt(listId) === 0 || prevIndex < 0) {
+        prevIndex = len - 1
+      }
+      //
+      if (nextIndex >= len) {
+        nextIndex = 0
+      }
+
+      this.swiperEpisode = {
+        prev: this.lists[prevIndex],
+        current: this.lists[currentIndex],
+        next: this.lists[nextIndex]
+      }
+      console.log('-->swiper data', JSON.stringify(this.swiperEpisode))
+
+      this.currentEpisode = this.lists[currentIndex]
+      console.log('-->currentEpisode', this.currentEpisode)
+      this.$nextTick(() => {
+        this.handlePlay(this.currentEpisode)
+      })
+    },
+    // 获取剧集详情
+    getEpisode() {
+      this.loading = true
+      this.$api.episode.detail(this.id).then(res => {
+        this.loading = false
+        this.episode = res.data
+        uni.setNavigationBarTitle({
+          title: this.episode.name + (this.episode.status === 0 ? ' | 更新中' : '已完结')
+        })
+        this.episode.lists.forEach((obj, index) => {
+          obj.isPlaying = false
+          obj.index = index
+          obj.progress = 0
+          obj.src = obj.url
+        })
+        this.lists = this.episode.lists
+        // 初始化 Swiper 剧集
+        this.initSwiperEpisode(this.listId)
+      })
+    }
+  },
+  async onLoad(options) {
+    this.id = options.id
+    this.listId = options?.list_id
+    this.listId = this.listId ? this.listId : 0
+    await this.getBuyRecord()
+    this.getEpisode()
+  },
+  // 分享
+  onShareAppMessage(res) {
+    if (res.from === 'button') { // 来自页面内分享按钮
+      console.log(res.target)
+    }
+    let options = {
+      title: '',
+      path: `/pages/episode/play?id=${this.id}&user_id=${this.userInfo.user_id}`
+    }
+    if (this.episode) {
+      // 没有success 回调 只要点击分享了就默认成功
+      this.handleShared(this.id)
+      options = {
+        title: this.episode.name,
+        path: `/pages/episode/play?id=${this.id}&user_id=${this.userInfo.user_id}`,
+        imageUrl: this.episode.cover_img,
+        desc: this.episode.name
+      }
+    }
+    return options
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .play-container {
+    font-size: 28rpx;
+
+    .video-box {
+      position: fixed;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+
+      .play-layer {
+        position: fixed;
+        top: 0;
+        left: 0;
+        // #ifdef  MP-KUAISHOU | MP-WEIXIN
+        bottom: 240rpx;
+        // #endif
+        // #ifdef  MP-TOUTIAO
+        bottom: 360rpx;
+        // #endif
+        right: 0;
+        background: transparent;
+        z-index: 999;
+
+        .icon {
+          position: absolute;
+          top: 50%;
+          transform: translate(2px, 63%);
+        }
+      }
+
+      .pause-layer {
+        position: absolute;
+        top: 0;
+        left: 0;
+        // #ifdef  MP-KUAISHOU | MP-WEIXIN
+        bottom: 240rpx;
+        // #endif
+        // #ifdef  MP-TOUTIAO
+        bottom: 360rpx;
+        // #endif
+        right: 0;
+        background: transparent;
+        z-index: 99;
+      }
+
+      .swiper {
+        width: 100%;
+        height: 100vh;
+        position: relative;
+        z-index: 99;
+
+        .swiper-item {
+          width: 100%;
+          height: 100vh !important;
+        }
+      }
+
+      .progress-container {
+        width: 93vw;
+        background: #fff;
+        height: 10rpx;
+        position: fixed;
+        z-index: 100;
+        bottom: 160rpx;
+
+        .progress {
+          height: 10rpx;
+          background: linear-gradient(270deg, #6EEBE8 0%, #FF74B9 100%);
+        }
+      }
+    }
+  }
+</style>

+ 131 - 0
mini/pages/index/components/EpisodeBox.vue

xqd
@@ -0,0 +1,131 @@
+<template>
+  <view class="episode-box">
+    <view class="header-box main-between cross-center">
+      <text class="title">{{ title }}</text>
+      <view
+        v-if="refreshType.indexOf(type) !== -1"
+        class="refresh main-left cross-center"
+        @click="handleRefresh"
+      >
+        <text>换一批</text>
+        <view class="icon">
+          <u-icon name="reload" size="32rpx" bold />
+        </view>
+      </view>
+    </view>
+    <view
+      class="container dir-left-wrap"
+      :class="{
+        loading: loading,
+        'main-center':loading,
+        'cross-center': loading
+      }"
+    >
+      <u-loading-icon :show="loading" vertical />
+      <template v-if="episodes.length && !loading">
+        <episode
+          v-for="(episode,index) in episodes"
+          :key="index"
+          :episode="episode"
+          :guess="episode.guess"
+          :recent="episode.recent"
+          :rank="episode.rank"
+          :custom-style="{
+            marginRight: ((index+1) % 3 !== 0 ? '20rpx' :''),
+          }"
+        />
+      </template>
+    </view>
+  </view>
+</template>
+
+<script>
+import Episode from '../../../components/Episode/index'
+export default {
+  name: 'EpisodeBox',
+  components: { Episode },
+  props: {
+    title: {
+      type: String,
+      required: true
+    },
+    type: {
+      type: String,
+      required: true
+    }
+  },
+  data() {
+    return {
+      loading: false,
+      refreshType: ['recommend'],
+      episodes: []
+    }
+  },
+  computed: {},
+  created() {
+    this.handleRefresh()
+  },
+  methods: {
+    getRecommend() {
+      this.loading = true
+      this.$api.episode.recommend().then(res => {
+        this.loading = false
+        this.episodes = res.data
+      })
+    },
+    getTodayRecommend() {
+      this.loading = true
+      this.$api.episode.todayRecommend().then(res => {
+        this.loading = false
+        this.episodes = res.data
+      })
+    },
+    getNews() {
+      this.loading = true
+      this.$api.episode.news().then(res => {
+        this.loading = false
+        this.episodes = res.data
+      })
+    },
+    getRank() {
+      this.loading = true
+      this.$api.episode.rank().then(res => {
+        this.loading = false
+        this.episodes = res.data
+      })
+    },
+    handleRefresh() {
+      this.type === 'recommend' && this.getRecommend()
+      this.type === 'todayRecommend' && this.getTodayRecommend()
+      this.type === 'news' && this.getNews()
+      this.type === 'rank' && this.getRank()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+    .episode-box {
+      margin-top: 30rpx;
+      .header-box{
+        .title{
+          font-size: 32rpx;
+          color: #ffffff;
+        }
+        .refresh{
+          text{
+            color: $info-color;
+            font-size: 24rpx;
+            line-height: 22rpx;
+            margin-right: 4rpx;
+          }
+        }
+      }
+      .container{
+        margin-top: 30rpx;
+        min-height: 458rpx;
+        &.loading{
+        }
+      }
+    }
+</style>

+ 123 - 0
mini/pages/index/components/Recent.vue

xqd
@@ -0,0 +1,123 @@
+<template>
+  <view v-if="show" class="recent-container main-between cross-center">
+    <view class="close" @click="handleClose">
+      <u-icon name="close-circle" :color="$colors.primaryColor" size="54rpx" />
+    </view>
+    <view class="cover-image">
+      <image :src="recent.detail.episode.cover_img" mode="aspectFill" />
+    </view>
+    <view class="info">
+      <text class="name">{{ recent.detail.episode.name }}</text>
+      <view class="status-box">
+        <text class="status">{{ recent.detail.episode.status_text }}</text>
+        <text>共{{ recent.detail.episode.total }}集</text>
+      </view>
+      <view class="recent">上次观看至 <text>第{{ recent.detail.sort }}集</text></view>
+    </view>
+    <view class="play-btn" @click="handlePlay(recent)">继续观看</view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'Recent',
+  data() {
+    return {
+      show: false,
+      recent: []
+    }
+  },
+  created() {
+    this.getRecent()
+  },
+  methods: {
+    handlePlay(item) {
+      this.show = false
+      this.$u.route({
+        url: '/pages/episode/play',
+        params: {
+          id: item.episode_id,
+          list_id: item.list_id
+        }
+      })
+    },
+    getRecent() {
+      this.$api.user.episode.recent().then(res => {
+        if (res.data) {
+          this.recent = res.data
+          setTimeout(() => {
+            this.show = true
+          }, 500)
+        }
+      })
+    },
+    handleClose() {
+      this.show = false
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+    .recent-container {
+      position: fixed;
+      bottom: 140rpx;
+      width: 96vw;
+      left: 50%;
+      transform: translateX(-50%);
+      border: 1rpx solid $primary-color;
+      height: 300rpx;
+      border-radius: 20rpx;
+      background: $bg-trans-color;
+      padding: 20rpx 26rpx;
+      font-size: 32rpx;
+      .close{
+        position: absolute;
+        top: -70rpx;
+        right: 0;
+      }
+      .cover-image{
+        image{
+          width: 200rpx;
+          height: 240rpx;
+        }
+      }
+      .info{
+        flex: 1;
+        margin-left: 20rpx;
+        color: #fff;
+        .name{
+          font-weight: 800;
+          font-size: 34rpx;
+        }
+        .status-box{
+          margin-bottom: 10rpx;
+          font-size: 24rpx;
+          margin-top: 60rpx;
+          .status{
+            color: $primary-color;
+            margin-right: 14rpx;
+          }
+        }
+        .recent{
+          color: $info-color;
+          font-size: 24rpx;
+          text{
+            color: #FB3651;
+            margin-left: 8rpx;
+          }
+        }
+      }
+      .play-btn{
+        width: 180rpx;
+        border-radius: 30rpx;
+        padding: 20rpx 0;
+        background: linear-gradient(270deg, #6EEBE8 0%, #FF74B9 100%);
+        text-align: center;
+        flex-shrink: 0;
+        font-size: 32rpx;
+        color: $default-color;
+
+      }
+    }
+</style>

+ 154 - 0
mini/pages/index/index.vue

xqd
@@ -0,0 +1,154 @@
+<template>
+  <view class="container">
+    <check-login />
+    <template v-if="isLogin">
+      <!--search-->
+      <view class="search-box main-center cross-center">
+        <u-input
+          v-model="keywords"
+          placeholder="请输入剧名"
+          color="#fff"
+          border="none"
+          prefix-icon="search"
+          :prefix-icon-style="{
+            color: $colors.primaryColor,
+            fontSize: '50rpx',
+            fontWeight: 500,
+            lineHeight: '50rpx'
+          }"
+          :custom-style="{
+            backgroundColor: $colors.bgOpColor,
+            padding: '14rpx 30rpx',
+            width: '700rpx',
+            borderRadius: '36rpx',
+            color: '#fff'
+          }"
+          @confirm="handleSearch"
+        />
+      </view>
+      <!--swpier-->
+      <swiper-box />
+      <!--栏目分类-->
+      <nav-bar />
+      <!--分类-->
+      <episode-box
+        v-for="(item,index) in homeColumn"
+        :key="index"
+        :title="item.name"
+        :type="item.type"
+      />
+      <!--tab bar-->
+      <tab-bar />
+      <!--最近播放-->
+      <recent />
+    </template>
+  </view>
+</template>
+
+<script>
+import SwiperBox from '../../components/SwiperBox/index'
+import EpisodeBox from './components/EpisodeBox'
+import TabBar from '../../components/TabBar/index'
+import NavBar from '../../components/NavBar/index'
+import Recent from './components/Recent'
+import CheckLogin from '../../components/CheckLogin/index'
+import { mapState } from 'vuex'
+import Cache from '../../utils/cache'
+export default {
+  components: { CheckLogin, Recent, NavBar, TabBar, EpisodeBox, SwiperBox },
+  data() {
+    return {
+      keywords: '',
+      homeColumnType: {
+        1: 'recommend',
+        2: 'todayRecommend',
+        3: 'news',
+        4: 'rank'
+      },
+      homeColumn: [],
+      parent_id: 0,
+      scene_code: ''
+    }
+  },
+  computed: {
+    isLogin() {
+      return this.$api.user.isLogin()
+    },
+    ...mapState({
+      userInfo: seate => seate.user.info
+    })
+  },
+  onLoad(options) {
+    // 直接传递user_id
+    if (typeof options.user_id !== 'undefined' && options.user_id) {
+      Cache.set('parent_id', options.user_id)
+    }
+    // 微信小程序 对应的二维码是 scene_code
+    if (typeof options.scene !== 'undefined' && options.scene) {
+      Cache.set('parent_id', options.scene)
+    }
+    console.log('-->index data', options)
+    this.isLogin && this.getHomeColumn()
+    this.isLogin && this.bindParent()
+  },
+  methods: {
+    handleSearch() {
+      if (!this.keywords) {
+        this.$u.toast('请输入剧集名称')
+        return
+      }
+      this.$u.route({
+        url: '/pages/index/search',
+        params: {
+          keywords: this.keywords
+        }
+      })
+    },
+    getHomeColumn() {
+      this.$api.setting.homeColumn().then(res => {
+        this.homeColumn = res.data
+        this.homeColumn.forEach(obj => {
+          obj.type = this.homeColumnType[obj.type]
+        })
+      })
+    },
+    bindParent() {
+      const parentId = Cache.get('parent_id')
+      if (parentId && !this.userInfo.parent_id) {
+        this.$api.user.bind(parentId).then(res => {
+          console.log('-->bind success')
+          this.$store.dispatch('user/info', res.data)
+          Cache.remove('parent_id')
+        })
+      }
+    }
+  },
+  onShareAppMessage() {
+    return this.$util.shareMessage(this.userInfo)
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .container{
+    padding: 30rpx 20rpx 160px;
+    .search-box{
+      /*margin-top: 30rpx;*/
+    }
+    .category-box{
+      .category-item{
+        flex: 1;
+        font-size: 32rpx;
+        .icon{
+          image{
+            width: 100rpx;
+            height: 100rpx;
+          }
+        }
+        text{
+          color: #fff;
+        }
+      }
+    }
+  }
+</style>

+ 56 - 0
mini/pages/index/news.vue

xqd
@@ -0,0 +1,56 @@
+<template>
+  <view class="free-container dir-left-wrap">
+    <episode
+      v-for="(episode,index) in episodes"
+      :key="index"
+      :episode="episode"
+      :custom-style="{
+        marginRight: ((index+1) % 3 !== 0 ? '20rpx' :''),
+      }"
+    />
+  </view>
+</template>
+
+<script>
+import Episode from '../../components/Episode/index'
+export default {
+  name: 'Free',
+  components: { Episode },
+  data() {
+    return {
+      limit: 30,
+      page: 1,
+      isMore: true,
+      episodes: []
+    }
+  },
+  computed: {},
+  methods: {
+    getNews() {
+      this.loading = true
+      this.$api.episode.news({ limit: this.limit, page: this.page }).then(res => {
+        this.loading = false
+        if (res.data.length) {
+          this.episodes = this.episodes.concat(res.data)
+        } else {
+          this.isMore = false
+        }
+      })
+    }
+  },
+  onLoad() {
+    this.getNews()
+  },
+  onReachBottom(e) {
+    if (!this.isMore) return
+    this.page += 1
+    this.getNews()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .free-container {
+    padding: 20rpx;
+  }
+</style>

+ 57 - 0
mini/pages/index/rank.vue

xqd
@@ -0,0 +1,57 @@
+<template>
+  <view class="free-container dir-left-wrap">
+    <episode
+      v-for="(episode,index) in episodes"
+      :key="index"
+      :episode="episode"
+      :rank="episode.rank"
+      :custom-style="{
+        marginRight: ((index+1) % 3 !== 0 ? '20rpx' :''),
+      }"
+    />
+  </view>
+</template>
+
+<script>
+import Episode from '../../components/Episode/index'
+export default {
+  name: 'Free',
+  components: { Episode },
+  data() {
+    return {
+      limit: 12,
+      page: 1,
+      isMore: true,
+      episodes: []
+    }
+  },
+  computed: {},
+  methods: {
+    getRank() {
+      this.loading = true
+      this.$api.episode.rank({ limit: this.limit, page: this.page }).then(res => {
+        this.loading = false
+        if (res.data.length) {
+          this.episodes = this.episodes.concat(res.data)
+        } else {
+          this.isMore = false
+        }
+      })
+    }
+  },
+  onLoad() {
+    this.getRank()
+  },
+  onReachBottom(e) {
+    if (!this.isMore) return
+    // this.page += 1
+    // this.getRank()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .free-container {
+    padding: 20rpx;
+  }
+</style>

+ 58 - 0
mini/pages/index/search.vue

xqd
@@ -0,0 +1,58 @@
+<template>
+  <view class="free-container dir-left-wrap">
+    <episode
+      v-for="(episode,index) in episodes"
+      :key="index"
+      :episode="episode"
+      :custom-style="{
+        marginRight: ((index+1) % 3 !== 0 ? '20rpx' :''),
+      }"
+    />
+  </view>
+</template>
+
+<script>
+import Episode from '../../components/Episode/index'
+export default {
+  name: 'Free',
+  components: { Episode },
+  data() {
+    return {
+      limit: 30,
+      page: 1,
+      isMore: true,
+      keywords: '',
+      episodes: []
+    }
+  },
+  computed: {},
+  methods: {
+    getSearch() {
+      this.loading = true
+      this.$api.episode.search({ limit: this.limit, page: this.page, keywords: this.keywords }).then(res => {
+        this.loading = false
+        if (res.data.length) {
+          this.episodes = this.episodes.concat(res.data)
+        } else {
+          this.isMore = false
+        }
+      })
+    }
+  },
+  onLoad(options) {
+    this.keywords = options?.keywords
+    this.getSearch()
+  },
+  onReachBottom(e) {
+    if (!this.isMore) return
+    this.page += 1
+    this.getSearch()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .free-container {
+    padding: 20rpx;
+  }
+</style>

+ 99 - 0
mini/pages/login.vue

xqd
@@ -0,0 +1,99 @@
+<template>
+  <view class="login">
+    <u-loading-page
+      :loading="true"
+      loading-text="登陆中..."
+      :bg-color="$colors.bgColor"
+      color="#ffffff"
+    />
+  </view>
+</template>
+
+<script>
+import Cache from '../utils/cache'
+
+export default {
+  components: {},
+
+  data() {
+    return {
+      path: '/pages/index/index',
+      query: {}
+    }
+  },
+  computed: {},
+  methods: {
+    login() {
+      // this.$loading('登陆中...')
+      this.$api.user.login().then(async res => {
+        this.$hideLoading()
+        const { token, user_info } = res.data
+        await this.$store.dispatch('user/token', token)
+        await this.$store.dispatch('user/info', user_info)
+        // 绑定上级
+        const parentId = Cache.get('parent_id')
+        if (parentId && !user_info.info.parent_id) {
+          this.$api.user.bind(parentId).then(res => {
+            console.log('-->bind parent success')
+            this.$store.dispatch('user/info', res.data)
+            Cache.remove('parent_id')
+          })
+        }
+        this.relaunch()
+      }).catch(async() => {
+        await this.$store.dispatch('user/clear')
+        this.relaunch()
+      })
+    },
+    relaunch() {
+      console.log('-->reLaunch path', this.path)
+      uni.reLaunch({
+        url: this.path,
+        success() {
+          console.log('-->reLaunch success')
+        },
+        fail(err) {
+          uni.reLaunch({
+            url: '/pages/index/index'
+          })
+          console.log('-->reLaunch error', err)
+        }
+      })
+    }
+  },
+  onLoad(options) {
+    console.log('-->data', options)
+    if (this.$api.user.isLogin()) {
+      uni.reLaunch({
+        url: '/pages/index/index'
+      })
+      return
+    }
+    this.path = options.path ? options.path : this.path
+    this.path = this.path.replace('//', '/')
+    this.path = this.path.indexOf('/') !== 0 ? '/' + this.path : this.path
+    const query = options.query ? JSON.parse(decodeURI(options.query)) : {}
+    const queryArr = []
+    for (const queryKey in query) {
+      queryArr.push(`${queryKey}=${query[queryKey]}`)
+    }
+
+    this.path = this.path + '?' + queryArr.join('&')
+
+    this.login()
+  },
+  onShow() {
+    if (this.$api.user.isLogin()) {
+      // uni.reLaunch({
+      //   url: '/pages/index/index'
+      // })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+    .login{
+
+    }
+</style>

+ 56 - 0
mini/pages/member/free.vue

xqd
@@ -0,0 +1,56 @@
+<template>
+  <view class="free-container dir-left-wrap">
+    <episode
+      v-for="(episode,index) in episodes"
+      :key="index"
+      :episode="episode"
+      :custom-style="{
+        marginRight: ((index+1) % 3 !== 0 ? '20rpx' :''),
+      }"
+    />
+  </view>
+</template>
+
+<script>
+import Episode from '../../components/Episode/index'
+export default {
+  name: 'Free',
+  components: { Episode },
+  data() {
+    return {
+      limit: 30,
+      page: 1,
+      isMore: true,
+      episodes: []
+    }
+  },
+  computed: {},
+  methods: {
+    getFree() {
+      this.loading = true
+      this.$api.episode.vipFree({ limit: this.limit, page: this.page }).then(res => {
+        this.loading = false
+        if (res.data.length) {
+          this.episodes = this.episodes.concat(res.data)
+        } else {
+          this.isMore = false
+        }
+      })
+    }
+  },
+  onLoad() {
+    this.getFree()
+  },
+  onReachBottom(e) {
+    if (!this.isMore) return
+    this.page += 1
+    this.getFree()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .free-container {
+    padding: 20rpx;
+  }
+</style>

+ 310 - 0
mini/pages/member/index.vue

xqd
@@ -0,0 +1,310 @@
+<template>
+  <u-loading-page v-if="loading" :loading="loading" :bg-color="$colors.bgColor" />
+  <view v-else class="member-container">
+    <view class="header dir-top-wrap cross-center">
+      <view class="title">会员充值</view>
+      <text class="tips">会员指定短剧无限观看、xxx 等特权</text>
+    </view>
+    <view class="content main-left">
+      <view
+        v-for="(item,index) in settings"
+        :key="index"
+        class="item dir-top-wrap cross-center main-center"
+        :class="{active: activeIndex === index}"
+        @click="handleSelect(index)"
+      >
+        <view class="border" />
+        <view v-if="activeIndex === index" class="selected">已选择</view>
+        <text class="day">{{ item.valid_day }}天</text>
+        <text class="price">¥{{ item.price }}</text>
+      </view>
+    </view>
+    <view class="free main-center cross-center" @click="$u.route('/pages/member/free')">
+      查看 <text>会员片单</text>
+    </view>
+
+    <view class="footer main-between">
+      <view class="price main-left cross-bottom">
+        <text>共计:</text>
+        <view class="price">¥{{ settings[activeIndex].price }}</view>
+      </view>
+      <view class="buy-btn main-center cross-center" @click="handleBuy">立即支付</view>
+    </view>
+    <!--购买弹窗-->
+    <u-modal
+      :show="modal.show"
+      content="确定购买?"
+      show-cancel-button
+      @confirm="handleBuy"
+      @cancel="modal.show = false"
+    />
+  </view>
+</template>
+
+<script>
+
+import ULoadingPage from '../../uni_modules/uview-ui/components/u-loading-page/u-loading-page'
+export default {
+  components: { ULoadingPage },
+  data() {
+    return {
+      settings: {},
+      activeIndex: 0,
+      payId: null,
+      interval: null,
+      modal: {
+        show: false
+      }
+    }
+  },
+  computed: {
+    loading() {
+      return Object.keys(this.settings).length === 0
+    }
+  },
+  methods: {
+    getSetting() {
+      this.$api.user.vip.setting().then(res => {
+        this.settings = res.data
+      })
+    },
+    handleSelect(index) {
+      this.activeIndex = index
+      // this.modal.show = true
+    },
+    handleBuy() {
+      // #ifdef MP-TOUTIAO
+      if (!this.$util.checkOS()) return
+      // #endif
+      const item = this.settings[this.activeIndex]
+      this.$loading('购买中...')
+      this.$api.user.vip.create({ id: item.id }).then(res => {
+        this.$hideLoading()
+        this.payId = res.pay_id
+        delete res.pay_id
+        this.modal.show = false
+        // #ifdef MP-TOUTIAO
+        tt.pay({
+          service: 5,
+          orderInfo: {
+            order_id: res.data.order_id,
+            order_token: res.data.order_token
+          },
+          success: payRes => {
+            if (payRes.code === 0) {
+              this.$loading('支付结果查询中...')
+              this.query()
+            } else {
+              this.$u.toast('支付失败')
+            }
+          },
+          fail: err => {
+            console.log('-->data', err)
+            // 调起收银台失败处理逻辑
+          }
+        })
+        // #endif
+        // #ifdef MP-KUAISHOU
+        ks.pay({
+          serviceId: '1',
+          orderInfo: {
+            order_no: res.data.order_id,
+            order_info_token: res.data.order_token
+          },
+          success: payRes => {
+            this.$loading('支付结果查询中...')
+            this.query()
+          },
+          fail: err => {
+            console.log('-->data', err)
+            // 调起收银台失败处理逻辑
+          }
+        })
+        // #endif
+        // #ifdef MP-WEIXIN
+        uni.requestPayment({
+          ...res.data,
+          provider: 'wxpay',
+          success: res => {
+            console.log('success:' + JSON.stringify(res))
+            // _this.$u.toast("支付成功")
+            this.$loading('支付结果查询中...')
+            this.query()
+          },
+          fail: err => {
+            console.log('fail:' + JSON.stringify(err))
+            // _this.$u.toast("支付失败")
+            clearInterval(this.interval)
+          }
+        })
+        // #endif
+      }).catch(() => {
+        this.$hideLoading()
+      })
+    },
+    query() {
+      if (this.interval) return
+      this.interval = setInterval(() => {
+        this.$api.pay.query(this.payId).then(res => {
+          this.$hideLoading()
+          this.$u.toast('支付成功')
+          clearInterval(this.interval)
+          // 获取用户信息
+          this.$api.user.info().then(res => {
+            this.$store.dispatch('user/info', res.data)
+          })
+        }).catch(err => {
+
+        })
+      }, 1000)
+    }
+  },
+  onLoad() {
+    this.getSetting()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+    .member-container{
+      padding: 20rpx 0;
+      font-size: 30rpx;
+      .header{
+        color: $primary-color;
+        margin-top: 50rpx;
+        .title{
+          position: relative;
+          font-size: 42rpx;
+          font-weight: 600;
+          width: 100%;
+          text-align: center;
+          &:before,&:after{
+            content: "";
+            background: url("/static/image/member-line-bg.png") no-repeat;
+            background-size: 100%;
+            position: absolute;
+            top: 50%;
+            left: 60rpx;
+            width: 200rpx;
+            height: 20rpx;
+          }
+          &:after{
+            transform: rotate(180deg);
+            right: 60rpx;
+            left: unset;
+            top: 20%;
+          }
+        }
+        .tips{
+          color: $dark-color;
+          font-size: 26rpx;
+          margin-top: 20rpx;
+        }
+      }
+      .content{
+        padding: 0 20rpx;
+        margin-top: 80rpx;
+        .item{
+          color: $default-color;
+          margin-left: 20rpx;
+          flex: 1;
+          height: 300rpx;
+          position: relative;
+          .border{
+            position: absolute;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            z-index: 0;
+            border: 4rpx solid $info-color;
+            border-radius: 10rpx;
+            overflow: hidden;
+          }
+          &.active .border{
+            border: unset;
+            &:after{
+              content: "";
+              position: absolute;
+              top: 0;
+              left: 0;
+              right: 0;
+              bottom: 0;
+              border: 4rpx solid;
+              border-image: linear-gradient(222deg, #6EEBE8, #FF74B9) 1;
+              z-index: 0;
+            }
+          }
+          .selected{
+            position: absolute;
+            top: -20rpx;
+            background: url("/static/image/member-selected-bg.png") no-repeat;
+            background-size: 100%;
+            text-align: center;
+            width: 120rpx;
+            height: 40rpx;
+            font-size: 24rpx;
+            line-height: 40rpx;
+            left: 20rpx;
+            z-index: 1;
+          }
+          &:first-child{
+            margin-left: 0;
+          }
+          .day{
+            font-size: 32rpx;
+            margin-bottom: 80rpx;
+          }
+          .price{
+            font-size: 38rpx;
+            color: $primary-color;
+          }
+        }
+      }
+      .free{
+        background: #1B1E32;
+        width: 100%;
+        padding: 20rpx 0;
+        color: $info-color;
+        text-align: center;
+        font-size: 42rpx;
+        margin-top: 60px;
+        font-weight: bold;
+        text{
+          color: $dark-color;
+        }
+      }
+
+      .footer{
+        position: fixed;
+        bottom: 60rpx;
+        left: 0;
+        right: 0;
+        padding: 0 30rpx;
+        .price{
+          text{
+            color: #fff;
+          }
+          .price{
+            color: $primary-color;
+            font-size: 48rpx;
+            margin-left: 10rpx;
+            font-weight: 500;
+            line-height: 1;
+            &:first-letter{
+              font-size: 30rpx;
+              font-weight: normal;
+            }
+          }
+        }
+        .buy-btn{
+          background: linear-gradient(270deg, #6EEBE8 0%, #FF74B9 100%);
+          border-radius: 42rpx;
+          width: 400rpx;
+          font-weight: 600;
+          color: #151728;
+          padding: 18rpx 0;
+        }
+      }
+    }
+</style>

+ 168 - 0
mini/pages/my/consume.vue

xqd
@@ -0,0 +1,168 @@
+<template>
+  <view class="consume">
+    <view class="header main-left cross-center" @click="datePicker.show = true">
+      <picker
+        mode="date"
+        fields="month"
+        :value="date"
+        @change="handleDateConfirm"
+      >
+        <text>{{ date }}</text>
+      </picker>
+      <view class="icon" :class="{rotate: datePicker.show}">
+        <u-icon name="arrow-down" />
+      </view>
+    </view>
+    <view class="content">
+      <view
+        v-for="(item,index) in consume"
+        :key="index"
+        class="consume-item dir-left-nowrap cross-center"
+      >
+        <view class="cover-image">
+          <image :src="item.detail.episode.cover_img" />
+        </view>
+        <view class="episode-box dir-top-wrap ">
+          <view class="name">{{ item.detail.episode.name }}</view>
+          <view class="status-box main-left cross-center">
+            <text class="status">{{ item.detail.episode.status_text }}</text>
+            <text>共{{ item.detail.episode.total }}集</text>
+          </view>
+          <view class="detail">{{ item.detail.sort }}集</view>
+          <text class="time">{{ item.created_at }}</text>
+        </view>
+        <view class="gold">
+          <text class="number">-{{ item.price }}</text>
+          <text>金币</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      limit: 10,
+      page: 1,
+      isMore: true,
+      consume: [],
+      datePicker: {
+        date: uni.$u.timeFormat(Number(new Date()), 'yyyy-mm'),
+        show: false
+      }
+    }
+  },
+  computed: {
+    date() {
+      const date = this.datePicker.date
+      const arr = date.split('-')
+      return `${arr[0]}年${arr[1]}月`
+    }
+  },
+  methods: {
+    handlePlay(detail) {
+      this.$u.route({
+        url: '/pages/episode/play',
+        params: {
+          id: detail.episode.id,
+          list_id: detail.id
+        }
+      })
+    },
+    handleDateConfirm({ detail }) {
+      this.datePicker.date = detail.value
+      this.datePicker.show = false
+      this.consume = []
+      this.page = 1
+      this.isMore = true
+      this.getConsume()
+    },
+    getConsume() {
+      this.$loading()
+      this.$api.user.consume.record({ date: this.datePicker.date, limit: this.limit, page: this.page }).then(res => {
+        this.$hideLoading()
+        if (res.data.length) {
+          this.consume = this.consume.concat(res.data)
+        } else {
+          this.isMore = false
+        }
+      }).catch(err => {
+        this.$hideLoading()
+        console.error('-->error', err)
+      })
+    }
+  },
+  onLoad() {
+    this.getConsume()
+  },
+  onReachBottom(e) {
+    if (!this.isMore) return
+    this.page += 1
+    this.getConsume()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+    .consume {
+      padding: 20rpx 20rpx;
+      font-size: 30rpx;
+      .header{
+        color: $info-color;
+        text{
+          margin-right: 20rpx;
+          font-size: 32rpx;
+        }
+        .icon{
+          transition: .3s;
+          &.rotate{
+            transform: rotate(-180deg);
+          }
+        }
+      }
+      .content{
+        .consume-item{
+          color: $info-color;
+          margin-top: 20rpx;
+          .cover-image{
+            image{
+              width: 200rpx;
+              height: 260rpx;
+              border-radius: 16rpx;
+            }
+          }
+          .episode-box{
+            flex: 1;
+            margin-left: 20rpx;
+            font-size: 26rpx;
+            .name{
+              color: $default-color;
+              font-size: 38rpx;
+              font-weight: 600;
+              margin-bottom: 10rpx;
+            }
+            .status-box{
+              margin-bottom: 50rpx;
+              .status{
+                color: $primary-color;
+                margin-right: 14rpx;
+              }
+            }
+            .detail{
+              font-size: 32rpx;
+              color: $default-color;
+              margin-bottom: 10rpx;
+            }
+          }
+          .gold{
+            .number{
+              color: $pink-color;
+              font-size: 42rpx;
+            }
+          }
+        }
+      }
+    }
+</style>

+ 168 - 0
mini/pages/my/history.vue

xqd
@@ -0,0 +1,168 @@
+<template>
+  <view class="history">
+    <view
+      v-for="(item,index) in history"
+      :key="index"
+      class="item main-left"
+    >
+      <view class="cover-image">
+        <image :src="item.detail.episode.cover_img" />
+      </view>
+      <view class="right-box">
+        <view class="op-group main-right cross-center">
+          <view class="delete" @click="handleModal(item.id)">删除</view>
+          <view class="play" @click="handlePlay(item.detail)">播放</view>
+        </view>
+        <view class="name">{{ item.detail.episode.name }}</view>
+        <view class="status-box">
+          <text class="status">{{ item.detail.episode.status_text }}</text>
+          <text>共{{ item.detail.episode.total }}集</text>
+        </view>
+        <view class="record">
+          上次看至 <text>第{{ item.detail.sort }}集</text>
+        </view>
+      </view>
+    </view>
+    <u-modal
+      title="确定删除"
+      :show="modal.show"
+      :show-cancel-button="true"
+      @confirm="handleDelete"
+      @cancel="modal.show = false"
+    />
+  </view>
+</template>
+
+<script>
+import UModal from '../../uni_modules/uview-ui/components/u-modal/u-modal'
+export default {
+  components: { UModal },
+  data() {
+    return {
+      limit: 10,
+      page: 1,
+      isMore: true,
+      history: [],
+      modal: {
+        show: false,
+        id: null
+      }
+    }
+  },
+  computed: {},
+  methods: {
+    handlePlay(detail) {
+      this.$u.route({
+        url: '/pages/episode/play',
+        params: {
+          id: detail.episode.id,
+          list_id: detail.id
+        }
+      })
+    },
+    handleModal(id) {
+      this.modal.id = id
+      this.modal.show = true
+    },
+    handleDelete() {
+      this.$loading('删除中...')
+      this.$api.user.episode.deleteRecord(this.modal.id).then(res => {
+        this.$hideLoading()
+        this.page = 1
+        this.isMore = true
+        this.history = []
+        this.getHistory()
+        this.modal.show = false
+      }).catch(err => {
+        this.$hideLoading()
+      })
+    },
+    getHistory() {
+      this.$loading()
+      this.$api.user.episode.record({ limit: this.limit, page: this.page }).then(res => {
+        this.$hideLoading()
+        if (res.data.length) {
+          this.history = this.history.concat(res.data)
+        } else {
+          this.isMore = false
+        }
+      }).catch(err => {
+        this.$hideLoading()
+        console.error('-->error', err)
+      })
+    }
+  },
+  onLoad() {
+    this.getHistory()
+  },
+  onReachBottom(e) {
+    if (!this.isMore) return
+    this.page += 1
+    this.getHistory()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+    .history{
+      padding: 20rpx;
+      color: $default-color ;
+      font-size: 30rpx;
+      .item{
+        padding: 30rpx;
+        border: 1rpx solid $primary-color;
+        border-radius: 30rpx;
+        margin-top: 30rpx;
+        .cover-image{
+          image{
+            width: 200rpx;
+            height: 280rpx;
+            border-radius: 16rpx;
+          }
+        }
+        .right-box{
+          flex: 1;
+          margin-left: 20rpx;
+          .op-group{
+            .delete{
+              border: 1rpx solid #FB3651;
+              color: #FB3651;
+              border-radius: 30rpx;
+              width: 140rpx;
+              padding: 10rpx;
+              text-align: center;
+              margin-right: 30rpx;
+            }
+            .play{
+              background: linear-gradient(90deg, #FF74B9,#6EEBE8);
+              color: $default-color;
+              border-radius: 30rpx;
+              width: 140rpx;
+              padding: 10rpx;
+              text-align: center;
+            }
+          }
+          .name{
+            font-size: 38rpx;
+            font-weight: 600;
+            margin-bottom: 10rpx;
+            margin-top: 50rpx;
+          }
+          .status-box{
+            font-size: 28rpx;
+            margin-bottom: 10rpx;
+            color: $info-color;
+            .status{
+              color: $primary-color;
+              margin-right: 14rpx;
+            }
+          }
+          .record{
+            text{
+              color: #FB3651;
+            }
+          }
+        }
+      }
+    }
+</style>

+ 497 - 0
mini/pages/my/index.vue

xqd
@@ -0,0 +1,497 @@
+<template>
+  <view class="container">
+    <!--用户信息-->
+    <view class="header main-between cross-center">
+      <view class="user-box main-left cross-center">
+        <view class="head-img">
+          <!--   #ifdef  MP-KUAISHOU | MP-TOUTIAO   -->
+          <image :src="userInfo.avatar?userInfo.avatar:'/static/image/default-head-img.png'" />
+          <!--   #endif  -->
+          <!--   #ifdef  MP-WEIXIN   -->
+          <button class="avatar cross-center" @click="handleGetWechatUserInfo">
+            <image :src="userInfo.avatar?userInfo.avatar:'/static/image/default-head-img.png'" />
+          </button>
+          <!--   #endif  -->
+        </view>
+        <view class="base">
+          <!--   #ifdef  MP-KUAISHOU | MP-TOUTIAO   -->
+          <view v-if="!userInfo.nickname" class="nickname cross-center" @click="handleGetUserInfo">未授权登陆</view>
+          <!--   #endif  -->
+          <!--   #ifdef  MP-WEIXIN   -->
+          <view v-if="!userInfo.nickname" class="nickname cross-center" @click="handleGetWechatUserInfo">未授权登陆</view>
+          <!--   #endif  -->
+          <view v-if="userInfo.nickname" class="nickname cross-center">{{ userInfo.nickname }}</view>
+          <view v-if="userInfo.info.is_vip" class="vip cross-center">
+            VIP到期时间: {{ userInfo.info.end_at }}
+          </view>
+        </view>
+      </view>
+      <!--   #ifdef  MP-KUAISHOU | MP-TOUTIAO   -->
+      <button open-type="getUserInfo" class="refresh" @click="handleGetUserInfo">刷新</button>
+      <!--   #endif  -->
+      <!--   #ifdef  MP-WEIXIN   -->
+      <button open-type="getUserInfo" class="refresh" @click="handleGetWechatUserInfo">刷新</button>
+      <!--   #endif  -->
+    </view>
+    <!--充值-->
+    <view class="recharge main-between cross-center">
+      <view class="static-box main-left cross-center">
+        <view class="icon">
+          <image src="/static/image/gold-bag.png" mode="aspectFill" />
+        </view>
+        <view class="overage">{{ userInfo.info.integral }}金币</view>
+      </view>
+      <view class="recharge-btn" @click="recharge.show = true">充值</view>
+    </view>
+    <!--历史-->
+    <view class="history" @click="$u.route('/pages/my/history')">
+      <view class="header main-between cross-center">
+        <text>历史观看记录</text>
+        <u-icon name="arrow-right" :color="$colors.infoColor" bold />
+      </view>
+      <view class="content dir-left-nowrap cross-center">
+        <view
+          v-for="(item,index) in history"
+          :key="index"
+          class="episode"
+        >
+          <view class="cover-image">
+            <image :src="item.detail.episode.cover_img" mode="aspectFill" />
+          </view>
+        </view>
+      </view>
+    </view>
+    <!--菜单-->
+    <view class="menu-group">
+      <template v-for="(menu,index) in menus">
+        <button
+          v-if="menu.href !== '/pages/share/index' || (menu.href === '/pages/share/index' && userInfo.is_share !== '0')"
+          :key="index"
+          class="menu-item main-between cross-center"
+          :open-type="menu.type ? menu.type : ''"
+          @bindcontact="handleMenu"
+          @click="handleMenu(menu)"
+        >
+          <view class="left-box dir-left-nowrap cross-center">
+            <view class="icon">
+              <image :src="menu.icon" />
+            </view>
+            <text>{{ menu.name }}</text>
+          </view>
+          <u-icon name="arrow-right" :color="$colors.infoColor" bold />
+        </button>
+      </template>
+    </view>
+    <!--tab bar-->
+    <tab-bar />
+    <!--充值-->
+    <recharge :show.sync="recharge.show" />
+    <!--   #ifdef  MP-WEIXIN   -->
+    <u-modal
+      :show="modal.show"
+      :title="modal.title"
+      :show-confirm-button="false"
+    >
+      <view class="slot-content dir-top-wrap cross-center">
+        <button open-type="chooseAvatar" class="avatar cross-center" @chooseavatar="handleChooseAvatar">
+          <image :src="modal.avatar?modal.avatar:'/static/image/default-head-img.png'" />
+        </button>
+        <input
+          class="nickname cross-center"
+          type="nickname"
+          :value="modal.nickname"
+          placeholder="填写昵称"
+          @change="handleChangeNickname"
+        >
+
+        <button class="confirm" @click="handleConfirmWechatUserInfo">提交</button>
+      </view>
+    </u-modal>
+    <!--   #endif  -->
+  </view>
+</template>
+
+<script>
+import TabBar from '../../components/TabBar/index'
+import { mapState } from 'vuex'
+import Recharge from '../../components/Recharge/index'
+export default {
+  name: 'My',
+  components: { Recharge, TabBar },
+  data() {
+    return {
+      history: [],
+      menus: [
+        { icon: '/static/image/my-page/order.png', name: '消费记录', href: '/pages/my/consume' },
+        { icon: '/static/image/my-page/recharge.png', name: '充值记录', href: '/pages/my/recharge' },
+        { icon: '/static/image/my-page/contact.png', name: '联系客服', type: 'contact' },
+        { icon: '/static/image/my-page/share.png', name: '分享中心', href: '/pages/share/index' },
+        { icon: '/static/image/my-page/protocol.png', name: '用户协议', href: '/pages/my/protocol' }
+      ],
+      recharge: {
+        show: false
+      },
+      code: null,
+      config: {},
+      modal: {
+        show: false,
+        title: '用户信息获取',
+        nickname: '',
+        avatar: ''
+      },
+      btnLock: false
+    }
+  },
+  computed: {
+    ...mapState({
+      userInfo: seate => seate.user.info
+    })
+  },
+  methods: {
+    getHistory() {
+      this.$api.user.episode.record({ limit: 3 }).then(res => {
+        this.history = res.data
+      })
+    },
+    handleMenu(menu) {
+      // #ifdef MP-KUAISHOU
+      if (menu.type === 'contact') {
+        ks.makePhoneCall({
+          phoneNumber: this.config.contact,
+          success(res) {
+            console.log('-->success', res)
+          },
+          fail(err) {
+            console.log('-->error', err)
+          }
+        })
+      }
+      // #endif
+      if (menu.href) {
+        this.$u.route(menu.href)
+      }
+    },
+    // 微信获取头像
+    handleGetWechatUserInfo() {
+      this.modal.show = true
+      this.modal.nickname = this.userInfo.nickname
+      this.modal.avatar = this.userInfo.avatar
+    },
+    // 微信获取头像
+    handleChooseAvatar({ detail }) {
+      const _this = this
+      uni.getFileSystemManager().readFile({
+        filePath: detail.avatarUrl, // 选择图片返回的相对路径
+        encoding: 'base64', // 编码格式
+        success: res => { // 成功的回调
+          uni.uploadFile({
+            url: _this.$setting.BASE_URL + '/avatar/upload',
+            // filePath: 'data:image/png;base64,' + res.data,
+            filePath: detail.avatarUrl,
+            name: 'file',
+            success({ data }) {
+              const res = JSON.parse(data)
+              const { url } = res.data
+              _this.modal.avatar = url
+              console.log('-->data', _this.modal.avatar)
+            }
+          })
+          // this.modal.avatar = detail.avatarUrl
+          // console.log('data:image/png;base64,' + res.data)
+        }
+      })
+    },
+    handleChangeNickname(res) {
+      this.modal.nickname = res.detail.value
+    },
+    // 微信获取头像
+    handleConfirmWechatUserInfo() {
+      console.log('-->data', this.modal)
+      if (!this.modal.avatar) {
+        this.$u.toast('请上传头像')
+        return
+      }
+      if (!this.modal.nickname) {
+        this.$u.toast('请填写昵称')
+        return
+      }
+      this.$loading('数据提交中...')
+      this.$api.user.update({ avatar: this.modal.avatar, nickname: this.modal.nickname }).then(res => {
+        this.code = null
+        this.$hideLoading()
+        this.$store.dispatch('user/info', res.data)
+        this.modal.show = false
+      }).catch(() => {
+        this.$hideLoading()
+      })
+    },
+    handleGetUserInfo(res) {
+      if (this.btnLock) return
+      this.btnLock = true
+      // #ifdef  MP-KUAISHOU
+      this.$loading('数据刷新中...')
+      uni.authorize({
+        scope: 'scope.userInfo',
+        success: () => {
+          uni.getUserInfo({
+            withCredentials: true,
+            success: res => {
+              this.getCode().then(code => {
+                const params = {
+                  encryptedData: res.encryptedData,
+                  iv: res.iv,
+                  signature: res.signature,
+                  code: code
+                }
+                this.$api.user.update(params).then(res => {
+                  this.btnLock = false
+                  this.code = null
+                  this.$hideLoading()
+                  this.$store.dispatch('user/info', res.data)
+                }).catch(() => {
+                  this.code = null
+                  this.btnLock = false
+                  this.$hideLoading()
+                })
+              })
+            },
+            fail: () => {
+              this.btnLock = false
+            }
+          })
+        },
+        fail: () => {
+          this.btnLock = false
+        }
+      })
+      // #endif
+      // #ifdef  MP-TOUTIAO
+      uni.getUserProfile({
+        success: res => {
+          this.getCode().then(code => {
+            const params = {
+              encryptedData: res.encryptedData,
+              iv: res.iv,
+              signature: res.signature,
+              code: code
+            }
+            this.$loading('数据刷新中...')
+            this.$api.user.update(params).then(res => {
+              this.btnLock = false
+              this.code = null
+              this.$hideLoading()
+              this.$store.dispatch('user/info', res.data)
+            }).catch(() => {
+              this.code = null
+              this.btnLock = false
+              this.$hideLoading()
+            })
+          })
+        },
+        fail: () => {
+          this.btnLock = false
+        }
+      })
+      // #endif
+    },
+    getCode() {
+      return new Promise((resolve, reject) => {
+        if (this.code) {
+          return resolve(this.code)
+        }
+        uni.login({
+          provider: uni.$u.platform,
+          success: loginRes => {
+            this.code = loginRes.code
+            // 四分钟失效
+            setTimeout(() => {
+              this.code = null
+            }, 1000 * 4 * 60)
+            resolve(this.code)
+          },
+          fail: () => {
+            this.btnLock = false
+            reject()
+          }
+        })
+      })
+    },
+    getConfig() {
+      this.$api.setting.config().then(res => {
+        this.config = res.data
+      })
+    }
+  },
+  onShow() {
+    this.getHistory()
+    this.getConfig()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .container {
+    padding: 20px 20rpx 180rpx;
+    font-size: 30rpx;
+    >.header{
+      .user-box{
+        .head-img{
+          .avatar{
+            background: transparent;
+          }
+          image{
+            width: 100rpx;
+            height: 100rpx;
+            border-radius: 50%;
+          }
+        }
+        .base{
+          margin-left: 30rpx;
+          .nickname{
+            color: $primary-color;
+            font-size: 32rpx;
+          }
+          .vip{
+            font-size: 22rpx;
+            color: #fff;
+            margin-top: 6rpx;
+          }
+        }
+      }
+      .refresh{
+        background: $primary-color;
+        color: #fff;
+        border-radius: 30rpx;
+        padding: 10rpx 0;
+        width: 140rpx;
+        text-align: center;
+        letter-spacing: .1rem;
+        line-height: initial;
+      }
+    }
+    .recharge{
+      background: url("/static/image/my-recharge-bg.png") no-repeat center;
+      background-size: 100%;
+      height: 200rpx;
+      margin-top: 20rpx;
+      margin-bottom: 10rpx;
+      .static-box{
+        margin-left: 30rpx;
+        .icon{
+          border-radius: 20rpx;
+          image{
+            width: 100rpx;
+            height: 100rpx;
+          }
+        }
+        .overage{
+          font-weight: 800;
+          margin-left: 20rpx;
+          font-size: 32rpx;
+        }
+      }
+      .recharge-btn{
+        border: 1rpx solid #fff;
+        padding: 10rpx 0 ;
+        text-align: center;
+        width: 140rpx;
+        margin-right: 20rpx;
+        border-radius: 30rpx;
+        color: #ffffff;
+      }
+    }
+    .history{
+      background: $bg-op-color;
+      height: 300rpx;
+      border-radius: 15rpx;
+      padding: 20rpx 30rpx;
+      margin-bottom: 40rpx;
+      >.header{
+        text{
+          color: $info-color;
+          font-weight: 600;
+        }
+      }
+      .content{
+        margin-top: 20rpx;
+        .episode{
+          width: calc(100%/4);
+          margin-left: 20rpx;
+          &:first-child{
+            margin-left: 0;
+          }
+          .cover-image{
+            image{
+              width: 100%;
+              height: 200rpx;
+              border-radius: 16rpx;
+            }
+          }
+        }
+      }
+    }
+    .menu-group{
+      .menu-item{
+        padding: 20rpx 0;
+        background: transparent;
+        border: none;
+        text-align: unset;
+        width: 100%;
+        line-height: initial;
+        font-size: initial;
+        justify-content: space-between;
+        &:after{
+          content: unset;
+        }
+        .left-box{
+          flex: 1;
+          .icon{
+            transform: translateY(4rpx);
+            image{
+              width: 55rpx;
+              height: 55rpx;
+            }
+          }
+          text{
+            color: #fff;
+            margin-left: 10rpx;
+          }
+        }
+      }
+    }
+    /* 微信获取用户信息 弹窗*/
+    .slot-content{
+      width: 100%;
+      .avatar{
+        background: transparent;
+        &:after{
+          content: unset;
+        }
+        image{
+          width: 140rpx;
+          height: 140rpx;
+          border-radius: 50%;
+        }
+      }
+      .nickname{
+        width: 100%;
+        margin: 30rpx 0 50rpx;
+        border: 1rpx solid #ccc;
+        padding: 0 20rpx;
+        height: 76rpx;
+        line-height: 76rpx;
+        border-radius: 10rpx;
+      }
+      .confirm{
+        background: $primary-color;
+        color: #fff;
+        border-radius: 30rpx;
+        padding: 12rpx 0;
+        width: 100%;
+        text-align: center;
+        letter-spacing: .1rem;
+        line-height: initial;
+      }
+    }
+  }
+</style>

+ 34 - 0
mini/pages/my/protocol.vue

xqd
@@ -0,0 +1,34 @@
+<template>
+  <view class="protocol">
+    <u-parse :content="data" />
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      data: ''
+    }
+  },
+  computed: {},
+  methods: {
+    getSetting() {
+      this.$api.setting.config().then(res => {
+        this.data = res.data.protocol
+      })
+    }
+  },
+  onLoad() {
+    this.getSetting()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+    .protocol{
+      color: $info-color;
+      font-size: 32rpx;
+      padding: 30rpx 30rpx 80rpx;
+    }
+</style>

+ 155 - 0
mini/pages/my/recharge.vue

xqd
@@ -0,0 +1,155 @@
+<template>
+  <view class="recharge">
+    <view class="header main-left cross-center" @click="datePicker.show = true">
+      <picker
+        mode="date"
+        fields="month"
+        :value="date"
+        @change="handleDateConfirm"
+      >
+        <text>{{ date }}</text>
+      </picker>
+      <view class="icon" :class="{rotate: datePicker.show}">
+        <u-icon name="arrow-down" />
+      </view>
+    </view>
+    <view class="content">
+      <view
+        v-for="(item,index) in recharge"
+        :key="index"
+        class="recharge-item"
+      >
+        <view class="gold-box main-between cross-center">
+          <view class="left-box main-left cross-center">
+            <view class="gold">
+              <text class="number">+{{ item.gold + item.gift }}</text>
+              <text>金币</text>
+            </view>
+            <view class="gift">充{{ item.gold }}赠{{ item.gift }}</view>
+          </view>
+          <view class="right-box">
+            <text class="number">{{ item.price }}</text>
+            <text>元</text>
+          </view>
+        </view>
+        <text class="time">{{ item.created_at }}</text>
+      </view>
+    </view>
+    <!--时间选择-->
+    <!--    <u-datetime-picker-->
+    <!--      ref="datePicker"-->
+    <!--      v-model="datePicker.value"-->
+    <!--      :show="datePicker.show"-->
+    <!--      mode="year-month"-->
+    <!--      @confirm="handleDateConfirm"-->
+    <!--      @cancel="datePicker.show = false"-->
+    <!--    />-->
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      limit: 10,
+      page: 1,
+      isMore: true,
+      recharge: [],
+      datePicker: {
+        date: uni.$u.timeFormat(Number(new Date()), 'yyyy-mm'),
+        show: false
+      }
+    }
+  },
+  computed: {
+    date() {
+      const date = this.datePicker.date
+      const arr = date.split('-')
+      return `${arr[0]}年${arr[1]}月`
+    }
+  },
+  methods: {
+    handleDateConfirm({ detail }) {
+      this.datePicker.date = detail.value
+      this.datePicker.show = false
+      this.isMore = true
+      this.page = 1
+      this.recharge = []
+      this.getRecharge()
+    },
+    getRecharge() {
+      this.$loading()
+      this.$api.user.recharge.record({ date: this.datePicker.date, limit: this.limit, page: this.page }).then(res => {
+        this.$hideLoading()
+        if (res.data.length) {
+          this.recharge = this.recharge.concat(res.data)
+        } else {
+          this.isMore = false
+        }
+      }).catch(err => {
+        this.$hideLoading()
+        console.error('-->error', err)
+      })
+    }
+  },
+  onLoad() {
+    this.getRecharge()
+  },
+  onReachBottom(e) {
+    if (!this.isMore) return
+    this.page += 1
+    this.getConsume()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+    .recharge {
+      padding: 20rpx 20rpx;
+      .header{
+        color: $info-color;
+        text{
+          margin-right: 20rpx;
+          font-size: 32rpx;
+        }
+        .icon{
+          transition: .3s;
+          &.rotate{
+            transform: rotate(-180deg);
+          }
+        }
+      }
+      .content{
+        .recharge-item{
+          border-bottom: 1rpx solid $info-color;
+          color: $info-color;
+          font-size: 26rpx;
+          padding: 20rpx 0;
+          &:last-child{
+            border-bottom: none;
+          }
+          .gold-box{
+            padding: 16rpx 0;
+            .number{
+              color: $primary-color;
+              font-size: 38rpx;
+              font-weight: 600;
+              margin-right: 10rpx;
+            }
+            .gold .number{
+              color: $default-color;
+            }
+            .left-box{
+              .gift{
+                color: $pink-color;
+                margin-left: 40rpx;
+              }
+            }
+          }
+          text.time{
+            font-size: 24rpx;
+          }
+        }
+      }
+    }
+</style>

+ 130 - 0
mini/pages/share/components/Qrcode.vue

xqd
@@ -0,0 +1,130 @@
+<template>
+  <view class="qrcode-modal" :class="{show: show}">
+    <view class="content">
+      <view class="qrcode-modal-content dir-top-wrap cross-center">
+        <view class="head-img">
+          <u-image
+            width="150rpx"
+            height="150rpx"
+            :src="userInfo.avatar"
+            shape="circle"
+          />
+        </view>
+        <view class="nickname">{{ userInfo.nickname }}</view>
+        <u-line class="u-line" border-style="dashed" length="90%" />
+        <view class="qrcode">
+          <u-image
+            width="320rpx"
+            height="320rpx"
+            :src="userInfo.share_qrcode"
+          />
+        </view>
+      </view>
+      <u-button
+        shape="circle"
+        hover-class="none"
+        :custom-style="{
+          background:'linear-gradient(270deg, #6EEBE8 0%, #FF74B9 100%)',
+          boxShadow: '0 0 20rpx rgba(13,239,250,.3)',
+          borderColor:'none',
+          color: '#ffffff',
+          marginTop: '50rpx'}"
+        @click="handleSave"
+      >保存</u-button>
+      <u-button
+        shape="circle"
+        hover-class="none"
+        :custom-style="{
+          borderColor:'none',
+          marginTop: '30rpx'}"
+        @click="handleCancel"
+      >取消</u-button>
+    </view>
+  </view>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+export default {
+  name: 'Qrcode',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {}
+  },
+  computed: {
+    ...mapState({
+      userInfo: seate => seate.user.info
+    })
+  },
+  methods: {
+    handleSave() {
+      console.log('-->data', this.userInfo.share_qrcode)
+      this.$util.saveImage(this.userInfo.share_qrcode).then(res => {
+        this.$emit('update:show', false)
+      })
+    },
+    handleCancel() {
+      this.$emit('update:show', false)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.qrcode-modal{
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 750rpx;
+  height: 100vh;
+  background: $bg-color;
+  z-index: 999;
+  display: none;
+  animation: show-modal linear .5s;
+  &.show{
+    display: block;
+  }
+  .content{
+    position: absolute;
+    width: 600rpx;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%,-50%);
+    .qrcode-modal-content{
+      background: #1B203C;
+      position: relative;
+      width: 100%;
+      height: 50vh;
+      background: url("/static/image/share-qrcode-bg.png") no-repeat center;
+      background-size: 100% 100%;
+      .head-img{
+        position: absolute;
+        top: -75rpx;
+      }
+      .nickname{
+        position: absolute;
+        color: #ffffff;
+        top: 15%;
+        font-size: 34rpx;
+      }
+      .u-line{
+        position: absolute;
+        top: 28.55%;
+      }
+      .qrcode{
+        position: absolute;
+        top: 35%;
+      }
+    }
+  }
+}
+@keyframes show-modal {
+  0%{opacity: 0;}
+  100%{opacity: 1;}
+}
+</style>

+ 145 - 0
mini/pages/share/income.vue

xqd
@@ -0,0 +1,145 @@
+<template>
+  <view class="share-income">
+    <view class="head-box main-between cross-center">
+      <view class="left-box">
+        <text>分享佣金</text>
+        <view class="price main-left cross-bottom">{{ userInfo.total_income }}
+          <view class="unit">元</view>
+        </view>
+      </view>
+      <view
+        class="right-box"
+        @click="$u.route('/pages/share/withdrawDetail')"
+      >提现明细
+      </view>
+    </view>
+    <view class="menu-group">
+      <button
+        v-for="(menu,index) in menus"
+        :key="index"
+        class="menu-item main-between cross-center"
+        @click="handleMenu(menu)"
+      >
+        <view class="left-box dir-left-nowrap cross-center">
+          <text>{{ menu.name }}</text>
+        </view>
+        <view class="right-box main-left cross-center">
+          <text v-if="menu.showNumber">{{ menu.number }} 元</text>
+          <u-icon name="arrow-right" :color="$colors.infoColor" bold top="1" />
+        </view>
+      </button>
+    </view>
+  </view>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+export default {
+  name: 'ShareIncome',
+  data() {
+    return {
+      menus: [
+        { name: '可提现佣金', href: '/pages/share/withdraw', showNumber: true, number: 0 },
+        { name: '已提现佣金', href: '/pages/share/withdrawDetail?active=3', showNumber: true, number: 0 },
+        { name: '待打款佣金', href: '/pages/share/withdrawDetail?active=2', showNumber: true, number: 0 },
+        { name: '用户须知', href: '/pages/share/tips', showNumber: false }
+      ]
+    }
+  },
+  computed: {
+    ...mapState({
+      userInfo: seate => seate.user.info
+    })
+  },
+  methods: {
+    handleMenu(menu) {
+      if (menu.href) {
+        this.$u.route(menu.href)
+      }
+    },
+    getIncome() {
+      this.$api.share.income().then(res => {
+        this.menus[0].number = this.userInfo.income
+        this.menus[1].number = res.data.been
+        this.menus[2].number = res.data.wait
+      })
+    }
+  },
+  onLoad() {
+    this.getIncome()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.share-income {
+  padding: 30rpx 30rpx 80rpx;
+  .head-box{
+    background: $bg-op-color;
+    color: #6eebe8;
+    padding: 50rpx 30rpx;
+    border-radius: 20rpx;
+    .left-box{
+      text{
+        font-size: 26rpx;
+      }
+      .price{
+        margin-top: 12rpx;
+        font-size: 42rpx;
+        font-weight: bold;
+        .unit{
+          font-weight: normal;
+          font-size: 28rpx;
+          margin-bottom: 5rpx;
+          margin-left: 2rpx;
+        }
+      }
+    }
+    .right-box{
+      border: 1rpx solid #6eebe8;
+      border-radius: 30rpx;
+      padding: 6rpx 20rpx;
+      font-size: 26rpx;
+    }
+  }
+
+  .menu-group{
+    .menu-item{
+      padding: 20rpx 0;
+      background: transparent;
+      border: none;
+      text-align: unset;
+      width: 100%;
+      line-height: initial;
+      font-size: initial;
+      justify-content: space-between;
+      border-bottom: 1rpx solid $border-op-color;
+      border-radius: 0;
+      color: #fff;
+      margin-top: 40rpx;
+      &:after{
+        content: unset;
+      }
+      .left-box{
+        flex: 1;
+        .icon{
+          transform: translateY(4rpx);
+          image{
+            width: 55rpx;
+            height: 55rpx;
+          }
+        }
+        text{
+          color: #fff;
+          margin-left: 10rpx;
+        }
+      }
+      .right-box{
+        text{
+          margin-right: 20rpx;
+        }
+      }
+    }
+  }
+}
+</style>

+ 243 - 0
mini/pages/share/index.vue

xqd
@@ -0,0 +1,243 @@
+<template>
+  <view class="share-index">
+    <view class="head-box">
+      <view class="user-info dir-left-nowrap">
+        <view class="head-img">
+          <image :src="userInfo.avatar" alt="" />
+        </view>
+        <view class="base-info dir-top-wrap main-center">
+          <view class="nickname">{{ userInfo.nickname }}</view>
+          <view class="parent">推荐人: {{ parent.nickname || '-' }}</view>
+        </view>
+      </view>
+      <view class="withdraw-box main-between cross-center">
+        <view class="withdraw-item dir-top-wrap main-center" @click="$u.route('/pages/share/withdraw')">
+          <view class="static-txt main-left cross-center">
+            可提现佣金
+            <view class="badge">提现</view>
+          </view>
+          <view class="price main-left cross-center">
+            {{ userInfo.income }}
+            <view class="unit">元</view>
+          </view>
+        </view>
+        <view class="withdraw-item dir-top-wrap main-center">
+          <view class="static-txt">
+            已提现佣金
+          </view>
+          <view class="price main-left cross-center">
+            {{ parseFloat(userInfo.total_income - userInfo.income).toFixed(2) }}
+            <view class="unit">元</view>
+          </view>
+        </view>
+      </view>
+    </view>
+    <!--菜单-->
+    <view class="menu-group">
+      <button
+        v-for="(menu,index) in menus"
+        :key="index"
+        class="menu-item main-between cross-center"
+        :open-type="menu.type ? menu.type : ''"
+        @bindcontact="handleMenu"
+        @click="handleMenu(menu)"
+      >
+        <view class="left-box dir-left-nowrap cross-center">
+          <view class="icon">
+            <image :src="menu.icon" />
+          </view>
+          <text>{{ menu.name }}</text>
+        </view>
+        <u-icon name="arrow-right" :color="$colors.infoColor" bold />
+      </button>
+    </view>
+    <!-- QRCODE 弹窗   -->
+    <qrcode :show.sync="qrcodeModal.show" />
+  </view>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+import Qrcode from './components/Qrcode'
+import { parent } from '../../api/user'
+export default {
+  components: { Qrcode },
+  data() {
+    return {
+      menus: [
+        { icon: '/static/image/my-page/share.png', name: '分享好友', type: 'share' },
+        { icon: '/static/image/share/income.png', name: '分享佣金', href: '/pages/share/income' },
+        { icon: '/static/image/share/order.png', name: '分享订单', href: '/pages/share/order' },
+        { icon: '/static/image/share/detail.png', name: '提现明细', href: '/pages/share/withdrawDetail' },
+        { icon: '/static/image/share/team.png', name: '我的团队', href: '/pages/share/team' },
+        // #ifdef MP-TOUTIAO | MP-WEIXIN
+        { icon: '/static/image/share/qrcode.png', name: '分享二维码', type: 'share_qrcode' }
+        // #endif
+      ],
+      qrcodeModal: {
+        show: false
+      },
+      parent: {}
+    }
+  },
+  computed: {
+    ...mapState({
+      userInfo: seate => seate.user.info
+    })
+  },
+  methods: {
+    handleMenu(menu) {
+      // #ifdef MP-KUAISHOU
+      if (menu.type === 'contact') {
+        ks.makePhoneCall({
+          phoneNumber: this.config.contact,
+          success(res) {
+            console.log('-->success', res)
+          },
+          fail(err) {
+            console.log('-->error', err)
+          }
+        })
+      }
+      // #endif
+      if (menu.type === 'share_qrcode') {
+        this.handleGenerateQrcode()
+      }
+      if (menu.href) {
+        this.$u.route(menu.href)
+      }
+    },
+    handleGenerateQrcode() {
+      if (this.userInfo.share_qrcode) {
+        this.qrcodeModal.show = true
+        return
+      }
+      this.$loading()
+      this.$api.share.generateQrcode().then(res => {
+        this.$hideLoading()
+        // 获取用户信息
+        this.$api.user.info().then(res => {
+          this.$store.dispatch('user/info', res.data)
+          this.qrcodeModal.show = true
+        })
+      }).catch(() => {
+        this.$hideLoading()
+      })
+    },
+    getParentInfo() {
+      this.$api.user.parent().then(res => {
+        this.parent = res.data
+      })
+    }
+  },
+  onShareAppMessage() {
+    return this.$util.shareMessage(this.userInfo)
+  },
+  onShow() {
+    this.getParentInfo()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.share-index {
+  color: #6eebe8;
+  font-size: 32rpx;
+  padding: 40rpx 30rpx 80rpx;
+  .head-box{
+    background: $bg-op-color;
+    border-radius: 20rpx;
+    padding: 40rpx 0;
+    margin-bottom: 50rpx;
+    .user-info{
+      padding: 0 30rpx 40rpx;
+      border-bottom: 1rpx $border-op-color solid;
+      .head-img{
+        width: 120rpx;
+        height: 120rpx;
+        border-radius: 50%;
+        overflow: hidden;
+        image{
+          width: 100%;
+          height: 100%;
+        }
+      }
+      .base-info{
+        margin-left: 30rpx;
+        .parent{
+          font-size: 26rpx;
+        }
+      }
+    }
+    .withdraw-box{
+      padding: 40rpx 0 0;
+      .withdraw-item{
+        flex: 1;
+        position: relative;
+        padding: 0 40rpx;
+        &:first-child{
+          &:after{
+            content: "";
+            position: absolute;
+            background: $border-op-color;
+            right: 0;
+            top: 50%;
+            height: 60%;
+            width: 1rpx;
+            transform: translateY(-50%);
+          }
+        }
+        .static-txt{
+          font-size: 26rpx;
+          .badge{
+            font-size: 18rpx;
+            border: 1rpx solid #6eebe8;
+            border-radius: 30rpx;
+            padding: 2rpx 20rpx;
+            margin-left: 30rpx;
+          }
+        }
+        .price{
+          margin-top: 10rpx;
+          font-size: 48rpx;
+          .unit{
+            font-size: 34rpx;
+            margin-left: 4rpx;
+          }
+        }
+      }
+    }
+  }
+  .menu-group{
+    .menu-item{
+      padding: 20rpx 0;
+      background: transparent;
+      border: none;
+      text-align: unset;
+      width: 100%;
+      line-height: initial;
+      font-size: initial;
+      justify-content: space-between;
+      border-bottom: 1rpx solid $border-op-color;
+      border-radius: 0;
+      &:after{
+        content: unset;
+      }
+      .left-box{
+        flex: 1;
+        .icon{
+          transform: translateY(4rpx);
+          image{
+            width: 55rpx;
+            height: 55rpx;
+          }
+        }
+        text{
+          color: #fff;
+          margin-left: 10rpx;
+        }
+      }
+    }
+  }
+}
+</style>

+ 166 - 0
mini/pages/share/order.vue

xqd
@@ -0,0 +1,166 @@
+<template>
+  <view class="share-order">
+    <view
+      v-for="(item, index) in order"
+      :key="index"
+      class="order-box"
+    >
+      <view class="order-num">
+        订单号: {{ item.order_id }}
+      </view>
+      <view class="child-info main-between cross-center">
+        <view class="base main-left cross-center">
+          <view class="head-img">
+            <image :src="item.order.user.avatar" />
+          </view>
+          <view class="nickname main-left cross-center">
+            <view class="nickname">
+              <u-text :text="item.order.user.nickname" :lines="1" color="#ffffff" size="28rpx" />
+            </view>
+            <view class="level">1级</view>
+          </view>
+        </view>
+        <view class="income-info main-left cross-center">
+          <text>已得佣金:{{ item.income }}元</text>
+          <u-icon :name="item.showDetail?'arrow-up':'arrow-down'" top="1" @click="handleShow(item)" />
+        </view>
+      </view>
+      <view v-show="item.showDetail" class="detail main-between cross-center">
+        <view class="goods-info main-left cross-center">
+          <view class="cover-img">
+            <image />
+          </view>
+          <view class="combo dir-top-wrap main-center">
+            <text class="type">{{ item.type_name }}</text>
+            <text>{{ item.order.combo.name }}</text>
+          </view>
+        </view>
+        <view class="income dir-top-wrap">
+          <view class="num">x1</view>
+          <view class="price">¥{{ item.order.combo.price }}</view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'ShareOrder',
+  components: {},
+
+  data() {
+    return {
+      limit: 10,
+      page: 1,
+      isMore: true,
+      order: []
+    }
+  },
+  computed: {},
+  methods: {
+    handleShow(item) {
+      item.showDetail = !item.showDetail
+    },
+    getOrder() {
+      this.$loading()
+      this.$api.share.order.lists({ limit: this.limit, page: this.page }).then(res => {
+        this.$hideLoading()
+        if (res.data.length) {
+          res.data.forEach(obj => {
+            obj.showDetail = false
+          })
+          this.order = this.order.concat(res.data)
+        } else {
+          this.isMore = false
+        }
+      })
+    }
+  },
+  onLoad() {
+    this.getOrder()
+  },
+  onReachBottom(e) {
+    if (!this.isMore) return
+    this.page += 1
+    this.getOrder()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.share-order {
+  padding: 40rpx 30rpx;
+  font-size: 28rpx;
+  .order-box{
+    background: $bg-op-color;
+    color: #ffffff;
+    padding: 30rpx 0;
+    border-radius: 20rpx;
+    margin-bottom: 30px;
+    .order-num{
+      padding: 0 30rpx 30rpx;
+      border-bottom: 1rpx $border-op-color solid;
+    }
+    .child-info{
+      padding: 30rpx 30rpx;
+      .base{
+        .head-img{
+          width: 70rpx;
+          height: 70rpx;
+          border-radius: 50%;
+          overflow: hidden;
+          background: #ccc;
+          image{
+            width: 100%;
+            height: 100%;
+          }
+        }
+        .nickname{
+          margin-left: 10rpx;
+          flex: 1;
+          .level{
+            color: #6eebe8;
+          }
+        }
+      }
+      .income-info{
+        text{
+          margin-right: 18rpx;
+        }
+      }
+    }
+    .detail{
+      padding: 30rpx 30rpx 0;
+      border-top: 1rpx $border-op-color solid;
+      .goods-info{
+        .cover-img{
+          width: 80rpx;
+          height: 80rpx;
+          border-radius: 8rpx;
+          overflow: hidden;
+          background: #ccc;
+          image{
+            width: 100%;
+            height: 100%;
+          }
+        }
+        .combo{
+          margin-left: 20rpx;
+          .type{
+            margin-bottom: 6rpx;
+          }
+        }
+      }
+      .income{
+        color: #6eebe8;
+        text-align: right;
+        .num{
+          font-size: 24rpx;
+          margin-bottom: 6rpx;
+        }
+      }
+    }
+  }
+}
+</style>

+ 134 - 0
mini/pages/share/team.vue

xqd
@@ -0,0 +1,134 @@
+<template>
+  <view class="share-team">
+    <view class="title">
+      <text>一级分享名称</text>
+    </view>
+
+    <view
+      v-for="(item,index) in data"
+      :key="index"
+      class="child-info"
+    >
+      <view class="head-box main-between cross-center">
+        <view class="base-info dir-left-nowrap">
+          <view class="head-img">
+            <image :src="item.avatar" />
+          </view>
+          <view class="nickname-box dir-top-wrap main-center">
+            <view class="nickname">
+              {{ item.nickname }}
+            </view>
+            <view class="bind-at">
+              绑定时间:{{ item.become_child_at }}
+            </view>
+          </view>
+        </view>
+        <view class="child-num">
+          推广{{ item.child_count }}人
+        </view>
+      </view>
+      <view class="price-box main-between cross-center">
+        <view>{{ item.child_order_sum_income }}元</view>
+        <view>{{ item.child_order_count }}个订单</view>
+      </view>
+    </view>
+  </view></template>
+
+<script>
+import { mapState } from 'vuex'
+export default {
+  name: 'ShareTeam',
+  data() {
+    return {
+      limit: 10,
+      page: 1,
+      isMore: true,
+      data: []
+    }
+  },
+  computed: {
+    ...mapState({
+      userInfo: seate => seate.user.info
+    })
+  },
+  methods: {
+    getTeam() {
+      this.$loading()
+      this.$api.share.team.lists({ limit: this.limit, page: this.page }).then(res => {
+        this.$hideLoading()
+        if (res.data.length) {
+          this.data = this.data.concat(res.data)
+        } else {
+          this.isMore = false
+        }
+      })
+    }
+  },
+  onLoad() {
+    this.getTeam()
+  },
+  onReachBottom(e) {
+    if (!this.isMore) return
+    this.page += 1
+    this.getTeam()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.share-team {
+  padding: 20rpx 30rpx 80rpx;
+  font-size: 28rpx;
+  .title{
+    text-align: center;
+    color: #6eebe8;
+    margin-bottom: 30rpx;
+    text{
+      padding-bottom: 10rpx;
+      border-bottom: 4rpx solid #6eebe8;
+    }
+  }
+  .child-info{
+    background: $bg-op-color;
+    color: #ffffff;
+    padding: 30rpx 0;
+    border-radius: 20rpx;
+    margin-bottom: 30px;
+    .head-box{
+      padding: 0 30rpx 30rpx;
+      border-bottom: 1rpx $border-op-color solid;
+      .base-info{
+        .head-img{
+          width: 80rpx;
+          height: 80rpx;
+          border-radius: 50%;
+          overflow: hidden;
+          background: #ccc;
+          image{
+            width: 100%;
+            height: 100%;
+          }
+        }
+        .nickname-box{
+          margin-left: 10rpx;
+          .nickname{
+            font-size: 30rpx;
+            margin-bottom: 4rpx;
+          }
+          .bind-at{
+            font-size: 24rpx;
+          }
+        }
+      }
+      .child-num{
+        color: #6eebe8;
+        font-size: 32rpx;
+        margin-top: -20rpx;
+      }
+    }
+    .price-box{
+      padding: 30rpx 30rpx 0;
+    }
+  }
+}
+</style>

+ 34 - 0
mini/pages/share/tips.vue

xqd
@@ -0,0 +1,34 @@
+<template>
+  <view class="withdraw-tips">
+    <u-parse :content="tips" />
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'Withdraw',
+  data() {
+    return {
+      tips: ''
+    }
+  },
+  computed: {},
+  methods: {
+    getTips() {
+      this.$api.share.tips().then(res => {
+        this.tips = res.data
+      })
+    }
+  },
+  onLoad() {
+    this.getTips()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.withdraw-tips {
+  color: #fff;
+  padding: 30rpx 30rpx 80rpx;
+}
+</style>

+ 294 - 0
mini/pages/share/withdraw.vue

xqd
@@ -0,0 +1,294 @@
+<template>
+  <view class="withdraw">
+    <view class="overage">
+      账户剩余金额:<text class="number">{{ userInfo.income }}元</text>
+    </view>
+    <view class="input-box main-left cross-center">
+      <view class="left">¥</view>
+      <view class="center">
+        <input v-model="price" type="text" placeholder="请输入提现金额">
+      </view>
+      <view class="right" @click="handleAll">全部</view>
+    </view>
+
+    <view class="tips">
+      <view>提现金额不能小于{{ setting.withdraw_min }}元</view>
+      <view>提现需要加收{{ setting.withdraw_discount }}%手续费</view>
+    </view>
+    <view class="type-box main-between cross-center" @click="show = true">
+      <view>提现方式</view>
+      <view class="main-left cross-center">
+        <text>{{ selectType.name }}</text>
+        <u-icon name="arrow-right" :color="$colors.infoColor" bold top="1" />
+      </view>
+    </view>
+    <template v-if="Object.keys(selectType).length">
+      <view class="input-box main-left cross-center">
+        <view class="left">姓名</view>
+        <view class="center">
+          <input v-model="name" type="text" placeholder="请输入姓名">
+        </view>
+      </view>
+      <view class="input-box main-left cross-center">
+        <view class="left">手机号</view>
+        <view class="center">
+          <input v-model="phone_num" type="number" placeholder="请输入手机号">
+        </view>
+      </view>
+      <template v-if="selectType.value === 3">
+        <view class="input-box main-left cross-center">
+          <view class="left">提现银行</view>
+          <view class="center">
+            <input v-model="bank_name" type="text" placeholder="请输入提现银行">
+          </view>
+        </view>
+      </template>
+      <view class="input-box main-left cross-center">
+        <view class="left">提现账号</view>
+        <view class="center">
+          <input v-model="account" :type="selectType.value === 3 ?'number':'text'" placeholder="请输入提现账号">
+        </view>
+      </view>
+    </template>
+
+    <view class="btn" @click="handleConfirm">提交申请</view>
+    <!--   选择提现方式 -->
+    <u-popup
+      :show="show"
+      :round="20"
+      closeable
+      @close="handleClose"
+    >
+      <view class="popup-content">
+        <view
+          v-for="(type, index) in types"
+          :key="index"
+          class="type-item main-between cross-center"
+          @click="handleSelect(type, index)"
+        >
+          <view class="left-box dir-left-nowrap cross-center">
+            <view class="icon" :class="{bank: index === 2}">
+              <image :src="type.icon" />
+            </view>
+            <text>{{ type.name }}</text>
+          </view>
+          <u-icon v-if="selectIndex === index" name="checkmark" color="#68EBE9" size="24" />
+        </view>
+      </view>
+    </u-popup>
+  </view>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+export default {
+  name: 'Withdraw',
+  data() {
+    return {
+      price: '',
+      account: '',
+      name: '',
+      phone_num: '',
+      bank_name: '',
+      setting: { withdraw_min: 0, withdraw_discount: 0 },
+      show: false,
+      types: [
+        { icon: '/static/image/share/wechat.png', value: 1, name: '微信线下打款' },
+        { icon: '/static/image/share/alipay.png', value: 2, name: '支付宝线下打款' },
+        { icon: '/static/image/share/bank.png', value: 3, name: '银联线下打款' }
+      ],
+      selectIndex: 0,
+      selectType: {}
+    }
+  },
+  computed: {
+    ...mapState({
+      userInfo: seate => seate.user.info
+    })
+  },
+  methods: {
+    handleConfirm() {
+      if (!this.price) {
+        this.$u.toast(`请输入提现金额`)
+        return
+      }
+      if (Object.keys(this.selectType).length === 0) {
+        this.$u.toast(`请选择提现方式`)
+        return
+      }
+      if (parseFloat(this.price) < parseFloat(this.setting.withdraw_min)) {
+        this.$u.toast(`提现金额不能小于${this.setting.withdraw_min}元`)
+        return
+      }
+      if (!this.name) {
+        this.$u.toast(`请输入姓名`)
+        return
+      }
+      if (!this.phone_num) {
+        this.$u.toast(`请输入手机号`)
+        return
+      }
+      if (!this.account) {
+        this.$u.toast(`请输入账号`)
+        return
+      }
+      if (parseFloat(this.price) > this.userInfo.income) {
+        this.$u.toast(`提现金额不能大于可提现余额`)
+        return
+      }
+      if (Object.keys(this.selectType).length === 0) {
+        this.$u.toast(`请选择提现方式`)
+        return
+      }
+
+      if (this.selectType.value === 3 && !this.bank_name) {
+        this.$u.toast(`请输入提现银行`)
+        return
+      }
+
+      const params = {
+        price: this.price,
+        type: this.selectType.value,
+        account: this.account,
+        name: this.name,
+        phone_num: this.phone_num,
+        bank_name: this.bank_name
+      }
+      this.$loading()
+      this.$api.share.withdraw.create(params).then(res => {
+        this.$hideLoading()
+        this.$u.toast(`提交成功`)
+        this.$api.user.info().then(res => {
+          this.$store.dispatch('user/info', res.data)
+          setTimeout(() => {
+            this.$u.route({ type: 'redirect', url: '/pages/share/withdrawDetail?active=1' })
+          }, 500)
+        })
+      }).catch(() => {
+        this.$hideLoading()
+      })
+    },
+    handleAll() {
+      this.price = this.userInfo.income
+    },
+    handleSelect(item, index) {
+      this.selectIndex = index
+      this.selectType = item
+      this.handleClose()
+    },
+    handleClose() {
+      this.show = false
+    },
+    getSetting() {
+      this.$loading()
+      this.$api.share.setting().then(res => {
+        this.$hideLoading()
+        this.setting = res.data
+      })
+    }
+  },
+  onLoad() {
+    this.getSetting()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.withdraw {
+  padding: 30rpx 30rpx 80rpx;
+  color: #FFFFFF;
+  font-size: 38rpx;
+  input{
+    color: #fff;
+    font-size: 38rpx;
+  }
+  .overage{
+    font-weight: bold;
+    padding-bottom: 30rpx;
+    border-bottom: 1rpx solid $border-op-color;
+    .number{
+      color: #6eebe8;
+    }
+  }
+  .input-box{
+    padding: 30rpx 0;
+    border-bottom: 1rpx solid $border-op-color;
+    .left{
+      width: 180rpx;
+    }
+    .center{
+      flex: 1;
+      margin-left: 10rpx;
+    }
+    .right{
+      font-size: 28rpx;
+      color: #6eebe8;
+    }
+  }
+  .tips{
+    background: $bg-op-color;
+    border-radius: 20rpx;
+    padding: 30rpx 20rpx;
+    color: #ccc;
+    margin: 30rpx 0;
+    font-size: 24rpx;
+  }
+  .type-box{
+    border-bottom: 1rpx solid $border-op-color;
+    padding: 30rpx 0;
+    text{
+      margin-right: 10rpx;
+      font-size: 32rpx;
+    }
+  }
+  .btn{
+    background: linear-gradient(270deg, #6EEBE8 0%, #FF74B9 100%);
+    text-align: center;
+    padding: 26rpx 0;
+    width: 90%;
+    margin: 60rpx auto;
+    border-radius: 50rpx;
+    font-size: 32rpx;
+  }
+  .popup-content{
+    padding: 120rpx 0 40rpx;
+    .type-item{
+      padding: 20rpx 40rpx;
+      background: transparent;
+      border: none;
+      text-align: unset;
+      width: 100%;
+      line-height: initial;
+      font-size: initial;
+      justify-content: space-between;
+      border-top: 1rpx solid #CECFD1;
+      border-radius: 0;
+      &:first-child{
+
+      }
+      .left-box{
+        flex: 1;
+        .icon{
+          width: 60rpx;
+          height: 48rpx;
+          transform: translateY(4rpx);
+          image{
+            width: 48rpx;
+            height: 48rpx;
+          }
+          &.bank{
+            image{
+              width: 56rpx;
+              height: 38rpx;
+            }
+          }
+        }
+        text{
+          color: #777777;
+          margin-left: 10rpx;
+        }
+      }
+    }
+  }
+}
+</style>

+ 151 - 0
mini/pages/share/withdrawDetail.vue

xqd
@@ -0,0 +1,151 @@
+<template>
+  <view class="withdraw-detail">
+    <u-sticky bg-color="#151728">
+      <u-tabs
+        :list="tabs"
+        line-color="#6EEBE8"
+        :active-style="{color: '#6EEBE8'}"
+        :inactive-style="{color: '#ffffff'}"
+        :item-style="{width: 'calc(750rpx / 5)', height: '44px'}"
+        :current="activeIndex"
+        @click="handleTabClick"
+      />
+    </u-sticky>
+    <template v-for="(tab, index) in tabs">
+      <view
+        v-if="activeIndex === index"
+        :key="index"
+        class="detail-box"
+      >
+        <view
+          v-for="(data, dataIndex) in tab.data"
+          :key="dataIndex"
+          class="detail-item"
+        >
+          <view class="date">
+            {{ data.created_at }}
+          </view>
+          <view class="info">
+            <view class="type main-left cross-center">
+              {{ data.type_name }}
+              <view class="status">{{ data.status_name }}</view>
+            </view>
+            <view class="withdraw-box main-between cross-center">
+              <view class="item">
+                <view>提现账户:{{ data.account }}</view>
+                <view>提现时间: {{ data.created_at }}</view>
+              </view>
+              <view class="item price-box">
+                <view class="price">{{ data.price }}</view>
+                <view class="discount">手续费: {{ data.discount }}</view>
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+    </template>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      tabs: [
+        { name: '全部', status: -1, data: [], limit: 10, page: 1, isMore: true },
+        { name: '待审核', status: 0, data: [], limit: 10, page: 1, isMore: true },
+        { name: '待打款', status: 1, data: [], limit: 10, page: 1, isMore: true },
+        { name: '已打款', status: 2, data: [], limit: 10, page: 1, isMore: true },
+        { name: '无效', status: 3, data: [], limit: 10, page: 1, isMore: true }
+      ],
+      activeIndex: 0
+    }
+  },
+  computed: {},
+  methods: {
+    handleTabClick(item) {
+      this.activeIndex = item.index
+      const tab = this.tabs[this.activeIndex]
+      if (tab.data.length === 0 && tab.isMore) {
+        this.getData()
+      }
+    },
+    getData() {
+      this.$loading()
+      const item = this.tabs[this.activeIndex]
+      const params = { limit: item.limit, page: item.page, status: item.status }
+      this.$api.share.withdraw.lists(params).then(res => {
+        this.$hideLoading()
+        if (res.data.length) {
+          item.data = item.data.concat(res.data)
+        } else {
+          item.isMore = false
+        }
+      })
+    }
+  },
+  onLoad(options) {
+    this.activeIndex = typeof options.active !== 'undefined' ? parseInt(options.active) : 0
+    this.getData()
+  },
+  onReachBottom(e) {
+    const item = this.tabs[this.activeIndex]
+    if (!item.isMore) return
+    item.page += 1
+    this.getData()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.withdraw-detail {
+  font-size: 28rpx;
+  padding: 30rpx 0 80rpx;
+  .detail-box{
+    padding: 0 30rpx;
+    margin-top: 30rpx;
+    .detail-item{
+      background: $bg-op-color;
+      color: #ffffff;
+      border-radius: 10rpx;
+      padding: 30rpx 0;
+      margin-bottom: 20rpx;
+      .date{
+        border-bottom: 1rpx $border-op-color solid;
+        padding: 0 30rpx 30rpx;
+      }
+      .info{
+        padding: 30rpx 30rpx 0;
+        .type{
+          font-size: 32rpx;
+          .status{
+            font-size: 20rpx;
+            color: #ff74b9;
+            border: 1rpx solid #FF74B9;
+            border-radius: 30rpx;
+            padding: 2rpx 20rpx;
+            margin-left: 20rpx;
+          }
+        }
+        .withdraw-box{
+          margin-top: 20rpx;
+          .item{
+            >view:first-child{
+              margin-bottom: 10rpx;
+            }
+            &.price-box{
+              color: #6eebe8;
+              font-size: 36rpx;
+              font-weight: bold;
+              .discount{
+                font-size: 24rpx;
+                font-weight: normal;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 205 - 0
mini/pages/sign/index.vue

xqd
@@ -0,0 +1,205 @@
+<template>
+  <view class="sign-container">
+    <u-loading-page
+      :loading="loading"
+      :bg-color="$colors.bgColor"
+      :color="$colors.primaryColor"
+      :loading-color="$colors.primaryColor"
+    />
+    <template v-if="!loading">
+      <view
+        class="header"
+        :style="{
+          backgroundImage: `url(${$setting.IMAGE_URL}/sign/bg.png)`
+        } "
+      >
+        <view
+          class="icon"
+          :style="{
+            backgroundImage: `url(${$setting.IMAGE_URL}/sign/icon.png)`
+          } "
+        />
+        <view class="been-sign">已连续签到{{ signDays }}天</view>
+        <view class="tips">在连续签到{{ 7 - signDays }}天立得{{ setting[7].award }}金</view>
+      </view>
+      <view class="content dir-left-wrap">
+        <view
+          v-for="(item, index) in setting"
+          :key="index"
+          class="item main-center cross-center"
+          :class="{active: parseInt(index) === (parseInt(signDays) + 1)}"
+        >
+          <view
+            class="left-box dir-top-wrap"
+            :class="{
+              'cross-center': parseInt(index) !== 7,
+              'last': parseInt(index) === 7,
+            }"
+          >
+            <text class="title">{{ item.name }}</text>
+            <view v-if="parseInt(index) !== 7" class="icon">
+              <image src="@/static/image/gold.png" />
+            </view>
+            <view class="number">{{ item.award }}金币</view>
+          </view>
+          <view v-if="parseInt(index) === 7" class="right-box">
+            <image src="@/static/image/gold-bag.png" />
+          </view>
+        </view>
+      </view>
+      <view class="btn" :class="{todaySign: todaySign}" @click="handleSign">
+        {{ todaySign?'已签到' : '立即签到' }}
+      </view>
+    </template>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      signDays: 0,
+      loading: true,
+      todaySign: false,
+      setting: []
+    }
+  },
+  computed: {},
+  methods: {
+    handleSign() {
+      if (!this.todaySign) {
+        this.$loading('签到中...')
+        this.$api.sign.handle().then(res => {
+          this.$hideLoading()
+          if (res.code === 0) {
+            this.$u.toast(`签到成功,获取 ${res.data.award} 金币`)
+            this.todaySign = true
+            this.signDays += 1
+            this.$api.user.info().then(res => {
+              this.$store.dispatch('user/info', res.data)
+            })
+          }
+        })
+      }
+    },
+    checkSetting() {
+      this.$api.sign.setting().then(res => {
+        this.loading = false
+        const { data } = res
+        this.signDays = data.signDays
+        this.setting = data.setting
+        this.todaySign = data.todaySign
+      })
+    }
+  },
+  onLoad() {
+    this.checkSetting()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+    .sign-container{
+      padding: 20rpx;
+      font-size: 28rpx;
+      .header{
+        background-repeat: no-repeat;
+        background-size: 100% 100%;
+        height: 300rpx;
+        border-radius: 10rpx;
+        position: relative;
+        margin-top: 100rpx;
+        padding-top: 80rpx;
+        .icon{
+          background-repeat: no-repeat;
+          background-size: 100%;
+          height: 180rpx;
+          width: 240rpx;
+          position: absolute;
+          top: -50%;
+          transform: translateY(30%) translateX(-50%);
+          left: 50%;
+        }
+        .been-sign{
+          background: linear-gradient(303deg, #FF74B9, #6EEBE8, #FFFFFF);
+          -webkit-background-clip: text;
+          color: transparent;
+          text-align: center;
+          margin-bottom: 30rpx;
+          font-size: 46rpx;
+          font-weight: bold;
+        }
+        .tips{
+          color: $info-color;
+          text-align: center;
+          width: 500rpx;
+          margin: 0 auto;
+          border-radius: 30rpx;
+          padding: 12rpx 0;
+          background: #1f2336;
+          font-size: 26rpx;
+        }
+      }
+      .content{
+        margin-top: 40rpx;
+        .item{
+          color: $default-color;
+          width: calc(#{650rpx}/4);
+          background: #1B1E32;
+          border-radius: 10rpx;
+          padding: 28rpx 0;
+          margin-bottom: 20rpx;
+          margin-right: 20rpx;
+          &.active{
+            background: linear-gradient(221deg, #6EEBE8, #FF74B9);
+          }
+          &:nth-child(4){
+            margin-right: 0;
+          }
+          &:last-child{
+            flex: 1;
+            margin-right: 0;
+          }
+          .left-box{
+            &.last{
+              height: 100%;
+              .title{
+                margin-bottom: 10rpx;
+              }
+            }
+            .title{
+              font-size: 32rpx;
+            }
+            .icon{
+              margin-top: 14rpx;
+              image{
+                width: 100rpx;
+                height: 100rpx;
+              }
+            }
+            .number{
+              color: $primary-color;
+            }
+          }
+          .right-box{
+            margin-bottom: -100rpx;
+            margin-right: -20rpx;
+            margin-left: 40rpx;
+            image{
+              width: 160rpx;
+              height: 160rpx;
+            }
+          }
+        }
+      }
+      .btn{
+        width: 690rpx;
+        margin: 80rpx auto 0;
+        padding: 24rpx 0;
+        color: $default-color;
+        background: linear-gradient(270deg, #FB3651 0%, #FF8999 100%);
+        border-radius: 45px;
+        text-align: center;
+      }
+    }
+</style>

+ 160 - 0
mini/pages/trace/collect.vue

xqd
@@ -0,0 +1,160 @@
+<template>
+  <view class="collect">
+    <view
+      v-for="(item,index) in collect"
+      :key="index"
+      class="item main-left"
+    >
+      <view class="cover-image">
+        <image :src="item.episode.cover_img" />
+      </view>
+      <view class="right-box">
+        <view class="op-group main-right cross-center">
+          <view class="delete" @click="handleDelete(item)">删除</view>
+          <view class="play" @click="handlePlay(item)">播放</view>
+        </view>
+        <view class="name">{{ item.episode.name }}</view>
+        <view class="status-box">
+          <text class="status">{{ item.episode.status_text }}</text>
+          <text>共{{ item.episode.total }}集</text>
+        </view>
+        <view class="record">
+          上次看至 <text>第{{ item.watch_record.detail.sort }}集</text>
+        </view>
+      </view>
+    </view>
+    <!--删除  收藏-->
+    <u-modal
+      :show="modal.show"
+      :content="`确定删除收藏【${modal.item.episode.name}】?`"
+      show-cancel-button
+      @confirm="destroy"
+      @cancel="modal.show = false"
+    />
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      limit: 10,
+      page: 1,
+      isMore: true,
+      collect: [],
+      modal: {
+        show: false,
+        item: {}
+      }
+
+    }
+  },
+  computed: {},
+  methods: {
+    handlePlay(item) {
+      this.$u.route({
+        url: '/pages/episode/play',
+        params: {
+          id: item.episode.id,
+          list_id: item.watch_record.list_id
+        }
+      })
+    },
+    handleDelete(item) {
+      this.modal.item = item
+      this.modal.show = true
+    },
+    destroy() {
+      this.$api.user.collect.destroy(this.modal.item.episode_id).then(res => {
+        this.$u.toast('删除成功')
+        this.modal.show = false
+        this.collect = []
+        this.page = 1
+        this.isMore = true
+        this.getCollect()
+      })
+    },
+    getCollect() {
+      this.$loading()
+      this.$api.user.collect.record({ limit: this.limit, page: this.page }).then(res => {
+        this.$hideLoading()
+        if (res.data.length) {
+          this.collect = this.collect.concat(res.data)
+        } else {
+          this.isMore = false
+        }
+      })
+    }
+  },
+  onLoad() {
+    this.getCollect()
+  },
+  onReachBottom(e) {
+    if (!this.isMore) return
+    this.page += 1
+    this.getCollect()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .collect{
+    padding: 20rpx;
+    color: $default-color ;
+    font-size: 30rpx;
+    .item{
+      padding: 30rpx;
+      border: 1rpx solid $primary-color;
+      border-radius: 30rpx;
+      margin-top: 30rpx;
+      .cover-image{
+        image{
+          width: 200rpx;
+          height: 280rpx;
+          border-radius: 16rpx;
+        }
+      }
+      .right-box{
+        flex: 1;
+        margin-left: 20rpx;
+        .op-group{
+          .delete{
+            border: 1rpx solid #FB3651;
+            color: #FB3651;
+            border-radius: 30rpx;
+            width: 140rpx;
+            padding: 10rpx;
+            text-align: center;
+            margin-right: 30rpx;
+          }
+          .play{
+            background: linear-gradient(90deg, #FF74B9,#6EEBE8);
+            color: $default-color;
+            border-radius: 30rpx;
+            width: 140rpx;
+            padding: 10rpx;
+            text-align: center;
+          }
+        }
+        .name{
+          font-size: 38rpx;
+          font-weight: 600;
+          margin-bottom: 10rpx;
+          margin-top: 50rpx;
+        }
+        .status-box{
+          margin-bottom: 10rpx;
+          .status{
+            color: $primary-color;
+            margin-right: 14rpx;
+          }
+        }
+        .record{
+          text{
+            color: #FB3651;
+          }
+        }
+      }
+    }
+  }
+</style>

+ 161 - 0
mini/pages/trace/index.vue

xqd
@@ -0,0 +1,161 @@
+<template>
+  <view class="trace-container">
+    <!--我的收藏-->
+    <view class="collect-container">
+      <view class="header main-between cross-center" @click="$u.route('/pages/trace/collect')">
+        <text>我的收藏</text>
+        <view class="more main-left cross-center">查看全部
+          <u-icon name="arrow-right" />
+        </view>
+      </view>
+      <!--剧集-->
+      <view class="content main-left cross-center">
+        <episode
+          v-for="(item,index) in collect"
+          :key="index"
+          :episode="item.episode"
+          :custom-style="{
+            marginRight: ((index+1) % 4 !== 0 ? '10rpx' :''),
+            width: ((710 - 60) / 4 )+'rpx' // 60为间隔 710为 collect-container宽度
+          }"
+          :image-style="{
+            height: '200rpx'
+          }"
+        />
+      </view>
+    </view>
+    <!--其他剧集-->
+    <view class="title">其他剧集</view>
+    <view
+      v-for="(item,index) in category"
+      :key="index"
+      class="category main-left"
+    >
+      <view class="name" @click="$u.route({url: '/pages/trace/list', params:{cateId: item.id}})">{{ item.name }}</view>
+      <view class="episode-box dir-left-wrap">
+        <episode
+          v-for="(episode,episodeIndex) in item.episodes"
+          :key="episodeIndex"
+          :episode="episode"
+          :custom-style="{
+            marginRight: ((episodeIndex + 1) % 3 !== 0 ? '10rpx' :''),
+            width: ((700 - 80) / 3 )+'rpx'
+          }"
+          :image-style="{
+            height: '280rpx'
+          }"
+        />
+      </view>
+    </view>
+    <!--tab bar-->
+    <tab-bar />
+  </view>
+</template>
+
+<script>
+import Episode from '../../components/Episode/index'
+import TabBar from '../../components/TabBar/index'
+export default {
+  name: 'Trace',
+  components: { TabBar, Episode },
+  data() {
+    return {
+      collect: [],
+      category: []
+    }
+  },
+  computed: {},
+  methods: {
+    getCollect() {
+      this.$api.user.collect.record({ limit: 4 }).then(res => {
+        this.collect = res.data
+      })
+    },
+    getTrace() {
+      this.$loading()
+      this.$api.episode.trace().then(res => {
+        this.$hideLoading()
+        this.category = res.data
+      })
+    }
+  },
+  onLoad() {
+    this.getCollect()
+    this.getTrace()
+  },
+  onShow() {
+    this.getCollect()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+    .trace-container {
+      padding: 20rpx 0 120rpx;
+      font-size: 30rpx;
+      color: $default-color;
+      .status-box{
+        margin-top: 10rpx;
+        color: $info-color;
+        font-size: 28rpx;
+        .status{
+          color: $primary-color;
+          margin-right: 14rpx;
+        }
+      }
+      .collect-container{
+        background: $bg-op-color;
+        border-radius: 30rpx;
+        padding: 20rpx;
+        width: 710rpx;
+        margin: 0 auto;
+        .header{
+          text{
+            color: $primary-color;
+            font-size: 38rpx;
+            font-weight: 800;
+          }
+          .more{
+            color: $info-color;
+          }
+        }
+        .content{
+          margin-top: 20rpx;
+          min-height: 200rpx;
+          .collect-item{
+            .cover-image{
+              image{
+                width: 200rpx;
+                height: 280rpx;
+              }
+            }
+            .name{
+              margin-top: 10rpx;
+            }
+          }
+        }
+      }
+      .title{
+        color: $primary-color;
+        font-size: 38rpx;
+        font-weight: 800;
+        padding: 0 20rpx;
+        margin: 40rpx 0 30rpx;
+      }
+      .category{
+        margin-bottom: 30rpx;
+        .name{
+          background: #284150;
+          writing-mode: vertical-lr;
+          padding: 0 20rpx;
+          border-top-right-radius: 30rpx;
+          border-bottom-right-radius: 30rpx;
+          text-align: center;
+          min-height: 400rpx;
+        }
+        .episode-box{
+          margin-left: 20rpx;
+        }
+      }
+    }
+</style>

+ 59 - 0
mini/pages/trace/list.vue

xqd
@@ -0,0 +1,59 @@
+<template>
+  <view class="free-container dir-left-wrap">
+    <episode
+      v-for="(episode,index) in episodes"
+      :key="index"
+      :episode="episode"
+      :rank="episode.rank"
+      :custom-style="{
+        marginRight: ((index+1) % 3 !== 0 ? '20rpx' :''),
+      }"
+    />
+  </view>
+</template>
+
+<script>
+import Episode from '../../components/Episode/index'
+export default {
+  name: 'Free',
+  components: { Episode },
+  data() {
+    return {
+      cateId: 0,
+      limit: 30,
+      page: 1,
+      isMore: true,
+      episodes: []
+    }
+  },
+  computed: {},
+  methods: {
+    getLists() {
+      this.loading = true
+      this.$api.episode.list({ cateId: this.cateId, page: this.page }).then(res => {
+        this.loading = false
+        if (res.data.length) {
+          this.episodes = this.episodes.concat(res.data)
+        } else {
+          this.isMore = false
+        }
+      })
+    }
+  },
+  onLoad(options) {
+    this.cateId = options.cateId
+    this.getLists()
+  },
+  onReachBottom(e) {
+    if (!this.isMore) return
+    this.page += 1
+    this.getLists()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .free-container {
+    padding: 20rpx;
+  }
+</style>

+ 44 - 0
mini/project.config.json

xqd
@@ -0,0 +1,44 @@
+{
+    "appid": "wx4d0105fabd19dfe2",
+    "compileType": "miniprogram",
+    "libVersion": "2.24.2",
+    "packOptions": {
+        "ignore": [],
+        "include": []
+    },
+    "setting": {
+        "urlCheck": true,
+        "coverView": true,
+        "es6": true,
+        "postcss": true,
+        "lazyloadPlaceholderEnable": false,
+        "preloadBackgroundData": false,
+        "minified": true,
+        "autoAudits": false,
+        "uglifyFileName": false,
+        "uploadWithSourceMap": true,
+        "enhance": true,
+        "useMultiFrameRuntime": true,
+        "showShadowRootInWxmlPanel": true,
+        "packNpmManually": false,
+        "packNpmRelationList": [],
+        "minifyWXSS": true,
+        "useStaticServer": true,
+        "showES6CompileOption": false,
+        "checkInvalidKey": true,
+        "babelSetting": {
+            "ignore": [],
+            "disablePlugins": [],
+            "outputPath": ""
+        },
+        "disableUseStrict": false,
+        "useCompilerPlugins": false,
+        "minifyWXML": true
+    },
+    "condition": {},
+    "editorSetting": {
+        "tabIndent": "insertSpaces",
+        "tabSize": 4
+    },
+    "description": "项目配置文件,详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html"
+}

+ 7 - 0
mini/project.private.config.json

xqd
@@ -0,0 +1,7 @@
+{
+    "projectname": "user",
+    "setting": {
+        "compileHotReLoad": true
+    },
+    "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html"
+}

+ 15 - 0
mini/setting.js

xqd
@@ -0,0 +1,15 @@
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/3/18.
+ */
+const IS_DEV = process.env.NODE_ENV === 'development'
+const URL = 'https://zhangsiye.9026.com'
+// const URL = 'https://t3.9026.com'
+
+module.exports = {
+  // 版本
+  VERSION: '0.0.1',
+  // API 接口URL
+  BASE_URL: IS_DEV ? 'https://zhangsiye.9026.com/api' : URL + '/api',
+  // API 接口URL
+  IMAGE_URL: IS_DEV ? 'http://www.zsy.me/static/image' : URL + '/static/image'
+}

+ 17 - 0
mini/static/css/common.scss

xqd
@@ -0,0 +1,17 @@
+view,image,text,rich-text,progress,button,input,form,label,textarea{
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+}
+
+page,
+uni-page-body{
+  box-sizing: border-box;
+  height: 100%;
+  background: $bg-color;
+  padding-bottom: 80rpx;
+}
+.hidden{
+  display: flex;
+  visibility: hidden;
+}

+ 266 - 0
mini/static/css/flex.scss

xqd
@@ -0,0 +1,266 @@
+.dir-left-nowrap {
+    /* 主轴 排列方式从左侧开始 不换行*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: row;
+    flex-direction: row;
+    flex-wrap: nowrap;
+}
+
+.dir-left-wrap {
+    /* 主轴 排列方式从左侧开始 换行*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: row;
+    flex-direction: row;
+    flex-wrap: wrap;
+}
+
+.dir-left-wrap-reverse {
+    /* 主轴 排列方式从左侧开始 换行 第一行在下方*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: row;
+    flex-direction: row;
+    flex-wrap: wrap-reverse;
+}
+
+.dir-right-nowrap {
+    /* 主轴 排列方式从 右侧开始 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: row-reverse;
+    flex-direction: row-reverse;
+    flex-wrap: nowrap;
+}
+
+.dir-right-wrap {
+    /* 主轴 排列方式从 右侧开始 换行*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: row;
+    flex-direction: row;
+    flex-wrap: wrap;
+}
+
+.dir-right-wrap-reverse {
+    /* 主轴 排列方式从 右侧开始 换行 第一行在下方*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: row;
+    flex-direction: row;
+    flex-wrap: wrap-reverse;
+}
+
+.dir-top-nowrap {
+    /* 主轴 排列方式从顶部开始 不换行 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-box-orient: vertical;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    flex-wrap: nowrap;
+}
+
+.dir-top-wrap {
+    /* 主轴 排列方式从顶部开始  换行*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-box-orient: vertical;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    flex-wrap: wrap;
+}
+
+.dir-top-wrap-reverse {
+    /* 主轴 排列方式从顶部开始 不换行换行 第一行在下方*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-box-orient: vertical;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    flex-wrap: wrap-reverse;
+}
+
+.dir-bottom-nowrap {
+    /* 主轴 排列方式从底部开始 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: column-reverse;
+    flex-direction: column-reverse;
+    flex-wrap: nowrap;
+}
+
+.dir-bottom-wrap {
+    /* 主轴 排列方式从底部开始 不换行 换行*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-box-orient: vertical;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    flex-wrap: wrap;
+}
+
+.dir-bottom-wrap-reverse {
+    /* 主轴 排列方式从底部开始 不换行换行 第一行在下方*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-box-orient: vertical;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    flex-wrap: wrap-reverse;
+}
+
+.main-left {
+    /* 主轴 左对齐 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+}
+
+.main-right {
+    /* 主轴 右对齐 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-justify-content: flex-end;
+    justify-content: flex-end;
+}
+
+.main-between {
+    /* 主轴 两端对齐 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-justify-content: space-between;
+    justify-content: space-between;
+}
+
+.main-center {
+    /* 主轴 居中对齐 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-box-pack: center;
+    -webkit-justify-content: center;
+    -ms-flex-pack: center;
+    justify-content: center;
+}
+
+.main-around {
+    /* 主轴 项目位于各行之前、之间、之后都留有空白的容器内*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-justify-content: space-around;
+    justify-content: space-around;
+}
+
+.cross-top {
+    /* 交叉轴 起点对齐 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: flex-start;
+    align-items: flex-start;
+}
+
+.cross-bottom {
+    /* 交叉轴 终点对齐 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-box-align: end;
+    -webkit-align-items: flex-end;
+    -ms-flex-align: end;
+    -ms-grid-row-align: flex-end;
+    align-items: flex-end;
+}
+
+.cross-baseline {
+    /* 交叉轴 第一行文字基线对齐 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: baseline;
+    align-items: baseline;
+}
+
+.cross-center {
+    /* 交叉轴 居中对齐 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+}
+
+.cross-stretch {
+    /* 交叉轴 高度并排铺满 高度不固定*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: stretch;
+    align-items: stretch;
+}
+
+.flex-wrap {
+    /* 流模式 第一行在上方 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-wrap: wrap;
+    flex-wrap: wrap;
+}
+
+.flex-wrap-reverse {
+    /* 流模式 第一行在下方 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-wrap: wrap-reverse;
+    flex-wrap: wrap-reverse;
+}
+
+.box-grow-0 {
+    /* flex 子元素固定宽度*/
+    min-width: 0;
+    -webkit-box-flex: 0;
+    -webkit-flex-grow: 0;
+    -ms-flex-positive: 0;
+    flex-grow: 0;
+    -webkit-flex-shrink: 0;
+    -ms-flex-negative: 0;
+    flex-shrink: 0;
+}
+
+.box-grow-1 {
+    /* flex 子元素等分 */
+    min-width: 0;
+    -webkit-box-flex: 1;
+    -webkit-flex-grow: 1;
+    -ms-flex-positive: 1;
+    flex-grow: 1;
+    -webkit-flex-shrink: 1;
+    -ms-flex-negative: 1;
+    flex-shrink: 1;
+}
+
+.inline-flex {
+    display: -webkit-inline-flex;
+    display: inline-flex;
+}

File diff suppressed because it is too large
+ 3 - 0
mini/static/css/iconfont.css


+ 12 - 0
mini/static/css/mixin.scss

xqd
@@ -0,0 +1,12 @@
+@mixin vipBorder(){
+  border: 4rpx solid;
+  border-image: linear-gradient(to bottom, #F58A82,#733213) 1;
+  background: linear-gradient(#783518,#EC857C);
+}
+
+@mixin background(){
+  background: linear-gradient(#4C453D, #201B15);
+}
+@mixin backgroundCancel(){
+  background: linear-gradient(#f5f7fa 0%, #c3cfe2 100%);
+}

+ 172 - 0
mini/static/css/store-buy.scss

xqd
@@ -0,0 +1,172 @@
+@import "@/static/css/mixin";
+.store-buy {
+  background: url("@/static/image/buy-bg.jpg") no-repeat top;
+  background-size: 100% 40vh;
+  background-repeat: no-repeat;
+  padding-top: 50rpx;
+  .card{
+    width: 720rpx;
+    margin: 0 auto;
+  }
+  .card-info{
+    background: url("/static/image/coupon_wait_use.png") no-repeat top right;
+    width: 700rpx;
+    flex-shrink: 0;
+    height: 190rpx;
+    background-size: 100% 100%;
+    .left-box{
+      flex: 1;
+      padding: 0 20rpx;
+      .price{
+        white-space: nowrap;
+        color: #888d93;
+        font-size: 28rpx;
+        .num{
+          color: #E8B388;
+          font-size: 60rpx;
+        }
+      }
+      .info-box{
+        margin-left: 20rpx;
+        .tags{
+          margin-top: 10rpx;
+          .tag{
+            background: #FDE7DA;
+            color: #424751;
+            padding: 3rpx 8rpx;
+            margin-right: 8rpx;
+            font-size: 22rpx;
+            border-radius: 6rpx;
+          }
+        }
+      }
+    }
+    .right-box{
+      width: 165rpx;
+    }
+  }
+  .price-tags{
+    margin-bottom: 50rpx;
+    margin-top: 20rpx;
+    padding: 0 20rpx;
+    .price-tag{
+      flex: 1;
+      color: #fff;
+      font-size: 26rpx;
+      padding: 4rpx 6rpx;
+      white-space: nowrap;
+      &.normal-price{
+        background: linear-gradient(#858A90,#4C525E);
+      }
+      &.sale-price{
+        background: linear-gradient(#504740,#1F1A14);
+        margin: 0 50rpx;
+      }
+      &.vip-price{
+        @include vipBorder;
+        border-width: 1rpx;
+      }
+    }
+  }
+  .store-info{
+    background: #fff;
+    border-radius: 10rpx;
+    padding: 10rpx 30rpx;
+  }
+  .desc{
+    .content{
+      color: $text-deep-grey-color;
+    }
+  }
+  .buy-btn{
+    @include background;
+    color: #fff;
+    width: 90vw;
+    margin: 80rpx auto 0rpx;
+    padding: 20rpx 0;
+    border-radius: 10rpx;
+    bottom: 0;
+    text-align: center;
+  }
+  .buy-popup{
+    padding: 30rpx 0;
+    font-size: 26rpx;
+    .goods-box{
+      padding: 0 30rpx;
+      .cover-img{
+        width: 240rpx;
+        height: 200rpx;
+        >image{
+          width: 100%;
+          height: 100%;
+        }
+      }
+      .goods-info{
+        background: #FEF9F6;
+        flex: 1;
+        padding: 20rpx;
+      }
+    }
+    .spec-box{
+      padding: 0 30rpx;
+      font-size: 26rpx;
+      .name{
+        color: $text-black-color;
+      }
+    }
+    .spec-detail{
+      box-shadow: 0 0 10rpx rgba(0,0,0,.15);
+      border-radius: 12rpx;
+      padding: 32rpx 16rpx;
+      .spec-name{
+        color: $text-black-color;
+        text-align: center;
+        padding-bottom: 22rpx;
+      }
+      .detail-box{
+        height: 150rpx;
+        .detail-item{
+          padding: 12rpx 0;
+          text-align: center;
+          color: $text-grey-color;
+          >text{
+            flex: 1;
+          }
+          .name{
+            flex: 2;
+          }
+        }
+      }
+    }
+    .price-box{
+      padding: 16rpx 32rpx;
+      position: relative;
+      margin-bottom: 16rpx;
+      margin-top: 16rpx;
+      &:after{
+        content: "";
+        position: absolute;
+        height: 1rpx;
+        width: 95%;
+        background: $grey-color;
+        bottom: 0;
+        left: 50%;
+        transform: translateX(-50%);
+      }
+      .normal-price{
+        flex: 1;
+        text{
+          margin-right: 16rpx;
+        }
+      }
+      .goods-num{
+        display: flex;
+        flex: 1;
+        justify-content: flex-end;
+      }
+    }
+    .buy-btn{
+      margin-top: 30rpx;
+    }
+  }
+}

+ 12 - 0
mini/static/css/variable.scss

xqd
@@ -0,0 +1,12 @@
+$bg-color: #151728;
+
+$bg-op-color: #1B203C;
+$bg-trans-color: rgba(24, 28, 47, 0.8);
+$border-op-color: #232849;
+
+$primary-color: #6EEBE8;
+$dark-color: #48979C;
+$default-color: #FFFFFF;
+$info-color: #6F717F;
+
+$pink-color: #FF74B9;

BIN
mini/static/font/iconfont.eot


+ 46 - 0
mini/static/font/iconfont.svg

xqd
@@ -0,0 +1,46 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
+<metadata>
+Created by FontForge 20170731 at Tue Jun 28 05:30:00 2022
+ By Aleksey,,,
+Created by iconfont
+</metadata>
+<defs>
+<font id="iconfont" horiz-adv-x="1024" >
+  <font-face 
+    font-family="iconfont"
+    font-weight="400"
+    font-stretch="normal"
+    units-per-em="1024"
+    panose-1="2 0 5 3 0 0 0 0 0 0"
+    ascent="896"
+    descent="-128"
+    bbox="0 -128 1077 896"
+    underline-thickness="0"
+    underline-position="10"
+    unicode-range="U+E608-E735"
+  />
+<missing-glyph 
+ />
+    <glyph glyph-name=".notdef" 
+ />
+    <glyph glyph-name=".null" horiz-adv-x="0" 
+ />
+    <glyph glyph-name="nonmarkingreturn" horiz-adv-x="341" 
+ />
+    <glyph glyph-name="dian" unicode="&#xe608;" 
+d="M512 576q51 -1 94 -27.5t68 -70t25 -94.5t-25 -94.5t-68 -70t-94.5 -27t-95.5 25.5t-70 70t-26 96t26 96t70 70t96 26z" />
+    <glyph glyph-name="checked" unicode="&#xe637;" 
+d="M302 481l-72 -71l231 -231l512 512l-72 72l-440 -440zM922 384q0 -111 -57 -206q-54 -93 -147 -147q-95 -57 -206 -57t-206 57q-93 54 -147 147q-57 95 -57 206t57 206q54 93 147 147q95 57 206 57q59 0 113 -16l82 82q-108 36 -195 36q-104 0 -199 -40
+q-92 -39 -163 -110t-110 -163q-40 -95 -40 -199t40 -199q39 -92 110 -163t163 -110q95 -40 199 -40t199 40q92 39 163 110t110 163q40 95 40 199h-102z" />
+    <glyph glyph-name="yue" unicode="&#xe69d;" 
+d="M694 558l2 4q37 46 41 98q2 28 -6.5 51t-28 38t-45.5 18t-53 -8q-39 35 -91 35q-48 0 -85 -31q-29 12 -56.5 9.5t-47.5 -18.5q-19 -14 -28 -37.5t-7 -50.5q5 -52 41 -98l6 -8q-54 -38 -99 -85q-52 -55 -82 -115q-38 -75 -38 -152q0 -127 60 -185q29 -28 69 -37
+q21 -5 46 -4h451q20 0 38 4q40 9 69 37q60 58 60 185q0 80 -41 157q-32 62 -88 119q-40 40 -87 74zM809 65q-26 -26 -73 -25h-446q-18 0 -31 3q-24 5 -42 22q-42 40 -42 143q0 117 105 227q34 36 77 68q23 17 41 28l37 22l-34 26q-14 11 -25 25q-16 20 -23 41.5t-5 38.5
+t11.5 24.5t25.5 6t34 -11.5l21 -12l15 18q11 13 26.5 19.5t32.5 6.5t33 -7.5t27 -21.5l15 -19l22 12q17 9 32 10t24.5 -6.5t11.5 -24.5t-5 -38.5t-23 -41.5q-9 -12 -21 -22l-31 -26l34 -21q36 -23 73 -54q61 -53 97 -111q49 -77 49 -156q-1 -103 -43 -143zM560 467l-47 -98
+l-46 98h-103l85 -163h-74v-31h89v-73h-79v-32h79v-89h98v89h80v32h-80v73h89v31h-74l85 163h-102z" />
+    <glyph glyph-name="wechat" unicode="&#xe735;" horiz-adv-x="1076" 
+d="M410 252q-22 -13 -38 -13q-14 1 -23 9q-6 6 -10 16l-3 9l-80 194q-14 40 -7 51q5 8 20 0q9 -5 14 -10q2 -2 29 -22q35 -26 58.5 -40.5t54.5 -12.5q15 2 26 6l528 251q-75 95 -189 150q-117 56 -252 56q-109 0 -209 -38q-97 -37 -171.5 -104t-115.5 -154q-42 -90 -42 -189
+q0 -113 55 -214q54 -98 149 -166l-24 -142q0 -10 5 -14q7 -5 24 3q30 14 130 83q96 -35 199 -35q110 0 210 38q97 37 171.5 104t114.5 154q43 90 43 189q0 114 -57 216q-555 -344 -610 -375z" />
+  </font>
+</defs></svg>

BIN
mini/static/font/iconfont.ttf


BIN
mini/static/font/iconfont.woff


BIN
mini/static/font/iconfont.woff2


BIN
mini/static/image/default-head-img.png


BIN
mini/static/image/default-movie.png


BIN
mini/static/image/gold-bag.png


BIN
mini/static/image/gold.png


BIN
mini/static/image/member-line-bg.png


BIN
mini/static/image/member-selected-bg.png


BIN
mini/static/image/my-page/contact.png


BIN
mini/static/image/my-page/order.png


BIN
mini/static/image/my-page/protocol.png


BIN
mini/static/image/my-page/recharge.png


BIN
mini/static/image/my-page/share.png


BIN
mini/static/image/my-recharge-bg.png


BIN
mini/static/image/playing.png


BIN
mini/static/image/share-qrcode-bg.png


BIN
mini/static/image/share/alipay.png


BIN
mini/static/image/share/bank.png


BIN
mini/static/image/share/detail.png


BIN
mini/static/image/share/income.png


BIN
mini/static/image/share/order.png


BIN
mini/static/image/share/qrcode.png


BIN
mini/static/image/share/team.png


BIN
mini/static/image/share/wechat.png


BIN
mini/static/image/tab/home-HL.png


BIN
mini/static/image/tab/home.png


Some files were not shown because too many files changed in this diff