zouling 1 місяць тому
батько
коміт
37cba4f244
91 змінених файлів з 5693 додано та 193 видалено
  1. 2 2
      .env.development
  2. 1 0
      .env.production
  3. 216 81
      package-lock.json
  4. 6 3
      package.json
  5. 200 17
      public/index.html
  6. 0 0
      public/styles/theme-chalk/index.css
  7. 60 0
      src/api/login.js
  8. 9 0
      src/api/menu.js
  9. 52 0
      src/api/system/dict/data.js
  10. 60 0
      src/api/system/dict/type.js
  11. BIN
      src/assets/images/boxbg.png
  12. BIN
      src/assets/images/cenbg.png
  13. BIN
      src/assets/images/centbg.png
  14. BIN
      src/assets/images/down.png
  15. BIN
      src/assets/images/fixedbg.png
  16. BIN
      src/assets/images/gjicoa.png
  17. BIN
      src/assets/images/gjicob.png
  18. BIN
      src/assets/images/head.png
  19. BIN
      src/assets/images/header-bg.png
  20. BIN
      src/assets/images/header.png
  21. BIN
      src/assets/images/headertit.png
  22. BIN
      src/assets/images/icon_htdp_xl.png
  23. BIN
      src/assets/images/icon_htdp_zxt_tl1.png
  24. BIN
      src/assets/images/icon_htdp_zxt_tl2.png
  25. BIN
      src/assets/images/icon_htgl_hxl.png
  26. BIN
      src/assets/images/jzicoa.png
  27. BIN
      src/assets/images/jzicob.png
  28. BIN
      src/assets/images/jzicoc.png
  29. BIN
      src/assets/images/jzicod.png
  30. BIN
      src/assets/images/jzicoe.png
  31. BIN
      src/assets/images/jziconf.png
  32. BIN
      src/assets/images/line.png
  33. BIN
      src/assets/images/login_pwd.png
  34. BIN
      src/assets/images/login_tit.png
  35. BIN
      src/assets/images/loginbg.png
  36. BIN
      src/assets/images/logintit.png
  37. BIN
      src/assets/images/ltaba.png
  38. BIN
      src/assets/images/ltabb.png
  39. BIN
      src/assets/images/ltabc.png
  40. BIN
      src/assets/images/ltabd.png
  41. BIN
      src/assets/images/profile.jpg
  42. BIN
      src/assets/images/tip.png
  43. BIN
      src/assets/images/up.png
  44. BIN
      src/assets/images/zcicoa.png
  45. BIN
      src/assets/images/zcicob.png
  46. 99 0
      src/assets/styles/btn.scss
  47. 92 0
      src/assets/styles/element-ui.scss
  48. 31 0
      src/assets/styles/element-variables.scss
  49. 178 0
      src/assets/styles/index.scss
  50. 66 0
      src/assets/styles/mixin.scss
  51. 287 0
      src/assets/styles/ruoyi.scss
  52. 227 0
      src/assets/styles/sidebar.scss
  53. 49 0
      src/assets/styles/transition.scss
  54. 54 0
      src/assets/styles/variables.scss
  55. 147 0
      src/components/AppHeader.vue
  56. 20 2
      src/main.js
  57. 43 0
      src/mixins/ChartMixin.js
  58. 63 0
      src/permission.js
  59. 60 0
      src/plugins/auth.js
  60. 79 0
      src/plugins/cache.js
  61. 20 0
      src/plugins/index.js
  62. 83 0
      src/plugins/modal.js
  63. 24 8
      src/router/index.js
  64. 3 3
      src/settings.js
  65. 19 0
      src/store/getters.js
  66. 18 8
      src/store/index.js
  67. 66 0
      src/store/modules/app.js
  68. 50 0
      src/store/modules/dict.js
  69. 42 0
      src/store/modules/settings.js
  70. 106 0
      src/store/modules/user.js
  71. 30 0
      src/utils/jsencrypt.js
  72. 100 49
      src/utils/request.js
  73. 228 0
      src/utils/ruoyi.js
  74. 114 0
      src/utils/validate.js
  75. 35 0
      src/views/loginmain.vue
  76. 26 12
      src/views/main.vue
  77. 3 2
      src/views/pages/common/ScaleBox.vue
  78. 44 0
      src/views/pages/components/box.vue
  79. 326 0
      src/views/pages/components/center.vue
  80. 180 0
      src/views/pages/components/jbbarChart.vue
  81. 218 0
      src/views/pages/components/lineChart.vue
  82. 191 0
      src/views/pages/components/ltbarChart.vue
  83. 119 0
      src/views/pages/components/morepieChart.vue
  84. 119 0
      src/views/pages/components/morepieCharts.vue
  85. 258 0
      src/views/pages/components/moreringChart.vue
  86. 123 0
      src/views/pages/components/radarChart.vue
  87. 208 0
      src/views/pages/components/table - 副本.vue
  88. 239 0
      src/views/pages/components/table.vue
  89. 339 3
      src/views/pages/index.vue
  90. 253 0
      src/views/pages/login.vue
  91. 8 3
      vue.config.js

+ 2 - 2
.env.development

@@ -1,8 +1,8 @@
+VUE_APP_TITLE = AI-Hub智能处理平台
 # 开发环境配置
 ENV = 'development'
-
 # 潜山市云数据中心/开发环境/dev-api /prod-api
-VUE_APP_BASE_API = '/prod-api'
+VUE_APP_BASE_API = '/dev-api'
 
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true

+ 1 - 0
.env.production

@@ -1,3 +1,4 @@
+VUE_APP_TITLE = AI-Hub智能处理平台
 # 生产环境配置
 ENV = 'production'
 

+ 216 - 81
package-lock.json

@@ -8,9 +8,10 @@
       "name": "base-project",
       "version": "0.1.0",
       "dependencies": {
+        "@amap/amap-jsapi-loader": "^1.0.1",
         "axios": "^0.21.1",
         "core-js": "^3.6.5",
-        "echarts": "^5.3.3",
+        "echarts": "^5.4.0",
         "element-ui": "^2.14.1",
         "js-cookie": "3.0.1",
         "jsencrypt": "3.0.0-rc.1",
@@ -28,6 +29,8 @@
         "babel-plugin-component": "^1.1.1",
         "less": "^3.0.4",
         "less-loader": "^5.0.0",
+        "sass": "1.32.13",
+        "sass-loader": "10.1.1",
         "vue-template-compiler": "^2.6.11"
       }
     },
@@ -45,6 +48,11 @@
         "node": "8 || 10 || 12 || 14 || 16 || 17"
       }
     },
+    "node_modules/@amap/amap-jsapi-loader": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@amap/amap-jsapi-loader/-/amap-jsapi-loader-1.0.1.tgz",
+      "integrity": "sha512-nPyLKt7Ow/ThHLkSvn2etQlUzqxmTVgK7bIgwdBRTg2HK5668oN7xVxkaiRe3YZEzGzfV2XgH5Jmu2T73ljejw=="
+    },
     "node_modules/@ampproject/remapping": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -73,9 +81,9 @@
       }
     },
     "node_modules/@babel/compat-data": {
-      "version": "7.27.1",
-      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.1.tgz",
-      "integrity": "sha512-Q+E+rd/yBzNQhXkG+zQnF58e4zoZfBedaxwzPmicKsiK3nt8iJYrSrDbjwFFDGC4f+rPafqRaPH6TsDoSvMf7A==",
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz",
+      "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
@@ -140,12 +148,12 @@
       }
     },
     "node_modules/@babel/helper-compilation-targets": {
-      "version": "7.27.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.1.tgz",
-      "integrity": "sha512-2YaDd/Rd9E598B5+WIc8wJPmWETiiJXFYVE60oX8FDohv7rAUU3CQj+A1MgeEmcsk2+dQuEjIe/GDvig0SqL4g==",
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+      "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
       "dev": true,
       "dependencies": {
-        "@babel/compat-data": "^7.27.1",
+        "@babel/compat-data": "^7.27.2",
         "@babel/helper-validator-option": "^7.27.1",
         "browserslist": "^4.24.0",
         "lru-cache": "^5.1.1",
@@ -373,9 +381,9 @@
       }
     },
     "node_modules/@babel/parser": {
-      "version": "7.27.1",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.1.tgz",
-      "integrity": "sha512-I0dZ3ZpCrJ1c04OqlNsQcKiZlsrXf/kkE4FXzID9rIOYICsAbA8mMDzhW/luRNAHdCNt7os/u8wenklZDlUVUQ==",
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz",
+      "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
       "dependencies": {
         "@babel/types": "^7.27.1"
       },
@@ -1074,13 +1082,14 @@
       }
     },
     "node_modules/@babel/plugin-transform-object-rest-spread": {
-      "version": "7.27.1",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.1.tgz",
-      "integrity": "sha512-/sSliVc9gHE20/7D5qsdGlq7RG5NCDTWsAhyqzGuq174EtWJoGzIu1BQ7G56eDsTcy1jseBZwv50olSdXOlGuA==",
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.2.tgz",
+      "integrity": "sha512-AIUHD7xJ1mCrj3uPozvtngY3s0xpv7Nu7DoUSnzNY6Xam1Cy4rUznR//pvMHOhQ4AvbCexhbqXCtpxGHOGOO6g==",
       "dev": true,
       "dependencies": {
-        "@babel/helper-compilation-targets": "^7.27.1",
+        "@babel/helper-compilation-targets": "^7.27.2",
         "@babel/helper-plugin-utils": "^7.27.1",
+        "@babel/plugin-transform-destructuring": "^7.27.1",
         "@babel/plugin-transform-parameters": "^7.27.1"
       },
       "engines": {
@@ -1406,13 +1415,13 @@
       }
     },
     "node_modules/@babel/preset-env": {
-      "version": "7.27.1",
-      "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.1.tgz",
-      "integrity": "sha512-TZ5USxFpLgKDpdEt8YWBR7p6g+bZo6sHaXLqP2BY/U0acaoI8FTVflcYCr/v94twM1C5IWFdZ/hscq9WjUeLXA==",
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.2.tgz",
+      "integrity": "sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==",
       "dev": true,
       "dependencies": {
-        "@babel/compat-data": "^7.27.1",
-        "@babel/helper-compilation-targets": "^7.27.1",
+        "@babel/compat-data": "^7.27.2",
+        "@babel/helper-compilation-targets": "^7.27.2",
         "@babel/helper-plugin-utils": "^7.27.1",
         "@babel/helper-validator-option": "^7.27.1",
         "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1",
@@ -1454,7 +1463,7 @@
         "@babel/plugin-transform-new-target": "^7.27.1",
         "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
         "@babel/plugin-transform-numeric-separator": "^7.27.1",
-        "@babel/plugin-transform-object-rest-spread": "^7.27.1",
+        "@babel/plugin-transform-object-rest-spread": "^7.27.2",
         "@babel/plugin-transform-object-super": "^7.27.1",
         "@babel/plugin-transform-optional-catch-binding": "^7.27.1",
         "@babel/plugin-transform-optional-chaining": "^7.27.1",
@@ -1512,13 +1521,13 @@
       }
     },
     "node_modules/@babel/template": {
-      "version": "7.27.1",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.1.tgz",
-      "integrity": "sha512-Fyo3ghWMqkHHpHQCoBs2VnYjR4iWFFjguTDEqA5WgZDOrFesVjMhMM2FSqTKSoUSDO1VQtavj8NFpdRBEvJTtg==",
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+      "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
       "dev": true,
       "dependencies": {
         "@babel/code-frame": "^7.27.1",
-        "@babel/parser": "^7.27.1",
+        "@babel/parser": "^7.27.2",
         "@babel/types": "^7.27.1"
       },
       "engines": {
@@ -1821,9 +1830,9 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "22.15.11",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.11.tgz",
-      "integrity": "sha512-rlyK0vuU7VLEYQfXuC7QTFxDvkb6tKhDI7wR4r6ZzM0k8BJd44W0jxo6xmUjqSs4AlYmiYfLJU2f0pAG/FtCRw==",
+      "version": "22.15.16",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.16.tgz",
+      "integrity": "sha512-3pr+KjwpVujqWqOKT8mNR+rd09FqhBLwg+5L/4t0cNYBzm/yEiYGCxWttjaPBsLtAo+WFNoXzGJfolM1JuRXoA==",
       "dev": true,
       "dependencies": {
         "undici-types": "~6.21.0"
@@ -3481,7 +3490,6 @@
       "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
       "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
       "dev": true,
-      "optional": true,
       "engines": {
         "node": ">=8"
       },
@@ -4126,7 +4134,6 @@
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
       "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
       "dev": true,
-      "optional": true,
       "dependencies": {
         "anymatch": "~3.1.2",
         "braces": "~3.0.2",
@@ -4151,7 +4158,6 @@
       "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
       "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
       "dev": true,
-      "optional": true,
       "dependencies": {
         "fill-range": "^7.1.1"
       },
@@ -4164,7 +4170,6 @@
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
       "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
       "dev": true,
-      "optional": true,
       "dependencies": {
         "to-regex-range": "^5.0.1"
       },
@@ -4177,7 +4182,6 @@
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
       "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
       "dev": true,
-      "optional": true,
       "dependencies": {
         "is-glob": "^4.0.1"
       },
@@ -4190,7 +4194,6 @@
       "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
       "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
       "dev": true,
-      "optional": true,
       "engines": {
         "node": ">=0.12.0"
       }
@@ -4200,7 +4203,6 @@
       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
       "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
       "dev": true,
-      "optional": true,
       "dependencies": {
         "is-number": "^7.0.0"
       },
@@ -6111,9 +6113,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.5.150",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.150.tgz",
-      "integrity": "sha512-rOOkP2ZUMx1yL4fCxXQKDHQ8ZXwisb2OycOQVKHgvB3ZI4CvehOd4y2tfnnLDieJ3Zs1RL1Dlp3cMkyIn7nnXA==",
+      "version": "1.5.151",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz",
+      "integrity": "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==",
       "dev": true
     },
     "node_modules/element-ui": {
@@ -8370,7 +8372,6 @@
       "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
       "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
       "dev": true,
-      "optional": true,
       "dependencies": {
         "binary-extensions": "^2.0.0"
       },
@@ -9073,6 +9074,15 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/klona": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz",
+      "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
     "node_modules/launch-editor": {
       "version": "2.10.0",
       "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz",
@@ -11805,7 +11815,6 @@
       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
       "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
       "dev": true,
-      "optional": true,
       "dependencies": {
         "picomatch": "^2.2.1"
       },
@@ -12271,6 +12280,88 @@
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
       "dev": true
     },
+    "node_modules/sass": {
+      "version": "1.32.13",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.13.tgz",
+      "integrity": "sha512-dEgI9nShraqP7cXQH+lEXVf73WOPCse0QlFzSD8k+1TcOxCMwVXfQlr0jtoluZysQOyJGnfr21dLvYKDJq8HkA==",
+      "dev": true,
+      "dependencies": {
+        "chokidar": ">=3.0.0 <4.0.0"
+      },
+      "bin": {
+        "sass": "sass.js"
+      },
+      "engines": {
+        "node": ">=8.9.0"
+      }
+    },
+    "node_modules/sass-loader": {
+      "version": "10.1.1",
+      "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.1.1.tgz",
+      "integrity": "sha512-W6gVDXAd5hR/WHsPicvZdjAWHBcEJ44UahgxcIE196fW2ong0ZHMPO1kZuI5q0VlvMQZh32gpv69PLWQm70qrw==",
+      "dev": true,
+      "dependencies": {
+        "klona": "^2.0.4",
+        "loader-utils": "^2.0.0",
+        "neo-async": "^2.6.2",
+        "schema-utils": "^3.0.0",
+        "semver": "^7.3.2"
+      },
+      "engines": {
+        "node": ">= 10.13.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      },
+      "peerDependencies": {
+        "fibers": ">= 3.1.0",
+        "node-sass": "^4.0.0 || ^5.0.0",
+        "sass": "^1.3.0",
+        "webpack": "^4.36.0 || ^5.0.0"
+      },
+      "peerDependenciesMeta": {
+        "fibers": {
+          "optional": true
+        },
+        "node-sass": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/sass-loader/node_modules/schema-utils": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+      "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+      "dev": true,
+      "dependencies": {
+        "@types/json-schema": "^7.0.8",
+        "ajv": "^6.12.5",
+        "ajv-keywords": "^3.5.2"
+      },
+      "engines": {
+        "node": ">= 10.13.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      }
+    },
+    "node_modules/sass-loader/node_modules/semver": {
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
+      "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/sax": {
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
@@ -15890,6 +15981,11 @@
         "js-message": "1.0.7"
       }
     },
+    "@amap/amap-jsapi-loader": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@amap/amap-jsapi-loader/-/amap-jsapi-loader-1.0.1.tgz",
+      "integrity": "sha512-nPyLKt7Ow/ThHLkSvn2etQlUzqxmTVgK7bIgwdBRTg2HK5668oN7xVxkaiRe3YZEzGzfV2XgH5Jmu2T73ljejw=="
+    },
     "@ampproject/remapping": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -15912,9 +16008,9 @@
       }
     },
     "@babel/compat-data": {
-      "version": "7.27.1",
-      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.1.tgz",
-      "integrity": "sha512-Q+E+rd/yBzNQhXkG+zQnF58e4zoZfBedaxwzPmicKsiK3nt8iJYrSrDbjwFFDGC4f+rPafqRaPH6TsDoSvMf7A==",
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz",
+      "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==",
       "dev": true
     },
     "@babel/core": {
@@ -15963,12 +16059,12 @@
       }
     },
     "@babel/helper-compilation-targets": {
-      "version": "7.27.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.1.tgz",
-      "integrity": "sha512-2YaDd/Rd9E598B5+WIc8wJPmWETiiJXFYVE60oX8FDohv7rAUU3CQj+A1MgeEmcsk2+dQuEjIe/GDvig0SqL4g==",
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+      "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
       "dev": true,
       "requires": {
-        "@babel/compat-data": "^7.27.1",
+        "@babel/compat-data": "^7.27.2",
         "@babel/helper-validator-option": "^7.27.1",
         "browserslist": "^4.24.0",
         "lru-cache": "^5.1.1",
@@ -16130,9 +16226,9 @@
       }
     },
     "@babel/parser": {
-      "version": "7.27.1",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.1.tgz",
-      "integrity": "sha512-I0dZ3ZpCrJ1c04OqlNsQcKiZlsrXf/kkE4FXzID9rIOYICsAbA8mMDzhW/luRNAHdCNt7os/u8wenklZDlUVUQ==",
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz",
+      "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
       "requires": {
         "@babel/types": "^7.27.1"
       }
@@ -16564,13 +16660,14 @@
       }
     },
     "@babel/plugin-transform-object-rest-spread": {
-      "version": "7.27.1",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.1.tgz",
-      "integrity": "sha512-/sSliVc9gHE20/7D5qsdGlq7RG5NCDTWsAhyqzGuq174EtWJoGzIu1BQ7G56eDsTcy1jseBZwv50olSdXOlGuA==",
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.2.tgz",
+      "integrity": "sha512-AIUHD7xJ1mCrj3uPozvtngY3s0xpv7Nu7DoUSnzNY6Xam1Cy4rUznR//pvMHOhQ4AvbCexhbqXCtpxGHOGOO6g==",
       "dev": true,
       "requires": {
-        "@babel/helper-compilation-targets": "^7.27.1",
+        "@babel/helper-compilation-targets": "^7.27.2",
         "@babel/helper-plugin-utils": "^7.27.1",
+        "@babel/plugin-transform-destructuring": "^7.27.1",
         "@babel/plugin-transform-parameters": "^7.27.1"
       }
     },
@@ -16770,13 +16867,13 @@
       }
     },
     "@babel/preset-env": {
-      "version": "7.27.1",
-      "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.1.tgz",
-      "integrity": "sha512-TZ5USxFpLgKDpdEt8YWBR7p6g+bZo6sHaXLqP2BY/U0acaoI8FTVflcYCr/v94twM1C5IWFdZ/hscq9WjUeLXA==",
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.2.tgz",
+      "integrity": "sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==",
       "dev": true,
       "requires": {
-        "@babel/compat-data": "^7.27.1",
-        "@babel/helper-compilation-targets": "^7.27.1",
+        "@babel/compat-data": "^7.27.2",
+        "@babel/helper-compilation-targets": "^7.27.2",
         "@babel/helper-plugin-utils": "^7.27.1",
         "@babel/helper-validator-option": "^7.27.1",
         "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1",
@@ -16818,7 +16915,7 @@
         "@babel/plugin-transform-new-target": "^7.27.1",
         "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
         "@babel/plugin-transform-numeric-separator": "^7.27.1",
-        "@babel/plugin-transform-object-rest-spread": "^7.27.1",
+        "@babel/plugin-transform-object-rest-spread": "^7.27.2",
         "@babel/plugin-transform-object-super": "^7.27.1",
         "@babel/plugin-transform-optional-catch-binding": "^7.27.1",
         "@babel/plugin-transform-optional-chaining": "^7.27.1",
@@ -16864,13 +16961,13 @@
       "dev": true
     },
     "@babel/template": {
-      "version": "7.27.1",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.1.tgz",
-      "integrity": "sha512-Fyo3ghWMqkHHpHQCoBs2VnYjR4iWFFjguTDEqA5WgZDOrFesVjMhMM2FSqTKSoUSDO1VQtavj8NFpdRBEvJTtg==",
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+      "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
       "dev": true,
       "requires": {
         "@babel/code-frame": "^7.27.1",
-        "@babel/parser": "^7.27.1",
+        "@babel/parser": "^7.27.2",
         "@babel/types": "^7.27.1"
       }
     },
@@ -17132,9 +17229,9 @@
       "dev": true
     },
     "@types/node": {
-      "version": "22.15.11",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.11.tgz",
-      "integrity": "sha512-rlyK0vuU7VLEYQfXuC7QTFxDvkb6tKhDI7wR4r6ZzM0k8BJd44W0jxo6xmUjqSs4AlYmiYfLJU2f0pAG/FtCRw==",
+      "version": "22.15.16",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.16.tgz",
+      "integrity": "sha512-3pr+KjwpVujqWqOKT8mNR+rd09FqhBLwg+5L/4t0cNYBzm/yEiYGCxWttjaPBsLtAo+WFNoXzGJfolM1JuRXoA==",
       "dev": true,
       "requires": {
         "undici-types": "~6.21.0"
@@ -18477,8 +18574,7 @@
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
       "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
-      "dev": true,
-      "optional": true
+      "dev": true
     },
     "bindings": {
       "version": "1.5.0",
@@ -18993,7 +19089,6 @@
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
       "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
       "dev": true,
-      "optional": true,
       "requires": {
         "anymatch": "~3.1.2",
         "braces": "~3.0.2",
@@ -19010,7 +19105,6 @@
           "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
           "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
           "dev": true,
-          "optional": true,
           "requires": {
             "fill-range": "^7.1.1"
           }
@@ -19020,7 +19114,6 @@
           "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
           "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
           "dev": true,
-          "optional": true,
           "requires": {
             "to-regex-range": "^5.0.1"
           }
@@ -19030,7 +19123,6 @@
           "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
           "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
           "dev": true,
-          "optional": true,
           "requires": {
             "is-glob": "^4.0.1"
           }
@@ -19039,15 +19131,13 @@
           "version": "7.0.0",
           "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
           "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "to-regex-range": {
           "version": "5.0.1",
           "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
           "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
           "dev": true,
-          "optional": true,
           "requires": {
             "is-number": "^7.0.0"
           }
@@ -20527,9 +20617,9 @@
       "dev": true
     },
     "electron-to-chromium": {
-      "version": "1.5.150",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.150.tgz",
-      "integrity": "sha512-rOOkP2ZUMx1yL4fCxXQKDHQ8ZXwisb2OycOQVKHgvB3ZI4CvehOd4y2tfnnLDieJ3Zs1RL1Dlp3cMkyIn7nnXA==",
+      "version": "1.5.151",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz",
+      "integrity": "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==",
       "dev": true
     },
     "element-ui": {
@@ -22259,7 +22349,6 @@
       "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
       "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
       "dev": true,
-      "optional": true,
       "requires": {
         "binary-extensions": "^2.0.0"
       }
@@ -22757,6 +22846,12 @@
       "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
       "dev": true
     },
+    "klona": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz",
+      "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==",
+      "dev": true
+    },
     "launch-editor": {
       "version": "2.10.0",
       "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz",
@@ -25004,7 +25099,6 @@
       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
       "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
       "dev": true,
-      "optional": true,
       "requires": {
         "picomatch": "^2.2.1"
       }
@@ -25376,6 +25470,47 @@
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
       "dev": true
     },
+    "sass": {
+      "version": "1.32.13",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.13.tgz",
+      "integrity": "sha512-dEgI9nShraqP7cXQH+lEXVf73WOPCse0QlFzSD8k+1TcOxCMwVXfQlr0jtoluZysQOyJGnfr21dLvYKDJq8HkA==",
+      "dev": true,
+      "requires": {
+        "chokidar": ">=3.0.0 <4.0.0"
+      }
+    },
+    "sass-loader": {
+      "version": "10.1.1",
+      "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.1.1.tgz",
+      "integrity": "sha512-W6gVDXAd5hR/WHsPicvZdjAWHBcEJ44UahgxcIE196fW2ong0ZHMPO1kZuI5q0VlvMQZh32gpv69PLWQm70qrw==",
+      "dev": true,
+      "requires": {
+        "klona": "^2.0.4",
+        "loader-utils": "^2.0.0",
+        "neo-async": "^2.6.2",
+        "schema-utils": "^3.0.0",
+        "semver": "^7.3.2"
+      },
+      "dependencies": {
+        "schema-utils": {
+          "version": "3.3.0",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+          "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+          "dev": true,
+          "requires": {
+            "@types/json-schema": "^7.0.8",
+            "ajv": "^6.12.5",
+            "ajv-keywords": "^3.5.2"
+          }
+        },
+        "semver": {
+          "version": "7.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
+          "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+          "dev": true
+        }
+      }
+    },
     "sax": {
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",

+ 6 - 3
package.json

@@ -7,13 +7,14 @@
     "build": "vue-cli-service build --report"
   },
   "dependencies": {
+    "@amap/amap-jsapi-loader": "^1.0.1",
     "axios": "^0.21.1",
     "core-js": "^3.6.5",
-    "echarts": "^5.3.3",
+    "echarts": "^5.4.0",
     "element-ui": "^2.14.1",
+    "js-cookie": "3.0.1",
+    "jsencrypt": "3.0.0-rc.1",
     "nprogress": "^0.2.0",
-	"js-cookie": "3.0.1",
-	"jsencrypt": "3.0.0-rc.1",
     "vue": "^2.6.11",
     "vue-router": "^3.2.0",
     "vue-seamless-scroll": "^1.1.23",
@@ -27,6 +28,8 @@
     "babel-plugin-component": "^1.1.1",
     "less": "^3.0.4",
     "less-loader": "^5.0.0",
+    "sass": "1.32.13",
+    "sass-loader": "10.1.1",
     "vue-template-compiler": "^2.6.11"
   }
 }

