Forráskód Böngészése

完成摄像头管理

nahida 1 éve
szülő
commit
6211c440e9

+ 1 - 0
.env.development

@@ -1,3 +1,4 @@
 # 变量必须以 VITE_ 为前缀才能暴露给外部读取
 NODE_ENV = 'development'
 VITE_APP_BASE_API = '/api'
+VITE_MINIO_BASE_URL = 'http://192.168.110.30:9000/zksy-file'

+ 2 - 1
.env.production

@@ -1,3 +1,4 @@
 # 变量必须以 VITE_ 为前缀才能暴露给外部读取
 NODE_ENV = 'production'
-VITE_APP_BASE_API = 'https://gxq.huaihua.gov.cn/qyxyfjflserver'
+VITE_APP_BASE_API = 'https://gxq.huaihua.gov.cn/qyxyfjflserver'
+VITE_MINIO_BASE_URL = 'http://192.168.110.30:9000/zksy-file'

+ 6 - 0
components.d.ts

@@ -23,8 +23,11 @@ declare module 'vue' {
     ElMain: typeof import('element-plus/es')['ElMain']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
+    ElOption: typeof import('element-plus/es')['ElOption']
+    ElPagination: typeof import('element-plus/es')['ElPagination']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
+    ElSelect: typeof import('element-plus/es')['ElSelect']
     ElStatistic: typeof import('element-plus/es')['ElStatistic']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
@@ -43,4 +46,7 @@ declare module 'vue' {
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
   }
+  export interface ComponentCustomProperties {
+    vLoading: typeof import('element-plus/es')['ElLoadingDirective']
+  }
 }

+ 342 - 0
package-lock.json

@@ -14,6 +14,8 @@
         "echarts": "^5.5.1",
         "element-plus": "^2.9.0",
         "pinia": "^2.2.6",
+        "video.js": "^8.21.0",
+        "videojs-flvjs": "^0.3.1",
         "vue": "^3.5.13",
         "vue-echarts": "^7.0.3",
         "vue-router": "^4.4.5"
@@ -510,6 +512,18 @@
         "@babel/core": "^7.0.0-0"
       }
     },
+    "node_modules/@babel/runtime": {
+      "version": "7.26.0",
+      "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.26.0.tgz",
+      "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
+      "license": "MIT",
+      "dependencies": {
+        "regenerator-runtime": "^0.14.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
     "node_modules/@babel/template": {
       "version": "7.25.9",
       "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.25.9.tgz",
@@ -2392,6 +2406,54 @@
         "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0"
       }
     },
+    "node_modules/@videojs/http-streaming": {
+      "version": "3.16.2",
+      "resolved": "https://registry.npmmirror.com/@videojs/http-streaming/-/http-streaming-3.16.2.tgz",
+      "integrity": "sha512-fvt4ko7FknxiT9FnjyNQt6q2px+awrkM+Orv7IB/4gldvj94u4fowGfmNHynnvNTPgPkdxHklGmFLGfclYw8HA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@babel/runtime": "^7.12.5",
+        "@videojs/vhs-utils": "^4.1.1",
+        "aes-decrypter": "^4.0.2",
+        "global": "^4.4.0",
+        "m3u8-parser": "^7.2.0",
+        "mpd-parser": "^1.3.1",
+        "mux.js": "7.1.0",
+        "video.js": "^7 || ^8"
+      },
+      "engines": {
+        "node": ">=8",
+        "npm": ">=5"
+      },
+      "peerDependencies": {
+        "video.js": "^8.19.0"
+      }
+    },
+    "node_modules/@videojs/vhs-utils": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmmirror.com/@videojs/vhs-utils/-/vhs-utils-4.1.1.tgz",
+      "integrity": "sha512-5iLX6sR2ownbv4Mtejw6Ax+naosGvoT9kY+gcuHzANyUZZ+4NpeNdKMUhb6ag0acYej1Y7cmr/F2+4PrggMiVA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.12.5",
+        "global": "^4.4.0"
+      },
+      "engines": {
+        "node": ">=8",
+        "npm": ">=5"
+      }
+    },
+    "node_modules/@videojs/xhr": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmmirror.com/@videojs/xhr/-/xhr-2.7.0.tgz",
+      "integrity": "sha512-giab+EVRanChIupZK7gXjHy90y3nncA2phIOyG3Ne5fvpiMJzvqYwiTOnEVW2S4CoYcuKJkomat7bMXA/UoUZQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.5.5",
+        "global": "~4.4.0",
+        "is-function": "^1.0.1"
+      }
+    },
     "node_modules/@vitejs/plugin-vue": {
       "version": "5.2.1",
       "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz",
@@ -2924,6 +2986,15 @@
         "url": "https://github.com/sponsors/antfu"
       }
     },
+    "node_modules/@xmldom/xmldom": {
+      "version": "0.8.10",
+      "resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
+      "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
     "node_modules/abbrev": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-2.0.0.tgz",
@@ -2957,6 +3028,18 @@
         "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
       }
     },
+    "node_modules/aes-decrypter": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/aes-decrypter/-/aes-decrypter-4.0.2.tgz",
+      "integrity": "sha512-lc+/9s6iJvuaRe5qDlMTpCFjnwpkeOXp8qP3oiZ5jsj1MRg+SBVUmmICrhxHvc8OELSmc+fEyyxAuppY6hrWzw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@babel/runtime": "^7.12.5",
+        "@videojs/vhs-utils": "^4.1.1",
+        "global": "^4.4.0",
+        "pkcs7": "^1.0.4"
+      }
+    },
     "node_modules/agent-base": {
       "version": "7.1.3",
       "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.3.tgz",
@@ -3134,6 +3217,16 @@
         "node": ">=8"
       }
     },
+    "node_modules/browserify-versionify": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/browserify-versionify/-/browserify-versionify-1.0.6.tgz",
+      "integrity": "sha512-DhYuu4atLBdEZcJdEcoMTo0iKvW8B8nOlATEZMZhk/m4GJ67lHtwbjXuEsdz4cdBjngjLA+ftVY0VTGiZM1Fhw==",
+      "license": "MIT",
+      "dependencies": {
+        "find-root": "^0.1.1",
+        "through2": "0.6.3"
+      }
+    },
     "node_modules/browserslist": {
       "version": "4.24.2",
       "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.24.2.tgz",
@@ -3429,6 +3522,12 @@
         "url": "https://github.com/sponsors/mesqueeb"
       }
     },
+    "node_modules/core-util-is": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
+      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+      "license": "MIT"
+    },
     "node_modules/cross-spawn": {
       "version": "7.0.6",
       "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -3625,6 +3724,11 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/dom-walk": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmmirror.com/dom-walk/-/dom-walk-0.1.2.tgz",
+      "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
+    },
     "node_modules/duplexer": {
       "version": "0.1.2",
       "resolved": "https://registry.npmmirror.com/duplexer/-/duplexer-0.1.2.tgz",
@@ -3801,6 +3905,13 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/es6-promise": {
+      "version": "4.2.8",
+      "resolved": "https://registry.npmmirror.com/es6-promise/-/es6-promise-4.2.8.tgz",
+      "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
+      "license": "MIT",
+      "peer": true
+    },
     "node_modules/esbuild": {
       "version": "0.24.0",
       "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.24.0.tgz",
@@ -4310,6 +4421,12 @@
         "node": ">=8"
       }
     },
