Jelajahi Sumber

first commit

NobleLeong 4 hari lalu
melakukan
66617614e3
100 mengubah file dengan 2726 tambahan dan 0 penghapusan
  1. 11 0
      .babelrc
  2. 24 0
      .editorconfig
  3. 9 0
      .env
  4. 9 0
      .env.call-dev
  5. 8 0
      .env.call-prod
  6. 9 0
      .env.call-test
  7. 8 0
      .env.platform-dev
  8. 7 0
      .env.platform-prod
  9. 8 0
      .env.platform-test
  10. 0 0
      .eslintignore
  11. 30 0
      .eslintrc.js
  12. 39 0
      .gitignore
  13. 5 0
      .postcssrc.js
  14. 4 0
      .prettierrc
  15. 9 0
      Dockerfile100
  16. 5 0
      Dockerfile8001
  17. 8 0
      LICENSE
  18. 1 0
      MP_verify_0NM73Yj26xb5VRAp.txt
  19. 16 0
      README.md
  20. 1 0
      WW_verify_QLYznJ5hG2EzTZN9.txt
  21. 1 0
      X9cWzO1HNk.txt
  22. 3 0
      cypress.json
  23. 28 0
      default100.conf
  24. 28 0
      default8001.conf
  25. 13 0
      deploy-single.sh
  26. 81 0
      package.json
  27. 120 0
      public/call.html
  28. TEMPAT SAMPAH
      public/favicon.ico
  29. TEMPAT SAMPAH
      public/img/add_btn@2x.png
  30. TEMPAT SAMPAH
      public/img/add_circle_icon.png
  31. TEMPAT SAMPAH
      public/img/add_task_icon.png
  32. TEMPAT SAMPAH
      public/img/ai_img.png
  33. TEMPAT SAMPAH
      public/img/call_logo.png
  34. TEMPAT SAMPAH
      public/img/close_icon2.png
  35. TEMPAT SAMPAH
      public/img/confirm_icon@2x.png
  36. TEMPAT SAMPAH
      public/img/down_jt_icon@2x.png
  37. TEMPAT SAMPAH
      public/img/fail_icon@2x.png
  38. TEMPAT SAMPAH
      public/img/head.png
  39. TEMPAT SAMPAH
      public/img/id_jt_icon1@2x.png
  40. TEMPAT SAMPAH
      public/img/id_jt_icon2@2x.png
  41. TEMPAT SAMPAH
      public/img/login_bg.png
  42. TEMPAT SAMPAH
      public/img/login_icon.png
  43. TEMPAT SAMPAH
      public/img/login_icon1.png
  44. TEMPAT SAMPAH
      public/img/logout_icon@2x.png
  45. TEMPAT SAMPAH
      public/img/menu/administrator_menu_icon@2x.png
  46. TEMPAT SAMPAH
      public/img/menu/config_menu_icon1@2x.png
  47. TEMPAT SAMPAH
      public/img/menu/config_menu_icon2@2x.png
  48. TEMPAT SAMPAH
      public/img/menu/data_menu_icon@2x.png
  49. TEMPAT SAMPAH
      public/img/menu/home_menu_icon1@2x.png
  50. TEMPAT SAMPAH
      public/img/menu/home_menu_icon2@2x.png
  51. TEMPAT SAMPAH
      public/img/menu/home_menu_icon@2x.png
  52. TEMPAT SAMPAH
      public/img/menu/logo_icon@2x.png
  53. TEMPAT SAMPAH
      public/img/menu/orders_menu_icon1@2x.png
  54. TEMPAT SAMPAH
      public/img/menu/orders_menu_icon2@2x.png
  55. TEMPAT SAMPAH
      public/img/menu/product_menu_icon1@2x.png
  56. TEMPAT SAMPAH
      public/img/menu/product_menu_icon2@2x.png
  57. TEMPAT SAMPAH
      public/img/menu/record_menu_icon1@2x.png
  58. TEMPAT SAMPAH
      public/img/menu/record_menu_icon2@2x.png
  59. TEMPAT SAMPAH
      public/img/menu/reward_menu_icon@2x.png
  60. TEMPAT SAMPAH
      public/img/menu/storeAdm_menu_icon1@2x.png
  61. TEMPAT SAMPAH
      public/img/menu/storeAdm_menu_icon2@2x.png
  62. TEMPAT SAMPAH
      public/img/menu/task_menu_icon@2x.png
  63. TEMPAT SAMPAH
      public/img/menu/user_menu_icon@2x.png
  64. TEMPAT SAMPAH
      public/img/menu/works_menu_icon@2x.png
  65. TEMPAT SAMPAH
      public/img/pause_icon.png
  66. TEMPAT SAMPAH
      public/img/play_icon.png
  67. TEMPAT SAMPAH
      public/img/task_name_icon.png
  68. TEMPAT SAMPAH
      public/img/time_icon.png
  69. TEMPAT SAMPAH
      public/img/upload_icon@2x.png
  70. TEMPAT SAMPAH
      public/img/user_info_icon11.png
  71. TEMPAT SAMPAH
      public/img/user_info_icon12.png
  72. TEMPAT SAMPAH
      public/img/user_info_icon21.png
  73. TEMPAT SAMPAH
      public/img/user_info_icon22.png
  74. TEMPAT SAMPAH
      public/img/user_info_icon31.png
  75. TEMPAT SAMPAH
      public/img/user_info_icon32.png
  76. TEMPAT SAMPAH
      public/img/user_info_icon41.png
  77. TEMPAT SAMPAH
      public/img/user_info_icon42.png
  78. 35 0
      public/index.html
  79. 436 0
      public/styles/index.css
  80. 139 0
      public/styles/reset.css
  81. TEMPAT SAMPAH
      sound-chain-pc.7z
  82. 83 0
      src/api/index.js
  83. 0 0
      src/assets/images/error-page/error-401.svg
  84. 0 0
      src/assets/images/error-page/error-404.svg
  85. 0 0
      src/assets/images/error-page/error-500.svg
  86. 165 0
      src/components/content-page/existreturn.vue
  87. 147 0
      src/components/content-page/index.vue
  88. 93 0
      src/components/errorPage/401.vue
  89. 101 0
      src/components/errorPage/404.vue
  90. 85 0
      src/components/errorPage/500.vue
  91. 366 0
      src/components/main/Main.vue
  92. 90 0
      src/components/main/components/backTop/BackTop.vue
  93. 84 0
      src/components/main/components/fullscreen/Fullscreen.vue
  94. 57 0
      src/components/main/components/headerBar/HeaderBar.vue
  95. 52 0
      src/components/main/components/headerBar/customBreadCrumb/CustomBreadCrumb.vue
  96. 0 0
      src/components/main/components/headerBar/customBreadCrumb/customBreadCrumb.less
  97. 60 0
      src/components/main/components/headerBar/sideTrigger/SideTrigger.vue
  98. 65 0
      src/components/main/components/sideMenu/CollapsedMenu.vue
  99. 135 0
      src/components/main/components/sideMenu/SideMenu.vue
  100. 48 0
      src/components/main/components/sideMenu/SideMenuGroupItem.vue

+ 11 - 0
.babelrc

@@ -0,0 +1,11 @@
+{
+  "env":{
+    "development":{
+      "sourceMaps":true,
+      "retainLines":true, 
+    }
+  },
+  "presets": [
+    "@vue/app"
+  ]
+}

+ 24 - 0
.editorconfig

@@ -0,0 +1,24 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 4
+# 换行符类型 = lf
+end_of_line = lf
+# 字符集=utf-8
+charset = utf-8
+# 删除行尾空格 = 是
+trim_trailing_whitespace = true
+# 插入最后一行=真
+insert_final_newline = true
+
+[*.md]
+# 删除行尾空格 = 否
+trim_trailing_whitespace = false
+
+[package.json]
+# 缩进样式=空格
+indent_style = space
+# 缩进大小=2
+indent_size = 4

+ 9 - 0
.env

@@ -0,0 +1,9 @@
+VUE_APP_INDEX_PAGE=home
+VUE_APP_AUTH_TOKEN=Authentication
+VUE_APP_TAG_NAV_LIST=tagNavList
+VUE_APP_USER=user
+VUE_APP_BAIDU_MAP_AK=GpTV7fjLH6LCZlS2A4HWRd1gVxSNic1W
+VUE_APP_BAIDU_MAP_SK=gD00bVSOgNW7LNbpW9UrylN6LmXagZDV
+VUE_APP_VERIFY_CODE_KEY=verifycodesession
+VUE_APP_FILELOAD=wl-1306604067
+VUE_APP_SERVER=qyscrm.miaoxing100.com

+ 9 - 0
.env.call-dev

@@ -0,0 +1,9 @@
+VUE_APP_BASE_URL=http://test.wefanbot.com:18993
+VUE_APP_MODE=call
+VUE_APP_WEB_SOCKET=ws://test.wefanbot.com:18993
+VUE_APP_BASE_URL_S=https://test.wefanbot.com:28993
+VUE_APP_WEB_SOCKET_S=wss://test.wefanbot.com:28993
+VUE_APP_TITLE=AI外呼
+VUE_APP_DEV=true
+NODE_ENV=DEV
+VUE_APP_H5_URL=https://test.wefanbot.com:18211

+ 8 - 0
.env.call-prod

@@ -0,0 +1,8 @@
+VUE_APP_BASE_URL=http://wlapi.wefanbot.com
+VUE_APP_WEB_SOCKET=ws://wlapi.wefanbot.com
+VUE_APP_BASE_URL_S=https://wlapi.wefanbot.com
+VUE_APP_WEB_SOCKET_S=wss://wlapi.wefanbot.com
+VUE_APP_MODE=call
+NODE_ENV=production
+VUE_APP_TITLE=AI外呼
+VUE_APP_H5_URL=https://call.wefanbot.com

+ 9 - 0
.env.call-test

@@ -0,0 +1,9 @@
+VUE_APP_BASE_URL=http://test.wefanbot.com:18993
+VUE_APP_WEB_SOCKET=ws://test.wefanbot.com:18993
+VUE_APP_BASE_URL_S=https://test.wefanbot.com:28993
+VUE_APP_WEB_SOCKET_S=wss://test.wefanbot.com:28993
+VUE_APP_TEST=true
+NODE_ENV=production
+VUE_APP_MODE=call
+VUE_APP_TITLE=AI外呼
+VUE_APP_H5_URL=https://test.wefanbot.com:18211

+ 8 - 0
.env.platform-dev

@@ -0,0 +1,8 @@
+VUE_APP_BASE_URL=http://test.wefanbot.com:18993
+VUE_APP_MODE=platform
+VUE_APP_WEB_SOCKET=ws://test.wefanbot.com:18993
+VUE_APP_BASE_URL_S=https://test.wefanbot.com:28993
+VUE_APP_WEB_SOCKET_S=wss://test.wefanbot.com:28993
+VUE_APP_TITLE=AI外呼管理后台
+VUE_APP_DEV=true
+NODE_ENV=DEV

+ 7 - 0
.env.platform-prod

@@ -0,0 +1,7 @@
+VUE_APP_BASE_URL=http://wlapi.wefanbot.com
+VUE_APP_WEB_SOCKET=ws://wlapi.wefanbot.com
+VUE_APP_BASE_URL_S=https://wlapi.wefanbot.com
+VUE_APP_WEB_SOCKET_S=wss://wlapi.wefanbot.com
+VUE_APP_MODE=platform
+NODE_ENV=production
+VUE_APP_TITLE=AI外呼管理后台

+ 8 - 0
.env.platform-test

