W 1 year ago
commit
c92dd52113
100 changed files with 3706 additions and 0 deletions
  1. 14 0
      .editorconfig
  2. 14 0
      .env.development
  3. 5 0
      .env.production
  4. 8 0
      .env.staging
  5. 4 0
      .eslintignore
  6. 198 0
      .eslintrc.js
  7. 17 0
      .gitignore
  8. 5 0
      .travis.yml
  9. 21 0
      LICENSE
  10. 102 0
      README-zh.md
  11. 90 0
      README.md
  12. 14 0
      babel.config.js
  13. 35 0
      build/index.js
  14. BIN
      dist.rar
  15. BIN
      dist.zip
  16. 24 0
      jest.config.js
  17. 9 0
      jsconfig.json
  18. 57 0
      mock/index.js
  19. 81 0
      mock/mock-server.js
  20. 29 0
      mock/table.js
  21. 84 0
      mock/user.js
  22. 25 0
      mock/utils.js
  23. 77 0
      package.json
  24. 8 0
      postcss.config.js
  25. BIN
      public/favicon.ico
  26. 17 0
      public/index.html
  27. 109 0
      src/App.vue
  28. 6 0
      src/api/activity.js
  29. 39 0
      src/api/admin.js
  30. 11 0
      src/api/category.js
  31. 9 0
      src/api/comment.js
  32. 95 0
      src/api/company.js
  33. 13 0
      src/api/friendlyLinks.js
  34. 15 0
      src/api/goods.js
  35. 49 0
      src/api/index.js
  36. 26 0
      src/api/invest.js
  37. 14 0
      src/api/login.js
  38. 17 0
      src/api/notice.js
  39. 32 0
      src/api/role.js
  40. 36 0
      src/api/system/index.js
  41. 36 0
      src/api/system/reward.js
  42. 46 0
      src/api/system/version.js
  43. 9 0
      src/api/table.js
  44. 5 0
      src/api/upload.js
  45. 17 0
      src/api/workstation.js
  46. BIN
      src/assets/401_images/401.gif
  47. BIN
      src/assets/404_images/404.png
  48. BIN
      src/assets/404_images/404_cloud.png
  49. BIN
      src/assets/img/arrow.png
  50. BIN
      src/assets/img/bottom.jpg
  51. BIN
      src/assets/img/center.jpg
  52. BIN
      src/assets/img/copper.png
  53. BIN
      src/assets/img/del.png
  54. BIN
      src/assets/img/del_no.png
  55. BIN
      src/assets/img/double.png
  56. BIN
      src/assets/img/gold.png
  57. BIN
      src/assets/img/ref.png
  58. BIN
      src/assets/img/side.png
  59. BIN
      src/assets/img/silver.png
  60. BIN
      src/assets/img/top.jpg
  61. BIN
      src/assets/vip.png
  62. 80 0
      src/components/Breadcrumb/index.vue
  63. 78 0
      src/components/ErrorLog/index.vue
  64. 44 0
      src/components/Hamburger/index.vue
  65. 187 0
      src/components/HeaderSearch/index.vue
  66. 105 0
      src/components/Pagination/index.vue
  67. 120 0
      src/components/ResetPwd/index.vue
  68. 145 0
      src/components/RightPanel/index.vue
  69. 52 0
      src/components/RoleCheckbox/index.vue
  70. 60 0
      src/components/Screenfull/index.vue
  71. 57 0
      src/components/SizeSelect/index.vue
  72. 62 0
      src/components/SvgIcon/index.vue
  73. 175 0
      src/components/ThemePicker/index.vue
  74. 111 0
      src/components/Tinymce/components/EditorImage.vue
  75. 59 0
      src/components/Tinymce/dynamicLoadScript.js
  76. 254 0
      src/components/Tinymce/index.vue
  77. 12 0
      src/components/Tinymce/plugins.js
  78. 8 0
      src/components/Tinymce/toolbar.js
  79. 4 0
      src/filterRouter/_import_development.js
  80. 5 0
      src/filterRouter/_import_production.js
  81. 21 0
      src/filterRouter/index.js
  82. 231 0
      src/filters/common.js
  83. 103 0
      src/filters/compressPic.js
  84. 110 0
      src/filters/index.js
  85. 8 0
      src/filters/systemSet.js
  86. 74 0
      src/filters/validate.js
  87. 9 0
      src/icons/index.js
  88. 1 0
      src/icons/svg/account.svg
  89. 0 0
      src/icons/svg/dashboard.svg
  90. 1 0
      src/icons/svg/edit.svg
  91. 1 0
      src/icons/svg/email.svg
  92. 1 0
      src/icons/svg/example.svg
  93. 1 0
      src/icons/svg/excel.svg
  94. 1 0
      src/icons/svg/eye-open.svg
  95. 1 0
      src/icons/svg/eye.svg
  96. 0 0
      src/icons/svg/form.svg
  97. 1 0
      src/icons/svg/goods.svg
  98. 1 0
      src/icons/svg/home.svg
  99. 1 0
      src/icons/svg/inte.svg
  100. 0 0
      src/icons/svg/inte_manage.svg

+ 14 - 0
.editorconfig

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

+ 14 - 0
.env.development

@@ -0,0 +1,14 @@
+# just a flag
+ENV = 'development'
+
+# base api
+VUE_APP_BASE_API = '/dev-api'
+
+# vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable,
+# to control whether the babel-plugin-dynamic-import-node plugin is enabled.
+# It only does one thing by converting all import() to require().
+# This configuration can significantly increase the speed of hot updates,
+# when you have a large number of pages.
+# Detail:  https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js
+
+VUE_CLI_BABEL_TRANSPILE_MODULES = true

+ 5 - 0
.env.production

@@ -0,0 +1,5 @@
+# just a flag
+ENV = 'production'
+
+VUE_APP_BASE_API = 'http://api.xysyszhd.dev.xmnk.cn/api'
+

+ 8 - 0
.env.staging

@@ -0,0 +1,8 @@
+NODE_ENV = production
+
+# just a flag
+ENV = 'staging'
+
+# base api
+VUE_APP_BASE_API = '/stage-api'
+

+ 4 - 0
.eslintignore

@@ -0,0 +1,4 @@
+build/*.js
+src/assets
+public
+dist

+ 198 - 0
.eslintrc.js

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

+ 17 - 0
.gitignore

@@ -0,0 +1,17 @@
+.DS_Store
+node_modules/
+dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+package-lock.json
+tests/**/coverage/
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+yarn-error.log

+ 5 - 0
.travis.yml

@@ -0,0 +1,5 @@
+language: node_js
+node_js: 10
+script: npm run test
+notifications:
+  email: false

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017-present PanJiaChen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 102 - 0
README-zh.md