+    "node_modules/find-root": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmmirror.com/find-root/-/find-root-0.1.2.tgz",
+      "integrity": "sha512-GyDxVgA61TZcrgDJPqOqGBpi80Uf2yIstubgizi7AjC9yPdRrqBR+Y0MvK4kXnYlaoz3d+SGxDHMYVkwI/yd2w==",
+      "license": "MIT"
+    },
     "node_modules/find-up": {
       "version": "5.0.0",
       "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz",
@@ -4348,6 +4465,17 @@
       "dev": true,
       "license": "ISC"
     },
+    "node_modules/flv.js": {
+      "version": "1.6.2",
+      "resolved": "https://registry.npmmirror.com/flv.js/-/flv.js-1.6.2.tgz",
+      "integrity": "sha512-xre4gUbX1MPtgQRKj2pxJENp/RnaHaxYvy3YToVVCrSmAWUu85b9mug6pTXF6zakUjNP2lFWZ1rkSX7gxhB/2A==",
+      "license": "Apache-2.0",
+      "peer": true,
+      "dependencies": {
+        "es6-promise": "^4.2.8",
+        "webworkify-webpack": "^2.1.5"
+      }
+    },
     "node_modules/follow-redirects": {
       "version": "1.15.9",
       "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
@@ -4503,6 +4631,16 @@
         "node": ">=10.13.0"
       }
     },
+    "node_modules/global": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmmirror.com/global/-/global-4.4.0.tgz",
+      "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
+      "license": "MIT",
+      "dependencies": {
+        "min-document": "^2.19.0",
+        "process": "^0.11.10"
+      }
+    },
     "node_modules/globals": {
       "version": "11.12.0",
       "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz",
@@ -5161,6 +5299,12 @@
         "node": ">=0.8.19"
       }
     },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
     "node_modules/ini": {
       "version": "1.3.8",
       "resolved": "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz",
@@ -5217,6 +5361,12 @@
         "node": ">=8"
       }
     },
+    "node_modules/is-function": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/is-function/-/is-function-1.0.2.tgz",
+      "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==",
+      "license": "MIT"
+    },
     "node_modules/is-glob": {
       "version": "4.0.3",
       "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
@@ -5334,6 +5484,12 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/isarray": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmmirror.com/isarray/-/isarray-0.0.1.tgz",
+      "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
+      "license": "MIT"
+    },
     "node_modules/isexe": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
@@ -5674,6 +5830,17 @@
         "yallist": "^3.0.2"
       }
     },
+    "node_modules/m3u8-parser": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmmirror.com/m3u8-parser/-/m3u8-parser-7.2.0.tgz",
+      "integrity": "sha512-CRatFqpjVtMiMaKXxNvuI3I++vUumIXVVT/JpCpdU/FynV/ceVw1qpPyyBNindL+JlPMSesx+WX1QJaZEJSaMQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@babel/runtime": "^7.12.5",
+        "@videojs/vhs-utils": "^4.1.1",
+        "global": "^4.4.0"
+      }
+    },
     "node_modules/magic-string": {
       "version": "0.30.15",
       "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.15.tgz",
@@ -5750,6 +5917,14 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/min-document": {
+      "version": "2.19.0",
+      "resolved": "https://registry.npmmirror.com/min-document/-/min-document-2.19.0.tgz",
+      "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==",
+      "dependencies": {
+        "dom-walk": "^0.1.0"
+      }
+    },
     "node_modules/minimatch": {
       "version": "9.0.5",
       "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz",
@@ -5796,6 +5971,21 @@
         "ufo": "^1.5.4"
       }
     },
+    "node_modules/mpd-parser": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmmirror.com/mpd-parser/-/mpd-parser-1.3.1.tgz",
+      "integrity": "sha512-1FuyEWI5k2HcmhS1HkKnUAQV7yFPfXPht2DnRRGtoiiAAW+ESTbtEXIDpRkwdU+XyrQuwrIym7UkoPKsZ0SyFw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@babel/runtime": "^7.12.5",
+        "@videojs/vhs-utils": "^4.0.0",
+        "@xmldom/xmldom": "^0.8.3",
+        "global": "^4.4.0"
+      },
+      "bin": {
+        "mpd-to-m3u8-json": "bin/parse.js"
+      }
+    },
     "node_modules/mrmime": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.0.tgz",
@@ -5820,6 +6010,23 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/mux.js": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmmirror.com/mux.js/-/mux.js-7.1.0.tgz",
+      "integrity": "sha512-NTxawK/BBELJrYsZThEulyUMDVlLizKdxyAsMuzoCD1eFj97BVaA8D/CvKsKu6FOLYkFojN5CbM9h++ZTZtknA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@babel/runtime": "^7.11.2",
+        "global": "^4.4.0"
+      },
+      "bin": {
+        "muxjs-transmux": "bin/transmux.js"
+      },
+      "engines": {
+        "node": ">=8",
+        "npm": ">=5"
+      }
+    },
     "node_modules/nanoid": {
       "version": "3.3.8",
       "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.8.tgz",
@@ -6280,6 +6487,18 @@
         }
       }
     },
+    "node_modules/pkcs7": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmmirror.com/pkcs7/-/pkcs7-1.0.4.tgz",
+      "integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@babel/runtime": "^7.5.5"
+      },
+      "bin": {
+        "pkcs7": "bin/cli.js"
+      }
+    },
     "node_modules/pkg-types": {
       "version": "1.2.1",
       "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.2.1.tgz",
@@ -6389,6 +6608,15 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/process": {
+      "version": "0.11.10",
+      "resolved": "https://registry.npmmirror.com/process/-/process-0.11.10.tgz",
+      "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6.0"
+      }
+    },
     "node_modules/proto-list": {
       "version": "1.2.4",
       "resolved": "https://registry.npmmirror.com/proto-list/-/proto-list-1.2.4.tgz",
@@ -6447,6 +6675,18 @@
         "node": "^18.17.0 || >=20.5.0"
       }
     },
+    "node_modules/readable-stream": {
+      "version": "1.0.34",
+      "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-1.0.34.tgz",
+      "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==",
+      "license": "MIT",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.1",
+        "isarray": "0.0.1",
+        "string_decoder": "~0.10.x"
+      }
+    },
     "node_modules/readdirp": {
       "version": "3.6.0",
       "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
@@ -6460,6 +6700,12 @@
         "node": ">=8.10.0"
       }
     },
+    "node_modules/regenerator-runtime": {
+      "version": "0.14.1",
+      "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+      "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
+      "license": "MIT"
+    },
     "node_modules/resolve-from": {
       "version": "4.0.0",
       "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -6725,6 +6971,12 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/string_decoder": {
+      "version": "0.10.31",
+      "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-0.10.31.tgz",
+      "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==",
+      "license": "MIT"
+    },
     "node_modules/string-width": {
       "version": "5.1.2",
       "resolved": "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz",
@@ -6931,6 +7183,16 @@
         "url": "https://opencollective.com/unts"
       }
     },