+ 200 - 17
public/index.html

@@ -1,25 +1,208 @@
 <!DOCTYPE html>
-<html lang="en">
+<html>
   <head>
     <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="renderer" content="webkit">
+    <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></title>
-  
+    <title><%= webpackConfig.name %></title>
+    <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
+	  <style>
+    html,
+    body,
+    #app {
+      height: 100%;
+      margin: 0px;
+      padding: 0px;
+    }
+    .chromeframe {
+      margin: 0.2em 0;
+      background: #ccc;
+      color: #000;
+      padding: 0.2em 0;
+    }
+
+    #loader-wrapper {
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      z-index: 999999;
+    }
+
+    #loader {
+      display: block;
+      position: relative;
+      left: 50%;
+      top: 50%;
+      width: 150px;
+      height: 150px;
+      margin: -75px 0 0 -75px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      border-top-color: #FFF;
+      -webkit-animation: spin 2s linear infinite;
+      -ms-animation: spin 2s linear infinite;
+      -moz-animation: spin 2s linear infinite;
+      -o-animation: spin 2s linear infinite;
+      animation: spin 2s linear infinite;
+      z-index: 1001;
+    }
+
+    #loader:before {
+      content: "";
+      position: absolute;
+      top: 5px;
+      left: 5px;
+      right: 5px;
+      bottom: 5px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      border-top-color: #FFF;
+      -webkit-animation: spin 3s linear infinite;
+      -moz-animation: spin 3s linear infinite;
+      -o-animation: spin 3s linear infinite;
+      -ms-animation: spin 3s linear infinite;
+      animation: spin 3s linear infinite;
+    }
+
+    #loader:after {
+      content: "";
+      position: absolute;
+      top: 15px;
+      left: 15px;
+      right: 15px;
+      bottom: 15px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      border-top-color: #FFF;
+      -moz-animation: spin 1.5s linear infinite;
+      -o-animation: spin 1.5s linear infinite;
+      -ms-animation: spin 1.5s linear infinite;
+      -webkit-animation: spin 1.5s linear infinite;
+      animation: spin 1.5s linear infinite;
+    }
+
+
+    @-webkit-keyframes spin {
+      0% {
+        -webkit-transform: rotate(0deg);
+        -ms-transform: rotate(0deg);
+        transform: rotate(0deg);
+      }
+      100% {
+        -webkit-transform: rotate(360deg);
+        -ms-transform: rotate(360deg);
+        transform: rotate(360deg);
+      }
+    }
+
+    @keyframes spin {
+      0% {
+        -webkit-transform: rotate(0deg);
+        -ms-transform: rotate(0deg);
+        transform: rotate(0deg);
+      }
+      100% {
+        -webkit-transform: rotate(360deg);
+        -ms-transform: rotate(360deg);
+        transform: rotate(360deg);
+      }
+    }
+
+
+    #loader-wrapper .loader-section {
+      position: fixed;
+      top: 0;
+      width: 51%;
+      height: 100%;
+      background: #7171C6;
+      z-index: 1000;
+      -webkit-transform: translateX(0);
+      -ms-transform: translateX(0);
+      transform: translateX(0);
+    }
+
+    #loader-wrapper .loader-section.section-left {
+      left: 0;
+    }
+
+    #loader-wrapper .loader-section.section-right {
+      right: 0;
+    }
+
+
+    .loaded #loader-wrapper .loader-section.section-left {
+      -webkit-transform: translateX(-100%);
+      -ms-transform: translateX(-100%);
+      transform: translateX(-100%);
+      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+    }
+
+    .loaded #loader-wrapper .loader-section.section-right {
+      -webkit-transform: translateX(100%);
+      -ms-transform: translateX(100%);
+      transform: translateX(100%);
+      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+    }
+
+    .loaded #loader {
+      opacity: 0;
+      -webkit-transition: all 0.3s ease-out;
+      transition: all 0.3s ease-out;
+    }
+
+    .loaded #loader-wrapper {
+      visibility: hidden;
+      -webkit-transform: translateY(-100%);
+      -ms-transform: translateY(-100%);
+      transform: translateY(-100%);
+      -webkit-transition: all 0.3s 1s ease-out;
+      transition: all 0.3s 1s ease-out;
+    }
+
+    .no-js #loader-wrapper {
+      display: none;
+    }
+
+    .no-js h1 {
+      color: #222222;
+    }
+
+    #loader-wrapper .load_title {
+      font-family: 'Open Sans';
+      color: #FFF;
+      font-size: 19px;
+      width: 100%;
+      text-align: center;
+      z-index: 9999999999999;
+      position: absolute;
+      top: 60%;
+      opacity: 1;
+      line-height: 30px;
+    }
+
+    #loader-wrapper .load_title span {
+      font-weight: normal;
+      font-style: italic;
+      font-size: 13px;
+      color: #FFF;
+      opacity: 0.5;
+    }
+  </style>
   </head>
   <body>
-    <noscript>
-      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
-    </noscript>
-    <div id="app"></div>
-    <!-- built files will be auto injected -->
-    <!-- <script>
-     window.oncontextmenu = function (e) {
-        //取消默认的浏览器自带右键 很重要!!
-        e.preventDefault();
-      }
-    </script> -->
+    <div id="app">
+	    <div id="loader-wrapper">
+		    <div id="loader"></div>
+		    <div class="loader-section section-left"></div>
+		    <div class="loader-section section-right"></div>
+		    <div class="load_title">正在加载系统资源,请耐心等待</div>
+        </div>
+	</div>
   </body>
-  
 </html>

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
public/styles/theme-chalk/index.css


+ 60 - 0
src/api/login.js

@@ -0,0 +1,60 @@
+import request from '@/utils/request'
+
+// 登录方法
+export function login(username, password, code, uuid) {
+  const data = {
+    username,
+    password,
+    code,
+    uuid
+  }
+  return request({
+    url: '/login',
+    headers: {
+      isToken: false,
+      repeatSubmit: false
+    },
+    method: 'post',
+    data: data
+  })
+}
+
+// 注册方法
+export function register(data) {
+  return request({
+    url: '/register',
+    headers: {
+      isToken: false
+    },
+    method: 'post',
+    data: data
+  })
+}
+
+// 获取用户详细信息
+export function getInfo() {
+  return request({
+    url: '/getInfo',
+    method: 'get'
+  })
+}
+
+// 退出方法
+export function logout() {
+  return request({
+    url: '/logout',
+    method: 'post'
+  })
+}
+
+// 获取验证码
+export function getCodeImg() {
+  return request({
+    url: '/captchaImage',
+    headers: {
+      isToken: false
+    },
+    method: 'get',
+    timeout: 20000
+  })
+}

+ 9 - 0
src/api/menu.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+// 获取路由
+export const getRouters = () => {
+  return request({
+    url: '/getRouters',
+    method: 'get'
+  })
+}

+ 52 - 0
src/api/system/dict/data.js

