Przeglądaj źródła

Merge branch 'developer' of http://git.caimei365.com/caimei365/caimei365-manager-ui into developer

zhengjinyi 1 rok temu
rodzic
commit
d162d83893
40 zmienionych plików z 2978 dodań i 589 usunięć
  1. 2 1
      .env.development
  2. 0 5
      package-lock.json
  3. 2 0
      package.json
  4. 418 0
      pnpm-lock.yaml
  5. 58 0
      src/api/contentLibrary/context.js
  6. 25 0
      src/api/contentLibrary/store.js
  7. 109 0
      src/components/Editor/index.vue
  8. 3 1
      src/router/index.js
  9. 57 0
      src/router/modules/database.js
  10. 1 1
      src/utils/request.js
  11. 189 0
      src/views/dataBase/components/UploadFile.vue
  12. 115 0
      src/views/dataBase/components/data-select.vue
  13. 44 0
      src/views/dataBase/components/dataManageTab.vue
  14. 137 0
      src/views/dataBase/components/dialogTable.vue
  15. 143 0
      src/views/dataBase/components/dialogTypeTable.vue
  16. 44 0
      src/views/dataBase/components/storeItem.vue
  17. 145 0
      src/views/dataBase/mixins/index.js
  18. 228 0
      src/views/dataBase/store-list/data-management/edit.vue
  19. 237 0
      src/views/dataBase/store-list/data-management/list.vue
  20. 190 0
      src/views/dataBase/store-list/edit.vue
  21. 146 0
      src/views/dataBase/store-list/index.vue
  22. 143 39
      src/views/user/record/list.vue
  23. 129 129
      src/views/wechat/components/textForm.vue
  24. 16 16
      src/views/wechat/hehe/article/form.vue
  25. 16 16
      src/views/wechat/hehe/article/list.vue
  26. 13 13
      src/views/wechat/hehe/menus/form.vue
  27. 16 16
      src/views/wechat/hehe/menus/list.vue
  28. 16 16
      src/views/wechat/hehe/reply/form.vue
  29. 16 16
      src/views/wechat/hehe/reply/list.vue
  30. 16 16
      src/views/wechat/hehe/text/form.vue
  31. 16 16
      src/views/wechat/hehe/text/list.vue
  32. 11 11
      tailwind.config.js
  33. 5 5
      tests/unit/.eslintrc.js
  34. 18 18
      tests/unit/components/Hamburger.spec.js
  35. 22 22
      tests/unit/components/SvgIcon.spec.js
  36. 29 29
      tests/unit/utils/formatTime.spec.js
  37. 14 14
      tests/unit/utils/param2Obj.spec.js
  38. 37 37
      tests/unit/utils/parseTime.spec.js
  39. 28 28
      tests/unit/utils/validate.spec.js
  40. 124 124
      vue.config.js

+ 2 - 1
.env.development

@@ -5,6 +5,7 @@ ENV = 'development'
 # VUE_APP_BASE_API = 'http://192.168.2.67:18015'
 # VUE_APP_BASE_API = 'http://localhost:18015'
 # VUE_APP_BASE_API = 'http://192.168.2.102:18015'
+# VUE_APP_BASE_API = 'http://192.168.2.103:18015'
 VUE_APP_BASE_API = 'https://mapi-b.caimei365.com'
 # 图片上传,索引更新等
 VUE_APP_CORE_API = 'https://core-b.caimei365.com'
@@ -13,4 +14,4 @@ VUE_APP_CORE_API = 'https://core-b.caimei365.com'
 VUE_APP_CAIMEI_URL = 'http://120.79.25.27:8009'
 
 # 采美旧后台
-VUE_APP_ADMIN_URL = 'https://admin-b.caimei365.com'
+VUE_APP_ADMIN_URL = 'https://admin-b.caimei365.com'

+ 0 - 5
package-lock.json

@@ -11810,11 +11810,6 @@
       "resolved": "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz",
       "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
     },
-    "moment": {
-      "version": "2.29.4",
-      "resolved": "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz",
-      "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
-    },
     "move-concurrently": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",

+ 2 - 0
package.json

@@ -52,6 +52,8 @@
     "@vue/cli-plugin-unit-jest": "4.4.4",
     "@vue/cli-service": "4.4.4",
     "@vue/test-utils": "1.0.0-beta.29",
+    "@wangeditor/editor": "^5.1.23",
+    "@wangeditor/editor-for-vue": "^1.0.2",
     "autoprefixer": "9.8.8",
     "babel-eslint": "10.1.0",
     "babel-jest": "23.6.0",

+ 418 - 0
pnpm-lock.yaml

@@ -112,6 +112,12 @@ devDependencies:
   '@vue/test-utils':
     specifier: 1.0.0-beta.29
     version: 1.0.0-beta.29(vue-template-compiler@2.6.10)(vue@2.6.10)
+  '@wangeditor/editor':
+    specifier: ^5.1.23
+    version: 5.1.23
+  '@wangeditor/editor-for-vue':
+    specifier: ^1.0.2
+    version: 1.0.2(@wangeditor/editor@5.1.23)(vue@2.6.10)
   autoprefixer:
     specifier: 9.8.8
     version: 9.8.8
@@ -1829,6 +1835,10 @@ packages:
       - ts-node
     dev: true
 