@@ -0,0 +1,8 @@
+VUE_APP_BASE_URL=http://test.wefanbot.com:18993
+VUE_APP_WEB_SOCKET=ws://test.wefanbot.com:18993
+VUE_APP_BASE_URL_S=https://test.wefanbot.com:28993
+VUE_APP_WEB_SOCKET_S=wss://test.wefanbot.com:28993
+VUE_APP_TEST=true
+NODE_ENV=DEV
+VUE_APP_MODE=platform
+VUE_APP_TITLE=AI外呼管理后台

+ 0 - 0
.eslintignore


+ 30 - 0
.eslintrc.js

@@ -0,0 +1,30 @@
+module.exports = {
+    root: true,
+    'extends': [
+        'plugin:vue/essential',
+        '@vue/standard'
+    ],
+    rules: {
+        'indent': ['off', 4],
+        'generator-star-spacing': 'off',
+        'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
+        'vue/no-parsing-error': [2, {
+            'x-invalid-end-tag': false
+        }],
+        'no-undef': 'off',
+        'vue/no-async-in-computed-properties': 'off',
+        'vue/no-parsing-error': [2, { "x-invalid-end-tag": false }],
+        'camelcase': 'off',
+        'no-unused-vars': 'off',
+        'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
+        // 要求 switch 语句中有 default 分支
+        'default-case': 'error',
+        'comma-dangle': 'off',
+        'no-var': 'error',
+        'spaced-comment': 'off',
+        'space-before-function-paren': 'off'
+    },
+    parserOptions: {
+        parser: 'babel-eslint'
+    }
+}

+ 39 - 0
.gitignore

@@ -0,0 +1,39 @@
+# ---> Actionscript
+# Build and Release Folders
+bin/
+bin-debug/
+bin-release/
+
+# Other files and folders
+.settings/
+
+.DS_Store
+node_modules
+/dist
+
+package-lock.json
+
+/tests/e2e/videos/
+/tests/e2e/screenshots/
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw*
+
+build/env.js
+
+

+ 5 - 0
.postcssrc.js

@@ -0,0 +1,5 @@
+module.exports = {
+  plugins: {
+    autoprefixer: {}
+  }
+}

+ 4 - 0
.prettierrc

@@ -0,0 +1,4 @@
+{
+      "singleQuote": true,
+      "semi": false
+}

+ 9 - 0
Dockerfile100

@@ -0,0 +1,9 @@
+FROM nginx:latest
+MAINTAINER dengchangfu <401094431@qq.com>
+RUN rm /etc/nginx/conf.d/default.conf
+ADD default100.conf /etc/nginx/conf.d/
+COPY dist/  /usr/share/nginx/html/
+COPY MP_verify_0NM73Yj26xb5VRAp.txt /usr/share/nginx/html/
+COPY WW_verify_QLYznJ5hG2EzTZN9.txt /usr/share/nginx/html/
+COPY X9cWzO1HNk.txt /usr/share/nginx/html/
+COPY tSLV02lG45.txt /usr/share/nginx/html/

+ 5 - 0
Dockerfile8001

@@ -0,0 +1,5 @@
+FROM nginx:latest
+MAINTAINER dengchangfu <401094431@qq.com>
+RUN rm /etc/nginx/conf.d/default.conf
+ADD default8001.conf /etc/nginx/conf.d/default.conf
+COPY dist/  /usr/share/nginx/html/

+ 8 - 0
LICENSE

@@ -0,0 +1,8 @@
+MIT License
+Copyright (c) <year> <copyright holders>
+
+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.

+ 1 - 0
MP_verify_0NM73Yj26xb5VRAp.txt

@@ -0,0 +1 @@
+0NM73Yj26xb5VRAp

+ 16 - 0
README.md

@@ -0,0 +1,16 @@
+## SCRM后台管理系统
+
+```
+ // 安装yarn包
+ yarn install
+
+ // 打包商家
+ 本地开发:yarn dev:merchant
+ 测试环境:yarn test:merchant
+ 生产环境:yarn build:merchant
+
+ //打包平台
+ 本地开发:yarn dev:platform
+ 测试环境:yarn test:platform
+ 生产环境:yarn build:platform
+```

+ 1 - 0
WW_verify_QLYznJ5hG2EzTZN9.txt

@@ -0,0 +1 @@
+QLYznJ5hG2EzTZN9

+ 1 - 0
X9cWzO1HNk.txt

@@ -0,0 +1 @@
+408faa0625dc6e4a8146f52ae32ee55c

+ 3 - 0
cypress.json

@@ -0,0 +1,3 @@
+{
+  "pluginsFile": "tests/e2e/plugins/index.js"
+}

+ 28 - 0
default100.conf

@@ -0,0 +1,28 @@
+server {
+    listen       108;
+    server_name  localhost;
+
+    #charset koi8-r;
+    #access_log  /var/log/nginx/log/host.access.log  main;
+
+    location / {
+        root   /usr/share/nginx/html;
+        index  index.html index.htm;
+        gzip on;
+  	gzip_buffers 4 16k;
+  	gzip_comp_level 2;
+  	gzip_vary on;
+  	gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
+    }
+
+
+    #error_page  404              /404.html;
+
+    # redirect server error pages to the static page /50x.html
+    #
+    error_page   500 502 503 504  /50x.html;
+    location = /50x.html {
+        root   /usr/share/nginx/html;
+    }
+
+}

+ 28 - 0
default8001.conf

@@ -0,0 +1,28 @@
+server {
+    listen       8018;
+    server_name  localhost;
+
+    #charset koi8-r;
+    #access_log  /var/log/nginx/log/host.access.log  main;
+
+    location / {
+        root   /usr/share/nginx/html;
+        index  index.html index.htm;
+  	gzip on;
+  	gzip_buffers 4 16k;
+  	gzip_comp_level 2;
+  	gzip_vary on;
+  	gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
+    }
+
+
+    #error_page  404              /404.html;
+
+    # redirect server error pages to the static page /50x.html
+    #
+    error_page   500 502 503 504  /50x.html;
+    location = /50x.html {
+        root   /usr/share/nginx/html;
+    }
+
+}

+ 13 - 0
deploy-single.sh

@@ -0,0 +1,13 @@
+#!/bin/bash
+echo "user name = ${USER}"
+SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
+echo "PWD:"$SHELL_FOLDER
+npm -v
+node -v
+echo "yarn install start"
+yarn install
+echo "yarn install end"
+yarn run test:merchant
+echo "yarn run test:merchant 完成"
+\cp -rf dist /opt/wfg/merchant/dist
+echo "\cp -rf dist /opt/wfg/merchant/dist 完成"

+ 81 - 0
package.json

@@ -0,0 +1,81 @@
+{
+  "name": "sound-chain-pc",
+  "version": "0.0.1",
+  "author": "deng",
+  "private": false,
+  "scripts": {
+    "dev:call": "vue-cli-service serve --open --mode call-dev",
+    "test:call": "vue-cli-service build --mode call-test",
+    "build:call": "vue-cli-service build --mode call-prod",
+    "dev:platform": "vue-cli-service serve --open --mode platform-dev",
+    "test:platform": "vue-cli-service build --mode platform-test",
+    "build:platform": "vue-cli-service build --mode platform-prod",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "@lottiefiles/lottie-player": "^2.0.2",
+    "animate.css": "^4.1.0",
+    "axios": "^0.18.1",
+    "cos-js-sdk-v5": "^0.5.27",
+    "i": "^0.3.6",
+    "iview-area": "^1.6.0",
+    "js-base64": "^3.4.5",
+    "reset.css": "^2.0.2",
+    "v-viewer": "^1.6.4",
+    "vue-clipboard2": "^0.3.1",
+    "vue-drag-resize": "^1.5.4",
+    "vue-qr": "^4.0.9",
+    "vuex-persistedstate": "^4.0.0-beta.1"
+  },
+  "resolutions": {
+    "es-abstract": "^1.20.1"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "^3.0.1",
+    "@vue/cli-plugin-eslint": "^3.0.1",
+    "@vue/cli-plugin-unit-mocha": "^3.0.1",
+    "@vue/cli-service": "^3.0.1",
+    "@vue/eslint-config-standard": "^3.0.0-beta.10",
+    "@vue/test-utils": "^1.0.0-beta.10",
+    "chai": "^4.1.2",
+    "eslint-plugin-cypress": "^2.0.1",
+    "html-webpack-plugin": "^4.5.0",
+    "less": "^2.7.3",
+    "less-loader": "^4.1.0",
+    "lint-staged": "^6.0.0",
+    "mockjs": "^1.0.1-beta3",
+    "uglifyjs-webpack-plugin": "^2.2.0",
+    "vue-template-compiler": "^2.6.11",
+    "vuedraggable": "^2.24.3",
+    "vuepress": "^1.5.0",
+    "webpack-bundle-analyzer": "^4.1.0"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not ie <= 8"
+  ],
+  "gitHooks": {
+    "pre-commit": "lint-staged"
+  },
+  "lint-staged": {
+    "*.js": [
+      "vue-cli-service lint",
+      "git add"
+    ],
+    "*.vue": [
+      "vue-cli-service lint",
+      "git add"
+    ]
+  },
+  "description": "```\r  // 安装npm包\r  npm install",
+  "main": ".eslintrc.js",
+  "directories": {
+    "doc": "docs"
+  },
+  "repository": {
+    "type": "git",
+    "url": "ssh://git@gogs.test.wefanbot.com:3000"
+  },
+  "license": "ISC"
+}

+ 120 - 0
public/call.html

@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+    <meta name="renderer" content="webkit">
+    <meta name="force-rendering" content="webkit">
+    <meta name="google" value="notranslate">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="renderer" content="webkit|ie-comp|ie-stand">
+    <meta name="keywords" content="AI外呼,广州微分,">
+    <meta name="description" itemprop="description" content="AI外呼">
+    <!-- <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"> -->
+    <script>
+        function isPC () {
+            const ua = navigator.userAgent;
+            const platform = navigator.platform;
+
+            // 移动设备检测
+            const mobileRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i;
+            const isMobile = mobileRegex.test(ua);
+
+            // 平板设备检测
+            const tabletRegex = /iPad|Tablet|PlayBook|Silk|Kindle|(Android(?!.*Mobile))|(Windows(?!.*Phone))(.*Touch)/i;
+            const isTablet = tabletRegex.test(ua);
+
+            // 屏幕尺寸检测
+            const isSmallScreen = window.innerWidth < 768;
+
+            // 触摸支持检测
+            const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
+
+            // 如果是移动设备、平板设备、小屏幕设备,则不是PC
+            if (isMobile || isTablet || isSmallScreen) {
+                return false;
+            }
+
+            // 额外的PC特征检测
+            const isWindows = /Win/i.test(platform);
+            const isMac = /Mac/i.test(platform);
+            const isLinux = /Linux/i.test(platform);
+
+            return (isWindows || isMac || isLinux) && !hasTouch;
+        }
+        if (!isPC()) {
+            window.location.replace('<%= VUE_APP_H5_URL %>')
+        }
+        window.addEventListener('resize', function() {
+            if (!isPC()) {
+                window.location.replace('<%= VUE_APP_H5_URL %>')
+            }
+        })
+    </script>
+    <script src="https://wfg-1631.oss.wefanbot.com/cdn/vue.min.js"></script>
+    <script src="https://wfg-1631.oss.wefanbot.com/cdn/vue-router.min.js"></script>
+    <script src="https://wfg-1631.oss.wefanbot.com/cdn/vuex.min.js"></script>
+    <script src="https://wfg-1631.oss.wefanbot.com/cdn/iview.min.js"></script>
+    <link rel="shortcut icon" href="<%= BASE_URL %>favicon.ico">
+    <title>
+        <%= htmlWebpackPlugin.options.title %>
+    </title>
+    <link rel="stylesheet" href="https://wfg-1631.oss.wefanbot.com/cdn/iview.min.css">
+    <link rel="stylesheet" href="/styles/index.css">
+</head>
+
+<body>
+    <h1 style="width: 0px; height: 0px;overflow: hidden;" title="AI外呼">
+        <a>AI外呼</a>
+    </h1>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+</body>
+
+</html>
+<script>
+        (function flexible (window, document) {
+            let docEl = document.documentElement
+            let dpr = window.devicePixelRatio || 1
+
+            // adjust body font size
+            function setBodyFontSize () {
+                if (document.body) {
+                    document.body.style.fontSize = (12 * dpr) + 'px'
+                } else {
+                    document.addEventListener('DOMContentLoaded', setBodyFontSize)
+                }
+            }
+            setBodyFontSize()
+
+            // set 1rem = viewWidth / 10
+            function setRemUnit () {
+                let rem = docEl.clientWidth / 1440
+                docEl.style.fontSize = rem + 'px'
+            }
+
+            setRemUnit()
+
+            // reset rem unit on page resize
+            window.addEventListener('resize', setRemUnit)
+            window.addEventListener('pageshow', function(e) {
+                if (e.persisted) {
+                    setRemUnit()
+                }
+            })
+
+            // detect 0.5px supports
+            if (dpr >= 2) {
+                let fakeBody = document.createElement('body')
+                let testElement = document.createElement('div')
+                testElement.style.border = '.5px solid transparent'
+                fakeBody.appendChild(testElement)
+                docEl.appendChild(fakeBody)
+                if (testElement.offsetHeight === 1) {
+                    docEl.classList.add('hairlines')
+                }
+                docEl.removeChild(fakeBody)
+            }
+        }(window, document))
+</script>

