Parcourir la source

Merge branch 'dashboard' into 'master'

替换Dashboard主页

See merge request tian-group/skyeye-admin-fe!43
孙宏耀 il y a 2 ans
Parent
commit
984571fda2

+ 10 - 0
package.json

@@ -35,6 +35,7 @@
     "@vueuse/router": "10.6.1",
     "@wangeditor/editor": "5.1.23",
     "@wangeditor/editor-for-vue": "5.1.12",
+    "animate.css": "4.1.1",
     "axios": "0.27.2",
     "blueimp-md5": "2.19.0",
     "canvg": "4.0.1",
@@ -59,7 +60,9 @@
     "qs": "6.11.0",
     "uid": "2.0.2",
     "url-join": "5.0.0",
+    "vant": "4.6.2",
     "vue": "3.3.4",
+    "vue-echarts": "^6.6.8",
     "vue-hooks-plus": "1.8.6",
     "vue-konva": "3.0.2",
     "vue-router": "4.1.2",
@@ -81,6 +84,7 @@
     "@vitejs/plugin-vue-jsx": "1.3.10",
     "@vue/compiler-sfc": "3.3.4",
     "autoprefixer": "10.4.7",
+    "chalk": "5.3.0",
     "colors": "1.4.0",
     "commitizen": "4.2.5",
     "core-js": "3.23.5",
@@ -92,11 +96,15 @@
     "eslint-plugin-prettier": "4.2.1",
     "eslint-plugin-vue": "8.7.1",
     "esno": "0.16.3",
+    "fast-glob": "3.3.1",
     "fs-extra": "10.1.0",
     "gh-pages": "4.0.0",
     "husky": "8.0.1",
+    "less": "4.1.3",
+    "less-loader": "11.1.3",
     "lint-staged": "13.0.3",
     "picocolors": "1.0.0",
+    "postcss-px-to-viewport": "1.1.1",
     "postcss": "8.4.14",
     "prettier": "2.7.1",
     "pretty-quick": "3.1.3",
@@ -112,6 +120,7 @@
     "tailwindcss": "3.3.2",
     "ts-node": "10.9.1",
     "typescript": "4.7.4",
+    "vconsole": "3.15.1",
     "vite": "5.1.3",
     "vite-plugin-compression": "0.5.1",
     "vite-plugin-html": "3.2.2",
@@ -119,6 +128,7 @@
     "vite-plugin-style-import": "2.0.0",
     "vite-plugin-svg-icons": "2.0.1",
     "vite-plugin-vue-setup-extend": "0.4.0",
+    "vite-plugin-vconsole": "1.3.1",
     "vue-eslint-parser": "9.0.3",
     "vue-tsc": "0.35.2"
   },

+ 235 - 19
pnpm-lock.yaml

@@ -32,6 +32,9 @@ dependencies:
   '@wangeditor/editor-for-vue':
     specifier: 5.1.12
     version: 5.1.12(@wangeditor/editor@5.1.23)(vue@3.3.4)
+  animate.css:
+    specifier: 4.1.1
+    version: 4.1.1
   axios:
     specifier: 0.27.2
     version: 0.27.2
@@ -104,9 +107,15 @@ dependencies:
   url-join:
     specifier: 5.0.0
     version: 5.0.0
+  vant:
+    specifier: 4.6.2
+    version: 4.6.2(vue@3.3.4)
   vue:
     specifier: 3.3.4
     version: 3.3.4
+  vue-echarts:
+    specifier: ^6.6.8
+    version: 6.6.9(echarts@5.3.3)(vue@3.3.4)
   vue-hooks-plus:
     specifier: 1.8.6
     version: 1.8.6(vue@3.3.4)
@@ -166,6 +175,9 @@ devDependencies:
   autoprefixer:
     specifier: 10.4.7
     version: 10.4.7(postcss@8.4.14)
+  chalk:
+    specifier: 5.3.0
+    version: 5.3.0
   colors:
     specifier: 1.4.0
     version: 1.4.0
@@ -199,6 +211,9 @@ devDependencies:
   esno:
     specifier: 0.16.3
     version: 0.16.3
+  fast-glob:
+    specifier: 3.3.1
+    version: 3.3.1
   fs-extra:
     specifier: 10.1.0
     version: 10.1.0
@@ -208,6 +223,12 @@ devDependencies:
   husky:
     specifier: 8.0.1
     version: 8.0.1
+  less:
+    specifier: 4.1.3
+    version: 4.1.3
+  less-loader:
+    specifier: 11.1.3
+    version: 11.1.3(less@4.1.3)
   lint-staged:
     specifier: 13.0.3
     version: 13.0.3
@@ -217,6 +238,9 @@ devDependencies:
   postcss:
     specifier: 8.4.14
     version: 8.4.14
+  postcss-px-to-viewport:
+    specifier: 1.1.1
+    version: 1.1.1
   prettier:
     specifier: 2.7.1
     version: 2.7.1
@@ -259,9 +283,12 @@ devDependencies:
   typescript:
     specifier: 4.7.4
     version: 4.7.4
+  vconsole:
+    specifier: 3.15.1
+    version: 3.15.1
   vite:
     specifier: 5.1.3
-    version: 5.1.3(@types/node@17.0.45)(sass@1.53.0)
+    version: 5.1.3(@types/node@17.0.45)(less@4.1.3)(sass@1.53.0)
   vite-plugin-compression:
     specifier: 0.5.1
     version: 0.5.1(vite@5.1.3)
@@ -277,6 +304,9 @@ devDependencies:
   vite-plugin-svg-icons:
     specifier: 2.0.1
     version: 2.0.1(vite@5.1.3)
+  vite-plugin-vconsole:
+    specifier: 1.3.1
+    version: 1.3.1
   vite-plugin-vue-setup-extend:
     specifier: 0.4.0
     version: 0.4.0(vite@5.1.3)
@@ -561,7 +591,6 @@ packages:
     engines: {node: '>=6.9.0'}
     dependencies:
       regenerator-runtime: 0.13.11
-    dev: false
 
   /@babel/template@7.18.6:
     resolution: {integrity: sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw==}
@@ -1639,6 +1668,18 @@ packages:
       nanoid: 3.3.6
     dev: false
 