+  /@transloadit/prettier-bytes@0.0.7:
+    resolution: {integrity: sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==, tarball: https://registry.npmmirror.com/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz}
+    dev: true
+
   /@trysound/sax@0.2.0:
     resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==, tarball: https://registry.npmmirror.com/@trysound/sax/-/sax-0.2.0.tgz}
     engines: {node: '>=10.13.0'}
@@ -1863,6 +1873,10 @@ packages:
       '@babel/types': 7.23.5
     dev: true
 
+  /@types/event-emitter@0.3.5:
+    resolution: {integrity: sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==, tarball: https://registry.npmmirror.com/@types/event-emitter/-/event-emitter-0.3.5.tgz}
+    dev: true
+
   /@types/glob@7.2.0:
     resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==, tarball: https://registry.npmmirror.com/@types/glob/-/glob-7.2.0.tgz}
     dependencies:
@@ -1941,6 +1955,47 @@ packages:
       '@types/yargs-parser': 21.0.3
     dev: true
 
+  /@uppy/companion-client@2.2.2:
+    resolution: {integrity: sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==, tarball: https://registry.npmmirror.com/@uppy/companion-client/-/companion-client-2.2.2.tgz}
+    dependencies:
+      '@uppy/utils': 4.1.3
+      namespace-emitter: 2.0.1
+    dev: true
+
+  /@uppy/core@2.3.4:
+    resolution: {integrity: sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==, tarball: https://registry.npmmirror.com/@uppy/core/-/core-2.3.4.tgz}
+    dependencies:
+      '@transloadit/prettier-bytes': 0.0.7
+      '@uppy/store-default': 2.1.1
+      '@uppy/utils': 4.1.3
+      lodash.throttle: 4.1.1
+      mime-match: 1.0.2
+      namespace-emitter: 2.0.1
+      nanoid: 3.3.7
+      preact: 10.19.3
+    dev: true
+
+  /@uppy/store-default@2.1.1:
+    resolution: {integrity: sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ==, tarball: https://registry.npmmirror.com/@uppy/store-default/-/store-default-2.1.1.tgz}
+    dev: true
+
+  /@uppy/utils@4.1.3:
+    resolution: {integrity: sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==, tarball: https://registry.npmmirror.com/@uppy/utils/-/utils-4.1.3.tgz}
+    dependencies:
+      lodash.throttle: 4.1.1
+    dev: true
+
+  /@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4):
+    resolution: {integrity: sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==, tarball: https://registry.npmmirror.com/@uppy/xhr-upload/-/xhr-upload-2.1.3.tgz}
+    peerDependencies:
+      '@uppy/core': ^2.3.3
+    dependencies:
+      '@uppy/companion-client': 2.2.2
+      '@uppy/core': 2.3.4
+      '@uppy/utils': 4.1.3
+      nanoid: 3.3.7
+    dev: true
+
   /@vue/babel-helper-vue-jsx-merge-props@1.4.0:
     resolution: {integrity: sha512-JkqXfCkUDp4PIlFdDQ0TdXoIejMtTHP67/pvxlgeY+u5k3LEdKuWZ3LK6xkxo52uDoABIVyRwqVkfLQJhk7VBA==, tarball: https://registry.npmmirror.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.4.0.tgz}
     dev: true
@@ -2446,6 +2501,192 @@ packages:
     resolution: {integrity: sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA==, tarball: https://registry.npmmirror.com/@vue/web-component-wrapper/-/web-component-wrapper-1.3.0.tgz}
     dev: true
 
+  /@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19)(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.7)(slate@0.72.8)(snabbdom@3.5.1):
+    resolution: {integrity: sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==, tarball: https://registry.npmmirror.com/@wangeditor/basic-modules/-/basic-modules-1.1.7.tgz}
+    peerDependencies:
+      '@wangeditor/core': 1.x
+      dom7: ^3.0.0
+      lodash.throttle: ^4.1.1
+      nanoid: ^3.2.0
+      slate: ^0.72.0
+      snabbdom: ^3.1.0
+    dependencies:
+      '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.7)(slate@0.72.8)(snabbdom@3.5.1)
+      dom7: 3.0.0
+      is-url: 1.2.4
+      lodash.throttle: 4.1.1
+      nanoid: 3.3.7
+      slate: 0.72.8
+      snabbdom: 3.5.1
+    dev: true
+
+  /@wangeditor/code-highlight@1.0.3(@wangeditor/core@1.1.19)(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.5.1):
+    resolution: {integrity: sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==, tarball: https://registry.npmmirror.com/@wangeditor/code-highlight/-/code-highlight-1.0.3.tgz}
+    peerDependencies:
+      '@wangeditor/core': 1.x
+      dom7: ^3.0.0
+      slate: ^0.72.0
+      snabbdom: ^3.1.0
+    dependencies:
+      '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.7)(slate@0.72.8)(snabbdom@3.5.1)
+      dom7: 3.0.0
+      prismjs: 1.29.0
+      slate: 0.72.8
+      snabbdom: 3.5.1
+    dev: true
+
+  /@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.7)(slate@0.72.8)(snabbdom@3.5.1):
+    resolution: {integrity: sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==, tarball: https://registry.npmmirror.com/@wangeditor/core/-/core-1.1.19.tgz}
+    peerDependencies:
+      '@uppy/core': ^2.1.1
+      '@uppy/xhr-upload': ^2.0.3
+      dom7: ^3.0.0
+      is-hotkey: ^0.2.0
+      lodash.camelcase: ^4.3.0
+      lodash.clonedeep: ^4.5.0
+      lodash.debounce: ^4.0.8
+      lodash.foreach: ^4.5.0
+      lodash.isequal: ^4.5.0
+      lodash.throttle: ^4.1.1
+      lodash.toarray: ^4.4.0
+      nanoid: ^3.2.0
+      slate: ^0.72.0
+      snabbdom: ^3.1.0
+    dependencies:
+      '@types/event-emitter': 0.3.5
+      '@uppy/core': 2.3.4
+      '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
+      dom7: 3.0.0
+      event-emitter: 0.3.5
+      html-void-elements: 2.0.1
+      i18next: 20.6.1
+      is-hotkey: 0.2.0
+      lodash.camelcase: 4.3.0
+      lodash.clonedeep: 4.5.0
+      lodash.debounce: 4.0.8
+      lodash.foreach: 4.5.0
+      lodash.isequal: 4.5.0
+      lodash.throttle: 4.1.1
+      lodash.toarray: 4.4.0
+      nanoid: 3.3.7
+      scroll-into-view-if-needed: 2.2.31
+      slate: 0.72.8
+      slate-history: 0.66.0(slate@0.72.8)
+      snabbdom: 3.5.1
+    dev: true
+
+  /@wangeditor/editor-for-vue@1.0.2(@wangeditor/editor@5.1.23)(vue@2.6.10):
+    resolution: {integrity: sha512-BOENvAXJVtVXlE2X50AAvjV82YlCUeu5cbeR0cvEQHQjYtiVnJtq7HSoj85r2kTgGouI5OrpJG9BBEjSjUSPyA==, tarball: https://registry.npmmirror.com/@wangeditor/editor-for-vue/-/editor-for-vue-1.0.2.tgz}
+    peerDependencies:
+      '@wangeditor/editor': '>=5.1.0'
+      vue: ^2.6.14
+    dependencies:
+      '@wangeditor/editor': 5.1.23
+      vue: 2.6.10
+    dev: true
+
+  /@wangeditor/editor@5.1.23:
+    resolution: {integrity: sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==, tarball: https://registry.npmmirror.com/@wangeditor/editor/-/editor-5.1.23.tgz}
+    dependencies:
+      '@uppy/core': 2.3.4
+      '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
+      '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19)(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.7)(slate@0.72.8)(snabbdom@3.5.1)
+      '@wangeditor/code-highlight': 1.0.3(@wangeditor/core@1.1.19)(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.5.1)
+      '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.7)(slate@0.72.8)(snabbdom@3.5.1)
+      '@wangeditor/list-module': 1.0.5(@wangeditor/core@1.1.19)(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.5.1)
+      '@wangeditor/table-module': 1.1.4(@wangeditor/core@1.1.19)(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.7)(slate@0.72.8)(snabbdom@3.5.1)
+      '@wangeditor/upload-image-module': 1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(@wangeditor/basic-modules@1.1.7)(@wangeditor/core@1.1.19)(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.5.1)
+      '@wangeditor/video-module': 1.1.4(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(@wangeditor/core@1.1.19)(dom7@3.0.0)(nanoid@3.3.7)(slate@0.72.8)(snabbdom@3.5.1)
+      dom7: 3.0.0
+      is-hotkey: 0.2.0
+      lodash.camelcase: 4.3.0
+      lodash.clonedeep: 4.5.0
+      lodash.debounce: 4.0.8
+      lodash.foreach: 4.5.0
+      lodash.isequal: 4.5.0
+      lodash.throttle: 4.1.1
+      lodash.toarray: 4.4.0
+      nanoid: 3.3.7
+      slate: 0.72.8
+      snabbdom: 3.5.1
+    dev: true
+
+  /@wangeditor/list-module@1.0.5(@wangeditor/core@1.1.19)(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.5.1):
+    resolution: {integrity: sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==, tarball: https://registry.npmmirror.com/@wangeditor/list-module/-/list-module-1.0.5.tgz}
+    peerDependencies:
+      '@wangeditor/core': 1.x
+      dom7: ^3.0.0
+      slate: ^0.72.0
+      snabbdom: ^3.1.0
+    dependencies:
+      '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.7)(slate@0.72.8)(snabbdom@3.5.1)
+      dom7: 3.0.0
+      slate: 0.72.8
+      snabbdom: 3.5.1
+    dev: true
+
+  /@wangeditor/table-module@1.1.4(@wangeditor/core@1.1.19)(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.7)(slate@0.72.8)(snabbdom@3.5.1):
+    resolution: {integrity: sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==, tarball: https://registry.npmmirror.com/@wangeditor/table-module/-/table-module-1.1.4.tgz}
+    peerDependencies:
+      '@wangeditor/core': 1.x
+      dom7: ^3.0.0
+      lodash.isequal: ^4.5.0
+      lodash.throttle: ^4.1.1
+      nanoid: ^3.2.0
+      slate: ^0.72.0
+      snabbdom: ^3.1.0
+    dependencies:
+      '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.7)(slate@0.72.8)(snabbdom@3.5.1)
+      dom7: 3.0.0
+      lodash.isequal: 4.5.0
+      lodash.throttle: 4.1.1
+      nanoid: 3.3.7
+      slate: 0.72.8
+      snabbdom: 3.5.1
+    dev: true
+
+  /@wangeditor/upload-image-module@1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(@wangeditor/basic-modules@1.1.7)(@wangeditor/core@1.1.19)(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.5.1):
+    resolution: {integrity: sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==, tarball: https://registry.npmmirror.com/@wangeditor/upload-image-module/-/upload-image-module-1.0.2.tgz}
+    peerDependencies:
+      '@uppy/core': ^2.0.3
+      '@uppy/xhr-upload': ^2.0.3
+      '@wangeditor/basic-modules': 1.x
+      '@wangeditor/core': 1.x
+      dom7: ^3.0.0
+      lodash.foreach: ^4.5.0
+      slate: ^0.72.0
+      snabbdom: ^3.1.0
+    dependencies:
+      '@uppy/core': 2.3.4
+      '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
+      '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19)(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.7)(slate@0.72.8)(snabbdom@3.5.1)
+      '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.7)(slate@0.72.8)(snabbdom@3.5.1)
+      dom7: 3.0.0
+      lodash.foreach: 4.5.0
+      slate: 0.72.8
+      snabbdom: 3.5.1
+    dev: true
+
+  /@wangeditor/video-module@1.1.4(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(@wangeditor/core@1.1.19)(dom7@3.0.0)(nanoid@3.3.7)(slate@0.72.8)(snabbdom@3.5.1):
+    resolution: {integrity: sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==, tarball: https://registry.npmmirror.com/@wangeditor/video-module/-/video-module-1.1.4.tgz}
+    peerDependencies:
+      '@uppy/core': ^2.1.4
+      '@uppy/xhr-upload': ^2.0.7
+      '@wangeditor/core': 1.x
+      dom7: ^3.0.0
+      nanoid: ^3.2.0
+      slate: ^0.72.0
+      snabbdom: ^3.1.0
+    dependencies:
+      '@uppy/core': 2.3.4
+      '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4)
+      '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3)(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.7)(slate@0.72.8)(snabbdom@3.5.1)
+      dom7: 3.0.0
+      nanoid: 3.3.7
+      slate: 0.72.8
+      snabbdom: 3.5.1
+    dev: true
+
   /@webassemblyjs/ast@1.9.0:
     resolution: {integrity: sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==, tarball: https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.9.0.tgz}
     dependencies:
@@ -4301,6 +4542,10 @@ packages:
       - supports-color
     dev: true
 
+  /compute-scroll-into-view@1.0.20:
+    resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==, tarball: https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz}
+    dev: true
+
   /concat-map@0.0.1:
     resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, tarball: https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz}
     dev: true
@@ -4956,6 +5201,13 @@ packages:
     resolution: {integrity: sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA==, tarball: https://registry.npmmirror.com/cyclist/-/cyclist-1.0.2.tgz}
     dev: true
 
+  /d@1.0.1:
+    resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==, tarball: https://registry.npmmirror.com/d/-/d-1.0.1.tgz}
+    dependencies:
+      es5-ext: 0.10.62
+      type: 1.2.0
+    dev: true
+
   /dashdash@1.14.1:
     resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==, tarball: https://registry.npmmirror.com/dashdash/-/dashdash-1.14.1.tgz}
     engines: {node: '>=0.10'}
@@ -5329,6 +5581,12 @@ packages:
       entities: 4.5.0
     dev: true
 
+  /dom7@3.0.0:
+    resolution: {integrity: sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==, tarball: https://registry.npmmirror.com/dom7/-/dom7-3.0.0.tgz}
+    dependencies:
+      ssr-window: 3.0.0
+    dev: true
+
   /domain-browser@1.2.0:
     resolution: {integrity: sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==, tarball: https://registry.npmmirror.com/domain-browser/-/domain-browser-1.2.0.tgz}
     engines: {node: '>=0.4', npm: '>=1.2'}
@@ -5658,6 +5916,31 @@ packages:
       is-symbol: 1.0.4
     dev: true
 
+  /es5-ext@0.10.62:
+    resolution: {integrity: sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==, tarball: https://registry.npmmirror.com/es5-ext/-/es5-ext-0.10.62.tgz}
+    engines: {node: '>=0.10'}
+    requiresBuild: true
+    dependencies:
+      es6-iterator: 2.0.3
+      es6-symbol: 3.1.3
+      next-tick: 1.1.0
+    dev: true
+
+  /es6-iterator@2.0.3:
+    resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==, tarball: https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz}
+    dependencies:
+      d: 1.0.1
+      es5-ext: 0.10.62
+      es6-symbol: 3.1.3
+    dev: true
+
+  /es6-symbol@3.1.3:
+    resolution: {integrity: sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==, tarball: https://registry.npmmirror.com/es6-symbol/-/es6-symbol-3.1.3.tgz}
+    dependencies:
+      d: 1.0.1
+      ext: 1.7.0
+    dev: true
+
   /escalade@3.1.1:
     resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==, tarball: https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz}
     engines: {node: '>=6'}
@@ -5847,6 +6130,13 @@ packages:
     resolution: {integrity: sha512-jrxnPsCGqng1UZuEp9DecX/AuSyAszATSjf4oEcRxvfxa1Oux4KkIPKBAAWWnpdwfARtr+Q0o9aPYWjsROD7ug==, tarball: https://registry.npmmirror.com/eve-raphael/-/eve-raphael-0.5.0.tgz}
     dev: false
 
+  /event-emitter@0.3.5:
+    resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==, tarball: https://registry.npmmirror.com/event-emitter/-/event-emitter-0.3.5.tgz}
+    dependencies:
+      d: 1.0.1
+      es5-ext: 0.10.62
+    dev: true
+
   /event-pubsub@4.3.0:
     resolution: {integrity: sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==, tarball: https://registry.npmmirror.com/event-pubsub/-/event-pubsub-4.3.0.tgz}
     engines: {node: '>=4.0.0'}
@@ -6018,6 +6308,12 @@ packages:
       - supports-color
     dev: true
 
+  /ext@1.7.0:
+    resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==, tarball: https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz}
+    dependencies:
+      type: 2.7.2
+    dev: true
+
   /extend-shallow@2.0.1:
     resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==, tarball: https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz}
     engines: {node: '>=0.10.0'}
@@ -7090,6 +7386,10 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
+  /html-void-elements@2.0.1:
+    resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==, tarball: https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz}
+    dev: true
+
   /html-webpack-plugin@3.2.0(webpack@4.47.0):
     resolution: {integrity: sha512-Br4ifmjQojUP4EmHnRBoUIYcZ9J7M4bTMcm7u6xoIAIuq2Nte4TzXX0533owvkQKQD1WeMTTTyD4Ni4QKxS0Bg==, tarball: https://registry.npmmirror.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz}
     engines: {node: '>=6.9'}
@@ -7216,6 +7516,12 @@ packages:
       slash: 2.0.0
     dev: true
 
+  /i18next@20.6.1:
+    resolution: {integrity: sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==, tarball: https://registry.npmmirror.com/i18next/-/i18next-20.6.1.tgz}
+    dependencies:
+      '@babel/runtime': 7.23.5
+    dev: true
+
   /iconv-lite@0.4.24:
     resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==, tarball: https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz}
     engines: {node: '>=0.10.0'}
@@ -7257,6 +7563,10 @@ packages:
     resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==, tarball: https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz}
     dev: false
 
+  /immer@9.0.21:
+    resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==, tarball: https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz}
+    dev: true
+
   /import-cwd@2.1.0:
     resolution: {integrity: sha512-Ew5AZzJQFqrOV5BTW3EIoHAnoie1LojZLXKcCQ/yTRyVZosBhK1x1ViYjHGf5pAFOq8ZyChZp6m/fSN7pJyZtg==, tarball: https://registry.npmmirror.com/import-cwd/-/import-cwd-2.1.0.tgz}
     engines: {node: '>=4'}
@@ -7662,6 +7972,10 @@ packages:
       is-extglob: 2.1.1
     dev: true
 
+  /is-hotkey@0.2.0:
+    resolution: {integrity: sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==, tarball: https://registry.npmmirror.com/is-hotkey/-/is-hotkey-0.2.0.tgz}
+    dev: true
+
   /is-lower-case@1.1.3:
     resolution: {integrity: sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==, tarball: https://registry.npmmirror.com/is-lower-case/-/is-lower-case-1.1.3.tgz}
     dependencies:
@@ -7771,6 +8085,11 @@ packages:
       isobject: 3.0.1
     dev: true
 
+  /is-plain-object@5.0.0:
+    resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==, tarball: https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
   /is-posix-bracket@0.1.1:
     resolution: {integrity: sha512-Yu68oeXJ7LeWNmZ3Zov/xg/oDBnBK2RNxwYY1ilNJX+tKKZqgPK+qOn/Gs9jEu66KDY9Netf5XLKNGzas/vPfQ==, tarball: https://registry.npmmirror.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz}
     engines: {node: '>=0.10.0'}
@@ -7863,6 +8182,10 @@ packages:
       upper-case: 1.1.3
     dev: true
 
+  /is-url@1.2.4:
+    resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==, tarball: https://registry.npmmirror.com/is-url/-/is-url-1.2.4.tgz}
+    dev: true
+
   /is-utf8@0.2.1:
     resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==, tarball: https://registry.npmmirror.com/is-utf8/-/is-utf8-0.2.1.tgz}
     dev: true
@@ -8944,6 +9267,14 @@ packages:
       p-locate: 4.1.0
     dev: true
 
+  /lodash.camelcase@4.3.0:
+    resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==, tarball: https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz}
+    dev: true
+
+  /lodash.clonedeep@4.5.0:
+    resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==, tarball: https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz}
+    dev: true
+
   /lodash.debounce@4.0.8:
     resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==, tarball: https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz}
     dev: true
@@ -8952,10 +9283,18 @@ packages:
     resolution: {integrity: sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==, tarball: https://registry.npmmirror.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz}
     dev: true
 
+  /lodash.foreach@4.5.0:
+    resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==, tarball: https://registry.npmmirror.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz}
+    dev: true
+
   /lodash.get@4.4.2:
     resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==, tarball: https://registry.npmmirror.com/lodash.get/-/lodash.get-4.4.2.tgz}
     dev: true
 
+  /lodash.isequal@4.5.0:
+    resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==, tarball: https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz}
+    dev: true
+
   /lodash.kebabcase@4.1.1:
     resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==, tarball: https://registry.npmmirror.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz}
     dev: true
@@ -8976,6 +9315,14 @@ packages:
     resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==, tarball: https://registry.npmmirror.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz}
     dev: true
 
+  /lodash.throttle@4.1.1:
+    resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==, tarball: https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz}
+    dev: true
+
+  /lodash.toarray@4.4.0:
+    resolution: {integrity: sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==, tarball: https://registry.npmmirror.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz}
+    dev: true
+
   /lodash.topath@4.5.2:
     resolution: {integrity: sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg==, tarball: https://registry.npmmirror.com/lodash.topath/-/lodash.topath-4.5.2.tgz}
     dev: true
@@ -9291,6 +9638,12 @@ packages:
     engines: {node: '>= 0.6'}
     dev: true
 
+  /mime-match@1.0.2:
+    resolution: {integrity: sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==, tarball: https://registry.npmmirror.com/mime-match/-/mime-match-1.0.2.tgz}
+    dependencies:
+      wildcard: 1.1.2
+    dev: true
+
   /mime-types@2.1.35:
     resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==, tarball: https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz}
     engines: {node: '>= 0.6'}
@@ -9504,6 +9857,10 @@ packages:
       thenify-all: 1.6.0
     dev: true
 
+  /namespace-emitter@2.0.1:
+    resolution: {integrity: sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==, tarball: https://registry.npmmirror.com/namespace-emitter/-/namespace-emitter-2.0.1.tgz}
+    dev: true
+
   /nan@2.18.0:
     resolution: {integrity: sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==, tarball: https://registry.npmmirror.com/nan/-/nan-2.18.0.tgz}
     requiresBuild: true
@@ -9548,6 +9905,10 @@ packages:
     resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==, tarball: https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz}
     dev: true
 
+  /next-tick@1.1.0:
+    resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==, tarball: https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz}
+    dev: true
+
   /nice-try@1.0.5:
     resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==, tarball: https://registry.npmmirror.com/nice-try/-/nice-try-1.0.5.tgz}
     dev: true
@@ -10890,6 +11251,10 @@ packages:
       posthtml-render: 1.4.0
     dev: true
 
+  /preact@10.19.3:
+    resolution: {integrity: sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==, tarball: https://registry.npmmirror.com/preact/-/preact-10.19.3.tgz}
+    dev: true
+
   /prelude-ls@1.1.2:
     resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==, tarball: https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.1.2.tgz}
     engines: {node: '>= 0.8.0'}
@@ -10950,6 +11315,11 @@ packages:
     hasBin: true
     dev: false
 
+  /prismjs@1.29.0:
+    resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==, tarball: https://registry.npmmirror.com/prismjs/-/prismjs-1.29.0.tgz}
+    engines: {node: '>=6'}
+    dev: true
+
   /private@0.1.8:
     resolution: {integrity: sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==, tarball: https://registry.npmmirror.com/private/-/private-0.1.8.tgz}
     engines: {node: '>= 0.6'}
@@ -11766,6 +12136,12 @@ packages:
       raw-loader: 0.5.1
     dev: false
 
+  /scroll-into-view-if-needed@2.2.31:
+    resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==, tarball: https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz}
+    dependencies:
+      compute-scroll-into-view: 1.0.20
+    dev: true
+
   /select-hose@2.0.0:
     resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==, tarball: https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz}
     dev: true
@@ -12043,6 +12419,23 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
+  /slate-history@0.66.0(slate@0.72.8):
+    resolution: {integrity: sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==, tarball: https://registry.npmmirror.com/slate-history/-/slate-history-0.66.0.tgz}
+    peerDependencies:
+      slate: '>=0.65.3'
+    dependencies:
+      is-plain-object: 5.0.0
+      slate: 0.72.8
+    dev: true
+
+  /slate@0.72.8:
+    resolution: {integrity: sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==, tarball: https://registry.npmmirror.com/slate/-/slate-0.72.8.tgz}
+    dependencies:
+      immer: 9.0.21
+      is-plain-object: 5.0.0
+      tiny-warning: 1.0.3
+    dev: true
+
   /slice-ansi@0.0.4:
     resolution: {integrity: sha512-up04hB2hR92PgjpyU3y/eg91yIBILyjVY26NvvciY3EVVPjybkMszMpXQ9QAkcS3I5rtJBDLoTxxg+qvW8c7rw==, tarball: https://registry.npmmirror.com/slice-ansi/-/slice-ansi-0.0.4.tgz}
     engines: {node: '>=0.10.0'}
@@ -12057,6 +12450,11 @@ packages:
       is-fullwidth-code-point: 2.0.0
     dev: true
 
+  /snabbdom@3.5.1:
+    resolution: {integrity: sha512-wHMNIOjkm/YNE5EM3RCbr/+DVgPg6AqQAX1eOxO46zYNvCXjKP5Y865tqQj3EXnaMBjkxmQA5jFuDpDK/dbfiA==, tarball: https://registry.npmmirror.com/snabbdom/-/snabbdom-3.5.1.tgz}
+    engines: {node: '>=8.3.0'}
+    dev: true
+
   /snake-case@2.1.0:
     resolution: {integrity: sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==, tarball: https://registry.npmmirror.com/snake-case/-/snake-case-2.1.0.tgz}
     dependencies:
@@ -12258,6 +12656,10 @@ packages:
       tweetnacl: 0.14.5
     dev: true
 
+  /ssr-window@3.0.0:
+    resolution: {integrity: sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==, tarball: https://registry.npmmirror.com/ssr-window/-/ssr-window-3.0.0.tgz}
+    dev: true
+
   /ssri@6.0.2:
     resolution: {integrity: sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==, tarball: https://registry.npmmirror.com/ssri/-/ssri-6.0.2.tgz}
     dependencies:
@@ -12858,6 +13260,10 @@ packages:
     resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==, tarball: https://registry.npmmirror.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz}
     dev: false
 
+  /tiny-warning@1.0.3:
+    resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==, tarball: https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz}
+    dev: true
+
   /title-case@2.1.1:
     resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==, tarball: https://registry.npmmirror.com/title-case/-/title-case-2.1.1.tgz}
     dependencies:
@@ -13065,6 +13471,14 @@ packages:
       mime-types: 2.1.35
     dev: true
 
+  /type@1.2.0:
+    resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==, tarball: https://registry.npmmirror.com/type/-/type-1.2.0.tgz}
+    dev: true
+
+  /type@2.7.2:
+    resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==, tarball: https://registry.npmmirror.com/type/-/type-2.7.2.tgz}
+    dev: true
+
   /typed-array-buffer@1.0.0:
     resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==, tarball: https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz}
     engines: {node: '>= 0.4'}
@@ -13899,6 +14313,10 @@ packages:
       isexe: 2.0.0
     dev: true
 
+  /wildcard@1.1.2:
+    resolution: {integrity: sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==, tarball: https://registry.npmmirror.com/wildcard/-/wildcard-1.1.2.tgz}
+    dev: true
+
   /word-wrap@1.2.5:
     resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, tarball: https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz}
     engines: {node: '>=0.10.0'}

+ 58 - 0
src/api/contentLibrary/context.js

@@ -0,0 +1,58 @@
+import request from '@/utils/request'
+
+export const getContextList = (params) => request({
+  url: '/productArchive/content/list',
+  method: 'GET',
+  params
+})
+
+export const getLabelKey = (params) => request({
+  url: process.env.VUE_APP_CORE_API + '/commodity/product/archive/findKeyWordList',
+  method: 'GET',
+  params
+})
+
+export const addContext = (data) => request({
+  url: '/productArchive/content/add',
+  method: 'POST',
+  data
+})
+
+export const getContextDetail = (params) => request({
+  url: '/productArchive/content/from',
+  method: 'GET',
+  params
+})
+
+export const uploadFile = (data) => request({
+  url: '/formData/oss/MultiPictareaddData',
+  method: 'POST',
+  headers: {
+    'Content-Type': 'multipart/form-data'
+  },
+  data
+})
+
+export const uploadFileNew = (data) => request({
+  url: 'https://core-b.caimei365.com/tools/file/upload/oss',
+  method: 'POST',
+  headers: {
+    'Content-Type': 'multipart/form-data'
+  },
+  data
+})
+
+export const deleteContext = (data) => request({
+  url: '/productArchive/content/del',
+  method: 'POST',
+  headers: {
+    'Content-Type': 'multipart/form-data'
+  },
+  data
+})
+
+export const findKeyWordList = (params) => request({
+  url: '/home/findKeyWordList',
+  method: 'GET',
+  params
+})

+ 25 - 0
src/api/contentLibrary/store.js

@@ -0,0 +1,25 @@
+import request from '@/utils/request'
+
+export const getStoreBaseList = (params) => request({
+  url: '/home/findProductList',
+  method: 'GET',
+  params
+})
+
+export const getStoreList = (params) => request({
+  url: '/productArchive/list',
+  method: 'GET',
+  params
+})
+
+export const getStoreDetail = (params) => request({
+  url: '/productArchive/from',
+  method: 'GET',
+  params
+})
+
+export const addStore = (data) => request({
+  url: '/productArchive/add',
+  method: 'POST',
+  data
+})

+ 109 - 0
src/components/Editor/index.vue

@@ -0,0 +1,109 @@
+<template>
+  <div style="border: 1px solid #ccc;">
+    <Toolbar
+      style="border-bottom: 1px solid #ccc"
+      :editor="editor"
+      :default-config="toolbarConfig"
+      :mode="mode"
+    />
+    <Editor
+      v-model="html"
+      style="height: 500px; overflow-y: hidden;"
+      :default-config="editorConfig"
+      :mode="mode"
+      @onCreated="onCreated"
+      @onChange="onChange"
+      @onDestroyed="onDestroyed"
+      @onMaxLength="onMaxLength"
+      @onFocus="onFocus"
+      @onBlur="onBlur"
+      @customAlert="customAlert"
+    />
+  </div>
+</template>
+
+<script>
+import Vue from 'vue'
+import { Editor, Toolbar } from '/node_modules/@wangeditor/editor-for-vue'
+import '/node_modules/@wangeditor/editor/dist/css/style.css'
+
+export default Vue.extend({
+  components: { Editor, Toolbar },
+  props: {
+    value: {
+      type: String,
+      default: () => ''
+    }
+  },
+  data() {
+    return {
+      editor: null,
+      html: '',
+      toolbarConfig: {},
+      editorConfig: {
+        MENU_CONF: {
+          'uploadImage': {
+            server: process.env.VUE_APP_BASE_API + '/formData/MultiPictareaddData',
+            fieldName: 'file',
+            onSuccess(file, res) { // JS 语法
+              console.log(`${file.name} 上传成功`, res)
+            },
+            customInsert(res, insertFn) { // JS 语法
+              // res 即服务端的返回结果
+              console.log(res)
+
+              // 从 res 中找到 url alt href ,然后插入图片
+              insertFn(res.data)
+            }
+          }
+        },
+        placeholder: '请输入内容...'
+      },
+      mode: 'simple' // or 'simple'
+    }
+  },
+  watch: {
+    html(val) {
+      this.$emit('input', val)
+    },
+    value(val) {
+      this.html = val
+    }
+  },
+  beforeDestroy() {
+    const editor = this.editor
+    if (editor == null) return
+    editor.destroy() // 组件销毁时,及时销毁编辑器
+  },
+  methods: {
+    onCreated(editor) {
+      this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错
+    },
+    onChange(editor) { console.log('onChange', editor.children) },
+    onDestroyed(editor) { console.log('onDestroyed', editor) },
+    onMaxLength(editor) { console.log('onMaxLength', editor) },
+    onFocus(editor) { console.log('onFocus', editor) },
+    onBlur(editor) { console.log('onBlur', editor) },
+    customAlert(info, type) { window.alert(`customAlert in Vue demo\n${type}:\n${info}`) }
+  }
+})
+</script>
+
+<style>
+.w-e-modal button {
+  height: auto;
+}
+.w-e-text-container a {
+  color: blue;
+  text-decoration-line: underline;
+}
+.w-e-hover-bar {
+  display: none;
+}
+.w-e-text-placeholder {
+  top: 8px;
+}
+.w-e-full-screen-container {
+  z-index: 99999999;
+}
+</style>

+ 3 - 1
src/router/index.js

@@ -17,6 +17,7 @@ import keywordLibraryRouter from './modules/keywordLibrary'
 import memberRouter from './modules/member'
 import mallProtraitRouter from './modules/mallPortrait'
 import serviceSettlement from './modules/serviceSettlement'
+import dataBase from './modules/database'
 // import tableRouter from './modules/table'
 // import nestedRouter from './modules/nested'
 
@@ -105,7 +106,8 @@ export const asyncRoutes = [
   memberRouter,
   keywordLibraryRouter,
   mallProtraitRouter,
-  serviceSettlement
+  serviceSettlement,
+  dataBase
 ]
 
 /**

+ 57 - 0
src/router/modules/database.js

@@ -0,0 +1,57 @@
+import Layout from '@/layout'
+
+export default {
+  path: '/database',
+  component: Layout,
+  meta: { title: '内容库', icon: 'link' },
+  name: 'Database',
+  redirect: '/database/store-list',
+  alwaysShow: true,
+  children: [
+    {
+      path: 'store-list',
+      name: 'StoreList',
+      meta: { title: '商品列表', icon: 'link' },
+      redirect: '/database/store-list/list',
+      component: () => import('@/views/index'),
+      children: [
+        {
+          path: 'list',
+          name: 'List',
+          component: () => import('@/views/dataBase/store-list/index.vue'),
+          meta: { title: '商品列表', activeMenu: '/database/store-list' }
+        },
+        {
+          path: 'edit',
+          name: 'StoreEdit',
+          component: () => import('@/views/dataBase/store-list/edit.vue'),
+          meta: { title: '商品编辑', activeMenu: '/database/store-list' }
+        },
+        {
+          path: 'data-manage',
+          hidden: true,
+          component: () => import('@/views/index'),
+          name: 'DataManage',
+          redirect: '/database/store-list/data-manage/list',
+          meta: { title: '资料管理', noCache: true, activeMenu: '/database/store-list' },
+          children: [
+            {
+              path: 'edit',
+              hidden: true,
+              component: () => import('@/views/dataBase/store-list/data-management/edit.vue'),
+              name: 'DataManageEdit',
+              meta: { title: '资料编辑', noCache: true, activeMenu: '/database/store-list' }
+            },
+            {
+              path: 'list',
+              hidden: true,
+              component: () => import('@/views/dataBase/store-list/data-management/list.vue'),
+              name: 'DataManageList',
+              meta: { title: '资料列表', noCache: true, activeMenu: '/database/store-list' }
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}

+ 1 - 1
src/utils/request.js

@@ -7,7 +7,7 @@ import { getToken } from '@/utils/auth'
 const service = axios.create({
   baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
   // withCredentials: true, // send cookies when cross-domain requests
-  timeout: 10000 // request timeout
+  timeout: 25000 // request timeout
 })
 
 // request interceptor

+ 189 - 0
src/views/dataBase/components/UploadFile.vue

@@ -0,0 +1,189 @@
+<template>
+  <div>
+    <input
+      ref="uploadInp"
+      type="file"
+      :accept="accept"
+      required
+      style="display: none;"
+      @change="handleChange($event)"
+    />
+    <div class="tips">{{ tip }}</div>
+    <el-button size="mini" type="primary" @click="triggerInput">选择文件</el-button>
+    <div v-for="(file, index) in dataList" :key="index" class="file-list">
+      <span>{{ file.fileName }} </span>
+      <div>
+        <el-button type="text" size="small" @click="handlePreview(file.ossUrl)">预览</el-button>
+        <el-button type="text" size="small" style="color: red" @click="handleRemove(index)">删除</el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import { uploadFile } from '@/api/contentLibrary/context'
+export default {
+  name: 'UploadFile',
+  props: {
+    tip: {
+      type: String,
+      default: ''
+    },
+    multiple: {
+      type: Boolean,
+      default: false
+    },
+    limit: {
+      type: Number,
+      default: 1
+    },
+    accept: {
+      type: String,
+      default: '.docx,.pptx,.pdf,.mp4'
+    },
+    listType: {
+      type: String,
+      default: 'picture-card'
+    },
+    fileList: {
+      type: Array,
+      default: () => []
+    },
+    uuid: {
+      type: Number,
+      default: 0
+    },
+    beforeUpload: {
+      type: Function,
+      default: () => true
+    },
+    autoUpload: {
+      type: Boolean,
+      default: true
+    },
+    mode: {
+      type: String,
+      default: 'brand'
+    },
+    list: {
+      type: Array,
+      default: () => []
+    },
+    isCheck: {
+      type: Boolean,
+      default: () => true
+    },
+    actionUrl: {
+      type: String,
+      default: () => '/formData/MultiPictareaddData'
+    },
+    dataParams: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data() {
+    return {
+      dialogVisible: false,
+      dialogImageUrl: '',
+      dataList: [],
+      iframUrl: '',
+      dataObj: {
+        multipartFile: ''
+      },
+      count: 0,
+      loading: false
+    }
+  },
+  computed: {
+    ...mapGetters(['token']),
+    chooseState() {
+      return this.dataList.length < this.limit
+    },
+    action() {
+      return process.env.VUE_APP_BASE_API + this.actionUrl
+    },
+    headers() {
+      return {
+        'X-Token': this.token
+      }
+    }
+  },
+  watch: {
+    list: {
+      handler(val) {
+        console.log(val)
+        this.dataList = val
+      },
+      deep: true
+    }
+  },
+  methods: {
+    triggerInput() {
+      this.$refs.uploadInp.click()
+    },
+    // 删除
+    handleRemove(index) {
+      this.dataList = this.dataList.filter((_, i) => index !== i)
+      this.$emit('remove', { fileList: this.dataList })
+    },
+    // 变化
+    async handleChange(file) {
+      console.log(file)
+      const f = file.target.files[0]
+      console.log(f)
+      const formData = new FormData()
+      const isType = this.accept.indexOf('.pdf') !== -1
+      formData.append('multipartFile', f)
+      formData.append('type', isType ? 1 : '')
+      try {
+        this.$emit('change-loading')
+        const data = await uploadFile(formData)
+        if (data.success || data.code === 0) {
+          this.dataList.push({ fileName: data.fileName, ossUrl: data.url, ossName: data.ossName })
+          this.$emit('success', { fileList: this.dataList })
+          this.$emit('change-loading')
+          this.$message({
+            type: 'success',
+            message: '上传成功'
+          })
+        }
+      } catch (error) {
+        this.$message({
+          type: 'error',
+          message: '上传失败'
+        })
+        console.error(error)
+      }
+    },
+    // 上传失败
+    uploadError(err, file, fileList) {
+      this.dataList = fileList
+      this.$emit('error', { err, file, fileList })
+    },
+    handlePreview(f) {
+      if (f.indexOf('.docx') !== -1 || f.indexOf('.pptx') !== -1) {
+        window.open(`https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(f)}`)
+      } else {
+        window.open('javascript:window.name;', '<script>location.replace("' + f + '")<\/script>')
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep .el-upload-list {
+  display: none;
+}
+::v-deep .el-upload__tip {
+  position: static;
+}
+.file-list {
+  display: flex;
+  justify-content: space-between;
+  padding-right: 30px;
+  margin-top: 10px;
+}
+</style>

+ 115 - 0
src/views/dataBase/components/data-select.vue

@@ -0,0 +1,115 @@
+<template>
+  <div style="display: inline-block;">
+    <el-select
+      ref="select"
+      v-model="arr"
+      placeholder="标签"
+      filterable
+      remote
+      :multiple="isMultiple"
+      reserve-keyword
+      :remote-method="remoteMethod"
+      :loading="loading"
+    >
+      <el-option v-for="(i, index) in options" :key="index" :label="i.keyword" :value="i.id" @click.native="handleClick(i)" />
+    </el-select>
+    <div v-if="isEdit">
+      <el-tag
+        v-for="tag in emitArr"
+        :key="tag.id"
+        closable
+        :disable-transitions="false"
+        @close="handleClose(tag)"
+      >
+        {{ tag.keyword }}
+      </el-tag>
+    </div>
+  </div>
+</template>
+
+<script>
+import { findKeyWordList } from '@/api/contentLibrary/context'
+export default {
+  props: {
+    labelIds: {
+      type: Array,
+      default: () => []
+    },
+    isEdit: {
+      type: Boolean,
+      default: () => true
+    },
+    isMultiple: {
+      type: Boolean,
+      default: () => false
+    }
+  },
+  data() {
+    return {
+      emitArr: [],
+      list: [],
+      options: [],
+      loading: false,
+      arr: ''
+    }
+  },
+  watch: {
+    labelIds(val) {
+      console.log('传递来的emitArr', val)
+      this.emitArr = val
+    },
+    arr(val) {
+      if (this.isMultiple) {
+        this.$emit('handle-emit', val)
+      }
+    }
+  },
+  methods: {
+    async remoteMethod(v) {
+      if (v !== '') {
+        this.loading = true
+        try {
+          const { data } = await findKeyWordList({ keyword: v, pageSize: 10, pageNum: 1 })
+          this.list = data.results
+          const isShow = (i) => {
+            if (this.isMultiple) return !this.arr.some(e => e === i.id)
+            return !this.emitArr.some(e => Number(e.id) === Number(i.id))
+          }
+          this.options = this.list.filter(item => {
+            return ((item.keyword.toLowerCase()
+              .indexOf(v.toLowerCase()) > -1) && isShow(item))
+          })
+          console.log(this.options, this.labelIds)
+          this.loading = false
+        } catch (error) {
+          this.options = []
+          console.error(error)
+        }
+      } else {
+        this.options = []
+      }
+    },
+    handleClose(tag) {
+      this.emitArr = this.emitArr.filter(e => e.id !== tag.id)
+      this.$emit('handle-emit', this.emitArr)
+    },
+    handleClick($event) {
+      setTimeout(() => {
+        this.$refs.select.blur()
+        this.options = []
+      }, 200)
+      if (!this.isMultiple) {
+        this.emitArr.push($event)
+        console.log('传过去的', this.emitArr)
+        this.$emit('handle-emit', this.emitArr)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-tag {
+  margin: 5px 5px 5px 0;
+}
+</style>

+ 44 - 0
src/views/dataBase/components/dataManageTab.vue

@@ -0,0 +1,44 @@
+<template>
+  <div>
+    <el-tabs v-model="activeName">
+      <el-tab-pane
+        v-for="i in dataManageListType"
+        :key="i.id"
+        :label="i.value"
+        :name="i.id"
+      />
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import databaseMixin from '../mixins/index'
+
+export default {
+  mixins: [databaseMixin],
+  data() {
+    return {
+      activeName: sessionStorage.getItem('TabStorage') || '3'
+    }
+  },
+  watch: {
+    activeName: {
+      handler(val) {
+        if (val) {
+          sessionStorage.setItem('TabStorage', val)
+          this.$emit('change', val)
+        }
+      },
+      immediate: true
+    }
+  },
+  methods: {
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep .el-tabs__nav-wrap::after {
+  background: none;
+}
+</style>

+ 137 - 0
src/views/dataBase/components/dialogTable.vue

@@ -0,0 +1,137 @@
+<template>
+  <div class="supplier-list-selector">
+    <div class="filter-container">
+      <div class="filter-control">
+        <span>商品ID:</span>
+        <el-input v-model="listQuery.productId" size="mini" placeholder="商品ID" type="number" @keyup.enter.native="filterList" />
+      </div>
+      <div class="filter-control">
+        <span>商品名称:</span>
+        <el-input v-model="listQuery.productName" size="mini" placeholder="商品名称" @keyup.enter.native="filterList" />
+      </div>
+      <div class="filter-control">
+        <span>供应商名称:</span>
+        <el-input v-model="listQuery.shopName" size="mini" placeholder="供应商名称" @keyup.enter.native="filterList" />
+      </div>
+      <div class="filter-control">
+        <el-button type="primary" size="mini" @click="filterList">查询</el-button>
+      </div>
+    </div>
+    <el-table
+      ref="Table"
+      v-loading="isLoading"
+      :data="list"
+      border
+      fit
+      highlight-current-row
+      header-row-class-name="tableHeader"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column prop="productId" label="商品ID" align="center" />
+      <el-table-column prop="productImage" label="商品图片" align="center">
+        <template slot-scope="scope">
+          <el-image style="width: 150px;height: 100px" fit="contain" :src="scope.row.productImage" />
+        </template>
+      </el-table-column>
+      <el-table-column prop="productName" label="商品名称" align="center" />
+      <el-table-column prop="shopName" label="供应商名称" align="center" />
+    </el-table>
+    <!-- 页码 -->
+    <pagination :total="total" :page.sync="listQuery.pageNum" :limit.sync="listQuery.pageSize" @pagination="getList" />
+    <div class="control-footer">
+      <el-button type="primary" @click="onCancel">取消</el-button>
+      <el-button type="primary" @click="onConfirm">确认</el-button>
+    </div>
+  </div>
+</template>
+<script>
+import { getStoreBaseList } from '@/api/contentLibrary/store'
+
+export default {
+  name: 'ClubListSelector',
+  data() {
+    return {
+      listQuery: {
+        pageNum: 1, // 页码
+        pageSize: 10 // 分页
+      },
+      list: [],
+      total: 0,
+      multipleSelection: [],
+      isLoading: false
+    }
+  },
+  computed: {
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    // 取消
+    onCancel() {
+      this.multipleSelection = []
+      this.$emit('cancel', [])
+    },
+
+    // 确认
+    onConfirm() {
+      if (this.multipleSelection.length === 0) {
+        this.$message.warning('选择列表不能为空')
+        return
+      }
+      this.$emit('confirm', this.multipleSelection[0])
+    },
+
+    // 获取机构列表
+    async getList() {
+      this.isLoading = true
+      const { data } = await getStoreBaseList(this.listQuery)
+      console.log(data)
+      this.list = data.results
+      this.total = data.totalRecord
+      this.isLoading = false
+    },
+
+    // 筛选机构列表
+    filterList() {
+      this.listQuery.pageNum = 1
+      this.getList()
+    },
+
+    // 设置选中的行
+    setSelection() {
+      this.list.forEach((row) => {})
+    },
+
+    // 表格列选择
+    handleSelectionChange(rows) {
+      if (rows.length > 1) {
+        rows.shift()
+        this.$refs.Table.clearSelection()
+        this.$refs.Table.toggleRowSelection(rows[0])
+      }
+      this.multipleSelection = rows
+    },
+
+    // 表格索引
+    indexMethod(index) {
+      return index + this.listQuery.pageSize * (this.listQuery.pageNum - 1) + 1
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+::v-deep {
+  .pagination-container {
+    padding: 0 !important;
+  }
+}
+.control-footer {
+  text-align: right;
+  margin-top: 15px;
+}
+::v-deep .el-table__header-wrapper .el-table__header .el-checkbox {
+  display: none;
+}
+</style>

+ 143 - 0
src/views/dataBase/components/dialogTypeTable.vue

@@ -0,0 +1,143 @@
+<template>
+  <div class="supplier-list-selector">
+    <div class="filter-container">
+      <div class="filter-control">
+        <span>标题:</span>
+        <el-input v-model="listQuery.title" size="mini" placeholder="标题" @keyup.enter.native="filterList" />
+      </div>
+      <div class="filter-control">
+        <el-button type="primary" size="mini" @click="filterList">查询</el-button>
+      </div>
+    </div>
+    <el-table
+      ref="Table"
+      v-loading="isLoading"
+      :data="list"
+      border
+      highlight-current-row
+      header-row-class-name="tableHeader"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column
+        label="ID"
+        prop="id"
+        align="center"
+        width="80"
+      />
+      <el-table-column prop="title" label="标题" align="center" />
+      <el-table-column prop="stageStatus" label="用户阶段" align="center">
+        <template slot-scope="scope">
+          <div>
+            {{ scope.row.stageStatus | userStateFilter(userClass) }}
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column prop="labelIds" label="标签" align="center">
+        <template slot-scope="scope">
+          <div>
+            {{ scope.row.labelIds }}
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column prop="pv" label="浏览量" align="center" />
+      <el-table-column prop="addTime" label="添加时间" align="center" />
+      <el-table-column prop="createBy" label="创建人" align="center" />
+    </el-table>
+    <!-- 页码 -->
+    <pagination :total="total" :page.sync="listQuery.pageNum" :limit.sync="listQuery.pageSize" @pagination="getList" />
+    <div class="control-footer">
+      <el-button type="primary" @click="onCancel">取消</el-button>
+      <el-button type="primary" @click="onConfirm">确认</el-button>
+    </div>
+  </div>
+</template>
+<script>
+import { getContextList } from '@/api/contentLibrary/context'
+import databaseMixin from '../mixins/index'
+
+export default {
+  name: 'Selector',
+  mixins: [databaseMixin],
+  props: {
+    typeId: {
+      type: Number,
+      default: () => 0
+    }
+  },
+  data() {
+    return {
+      listQuery: {
+        pageNum: 1, // 页码
+        pageSize: 10, // 分页
+        title: ''
+      },
+      list: [],
+      total: 0,
+      multipleSelection: [],
+      isLoading: false
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    // 取消
+    onCancel() {
+      this.multipleSelection = []
+      this.$emit('cancel', [])
+    },
+
+    // 确认
+    onConfirm() {
+      if (this.multipleSelection.length === 0) {
+        this.$message.warning('选择列表不能为空')
+        return
+      }
+      this.$emit('confirm', this.multipleSelection)
+    },
+
+    // 获取机构列表
+    async getList() {
+      this.isLoading = true
+      const { data } = await getContextList(Object.assign(this.listQuery, { productArchiveId: this.id, type: this.typeId }))
+      console.log(data)
+      this.list = data.results
+      this.total = data.totalRecord
+      this.isLoading = false
+    },
+
+    // 筛选机构列表
+    filterList() {
+      this.listQuery.pageNum = 1
+      this.getList()
+    },
+
+    // 设置选中的行
+    setSelection() {
+      this.list.forEach((row) => {})
+    },
+
+    // 表格列选择
+    handleSelectionChange(rows) {
+      this.multipleSelection = rows
+    },
+
+    // 表格索引
+    indexMethod(index) {
+      return index + this.listQuery.pageSize * (this.listQuery.pageNum - 1) + 1
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+::v-deep {
+  .pagination-container {
+    padding: 0 !important;
+  }
+}
+.control-footer {
+  text-align: right;
+  margin-top: 15px;
+}
+</style>

+ 44 - 0
src/views/dataBase/components/storeItem.vue

@@ -0,0 +1,44 @@
+<template>
+  <div class="store-item">
+    <el-image :src="productImage" />
+    <div class="store-name">{{ productName }}</div>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    productImage: {
+      type: String,
+      default: () => ''
+    },
+    productName: {
+      type: String,
+      default: () => ''
+    }
+  },
+  data() {
+    return {
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.store-item {
+  padding: 10px;
+  margin: 10px 0;
+  display: flex;
+  align-items: center;
+  background: #66666636;
+  margin-bottom: 10px;
+  .el-image {
+    width: 80px;
+    height: 80px;
+    margin-right: 10px;
+  }
+  .store-name {
+    width: calc(100% - 80px);
+  }
+}
+</style>

+ 145 - 0
src/views/dataBase/mixins/index.js

@@ -0,0 +1,145 @@
+import { mapGetters } from 'vuex'
+import { getServiceList } from '@/api/library/keyword'
+
+export default {
+  data() {
+    return {
+      storeAttributes: [
+        {
+          id: '',
+          value: '请选择'
+        },
+        {
+          id: '1',
+          value: '产品'
+        }, {
+          id: '2',
+          value: '仪器'
+        }
+      ],
+      storeClass: [
+        {
+          id: '',
+          value: '请选择'
+        },
+        {
+          id: 1,
+          value: '医美'
+        }, {
+          id: 2,
+          value: '生美'
+        }
+      ],
+      storeInfo: [
+        {
+          id: 1,
+          value: '商品库获取'
+        },
+        {
+          id: 2,
+          value: '自定义'
+        }
+      ],
+      storeAddRules: {
+        name: [
+          { required: true, message: '请输入商品名称', trigger: 'blur' }
+        ],
+        image: [
+          { required: true, message: '请上传商品图片', trigger: 'blur' }
+        ],
+        linkMan: [
+          { required: true, message: '请输入供应商名称', trigger: 'blur' }
+        ]
+      },
+      fileAddRules: {
+        name: [
+          { required: true, message: '请输入标题', trigger: 'blur' }
+        ],
+        file: [
+          { required: true, message: '请上传文件', trigger: 'change' }
+        ],
+        userClass: [
+          { required: true, message: '请选择用户阶段', trigger: 'change' }
+        ],
+        userType: [
+          { require: true, message: '请选择标签', trigger: 'change' }
+        ]
+      },
+      userClass: [
+        {
+          id: '',
+          value: '请选择'
+        },
+        {
+          id: 1,
+          value: '认知阶段'
+        },
+        {
+          id: 2,
+          value: '兴趣阶段'
+        },
+        {
+          id: 3,
+          value: '决策阶段'
+        },
+        {
+          id: 4,
+          value: '购买阶段'
+        }
+      ],
+      userType: [],
+      dataManageListType: [
+        {
+          id: '3',
+          value: '文件'
+        },
+        {
+          id: '2',
+          value: '视频'
+        },
+        {
+          id: '1',
+          value: '图片'
+        },
+        {
+          id: '4',
+          value: '文本'
+        },
+        {
+          id: '5',
+          value: '话术'
+        }
+      ],
+      sellerList: []
+    }
+  },
+  computed: {
+    ...mapGetters(['userId', 'username', 'fullName', 'phone']),
+    type() {
+      return Number(this.$route.query.t) || false
+    },
+    id() {
+      return this.$route.query.id || false
+    },
+    tId() {
+      return this.$route.query.tId || false
+    },
+    pId() {
+      return this.$route.query.pid || false
+    }
+  },
+  filters: {
+    userStateFilter(val, list) {
+      return list.filter(e => e.id === val)[0].value
+    },
+    userTypeFilter(val, list) {
+      return list.filter(e => e.id === val)[0].value
+    }
+  },
+  methods: {
+    async getServiceList() {
+      const { data } = await getServiceList()
+      this.sellerList = data
+    }
+  }
+}

+ 228 - 0
src/views/dataBase/store-list/data-management/edit.vue

@@ -0,0 +1,228 @@
+<template>
+  <div>
+    <el-form ref="form" v-loading="loading" :model="form" :rules="fileAddRules" label-width="120px">
+      <el-form-item label="标题:" prop="title">
+        <el-input v-model="form.title" />
+      </el-form-item>
+      <el-form-item v-if="type === 3" label="文件路径:" prop="archiveFiles">
+        <file-upload
+          :limit="20"
+          :is-check="false"
+          :list="fileList"
+          :accept="'.docx,.pptx,.pdf'"
+          :tip="'支持上传pdf、docx、pptx文件格式'"
+          @change-loading="() => (loading = !loading)"
+          @success="UploadSuccess($event)"
+          @remove="HandleRemove($event)"
+        />
+      </el-form-item>
+      <el-form-item v-if="type === 2" label="视频路径:" prop="archiveFiles">
+        <file-upload
+          :limit="20"
+          :is-check="false"
+          :list="fileList"
+          :accept="'.mp4'"
+          :tip="'支持上传.mp4文件格式'"
+          @change-loading="() => (loading = !loading)"
+          @success="UploadSuccess($event)"
+          @remove="HandleRemove($event)"
+        />
+      </el-form-item>
+      <el-form-item v-if="type === 1" label="图片路径:" prop="archiveFiles">
+        <image-upload
+          :limit="6"
+          :image-list="fileList"
+          @success="handleImageSuccess($event, 'textRelateds')"
+          @remove="handleImageRemove($event, 'textRelateds')"
+        />
+      </el-form-item>
+      <el-form-item v-if="type === 4" label="文本内容:" prop="content">
+        <editor-input v-model="form.content" />
+      </el-form-item>
+      <el-form-item v-if="type === 5" label="话术内容:" prop="content">
+        <el-input
+          v-model="form.content"
+          type="textarea"
+          :autosize="{ minRows: 2, maxRows: 4}"
+          placeholder="请输入内容"
+        />
+      </el-form-item>
+      <el-form-item label="用户阶段:" prop="stageStatus">
+        <el-select v-model="form.stageStatus" placeholder="用户阶段">
+          <el-option
+            v-for="(i, index) in userClass"
+            :key="index"
+            :label="i.value"
+            :value="i.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="标签:" prop="labelIds">
+        <data-select :label-ids="labelIds" @handle-emit="handleSelect" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="submitForm('form')">提交</el-button>
+        <el-button @click="$router.back()">返回</el-button>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import ImageUpload from '@/components/UploadImage/index.vue'
+import FileUpload from '../../components/UploadFile.vue'
+import databaseMixin from '../../mixins/index'
+import EditorInput from '@/components/Editor/index.vue'
+import dataSelect from '../../components/data-select.vue'
+import { addContext, getContextDetail } from '@/api/contentLibrary/context'
+
+export default {
+  components: {
+    ImageUpload,
+    FileUpload,
+    EditorInput,
+    dataSelect
+  },
+  mixins: [databaseMixin],
+  data() {
+    return {
+      form: {
+        stageStatus: '',
+        productArchiveId: 0,
+        archiveFiles: [],
+        labelIds: '123'
+      },
+      fileList: [],
+      labelIds: [],
+      loading: false,
+      fileAddRules: {
+        stageStatus: [
+          {
+            required: true,
+            message: '选择用户阶段',
+            trigger: 'blur'
+          }
+        ],
+        archiveFiles: [
+          {
+            required: true,
+            message: '请上传文件',
+            trigger: 'change'
+          }
+        ],
+        title: [
+          {
+            required: true,
+            message: '请输入标题',
+            trigger: 'blur'
+          }
+        ],
+        labelIds: [
+          {
+            required: true,
+            message: '请选择标签',
+            trigger: 'blur'
+          }
+        ]
+      }
+    }
+  },
+  mounted() {
+    if (this.tId) {
+      this.getContextDetail()
+    }
+  },
+  methods: {
+    async getContextDetail() {
+      const { data } = await getContextDetail({ id: this.tId })
+      this.form = data
+      const isArray = (a) => a?.indexOf(',') !== -1
+      const k = isArray(data.keywords) ? data.keywords?.split(',') : [data.keywords]
+      const v = isArray(data.labelIds) ? data.labelIds?.split(',') : [data.labelIds]
+      this.labelIds = k?.map((e, i) => ({
+        id: v[i],
+        keyword: e
+      }))
+      this.fileList = data.type !== '1' ? data.archiveFiles : data.archiveFiles.map(e => ({
+        uid: e.id,
+        url: e.ossUrl,
+        name: e.id,
+        response: {
+          data: e.ossUrl
+        }
+      }))
+      console.log(this.labelIds)
+    },
+    HandleRemove(file) {
+      console.log(file)
+      this.form.archiveFiles = file.fileList
+    },
+    UploadSuccess(file) {
+      console.log(file)
+      this.form.archiveFiles = file.fileList
+    },
+    handleImageSuccess(file) {
+      console.log(file)
+      this.form.archiveFiles = file.fileList.map(e => ({
+        ossUrl: e.response.data,
+        fileName: e.name,
+        ossName: e.name
+      }))
+    },
+    handleImageRemove(file) {
+      this.form.archiveFiles = file.fileList.map(e => ({
+        ossUrl: e.response.data,
+        fileName: e.name,
+        ossName: e.name
+      }))
+    },
+    submitForm(form) {
+      this.$refs[form].validate(async valid => {
+        if (valid) {
+          try {
+            console.log(this.form.content)
+            const form = {
+              stageStatus: this.form.stageStatus.toString(),
+              type: this.type.toString(),
+              archiveFiles: this.form.archiveFiles || [],
+              productArchiveId: this.id,
+              labelIds: this.form.labelIds,
+              createBy: this.fullName,
+              title: this.form.title,
+              id: this.form.id || '',
+              content: this.form.content || ''
+            }
+            const data = await addContext(form)
+            if (data.code === 0) {
+              this.$router.back()
+            } else {
+              this.$message({
+                type: 'error',
+                message: data.msg
+              })
+            }
+          } catch (error) {
+            console.error(error)
+          }
+        }
+      })
+    },
+    handleSelect(val) {
+      console.log(val, '标签')
+      this.form.labelIds = val.map(e => e.id).join(',')
+      console.log(this.form.labelIds)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep .el-input,
+::v-deep .el-textarea__inner {
+  width: 500px;
+}
+::v-deep .el-form {
+  width: 650px;
+  margin: 20px auto;
+}
+</style>

+ 237 - 0
src/views/dataBase/store-list/data-management/list.vue

@@ -0,0 +1,237 @@
+<template>
+  <div class="app-container">
+    <!-- 顶部操作区域 -->
+    <div class="filter-container">
+      <manage-tab @change="handleChange" />
+
+      <div v-if="listQuery.type === 5">
+        <el-tabs v-model="listQuery.allStatus" @tab-click="handleChange(listQuery.type)">
+          <el-tab-pane label="企业话术" name="1" />
+          <el-tab-pane label="个人话术" name="2" />
+        </el-tabs>
+      </div>
+
+      <div class="filter-control">
+        <span>标题:</span>
+        <el-input
+          v-model="listQuery.title"
+          placeholder="标题"
+          clearable
+          @keyup.enter.native="handleChange(listQuery.type)"
+          @clear="handleChange(listQuery.type)"
+        />
+      </div>
+      <div v-if="listQuery.allStatus !== '2'" class="filter-control">
+        <span>用户阶段:</span>
+        <el-select v-model="listQuery.stageStatus" placeholder="用户阶段" @change="handleChange(listQuery.type)">
+          <el-option
+            v-for="(i, index) in userClass"
+            :key="index"
+            :label="i.value"
+            :value="i.id"
+          />
+        </el-select>
+      </div>
+      <div v-if="listQuery.allStatus !== '2'" class="filter-control">
+        <span>标签:</span>
+        <data-select :is-edit="false" :is-multiple="true" @handle-emit="handleSelect" />
+      </div>
+      <div v-if="listQuery.allStatus === '2'" class="filter-control">
+        <span>上传者:</span>
+        <el-select v-model="listQuery.spId" placeholder="上传者" @change="handleChange(listQuery.type)">
+          <el-option
+            v-for="item in sellerList"
+            :key="item.serviceProviderId"
+            :label="item.linkMan"
+            :value="item.serviceProviderId"
+          />
+        </el-select>
+      </div>
+      <div class="filter-control">
+        <el-button type="primary" @click="handleChange(listQuery.type)">查询</el-button>
+        <el-button v-if="listQuery.allStatus !== '2'" type="primary" @click="$router.push('/database/store-list/data-manage/edit?id=' + id + '&t=' + listQuery.type)">添加</el-button>
+        <el-button v-if="listQuery.allStatus !== '2'" type="primary" @click="exportDialogVisible = true">删除</el-button>
+      </div>
+    </div>
+
+    <el-table
+      ref="table"
+      v-loading="isLoading"
+      :data="list"
+      border
+    >
+      <el-table-column
+        label="ID"
+        prop="id"
+        align="center"
+        width="80"
+      />
+      <el-table-column prop="title" label="标题" align="center" />
+      <el-table-column prop="stageStatus" label="用户阶段" align="center">
+        <template slot-scope="scope">
+          <div>
+            {{ scope.row.stageStatus | userStateFilter(userClass) }}
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column prop="labelIds" label="标签" align="center">
+        <template slot-scope="scope">
+          <div>
+            {{ scope.row.keywords }}
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column prop="pv" label="浏览量" align="center" />
+      <el-table-column prop="addTime" label="添加时间" align="center" />
+      <el-table-column prop="createBy" label="创建人" align="center" />
+      <el-table-column label="操作" align="center" width="250">
+        <template slot-scope="{ row }">
+          <div class="labelCenter">
+            <el-button
+              v-if="listQuery.allStatus === '1'"
+              type="primary"
+              size="mini"
+              @click="$router.push('/database/store-list/data-manage/edit?id=' + id + '&t=' + listQuery.type + '&tId=' + row.id)"
+            >编辑</el-button>
+            <el-button
+              v-if="listQuery.allStatus === '1'"
+              type="primary"
+              size="mini"
+              @click="
+                handleDelete({ ids: row.id, productArchiveId: id, type: listQuery.type.toString() })
+              "
+            >删除</el-button>
+            <el-button
+              v-if="listQuery.type === 5 && listQuery.allStatus === '2'"
+              type="primary"
+              size="mini"
+              @click="
+                $router.push('/database/store-list/data-manage/edit?id=' + row.id)
+              "
+            >查看</el-button>
+          </div>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 页码 -->
+    <pagination
+      :total="total"
+      :page.sync="listQuery.pageNum"
+      :limit.sync="listQuery.pageSize"
+      @pagination="handleChange(listQuery.type)"
+    />
+
+    <el-dialog title="选择商品" :visible.sync="exportDialogVisible" width="70%">
+      <data-list-selector
+        v-if="exportDialogVisible"
+        :type-id="listQuery.type"
+        @cancel="onSelectorCancel"
+        @confirm="onSelectorConfirm"
+      />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import ManageTab from '../../components/dataManageTab.vue'
+import DataListSelector from '../../components/dialogTypeTable.vue'
+import databaseMixin from '../../mixins/index'
+import { getContextList, deleteContext } from '@/api/contentLibrary/context'
+import dataSelect from '../../components/data-select.vue'
+
+export default {
+  components: {
+    ManageTab,
+    DataListSelector,
+    dataSelect
+  },
+  mixins: [databaseMixin],
+  data() {
+    return {
+      listQuery: {
+        type: '',
+        pageNum: 1,
+        pageSize: 10,
+        labelIds: '',
+        stageStatus: '',
+        allStatus: '1',
+        spId: ''
+      },
+      list: [],
+      total: 0,
+      isLoading: false,
+      exportDialogVisible: false
+    }
+  },
+  watch: {
+    'listQuery.allStatus': {
+      handler(val) {
+        if (val) {
+          this.listQuery.labelIds = ''
+          this.listQuery.spId = ''
+        }
+      }
+    }
+  },
+  mounted() {
+    this.getServiceList()
+  },
+  methods: {
+    async getInfo() {
+      this.isLoading = true
+      const { data } = await getContextList(Object.assign(this.listQuery, { productArchiveId: this.id, productId: this.pId || '' }))
+      this.list = data.results
+      this.total = data.totalRecord
+      this.isLoading = false
+    },
+    handleSelect($event) {
+      this.listQuery.labelIds = $event.join(',')
+      this.handleChange(this.listQuery.type)
+    },
+    handleChange($event) {
+      if (this.id && $event) {
+        this.listQuery.type = Number($event)
+        this.isLoading = true
+        this.getInfo()
+      }
+    },
+    handleDelete(val) {
+      this.$confirm(`是否删除该资料?`, '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(async() => {
+        try {
+          const form = new FormData()
+          form.append('ids', val.ids)
+          form.append('type', val.type)
+          form.append('productArchiveId', val.productArchiveId)
+          await deleteContext(form)
+          this.$message({
+            message: '删除成功',
+            type: 'success'
+          })
+          this.handleChange(this.listQuery.type)
+        } catch (error) {
+          console.error(error)
+        }
+      })
+    },
+    onSelectorCancel() {
+      this.exportDialogVisible = false
+    },
+    onSelectorConfirm(row) {
+      console.log(row)
+      this.handleDelete({ ids: row.map(e => e.id).join(','), type: this.listQuery.type.toString(), productArchiveId: this.id })
+      this.exportDialogVisible = false
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep .el-tabs__nav-wrap::after {
+  background: none;
+}
+</style>

+ 190 - 0
src/views/dataBase/store-list/edit.vue

@@ -0,0 +1,190 @@
+<template>
+  <div class="store-edit">
+    <el-form ref="form" :model="form" :rules="storeAddRules" label-width="120px">
+      <el-form-item label="商品信息:">
+        <el-radio-group :key="radioKey" v-model="form.info" @change="handleRadio">
+          <el-radio v-for="i in storeInfo" :key="i.id" :label="i.id">{{
+            i.value
+          }}</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item v-if="form.info === 1" label="添加商品:" prop="productId">
+        <store-item v-if="selectStore.productName" :product-image="selectStore.productImage" :product-name="selectStore.productName" />
+        <el-button type="primary" @click="exportDialogVisible = true">{{ selectStore.productName ? '选择商品' : '添加' }}</el-button>
+      </el-form-item>
+      <div v-if="form.info === 2">
+        <el-form-item label="商品名称:" prop="productName">
+          <el-input v-model="form.productName" />
+        </el-form-item>
+        <el-form-item label="商品图片:" prop="productImage">
+          <image-upload
+            :limit="1"
+            :image-list="productImage"
+            @success="handleImageSuccess($event, 'productImage')"
+            @remove="handleImageRemove($event, 'productImage')"
+          />
+        </el-form-item>
+        <el-form-item label="供应商名称:" prop="shopName">
+          <el-input v-model="form.shopName" />
+        </el-form-item>
+      </div>
+      <el-form-item label="商品属性:">
+        <el-select v-model="form.productType" placeholder="请选择商品属性">
+          <el-option
+            v-for="(i, index) in storeAttributes"
+            :key="index"
+            :label="i.value"
+            :value="i.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="商品分类:">
+        <el-select v-model="form.productClassify" placeholder="请选择商品分类">
+          <el-option
+            v-for="(i, index) in storeClass"
+            :key="index"
+            :label="i.value"
+            :value="i.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="submitForm('form')">提交</el-button>
+        <el-button @click="$router.back()">返回</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-dialog title="选择商品" :visible.sync="exportDialogVisible" width="70%">
+      <store-list-selector
+        v-if="exportDialogVisible"
+        @cancel="onSelectorCancel"
+        @confirm="onSelectorConfirm"
+      />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import databaseMixin from '../mixins/index'
+import StoreListSelector from '../components/dialogTable.vue'
+import StoreItem from '../components/storeItem.vue'
+import ImageUpload from '@/components/UploadImage/index.vue'
+import { getStoreDetail, addStore } from '@/api/contentLibrary/store'
+
+export default {
+  components: {
+    StoreListSelector,
+    StoreItem,
+    ImageUpload
+  },
+  mixins: [databaseMixin],
+  data() {
+    return {
+      form: {
+        info: 1,
+        productClassify: '',
+        productType: '',
+        productId: '',
+        productName: ''
+      },
+      selectStore: {},
+      radioKey: 0,
+      exportDialogVisible: false,
+      productImage: [],
+      storeAddRules: {
+        productId: [
+          {
+            required: true,
+            message: '请选择添加商品',
+            trigger: 'blur'
+          }
+        ],
+        productName: [
+          {
+            required: true,
+            message: '请输入商品名称',
+            trigger: 'blur'
+          }
+        ],
+        productImage: [
+          {
+            required: true,
+            message: '请上传商品图片',
+            trigger: 'blur'
+          }
+        ],
+        shopName: [
+          {
+            required: true,
+            message: '请输入供应商名称',
+            trigger: 'change'
+          }
+        ]
+      }
+    }
+  },
+  async mounted() {
+    if (this.id) {
+      await this.getInfo()
+    }
+  },
+  methods: {
+    async getInfo() {
+      const { data } = await getStoreDetail({ id: this.id })
+      this.form = data
+      this.form.info = data.productId ? 1 : 2
+      this.selectStore.productImage = data.productImage
+      this.selectStore.productName = data.productName
+      this.selectStore.productId = data.productId
+      this.productImage = [{ uid: 1, url: data.productImage }]
+      console.log(this.selectStore.productImage)
+    },
+    onSelectorCancel() {
+      this.exportDialogVisible = false
+    },
+    onSelectorConfirm(val) {
+      console.log(val)
+      const v = {
+        productId: val.productId,
+        productImage: val.productImage,
+        productName: val.productName,
+        shopName: val.shopName
+      }
+      this.form = Object.assign(this.form, v)
+      this.selectStore = val
+      this.exportDialogVisible = false
+    },
+    handleRadio($event) {
+      if ($event) {
+        this.radioKey = Math.random() * 1000
+      }
+    },
+    handleImageRemove() {},
+    handleImageSuccess(file, name) {
+      this.form[name] = file.file.response.data
+    },
+    submitForm(form) {
+      this.$refs[form].validate(async(valid) => {
+        if (valid) {
+          if (this.form.info === 2) {
+            delete this.form.productId
+          }
+          try {
+            await addStore(this.form)
+            this.$router.back()
+          } catch (error) {
+            console.error(error)
+          }
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.store-edit {
+  width: 500px;
+  margin: 0 auto;
+}
+</style>

+ 146 - 0
src/views/dataBase/store-list/index.vue

@@ -0,0 +1,146 @@
+<template>
+  <div class="app-container">
+    <!-- 顶部操作区域 -->
+    <div class="filter-container">
+      <div class="filter-control">
+        <span>商品名称:</span>
+        <el-input
+          v-model="listQuery.productName"
+          placeholder="商品名称"
+          clearable
+          @keyup.enter.native="getList"
+          @clear="getList"
+        />
+      </div>
+      <div class="filter-control">
+        <span>商品属性:</span>
+        <el-select v-model="listQuery.productType" placeholder="请选择商品属性" @change="getList">
+          <el-option
+            v-for="(i, index) in storeAttributes"
+            :key="index"
+            :label="i.value"
+            :value="i.id"
+          />
+        </el-select>
+      </div>
+      <div class="filter-control">
+        <span>商品分类:</span>
+        <el-select v-model="listQuery.productClassify" placeholder="请选择商品分类" @change="getList">
+          <el-option
+            v-for="(i, index) in storeClass"
+            :key="index"
+            :label="i.value"
+            :value="i.id"
+            @change="getList"
+          />
+        </el-select>
+      </div>
+      <div class="filter-control">
+        <el-button type="primary" @click="getList">查询</el-button>
+        <el-button type="primary" @click="$router.push('/database/store-list/edit')">添加</el-button>
+      </div>
+    </div>
+
+    <el-table
+      ref="table"
+      v-loading="isLoading"
+      :data="list"
+      border
+      :default-sort="{ prop: 'frequency', order: 'descending' }"
+    >
+      <!--<el-table-column type="selection" width="55" align="center" />-->
+      <el-table-column
+        label="ID"
+        type="index"
+        sortable="custom"
+        align="center"
+        width="80"
+      />
+      <el-table-column prop="productName" label="商品名称" align="center" />
+      <el-table-column prop="productImage" label="商品图片" align="center" width="150">
+        <template slot-scope="scope">
+          <el-image :src="scope.row.productImage || 'https://www.caimei365.com/img/base/placeholder.png'" />
+        </template>
+      </el-table-column>
+      <el-table-column prop="productType" label="商品属性" align="center">
+        <template slot-scope="scope">
+          <div>
+            {{ scope.row.productType == "1" ? "产品" : "仪器" }}
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column prop="productClassify" label="商品分类" align="center">
+        <template slot-scope="scope">
+          <div>
+            {{ scope.row.productClassify === 1 ? "医美" : "生美" }}
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column prop="addTime" label="添加时间" align="center" />
+      <el-table-column label="操作" align="center" width="200">
+        <template slot-scope="{ row }">
+          <div class="labelCenter">
+            <el-button
+              type="primary"
+              size="mini"
+              @click="$router.push('/database/store-list/edit?id=' + row.id)"
+            >编辑</el-button>
+            <el-button
+              type="primary"
+              size="mini"
+              @click="
+                $router.push('/database/store-list/data-manage/list?id=' + row.id + '&pId=' + (row.productId || ''))
+              "
+            >资料管理</el-button>
+          </div>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 页码 -->
+    <pagination
+      :total="total"
+      :page.sync="listQuery.pageNum"
+      :limit.sync="listQuery.pageSize"
+      @pagination="getList"
+    />
+  </div>
+</template>
+
+<script>
+import databaseMixin from '../mixins/index'
+import { getStoreList } from '@/api/contentLibrary/store'
+
+export default {
+  mixins: [databaseMixin],
+  data() {
+    return {
+      listQuery: {
+        pageNum: 1,
+        pageSize: 10,
+        productType: '',
+        productClassify: ''
+      },
+      list: [],
+      total: 0,
+      isLoading: false
+    }
+  },
+  async mounted() {
+    await this.getList()
+  },
+  methods: {
+    async getList() {
+      this.isLoading = true
+      const { data } = await getStoreList(this.listQuery)
+      this.list = data.results
+      this.total = data.totalRecord
+      this.isLoading = false
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 143 - 39
src/views/user/record/list.vue

@@ -17,7 +17,13 @@
         </div>
         <div class="filter-control">
           <span>IP:</span>
-          <el-input v-model="listQuery.ip" placeholder="IP" clearable @keyup.enter.native="getList" @clear="getList" />
+          <el-input
+            v-model="listQuery.ip"
+            placeholder="IP"
+            clearable
+            @keyup.enter.native="getList"
+            @clear="getList"
+          />
         </div>
         <div class="filter-control">
           <span>手机号:</span>
@@ -41,20 +47,14 @@
             @clear="getList"
           />
         </div>
-        <div class="filter-control">
-          <span>协销:</span>
-          <el-input
-            v-model="listQuery.spName"
-            placeholder="协销"
-            clearable
-            style="width: 120px"
-            @keyup.enter.native="getList"
-            @clear="getList"
-          />
-        </div>
         <div class="filter-control">
           <span>公司类型:</span>
-          <el-select v-model="listQuery.companyType" style="width: 120px" clearable @change="getList">
+          <el-select
+            v-model="listQuery.companyType"
+            style="width: 120px"
+            clearable
+            @change="getList"
+          >
             <el-option value="" label="请选择" />
             <el-option :value="1" label="游客" />
             <el-option :value="2" label="机构" />
@@ -63,7 +63,12 @@
         </div>
         <div class="filter-control">
           <span>访问客户端:</span>
-          <el-select v-model="listQuery.accessClient" style="width: 120px" clearable @change="getList">
+          <el-select
+            v-model="listQuery.accessClient"
+            style="width: 120px"
+            clearable
+            @change="getList"
+          >
             <el-option value="" label="请选择" />
             <el-option :value="0" label="网站" />
             <el-option :value="1" label="小程序" />
@@ -73,27 +78,53 @@
           <span>关联供应商:</span>
           <el-select v-model="listQuery.relevanceShop" filterable @change="getList">
             <el-option value="" label="请选择" />
-            <el-option v-for="item in supplierList" :key="item.shopId" :label="item.shopName" :value="item.shopName" />
+            <el-option
+              v-for="item in supplierList"
+              :key="item.shopId"
+              :label="item.shopName"
+              :value="item.shopName"
+            />
           </el-select>
         </div>
         <div class="filter-control">
           <span>供应商标签:</span>
-          <el-select v-model="tagsList" multiple filterable placeholder="请选择" @change="handleTagsOptions">
-            <el-option v-for="(item, index) in tagsOptions" :key="index" :label="item.value" :value="item.value" />
+          <el-select
+            v-model="tagsList"
+            multiple
+            filterable
+            placeholder="请选择"
+            @change="handleTagsOptions"
+          >
+            <el-option
+              v-for="(item, index) in tagsOptions"
+              :key="index"
+              :label="item.value"
+              :value="item.value"
+            />
           </el-select>
         </div>
         <div class="filter-control">
           <span>父标签:</span>
           <el-select v-model="listQuery.pageLabels" filterable @change="getList">
             <el-option value="" label="请选择" />
-            <el-option v-for="item in labelsOptions" :key="item.id" :label="item.value" :value="item.value" />
+            <el-option
+              v-for="item in labelsOptions"
+              :key="item.id"
+              :label="item.value"
+              :value="item.value"
+            />
           </el-select>
         </div>
         <div class="filter-control">
           <span>页面类型:</span>
           <el-select v-model="listQuery.pageTypes" filterable @change="getList">
             <el-option value="" label="请选择" />
-            <el-option v-for="item in typesOptions" :key="item.id" :label="item.pageType" :value="item.pageType" />
+            <el-option
+              v-for="item in typesOptions"
+              :key="item.id"
+              :label="item.pageType"
+              :value="item.pageType"
+            />
           </el-select>
         </div>
         <div v-if="tabsCurrent === 1" class="filter-control">
@@ -124,21 +155,67 @@
             @change="getList"
           />
         </div>
+        <div class="filter-control">
+          <span>协销:</span>
+          <el-select v-model="listQuery.headUserId" placeholder="协销" @change="getList">
+            <el-option
+              v-for="item in sellerList"
+              :key="item.serviceProviderId"
+              :label="item.linkMan"
+              :value="item.serviceProviderId"
+            />
+          </el-select>
+        </div>
+        <div class="filter-control">
+          <span>内容库ID:</span>
+          <el-input
+            v-model="listQuery.spName"
+            placeholder="内容库"
+            clearable
+            style="width: 120px"
+            @keyup.enter.native="getList"
+            @clear="getList"
+          />
+        </div>
         <div class="filter-control">
           <el-button type="primary" @click="getList">查询</el-button>
-          <el-button v-permission="'keyword:list:export'" type="primary" @click="handleExport"> 导出 </el-button>
+          <el-button
+            v-permission="'keyword:list:export'"
+            type="primary"
+            @click="handleExport"
+          >
+            导出
+          </el-button>
         </div>
       </div>
       <!-- 关键词列表 -->
-      <el-table v-loading="isLoading" :data="list" border style="width: 100%" :height="tableHeight">
+      <el-table
+        v-loading="isLoading"
+        :data="list"
+        border
+        style="width: 100%"
+        :height="tableHeight"
+      >
         <el-table-column prop="ip" fixed label="IP" align="center" width="80" />
         <el-table-column prop="region" fixed label="地区" align="center" width="100" />
-        <el-table-column prop="accessClient" fixed label="访问客户端" align="center" width="70">
+        <el-table-column
+          prop="accessClient"
+          fixed
+          label="访问客户端"
+          align="center"
+          width="70"
+        >
           <template slot-scope="{ row }">
             {{ row.accessClient | accessClientFilters }}
           </template>
         </el-table-column>
-        <el-table-column prop="companyType" fixed label="公司类型" align="center" width="70">
+        <el-table-column
+          prop="companyType"
+          fixed
+          label="公司类型"
+          align="center"
+          width="70"
+        >
           <template slot-scope="{ row }">
             {{ row.companyType | companyTypeFilters }}
           </template>
@@ -147,11 +224,11 @@
           <template slot-scope="{ row }">
             <template v-if="row.companyType === '2'">
               <a style="color: #2fa4e7" target="_blank" :href="handleClubHrefLink(row)">
-                {{ row.corporateName ? row.corporateName : '---' }}
+                {{ row.corporateName ? row.corporateName : "---" }}
               </a>
             </template>
             <template v-else>
-              {{ row.corporateName ? row.corporateName : '---' }}
+              {{ row.corporateName ? row.corporateName : "---" }}
             </template>
           </template>
         </el-table-column>
@@ -159,29 +236,34 @@
           <template slot-scope="{ row }">
             <template v-if="row.companyType === '2'">
               <a style="color: #2fa4e7" target="_blank" :href="handleClubHrefLink(row)">
-                {{ row.contacts ? row.contacts : '---' }}
+                {{ row.contacts ? row.contacts : "---" }}
               </a>
             </template>
             <template v-else>
-              {{ row.contacts ? row.contacts : '---' }}
+              {{ row.contacts ? row.contacts : "---" }}
             </template>
           </template>
         </el-table-column>
         <el-table-column prop="phoneNumber" label="手机号" align="center" width="110">
           <template slot-scope="{ row }">
-            {{ row.phoneNumber ? row.phoneNumber : '---' }}
+            {{ row.phoneNumber ? row.phoneNumber : "---" }}
           </template>
         </el-table-column>
         <el-table-column prop="spName" label="所属协销" align="center" width="100">
           <template slot-scope="{ row }">
-            {{ row.spName ? row.spName : '---' }}
+            {{ row.spName ? row.spName : "---" }}
           </template>
         </el-table-column>
-        <el-table-column prop="relevanceShop" label="关联供应商" align="center" width="210">
+        <el-table-column
+          prop="relevanceShop"
+          label="关联供应商"
+          align="center"
+          width="210"
+        >
           <template slot-scope="{ row }">
             <template v-if="row.relevanceShop">
               <p v-for="item in handleRelevanceShop(row.relevanceShop)" :key="item">
-                {{ item ? item : '---' }}
+                {{ item ? item : "---" }}
               </p>
             </template>
             <p v-else>---</p>
@@ -190,25 +272,32 @@
         <el-table-column prop="spName" label="供应商标签" align="center" width="120">
           <template slot-scope="{ row }">
             <el-popover placement="top-start" width="400" trigger="hover">
-              <span>{{ row.label ? row.label : '---' }}</span>
-              <span slot="reference">{{ row.label ? row.label.substr(0, 9) + '...' : '--' }}</span>
+              <span>{{ row.label ? row.label : "---" }}</span>
+              <span slot="reference">{{
+                row.label ? row.label.substr(0, 9) + "..." : "--"
+              }}</span>
             </el-popover>
             <!-- {{ row.label ? row.label : '---' }} -->
           </template>
         </el-table-column>
         <el-table-column prop="addTime" label="注册时间" align="center" width="100">
           <template slot-scope="{ row }">
-            {{ row.addTime ? row.addTime : '---' }}
+            {{ row.addTime ? row.addTime : "---" }}
           </template>
         </el-table-column>
         <el-table-column prop="numbers" label="访问页面数量" align="center" width="50">
           <template slot-scope="{ row }">
-            {{ row.numbers ? row.numbers : '0' }}
+            {{ row.numbers ? row.numbers : "0" }}
           </template>
         </el-table-column>
         <el-table-column prop="accessDuration" label="总时长" align="center" width="80" />
         <el-table-column prop="accessDate" label="访问日期" align="center" width="100" />
-        <el-table-column prop="lastAccessTime" label="最后访问时间" align="center" width="100" />
+        <el-table-column
+          prop="lastAccessTime"
+          label="最后访问时间"
+          align="center"
+          width="100"
+        />
         <el-table-column fixed="right" label="操作" align="center" width="120">
           <template slot-scope="{ row }">
             <el-button type="primary" size="mini" @click="handleRecordDetail(row)">查看详情</el-button>
@@ -227,7 +316,12 @@
       />
     </el-tabs>
     <!--  详情弹窗 -->
-    <detail-dialog v-if="dialogVisible" ref="detailDialog" :detail-query="detailQuery" @cancel="handleCancel" />
+    <detail-dialog
+      v-if="dialogVisible"
+      ref="detailDialog"
+      :detail-query="detailQuery"
+      @cancel="handleCancel"
+    />
   </div>
 </template>
 
@@ -237,6 +331,7 @@ import { fetchRecordList, getKeyword, getPageType } from '@/api/user/record/reco
 import { fetchShopRelevanceList, searchBehaviorList } from '@/api/user/supplier/supplier'
 import { export_json_to_excel } from '@/vendor/Export2Excel'
 import pickerOptions from '@/utils/time-picker.js'
+import { getServiceList } from '@/api/library/keyword'
 import DetailDialog from './components/detail-dialog'
 const defaultListQuery = {
   todayType: 0, // 0 今日数据 1 以往数据
@@ -257,7 +352,9 @@ const defaultListQuery = {
   registerStartTime: '', // 注册开始时间
   registerEndTime: '', // 注册结束时间
   pageNum: 1,
-  pageSize: 100
+  pageSize: 100,
+  headUserId: '', // 协销id
+  productArchiveId: '' // 内容库id
 }
 export default {
   name: 'RecordList',
@@ -306,6 +403,7 @@ export default {
       typesOptions: [], // 页面类型
       tagsOptions: [],
       tagsList: [],
+      sellerList: [], // 协销列表
       tabsCurrent: 0,
       tableHeight: window.innerHeight - 120, // 表格动态高度
       screenHeight: window.innerHeight, // 内容区域高度
@@ -334,6 +432,7 @@ export default {
     this.getList()
     this.getKeyword()
     this.getPageType()
+    this.getServiceList()
   },
   mounted() {
     // window.onresize:浏览器尺寸变化响应事件
@@ -490,7 +589,8 @@ export default {
     async handleExport() {
       if (this.tabsCurrent === 0) {
         // 导出今日数据
-        const url = process.env.VUE_APP_BASE_API + '/user/behavior/exportToday?todayType=0'
+        const url =
+          process.env.VUE_APP_BASE_API + '/user/behavior/exportToday?todayType=0'
         downloadWithUrl(url, '用户行为记录')
       } else {
         const url =
@@ -572,6 +672,10 @@ export default {
         return array
       }
       return value.split(',')
+    },
+    async getServiceList() {
+      const { data } = await getServiceList()
+      this.sellerList = data
     }
   }
 }

+ 129 - 129
src/views/wechat/components/textForm.vue

@@ -1,129 +1,129 @@
-<template>
-  <div class="app-container">
-    <el-page-header :content="isEdit?'编辑文本素材':'添加文本素材'" @back="goBack" />
-    <el-card class="form-container" shadow="never">
-      <el-form ref="weChatTextFrom" :model="text" :rules="rules" label-width="150px">
-        <el-form-item label="标题:" prop="title">
-          <el-input v-model="text.title" />
-        </el-form-item>
-        <el-form-item label="内容:" prop="content">
-          <el-input v-model="text.content" type="textarea" />
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" @click="onSubmit('weChatTextFrom')">提交</el-button>
-          <el-button v-if="!isEdit" type="info" @click="resetForm('weChatTextFrom')">重置</el-button>
-          <el-button @click="goBack">返回</el-button>
-        </el-form-item>
-      </el-form>
-    </el-card>
-  </div>
-</template>
-<script>
-import { getText, updateText, createText } from '@/api/wechat/text'
-const defaultText = {
-  id: '',
-  title: '',
-  content: '',
-  type: ''
-}
-export default {
-  name: 'WeChatTextForm',
-  props: {
-    // type 类型: 1采美,2呵呵商城
-    type: {
-      type: Number,
-      default: 0
-    }
-  },
-  data() {
-    return {
-      rules: {
-        title: [{ required: true, message: '标题不能为空', trigger: 'blur' }]
-      },
-      text: Object.assign({}, defaultText),
-      isEdit: false
-    }
-  },
-  watch: {
-    $route(route) {
-      this.getFormData()
-    }
-  },
-  created() {
-    this.getFormData()
-  },
-  methods: {
-    goBack() {
-      // 调用全局挂载的方法,关闭当前标签页
-      this.$store.dispatch('tagsView/delView', this.$route)
-      // 返回上一步路由,返回上一个标签页
-      this.$router.go(-1)
-    },
-    getFormData() {
-      if (this.$route.query.id) {
-        this.text.id = this.$route.query.id
-        this.isEdit = true
-        getText(this.text.id).then(response => {
-          this.text.title = response.data.title
-          this.text.content = response.data.content
-        })
-      } else {
-        this.text.id = ''
-        this.isEdit = false
-        this.text = Object.assign({}, defaultText)
-      }
-      this.text.type = this.type
-    },
-    resetForm(formName) {
-      this.$refs[formName].resetFields()
-      this.text = Object.assign({}, defaultText)
-      this.getFormData()
-    },
-    onSubmit(formName) {
-      this.$refs[formName].validate(valid => {
-        console.log(this.text)
-        if (valid) {
-          this.$confirm('是否提交数据', '提示', {
-            confirmButtonText: '确定',
-            cancelButtonText: '取消',
-            type: 'warning'
-          }).then(() => {
-            const self = this
-            if (this.isEdit) {
-              updateText(this.$route.query.id, this.text).then(response => {
-                this.$message({
-                  message: '修改成功',
-                  type: 'success',
-                  duration: 1000
-                })
-                self.goBack()
-              })
-            } else {
-              createText(this.text).then(response => {
-                this.$refs[formName].resetFields()
-                this.resetForm(formName)
-                this.$message({
-                  message: '提交成功',
-                  type: 'success',
-                  duration: 1000
-                })
-                self.goBack()
-              })
-            }
-          })
-        } else {
-          this.$message({
-            message: '验证失败',
-            type: 'error',
-            duration: 1000
-          })
-          return false
-        }
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-</style>
+<template>
+  <div class="app-container">
+    <el-page-header :content="isEdit?'编辑文本素材':'添加文本素材'" @back="goBack" />
+    <el-card class="form-container" shadow="never">
+      <el-form ref="weChatTextFrom" :model="text" :rules="rules" label-width="150px">
+        <el-form-item label="标题:" prop="title">
+          <el-input v-model="text.title" />
+        </el-form-item>
+        <el-form-item label="内容:" prop="content">
+          <el-input v-model="text.content" type="textarea" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="onSubmit('weChatTextFrom')">提交</el-button>
+          <el-button v-if="!isEdit" type="info" @click="resetForm('weChatTextFrom')">重置</el-button>
+          <el-button @click="goBack">返回</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+  </div>
+</template>
+<script>
+import { getText, updateText, createText } from '@/api/wechat/text'
+const defaultText = {
+  id: '',
+  title: '',
+  content: '',
+  type: ''
+}
+export default {
+  name: 'WeChatTextForm',
+  props: {
+    // type 类型: 1采美,2呵呵商城
+    type: {
+      type: Number,
+      default: 0
+    }
+  },
+  data() {
+    return {
+      rules: {
+        title: [{ required: true, message: '标题不能为空', trigger: 'blur' }]
+      },
+      text: Object.assign({}, defaultText),
+      isEdit: false
+    }
+  },
+  watch: {
+    $route(route) {
+      this.getFormData()
+    }
+  },
+  created() {
+    this.getFormData()
+  },
+  methods: {
+    goBack() {
+      // 调用全局挂载的方法,关闭当前标签页
+      this.$store.dispatch('tagsView/delView', this.$route)
+      // 返回上一步路由,返回上一个标签页
+      this.$router.go(-1)
+    },
+    getFormData() {
+      if (this.$route.query.id) {
+        this.text.id = this.$route.query.id
+        this.isEdit = true
+        getText(this.text.id).then(response => {
+          this.text.title = response.data.title
+          this.text.content = response.data.content
+        })
+      } else {
+        this.text.id = ''
+        this.isEdit = false
+        this.text = Object.assign({}, defaultText)
+      }
+      this.text.type = this.type
+    },
+    resetForm(formName) {
+      this.$refs[formName].resetFields()
+      this.text = Object.assign({}, defaultText)
+      this.getFormData()
+    },
+    onSubmit(formName) {
+      this.$refs[formName].validate(valid => {
+        console.log(this.text)
+        if (valid) {
+          this.$confirm('是否提交数据', '提示', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'warning'
+          }).then(() => {
+            const self = this
+            if (this.isEdit) {
+              updateText(this.$route.query.id, this.text).then(response => {
+                this.$message({
+                  message: '修改成功',
+                  type: 'success',
+                  duration: 1000
+                })
+                self.goBack()
+              })
+            } else {
+              createText(this.text).then(response => {
+                this.$refs[formName].resetFields()
+                this.resetForm(formName)
+                this.$message({
+                  message: '提交成功',
+                  type: 'success',
+                  duration: 1000
+                })
+                self.goBack()
+              })
+            }
+          })
+        } else {
+          this.$message({
+            message: '验证失败',
+            type: 'error',
+            duration: 1000
+          })
+          return false
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+</style>

+ 16 - 16
src/views/wechat/hehe/article/form.vue

@@ -1,16 +1,16 @@
-<template>
-  <article-form :type="wechatType" />
-</template>
-<script>
-import ArticleForm from '../../components/articleForm'
-export default {
-  name: 'WeChatHeArticleEdit',
-  components: { ArticleForm },
-  data: function() {
-    return {
-      // wechatType 类型: 1采美,2呵呵商城
-      wechatType: 2
-    }
-  }
-}
-</script>
+<template>
+  <article-form :type="wechatType" />
+</template>
+<script>
+import ArticleForm from '../../components/articleForm'
+export default {
+  name: 'WeChatHeArticleEdit',
+  components: { ArticleForm },
+  data: function() {
+    return {
+      // wechatType 类型: 1采美,2呵呵商城
+      wechatType: 2
+    }
+  }
+}
+</script>

+ 16 - 16
src/views/wechat/hehe/article/list.vue

@@ -1,16 +1,16 @@
-<template>
-  <article-list :list-type="wechatType" />
-</template>
-<script>
-import ArticleList from '../../components/articleList'
-export default {
-  name: 'WeChatHeArticleList',
-  components: { ArticleList },
-  data: function() {
-    return {
-      // wechatType 类型: 1采美,2呵呵商城
-      wechatType: 2
-    }
-  }
-}
-</script>
+<template>
+  <article-list :list-type="wechatType" />
+</template>
+<script>
+import ArticleList from '../../components/articleList'
+export default {
+  name: 'WeChatHeArticleList',
+  components: { ArticleList },
+  data: function() {
+    return {
+      // wechatType 类型: 1采美,2呵呵商城
+      wechatType: 2
+    }
+  }
+}
+</script>

+ 13 - 13
src/views/wechat/hehe/menus/form.vue

@@ -1,13 +1,13 @@
-<template>
-  <menus-form />
-</template>
-<script>
-import MenusForm from '../../components/menusForm'
-export default {
-  name: 'WeChatHeMenuEdit',
-  components: { MenusForm }
-}
-</script>
-<style>
-
-</style>
+<template>
+  <menus-form />
+</template>
+<script>
+import MenusForm from '../../components/menusForm'
+export default {
+  name: 'WeChatHeMenuEdit',
+  components: { MenusForm }
+}
+</script>
+<style>
+
+</style>

+ 16 - 16
src/views/wechat/hehe/menus/list.vue

@@ -1,16 +1,16 @@
-<template>
-  <menus-list :list-type="wechatType" />
-</template>
-<script>
-import MenusList from '../../components/menusList'
-export default {
-  name: 'WeChatHeMenuList',
-  components: { MenusList },
-  data: function() {
-    return {
-      // wechatType 类型: 1采美,2呵呵商城
-      wechatType: 2
-    }
-  }
-}
-</script>
+<template>
+  <menus-list :list-type="wechatType" />
+</template>
+<script>
+import MenusList from '../../components/menusList'
+export default {
+  name: 'WeChatHeMenuList',
+  components: { MenusList },
+  data: function() {
+    return {
+      // wechatType 类型: 1采美,2呵呵商城
+      wechatType: 2
+    }
+  }
+}
+</script>

+ 16 - 16
src/views/wechat/hehe/reply/form.vue

@@ -1,16 +1,16 @@
-<template>
-  <reply-form :type="wechatType" />
-</template>
-<script>
-import ReplyForm from '../../components/replyForm'
-export default {
-  name: 'WeChatHeReplyEdit',
-  components: { ReplyForm },
-  data: function() {
-    return {
-      // wechatType 类型: 1采美,2呵呵商城
-      wechatType: 2
-    }
-  }
-}
-</script>
+<template>
+  <reply-form :type="wechatType" />
+</template>
+<script>
+import ReplyForm from '../../components/replyForm'
+export default {
+  name: 'WeChatHeReplyEdit',
+  components: { ReplyForm },
+  data: function() {
+    return {
+      // wechatType 类型: 1采美,2呵呵商城
+      wechatType: 2
+    }
+  }
+}
+</script>

+ 16 - 16
src/views/wechat/hehe/reply/list.vue

@@ -1,16 +1,16 @@
-<template>
-  <reply-list :list-type="wechatType" />
-</template>
-<script>
-import ReplyList from '../../components/replyList'
-export default {
-  name: 'WeChatHeReplyList',
-  components: { ReplyList },
-  data: function() {
-    return {
-      // wechatType 类型: 1采美,2呵呵商城
-      wechatType: 2
-    }
-  }
-}
-</script>
+<template>
+  <reply-list :list-type="wechatType" />
+</template>
+<script>
+import ReplyList from '../../components/replyList'
+export default {
+  name: 'WeChatHeReplyList',
+  components: { ReplyList },
+  data: function() {
+    return {
+      // wechatType 类型: 1采美,2呵呵商城
+      wechatType: 2
+    }
+  }
+}
+</script>

+ 16 - 16
src/views/wechat/hehe/text/form.vue

@@ -1,16 +1,16 @@
-<template>
-  <text-form :type="wechatType" />
-</template>
-<script>
-import TextForm from '../../components/textForm'
-export default {
-  name: 'WeChatHeTextEdit',
-  components: { TextForm },
-  data: function() {
-    return {
-      // wechatType 类型: 1采美,2呵呵商城
-      wechatType: 2
-    }
-  }
-}
-</script>
+<template>
+  <text-form :type="wechatType" />
+</template>
+<script>
+import TextForm from '../../components/textForm'
+export default {
+  name: 'WeChatHeTextEdit',
+  components: { TextForm },
+  data: function() {
+    return {
+      // wechatType 类型: 1采美,2呵呵商城
+      wechatType: 2
+    }
+  }
+}
+</script>

+ 16 - 16
src/views/wechat/hehe/text/list.vue

@@ -1,16 +1,16 @@
-<template>
-  <text-list :list-type="wechatType" />
-</template>
-<script>
-import TextList from '../../components/textList'
-export default {
-  name: 'WeChatHeTextList',
-  components: { TextList },
-  data: function() {
-    return {
-      // wechatType 类型: 1采美,2呵呵商城
-      wechatType: 2
-    }
-  }
-}
-</script>
+<template>
+  <text-list :list-type="wechatType" />
+</template>
+<script>
+import TextList from '../../components/textList'
+export default {
+  name: 'WeChatHeTextList',
+  components: { TextList },
+  data: function() {
+    return {
+      // wechatType 类型: 1采美,2呵呵商城
+      wechatType: 2
+    }
+  }
+}
+</script>

+ 11 - 11
tailwind.config.js

@@ -1,11 +1,11 @@
-module.exports = {
-  purge: ['./src/**/*.html', './src/**/*.vue'],
-  darkMode: false, // or 'media' or 'class'
-  theme: {
-    extend: {}
-  },
-  variants: {
-    extend: {}
-  },
-  plugins: []
-}
+module.exports = {
+  purge: ['./src/**/*.html', './src/**/*.vue'],
+  darkMode: false, // or 'media' or 'class'
+  theme: {
+    extend: {}
+  },
+  variants: {
+    extend: {}
+  },
+  plugins: []
+}

+ 5 - 5
tests/unit/.eslintrc.js

@@ -1,5 +1,5 @@
-module.exports = {
-  env: {
-    jest: true
-  }
-}
+module.exports = {
+  env: {
+    jest: true
+  }
+}

+ 18 - 18
tests/unit/components/Hamburger.spec.js

@@ -1,18 +1,18 @@
-import { shallowMount } from '@vue/test-utils'
-import Hamburger from '@/components/Hamburger/index.vue'
-describe('Hamburger.vue', () => {
-  it('toggle click', () => {
-    const wrapper = shallowMount(Hamburger)
-    const mockFn = jest.fn()
-    wrapper.vm.$on('toggleClick', mockFn)
-    wrapper.find('.hamburger').trigger('click')
-    expect(mockFn).toBeCalled()
-  })
-  it('prop isActive', () => {
-    const wrapper = shallowMount(Hamburger)
-    wrapper.setProps({ isActive: true })
-    expect(wrapper.contains('.is-active')).toBe(true)
-    wrapper.setProps({ isActive: false })
-    expect(wrapper.contains('.is-active')).toBe(false)
-  })
-})
+import { shallowMount } from '@vue/test-utils'
+import Hamburger from '@/components/Hamburger/index.vue'
+describe('Hamburger.vue', () => {
+  it('toggle click', () => {
+    const wrapper = shallowMount(Hamburger)
+    const mockFn = jest.fn()
+    wrapper.vm.$on('toggleClick', mockFn)
+    wrapper.find('.hamburger').trigger('click')
+    expect(mockFn).toBeCalled()
+  })
+  it('prop isActive', () => {
+    const wrapper = shallowMount(Hamburger)
+    wrapper.setProps({ isActive: true })
+    expect(wrapper.contains('.is-active')).toBe(true)
+    wrapper.setProps({ isActive: false })
+    expect(wrapper.contains('.is-active')).toBe(false)
+  })
+})

+ 22 - 22
tests/unit/components/SvgIcon.spec.js

@@ -1,22 +1,22 @@
-import { shallowMount } from '@vue/test-utils'
-import SvgIcon from '@/components/SvgIcon/index.vue'
-describe('SvgIcon.vue', () => {
-  it('iconClass', () => {
-    const wrapper = shallowMount(SvgIcon, {
-      propsData: {
-        iconClass: 'test'
-      }
-    })
-    expect(wrapper.find('use').attributes().href).toBe('#icon-test')
-  })
-  it('className', () => {
-    const wrapper = shallowMount(SvgIcon, {
-      propsData: {
-        iconClass: 'test'
-      }
-    })
-    expect(wrapper.classes().length).toBe(1)
-    wrapper.setProps({ className: 'test' })
-    expect(wrapper.classes().includes('test')).toBe(true)
-  })
-})
+import { shallowMount } from '@vue/test-utils'
+import SvgIcon from '@/components/SvgIcon/index.vue'
+describe('SvgIcon.vue', () => {
+  it('iconClass', () => {
+    const wrapper = shallowMount(SvgIcon, {
+      propsData: {
+        iconClass: 'test'
+      }
+    })
+    expect(wrapper.find('use').attributes().href).toBe('#icon-test')
+  })
+  it('className', () => {
+    const wrapper = shallowMount(SvgIcon, {
+      propsData: {
+        iconClass: 'test'
+      }
+    })
+    expect(wrapper.classes().length).toBe(1)
+    wrapper.setProps({ className: 'test' })
+    expect(wrapper.classes().includes('test')).toBe(true)
+  })
+})

+ 29 - 29
tests/unit/utils/formatTime.spec.js

@@ -1,29 +1,29 @@
-import { formatTime } from '@/utils/index.js'
-describe('Utils:formatTime', () => {
-  const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
-  const retrofit = 5 * 1000
-
-  it('ten digits timestamp', () => {
-    expect(formatTime((d / 1000).toFixed(0))).toBe('7月13日17时54分')
-  })
-  it('test now', () => {
-    expect(formatTime(+new Date() - 1)).toBe('刚刚')
-  })
-  it('less two minute', () => {
-    expect(formatTime(+new Date() - 60 * 2 * 1000 + retrofit)).toBe('2分钟前')
-  })
-  it('less two hour', () => {
-    expect(formatTime(+new Date() - 60 * 60 * 2 * 1000 + retrofit)).toBe('2小时前')
-  })
-  it('less one day', () => {
-    expect(formatTime(+new Date() - 60 * 60 * 24 * 1 * 1000)).toBe('1天前')
-  })
-  it('more than one day', () => {
-    expect(formatTime(d)).toBe('7月13日17时54分')
-  })
-  it('format', () => {
-    expect(formatTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
-    expect(formatTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
-    expect(formatTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
-  })
-})
+import { formatTime } from '@/utils/index.js'
+describe('Utils:formatTime', () => {
+  const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
+  const retrofit = 5 * 1000
+
+  it('ten digits timestamp', () => {
+    expect(formatTime((d / 1000).toFixed(0))).toBe('7月13日17时54分')
+  })
+  it('test now', () => {
+    expect(formatTime(+new Date() - 1)).toBe('刚刚')
+  })
+  it('less two minute', () => {
+    expect(formatTime(+new Date() - 60 * 2 * 1000 + retrofit)).toBe('2分钟前')
+  })
+  it('less two hour', () => {
+    expect(formatTime(+new Date() - 60 * 60 * 2 * 1000 + retrofit)).toBe('2小时前')
+  })
+  it('less one day', () => {
+    expect(formatTime(+new Date() - 60 * 60 * 24 * 1 * 1000)).toBe('1天前')
+  })
+  it('more than one day', () => {
+    expect(formatTime(d)).toBe('7月13日17时54分')
+  })
+  it('format', () => {
+    expect(formatTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
+    expect(formatTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
+    expect(formatTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
+  })
+})

+ 14 - 14
tests/unit/utils/param2Obj.spec.js

@@ -1,14 +1,14 @@
-import { param2Obj } from '@/utils/index.js'
-describe('Utils:param2Obj', () => {
-  const url = 'https://github.com/PanJiaChen/vue-element-admin?name=bill&age=29&sex=1&field=dGVzdA==&key=%E6%B5%8B%E8%AF%95'
-
-  it('param2Obj test', () => {
-    expect(param2Obj(url)).toEqual({
-      name: 'bill',
-      age: '29',
-      sex: '1',
-      field: window.btoa('test'),
-      key: '测试'
-    })
-  })
-})
+import { param2Obj } from '@/utils/index.js'
+describe('Utils:param2Obj', () => {
+  const url = 'https://github.com/PanJiaChen/vue-element-admin?name=bill&age=29&sex=1&field=dGVzdA==&key=%E6%B5%8B%E8%AF%95'
+
+  it('param2Obj test', () => {
+    expect(param2Obj(url)).toEqual({
+      name: 'bill',
+      age: '29',
+      sex: '1',
+      field: window.btoa('test'),
+      key: '测试'
+    })
+  })
+})

+ 37 - 37
tests/unit/utils/parseTime.spec.js

@@ -1,37 +1,37 @@
-import { parseTime } from '@/utils/index.js'
-
-describe('Utils:parseTime', () => {
-  const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
-  it('timestamp', () => {
-    expect(parseTime(d)).toBe('2018-07-13 17:54:01')
-  })
-
-  it('timestamp string', () => {
-    expect(parseTime((d + ''))).toBe('2018-07-13 17:54:01')
-  })
-
-  it('ten digits timestamp', () => {
-    expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01')
-  })
-  it('new Date', () => {
-    expect(parseTime(new Date(d))).toBe('2018-07-13 17:54:01')
-  })
-  it('format', () => {
-    expect(parseTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
-    expect(parseTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
-    expect(parseTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
-  })
-  it('get the day of the week', () => {
-    expect(parseTime(d, '{a}')).toBe('五') // 星期五
-  })
-  it('get the day of the week', () => {
-    expect(parseTime(+d + 1000 * 60 * 60 * 24 * 2, '{a}')).toBe('日') // 星期日
-  })
-  it('empty argument', () => {
-    expect(parseTime()).toBeNull()
-  })
-
-  it('null', () => {
-    expect(parseTime(null)).toBeNull()
-  })
-})
+import { parseTime } from '@/utils/index.js'
+
+describe('Utils:parseTime', () => {
+  const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
+  it('timestamp', () => {
+    expect(parseTime(d)).toBe('2018-07-13 17:54:01')
+  })
+
+  it('timestamp string', () => {
+    expect(parseTime((d + ''))).toBe('2018-07-13 17:54:01')
+  })
+
+  it('ten digits timestamp', () => {
+    expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01')
+  })
+  it('new Date', () => {
+    expect(parseTime(new Date(d))).toBe('2018-07-13 17:54:01')
+  })
+  it('format', () => {
+    expect(parseTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
+    expect(parseTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
+    expect(parseTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
+  })
+  it('get the day of the week', () => {
+    expect(parseTime(d, '{a}')).toBe('五') // 星期五
+  })
+  it('get the day of the week', () => {
+    expect(parseTime(+d + 1000 * 60 * 60 * 24 * 2, '{a}')).toBe('日') // 星期日
+  })
+  it('empty argument', () => {
+    expect(parseTime()).toBeNull()
+  })
+
+  it('null', () => {
+    expect(parseTime(null)).toBeNull()
+  })
+})

+ 28 - 28
tests/unit/utils/validate.spec.js

@@ -1,28 +1,28 @@
-import { validUsername, validURL, validLowerCase, validUpperCase, validAlphabets } from '@/utils/validate.js'
-describe('Utils:validate', () => {
-  it('validUsername', () => {
-    expect(validUsername('admin')).toBe(true)
-    expect(validUsername('editor')).toBe(true)
-    expect(validUsername('xxxx')).toBe(false)
-  })
-  it('validURL', () => {
-    expect(validURL('https://github.com/PanJiaChen/vue-element-admin')).toBe(true)
-    expect(validURL('http://github.com/PanJiaChen/vue-element-admin')).toBe(true)
-    expect(validURL('github.com/PanJiaChen/vue-element-admin')).toBe(false)
-  })
-  it('validLowerCase', () => {
-    expect(validLowerCase('abc')).toBe(true)
-    expect(validLowerCase('Abc')).toBe(false)
-    expect(validLowerCase('123abc')).toBe(false)
-  })
-  it('validUpperCase', () => {
-    expect(validUpperCase('ABC')).toBe(true)
-    expect(validUpperCase('Abc')).toBe(false)
-    expect(validUpperCase('123ABC')).toBe(false)
-  })
-  it('validAlphabets', () => {
-    expect(validAlphabets('ABC')).toBe(true)
-    expect(validAlphabets('Abc')).toBe(true)
-    expect(validAlphabets('123aBC')).toBe(false)
-  })
-})
+import { validUsername, validURL, validLowerCase, validUpperCase, validAlphabets } from '@/utils/validate.js'
+describe('Utils:validate', () => {
+  it('validUsername', () => {
+    expect(validUsername('admin')).toBe(true)
+    expect(validUsername('editor')).toBe(true)
+    expect(validUsername('xxxx')).toBe(false)
+  })
+  it('validURL', () => {
+    expect(validURL('https://github.com/PanJiaChen/vue-element-admin')).toBe(true)
+    expect(validURL('http://github.com/PanJiaChen/vue-element-admin')).toBe(true)
+    expect(validURL('github.com/PanJiaChen/vue-element-admin')).toBe(false)
+  })
+  it('validLowerCase', () => {
+    expect(validLowerCase('abc')).toBe(true)
+    expect(validLowerCase('Abc')).toBe(false)
+    expect(validLowerCase('123abc')).toBe(false)
+  })
+  it('validUpperCase', () => {
+    expect(validUpperCase('ABC')).toBe(true)
+    expect(validUpperCase('Abc')).toBe(false)
+    expect(validUpperCase('123ABC')).toBe(false)
+  })
+  it('validAlphabets', () => {
+    expect(validAlphabets('ABC')).toBe(true)
+    expect(validAlphabets('Abc')).toBe(true)
+    expect(validAlphabets('123aBC')).toBe(false)
+  })
+})

+ 124 - 124
vue.config.js

@@ -1,124 +1,124 @@
-'use strict'
-const path = require('path')
-const defaultSettings = require('./src/settings.js')
-
-function resolve(dir) {
-  return path.join(__dirname, dir)
-}
-
-const name = defaultSettings.title || '采美后台' // page title
-
-// If your port is set to 80,
-// use administrator privileges to execute the command line.
-// For example, Mac: sudo npm run
-// You can change the port by the following method:
-// port = 9527 npm run dev OR npm run dev --port = 9527
-const port = process.env.port || process.env.npm_config_port || 9527 // dev port
-
-// All configuration item explanations can be find in https://cli.vuejs.org/config/
-module.exports = {
-  /**
-   * You will need to set publicPath if you plan to deploy your site under a sub path,
-   * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
-   * then publicPath should be set to "/bar/".
-   * In most cases please use '/' !!!
-   * Detail: https://cli.vuejs.org/config/#publicpath
-   */
-  publicPath: '/',
-  outputDir: 'dist',
-  assetsDir: 'static',
-  lintOnSave: process.env.NODE_ENV === 'development',
-  productionSourceMap: false,
-  devServer: {
-    port: port,
-    open: true,
-    overlay: {
-      warnings: false,
-      errors: true
-    }
-    // before: require('./mock/mock-server.js')
-  },
-  configureWebpack: {
-    // provide the app's title in webpack's name field, so that
-    // it can be accessed in index.html to inject the correct title.
-    name: name,
-    resolve: {
-      alias: {
-        '@': resolve('src')
-      }
-    }
-  },
-  chainWebpack(config) {
-    // it can improve the speed of the first screen, it is recommended to turn on preload
-    // it can improve the speed of the first screen, it is recommended to turn on preload
-    config.plugin('preload').tap(() => [
-      {
-        rel: 'preload',
-        // to ignore runtime.js
-        // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
-        fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
-        include: 'initial'
-      }
-    ])
-
-    // when there are many pages, it will cause too many meaningless requests
-    config.plugins.delete('prefetch')
-
-    // set svg-sprite-loader
-    config.module
-      .rule('svg')
-      .exclude.add(resolve('src/icons'))
-      .end()
-    config.module
-      .rule('icons')
-      .test(/\.svg$/)
-      .include.add(resolve('src/icons'))
-      .end()
-      .use('svg-sprite-loader')
-      .loader('svg-sprite-loader')
-      .options({
-        symbolId: 'icon-[name]'
-      })
-      .end()
-
-    config
-      .when(process.env.NODE_ENV !== 'development',
-        config => {
-          config
-            .plugin('ScriptExtHtmlWebpackPlugin')
-            .after('html')
-            .use('script-ext-html-webpack-plugin', [{
-            // `runtime` must same as runtimeChunk name. default is `runtime`
-              inline: /runtime\..*\.js$/
-            }])
-            .end()
-          config
-            .optimization.splitChunks({
-              chunks: 'all',
-              cacheGroups: {
-                libs: {
-                  name: 'chunk-libs',
-                  test: /[\\/]node_modules[\\/]/,
-                  priority: 10,
-                  chunks: 'initial' // only package third parties that are initially dependent
-                },
-                elementUI: {
-                  name: 'chunk-elementUI', // split elementUI into a single package
-                  priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
-                  test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
-                },
-                commons: {
-                  name: 'chunk-commons',
-                  test: resolve('src/components'), // can customize your rules
-                  minChunks: 3, //  minimum common number
-                  priority: 5,
-                  reuseExistingChunk: true
-                }
-              }
-            })
-          // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
-          config.optimization.runtimeChunk('single')
-        }
-      )
-  }
-}
+'use strict'
+const path = require('path')
+const defaultSettings = require('./src/settings.js')
+
+function resolve(dir) {
+  return path.join(__dirname, dir)
+}
+
+const name = defaultSettings.title || '采美后台' // page title
+
+// If your port is set to 80,
+// use administrator privileges to execute the command line.
+// For example, Mac: sudo npm run
+// You can change the port by the following method:
+// port = 9527 npm run dev OR npm run dev --port = 9527
+const port = process.env.port || process.env.npm_config_port || 9527 // dev port
+
+// All configuration item explanations can be find in https://cli.vuejs.org/config/
+module.exports = {
+  /**
+   * You will need to set publicPath if you plan to deploy your site under a sub path,
+   * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
+   * then publicPath should be set to "/bar/".
+   * In most cases please use '/' !!!
+   * Detail: https://cli.vuejs.org/config/#publicpath
+   */
+  publicPath: '/',
+  outputDir: 'dist',
+  assetsDir: 'static',
+  lintOnSave: process.env.NODE_ENV === 'development',
+  productionSourceMap: false,
+  devServer: {
+    port: port,
+    open: true,
+    overlay: {
+      warnings: false,
+      errors: true
+    }
+    // before: require('./mock/mock-server.js')
+  },
+  configureWebpack: {
+    // provide the app's title in webpack's name field, so that
+    // it can be accessed in index.html to inject the correct title.
+    name: name,
+    resolve: {
+      alias: {
+        '@': resolve('src')
+      }
+    }
+  },
+  chainWebpack(config) {
+    // it can improve the speed of the first screen, it is recommended to turn on preload
+    // it can improve the speed of the first screen, it is recommended to turn on preload
+    config.plugin('preload').tap(() => [
+      {
+        rel: 'preload',
+        // to ignore runtime.js
+        // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
+        fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
+        include: 'initial'
+      }
+    ])
+
+    // when there are many pages, it will cause too many meaningless requests
+    config.plugins.delete('prefetch')
+
+    // set svg-sprite-loader
+    config.module
+      .rule('svg')
+      .exclude.add(resolve('src/icons'))
+      .end()
+    config.module
+      .rule('icons')
+      .test(/\.svg$/)
+      .include.add(resolve('src/icons'))
+      .end()
+      .use('svg-sprite-loader')
+      .loader('svg-sprite-loader')
+      .options({
+        symbolId: 'icon-[name]'
+      })
+      .end()
+
+    config
+      .when(process.env.NODE_ENV !== 'development',
+        config => {
+          config
+            .plugin('ScriptExtHtmlWebpackPlugin')
+            .after('html')
+            .use('script-ext-html-webpack-plugin', [{
+            // `runtime` must same as runtimeChunk name. default is `runtime`
+              inline: /runtime\..*\.js$/
+            }])
+            .end()
+          config
+            .optimization.splitChunks({
+              chunks: 'all',
+              cacheGroups: {
+                libs: {
+                  name: 'chunk-libs',
+                  test: /[\\/]node_modules[\\/]/,
+                  priority: 10,
+                  chunks: 'initial' // only package third parties that are initially dependent
+                },
+                elementUI: {
+                  name: 'chunk-elementUI', // split elementUI into a single package
+                  priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
+                  test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
+                },
+                commons: {
+                  name: 'chunk-commons',
+                  test: resolve('src/components'), // can customize your rules
+                  minChunks: 3, //  minimum common number
+                  priority: 5,
+                  reuseExistingChunk: true
+                }
+              }
+            })
+          // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
+          config.optimization.runtimeChunk('single')
+        }
+      )
+  }
+}