TEMPAT SAMPAH
public/favicon.ico


TEMPAT SAMPAH
public/img/add_btn@2x.png


TEMPAT SAMPAH
public/img/add_circle_icon.png


TEMPAT SAMPAH
public/img/add_task_icon.png


TEMPAT SAMPAH
public/img/ai_img.png


TEMPAT SAMPAH
public/img/call_logo.png


TEMPAT SAMPAH
public/img/close_icon2.png


TEMPAT SAMPAH
public/img/confirm_icon@2x.png


TEMPAT SAMPAH
public/img/down_jt_icon@2x.png


TEMPAT SAMPAH
public/img/fail_icon@2x.png


TEMPAT SAMPAH
public/img/head.png


TEMPAT SAMPAH
public/img/id_jt_icon1@2x.png


TEMPAT SAMPAH
public/img/id_jt_icon2@2x.png


TEMPAT SAMPAH
public/img/login_bg.png


TEMPAT SAMPAH
public/img/login_icon.png


TEMPAT SAMPAH
public/img/login_icon1.png


TEMPAT SAMPAH
public/img/logout_icon@2x.png


TEMPAT SAMPAH
public/img/menu/administrator_menu_icon@2x.png


TEMPAT SAMPAH
public/img/menu/config_menu_icon1@2x.png


TEMPAT SAMPAH
public/img/menu/config_menu_icon2@2x.png


TEMPAT SAMPAH
public/img/menu/data_menu_icon@2x.png


TEMPAT SAMPAH
public/img/menu/home_menu_icon1@2x.png


TEMPAT SAMPAH
public/img/menu/home_menu_icon2@2x.png


TEMPAT SAMPAH
public/img/menu/home_menu_icon@2x.png


TEMPAT SAMPAH
public/img/menu/logo_icon@2x.png


TEMPAT SAMPAH
public/img/menu/orders_menu_icon1@2x.png


TEMPAT SAMPAH
public/img/menu/orders_menu_icon2@2x.png


TEMPAT SAMPAH
public/img/menu/product_menu_icon1@2x.png


TEMPAT SAMPAH
public/img/menu/product_menu_icon2@2x.png


TEMPAT SAMPAH
public/img/menu/record_menu_icon1@2x.png


TEMPAT SAMPAH
public/img/menu/record_menu_icon2@2x.png


TEMPAT SAMPAH
public/img/menu/reward_menu_icon@2x.png


TEMPAT SAMPAH
public/img/menu/storeAdm_menu_icon1@2x.png


TEMPAT SAMPAH
public/img/menu/storeAdm_menu_icon2@2x.png


TEMPAT SAMPAH
public/img/menu/task_menu_icon@2x.png


TEMPAT SAMPAH
public/img/menu/user_menu_icon@2x.png


TEMPAT SAMPAH
public/img/menu/works_menu_icon@2x.png


TEMPAT SAMPAH
public/img/pause_icon.png


TEMPAT SAMPAH
public/img/play_icon.png


TEMPAT SAMPAH
public/img/task_name_icon.png


TEMPAT SAMPAH
public/img/time_icon.png


TEMPAT SAMPAH
public/img/upload_icon@2x.png


TEMPAT SAMPAH
public/img/user_info_icon11.png


TEMPAT SAMPAH
public/img/user_info_icon12.png


TEMPAT SAMPAH
public/img/user_info_icon21.png


TEMPAT SAMPAH
public/img/user_info_icon22.png


TEMPAT SAMPAH
public/img/user_info_icon31.png


TEMPAT SAMPAH
public/img/user_info_icon32.png


TEMPAT SAMPAH
public/img/user_info_icon41.png


TEMPAT SAMPAH
public/img/user_info_icon42.png


+ 35 - 0
public/index.html

@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+    <meta name="renderer" content="webkit">
+    <meta name="force-rendering" content="webkit">
+    <meta name="google" value="notranslate">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="renderer" content="webkit|ie-comp|ie-stand">
+    <meta name="keywords" content="AI外呼,广州微分,">
+    <meta name="description" itemprop="description" content="AI外呼">
+    <!-- <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"> -->
+    <script src="https://wfg-1631.oss.wefanbot.com/cdn/vue.min.js"></script>
+    <script src="https://wfg-1631.oss.wefanbot.com/cdn/vue-router.min.js"></script>
+    <script src="https://wfg-1631.oss.wefanbot.com/cdn/vuex.min.js"></script>
+    <script src="https://wfg-1631.oss.wefanbot.com/cdn/iview.min.js"></script>
+    <link rel="shortcut icon" href="<%= BASE_URL %>favicon.ico">
+    <title>
+        <%= htmlWebpackPlugin.options.title %>
+    </title>
+    <link rel="stylesheet" href="https://wfg-1631.oss.wefanbot.com/cdn/iview.min.css">
+    <link rel="stylesheet" href="/styles/index.css">
+</head>
+
+<body>
+    <h1 style="width: 0px; height: 0px;overflow: hidden;" title="AI外呼">
+        <a>AI外呼</a>
+    </h1>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+</body>
+
+</html>

+ 436 - 0
public/styles/index.css