@@ -0,0 +1,52 @@
+import request from '@/utils/request'
+
+// 查询字典数据列表
+export function listData(query) {
+  return request({
+    url: '/system/dict/data/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询字典数据详细
+export function getData(dictCode) {
+  return request({
+    url: '/system/dict/data/' + dictCode,
+    method: 'get'
+  })
+}
+
+// 根据字典类型查询字典数据信息
+export function getDicts(dictType) {
+  return request({
+    url: '/system/dict/data/type/' + dictType,
+    method: 'get'
+  })
+}
+
+// 新增字典数据
+export function addData(data) {
+  return request({
+    url: '/system/dict/data',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改字典数据
+export function updateData(data) {
+  return request({
+    url: '/system/dict/data',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除字典数据
+export function delData(dictCode) {
+  return request({
+    url: '/system/dict/data/' + dictCode,
+    method: 'delete'
+  })
+}

+ 60 - 0
src/api/system/dict/type.js

@@ -0,0 +1,60 @@
+import request from '@/utils/request'
+
+// 查询字典类型列表
+export function listType(query) {
+  return request({
+    url: '/system/dict/type/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询字典类型详细
+export function getType(dictId) {
+  return request({
+    url: '/system/dict/type/' + dictId,
+    method: 'get'
+  })
+}
+
+// 新增字典类型
+export function addType(data) {
+  return request({
+    url: '/system/dict/type',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改字典类型
+export function updateType(data) {
+  return request({
+    url: '/system/dict/type',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除字典类型
+export function delType(dictId) {
+  return request({
+    url: '/system/dict/type/' + dictId,
+    method: 'delete'
+  })
+}
+
+// 刷新字典缓存
+export function refreshCache() {
+  return request({
+    url: '/system/dict/type/refreshCache',
+    method: 'delete'
+  })
+}
+
+// 获取字典选择框列表
+export function optionselect() {
+  return request({
+    url: '/system/dict/type/optionselect',
+    method: 'get'
+  })
+}

BIN
src/assets/images/boxbg.png


BIN
src/assets/images/cenbg.png


BIN
src/assets/images/centbg.png


BIN
src/assets/images/down.png


BIN
src/assets/images/fixedbg.png


BIN
src/assets/images/gjicoa.png


BIN
src/assets/images/gjicob.png


BIN
src/assets/images/head.png


BIN
src/assets/images/header-bg.png


BIN
src/assets/images/header.png


BIN
src/assets/images/headertit.png


BIN
src/assets/images/icon_htdp_xl.png


BIN
src/assets/images/icon_htdp_zxt_tl1.png


BIN
src/assets/images/icon_htdp_zxt_tl2.png


BIN
src/assets/images/icon_htgl_hxl.png


BIN
src/assets/images/jzicoa.png


BIN
src/assets/images/jzicob.png


BIN
src/assets/images/jzicoc.png


BIN
src/assets/images/jzicod.png


BIN
src/assets/images/jzicoe.png


BIN
src/assets/images/jziconf.png


BIN
src/assets/images/line.png


BIN
src/assets/images/login_pwd.png


BIN
src/assets/images/login_tit.png


BIN
src/assets/images/loginbg.png


BIN
src/assets/images/logintit.png


BIN
src/assets/images/ltaba.png


BIN
src/assets/images/ltabb.png


BIN
src/assets/images/ltabc.png


BIN
src/assets/images/ltabd.png


BIN
src/assets/images/profile.jpg


BIN
src/assets/images/tip.png


BIN
src/assets/images/up.png


BIN
src/assets/images/zcicoa.png


BIN
src/assets/images/zcicob.png


+ 99 - 0
src/assets/styles/btn.scss

@@ -0,0 +1,99 @@
+@import './variables.scss';
+
+@mixin colorBtn($color) {
+  background: $color;
+
+  &:hover {
+    color: $color;
+
+    &:before,
+    &:after {
+      background: $color;
+    }
+  }
+}
+
+.blue-btn {
+  @include colorBtn($blue)
+}
+
+.light-blue-btn {
+  @include colorBtn($light-blue)
+}
+
+.red-btn {
+  @include colorBtn($red)
+}
+
+.pink-btn {
+  @include colorBtn($pink)
+}
+
+.green-btn {
+  @include colorBtn($green)
+}
+
+.tiffany-btn {
+  @include colorBtn($tiffany)
+}
+
+.yellow-btn {
+  @include colorBtn($yellow)
+}
+
+.pan-btn {
+  font-size: 14px;
+  color: #fff;
+  padding: 14px 36px;
+  border-radius: 8px;
+  border: none;
+  outline: none;
+  transition: 600ms ease all;
+  position: relative;
+  display: inline-block;
+
+  &:hover {
+    background: #fff;
+
+    &:before,
+    &:after {
+      width: 100%;
+      transition: 600ms ease all;
+    }
+  }
+
+  &:before,
+  &:after {
+    content: '';
+    position: absolute;
+    top: 0;
+    right: 0;
+    height: 2px;
+    width: 0;
+    transition: 400ms ease all;
+  }
+
+  &::after {
+    right: inherit;
+    top: inherit;
+    left: 0;
+    bottom: 0;
+  }
+}
+
+.custom-button {
+  display: inline-block;
+  line-height: 1;
+  white-space: nowrap;
+  cursor: pointer;
+  background: #fff;
+  color: #fff;
+  -webkit-appearance: none;
+  text-align: center;
+  box-sizing: border-box;
+  outline: 0;
+  margin: 0;
+  padding: 10px 15px;
+  font-size: 14px;
+  border-radius: 4px;
+}

+ 92 - 0
src/assets/styles/element-ui.scss

@@ -0,0 +1,92 @@
+// cover some element-ui styles
+
+.el-breadcrumb__inner,
+.el-breadcrumb__inner a {
+  font-weight: 400 !important;
+}
+
+.el-upload {
+  input[type="file"] {
+    display: none !important;
+  }
+}
+
+.el-upload__input {
+  display: none;
+}
+
+.cell {
+  .el-tag {
+    margin-right: 0px;
+  }
+}
+
+.small-padding {
+  .cell {
+    padding-left: 5px;
+    padding-right: 5px;
+  }
+}
+
+.fixed-width {
+  .el-button--mini {
+    padding: 7px 10px;
+    width: 60px;
+  }
+}
+
+.status-col {
+  .cell {
+    padding: 0 10px;
+    text-align: center;
+
+    .el-tag {
+      margin-right: 0px;
+    }
+  }
+}
+
+// to fixed https://github.com/ElemeFE/element/issues/2461
+.el-dialog {
+  transform: none;
+  left: 0;
+  position: relative;
+  margin: 0 auto;
+}
+
+// refine element ui upload
+.upload-container {
+  .el-upload {
+    width: 100%;
+
+    .el-upload-dragger {
+      width: 100%;
+      height: 200px;
+    }
+  }
+}
+
+// dropdown
+.el-dropdown-menu {
+  a {
+    display: block
+  }
+}
+
+// fix date-picker ui bug in filter-item
+.el-range-editor.el-input__inner {
+  display: inline-flex !important;
+}
+
+// to fix el-date-picker css style
+.el-range-separator {
+  box-sizing: content-box;
+}
+
+.el-menu--collapse
+  > div
+  > .el-submenu
+  > .el-submenu__title
+  .el-submenu__icon-arrow {
+  display: none;
+}

+ 31 - 0
src/assets/styles/element-variables.scss

@@ -0,0 +1,31 @@
+/**
+* I think element-ui's default theme color is too light for long-term use.
+* So I modified the default color and you can modify it to your liking.
+**/
+
+/* theme color */
+$--color-primary: #1890ff;
+$--color-success: #13ce66;
+$--color-warning: #ffba00;
+$--color-danger: #ff4949;
+// $--color-info: #1E1E1E;
+
+$--button-font-weight: 400;
+
+// $--color-text-regular: #1f2d3d;
+
+$--border-color-light: #dfe4ed;
+$--border-color-lighter: #e6ebf5;
+
+$--table-border: 1px solid #dfe6ec;
+
+/* icon font path, required */
+$--font-path: '~element-ui/lib/theme-chalk/fonts';
+
+@import "~element-ui/packages/theme-chalk/src/index";
+
+// the :export directive is the magic sauce for webpack
+// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
+:export {
+  theme: $--color-primary;
+}

+ 178 - 0
src/assets/styles/index.scss

@@ -0,0 +1,178 @@
+@import './variables.scss';
+@import './mixin.scss';
+@import './transition.scss';
+@import './element-ui.scss';
+@import './sidebar.scss';
+@import './btn.scss';
+
+body {
+  height: 100%;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-font-smoothing: antialiased;
+  text-rendering: optimizeLegibility;
+  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
+}
+
+label {
+  font-weight: 700;
+}
+
+html {
+  height: 100%;
+  box-sizing: border-box;
+}
+
+#app {
+  height: 100%;
+}
+
+*,
+*:before,
+*:after {
+  box-sizing: inherit;
+}
+
+.no-padding {
+  padding: 0px !important;
+}
+
+.padding-content {
+  padding: 4px 0;
+}
+
+a:focus,
+a:active {
+  outline: none;
+}
+
+a,
+a:focus,
+a:hover {
+  cursor: pointer;
+  color: inherit;
+  text-decoration: none;
+}
+
+div:focus {
+  outline: none;
+}
+
+.fr {
+  float: right;
+}
+
+.fl {
+  float: left;
+}
+
+.pr-5 {
+  padding-right: 5px;
+}
+
+.pl-5 {
+  padding-left: 5px;
+}
+
+.block {
+  display: block;
+}
+
+.pointer {
+  cursor: pointer;
+}
+
+.inlineBlock {
+  display: block;
+}
+
+.clearfix {
+  &:after {
+    visibility: hidden;
+    display: block;
+    font-size: 0;
+    content: " ";
+    clear: both;
+    height: 0;
+  }
+}
+
+aside {
+  background: #eef1f6;
+  padding: 8px 24px;
+  margin-bottom: 20px;
+  border-radius: 2px;
+  display: block;
+  line-height: 32px;
+  font-size: 16px;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
+  color: #2c3e50;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+
+  a {
+    color: #337ab7;
+    cursor: pointer;
+
+    &:hover {
+      color: rgb(32, 160, 255);
+    }
+  }
+}
+
+//main-container全局样式
+.app-container {
+  padding: 20px;
+}
+
+.components-container {
+  margin: 30px 50px;
+  position: relative;
+}
+
+.text-center {
+  text-align: center
+}
+
+.sub-navbar {
+  height: 50px;
+  line-height: 50px;
+  position: relative;
+  width: 100%;
+  text-align: right;
+  padding-right: 20px;
+  transition: 600ms ease position;
+  background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
+
+  .subtitle {
+    font-size: 20px;
+    color: #fff;
+  }
+
+  &.draft {
+    background: #d0d0d0;
+  }
+
+  &.deleted {
+    background: #d0d0d0;
+  }
+}
+
+.link-type,
+.link-type:focus {
+  color: #337ab7;
+  cursor: pointer;
+
+  &:hover {
+    color: rgb(32, 160, 255);
+  }
+}
+
+.filter-container {
+  padding-bottom: 10px;
+
+  .filter-item {
+    display: inline-block;
+    vertical-align: middle;
+    margin-bottom: 10px;
+  }
+}

+ 66 - 0
src/assets/styles/mixin.scss

@@ -0,0 +1,66 @@
+@mixin clearfix {
+  &:after {
+    content: "";
+    display: table;
+    clear: both;
+  }
+}
+
+@mixin scrollBar {
+  &::-webkit-scrollbar-track-piece {
+    background: #d3dce6;
+  }
+
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: #99a9bf;
+    border-radius: 20px;
+  }
+}
+
+@mixin relative {
+  position: relative;
+  width: 100%;
+  height: 100%;
+}
+
+@mixin pct($pct) {
+  width: #{$pct};
+  position: relative;
+  margin: 0 auto;
+}
+
+@mixin triangle($width, $height, $color, $direction) {
+  $width: $width/2;
+  $color-border-style: $height solid $color;
+  $transparent-border-style: $width solid transparent;
+  height: 0;
+  width: 0;
+
+  @if $direction==up {
+    border-bottom: $color-border-style;
+    border-left: $transparent-border-style;
+    border-right: $transparent-border-style;
+  }
+
+  @else if $direction==right {
+    border-left: $color-border-style;
+    border-top: $transparent-border-style;
+    border-bottom: $transparent-border-style;
+  }
+
+  @else if $direction==down {
+    border-top: $color-border-style;
+    border-left: $transparent-border-style;
+    border-right: $transparent-border-style;
+  }
+
+  @else if $direction==left {
+    border-right: $color-border-style;
+    border-top: $transparent-border-style;
+    border-bottom: $transparent-border-style;
+  }
+}

+ 287 - 0
src/assets/styles/ruoyi.scss

@@ -0,0 +1,287 @@
+/**
+* 通用css样式布局处理
+* Copyright (c) 2019 ruoyi
+*/
+
+/** 基础通用 **/
+.pt5 {
+  padding-top: 5px;
+}
+
+.pr5 {
+  padding-right: 5px;
+}
+
+.pb5 {
+  padding-bottom: 5px;
+}
+
+.mt5 {
+  margin-top: 5px;
+}
+
+.mr5 {
+  margin-right: 5px;
+}
+
+.mb5 {
+  margin-bottom: 5px;
+}
+
+.mb8 {
+  margin-bottom: 8px;
+}
+
+.ml5 {
+  margin-left: 5px;
+}
+
+.mt10 {
+  margin-top: 10px;
+}
+
+.mr10 {
+  margin-right: 10px;
+}
+
+.mb10 {
+  margin-bottom: 10px;
+}
+.ml10 {
+	margin-left: 10px;
+}
+
+.mt20 {
+  margin-top: 20px;
+}
+
+.mr20 {
+  margin-right: 20px;
+}
+
+.mb20 {
+  margin-bottom: 20px;
+}
+.ml20 {
+	margin-left: 20px;
+}
+
+.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
+  font-family: inherit;
+  font-weight: 500;
+  line-height: 1.1;
+  color: inherit;
+}
+
+.el-message-box__status + .el-message-box__message{
+  word-break: break-word;
+}
+
+.el-dialog:not(.is-fullscreen) {
+  margin-top: 6vh !important;
+}
+
+.el-dialog__wrapper.scrollbar .el-dialog .el-dialog__body {
+  overflow: auto;
+  overflow-x: hidden;
+  max-height: 70vh;
+  padding: 10px 20px 0;
+}
+
+.el-table {
+  .el-table__header-wrapper, .el-table__fixed-header-wrapper {
+    th {
+      word-break: break-word;
+      background-color: #f8f8f9;
+      color: #515a6e;
+      height: 40px;
+      font-size: 13px;
+    }
+  }
+
+  .el-table__body-wrapper {
+    .el-button [class*="el-icon-"] + span {
+      margin-left: 1px;
+    }
+  }
+}
+
+/** 表单布局 **/
+.form-header {
+  font-size: 15px;
+  color: #6379bb;
+  border-bottom: 1px solid #ddd;
+  margin: 8px 10px 25px 10px;
+  padding-bottom: 5px
+}
+
+/** 表格布局 **/
+.pagination-container {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 20px;
+}
+
+/* tree border */
+.tree-border {
+  margin-top: 5px;
+  border: 1px solid #e5e6e7;
+  background: #FFFFFF none;
+  border-radius: 4px;
+}
+
+@media (max-width: 768px) {
+  .pagination-container .el-pagination > .el-pagination__jump {
+    display: none !important;
+  }
+  .pagination-container .el-pagination > .el-pagination__sizes {
+    display: none !important;
+  }
+}
+
+.el-table .fixed-width .el-button--mini {
+  padding-left: 0;
+  padding-right: 0;
+  width: inherit;
+}
+
+/** 表格更多操作下拉样式 */
+.el-table .el-dropdown-link,.el-table .el-dropdown-selfdefine {
+	cursor: pointer;
+	margin-left: 5px;
+}
+
+.el-table .el-dropdown, .el-icon-arrow-down {
+  font-size: 12px;
+}
+
+.el-tree-node__content > .el-checkbox {
+  margin-right: 8px;
+}
+
+.list-group-striped > .list-group-item {
+  border-left: 0;
+  border-right: 0;
+  border-radius: 0;
+  padding-left: 0;
+  padding-right: 0;
+}
+
+.list-group {
+  padding-left: 0px;
+  list-style: none;
+}
+
+.list-group-item {
+  border-bottom: 1px solid #e7eaec;
+  border-top: 1px solid #e7eaec;
+  margin-bottom: -1px;
+  padding: 11px 0px;
+  font-size: 13px;
+}
+
+.pull-right {
+  float: right !important;
+}
+
+.el-card__header {
+  padding: 14px 15px 7px;
+  min-height: 40px;
+}
+
+.el-card__body {
+  padding: 15px 20px 20px 20px;
+}
+
+.card-box {
+  margin-bottom: 10px;
+}
+
+/* button color */
+.el-button--cyan.is-active,
+.el-button--cyan:active {
+  background: #20B2AA;
+  border-color: #20B2AA;
+  color: #FFFFFF;
+}
+
+.el-button--cyan:focus,
+.el-button--cyan:hover {
+  background: #48D1CC;
+  border-color: #48D1CC;
+  color: #FFFFFF;
+}
+
+.el-button--cyan {
+  background-color: #20B2AA;
+  border-color: #20B2AA;
+  color: #FFFFFF;
+}
+
+/* text color */
+.text-navy {
+  color: #1ab394;
+}
+
+.text-primary {
+  color: inherit;
+}
+
+.text-success {
+  color: #1c84c6;
+}
+
+.text-info {
+  color: #23c6c8;
+}
+
+.text-warning {
+  color: #f8ac59;
+}
+
+.text-danger {
+  color: #ed5565;
+}
+
+.text-muted {
+  color: #888888;
+}
+
+/* image */
+.img-circle {
+  border-radius: 50%;
+}
+
+.img-lg {
+  width: 120px;
+  height: 120px;
+}
+
+.avatar-upload-preview {
+  position: relative;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  width: 200px;
+  height: 200px;
+  border-radius: 50%;
+  box-shadow: 0 0 4px #ccc;
+  overflow: hidden;
+}
+
+/* 拖拽列样式 */
+.sortable-ghost {
+  opacity: .8;
+  color: #fff !important;
+  background: #42b983 !important;
+}
+
+.top-right-btn {
+  position: relative;
+  float: right;
+}
+
+/* 分割面板样式 */
+.splitpanes.default-theme .splitpanes__pane {
+  background-color: #fff!important;
+}

+ 227 - 0
src/assets/styles/sidebar.scss

@@ -0,0 +1,227 @@
+#app {
+
+  .main-container {
+    height: 100%;
+    transition: margin-left .28s;
+    margin-left: $base-sidebar-width;
+    position: relative;
+  }
+
+  .sidebarHide {
+    margin-left: 0!important;
+  }
+
+  .sidebar-container {
+    -webkit-transition: width .28s;
+    transition: width 0.28s;
+    width: $base-sidebar-width !important;
+    background-color: $base-menu-background;
+    height: 100%;
+    position: fixed;
+    font-size: 0px;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    z-index: 1001;
+    overflow: hidden;
+    -webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35);
+    box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
+
+    // reset element-ui css
+    .horizontal-collapse-transition {
+      transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
+    }
+
+    .scrollbar-wrapper {
+      overflow-x: hidden !important;
+    }
+
+    .el-scrollbar__bar.is-vertical {
+      right: 0px;
+    }
+
+    .el-scrollbar {
+      height: 100%;
+    }
+
+    &.has-logo {
+      .el-scrollbar {
+        height: calc(100% - 50px);
+      }
+    }
+
+    .is-horizontal {
+      display: none;
+    }
+
+    a {
+      display: inline-block;
+      width: 100%;
+      overflow: hidden;
+    }
+
+    .svg-icon {
+      margin-right: 16px;
+    }
+
+    .el-menu {
+      border: none;
+      height: 100%;
+      width: 100% !important;
+    }
+
+    .el-menu-item, .el-submenu__title {
+      overflow: hidden !important;
+      text-overflow: ellipsis !important;
+      white-space: nowrap !important;
+    }
+
+    // menu hover
+    .submenu-title-noDropdown,
+    .el-submenu__title {
+      &:hover {
+        background-color: rgba(0, 0, 0, 0.06) !important;
+      }
+    }
+
+    & .theme-dark .is-active > .el-submenu__title {
+      color: $base-menu-color-active !important;
+    }
+
+    & .nest-menu .el-submenu>.el-submenu__title,
+    & .el-submenu .el-menu-item {
+      min-width: $base-sidebar-width !important;
+
+      &:hover {
+        background-color: rgba(0, 0, 0, 0.06) !important;
+      }
+    }
+
+    & .theme-dark .nest-menu .el-submenu>.el-submenu__title,
+    & .theme-dark .el-submenu .el-menu-item {
+      background-color: $base-sub-menu-background !important;
+
+      &:hover {
+        background-color: $base-sub-menu-hover !important;
+      }
+    }
+  }
+
+  .hideSidebar {
+    .sidebar-container {
+      width: 54px !important;
+    }
+
+    .main-container {
+      margin-left: 54px;
+    }
+
+    .submenu-title-noDropdown {
+      padding: 0 !important;
+      position: relative;
+
+      .el-tooltip {
+        padding: 0 !important;
+
+        .svg-icon {
+          margin-left: 20px;
+        }
+      }
+    }
+
+    .el-submenu {
+      overflow: hidden;
+
+      &>.el-submenu__title {
+        padding: 0 !important;
+
+        .svg-icon {
+          margin-left: 20px;
+        }
+
+      }
+    }
+
+    .el-menu--collapse {
+      .el-submenu {
+        &>.el-submenu__title {
+          &>span {
+            height: 0;
+            width: 0;
+            overflow: hidden;
+            visibility: hidden;
+            display: inline-block;
+          }
+        }
+      }
+    }
+  }
+
+  .el-menu--collapse .el-menu .el-submenu {
+    min-width: $base-sidebar-width !important;
+  }
+
+  // mobile responsive
+  .mobile {
+    .main-container {
+      margin-left: 0px;
+    }
+
+    .sidebar-container {
+      transition: transform .28s;
+      width: $base-sidebar-width !important;
+    }
+
+    &.hideSidebar {
+      .sidebar-container {
+        pointer-events: none;
+        transition-duration: 0.3s;
+        transform: translate3d(-$base-sidebar-width, 0, 0);
+      }
+    }
+  }
+
+  .withoutAnimation {
+
+    .main-container,
+    .sidebar-container {
+      transition: none;
+    }
+  }
+}
+
+// when menu collapsed
+.el-menu--vertical {
+  &>.el-menu {
+    .svg-icon {
+      margin-right: 16px;
+    }
+  }
+
+  .nest-menu .el-submenu>.el-submenu__title,
+  .el-menu-item {
+    &:hover {
+      // you can use $subMenuHover
+      background-color: rgba(0, 0, 0, 0.06) !important;
+    }
+  }
+
+  // the scroll bar appears when the subMenu is too long
+  >.el-menu--popup {
+    max-height: 100vh;
+    overflow-y: auto;
+
+    &::-webkit-scrollbar-track-piece {
+      background: #d3dce6;
+    }
+
+    &::-webkit-scrollbar {
+      width: 6px;
+    }
+
+    &::-webkit-scrollbar-thumb {
+      background: #99a9bf;
+      border-radius: 20px;
+    }
+  }
+}

+ 49 - 0
src/assets/styles/transition.scss

@@ -0,0 +1,49 @@
+// global transition css
+
+/* fade */
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.28s;
+}
+
+.fade-enter,
+.fade-leave-active {
+  opacity: 0;
+}
+
+/* fade-transform */
+.fade-transform--move,
+.fade-transform-leave-active,
+.fade-transform-enter-active {
+  transition: all .5s;
+}
+
+.fade-transform-enter {
+  opacity: 0;
+  transform: translateX(-30px);
+}
+
+.fade-transform-leave-to {
+  opacity: 0;
+  transform: translateX(30px);
+}
+
+/* breadcrumb transition */
+.breadcrumb-enter-active,
+.breadcrumb-leave-active {
+  transition: all .5s;
+}
+
+.breadcrumb-enter,
+.breadcrumb-leave-active {
+  opacity: 0;
+  transform: translateX(20px);
+}
+
+.breadcrumb-move {
+  transition: all .5s;
+}
+
+.breadcrumb-leave-active {
+  position: absolute;
+}

+ 54 - 0
src/assets/styles/variables.scss

@@ -0,0 +1,54 @@
+// base color
+$blue:#324157;
+$light-blue:#3A71A8;
+$red:#C03639;
+$pink: #E65D6E;
+$green: #30B08F;
+$tiffany: #4AB7BD;
+$yellow:#FEC171;
+$panGreen: #30B08F;
+
+// 默认菜单主题风格
+$base-menu-color:#bfcbd9;
+$base-menu-color-active:#f4f4f5;
+$base-menu-background:#304156;
+$base-logo-title-color: #ffffff;
+
+$base-menu-light-color:rgba(0,0,0,.70);
+$base-menu-light-background:#ffffff;
+$base-logo-light-title-color: #001529;
+
+$base-sub-menu-background:#1f2d3d;
+$base-sub-menu-hover:#001528;
+
+// 自定义暗色菜单风格
+/**
+$base-menu-color:hsla(0,0%,100%,.65);
+$base-menu-color-active:#fff;
+$base-menu-background:#001529;
+$base-logo-title-color: #ffffff;
+
+$base-menu-light-color:rgba(0,0,0,.70);
+$base-menu-light-background:#ffffff;
+$base-logo-light-title-color: #001529;
+
+$base-sub-menu-background:#000c17;
+$base-sub-menu-hover:#001528;
+*/
+
+$base-sidebar-width: 200px;
+
+// the :export directive is the magic sauce for webpack
+// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
+:export {
+  menuColor: $base-menu-color;
+  menuLightColor: $base-menu-light-color;
+  menuColorActive: $base-menu-color-active;
+  menuBackground: $base-menu-background;
+  menuLightBackground: $base-menu-light-background;
+  subMenuBackground: $base-sub-menu-background;
+  subMenuHover: $base-sub-menu-hover;
+  sideBarWidth: $base-sidebar-width;
+  logoTitleColor: $base-logo-title-color;
+  logoLightTitleColor: $base-logo-light-title-color
+}

+ 147 - 0
src/components/AppHeader.vue

@@ -0,0 +1,147 @@
+<template>
+  <div class="app-header">
+	<div class="app-header-time">{{time}}</div>
+	<div class="app-header-tit">
+		<!-- AI-Hub智能处理平台 -->
+	</div>
+	<!-- <div>
+		<img src="@/assets/images/header.png"/>
+		<div>admin</div>
+	</div> -->
+	<div class="avatar-container right-menu-item hover-effect">
+		<div class="avatar-wrapper">
+		  <img :src="avatar" class="user-avatar">
+			<div class="name">{{name}}</div>
+			<div class="line"></div>
+			<div class="out" @click="logout">退出</div>
+		</div>
+	</div>
+	
+	<!-- <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
+	  <div class="avatar-wrapper">
+	    <img :src="avatar" class="user-avatar">
+		<div class="name">123</div>
+		<div @click.native="logout">退出</div>
+	    <i class="el-icon-caret-bottom" />
+	  </div>
+	  <el-dropdown-menu slot="dropdown">
+	    <router-link to="/user/profile">
+	      <el-dropdown-item>个人中心</el-dropdown-item>
+	    </router-link>
+	    <el-dropdown-item divided @click.native="logout">
+	      <span>退出登录</span>
+	    </el-dropdown-item>
+	  </el-dropdown-menu>
+	</el-dropdown> -->
+  </div>
+</template>
+
+<script>
+	import { mapGetters } from 'vuex'
+  // import 'qweather-icons/font/qweather-icons.css'
+  // import { getWeatherInfo } from '@/utils/weather'
+  export default {
+    data() {
+      return {
+        time: '',
+        timer: null,
+        mhHost: '',
+      }
+    },
+	computed:{
+		...mapGetters([
+		  'avatar','name'
+		]),
+	},
+    async mounted() {
+      this.refreshTime()
+      this.timer = setInterval(this.refreshTime, 1000);
+      // this.weather = await getWeatherInfo(this.city);
+    },
+    methods: {
+      refreshTime() {
+        this.time = new Date().toLocaleString('zh-CN', {
+          year: 'numeric',
+          hour12: false,
+          month: '2-digit',
+          day: '2-digit',
+          // hour: 'numeric',
+          // minute: 'numeric',
+          // second: 'numeric',
+          weekday: 'long',
+        }).replace(/(星期|周)/, ' $1');
+		
+		// const date = new Date();
+		// const options = { 
+		//   weekday: 'long', 
+		//   year: 'numeric', 
+		//   month: 'long', 
+		//   day: 'numeric', 
+		// };
+		// const parts = new Intl.DateTimeFormat('zh-CN', options).formatToParts(date);
+		// let result = '';
+		// for (const part of parts) {
+		//   if (part.type === 'weekday') {
+		//     result += ' ' + part.value; // 在星期前加空格
+		//   } else {
+		//     result += part.value;
+		//   }
+		// }
+		// this.time=result
+      },
+	  //退出
+	  logout() {
+	    this.$confirm('确定注销并退出系统吗?', '提示', {
+	      confirmButtonText: '确定',
+	      cancelButtonText: '取消',
+	      type: 'warning'
+	    }).then(() => {
+	      this.$store.dispatch('LogOut').then(() => {
+	        location.href = '/login'
+	      })
+	    }).catch(() => {})
+	  }
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+.app-header {
+  display: flex;
+  align-items: flex-start;
+  font-size: 14px;
+  font-weight: 400;
+  color: #ffffff;position: relative;
+padding:28px 45px 0 45px;
+  .app-header-time{padding-left: 2px;}
+  .app-header-tit{ background: url('~@/assets/images/headertit.png') no-repeat;
+  background-size: contain;width:601px;height: 36px;flex:1;
+  background-position: center center;}
+}
+.avatar-container {
+      margin-right: 30px;
+
+      .avatar-wrapper {
+        position: relative;
+		display: flex;align-items: center;
+        .user-avatar {
+          cursor: pointer;
+          width: 40px;
+          height: 40px;
+          border-radius: 40px;
+        }
+		.name{font-weight: bold;
+font-size: 14px;
+color: #FFFFFF;margin-left:10px;}
+        .el-icon-caret-bottom {margin-left:8px;
+          // cursor: pointer;
+          // position: absolute;
+          // right: -20px;
+          // top: 25px;
+          font-size: 14px;
+        }
+		.line{width: 1px;height: 12px;background: #33536F;margin-left: 12px;}
+		.out{margin-left: 12px;}
+      }
+    }
+</style>

+ 20 - 2
src/main.js

@@ -1,19 +1,37 @@
 import Vue from 'vue'
+import Cookies from 'js-cookie'
+
+import Element from 'element-ui'
+import '@/assets/styles/element-variables.scss'
+
+import '@/assets/styles/index.scss' // global css
+import '@/assets/styles/ruoyi.scss' // ruoyi css
+
 import App from './App.vue'
 import router from './router'
 import store from './store'
 
+import { getDicts } from "@/api/system/dict/data";
+
+import './permission' // permission control
 import utils from './utils/index';
 import './utils/ui/index'
+import plugins from './plugins' // plugins
 // import './utils/ui/option'
 import './router/permission'
+import { selectDictLabel,parseTime} from "@/utils/ruoyi";
+
 
 // import echarts from "echarts";
 // Vue.prototype.$echarts = echarts;
 Vue.use(utils)
-
+Vue.use(plugins)
 Vue.config.productionTip = false
-
+Vue.use(Element)
+// 全局方法挂载
+Vue.prototype.getDicts = getDicts
+Vue.prototype.parseTime = parseTime
+Vue.prototype.selectDictLabel = selectDictLabel
 
 new Vue({
   router,

+ 43 - 0
src/mixins/ChartMixin.js

@@ -0,0 +1,43 @@
+import * as echarts from 'echarts';
+
+export default {
+  data() {
+    return {
+      chartInstance: null,
+      timer: null,
+      interval: 5000,
+    };
+  },
+  mounted() {
+    if(this.refresh !== 'custom') {
+      this.timer = setInterval(() => {
+        this.init();
+      }, this.interval);
+    }
+    setTimeout(() => {
+      this.init();
+    }, 3000)
+  },
+  beforeDestroy() {
+    clearInterval(this.timer);
+    this.timer = null;
+  },
+  methods: {
+    init() {
+      if (!this.chartInstance) {
+        this.initCharts();
+        if (!this.chartInstance) return;
+      }
+      this.chartInstance.clear();
+      this.chartInstance.setOption(this.getOption());
+      this.finished && this.finished();
+    },
+    initCharts() {
+      const el = this.$refs.chartRef;
+      if (!el) {
+        return;
+      }
+      this.chartInstance = this.$echarts.init(el);
+    },
+  },
+};

+ 63 - 0
src/permission.js

@@ -0,0 +1,63 @@
+import router from './router'
+import store from './store'
+import { Message } from 'element-ui'
+import NProgress from 'nprogress'
+import 'nprogress/nprogress.css'
+import { getToken } from '@/utils/auth'
+import { isPathMatch } from '@/utils/validate'
+import { isRelogin } from '@/utils/request'
+
+NProgress.configure({ showSpinner: false })
+
+const whiteList = ['/login', '/register']
+
+const isWhiteList = (path) => {
+  return whiteList.some(pattern => isPathMatch(pattern, path))
+}
+
+router.beforeEach((to, from, next) => {
+  NProgress.start()
+  if (getToken()) {
+    to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
+    /* has token*/
+    if (to.path === '/login') {
+      next({ path: '/' })
+      NProgress.done()
+    } else if (isWhiteList(to.path)) {
+      next()
+    } else {
+      if (store.getters.roles.length === 0) {
+        isRelogin.show = true
+        // 判断当前用户是否已拉取完user_info信息
+        store.dispatch('GetInfo').then(() => {
+          isRelogin.show = false
+          // store.dispatch('GenerateRoutes').then(accessRoutes => {
+          //   // 根据roles权限生成可访问的路由表
+          //   router.addRoutes(accessRoutes) // 动态添加可访问路由表
+            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
+          // })
+        }).catch(err => {
+            store.dispatch('LogOut').then(() => {
+              Message.error(err)
+              next({ path: '/' })
+            })
+          })
+      } else {
+        next()
+      }
+    }
+  } else {
+    // 没有token
+    if (isWhiteList(to.path)) {
+      // 在免登录白名单,直接进入
+      next()
+    } else {
+      next(`/login?redirect=${encodeURIComponent(to.fullPath)}`) // 否则全部重定向到登录页
+      NProgress.done()
+    }
+  }
+})
+
+router.afterEach(() => {
+  NProgress.done()
+})

+ 60 - 0
src/plugins/auth.js

@@ -0,0 +1,60 @@
+import store from '@/store'
+
+function authPermission(permission) {
+  const all_permission = "*:*:*"
+  const permissions = store.getters && store.getters.permissions
+  if (permission && permission.length > 0) {
+    return permissions.some(v => {
+      return all_permission === v || v === permission
+    })
+  } else {
+    return false
+  }
+}
+
+function authRole(role) {
+  const super_admin = "admin"
+  const roles = store.getters && store.getters.roles
+  if (role && role.length > 0) {
+    return roles.some(v => {
+      return super_admin === v || v === role
+    })
+  } else {
+    return false
+  }
+}
+
+export default {
+  // 验证用户是否具备某权限
+  hasPermi(permission) {
+    return authPermission(permission)
+  },
+  // 验证用户是否含有指定权限,只需包含其中一个
+  hasPermiOr(permissions) {
+    return permissions.some(item => {
+      return authPermission(item)
+    })
+  },
+  // 验证用户是否含有指定权限,必须全部拥有
+  hasPermiAnd(permissions) {
+    return permissions.every(item => {
+      return authPermission(item)
+    })
+  },
+  // 验证用户是否具备某角色
+  hasRole(role) {
+    return authRole(role)
+  },
+  // 验证用户是否含有指定角色,只需包含其中一个
+  hasRoleOr(roles) {
+    return roles.some(item => {
+      return authRole(item)
+    })
+  },
+  // 验证用户是否含有指定角色,必须全部拥有
+  hasRoleAnd(roles) {
+    return roles.every(item => {
+      return authRole(item)
+    })
+  }
+}

+ 79 - 0
src/plugins/cache.js

@@ -0,0 +1,79 @@
+const sessionCache = {
+  set (key, value) {
+    if (!sessionStorage) {
+      return
+    }
+    if (key != null && value != null) {
+      sessionStorage.setItem(key, value)
+    }
+  },
+  get (key) {
+    if (!sessionStorage) {
+      return null
+    }
+    if (key == null) {
+      return null
+    }
+    return sessionStorage.getItem(key)
+  },
+  setJSON (key, jsonValue) {
+    if (jsonValue != null) {
+      this.set(key, JSON.stringify(jsonValue))
+    }
+  },
+  getJSON (key) {
+    const value = this.get(key)
+    if (value != null) {
+      return JSON.parse(value)
+    }
+    return null
+  },
+  remove (key) {
+    sessionStorage.removeItem(key)
+  }
+}
+const localCache = {
+  set (key, value) {
+    if (!localStorage) {
+      return
+    }
+    if (key != null && value != null) {
+      localStorage.setItem(key, value)
+    }
+  },
+  get (key) {
+    if (!localStorage) {
+      return null
+    }
+    if (key == null) {
+      return null
+    }
+    return localStorage.getItem(key)
+  },
+  setJSON (key, jsonValue) {
+    if (jsonValue != null) {
+      this.set(key, JSON.stringify(jsonValue))
+    }
+  },
+  getJSON (key) {
+    const value = this.get(key)
+    if (value != null) {
+      return JSON.parse(value)
+    }
+    return null
+  },
+  remove (key) {
+    localStorage.removeItem(key)
+  }
+}
+
+export default {
+  /**
+   * 会话级缓存
+   */
+  session: sessionCache,
+  /**
+   * 本地缓存
+   */
+  local: localCache
+}

+ 20 - 0
src/plugins/index.js

@@ -0,0 +1,20 @@
+// import tab from './tab'
+// import auth from './auth'
+// import cache from './cache'
+import modal from './modal'
+// import download from './download'
+
+export default {
+  install(Vue) {
+    // 页签操作
+    // Vue.prototype.$tab = tab
+    // // 认证对象
+    // Vue.prototype.$auth = auth
+    // // 缓存对象
+    // Vue.prototype.$cache = cache
+    // 模态框对象
+    Vue.prototype.$modal = modal
+    // 下载文件
+    // Vue.prototype.$download = download
+  }
+}

+ 83 - 0
src/plugins/modal.js

@@ -0,0 +1,83 @@
+import { Message, MessageBox, Notification, Loading } from 'element-ui'
+
+let loadingInstance
+
+export default {
+  // 消息提示
+  msg(content) {
+    Message.info(content)
+  },
+  // 错误消息
+  msgError(content) {
+    Message.error(content)
+  },
+  // 成功消息
+  msgSuccess(content) {
+    Message.success(content)
+  },
+  // 警告消息
+  msgWarning(content) {
+    Message.warning(content)
+  },
+  // 弹出提示
+  alert(content) {
+    MessageBox.alert(content, "系统提示")
+  },
+  // 错误提示
+  alertError(content) {
+    MessageBox.alert(content, "系统提示", { type: 'error' })
+  },
+  // 成功提示
+  alertSuccess(content) {
+    MessageBox.alert(content, "系统提示", { type: 'success' })
+  },
+  // 警告提示
+  alertWarning(content) {
+    MessageBox.alert(content, "系统提示", { type: 'warning' })
+  },
+  // 通知提示
+  notify(content) {
+    Notification.info(content)
+  },
+  // 错误通知
+  notifyError(content) {
+    Notification.error(content)
+  },
+  // 成功通知
+  notifySuccess(content) {
+    Notification.success(content)
+  },
+  // 警告通知
+  notifyWarning(content) {
+    Notification.warning(content)
+  },
+  // 确认窗体
+  confirm(content) {
+    return MessageBox.confirm(content, "系统提示", {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: "warning",
+    })
+  },
+  // 提交内容
+  prompt(content) {
+    return MessageBox.prompt(content, "系统提示", {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: "warning",
+    })
+  },
+  // 打开遮罩层
+  loading(content) {
+    loadingInstance = Loading.service({
+      lock: true,
+      text: content,
+      spinner: "el-icon-loading",
+      background: "rgba(0, 0, 0, 0.7)",
+    })
+  },
+  // 关闭遮罩层
+  closeLoading() {
+    loadingInstance.close()
+  }
+}

+ 24 - 8
src/router/index.js

@@ -4,19 +4,35 @@ import VueRouter from 'vue-router'
 
 Vue.use(VueRouter)
 
-const routes = [{
-  path: '/',
-  name: 'Home',
-  component: () => import('../views/main.vue'),
-  redirect:'/index',
+const routes = [
+	{
+	  path: '/',
+	  name: 'Home',
+	  component: () => import('@/views/main.vue'),
+	  redirect: '/index',
+	  children: [
+	    {
+	      path: 'index',
+	      name: 'Index',
+	      component: () => import('@/views/pages/index.vue')
+	    },
+	
+	  ]
+	},
+	{
+  path: '/login',
+  name: 'login',
+  component: () => import('@/views/loginmain.vue'),
+  redirect:'/login',
   children: [
     {
-      path: '/index',
+      path: '/login',
       name: 'Home',
-      component: () => import('@/views/pages/index.vue')
+      component: () => import('@/views/pages/login.vue')
     }
   ]
-}
+},
+
 
 ]
 

+ 3 - 3
src/settings.js

@@ -1,8 +1,8 @@
 module.exports = {
-  title: '大屏展示系统',
+  title: 'AI-Hub智能处理平台',
   // urls: `http://114.99.127.243:52002`,
-  urls: `https://lydp.qs163.cn`,
-   // urls: `http://192.168.101.245:8099`,
+  // urls: `https://lydp.qs163.cn`,
+   urls: `http://192.168.101.245:8099`,
    // urls: `http://192.168.101.11:8089`,
   /**
    * 侧边栏主题 深色主题theme-dark,浅色主题theme-light

+ 19 - 0
src/store/getters.js

@@ -0,0 +1,19 @@
+const getters = {
+  sidebar: state => state.app.sidebar,
+  size: state => state.app.size,
+  device: state => state.app.device,
+  dict: state => state.dict.dict,
+  visitedViews: state => state.tagsView.visitedViews,
+  cachedViews: state => state.tagsView.cachedViews,
+  token: state => state.user.token,
+  avatar: state => state.user.avatar,
+  name: state => state.user.name,
+  introduction: state => state.user.introduction,
+  roles: state => state.user.roles,
+  permissions: state => state.user.permissions,
+  permission_routes: state => state.permission.routes,
+  topbarRouters:state => state.permission.topbarRouters,
+  defaultRoutes:state => state.permission.defaultRoutes,
+  sidebarRouters:state => state.permission.sidebarRouters
+}
+export default getters

+ 18 - 8
src/store/index.js

@@ -1,15 +1,25 @@
 import Vue from 'vue'
 import Vuex from 'vuex'
+import app from './modules/app'
+import dict from './modules/dict'
+import user from './modules/user'
+// import tagsView from './modules/tagsView'
+// import permission from './modules/permission'
+import settings from './modules/settings'
+import getters from './getters'
 
 Vue.use(Vuex)
 
-export default new Vuex.Store({
-  state: {
-  },
-  mutations: {
-  },
-  actions: {
-  },
+const store = new Vuex.Store({
   modules: {
-  }
+    app,
+    dict,
+    user,
+    // tagsView,
+    // permission,
+    settings
+  },
+  getters
 })
+
+export default store

+ 66 - 0
src/store/modules/app.js

@@ -0,0 +1,66 @@
+import Cookies from 'js-cookie'
+
+const state = {
+  sidebar: {
+    opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
+    withoutAnimation: false,
+    hide: false
+  },
+  device: 'desktop',
+  size: Cookies.get('size') || 'medium'
+}
+
+const mutations = {
+  TOGGLE_SIDEBAR: state => {
+    if (state.sidebar.hide) {
+      return false
+    }
+    state.sidebar.opened = !state.sidebar.opened
+    state.sidebar.withoutAnimation = false
+    if (state.sidebar.opened) {
+      Cookies.set('sidebarStatus', 1)
+    } else {
+      Cookies.set('sidebarStatus', 0)
+    }
+  },
+  CLOSE_SIDEBAR: (state, withoutAnimation) => {
+    Cookies.set('sidebarStatus', 0)
+    state.sidebar.opened = false
+    state.sidebar.withoutAnimation = withoutAnimation
+  },
+  TOGGLE_DEVICE: (state, device) => {
+    state.device = device
+  },
+  SET_SIZE: (state, size) => {
+    state.size = size
+    Cookies.set('size', size)
+  },
+  SET_SIDEBAR_HIDE: (state, status) => {
+    state.sidebar.hide = status
+  }
+}
+
+const actions = {
+  toggleSideBar({ commit }) {
+    commit('TOGGLE_SIDEBAR')
+  },
+  closeSideBar({ commit }, { withoutAnimation }) {
+    commit('CLOSE_SIDEBAR', withoutAnimation)
+  },
+  toggleDevice({ commit }, device) {
+    commit('TOGGLE_DEVICE', device)
+  },
+  setSize({ commit }, size) {
+    commit('SET_SIZE', size)
+  },
+  toggleSideBarHide({ commit }, status) {
+    commit('SET_SIDEBAR_HIDE', status)
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}

+ 50 - 0
src/store/modules/dict.js

@@ -0,0 +1,50 @@
+const state = {
+  dict: new Array()
+}
+const mutations = {
+  SET_DICT: (state, { key, value }) => {
+    if (key !== null && key !== "") {
+      state.dict.push({
+        key: key,
+        value: value
+      })
+    }
+  },
+  REMOVE_DICT: (state, key) => {
+    try {
+      for (let i = 0; i < state.dict.length; i++) {
+        if (state.dict[i].key == key) {
+          state.dict.splice(i, 1)
+          return true
+        }
+      }
+    } catch (e) {
+    }
+  },
+  CLEAN_DICT: (state) => {
+    state.dict = new Array()
+  }
+}
+
+const actions = {
+  // 设置字典
+  setDict({ commit }, data) {
+    commit('SET_DICT', data)
+  },
+  // 删除字典
+  removeDict({ commit }, key) {
+    commit('REMOVE_DICT', key)
+  },
+  // 清空字典
+  cleanDict({ commit }) {
+    commit('CLEAN_DICT')
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}
+

+ 42 - 0
src/store/modules/settings.js

@@ -0,0 +1,42 @@
+import defaultSettings from '@/settings'
+
+const { sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle } = defaultSettings
+
+const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
+const state = {
+  title: '',
+  theme: storageSetting.theme || '#409EFF',
+  sideTheme: storageSetting.sideTheme || sideTheme,
+  showSettings: showSettings,
+  topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav,
+  tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView,
+  fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader,
+  sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo,
+  dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle
+}
+const mutations = {
+  CHANGE_SETTING: (state, { key, value }) => {
+    if (state.hasOwnProperty(key)) {
+      state[key] = value
+    }
+  }
+}
+
+const actions = {
+  // 修改布局设置
+  changeSetting({ commit }, data) {
+    commit('CHANGE_SETTING', data)
+  },
+  // 设置网页标题
+  setTitle({ commit }, title) {
+    state.title = title
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}
+

+ 106 - 0
src/store/modules/user.js

@@ -0,0 +1,106 @@
+import { login, logout, getInfo } from '@/api/login'
+import { getToken, setToken, removeToken } from '@/utils/auth'
+import { isHttp, isEmpty } from "@/utils/validate"
+import defAva from '@/assets/images/profile.jpg'
+
+const user = {
+  state: {
+    token: getToken(),
+    id: '',
+    name: '',
+    avatar: '',
+    roles: [],
+    permissions: []
+  },
+
+  mutations: {
+    SET_TOKEN: (state, token) => {
+      state.token = token
+    },
+    SET_ID: (state, id) => {
+      state.id = id
+    },
+    SET_NAME: (state, name) => {
+      state.name = name
+    },
+    SET_AVATAR: (state, avatar) => {
+      state.avatar = avatar
+    },
+    SET_ROLES: (state, roles) => {
+      state.roles = roles
+    },
+    SET_PERMISSIONS: (state, permissions) => {
+      state.permissions = permissions
+    }
+  },
+
+  actions: {
+    // 登录
+    Login({ commit }, userInfo) {
+      const username = userInfo.username.trim()
+      const password = userInfo.password
+      const code = userInfo.code
+      const uuid = userInfo.uuid
+      return new Promise((resolve, reject) => {
+        login(username, password, code, uuid).then(res => {
+          setToken(res.token)
+          commit('SET_TOKEN', res.token)
+          resolve()
+        }).catch(error => {
+          reject(error)
+        })
+      })
+    },
+
+    // 获取用户信息
+    GetInfo({ commit, state }) {
+      return new Promise((resolve, reject) => {
+        getInfo().then(res => {
+          const user = res.user
+          let avatar = user.avatar || ""
+          if (!isHttp(avatar)) {
+            avatar = (isEmpty(avatar)) ? defAva : process.env.VUE_APP_BASE_API + avatar
+          }
+          if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
+            commit('SET_ROLES', res.roles)
+            commit('SET_PERMISSIONS', res.permissions)
+          } else {
+            commit('SET_ROLES', ['ROLE_DEFAULT'])
+          }
+          commit('SET_ID', user.userId)
+          commit('SET_NAME', user.userName)
+          commit('SET_AVATAR', avatar)
+          resolve(res)
+        }).catch(error => {
+          reject(error)
+        })
+      })
+    },
+
+    // 退出系统
+    LogOut({ commit, state }) {
+      return new Promise((resolve, reject) => {
+        logout(state.token).then(() => {
+          commit('SET_TOKEN', '')
+          commit('SET_ROLES', [])
+          commit('SET_PERMISSIONS', [])
+          removeToken()
+          resolve()
+        }).catch(error => {
+          reject(error)
+        })
+      })
+    },
+
+    // 前端 登出
+    FedLogOut({ commit }) {
+      return new Promise(resolve => {
+        commit('SET_TOKEN', '')
+        removeToken()
+        resolve()
+      })
+    }
+  }
+}
+
+export default user

+ 30 - 0
src/utils/jsencrypt.js

@@ -0,0 +1,30 @@
+import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
+
+// 密钥对生成 http://web.chacuo.net/netrsakeypair
+
+const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' +
+  'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
+
+const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' +
+  '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' +
+  'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' +
+  'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' +
+  'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' +
+  'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' +
+  'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' +
+  'UP8iWi1Qw0Y='
+
+// 加密
+export function encrypt(txt) {
+  const encryptor = new JSEncrypt()
+  encryptor.setPublicKey(publicKey) // 设置公钥
+  return encryptor.encrypt(txt) // 对数据进行加密
+}
+
+// 解密
+export function decrypt(txt) {
+  const encryptor = new JSEncrypt()
+  encryptor.setPrivateKey(privateKey) // 设置私钥
+  return encryptor.decrypt(txt) // 对数据进行解密
+}
+

+ 100 - 49
src/utils/request.js

@@ -1,44 +1,70 @@
 import axios from 'axios'
-import { Notification, MessageBox, Message } from 'element-ui'
+import { Notification, MessageBox, Message, Loading } from 'element-ui'
+console.log(MessageBox)
 import store from '@/store'
 import { getToken } from '@/utils/auth'
 import errorCode from '@/utils/errorCode'
+import { tansParams, blobValidate } from "@/utils/ruoyi"
+import cache from '@/plugins/cache'
+// import { saveAs } from 'file-saver'
+
+let downloadLoadingInstance
+// 是否显示重新登录
+export let isRelogin = { show: false }
+
 axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
 // 创建axios实例
 const service = axios.create({
   // axios中请求配置有baseURL选项,表示请求URL公共部分
   baseURL: process.env.VUE_APP_BASE_API,
   // 超时
-  timeout: 120000
+  timeout: 10000
 })
+
 // request拦截器
 service.interceptors.request.use(config => {
   // 是否需要设置 token
   const isToken = (config.headers || {}).isToken === false
+  // 是否需要防止数据重复提交
+  const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
   if (getToken() && !isToken) {
     config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
   }
   // get请求映射params参数
   if (config.method === 'get' && config.params) {
-    let url = config.url + '?';
-    for (const propName of Object.keys(config.params)) {
-      const value = config.params[propName];
-      var part = encodeURIComponent(propName) + "=";
-      if (value !== null && typeof(value) !== "undefined") {
-        if (typeof value === 'object') {
-          for (const key of Object.keys(value)) {
-            let params = propName + '[' + key + ']';
-            var subPart = encodeURIComponent(params) + "=";
-            url += subPart + encodeURIComponent(value[key]) + "&";
-          }
-        } else {
-          url += part + encodeURIComponent(value) + "&";
-        }
+    let url = config.url + '?' + tansParams(config.params)
+    url = url.slice(0, -1)
+    config.params = {}
+    config.url = url
+  }
+  if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
+    const requestObj = {
+      url: config.url,
+      data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
+      time: new Date().getTime()
+    }
+    const requestSize = Object.keys(JSON.stringify(requestObj)).length // 请求数据大小
+    const limitSize = 5 * 1024 * 1024 // 限制存放数据5M
+    if (requestSize >= limitSize) {
+      console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。')
+      return config
+    }
+    const sessionObj = cache.session.getJSON('sessionObj')
+    if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
+      cache.session.setJSON('sessionObj', requestObj)
+    } else {
+      const s_url = sessionObj.url                  // 请求地址
+      const s_data = sessionObj.data                // 请求数据
+      const s_time = sessionObj.time                // 请求时间
+      const interval = 1000                         // 间隔时间(ms),小于此时间视为重复提交
+      if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
+        const message = '数据正在处理,请勿重复提交'
+        console.warn(`[${s_url}]: ` + message)
+        return Promise.reject(new Error(message))
+      } else {
+        cache.session.setJSON('sessionObj', requestObj)
       }
     }
-    url = url.slice(0, -1);
-    config.params = {};
-    config.url = url;
   }
   return config
 }, error => {
@@ -49,30 +75,34 @@ service.interceptors.request.use(config => {
 // 响应拦截器
 service.interceptors.response.use(res => {
     // 未设置状态码则默认成功状态
-    const code = res.data.code || 200;
+    const code = res.data.code || 200
     // 获取错误信息
     const msg = errorCode[code] || res.data.msg || errorCode['default']
+    // 二进制数据则直接返回
+    if (res.request.responseType ===  'blob' || res.request.responseType ===  'arraybuffer') {
+      return res.data
+    }
     if (code === 401) {
-      MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
-          confirmButtonText: '重新登录',
-          cancelButtonText: '取消',
-          type: 'warning'
-        }
-      ).then(() => {
-        // store.dispatch('LogOut').then(() => {
-        //   location.href = '/index';
-        // })
+      if (!isRelogin.show) {
+        isRelogin.show = true
+        MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
+          isRelogin.show = false
+          store.dispatch('LogOut').then(() => {
+            location.href = '/index'
+          })
+      }).catch(() => {
+        isRelogin.show = false
       })
+    }
+      return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
     } else if (code === 500) {
-      Message({
-        message: msg,
-        type: 'error'
-      })
+      Message({ message: msg, type: 'error' })
       return Promise.reject(new Error(msg))
+    } else if (code === 601) {
+      Message({ message: msg, type: 'warning' })
+      return Promise.reject('error')
     } else if (code !== 200) {
-      Notification.error({
-        title: msg
-      })
+      Notification.error({ title: msg })
       return Promise.reject('error')
     } else {
       return res.data
@@ -80,23 +110,44 @@ service.interceptors.response.use(res => {
   },
   error => {
     console.log('err' + error)
-    let { message } = error;
+    let { message } = error
     if (message == "Network Error") {
-      message = "后端接口连接异常";
-    }
-    else if (message.includes("timeout")) {
-      message = "系统接口请求超时";
+      message = "后端接口连接异常"
+    } else if (message.includes("timeout")) {
+      message = "系统接口请求超时"
+    } else if (message.includes("Request failed with status code")) {
+      message = "系统接口" + message.substr(message.length - 3) + "异常"
     }
-    else if (message.includes("Request failed with status code")) {
-      message = "系统接口" + message.substr(message.length - 3) + "异常";
-    }
-    Message({
-      message: message,
-      type: 'error',
-      duration: 5 * 1000
-    })
+    Message({ message: message, type: 'error', duration: 5 * 1000 })
     return Promise.reject(error)
   }
 )
 
+// 通用下载方法
+export function download(url, params, filename, config) {
+  downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", })
+  return service.post(url, params, {
+    transformRequest: [(params) => { return tansParams(params) }],
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+    responseType: 'blob',
+    ...config
+  }).then(async (data) => {
+    const isBlob = blobValidate(data)
+    if (isBlob) {
+      const blob = new Blob([data])
+      saveAs(blob, filename)
+    } else {
+      const resText = await data.text()
+      const rspObj = JSON.parse(resText)
+      const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
+      Message.error(errMsg)
+    }
+    downloadLoadingInstance.close()
+  }).catch((r) => {
+    console.error(r)
+    Message.error('下载文件出现错误,请联系管理员!')
+    downloadLoadingInstance.close()
+  })
+}
+
 export default service

+ 228 - 0
src/utils/ruoyi.js

@@ -0,0 +1,228 @@
+/**
+ * 通用js方法封装处理
+ * Copyright (c) 2019 ruoyi
+ */
+
+// 日期格式化
+export function parseTime(time, pattern) {
+  if (arguments.length === 0 || !time) {
+    return null
+  }
+  const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
+  let date
+  if (typeof time === 'object') {
+    date = time
+  } else {
+    if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
+      time = parseInt(time)
+    } else if (typeof time === 'string') {
+      time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '')
+    }
+    if ((typeof time === 'number') && (time.toString().length === 10)) {
+      time = time * 1000
+    }
+    date = new Date(time)
+  }
+  const formatObj = {
+    y: date.getFullYear(),
+    m: date.getMonth() + 1,
+    d: date.getDate(),
+    h: date.getHours(),
+    i: date.getMinutes(),
+    s: date.getSeconds(),
+    a: date.getDay()
+  }
+  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+    let value = formatObj[key]
+    // Note: getDay() returns 0 on Sunday
+    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
+    if (result.length > 0 && value < 10) {
+      value = '0' + value
+    }
+    return value || 0
+  })
+  return time_str
+}
+
+// 表单重置
+export function resetForm(refName) {
+  if (this.$refs[refName]) {
+    this.$refs[refName].resetFields()
+  }
+}
+
+// 添加日期范围
+export function addDateRange(params, dateRange, propName) {
+  let search = params
+  search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}
+  dateRange = Array.isArray(dateRange) ? dateRange : []
+  if (typeof (propName) === 'undefined') {
+    search.params['beginTime'] = dateRange[0]
+    search.params['endTime'] = dateRange[1]
+  } else {
+    search.params['begin' + propName] = dateRange[0]
+    search.params['end' + propName] = dateRange[1]
+  }
+  return search
+}
+
+// 回显数据字典
+export function selectDictLabel(datas, value) {
+  if (value === undefined) {
+    return ""
+  }
+  var actions = []
+  Object.keys(datas).some((key) => {
+    if (datas[key].dictValue == ('' + value)) {
+      actions.push(datas[key].dictLabel)
+      return true
+    }
+  })
+  if (actions.length === 0) {
+    actions.push(dictValue)
+  }
+  return actions.join('')
+}
+
+// 回显数据字典(字符串、数组)
+export function selectDictLabels(datas, value, separator) {
+  if (value === undefined || value.length ===0) {
+    return ""
+  }
+  if (Array.isArray(value)) {
+    value = value.join(",")
+  }
+  var actions = []
+  var currentSeparator = undefined === separator ? "," : separator
+  var temp = value.split(currentSeparator)
+  Object.keys(value.split(currentSeparator)).some((val) => {
+    var match = false
+    Object.keys(datas).some((key) => {
+      if (datas[key].value == ('' + temp[val])) {
+        actions.push(datas[key].label + currentSeparator)
+        match = true
+      }
+    })
+    if (!match) {
+      actions.push(temp[val] + currentSeparator)
+    }
+  })
+  return actions.join('').substring(0, actions.join('').length - 1)
+}
+
+// 字符串格式化(%s )
+export function sprintf(str) {
+  var args = arguments, flag = true, i = 1
+  str = str.replace(/%s/g, function () {
+    var arg = args[i++]
+    if (typeof arg === 'undefined') {
+      flag = false
+      return ''
+    }
+    return arg
+  })
+  return flag ? str : ''
+}
+
+// 转换字符串,undefined,null等转化为""
+export function parseStrEmpty(str) {
+  if (!str || str == "undefined" || str == "null") {
+    return ""
+  }
+  return str
+}
+
+// 数据合并
+export function mergeRecursive(source, target) {
+  for (var p in target) {
+    try {
+      if (target[p].constructor == Object) {
+        source[p] = mergeRecursive(source[p], target[p])
+      } else {
+        source[p] = target[p]
+      }
+    } catch (e) {
+      source[p] = target[p]
+    }
+  }
+  return source
+}
+
+/**
+ * 构造树型结构数据
+ * @param {*} data 数据源
+ * @param {*} id id字段 默认 'id'
+ * @param {*} parentId 父节点字段 默认 'parentId'
+ * @param {*} children 孩子节点字段 默认 'children'
+ */
+export function handleTree(data, id, parentId, children) {
+  let config = {
+    id: id || 'id',
+    parentId: parentId || 'parentId',
+    childrenList: children || 'children'
+  }
+
+  var childrenListMap = {}
+  var tree = []
+  for (let d of data) {
+    let id = d[config.id]
+    childrenListMap[id] = d
+    if (!d[config.childrenList]) {
+      d[config.childrenList] = []
+    }
+  }
+
+  for (let d of data) {
+    let parentId = d[config.parentId]
+    let parentObj = childrenListMap[parentId]
+    if (!parentObj) {
+      tree.push(d)
+    } else {
+      parentObj[config.childrenList].push(d)
+    }
+  }
+  return tree
+}
+
+/**
+* 参数处理
+* @param {*} params  参数
+*/
+export function tansParams(params) {
+  let result = ''
+  for (const propName of Object.keys(params)) {
+    const value = params[propName]
+    var part = encodeURIComponent(propName) + "="
+    if (value !== null && value !== "" && typeof (value) !== "undefined") {
+      if (typeof value === 'object') {
+        for (const key of Object.keys(value)) {
+          if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
+            let params = propName + '[' + key + ']'
+            var subPart = encodeURIComponent(params) + "="
+            result += subPart + encodeURIComponent(value[key]) + "&"
+          }
+        }
+      } else {
+        result += part + encodeURIComponent(value) + "&"
+      }
+    }
+  }
+  return result
+}
+
+// 返回项目路径
+export function getNormalPath(p) {
+  if (p.length === 0 || !p || p == 'undefined') {
+    return p
+  }
+  let res = p.replace('//', '/')
+  if (res[res.length - 1] === '/') {
+    return res.slice(0, res.length - 1)
+  }
+  return res
+}
+
+// 验证是否为blob格式
+export function blobValidate(data) {
+  return data.type !== 'application/json'
+}

+ 114 - 0
src/utils/validate.js

@@ -0,0 +1,114 @@
+/**
+ * 路径匹配器
+ * @param {string} pattern
+ * @param {string} path
+ * @returns {Boolean}
+ */
+export function isPathMatch(pattern, path) {
+  const regexPattern = pattern.replace(/\//g, '\\/').replace(/\*\*/g, '.*').replace(/\*/g, '[^\\/]*')
+  const regex = new RegExp(`^${regexPattern}$`)
+  return regex.test(path)
+}
+
+/**
+ * 判断value字符串是否为空 
+ * @param {string} value
+ * @returns {Boolean}
+ */
+export function isEmpty(value) {
+  if (value == null || value == "" || value == undefined || value == "undefined") {
+    return true
+  }
+  return false
+}
+
+/**
+ * 判断url是否是http或https 
+ * @param {string} url
+ * @returns {Boolean}
+ */
+export function isHttp(url) {
+  return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1
+}
+
+/**
+ * 判断path是否为外链
+ * @param {string} path
+ * @returns {Boolean}
+ */
+export function isExternal(path) {
+  return /^(https?:|mailto:|tel:)/.test(path)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validUsername(str) {
+  const valid_map = ['admin', 'editor']
+  return valid_map.indexOf(str.trim()) >= 0
+}
+
+/**
+ * @param {string} url
+ * @returns {Boolean}
+ */
+export function validURL(url) {
+  const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
+  return reg.test(url)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validLowerCase(str) {
+  const reg = /^[a-z]+$/
+  return reg.test(str)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validUpperCase(str) {
+  const reg = /^[A-Z]+$/
+  return reg.test(str)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validAlphabets(str) {
+  const reg = /^[A-Za-z]+$/
+  return reg.test(str)
+}
+
+/**
+ * @param {string} email
+ * @returns {Boolean}
+ */
+export function validEmail(email) {
+  const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+  return reg.test(email)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function isString(str) {
+  return typeof str === 'string' || str instanceof String
+}
+
+/**
+ * @param {Array} arg
+ * @returns {Boolean}
+ */
+export function isArray(arg) {
+  if (typeof Array.isArray === 'undefined') {
+    return Object.prototype.toString.call(arg) === '[object Array]'
+  }
+  return Array.isArray(arg)
+}

+ 35 - 0
src/views/loginmain.vue

@@ -0,0 +1,35 @@
+<template>
+  <div class="layout-main">
+    <Scale-box>
+      <router-view />
+    </Scale-box>
+  </div>
+</template>
+
+<script>
+import ScaleBox from './pages/common/ScaleBox'
+export default {
+  components:{ ScaleBox },
+  data(){
+    return{}
+  },
+  mounted(){
+   
+  },
+  methods:{
+  
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.layout-main{
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  position: relative;
+  background-color: #0c0926;
+  // overflow: scroll;
+}
+  
+</style>

+ 26 - 12
src/views/main.vue

@@ -1,24 +1,27 @@
 <template>
   <div class="layout-main">
-    <Scale-box>
-      <router-view />
+    <Scale-box >
+		<template v-slot:default="scalebox">
+			<div class="app-layout">
+			  <app-header></app-header>
+			  <router-view :scale='scalebox.scale'/>
+			</div>
+		</template>
+      
     </Scale-box>
   </div>
 </template>
 
 <script>
 import ScaleBox from './pages/common/ScaleBox'
+import AppHeader from '@/components/AppHeader'
 export default {
-  components:{ ScaleBox },
+  components:{ ScaleBox, AppHeader },
   data(){
-    return{}
-  },
-  mounted(){
-   
+    return{
+
+    }
   },
-  methods:{
-  
-  }
 }
 </script>
 
@@ -28,8 +31,19 @@ export default {
   height: 100%;
   overflow: hidden;
   position: relative;
-  background-color: #0c0926;
-  // overflow: scroll;
+  background-color: #00365A;
+  .app-layout {
+    padding: 10px 0px 14px;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+	background: url('~@/assets/images/header-bg.png') no-repeat;
+	background-size: contain;
+	background-position: center center;
+    .app-body {
+      flex: 1;
+    }
+  }
 }
   
 </style>

+ 3 - 2
src/views/pages/common/ScaleBox.vue

@@ -14,8 +14,8 @@
 		data() {
 			return {
 				scale: 0,
-				width: 1920,
-				height: 1080,
+				width: 1440,
+				height: 810,
 			};
 		},
 		mounted() {
@@ -39,6 +39,7 @@
 				this.scale = this.getScale();
 				if (this.$refs.ScaleBox) {
 					this.$refs.ScaleBox.style.setProperty("--scale", this.scale);
+					// this.$refs.ScaleBox.style.setProperty("--scale", 1);
 				}
 			},
 			debounce(fn, delay) {

+ 44 - 0
src/views/pages/components/box.vue

@@ -0,0 +1,44 @@
+<template>
+  <div class="box">
+	  <img src="@/assets/images/line.png" class="line"/>
+	  <div class="flexc tits">
+	  	<img src="@/assets/images/tip.png" class="tip"/>
+	  	<span class="tit">{{title}}</span>
+	  	<div v-if="count" class="cont">本月告警<span>{{count}}</span>件</div>
+	  </div>
+    <div class="box-content">
+      <slot/>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    title: {
+      type: String,
+      default: ''
+    },
+	count:{
+		type: String,
+		default: ''
+	},
+	
+  },
+  
+};
+</script>
+
+<style lang="scss" scoped>
+.flexc{display: flex;align-items: center;}
+.box {
+  .line{width: 100%;height: 2px;display: block;}
+  .tits{padding: 0 14px;color: #FFFFFF;
+	  .tip{width:14px;height: 11px;margin-right: 9px;flex: 0 0 auto;}
+	  .tit{font-weight: 500;font-size: 16px;color: #FFFFFF;flex: 1;}
+	  .cont{font-size: 14px;flex: 0 0 auto;
+		span{ont-weight: bold;font-size: 14px;color: #00FFFF;}
+	  }
+  }
+}
+</style>

+ 326 - 0
src/views/pages/components/center.vue

@@ -0,0 +1,326 @@
+<template>
+  <div class="box">
+	  <div class="overbtn" v-if="value==1">
+	  	<div class="arrowbtn act">
+	  	    <img src="@/assets/images/up.png"  @click="oarrowClick('left')" />
+	  	</div>
+	  	<el-carousel ref="overcardShow" height="100%" direction='vertical'  :autoplay='false' indicator-position='none' arrow="never">
+	  	      <el-carousel-item v-for="(cite,cidx) in annexList">
+	  	        <div class="arrowbox" v-for="(ite,idx) in cite"  :key="idx">
+	  				<div class="adrbox " :class="annexId==ite.id?'act':''" @click="getVrFn(ite.id)">
+
+	  					<div class="tit">{{ite.tit}}</div>
+	  				</div>
+	  			</div>
+	  	      </el-carousel-item>
+	  	</el-carousel>
+	  	<div class="arrowbtn">
+	  		<!-- v-if="pageLength !== currentPage + 1" -->
+	  	    <img src="@/assets/images/down.png" @click="oarrowClick('right')"  />
+	  	</div>
+	  </div>
+    <div class="box-content">
+		<div class="pr" v-show="value==1">
+			<img src="@/assets/images/centbg.png" class="centbg"/>
+			<div class="boxa">
+				<img src="@/assets/images/jzicoa.png" class="cira"/>
+				<img src="@/assets/images/jzicob.png" class="cirb"/>
+				<div class="tit">财务室红外探测器</div>
+			</div>
+			<div class="boxb">
+				<img src="@/assets/images/jziconf.png" class="cira"/>
+				<img src="@/assets/images/jzicoc.png" class="cirb"/>
+				<div class="tit">2号仓库半球摄像头</div>
+			</div>
+			<div class="boxc">
+				<img src="@/assets/images/jzicoe.png" class="cira"/>
+				<div class="line"></div>
+				<div class="cirbox">
+					<div class="tit">2号仓库半球摄像头</div>
+					<img src="@/assets/images/jzicod.png" class="cirb"/>
+				</div>
+				<div class="cirbox cirboxa">
+					<div class="tit">2号仓库半球摄像头</div>
+					<img src="@/assets/images/jzicod.png" class="cirb"/>
+				</div>
+			</div>
+		</div>
+		<!-- 地图 -->
+		<div v-show="value==2">
+			<div  id="container" style="width: 711px;height: 421px">
+			</div>
+		</div>
+		    
+      <!-- <slot/> -->
+    </div>
+	<!-- 右侧选择 -->
+	<div class="chosebtn">
+		<el-select v-model="value" placeholder="请选择">
+		    <el-option
+		      v-for="item in mapoptiops"
+		      :key="item.value"
+		      :label="item.label"
+		      :value="item.value">
+		    </el-option>
+		  </el-select>
+	</div>
+  </div>
+</template>
+
+<script>
+//引入缺德地图
+import AMapLoader from '@amap/amap-jsapi-loader'
+window._AMapSecurityConfig = {
+    securityJsCode: ''  //填写你的安全密钥
+}
+//web端:密钥b06df55c66884ce2c9a3f3281c2c5ca9
+		//keyfba818d626f91cf5a13cd61943a7667e
+
+//web服务8de52b560193f01932ad997dba3e76c0
+export default {
+  props: {
+    annexList: {
+      type: [],
+      default: []
+    },
+	mapoptiops:{
+		type: [],
+		default: []
+	}
+  },
+  data(){
+	  return{
+		annexId:0,
+		value: '1',
+		map: null,
+		mapfalg:true,
+		//标记点的位置信息
+		      mapData: [
+		        {
+		          name: '水域告警',
+					type:2,
+		          longitude: '117.211954',
+		          latitude: '31.839676',
+		          content:'SYS-463520',
+		          img:'https://n.sinaimg.cn/spider20220702/290/w1080h810/20220702/bdee-24bda60bf8b6cf30b658f25de78e12de.jpg'
+		        },
+		        {
+		          name: '正常位置',
+				  type:1,
+		          longitude: '117.210150',
+		          latitude: '31.839670',
+		          content:'好吃是好吃,吃完必上火,把子肉嘎嘎香',
+		          img:'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F25fac10a-1e66-4a6d-a4a1-4bf07d2666a1%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1690684148&t=c2560eb44f5348fc210f74ccd4552555'
+		        }
+		      ],
+	  }
+  },
+  watch:{
+	  value(val){
+		  if(val==2&&this.mapfalg){
+			  //DOM初始化完成进行地图初始化
+			  this.initMap()
+			  this.mapfalg=false;
+		  }
+	  }
+  },
+  mounted() {
+     
+    },
+  methods:{
+	  getVrFn(id){
+		  this.annexId=id
+	  },
+	  oarrowClick(val) {
+	      if(val === 'right') {
+	  		this.$refs.overcardShow.next()
+	      } else {
+	          this.$refs.overcardShow.prev()
+	      }
+	  },
+		initMap() {
+				AMapLoader.reset()
+		      AMapLoader.load({
+		        key: "8de52b560193f01932ad997dba3e76c0",             // 申请好的Web端开发者Key,首次调用 load 时必填
+		        version: "2.0",      // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
+		        plugins: ['AMap.AutoComplete', 'AMap.Marker', 'AMap.PlaceSearch', 'AMap.Geolocation'],       // 需要使用的的插件列表,如比例尺'AMap.Scale'等
+		      }).then((AMap) => {
+		        console.log(AMap)
+		        this.map = new AMap.Map("container", {  //设置地图容器id
+		          mapStyle: 'amap://styles/darkblue', //设置地图的显示样式
+		          viewMode: "3D",    //是否为3D地图模式
+		          zoom: 17.8,           //初始化地图级别
+		          center: [117.211954, 31.839676], //初始化地图中心点位置
+		          pitch: 40, // 地图俯仰角度,有效范围 0 度- 83 度
+		          terrain: true, // 开启地形图
+		 
+		          buildingAnimation: true,//楼块出现是否带动画
+		        });
+				
+				// this.getCenter()		  
+		        this.markPoints()
+		        this.latitude()
+		      }).catch(e => {
+		        console.log(e);
+		      })
+		    },
+			// 获取当前定位
+			getCenter(){
+				AMap.plugin('AMap.Geolocation', () => {
+				        const geolocation = new AMap.Geolocation({
+				          enableHighAccuracy: true, //是否使用高精度定位,默认:true
+				          // timeout: 10000,           //超过10秒后停止定位,默认:无穷大
+				          buttonOffset: new AMap.Pixel(10, 20),  //定位按钮与设置的停靠位置的偏移量,默认:Pixel(10, 20)
+				          zoomToAccuracy: true      //定位成功后是否自动调整地图视野到定位点
+				        });
+				
+				        this.map.addControl(geolocation);
+				
+				        geolocation.getCurrentPosition((status, result) => {
+				          if (status === 'complete') {
+				            // 定位成功,显示当前位置信息
+				            const lnglat = result.position;
+				            console.log('当前位置:', lnglat);
+				            // 可以在这里将位置信息保存到Vue的data中,以便后续使用
+				            // this.currentPosition = lnglat;
+				            // 并在模板中展示
+				          } else {
+				            console.error('定位失败:', result.message);
+				          }
+				        });
+				      });
+			},
+		    //遍历显示标记点
+		    markPoints() {
+				const icon = new AMap.Icon({
+				        image: require('@/assets/images/gjicob.png'), // 确保路径正确,根据实际情况调整
+				        size: new AMap.Size(55, 63), // 图标大小
+				        imageSize: new AMap.Size(55, 63) // 图标实际大小,如果图片本身大小与此不符,可以调整此属性以适应图片大小
+				});
+				const icona = new AMap.Icon({
+				        image: require('@/assets/images/zcicob.png'), // 确保路径正确,根据实际情况调整
+				        size: new AMap.Size(90, 90), // 图标大小
+				        imageSize: new AMap.Size(90, 90) // 图标实际大小,如果图片本身大小与此不符,可以调整此属性以适应图片大小
+				});
+		      this.mapData.forEach(item => {
+		        // 创建一个 Marker 实例:
+		        const marker = new AMap.Marker({
+		          position: new AMap.LngLat(item.longitude, item.latitude),   // 经纬度对象,也可以是经纬度构成的一维数组[lng, lat]
+				  icon: item.type==1?icona:icon,
+				  offset: new AMap.Pixel(-25, -10),
+		        });
+		        // 将创建的点标记添加到已有的地图实例:
+		        this.map.add(marker);
+		 
+		        //给标记点添加事件
+				marker.on('click', (e) => {
+				  this.setInfoWindows(e, item)
+				})
+		      });
+		    },
+		    //显示的弹出框组件
+		    setInfoWindows(e, item) {
+		      // 信息窗体的内容
+		      let content = [
+		        // e.pos[0],e.pos[1],
+		        `<div class="fixbox" style='\'padding:0px' 0px = '' 4px; \'=''><h1>${item.name}</h1>`,
+		        `<div class="fixtit">设备id :${item.content}</div>`,
+		        `<div class="fixtit">告警内容:未成年入水</div>`,
+		        // "地址 : XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</div></div>",
+		        // `<img src=${item.img} alt="" style="width: 100px;height: 100px">`
+		      ];
+		      // 创建 infoWindow 实例	
+		      let infoWindow = new AMap.InfoWindow({
+		        // content: content.join("<br>")  //传入 dom 对象,或者 html 字符串
+		        content: content.join(" ")  //传入 dom 对象,或者 html 字符串
+		      });
+		      // 打开信息窗体
+		      let dd = this.map.getCenter()
+		      // dd.pos = [e.pos[0], e.pos[1]]
+		      dd.lat = item.latitude
+		      dd.lng = item.longitude
+		      console.log(dd)
+			  if(item.type==2){
+				   infoWindow.open(this.map, dd);
+			  }
+		    },
+		    //点击获取经纬度
+		    latitude() {
+		      this.map.on('click', function (ev) {
+		        // console.log(ev)
+		        // 触发事件的对象
+		        let target = ev.target;
+		        // 触发事件的地理坐标,AMap.LngLat 类型
+		        let lnglat = ev.lnglat;
+		        // 触发事件的像素坐标,AMap.Pixel 类型
+		        let pixel = ev.pixel;
+		        // 触发事件类型
+		        let type = ev.type;
+		        console.log(target, lnglat, pixel, type)
+		      });
+		    },
+  }
+  
+};
+</script>
+<style>
+	.fixbox{font-size: 14px;color: #FFFFFF;line-height: 18px;padding-top: 2px;}
+.fixbox h1{font-weight: bold;font-size: 16px;color: #FFBB37;font-style: italic;line-height: 36px;margin-bottom: 4px;}
+.fixtit{font-size: 14px;color: #FFFFFF;}
+</style>
+<style lang="scss" scoped>
+.flexc{display: flex;align-items: center;}
+.box{position: relative;width: 100%;height: 100%;}
+.overbtn{width: 83px;height: 112px;display: flex;flex-direction: column;align-items: center;box-sizing: border-box;position: absolute;left:16px;top:20px;z-index: 3;background-color: rgba(27,130,183,0.12);;
+	.arrowbtn{width:100%;height: 28px;display: flex;align-items: center;justify-content: center;
+		&.act{background-color: rgba(27,130,183,0.22);}
+			
+			img{width: 12px;height: 6px;}
+		}
+	.arrowbox{display: flex;flex-direction: column;
+		&:nth-of-type(2n){background-color: rgba(27,130,183,0.22);}
+	}
+	::v-deep .el-carousel{flex:1;width:82px;}
+	.adrbox{position: relative;width: 100%;height: 28px;line-height: 28px;
+		&.act{
+			.tit{color: #99F17A;}
+		}
+		.tit{font-size: 14px;font-weight: 500;text-align: center;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;
+			color: #B9CCDF;box-sizing: border-box;padding: 0 10px;}
+	}
+}
+.chosebtn{position: absolute;right: 19px;top: 19px;}
+.box-content{width: 100%;height: 100%;display: flex;align-items: center;justify-content: center;}
+.centbg{width: 628px;height: 349px;}
+// 弹窗
+::v-deep .amap-info-content{width:200px;height: 112px;background: url('~@/assets/images/fixedbg.png') no-repeat;background-size: 100% 100%;}
+
+.pr{position: relative;}
+// 建筑
+.boxa{
+	position: absolute;left: 46px;top: 146px;border-top: 1px dashed #FFBA00;
+	border-left: 1px dashed #FFBA00;width: 178px;height: 40px;
+	.cira{position: absolute;width: 24px;height: 24px;right: -12px;top: -12px;}
+	.cirb{width: 52px;height: 53px;position: absolute;bottom: -30px;left: -26px;}
+	.tit{background: rgba(255, 187, 55, 0.9);font-size: 12px;padding: 3px 10px;color: #FFFFFF;position: absolute;bottom: -46px;transform: translateX(-50%);}
+}
+.boxb{
+	position: absolute;right: 35px;top: 107px;border-bottom: 1px dashed #06C4AC;
+	border-right: 1px dashed #06C4AC;width: 87px;height: 24px;
+	.cira{position: absolute;width: 8px;height: 8px;left: -4px;bottom: -4px;}
+	.cirb{width: 53px;height: 53px;position: absolute;bottom: 9px;right: -26px;}
+	.tit{background: rgba(15, 232, 153, 0.5);font-size: 12px;padding: 3px 10px;color: #FFFFFF;position: absolute;bottom: 58px;transform: translateX(50%);word-break: keep-all;right: 0;}
+}
+.boxc{
+	position: absolute;left: 144px;bottom: 36px;border: 1px dashed #16D0FF;border-left: none;width: 15px;height: 51px;
+	.line{width: 110px;height: 1px;position: absolute;left: 15px;bottom: 26px;border-bottom:1px dashed #16D0FF ;}
+	.cira{position: absolute;width: 9px;height: 9px;right: -113px;bottom: 21px;z-index: 1;}
+	
+	.cirbox{
+		position: absolute;top: -23px;right: 0px;display: flex;align-items: center;
+		&.cirboxa{top: 23px;}
+		.cirb{width: 47px;height: 47px;}
+		.tit{background: rgba(50, 155, 235, 0.7);font-size: 12px;padding: 3px 10px;color: #FFFFFF;word-break: keep-all;}
+	}
+}
+</style>

+ 180 - 0
src/views/pages/components/jbbarChart.vue

@@ -0,0 +1,180 @@
+<template>
+  <div :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import * as echarts from 'echarts';
+import chartMixin from '@/mixins/ChartMixin';
+export default {
+  mixins: [chartMixin],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '100%'
+    },
+	chartData: {
+	  type: Object,
+	  required: true
+	}
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  watch: {
+    chartData: {
+      deep: true,
+      handler(val) {
+        this.setOptions(val)
+      }
+    }
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initChart()
+    })
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(this.$el, 'macarons')
+		this.setOptions(this.chartData) 
+    },
+	setOptions({ date,gjzs, bfb} = {}) {
+	  this.chart.setOption({
+		  grid: { x: 50, y: 25, x2: 30, y2: 35 },
+			  xAxis: {
+			    data: date,
+			    axisLabel: {
+			      color: '#fff'
+			    },
+			    axisTick: {
+			      show: false
+			    },
+			    axisLine: {
+			      show: false
+			    },
+			  },
+			  yAxis: {
+			    axisLine: {
+			      show: false
+			    },
+			    axisTick: {
+			      show: false
+			    },
+				splitLine: {//网格线
+					// show:false,
+					lineStyle: {
+					    color: 'rgba(194, 201, 230, 0.2)', // 设置网格线颜色为灰色
+					     type: 'solid', // 设置网格线类型为虚线,可选'solid'、'dotted'、'dashed'等
+					    width: 1 // 设置网格线宽度
+					}
+				},
+			    axisLabel: {
+			      color: '#999'
+			    }
+			  },
+			  // dataZoom: [
+			  //   {
+			  //     type: 'inside'
+			  //   }
+			  // ],
+			  title: {
+			      text: '件',
+			  			  left: "20",
+			  			  top: '-5',
+			  			  textStyle: {
+			  			    color: "#FFFFFF",
+			  			    fontWeight: '400',
+			  			    fontSize: 12,
+			  			    lineHeight: 24, // 行高
+			  			  }
+			    },
+				legend: {
+					itemHeight: 10,
+					itemWidth: 10,
+					textStyle: {
+					    color: '#ffffff' // 设置图例文字颜色为深灰色
+					},
+					right:'0',
+				  data: ['本月告警数', '上月告警数']
+				},
+			  series: [
+			    {
+					name:'本月告警数',
+			      type: 'bar',
+			      showBackground: true,
+				  barWidth:6,
+			      itemStyle: {
+					barBorderRadius:[3, 3, 0, 0],
+			        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+			          { offset: 0.2, color: '#3A6DFF' },
+			          { offset: 0.5, color: 'rgba(0,240,255,0.45)' },
+			          { offset: 1, color: 'rgba(115,219,232,0.2)' }
+			        ]),
+					borderColor: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+					  { offset: 0.2, color: '#3A6DFF' },
+					  { offset: 0.5, color: 'rgba(0,240,255,0.45)' },
+					  { offset: 1, color: 'rgba(115,219,232,0.2)' }
+					])
+			      },
+			      // emphasis: {
+			      //   itemStyle: {barBorderRadius:[3, 3, 0, 0],
+			      //     color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+			      //      { offset: 0.2, color: '#3A6DFF' },
+			      //      { offset: 0.5, color: 'rgba(0,240,255,0.45)' },
+			      //      { offset: 1, color: 'rgba(115,219,232,0.2)' }
+			      //     ])
+			      //   }
+			      // },
+			      data: gjzs
+			    },
+				{
+					name:'上月告警数',
+				  type: 'bar',
+				  showBackground: true,
+				   barWidth:6,
+				   
+				  itemStyle: {
+					  barBorderRadius:[3,3, 0, 0],
+				    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+				      { offset: 0.2, color: '#FFFC00' },
+				      { offset: 0.5, color: 'rgba(255,126,0,0.45)' },
+				      { offset: 1, color: 'rgba(232,181,115,0.2)' }
+				    ])
+				  },
+				  emphasis: {
+				    itemStyle: {barBorderRadius:[3, 3, 0, 0],
+				      color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+				       { offset: 0.2, color: '#FFFC00' },
+				       { offset: 0.5, color: 'rgba(255,126,0,0.45)' },
+				       { offset: 1, color: 'rgba(232,181,115,0.2)' }
+				      ])
+				    }
+				  },
+				  data: bfb
+				}
+			  ]
+	  },true)
+	}
+  }
+}
+</script>
+<style lang="less" scoped>
+
+</style>

+ 218 - 0
src/views/pages/components/lineChart.vue

@@ -0,0 +1,218 @@
+<template>
+	<div :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+	import * as echarts from 'echarts';
+	import chartMixin from '@/mixins/ChartMixin';
+	export default {
+		mixins: [chartMixin],
+		props: {
+			className: {
+				type: String,
+				default: 'chart'
+			},
+			width: {
+				type: String,
+				default: '100%'
+			},
+			height: {
+				type: String,
+				default: '100%'
+			},
+			chartData: {
+				type: Object,
+				required: true
+			}
+		},
+		data() {
+			return {
+				chart: null
+			}
+		},
+		watch: {
+			chartData: {
+				deep: true,
+				handler(val) {
+					this.setOptions(val)
+				}
+			}
+		},
+		mounted() {
+			this.$nextTick(() => {
+				this.initChart()
+			})
+		},
+		beforeDestroy() {
+			if (!this.chart) {
+				return
+			}
+			this.chart.dispose()
+			this.chart = null
+		},
+		methods: {
+			initChart() {
+				this.chart = echarts.init(this.$el, 'macarons')
+				this.setOptions(this.chartData)
+			},
+			setOptions({
+				date,
+				gjzs,
+				yxgj
+			} = {}) {
+				this.chart.setOption({
+					title: {
+						text: '件',
+						left: "12",
+						top: 'top',
+						textStyle: {
+							color: "#FFFFFF",
+							fontWeight: '400',
+							fontSize: 12,
+							lineHeight: 24, // 行高
+						}
+					},
+					xAxis: {
+						data: date,
+						boundaryGap: false,
+						axisTick: {
+							show: false
+						},
+						splitLine: {
+							show: false,
+							lineStyle: {
+								color: '#464D71',
+								width: 1,
+								type: 'solid'
+							}
+						},
+						axisLabel: {
+							color: '#fff' // 标签颜色设置为白色,增加可读性
+						}
+					},
+					grid: {
+						left: 10,
+						right: 10,
+						bottom: 20,
+						top: 30,
+						containLabel: true,
+					},
+					tooltip: {
+						trigger: 'axis',
+						axisPointer: {
+							type: 'cross'
+						},
+						padding: [5, 10]
+					},
+					yAxis: {
+						axisLine: {
+							show: false //隐藏y坐标轴
+						},
+						axisTick: {
+							show: false //隐藏y刻度
+						},
+
+						splitLine: {
+							show: false,
+							lineStyle: {
+								color: '#464D71',
+								width: 1,
+								type: 'solid'
+							}
+						},
+						axisLabel: {
+							color: '#fff' // 标签颜色设置为白色,增加可读性
+						},
+						splitArea: {
+							show: false,
+							areaStyle: {
+								color: [
+									'#1E586B',
+								]
+							}
+						},
+						
+					},
+					legend: {
+						icon: 'circle',
+						itemHeight: 5,
+						itemWidth: 5,
+						textStyle: {
+							color: '#ffffff' // 设置图例文字颜色为深灰色
+						},
+						data: ['告警总数', '越线告警']
+					},
+					series: [{
+							name: '告警总数',
+							itemStyle: {
+								normal: {
+									color: '#FFAB2B',
+									lineStyle: {
+										color: '#FFAB2B',
+										width: 1
+									}
+								}
+							},
+							symbol: 'none',
+							// smooth: true,
+							type: 'line',
+							data: gjzs,
+							areaStyle: {
+								color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+											offset: 0,
+											color: '#FFAB2B'
+										},
+										{
+											offset: 0.6,
+											color: '#FAAD1470'
+										},
+										{
+											offset: 1,
+											color: '#FAAD1410'
+										}
+									],
+									false)
+							},
+						},
+						{
+							name: '越线告警',
+							smooth: true,
+							type: 'line',
+							itemStyle: {
+								normal: {
+									color: '#00FEFE',
+									lineStyle: {
+										color: '#00FEFE',
+										width: 1
+									},
+								}
+							},
+							symbol: 'none',
+							data: yxgj,
+							areaStyle: {
+								color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+											offset: 0,
+											color: '#00F2F3'
+										},
+										{
+											offset: 0.6,
+											color: '#2B98F770'
+										},
+										{
+											offset: 1,
+											color: '#2B98F710'
+										}
+									],
+									false
+								)
+							},
+						}
+					]
+				}, true)
+			}
+		}
+	}
+</script>
+<style lang="less" scoped>
+
+</style>

+ 191 - 0
src/views/pages/components/ltbarChart.vue

@@ -0,0 +1,191 @@
+<template>
+	<div :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+	import * as echarts from 'echarts';
+	import chartMixin from '@/mixins/ChartMixin';
+	export default {
+		mixins: [chartMixin],
+		props: {
+			className: {
+				type: String,
+				default: 'chart'
+			},
+			width: {
+				type: String,
+				default: '100%'
+			},
+			height: {
+				type: String,
+				default: '100%'
+			},
+			chartData: {
+				type: Object,
+				required: true
+			}
+		},
+		data() {
+			return {
+				chart: null
+			}
+		},
+		watch: {
+			chartData: {
+				deep: true,
+				handler(val) {
+					this.setOptions(val)
+				}
+			}
+		},
+		mounted() {
+			this.$nextTick(() => {
+				this.initChart()
+			})
+		},
+		beforeDestroy() {
+			if (!this.chart) {
+				return
+			}
+			this.chart.dispose()
+			this.chart = null
+		},
+		methods: {
+			initChart() {
+				this.chart = echarts.init(this.$el, 'macarons')
+				this.setOptions(this.chartData)
+			},
+			setOptions({date,gjzs,} = {}) {
+				this.chart.setOption({
+					color: ["#3398DB"],
+					xAxis: {
+						data: date,
+						axisLabel: {
+							color: "#FFFFFF",
+						},
+						axisLine: {
+							lineStyle: {
+								color: "#8CBAEE"
+							}
+						},
+						axisTick: {
+							show: false
+						}
+					},
+					yAxis: {
+						axisLabel: {
+							color: "#8CBAEE"
+						},
+						axisLine: {
+							lineStyle: {
+								color: "#8CBAEE"
+							}
+						},
+						axisTick: {
+							show: false
+						},
+						splitLine: {
+							lineStyle: {
+								color: "#0B0842"
+							}
+						}
+					},
+					label: {
+					show: true,
+					position: "top", // 展示在柱子的上方
+					color: "#333",
+					},
+					// 立方体柱状
+					series: [{
+						type: "custom",
+						data: gjzs,
+						renderItem: (params, api) => {
+							const basicsCoord = api.coord([api.value(0), api.value(1)]);
+							const topBasicsYAxis = basicsCoord[1];
+							const basicsXAxis = basicsCoord[0];
+							const bottomYAxis = api.coord([api.value(0), 0])[1];
+							return {
+								type: "group",
+								children: [
+									// 左侧
+									{
+										type: "polygon",
+										shape: {
+											points: [
+												[basicsXAxis - 10, topBasicsYAxis - 4],
+												[basicsXAxis - 10, bottomYAxis],
+												[basicsXAxis, bottomYAxis],
+												[basicsXAxis, topBasicsYAxis]
+											]
+										},
+										style: {
+											fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+													offset: 0,
+													color: '#387BFE'
+												},
+												{
+													offset: 1,
+													color: '#7CB1FD'
+												}
+											])
+										}
+									},
+									// 右侧
+									{
+										type: "polygon",
+										shape: {
+											points: [
+												[basicsXAxis, topBasicsYAxis],
+												[basicsXAxis, bottomYAxis],
+												[basicsXAxis + 10, bottomYAxis],
+												[basicsXAxis + 10, topBasicsYAxis - 4]
+											]
+										},
+										style: {
+											fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+													offset: 0,
+													color: '#387BFE'
+												},
+												{
+													offset: 1,
+													color: '#7CB1FD'
+												}
+											])
+										}
+									},
+									// 顶部
+									{
+										type: "polygon",
+										shape: {
+											points: [
+												[basicsXAxis, topBasicsYAxis],
+												[basicsXAxis - 10, topBasicsYAxis - 4],
+												[basicsXAxis, topBasicsYAxis - 8],
+												[basicsXAxis + 10, topBasicsYAxis - 4]
+											]
+										},
+										style: {
+											fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+													offset: 0,
+													color: '#387BFE'
+												},
+												{
+													offset: 1,
+													color: '#7CB1FD'
+												}
+											])
+										}
+									}
+								]
+							};
+						}
+					}]
+
+				}, true)
+			}
+		}
+	}
+</script>
+<style lang="less" scoped>
+
+</style>

+ 119 - 0
src/views/pages/components/morepieChart.vue

@@ -0,0 +1,119 @@
+<template>
+	<div :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+	import * as echarts from 'echarts';
+	import chartMixin from '@/mixins/ChartMixin';
+//灰色部分统一设置颜色
+var placeHolderStyle = {
+  normal: {
+    color: '#001428'
+  },
+  emphasis: {
+    color: '#001428'
+  }
+}
+	export default {
+		mixins: [chartMixin],
+		props: {
+			className: {
+				type: String,
+				default: 'chart'
+			},
+			width: {
+				type: String,
+				default: '100%'
+			},
+			height: {
+				type: String,
+				default: '100%'
+			},
+			chartData: {
+				type: Array,
+				required: true
+			}
+		},
+		data() {
+			return {
+				chart: null,
+				option:{},
+				colorArr: [
+				        "#06C770", "#FFAB2B", "#F59FBC", "#3FBCEF", "#7CC623", "#FF6969",
+				],
+				//圆环中心位置
+				positionArr: [
+				        ["18%", "25%"],
+				        ["50%", "25%"],
+				        ["82%", "25%"],
+				        ["18%", "75%"],
+				        ["50%", "75%"],
+				        ["82%", "75%"],
+				      ],
+			}
+		},
+		watch: {
+			chartData: {
+				deep: true,
+				handler(val) {
+					this.setOptions(val)
+				}
+			}
+		},
+		mounted() {
+			this.$nextTick(() => {
+				this.initChart()
+			})
+		},
+		beforeDestroy() {
+			if (!this.chart) {
+				return
+			}
+			this.chart.dispose()
+			this.chart = null
+		},
+		methods: {
+			initChart() {
+				this.chart = echarts.init(this.$el, 'macarons')
+				this.setOptions(this.chartData)
+			},
+			setOptions(chartData) {
+				let seriesArr = chartData.map((item, index) => {
+					return {
+						name: item.name,
+						type: "pie",
+						radius: [30, 35], // 圆环饼图内外直径的大小
+						center: this.positionArr[index], // 饼图的位置——正中心
+						label: {
+							show: true, // 显示饼图每块区域的名称
+							position: "center", // 标签文字显示在圆环中间
+							color: this.colorArr[index], // 每个饼图的标签文字颜色
+						},
+						labelLine: {
+							show: false, // 隐藏名字和图之间默认的一条线
+						},
+						data: [{
+								value: item.value,
+								name: item.value + "%", //lable显示的文字内容
+								itemStyle: {
+									color: this.colorArr[index], //该区域对应渐变色
+								}
+							},
+							{
+								// name: "空白",
+								// name: item.value + "%" + "\n\n" + item.name, //lable显示的文字内容
+								value: 100-item.value,
+								itemStyle: placeHolderStyle,
+							},
+						],
+					};
+				})
+				this.option.series = seriesArr
+				this.chart.setOption(this.option);
+			}
+		}
+	}
+</script>
+<style lang="less" scoped>
+
+</style>

+ 119 - 0
src/views/pages/components/morepieCharts.vue

@@ -0,0 +1,119 @@
+<template>
+	<div :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+	import * as echarts from 'echarts';
+	import chartMixin from '@/mixins/ChartMixin';
+//灰色部分统一设置颜色
+var placeHolderStyle = {
+  normal: {
+    color: '#001428'
+  },
+  emphasis: {
+    color: '#001428'
+  }
+}
+	export default {
+		mixins: [chartMixin],
+		props: {
+			className: {
+				type: String,
+				default: 'chart'
+			},
+			width: {
+				type: String,
+				default: '100%'
+			},
+			height: {
+				type: String,
+				default: '100%'
+			},
+			chartData: {
+				type: Array,
+				required: true
+			}
+		},
+		data() {
+			return {
+				chart: null,
+				option:{},
+				colorArr: [
+				        "#06C770", "#FFAB2B", "#F59FBC", "#3FBCEF", "#7CC623", "#FF6969",
+				],
+				//圆环中心位置
+				positionArr: [
+				        ["18%", "25%"],
+				        ["50%", "25%"],
+				        ["82%", "25%"],
+				        ["18%", "75%"],
+				        ["50%", "75%"],
+				        ["82%", "75%"],
+				      ],
+			}
+		},
+		watch: {
+			chartData: {
+				deep: true,
+				handler(val) {
+					this.setOptions(val)
+				}
+			}
+		},
+		mounted() {
+			this.$nextTick(() => {
+				this.initChart()
+			})
+		},
+		beforeDestroy() {
+			if (!this.chart) {
+				return
+			}
+			this.chart.dispose()
+			this.chart = null
+		},
+		methods: {
+			initChart() {
+				this.chart = echarts.init(this.$el, 'macarons')
+				this.setOptions(this.chartData)
+			},
+			setOptions(chartData) {
+				let seriesArr = chartData.map((item, index) => {
+					return {
+						name: item.name,
+						type: "pie",
+						radius: [30, 35], // 圆环饼图内外直径的大小
+						center: this.positionArr[index], // 饼图的位置——正中心
+						label: {
+							show: true, // 显示饼图每块区域的名称
+							position: "center", // 标签文字显示在圆环中间
+							color: this.colorArr[index], // 每个饼图的标签文字颜色
+						},
+						labelLine: {
+							show: false, // 隐藏名字和图之间默认的一条线
+						},
+						data: [{
+								value: item.value,
+								name: item.value + "%", //lable显示的文字内容
+								itemStyle: {
+									color: this.colorArr[index], //该区域对应渐变色
+								}
+							},
+							{
+								// name: "空白",
+								// name: item.value + "%" + "\n\n" + item.name, //lable显示的文字内容
+								value: 100-item.value,
+								itemStyle: placeHolderStyle,
+							},
+						],
+					};
+				})
+				this.option.series = seriesArr
+				this.chart.setOption(this.option);
+			}
+		}
+	}
+</script>
+<style lang="less" scoped>
+
+</style>

+ 258 - 0
src/views/pages/components/moreringChart.vue

@@ -0,0 +1,258 @@
+<template>
+	<div :class="className" :style="{height:height,width:width}" id="myChartZyzxl" />
+</template>
+
+<script>
+	import * as echarts from 'echarts';
+	import chartMixin from '@/mixins/ChartMixin';
+	export var data1 = [{
+			value: 44,
+			name: '低等级'
+		},
+		{
+			value: 68,
+			name: '中等级'
+		},
+		{
+			value: 34,
+			name: '高等级'
+		},
+	]
+
+	export default {
+		mixins: [chartMixin],
+		props: {
+			className: {
+				type: String,
+				default: 'chart'
+			},
+			width: {
+				type: String,
+				default: '120px'
+			},
+			height: {
+				type: String,
+				default: '180px'
+			},
+			chartData: {
+				type: Object,
+				required: true
+			}
+		},
+		data() {
+			return {
+				timer: 0,
+				chart: ''
+			}
+		},
+		watch: {
+			chartData: {
+				deep: true,
+				handler(val) {
+					this.setOption(val)
+				}
+			}
+		},
+		mounted() {
+			// this.$nextTick(() => {
+			this.initChart();
+			// })
+		},
+		beforeDestroy() {
+
+		},
+		methods: {
+			initChart() {
+				this.chart = echarts.init(document.getElementById('myChartZyzxl'));
+				this.setOptions(this.chartData)
+				window.addEventListener('resize', function() {
+					this.chart.resize()
+				})
+			},
+			setOptions({
+				ddj,
+				zdj,
+				gdj
+			} = {}) {
+				let total = parseInt(ddj) + parseInt(zdj) + parseInt(gdj);
+				this.chart.setOption({
+					color: [
+						"#0096FF",
+						"#00FFFF",
+						"#FFBB37",
+						"#7948EA"
+					],
+					graphic: [ // 使用 graphic 组件添加文本
+						{
+							type: 'text', // 文本类型
+							left: 'center', // 文本水平居中
+							top: '25%', // 文本垂直居中
+							style: {
+								text: ['{a|总告警数\n}{b|146}{c|次}'], // 多行文本,每行之间用 \n 分隔
+								// fontSize: 14, // 字体大小
+								fill: '#FFFFFF', // 第一行文本颜色
+								rich: { // 富文本功能,用于设置不同样式的文本段
+									a: {
+										fontSize: 14,color: '#FFFFFF',align: 'center',lineHeight: 20
+									},
+									c: {
+										fontSize: 10,color: '#FFFFFF',align: 'center',lineHeight: 20
+									},
+									// 需要渐变高亮的文字样式
+									b: { 
+										fontSize: 14,
+									    align: 'center',
+										color:'#00F8F9',
+										color: {
+												type: 'linear',
+												x: 0,
+												y: 0,
+												x2: 1,
+												y2: 0,
+												colorStops: [{offset: 0,color: '#00F8F9'}, {offset: 1,color: '#FFFFFF'}]
+											},
+										
+									},
+								}
+							},
+						}
+					],
+					legend: {
+						orient: 'horizontal',
+						left: 0, // 设置图例在容器中的水平位置
+						bottom: 12,
+						itemHeight: 10,
+						itemWidth: 10,
+						// width: 10, //图例的宽度,想要对齐这个必须要有
+						itemGap: 6, //各组的间距
+						data: ['低等级', '中等级', '高等级'],
+						textStyle: {
+							fontSize: 12,
+							color: '#ffffff' // 设置图例文字颜色为深灰色
+						},
+					},
+					series: [
+						// A版块
+						{
+							name: "低等级",
+							type: "pie",
+							radius: ["85%", "90%"],
+							center: ["50%", "34%"],
+							//环的位置
+							// label: {
+							// 	show: false,
+							// 	position: "center",
+							// },
+							labelLine: {
+								normal: {
+									show: false,
+								},
+							},
+							data: [{
+									value: ddj, //需要显示的数据
+									name: '低等级',
+									itemStyle: {
+										normal: {
+											color: "#0096FF",
+										},
+									},
+								},
+								{
+									value: total - ddj,
+									//不需要显示的数据,颜色设置成和背景一样
+									itemStyle: {
+										normal: {
+											color: "#3C4E78",
+										},
+									},
+								},
+							],
+						},
+						// B版块
+						{
+							name: "中等级",
+							type: "pie",
+							radius: ["70%", "75%"],
+							center: ["50%", "34%"],
+							labelLine: {
+								normal: {
+									show: false,
+								},
+							},
+							data: [{
+									value: zdj, //需要显示的数据
+									name: '中等级',
+									itemStyle: {
+										normal: {
+											color: "#00FFFF",
+										},
+									},
+								},
+								{
+									value: total - zdj,
+									//不需要显示的数据,颜色设置成和背景一样
+									itemStyle: {
+										normal: {
+											color: "#3C4E78",
+										},
+									},
+								},
+							],
+						},
+						// C版块
+						{
+							name: "高等级",
+							type: "pie",
+							radius: ["55%", "60%"],
+							center: ["50%", "34%"],
+							labelLine: {
+								normal: {
+									show: false,
+								},
+							},
+							data: [{
+									value: gdj, //需要显示的数据
+									name: '高等级',
+									itemStyle: {
+										normal: {
+											color: "#FFBB37",
+										},
+									},
+								},
+								{
+									value: total - gdj,
+									//不需要显示的数据,颜色设置成和背景一样
+									itemStyle: {
+										normal: {
+											color: "#3C4E78",
+										},
+									},
+								},
+							],
+						},
+
+					],
+				})
+			},
+			// 		initChart() {
+			// 			var myChart = echarts.init(document.getElementById('myChartZyzxl'));
+			// 			// total 和 salary的数据就是各个环的数据
+			// 			// let salayry1 = 44;
+			// 			// let salayry2 = 68;
+			// 			// let salayry3 = 34;
+			// 			let total = parseInt(ddj) + parseInt(zdj) + parseInt(gdj) ;
+			// 			// console.log(total, '222')
+			// 			let option = {
+
+			// 			};
+			// 			myChart.setOption(option);
+
+			// 			// window.addEventListener('resize', myChart.resize);
+
+
+			// 		},
+
+
+		}
+	}
+</script>

+ 123 - 0
src/views/pages/components/radarChart.vue

@@ -0,0 +1,123 @@
+<template>
+  <div :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import * as echarts from 'echarts';
+import chartMixin from '@/mixins/ChartMixin';
+export default {
+  mixins: [chartMixin],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '100%'
+    },
+	chartData: {
+	  type: Object,
+	  required: true
+	}
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  watch: {
+    chartData: {
+      deep: true,
+      handler(val) {
+        this.setOptions(val)
+      }
+    }
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initChart()
+    })
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(this.$el, 'macarons')
+		this.setOptions(this.chartData) 
+    },
+	setOptions({ date,gjzs,} = {}) {
+	  this.chart.setOption({
+		   color: ['#FFBB37',],
+		   // title: {
+		   //    text: 'Basic Radar Chart'
+		   //  },
+		    // legend: {
+		    //   data: ['Allocated Budget', 'Actual Spending']
+		    // },
+		    radar: {
+		      // shape: 'circle',
+			  indicator: [
+			          { text: date[0] },
+			          { text: date[1] },
+			          { text: date[2] },
+			          { text: date[3] },
+			          { text: date[4] }
+			  ],
+				splitLine : { //网格颜色设置
+			        show : true,
+			        lineStyle : {
+			            width : 1,
+			             color : 'rgba(194, 201, 230, 0.2)',
+			         },
+			   },
+			   splitArea : { //网格颜色设置
+			        show : true,
+			        lineStyle : {
+			            width : 1,
+			             color : 'rgba(194, 201, 230, 0.2)',
+			         },
+			   },
+			   splitArea : {  //设置图表颜色,show的值为true
+			        show : false,
+			    },
+				nameGap : 2,
+				
+		    },
+		    series: [	
+		      {
+		        type: 'radar',
+				 emphasis: {
+				        lineStyle: {
+				          width: 4
+				        }
+				      },
+				symbol:'none',
+		        data: [
+		          {
+		            value: gjzs,
+		            name: 'Allocated Budget',
+					areaStyle: {
+					   color: 'rgba(255, 228, 52, 0.6)'
+					}
+		          },
+		        ],
+		      }
+		    ]
+	  },true)
+	}
+  }
+}
+</script>
+<style lang="less" scoped>
+
+</style>

+ 208 - 0
src/views/pages/components/table - 副本.vue

@@ -0,0 +1,208 @@
+<template>
+  <div class="scrolling-data-container">
+    <!-- 固定顶部 -->
+    <div class="fixed-header">
+      <h3>数据展示</h3>
+    </div>
+    
+    <!-- 滚动数据区域 -->
+    <div class="scrolling-data-wrapper" ref="scrollWrapper">
+      <div 
+        class="data-item" 
+        v-for="(item, index) in visibleData" 
+        :key="item.id"
+        :style="{ height: itemHeight + 'px' }"
+      >
+        <el-card shadow="hover" class="data-card">
+          <div class="item-content">
+            <span class="item-title">{{ item.title }}</span>
+            <span class="item-value">{{ item.value }}</span>
+          </div>
+        </el-card>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ScrollingData',
+  props: {
+    data: {
+      type: Array,
+      required: true
+    },
+    itemHeight: {
+      type: Number,
+      default: 28
+    },
+    visibleCount: {
+      type: Number,
+      default: 5
+    },
+    refreshInterval: {
+      type: Number,
+      default: 30000 // 30秒
+    },
+    scrollInterval: {
+      type: Number,
+      default: 3000 // 3秒滚动一次
+    }
+  },
+  data() {
+    return {
+      currentIndex: 0,
+      scrollTimer: null,
+      refreshTimer: null,
+      isScrolling: false
+    }
+  },
+  computed: {
+    visibleData() {
+      // 如果数据不足5条,直接显示所有数据
+      if (this.data.length <= this.visibleCount) {
+        return this.data
+      }
+      
+      // 计算当前应该显示的数据范围
+      let endIndex = this.currentIndex + this.visibleCount
+      if (endIndex > this.data.length) {
+        // 如果超出数组长度,从开头补足
+        return [
+          ...this.data.slice(this.currentIndex),
+          ...this.data.slice(0, endIndex - this.data.length)
+        ]
+      } else {
+        return this.data.slice(this.currentIndex, endIndex)
+      }
+    }
+  },
+  mounted() {
+    this.initScroll()
+    this.initRefresh()
+  },
+  beforeDestroy() {
+    this.clearTimers()
+  },
+  methods: {
+    initScroll() {
+      this.clearScrollTimer()
+      
+      // 如果数据不足5条,不需要滚动
+      if (this.data.length <= this.visibleCount) {
+        return
+      }
+      
+      this.scrollTimer = setInterval(() => {
+        this.scrollToNext()
+      }, this.scrollInterval)
+    },
+    
+    scrollToNext() {
+      if (this.isScrolling) return
+      
+      this.isScrolling = true
+      const wrapper = this.$refs.scrollWrapper
+      
+      // 添加过渡效果
+      wrapper.style.transition = 'transform 0.5s ease-in-out'
+      wrapper.style.transform = `translateY(-${this.itemHeight}px)`
+      
+      // 动画结束后更新数据
+      setTimeout(() => {
+        wrapper.style.transition = 'none'
+        wrapper.style.transform = 'translateY(0)'
+        
+        // 更新当前索引
+        this.currentIndex = (this.currentIndex + 1) % this.data.length
+        
+        // 强制重新渲染以确保DOM更新
+        this.$nextTick(() => {
+          this.isScrolling = false
+        })
+      }, 500)
+    },
+    
+    initRefresh() {
+      this.clearRefreshTimer()
+      this.refreshTimer = setInterval(() => {
+        this.$emit('refresh-data')
+      }, this.refreshInterval)
+    },
+    
+    clearScrollTimer() {
+      if (this.scrollTimer) {
+        clearInterval(this.scrollTimer)
+        this.scrollTimer = null
+      }
+    },
+    
+    clearRefreshTimer() {
+      if (this.refreshTimer) {
+        clearInterval(this.refreshTimer)
+        this.refreshTimer = null
+      }
+    },
+    
+    clearTimers() {
+      this.clearScrollTimer()
+      this.clearRefreshTimer()
+    }
+  },
+  watch: {
+    data() {
+      // 数据更新时重置索引
+      this.currentIndex = 0
+      this.initScroll()
+    }
+  }
+}
+</script>
+
+<style scoped>
+.scrolling-data-container {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  overflow: hidden;
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+}
+
+.fixed-header {
+  padding: 15px;
+  background-color: #f5f7fa;
+  border-bottom: 1px solid #ebeef5;
+  position: sticky;
+  top: 0;
+  z-index: 10;
+}
+
+.scrolling-data-wrapper {
+  width: 100%;
+}
+
+.data-item {
+  width: 100%;
+  box-sizing: border-box;
+  padding: 10px;
+}
+
+.data-card {
+  width: 100%;
+}
+
+.item-content {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.item-title {
+  font-weight: bold;
+}
+
+.item-value {
+  color: #409eff;
+}
+</style>

+ 239 - 0
src/views/pages/components/table.vue

@@ -0,0 +1,239 @@
+<template>
+  <div class="scrolling-table-container">
+    <el-table
+      :data="visibleData"
+      :height="tableHeight"
+	  stripe
+      style="width: 100%;background: transparent;"
+      header-row-class-name="fixed-header"
+      ref="scrollTable"
+    >
+	<el-table-column
+	align='center'
+	  prop="date"
+	  label="告警任务"
+	  width="100">
+	</el-table-column>
+	<el-table-column
+	align='center'
+	  prop="name"
+	  label="告警内容"
+	  width="90">
+			  <template slot-scope="scope">
+			             <span :class="[scope.row.name == '1'||scope.row.name == '5'? 'coa' : scope.row.name == '2'? 'cob':scope.row.name == '3'||scope.row.name == '4'? 'coc'  : '']"  >{{ statusFormat(scope.row.name,statusOptions)}}</span>
+			    </template>
+	</el-table-column>
+	<el-table-column
+	align='center'
+	  prop="createTime"
+	  label="告警时间">
+	  <!-- <template slot-scope="scope">
+	    <span>{{ parseTime(scope.row.createTime) }}</span>
+	  </template> -->
+	</el-table-column>
+
+    </el-table>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ScrollingTable',
+  props: {
+    data: {
+      type: Array,
+      required: true,
+      default: () => []
+    },
+	statusOptions:{
+		type: Array,
+		required: true,
+		default: () => []
+	},
+    // columns: {
+    //   type: Array,
+    //   required: true,
+    //   default: () => []
+    // },
+    rowHeight: {
+      type: Number,
+      default: 28 // ElementUI默认行高
+    },
+    visibleRows: {
+      type: Number,
+      default: 5
+    },
+    refreshInterval: {
+      type: Number,
+      default: 30000 // 30秒
+    },
+    scrollInterval: {
+      type: Number,
+      default: 3000 // 3秒滚动一次
+    }
+  },
+  data() {
+    return {
+      currentIndex: 0,
+      scrollTimer: null,
+      refreshTimer: null,
+      isScrolling: false,
+      tableHeight: null,
+    }
+  },
+  computed: {
+    visibleData() {
+      // 如果数据不足visibleRows条,直接显示所有数据
+      if (this.data.length <= this.visibleRows) {
+        return this.data
+      }
+      
+      // 计算当前应该显示的数据范围
+      let endIndex = this.currentIndex + this.visibleRows
+      if (endIndex > this.data.length) {
+        // 如果超出数组长度,从开头补足
+        return [
+          ...this.data.slice(this.currentIndex),
+          ...this.data.slice(0, endIndex - this.data.length)
+        ]
+      } else {
+        return this.data.slice(this.currentIndex, endIndex)
+      }
+    }
+  },
+  mounted() {
+    // this.calculateTableHeight()
+    // this.initScroll()
+    // this.initRefresh()
+    
+    // 监听窗口变化重新计算高度
+    window.addEventListener('resize', this.calculateTableHeight)
+  },
+  beforeDestroy() {
+    this.clearTimers()
+    window.removeEventListener('resize', this.calculateTableHeight)
+  },
+  methods: {
+	  statusFormat(row, list) {
+	    return this.selectDictLabel(list, row);
+	  },
+    calculateTableHeight() {
+      // 计算表格高度 = 表头高度 + 可见行数 * 行高
+      const headerHeight = 48 // ElementUI默认表头高度
+      this.tableHeight = headerHeight + (this.visibleRows * this.rowHeight)
+    },
+    
+    initScroll() {
+      this.clearScrollTimer()
+      
+      // 如果数据不足visibleRows条,不需要滚动
+      if (this.data.length <= this.visibleRows) {
+        return
+      }
+      
+      this.scrollTimer = setInterval(() => {
+        this.scrollToNext()
+      }, this.scrollInterval)
+    },
+    
+    scrollToNext() {
+      if (this.isScrolling) return
+      
+      this.isScrolling = true
+      const tableBody = this.$refs.scrollTable.$el.querySelector('.el-table__body-wrapper')
+      
+      // 添加过渡效果
+      tableBody.style.transition = 'transform 0.5s ease-in-out'
+      tableBody.style.transform = `translateY(-${this.rowHeight}px)`
+      
+      // 动画结束后更新数据
+      setTimeout(() => {
+        tableBody.style.transition = 'none'
+        tableBody.style.transform = 'translateY(0)'
+        
+        // 更新当前索引
+        this.currentIndex = (this.currentIndex + 1) % this.data.length
+        
+        // 强制重新渲染以确保DOM更新
+        this.$nextTick(() => {
+          this.isScrolling = false
+        })
+      }, 500)
+    },
+    
+    initRefresh() {
+      this.clearRefreshTimer()
+      this.refreshTimer = setInterval(() => {
+        this.$emit('refresh-data')
+      }, this.refreshInterval)
+    },
+    
+    clearScrollTimer() {
+      if (this.scrollTimer) {
+        clearInterval(this.scrollTimer)
+        this.scrollTimer = null
+      }
+    },
+    
+    clearRefreshTimer() {
+      if (this.refreshTimer) {
+        clearInterval(this.refreshTimer)
+        this.refreshTimer = null
+      }
+    },
+    
+    clearTimers() {
+      this.clearScrollTimer()
+      this.clearRefreshTimer()
+    }
+  },
+  watch: {
+    data() {
+      // 数据更新时重置索引
+      this.currentIndex = 0
+      this.initScroll()
+    },
+    visibleRows() {
+      this.calculateTableHeight()
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+	.coa{color: #FFBB37;}
+	.cob{color: #0096FF;}
+	.coc{color: #00FFFF;}
+::v-deep .el-table tr{background-color: transparent;color: #fff;}
+::v-deep .el-table td{border:none;font-size: 14px;height: 28px;padding: 0;}
+::v-deep .el-table .el-table__header-wrapper th{background-color: #011C39 !important;color: #fff;padding: 2px 0;height: 27px;box-sizing: border-box;font-size: 14px;border: none;z-index: 10;}
+::v-deep .el-table__row--striped{background: rgba(185, 223, 250, 0.05) !important}
+::v-deep .el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell{background-color: transparent;}
+::v-deep  .el-table .current-row > td {
+  background-color: transparent !important; /* 或者你想要的颜色 */
+}
+::v-deep .el-table--enable-row-hover .el-table__body tr:hover{
+	background-color: transparent !important; /* 或者你想要的颜色 */
+}
+::v-deep .el-table--enable-row-hover .el-table__body tr:hover > td.el-table__cell{
+	background-color: transparent !important; /* 或者你想要的颜色 */
+}
+::v-deep .el-table::before{height: 0;}
+.scrolling-table-container {
+  width: 100%;
+  overflow: hidden;
+}
+
+/* 固定表头样式 */
+.fixed-header {
+  position: sticky;
+  top: 0;
+  z-index: 10;
+  // background-color: #fff;
+}
+
+/* 表格滚动区域样式 */
+.el-table__body-wrapper {
+  overflow: hidden;
+}
+</style>

+ 339 - 3
src/views/pages/index.vue

@@ -1,21 +1,269 @@
 <template>
+	<div class="home">
+		<div class="home-left">
+			<!-- 告警内容 -->
+			<Box title="告警内容统计" count='17'>
+				<div class="boxbg bga mb33">
+					<div class="flexc boxbga">
+						<div class="imgs"><img src="@/assets/images/ltaba.png" class="imga"/></div>
+						<div class="contnum">
+							<div class="f14 cof">越线告警</div>
+							<div class="num">08</div>
+						</div>
+					</div>
+					<div class="flexc boxbga">
+						<div class="imgs"><img src="@/assets/images/ltabb.png" class="imgb"/></div>
+						<div class="contnum">
+							<div class="f14 cof">徘徊告警</div>
+							<div class="num">06</div>
+						</div>
+					</div>
+					<div class="flexc boxbga">
+						<div class="imgs"><img src="@/assets/images/ltabc.png" class="imgc"/></div>
+						<div class="contnum">
+							<div class="f14 cof">陌生人告警</div>
+							<div class="num">02</div>
+						</div>
+					</div>
+					<div class="flexc boxbga">
+						<div class="imgs"><img src="@/assets/images/ltabd.png" class="imgd"/></div>
+						<div class="contnum">
+							<div class="f14 cof">火焰/烟雾告警</div>
+							<div class="num">01</div>
+						</div>
+					</div>
+				</div>
+			</Box>
+			<!-- 年度告警 -->
+			<div class="inbox">
+				<img src="@/assets/images/line.png" class="line"/>
+				<div class="flexc tits">
+					<img src="@/assets/images/tip.png" class="tip"/>
+					<span class="tit">年度告警统计</span>
+					<el-select v-model="value" placeholder="请选择">
+					    <el-option
+					      v-for="item in options"
+					      :key="item.value"
+					      :label="item.label"
+					      :value="item.value">
+					    </el-option>
+					  </el-select>
+				</div>
+				<div class="bgc mb33 h171">
+					<line-chart :chart-data="rllineconut"></line-chart>
+				</div>
+			</div>
+			<!-- 年度告警 -->
+			<div class="inbox">
+				<img src="@/assets/images/line.png" class="line"/>
+				<div class="flexc tits">
+					<img src="@/assets/images/tip.png" class="tip"/>
+					<span class="tit">告警严重程度分析</span>
+					<el-date-picker
+					      v-model="value1"
+					      type="year"
+						  :picker-options="pickerOptions"
+					      placeholder="选择年">
+
+					 </el-date-picker>
+				</div>
+				<!-- <i class="el-icon-caret-bottom"></i>
+				<i class="el-icon-caret-top"></i> -->
+				<div class="bgc h193 mb33" style="padding-left: 18px;padding-top: 13px;padding-right: 15px;display: flex;">
+					<div>
+						<morering-chart :chart-data="ringconut"></morering-chart>
+					</div>
+					<div class="fxbox flex1">
+						<div class="fxlist flext">
+							<span class="cir"></span>
+							<div class=" flex1">
+								<div class="flexc"><span class="fxtit">徘徊告警</span><span class="fxnum">44</span></div>
+							</div>
+						</div>
+						<div class="fxlist flext ca">
+							<span class="cir"></span>
+							<div class=" flex1">
+								<div class="flexc mb5"><span class="fxtit">陌生人告警</span><span class="fxnum">42</span></div>
+								<div class="flexc"><span class="fxtit">越线告警</span><span class="fxnum">26</span></div>
+							</div>
+						</div>
+						<div class="fxlist flext cb">
+							<span class="cir"></span>
+							<div class=" flex1">
+								<div class="flexc mb5"><span class="fxtit">火焰告警</span><span class="fxnum">13</span></div>
+								<div class="flexc"><span class="fxtit">烟雾告警</span><span class="fxnum">21</span></div>
+							</div>
+						</div>
+					</div>
+					<!-- <line-chart :chart-data="rllineconut"></line-chart> -->
+				</div>
+			</div>
+		</div>
+		<div class="home-mid">
+			<div class="bgd" style="height: 421px;width: 711px;margin-bottom: 33px;">
+				<!-- 建筑 -->
+				<!-- 地图 -->
+				<box-center :annexList="annexList" :mapoptiops="mapoptiops"></box-center>
+			</div>
+			<Box title="通道告警比重分析">
+				<!-- /柱状图 -->
+				<div class="flext bgc">
+					<div style="width: 390px;height: 193px;">
+						<ltbar-chart :chart-data="ltbarconut"></ltbar-chart>
+					</div>
+					<!-- 仪表盘 -->
+					<div style="height: 193px;flex: 1;">
+						<morepie-chart :chart-data="looppieData"></morepie-chart>
+					</div>	
+				</div>
+			</Box>
+		</div>
+		<div class="home-right">
+			<Box title="较上月告警数对比">
+				<div class="bgc mb33 h171" >
+					<jbbar-chart :chart-data="jbbarconut"></jbbar-chart>
+				</div>
+			</Box>
+			<!-- 图表 -->
+			<Box title="告警记录">
+				<div class="bgc mb33 scrollable-data-container h171">
+					<box-table :data="dataList" :statusOptions="statusOptions"  @refresh-data="fetchData"></box-table>
+					<!-- <box-table :data="dataList"   @refresh-data="fetchData"></box-table> -->
+				</div>
+			</Box>
+			<!-- 年度告警 -->
+			<div class="inbox">
+				<img src="@/assets/images/line.png" class="line"/>
+				<div class="flexc tits">
+					<img src="@/assets/images/tip.png" class="tip"/>
+					<span class="tit">年度告警统计</span>
+					<el-select v-model="value" placeholder="请选择">
+					    <el-option
+					      v-for="item in options"
+					      :key="item.value"
+					      :label="item.label"
+					      :value="item.value">
+					    </el-option>
+					  </el-select>
+				</div>
+				<div class="bgc mb33 h193">
+					<radar-chart :chart-data="radarData"></radar-chart>
+				</div>
+			</div>
+		</div>
+	</div>
 </template>
 
 <script>
 import {getCameraList} from '@/api/order.js'
+import Box from './components/box'
+import lineChart from "./components/lineChart.vue"
+import moreringChart from "./components/moreringChart.vue"
+import ltbarChart from "./components/ltbarChart.vue"
+import morepieChart from "./components/morepieChart.vue"
+import jbbarChart from "./components/jbbarChart.vue"
+import radarChart from "./components/radarChart.vue"
+import boxTable from "./components/table.vue"
+import boxCenter from "./components/center.vue"
 export default {
-	components:{
+	components:{Box,lineChart,moreringChart,ltbarChart,morepieChart,jbbarChart,radarChart,boxTable,boxCenter
 	},
   data(){
     return {
-		
+		 pickerOptions: {},
+		rllineconut:{date:['3.7','3.12','3.24','3.26','4.11','4.18','4.23',],
+	  gjzs: [10, 20, 30, 11, 4, 9, 30],
+	  yxgj: [40, 61, 18, 70, 1, 43, 32]},//年度告警统计
+	  ringconut:{ddj:44,zdj:68,gdj:34},//年度告警统计
+	  ltbarconut:{date:['通道1','通道2','通道3','通道4','通道5','通道6'],gjzs: [81, 26,72,38, 26, 38],bfb: [25, 25,25,25, 25, 25],},//通道告警比重分析
+	  jbbarconut:{date:['徘徊告警','越线告警','陌生人告警','火焰告警','烟雾告警',],gjzs: [81, 26,72,38, 26,],bfb: [25, 25,25,25, 25,],},//较上月告警数对比
+	  looppieData: [{name: "通道1",value: 100,},{name: "通道2",value: 60,},{name: "通道3",value: 60,},{name: "通道4",value: 60,},{name: "通道5",value: 60,},{name: "通道6",value: 60},],
+	  radarData: {date:['烟雾告警','火焰告警','陌生人告警','徘徊告警','越线告警',],gjzs: [0, 20,18, 26, 60],},
+	  options: [{
+	            value: '选项1',
+	            label: '黄金糕'
+	          }, {
+	            value: '选项2',
+	            label: '双皮奶'
+	          }, {
+	            value: '选项3',
+	            label: '蚵仔煎'
+	          }, {
+	            value: '选项4',
+	            label: '龙须面'
+	          }, {
+	            value: '选项5',
+	            label: '北京烤鸭'
+	          }],
+	          value: '',
+			  value1:'',
+			  mapoptiops:[{value: '1',label: '建筑'}, {value: '2',label: '地图'}],
+			  dataList: [{date: '告警任务04',name: '1',createTime: '25-04-27  09:35:42'}, 
+			  {date: '告警任务01',name: '2',createTime: '25-04-27  09:35:42'}, 
+			  {date: '告警任务02',name: '3',createTime: '25-04-27  09:35:42'}, 
+			  {date: '告警任务01',name: '4',createTime: '25-04-27  09:35:42'}, 
+			  {date: '告警任务01',name: '5',createTime: '25-04-27  09:35:42'}, 
+			  ],
+			  statusOptions:[{dictValue:"1",dictLabel:'烟雾告警'},{dictValue:"2",dictLabel:'徘徊告警'},{dictValue:"3",dictLabel:'陌生人告警'},{dictValue:"4",dictLabel:'越线告警'},{dictValue:"5",dictLabel:'火焰告警'},],
+			allData:[],// 所有数据
+			tableColumns: [
+			        { prop: 'date', label: '日期', width: '100' },
+			        { prop: 'name', label: '姓名', width: '90' },
+			        { prop: 'address', label: '地址' }
+			      ],
+			currentIndex: 0, // 当前显示数据的起始索引
+			refreshInterval: null, // 刷新数据的定时器
+			scrollInterval: null, // 滚动数据的定时器
+			isScrolling: false ,// 是否正在滚动
+			annexList:[[{tit:'全部',id:0,},{tit:'办公区',id:1,}],[{tit:'车位',id:2,},{tit:'大厅',id:3,}]]
 	}
   },
   mounted(){
    // this.getCameraList()
+// this.fetchData()
+  },
+ 
+  created() {
+    // this.getCurrentTime()
+    // this.getDicts("sys_normal_disable").then(response => {
+    //   this.statusOptions = response.data;
+    // });
   },
 
   methods:{
+	  statusFormat(row, list) {
+	    return this.selectDictLabel(list, row);
+	  },
+	  fetchData() {
+	        // 模拟API请求获取数据
+	        setTimeout(() => {
+	          const mockData = []
+	          // const count = Math.floor(Math.random() * 10) + 5 // 5-15条随机数据
+	          const count = Math.floor(Math.random() * 10) // 5-15条随机数据
+	          
+	          for (let i = 0; i < count; i++) {
+	            mockData.push({
+	              date: `告警任务${i + 1}`,
+	              name: `${i >4 ? 2 : i + 1}`,
+	              createTime: `2023-06-${i < 9 ? '0' + (i + 1) : i + 1}`
+	            })
+	          }
+	          
+	          this.dataList = mockData
+	        }, 500)
+	      },
+		getannexListFn(){
+				 getannexListFn().then(res=>{
+				 	if(res.code==200){
+						let newList = [];
+						for (var i = 0; i < res.rows.length; i += 4) {
+							newList.push(res.rows.slice(i, i + 4));
+						}
+						this.annexList=newList
+						// this.annexList=res.rows;
+				 	} 
+				 }) 
+		},
     // 摄像头列表
     getCameraList(){
     		  var params={
@@ -36,7 +284,95 @@ export default {
   }
 }
 </script>
-<style lang='less' scoped>
+<style lang="scss" scoped>
+.coa{color: #FFBB37;}
+.cob{color: #0096FF;}
+.coc{color: #00FFFF;}
+
+
+/* 自定义滚动条样式 */
+.el-table__body-wrapper::-webkit-scrollbar {
+  width: 6px;
+  height: 6px;
+}
+.el-table__body-wrapper::-webkit-scrollbar-thumb {
+  background-color: #c1c1c1;
+  border-radius: 3px;
+}
+.h171{width: 328px;height: 171px;}
+.h193{width: 328px;height: 193px;}
+.flexc{display: flex;align-items: center;}
+.flex1{flex: 1;}
+.flext{display: flex;align-items: flex-start;}
+.f14{font-size: 14px;}
+.mb5{margin-bottom: 5px;}
+.cof{color: #fff;}
+.mb33{margin-bottom: 33px;}
+.home{height: 100vh;
+	display: flex;padding: 37px 20px 44px;
+	.home-left,.home-right{width:330px;}
+	.home-mid{flex: 1;margin-left: 16px;}
+	.home-left{
+		.boxbg{width: 328px;height: 171px;display: flex;flex-wrap: wrap;box-sizing: border-box;
+			&.bga{background: url('~@/assets/images/boxbg.png') no-repeat;background-size: 100% 100%;padding: 25px 12px 1px 12px;}
+			&.bgb{background: linear-gradient(0deg, rgba(9,59,151,0.2) 0%, rgba(4,24,84,0.2) 100%);}
+			.boxbga{width: 50%;margin-bottom: 19px;}
+			.imgs{width: 46px;height: 54px;display: flex;align-items: center;justify-content: center;margin-right: 9px;
+				.imga{width: 46px;height: 54px;}
+				.imgb{width: 45px;height: 54px;}
+				.imgc{width: 46px;height: 53px;}
+				.imgd{width: 45px;height: 53px;}
+			}
+			.contnum{
+				.num{font-weight: bold;font-size: 18px;color: #00D2FF;line-height: 32px;background: linear-gradient(0deg, #48BBF0 0%, #FFFFFF 86.4501953125%, #FFFFFF 100%);-webkit-background-clip: text;-webkit-text-fill-color: transparent}
+			}
+			
+		}
+		.fxbox{margin-left: 18px;
+			.fxlist{border-bottom: 1px dashed #C9CDD6;padding: 8px 0 12px;
+				&.ca{
+					.cir{border-color: #00FFFF;}
+					.fxnum{color: #00FFFF;}
+				}
+				&.cb{
+					.cir{border-color: #FFBB37;}
+					.fxnum{color: #FFBB37;}
+				}
+			}
+			.cir{width: 12px;height: 12px;border-radius: 50%;border: 2px solid #0096FF;margin-right: 8px;flex: 0 0 auto;;margin-top: 4px;	}
+			.fxtit{font-size: 14px;color: #FFFFFF;flex: 0 0 auto;}
+			.fxnum{font-weight: bold;font-size: 14px;color: #0096FF;flex: 1;text-align: right;padding-right: 10px;}
+		}
+	}
+	.home-right{margin-left: 16px;}
+	.bgc{background: url('~@/assets/images/boxbg.png') no-repeat;background-size: 100% 100%;}
+	.bgd{background: url('~@/assets/images/cenbg.png') no-repeat;background-size: 100% 100%;}
+	.bgb{background: linear-gradient(0deg, rgba(9,59,151,0.2) 0%, rgba(4,24,84,0.2) 100%);}
+}
+
+.inbox {
+	
+  .line{width: 100%;height: 2px;display: block;}
+  .tits{padding: 0 14px;color: #FFFFFF;
+	  .tip{width:14px;height: 11px;margin-right: 9px;flex: 0 0 auto;}
+	  .tit{font-weight: 500;font-size: 16px;color: #FFFFFF;flex: 1;}
+	  .cont{font-size: 14px;flex: 0 0 auto;
+		span{ont-weight: bold;font-size: 14px;color: #00FFFF;}
+	  }
+  }
+}
+::v-deep .el-select .el-icon-arrow-up::before {
+  content: "\e78f"; /* 这里使用 Element UI 的 iconfont 图标,例如一个自定义的 iconfont 图标 */
+
+}
+::v-deep .el-input__icon{line-height: 21px;}
+::v-deep .el-input--suffix{width: 100px;border: none;}
+::v-deep .el-input__inner{background-color: transparent;color: #FFFFFF;font-size: 14px;border: none;text-align: right;height: 21px;
+		// &::placeholder{color: #445055;}
+	}
+
+// ::v-deep .el-input__prefix{display: none;}
 
+::v-deep .el-select .el-select__caret{color: #077A7E;}
 </style>
 

+ 253 - 0
src/views/pages/login.vue

@@ -0,0 +1,253 @@
+<template>
+  <div class="login">
+    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
+		<img  src="../../assets/images/head.png" class="headimg"/>
+		<img  src="../../assets/images/logintit.png" class="logintit"/>
+      <!-- <h3 class="title">{{title}}</h3> -->
+      <el-form-item prop="username">
+		  <img src="../../assets/images/login_tit.png" class="login_tit" alt="">
+        <el-input
+          v-model="loginForm.username"
+          type="text"
+          auto-complete="off"
+          placeholder="请输入账号"
+		  class="custom-input"
+        >
+		
+          <!-- <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" /> -->
+        </el-input>
+      </el-form-item>
+      <el-form-item prop="password">
+		  <img src="../../assets/images/login_pwd.png" alt="" class="login_tit">
+        <el-input
+          v-model="loginForm.password"
+          type="password"
+          auto-complete="off"
+          placeholder="请输入密码"
+		  class="custom-input"
+          @keyup.enter.native="handleLogin"
+        >
+          <!-- <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" /> -->
+        </el-input>
+      </el-form-item>
+      <el-form-item prop="code" v-if="captchaEnabled">
+        <el-input
+          v-model="loginForm.code"
+          auto-complete="off"
+          placeholder="验证码"
+          style="width: 63%;"
+		  class="custom-input"
+          @keyup.enter.native="handleLogin"
+        >
+          <!-- <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" /> -->
+        </el-input>
+        <div class="login-code">
+          <img :src="codeUrl" @click="getCode" class="login-code-img"/>
+        </div>
+      </el-form-item>
+      <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
+	  <el-button
+	    :loading="loading"
+	    size="medium"
+	    type="primary"
+	    style="width:100%;"
+	    @click.native.prevent="handleLogin"
+	  >
+	    <span v-if="!loading">登 录</span>
+	    <span v-else>登 录 中...</span>
+	  </el-button>
+	  <div style="float: right;" v-if="register">
+	    <router-link class="link-type" :to="'/register'">立即注册</router-link>
+	  </div>
+      <!-- <el-form-item style="width:100%;padding: 0;border: none;">
+        
+        
+      </el-form-item> -->
+    </el-form>
+    <!--  底部  -->
+    <div class="el-login-footer">
+      <span>Copyright © 2018-2025 ruoyi.vip All Rights Reserved.</span>
+    </div>
+  </div>
+</template>
+
+<script>
+import { getCodeImg } from "@/api/login"
+import Cookies from "js-cookie"
+import { encrypt, decrypt } from '@/utils/jsencrypt'
+
+export default {
+  name: "Login",
+  data() {
+    return {
+      title: process.env.VUE_APP_TITLE,
+      codeUrl: "",
+      loginForm: {
+        username: "admin",
+        password: "admin123",
+        rememberMe: false,
+        code: "",
+        uuid: ""
+      },
+      loginRules: {
+        username: [
+          { required: true, trigger: "blur", message: "请输入您的账号" }
+        ],
+        password: [
+          { required: true, trigger: "blur", message: "请输入您的密码" }
+        ],
+        code: [{ required: true, trigger: "change", message: "请输入验证码" }]
+      },
+      loading: false,
+      // 验证码开关
+      captchaEnabled: true,
+      // 注册开关
+      register: false,
+      redirect: undefined
+    }
+  },
+  watch: {
+    $route: {
+      handler: function(route) {
+        this.redirect = route.query && route.query.redirect
+      },
+      immediate: true
+    }
+  },
+  created() {
+    this.getCode()
+    this.getCookie()
+  },
+  methods: {
+    getCode() {
+      getCodeImg().then(res => {
+        this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled
+        if (this.captchaEnabled) {
+          this.codeUrl = "data:image/gif;base64," + res.img
+          this.loginForm.uuid = res.uuid
+        }
+      })
+    },
+    getCookie() {
+      const username = Cookies.get("username")
+      const password = Cookies.get("password")
+      const rememberMe = Cookies.get('rememberMe')
+      this.loginForm = {
+        username: username === undefined ? this.loginForm.username : username,
+        password: password === undefined ? this.loginForm.password : decrypt(password),
+        rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
+      }
+    },
+    handleLogin() {
+      this.$refs.loginForm.validate(valid => {
+        if (valid) {
+          this.loading = true
+          if (this.loginForm.rememberMe) {
+            Cookies.set("username", this.loginForm.username, { expires: 30 })
+            Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 })
+            Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 })
+          } else {
+            Cookies.remove("username")
+            Cookies.remove("password")
+            Cookies.remove('rememberMe')
+          }
+          this.$store.dispatch("Login", this.loginForm).then(() => {
+            this.$router.push({ path:  "/index" }).catch(()=>{})
+          }).catch(() => {
+            this.loading = false
+            if (this.captchaEnabled) {
+              this.getCode()
+            }
+          })
+        }
+      })
+    }
+  }
+}
+</script>
+<style scoped>
+	
+</style>
+<style rel="stylesheet/scss" lang="scss">
+.custom-input ::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
+	  color: #2882FE;
+	  opacity: 1; /* Firefox */
+	}
+.login {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+  background-image: url("../../assets/images/loginbg.png");
+  background-size: cover;
+}
+.title {
+  margin: 0px auto 30px auto;
+  text-align: center;
+  color: #707070;
+}
+
+.login-form {
+  border-radius: 24px;
+  background: #172036;
+  width: 460px;
+  padding: 64px 70px;
+  box-sizing: border-box;
+  z-index: 1;
+  .headimg{width: 241px;height: 25px;margin: 0 auto 20px;display: block;}
+  .logintit{width: 101px;height: 25px;margin:0 auto 58px;display: block;}
+  .login_tit{width: 16px;height: 18px;}
+  .el-form-item__content{
+	  display: flex;align-items: center;
+	  border-radius: 10px;
+	  border: 2px solid #2882FE;
+	  background: #172036;
+	  padding:0 14px ;
+  }
+  .el-button--medium{height: 44px;background: #2882FE;border-radius: 10px;}
+  .el-form-item__error{color: #FFFFFF;}
+  .el-input {
+    height: 44px;
+    input {
+      height: 44px;
+	  color: #2882FE;
+	  background: #172036;
+	  border: none;
+    }
+  }
+  .input-icon {
+    height: 39px;
+    width: 14px;
+    margin-left: 2px;
+  }
+}
+.login-tip {
+  font-size: 13px;
+  text-align: center;
+  color: #bfbfbf;
+}
+.login-code {
+  width: 33%;
+  height: 44px;
+  float: right;
+  img {
+    cursor: pointer;
+    vertical-align: middle;
+  }
+}
+.el-login-footer {
+  height: 40px;
+  line-height: 40px;
+  position: fixed;
+  bottom: 0;
+  width: 100%;
+  text-align: center;
+  color: #fff;
+  font-family: Arial;
+  font-size: 12px;
+  letter-spacing: 1px;
+}
+.login-code-img {
+  height: 44px;
+}
+</style>

+ 8 - 3
vue.config.js

@@ -31,16 +31,21 @@ module.exports={
     },
 	devServer: {
 		  host: '0.0.0.0',
-		  // port: port,
+		  port: port,
 		  open: true,
 		  proxy: {
 			[process.env.VUE_APP_BASE_API]: {
 			  target: defaultSettings.urls,
 			  changeOrigin: true,
 			  pathRewrite: {
-			    ['^' + process.env.VUE_APP_BASE_API]: process.env.VUE_APP_BASE_API
+			    ['^' + process.env.VUE_APP_BASE_API]: ''
 			  }
 			},
-		  }
+			'^/v3/api-docs/(.*)': {
+			  target: defaultSettings.urls,
+			  changeOrigin: true
+			}
+		  },
+		  disableHostCheck: true
 	},
 }

Деякі файли не було показано, через те що забагато файлів було змінено