+    "node_modules/through2": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmmirror.com/through2/-/through2-0.6.3.tgz",
+      "integrity": "sha512-6UXIsO0fTTYMgxeQ9pisMOIqF/uL6Ebva+4HxihtLLR2gscWEu+OTMwar/0TYZaeDSNS1msIJAXJRis+GojL8g==",
+      "license": "MIT",
+      "dependencies": {
+        "readable-stream": ">=1.0.33-1 <1.1.0-0",
+        "xtend": ">=4.0.0 <4.1.0-0"
+      }
+    },
     "node_modules/tinybench": {
       "version": "2.9.0",
       "resolved": "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz",
@@ -7967,6 +8229,70 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/video.js": {
+      "version": "8.21.0",
+      "resolved": "https://registry.npmmirror.com/video.js/-/video.js-8.21.0.tgz",
+      "integrity": "sha512-zcwerRb257QAuWfi8NH9yEX7vrGKFthjfcONmOQ4lxFRpDAbAi+u5LAjCjMWqhJda6zEmxkgdDpOMW3Y21QpXA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@babel/runtime": "^7.12.5",
+        "@videojs/http-streaming": "^3.16.2",
+        "@videojs/vhs-utils": "^4.1.1",
+        "@videojs/xhr": "2.7.0",
+        "aes-decrypter": "^4.0.2",
+        "global": "4.4.0",
+        "m3u8-parser": "^7.2.0",
+        "mpd-parser": "^1.3.1",
+        "mux.js": "^7.0.1",
+        "videojs-contrib-quality-levels": "4.1.0",
+        "videojs-font": "4.2.0",
+        "videojs-vtt.js": "0.15.5"
+      }
+    },
+    "node_modules/videojs-contrib-quality-levels": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-4.1.0.tgz",
+      "integrity": "sha512-TfrXJJg1Bv4t6TOCMEVMwF/CoS8iENYsWNKip8zfhB5kTcegiFYezEA0eHAJPU64ZC8NQbxQgOwAsYU8VXbOWA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "global": "^4.4.0"
+      },
+      "engines": {
+        "node": ">=16",
+        "npm": ">=8"
+      },
+      "peerDependencies": {
+        "video.js": "^8"
+      }
+    },
+    "node_modules/videojs-flvjs": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmmirror.com/videojs-flvjs/-/videojs-flvjs-0.3.1.tgz",
+      "integrity": "sha512-Ev3bUbOvRq7x7EraGwjke7ETUbqWuCLp4kR0DJCLGnMq7LPHig5UJN/f/0O5oLNIpUPUvTsrKcn6X2H7VaW4AQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "browserify-versionify": "^1.0.6"
+      },
+      "peerDependencies": {
+        "flv.js": "^1",
+        "video.js": "^6 || ^7 || ^8"
+      }
+    },
+    "node_modules/videojs-font": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/videojs-font/-/videojs-font-4.2.0.tgz",
+      "integrity": "sha512-YPq+wiKoGy2/M7ccjmlvwi58z2xsykkkfNMyIg4xb7EZQQNwB71hcSsB3o75CqQV7/y5lXkXhI/rsGAS7jfEmQ==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/videojs-vtt.js": {
+      "version": "0.15.5",
+      "resolved": "https://registry.npmmirror.com/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz",
+      "integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "global": "^4.3.1"
+      }
+    },
     "node_modules/vite": {
       "version": "6.0.3",
       "resolved": "https://registry.npmmirror.com/vite/-/vite-6.0.3.tgz",
@@ -9482,6 +9808,13 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/webworkify-webpack": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmmirror.com/webworkify-webpack/-/webworkify-webpack-2.1.5.tgz",
+      "integrity": "sha512-2akF8FIyUvbiBBdD+RoHpoTbHMQF2HwjcxfDvgztAX5YwbZNyrtfUMgvfgFVsgDhDPVTlkbb5vyasqDHfIDPQw==",
+      "license": "MIT",
+      "peer": true
+    },
     "node_modules/whatwg-encoding": {
       "version": "3.1.1",
       "resolved": "https://registry.npmmirror.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
@@ -9696,6 +10029,15 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4"
+      }
+    },
     "node_modules/yallist": {
       "version": "3.1.1",
       "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",

+ 2 - 0
package.json

@@ -20,6 +20,8 @@
     "echarts": "^5.5.1",
     "element-plus": "^2.9.0",
     "pinia": "^2.2.6",
+    "video.js": "^8.21.0",
+    "videojs-flvjs": "^0.3.1",
     "vue": "^3.5.13",
     "vue-echarts": "^7.0.3",
     "vue-router": "^4.4.5"

+ 100 - 0
src/stores/ParkSecurityStore.ts

@@ -0,0 +1,100 @@
+import { defineStore } from 'pinia'
+import { clientDel, clientGet, clientPost } from '@/utils/request.ts'
+import type { ChannelInfo, ChannelInfoResponse, RtspUrl, RtspUrlResponse } from '@/views/zhdpgl/yqzl/yqaf.vue'
+import { ElLoading, ElMessage } from 'element-plus'
+import type { BaseResponse } from '@/utils/type.ts'
+
+interface ParkVisualVideoDto{
+  url:string,
+  token:string,
+  area:number,
+  channelId:string,
+  id?:string
+}
+
+interface ParkVisualVideoResponse extends BaseResponse {
+  data: ParkVisualVideoDto[]
+}
+
+export const useParkSecurityStore = defineStore('parkSecurity', {
+  state: () => ({
+    dataList: [] as ChannelInfo[],
+  }),
+  actions: {
+    async getSecurityList() {
+      const params = {
+        pageNum: '1',
+        pageSize: '999',
+        unitTypeList: '1'
+      };
+      const urlSearchParams = new URLSearchParams(params)
+      const res = await clientPost<null,ChannelInfoResponse>('/visualization/deviceInfo/getChannelPage?'+urlSearchParams);
+      if(res.code !== 200){
+        ElMessage.error(res.msg)
+        return;
+      }
+      this.dataList = res.data.pageData;
+    },
+    async getVideoChannel(list:Array<Array<string>>){
+      const loading = ElLoading.service({
+        lock: true,
+        text: '执行中......',
+        fullscreen: true,
+      })
+      try {
+        const channelList = await clientGet<null, ParkVisualVideoResponse>('/park/ParkVisualVideo/getList')
+        const ids = channelList.data.map(item => item.id)
+        if (ids!.length > 0 || channelList) {
+          await clientDel('/park/ParkVisualVideo/deleteBatchById', {
+            params: {
+              ids: ids.toString()
+            }
+          })
+        }
+        const toServerList: ParkVisualVideoDto[] = []
+        for (const [i, q] of list.entries()) {
+          for (const w of q) {
+            const urlAndTokenObj: RtspUrl = await getUrlAndToken(w)
+            const r: ParkVisualVideoDto = {
+              channelId: w,
+              area: i,
+              url: urlAndTokenObj.url,
+              token: urlAndTokenObj.token
+            }
+            toServerList.push(r)
+          }
+        }
+        const res = await clientPost<ParkVisualVideoDto[], BaseResponse>('/park/ParkVisualVideo/saveBatch', toServerList)
+        if (res.code === 200) {
+          return true;
+        }
+        return false;
+      } finally {
+        loading.close()
+      }
+    },
+    async getAllVideoChannel(){
+      const allList= await clientGet<null,ParkVisualVideoResponse>("/park/ParkVisualVideo/getList")
+      if(allList.code !== 200){
+        ElMessage.error(allList.msg)
+        return;
+      }
+      return allList.data.map(item => {
+        return {
+          channelId: item.channelId,
+          area: item.area
+        }
+      });
+    }
+  },
+});
+
+const getUrlAndToken = async (channelCode: string) => {
+  const paramsStr = new URLSearchParams({
+    'data.channelId': channelCode,
+    'data.dataType': '1',
+    'data.streamType': '1'
+  }).toString()
+  const res = await clientPost<null, RtspUrlResponse>('/visualization/realTimePreviewVideo/getRtspUrl?' + paramsStr)
+  return res.data;
+}

+ 4 - 0
src/utils/getSuffix.ts

@@ -0,0 +1,4 @@
+export const getSuffix = (filename: string|undefined) => {
+  if(!filename) return "";
+  return filename.split(".").pop();
+};

+ 6 - 6
src/utils/request.ts

@@ -45,11 +45,11 @@ instance.interceptors.response.use(
 export const clientGet = <T, R>(url: string, config?: AxiosRequestConfig) =>
   instance.get<T, R>(url, config);
 
-export const clientPost = <T>(url: string, data?: T, config?: AxiosRequestConfig) =>
-  instance.post<T>(url, data, config);
+export const clientPost = <T,R>(url: string, data?: T, config?: AxiosRequestConfig) =>
+  instance.post<T,R>(url, data, config);
 
-export const clientPut = <T>(url: string, data?: T, config?: AxiosRequestConfig) =>
-  instance.put<T>(url, data, config);
+export const clientPut = <T,R>(url: string, data?: T, config?: AxiosRequestConfig) =>
+  instance.put<T,R>(url, data, config);
 
-export const clientDel = <T>(url: string, config?: AxiosRequestConfig) =>
-  instance.delete<T>(url, config);
+export const clientDel = <T,R>(url: string, config?: AxiosRequestConfig) =>
+  instance.delete<T,R>(url, config);

+ 268 - 104
src/views/zhdpgl/yqzl/yqaf.vue

@@ -1,114 +1,278 @@
-<template>
-  <div class="yqaf_box">
-    <h1>摄像头列表</h1>
-    <el-table :data="cameraList" style="width: 100%">
-      <el-table-column prop="id" label="ID" width="180"></el-table-column>
-      <el-table-column prop="name" label="名称"></el-table-column>
-      <el-table-column prop="state" label="状态"></el-table-column>
-      <el-table-column prop="displaystate" label="显示状态"></el-table-column>
-      <el-table-column prop="cjName" label="厂家"></el-table-column>
-      <el-table-column prop="location" label="位置"></el-table-column>
-      <el-table-column label="操作">
-        <template #default="scope">
-          <el-button size="mini" @click="handleView(scope.$index, scope.row)">查看</el-button>
-          <el-button size="mini" type="primary" @click="handleStart(scope.$index, scope.row)">启动</el-button>
-          <el-button size="mini" type="danger" @click="handleStop(scope.$index, scope.row)">停止</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-  </div>
-</template>
+<script setup lang="ts">
+import { ref, toRaw } from 'vue'
+import { ElMessage } from 'element-plus'
+import { clientPost } from '@/utils/request.ts'
+import type { BaseResponse } from '@/utils/type.ts'
+import { useParkSecurityStore } from '@/stores/ParkSecurityStore.ts'
+
+interface ChannelInfoParams {
+  access?: string;
+  channelCodeList?: string;
+  channelTypeList?: string;
+  deviceCategory?: string;
+  deviceCodeList?: string;
+  deviceType?: string;
+  includeSubOwnerCodeFlag?: string;
+  isOnline?: string;
+  isVirtual?: string;
+  ownerCode?: string;
+  pageNum?: number;
+  pageSize?: number;
+  sort?: string;
+  sortType?: string;
+  stat?: string;
+  unitTypeList?: string;
+}
 
-<script setup>
-import { ref, onMounted } from 'vue'
-// import axios from 'axios'
-
-const cameraList = [{
-      id:1,
-      name:"指挥中心一楼门口球机",
-      state:'开启',
-      cjName:"萨斯噶三个",
-      displaystate:'实时监控',
-      location:"242"
-    },{
-      id:1,
-      name:"指挥中心一楼门口球机",
-      state:'开启',
-      cjName:"萨斯噶三个",
-      displaystate:'实时监控',
-      location:"242"
-    },{
-      id:1,
-      name:"指挥中心一楼门口球机",
-      state:'开启',
-      cjName:"萨斯噶三个",
-      displaystate:'实时监控',
-      location:"242"
-    },{
-      id:1,
-      name:"指挥中心一楼门口球机",
-      state:'开启',
-      cjName:"萨斯噶三个",
-      displaystate:'实时监控',
-      location:"242"
-    },{
-      id:1,
-      name:"指挥中心一楼门口球机",
-      state:'开启',
-      cjName:"萨斯噶三个",
-      displaystate:'实时监控',
-      location:"242"
-    },{
-      id:1,
-      name:"指挥中心一楼门口球机",
-      state:'开启',
-      cjName:"萨斯噶三个",
-      displaystate:'实时监控',
-      location:"242"
-    },{
-      id:1,
-      name:"指挥中心一楼门口球机",
-      state:'开启',
-      cjName:"萨斯噶三个",
-      displaystate:'实时监控',
-      location:"242"
-    }]
-
-// const cameraList = ref([])
-
-// 模拟获取摄像头列表的数据
-onMounted(async () => {
-  // try {
-    // const response = await axios.get('https://api.example.com/cameras') // 替换为实际API地址
-    // cameraList.value = response.data
-    // cameraList.value = [{
-    //   id:1,
-    //   name:"123",
-    //   location:"242"
-    // }]
-  // } catch (error) {
-    // console.error('Error fetching camera list:', error)
-  // }
+export interface ChannelInfo {
+  id: string;
+  deviceCode: string;
+  unitType: number;
+  unitSeq: number;
+  channelSeq: number;
+  channelCode: string;
+  channelSn: string;
+  channelName: string;
+  channelType: string;
+  cameraType: string;
+  ownerCode: string;
+  isOnline: number;
+  stat: number;
+  capability: string;
+  chExt: string;
+  isVirtual: boolean;
+  createTime: string;
+}
+
+export interface ChannelInfoResponse extends BaseResponse {
+  data: {
+    currentPage: number;
+    totalPage: number;
+    pageSize: number;
+    totalRows: number;
+    pageData: ChannelInfo[];
+  }
+}
+
+export interface RtspUrlResponse extends BaseResponse {
+  data: RtspUrl
+}
+
+export interface RtspUrl {
+  compress: boolean;
+  connectType: string | null;
+  innerIp: string;
+  ip: string | null;
+  minRate: number | null;
+  netFlag: string;
+  port: number | null;
+  protocol: string | null;
+  reachable: boolean | null;
+  session: string;
+  stream: string | null;
+  stunEnable: boolean | null;
+  stunPort: number | null;
+  token: string;
+  trackId: string | null;
+  url: string;
+  urlList: string[] | null;
+  wssDirect: number;
+}
+
+const tableData = ref<ChannelInfo[]>([])
+const loading = ref(false)
+const currentPage = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
+const dialogVisible = ref(false)
+const parkSecurityStore = useParkSecurityStore()
+const form = ref({
+  select1: [] as string[]|undefined,
+  select2: [] as string[]|undefined,
+  select3: [] as string[]|undefined
 })
 
-function handleView(index, row) {
-  console.log(`查看摄像头 ${row.name}`)
-  // 实现查看摄像头详情逻辑
+const getData = async () => {
+  loading.value = true
+  try {
+    const params: ChannelInfoParams = {
+      pageNum: currentPage.value,
+      pageSize: pageSize.value,
+      unitTypeList: '1'
+    }
+    const paramsStr = new URLSearchParams(params as never).toString()
+    const res = await clientPost<ChannelInfoParams, ChannelInfoResponse>('/visualization/deviceInfo/getChannelPage?' + paramsStr)
+
+    if (res.code === 200) {
+      tableData.value = res.data.pageData
+      total.value = res.data.totalRows
+    } else {
+      ElMessage.error(res.msg || '获取数据失败')
+    }
+  } catch (error) {
+    console.error('Error fetching data:', error)
+    ElMessage.error('获取数据时发生错误')
+  } finally {
+    loading.value = false
+  }
 }
 
-function handleStart(index, row) {
-  console.log(`启动摄像头 ${row.name}`)
-  // 实现启动摄像头逻辑
+const getCameraType = (q: string) => {
+  switch (q) {
+    case '1':
+      return '枪机'
+    case '2':
+      return '球机'
+    case '3':
+      return '半球'
+    case '5':
+      return '本地采集输入'
+    default:
+      return '其他'
+  }
 }
 
-function handleStop(index, row) {
-  console.log(`停止摄像头 ${row.name}`)
-  // 实现停止摄像头逻辑
+// const checkCamera = async (row: ChannelInfo) => {
+//   const paramsStr = new URLSearchParams({
+//     'data.channelId': row.channelCode,
+//     'data.dataType': '1',
+//     'data.streamType': '1'
+//   }).toString()
+//   const res = await clientPost<null, RtspUrlResponse>('/visualization/realTimePreviewVideo/getRtspUrl?' + paramsStr)
+//   console.log(res)
+// }
+
+const handleSizeChange = (val: number) => {
+  pageSize.value = val
+  getData()
 }
-</script>
 
-<style>
-.yqaf_box{
-  margin: 20px;
+const handleCurrentChange = (val: number) => {
+  currentPage.value = val
+  getData()
 }
-</style>
+
+const handleConfirm = async ()=>{
+  const resList:[
+    select1:string[],
+    select2:string[],
+    select3:string[],
+  ] = [
+    [],
+    [],
+    [],
+  ];
+  for (const k in form.value) {
+    switch (k) {
+      case 'select1':
+        resList[0] = toRaw(form.value.select1) as string[];
+        break;
+      case 'select2':
+        resList[1] = toRaw(form.value.select2) as string[];
+        break;
+      case 'select3':
+        resList[2] = toRaw(form.value.select3) as string[];
+        break;
+    }
+  }
+  const res = await parkSecurityStore.getVideoChannel(resList);
+  if(res){
+    dialogVisible.value = false;
+    ElMessage.success('保存成功')
+  }
+}
+
+const showDialogVisible = async ()=>{
+  dialogVisible.value = true;
+  const videoChannel = await parkSecurityStore.getAllVideoChannel();
+  if (videoChannel!.length > 0 || videoChannel) {
+    form.value.select1 = videoChannel!.filter(item => item.area === 0).map(item => item.channelId);
+    form.value.select2 = videoChannel!.filter(item => item.area === 1).map(item => item.channelId);
+    form.value.select3 = videoChannel!.filter(item => item.area === 2).map(item => item.channelId);
+  }
+}
+
+const init = () => {
+  getData()
+  parkSecurityStore.getSecurityList();
+}
+init()
+</script>
+
+<template>
+  <div class="pt-20px">
+    <el-button type="primary" @click="showDialogVisible">配置大屏路径</el-button>
+    <el-table :data="tableData" style="width: 100%" v-loading="loading">
+      <el-table-column prop="id" label="id" width="100" />
+      <el-table-column prop="deviceCode" label="设备编码" width="100" />
+      <el-table-column prop="channelCode" label="通道编码" width="200" />
+      <el-table-column prop="channelName" label="通道名称" width="200" />
+      <el-table-column prop="channelType" label="通道类型">
+        <template #default="scope">
+          {{ scope.row.channelType === '1' ? '视频通道' : '未知' }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="cameraType" label="摄像机类型">
+        <template #default="scope">
+          {{ getCameraType(scope.row.cameraType) }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="ownerCode" label="所属组织编码" />
+      <el-table-column prop="isOnline" label="在线状态">
+        <template #default="scope">
+          <el-tag :type="scope.row.isOnline === 1 ? 'success' : 'danger'">
+            {{ scope.row.isOnline === 1 ? '在线' : '离线' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="stat" label="状态">
+        <template #default="scope">
+          <el-tag :type="scope.row.stat === 1 ? 'success' : 'warning'">
+            {{ scope.row.stat === 1 ? '正常' : '异常' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+<!--      <el-table-column label="操作">-->
+<!--        <template #default="scope">-->
+<!--          <el-button type="primary" size="small" @click="checkCamera(scope.row)">-->
+<!--            预览-->
+<!--          </el-button>-->
+<!--        </template>-->
+<!--      </el-table-column>-->
+    </el-table>
+    <el-pagination
+      v-model:current-page="currentPage"
+      v-model:page-size="pageSize"
+      :page-sizes="[10, 20, 50, 100]"
+      :total="total"
+      @size-change="handleSizeChange"
+      @current-change="handleCurrentChange"
+      layout="total, sizes, prev, pager, next, jumper"
+      class="mt-20px text-right"
+    />
+    <el-dialog v-model="dialogVisible" title="配置大屏路径">
+      <el-form-item label="实时监控">
+        <el-select v-model="form.select1" multiple :multiple-limit="8" placeholder="请选择">
+          <el-option v-for="(item,index) in parkSecurityStore.dataList" :key="index" :label="item.channelName" :value="item.channelCode"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="信息中心大楼出入管理">
+        <el-select v-model="form.select2" multiple :multiple-limit="4" placeholder="请选择">
+          <el-option v-for="(item,index) in parkSecurityStore.dataList" :key="index" :label="item.channelName" :value="item.channelCode"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="综合楼出入管理">
+        <el-select v-model="form.select3" multiple :multiple-limit="4" placeholder="请选择">
+          <el-option v-for="(item,index) in parkSecurityStore.dataList" :key="index" :label="item.channelName" :value="item.channelCode"></el-option>
+        </el-select>
+      </el-form-item>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="handleConfirm">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+

+ 378 - 77
src/views/zhdpgl/yqzl/yqjs.vue

@@ -1,83 +1,384 @@
-<template>
-  <div class="yqjs_box">
-    <h1>编辑园区信息</h1>
-    <el-form :model="parkForm" label-width="100px">
-      <el-form-item label="园区名称">
-        <el-input v-model="parkForm.name"></el-input>
-      </el-form-item>
-      <el-form-item label="描述">
-        <el-input type="textarea" v-model="parkForm.description"></el-input>
-      </el-form-item>
-      <el-form-item label="园区总面积">
-        <el-input v-model="parkForm.contact"></el-input>
-      </el-form-item>
-      <el-form-item label="入职企业">
-        <el-input v-model="parkForm.phone"></el-input>
-      </el-form-item>
-      <el-form-item label="楼栋数量">
-        <el-input v-model="parkForm.email"></el-input>
-      </el-form-item>
-      <el-form-item label="园区人员">
-        <el-input v-model="parkForm.email"></el-input>
-      </el-form-item>
-      <el-form-item label="标准厂房">
-        <el-input v-model="parkForm.email"></el-input>
-      </el-form-item>
-      <el-form-item label="总车位">
-        <el-input v-model="parkForm.email"></el-input>
-      </el-form-item>
-      <el-form-item label="生产用地">
-        <el-input v-model="parkForm.email"></el-input>
-      </el-form-item>
-      <el-form-item label="办公用地">
-        <el-input v-model="parkForm.email"></el-input>
-      </el-form-item>
-      <el-form-item label="绿色植被">
-        <el-input v-model="parkForm.email"></el-input>
-      </el-form-item>
-      <el-form-item label="基础设施">
-        <el-input v-model="parkForm.email"></el-input>
-      </el-form-item>
-      <el-form-item label="待开发区">
-        <el-input v-model="parkForm.email"></el-input>
-      </el-form-item>
-      <div>
-      <h1>上传园区视频</h1>
-      <el-upload class="upload-demo" drag action="https://jsonplaceholder.typicode.com/posts/" multiple>
-        <i class="el-icon-upload"></i>
-        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
-        <template #tip>
-          <div class="el-upload__tip">只能上传 mp4 文件,且不超过 50MB</div>
-        </template>
-      </el-upload>
-    </div>
-      <el-form-item>
-        <el-button type="primary" @click="onSubmit">保存</el-button>
-      </el-form-item>
-    </el-form>
-  </div>
-</template>
-
-<script setup>
+<script setup lang="ts">
 import { ref } from 'vue'
+import { ElDescriptions, ElDescriptionsItem, ElDialog, ElMessage, ElMessageBox, type UploadFile } from 'element-plus'
+import { clientDel, clientGet, clientPost, clientPut } from '@/utils/request.ts'
+import type { BaseResponse } from '@/utils/type.ts'
+import { getSuffix } from '@/utils/getSuffix.ts'
 
-const parkForm = ref({
-  name: '',
-  contact: '',
-  phone: '',
-  email: '',
-  description: ''
-})
-
-function onSubmit() {
-  console.log('提交表单:', parkForm.value)
-  // 在这里处理表单提交逻辑,例如发送到服务器
+interface IntroductItem {
+  id: string;
+  parkName: string;
+  remarks: string;
+  totalArea: string;
+  enteredCompany: string;
+  numberBuildings: string;
+  parkPersonnel: string;
+  standardFactory: string;
+  parkingLot: string;
+  productionLand: string;
+  officeLand: string;
+  greenVegetation: string;
+  infrastructure: string;
+  developmentArea: string;
+  createTime: string;
+  businessType: string;
+  updateTime: string;
+  isEnable: number;
+  filePath?: string;
+  file?: File;
 }
-</script>
 
+interface IntroductResponse extends BaseResponse {
+  data: IntroductItem[]
+}
+
+interface IntroductItemResponse extends BaseResponse {
+  data: IntroductItem
+}
+
+
+const tableData = ref<IntroductItem[]>([])
+
+const dialogVisible = ref(false)
+const currentRow = ref<IntroductItem>()
+const addDialogVisible = ref(false)
+const editDialogVisible = ref(false)
+const minioBaseUrl = import.meta.env.VITE_MINIO_BASE_URL
+
+const initData: IntroductItem = {
+  id: '',
+  parkName: '',
+  remarks: '',
+  totalArea: '',
+  enteredCompany: '',
+  numberBuildings: '',
+  parkPersonnel: '',
+  standardFactory: '',
+  parkingLot: '',
+  productionLand: '',
+  officeLand: '',
+  greenVegetation: '',
+  infrastructure: '',
+  developmentArea: '',
+  createTime: '',
+  businessType: '',
+  updateTime: '',
+  isEnable: 0,
+  filePath: '',
+  file: new File([], '')
+}
 
-<style>
-.yqjs_box {
-  margin: 20px;
+const handleView = async (row: IntroductItem) => {
+  dialogVisible.value = true
+  const res = await clientGet<null, IntroductItemResponse>('/park/parkInfo/getById',{
+    params:{
+      id:row.id
+    }
+  })
+  if (res.code !== 200) {
+    ElMessage.error(res.msg)
+  }
+  currentRow.value = res.data
 }
-</style>
+const addForm = ref<IntroductItem>(initData)
+const editForm = ref<IntroductItem>(initData)
+
+const handleEdit = (row: IntroductItem) => {
+  editDialogVisible.value = true
+  editForm.value = { ...row }
+}
+const handleAdd = async () => {
+  try {
+    const formData = new FormData();
+    Object.entries(addForm.value).forEach(([key, value]) => {
+      if (key === 'file' && value instanceof File) {
+        formData.append('file', value);
+      } else {
+        formData.append(key, String(value));
+      }
+    });
+
+    const res = await clientPost<FormData, BaseResponse>('/park/parkInfo/save', formData, {
+      headers: {
+        'Content-Type': 'multipart/form-data',
+      },
+    });
+
+    if (res.code === 200) {
+      getData();
+      addDialogVisible.value = false;
+      ElMessage.success('新增成功');
+      addForm.value = initData;
+    }
+  } catch (error) {
+    console.error('新增失败', error);
+    ElMessage.error('新增失败');
+  }
+}
+
+const handleSaveEdit = async () => {
+  try {
+    const formData = new FormData();
+    Object.entries(editForm.value).forEach(([key, value]) => {
+      if (key === 'file' && value instanceof File) {
+        formData.append('file', value);
+      } else {
+        formData.append(key, String(value));
+      }
+    });
+
+    const res = await clientPut<FormData, BaseResponse>('/park/parkInfo/updateById', formData, {
+      headers: {
+        'Content-Type': 'multipart/form-data',
+      },
+    });
+
+    if (res.code === 200) {
+      getData();
+      editDialogVisible.value = false;
+      ElMessage.success('更新成功');
+    }
+  } catch (error) {
+    console.error('更新失败', error);
+    ElMessage.error('更新失败');
+  }
+}
+const handleDelete = (row: IntroductItem) => {
+  ElMessageBox.confirm('确定要删除该园区信息吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(() => {
+    return clientDel<{ id: string }, BaseResponse>(`/park/parkInfo/deleteById`, {
+      params: {
+        id: row.id
+      }
+    })
+  }).then(res => {
+    if (res.code === 200) {
+      getData() // 刷新数据
+      ElMessage.success('删除成功')
+    } else {
+      ElMessage.error('删除失败')
+    }
+  })
+}
+const getData = async () => {
+  const res = await clientGet<null, IntroductResponse>('/park/parkInfo/getList')
+  if (res.code === 200) {
+    tableData.value = res.data
+  }
+}
+const init = () => {
+  getData()
+}
+init()
+</script>
+
+
+<template>
+  <div class="p-20px">
+    <el-button type="primary" size="small" @click="addDialogVisible = true">新增</el-button>
+    <el-table :data="tableData" style="width: 100%">
+      <el-table-column :show-overflow-tooltip="true" type="index" label="序号"></el-table-column>
+      <el-table-column :show-overflow-tooltip="true" prop="parkName" label="园区名称"></el-table-column>
+      <el-table-column :show-overflow-tooltip="true" prop="totalArea" label="园区总面积"></el-table-column>
+      <el-table-column :show-overflow-tooltip="true" prop="enteredCompany" label="入职企业"></el-table-column>
+      <el-table-column :show-overflow-tooltip="true" prop="numberBuildings" label="楼栋数量"></el-table-column>
+      <el-table-column :show-overflow-tooltip="true" prop="parkPersonnel" label="园区人员"></el-table-column>
+      <el-table-column :show-overflow-tooltip="true" prop="standardFactory" label="标准厂房"></el-table-column>
+      <el-table-column :show-overflow-tooltip="true" prop="parkingLot" label="总车位"></el-table-column>
+      <el-table-column :show-overflow-tooltip="true" prop="productionLand" label="生产用地"></el-table-column>
+      <el-table-column :show-overflow-tooltip="true" prop="officeLand" label="办公用地"></el-table-column>
+      <el-table-column :show-overflow-tooltip="true" prop="greenVegetation" label="绿色植被"></el-table-column>
+      <el-table-column :show-overflow-tooltip="true" prop="infrastructure" label="基础设施"></el-table-column>
+      <el-table-column :show-overflow-tooltip="true" prop="developmentArea" label="待开发区"></el-table-column>
+      <el-table-column prop="businessType" label="业务类型"></el-table-column>
+      <el-table-column prop="isEnable" label="是否启用">
+        <template #default="scope">
+          <el-tag :type="scope.row.isEnable === 1 ? 'success' : 'danger'">
+            {{ scope.row.isEnable === 1 ? '启用' : '禁用' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="200">
+        <template #default="scope">
+          <el-button type="primary" size="small" @click="handleView(scope.row)">查看</el-button>
+          <el-button type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
+          <el-button type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-dialog v-model="dialogVisible" title="详情" width="80%" destroy-on-close>
+      <el-descriptions border>
+        <el-descriptions-item min-width="200" label="ID">{{ currentRow?.id }}</el-descriptions-item>
+        <el-descriptions-item min-width="200" label="园区名称">{{ currentRow?.parkName }}</el-descriptions-item>
+        <el-descriptions-item min-width="200" label="园区总面积">{{ currentRow?.totalArea }}</el-descriptions-item>
+        <el-descriptions-item min-width="200" label="入职企业">{{ currentRow?.enteredCompany }}</el-descriptions-item>
+        <el-descriptions-item min-width="200" label="楼栋数量">{{ currentRow?.numberBuildings }}</el-descriptions-item>
+        <el-descriptions-item min-width="200" label="园区人员">{{ currentRow?.parkPersonnel }}</el-descriptions-item>
+        <el-descriptions-item min-width="200" label="标准厂房">{{ currentRow?.standardFactory }}</el-descriptions-item>
+        <el-descriptions-item min-width="200" label="总车位">{{ currentRow?.parkingLot }}</el-descriptions-item>
+        <el-descriptions-item min-width="200" label="生产用地">{{ currentRow?.productionLand }}</el-descriptions-item>
+        <el-descriptions-item min-width="200" label="办公用地">{{ currentRow?.officeLand }}</el-descriptions-item>
+        <el-descriptions-item min-width="200" label="绿色植被">{{ currentRow?.greenVegetation }}</el-descriptions-item>
+        <el-descriptions-item min-width="200" label="基础设施">{{ currentRow?.infrastructure }}</el-descriptions-item>
+        <el-descriptions-item min-width="200" label="待开发区">{{ currentRow?.developmentArea }}</el-descriptions-item>
+        <el-descriptions-item min-width="200" label="业务类型">{{ currentRow?.businessType }}</el-descriptions-item>
+        <el-descriptions-item min-width="200" label="是否启用">
+          <el-tag :type="currentRow?.isEnable === 1 ? 'success' : 'danger'">
+            {{ currentRow?.isEnable === 1 ? '启用' : '禁用' }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item min-width="200" label="附件">
+          <template #default>
+            <video v-if="getSuffix(currentRow?.filePath) === 'mp4'" :src="minioBaseUrl+currentRow?.filePath"
+                   controls="true" class="w-full h-full" autoplay="true" />
+            <el-link v-else :href="minioBaseUrl+currentRow?.filePath" type="primary" target="_blank">
+              {{ currentRow?.filePath }}
+            </el-link>
+          </template>
+        </el-descriptions-item>
+      </el-descriptions>
+    </el-dialog>
+    <el-dialog v-model="editDialogVisible" title="编辑园区信息" width="50%">
+      <el-form :model="editForm" label-width="120px">
+        <el-form-item label="园区名称">
+          <el-input v-model="editForm.parkName"></el-input>
+        </el-form-item>
+        <el-form-item label="园区总面积">
+          <el-input v-model="editForm.totalArea"></el-input>
+        </el-form-item>
+        <el-form-item label="入职企业">
+          <el-input v-model="editForm.enteredCompany"></el-input>
+        </el-form-item>
+        <el-form-item label="楼栋数量">
+          <el-input v-model="editForm.numberBuildings"></el-input>
+        </el-form-item>
+        <el-form-item label="园区人员">
+          <el-input v-model="editForm.parkPersonnel"></el-input>
+        </el-form-item>
+        <el-form-item label="标准厂房">
+          <el-input v-model="editForm.standardFactory"></el-input>
+        </el-form-item>
+        <el-form-item label="总车位">
+          <el-input v-model="editForm.parkingLot"></el-input>
+        </el-form-item>
+        <el-form-item label="生产用地">
+          <el-input v-model="editForm.productionLand"></el-input>
+        </el-form-item>
+        <el-form-item label="办公用地">
+          <el-input v-model="editForm.officeLand"></el-input>
+        </el-form-item>
+        <el-form-item label="绿色植被">
+          <el-input v-model="editForm.greenVegetation"></el-input>
+        </el-form-item>
+        <el-form-item label="基础设施">
+          <el-input v-model="editForm.infrastructure"></el-input>
+        </el-form-item>
+        <el-form-item label="待开发区">
+          <el-input v-model="editForm.developmentArea"></el-input>
+        </el-form-item>
+        <el-form-item label="业务类型">
+          <el-input v-model="editForm.businessType"></el-input>
+        </el-form-item>
+        <el-form-item label="上传文件">
+          <el-upload
+            action=""
+            :auto-upload="false"
+            :limit="1"
+            :on-change="(file:UploadFile) => { editForm.file = file.raw }"
+          >
+            <template #trigger>
+              <el-button type="primary">选取文件</el-button>
+            </template>
+            <template #tip>
+              <div class="el-upload__tip">
+                只能上传 jpg/png/mp4 文件,且不超过 10MB
+              </div>
+            </template>
+          </el-upload>
+        </el-form-item>
+        <el-form-item label="是否启用">
+          <el-switch v-model="editForm.isEnable" :active-value="1" :inactive-value="0"></el-switch>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+    <span class="dialog-footer">
+      <el-button @click="editDialogVisible = false">取消</el-button>
+      <el-button type="primary" @click="handleSaveEdit">保存</el-button>
+    </span>
+      </template>
+    </el-dialog>
+    <el-dialog v-model="addDialogVisible" title="新增园区信息" width="50%">
+      <el-form :model="addForm" label-width="120px">
+        <el-form-item label="园区名称">
+          <el-input v-model="addForm.parkName"></el-input>
+        </el-form-item>
+        <el-form-item label="园区总面积">
+          <el-input v-model="addForm.totalArea"></el-input>
+        </el-form-item>
+        <el-form-item label="入职企业">
+          <el-input v-model="addForm.enteredCompany"></el-input>
+        </el-form-item>
+        <el-form-item label="楼栋数量">
+          <el-input v-model="addForm.numberBuildings"></el-input>
+        </el-form-item>
+        <el-form-item label="园区人员">
+          <el-input v-model="addForm.parkPersonnel"></el-input>
+        </el-form-item>
+        <el-form-item label="标准厂房">
+          <el-input v-model="addForm.standardFactory"></el-input>
+        </el-form-item>
+        <el-form-item label="总车位">
+          <el-input v-model="addForm.parkingLot"></el-input>
+        </el-form-item>
+        <el-form-item label="生产用地">
+          <el-input v-model="addForm.productionLand"></el-input>
+        </el-form-item>
+        <el-form-item label="办公用地">
+          <el-input v-model="addForm.officeLand"></el-input>
+        </el-form-item>
+        <el-form-item label="绿色植被">
+          <el-input v-model="addForm.greenVegetation"></el-input>
+        </el-form-item>
+        <el-form-item label="基础设施">
+          <el-input v-model="addForm.infrastructure"></el-input>
+        </el-form-item>
+        <el-form-item label="待开发区">
+          <el-input v-model="addForm.developmentArea"></el-input>
+        </el-form-item>
+        <el-form-item label="业务类型">
+          <el-input v-model="addForm.businessType"></el-input>
+        </el-form-item>
+        <el-form-item label="上传文件">
+          <el-upload
+            action=""
+            :auto-upload="false"
+            :limit="1"
+            :on-change="(file:UploadFile) => { addForm.file = file.raw }"
+          >
+            <template #trigger>
+              <el-button type="primary">选取文件</el-button>
+            </template>
+            <template #tip>
+              <div class="el-upload__tip">
+                只能上传 jpg/png/mp4 文件,且不超过 10MB
+              </div>
+            </template>
+          </el-upload>
+        </el-form-item>
+        <el-form-item label="是否启用">
+          <el-switch v-model="addForm.isEnable" :active-value="1" :inactive-value="0"></el-switch>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="addDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="handleAdd">保存</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+

+ 21 - 17
src/views/zhld/dggl.vue

@@ -27,7 +27,7 @@ interface LightData {
 
 const cardDatas = ref<LightData[]>([]);
 const linkDialogVisible = ref(false);
-const downloadUrl = ref('public/LedOK Express.rar');
+// const downloadUrl = ref('public/LedOK Express.rar');
 
 const orignalParams = [
   {
@@ -59,23 +59,28 @@ const getData = async () => {
     background: 'rgba(200, 200, 200, .8)'
   })
   const list = []
-  for (let i = 0; i < orignalParams.length; i++) {
-    const res = await clientGet<StatusParams, StatusResponse>('/pole/instruct/issued/equipmentStatus', {
-      params: orignalParams[i]
+  try {
+    for (let i = 0; i < orignalParams.length; i++) {
+      const res = await clientGet<StatusParams, StatusResponse>('/pole/instruct/issued/equipmentStatus', {
+        params: orignalParams[i]
+      })
+      list.push(res)
+      process = i + 1
+      loading.setText('正在加载数据' + process + '/' + orignalParams.length)
+    }
+
+    const resList: LightData[] = list.map(res => {
+      return JSON.parse(JSON.parse(res.data).params)
+    })
+    cardDatas.value = resList.flat(-1 >>> 1).map(item => {
+      item.brightness = item.brightness != 0
+      return item
     })
-    list.push(res)
-    process = i + 1
-    loading.setText('正在加载数据' + process + '/' + orignalParams.length)
+  } catch (error) {
+    console.error('数据获取失败:', error)
+  } finally {
+    loading.close()
   }
-
-  const resList: LightData[] = list.map(res => {
-    return JSON.parse(JSON.parse(res.data).params)
-  })
-  cardDatas.value = resList.flat(-1 >>> 1).map(item => {
-    item.brightness = item.brightness != 0
-    return item
-  })
-  loading.close()
 }
 const changeLight = useDebounceFn(async (light: LightData) => {
   const loading = ElLoading.service({
@@ -237,7 +242,6 @@ init()
       <template #default>
         <div class="flex flex-col gap-10px">
           <div>
-            <!-- 使用 download 属性 -->
             <a :href="'/LedOK Express.rar'" download="LedOK_Express.rar">下载大屏显示器软件</a>
           </div>
         </div>

+ 2 - 1
vite.config.ts

@@ -31,7 +31,8 @@ export default defineConfig({
   server: {
     proxy: {
       '/api': {
-        target: 'http://172.16.102.52:8080',
+        // target: 'http://172.16.102.52:8080',
+        target: 'http://192.168.110.13:8080',
         changeOrigin: true,
         rewrite: (path) => path.replace(/^\/api/, '')
       }