@@ -0,0 +1,436 @@
+@import 'reset.css';
+body{
+    color: #111827;
+}
+.icon {
+    width: 1em;
+    height: 1em;
+    vertical-align: -0.15em;
+    fill: currentColor;
+    overflow: hidden;
+}
+img {
+    border-radius: 2px;
+}
+.margin-top-60 {
+    margin-top: 60px;
+}
+/* Modal */
+.ivu-modal-mask{
+    background-color: rgba(0, 0, 0, 0.5);
+}
+.ivu-modal-wrap{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.ivu-modal{
+    top: auto;
+}
+.ivu-modal-content{
+    box-shadow: 0px 25px 50px -12px rgba(0, 0, 0, 0.25);
+    border-radius: 20px;
+}
+.ivu-modal-body{
+    padding: 0 20px;
+    padding-top: 10px;
+    box-sizing: border-box;
+    max-height: calc(100vh - 70px - 10px - 100px - 40px - 40px);
+    overflow-y: auto;
+}
+    /*滚动条样式*/
+.ivu-modal-body::-webkit-scrollbar {
+    width: 0px;
+}
+.ivu-modal-body::-webkit-scrollbar-thumb {
+    border-radius: 10px;
+    -webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
+    background: rgba(0,0,0,0.2);
+}
+.ivu-modal-body::-webkit-scrollbar-track {
+    -webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
+    border-radius: 0;
+    background: rgba(0,0,0,0.1);
+
+}
+.ivu-modal-close{
+    top: 20px;
+    right: 20px;
+    width: 24px;
+    height: 24px;
+    background: url(/img/close_icon@2x.png) no-repeat center/24px 24px;
+}
+.ivu-modal-close .ivu-icon-ios-close{
+    display: none;
+}
+.ivu-modal-header{
+    border-bottom:0;
+    padding: 20px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    text-align: center;
+    font-weight: 500;
+    font-size: 16px;
+    color: #222222;
+    line-height: 24px;
+}
+
+.ivu-modal-footer{
+    display: flex;
+    justify-content: flex-end;
+    align-items: center;
+    padding: 20px 20px 24px 20px;
+    border-top:0;
+}
+.ivu-modal-confirm-footer{
+    display: flex;
+    justify-content: flex-end;
+    align-items: center;
+    border-top:0;
+    margin-top: 30px;
+}
+.ivu-modal-confirm{
+    padding: 10px 0 20px 0;
+}
+.ivu-modal-confirm-body{
+    padding-top: 20px;
+}
+.ivu-modal-confirm-footer .ivu-btn,
+.ivu-modal-footer .ivu-btn{
+    padding: 0 24px;
+    font-size: 14px;
+    height: 40px;
+    border-radius: 28px;
+    border: 1px solid #744BFF;
+    color: #7c3aed;
+}
+/* Form */
+.ivu-form .ivu-form-item-label{
+    color: #6B7280;
+    padding: 13px 12px 13px 0;
+}
+.ivu-radio-inner{
+    border-color: #9CA3AF;
+}
+.ivu-input:hover,
+.ivu-input-number:hover,
+.ivu-input:focus,
+.ivu-input-number:focus,
+.ivu-date-picker-focused input,
+.ivu-radio:hover .ivu-radio-inner,
+.ivu-radio-checked .ivu-radio-inner,
+.ivu-radio-checked:hover .ivu-radio-inner,
+.ivu-checkbox-checked:hover .ivu-checkbox-inner,
+.ivu-checkbox-indeterminate .ivu-checkbox-inner,
+.ivu-checkbox-indeterminate:hover .ivu-checkbox-inner,
+.ivu-radio-group-button .ivu-radio-wrapper-checked:first-child,
+.ivu-upload-drag:hover,
+.ivu-select-visible .ivu-select-selection,
+.ivu-select-selection-focused, 
+.ivu-select-selection:hover,
+.ivu-switch-checked {
+    border-color: #7C3AED !important;
+}
+.ivu-checkbox-indeterminate .ivu-checkbox-inner,
+.ivu-switch-checked,
+.ivu-radio-checked .ivu-radio-inner:after{
+    background-color: #7c3aed;
+}
+.ivu-radio-group-button .ivu-radio-wrapper-checked{
+    border-color: #7C3AED;
+    background: #7C3AED;
+    box-shadow: -1px 0 0 0 #7C3AED;
+    color: #fff;
+}
+.ivu-radio-group-button .ivu-radio-wrapper-checked:hover{
+    border-color: #7C3AED;
+    color: #fff !important;
+}
+.ivu-radio-group-button .ivu-radio-wrapper-checked.ivu-radio-focus:first-child,
+.ivu-radio-group-button .ivu-radio-wrapper-checked.ivu-radio-focus{
+    box-shadow: -1px 0 0 0 #7C3AED, 0 0 0 2px rgb(91 33 182 / 20%);
+}
+.ivu-radio-group-button .ivu-radio-wrapper:hover{
+    color: #7C3AED;
+}
+.ivu-btn-primary:focus,
+.ivu-checkbox-focus,
+.ivu-switch:focus,
+.ivu-radio-focus,
+.ivu-input:focus,
+.ivu-input-number:focus,
+.ivu-date-picker-focused input,
+.ivu-select-visible .ivu-select-selection {
+    -webkit-box-shadow: 0 0 0 2px rgb(91 33 182 / 20%) !important;
+    box-shadow: 0 0 0 2px rgb(91 33 182 / 20%) !important;
+}
+.ivu-date-picker-cells-focused em{
+    -webkit-box-shadow: 0 0 0 1px #7C3AED inset;
+    box-shadow: 0 0 0 1px #7C3AED inset;
+}
+.ivu-date-picker-cells-cell-today em:after{
+    background: #7C3AED;
+}
+.ivu-input,
+.ivu-input-number{
+    border: 1px solid #D1D5DB;
+    color: #111827;
+    background-color: #fff;
+    font-size: 14px;
+    height: 40px;
+}
+.ivu-select-single .ivu-select-selection .ivu-select-placeholder, 
+.ivu-input-number::placeholder,
+.ivu-input::placeholder{
+    color: #9CA3AF
+}
+.ivu-input-number-handler{
+    height: 20px;
+}
+.ivu-input-number-input-wrap{
+    height: 40px;
+}
+.ivu-input-number-input,
+.ivu-input-prefix i, .ivu-input-suffix i,
+.ivu-input-icon{
+    height: 40px;
+    line-height: 40px;
+}
+.ivu-spin{
+   color: #7C3AED; 
+}
+.ivu-btn-default:hover {
+    color: #7C3AED;
+    border-color: #7C3AED;
+}
+.ivu-btn-primary:hover{
+    border-color: #7C3AED;
+    color: #fff;
+}
+.ivu-radio-wrapper{
+    margin-right: 20px;
+    color: #111827;
+}
+.ivu-select-single .ivu-select-selection{
+    height: 40px;
+}
+.ivu-select-single .ivu-select-input,
+.ivu-select-single .ivu-select-selection .ivu-select-selected-value{
+    height: 38px;
+    line-height: 38px;
+    color: #111827;
+}
+.ivu-select-single .ivu-select-selection .ivu-select-placeholder{
+    height: 38px;
+    line-height: 38px;
+}
+.ivu-select-multiple .ivu-select-item-selected,
+.ivu-select-multiple .ivu-select-item-selected:after,
+.ivu-select-item-selected, .ivu-select-item-selected:hover{
+    color: #7C3AED;
+}
+.ivu-page-total,
+.ivu-page-item a{
+    color: #111827;
+}
+.ivu-page-item-jump-next:after, .ivu-page-item-jump-prev:after{
+    color: #111827;
+}
+.ivu-page-item-jump-next i, .ivu-page-item-jump-prev i{
+    color: #7C3AED;
+}
+.ivu-page .ivu-select-single .ivu-select-selection{
+    height: 32px;
+    border: 1px solid #D1D5DB;
+}
+.ivu-page .ivu-select-single .ivu-select-selection .ivu-select-selected-value{
+    height: 30px;
+    line-height: 30px;
+}
+.ivu-page-next:hover, 
+.ivu-page-prev:hover,
+.ivu-page-item-active,
+.ivu-page-item:hover{
+    border-color: #7C3AED;
+}
+.ivu-page-next:hover a, 
+.ivu-page-prev:hover a,
+.ivu-page-item-active a,
+.ivu-page-item:hover a{
+    color: #7C3AED;
+}
+.ivu-input-number-disabled:hover,
+.ivu-page-disabled:hover{
+    border-color: #D1D5DB;
+}
+.ivu-page-disabled:hover a{
+    color: #9CA3AF;
+}
+.ivu-form-item-content {
+    line-height: 40px;
+}
+/* tabs */
+.ivu-tabs-nav .ivu-tabs-tab-active{
+    color: #7C3AED;
+}
+.ivu-tabs-nav .ivu-tabs-tab:hover{
+    color: #7C3AED;
+}
+.ivu-tabs-ink-bar{
+    background-color: #7c3aed;
+}
+.ivu-tabs.ivu-tabs-card>.ivu-tabs-bar .ivu-tabs-tab-active{
+    color: #7C3AED;
+}
+/* table */
+.ivu-table{
+    color: #111827;
+}
+.ivu-table-cell-tree {
+    margin-right: 10px;
+}
+
+td .ivu-table-cell {
+    display: flex;
+    margin: 20px 0;
+    line-height: 21px;
+}
+
+.ivu-table-column-center .ivu-table-cell {
+    justify-content: center;
+    display: flex;
+}
+
+.ivu-table-column-right .ivu-table-cell {
+    justify-content: flex-end;
+}
+
+.ivu-table-cell-tooltip {
+    flex: 1;
+    overflow: hidden;
+    overflow-wrap: break-word;
+}
+.tree-item__title,
+.ivu-tree-title{
+    color: #111827;
+}
+/* checkbox */
+/deep/ .ivu-checkbox-inner {
+    width: 16px;
+    height: 16px;
+}
+
+/deep/.ivu-checkbox-disabled .ivu-checkbox-inner {
+    background-color: #ddd;
+}
+
+.ivu-checkbox-checked .ivu-checkbox-inner {
+    border-color: #7C3AED;
+    background-color: #7C3AED;
+}
+
+.ivu-checkbox-checked .ivu-checkbox-inner:after {
+    width: 5px;
+    height: 10px;
+    top: 1px;
+}
+
+/* tag */
+.custom-tag {
+    margin: 0 10px 10px 0 !important;
+    padding: 5px 8px !important;
+}
+
+.ivu-tag-success,
+.ivu-tag-success.ivu-tag-dot .ivu-tag-dot-inner.tag_lsit {
+    background: #EDF8FF;
+    border-radius: 2px;
+    border: 1px solid #B1C9FA;
+
+    
+}
+
+.ivu-tag-success .ivu-tag-color-white,
+.ivu-tag-success.ivu-tag-dot .ivu-tag-dot-inner.tag_lsit .ivu-tag-color-white {
+    color: #1354D3 !important;
+}
+
+#tag_list {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-right: 20px;
+    margin-left: 0px !important;
+    border: 0;
+    height: 29px;
+    background: #7C3AED;
+    border-radius: 4px;
+
+    
+}
+
+#tag_list .ivu-tag-color-white {
+    color: #FFFFFF !important;
+    margin-right: 6px;
+}
+
+.ivu-table-header {
+    line-height: 18px !important;
+    font-weight: bold !important;
+
+    
+}
+.ivu-table-fixed-header thead tr th,
+.ivu-table-header  thead tr th {
+        padding: 15px 0 !important;
+}
+.ivu-steps .ivu-steps-title {
+    font-size: 18px;
+    color: #666666;
+    font-weight: 400;
+}
+
+/deep/ .ivu-input-group {
+    height: 32px !important;
+}
+
+.ivu-btn-default{
+    color: #111827;
+    border-color: #D1D5DB;
+}
+
+.ivu-btn-primary {
+    background-color: #7C3AED !important;
+    border: 0 !important;
+    color: #fff !important;
+}
+
+.btn_cancle {
+    background: #96A0BC !important;
+    color: #fff !important;
+    border: 0 !important;
+}
+
+.btn_list {
+    color: #7C3AED;
+}
+
+.serialnumbers {
+    display: inline-block;
+    margin-right: 10px;
+    width: 16px;
+    height: 16px;
+    background: #7C3AED;
+    border-radius: 50%;
+    color: #fff;
+    text-align: center;
+    font-size: 14px;
+    line-height: 16px;
+    font-weight: 400;
+}
+
+.ivu-page-item-jump-next:after,
+.ivu-page-item-jump-prev:after {
+    content: "\2022\2022\2022" !important;
+}

+ 139 - 0
public/styles/reset.css

@@ -0,0 +1,139 @@
+/* http://meyerweb.com/eric/tools/css/reset/ 
+   v2.0 | 20110126
+   License: none (public domain)
+*/
+
+html,
+body,
+div,
+span,
+applet,
+object,
+iframe,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+blockquote,
+pre,
+a,
+abbr,
+acronym,
+address,
+big,
+cite,
+code,
+del,
+dfn,
+em,
+img,
+ins,
+kbd,
+q,
+s,
+samp,
+small,
+strike,
+strong,
+sub,
+sup,
+tt,
+var,
+b,
+u,
+i,
+center,
+dl,
+dt,
+dd,
+ol,
+ul,
+li,
+fieldset,
+form,
+label,
+legend,
+table,
+caption,
+tbody,
+tfoot,
+thead,
+tr,
+th,
+td,
+article,
+aside,
+canvas,
+details,
+embed,
+figure,
+figcaption,
+footer,
+header,
+hgroup,
+menu,
+nav,
+output,
+ruby,
+section,
+summary,
+time,
+mark,
+audio,
+video {
+    margin: 0;
+    padding: 0;
+    border: 0;
+    font: revert;
+    /* font-size: 100%;
+	font: inherit;
+	font-weight:normal;
+	font-style:normal;
+	font-size:normal; 
+	vertical-align: baseline;*/
+}
+
+/* HTML5 display-role reset for older browsers */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+    display: block;
+}
+
+body {
+    line-height: 1;
+}
+
+ol,
+ul {
+    list-style: none;
+}
+
+blockquote,
+q {
+    quotes: none;
+}
+
+blockquote:before,
+blockquote:after,
+q:before,
+q:after {
+    content: '';
+    content: none;
+}
+
+table {
+    border-collapse: collapse;
+    border-spacing: 0;
+}

TEMPAT SAMPAH
sound-chain-pc.7z


+ 83 - 0
src/api/index.js

@@ -0,0 +1,83 @@
+import axios from 'axios'
+import Vue from 'vue'
+import store from '@/store'
+import {
+    cacheStorage
+} from '@/utils'
+
+axios.defaults.timeout = 200000
+axios.defaults.withCredentials = true
+// axios.defaults.baseURL = process.env.VUE_APP_BASE_URL
+if (document.location.protocol === 'http:') {
+    axios.defaults.baseURL = process.env.VUE_APP_BASE_URL
+} else {
+    axios.defaults.baseURL = process.env.VUE_APP_BASE_URL_S
+}
+axios.interceptors.request.use(function(config) {
+    if (store.state) {
+        let token = store.state.token
+        if (token) {
+            config.headers.token = token
+        }
+    }
+    let userInfo = cacheStorage.getItem('user')
+
+    if (userInfo) {
+        userInfo = JSON.parse(userInfo)
+        config.headers.userId = userInfo.id
+    }
+    return config
+}, function(error) {
+    return Promise.reject(error)
+})
+// 添加响应拦截器
+axios.interceptors.response.use(function(response) {
+    let data = response.data || response
+    const {
+        verifycodesession,
+        token
+    } = response.headers
+    if (token) {
+        store.dispatch('setToken', token)
+    }
+    if (response.data.code === 10001 || response.data.code === 20103) {
+        cacheStorage.clear()
+        Vue.prototype.$Notice.error({
+            title: '登录信息过期,请重新登录',
+            desc: response.data.message
+        })
+        window.location.href = location.href.split('?')[0]
+        window.location.reload()
+    }
+    // 登录验证码 10001
+    if (verifycodesession) {
+        cacheStorage.setItem(process.env.VUE_APP_VERIFY_CODE_KEY, verifycodesession)
+    }
+    return Promise.resolve(data)
+}, function(error) {
+    // 如果存在 error.response 为后台错误,在不同的状态码做不同的操作
+    // 否则为网络错误 todo
+    if (error.response) {
+        /*if (process.env.NODE_ENV === 'development') {
+            const {
+                response
+            } = error
+            Vue.prototype.$Notice.error({
+                title: response.status,
+                desc: response.data.message
+            })
+        }*/
+        // 2021-03-13 优化,提示后台错误,例如参数错误等信息
+        const { response } = error
+        Vue.prototype.$Notice.warning({
+            title: response.data.msg,
+            desc: response.data.msg
+        })
+    } else {
+        console.log(error, 'error')
+    }
+    return Promise.reject(error)
+})
+
+Vue.prototype.$http = axios
+export default axios