@@ -0,0 +1,102 @@
+# vue-admin-template
+
+> 这是一个极简的 vue admin 管理后台。它只包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。
+
+[线上地址](http://panjiachen.github.io/vue-admin-template)
+
+[国内访问](https://panjiachen.gitee.io/vue-admin-template)
+
+目前版本为 `v4.0+` 基于 `vue-cli` 进行构建,若你想使用旧版本,可以切换分支到[tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0),它不依赖 `vue-cli`。
+
+## Extra
+
+如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
+
+## 相关项目
+
+- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
+
+- [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
+
+- [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
+
+- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
+
+写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目:
+
+- [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2)
+- [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac)
+- [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35)
+- [手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板,专门针对本项目的文章,算作是一篇文档)](https://juejin.im/post/595b4d776fb9a06bbe7dba56)
+- [手摸手,带你封装一个 vue component](https://segmentfault.com/a/1190000009090836)
+
+## Build Setup
+
+```bash
+# 克隆项目
+git clone https://github.com/PanJiaChen/vue-admin-template.git
+
+# 进入项目目录
+cd vue-admin-template
+
+# 安装依赖
+npm install
+
+# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
+npm install --registry=https://registry.npm.taobao.org
+
+# 启动服务
+npm run dev
+```
+
+浏览器访问 [http://localhost:9528](http://localhost:9528)
+
+## 发布
+
+```bash
+# 构建测试环境
+npm run build:stage
+
+# 构建生产环境
+npm run build:prod
+```
+
+## 其它
+
+```bash
+# 预览发布环境效果
+npm run preview
+
+# 预览发布环境效果 + 静态资源分析
+npm run preview -- --report
+
+# 代码格式检查
+npm run lint
+
+# 代码格式检查并自动修复
+npm run lint -- --fix
+```
+
+更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/)
+
+## 购买贴纸
+
+你也可以通过 购买[官方授权的贴纸](https://smallsticker.com/product/vue-element-admin) 的方式来支持 vue-element-admin - 每售出一张贴纸,我们将获得 2 元的捐赠。
+
+## Demo
+
+![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif)
+
+## Browsers support
+
+Modern browsers and Internet Explorer 10+.
+
+| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
+| --------- | --------- | --------- | --------- |
+| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
+
+## License
+
+[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license.
+
+Copyright (c) 2017-present PanJiaChen

+ 90 - 0
README.md

@@ -0,0 +1,90 @@
+# vue-admin-template
+
+English | [简体中文](./README-zh.md)
+
+> A minimal vue admin template with Element UI & axios & iconfont & permission control & lint
+
+**Live demo:** http://panjiachen.github.io/vue-admin-template
+
+
+**The current version is `v4.0+` build on `vue-cli`. If you want to use the old version , you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0), it does not rely on `vue-cli`**
+
+## Build Setup
+
+```bash
+# clone the project
+git clone https://github.com/PanJiaChen/vue-admin-template.git
+
+# enter the project directory
+cd vue-admin-template
+
+# install dependency
+npm install
+
+# develop
+npm run dev
+```
+
+This will automatically open http://localhost:9528
+
+## Build
+
+```bash
+# build for test environment
+npm run build:stage
+
+# build for production environment
+npm run build:prod
+```
+
+## Advanced
+
+```bash
+# preview the release environment effect
+npm run preview
+
+# preview the release environment effect + static resource analysis
+npm run preview -- --report
+
+# code format check
+npm run lint
+
+# code format check and auto fix
+npm run lint -- --fix
+```
+
+Refer to [Documentation](https://panjiachen.github.io/vue-element-admin-site/guide/essentials/deploy.html) for more information
+
+## Demo
+
+![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif)
+
+## Extra
+
+If you want router permission && generate menu by user roles , you can use this branch [permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
+
+For `typescript` version, you can use [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour))
+
+## Related Project
+
+- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
+
+- [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
+
+- [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
+
+- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
+
+## Browsers support
+
+Modern browsers and Internet Explorer 10+.
+
+| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
+| --------- | --------- | --------- | --------- |
+| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
+
+## License
+
+[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license.
+
+Copyright (c) 2017-present PanJiaChen

+ 14 - 0
babel.config.js

@@ -0,0 +1,14 @@
+module.exports = {
+  presets: [
+    // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
+    '@vue/cli-plugin-babel/preset'
+  ],
+  'env': {
+    'development': {
+      // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
+      // This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
+      // https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
+      'plugins': ['dynamic-import-node']
+    }
+  }
+}

+ 35 - 0
build/index.js

@@ -0,0 +1,35 @@
+const { run } = require('runjs')
+const chalk = require('chalk')
+const config = require('../vue.config.js')
+const rawArgv = process.argv.slice(2)
+const args = rawArgv.join(' ')
+
+if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
+  const report = rawArgv.includes('--report')
+
+  run(`vue-cli-service build ${args}`)
+
+  const port = 9526
+  const publicPath = config.publicPath
+
+  var connect = require('connect')
+  var serveStatic = require('serve-static')
+  const app = connect()
+
+  app.use(
+    publicPath,
+    serveStatic('./dist', {
+      index: ['index.html', '/']
+    })
+  )
+
+  app.listen(port, function () {
+    console.log(chalk.green(`> Preview at  http://localhost:${port}${publicPath}`))
+    if (report) {
+      console.log(chalk.green(`> Report at  http://localhost:${port}${publicPath}report.html`))
+    }
+
+  })
+} else {
+  run(`vue-cli-service build ${args}`)
+}

BIN
dist.rar


BIN
dist.zip


+ 24 - 0
jest.config.js

@@ -0,0 +1,24 @@
+module.exports = {
+  moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
+  transform: {
+    '^.+\\.vue$': 'vue-jest',
+    '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
+      'jest-transform-stub',
+    '^.+\\.jsx?$': 'babel-jest'
+  },
+  moduleNameMapper: {
+    '^@/(.*)$': '<rootDir>/src/$1'
+  },
+  snapshotSerializers: ['jest-serializer-vue'],
+  testMatch: [
+    '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
+  ],
+  collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
+  coverageDirectory: '<rootDir>/tests/unit/coverage',
+  // 'collectCoverage': true,
+  'coverageReporters': [
+    'lcov',
+    'text-summary'
+  ],
+  testURL: 'http://localhost/'
+}

+ 9 - 0
jsconfig.json

@@ -0,0 +1,9 @@
+{
+  "compilerOptions": {
+    "baseUrl": "./",
+    "paths": {
+        "@/*": ["src/*"]
+    }
+  },
+  "exclude": ["node_modules", "dist"]
+}

+ 57 - 0
mock/index.js

@@ -0,0 +1,57 @@
+const Mock = require('mockjs')
+const { param2Obj } = require('./utils')
+
+const user = require('./user')
+const table = require('./table')
+
+const mocks = [
+  ...user,
+  ...table
+]
+
+// for front mock
+// please use it cautiously, it will redefine XMLHttpRequest,
+// which will cause many of your third-party libraries to be invalidated(like progress event).
+function mockXHR() {
+  // mock patch
+  // https://github.com/nuysoft/Mock/issues/300
+  Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
+  Mock.XHR.prototype.send = function() {
+    if (this.custom.xhr) {
+      this.custom.xhr.withCredentials = this.withCredentials || false
+
+      if (this.responseType) {
+        this.custom.xhr.responseType = this.responseType
+      }
+    }
+    this.proxy_send(...arguments)
+  }
+
+  function XHR2ExpressReqWrap(respond) {
+    return function(options) {
+      let result = null
+      if (respond instanceof Function) {
+        const { body, type, url } = options
+        // https://expressjs.com/en/4x/api.html#req
+        result = respond({
+          method: type,
+          body: JSON.parse(body),
+          query: param2Obj(url)
+        })
+      } else {
+        result = respond
+      }
+      return Mock.mock(result)
+    }
+  }
+
+  for (const i of mocks) {
+    Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
+  }
+}
+
+module.exports = {
+  mocks,
+  mockXHR
+}
+

+ 81 - 0
mock/mock-server.js

@@ -0,0 +1,81 @@
+const chokidar = require('chokidar')
+const bodyParser = require('body-parser')
+const chalk = require('chalk')
+const path = require('path')
+const Mock = require('mockjs')
+
+const mockDir = path.join(process.cwd(), 'mock')
+
+function registerRoutes(app) {
+  let mockLastIndex
+  const { mocks } = require('./index.js')
+  const mocksForServer = mocks.map(route => {
+    return responseFake(route.url, route.type, route.response)
+  })
+  for (const mock of mocksForServer) {
+    app[mock.type](mock.url, mock.response)
+    mockLastIndex = app._router.stack.length
+  }
+  const mockRoutesLength = Object.keys(mocksForServer).length
+  return {
+    mockRoutesLength: mockRoutesLength,
+    mockStartIndex: mockLastIndex - mockRoutesLength
+  }
+}
+
+function unregisterRoutes() {
+  Object.keys(require.cache).forEach(i => {
+    if (i.includes(mockDir)) {
+      delete require.cache[require.resolve(i)]
+    }
+  })
+}
+
+// for mock server
+const responseFake = (url, type, respond) => {
+  return {
+    url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
+    type: type || 'get',
+    response(req, res) {
+      console.log('request invoke:' + req.path)
+      res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
+    }
+  }
+}
+
+module.exports = app => {
+  // parse app.body
+  // https://expressjs.com/en/4x/api.html#req.body
+  app.use(bodyParser.json())
+  app.use(bodyParser.urlencoded({
+    extended: true
+  }))
+
+  const mockRoutes = registerRoutes(app)
+  var mockRoutesLength = mockRoutes.mockRoutesLength
+  var mockStartIndex = mockRoutes.mockStartIndex
+
+  // watch files, hot reload mock server
+  chokidar.watch(mockDir, {
+    ignored: /mock-server/,
+    ignoreInitial: true
+  }).on('all', (event, path) => {
+    if (event === 'change' || event === 'add') {
+      try {
+        // remove mock routes stack
+        app._router.stack.splice(mockStartIndex, mockRoutesLength)
+
+        // clear routes cache
+        unregisterRoutes()
+
+        const mockRoutes = registerRoutes(app)
+        mockRoutesLength = mockRoutes.mockRoutesLength
+        mockStartIndex = mockRoutes.mockStartIndex
+
+        console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed  ${path}`))
+      } catch (error) {
+        console.log(chalk.redBright(error))
+      }
+    }
+  })
+}

+ 29 - 0
mock/table.js

@@ -0,0 +1,29 @@
+const Mock = require('mockjs')
+
+const data = Mock.mock({
+  'items|30': [{
+    id: '@id',
+    title: '@sentence(10, 20)',
+    'status|1': ['published', 'draft', 'deleted'],
+    author: 'name',
+    display_time: '@datetime',
+    pageviews: '@integer(300, 5000)'
+  }]
+})
+
+module.exports = [
+  {
+    url: '/vue-admin-template/table/list',
+    type: 'get',
+    response: config => {
+      const items = data.items
+      return {
+        code: 20000,
+        data: {
+          total: items.length,
+          items: items
+        }
+      }
+    }
+  }
+]

+ 84 - 0
mock/user.js

@@ -0,0 +1,84 @@
+
+const tokens = {
+  admin: {
+    token: 'admin-token'
+  },
+  editor: {
+    token: 'editor-token'
+  }
+}
+
+const users = {
+  'admin-token': {
+    roles: ['admin'],
+    introduction: 'I am a super administrator',
+    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
+    name: 'Super Admin'
+  },
+  'editor-token': {
+    roles: ['editor'],
+    introduction: 'I am an editor',
+    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
+    name: 'Normal Editor'
+  }
+}
+
+module.exports = [
+  // user login
+  {
+    url: '/vue-admin-template/user/login',
+    type: 'post',
+    response: config => {
+      const { username } = config.body
+      const token = tokens[username]
+
+      // mock error
+      if (!token) {
+        return {
+          code: 60204,
+          message: 'Account and password are incorrect.'
+        }
+      }
+
+      return {
+        code: 20000,
+        data: token
+      }
+    }
+  },
+
+  // get user info
+  {
+    url: '/vue-admin-template/user/info\.*',
+    type: 'get',
+    response: config => {
+      const { token } = config.query
+      const info = users[token]
+
+      // mock error
+      if (!info) {
+        return {
+          code: 50008,
+          message: 'Login failed, unable to get user details.'
+        }
+      }
+
+      return {
+        code: 20000,
+        data: info
+      }
+    }
+  },
+
+  // user logout
+  {
+    url: '/vue-admin-template/user/logout',
+    type: 'post',
+    response: _ => {
+      return {
+        code: 20000,
+        data: 'success'
+      }
+    }
+  }
+]

+ 25 - 0
mock/utils.js

@@ -0,0 +1,25 @@
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
+function param2Obj(url) {
+  const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
+  if (!search) {
+    return {}
+  }
+  const obj = {}
+  const searchArr = search.split('&')
+  searchArr.forEach(v => {
+    const index = v.indexOf('=')
+    if (index !== -1) {
+      const name = v.substring(0, index)
+      const val = v.substring(index + 1, v.length)
+      obj[name] = val
+    }
+  })
+  return obj
+}
+
+module.exports = {
+  param2Obj
+}

+ 77 - 0
package.json

@@ -0,0 +1,77 @@
+{
+  "name": "vue-admin-template",
+  "version": "4.4.0",
+  "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
+  "author": "Pan <panfree23@gmail.com>",
+  "scripts": {
+    "dev": "vue-cli-service serve",
+    "build:prod": "vue-cli-service build",
+    "build:stage": "vue-cli-service build --mode staging",
+    "preview": "node build/index.js --preview",
+    "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
+    "lint": "eslint --ext .js,.vue src",
+    "test:unit": "jest --clearCache && vue-cli-service test:unit",
+    "test:ci": "npm run lint && npm run test:unit"
+  },
+  "dependencies": {
+    "axios": "0.18.1",
+    "blueimp-md5": "^2.19.0",
+    "core-js": "3.6.5",
+    "echarts": "^4.1.0",
+    "element-ui": "2.13.2",
+    "file-saver": "^2.0.5",
+    "fuse.js": "^6.4.6",
+    "html2canvas": "^1.0.0-rc.7",
+    "jquery": "^3.6.0",
+    "js-cookie": "2.2.0",
+    "normalize.css": "7.0.0",
+    "nprogress": "0.2.0",
+    "path-to-regexp": "2.4.0",
+    "qiniu-js": "^3.4.1",
+    "screenfull": "^5.1.0",
+    "sortablejs": "^1.13.0",
+    "vue": "2.6.10",
+    "vue-canvas-poster": "^1.2.1",
+    "vue-html2canvas": "0.0.4",
+    "vue-quill-editor": "^3.0.6",
+    "vue-router": "3.0.6",
+    "vuex": "3.1.0",
+    "xlsx": "^0.16.9"
+  },
+  "devDependencies": {
+    "@babel/polyfill": "^7.12.1",
+    "@vue/cli-plugin-babel": "4.4.4",
+    "@vue/cli-plugin-eslint": "4.4.4",
+    "@vue/cli-plugin-unit-jest": "4.4.4",
+    "@vue/cli-service": "4.4.4",
+    "@vue/test-utils": "1.0.0-beta.29",
+    "autoprefixer": "9.5.1",
+    "babel-eslint": "10.1.0",
+    "babel-jest": "23.6.0",
+    "babel-plugin-dynamic-import-node": "2.3.3",
+    "babel-plugin-transform-runtime": "^6.23.0",
+    "chalk": "2.4.2",
+    "connect": "3.6.6",
+    "eslint": "6.7.2",
+    "eslint-plugin-vue": "6.2.2",
+    "html-webpack-plugin": "3.2.0",
+    "runjs": "4.3.2",
+    "sass": "1.26.8",
+    "sass-loader": "8.0.2",
+    "script-ext-html-webpack-plugin": "2.1.3",
+    "serve-static": "1.13.2",
+    "svg-sprite-loader": "4.1.3",
+    "svgo": "1.2.2",
+    "vue-template-compiler": "2.6.10",
+    "webpack-bundle-analyzer": "^3.6.0"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions"
+  ],
+  "engines": {
+    "node": ">=8.9",
+    "npm": ">= 3.0.0"
+  },
+  "license": "MIT"
+}

+ 8 - 0
postcss.config.js

@@ -0,0 +1,8 @@
+// https://github.com/michael-ciniawsky/postcss-load-config
+
+module.exports = {
+  'plugins': {
+    // to edit target browsers: use "browserslist" field in package.json
+    'autoprefixer': {}
+  }
+}

BIN
public/favicon.ico


+ 17 - 0
public/index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title><%= webpackConfig.name %></title>
+  </head>
+  <body>
+    <noscript>
+      <strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 109 - 0
src/App.vue

@@ -0,0 +1,109 @@
+
+<template>
+  <div id="app">
+    <router-view />
+  </div>
+</template>
+
+<script>
+export default {
+  name: "App"
+};
+</script>
+<style lang="scss">
+*::-webkit-scrollbar {
+  width: 7px;
+  height: 10px;
+  background-color: transparent;
+} /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
+*::-webkit-scrollbar-track {
+  background-color: #f0f6ff;
+} /*定义滚动条轨道 内阴影+圆角*/
+*::-webkit-scrollbar-thumb {
+  background-color: #c0c0c0;
+  border-radius: 6px;
+} /*定义滑块 内阴影+圆角*/
+.scrollbarHide::-webkit-scrollbar {
+  display: none;
+}
+.scrollbarShow::-webkit-scrollbar {
+  display: block;
+}
+.flexV {
+	display: flex;
+	align-items: center;
+}
+.flexT {
+	display: flex;
+	justify-content: flex-start;
+	align-items: flex-start;
+}
+.flexC {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.flexS {
+	display: flex;
+	align-items: center;
+	justify-content: flex-start;
+}
+.flexSS {
+	display: flex;
+	align-items: flex-start;
+	justify-content: flex-start;
+}
+
+.flexE {
+	display: flex;
+	align-items: center;
+	justify-content: flex-end;
+}
+
+.flexA {
+	display: flex;
+	justify-content: space-around;
+	align-items: center;
+}
+
+.flexB {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+}
+.flexBS {
+	display: flex;
+	justify-content: space-between;
+	align-items: flex-start;
+}
+
+.flexCC {
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	align-items: center;
+}
+.content {
+  width: 100%;
+  padding: 24px;
+  box-sizing: border-box;
+  .search_box{
+    margin-bottom:24px;
+	.el-input{
+		width: 200px;
+		margin:0 10px 0 10px;
+	}
+	.el-select{
+		width: 200px;
+		margin-right:10px;
+	}
+  }
+  .paging_box{
+	margin-top:24px;
+  }
+}
+.el-drawer__body{
+  overflow-y: scroll;
+}
+</style>

+ 6 - 0
src/api/activity.js

@@ -0,0 +1,6 @@
+import request from '@/utils/request'
+
+// 管理员登出
+export const loginOut = data => request({ url: '/auth/logout', method: 'post', data })
+// 获取管理员信息
+export const getUserInfo = params => request({ url: '/auth/login/user', method: 'get', params })

+ 39 - 0
src/api/admin.js

@@ -0,0 +1,39 @@
+/*
+ * @Author: 阿小凉
+ * @Date: 2023-11-25 09:01:46
+ * @LastEditTime: 2023-11-27 10:05:12
+ */
+import request from '@/utils/request'
+
+// 管理员列表
+export const adminList = params => request({
+  url: '/user/list',
+  method: 'get',
+  params
+})
+// 添加管理员
+export const addAccount = data => request({
+  url: '/user/add',
+  method: 'post',
+  data
+})
+// 修改管理员
+export const editAccount = data => request({
+  url: '/user/edit',
+  method: 'post',
+  data
+})
+
+// 删除管理员
+export const deleteAccount = data => request({
+  url: '/user/delete',
+  method: 'post',
+  data
+})
+
+// 禁用管理员
+export const disabledAdmin = data => request({
+  url: '/user/disable',
+  method: 'post',
+  data
+})

+ 11 - 0
src/api/category.js

@@ -0,0 +1,11 @@
+// 分类
+import request from '@/utils/request'
+
+// 分类列表
+export const categoryList = params => request({ url: '/category/list', method: 'get', params })
+// 新增分类
+export const addCategory = data => request({ url: '/category/add', method: 'post', data })
+// 修改分类
+export const editCategory = data => request({ url: '/category/edit', method: 'post', data })
+// 删除分类
+export const deleteCategory = data => request({ url: '/category/delete', method: 'post', data })

+ 9 - 0
src/api/comment.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+ 
+export const getCommentList = params => request({ url: '/comment/list', method: 'get', params })  //获取所有评论
+
+export const getDetail = params => request({ url: '/comment/detail', method: 'get', params })  //获取所有评论
+
+export const delComment = data => request({ url: '/comment/destroy', method: 'post', data })  //获取所有评论
+

+ 95 - 0
src/api/company.js

@@ -0,0 +1,95 @@
+/*
+ * @Author: 阿小凉
+ * @Date: 2023-11-25 09:01:46
+ * @LastEditTime: 2023-11-27 15:27:55
+ */
+
+// 入驻企业
+import request from '@/utils/request'
+
+// 企业列表
+export const companyList = params => request({ url: '/company/list', method: 'get', params })
+// 企业信息
+export const companyInfo = params => request({ url: '/company/info', method: 'get', params })
+// 全部企业
+export const companyAll = params => request({ url: '/company/all', method: 'get', params })
+// 模块分类
+export const companyCategory = params => request({ url: '/company/category', method: 'get', params })
+// 新增企业
+export const addCompany = data => request({ url: '/company/add', method: 'post', data })
+// 修改企业
+export const editCompany = data => request({ url: '/company/edit', method: 'post', data })
+// 删除企业
+export const deleteCompany = data => request({ url: '/company/delete', method: 'post', data })
+// 导入企业
+export const importCompany = data => request({ url: '/company/import', method: 'post', data })
+
+
+// 人才管理
+
+// 人才列表
+export const talentList = params => request({ url: '/talent/list', method: 'get', params })
+// 分类模块
+export const talentCategory = params => request({ url: '/talent/category', method: 'get', params })
+// 新增人才
+export const addTalent = data => request({ url: '/talent/add', method: 'post', data })
+// 修改人才
+export const editTalent = data => request({ url: '/talent/edit', method: 'post', data })
+// 删除人才
+export const deleteTalent = data => request({ url: '/talent/delete', method: 'post', data })
+// 导入人才
+export const importTalent = data => request({ url: '/talent/import', method: 'post', data })
+
+// 知识产权
+// 知识产权列表
+export const patentList = params => request({ url: '/patent/list', method: 'get', params })
+// 知识产权模块
+export const patentCategory = params => request({ url: '/patent/category', method: 'get', params })
+// 新增知识产权
+export const addPatent = data => request({ url: '/patent/add', method: 'post', data })
+// 修改知识产权
+export const editPatent = data => request({ url: '/patent/edit', method: 'post', data })
+// 删除知识产权
+export const deletePatent = data => request({ url: '/patent/delete', method: 'post', data })
+// 导入知识产权
+export const importPatent = data => request({ url: '/patent/import', method: 'post', data })
+
+// 企业项目
+// 企业项目列表
+export const projectList = params => request({ url: '/project/list', method: 'get', params })
+// 所有项目
+export const projectAll = params => request({ url: '/project/all', method: 'get', params })
+// 企业项目模块
+export const projectCategory = params => request({ url: '/project/category', method: 'get', params })
+// 新增企业项目
+export const addProject = data => request({ url: '/project/add', method: 'post', data })
+// 修改企业项目
+export const editProject = data => request({ url: '/project/edit', method: 'post', data })
+// 删除企业项目
+export const deleteProject = data => request({ url: '/project/delete', method: 'post', data })
+// 导入企业项目
+export const importProject = data => request({ url: '/project/import', method: 'post', data })
+
+// 企业产值
+// 企业产值列表
+export const infoList = params => request({ url: '/info/list', method: 'get', params })
+// 新增企业产值
+export const addInfo = data => request({ url: '/info/add', method: 'post', data })
+// 修改企业产值
+export const editInfo = data => request({ url: '/info/edit', method: 'post', data })
+// 删除企业产值
+export const deleteInfo = data => request({ url: '/info/delete', method: 'post', data })
+// 导入企业产值
+export const importInfo = data => request({ url: '/info/import', method: 'post', data })
+
+// 企业产品
+// 企业产品列表
+export const productList = params => request({ url: '/product/list', method: 'get', params })
+// 新增企业产品
+export const addProduct = data => request({ url: '/product/add', method: 'post', data })
+// 修改企业产品
+export const editProduct = data => request({ url: '/product/edit', method: 'post', data })
+// 删除企业产品
+export const deleteProduct = data => request({ url: '/product/delete', method: 'post', data })
+// 导入企业产品
+export const importProduct = data => request({ url: '/product/import', method: 'post', data })

+ 13 - 0
src/api/friendlyLinks.js

@@ -0,0 +1,13 @@
+// 友情链接
+import request from '@/utils/request'
+
+// 友情链接列表
+export const linkList = params => request({ url: '/link/list', method: 'get', params })
+// 增加友情链接
+export const addLink = data => request({ url: '/link/add', method: 'post', data })
+// 修改友情链接
+export const editLink = data => request({ url: '/link/edit', method: 'post', data })
+// 删除友情链接
+export const deleteLink = data => request({ url: '/link/delete', method: 'post', data })
+// 导入友情链接
+export const importLink = data => request({ url: '/link/import', method: 'post', data })

+ 15 - 0
src/api/goods.js

@@ -0,0 +1,15 @@
+
+import request from '@/utils/request'
+
+export const getList = params => request({ url: '/company/list', method: 'get', params }) // 获取企业列表
+
+export const addCompany = data => request({ url: '/company/add', method: 'post', data }) // 添加企业
+
+export const upLoadImg = data => request({ url: '/goods/upload_img', method: 'post', data }) // 上传商品图片
+
+export const deleteCompany = data => request({ url: '/company/delete', method: 'post', data }) // 删除企业
+
+export const editCompany = data => request({ url: '/company/edit', method: 'post', data }) // 编辑企业信息
+
+export const importCompany = data => request({ url: '/company/import', method: 'post', data }) // 导入企业信息
+

+ 49 - 0
src/api/index.js

@@ -0,0 +1,49 @@
+import request from '@/utils/request'
+// 获取所有商品尺码销量,销售额
+export const skuDaySale = params => request({ url: '/goods/get_goods_sku_sales_value', method: 'get', params })
+
+
+// 获取每日销售额
+export const dailySale = params => request({ url: '/order/get_order_daily_sales', method: 'get', params })
+
+// 获取店铺前十
+export const topTen = params => request({ url: '/order/get_store_top10', method: 'get', params })
+
+// 获取首页展示数据
+export const getData = params => request({ url: '/index/index', method: 'get', params })
+
+
+
+// 获取各省各种款式销售信息
+export const getProvinceAttr = params => request({ url: '/index/get_province_sales_attr', method: 'get', params })
+
+// 获取销售曲线
+export const getSaleInfo = params => request({ url: '/index/get_sale_info', method: 'get', params })
+
+// 获取销售总数据
+export const allOrderBy = params => request({ url: '/index/all_order_by', method: 'get', params })
+
+// 获取销售总数据
+export const yesterdayOrderBy = params => request({ url: '/index/yesterday_order_by', method: 'get', params })
+
+// 获取总销售额,退货额,取消额
+export const getHistoryTotal = params => request({ url: '/index/get_history_total', method: 'get', params })
+
+// 获取总销售额,退货额,取消额
+export const getSkuHistoryNum = params => request({ url: '/index/get_sku_history_num', method: 'get', params })
+
+// 获取总销售额,退货额,取消额
+export const skuOrderBy = params => request({ url: '/index/get_sku_order_by', method: 'get', params })
+
+// 每日新增注册人数
+export const addAgent = params => request({ url: '/index/get_every_add_agent', method: 'get', params })
+
+// 客服每日新增注册人数排行
+export const addSort = params => request({ url: '/index/get_every_add_agent_sort', method: 'get', params })
+
+// 每日客服新增公司客户变化曲线
+export const addSortLine = params => request({ url: '/index/get_every_add_agent_line', method: 'get', params })
+
+// 获取支付比例
+export const payRate = params => request({ url: '/index/get_agent_pay_rate', method: 'get', params })
+

+ 26 - 0
src/api/invest.js

@@ -0,0 +1,26 @@
+// 招商企业
+import request from '@/utils/request'
+
+
+// 活动列表
+export const investActList = params => request({ url: '/invest/act/list', method: 'get', params })
+// 所有活动
+export const investActAll = params => request({ url: '/invest/act/all', method: 'get', params })
+// 新增企业
+export const addInvestAct = data => request({ url: '/invest/act/add', method: 'post', data })
+// 修改企业
+export const editInvestAct = data => request({ url: '/invest/act/edit', method: 'post', data })
+// 删除企业
+export const deleteInvestAct = data => request({ url: '/invest/act/delete', method: 'post', data })
+
+
+// 招商成果
+// 成果列表
+export const investAchievementList = params => request({ url: '/invest/achievement/list', method: 'get', params })
+// 新增成果
+export const addInvestAchievement = data => request({ url: '/invest/achievement/add', method: 'post', data })
+// 修改项目
+export const editInvestAchievement = data => request({ url: '/invest/achievement/edit', method: 'post', data })
+// 删除成果
+export const deleteInvestAchievement = data => request({ url: '/invest/achievement/delete', method: 'post', data })
+

+ 14 - 0
src/api/login.js

@@ -0,0 +1,14 @@
+/*
+ * @Author: 阿小凉
+ * @Date: 2023-11-24 11:04:58
+ * @LastEditTime: 2023-11-24 14:07:57
+ */
+import request from '@/utils/request'
+
+// 管理员登录
+export const loginIn = data => request({ url: '/auth/login', method: 'post', data })
+// 管理员登出
+export const loginOut = data => request({ url: '/auth/logout', method: 'post', data })
+// 获取管理员信息
+export const getUserInfo = data => request({ url: '/auth/me', method: 'post', data })
+

+ 17 - 0
src/api/notice.js

@@ -0,0 +1,17 @@
+
+// 政策公告
+import request from '@/utils/request'
+
+
+// 政策公告列表
+export const noticeList = params => request({ url: '/notice/list', method: 'get', params })
+// 分类模块
+export const noticeCategory = params => request({ url: '/notice/category', method: 'get', params })
+// 新增政策公告
+export const addNotice = data => request({ url: '/notice/add', method: 'post', data })
+// 修改政策公告
+export const editNotice = data => request({ url: '/notice/edit', method: 'post', data })
+// 删除政策公告
+export const deleteNotice = data => request({ url: '/notice/delete', method: 'post', data })
+// 导入政策公告
+export const importNotice = data => request({ url: '/notice/import', method: 'post', data })

+ 32 - 0
src/api/role.js

@@ -0,0 +1,32 @@
+import request from '@/utils/request'
+
+// 所有角色
+export const getRoles = params => request({
+  url: '/role/all',
+  method: 'get',
+  params
+})
+// 角色列表
+export const roleList = params => request({
+  url: '/role/list',
+  method: 'get',
+  params
+})
+// 添加角色
+export const addRole = data => request({
+  url: '/role/add',
+  method: 'post',
+  data
+})
+// 修改角色
+export const editRole = data => request({
+  url: '/admin/update_role',
+  method: 'post',
+  data
+})
+// 删除角色
+export const delRole = data => request({
+  url: '/role/delete',
+  method: 'post',
+  data
+})

+ 36 - 0
src/api/system/index.js

@@ -0,0 +1,36 @@
+import request from '@/utils/request'
+
+// 获取所有设置
+export function getSetting() {
+  return request({
+    url: '/setting/getsetting',
+    method: 'get'
+  })
+}
+
+// 更改设置
+export function setSetting(data) {
+  return request({
+    url: '/setting/updatesystemset',
+    method: 'post',
+    data
+  })
+}
+
+// 添加字典设置
+export function addSetting(data) {
+  return request({
+    url: '/setting/uploadsetting',
+    method: 'post',
+    data
+  })
+}
+
+// 修改字典设置
+export function editSetting(data) {
+  return request({
+    url: '/setting/updatesetting',
+    method: 'post',
+    data
+  })
+}

+ 36 - 0
src/api/system/reward.js

@@ -0,0 +1,36 @@
+import request from '@/utils/request'
+
+// 设置奖励支出
+export function setFixed(data) {
+  return request({
+    url: '/setting/updaterewardpay',
+    method: 'post',
+    data
+  })
+}
+
+// 获取所有设置
+export function getSetting() {
+  return request({
+    url: '/setting/getsetting',
+    method: 'get'
+  })
+}
+
+// 设置店铺奖励
+export function setShop(data) {
+  return request({
+    url: '/setting/updatestorereward',
+    method: 'post',
+    data
+  })
+}
+
+// 设置奖励区间
+export function setQuota(data) {
+  return request({
+    url: '/setting/salesreward',
+    method: 'post',
+    data
+  })
+}

+ 46 - 0
src/api/system/version.js

@@ -0,0 +1,46 @@
+import request from '@/utils/request'
+
+// 获取列表
+export function getList(data) {
+  return request({
+    url: '/version/getversionlist',
+    method: 'Get',
+    params: data
+  })
+}
+
+// 上传新版本
+export function uploadVersion(data) {
+  return request({
+    url: '/version/uploadversion',
+    method: 'post',
+    data
+  })
+}
+
+// 修改新版本
+export function editVersion(data) {
+  return request({
+    url: '/version/updateversion',
+    method: 'post',
+    data
+  })
+}
+
+// 版本上线
+export function upVersion(data) {
+  return request({
+    url: '/version/setversiontop',
+    method: 'post',
+    data
+  })
+}
+
+// 删除
+export function deleteVersion(data) {
+  return request({
+    url: '/version/destoryversion',
+    method: 'post',
+    data
+  })
+}

+ 9 - 0
src/api/table.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+export function getList(params) {
+  return request({
+    url: '/vue-admin-template/table/list',
+    method: 'get',
+    params
+  })
+}

+ 5 - 0
src/api/upload.js

@@ -0,0 +1,5 @@
+// 上传文件
+import request from '@/utils/request'
+
+// 上传文件
+export const uploadFile = data => request({ url: '/file/upload', method: 'post', data })

+ 17 - 0
src/api/workstation.js

@@ -0,0 +1,17 @@
+
+// 工作站
+import request from '@/utils/request'
+
+
+// 工作站列表
+export const workstationList = params => request({ url: '/workstation/list', method: 'get', params })
+// 分类模块
+export const workstationCategory = params => request({ url: '/workstation/category', method: 'get', params })
+// 新增工作站
+export const addWorkstation = data => request({ url: '/workstation/add', method: 'post', data })
+// 修改工作站
+export const editWorkstation = data => request({ url: '/workstation/edit', method: 'post', data })
+// 删除工作站
+export const deleteWorkstation = data => request({ url: '/workstation/delete', method: 'post', data })
+// 导入工作站
+export const importWorkstation = data => request({ url: '/workstation/import', method: 'post', data })

BIN
src/assets/401_images/401.gif


BIN
src/assets/404_images/404.png


BIN
src/assets/404_images/404_cloud.png


BIN
src/assets/img/arrow.png


BIN
src/assets/img/bottom.jpg


BIN
src/assets/img/center.jpg


BIN
src/assets/img/copper.png


BIN
src/assets/img/del.png


BIN
src/assets/img/del_no.png


BIN
src/assets/img/double.png


BIN
src/assets/img/gold.png


BIN
src/assets/img/ref.png


BIN
src/assets/img/side.png


BIN
src/assets/img/silver.png


BIN
src/assets/img/top.jpg


BIN
src/assets/vip.png


+ 80 - 0
src/components/Breadcrumb/index.vue

@@ -0,0 +1,80 @@
+<template>
+  <el-breadcrumb class="app-breadcrumb" separator="/">
+    <transition-group name="breadcrumb">
+      <el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
+        <span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
+        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
+      </el-breadcrumb-item>
+    </transition-group>
+  </el-breadcrumb>
+</template>
+
+<script>
+import pathToRegexp from 'path-to-regexp'
+
+export default {
+  data() {
+    return {
+      levelList: null
+    }
+  },
+  watch: {
+    $route(route) {
+      // if you go to the redirect page, do not update the breadcrumbs
+      if (route.path.startsWith('/redirect/')) {
+        return
+      }
+      this.getBreadcrumb()
+    }
+  },
+  created() {
+    this.getBreadcrumb()
+  },
+  methods: {
+    getBreadcrumb() {
+      // only show routes with meta.title
+      const matched = this.$route.matched.filter(item => item.meta && item.meta.title)
+      const first = matched[0]
+      if (!this.isDashboard(first)) {
+        // matched = [{ path: '/order', meta: { title: '订单管理' }}].concat(matched)
+      }
+
+      this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
+    },
+    isDashboard(route) {
+      const name = route && route.name
+      if (!name) {
+        return false
+      }
+      return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
+    },
+    pathCompile(path) {
+      const { params } = this.$route
+      var toPath = pathToRegexp.compile(path)
+      return toPath(params)
+    },
+    handleLink(item) {
+      const { redirect, path } = item
+      if (redirect) {
+        this.$router.push(redirect)
+        return
+      }
+      this.$router.push(this.pathCompile(path))
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.app-breadcrumb.el-breadcrumb {
+  display: inline-block;
+  font-size: 14px;
+  line-height: 50px;
+  margin-left: 8px;
+
+  .no-redirect {
+    color: #97a8be;
+    cursor: text;
+  }
+}
+</style>

+ 78 - 0
src/components/ErrorLog/index.vue

@@ -0,0 +1,78 @@
+<template>
+  <div v-if="errorLogs.length>0">
+    <el-badge :is-dot="true" style="line-height: 25px;margin-top: -5px;" @click.native="dialogTableVisible=true">
+      <el-button style="padding: 8px 10px;" size="small" type="danger">
+        <svg-icon icon-class="bug" />
+      </el-button>
+    </el-badge>
+
+    <el-dialog :visible.sync="dialogTableVisible" width="80%" append-to-body>
+      <div slot="title">
+        <span style="padding-right: 10px;">Error Log</span>
+        <el-button size="mini" type="primary" icon="el-icon-delete" @click="clearAll">Clear All</el-button>
+      </div>
+      <el-table :data="errorLogs" border>
+        <el-table-column label="Message">
+          <template slot-scope="{row}">
+            <div>
+              <span class="message-title">Msg:</span>
+              <el-tag type="danger">
+                {{ row.err.message }}
+              </el-tag>
+            </div>
+            <br>
+            <div>
+              <span class="message-title" style="padding-right: 10px;">Info: </span>
+              <el-tag type="warning">
+                {{ row.vm.$vnode.tag }} error in {{ row.info }}
+              </el-tag>
+            </div>
+            <br>
+            <div>
+              <span class="message-title" style="padding-right: 16px;">Url: </span>
+              <el-tag type="success">
+                {{ row.url }}
+              </el-tag>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="Stack">
+          <template slot-scope="scope">
+            {{ scope.row.err.stack }}
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ErrorLog',
+  data() {
+    return {
+      dialogTableVisible: false
+    }
+  },
+  computed: {
+    errorLogs() {
+      return this.$store.getters.errorLogs
+    }
+  },
+  methods: {
+    clearAll() {
+      this.dialogTableVisible = false
+      this.$store.dispatch('errorLog/clearErrorLog')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.message-title {
+  font-size: 16px;
+  color: #333;
+  font-weight: bold;
+  padding-right: 8px;
+}
+</style>

+ 44 - 0
src/components/Hamburger/index.vue

@@ -0,0 +1,44 @@
+<template>
+  <div style="padding: 0 15px;" @click="toggleClick">
+    <svg
+      :class="{'is-active':isActive}"
+      class="hamburger"
+      viewBox="0 0 1024 1024"
+      xmlns="http://www.w3.org/2000/svg"
+      width="64"
+      height="64"
+    >
+      <path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
+    </svg>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Hamburger',
+  props: {
+    isActive: {
+      type: Boolean,
+      default: false
+    }
+  },
+  methods: {
+    toggleClick() {
+      this.$emit('toggleClick')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.hamburger {
+  display: inline-block;
+  vertical-align: middle;
+  width: 20px;
+  height: 20px;
+}
+
+.hamburger.is-active {
+  transform: rotate(180deg);
+}
+</style>

+ 187 - 0
src/components/HeaderSearch/index.vue

@@ -0,0 +1,187 @@
+<template>
+  <div :class="{'show':show}" class="header-search">
+    <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
+    <el-select
+      ref="headerSearchSelect"
+      v-model="search"
+      :remote-method="querySearch"
+      filterable
+      default-first-option
+      remote
+      placeholder="Search"
+      class="header-search-select"
+      @change="change"
+    >
+      <el-option v-for="item in options" :key="item.path" :value="item" :label="item.title.join(' > ')" />
+    </el-select>
+  </div>
+</template>
+
+<script>
+// fuse is a lightweight fuzzy-search module
+// make search results more in line with expectations
+import Fuse from 'fuse.js'
+import path from 'path'
+
+export default {
+  name: 'HeaderSearch',
+  data() {
+    return {
+      search: '',
+      options: [],
+      searchPool: [],
+      show: false,
+      fuse: undefined
+    }
+  },
+  computed: {
+    routes() {
+      return this.$store.getters.permission_routes
+    },
+    lang() {
+      return this.$store.getters.language
+    }
+  },
+  watch: {
+    lang() {
+      this.searchPool = this.generateRoutes(this.routes)
+    },
+    routes() {
+      this.searchPool = this.generateRoutes(this.routes)
+    },
+    searchPool(list) {
+      this.initFuse(list)
+    },
+    show(value) {
+      if (value) {
+        document.body.addEventListener('click', this.close)
+      } else {
+        document.body.removeEventListener('click', this.close)
+      }
+    }
+  },
+  mounted() {
+    this.searchPool = this.generateRoutes(this.routes)
+  },
+  methods: {
+    click() {
+      this.show = !this.show
+      if (this.show) {
+        this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
+      }
+    },
+    close() {
+      this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
+      this.options = []
+      this.show = false
+    },
+    change(val) {
+      this.$router.push(val.path)
+      this.search = ''
+      this.options = []
+      this.$nextTick(() => {
+        this.show = false
+      })
+    },
+    initFuse(list) {
+      this.fuse = new Fuse(list, {
+        shouldSort: true,
+        threshold: 0.4,
+        location: 0,
+        distance: 100,
+        maxPatternLength: 32,
+        minMatchCharLength: 1,
+        keys: [{
+          name: 'title',
+          weight: 0.7
+        }, {
+          name: 'path',
+          weight: 0.3
+        }]
+      })
+    },
+    // Filter out the routes that can be displayed in the sidebar
+    // And generate the internationalized title
+    generateRoutes(routes, basePath = '/', prefixTitle = []) {
+      let res = []
+
+      for (const router of routes) {
+        // skip hidden router
+        if (router.hidden) { continue }
+
+        const data = {
+          path: path.resolve(basePath, router.path),
+          title: [...prefixTitle]
+        }
+
+        if (router.meta && router.meta.title) {
+
+          data.title = router.meta.title
+
+          if (router.redirect !== 'noRedirect') {
+            // only push the routes with title
+            // special case: need to exclude parent router without redirect
+            res.push(data)
+          }
+        }
+
+        // recursive child routes
+        if (router.children) {
+          const tempRoutes = this.generateRoutes(router.children, data.path, data.title)
+          if (tempRoutes.length >= 1) {
+            res = [...res, ...tempRoutes]
+          }
+        }
+      }
+      return res
+    },
+    querySearch(query) {
+      if (query !== '') {
+        this.options = this.fuse.search(query)
+      } else {
+        this.options = []
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.header-search {
+  font-size: 0 !important;
+
+  .search-icon {
+    cursor: pointer;
+    font-size: 18px;
+    vertical-align: middle;
+  }
+
+  .header-search-select {
+    font-size: 18px;
+    transition: width 0.2s;
+    width: 0;
+    overflow: hidden;
+    background: transparent;
+    border-radius: 0;
+    display: inline-block;
+    vertical-align: middle;
+
+    >>> .el-input__inner {
+      border-radius: 0;
+      border: 0;
+      padding-left: 0;
+      padding-right: 0;
+      box-shadow: none !important;
+      border-bottom: 1px solid #d9d9d9;
+      vertical-align: middle;
+    }
+  }
+
+  &.show {
+    .header-search-select {
+      width: 210px;
+      margin-left: 10px;
+    }
+  }
+}
+</style>

+ 105 - 0
src/components/Pagination/index.vue

@@ -0,0 +1,105 @@
+<template>
+  <div :class="{'hidden':hidden}" class="pagination-container">
+    <el-pagination
+      :background="background"
+      :current-page.sync="currentPage"
+      :page-size.sync="pageSize"
+      :layout="layout"
+      :page-sizes="pageSizes"
+      :total="total"
+      v-bind="$attrs"
+      @size-change="handleSizeChange"
+      @current-change="handleCurrentChange"
+    />
+  </div>
+</template>
+
+<script>
+import { scrollTo } from '@/utils/scroll-to'
+
+export default {
+  name: 'Pagination',
+  props: {
+    total: {
+      required: true,
+      type: Number
+    },
+    page: {
+      type: Number,
+      default: 1
+    },
+    limit: {
+      type: Number,
+      default: 20
+    },
+    pageSizes: {
+      type: Array,
+      default() {
+        return [10, 15, 20, 30,100]
+      }
+    },
+    layout: {
+      type: String,
+      default: 'total, sizes, prev, pager, next, jumper'
+    },
+    background: {
+      type: Boolean,
+      default: true
+    },
+    autoScroll: {
+      type: Boolean,
+      default: true
+    },
+    hidden: {
+      type: Boolean,
+      default: false
+    }
+  },
+  computed: {
+    currentPage: {
+      get() {
+        return this.page
+      },
+      set(val) {
+        this.$emit('update:page', { page: val, limit: this.pageSize })
+      }
+    },
+    pageSize: {
+      get() {
+        return this.limit
+      },
+      set(val) {
+        this.$emit('update:limit', { page: this.currentPage, limit: val })
+      }
+    }
+  },
+  methods: {
+    handleSizeChange(val) {
+      // 切换页数时 当前页变成第一页
+      this.$emit('pagination', { page: 1, limit: val })
+      if (this.autoScroll) {
+        scrollTo(0, 800)
+      }
+    },
+    handleCurrentChange(val) {
+      this.$emit('pagination', { page: val, limit: this.pageSize })
+      if (this.autoScroll) {
+        scrollTo(0, 800)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.pagination-container {
+  background: #fff;
+  padding: 2px;
+  .el-pagination {
+    text-align: center;
+  }
+}
+.pagination-container.hidden {
+  display: none;
+}
+</style>

+ 120 - 0
src/components/ResetPwd/index.vue

@@ -0,0 +1,120 @@
+<template>
+  <div class="app-container">
+    <el-form
+      ref="resetForm"
+      :model="resetForm"
+      :rules="resetRules"
+      label-width="80px"
+    >
+      <el-form-item label="原密码" prop="old">
+        <el-input
+          v-model="resetForm.old"
+          autofocus
+          minlength="6"
+          maxlength="12"
+          style="width:350px"
+        />
+      </el-form-item>
+      <el-form-item label="新密码" prop="new">
+        <el-input
+          v-model="resetForm.new"
+          minlength="6"
+          maxlength="12"
+          show-password
+          style="width:350px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button
+          size="mini"
+          @click="handleCancel"
+        >
+          取消
+        </el-button>
+        <el-button
+          type="primary"
+          size="mini"
+          @click="handleReset"
+        >
+          修改
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+<script>
+
+import {
+  resetPwd
+} from '@/api/login'
+
+export default {
+  data() {
+    return {
+      resetRules: {
+        old: [
+          { required: true, message: '请输入原密码', trigger: 'blur' },
+          { min: 6, max: 12, message: '密码长度为6到12个字符', trigger: 'blur' }
+        ],
+        new: [
+          { required: true, message: '请输入新密码', trigger: 'blur' },
+          { min: 6, max: 12, message: '密码长度为6到12个字符', trigger: 'blur' }
+        ]
+      },
+      resetForm: {
+        old: '',
+        new: ''
+      }
+    }
+  },
+  methods: {
+    // 取消
+    handleCancel() {
+      this.$refs.resetForm.resetFields()
+      this.$emit('update:cancel')
+    },
+    // 重置
+    reset() {
+      this.$refs.resetForm.resetFields()
+    },
+    // 修改密码
+    handleReset() {
+      this.$refs.resetForm.validate((valid) => {
+        if (valid) {
+          const data = {
+            oldpassword: this.resetForm.old,
+            password: this.resetForm.new
+          }
+          this.resetPwd(data)
+        } else {
+          return false
+        }
+      })
+    },
+    async resetPwd(data) {
+      const res = await resetPwd(data)
+      try {
+        const { message, code } = res
+        if (code === 200) {
+          this.$message({
+            message: '修改密码成功,请您重新登陆',
+            type: 'success'
+          })
+          this.$refs.resetForm.resetFields()
+          this.$emit('update:success')
+        } else {
+          this.$message.error(message)
+        }
+      } catch (e) {
+        let message = res.message ? res.message : e.message
+        message = e.message ? e.message : '请求失败'
+        this.$message.error(message)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 145 - 0
src/components/RightPanel/index.vue

@@ -0,0 +1,145 @@
+<template>
+  <div ref="rightPanel" :class="{show:show}" class="rightPanel-container">
+    <div class="rightPanel-background" />
+    <div class="rightPanel">
+      <div class="handle-button" :style="{'bottom':buttonBom+'px','background-color':theme}" @click="show=!show">
+        <i :class="show?'el-icon-close':'el-icon-setting'" />
+      </div>
+      <div class="rightPanel-items">
+        <slot />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { addClass, removeClass } from '@/utils'
+
+export default {
+  name: 'RightPanel',
+  props: {
+    clickNotClose: {
+      default: false,
+      type: Boolean
+    },
+    buttonBom: {
+      default: 0,
+      type: Number
+    }
+  },
+  data() {
+    return {
+      show: false
+    }
+  },
+  computed: {
+    theme() {
+      return this.$store.state.settings.theme
+    }
+  },
+  watch: {
+    show(value) {
+      if (value && !this.clickNotClose) {
+        this.addEventClick()
+      }
+      if (value) {
+        addClass(document.body, 'showRightPanel')
+      } else {
+        removeClass(document.body, 'showRightPanel')
+      }
+    }
+  },
+  mounted() {
+    this.insertToBody()
+  },
+  beforeDestroy() {
+    const elx = this.$refs.rightPanel
+    elx.remove()
+  },
+  methods: {
+    addEventClick() {
+      window.addEventListener('click', this.closeSidebar)
+    },
+    closeSidebar(evt) {
+      const parent = evt.target.closest('.rightPanel')
+      if (!parent) {
+        this.show = false
+        window.removeEventListener('click', this.closeSidebar)
+      }
+    },
+    insertToBody() {
+      const elx = this.$refs.rightPanel
+      const body = document.querySelector('body')
+      body.insertBefore(elx, body.firstChild)
+    }
+  }
+}
+</script>
+
+<style>
+.showRightPanel {
+  overflow: hidden;
+  position: relative;
+  width: calc(100% - 15px);
+}
+</style>
+
+<style lang="scss" scoped>
+.rightPanel-background {
+  position: fixed;
+  top: 0;
+  left: 0;
+  opacity: 0;
+  transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
+  background: rgba(0, 0, 0, .2);
+  z-index: -1;
+}
+
+.rightPanel {
+  width: 100%;
+  max-width: 260px;
+  height: 100vh;
+  position: fixed;
+  top: 0;
+  right: 0;
+  box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
+  transition: all .25s cubic-bezier(.7, .3, .1, 1);
+  transform: translate(100%);
+  background: #fff;
+  z-index: 40000;
+}
+
+.show {
+  transition: all .3s cubic-bezier(.7, .3, .1, 1);
+
+  .rightPanel-background {
+    z-index: 20000;
+    opacity: 1;
+    width: 100%;
+    height: 100%;
+  }
+
+  .rightPanel {
+    transform: translate(0);
+  }
+}
+
+.handle-button {
+  width: 48px;
+  height: 48px;
+  position: absolute;
+  left: -48px;
+  text-align: center;
+  font-size: 24px;
+  border-radius: 6px 0 0 6px !important;
+  z-index: 0;
+  pointer-events: auto;
+  cursor: pointer;
+  color: #fff;
+  line-height: 48px;
+  i {
+    font-size: 24px;
+    line-height: 48px;
+  }
+}
+</style>

+ 52 - 0
src/components/RoleCheckbox/index.vue

@@ -0,0 +1,52 @@
+<template>
+  <div>
+    <el-checkbox-group v-model="auth" :disabled="!selected">
+      <el-checkbox
+        v-for="item in authList"
+        :key="item.value"
+        :label="item.value"
+        @change="changeBtnAuth"
+      >
+        {{ item.name }}
+      </el-checkbox>
+    </el-checkbox-group>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    authList: {
+      type: Array,
+      default: () => { return [] }
+    },
+    sureList: {
+      type: Array,
+      default: () => { return [] }
+    },
+    selected: {
+      type: Boolean,
+      default: false
+    },
+    parentKey: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    return {
+      auth: []
+    }
+  },
+  watch: {
+    'sureList'(newV, oldV) {
+      this.auth = newV
+    }
+  },
+  methods: {
+    changeBtnAuth() {
+      this.$emit('changebtnauth', this.auth, this.parentKey)
+    }
+  }
+}
+</script>

+ 60 - 0
src/components/Screenfull/index.vue

@@ -0,0 +1,60 @@
+<template>
+  <div>
+    <svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="click" />
+  </div>
+</template>
+
+<script>
+import screenfull from 'screenfull'
+
+export default {
+  name: 'Screenfull',
+  data() {
+    return {
+      isFullscreen: false
+    }
+  },
+  mounted() {
+    this.init()
+  },
+  beforeDestroy() {
+    this.destroy()
+  },
+  methods: {
+    click() {
+      if (!screenfull.enabled) {
+        this.$message({
+          message: 'you browser can not work',
+          type: 'warning'
+        })
+        return false
+      }
+      screenfull.toggle()
+    },
+    change() {
+      this.isFullscreen = screenfull.isFullscreen
+    },
+    init() {
+      if (screenfull.enabled) {
+        screenfull.on('change', this.change)
+      }
+    },
+    destroy() {
+      if (screenfull.enabled) {
+        screenfull.off('change', this.change)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.screenfull-svg {
+  display: inline-block;
+  cursor: pointer;
+  fill: #5a5e66;;
+  width: 20px;
+  height: 20px;
+  vertical-align: 10px;
+}
+</style>

+ 57 - 0
src/components/SizeSelect/index.vue

@@ -0,0 +1,57 @@
+<template>
+  <el-dropdown trigger="click" @command="handleSetSize">
+    <div>
+      <svg-icon class-name="size-icon" icon-class="size" />
+    </div>
+    <el-dropdown-menu slot="dropdown">
+      <el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size===item.value" :command="item.value">
+        {{
+          item.label }}
+      </el-dropdown-item>
+    </el-dropdown-menu>
+  </el-dropdown>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      sizeOptions: [
+        { label: 'Default', value: 'default' },
+        { label: 'Medium', value: 'medium' },
+        { label: 'Small', value: 'small' },
+        { label: 'Mini', value: 'mini' }
+      ]
+    }
+  },
+  computed: {
+    size() {
+      return this.$store.getters.size
+    }
+  },
+  methods: {
+    handleSetSize(size) {
+      this.$ELEMENT.size = size
+      this.$store.dispatch('app/setSize', size)
+      this.refreshView()
+      this.$message({
+        message: 'Switch Size Success',
+        type: 'success'
+      })
+    },
+    refreshView() {
+      // In order to make the cached page re-rendered
+      this.$store.dispatch('tagsView/delAllCachedViews', this.$route)
+
+      const { fullPath } = this.$route
+
+      this.$nextTick(() => {
+        this.$router.replace({
+          path: '/redirect' + fullPath
+        })
+      })
+    }
+  }
+
+}
+</script>

+ 62 - 0
src/components/SvgIcon/index.vue

@@ -0,0 +1,62 @@
+<template>
+  <div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
+  <svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
+    <use :xlink:href="iconName" />
+  </svg>
+</template>
+
+<script>
+// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
+import { isExternal } from '@/utils/validate'
+
+export default {
+  name: 'SvgIcon',
+  props: {
+    iconClass: {
+      type: String,
+      required: true
+    },
+    className: {
+      type: String,
+      default: ''
+    }
+  },
+  computed: {
+    isExternal() {
+      return isExternal(this.iconClass)
+    },
+    iconName() {
+      return `#icon-${this.iconClass}`
+    },
+    svgClass() {
+      if (this.className) {
+        return 'svg-icon ' + this.className
+      } else {
+        return 'svg-icon'
+      }
+    },
+    styleExternalIcon() {
+      return {
+        mask: `url(${this.iconClass}) no-repeat 50% 50%`,
+        '-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.svg-icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+
+.svg-external-icon {
+  background-color: currentColor;
+  mask-size: cover!important;
+  display: inline-block;
+}
+</style>

+ 175 - 0
src/components/ThemePicker/index.vue

@@ -0,0 +1,175 @@
+<template>
+  <el-color-picker
+    v-model="theme"
+    :predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
+    class="theme-picker"
+    popper-class="theme-picker-dropdown"
+  />
+</template>
+
+<script>
+const version = require('element-ui/package.json').version // element-ui version from node_modules
+const ORIGINAL_THEME = '#409EFF' // default color
+
+export default {
+  data() {
+    return {
+      chalk: '', // content of theme-chalk css
+      theme: ''
+    }
+  },
+  computed: {
+    defaultTheme() {
+      return this.$store.state.settings.theme
+    }
+  },
+  watch: {
+    defaultTheme: {
+      handler: function(val, oldVal) {
+        this.theme = val
+      },
+      immediate: true
+    },
+    async theme(val) {
+      const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
+      if (typeof val !== 'string') return
+      const themeCluster = this.getThemeCluster(val.replace('#', ''))
+      const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
+      console.log(themeCluster, originalCluster)
+
+      const $message = this.$message({
+        message: '  Compiling the theme',
+        customClass: 'theme-message',
+        type: 'success',
+        duration: 0,
+        iconClass: 'el-icon-loading'
+      })
+
+      const getHandler = (variable, id) => {
+        return () => {
+          const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
+          const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
+
+          let styleTag = document.getElementById(id)
+          if (!styleTag) {
+            styleTag = document.createElement('style')
+            styleTag.setAttribute('id', id)
+            document.head.appendChild(styleTag)
+          }
+          styleTag.innerText = newStyle
+        }
+      }
+
+      if (!this.chalk) {
+        const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
+        await this.getCSSString(url, 'chalk')
+      }
+
+      const chalkHandler = getHandler('chalk', 'chalk-style')
+
+      chalkHandler()
+
+      const styles = [].slice.call(document.querySelectorAll('style'))
+        .filter(style => {
+          const text = style.innerText
+          return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
+        })
+      styles.forEach(style => {
+        const { innerText } = style
+        if (typeof innerText !== 'string') return
+        style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
+      })
+
+      this.$emit('change', val)
+
+      $message.close()
+    }
+  },
+
+  methods: {
+    updateStyle(style, oldCluster, newCluster) {
+      let newStyle = style
+      oldCluster.forEach((color, index) => {
+        newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
+      })
+      return newStyle
+    },
+
+    getCSSString(url, variable) {
+      return new Promise(resolve => {
+        const xhr = new XMLHttpRequest()
+        xhr.onreadystatechange = () => {
+          if (xhr.readyState === 4 && xhr.status === 200) {
+            this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
+            resolve()
+          }
+        }
+        xhr.open('GET', url)
+        xhr.send()
+      })
+    },
+
+    getThemeCluster(theme) {
+      const tintColor = (color, tint) => {
+        let red = parseInt(color.slice(0, 2), 16)
+        let green = parseInt(color.slice(2, 4), 16)
+        let blue = parseInt(color.slice(4, 6), 16)
+
+        if (tint === 0) { // when primary color is in its rgb space
+          return [red, green, blue].join(',')
+        } else {
+          red += Math.round(tint * (255 - red))
+          green += Math.round(tint * (255 - green))
+          blue += Math.round(tint * (255 - blue))
+
+          red = red.toString(16)
+          green = green.toString(16)
+          blue = blue.toString(16)
+
+          return `#${red}${green}${blue}`
+        }
+      }
+
+      const shadeColor = (color, shade) => {
+        let red = parseInt(color.slice(0, 2), 16)
+        let green = parseInt(color.slice(2, 4), 16)
+        let blue = parseInt(color.slice(4, 6), 16)
+
+        red = Math.round((1 - shade) * red)
+        green = Math.round((1 - shade) * green)
+        blue = Math.round((1 - shade) * blue)
+
+        red = red.toString(16)
+        green = green.toString(16)
+        blue = blue.toString(16)
+
+        return `#${red}${green}${blue}`
+      }
+
+      const clusters = [theme]
+      for (let i = 0; i <= 9; i++) {
+        clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
+      }
+      clusters.push(shadeColor(theme, 0.1))
+      return clusters
+    }
+  }
+}
+</script>
+
+<style>
+.theme-message,
+.theme-picker-dropdown {
+  z-index: 99999 !important;
+}
+
+.theme-picker .el-color-picker__trigger {
+  height: 26px !important;
+  width: 26px !important;
+  padding: 2px;
+}
+
+.theme-picker-dropdown .el-color-dropdown__link-btn {
+  display: none;
+}
+</style>

+ 111 - 0
src/components/Tinymce/components/EditorImage.vue

@@ -0,0 +1,111 @@
+<template>
+  <div class="upload-container">
+    <el-button :style="{background:color,borderColor:color}" icon="el-icon-upload" size="mini" type="primary" @click=" dialogVisible=true">
+      上传图片
+    </el-button>
+    <el-dialog :visible.sync="dialogVisible">
+      <el-upload
+        :multiple="true"
+        :file-list="fileList"
+        :show-file-list="true"
+        :on-remove="handleRemove"
+        :on-success="handleSuccess"
+        :before-upload="beforeUpload"
+        class="editor-slide-upload"
+        action="https://httpbin.org/post"
+        list-type="picture-card"
+      >
+        <el-button size="small" type="primary">
+          点击上传
+        </el-button>
+      </el-upload>
+      <el-button @click="dialogVisible = false">
+        取消
+      </el-button>
+      <el-button type="primary" @click="handleSubmit">
+        确定
+      </el-button>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+// import { getToken } from 'api/qiniu'
+
+export default {
+  name: 'EditorSlideUpload',
+  props: {
+    color: {
+      type: String,
+      default: '#1890ff'
+    }
+  },
+  data() {
+    return {
+      dialogVisible: false,
+      listObj: {},
+      fileList: []
+    }
+  },
+  methods: {
+    checkAllSuccess() {
+      return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess)
+    },
+    handleSubmit() {
+      const arr = Object.keys(this.listObj).map(v => this.listObj[v])
+      if (!this.checkAllSuccess()) {
+        this.$message('Please wait for all images to be uploaded successfully. If there is a network problem, please refresh the page and upload again!')
+        return
+      }
+      this.$emit('successCBK', arr)
+      this.listObj = {}
+      this.fileList = []
+      this.dialogVisible = false
+    },
+    handleSuccess(response, file) {
+      const uid = file.uid
+      const objKeyArr = Object.keys(this.listObj)
+      for (let i = 0, len = objKeyArr.length; i < len; i++) {
+        if (this.listObj[objKeyArr[i]].uid === uid) {
+          this.listObj[objKeyArr[i]].url = response.files.file
+          this.listObj[objKeyArr[i]].hasSuccess = true
+          return
+        }
+      }
+    },
+    handleRemove(file) {
+      const uid = file.uid
+      const objKeyArr = Object.keys(this.listObj)
+      for (let i = 0, len = objKeyArr.length; i < len; i++) {
+        if (this.listObj[objKeyArr[i]].uid === uid) {
+          delete this.listObj[objKeyArr[i]]
+          return
+        }
+      }
+    },
+    beforeUpload(file) {
+      const _self = this
+      const _URL = window.URL || window.webkitURL
+      const fileName = file.uid
+      this.listObj[fileName] = {}
+      return new Promise((resolve, reject) => {
+        const img = new Image()
+        img.src = _URL.createObjectURL(file)
+        img.onload = function() {
+          _self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height }
+        }
+        resolve(true)
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.editor-slide-upload {
+  margin-bottom: 20px;
+  ::v-deep .el-upload--picture-card {
+    width: 100%;
+  }
+}
+</style>

+ 59 - 0
src/components/Tinymce/dynamicLoadScript.js

@@ -0,0 +1,59 @@
+let callbacks = []
+
+function loadedTinymce() {
+  // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144
+  // check is successfully downloaded script
+  return window.tinymce
+}
+
+const dynamicLoadScript = (src, callback) => {
+  const existingScript = document.getElementById(src)
+  const cb = callback || function() {}
+
+  if (!existingScript) {
+    const script = document.createElement('script')
+    script.src = src // src url for the third-party library being loaded.
+    script.id = src
+    document.body.appendChild(script)
+    callbacks.push(cb)
+    const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd
+    onEnd(script)
+  }
+
+  if (existingScript && cb) {
+    if (loadedTinymce()) {
+      cb(null, existingScript)
+    } else {
+      callbacks.push(cb)
+    }
+  }
+
+  function stdOnEnd(script) {
+    script.onload = function() {
+      // this.onload = null here is necessary
+      // because even IE9 works not like others
+      this.onerror = this.onload = null
+      for (const cb of callbacks) {
+        cb(null, script)
+      }
+      callbacks = null
+    }
+    script.onerror = function() {
+      this.onerror = this.onload = null
+      cb(new Error('Failed to load ' + src), script)
+    }
+  }
+
+  function ieOnEnd(script) {
+    script.onreadystatechange = function() {
+      if (this.readyState !== 'complete' && this.readyState !== 'loaded') return
+      this.onreadystatechange = null
+      for (const cb of callbacks) {
+        cb(null, script) // there is no way to catch loading errors in IE8
+      }
+      callbacks = null
+    }
+  }
+}
+
+export default dynamicLoadScript

+ 254 - 0
src/components/Tinymce/index.vue

@@ -0,0 +1,254 @@
+<template>
+  <div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth}">
+    <textarea :id="tinymceId" class="tinymce-textarea" />
+    <div class="editor-custom-btn-container">
+      <editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
+    </div>
+  </div>
+</template>
+
+<script>
+/**
+ * docs:
+ * https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
+ */
+import editorImage from './components/EditorImage'
+import plugins from './plugins'
+import toolbar from './toolbar'
+import load from './dynamicLoadScript'
+
+// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
+const tinymceCDN = 'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'
+
+export default {
+  name: 'Tinymce',
+  components: { editorImage },
+  props: {
+    id: {
+      type: String,
+      default: function() {
+        return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
+      }
+    },
+    value: {
+      type: String,
+      default: ''
+    },
+    toolbar: {
+      type: Array,
+      required: false,
+      default() {
+        return []
+      }
+    },
+    menubar: {
+      type: String,
+      default: 'file edit insert view format table'
+    },
+    height: {
+      type: [Number, String],
+      required: false,
+      default: 360
+    },
+    width: {
+      type: [Number, String],
+      required: false,
+      default: 'auto'
+    }
+  },
+  data() {
+    return {
+      hasChange: false,
+      hasInit: false,
+      tinymceId: this.id,
+      fullscreen: false,
+      languageTypeList: {
+        'en': 'en',
+        'zh': 'zh_CN',
+        'es': 'es_MX',
+        'ja': 'ja'
+      }
+    }
+  },
+  computed: {
+    language() {
+      return this.languageTypeList[this.$store.getters.language]
+    },
+    containerWidth() {
+      const width = this.width
+      if (/^[\d]+(\.[\d]+)?$/.test(width)) { // matches `100`, `'100'`
+        return `${width}px`
+      }
+      return width
+    }
+  },
+  watch: {
+    value(val) {
+      if (!this.hasChange && this.hasInit) {
+        this.$nextTick(() =>
+          window.tinymce.get(this.tinymceId).setContent(val || ''))
+      }
+    },
+    language() {
+      this.destroyTinymce()
+      this.$nextTick(() => this.initTinymce())
+    }
+  },
+  mounted() {
+    this.init()
+  },
+  activated() {
+    if (window.tinymce) {
+      this.initTinymce()
+    }
+  },
+  deactivated() {
+    this.destroyTinymce()
+  },
+  destroyed() {
+    this.destroyTinymce()
+  },
+  methods: {
+    init() {
+      // dynamic load tinymce from cdn
+      load(tinymceCDN, (err) => {
+        if (err) {
+          this.$message.error(err.message)
+          return
+        }
+        this.initTinymce()
+      })
+    },
+    initTinymce() {
+      const _this = this
+      window.tinymce.init({
+        language: this.language,
+        selector: `#${this.tinymceId}`,
+        height: this.height,
+        body_class: 'panel-body ',
+        object_resizing: false,
+        toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
+        menubar: this.menubar,
+        plugins: plugins,
+        end_container_on_empty_block: true,
+        powerpaste_word_import: 'clean',
+        code_dialog_height: 450,
+        code_dialog_width: 1000,
+        advlist_bullet_styles: 'square',
+        advlist_number_styles: 'default',
+        imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],
+        default_link_target: '_blank',
+        link_title: false,
+        nonbreaking_force_tab: true, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
+        init_instance_callback: editor => {
+          if (_this.value) {
+            editor.setContent(_this.value)
+          }
+          _this.hasInit = true
+          editor.on('NodeChange Change KeyUp SetContent', () => {
+            this.hasChange = true
+            this.$emit('input', editor.getContent())
+          })
+        },
+        setup(editor) {
+          editor.on('FullscreenStateChanged', (e) => {
+            _this.fullscreen = e.state
+          })
+        },
+        // it will try to keep these URLs intact
+        // https://www.tiny.cloud/docs-3x/reference/configuration/Configuration3x@convert_urls/
+        // https://stackoverflow.com/questions/5196205/disable-tinymce-absolute-to-relative-url-conversions
+        convert_urls: false
+        // 整合七牛上传
+        // images_dataimg_filter(img) {
+        //   setTimeout(() => {
+        //     const $image = $(img);
+        //     $image.removeAttr('width');
+        //     $image.removeAttr('height');
+        //     if ($image[0].height && $image[0].width) {
+        //       $image.attr('data-wscntype', 'image');
+        //       $image.attr('data-wscnh', $image[0].height);
+        //       $image.attr('data-wscnw', $image[0].width);
+        //       $image.addClass('wscnph');
+        //     }
+        //   }, 0);
+        //   return img
+        // },
+        // images_upload_handler(blobInfo, success, failure, progress) {
+        //   progress(0);
+        //   const token = _this.$store.getters.token;
+        //   getToken(token).then(response => {
+        //     const url = response.data.qiniu_url;
+        //     const formData = new FormData();
+        //     formData.append('token', response.data.qiniu_token);
+        //     formData.append('key', response.data.qiniu_key);
+        //     formData.append('file', blobInfo.blob(), url);
+        //     upload(formData).then(() => {
+        //       success(url);
+        //       progress(100);
+        //     })
+        //   }).catch(err => {
+        //     failure('出现未知问题,刷新页面,或者联系程序员')
+        //     console.log(err);
+        //   });
+        // },
+      })
+    },
+    destroyTinymce() {
+      const tinymce = window.tinymce.get(this.tinymceId)
+      if (this.fullscreen) {
+        tinymce.execCommand('mceFullScreen')
+      }
+
+      if (tinymce) {
+        tinymce.destroy()
+      }
+    },
+    setContent(value) {
+      window.tinymce.get(this.tinymceId).setContent(value)
+    },
+    getContent() {
+      window.tinymce.get(this.tinymceId).getContent()
+    },
+    imageSuccessCBK(arr) {
+      arr.forEach(v => window.tinymce.get(this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`))
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.tinymce-container {
+  position: relative;
+  line-height: normal;
+}
+
+.tinymce-container {
+  ::v-deep {
+    .mce-fullscreen {
+      z-index: 10000;
+    }
+  }
+}
+
+.tinymce-textarea {
+  visibility: hidden;
+  z-index: -1;
+}
+
+.editor-custom-btn-container {
+  position: absolute;
+  right: 4px;
+  top: 4px;
+  /*z-index: 2005;*/
+}
+
+.fullscreen .editor-custom-btn-container {
+  z-index: 10000;
+  position: fixed;
+}
+
+.editor-upload-btn {
+  display: inline-block;
+}
+</style>

+ 12 - 0
src/components/Tinymce/plugins.js

@@ -0,0 +1,12 @@
+/*
+ * @Author: 阿小凉
+ * @Date: 2023-11-23 10:33:53
+ * @LastEditTime: 2023-11-23 15:01:52
+ */
+// Any plugins you want to use has to be imported
+// Detail plugins list see https://www.tinymce.com/docs/plugins/
+// Custom builds see https://www.tinymce.com/download/custom-builds/
+
+const plugins = ['advlist anchor autolink autosave colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount']
+
+export default plugins

+ 8 - 0
src/components/Tinymce/toolbar.js

@@ -0,0 +1,8 @@
+// Here is a list of the toolbar
+// Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols
+
+const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent  blockquote undo redo removeformat subscript superscript ', 'hr bullist numlist link image charmap preview  pagebreak insertdatetime media table  forecolor backcolor fullscreen']
+// 
+//searchreplace 查找替换
+// bold 加粗
+export default toolbar

+ 4 - 0
src/filterRouter/_import_development.js

@@ -0,0 +1,4 @@
+// 开发环境导入组件
+// module.exports = file => require('@/pages' + file + '.vue').default
+module.exports = file => require('@/views' + file + '.vue').default
+

+ 5 - 0
src/filterRouter/_import_production.js

@@ -0,0 +1,5 @@
+// 生产环境导入组件
+// module.exports = file => () => import('@/pages' + file + '.vue')
+
+module.exports = file => () => import('@/views' + file + '.vue')
+

+ 21 - 0
src/filterRouter/index.js

@@ -0,0 +1,21 @@
+import Layout from '@/layout'
+// 获取组件的方法
+const _import = require('./_import_' + process.env.NODE_ENV)
+
+// 动态加载路由
+export function filterRouter(asyncRouterMap) {
+  const accessedRouters = asyncRouterMap.filter(route => {
+    if (route.component) {
+      if (route.component === 'Layout') {
+        route.component = Layout
+      } else {
+        route.component = _import(route.component)
+      }
+    }
+    if (route.children && route.children.length) {
+      route.children = filterRouter(route.children)
+    }
+    return true
+  })
+  return accessedRouters
+}

+ 231 - 0
src/filters/common.js

@@ -0,0 +1,231 @@
+export function blanceFmt(val) { // 分转化为元
+  var num = Number(val)
+  if (!num) { // 等于0
+    return num + '.00'
+  } else { // 不等于0
+    num = Math.round((num) * 100) / 10000
+    num = num.toFixed(2)
+    num += '' // 转成字符串
+    var reg = num.indexOf('.') > -1 ? /(\d{1,3})(?=(?:\d{3})+\.)/g : /(\d{1,3})(?=(?:\d{3})+$)/g // 千分符的正则
+    return num.replace(reg, '$1,') // 千分位格式化
+  }
+}
+
+export function format(num, decimal) { // 万分位
+  if (num) {
+    // return (decimal?Number(num) + '':Number(num).toFixed(2) + '').replace(/\d{1,4}(?=(\d{4})+(\.\d*)?$)/g, '$&,')
+    return (Number(num) + '').replace(/\d{1,4}(?=(\d{4})+(\.\d*)?$)/g, '$&,')
+  } else {
+    return 0
+  }
+}
+// 订单状态
+export function orderStatus(apply_status, status) {
+  if (apply_status) {
+    const m1 = new Map([
+      [0, { color: '#909399', name: '没取消' }],
+      [1, { color: '#909399', name: '审核中' }],
+      [2, { color: '#909399', name: '已取消' }],
+      [3, { color: '#909399', name: '被驳回' }]
+    ])
+    return m1.get(apply_status) || {
+      name: '未知订单状态',
+      color: '#909399'
+    }
+  } else {
+    const m2 = new Map([
+      [0, { color: '#f97547', name: '待付款' }],
+      [1, { color: '#13c19f', name: '待发货' }],
+      [2, { color: '#ffaa1d', name: '待收货' }],
+      [3, { color: '#409EFF', name: '已完成' }],
+      [4, { color: '#74a2e6', name: '已完成' }]
+    ])
+    return m2.get(status) || {
+      name: '未知订单状态',
+      color: '#909399'
+    }
+  }
+}
+
+// 店铺状态
+export function storeStatus(status) {
+  switch (+status) {
+    case 0:
+      return {
+        name: '未认证',
+        color: '#f97547'
+      }
+    case 1:
+      return {
+        name: '审核中',
+        color: '#13c19f'
+      }
+    case 2:
+      return {
+        name: '待签约',
+        color: '#ffaa1d'
+      }
+    case 3:
+      return {
+        name: '已完成',
+        color: '#409EFF'
+      }
+    case 4:
+      return {
+        name: '被驳回',
+        color: '#999'
+      }
+    case 5:
+      return {
+        name: '已冻结',
+        color: '#ccc'
+      }
+    default :
+      return {
+        name: '审核中',
+        color: '#13c19f'
+      }
+  }
+}
+
+// 退货审核状态
+export function refundStatus(status) {
+  switch (+status) {
+    case 0:
+      return {
+        name: '未审核',
+        color: '#f96b6b'
+      }
+    case 1:
+      return {
+        name: '已同意',
+        color: '#13c19f'
+      }
+    case 2:
+      return {
+        name: '已驳回',
+        color: '#415161'
+      }
+    case 3:
+      return {
+        name: '已完成',
+        color: '#f96b6b'
+      }
+    default :
+      return {
+        name: '未知支付状态',
+        color: '#909399'
+      }
+  }
+}
+
+// 支付状态
+export function payStatus(status) {
+  switch (+status) {
+    case 0:
+      return {
+        name: '未付款',
+        color: '#f96b6b'
+      }
+    case 1:
+      return {
+        name: '部分付款',
+        color: '#f96b6b'
+      }
+    case 2:
+      return {
+        name: '已付款',
+        color: '#415161'
+      }
+    case 3:
+      return {
+        name: '待退款',
+        color: '#f96b6b'
+      }
+    default :
+      return {
+        name: '未知支付状态',
+        color: '#909399'
+      }
+  }
+}
+
+
+// 认证状态显示
+export function certStatus(status) {
+  let out
+  switch (status) {
+    case 0:
+      out = '销售未实名认证'
+      break
+    case 1:
+      out = '等待邀请人审核'
+      break
+    case 2:
+      out = '等待审核'
+      break
+    case 3:
+      out = '等待用户确认'
+      break
+    case 4:
+      out = '邀请人审核驳回'
+      break
+    case 5:
+      out = '审核驳回'
+      break
+    case 6:
+      out = '授权成功'
+      break
+    default:
+      out = status
+  }
+  return out
+}
+// 系统设置拆解
+export function filterSet(arr, key) {
+  if (arr.length < 1) {
+    return null
+  }
+  return arr.filter(item => {
+    return item.keys === key
+  })[0]
+}
+
+// 总金额
+export const totalPrice = data => {
+  let totalPrice = 0
+  data.map(i => {
+    i.sku.map(j => {
+      totalPrice += j.num * j.price
+    })
+  })
+  return totalPrice
+}
+// 总数量
+export const totalNum = data => {
+  let totalNum = 0
+  data.map(i => {
+    i.sku.map(j => {
+      totalNum += j.num
+    })
+  })
+  return totalNum
+}
+// 计算购买时长
+export function coutdown(now, old) {
+  if (now == null) { return 0 }
+  const big = Date.parse(new Date(old)) / 1000
+  const small = Date.parse(new Date(now)) / 1000
+  const add0 = num => (num < 10 ? '0' + num : num)
+  const sec = big - small
+  if (sec > 0) {
+    const day = Math.floor(sec / 86400)
+    const hour = Math.floor((sec % 86400) / 3600)
+    const minite = Math.floor((sec - 86400 * day - hour * 3600) / 60)
+    const second = Math.floor(sec - 86400 * day - hour * 3600 - minite * 60)
+    return `${add0(day)}`
+  } else {
+    return 0
+  }
+}
+

+ 103 - 0
src/filters/compressPic.js

@@ -0,0 +1,103 @@
+const UploadImg = {
+  imageHandle(files, maxSize, ajaxName) {
+    console.log(1)
+    const that = this
+    const formdata = new FormData()
+    const reader = new FileReader()
+    maxSize = maxSize || 50 * 1024 //  不设置图片大小,则判断小于50K的不压缩
+    ajaxName = ajaxName || 'file' // 上传是传参名称
+    reader.readAsDataURL(files) // 将图片转成base64格式
+    // reader.onload是异步,要用到Promise对象将值返回出去
+    return new Promise((resolved, rejected) => {
+      reader.onload = function() {
+        const result = this.result
+        let img = new Image()
+        img.src = result
+        if (this.result.length <= maxSize) {
+          img = null
+          formdata.append(ajaxName, that._upload(result, files.name, files.type))
+          resolved(formdata)
+        } else {
+          img.onload = function() {
+            const data = that._compress(img)
+            formdata.append(ajaxName, that._upload(data, files.name, files.type))
+            resolved(formdata)
+          }
+        }
+      }
+    })
+  },
+  _compress(img) {
+    const canvas = document.createElement('canvas')
+    const ctx = canvas.getContext('2d')
+    // 瓦片
+    const tCanvas = document.createElement('canvas')
+    const tctx = tCanvas.getContext('2d')
+    let width = img.width
+    let height = img.height
+    // 如果图片大于四百万像素,计算压缩比并将大小压至400万以下
+    let ratio
+    if ((ratio = (width * height) / 4000000) > 1) {
+      ratio = Math.sqrt(ratio)
+      width /= ratio
+      height /= ratio
+    } else {
+      ratio = 1
+    }
+    canvas.width = width
+    canvas.height = height
+    // 铺底色
+    ctx.fillStyle = '#fff'
+    ctx.fillRect(0, 0, canvas.width, canvas.height)
+    // 如果图片像素大于100万则使用瓦片绘制
+    let count
+    if ((count = (width * height) / 1000000) > 1) {
+      count = ~~(Math.sqrt(count) + 1) // 计算要分成多少瓦片
+      // 计算每块瓦片的宽高
+      const nw = ~~(width / count)
+      const nh = ~~(height / count)
+      tCanvas.width = nw
+      tCanvas.height = nh
+      for (let i = 0; i < count; i++) {
+        for (let j = 0; j < count; j++) {
+          tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh)
+          ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh)
+        }
+      }
+    } else {
+      ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
+    }
+    // 进行最小压缩
+    const ndata = canvas.toDataURL('image/jpeg', 0.3)
+    tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0
+    return ndata
+  },
+  _upload(data, name, type) {
+    const text = window.atob(data.split(',')[1])
+    const buffer = new ArrayBuffer(text.length)
+    const ubuffer = new Uint8Array(buffer)
+
+    for (var i = 0; i < text.length; i++) {
+      ubuffer[i] = text.charCodeAt(i)
+    }
+
+    const Builder =
+    window.BlobBuilder ||
+    window.WebKitBlobBuilder ||
+    window.MozBlobBuilder ||
+    window.MSBlobBuilder
+    let blob
+    if (Builder) {
+      var builder = new Builder()
+      builder.append(buffer)
+      blob = builder.getBlob(type)
+    } else {
+      blob = new window.Blob([ubuffer], { type: type })
+    }
+    // blob 转file
+    var fileOfBlob = new File([blob], name, { type: type })
+    return fileOfBlob
+  }
+}
+
+export default UploadImg

+ 110 - 0
src/filters/index.js

@@ -0,0 +1,110 @@
+// import parseTime, formatTime and set to filter
+export { parseTime, formatTime } from '@/utils'
+
+/**
+ * Show plural label if time is plural number
+ * @param {number} time
+ * @param {string} label
+ * @return {string}
+ */
+function pluralize(time, label) {
+  if (time === 1) {
+    return time + label
+  }
+  return time + label + 's'
+}
+
+/**
+ * @param {number} time
+ */
+export function timeAgo(time) {
+  const between = Date.now() / 1000 - Number(time)
+  if (between < 3600) {
+    return pluralize(~~(between / 60), ' minute')
+  } else if (between < 86400) {
+    return pluralize(~~(between / 3600), ' hour')
+  } else {
+    return pluralize(~~(between / 86400), ' day')
+  }
+}
+
+/**
+ * Number formatting
+ * like 10000 => 10k
+ * @param {number} num
+ * @param {number} digits
+ */
+export function numberFormatter(num, digits) {
+  const si = [
+    { value: 1E18, symbol: 'E' },
+    { value: 1E15, symbol: 'P' },
+    { value: 1E12, symbol: 'T' },
+    { value: 1E9, symbol: 'G' },
+    { value: 1E6, symbol: 'M' },
+    { value: 1E3, symbol: 'k' }
+  ]
+  for (let i = 0; i < si.length; i++) {
+    if (num >= si[i].value) {
+      return (num / si[i].value).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol
+    }
+  }
+  return num.toString()
+}
+
+/**
+ * 10000 => "10,000"
+ * @param {number} num
+ */
+export function toThousandFilter(num) {
+  return (+num || 0).toString().replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ','))
+}
+
+/**
+ * Upper case first char
+ * @param {String} string
+ */
+export function uppercaseFirst(string) {
+  return string.charAt(0).toUpperCase() + string.slice(1)
+}
+
+// 验证金额
+export function ismoney(money) {
+  var reg = /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/
+  if (reg.test(money)) {
+    return true
+  } else {
+    return false
+  }
+}
+
+// 格式化时间
+export function formatter(thistime, fmt) {
+  const $this = new Date(thistime)
+  const o = {
+    'M+': $this.getMonth() + 1,
+    'd+': $this.getDate(),
+    'h+': $this.getHours(),
+    'm+': $this.getMinutes(),
+    's+': $this.getSeconds(),
+    'q+': Math.floor(($this.getMonth() + 3) / 3),
+    'S': $this.getMilliseconds()
+  }
+  if (/(y+)/.test(fmt)) {
+    fmt = fmt.replace(RegExp.$1, ($this.getFullYear() + '').substr(4 - RegExp.$1.length))
+  }
+  for (var k in o) {
+    if (new RegExp('(' + k + ')').test(fmt)) {
+      fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
+    }
+  }
+  return fmt
+}
+
+export function getTime(time) {
+  const date = new Date(time)
+  const add0 = num => num < 10 ? '0' + num : num
+  return `${date.getFullYear()}/${add0(date.getMonth() + 1)}/${add0(date.getDate())} ${add0(date.getHours())}:${add0(date.getMinutes())}`
+}
+
+
+

+ 8 - 0
src/filters/systemSet.js

@@ -0,0 +1,8 @@
+export function filterSet(arr, key) {
+  if (arr.length < 1) {
+    return null
+  }
+  return arr.filter(item => {
+    return item.keys === key
+  })[0]
+}

+ 74 - 0
src/filters/validate.js

@@ -0,0 +1,74 @@
+// 校验正分钟
+export function validateCreNum(rule, value, callback) {
+  if (value === '') {
+    callback(new Error('请输入身份证号!'))
+  } else if (!checkIDCard(value)) {
+    callback(new Error('请输入正确的身份证号!'))
+  } else {
+    callback()
+  }
+}
+
+// 校验手机号
+export function validateMobile(rule, value, callback) {
+  if (value === '') {
+    callback(new Error('请输入手机号!'))
+  } else if (!checkMobile(value)) {
+    callback(new Error('请输入正确的手机号!'))
+  } else {
+    callback()
+  }
+}
+
+// 身份证验证
+// 函数参数必须是字符串,因为二代身份证号码是十八位,而在javascript中,十八位的数值会超出计算范围,造成不精确的结果,导致最后两位和计算的值不一致,从而该函数出现错误。
+// 详情查看javascript的数值范围
+function checkIDCard(idcode) {
+  // 加权因子
+  var weight_factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
+  // 校验码
+  var check_code = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']
+
+  var code = idcode + ''
+  var last = idcode[17] // 最后一位
+
+  var seventeen = code.substring(0, 17)
+
+  // ISO 7064:1983.MOD 11-2
+  // 判断最后一位校验码是否正确
+  var arr = seventeen.split('')
+  var len = arr.length
+  var num = 0
+  for (var i = 0; i < len; i++) {
+    num = num + arr[i] * weight_factor[i]
+  }
+
+  // 获取余数
+  var resisue = num % 11
+  var last_no = check_code[resisue]
+
+  // 格式的正则
+  // 正则思路
+  /*
+  第一位不可能是0
+  第二位到第六位可以是0-9
+  第七位到第十位是年份,所以七八位为19或者20
+  十一位和十二位是月份,这两位是01-12之间的数值
+  十三位和十四位是日期,是从01-31之间的数值
+  十五,十六,十七都是数字0-9
+  十八位可能是数字0-9,也可能是X
+  */
+  var idcard_patter = /^[1-9][0-9]{5}([1][9][0-9]{2}|[2][0][0|1][0-9])([0][1-9]|[1][0|1|2])([0][1-9]|[1|2][0-9]|[3][0|1])[0-9]{3}([0-9]|[X])$/
+
+  // 判断格式是否正确
+  var format = idcard_patter.test(idcode)
+
+  // 返回验证结果,校验码和格式同时正确才算是合法的身份证号码
+  return !!(last === last_no && format)
+}
+
+// 手机号验证
+function checkMobile(str) {
+  const reg = /^[1][3,4,5,6,7,8,9][0-9]{9}$/
+  return reg.test(str)
+}

+ 9 - 0
src/icons/index.js

@@ -0,0 +1,9 @@
+import Vue from 'vue'
+import SvgIcon from '@/components/SvgIcon'// svg component
+
+// register globally
+Vue.component('svg-icon', SvgIcon)
+
+const req = require.context('./svg', false, /\.svg$/)
+const requireAll = requireContext => requireContext.keys().map(requireContext)
+requireAll(req)

+ 1 - 0
src/icons/svg/account.svg

@@ -0,0 +1 @@
+<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>

File diff suppressed because it is too large
+ 0 - 0
src/icons/svg/dashboard.svg


+ 1 - 0
src/icons/svg/edit.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M106.133 67.2a4.797 4.797 0 0 0-4.8 4.8c0 .187.014.36.027.533h-.027V118.4H9.6V26.667h50.133c2.654 0 4.8-2.147 4.8-4.8 0-2.654-2.146-4.8-4.8-4.8H9.6a9.594 9.594 0 0 0-9.6 9.6V118.4c0 5.307 4.293 9.6 9.6 9.6h91.733c5.307 0 9.6-4.293 9.6-9.6V72.533h-.026c.013-.173.026-.346.026-.533 0-2.653-2.146-4.8-4.8-4.8z"/><path d="M125.16 13.373L114.587 2.8c-3.747-3.747-9.854-3.72-13.6.027l-52.96 52.96a4.264 4.264 0 0 0-.907 1.36L33.813 88.533c-.746 1.76-.226 3.534.907 4.68 1.133 1.147 2.92 1.667 4.693.92l31.4-13.293c.507-.213.96-.52 1.36-.907l52.96-52.96c3.747-3.746 3.774-9.853.027-13.6zM66.107 72.4l-18.32 7.76 7.76-18.32L92.72 24.667l10.56 10.56L66.107 72.4zm52.226-52.227l-8.266 8.267-10.56-10.56 8.266-8.267.027-.026 10.56 10.56-.027.026z"/></svg>

+ 1 - 0
src/icons/svg/email.svg

@@ -0,0 +1 @@
+<svg width="128" height="96" xmlns="http://www.w3.org/2000/svg"><path d="M64.125 56.975L120.188.912A12.476 12.476 0 0 0 115.5 0h-103c-1.588 0-3.113.3-4.513.838l56.138 56.137z"/><path d="M64.125 68.287l-62.3-62.3A12.42 12.42 0 0 0 0 12.5v71C0 90.4 5.6 96 12.5 96h103c6.9 0 12.5-5.6 12.5-12.5v-71a12.47 12.47 0 0 0-1.737-6.35L64.125 68.287z"/></svg>

+ 1 - 0
src/icons/svg/example.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M96.258 57.462h31.421C124.794 27.323 100.426 2.956 70.287.07v31.422a32.856 32.856 0 0 1 25.971 25.97zm-38.796-25.97V.07C27.323 2.956 2.956 27.323.07 57.462h31.422a32.856 32.856 0 0 1 25.97-25.97zm12.825 64.766v31.421c30.46-2.885 54.507-27.253 57.713-57.712H96.579c-2.886 13.466-13.146 23.726-26.292 26.291zM31.492 70.287H.07c2.886 30.46 27.253 54.507 57.713 57.713V96.579c-13.466-2.886-23.726-13.146-26.291-26.292z"/></svg>

+ 1 - 0
src/icons/svg/excel.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.208 16.576v8.384h38.72v5.376h-38.72v8.704h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.512h38.72v5.376h-38.72v11.136H128v-94.72H78.208zM0 114.368L72.128 128V0L0 13.632v100.736z"/><path d="M28.672 82.56h-11.2l14.784-23.488-14.08-22.592h11.52l8.192 14.976 8.448-14.976h11.136l-14.08 22.208L58.368 82.56H46.656l-8.768-15.68z"/></svg>

+ 1 - 0
src/icons/svg/eye-open.svg

@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>

+ 1 - 0
src/icons/svg/eye.svg

@@ -0,0 +1 @@
+<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg>

File diff suppressed because it is too large
+ 0 - 0
src/icons/svg/form.svg


+ 1 - 0
src/icons/svg/goods.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1609835440983" class="icon" viewBox="0 0 1820 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5105" xmlns:xlink="http://www.w3.org/1999/xlink" width="355.46875" height="200"><defs><style type="text/css"></style></defs><path d="M521.443556 511.772444c0 214.480593 173.871407 388.361481 388.361481 388.361482 214.480593 0 388.361481-173.871407 388.361482-388.361482 0-214.490074-173.871407-388.361481-388.361482-388.361481-214.490074 0-388.361481 173.871407-388.361481 388.361481z m0 0" fill="#00DEBF" p-id="5106"></path><path d="M1092.901926 387.176296c-10.657185-12.183704-25.874963-28.928-39.575704-45.672296-16.744296-13.700741-31.971556-28.928-45.672296-39.575704-19.787852-15.217778-30.445037-18.261333-39.575704-18.261333h-222.245926c-18.270815 0-35.015111 16.744296-35.015111 35.015111V703.81037c0 19.787852 16.744296 36.532148 35.015111 36.532149h328.808297c19.787852 0 33.488593-16.744296 33.488592-35.00563V426.752c3.034074-6.087111 3.034074-16.744296-15.227259-39.575704z m-114.166519 184.18726h38.058667v57.846518h-36.532148v-57.846518h-1.526519z m-56.329481-73.064297h36.532148v130.910815h-36.532148V498.299259z m-57.846519 53.276445h36.532149v77.63437h-36.532149v-77.63437z m-56.32-111.122963h36.532149v188.757333h-36.532149V440.452741z m254.217482 245.086815h-302.933333V649.007407h302.933333v36.532149z m-65.460148-284.662519v-71.547259c12.174222 9.130667 22.831407 19.787852 36.532148 35.005629 13.700741 15.217778 22.831407 25.874963 31.971555 36.532149h-68.503703z m0 0" fill="#FFFFFF" p-id="5107"></path></svg>

+ 1 - 0
src/icons/svg/home.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1609138462659" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3200" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M992.8 558.1c-8 0-16-3-22.1-9.1L552 130.3c-10.7-10.7-24.9-16.6-40-16.6-15.1 0-29.3 5.9-40 16.6L53.3 549c-12.2 12.2-32 12.2-44.2 0-12.1-12.2-12.1-32 0-44.2L427.8 86.2c22.5-22.5 52.4-34.9 84.2-34.9s61.7 12.4 84.2 34.9l418.7 418.7c12.2 12.2 12.2 32 0 44.2-6.1 6-14.1 9-22.1 9z" fill="" p-id="3201"></path><path d="M867 461.4H157c-33 0-60 27-60 60v391.3c0 33 27 60 60 60h191V695h296c17.7 0 32 14.3 32 32s-14.3 32-32 32H412v213.7h455c33 0 60-27 60-60V521.4c0-33-27-60-60-60z" fill="" p-id="3202"></path><path d="M918.7 480.9L101.4 484 511.6 73.8z" fill="" p-id="3203"></path></svg>

+ 1 - 0
src/icons/svg/inte.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629948245578" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2413" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M880 344l-92 25.2V148c0-19.9-16.1-36-36-36H272c-19.9 0-36 16.1-36 36v221.5L144 344c-17.6 0-32 14.6-32 32.5v503.1c0 17.9 14.4 32.5 32 32.5h736c17.6 0 32-14.6 32-32.5V376.5c0-17.9-14.4-32.5-32-32.5zM308 184h408v205l-206 56.5-202-56V184z m532 656H184V429.8l306.8 85.1c6.3 1.7 12.8 2.6 19.2 2.6 6.4 0 12.8-0.9 19-2.6l311-85.3V840z" p-id="2414"></path><path d="M648 322v-40c0-6.6-5.4-12-12-12H388c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h248c6.6 0 12-5.4 12-12zM511.5 673.5c-0.6 0-1.2-0.3-1.5-0.9-7.6-13.1-10.7-19-23.1-40L462.1 590c-0.3-0.5-0.9-0.9-1.5-0.9h-65.2c-1.5 0-2.3 1.7-1.4 2.9l72.6 90c0.9 1.1 0.1 2.9-1.4 2.9h-46.5c-1 0-1.8 0.8-1.8 1.8v18.7c0 1 0.8 1.8 1.8 1.8h60.7c1 0 1.8 0.8 1.8 1.8v12c0 1-0.8 1.8-1.8 1.8h-60.7c-1 0-1.8 0.8-1.8 1.8v18.7c0 1 0.8 1.8 1.8 1.8h60.7c1 0 1.8 0.8 1.8 1.8v34.3c0 1 0.8 1.8 1.8 1.8h55.7c1 0 1.8-0.8 1.8-1.8v-34.3c0-1 0.8-1.8 1.8-1.8h60.1c1 0 1.8-0.8 1.8-1.8v-18.7c0-1-0.8-1.8-1.8-1.8h-60.1c-1 0-1.8-0.8-1.8-1.8v-12c0-1 0.8-1.8 1.8-1.8h60.1c1 0 1.8-0.8 1.8-1.8v-18.7c0-1-0.8-1.8-1.8-1.8h-46.5c-1.5 0-2.3-1.7-1.4-2.9l72.6-90c0.9-1.1 0.1-2.9-1.4-2.9h-63.9c-0.6 0-1.2 0.3-1.5 0.9l-24.1 42.6c-11.4 20-15.2 26.9-23.1 40-0.4 0.6-1 0.9-1.6 0.9z" p-id="2415"></path></svg>

File diff suppressed because it is too large
+ 0 - 0
src/icons/svg/inte_manage.svg


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