344080Wuyunfeng před 2 roky
rodič
revize
5636818588

+ 9 - 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,6 +60,7 @@
     "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",
@@ -82,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",
@@ -93,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",
@@ -113,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",
@@ -120,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"
   },

+ 208 - 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,6 +107,9 @@ 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
@@ -169,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
@@ -202,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
@@ -211,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
@@ -220,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
@@ -262,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)
@@ -280,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)
@@ -564,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==}
@@ -1642,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
@@ -1671,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
 
@@ -2194,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'}
@@ -2674,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:
@@ -3011,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
@@ -3576,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:
@@ -4243,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
@@ -4693,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
@@ -4962,7 +5029,6 @@ packages:
     requiresBuild: true
     dependencies:
       safer-buffer: 2.1.2
-    dev: false
     optional: true
 
   /ieee754@1.2.1:
@@ -5252,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'}
@@ -5451,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'}
@@ -5658,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'}
@@ -5804,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'}
@@ -5929,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
@@ -5985,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
@@ -6289,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'}
@@ -6386,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:
@@ -6500,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
@@ -6651,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
@@ -6808,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==}
@@ -7021,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'}
@@ -7501,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
@@ -7661,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
@@ -8067,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:
@@ -8080,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
@@ -8097,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):
@@ -8119,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
@@ -8140,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):
@@ -8156,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:
@@ -8168,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
@@ -8201,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

+ 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,
+  });
+};

+ 43 - 6
src/layout/components/Header/QRcodePopover.vue

@@ -1,15 +1,52 @@
 <template>
-  <el-popover placement="bottom" trigger="hover">
+  <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>
-      <el-icon :size="18"><Download /></el-icon>
-      下载
+      <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>
-    <img src="~@/assets/images/QRcodeExample.png" />
+    <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>
-  import { Download } from '@element-plus/icons-vue';
+  // 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 - 25
src/layout/components/Header/index.vue

@@ -112,29 +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">
-        <a href="/skyeye-world" target="_blank">安全管控平台</a>
-      </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">
@@ -164,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"
@@ -175,7 +175,7 @@
             <SettingOutlined />
           </el-icon>
         </el-tooltip>
-      </div>
+      </div> -->
     </div>
   </div>
   <!--项目配置-->

+ 6 - 6
src/views/dashboard/home/Home.vue

@@ -1,20 +1,20 @@
 <template>
   <div class="home-page">
-    <!-- <HomeHeader style="z-index: 2" /> -->
     <div class="flex">
-      <CameraInfo class="flex-1" />
-      <AlgoData />
+      <CameraInfo class="flex-1" :data="sceneData" :get-algoes="getAlgoList" />
+      <AlgoData :data="violationData" :get-violations="getViolationCount" />
     </div>
-    <!-- <MoreMask class="mask-pos" /> -->
   </div>
 </template>
 
 <script setup lang="ts">
   // import { ref } from "vue";
-  // import HomeHeader from './components/header/PageHeader.vue';
   import CameraInfo from './components/CameraInfo.vue';
   import AlgoData from './components/AlgoDataPanel.vue';
-  // import MoreMask from './components/moreFeatureIMark/index.vue';
+  import useHomeInfo from './hooks/useHomeInfo';
+
+  const homeInfos = useHomeInfo();
+  const { sceneData, violationData, getAlgoList, getViolationCount } = homeInfos;
 </script>
 
 <style scoped>

+ 25 - 9
src/views/dashboard/home/components/AlgoCensusTabs.vue

@@ -1,7 +1,12 @@
 <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)">
+      <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>
@@ -23,28 +28,39 @@
 <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 timeSlot = ref('');
+  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 resetTime = () => {
-    timeSlot.value = '';
-  };
+  const timeSlot = ref([today, today]);
 
   const onClickTab = (tabItem: any) => {
     activeTab.value = tabItem.value;
-    emits('checkTab', tabItem.value);
-    if (tabItem.value !== 'range') {
-      resetTime();
+    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);
-    console.log(date);
   };
 </script>
 

+ 125 - 86
src/views/dashboard/home/components/AlgoDataPanel.vue

@@ -4,19 +4,19 @@
     <CensusTabs @check-tab="onCheckTab" />
     <v-chart class="chart" :option="option" />
     <div class="stat-show">
-      <ViolationStatItem :data="violationHandleCounts[0]" />
+      <ViolationStatItem :data="getVioStatData(0)" />
       <div class="stat-divider"></div>
-      <ViolationStatItem :data="violationHandleCounts[1]" />
+      <ViolationStatItem :data="getVioStatData(1)" />
       <div class="stat-divider"></div>
-      <ViolationStatItem :data="violationHandleCounts[2]" />
+      <ViolationStatItem :data="getVioStatData(2)" />
       <div class="stat-divider"></div>
-      <ViolationStatItem :data="violationHandleCounts[3]" />
+      <ViolationStatItem :data="getVioStatData(3)" />
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-  import { ref } from 'vue';
+  import { computed, ref } from 'vue';
   import CensusTabs from './AlgoCensusTabs.vue';
   import ViolationStatItem from './ViolationStatItem.vue';
   import { TimeTabEnum, violationHandleCounts } from '../types';