File diff ditekan karena terlalu besar
+ 0 - 0
src/assets/images/error-page/error-401.svg


File diff ditekan karena terlalu besar
+ 0 - 0
src/assets/images/error-page/error-404.svg


File diff ditekan karena terlalu besar
+ 0 - 0
src/assets/images/error-page/error-500.svg


+ 165 - 0
src/components/content-page/existreturn.vue

@@ -0,0 +1,165 @@
+<template>
+  <div class="content-page">
+    <div class="content-page__header">
+      <div class="content-page__header-title">
+        <span class="black_box">
+          <Button type="text" class="blackup-btn" @click="returnbtn">返回/</Button>
+          <span name="title">{{title2}}</span>
+        </span>
+        <span class="content-page__header-subtitle">
+          <span name="sub-title">{{subTitle}}</span>
+        </span>
+      </div>
+    </div>
+
+    <div class="content-page__content" :style="contentStyle" ref="content" @scroll="resLisScroll">
+      <div style="display: flex;height: 100%;justify-content: center;align-items: center;" v-if="loading">
+        <Spin size="large"></Spin>
+      </div>
+      <slot v-else />
+    </div>
+  </div>
+</template>
+
+<script>
+
+export default {
+  name: 'ContentPage',
+  data () {
+    return {
+      getScrollTop: 0,
+      fromPath: '',
+    }
+  },
+  props: {
+    title: String,
+    subTitle: String,
+    contentPadding: [String, Number],
+    loading: Boolean
+  },
+  watch: {
+    '$route' (to, from) {
+      this.fromPath = from.path
+    }
+  },
+  computed: {
+    title2 () {
+      let _title = this.title || this.$route.meta.title
+      return typeof _title === 'function' ? _title(this.$route) : _title
+    },
+    contentStyle () {
+      if (this.contentPadding) {
+        return {
+          padding: typeof this.contentPadding === 'string' ? this.contentPadding : this.contentPadding + 'px'
+        }
+      }
+      return ''
+    },
+  },
+  created () {
+
+  },
+  activated () {
+    if (this.fromPath === '/setImgGroup') {
+      this.$refs.content.scrollTop = this.getScrollTop
+    } else {
+      this.$refs.content.scrollTop = 0
+    }
+  },
+  methods: {
+    returnbtn () {
+      this.$router.go(-1)
+    },
+    resLisScroll () {
+        this.getScrollTop = this.$refs.content.scrollTop
+        this.$emit('contentScroll', this.getScrollTop)
+    },
+  }
+}
+</script>
+
+<style scoped>
+.content-page {
+  height: 100%;
+  padding: 0px 2px;
+}
+
+.content-page__content::-webkit-scrollbar {
+  width: 4px;
+  /* height: 4px; */
+}
+.content-page__content::-webkit-scrollbar-thumb {
+  border-radius: 10px;
+  -webkit-box-shadow: inset 0 0 5px #cfcfcf;
+  background: #cfcfcf;
+}
+.content-page__content::-webkit-scrollbar-track {
+  -webkit-box-shadow: inset 0 0 5px #ffffff;
+  border-radius: 0;
+  background: #ffffff;
+}
+
+.content-page__header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  /* height: 40px; */
+  color: #111827;
+}
+.content-page__header-title >>> .ivu-tabs-nav {
+  color: #111827;
+}
+
+.content-page__header-title {
+  font-size: 16px;
+  /* margin-left: 45px; */
+}
+
+.content-page__header-subtitle {
+  font-size: 12px;
+  color: #999;
+  margin-left: 12px;
+}
+
+.content-page__content {
+  box-shadow: 0 12px 32px -12px #ffffff;
+  border-radius: 8px 8px 0px 0px;
+  /* overflow: hidden; */
+  height: calc(100% - 63px);
+  background-color: #fff;
+  overflow-y: auto;
+  scroll-behavior: smooth;
+  padding: 20px;
+}
+/* tab样式 */
+
+.content-page__header-title >>> .ivu-tabs {
+  top: 10px;
+}
+
+.content-page__header-title >>> .ivu-tabs-ink-bar {
+  height: 3px;
+  /* width: 56px !important;
+  left: 16px !important; */
+}
+.content-page__header-title >>> .ivu-tabs-bar {
+  border-width: 0px;
+  margin-bottom: 0;
+}
+/* 返回按钮前加上左箭头 */
+.black_box{
+    display: flex;
+    align-items: center;
+    margin-top: 15px;
+}
+.blackup-btn ::before {
+  content: '<';
+}
+.blackup-btn{
+    color: #5b21b6;
+    cursor: pointer;
+    font-size:16px;
+    padding-left: 0px;
+    padding-right: 5px;
+}
+</style>

+ 147 - 0
src/components/content-page/index.vue

@@ -0,0 +1,147 @@
+<template>
+  <div class="content-page">
+    <div class="content-page__header">
+      <div class="content-page__header-title">
+        <slot name="title"></slot>
+      </div>
+    </div>
+
+    <div class="content-page__content" :style="contentStyle" ref="content">
+      <div class="content_loading" v-if="loading">
+        <Spin size="large"></Spin>
+      </div>
+      <slot v-else />
+    </div>
+    <!-- <Divider style="margin: 0px;" />
+    <div class="content-foot">
+      <div>微线索仅作为工具提供方,请遵守</div>
+      <div>
+        <a href="https://mp.weixin.qq.com/mp/opshowpage?action=newoplaw#t3-1" target="_blank">《微信公众平台运营规范》</a>
+        <a href="https://open.weixin.qq.com/cgi-bin/frame?t=news/protocol_developer_tmpl" target="_blank">《微信开放平台协议》</a>
+        <a href="https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&key=1503979103&version=1&lang=zh_CN&platform=2"
+          target="_blank">《微信公众平台协议》</a>
+        <a href="https://pay.weixin.qq.com/index.php/public/apply_sign/protocol_v2" target="_blank">《微信商户号服务协议》</a>
+        <a href="https://weixin.qq.com/cgi-bin/readtemplate?t=weixin_external_links_content_management_specification"
+          target="_blank">《微信外部链接内容管理规范》</a>
+        <a href="https://work.weixin.qq.com/nl/eula" target="_blank">《企业微信服务协议》</a>
+      </div>
+      <div>等微信相关管理规范</div>
+    </div> -->
+  </div>
+</template>
+
+<script>
+
+export default {
+  name: 'ContentPage',
+  data () {
+    return {
+    }
+  },
+  props: {
+    contentPadding: [String, Number],
+    loading: Boolean
+  },
+  computed: {
+    contentStyle () {
+      if (this.contentPadding) {
+        return {
+          padding: typeof this.contentPadding === 'string' ? this.contentPadding : this.contentPadding + 'px'
+        }
+      }
+      return ''
+    },
+  },
+  watch: {
+
+  },
+  created () {
+
+  },
+}
+</script>
+
+<style scoped>
+.content-page {
+  height: 100%;
+  padding: 0px 2px;
+}
+
+.content-page__content::-webkit-scrollbar {
+  width: 4px;
+  /* height: 4px; */
+}
+.content-page__content::-webkit-scrollbar-thumb {
+  border-radius: 10px;
+  -webkit-box-shadow: inset 0 0 5px #cfcfcf;
+  background: #cfcfcf;
+}
+.content-page__content::-webkit-scrollbar-track {
+  -webkit-box-shadow: inset 0 0 5px #ffffff;
+  border-radius: 0;
+  background: #ffffff;
+}
+.content-page__content .content_loading {
+    display: flex;
+    height: 100%;
+    justify-content: center;
+    align-items: center;
+}
+.content-page__header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  /* height: 40px; */
+  color: #111827;
+}
+.content-page__header-title >>> .ivu-tabs-nav {
+  color: #111827;
+}
+
+.content-page__header-title {
+  font-size: 16px;
+  /* margin-left: 45px; */
+}
+
+.content-page__header-subtitle {
+  font-size: 12px;
+  color: #999;
+  margin-left: 12px;
+}
+
+.content-page__content {
+  box-shadow: 0 12px 32px -12px #ffffff;
+  border-radius: 0px 8px 0px 0px;
+  /* overflow: hidden; */
+  height: 100%;
+  background-color: #fff;
+  overflow-y: auto;
+  scroll-behavior: smooth;
+  padding: 20px;
+  /* padding-bottom: 40px; */
+}
+.content-foot {
+  text-align: center;
+  padding: 10px 0px;
+  line-height: 22px;
+  font-size: 13px;
+  color: #515a6e;
+  background: #fff;
+  border-radius: 0px 0px 8px 8px;
+}
+/* tab样式 */
+
+.content-page__header-title >>> .ivu-tabs {
+  top: 10px;
+}
+
+.content-page__header-title >>> .ivu-tabs-ink-bar {
+  height: 3px;
+  /* width: 56px !important;
+  left: 16px !important; */
+}
+.content-page__header-title >>> .ivu-tabs-bar {
+  border-width: 0px;
+  margin-bottom: 0;
+}
+</style>

+ 93 - 0
src/components/errorPage/401.vue

@@ -0,0 +1,93 @@
+<template>
+    <div class="error-page">
+        <div class="content-con">
+            <img :src="require('_a/images/error-page/error-401.svg')">
+            <div class="text-con">
+                <h4>401</h4>
+                <h5>Oh~~无权限访问,请联系管理员分配权限~</h5>
+            </div>
+            <Button size="large" type="text" @click="backHome">返回首页</Button>
+            <Button size="large" type="text" @click="backPrev">返回上一页</Button>
+        </div>
+    </div>
+</template>
+
+<script>
+import { cacheStorage } from '@/utils'
+    export default {
+        name: 'error_401',
+        methods: {
+            backHome () {
+                console.log(cacheStorage.getItem(process.env.VUE_APP_AUTH_TOKEN))
+                this.$router.replace({
+                    name: cacheStorage.getItem(process.env.VUE_APP_AUTH_TOKEN) ? this.$store.state.auth.indexPage : 'login'
+                })
+            },
+            backPrev () {
+                console.log(cacheStorage.getItem(process.env.VUE_APP_AUTH_TOKEN))
+                if (cacheStorage.getItem(process.env.VUE_APP_AUTH_TOKEN)) {
+                    this.$router.go(-1)
+                } else {
+                    this.$router.replace({
+                        name: 'login'
+                    })
+                }
+            }
+        },
+    }
+</script>
+<style lang="less" scoped>
+    .error-page {
+        width: 100%;
+        height: 100%;
+        position: relative;
+        background: #f8f8f9;
+
+        .content-con {
+            width: 700px;
+            height: 600px;
+            position: absolute;
+            left: 50%;
+            top: 50%;
+            transform: translate(-50%, -60%);
+
+            img {
+                display: block;
+                width: 100%;
+                height: 100%;
+            }
+
+            .text-con {
+                position: absolute;
+                left: 0px;
+                top: 0px;
+
+                h4 {
+                    position: absolute;
+                    left: 0px;
+                    top: 0px;
+                    font-size: 80px;
+                    font-weight: 700;
+                    color: #348EED;
+                }
+
+                h5 {
+                    position: absolute;
+                    width: 700px;
+                    left: 0px;
+                    top: 100px;
+                    font-size: 20px;
+                    font-weight: 700;
+                    color: #67647D;
+                }
+            }
+
+            .back-btn-group {
+                position: absolute;
+                right: 0px;
+                bottom: 20px;
+            }
+        }
+    }
+
+</style>