+  /@vant/popperjs@1.3.0:
+    resolution: {integrity: sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==}
+    dev: false
+
+  /@vant/use@1.6.0(vue@3.3.4):
+    resolution: {integrity: sha512-PHHxeAASgiOpSmMjceweIrv2AxDZIkWXyaczksMoWvKV2YAYEhoizRuk/xFnKF+emUIi46TsQ+rvlm/t2BBCfA==}
+    peerDependencies:
+      vue: ^3.0.0
+    dependencies:
+      vue: 3.3.4
+    dev: false
+
   /@vicons/antd@0.12.0:
     resolution: {integrity: sha512-C0p6aO1EmGG1QHrqgUWQS1No20934OdWSRQshM5NIDK5H1On6tC26U0hT6Rmp40KfUsvhvX5YW8BoWJdNFifPg==}
     dev: false
@@ -1668,7 +1709,7 @@ packages:
       vite: ^2.5.10
       vue: ^3.2.25
     dependencies:
-      vite: 5.1.3(@types/node@17.0.45)(sass@1.53.0)
+      vite: 5.1.3(@types/node@17.0.45)(less@4.1.3)(sass@1.53.0)
       vue: 3.3.4
     dev: true
 
@@ -2191,6 +2232,10 @@ packages:
       uri-js: 4.4.1
     dev: true
 
+  /animate.css@4.1.1:
+    resolution: {integrity: sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==}
+    dev: false
+
   /ansi-escapes@4.3.2:
     resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
     engines: {node: '>=8'}
@@ -2671,6 +2716,11 @@ packages:
       supports-color: 7.2.0
     dev: true
 
+  /chalk@5.3.0:
+    resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
+    engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+    dev: true
+
   /change-case@4.1.2:
     resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==}
     dependencies:
@@ -3008,11 +3058,22 @@ packages:
       safe-buffer: 5.1.2
     dev: true
 
+  /copy-anything@2.0.6:
+    resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
+    dependencies:
+      is-what: 3.14.1
+    dev: true
+
   /copy-descriptor@0.1.1:
     resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==}
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /copy-text-to-clipboard@3.2.0:
+    resolution: {integrity: sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==}
+    engines: {node: '>=12'}
+    dev: true
+
   /core-js@3.23.5:
     resolution: {integrity: sha512-7Vh11tujtAZy82da4duVreQysIoO2EvVrur7y6IzZkH1IHPSekuDi8Vuw1+YKjkbfWLRD7Nc9ICQ/sIUDutcyg==}
     requiresBuild: true
@@ -3573,6 +3634,15 @@ packages:
     resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
     dev: true
 
+  /errno@0.1.8:
+    resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
+    hasBin: true
+    requiresBuild: true
+    dependencies:
+      prr: 1.0.1
+    dev: true
+    optional: true
+
   /error-ex@1.3.2:
     resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
     dependencies:
@@ -4240,8 +4310,8 @@ packages:
   /fast-diff@1.2.0:
     resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==}
 
-  /fast-glob@3.2.12:
-    resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
+  /fast-glob@3.3.1:
+    resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
     engines: {node: '>=8.6.0'}
     dependencies:
       '@nodelib/fs.stat': 2.0.5
@@ -4690,7 +4760,7 @@ packages:
     dependencies:
       array-union: 2.1.0
       dir-glob: 3.0.1
-      fast-glob: 3.2.12
+      fast-glob: 3.3.1
       ignore: 5.2.0
       merge2: 1.4.1
       slash: 3.0.0
@@ -4959,7 +5029,6 @@ packages:
     requiresBuild: true
     dependencies:
       safer-buffer: 2.1.2
-    dev: false
     optional: true
 
   /ieee754@1.2.1:
@@ -5249,6 +5318,10 @@ packages:
     resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==}
     dev: true
 
+  /is-what@3.14.1:
+    resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}
+    dev: true
+
   /is-windows@1.0.2:
     resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
     engines: {node: '>=0.10.0'}
@@ -5448,6 +5521,37 @@ packages:
     resolution: {integrity: sha512-qLTW06GRwb+WMMUXJcGIb0qP4uO0mZLAwgRI82zuCkRmCH1lFsVPmrPzqqHnjKCMu4Jzw6d/R8JxkPw7gkVnuw==}
     dev: false
 
+  /less-loader@11.1.3(less@4.1.3):
+    resolution: {integrity: sha512-A5b7O8dH9xpxvkosNrP0dFp2i/dISOJa9WwGF3WJflfqIERE2ybxh1BFDj5CovC2+jCE4M354mk90hN6ziXlVw==}
+    engines: {node: '>= 14.15.0'}
+    peerDependencies:
+      less: ^3.5.0 || ^4.0.0
+      webpack: ^5.0.0
+    peerDependenciesMeta:
+      webpack:
+        optional: true
+    dependencies:
+      less: 4.1.3
+    dev: true
+
+  /less@4.1.3:
+    resolution: {integrity: sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==}
+    engines: {node: '>=6'}
+    hasBin: true
+    dependencies:
+      copy-anything: 2.0.6
+      parse-node-version: 1.0.1
+      tslib: 2.4.0
+    optionalDependencies:
+      errno: 0.1.8
+      graceful-fs: 4.2.10
+      image-size: 0.5.5
+      make-dir: 2.1.0
+      mime: 1.6.0
+      needle: 3.3.1
+      source-map: 0.6.1
+    dev: true
+
   /levn@0.4.1:
     resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
     engines: {node: '>= 0.8.0'}
@@ -5655,6 +5759,16 @@ packages:
     dependencies:
       '@jridgewell/sourcemap-codec': 1.4.15
 
+  /make-dir@2.1.0:
+    resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
+    engines: {node: '>=6'}
+    requiresBuild: true
+    dependencies:
+      pify: 4.0.1
+      semver: 5.7.1
+    dev: true
+    optional: true
+
   /make-dir@3.1.0:
     resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
     engines: {node: '>=8'}
@@ -5801,6 +5915,14 @@ packages:
       mime-db: 1.52.0
     dev: false
 
+  /mime@1.6.0:
+    resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
+    engines: {node: '>=4'}
+    hasBin: true
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /mimic-fn@2.1.0:
     resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
     engines: {node: '>=6'}
@@ -5926,6 +6048,10 @@ packages:
       minimatch: 3.1.2
     dev: true
 
+  /mutation-observer@1.0.3:
+    resolution: {integrity: sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA==}
+    dev: true
+
   /mute-stream@0.0.8:
     resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
     dev: true
@@ -5982,6 +6108,17 @@ packages:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
     dev: true
 
+  /needle@3.3.1:
+    resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==}
+    engines: {node: '>= 4.4.x'}
+    hasBin: true
+    requiresBuild: true
+    dependencies:
+      iconv-lite: 0.6.3
+      sax: 1.3.0
+    dev: true
+    optional: true
+
   /next-tick@1.1.0:
     resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
     dev: false
@@ -6286,6 +6423,11 @@ packages:
       lines-and-columns: 1.2.4
     dev: true
 
+  /parse-node-version@1.0.1:
+    resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
+    engines: {node: '>= 0.10'}
+    dev: true
+
   /parse-passwd@1.0.0:
     resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==}
     engines: {node: '>=0.10.0'}
@@ -6383,6 +6525,13 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /pify@4.0.1:
+    resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
+    engines: {node: '>=6'}
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /pinia@2.0.16(typescript@4.7.4)(vue@3.3.4):
     resolution: {integrity: sha512-9/LMVO+/epny1NBfC77vnps4g3JRezxhhoF1xLUk8mZkUIxVnwfEAIRiAX8mYBTD/KCwZqnDMqXc8w3eU0FQGg==}
     peerDependencies:
@@ -6497,6 +6646,13 @@ packages:
       postcss: 5.2.18
     dev: true
 
+  /postcss-px-to-viewport@1.1.1:
+    resolution: {integrity: sha512-2x9oGnBms+e0cYtBJOZdlwrFg/mLR4P1g2IFu7jYKvnqnH/HLhoKyareW2Q/x4sg0BgklHlP1qeWo2oCyPm8FQ==}
+    dependencies:
+      object-assign: 4.1.1
+      postcss: 8.4.14
+    dev: true
+
   /postcss-resolve-nested-selector@0.1.1:
     resolution: {integrity: sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==}
     dev: true
@@ -6648,6 +6804,12 @@ packages:
     engines: {node: '>=6'}
     dev: false
 
+  /prr@1.0.1:
+    resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /psl@1.9.0:
     resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
     requiresBuild: true
@@ -6805,7 +6967,6 @@ packages:
 
   /regenerator-runtime@0.13.11:
     resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
-    dev: false
 
   /regex-not@1.0.2:
     resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==}
@@ -6863,6 +7024,10 @@ packages:
     dev: false
     optional: true
 
+  /resize-detector@0.3.0:
+    resolution: {integrity: sha512-R/tCuvuOHQ8o2boRP6vgx8hXCCy87H1eY9V5imBYeVNyNVpuL9ciReSccLj2gDcax9+2weXy3bc8Vv+NRXeEvQ==}
+    dev: false
+
   /resolve-dir@1.0.1:
     resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==}
     engines: {node: '>=0.10.0'}
@@ -7014,6 +7179,12 @@ packages:
       source-map-js: 1.0.2
     dev: true
 
+  /sax@1.3.0:
+    resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
+    requiresBuild: true
+    dev: true
+    optional: true
+
   /saxes@5.0.1:
     resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==}
     engines: {node: '>=10'}
@@ -7494,7 +7665,7 @@ packages:
       css-functions-list: 3.1.0
       debug: 4.3.4
       execall: 2.0.0
-      fast-glob: 3.2.12
+      fast-glob: 3.3.1
       fastest-levenshtein: 1.0.14
       file-entry-cache: 6.0.1
       get-stdin: 8.0.0
@@ -7654,7 +7825,7 @@ packages:
       chokidar: 3.5.3
       didyoumean: 1.2.2
       dlv: 1.1.3
-      fast-glob: 3.2.12
+      fast-glob: 3.3.1
       glob-parent: 6.0.2
       is-glob: 4.0.3
       jiti: 1.18.2
@@ -8060,11 +8231,31 @@ packages:
       spdx-expression-parse: 3.0.1
     dev: true
 
+  /vant@4.6.2(vue@3.3.4):
+    resolution: {integrity: sha512-6EHCCAGM5a9VVzpBg/wZNPDFmJ8T1a4k29DPNcEMW3X670awW3rnD7+/x3dw+bE17JhhSg49V/+fQwBP2iQkAg==}
+    peerDependencies:
+      vue: ^3.0.0
+    dependencies:
+      '@vant/popperjs': 1.3.0
+      '@vant/use': 1.6.0(vue@3.3.4)
+      '@vue/shared': 3.3.4
+      vue: 3.3.4
+    dev: false
+
   /vary@1.1.2:
     resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
     engines: {node: '>= 0.8'}
     dev: true
 
+  /vconsole@3.15.1:
+    resolution: {integrity: sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==}
+    dependencies:
+      '@babel/runtime': 7.20.6
+      copy-text-to-clipboard: 3.2.0
+      core-js: 3.23.5
+      mutation-observer: 1.0.3
+    dev: true
+
   /vite-plugin-compression@0.5.1(vite@5.1.3):
     resolution: {integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==}
     peerDependencies:
@@ -8073,7 +8264,7 @@ packages:
       chalk: 4.1.2
       debug: 4.3.4
       fs-extra: 10.1.0
-      vite: 5.1.3(@types/node@17.0.45)(sass@1.53.0)
+      vite: 5.1.3(@types/node@17.0.45)(less@4.1.3)(sass@1.53.0)
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -8090,12 +8281,12 @@ packages:
       dotenv: 16.0.1
       dotenv-expand: 8.0.3
       ejs: 3.1.8
-      fast-glob: 3.2.12
+      fast-glob: 3.3.1
       fs-extra: 10.1.0
       html-minifier-terser: 6.1.0
       node-html-parser: 5.3.3
       pathe: 0.2.0
-      vite: 5.1.3(@types/node@17.0.45)(sass@1.53.0)
+      vite: 5.1.3(@types/node@17.0.45)(less@4.1.3)(sass@1.53.0)
     dev: true
 
   /vite-plugin-mock@2.9.6(mockjs@1.1.0)(vite@5.1.3):
@@ -8112,10 +8303,10 @@ packages:
       connect: 3.7.0
       debug: 4.3.4
       esbuild: 0.11.3
-      fast-glob: 3.2.12
+      fast-glob: 3.3.1
       mockjs: 1.1.0
       path-to-regexp: 6.2.1
-      vite: 5.1.3(@types/node@17.0.45)(sass@1.53.0)
+      vite: 5.1.3(@types/node@17.0.45)(less@4.1.3)(sass@1.53.0)
     transitivePeerDependencies:
       - rollup
       - supports-color
@@ -8133,7 +8324,7 @@ packages:
       fs-extra: 10.1.0
       magic-string: 0.25.9
       pathe: 0.2.0
-      vite: 5.1.3(@types/node@17.0.45)(sass@1.53.0)
+      vite: 5.1.3(@types/node@17.0.45)(less@4.1.3)(sass@1.53.0)
     dev: true
 
   /vite-plugin-svg-icons@2.0.1(vite@5.1.3):
@@ -8149,11 +8340,15 @@ packages:
       pathe: 0.2.0
       svg-baker: 1.7.0
       svgo: 2.8.0
-      vite: 5.1.3(@types/node@17.0.45)(sass@1.53.0)
+      vite: 5.1.3(@types/node@17.0.45)(less@4.1.3)(sass@1.53.0)
     transitivePeerDependencies:
       - supports-color
     dev: true
 
+  /vite-plugin-vconsole@1.3.1:
+    resolution: {integrity: sha512-fSuk+UROXpxIZsQID7/CSvBxIRO4Zug5P+Qv8H2Rdnl4ks9nAhSCzNBE4amyZhJLJE1Rl4z0EMIOomK8sHLugQ==}
+    dev: true
+
   /vite-plugin-vue-setup-extend@0.4.0(vite@5.1.3):
     resolution: {integrity: sha512-WMbjPCui75fboFoUTHhdbXzu4Y/bJMv5N9QT9a7do3wNMNHHqrk+Tn2jrSJU0LS5fGl/EG+FEDBYVUeWIkDqXQ==}
     peerDependencies:
@@ -8161,10 +8356,10 @@ packages:
     dependencies:
       '@vue/compiler-sfc': 3.3.4
       magic-string: 0.25.9
-      vite: 5.1.3(@types/node@17.0.45)(sass@1.53.0)
+      vite: 5.1.3(@types/node@17.0.45)(less@4.1.3)(sass@1.53.0)
     dev: true
 
-  /vite@5.1.3(@types/node@17.0.45)(sass@1.53.0):
+  /vite@5.1.3(@types/node@17.0.45)(less@4.1.3)(sass@1.53.0):
     resolution: {integrity: sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==}
     engines: {node: ^18.0.0 || >=20.0.0}
     hasBin: true
@@ -8194,6 +8389,7 @@ packages:
     dependencies:
       '@types/node': 17.0.45
       esbuild: 0.19.10
+      less: 4.1.3
       postcss: 8.4.35
       rollup: 4.9.1
       sass: 1.53.0
@@ -8231,6 +8427,26 @@ packages:
       vue: 3.3.4
     dev: false
 
+  /vue-echarts@6.6.9(echarts@5.3.3)(vue@3.3.4):
+    resolution: {integrity: sha512-mojIq3ZvsjabeVmDthhAUDV8Kgf2Rr/X4lV4da7gEFd1fP05gcSJ0j7wa7HQkW5LlFmF2gdCJ8p4Chas6NNIQQ==}
+    requiresBuild: true
+    peerDependencies:
+      '@vue/composition-api': ^1.0.5
+      '@vue/runtime-core': ^3.0.0
+      echarts: ^5.4.1
+      vue: ^2.6.12 || ^3.1.1
+    peerDependenciesMeta:
+      '@vue/composition-api':
+        optional: true
+      '@vue/runtime-core':
+        optional: true
+    dependencies:
+      echarts: 5.3.3
+      resize-detector: 0.3.0
+      vue: 3.3.4
+      vue-demi: 0.13.11(vue@3.3.4)
+    dev: false
+
   /vue-eslint-parser@8.3.0(eslint@8.20.0):
     resolution: {integrity: sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}

+ 265 - 0
src/api/home/home.ts

@@ -0,0 +1,265 @@
+import { http } from '@/utils/http/axios';
+import { ViolationHandleStat } from '@/views/dashboard/home/types';
+
+/** 场景标签信息 */
+export type SceneLabelOrModuleItem = {
+  /** 标签id */
+  id: number;
+  /** 标签代码 */
+  code: string;
+  /** 创建时间 */
+  createdAt: string;
+  /** 0-未删除,大于0-已删除 */
+  isDeleted: number;
+  /** 标签名称 */
+  name: string;
+  /** 说明 */
+  remark: string;
+  /** 状态: 0-正常,1-不正常 */
+  status: number;
+  /** 更新时间 */
+  updatedAt: string;
+};
+
+/** 相机信息 */
+export type CameraInfoItem = {
+  /** 相机id */
+  id: number;
+
+  /** 相机IP */
+  cameraIp: string;
+  /**	相机协议类型 */
+  cameraType: string;
+  /** 相机端口 */
+  cameraPort: string;
+  /** 相机ID */
+  code: string;
+  /** 工位场景Id */
+  workspaceId: string;
+  /** 描述 */
+  remark?: string;
+  /** 相机名称 */
+  name: string;
+  /** 相机MAC地址 */
+  cameraMac: string;
+  /** 车间场景名称 */
+  workshopName: string;
+  /** 工位场景名称 */
+  workspaceName: string;
+  /** 联网状态: 0-启用, 1-禁用 */
+  networkingState: number;
+  /** 是否删除: 0-未删除, 1-删除 */
+  isDeleted: number;
+  /** 状态: 0-启用, 1-禁用 */
+  status: number;
+  /** 用户名 */
+  username?: string;
+  /** 密码 */
+  password?: string;
+  /** 层级类型 */
+  nodeType: string;
+};
+
+/** 工位信息 */
+export type WorkSpaceInfoItem = {
+  /** 工位id */
+  id: number;
+  /** 所属工厂id */
+  workshopId: number;
+  /** 工位名称 */
+  name: string;
+  /** 工位code */
+  code: string;
+  /** 工位描述 */
+  remark: string;
+  /** 状态: 0-启用, 1-禁用 */
+  status: number;
+  /** 创建时间 */
+  createdAt: string;
+  /** 更新时间 */
+  updatedAt: string;
+  /** 	0-未删除,大于0-已删除 */
+  isDeleted: number;
+  /** 工位负责人 */
+  principal: string;
+  /** 排序序号 */
+  serial: number;
+  /** 层级类型 */
+  nodeType: string;
+  /** 下属相机列表 */
+  children: CameraInfoItem[];
+};
+
+/** 工厂信息 */
+export type WorkShopInfoItem = {
+  /** 工厂id */
+  id: number;
+  /** 所属公司id */
+  companyId: number;
+  /** 1-生产安全 2-安全环保 */
+  type: number;
+  /** 工厂名称 */
+  name: string;
+  /** 工厂code */
+  code: string;
+  /** 工厂描述 */
+  remark: string;
+  /** 状态: 0-启用, 1-禁用 */
+  status: number;
+  /** 创建时间 */
+  createdAt: string;
+  /** 更新时间 */
+  updatedAt: string;
+  /** 	0-未删除,大于0-已删除 */
+  isDeleted: number;
+  /** 层级类型 */
+  nodeType: string;
+  /** 下属工位列表 */
+  children: WorkSpaceInfoItem[];
+  /** 场景标签 */
+  labelName: string;
+  /** 场景标签id */
+  sceneLabelId: number;
+  /** 排序序号 */
+  serial: number;
+  /** 车间模板 */
+  workshopModule: SceneLabelOrModuleItem;
+};
+
+/** 公司信息 */
+export type CompanyInfoItem = {
+  /** 公司id */
+  id: number;
+  /** 上级公司ID, 无上级为0 */
+  parentId: number;
+  /** 公司名称 */
+  name: string;
+  /** 公司code */
+  code: string;
+  /** 公司描述 */
+  remark: string;
+  /** 状态: 0-启用, 1-禁用 */
+  status: number;
+  /** 创建时间 */
+  createdAt: string;
+  /** 更新时间 */
+  updatedAt: string;
+  /** 排序序号 */
+  serial: number;
+  /** 	0-未删除,大于0-已删除 */
+  isDeleted: number;
+  /** 层级类型 */
+  nodeType: string;
+  /** 下属工厂列表 */
+  children: WorkShopInfoItem[];
+  /** 场景标签列表 */
+  labelList: SceneLabelOrModuleItem[];
+  /** 场景模板列表 */
+  moduleList: SceneLabelOrModuleItem[];
+};
+
+/** 根据用户权限查询场景树 */
+export const getAuthSceneList = () => {
+  return http.request<CompanyInfoItem[]>({
+    url: '/dataPreview/getList',
+    method: 'get',
+  });
+};
+
+/** 算法信息 */
+export type AlgoInfo = {
+  /**	算法提供编码 */
+  code: string;
+  /** 创建时间 */
+  createdAt: string;
+  /** id */
+  id: number;
+  /** 0-未删除,大于0(时间戳)-已删除 */
+  isDeleted: number;
+  /**	算法名称 */
+  name: string;
+  /** 推送链接提示 */
+  pushLinkPrompt: string;
+  /** 推送语句 */
+  pushStatement: string;
+  /** 描述 */
+  remark: string;
+  /** 前端显示名称 */
+  showName: string;
+  /** 状态: 0-启用, 1-禁用 */
+  status: number;
+  /** 更新时间 */
+  updatedAt: string;
+  /** 展示视频的地址 */
+  url: string;
+};
+
+/** 算法配置信息 */
+export type AlgoConfig = {
+  /** id */
+  id: number;
+  /** 算法id */
+  algoId: number;
+  /** 算法信息 */
+  algoInfo: AlgoInfo;
+  /** 相机id */
+  cameraId: number;
+  /** 创建时间 */
+  createdAt: string;
+  /** 检测频率 */
+  detectionFrequency: number;
+  /** 检测时间 */
+  detectionTime: string;
+  /** 电子围栏: 0-启用, 1-禁用 */
+  electronicFence: number;
+  /** 	0-未删除,大于0(时间戳)-已删除 */
+  isDeleted: number;
+  /** 算法状态: 0-启用, 1-禁用 */
+  status: number;
+  /** 更新时间 */
+  updatedAt: string;
+};
+
+/** 根据相机ID查询算法列表 */
+export const getAlgoByCameraId = (params: { cameraId: number }) => {
+  return http.request<AlgoConfig[]>({
+    url: '/dataPreview/getAlgo',
+    method: 'get',
+    params,
+  });
+};
+
+export type ViolationsQueryParam = {
+  /** 起始日期 */
+  startDate: string;
+  /** 结束日期 */
+  endDate: string;
+  /** 用户名 */
+  userName: string;
+};
+
+export type ViolationCount = {
+  /** 算法违规数量统计 */
+  violationAlgoList: {
+    /** 算法名称 */
+    name: string;
+    /** 算法违规占比 */
+    proportion: number;
+  }[];
+  /** 违规处理情况统计 */
+  statusCountList: {
+    /** 类型 */
+    name: ViolationHandleStat;
+    /** 数量 */
+    value: number;
+  }[];
+};
+
+/** 根据用户权限查询违规记录 */
+export const getViolation = (params: ViolationsQueryParam) => {
+  return http.request<ViolationCount>({
+    url: '/dataPreview/getViolation',
+    method: 'get',
+    params,
+  });
+};

BIN
src/assets/images/date-to.png


+ 47 - 5
src/layout/components/Header/QRcodePopover.vue

@@ -1,10 +1,52 @@
 <template>
-  <el-popover placement="bottom" trigger="hover">
-    <template #reference> 扫描二维码 </template>
-    <img src="~@/assets/images/QRcodeExample.png" />
+  <el-popover
+    popper-style="    
+    border: none;
+    padding: 0px;
+    width: 228px;
+    height: 308px;
+    background: linear-gradient(rgb(238, 246, 250), rgb(125, 194, 255));
+    "
+    placement="bottom"
+    trigger="hover"
+  >
+    <template #reference>
+      <div class="QR-btn">
+        <!-- <el-icon :size="18"><Download /></el-icon> -->
+        <el-button style="background-color: rgb(24, 144, 255); border: none" type="primary"
+          >下载APP</el-button
+        >
+      </div>
+    </template>
+    <div class="QR-body">
+      <div>安全管控平台</div>
+      <img src="~@/assets/images/QRcodeExample.png" />
+      <el-button
+        class="w-full"
+        style="background-color: rgb(24, 144, 255); border: none"
+        type="primary"
+        round
+        >保存到相册</el-button
+      >
+    </div>
   </el-popover>
 </template>
 
-<script lang="ts" setup></script>
+<script lang="ts" setup>
+  // import { Download } from '@element-plus/icons-vue';
+</script>
 
-<style scoped></style>
+<style scoped>
+  .QR-btn {
+    display: flex;
+    align-items: center;
+  }
+
+  .QR-body {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-around;
+    height: 100%;
+    padding: 20px;
+  }
+</style>

+ 25 - 21
src/layout/components/Header/index.vue

@@ -112,25 +112,6 @@
           </el-icon>
         </el-tooltip>
       </div>
-      <!--切换全屏-->
-      <div class="layout-header-trigger layout-header-trigger-min">
-        <el-tooltip placement="bottom" :content="isFullscreen ? '还原' : '全屏'">
-          <el-icon class="el-input__icon" :size="18" v-if="isFullscreen" @click="toggleFullScreen">
-            <FullscreenExitOutlined />
-          </el-icon>
-          <el-icon class="el-input__icon" :size="18" v-else @click="toggleFullScreen">
-            <FullscreenOutlined />
-          </el-icon>
-        </el-tooltip>
-      </div>
-      <!-- 弹出式二维码 -->
-      <div class="layout-header-trigger layout-header-trigger-min">
-        <QRcodePopover />
-      </div>
-      <!--消息-->
-      <div class="layout-header-trigger layout-header-trigger-min notifier-plus">
-        <NotifierProPlus />
-      </div>
       <!-- 个人中心 -->
       <div class="layout-header-trigger layout-header-trigger-min">
         <el-dropdown trigger="hover" @command="avatarSelect">
@@ -160,8 +141,31 @@
           </template>
         </el-dropdown>
       </div>
+      <!-- 弹出式二维码 -->
+      <div class="layout-header-trigger layout-header-trigger-min">
+        <QRcodePopover />
+      </div>
+      <!-- 安全管控平台 -->
+      <div class="layout-header-trigger layout-header-trigger-min">
+        <a href="/skyeye-world" target="_blank">安全管控平台</a>
+      </div>
+      <!--切换全屏-->
+      <!-- <div class="layout-header-trigger layout-header-trigger-min">
+        <el-tooltip placement="bottom" :content="isFullscreen ? '还原' : '全屏'">
+          <el-icon class="el-input__icon" :size="18" v-if="isFullscreen" @click="toggleFullScreen">
+            <FullscreenExitOutlined />
+          </el-icon>
+          <el-icon class="el-input__icon" :size="18" v-else @click="toggleFullScreen">
+            <FullscreenOutlined />
+          </el-icon>
+        </el-tooltip>
+      </div> -->
+      <!--消息-->
+      <!-- <div class="layout-header-trigger layout-header-trigger-min notifier-plus">
+        <NotifierProPlus />
+      </div> -->
       <!--设置-->
-      <div
+      <!-- <div
         id="setting-trigger"
         class="layout-header-trigger layout-header-trigger-min setting-trigger"
         @click="openSetting"
@@ -171,7 +175,7 @@
             <SettingOutlined />
           </el-icon>
         </el-tooltip>
-      </div>
+      </div> -->
     </div>
   </div>
   <!--项目配置-->

+ 31 - 0
src/views/dashboard/home/Home.vue

@@ -0,0 +1,31 @@
+<template>
+  <div class="home-page">
+    <div class="flex">
+      <CameraInfo class="flex-1" :data="sceneData" :get-algoes="getAlgoList" />
+      <AlgoData :data="violationData" :get-violations="getViolationCount" />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  // import { ref } from "vue";
+  import CameraInfo from './components/CameraInfo.vue';
+  import AlgoData from './components/AlgoDataPanel.vue';
+  import useHomeInfo from './hooks/useHomeInfo';
+
+  const homeInfos = useHomeInfo();
+  const { sceneData, violationData, getAlgoList, getViolationCount } = homeInfos;
+</script>
+
+<style scoped>
+  .home-page {
+    width: 100%;
+    height: 100%;
+    padding-bottom: 24px;
+    background: #ffffff;
+  }
+
+  .mask-pos {
+    margin-top: 16px;
+  }
+</style>

+ 109 - 0
src/views/dashboard/home/components/AlgoCensusTabs.vue

@@ -0,0 +1,109 @@
+<template>
+  <div class="flex justify-between" style="width: 100%">
+    <div class="text-tabs">
+      <div
+        v-for="item in timeTypeList"
+        class="tab-item"
+        @click="onClickTab(item)"
+        :key="item.label"
+      >
+        <span> {{ item.label }} </span>
+        <div v-if="activeTab == item.value" class="tab-underline"></div>
+      </div>
+    </div>
+    <el-date-picker
+      v-model="timeSlot"
+      type="daterange"
+      start-placeholder="开始日期"
+      end-placeholder="结束日期"
+      @change="onDateChnage"
+    >
+      <template #range-separator>
+        <img src="@/assets/images/date-to.png" />
+      </template>
+    </el-date-picker>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref } from 'vue';
+  import { TimeTabEnum, timeTypeList } from '../types';
+  import dayjs from 'dayjs';
+
+  const activeTab = ref<TimeTabEnum>(TimeTabEnum.DAY);
+
+  const emits = defineEmits(['checkTab', 'changeDateRange']);
+
+  const today = dayjs().format('YYYY-MM-DD');
+  const weekDay = dayjs().startOf('week').add(1, 'day').format('YYYY-MM-DD');
+  const monthDay = dayjs().startOf('month').format('YYYY-MM-DD');
+
+  const timeSlot = ref([today, today]);
+
+  const onClickTab = (tabItem: any) => {
+    activeTab.value = tabItem.value;
+    switch (tabItem.value) {
+      case TimeTabEnum.DAY:
+        timeSlot.value = [today, today];
+        break;
+
+      case TimeTabEnum.WEEK:
+        timeSlot.value = [weekDay, today];
+        break;
+
+      case TimeTabEnum.MONTH:
+        timeSlot.value = [monthDay, today];
+        break;
+    }
+    emits('checkTab', { tab: tabItem.value, data: timeSlot.value });
+  };
+
+  const onDateChnage = (date) => {
+    timeSlot.value = date.map((item) => dayjs(item).format('YYYY-MM-DD'));
+    onClickTab(TimeTabEnum.RANGE);
+  };
+</script>
+
+<style scoped>
+  .text-tabs {
+    width: 132px;
+    height: 26px;
+    display: flex;
+    justify-content: space-between;
+  }
+
+  .tab-item {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    align-items: center;
+    font-size: 14px;
+    font-weight: 400;
+    color: #2e2e2e;
+    line-height: 20px;
+    cursor: pointer;
+  }
+
+  .tab-underline {
+    width: 100%;
+    height: 2px;
+    background: #1677ff;
+  }
+
+  :deep(.el-date-editor .el-range__icon) {
+    display: none;
+  }
+  :deep(.el-date-editor .el-range__close-icon--hidden) {
+    display: none;
+  }
+
+  :deep(.el-range-editor.el-input__wrapper) {
+    width: 236px;
+    flex: unset;
+    height: 32px;
+  }
+
+  :deep(.el-input__wrapper) {
+    padding: 0;
+  }
+</style>

+ 204 - 0
src/views/dashboard/home/components/AlgoDataPanel.vue

@@ -0,0 +1,204 @@
+<template>
+  <div class="algo-data">
+    <span class="algo-tit">算法数据分析</span>
+    <CensusTabs @check-tab="onCheckTab" />
+    <v-chart class="chart" :option="option" />
+    <div class="stat-show">
+      <ViolationStatItem :data="getVioStatData(0)" />
+      <div class="stat-divider"></div>
+      <ViolationStatItem :data="getVioStatData(1)" />
+      <div class="stat-divider"></div>
+      <ViolationStatItem :data="getVioStatData(2)" />
+      <div class="stat-divider"></div>
+      <ViolationStatItem :data="getVioStatData(3)" />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { computed, ref } from 'vue';
+  import CensusTabs from './AlgoCensusTabs.vue';
+  import ViolationStatItem from './ViolationStatItem.vue';
+  import { TimeTabEnum, violationHandleCounts } from '../types';
+  import { use } from 'echarts/core';
+  import { CanvasRenderer } from 'echarts/renderers';
+  import { PieChart } from 'echarts/charts';
+  import { TooltipComponent, LegendComponent } from 'echarts/components';
+  import { ViolationCount } from '@/api/home/home.ts';
+  import VChart from 'vue-echarts';
+
+  const props = defineProps<{
+    data: ViolationCount;
+    getViolations: (range: string[]) => void;
+  }>();
+
+  use([CanvasRenderer, PieChart, TooltipComponent, LegendComponent]);
+
+  const algoData = computed(() => {
+    let newData: any[] = [];
+    const vioList = props.data.violationAlgoList;
+    if (vioList && vioList.length) {
+      newData = vioList.map((item) => {
+        return {
+          value: item.proportion,
+          name: item.name,
+        };
+      });
+    }
+    console.log(newData);
+
+    return newData;
+  });
+  //  [
+  //   { value: 335, name: "人员闯入" },
+  //   { value: 310, name: "未穿反光背心" },
+  //   { value: 2, name: "明火烟雾" },
+  //   { value: 135, name: "机翼保护垫" },
+  //   { value: 148, name: "工装未归位" },
+  //   { value: 335, name: "人员闯入1" },
+  //   { value: 310, name: "未穿反光背心1" },
+  //   { value: 2, name: "明火烟雾1" },
+  //   { value: 135, name: "机翼保护垫1" },
+  //   { value: 148, name: "工装未归位1" },
+  // ];
+
+  const statData = computed(() => props.data.statusCountList);
+
+  const getVioStatData = (index) => {
+    let count = 0;
+    if (statData.value && statData.value.length) {
+      const matchItem = statData.value.find(
+        (item) => item.name === violationHandleCounts[index].value,
+      );
+      if (matchItem) {
+        count = matchItem.value;
+      }
+    }
+    return { ...violationHandleCounts[index], count };
+  };
+
+  const option = computed(() => {
+    return {
+      tooltip: {
+        trigger: 'item',
+        formatter: '{a} <br/>{b} : {c} ({d}%)',
+      },
+      legend: {
+        orient: 'horizontial',
+        x: 'center',
+        y: 'bottom',
+        icon: 'circle',
+        width: '80%',
+        height: '28%',
+        type: 'scroll',
+        data: algoData.value.map((item) => item.name),
+        formatter: function (name) {
+          let total = 0;
+          let target;
+          for (let i = 0; i < algoData.value.length; i++) {
+            total += algoData.value[i].value;
+            if (algoData.value[i].name === name) {
+              target = algoData.value[i].value;
+            }
+          }
+          var arr = [
+            '{a|' + name + '}',
+            '{b|' + ' | ' + ((target / total) * 100).toFixed(0) + '%}\n',
+          ];
+          return arr.join('  ');
+        },
+        textStyle: {
+          padding: [8, 0, 0, 0],
+          fontSize: 14,
+          rich: {
+            a: {
+              fontSize: 15,
+            },
+            b: {
+              fontSize: 15,
+              color: '#c1c1c1',
+            },
+          },
+        },
+      },
+      series: [
+        {
+          name: '违规统计',
+          type: 'pie',
+          radius: ['40%', '65%'],
+          center: ['50%', '40%'],
+          labelLine: {
+            show: false,
+          },
+
+          label: {
+            show: false,
+            position: 'center',
+          },
+          data: algoData.value,
+          itemStyle: {
+            borderColor: '#fff',
+            borderWidth: 5,
+          },
+          emphasis: {
+            label: {
+              show: true,
+              fontSize: 20,
+              fontWeight: 'bold',
+            },
+            itemStyle: {
+              shadowBlur: 10,
+              shadowOffsetX: 0,
+              shadowColor: 'rgba(0, 0, 0, 0.5)',
+            },
+          },
+        },
+      ],
+    };
+  });
+
+  const timeTab = ref<TimeTabEnum>(TimeTabEnum.DAY);
+
+  const onCheckTab = (info: { tab: TimeTabEnum; data: string[] }) => {
+    timeTab.value = info.tab;
+    props.getViolations(info.data);
+  };
+</script>
+
+<style scoped>
+  .algo-data {
+    width: 484px;
+    padding: 12px 27px;
+    border-left: 2px solid #e8e8e8;
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+  }
+
+  .chart {
+    width: 100%;
+    height: 450px;
+  }
+
+  .algo-tit {
+    font-size: 16px;
+    font-weight: 500;
+    margin-bottom: 10px;
+    line-height: 44px;
+    color: #2e2e2e;
+  }
+
+  .stat-show {
+    width: 100%;
+    margin-top: 32px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+
+  .stat-divider {
+    width: 1px;
+    height: 40px;
+    background: #e9e9e9;
+  }
+</style>

+ 103 - 0
src/views/dashboard/home/components/CameraInfo.vue

@@ -0,0 +1,103 @@
+<template>
+  <div class="camera-info">
+    <span class="info-tit">相机视频流</span>
+    <div>
+      <el-tree-select
+        v-model="selectedCamera"
+        :data="props.data"
+        :render-after-expand="false"
+        :props="treeProp"
+        node-key="code"
+        :default-expand-all="true"
+        @current-change="onCurrentChange"
+      />
+    </div>
+    <div class="video-block">
+      <LiveVideo :url="`http://10.94.4.184:8090/live/JJ-GH-test0.flv`" />
+    </div>
+    <div class="flex" style="width: 100%">
+      <span class="algo-text">相关算法:</span>
+      <div class="tag-list">
+        <el-tag v-for="item in algoList" class="algo-name" :key="item.id">
+          {{ item.algoInfo.name }}
+        </el-tag>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { nextTick, ref } from 'vue';
+  import LiveVideo from '@/components/LiveVideo/LiveVideo.vue';
+  import { CompanyInfoItem, AlgoConfig } from '@/api/home/home.ts';
+
+  const props = defineProps<{
+    data: CompanyInfoItem[];
+    getAlgoes: (cameraId: number) => Promise<AlgoConfig[]>;
+  }>();
+
+  const treeProp = {
+    label: 'name',
+    disabled: (_, node) => node?.data && node.data.nodeType !== 'camera',
+  };
+
+  const selectedCamera = ref('');
+  const algoList = ref<AlgoConfig[]>([]);
+
+  const onCurrentChange = (_, node) => {
+    nextTick(() => {
+      if (node?.data && node.data.code === selectedCamera.value) {
+        props.getAlgoes(node.data.id).then((res) => {
+          algoList.value = res;
+        });
+      }
+    });
+  };
+</script>
+
+<style scoped>
+  .camera-info {
+    padding: 12px 30px;
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+  }
+
+  .info-tit {
+    font-size: 16px;
+    font-weight: 500;
+    color: #2e2e2e;
+    line-height: 44px;
+    margin-bottom: 10px;
+  }
+
+  .video-block {
+    /* width: 889px;
+  height: 500px; */
+    min-width: 444.5;
+    width: 100%;
+    margin-top: 16px;
+    margin-bottom: 28px;
+    aspect-ratio: 1920/1080;
+  }
+
+  .algo-text {
+    font-size: 14px;
+    font-weight: 400;
+    color: #2e2e2e;
+    line-height: 20px;
+  }
+
+  .tag-list {
+    max-width: 60%;
+    display: flex;
+    margin-left: 16px;
+    flex-wrap: wrap;
+    justify-content: flex-start;
+  }
+
+  .algo-name {
+    margin-right: 12px;
+    margin-bottom: 12px;
+  }
+</style>

+ 35 - 0
src/views/dashboard/home/components/ViolationStatItem.vue

@@ -0,0 +1,35 @@
+<template>
+  <div class="stat-item">
+    <span class="stat-type" :style="{ color: props.data.color }">
+      {{ props.data.label }}
+    </span>
+    <span class="stat-count">{{ props.data.count }}</span>
+  </div>
+</template>
+
+<script setup lang="ts">
+  const props = defineProps<{
+    data: { label: string; value: string; color: string; count: number };
+  }>();
+</script>
+
+<style scoped>
+  .stat-item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+  }
+
+  .stat-type {
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 22px;
+    margin-bottom: 4px;
+  }
+
+  .stat-count {
+    font-size: 24px;
+    color: #2e2e2e;
+    line-height: 32px;
+  }
+</style>

+ 66 - 0
src/views/dashboard/home/hooks/useHomeInfo.ts

@@ -0,0 +1,66 @@
+import { ref, onMounted, computed } from 'vue';
+import {
+  CompanyInfoItem,
+  getAuthSceneList,
+  getAlgoByCameraId,
+  ViolationsQueryParam,
+  ViolationCount,
+  getViolation,
+} from '@/api/home/home.ts';
+import dayjs from 'dayjs';
+import { useUserStore } from '@/store/modules/user.ts';
+import { storeToRefs } from 'pinia';
+
+export function useHomeInfo() {
+  const userStore = useUserStore();
+  const { info } = storeToRefs(userStore);
+
+  const userName = computed(() => info.value.username);
+
+  const sceneData = ref<CompanyInfoItem[]>([]);
+  const violationData = ref<ViolationCount>({} as ViolationCount);
+
+  const getSceneData = () => {
+    getAuthSceneList().then((res) => {
+      sceneData.value = res;
+    });
+  };
+
+  const getAlgoList = (cameraId: number) => {
+    return getAlgoByCameraId({ cameraId }).then((res) => {
+      return res;
+    });
+  };
+
+  const getViolationCount = (range: string[]) => {
+    const params: ViolationsQueryParam = {
+      startDate: range[0],
+      endDate: range[1],
+      userName: userName.value,
+    };
+    getViolation(params).then((res) => {
+      violationData.value = res;
+    });
+  };
+
+  const formatDay = (day) => {
+    return dayjs(day).format('YYYY-MM-DD');
+  };
+
+  onMounted(() => {
+    getSceneData();
+
+    const today = formatDay(dayjs());
+    getViolationCount([today, today]);
+  });
+
+  return {
+    sceneData,
+    violationData,
+    getSceneData,
+    getAlgoList,
+    getViolationCount,
+  };
+}
+
+export default useHomeInfo;

+ 43 - 0
src/views/dashboard/home/types/index.ts

@@ -0,0 +1,43 @@
+export enum TimeTabEnum {
+  DAY = 'day',
+  WEEK = 'week',
+  MONTH = 'month',
+  RANGE = 'range',
+}
+
+export const timeTypeList = [
+  {
+    label: '今日',
+    value: TimeTabEnum.DAY,
+  },
+  {
+    label: '本周',
+    value: TimeTabEnum.WEEK,
+  },
+  {
+    label: '本月',
+    value: TimeTabEnum.MONTH,
+  },
+];
+
+export enum ViolationHandleStat {
+  UNTREAT = 'untreat',
+  TREATED = 'treated',
+  OVERTIME = 'overtime',
+  LONGTIME = 'longtime',
+}
+
+export const violationHandleCounts = [
+  { label: '未处理', value: ViolationHandleStat.UNTREAT, color: '#FAAD14' },
+  { label: '已处理', value: ViolationHandleStat.TREATED, color: '#52C41A' },
+  {
+    label: '超期未处理',
+    value: ViolationHandleStat.OVERTIME,
+    color: '#FF4D4F',
+  },
+  {
+    label: '长期未处理',
+    value: ViolationHandleStat.LONGTIME,
+    color: '#FF4D4F',
+  },
+];

+ 0 - 1
src/views/system-config/scene-manage/SceneManage.vue

@@ -32,7 +32,6 @@
               </template>
               添加
             </el-button>
-            <a href="/skyeye-world" target="_blank">地球视角查看</a>
           </div>
         </template>
         <template #empty>