@@ -24,105 +24,144 @@
   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 data = [
-    { 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 option = ref({
-    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: data.map((item) => item.name),
-      formatter: function (name) {
-        let total = 0;
-        let target;
-        for (let i = 0; i < data.length; i++) {
-          total += data[i].value;
-          if (data[i].name === name) {
-            target = data[i].value;
-          }
-        }
-        var arr = [
-          '{a|' + name + '}',
-          '{b|' + ' | ' + ((target / total) * 100).toFixed(0) + '%}\n',
-        ];
-        return arr.join('  ');
+  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}%)',
       },
-      textStyle: {
-        padding: [8, 0, 0, 0],
-        fontSize: 14,
-        rich: {
-          a: {
-            fontSize: 15,
-          },
-          b: {
-            fontSize: 15,
-            color: '#c1c1c1',
+      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,
-        },
+      series: [
+        {
+          name: '违规统计',
+          type: 'pie',
+          radius: ['40%', '65%'],
+          center: ['50%', '40%'],
+          labelLine: {
+            show: false,
+          },
 
-        label: {
-          show: false,
-          position: 'center',
-        },
-        data,
-        itemStyle: {
-          borderColor: '#fff',
-          borderWidth: 5,
-        },
-        emphasis: {
           label: {
-            show: true,
-            fontSize: 20,
-            fontWeight: 'bold',
+            show: false,
+            position: 'center',
           },
+          data: algoData.value,
           itemStyle: {
-            shadowBlur: 10,
-            shadowOffsetX: 0,
-            shadowColor: 'rgba(0, 0, 0, 0.5)',
+            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 = (tab) => {
-    timeTab.value = tab;
+  const onCheckTab = (info: { tab: TimeTabEnum; data: string[] }) => {
+    timeTab.value = info.tab;
+    props.getViolations(info.data);
   };
 </script>
 

+ 39 - 7
src/views/dashboard/home/components/CameraInfo.vue

@@ -2,25 +2,57 @@
   <div class="camera-info">
     <span class="info-tit">相机视频流</span>
     <div>
-      <el-select v-model="selectedCamera" />
+      <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://172.16.23.243/tianyan11/live/C12-200-11.flv`" />
+      <LiveVideo :url="`http://10.94.4.184:8090/live/JJ-GH-test0.flv`" />
     </div>
-    <div class="flex">
+    <div class="flex" style="width: 100%">
       <span class="algo-text">相关算法:</span>
       <div class="tag-list">
-        <el-tag v-for="item in 20" class="algo-name"> Tag {{ item }} </el-tag>
+        <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 { ref } from 'vue';
-  import LiveVideo from './LiveVideo.vue';
+  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>
@@ -57,7 +89,7 @@
   }
 
   .tag-list {
-    width: 60%;
+    max-width: 60%;
     display: flex;
     margin-left: 16px;
     flex-wrap: wrap;

+ 0 - 52
src/views/dashboard/home/components/LegendItem.vue

@@ -1,52 +0,0 @@
-<template>
-  <div class="legend-col">
-    <div class="legend-mark" :style="{ background: props.color }"></div>
-    <span class="legend-name">{{ props.name }}</span>
-    <div class="legend-divider"></div>
-    <span class="legend-count">{{ props.count }}</span>
-  </div>
-</template>
-
-<script setup lang="ts">
-  const props = defineProps<{
-    color: string;
-    name: string;
-    count: string;
-  }>();
-</script>
-
-<style scoped>
-  .legend-col {
-    width: 120px;
-    height: 22px;
-    margin-bottom: 16px;
-    display: flex;
-    justify-content: flex-start;
-    align-items: center;
-  }
-
-  .legend-mark {
-    width: 8px;
-    height: 8px;
-    margin-right: 8px;
-    border-radius: 50%;
-  }
-
-  .legend-name {
-    font-size: 14px;
-    font-weight: 400;
-    color: #6d6d6d;
-  }
-
-  .legend-divider {
-    width: 1px;
-    height: 12px;
-    margin: 0 8px;
-    background: #d9d9d9;
-  }
-
-  .legend-count {
-    font-size: 14px;
-    color: #bdbdbd;
-  }
-</style>

+ 0 - 73
src/views/dashboard/home/components/LiveVideo.vue

@@ -1,73 +0,0 @@
-<template>
-  <video id="video" autoplay muted loop class="video-js video-content">
-    <source :src="props.url" />
-  </video>
-</template>
-
-<script setup lang="ts">
-  import { onMounted, onBeforeUnmount, watch } from 'vue';
-  import flvjs from 'flv.js';
-
-  const props = defineProps<{
-    url: string;
-  }>();
-
-  let player: flvjs.Player | null;
-
-  const initPlay = () => {
-    const videoElement = document.getElementById('video') as HTMLMediaElement;
-    player = flvjs.createPlayer({
-      type: 'flv',
-      isLive: true,
-      hasAudio: false,
-      url: props.url,
-    });
-    player.attachMediaElement(videoElement);
-    player.load();
-    player.on(flvjs.Events.METADATA_ARRIVED, () => {});
-    // player.play();
-    setTimeout(() => {
-      player?.play();
-    }, 50);
-  };
-
-  const destroyPlayer = () => {
-    if (player) {
-      player.pause();
-      player.unload();
-      player.detachMediaElement();
-      player.destroy();
-      player = null;
-    }
-  };
-
-  onMounted(() => {
-    initPlay();
-  });
-
-  //切换播放url
-  watch(
-    () => props.url,
-    () => {
-      destroyPlayer();
-      if (props.url) {
-        initPlay();
-      }
-    },
-    {
-      deep: true,
-    },
-  );
-
-  onBeforeUnmount(() => {
-    destroyPlayer();
-  });
-</script>
-
-<style scoped>
-  .video-content {
-    width: 100%;
-    height: 100%;
-    background-color: transparent !important;
-  }
-</style>

+ 2 - 2
src/views/dashboard/home/components/ViolationStatItem.vue

@@ -3,13 +3,13 @@
     <span class="stat-type" :style="{ color: props.data.color }">
       {{ props.data.label }}
     </span>
-    <span class="stat-count">25</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 };
+    data: { label: string; value: string; color: string; count: number };
   }>();
 </script>
 

+ 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;

+ 17 - 17
src/views/dashboard/home/types/index.ts

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