+ 101 - 0
src/components/errorPage/404.vue

@@ -0,0 +1,101 @@
+<template>
+  <div class="error-page">
+    <div class="content-con">
+      <img :src="require('_a/images/error-page/error-404.svg')" />
+      <div class="text-con">
+        <h4>404</h4>
+        <h5>Oh~~您的页面好像飞走了~</h5>
+      </div>
+     <!--  {{$store.state.auth.authSource}} ------
+      {{$store.state.auth.indexPage}} -->
+      <Button size="large" type="text" @click="backHome">返回首页</Button>
+      <Button size="large" type="text" @click="backPrev">返回上一页</Button>
+    </div>
+  </div>
+</template>
+
+<script>
+
+export default {
+  name: 'error_404',
+  methods: {
+    backHome() {
+      /* this.$router.go(-1) */
+      if (this.$store.state.auth.authSource) {
+        this.$router.replace({
+          name: 'login'
+        })
+        this.$store.dispatch('logout')
+      } else {
+        this.$router.replace({
+          name: this.$store.state.auth.indexPage
+        })
+      }
+    },
+    backPrev() {
+      if (this.$store.state.auth.authSource) {
+        this.$router.replace({
+          name: 'login'
+        })
+        this.$store.dispatch('logout')
+      } else {
+        this.$router.go(-1)
+      }
+    }
+  },
+}
+</script>
+<style lang="less" scoped>
+.error-page {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  background: #f8f8f9;
+
+  .content-con {
+    width: 700px;
+    height: 600px;
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -60%);
+
+    img {
+      display: block;
+      width: 100%;
+      height: 100%;
+    }
+
+    .text-con {
+      position: absolute;
+      left: 0px;
+      top: 0px;
+
+      h4 {
+        position: absolute;
+        left: 0px;
+        top: 0px;
+        font-size: 80px;
+        font-weight: 700;
+        color: #348eed;
+      }
+
+      h5 {
+        position: absolute;
+        width: 700px;
+        left: 0px;
+        top: 100px;
+        font-size: 20px;
+        font-weight: 700;
+        color: #67647d;
+      }
+    }
+
+    .back-btn-group {
+      position: absolute;
+      right: 0px;
+      bottom: 20px;
+    }
+  }
+}
+</style>

+ 85 - 0
src/components/errorPage/500.vue

@@ -0,0 +1,85 @@
+<template>
+    <div class="error-page">
+        <div class="content-con">
+            <img :src="require('_a/images/error-page/error-500.svg')">
+            <div class="text-con">
+                <h4>500</h4>
+                <h5>Oh~~服务器错误,请联系系统管理员~</h5>
+            </div>
+            <Button size="large" type="text" @click="backHome">返回首页</Button>
+            <Button size="large" type="text" @click="backPrev">返回上一页</Button>
+        </div>
+    </div>
+</template>
+
+<script>
+
+    export default {
+        name: 'error_500',
+        methods: {
+            backHome () {
+                this.$router.replace({
+                    name: this.$store.state.auth.indexPage
+                })
+            },
+            backPrev () {
+                this.$router.go(-1)
+            }
+        },
+    }
+</script>
+<style lang="less" scoped>
+    .error-page {
+        width: 100%;
+        height: 100%;
+        position: relative;
+        background: #f8f8f9;
+
+        .content-con {
+            width: 700px;
+            height: 600px;
+            position: absolute;
+            left: 50%;
+            top: 50%;
+            transform: translate(-50%, -60%);
+
+            img {
+                display: block;
+                width: 100%;
+                height: 100%;
+            }
+
+            .text-con {
+                position: absolute;
+                left: 0px;
+                top: 0px;
+
+                h4 {
+                    position: absolute;
+                    left: 0px;
+                    top: 0px;
+                    font-size: 80px;
+                    font-weight: 700;
+                    color: #348EED;
+                }
+
+                h5 {
+                    position: absolute;
+                    width: 700px;
+                    left: 0px;
+                    top: 100px;
+                    font-size: 20px;
+                    font-weight: 700;
+                    color: #67647D;
+                }
+            }
+
+            .back-btn-group {
+                position: absolute;
+                right: 0px;
+                bottom: 20px;
+            }
+        }
+    }
+
+</style>

+ 366 - 0
src/components/main/Main.vue

@@ -0,0 +1,366 @@
+<template>
+  <Layout style="height: 100%" class="main">
+    <Sider hide-trigger collapsible :width="250" :collapsed-width="53" v-model="collapsed" class="left-sider">
+      <side-menu accordion ref="sideMenu" :active-name="$route.name" :collapsed="collapsed" @on-select="turnToPage"
+        :menu-list="menuList">
+        <!-- 需要放在菜单上面的内容,如Logo,写在side-menu标签内部,如下 -->
+        <div v-if="!collapsed" class="logo-con">
+          <img src="@/assets/images/wfg_logo.png" key="max-logo" />
+        </div>
+        <div v-else class="logo-con min-logo">
+          <img class="min-logo-pic" src="@/assets/images/min_wfg_logo.png" key="min-logo" />
+        </div>
+      </side-menu>
+    </Sider>
+    <Layout>
+      <Header class="header-con">
+        <header-bar :collapsed="collapsed" @on-coll-change="handleCollapsedChange">
+          <user :user-avatar="userAvatar" />
+          <!--<theme style="margin: 0 5px;"></theme>
+           <fullscreen v-model="isFullscreen" style="margin-right: 10px;"/>
+           <Poptip placement="right" width="350" height="570">
+            <Button type="text">
+              <Icon custom="iconfont icon-icon-liaotian" style="margin-right:2px" />
+              联系客服
+            </Button>
+            <div class="api" slot="content">
+              <img src="@/assets/images/customer-service.jpg" style="width:100%;height: 100%" />
+            </div>
+          </Poptip>
+
+           <Button type="text" style="margin-top:13px" @click="buyEdition">
+            <Icon type="md-cart" size="16" style="margin-right:2px;color: #D81E06;" />
+           </Button>-->
+          <!--<Button type="text" style="margin-top:13px">
+              <Icon type="md-cart" size="16" style="margin-right:2px;color: #999;" />
+            </Button>-->
+          <msg />
+        </header-bar>
+      </Header>
+      <Content class="main-content-con">
+        <Layout class="main-layout-con">
+          <!-- <div class="tag-nav-wrapper">
+                        <tags-nav :value="$route" @input="handleClick" :list="tagNavList" @on-close="handleCloseTag"/>
+                    </div> -->
+          <Content class="content-wrapper">
+            <keep-alive>
+              <router-view v-if="!!keepAlive" style="height: 100%;width: 100%;box-sizing: border-box;" />
+            </keep-alive>
+            <router-view v-if="!keepAlive" style="height: 100%;width: 100%;box-sizing: border-box;" />
+            <ABackTop :height="100" :bottom="80" :right="50" container=".content-wrapper"></ABackTop>
+          </Content>
+        </Layout>
+      </Content>
+    </Layout>
+  </Layout>
+</template>
+<script>
+import SideMenu from '_c/main/components/sideMenu/SideMenu'
+import HeaderBar from '_c/main/components/headerBar/HeaderBar'
+import TagsNav from '_c/main/components/tagsNav/TagsNav'
+import User from '_c/main/components/user/User'
+import ABackTop from '_c/main/components/backTop/BackTop'
+// import Fullscreen from '_c/main/components/fullscreen/Fullscreen'
+// import Theme from '_c/main/components/theme/Theme'
+import { mapMutations, mapState } from 'vuex'
+import { getNewTagList, routeEqual, objEqual } from '@/utils'
+
+export default {
+  name: 'Main',
+  components: {
+    SideMenu,
+    HeaderBar,
+    TagsNav,
+    // Fullscreen,
+    User,
+    ABackTop,
+    // Theme,
+    // Msg
+  },
+  data () {
+    return {
+      collapsed: false,
+      isFullscreen: false,
+    }
+  },
+  computed: {
+    ...mapState({
+      tagNavList: (state) => state.auth.tagNavList,
+      userAvatar: (state) => state.auth.avatarImgPath,
+      mainPage: (state) => state.auth.mainPage,
+    }),
+    keepAlive () {
+        return this.$route.meta.keepAlive
+    },
+    menuList () {
+      return this.$store.getters.menuList
+    }
+  },
+  methods: {
+    ...mapMutations([
+      'setBreadCrumb',
+      'setTagNavList',
+      'addTag',
+      'closeTag'
+    ]),
+    turnToPage (route) {
+      let { name, params, query } = {}
+      // 当路由为页面配置为页面跳转
+      if (typeof route === 'string') {
+        name = route
+      } else {
+        // 如果为当前页面, 如果参数不一样,允许执行
+        if (this.$route.name === route.name &&
+          objEqual(this.$route.query, route.query) &&
+          objEqual(this.$route.params, route.params)) {
+          return
+        }
+        name = route.name
+        params = route.params
+        query = route.query
+      }
+      // 获取当前点击的一级菜单title
+      let menuData = this.$store.getters.menuList
+      menuData.forEach(item => {
+        item.children && item.children.forEach(v => {
+          if (name === v.name) {
+            localStorage.setItem('firstMenuName', item.name)
+          }
+        })
+      })
+      if (name.indexOf('isTurnByHref_') > -1) {
+        window.open(name.split('_')[1])
+        return
+      }
+      this.$router.push({
+        name,
+        params,
+        query
+      })
+    },
+    handleCollapsedChange (state) {
+      this.collapsed = state
+    },
+    handleCloseTag (res, type, route) {
+      if (type !== 'others') {
+        if (type === 'all') {
+          this.turnToPage(this.$store.state.auth.indexPage)
+        } else {
+          if (routeEqual(this.$route, route)) {
+            this.closeTag(route)
+          }
+        }
+      }
+      this.setTagNavList(res)
+    },
+    handleClick (item) {
+      this.turnToPage(item)
+    },
+    // 购买版本
+    buyEdition () {
+      this.$router.push({
+        path: '/edition'
+      })
+    },
+  },
+  watch: {
+    '$route' (newRoute) {
+      const { name, query, params, meta } = newRoute
+      this.addTag({
+        route: { name, query, params, meta },
+        type: 'push'
+      })
+      this.setBreadCrumb(newRoute)
+      this.setTagNavList(getNewTagList(this.tagNavList, newRoute))
+      this.$refs.sideMenu.updateOpenName(newRoute.name)
+    }
+  },
+  mounted () {
+    /**
+     * @description 初始化设置面包屑导航和标签导航
+     */
+    this.setTagNavList()
+    const { name, params, query, meta } = this.$route
+    this.addTag({
+      route: { name, params, query, meta }
+    })
+    this.setBreadCrumb(this.$route)
+    // 如果当前打开页面不在标签栏中,跳到homeName页
+    if (!this.tagNavList.find(item => item.name === this.$route.name)) {
+      this.$router.push({
+        name: this.$store.state.auth.indexPage
+      })
+    }
+    // 获取租户信息
+  }
+}
+</script>
+<style lang="less" scoped>
+.main {
+  .logo-con {
+    background: #ede9fe;
+    overflow: hidden;
+    padding: 25px 15px 0px 15px;
+    img {
+      height: 32px;
+      width: 96px;
+      display: block;
+    }
+    .min-logo-pic {
+      width: 31px;
+      height: 36px;
+    }
+  }
+  .min-logo {
+    background: #9146ff;
+    padding: 25px 11px 0px 11px;
+  }
+  .header-con {
+    background: #fff;
+    padding: 0 20px;
+    width: 100%;
+    height: 53px;
+    line-height: 53px;
+  }
+
+  .main-layout-con {
+    height: 100%;
+    overflow: hidden;
+  }
+
+  .main-content-con {
+    height: ~'calc(100% - 60px)';
+    overflow: hidden;
+  }
+
+  .tag-nav-wrapper {
+    padding: 0;
+    height: 40px;
+    background: #f0f0f0;
+  }
+  .content-wrapper {
+    padding: 18px;
+    height: ~'calc(100% - 80px)';
+    overflow: auto;
+    background: #fff;
+  }
+  .left-sider {
+    overflow: hidden;
+    background: #9146ff;
+    .ivu-layout-sider-children {
+      overflow-y: scroll;
+      margin-right: -18px;
+    }
+  }
+}
+/deep/.ivu-menu-light {
+  background: #ede9fe;
+}
+/deep/.ivu-menu-light.ivu-menu-vertical .ivu-menu-submenu-title:hover {
+  color: #5b21b6;
+}
+/deep/.ivu-menu-light.ivu-menu-vertical .ivu-menu-item:hover {
+  color: #5b21b6;
+}
+/deep/.ivu-menu-light.ivu-menu-vertical
+  .ivu-menu-item-active:not(.ivu-menu-submenu) {
+  color: #ffffff;
+  background: #7c3aed;
+  border-radius: 4px;
+  z-index: 2;
+}
+/* .ivu-menu-light.ivu-menu-vertical .ivu-menu {
+  background: #F0F0F2;
+  margin: 10px;
+} */
+/deep/.ivu-menu-light.ivu-menu-vertical
+  .ivu-menu-item-active:not(.ivu-menu-submenu):after {
+  content: '';
+  display: block;
+  width: 0;
+  position: absolute;
+  top: 10px;
+  bottom: 10px;
+  right: 10px;
+  background: #7c3aed;
+}
+
+/deep/.side-menu-wrapper .ivu-menu-submenu .ivu-menu {
+  background: #ede9fe;
+  padding: 0 0 10px 10px;
+}
+/deep/ .ivu-breadcrumb > span:last-child {
+  color: #000;
+  font-weight: 500;
+}
+
+/deep/.ivu-menu-item,
+/deep/.ivu-menu-submenu-title {
+  //border-bottom: 1px solid #d1d5db;
+  color: #000;
+  padding: 10px !important;
+  margin-top: 20px;
+}
+/deep/.ivu-menu-item::before {
+  // content: '';
+  // display: block;
+  // width: 100%;
+  // height: 1px;
+  // position: absolute;
+  // bottom: -10px;
+  // right: 0px;
+  // background: #d1d5db;
+}
+/deep/.ivu-menu-submenu > .ivu-menu > .ivu-menu-item::before {
+  height: 0;
+}
+/deep/.ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i {
+  margin-right: 8px !important;
+}
+.collased-menu-dropdown {
+  width: 100%;
+  margin: 0;
+  line-height: normal;
+  padding: 7px 0 6px 16px;
+  clear: both;
+  font-size: 12px !important;
+  white-space: nowrap;
+  list-style: none;
+  cursor: pointer;
+  transition: background 0.2s ease-in-out;
+  &:hover {
+    background: rgba(100, 100, 100, 0.1);
+  }
+  & * {
+    color: #666666;
+  }
+  .ivu-menu-item > i {
+    margin-right: 12px !important;
+  }
+  .ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i {
+    margin-right: 8px !important;
+  }
+  .collapsed-menu i {
+    color: #fff;
+  }
+}
+.ivu-select-dropdown.ivu-dropdown-transfer {
+  max-height: 400px;
+}
+/deep/.ivu-menu-vertical {
+  overflow-y: auto !important;
+  height: ~'calc(100vh - 53px)' !important;
+  padding: 0 15px 15px 15px;
+}
+/deep/.ivu-menu-vertical::-webkit-scrollbar {
+  /*滚动条整体样式*/
+  width: 1px; /*高宽分别对应横竖滚动条的尺寸*/
+  height: 1px;
+}
+/deep/.ivu-menu-vertical.ivu-menu-light:after {
+  width: 0;
+}
+/deep/.ivu-menu-vertical .ivu-menu-submenu-title-icon {
+  right: 0px;
+}
+</style>

+ 90 - 0
src/components/main/components/backTop/BackTop.vue

@@ -0,0 +1,90 @@
+<template>
+    <div :class="classes" :style="styles" @click="back">
+        <slot>
+            <div :class="innerClasses">
+                <i class="ivu-icon ivu-icon-ios-arrow-up"></i>
+            </div>
+        </slot>
+    </div>
+</template>
+<script>
+    import { scrollTop, on, off } from '@/utils'
+
+    const prefixCls = 'ivu-back-top'
+
+    export default {
+        name: 'BackTop',
+        props: {
+            height: {
+                type: Number,
+                default: 400
+            },
+            bottom: {
+                type: Number,
+                default: 30
+            },
+            right: {
+                type: Number,
+                default: 30
+            },
+            duration: {
+                type: Number,
+                default: 1000
+            },
+            container: {
+                type: null,
+                default: window
+            }
+        },
+        data () {
+            return {
+                backTop: false
+            }
+        },
+        mounted () {
+            // window.addEventListener('scroll', this.handleScroll, false)
+            // window.addEventListener('resize', this.handleScroll, false)
+            on(this.containerEle, 'scroll', this.handleScroll)
+            on(this.containerEle, 'resize', this.handleScroll)
+        },
+        beforeDestroy () {
+            // window.removeEventListener('scroll', this.handleScroll, false)
+            // window.removeEventListener('resize', this.handleScroll, false)
+            off(this.containerEle, 'scroll', this.handleScroll)
+            off(this.containerEle, 'resize', this.handleScroll)
+        },
+        computed: {
+            classes () {
+                return [
+                    `${prefixCls}`,
+                    {
+                        [`${prefixCls}-show`]: this.backTop
+                    }
+                ]
+            },
+            styles () {
+                return {
+                    bottom: `${this.bottom}px`,
+                    right: `${this.right}px`
+                }
+            },
+            innerClasses () {
+                return `${prefixCls}-inner`
+            },
+            containerEle () {
+                return this.container === window ? window : document.querySelector(this.container)
+            }
+        },
+        methods: {
+            handleScroll () {
+                this.backTop = this.containerEle.scrollTop >= this.height
+            },
+            back () {
+                let target = typeof this.container === 'string' ? this.containerEle : (document.documentElement || document.body)
+                const sTop = target.scrollTop
+                scrollTop(this.containerEle, sTop, 0, this.duration)
+                this.$emit('on-click')
+            }
+        }
+    }
+</script>

+ 84 - 0
src/components/main/components/fullscreen/Fullscreen.vue

@@ -0,0 +1,84 @@
+<template>
+  <div v-if="showFullScreenBtn" class="full-screen-btn-con">
+    <Tooltip :content="value ? '退出全屏' : '全屏'" placement="bottom">
+      <Icon @click.native="handleChange" :type="value ? 'md-contract' : 'md-expand'" :size="23"></Icon>
+    </Tooltip>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Fullscreen',
+  computed: {
+    showFullScreenBtn () {
+      return window.navigator.userAgent.indexOf('MSIE') < 0
+    }
+  },
+  props: {
+    value: {
+      type: Boolean,
+      default: false
+    }
+  },
+  methods: {
+    handleFullscreen () {
+      let main = document.body
+      if (this.value) {
+        if (document.exitFullscreen) {
+          document.exitFullscreen()
+        } else if (document.mozCancelFullScreen) {
+          document.mozCancelFullScreen()
+        } else if (document.webkitCancelFullScreen) {
+          document.webkitCancelFullScreen()
+        } else if (document.msExitFullscreen) {
+          document.msExitFullscreen()
+        }
+      } else {
+        if (main.requestFullscreen) {
+          main.requestFullscreen()
+        } else if (main.mozRequestFullScreen) {
+          main.mozRequestFullScreen()
+        } else if (main.webkitRequestFullScreen) {
+          main.webkitRequestFullScreen()
+        } else if (main.msRequestFullscreen) {
+          main.msRequestFullscreen()
+        }
+      }
+    },
+    handleChange () {
+      this.handleFullscreen()
+    }
+  },
+  mounted () {
+    let isFullscreen = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen
+    isFullscreen = !!isFullscreen
+    document.addEventListener('fullscreenchange', () => {
+      this.$emit('input', !this.value)
+      this.$emit('on-change', !this.value)
+    })
+    document.addEventListener('mozfullscreenchange', () => {
+      this.$emit('input', !this.value)
+      this.$emit('on-change', !this.value)
+    })
+    document.addEventListener('webkitfullscreenchange', () => {
+      this.$emit('input', !this.value)
+      this.$emit('on-change', !this.value)
+    })
+    document.addEventListener('msfullscreenchange', () => {
+      this.$emit('input', !this.value)
+      this.$emit('on-change', !this.value)
+    })
+    this.$emit('input', isFullscreen)
+  }
+}
+</script>
+
+<style lang="less">
+.full-screen-btn-con .ivu-tooltip-rel{
+  height: 64px;
+  line-height: 56px;
+  i{
+    cursor: pointer;
+  }
+}
+</style>

+ 57 - 0
src/components/main/components/headerBar/HeaderBar.vue

@@ -0,0 +1,57 @@
+<template>
+  <div class="header-bar">
+    <div class="left">
+      <side-trigger :collapsed="collapsed" icon="md-menu" @on-change="handleCollpasedChange"></side-trigger>
+      <custom-bread-crumb show-icon style="margin-left: 10px;" :list="breadCrumbList"></custom-bread-crumb>
+    </div>
+    <div class="custom-content-con">
+      <slot></slot>
+    </div>
+  </div>
+</template>
+<script>
+import SideTrigger from './sideTrigger/SideTrigger'
+import CustomBreadCrumb from './customBreadCrumb/CustomBreadCrumb'
+export default {
+  name: 'HeaderBar',
+  components: {
+    SideTrigger,
+    CustomBreadCrumb
+  },
+  props: {
+    collapsed: Boolean
+  },
+  computed: {
+    breadCrumbList () {
+      return this.$store.state.auth.breadCrumbList
+    }
+  },
+  mounted () {
+    /* console.log(this.$store.state.auth.breadCrumbList, '11224545') */
+  },
+  methods: {
+    handleCollpasedChange (state) {
+      this.$emit('on-coll-change', state)
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+.header-bar {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  display: flex;
+  justify-content: space-between;
+  .custom-content-con {
+    height: auto;
+    padding-right: 20px;
+    line-height: 53px;
+  }
+}
+@media screen and (max-width: 767px) {
+  .custom-bread-crumb {
+    display: none;
+  }
+}
+</style>

+ 52 - 0
src/components/main/components/headerBar/customBreadCrumb/CustomBreadCrumb.vue

@@ -0,0 +1,52 @@
+<template>
+  <div class="custom-bread-crumb">
+    <Breadcrumb :style="{fontSize: `${fontSize}px`}">
+      <BreadcrumbItem v-for="item in list" :to="item.to" :key="`bread-crumb-${item.name}`">
+        <!-- <Icon style="margin-right: 4px;" :type="item.icon || ''"/> -->
+        <Icon v-if="item.icon && item.icon.indexOf('iconfont') === -1" size="26" :type="item.icon" />
+        <Icon v-else :custom="item.icon" size="26"></Icon>
+        {{ showTitle(item) }}
+      </BreadcrumbItem>
+    </Breadcrumb>
+  </div>
+</template>
+<script>
+import { showTitle } from '@/utils'
+export default {
+  name: 'customBreadCrumb',
+  props: {
+    list: {
+      type: Array,
+      default: () => []
+    },
+    fontSize: {
+      type: Number,
+      default: 18
+    },
+    showIcon: {
+      type: Boolean,
+      default: false
+    }
+  },
+  methods: {
+    showTitle (item) {
+      return showTitle(item, this)
+    },
+    isCustomIcon (iconName) {
+      return iconName.indexOf('_') === 0
+    },
+    getCustomIconName (iconName) {
+      return iconName.slice(1)
+    }
+  }
+}
+</script>
+<style lang="less">
+.custom-bread-crumb {
+  display: inline-block;
+  vertical-align: top;
+  .ivu-breadcrumb {
+    color: #111827;
+  }
+}
+</style>

+ 0 - 0
src/components/main/components/headerBar/customBreadCrumb/customBreadCrumb.less


+ 60 - 0
src/components/main/components/headerBar/sideTrigger/SideTrigger.vue

@@ -0,0 +1,60 @@
+<template>
+  <a @click="handleChange" type="text" :class="['sider-trigger-a', collapsed ? 'collapsed' : '']">
+    <Icon :type="icon" :size="size" />
+  </a>
+</template>
+<script>
+export default {
+  name: 'SideTrigger',
+  props: {
+    collapsed: Boolean,
+    icon: {
+      type: String,
+      default: 'navicon-round'
+    },
+    size: {
+      type: Number,
+      default: 30
+    }
+  },
+  mounted () {
+    let _this = this
+    if (window.innerWidth && window.innerWidth < 1200) {
+      _this.$emit('on-change', true)
+    }
+    window.addEventListener('resize', function() {
+      if (window.innerWidth && window.innerWidth < 1200) {
+        _this.$emit('on-change', true)
+      }
+    })
+  },
+  methods: {
+    handleChange () {
+      this.$emit('on-change', !this.collapsed)
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+.trans {
+  transition: transform 0.2s ease;
+}
+@size: 40px;
+.sider-trigger-a {
+  padding: 6px;
+  width: @size;
+  height: @size;
+  display: inline-block;
+  text-align: center;
+  color: #000;
+  margin-top: 5px;
+  i {
+    .trans;
+    vertical-align: top;
+  }
+  &.collapsed i {
+    transform: rotateZ(90deg);
+    .trans;
+  }
+}
+</style>

+ 65 - 0
src/components/main/components/sideMenu/CollapsedMenu.vue

@@ -0,0 +1,65 @@
+<template>
+  <Dropdown ref="dropdown" @on-click="handleClick" :class="hideTitle ? '' : 'collased-menu-dropdown'"
+    :transfer="hideTitle" :placement="placement">
+    <a class="drop-menu-a" type="text" @mouseover="handleMousemove($event, children)"
+      :style="{textAlign: !hideTitle ? 'left' : ''}">
+      <Icon :size="rootIconSize" color="#fff" :type="parentItem.icon" />
+      <span class="menu-title" v-if="!hideTitle">{{ showTitle(parentItem) }}</span>
+      <Icon style="float: right;" v-if="!hideTitle" type="ios-arrow-forward" :size="16" />
+    </a>
+    <DropdownMenu ref="dropdown" slot="list">
+      <template v-for="child in children">
+        <collapsed-menu v-if="showChildren(child)" :icon-size="iconSize" :parent-item="child" :theme="theme"
+          :key="`drop-${child.name}`"></collapsed-menu>
+        <DropdownItem v-else :key="`drop-${child.name}`" :name="child.name">
+          <Icon :size="iconSize" :type="child.icon" />
+          <span class="menu-title">{{ showTitle(child) }}</span>
+        </DropdownItem>
+      </template>
+    </DropdownMenu>
+  </Dropdown>
+</template>
+<script>
+import mixin from './mixin'
+import itemMixin from './item.mixin'
+import { findNodeUpperByClasses } from '@/utils'
+
+export default {
+  name: 'CollapsedMenu',
+  mixins: [mixin, itemMixin],
+  props: {
+    hideTitle: {
+      type: Boolean,
+      default: false
+    },
+    rootIconSize: {
+      type: Number,
+      default: 16
+    },
+    theme: {
+      type: String,
+      required: true
+    }
+  },
+  data () {
+    return {
+      placement: 'right-end'
+    }
+  },
+  methods: {
+    handleClick (name) {
+      this.$emit('on-click', name)
+    },
+    handleMousemove (event, children) {
+      const { pageY } = event
+      const height = children.length * 38
+      const isOverflow = pageY + height < window.innerHeight
+      this.placement = isOverflow ? 'right-start' : 'right-end'
+    }
+  },
+  mounted () {
+    let dropdown = findNodeUpperByClasses(this.$refs.dropdown.$el, ['ivu-select-dropdown', 'ivu-dropdown-transfer'])
+    if (dropdown) dropdown.style.overflow = 'visible'
+  }
+}
+</script>

+ 135 - 0
src/components/main/components/sideMenu/SideMenu.vue

@@ -0,0 +1,135 @@
+<template>
+  <div class="side-menu-wrapper">
+    <slot></slot>
+    <Menu ref="menu" style="height: 100%;" v-show="!collapsed" :active-name="activeName" :open-names="openedNames"
+      :accordion="accordion" :theme="theme" width="auto" @on-select="handleSelect">
+      <template v-for="(item, index) in menuList">
+        <template v-if="item.children && item.children.length === 1">
+          <side-menu-item v-if="showChildren(item)" :key="`menu-${index}`" :parent-item="item"></side-menu-item>
+          <menu-item v-else :name="getNameOrHref(item, true)" :key="`menu-${item.children[0].name}`">
+            <Icon v-if="item.children[0].icon && item.children[0].icon.indexOf('iconfont') === -1" size="20"
+              :type="item.children[0].icon || ''" />
+            <Icon v-else :custom="item.children[0].icon" size="20"></Icon>
+            <span>
+              {{ showTitle(item.children[0]) }}
+            </span>
+          </menu-item>
+        </template>
+        <template v-else>
+          <side-menu-item v-if="showChildren(item)" :key="`menu-${index}`" :parent-item="item"></side-menu-item>
+          <menu-item v-else :name="getNameOrHref(item)" :key="`menu-${index}`">
+            <Icon :type="item.icon || ''" />
+            <span>{{ showTitle(item) }}</span>
+          </menu-item>
+        </template>
+      </template>
+    </Menu>
+    <div class="menu-collapsed" v-show="collapsed" :list="menuList">
+      <template v-for="(item, index) in menuList">
+        <collapsed-menu v-if="item.children && (item.children.length > 1 || item.meta.showAlways)"
+          @on-click="handleSelect" hide-title :root-icon-size="rootIconSize" :icon-size="iconSize" :theme="theme"
+          :parent-item="item" :key="`drop-menu-${index}`"></collapsed-menu>
+        <Tooltip transfer v-else :content="showTitle(item.children && item.children[0] ? item.children[0] : item)"
+          placement="right" :key="`drop-menu-${index}`">
+          <a @click="handleSelect(getNameOrHref(item, true))" class="drop-menu-a" :style="{textAlign: 'center'}">
+            <Icon :size="rootIconSize" color="#fff" :type="item.icon || (item.children && item.children[0].icon)" />
+          </a>
+        </Tooltip>
+      </template>
+    </div>
+  </div>
+</template>
+<script>
+import SideMenuItem from './SideMenuItem.vue'
+import CollapsedMenu from './CollapsedMenu.vue'
+import { getUnion } from '@/utils'
+import mixin from './mixin'
+import { mapState } from 'vuex'
+
+export default {
+  name: 'SideMenu',
+  mixins: [mixin],
+  components: {
+    SideMenuItem,
+    CollapsedMenu
+  },
+  computed: {
+    ...mapState({
+    }),
+  },
+  props: {
+    menuList: {
+      type: Array,
+      default () {
+        return []
+      }
+    },
+    collapsed: {
+      type: Boolean
+    },
+    rootIconSize: {
+      type: Number,
+      default: 20
+    },
+    iconSize: {
+      type: Number,
+      default: 16
+    },
+    accordion: Boolean,
+    activeName: {
+      type: String,
+      default: ''
+    },
+    openNames: {
+      type: Array,
+      default: () => []
+    }
+  },
+  data () {
+    return {
+      openedNames: [],
+    }
+  },
+  methods: {
+    handleSelect (name) {
+      if (name === this.$route.name) {
+        return
+      }
+      this.$emit('on-select', name)
+    },
+    getOpenedNamesByActiveName (name) {
+      return this.$route.matched.map(item => item.name).filter(item => item !== name)
+    },
+    updateOpenName (name) {
+      if (name === 'name') {
+        this.openedNames = []
+      } else {
+        this.openedNames = this.getOpenedNamesByActiveName(name)
+      }
+    }
+  },
+  watch: {
+    activeName (name) {
+      if (this.accordion) {
+        this.openedNames = this.getOpenedNamesByActiveName(name)
+      } else {
+        this.openedNames = getUnion(this.openedNames, this.getOpenedNamesByActiveName(name))
+      }
+    },
+    openNames (newNames) {
+      this.openedNames = newNames
+    },
+    openedNames () {
+      this.$nextTick(() => {
+        this.$refs.menu.updateOpened()
+      })
+    }
+  },
+  mounted () {
+    this.openedNames = getUnion(this.openedNames, this.getOpenedNamesByActiveName(name))
+  }
+}
+</script>
+<style lang="less">
+@import 'sideMenu.less';
+</style>

+ 48 - 0
src/components/main/components/sideMenu/SideMenuGroupItem.vue

@@ -0,0 +1,48 @@
+<template>
+  <MenuGroup :title="`${parentTitle}`">
+    <template v-for="item in children">
+      <template v-if="item.children && item.children.length === 1">
+        <side-menu-group-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item">
+        </side-menu-group-item>
+        <menu-item v-else :name="getNameOrHref(item, true)" :key="`menu-${item.children[0].name}`">
+          <Icon :type="item.children[0].icon || ''" />
+          <span>{{ showTitle(item.children[0]) }}</span>
+        </menu-item>
+      </template>
+      <template v-else>
+        <side-menu-group-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item">
+        </side-menu-group-item>
+        <menu-item v-else :name="getNameOrHref(item)" :key="`menu-${item.name}`">
+          <span>{{ showTitle(item) }}</span>
+        </menu-item>
+      </template>
+    </template>
+  </MenuGroup>
+</template>
+<script>
+import mixin from './mixin'
+import itemMixin from './item.mixin'
+import SideMenuGroupItem from './SideMenuGroupItem.vue'
+export default {
+  name: 'SideMenuItem',
+  mixins: [mixin, itemMixin],
+  components: {
+    SideMenuGroupItem,
+  },
+}
+</script>
+<style lang="less" scoped>
+.ivu-menu-item-group {
+  border-bottom: 1px solid #f2f2f2;
+  padding-bottom: 8px;
+  margin-bottom: 8px;
+}
+/deep/.ivu-menu-item-group-title {
+  padding-left: 0px !important;
+  height: 26px;
+  line-height: 26px;
+  font-size: 12px;
+  padding-left: 10px;
+  color: #2a2a2a;
+}
+</style>

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini