liujintao 2 săptămâni în urmă
comite
c876fd91f2
71 a modificat fișierele cu 9934 adăugiri și 0 ștergeri
  1. 3 0
      .env
  2. 16 0
      .env.development
  3. 17 0
      .env.production
  4. 8 0
      .eslintignore
  5. 59 0
      .eslintrc.cjs
  6. 30 0
      .gitignore
  7. 8 0
      .prettierignore
  8. 9 0
      .prettierrc.json
  9. 8 0
      .vscode/extensions.json
  10. 46 0
      README.md
  11. 9 0
      auto-imports.d.ts
  12. 34 0
      components.d.ts
  13. 16 0
      env.d.ts
  14. 14 0
      index.html
  15. 2877 0
      package-lock.json
  16. 34 0
      package.json
  17. BIN
      public/favicon.ico
  18. BIN
      public/mp4-encoder.wasm
  19. 2621 0
      public/wasmconverter.js
  20. BIN
      public/wasmconverter.wasm
  21. 0 0
      public/wfs-min.js
  22. 6 0
      src/App.vue
  23. 12 0
      src/api/login.ts
  24. 113 0
      src/api/setting.ts
  25. 11 0
      src/api/types/login.ts
  26. 21 0
      src/api/types/preview.ts
  27. 15 0
      src/api/types/setting.ts
  28. 10 0
      src/api/userManagement.ts
  29. 45 0
      src/assets/base.css
  30. BIN
      src/assets/black.jpg
  31. 1 0
      src/assets/logo.svg
  32. 19 0
      src/assets/main.css
  33. 76 0
      src/auto-import.d.ts
  34. 512 0
      src/components/myVideo.vue
  35. 28 0
      src/config/route.ts
  36. 7 0
      src/constants/app-key.ts
  37. 17 0
      src/constants/cache-key.ts
  38. 48 0
      src/hooks/useRouteListener.ts
  39. 29 0
      src/layouts/components/AppMain.vue
  40. 36 0
      src/layouts/components/Hamburger/index.vue
  41. 58 0
      src/layouts/components/NavigationBar/index.vue
  42. 105 0
      src/layouts/components/SideBar/index.vue
  43. 92 0
      src/layouts/components/SideBar/sidebarItem.vue
  44. 83 0
      src/layouts/index.vue
  45. 26 0
      src/main.ts
  46. 78 0
      src/router/index.ts
  47. 28 0
      src/router/permission.ts
  48. 5 0
      src/stores/index.ts
  49. 35 0
      src/stores/modules/app.ts
  50. 3 0
      src/stores/modules/tags-view.ts
  51. 47 0
      src/stores/modules/user.ts
  52. 33 0
      src/utils/cache/cookies.ts
  53. 38 0
      src/utils/cache/local-storage.ts
  54. 29 0
      src/utils/index.ts
  55. 44 0
      src/utils/request.ts
  56. 222 0
      src/views/login/index.vue
  57. 281 0
      src/views/preview/index.vue
  58. 191 0
      src/views/setting/netSetting/components/IPInputBox.vue
  59. 377 0
      src/views/setting/netSetting/index.vue
  60. 187 0
      src/views/setting/systemSetting/components/alarm/index.vue
  61. 75 0
      src/views/setting/systemSetting/components/cameraInfo/index.vue
  62. 90 0
      src/views/setting/systemSetting/components/nightVision/index.vue
  63. 237 0
      src/views/setting/systemSetting/components/time/index.vue
  64. 137 0
      src/views/setting/systemSetting/components/user/index.vue
  65. 296 0
      src/views/setting/systemSetting/components/volume/index.vue
  66. 181 0
      src/views/setting/systemSetting/index.vue
  67. 14 0
      tsconfig.app.json
  68. 11 0
      tsconfig.json
  69. 17 0
      tsconfig.node.json
  70. 6 0
      types/api.d.ts
  71. 93 0
      vite.config.ts

+ 3 - 0
.env

@@ -0,0 +1,3 @@
+## 项目标题
+VITE_APP_TITLE='Camera Management
+'

+ 16 - 0
.env.development

@@ -0,0 +1,16 @@
+# 开发环境自定义的环境变量(命名必须以 VITE_ 开头)
+
+VITE_PUBLIC_PATH='/'
+
+## 路由模式 hash 或 html5
+VITE_ROUTER_HISTORY='html5'
+
+
+VITE_HOST_IP = '192.168.32.103'
+
+VITE_VIDEO_PORT = '8000'
+
+VITE_HEART_BEAT_PORT = '8001'
+
+## 自动获取ip开关 开:true 关:false
+VITE_AUTOMATIC_IP_FLAG=true

+ 17 - 0
.env.production

@@ -0,0 +1,17 @@
+# 生产环境自定义的环境变量(命名必须以 VITE_ 开头)
+
+## 后端接口公共路径
+VITE_BASE_API='http://192.168.0.111:7000'
+
+## 路由模式 hash 或 html5
+VITE_ROUTER_HISTORY='html5'
+
+## 打包路径
+VITE_PUBLIC_PATH='/'
+
+## websocket地址
+VITE_AUTOMATIC_IP_FLAG=true
+
+VITE_VIDEO_PORT = '8000'
+
+VITE_HEART_BEAT_PORT = '8001'

+ 8 - 0
.eslintignore

@@ -0,0 +1,8 @@
+# Eslint 会忽略的文件
+
+.DS_Store
+node_modules
+dist
+dist-ssr
+*.local
+.npmrc

+ 59 - 0
.eslintrc.cjs

@@ -0,0 +1,59 @@
+/* eslint-env node */
+require('@rushstack/eslint-patch/modern-module-resolution')
+
+module.exports = {
+  root: true,
+  extends: [
+    'plugin:vue/vue3-essential',
+    'eslint:recommended',
+    '@vue/eslint-config-typescript',
+    '@vue/eslint-config-prettier/skip-formatting',
+    'plugin:prettier/recommended'
+  ],
+  parserOptions: {
+    ecmaVersion: 'latest'
+  },
+  rules: {
+    // Vue
+    'vue/no-v-html': 'off',
+    'vue/require-default-prop': 'off',
+    'vue/require-explicit-emits': 'off',
+    'vue/multi-word-component-names': [
+      'error',
+      {
+        ignores: ['index', 'main'] //需要忽略的组件名
+      }
+    ],
+    'vue/html-self-closing': [
+      'error',
+      {
+        html: {
+          void: 'always',
+          normal: 'always',
+          component: 'always'
+        },
+        svg: 'always',
+        math: 'always'
+      }
+    ],
+    'vue/attributes-order': [
+      'warn',
+      {
+        order: [
+          'DEFINITION',
+          'LIST_RENDERING',
+          'CONDITIONALS',
+          'RENDER_MODIFIERS',
+          'GLOBAL',
+          'UNIQUE',
+          'TWO_WAY_BINDING',
+          'OTHER_DIRECTIVES',
+          'OTHER_ATTR',
+          'EVENTS',
+          'CONTENT'
+        ],
+        alphabetical: true //字母顺序
+      }
+    ]
+  }
+}

+ 30 - 0
.gitignore

@@ -0,0 +1,30 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+*.tsbuildinfo

+ 8 - 0
.prettierignore

@@ -0,0 +1,8 @@
+# Prettier 会忽略的文件
+
+.DS_Store
+node_modules
+dist
+dist-ssr
+*.local
+.npmrc

+ 9 - 0
.prettierrc.json

@@ -0,0 +1,9 @@
+{
+  "$schema": "https://json.schemastore.org/prettierrc",
+  "printWidth": 80,
+  "semi": false,
+  "tabWidth": 2,
+  "singleQuote": true,
+  "trailingComma": "none",
+  "endOfLine": "auto"
+}

+ 8 - 0
.vscode/extensions.json

@@ -0,0 +1,8 @@
+{
+  "recommendations": [
+    "Vue.volar",
+    "Vue.vscode-typescript-vue-plugin",
+    "dbaeumer.vscode-eslint",
+    "esbenp.prettier-vscode"
+  ]
+}

+ 46 - 0
README.md

@@ -0,0 +1,46 @@
+# cw_web
+
+This template should help get you started developing with Vue 3 in Vite.
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
+
+## Type Support for `.vue` Imports in TS
+
+TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
+
+If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
+
+1. Disable the built-in TypeScript Extension
+    1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
+    2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
+2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vitejs.dev/config/).
+
+## Project Setup
+
+```sh
+yarn
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+yarn dev
+```
+
+### Type-Check, Compile and Minify for Production
+
+```sh
+yarn build
+```
+
+### Lint with [ESLint](https://eslint.org/)
+
+```sh
+yarn lint
+```

+ 9 - 0
auto-imports.d.ts

@@ -0,0 +1,9 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// noinspection JSUnusedGlobalSymbols
+// Generated by unplugin-auto-import
+export {}
+declare global {
+
+}

+ 34 - 0
components.d.ts

@@ -0,0 +1,34 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// Generated by unplugin-vue-components
+// Read more: https://github.com/vuejs/core/pull/3399
+export {}
+
+declare module 'vue' {
+  export interface GlobalComponents {
+    ElAside: typeof import('element-plus/es')['ElAside']
+    ElButton: typeof import('element-plus/es')['ElButton']
+    ElForm: typeof import('element-plus/es')['ElForm']
+    ElFormItem: typeof import('element-plus/es')['ElFormItem']
+    ElIcon: typeof import('element-plus/es')['ElIcon']
+    ElInput: typeof import('element-plus/es')['ElInput']
+    ElLink: typeof import('element-plus/es')['ElLink']
+    ElMenu: typeof import('element-plus/es')['ElMenu']
+    ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
+    ElOption: typeof import('element-plus/es')['ElOption']
+    ElRadio: typeof import('element-plus/es')['ElRadio']
+    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
+    ElSelect: typeof import('element-plus/es')['ElSelect']
+    ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
+    ElSwitch: typeof import('element-plus/es')['ElSwitch']
+    ElTabPane: typeof import('element-plus/es')['ElTabPane']
+    ElTabs: typeof import('element-plus/es')['ElTabs']
+    MyVideo: typeof import('./src/components/myVideo.vue')['default']
+    RouterLink: typeof import('vue-router')['RouterLink']
+    RouterView: typeof import('vue-router')['RouterView']
+  }
+  export interface ComponentCustomProperties {
+    vLoading: typeof import('element-plus/es')['ElLoadingDirective']
+  }
+}

+ 16 - 0
env.d.ts

@@ -0,0 +1,16 @@
+declare interface ImportMetaEnv {
+  readonly VITE_APP_TITLE: string
+  readonly VITE_BASE_API: string
+  readonly VITE_ROUTER_HISTORY: 'hash' | 'html5'
+  readonly VITE_PUBLIC_PATH: string
+}
+
+interface ImportMeta {
+  readonly env: ImportMetaEnv
+}
+
+declare module '*.vue' {
+  import { DefineComponent } from 'vue'
+  const component: DefineComponent<{}, {}, any>
+  export default component
+}

+ 14 - 0
index.html

@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8">
+    <link rel="icon" href="/favicon.ico">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>%VITE_APP_TITLE%</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+    <script type="module" src="/wfs-min.js"></script>
+  </body>
+</html>

+ 2877 - 0
package-lock.json

@@ -0,0 +1,2877 @@
+{
+  "name": "xwsport_web",
+  "version": "1.0.1",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "xwsport_web",
+      "version": "1.0.1",
+      "dependencies": {
+        "axios": "^1.6.5",
+        "date-fns": "^3.6.0",
+        "element-plus": "^2.5.1",
+        "js-cookie": "^3.0.5",
+        "nprogress": "^0.2.0",
+        "pinia": "^2.1.7",
+        "vue": "^3.3.11",
+        "vue-router": "^4.2.5"
+      },
+      "devDependencies": {
+        "@types/js-cookie": "^3.0.6",
+        "@types/nprogress": "^0.2.3",
+        "@vitejs/plugin-vue": "^4.5.2",
+        "@vue/tsconfig": "^0.5.0",
+        "sass": "^1.97.0",
+        "typescript": "~5.3.0",
+        "unplugin-auto-import": "^0.17.3",
+        "unplugin-vue-components": "^0.26.0",
+        "vite": "^5.4.21"
+      }
+    },
+    "node_modules/@antfu/utils": {
+      "version": "0.7.10",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@antfu/utils/-/utils-0.7.10.tgz",
+      "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==",
+      "dev": true,
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.23.6",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.23.6.tgz",
+      "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==",
+      "license": "MIT",
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@ctrl/tinycolor": {
+      "version": "3.6.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
+      "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@element-plus/icons-vue": {
+      "version": "2.3.2",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
+      "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
+      "license": "MIT",
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+      "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+      "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+      "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+      "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+      "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+      "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+      "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+      "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+      "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+      "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+      "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+      "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+      "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+      "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+      "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+      "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+      "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+      "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+      "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+      "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+      "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+      "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+      "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@floating-ui/core": {
+      "version": "1.7.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@floating-ui/core/-/core-1.7.3.tgz",
+      "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
+      "license": "MIT",
+      "dependencies": {
+        "@floating-ui/utils": "^0.2.10"
+      }
+    },
+    "node_modules/@floating-ui/dom": {
+      "version": "1.7.4",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@floating-ui/dom/-/dom-1.7.4.tgz",
+      "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
+      "license": "MIT",
+      "dependencies": {
+        "@floating-ui/core": "^1.7.3",
+        "@floating-ui/utils": "^0.2.10"
+      }
+    },
+    "node_modules/@floating-ui/utils": {
+      "version": "0.2.10",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@floating-ui/utils/-/utils-0.2.10.tgz",
+      "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+      "license": "MIT"
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+      "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+      "license": "MIT"
+    },
+    "node_modules/@nodelib/fs.scandir": {
+      "version": "2.1.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@nodelib/fs.stat": "2.0.5",
+        "run-parallel": "^1.1.9"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.stat": {
+      "version": "2.0.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.walk": {
+      "version": "1.2.8",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@nodelib/fs.scandir": "2.1.5",
+        "fastq": "^1.6.0"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@parcel/watcher": {
+      "version": "2.5.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@parcel/watcher/-/watcher-2.5.1.tgz",
+      "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "detect-libc": "^1.0.3",
+        "is-glob": "^4.0.3",
+        "micromatch": "^4.0.5",
+        "node-addon-api": "^7.0.0"
+      },
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      },
+      "optionalDependencies": {
+        "@parcel/watcher-android-arm64": "2.5.1",
+        "@parcel/watcher-darwin-arm64": "2.5.1",
+        "@parcel/watcher-darwin-x64": "2.5.1",
+        "@parcel/watcher-freebsd-x64": "2.5.1",
+        "@parcel/watcher-linux-arm-glibc": "2.5.1",
+        "@parcel/watcher-linux-arm-musl": "2.5.1",
+        "@parcel/watcher-linux-arm64-glibc": "2.5.1",
+        "@parcel/watcher-linux-arm64-musl": "2.5.1",
+        "@parcel/watcher-linux-x64-glibc": "2.5.1",
+        "@parcel/watcher-linux-x64-musl": "2.5.1",
+        "@parcel/watcher-win32-arm64": "2.5.1",
+        "@parcel/watcher-win32-ia32": "2.5.1",
+        "@parcel/watcher-win32-x64": "2.5.1"
+      }
+    },
+    "node_modules/@parcel/watcher-android-arm64": {
+      "version": "2.5.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
+      "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-darwin-arm64": {
+      "version": "2.5.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
+      "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-darwin-x64": {
+      "version": "2.5.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
+      "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-freebsd-x64": {
+      "version": "2.5.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
+      "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm-glibc": {
+      "version": "2.5.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
+      "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm-musl": {
+      "version": "2.5.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
+      "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm64-glibc": {
+      "version": "2.5.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
+      "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm64-musl": {
+      "version": "2.5.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
+      "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-x64-glibc": {
+      "version": "2.5.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
+      "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-x64-musl": {
+      "version": "2.5.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
+      "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-arm64": {
+      "version": "2.5.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
+      "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-ia32": {
+      "version": "2.5.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
+      "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-x64": {
+      "version": "2.5.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
+      "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@popperjs/core": {
+      "name": "@sxzz/popperjs-es",
+      "version": "2.11.7",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
+      "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/popperjs"
+      }
+    },
+    "node_modules/@rollup/pluginutils": {
+      "version": "5.3.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
+      "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "^1.0.0",
+        "estree-walker": "^2.0.2",
+        "picomatch": "^4.0.2"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "peerDependencies": {
+        "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+      },
+      "peerDependenciesMeta": {
+        "rollup": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.5.tgz",
+      "integrity": "sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.5.tgz",
+      "integrity": "sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.5.tgz",
+      "integrity": "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.5.tgz",
+      "integrity": "sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.5.tgz",
+      "integrity": "sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.5.tgz",
+      "integrity": "sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.5.tgz",
+      "integrity": "sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.5.tgz",
+      "integrity": "sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.5.tgz",
+      "integrity": "sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.5.tgz",
+      "integrity": "sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loong64-gnu": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.5.tgz",
+      "integrity": "sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.5.tgz",
+      "integrity": "sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.5.tgz",
+      "integrity": "sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-musl": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.5.tgz",
+      "integrity": "sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.5.tgz",
+      "integrity": "sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.5.tgz",
+      "integrity": "sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.5.tgz",
+      "integrity": "sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-openharmony-arm64": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.5.tgz",
+      "integrity": "sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.5.tgz",
+      "integrity": "sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.5.tgz",
+      "integrity": "sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-gnu": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.5.tgz",
+      "integrity": "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.5.tgz",
+      "integrity": "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.8",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@types/estree/-/estree-1.0.8.tgz",
+      "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/js-cookie": {
+      "version": "3.0.6",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@types/js-cookie/-/js-cookie-3.0.6.tgz",
+      "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/lodash": {
+      "version": "4.17.21",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@types/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==",
+      "license": "MIT"
+    },
+    "node_modules/@types/lodash-es": {
+      "version": "4.17.12",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+      "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/lodash": "*"
+      }
+    },
+    "node_modules/@types/node": {
+      "version": "20.19.27",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@types/node/-/node-20.19.27.tgz",
+      "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "undici-types": "~6.21.0"
+      }
+    },
+    "node_modules/@types/nprogress": {
+      "version": "0.2.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@types/nprogress/-/nprogress-0.2.3.tgz",
+      "integrity": "sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/web-bluetooth": {
+      "version": "0.0.16",
+      "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
+      "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==",
+      "license": "MIT"
+    },
+    "node_modules/@vitejs/plugin-vue": {
+      "version": "4.6.2",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz",
+      "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^4.0.0 || ^5.0.0",
+        "vue": "^3.2.25"
+      }
+    },
+    "node_modules/@vue/compiler-core": {
+      "version": "3.4.14",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.4.14.tgz",
+      "integrity": "sha512-ro4Zzl/MPdWs7XwxT7omHRxAjMbDFRZEEjD+2m3NBf8YzAe3HuoSEZosXQo+m1GQ1G3LQ1LdmNh1RKTYe+ssEg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.23.6",
+        "@vue/shared": "3.4.14",
+        "entities": "^4.5.0",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.0.2"
+      }
+    },
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.4.14",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.4.14.tgz",
+      "integrity": "sha512-nOZTY+veWNa0DKAceNWxorAbWm0INHdQq7cejFaWM1WYnoNSJbSEKYtE7Ir6lR/+mo9fttZpPVI9ZFGJ1juUEQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-core": "3.4.14",
+        "@vue/shared": "3.4.14"
+      }
+    },
+    "node_modules/@vue/compiler-sfc": {
+      "version": "3.4.14",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.4.14.tgz",
+      "integrity": "sha512-1vHc9Kv1jV+YBZC/RJxQJ9JCxildTI+qrhtDh6tPkR1O8S+olBUekimY0km0ZNn8nG1wjtFAe9XHij+YLR8cRQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.23.6",
+        "@vue/compiler-core": "3.4.14",
+        "@vue/compiler-dom": "3.4.14",
+        "@vue/compiler-ssr": "3.4.14",
+        "@vue/shared": "3.4.14",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.5",
+        "postcss": "^8.4.33",
+        "source-map-js": "^1.0.2"
+      }
+    },
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.4.14",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.4.14.tgz",
+      "integrity": "sha512-bXT6+oAGlFjTYVOTtFJ4l4Jab1wjsC0cfSfOe2B4Z0N2vD2zOBSQ9w694RsCfhjk+bC2DY5Gubb1rHZVii107Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-dom": "3.4.14",
+        "@vue/shared": "3.4.14"
+      }
+    },
+    "node_modules/@vue/devtools-api": {
+      "version": "6.5.1",
+      "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.5.1.tgz",
+      "integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==",
+      "license": "MIT"
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.4.14",
+      "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.4.14.tgz",
+      "integrity": "sha512-xRYwze5Q4tK7tT2J4uy4XLhK/AIXdU5EBUu9PLnIHcOKXO0uyXpNNMzlQKuq7B+zwtq6K2wuUL39pHA6ZQzObw==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/shared": "3.4.14"
+      }
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.4.14",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.4.14.tgz",
+      "integrity": "sha512-qu+NMkfujCoZL6cfqK5NOfxgXJROSlP2ZPs4CTcVR+mLrwl4TtycF5Tgo0QupkdBL+2kigc6EsJlTcuuZC1NaQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/reactivity": "3.4.14",
+        "@vue/shared": "3.4.14"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.4.14",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.4.14.tgz",
+      "integrity": "sha512-B85XmcR4E7XsirEHVqhmy4HPbRT9WLFWV9Uhie3OapV9m1MEN9+Er6hmUIE6d8/l2sUygpK9RstFM2bmHEUigA==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/runtime-core": "3.4.14",
+        "@vue/shared": "3.4.14",
+        "csstype": "^3.1.3"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.4.14",
+      "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.4.14.tgz",
+      "integrity": "sha512-pwSKXQfYdJBTpvWHGEYI+akDE18TXAiLcGn+Q/2Fj8wQSHWztoo7PSvfMNqu6NDhp309QXXbPFEGCU5p85HqkA==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.4.14",
+        "@vue/shared": "3.4.14"
+      },
+      "peerDependencies": {
+        "vue": "3.4.14"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.4.14",
+      "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.4.14.tgz",
+      "integrity": "sha512-nmi3BtLpvqXAWoRZ6HQ+pFJOHBU4UnH3vD3opgmwXac7vhaHKA9nj1VeGjMggdB9eLtW83eHyPCmOU1qzdsC7Q==",
+      "license": "MIT"
+    },
+    "node_modules/@vue/tsconfig": {
+      "version": "0.5.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@vue/tsconfig/-/tsconfig-0.5.1.tgz",
+      "integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@vueuse/core": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz",
+      "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/web-bluetooth": "^0.0.16",
+        "@vueuse/metadata": "9.13.0",
+        "@vueuse/shared": "9.13.0",
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/core/node_modules/vue-demi": {
+      "version": "0.14.6",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz",
+      "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vueuse/metadata": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz",
+      "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz",
+      "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
+      "license": "MIT",
+      "dependencies": {
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared/node_modules/vue-demi": {
+      "version": "0.14.6",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz",
+      "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/acorn": {
+      "version": "8.15.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/acorn/-/acorn-8.15.0.tgz",
+      "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/anymatch": {
+      "version": "3.1.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/anymatch/-/anymatch-3.1.3.tgz",
+      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/anymatch/node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/async-validator": {
+      "version": "4.2.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/async-validator/-/async-validator-4.2.5.tgz",
+      "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
+      "license": "MIT"
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "license": "MIT"
+    },
+    "node_modules/axios": {
+      "version": "1.6.5",
+      "resolved": "https://registry.npmmirror.com/axios/-/axios-1.6.5.tgz",
+      "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.15.4",
+        "form-data": "^4.0.0",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.3.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/binary-extensions/-/binary-extensions-2.3.0.tgz",
+      "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/brace-expansion": {
+      "version": "2.0.2",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/brace-expansion/-/brace-expansion-2.0.2.tgz",
+      "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "fill-range": "^7.1.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/chokidar": {
+      "version": "3.6.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/chokidar/-/chokidar-3.6.0.tgz",
+      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "license": "MIT",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/confbox": {
+      "version": "0.1.8",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/confbox/-/confbox-0.1.8.tgz",
+      "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/csstype": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
+      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+      "license": "MIT"
+    },
+    "node_modules/date-fns": {
+      "version": "3.6.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/date-fns/-/date-fns-3.6.0.tgz",
+      "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/kossnocorp"
+      }
+    },
+    "node_modules/dayjs": {
+      "version": "1.11.19",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/dayjs/-/dayjs-1.11.19.tgz",
+      "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
+      "license": "MIT"
+    },
+    "node_modules/debug": {
+      "version": "4.4.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/debug/-/debug-4.4.3.tgz",
+      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/detect-libc": {
+      "version": "1.0.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/detect-libc/-/detect-libc-1.0.3.tgz",
+      "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "optional": true,
+      "bin": {
+        "detect-libc": "bin/detect-libc.js"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/element-plus": {
+      "version": "2.12.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/element-plus/-/element-plus-2.12.0.tgz",
+      "integrity": "sha512-M9YLSn2np9OnqrSKWsiXvGe3qnF8pd94+TScsHj1aTMCD+nSEvucXermf807qNt6hOP040le0e5Aft7E9ZfHmA==",
+      "license": "MIT",
+      "dependencies": {
+        "@ctrl/tinycolor": "^3.4.1",
+        "@element-plus/icons-vue": "^2.3.2",
+        "@floating-ui/dom": "^1.0.1",
+        "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
+        "@types/lodash": "^4.17.20",
+        "@types/lodash-es": "^4.17.12",
+        "@vueuse/core": "^9.1.0",
+        "async-validator": "^4.2.5",
+        "dayjs": "^1.11.19",
+        "lodash": "^4.17.21",
+        "lodash-es": "^4.17.21",
+        "lodash-unified": "^1.0.3",
+        "memoize-one": "^6.0.0",
+        "normalize-wheel-es": "^1.2.0"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/entities": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
+      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.21.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/esbuild/-/esbuild-0.21.5.tgz",
+      "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.21.5",
+        "@esbuild/android-arm": "0.21.5",
+        "@esbuild/android-arm64": "0.21.5",
+        "@esbuild/android-x64": "0.21.5",
+        "@esbuild/darwin-arm64": "0.21.5",
+        "@esbuild/darwin-x64": "0.21.5",
+        "@esbuild/freebsd-arm64": "0.21.5",
+        "@esbuild/freebsd-x64": "0.21.5",
+        "@esbuild/linux-arm": "0.21.5",
+        "@esbuild/linux-arm64": "0.21.5",
+        "@esbuild/linux-ia32": "0.21.5",
+        "@esbuild/linux-loong64": "0.21.5",
+        "@esbuild/linux-mips64el": "0.21.5",
+        "@esbuild/linux-ppc64": "0.21.5",
+        "@esbuild/linux-riscv64": "0.21.5",
+        "@esbuild/linux-s390x": "0.21.5",
+        "@esbuild/linux-x64": "0.21.5",
+        "@esbuild/netbsd-x64": "0.21.5",
+        "@esbuild/openbsd-x64": "0.21.5",
+        "@esbuild/sunos-x64": "0.21.5",
+        "@esbuild/win32-arm64": "0.21.5",
+        "@esbuild/win32-ia32": "0.21.5",
+        "@esbuild/win32-x64": "0.21.5"
+      }
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "5.0.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+      "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+      "license": "MIT"
+    },
+    "node_modules/exsolve": {
+      "version": "1.0.8",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/exsolve/-/exsolve-1.0.8.tgz",
+      "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/fast-glob": {
+      "version": "3.3.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/fast-glob/-/fast-glob-3.3.3.tgz",
+      "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@nodelib/fs.stat": "^2.0.2",
+        "@nodelib/fs.walk": "^1.2.3",
+        "glob-parent": "^5.1.2",
+        "merge2": "^1.3.0",
+        "micromatch": "^4.0.8"
+      },
+      "engines": {
+        "node": ">=8.6.0"
+      }
+    },
+    "node_modules/fastq": {
+      "version": "1.19.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/fastq/-/fastq-1.19.1.tgz",
+      "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "reusify": "^1.0.4"
+      }
+    },
+    "node_modules/fill-range": {
+      "version": "7.1.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.5",
+      "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.5.tgz",
+      "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+      "license": "MIT",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "dev": true,
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/immutable": {
+      "version": "5.1.4",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/immutable/-/immutable-5.1.4.tgz",
+      "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-core-module": {
+      "version": "2.16.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/is-core-module/-/is-core-module-2.16.1.tgz",
+      "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/js-cookie": {
+      "version": "3.0.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/js-cookie/-/js-cookie-3.0.5.tgz",
+      "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/js-tokens": {
+      "version": "9.0.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/js-tokens/-/js-tokens-9.0.1.tgz",
+      "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/local-pkg": {
+      "version": "0.5.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/local-pkg/-/local-pkg-0.5.1.tgz",
+      "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "mlly": "^1.7.3",
+        "pkg-types": "^1.2.1"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/lodash": {
+      "version": "4.17.21",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "license": "MIT"
+    },
+    "node_modules/lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
+      "license": "MIT"
+    },
+    "node_modules/lodash-unified": {
+      "version": "1.0.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/lodash-unified/-/lodash-unified-1.0.3.tgz",
+      "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/lodash-es": "*",
+        "lodash": "*",
+        "lodash-es": "*"
+      }
+    },
+    "node_modules/magic-string": {
+      "version": "0.30.17",
+      "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz",
+      "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.0"
+      }
+    },
+    "node_modules/memoize-one": {
+      "version": "6.0.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/memoize-one/-/memoize-one-6.0.0.tgz",
+      "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
+      "license": "MIT"
+    },
+    "node_modules/merge2": {
+      "version": "1.4.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/merge2/-/merge2-1.4.1.tgz",
+      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/micromatch": {
+      "version": "4.0.8",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "braces": "^3.0.3",
+        "picomatch": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
+    "node_modules/micromatch/node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "9.0.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/minimatch/-/minimatch-9.0.5.tgz",
+      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/mlly": {
+      "version": "1.8.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/mlly/-/mlly-1.8.0.tgz",
+      "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "acorn": "^8.15.0",
+        "pathe": "^2.0.3",
+        "pkg-types": "^1.3.1",
+        "ufo": "^1.6.1"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.11",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/nanoid/-/nanoid-3.3.11.tgz",
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/node-addon-api": {
+      "version": "7.1.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/node-addon-api/-/node-addon-api-7.1.1.tgz",
+      "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/normalize-wheel-es": {
+      "version": "1.2.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
+      "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/nprogress": {
+      "version": "0.2.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/nprogress/-/nprogress-0.2.0.tgz",
+      "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==",
+      "license": "MIT"
+    },
+    "node_modules/path-parse": {
+      "version": "1.0.7",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/path-parse/-/path-parse-1.0.7.tgz",
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/pathe": {
+      "version": "2.0.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/pathe/-/pathe-2.0.3.tgz",
+      "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "license": "ISC"
+    },
+    "node_modules/picomatch": {
+      "version": "4.0.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/picomatch/-/picomatch-4.0.3.tgz",
+      "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/pinia": {
+      "version": "2.1.7",
+      "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.7.tgz",
+      "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/devtools-api": "^6.5.0",
+        "vue-demi": ">=0.14.5"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.4.0",
+        "typescript": ">=4.4.4",
+        "vue": "^2.6.14 || ^3.3.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        },
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/pinia/node_modules/vue-demi": {
+      "version": "0.14.6",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz",
+      "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/pkg-types": {
+      "version": "1.3.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/pkg-types/-/pkg-types-1.3.1.tgz",
+      "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "confbox": "^0.1.8",
+        "mlly": "^1.7.4",
+        "pathe": "^2.0.1"
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.5.6",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/postcss/-/postcss-8.5.6.tgz",
+      "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "nanoid": "^3.3.11",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+      "license": "MIT"
+    },
+    "node_modules/quansync": {
+      "version": "0.2.11",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/quansync/-/quansync-0.2.11.tgz",
+      "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/antfu"
+        },
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/sxzz"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/queue-microtask": {
+      "version": "1.2.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/queue-microtask/-/queue-microtask-1.2.3.tgz",
+      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/readdirp/node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/resolve": {
+      "version": "1.22.11",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/resolve/-/resolve-1.22.11.tgz",
+      "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "is-core-module": "^2.16.1",
+        "path-parse": "^1.0.7",
+        "supports-preserve-symlinks-flag": "^1.0.0"
+      },
+      "bin": {
+        "resolve": "bin/resolve"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/reusify": {
+      "version": "1.1.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/reusify/-/reusify-1.1.0.tgz",
+      "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "iojs": ">=1.0.0",
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/rollup": {
+      "version": "4.53.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/rollup/-/rollup-4.53.5.tgz",
+      "integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "1.0.8"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.53.5",
+        "@rollup/rollup-android-arm64": "4.53.5",
+        "@rollup/rollup-darwin-arm64": "4.53.5",
+        "@rollup/rollup-darwin-x64": "4.53.5",
+        "@rollup/rollup-freebsd-arm64": "4.53.5",
+        "@rollup/rollup-freebsd-x64": "4.53.5",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.53.5",
+        "@rollup/rollup-linux-arm-musleabihf": "4.53.5",
+        "@rollup/rollup-linux-arm64-gnu": "4.53.5",
+        "@rollup/rollup-linux-arm64-musl": "4.53.5",
+        "@rollup/rollup-linux-loong64-gnu": "4.53.5",
+        "@rollup/rollup-linux-ppc64-gnu": "4.53.5",
+        "@rollup/rollup-linux-riscv64-gnu": "4.53.5",
+        "@rollup/rollup-linux-riscv64-musl": "4.53.5",
+        "@rollup/rollup-linux-s390x-gnu": "4.53.5",
+        "@rollup/rollup-linux-x64-gnu": "4.53.5",
+        "@rollup/rollup-linux-x64-musl": "4.53.5",
+        "@rollup/rollup-openharmony-arm64": "4.53.5",
+        "@rollup/rollup-win32-arm64-msvc": "4.53.5",
+        "@rollup/rollup-win32-ia32-msvc": "4.53.5",
+        "@rollup/rollup-win32-x64-gnu": "4.53.5",
+        "@rollup/rollup-win32-x64-msvc": "4.53.5",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/run-parallel": {
+      "version": "1.2.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/run-parallel/-/run-parallel-1.2.0.tgz",
+      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "queue-microtask": "^1.2.2"
+      }
+    },
+    "node_modules/sass": {
+      "version": "1.97.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/sass/-/sass-1.97.0.tgz",
+      "integrity": "sha512-KR0igP1z4avUJetEuIeOdDlwaUDvkH8wSx7FdSjyYBS3dpyX3TzHfAMO0G1Q4/3cdjcmi3r7idh+KCmKqS+KeQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "chokidar": "^4.0.0",
+        "immutable": "^5.0.2",
+        "source-map-js": ">=0.6.2 <2.0.0"
+      },
+      "bin": {
+        "sass": "sass.js"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "optionalDependencies": {
+        "@parcel/watcher": "^2.4.1"
+      }
+    },
+    "node_modules/sass/node_modules/chokidar": {
+      "version": "4.0.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/chokidar/-/chokidar-4.0.3.tgz",
+      "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "readdirp": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 14.16.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/sass/node_modules/readdirp": {
+      "version": "4.1.2",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/readdirp/-/readdirp-4.1.2.tgz",
+      "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 14.18.0"
+      },
+      "funding": {
+        "type": "individual",
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/scule": {
+      "version": "1.3.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/scule/-/scule-1.3.0.tgz",
+      "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/strip-literal": {
+      "version": "2.1.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/strip-literal/-/strip-literal-2.1.1.tgz",
+      "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "js-tokens": "^9.0.1"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/supports-preserve-symlinks-flag": {
+      "version": "1.0.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.3.3",
+      "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.3.3.tgz",
+      "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
+      "devOptional": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/ufo": {
+      "version": "1.6.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/ufo/-/ufo-1.6.1.tgz",
+      "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/undici-types": {
+      "version": "6.21.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/undici-types/-/undici-types-6.21.0.tgz",
+      "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "peer": true
+    },
+    "node_modules/unimport": {
+      "version": "3.14.6",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/unimport/-/unimport-3.14.6.tgz",
+      "integrity": "sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@rollup/pluginutils": "^5.1.4",
+        "acorn": "^8.14.0",
+        "escape-string-regexp": "^5.0.0",
+        "estree-walker": "^3.0.3",
+        "fast-glob": "^3.3.3",
+        "local-pkg": "^1.0.0",
+        "magic-string": "^0.30.17",
+        "mlly": "^1.7.4",
+        "pathe": "^2.0.1",
+        "picomatch": "^4.0.2",
+        "pkg-types": "^1.3.0",
+        "scule": "^1.3.0",
+        "strip-literal": "^2.1.1",
+        "unplugin": "^1.16.1"
+      }
+    },
+    "node_modules/unimport/node_modules/confbox": {
+      "version": "0.2.2",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/confbox/-/confbox-0.2.2.tgz",
+      "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/unimport/node_modules/estree-walker": {
+      "version": "3.0.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/estree-walker/-/estree-walker-3.0.3.tgz",
+      "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "^1.0.0"
+      }
+    },
+    "node_modules/unimport/node_modules/local-pkg": {
+      "version": "1.1.2",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/local-pkg/-/local-pkg-1.1.2.tgz",
+      "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "mlly": "^1.7.4",
+        "pkg-types": "^2.3.0",
+        "quansync": "^0.2.11"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/unimport/node_modules/local-pkg/node_modules/pkg-types": {
+      "version": "2.3.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/pkg-types/-/pkg-types-2.3.0.tgz",
+      "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "confbox": "^0.2.2",
+        "exsolve": "^1.0.7",
+        "pathe": "^2.0.3"
+      }
+    },
+    "node_modules/unplugin": {
+      "version": "1.16.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/unplugin/-/unplugin-1.16.1.tgz",
+      "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "acorn": "^8.14.0",
+        "webpack-virtual-modules": "^0.6.2"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/unplugin-auto-import": {
+      "version": "0.17.8",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/unplugin-auto-import/-/unplugin-auto-import-0.17.8.tgz",
+      "integrity": "sha512-CHryj6HzJ+n4ASjzwHruD8arhbdl+UXvhuAIlHDs15Y/IMecG3wrf7FVg4pVH/DIysbq/n0phIjNHAjl7TG7Iw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@antfu/utils": "^0.7.10",
+        "@rollup/pluginutils": "^5.1.0",
+        "fast-glob": "^3.3.2",
+        "local-pkg": "^0.5.0",
+        "magic-string": "^0.30.10",
+        "minimatch": "^9.0.4",
+        "unimport": "^3.7.2",
+        "unplugin": "^1.11.0"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@nuxt/kit": "^3.2.2",
+        "@vueuse/core": "*"
+      },
+      "peerDependenciesMeta": {
+        "@nuxt/kit": {
+          "optional": true
+        },
+        "@vueuse/core": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/unplugin-vue-components": {
+      "version": "0.26.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/unplugin-vue-components/-/unplugin-vue-components-0.26.0.tgz",
+      "integrity": "sha512-s7IdPDlnOvPamjunVxw8kNgKNK8A5KM1YpK5j/p97jEKTjlPNrA0nZBiSfAKKlK1gWZuyWXlKL5dk3EDw874LQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@antfu/utils": "^0.7.6",
+        "@rollup/pluginutils": "^5.0.4",
+        "chokidar": "^3.5.3",
+        "debug": "^4.3.4",
+        "fast-glob": "^3.3.1",
+        "local-pkg": "^0.4.3",
+        "magic-string": "^0.30.3",
+        "minimatch": "^9.0.3",
+        "resolve": "^1.22.4",
+        "unplugin": "^1.4.0"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@babel/parser": "^7.15.8",
+        "@nuxt/kit": "^3.2.2",
+        "vue": "2 || 3"
+      },
+      "peerDependenciesMeta": {
+        "@babel/parser": {
+          "optional": true
+        },
+        "@nuxt/kit": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/unplugin-vue-components/node_modules/local-pkg": {
+      "version": "0.4.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/local-pkg/-/local-pkg-0.4.3.tgz",
+      "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/vite": {
+      "version": "5.4.21",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/vite/-/vite-5.4.21.tgz",
+      "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "esbuild": "^0.21.3",
+        "postcss": "^8.4.43",
+        "rollup": "^4.20.0"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^18.0.0 || >=20.0.0",
+        "less": "*",
+        "lightningcss": "^1.21.0",
+        "sass": "*",
+        "sass-embedded": "*",
+        "stylus": "*",
+        "sugarss": "*",
+        "terser": "^5.4.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "lightningcss": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue": {
+      "version": "3.4.14",
+      "resolved": "https://registry.npmmirror.com/vue/-/vue-3.4.14.tgz",
+      "integrity": "sha512-Rop5Al/ZcBbBz+KjPZaZDgHDX0kUP4duEzDbm+1o91uxYUNmJrZSBuegsNIJvUGy+epLevNRNhLjm08VKTgGyw==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-dom": "3.4.14",
+        "@vue/compiler-sfc": "3.4.14",
+        "@vue/runtime-dom": "3.4.14",
+        "@vue/server-renderer": "3.4.14",
+        "@vue/shared": "3.4.14"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue-router": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.2.5.tgz",
+      "integrity": "sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/devtools-api": "^6.5.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/webpack-virtual-modules": {
+      "version": "0.6.2",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
+      "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
+      "dev": true,
+      "license": "MIT"
+    }
+  }
+}

+ 34 - 0
package.json

@@ -0,0 +1,34 @@
+{
+  "name": "xwsport_web",
+  "version": "1.0.1",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build --mode prod",
+    "preview": "vite preview",
+    "build-only": "vite build"
+  },
+  "dependencies": {
+    "axios": "^1.6.5",
+    "date-fns": "^3.6.0",
+    "element-plus": "^2.5.1",
+    "js-cookie": "^3.0.5",
+    "nprogress": "^0.2.0",
+    "pinia": "^2.1.7",
+    "vue": "^3.3.11",
+    "vue-router": "^4.2.5"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^4.5.2",
+    "@types/js-cookie": "^3.0.6",
+    "@types/nprogress": "^0.2.3",
+    "@vue/tsconfig": "^0.5.0",
+    "sass": "^1.97.0",
+    "typescript": "~5.3.0",
+    "unplugin-auto-import": "^0.17.3",
+    "unplugin-vue-components": "^0.26.0",
+    "vite": "^5.4.21"
+  },
+  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
+}

BIN
public/favicon.ico


BIN
public/mp4-encoder.wasm


Fișier diff suprimat deoarece este prea mare
+ 2621 - 0
public/wasmconverter.js


BIN
public/wasmconverter.wasm


Fișier diff suprimat deoarece este prea mare
+ 0 - 0
public/wfs-min.js


+ 6 - 0
src/App.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+</script>
+
+<template>
+    <router-view />
+</template>

+ 12 - 0
src/api/login.ts

@@ -0,0 +1,12 @@
+import { request } from '@/utils/request'
+import type * as Login from './types/login'
+
+/** 登录并返回 Token */
+export function loginApi(data: Login.LoginRequestData) {
+  return request<Login.LoginResponseData>({
+    url: 'API/V1.0/Auth/WebLogin',
+    method: 'post',
+    data
+  })
+}
+

+ 113 - 0
src/api/setting.ts

@@ -0,0 +1,113 @@
+import { request } from '@/utils/request'
+import type * as Setting from './types/setting'
+import type { TimeParaData } from './types/setting'
+
+export function getUserSettingApi(NIC: number) {
+  return request<Setting.GetSettingResponseData>({
+    url: `API/V1.0/Network/NetworkV4Para?NIC=${NIC}`,
+    method: 'get'
+  })
+}
+
+export function putUserSettingApi(
+  NIC: number,
+  data: Setting.UpdateSettingRequestData
+) {
+  return request({
+    url: `API/V1.0/Network/NetworkV4Para?NIC=${NIC}`,
+    method: 'put',
+    data
+  })
+}
+
+//查询/设置  系统时间
+export function GetTimePara() {
+  return request({
+    url: `API/V1.0/System/TimePara`,
+    method: 'get'
+  })
+}
+
+
+export function PutTimePara(data: TimeParaData) {
+  return request({
+    url: `API/V1.0/System/TimePara`,
+    method: 'put',
+    data
+  })
+}
+
+
+export function cameraReset(data: any) {
+  return request({
+    url: `/API/V1.0/Device/ResetReboot`,
+    method: 'PUT',
+    data
+  })
+}
+
+export function getCameraVolume() {
+  return request({
+    url: `/API/V1.0/Audio/AudioPara`,
+    method: 'get'
+  })
+}
+
+
+export function cameraVolume(data: any) {
+  return request({
+    url: `/API/V1.0/Audio/AudioPara`,
+    method: 'put',
+    data
+  })
+}
+
+
+export function getCameraAlarm() {
+  return request({
+    url: `/API/V1.0/Alarm/AlarmPara`,
+    method: 'get'
+  })
+}
+
+
+export function cameraAlarm(data: any) {
+  return request({
+    url: `/API/V1.0/Alarm/AlarmPara`,
+    method: 'put',
+    data
+  })
+}
+
+
+export function getCameraNightMode() {
+  return request({
+    url: `/API/V1.0/Video/NightPara`,
+    method: 'get'
+  })
+}
+
+export function cameraNightMode(data: any) {
+  return request({
+    url: `/API/V1.0/Video/NightPara`,
+    method: 'put',
+    data
+  })
+}
+
+
+export function getCameraDeviceInfo() {
+  return request({
+    url: `/API/V1.0/DeviceInfo`,
+    method: 'get'
+  })
+}
+
+// export function cameraDeviceInfo(data: any) {
+//   return request({
+//     url: `/API/V1.0/DeviceInfo`,
+//     method: 'put',
+//     data
+//   })
+// }
+

+ 11 - 0
src/api/types/login.ts

@@ -0,0 +1,11 @@
+export interface LoginRequestData {
+  name: string
+  password: string
+}
+
+export type LoginResponseData = ApiResponseData<{ token: string }>
+
+export interface UserInfoResponseData {
+  pageSize: number
+  pageNo: number
+}

+ 21 - 0
src/api/types/preview.ts

@@ -0,0 +1,21 @@
+export interface initializeSessionRequestData {
+  transPayload: string
+  stream: {
+    meta: number
+    enableAudio: number
+  }
+  transProtocol?: {
+    websocketPort: number
+  }
+}
+
+export type initializeSessionResponseData = ApiResponseData<{
+  sessionsID: string
+  stream: {
+    meta: number
+    enableAudio: number
+  }
+  transProtocol?: {
+    websocketPort: null
+  }
+}>

+ 15 - 0
src/api/types/setting.ts

@@ -0,0 +1,15 @@
+export interface UpdateSettingRequestData {
+  NIC: number
+  enableDHCP?: number
+  ipAddress?: string
+  firstDNSAddress?: string
+  secondDNSAddress?: string
+  gateWayAddress?: string
+  subNetAddress?: string
+  deviceMac?: string
+}
+
+export interface TimeParaData {
+  time: string
+  timeMode?: number
+}

+ 10 - 0
src/api/userManagement.ts

@@ -0,0 +1,10 @@
+import { request } from '@/utils/request'
+import type * as UserManagement from './types/userManagement'
+
+export function putPasswordApi(data: UserManagement.UpdatePasswordRequestData) {
+  return request({
+    url: 'API/V1.0/Auth/WebUser',
+    method: 'put',
+    data
+  })
+}

+ 45 - 0
src/assets/base.css

@@ -0,0 +1,45 @@
+/** 全局 CSS 变量,这种变量不仅可以在 CSS 和 SCSS 中使用,还可以导入到 JS 中使用 */
+:root {
+  /** 全局背景色 */
+  --v3-body-bg-color: #f6f8f9;;
+  /** Header 区域 = NavigationBar 组件 + TagsView 组件 */
+  --v3-header-height: calc(var(--v3-navigationbar-height) + var(--v3-tagsview-height));
+  --v3-header-bg-color: #ffffff;
+  /** NavigationBar 组件 */
+  --v3-navigationbar-height: 50px;
+  /** Sidebar 组件(左侧模式全部生效、顶部模式全部不生效、混合模式非颜色部分生效) */
+  --v3-sidebar-width: 150px;
+  --v3-sidebar-hide-width: 58px;
+  --v3-sidebar-menu-item-height: 60px;
+  --v3-sidebar-menu-tip-line-bg-color: var(--el-color-primary);
+  --v3-sidebar-menu-bg-color: #001428;
+  --v3-sidebar-menu-hover-bg-color: #409eff10;
+  --v3-sidebar-menu-text-color: #c0c4cc;
+  --v3-sidebar-menu-active-text-color: #ffffff;
+  /** TagsView 组件 */
+  --v3-tagsview-height: 34px;
+  --v3-tagsview-tag-text-color: #495060;
+  --v3-tagsview-tag-active-text-color: #ffffff;
+  --v3-tagsview-tag-bg-color: #ffffff;
+  --v3-tagsview-tag-active-bg-color: var(--el-color-primary);
+  --v3-tagsview-tag-border-radius: 2px;
+  --v3-tagsview-tag-border-color: #d8dce5;
+  --v3-tagsview-tag-active-border-color: var(--el-color-primary);
+  --v3-tagsview-tag-active-before-color: #ffffff;
+  --v3-tagsview-tag-icon-hover-bg-color: #00000030;
+  --v3-tagsview-tag-icon-hover-color: #ffffff;
+  /** RightPanel 组件  */
+  --v3-rightpanel-button-bg-color: #001428;
+}
+
+/** 内容区放大时,将不需要的组件隐藏 */
+body.content-large {
+  /** Header 区域 = TagsView 组件 */
+  --v3-header-height: var(--v3-tagsview-height);
+  /** NavigationBar 组件 */
+  --v3-navigationbar-height: 0px;
+  /** Sidebar 组件 */
+  --v3-sidebar-width: 0px;
+  --v3-sidebar-hide-width: 0px;
+}
+

BIN
src/assets/black.jpg


+ 1 - 0
src/assets/logo.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

+ 19 - 0
src/assets/main.css

@@ -0,0 +1,19 @@
+@import './base.css';
+
+body {
+  height: 100%;
+  margin: 0;
+  background: var(--v3-body-bg-color);
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-font-smoothing: antialiased;
+  font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial,
+    sans-serif;
+}
+
+html {
+  height: 100%;
+}
+
+#app {
+  height: 100%;
+}

+ 76 - 0
src/auto-import.d.ts

@@ -0,0 +1,76 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// noinspection JSUnusedGlobalSymbols
+// Generated by unplugin-auto-import
+export {}
+declare global {
+  const EffectScope: typeof import('vue')['EffectScope']
+  const ElMessage: typeof import('element-plus/es')['ElMessage']
+  const computed: typeof import('vue')['computed']
+  const createApp: typeof import('vue')['createApp']
+  const customRef: typeof import('vue')['customRef']
+  const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
+  const defineComponent: typeof import('vue')['defineComponent']
+  const effectScope: typeof import('vue')['effectScope']
+  const getCurrentInstance: typeof import('vue')['getCurrentInstance']
+  const getCurrentScope: typeof import('vue')['getCurrentScope']
+  const h: typeof import('vue')['h']
+  const inject: typeof import('vue')['inject']
+  const isProxy: typeof import('vue')['isProxy']
+  const isReactive: typeof import('vue')['isReactive']
+  const isReadonly: typeof import('vue')['isReadonly']
+  const isRef: typeof import('vue')['isRef']
+  const markRaw: typeof import('vue')['markRaw']
+  const nextTick: typeof import('vue')['nextTick']
+  const onActivated: typeof import('vue')['onActivated']
+  const onBeforeMount: typeof import('vue')['onBeforeMount']
+  const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
+  const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
+  const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
+  const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
+  const onDeactivated: typeof import('vue')['onDeactivated']
+  const onErrorCaptured: typeof import('vue')['onErrorCaptured']
+  const onMounted: typeof import('vue')['onMounted']
+  const onRenderTracked: typeof import('vue')['onRenderTracked']
+  const onRenderTriggered: typeof import('vue')['onRenderTriggered']
+  const onScopeDispose: typeof import('vue')['onScopeDispose']
+  const onServerPrefetch: typeof import('vue')['onServerPrefetch']
+  const onUnmounted: typeof import('vue')['onUnmounted']
+  const onUpdated: typeof import('vue')['onUpdated']
+  const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
+  const provide: typeof import('vue')['provide']
+  const reactive: typeof import('vue')['reactive']
+  const readonly: typeof import('vue')['readonly']
+  const ref: typeof import('vue')['ref']
+  const resolveComponent: typeof import('vue')['resolveComponent']
+  const shallowReactive: typeof import('vue')['shallowReactive']
+  const shallowReadonly: typeof import('vue')['shallowReadonly']
+  const shallowRef: typeof import('vue')['shallowRef']
+  const toRaw: typeof import('vue')['toRaw']
+  const toRef: typeof import('vue')['toRef']
+  const toRefs: typeof import('vue')['toRefs']
+  const toValue: typeof import('vue')['toValue']
+  const triggerRef: typeof import('vue')['triggerRef']
+  const unref: typeof import('vue')['unref']
+  const useAttrs: typeof import('vue')['useAttrs']
+  const useCssModule: typeof import('vue')['useCssModule']
+  const useCssVars: typeof import('vue')['useCssVars']
+  const useId: typeof import('vue')['useId']
+  const useLink: typeof import('vue-router')['useLink']
+  const useModel: typeof import('vue')['useModel']
+  const useRoute: typeof import('vue-router')['useRoute']
+  const useRouter: typeof import('vue-router')['useRouter']
+  const useSlots: typeof import('vue')['useSlots']
+  const useTemplateRef: typeof import('vue')['useTemplateRef']
+  const watch: typeof import('vue')['watch']
+  const watchEffect: typeof import('vue')['watchEffect']
+  const watchPostEffect: typeof import('vue')['watchPostEffect']
+  const watchSyncEffect: typeof import('vue')['watchSyncEffect']
+}
+// for type re-export
+declare global {
+  // @ts-ignore
+  export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
+  import('vue')
+}

+ 512 - 0
src/components/myVideo.vue

@@ -0,0 +1,512 @@
+<template>
+  <div class="preview-container">
+    <div ref="videoContainer" class="video_container">
+      <video
+        id="player"
+        ref="videoElement"
+        autoplay
+        muted
+        oncontextmenu="return false;"
+        playsinline
+        poster="../assets/black.jpg"
+        @mousedown="startDrag"
+        @mousemove="onDrag"
+        @mouseup="endDrag"
+        @mouseleave="endDrag"
+      />
+    </div>
+  </div>
+  <div class="video-control">
+    <div class="video-control-left">
+      <!--刷新键-->
+      <div class="control-btn pull-left" title="刷新">
+        <el-icon
+          class="control-btn"
+          color="inherit"
+          @click="refreshWebSocket"
+        >
+          <refresh />
+        </el-icon>
+      </div>
+
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, onUnmounted, ref } from 'vue'
+import { Refresh } from '@element-plus/icons-vue'
+
+const props = defineProps({
+  dragFlag: {
+    type: Boolean,
+    default: false  // 是否允许拖拽和缩放
+  }
+})
+
+// 获取webSocket服务器IP地址
+const wsHost = import.meta.env.MODE === 'development'
+  ? import.meta.env.VITE_HOST_IP
+  : window.location.host
+
+// 视频流和心跳流端口
+const wsVideoPort = 8000
+const wsHeartbeatPort = 8001
+
+// Emits
+const emit = defineEmits(['video-error'])
+
+// Refs
+const videoContainer = ref<HTMLElement | null>(null)
+const videoElement = ref<HTMLVideoElement | null>(null)
+
+// 视频控制状态
+const scale = ref(1)
+const position = ref({ x: 0, y: 0 })
+let dragState = {
+  dragging: false,
+  startX: 0,
+  startY: 0
+}
+
+// WebSocket 状态
+let wfsObj: any = null
+let heartbeatWs: WebSocket | null = null
+let heartbeatTimer: ReturnType<typeof setTimeout> | null = null
+let reconnectTimer: ReturnType<typeof setTimeout> | null = null
+let reconnectAttempts = 0
+const maxReconnectAttempts = 5
+
+// 页面可见性状态
+const lastTimeStamp = ref(0)
+const lastVideoTime = ref(0)
+
+// 工具函数
+const debounce = (func: Function, wait: number) => {
+  let timeout: ReturnType<typeof setTimeout>
+  return function executedFunction(...args: any[]) {
+    const later = () => {
+      clearTimeout(timeout)
+      func(...args)
+    }
+    clearTimeout(timeout)
+    timeout = setTimeout(later, wait)
+  }
+}
+
+// 视频缩放和拖拽功能
+const zoom = (event: WheelEvent) => {
+  if (!props.dragFlag) return
+
+  event.preventDefault()
+  const zoomSpeed = 0.1
+  const delta = event.deltaY < 0 ? zoomSpeed : -zoomSpeed
+
+  scale.value = Math.max(1, Math.min(3, scale.value + delta))
+  applyTransform()
+}
+
+const startDrag = (event: MouseEvent) => {
+  if (!props.dragFlag) return
+
+  dragState.dragging = true
+  dragState.startX = event.clientX - position.value.x
+  dragState.startY = event.clientY - position.value.y
+
+  // 防止选中文本
+  event.preventDefault()
+}
+
+const onDrag = (event: MouseEvent) => {
+  if (!dragState.dragging || !props.dragFlag) return
+
+  position.value.x = event.clientX - dragState.startX
+  position.value.y = event.clientY - dragState.startY
+  applyTransform()
+}
+
+const endDrag = () => {
+  dragState.dragging = false
+}
+
+const applyTransform = () => {
+  if (!videoElement.value || !videoContainer.value || !props.dragFlag) return
+
+  const videoWidth = videoElement.value.offsetWidth * scale.value
+  const videoHeight = videoElement.value.offsetHeight * scale.value
+  const containerWidth = videoContainer.value.offsetWidth
+  const containerHeight = videoContainer.value.offsetHeight
+
+  // 计算最大移动范围
+  const maxX = Math.max(0, (videoWidth - containerWidth) / 2)
+  const maxY = Math.max(0, (videoHeight - containerHeight) / 2)
+
+  // 限制移动范围
+  position.value.x = Math.min(maxX, Math.max(-maxX, position.value.x))
+  position.value.y = Math.min(maxY, Math.max(-maxY, position.value.y))
+
+  // 应用变换
+  videoElement.value.style.transform =
+    `translate(${position.value.x}px, ${position.value.y}px) scale(${scale.value})`
+}
+
+// WebSocket 视频流管理
+const createWebSocket = async () => {
+  try {
+    if (!window.Wfs?.isSupported()) {
+      throw new Error('WFS not supported')
+    }
+
+    const video = videoElement.value
+    if (!video) {
+      throw new Error('Video element not found')
+    }
+
+    const config = {
+      wsMinPacketInterval: 2000,
+      wsMaxPacketInterval: 8000
+    }
+
+    const socketURL = `ws://${wsHost}:${wsVideoPort}/websocket`
+
+    wfsObj = new window.Wfs(config)
+    wfsObj.attachMedia(video, 'ch1', 'H264Raw', socketURL)
+
+    wfsObj.on(window.Wfs.Events.ERROR, handleWfsError)
+    wfsObj.on(window.Wfs.Events.MANIFEST_PARSED, () => {
+      reconnectAttempts = 0
+    })
+
+  } catch (error) {
+    console.error('Failed to create WebSocket:', error)
+    emit('video-error', error)
+    scheduleReconnect()
+  }
+}
+
+const handleWfsError = (eventName: string, data: any) => {
+  console.error('WFS Error:', eventName, data)
+
+  if (data.fatal) {
+    emit('video-error', data)
+
+    switch (data.type) {
+      case window.Wfs.ErrorTypes.MEDIA_ERROR:
+        console.log('Media error occurred')
+        break
+      case window.Wfs.ErrorTypes.NETWORK_ERROR:
+        console.log('Network error occurred')
+        break
+      default:
+        console.log('Unrecoverable error occurred')
+        break
+    }
+
+    scheduleReconnect()
+  }
+}
+
+// 心跳检测WebSocket
+const createHeartbeatWs = () => {
+  try {
+    const socketURL = `ws://${wsHost}:${wsHeartbeatPort}/websocket`
+    heartbeatWs = new WebSocket(socketURL)
+
+    heartbeatWs.onopen = () => {
+      console.log('Heartbeat WebSocket connected')
+      startHeartbeat()
+    }
+
+    heartbeatWs.onmessage = (event: MessageEvent) => {
+      handleHeartbeatMessage(event)
+    }
+
+    heartbeatWs.onclose = () => {
+      console.log('Heartbeat WebSocket disconnected')
+      stopHeartbeat()
+    }
+
+    heartbeatWs.onerror = (error) => {
+      console.error('Heartbeat WebSocket error:', error)
+    }
+
+  } catch (error) {
+    console.error('Failed to create heartbeat WebSocket:', error)
+  }
+}
+
+const handleHeartbeatMessage = (event: MessageEvent) => {
+  try {
+    let data: any
+
+    if (event.data instanceof Blob) {
+      const reader = new FileReader()
+      reader.readAsText(event.data, 'utf-8')
+      reader.onload = () => {
+        data = JSON.parse(reader.result as string)
+        processHeartbeatData(data)
+      }
+    } else if (typeof event.data === 'string') {
+      if (event.data.includes('pong')) {
+        // 心跳回复
+        startHeartbeat()
+        return
+      }
+      data = JSON.parse(event.data)
+      processHeartbeatData(data)
+    }
+  } catch (error) {
+    console.error('Failed to parse heartbeat message:', error)
+  }
+}
+
+const processHeartbeatData = (data: any) => {
+  if (data.serialNum && heartbeatWs?.readyState === WebSocket.OPEN) {
+    const response = { serialNum: data.serialNum }
+    heartbeatWs.send(JSON.stringify(response))
+  }
+}
+
+// 心跳机制
+const heartbeatConfig = {
+  timeout: 1000,
+  maxRetries: 3,
+  retryCount: 0
+}
+
+const startHeartbeat = () => {
+  stopHeartbeat()
+
+  heartbeatTimer = setTimeout(() => {
+    if (heartbeatWs?.readyState === WebSocket.OPEN) {
+      heartbeatWs.send('ping')
+      heartbeatConfig.retryCount = 0
+    } else {
+      handleHeartbeatFailure()
+    }
+  }, heartbeatConfig.timeout)
+}
+
+const stopHeartbeat = () => {
+  if (heartbeatTimer) {
+    clearTimeout(heartbeatTimer)
+    heartbeatTimer = null
+  }
+}
+
+const handleHeartbeatFailure = () => {
+  heartbeatConfig.retryCount++
+  if (heartbeatConfig.retryCount >= heartbeatConfig.maxRetries) {
+    console.log('Heartbeat failed, reconnecting...')
+    scheduleReconnect()
+  }
+}
+
+// 重连机制
+const scheduleReconnect = () => {
+  if (reconnectAttempts >= maxReconnectAttempts) {
+    console.error('Max reconnection attempts reached')
+    return
+  }
+
+  const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 10000) // 指数退避,最大10秒
+  reconnectAttempts++
+
+  if (reconnectTimer) {
+    clearTimeout(reconnectTimer)
+  }
+
+  reconnectTimer = setTimeout(() => {
+    console.log(`Reconnection attempt ${reconnectAttempts}`)
+    refreshWebSocket()
+  }, delay)
+}
+
+// 清理所有连接
+const cleanup = () => {
+  stopHeartbeat()
+
+  if (reconnectTimer) {
+    clearTimeout(reconnectTimer)
+    reconnectTimer = null
+  }
+
+  if (wfsObj) {
+    try {
+      wfsObj.destroy()
+    } catch (error) {
+      console.error('Error destroying WFS:', error)
+    }
+    wfsObj = null
+  }
+
+  if (heartbeatWs) {
+    try {
+      heartbeatWs.close()
+    } catch (error) {
+      console.error('Error closing heartbeat WebSocket:', error)
+    }
+    heartbeatWs = null
+  }
+}
+
+// 刷新连接
+const refreshWebSocket = () => {
+  cleanup()
+  reconnectAttempts = 0
+
+  setTimeout(() => {
+    createWebSocket()
+    createHeartbeatWs()
+  }, 1000)
+}
+
+// 页面可见性处理
+const handleVisibilityChange = () => {
+  const video = videoElement.value
+  if (!video) return
+
+  if (document.hidden) {
+    lastTimeStamp.value = Date.now()
+    lastVideoTime.value = video.currentTime
+  } else {
+    const elapsed = (Date.now() - lastTimeStamp.value) / 1000
+    video.currentTime = lastVideoTime.value + elapsed
+  }
+}
+
+// 防抖的缩放函数
+const debouncedZoom = debounce(zoom, 16) // 约60fps
+
+// 生命周期
+onMounted(() => {
+  // 添加滚轮事件监听
+  if (videoContainer.value) {
+    videoContainer.value.addEventListener('wheel', debouncedZoom, { passive: false })
+  }
+
+  // 页面可见性监听
+  document.addEventListener('visibilitychange', handleVisibilityChange)
+
+  // 页面卸载监听
+  window.addEventListener('beforeunload', cleanup)
+  window.addEventListener('unload', cleanup)
+
+  // 初始化连接
+  refreshWebSocket()
+})
+
+onUnmounted(() => {
+  // 清理事件监听
+  if (videoContainer.value) {
+    videoContainer.value.removeEventListener('wheel', debouncedZoom)
+  }
+  document.removeEventListener('visibilitychange', handleVisibilityChange)
+  window.addEventListener('beforeunload', cleanup)
+  window.addEventListener('unload', cleanup)
+
+  // 清理连接
+  cleanup()
+})
+
+// 暴露的方法和属性
+defineExpose({
+  zoom: debouncedZoom,
+  startDrag,
+  onDrag,
+  endDrag,
+  refreshWebSocket,
+  scale: readonly(scale),
+  position: readonly(position)
+})
+</script>
+
+<style lang="scss" scoped>
+.preview-container {
+  position: relative;
+  width: 100%;
+  height: 100%;
+}
+
+.video_container {
+  overflow: hidden;
+  position: relative;
+  width: 100%;
+  height: 100%;
+  background-color: #000;
+
+  video {
+    width: 100%;
+    height: 100%;
+    object-fit: contain;
+    cursor: grab;
+
+    &:active {
+      cursor: grabbing;
+    }
+  }
+}
+
+.video-control {
+  position: absolute;
+  width: 100%;
+  height: 32px;
+  line-height: 32px;
+  background: linear-gradient(to top, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.3));
+  left: 0;
+  right: 0;
+  bottom: 0;
+  backdrop-filter: blur(4px);
+  transition: opacity 0.3s ease;
+
+  &:hover {
+    opacity: 1;
+  }
+
+  .video-control-left {
+    position: absolute;
+    left: 8px;
+    height: 32px;
+    display: flex;
+    align-items: center;
+  }
+
+  .control-btn {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 24px;
+    height: 24px;
+    background: rgba(255, 255, 255, 0.1);
+    cursor: pointer;
+    font-size: 16px;
+    color: white;
+    border-radius: 4px;
+    transition: all 0.2s ease;
+    border: none;
+
+    &:hover {
+      background: rgba(255, 255, 255, 0.2);
+      transform: scale(1.1);
+    }
+
+    &:active {
+      transform: scale(0.95);
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .video-control {
+    height: 40px;
+    line-height: 40px;
+
+    .control-btn {
+      width: 32px;
+      height: 32px;
+      font-size: 18px;
+    }
+  }
+}
+</style>

+ 28 - 0
src/config/route.ts

@@ -0,0 +1,28 @@
+/** 动态路由配置 */
+interface RouteSettings {
+  /**
+   * 是否开启动态路由功能?
+   * 1. 开启后需要后端配合,在查询用户详情接口返回当前用户可以用来判断并加载动态路由的字段(该项目用的是角色 roles 字段)
+   * 2. 假如项目不需要根据不同的用户来显示不同的页面,则应该将 async: false
+   */
+  async: boolean
+  /** 当动态路由功能关闭时:
+   * 1. 应该将所有路由都写到常驻路由里面(表明所有登陆的用户能访问的页面都是一样的)
+   * 2. 系统自动给当前登录用户赋值一个没有任何作用的默认角色
+   */
+  defaultRoles: Array<string>
+  /**
+   * 是否开启三级及其以上路由缓存功能?
+   * 1. 开启后会进行路由降级(把三级及其以上的路由转化为二级路由)
+   * 2. 由于都会转成二级路由,所以二级及其以上路由有内嵌子路由将会失效
+   */
+  thirdLevelRouteCache: boolean
+}
+
+const routeSettings: RouteSettings = {
+  async: true,
+  defaultRoles: ["DEFAULT_ROLE"],
+  thirdLevelRouteCache: false
+}
+
+export default routeSettings

+ 7 - 0
src/constants/app-key.ts

@@ -0,0 +1,7 @@
+/** 侧边栏打开状态常量 */
+export const SIDEBAR_OPENED = 'opened'
+/** 侧边栏关闭状态常量 */
+export const SIDEBAR_CLOSED = 'closed'
+
+export type SidebarOpened = typeof SIDEBAR_OPENED
+export type SidebarClosed = typeof SIDEBAR_CLOSED

+ 17 - 0
src/constants/cache-key.ts

@@ -0,0 +1,17 @@
+const SYSTEM_NAME = "v3-admin-vite"
+
+/** 缓存数据时用到的 Key */
+class CacheKey {
+  static readonly TOKEN = `${SYSTEM_NAME}-token-key`
+  static readonly PROJECT = `${SYSTEM_NAME}-project`
+  static readonly SUPPORT_MUL = `${SYSTEM_NAME}-supportMul`
+  static readonly START_STATUS = `${SYSTEM_NAME}-start-status`
+  static readonly PROJECT_BALL = `${SYSTEM_NAME}-project-ball`
+  static readonly CONFIG_LAYOUT = `${SYSTEM_NAME}-config-layout-key`
+  static readonly SIDEBAR_STATUS = `${SYSTEM_NAME}-sidebar-status-key`
+  static readonly ACTIVE_THEME_NAME = `${SYSTEM_NAME}-active-theme-name-key`
+  static readonly VISITED_VIEWS = `${SYSTEM_NAME}-visited-views-key`
+  static readonly CACHED_VIEWS = `${SYSTEM_NAME}-cached-views-key`
+}
+
+export default CacheKey

+ 48 - 0
src/hooks/useRouteListener.ts

@@ -0,0 +1,48 @@
+import { onBeforeUnmount } from 'vue'
+import mitt, { type Handler } from 'mitt'
+import { type RouteLocationNormalized } from 'vue-router'
+
+/** 回调函数的类型 */
+type Callback = (route: RouteLocationNormalized) => void
+
+const emitter = mitt()
+const key = Symbol('ROUTE_CHANGE')
+let latestRoute: RouteLocationNormalized
+
+/** 设置最新的路由信息,触发路由变化事件 */
+export const setRouteChange = (to: RouteLocationNormalized) => {
+  // 触发事件
+  emitter.emit(key, to)
+  // 缓存最新的路由信息
+  latestRoute = to
+}
+
+/** 单独监听路由会浪费渲染性能,使用发布订阅模式去进行分发管理 */
+export function useRouteListener() {
+  /** 回调函数集合 */
+  const callbackList: Callback[] = []
+
+  /** 监听路由变化(可以选择立即执行) */
+  const listenerRouteChange = (callback: Callback, immediate = false) => {
+    // 缓存回调函数
+    callbackList.push(callback)
+    // 监听事件
+    emitter.on(key, callback as Handler)
+    // 可以选择立即执行一次回调函数
+    immediate && latestRoute && callback(latestRoute)
+  }
+
+  /** 移除路由变化事件监听器 */
+  const removeRouteListener = (callback: Callback) => {
+    emitter.off(key, callback as Handler)
+  }
+
+  /** 组件销毁前移除监听器 */
+  onBeforeUnmount(() => {
+    for (let i = 0; i < callbackList.length; i++) {
+      removeRouteListener(callbackList[i])
+    }
+  })
+
+  return { listenerRouteChange, removeRouteListener }
+}

+ 29 - 0
src/layouts/components/AppMain.vue

@@ -0,0 +1,29 @@
+<template>
+  <section>
+    <div class="app-scrollbar">
+      <router-view v-slot="{ Component, route }">
+        <transition mode="out-in" name="el-fade-in">
+          <component
+            :is="Component"
+            :key="route.path"
+            class="app-container-grow"
+          />
+        </transition>
+      </router-view>
+    </div>
+  </section>
+</template>
+
+<style lang="scss" scoped>
+.app-scrollbar {
+  height: inherit;
+  display: flex;
+  overflow: auto;
+}
+.app-container-grow {
+  //margin: 20px;
+  background: #F2F5F6;
+  width: 100%;
+  min-height: calc(99vh - var(--v3-header-height));
+}
+</style>

+ 36 - 0
src/layouts/components/Hamburger/index.vue

@@ -0,0 +1,36 @@
+<script lang="ts" setup>
+import { Expand, Fold } from '@element-plus/icons-vue'
+
+interface Props {
+  isActive?: boolean
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  isActive: false
+})
+
+/** Vue 3.3+ defineEmits 语法 */
+const emit = defineEmits<{
+  toggleClick: []
+}>()
+
+const toggleClick = () => {
+  emit('toggleClick')
+}
+</script>
+
+<template>
+  <div @click="toggleClick">
+    <el-icon class="icon" :size="20">
+      <Fold v-if="props.isActive" />
+      <Expand v-else />
+    </el-icon>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.icon {
+  vertical-align: middle;
+  color: var(--v3-hamburger-text-color);
+}
+</style>

+ 58 - 0
src/layouts/components/NavigationBar/index.vue

@@ -0,0 +1,58 @@
+<script lang="ts" setup>
+import { useRouter } from 'vue-router'
+import { useUserStore } from '@/stores/modules/user'
+import { useAppStore } from '@/stores/modules/app'
+import Hamburger from '../Hamburger/index.vue'
+
+const router = useRouter()
+const appStore = useAppStore()
+const userStore = useUserStore()
+
+/** 切换侧边栏 */
+const toggleSidebar = () => {
+  appStore.toggleSidebar(false)
+}
+
+/** 登出 */
+const logout = () => {
+  userStore.logout()
+  router.push('/login')
+}
+</script>
+
+<template>
+  <div class="navigation-bar">
+    <Hamburger
+      class="hamburger"
+      :is-active="appStore.sidebar.opened"
+      @toggle-click="toggleSidebar"
+    />
+    <div class="logout-btn">
+      <el-button @click="logout">退出登录</el-button>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.navigation-bar {
+  height: var(--v3-navigationbar-height);
+  overflow: hidden;
+  background: var(--v3-header-bg-color);
+  display: flex;
+  justify-content: space-between;
+  .hamburger {
+    display: flex;
+    align-items: center;
+    height: 100%;
+    padding: 0 15px;
+    cursor: pointer;
+  }
+  .logout-btn {
+    display: flex;
+    align-items: center;
+    justify-content: right;
+    margin-inline-start: auto;
+    margin-right: 10px;
+  }
+}
+</style>

+ 105 - 0
src/layouts/components/SideBar/index.vue

@@ -0,0 +1,105 @@
+<script lang="ts" setup>
+// sidebarItem 项组件
+import SideBarItem from './sidebarItem.vue'
+import { useRouter } from 'vue-router'
+import { useAppStore } from '@/stores/modules/app'
+// 拿到路由列表,过滤我们不想要的
+const router = useRouter()
+const routerList = router.getRoutes().filter((v) => v.meta && v.meta.isShow)
+const appStore = useAppStore()
+const title = import.meta.env.VITE_APP_TITLE
+
+const isCollapse = computed(() => !appStore.sidebar.opened)
+</script>
+
+<template>
+  <div class="sidebar">
+    <!-- 项目名称及logo -->
+    <div class="layout-logo-container" :class="{ collapse: isCollapse }">
+      <transition name="layout-logo-fade">
+        <router-link v-if="!isCollapse" replace to="/">
+          <div class="sidebar-logo flex-center">
+            <span>{{ title }}</span>
+          </div>
+        </router-link>
+        <router-link v-else replace to="/">
+          <div class="sidebar-none" />
+        </router-link>
+      </transition>
+    </div>
+    <!-- 导航菜单 -->
+    <el-aside>
+      <el-menu
+        active-text-color="#fff"
+        background-color="#001529"
+        :collapse="isCollapse"
+        :collapse-transition="false"
+        :default-active="$route.path"
+        :hide-timeout="1000"
+        router
+        text-color="#fff"
+        :unique-opened="true"
+      >
+        <!-- 引入子组件 -->
+        <SideBarItem :routerList="routerList" />
+      </el-menu>
+    </el-aside>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+$transition-time: 0.35s;
+.sidebar {
+  .layout-logo-container {
+    background-color: #002140;
+    position: relative;
+    width: 100%;
+    height: var(--v3-navigationbar-height);
+    line-height: var(--v3-header-height);
+    text-align: center;
+    overflow: hidden;
+    .sidebar-logo {
+      background-color: #002140;
+      color: #fff;
+      font-weight: 700;
+      line-height: 22px;
+      font-size: 15px;
+      display: flex;
+      align-items: center;
+      flex-direction: column;
+      text-align: center;
+      white-space: pre-line;
+    }
+    .sidebar-none {
+      color: #fff;
+      font-weight: 700;
+      line-height: 48px;
+      vertical-align: middle;
+      display: inline-block;
+    }
+  }
+  a {
+    text-decoration: none;
+  }
+  .el-aside {
+    height: 100%;
+    width: auto;
+    border-right: 0;
+  }
+  .el-menu {
+    height: calc(100% - 48px);
+    border-right: 0;
+  }
+  .el-aside::-webkit-scrollbar {
+    display: none;
+  }
+}
+.layout-logo-fade-enter-active,
+.layout-logo-fade-leave-active {
+  transition: opacity 0.2s;
+}
+.layout-logo-fade-enter-from,
+.layout-logo-fade-leave-to {
+  opacity: 0;
+}
+</style>

+ 92 - 0
src/layouts/components/SideBar/sidebarItem.vue

@@ -0,0 +1,92 @@
+<script setup lang="ts">
+import { type RouteRecordRaw } from 'vue-router'
+// 做类型限制,解决ts类型报错
+type CustomRouteRecordRaw = RouteRecordRaw & {
+  meta: {
+    isShow?: boolean
+  }
+}
+const props = defineProps({
+  // 拿到父组件传递过来的路由列表进行渲染
+  routerList: {
+    type: Array as () => CustomRouteRecordRaw[],
+    required: true
+  }
+})
+</script>
+<template>
+  <template v-for="item in props.routerList" :key="item.path">
+    <!-- 当该菜单项有子菜单时 -->
+    <el-sub-menu
+      v-if="item.children && item.children.length > 1"
+      :index="item.path"
+    >
+      <template #title v-if="item.meta.icon">
+        <!-- 菜单项名称,在路由中定义好 -->
+        <component :is="item.meta.icon" class="el-icon" />
+        <span>{{ item.meta.title }}</span>
+      </template>
+      <!-- 若路由中未定义菜单项icon,则仅展示名称 -->
+      <template #title v-else>{{ item.meta.title }}</template>
+      <!-- 递归遍历(核心代码) -->
+      <sidebarItem :routerList="item.children as CustomRouteRecordRaw[]" />
+    </el-sub-menu>
+
+    <!-- 当前菜单项无子菜单 -->
+    <el-menu-item :index="item.path" v-else-if="!item.meta.hidden">
+      <template v-if="item.meta.icon">
+        <component :is="item.meta.icon" class="el-icon" />
+        <span>{{ item.meta.title }}</span>
+      </template>
+      <template v-else>
+        {{ item.meta.title }}
+      </template>
+    </el-menu-item>
+  </template>
+</template>
+
+<style scoped lang="scss">
+.is-active {
+  background: #409eff;
+}
+
+.el-menu-item {
+  &:hover {
+    color: #fff;
+  }
+}
+
+.el-menu--collapse {
+  .el-menu-item {
+    justify-content: center;
+  }
+}
+
+// 下列代码是用于兼容horizontal所写,酌情删或留
+.el-menu--horizontal {
+  .el-menu-item.is-active {
+    background-color: transparent !important;
+    border-bottom: 2px solid #409eff !important;
+
+    .el-icon,
+    span {
+      color: #409eff !important;
+    }
+  }
+
+  .el-sub-menu.is-active {
+    .el-sub-menu__title {
+      border: 0 !important;
+    }
+
+    .el-icon {
+      width: 1em;
+      margin-right: 12px;
+      font-size: 18px;
+    }
+    span {
+      color: #409eff !important;
+    }
+  }
+}
+</style>

+ 83 - 0
src/layouts/index.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="app-wrapper" :class="layoutClasses">
+    <!-- 左侧边栏 -->
+    <Sidebar class="sidebar-container" />
+    <!-- 头部导航栏和标签栏 -->
+    <div class="main-container">
+      <NavigationBar />
+      <!-- 页面主体内容 -->
+      <AppMain class="app-main" />
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { format } from 'date-fns'
+import Sidebar from './components/SideBar/index.vue'
+import NavigationBar from './components/NavigationBar/index.vue'
+import AppMain from './components/AppMain.vue'
+import { useAppStore } from '@/stores/modules/app'
+import { PutTimePara } from '@/api/setting'
+import type {
+  TimeParaData,
+  UpdateSettingRequestData
+} from '@/api/types/setting'
+import router from '@/router'
+// import TagsView from './components/TagsView/index.vue'
+
+const appStore = useAppStore()
+const layoutClasses = computed(() => {
+  return {
+    hideSidebar: !appStore.sidebar.opened,
+    openSidebar: appStore.sidebar.opened
+  }
+})
+
+// 在组件挂载时添加事件监听
+onMounted(() => {
+  //每次组件挂载时就调同步事件接口
+  // const timeParaData: TimeParaData = {
+  //   time: format(new Date(), 'yyyy-MM-dd-HH-mm-ss')
+  // }
+  // PutTimePara(timeParaData)
+})
+</script>
+
+<style lang="scss" scoped>
+$transition-time: 0.35s;
+.app-wrapper {
+  position: relative;
+  width: 100%;
+}
+.sidebar-container {
+  background-color: var(--v3-sidebar-menu-bg-color);
+  transition: width $transition-time;
+  width: var(--v3-sidebar-width) !important;
+  height: 100%;
+  position: fixed;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1001;
+  overflow: hidden;
+}
+.main-container {
+  min-height: 100%;
+  transition: margin-left $transition-time;
+  margin-left: var(--v3-sidebar-width);
+  position: relative;
+}
+.app-main {
+  min-height: calc(100vh - var(--v3-navigationbar-height));
+  position: relative;
+  overflow: hidden;
+}
+.hideSidebar {
+  .sidebar-container {
+    width: var(--v3-sidebar-hide-width) !important;
+  }
+  .main-container {
+    margin-left: var(--v3-sidebar-hide-width);
+  }
+}
+</style>

+ 26 - 0
src/main.ts

@@ -0,0 +1,26 @@
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+import App from './App.vue'
+import router, { setRouter } from './router'
+import '@/router/permission'
+
+import { View, Setting, User, Lock, Expand, Fold, Refresh } from '@element-plus/icons-vue'
+
+import './assets/main.css'
+import 'element-plus/dist/index.css'
+
+const app = createApp(App)
+
+// 注册需要的图标组件
+app.component('View', View)
+app.component('Setting', Setting)
+app.component('User', User)
+app.component('Lock', Lock)
+app.component('Expand', Expand)
+app.component('Fold', Fold)
+app.component('Refresh', Refresh)
+
+app.use(createPinia())
+setRouter()
+app.use(router)
+app.mount('#app')

+ 78 - 0
src/router/index.ts

@@ -0,0 +1,78 @@
+import {
+  createRouter,
+  createWebHashHistory,
+  type RouteRecordRaw
+} from 'vue-router'
+
+
+const Layouts = () => import('@/layouts/index.vue')
+
+export const constantRoutes: RouteRecordRaw[] = [
+  {
+    path: '/',
+    component: Layouts,
+    redirect: '/preview',
+    children: [
+      {
+        path: 'preview',
+        component: () => import('@/views/preview/index.vue'),
+        name: 'Preview',
+        meta: {
+          title: '预览',
+          isShow: true,
+          icon: 'View'
+        }
+      }
+    ]
+  },
+  {
+    path: '/setting',
+    name: 'Setting',
+    component: Layouts,
+    redirect: '/setting',
+    meta: {
+      title: '设置',
+      isShow: true,
+      icon: 'Setting'
+    },
+    children: [
+      {
+        path: '/setting/systemSetting',
+        component: () => import('@/views/setting/systemSetting/index.vue'),
+        name: 'SystemSetting',
+        meta: {
+          title: '相机设置'
+        }
+      },
+      {
+        path: '/setting/netSetting',
+        component: () => import('@/views/setting/netSetting/index.vue'),
+        name: 'NetSetting',
+        meta: {
+          title: '网络设置'
+        }
+      }
+    ]
+  },
+]
+
+export const history = createWebHashHistory()
+
+const router = createRouter({
+  history,
+  routes: [
+    {
+      path: '/login',
+      component: () => import('@/views/login/index.vue'),
+      name: 'Login'
+    }
+  ]
+})
+
+export default router
+
+export function setRouter() {
+  constantRoutes.forEach((item) => {
+    router.addRoute(item)
+  })
+}

+ 28 - 0
src/router/permission.ts

@@ -0,0 +1,28 @@
+import router from '@/router'
+import { getToken } from '@/utils/cache/cookies'
+import NProgress from 'nprogress'
+import 'nprogress/nprogress.css'
+
+NProgress.configure({ showSpinner: false })
+
+router.beforeEach(async (to, _form, next) => {
+  NProgress.start()
+  const token = getToken()
+  if (!token) {
+    if (to.path !== '/login') {
+      next('/login')
+    } else {
+      next()
+    }
+  } else {
+    if (to.path === '/login') {
+      next({ path: '/' })
+    } else {
+      next()
+    }
+  }
+})
+
+router.afterEach(() => {
+  NProgress.done()
+})

+ 5 - 0
src/stores/index.ts

@@ -0,0 +1,5 @@
+import { createPinia } from 'pinia'
+
+const store = createPinia()
+
+export default store

+ 35 - 0
src/stores/modules/app.ts

@@ -0,0 +1,35 @@
+import { defineStore } from 'pinia'
+import { getSidebarStatus, setSidebarStatus } from '@/utils/cache/local-storage'
+import { SIDEBAR_OPENED, SIDEBAR_CLOSED } from '@/constants/app-key'
+
+interface Sidebar {
+  opened: boolean
+  withoutAnimation: boolean
+}
+
+/** 设置侧边栏状态本地缓存 */
+function handleSidebarStatus(opened: boolean) {
+  opened ? setSidebarStatus(SIDEBAR_OPENED) : setSidebarStatus(SIDEBAR_CLOSED)
+}
+
+export const useAppStore = defineStore('app', () => {
+  /** 侧边栏状态 */
+  const sidebar: Sidebar = reactive({
+    opened: getSidebarStatus() !== SIDEBAR_CLOSED,
+    withoutAnimation: false
+  })
+
+  /** 监听侧边栏 opened 状态 */
+  watch(
+    () => sidebar.opened,
+    (opened) => handleSidebarStatus(opened)
+  )
+
+  /** 切换侧边栏 */
+  const toggleSidebar = (withoutAnimation: boolean) => {
+    sidebar.opened = !sidebar.opened
+    sidebar.withoutAnimation = withoutAnimation
+  }
+
+  return { sidebar, toggleSidebar }
+})

+ 3 - 0
src/stores/modules/tags-view.ts

@@ -0,0 +1,3 @@
+import { type RouteLocationNormalized } from 'vue-router'
+
+export type TagView = Partial<RouteLocationNormalized>

+ 47 - 0
src/stores/modules/user.ts

@@ -0,0 +1,47 @@
+import { ref } from 'vue'
+import store from '@/stores'
+import { defineStore } from 'pinia'
+import { getToken, removeToken } from '@/utils/cache/cookies'
+import { loginApi } from '@/api/login'
+import { type LoginRequestData } from '@/api/types/login'
+import { removeProjectFromLocal } from '@/utils/cache/cookies'
+
+export const useUserStore = defineStore('user', () => {
+  const token = ref<string>(getToken() || '')
+  const username = ref<string>('')
+  const unToken =
+    'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySUQiOiIxNTE1NjQyNjIzMzc5Mzk1MTM4MDAxMzgwMDEiLCJsYW5nIjoiZW4iLCJ1c2VyIjoiMTM4MDAxMzgwMDEiLCJtX2NvZGUiOiIxMjM0MTMyNDMyMTQiLCJleHAiOjE3MTI5ODk4MTd9.JO0ctojoBoYBiZssp6gnhi68abcje7e6yIc3OMxQvYM'
+
+  /** 登录 */
+  const login = async ({ name, password }: LoginRequestData) => {
+    const { data } = await loginApi({ name, password })
+    username.value = name
+    token.value = unToken
+    return data
+  }
+
+  /** 登出 */
+  const logout = () => {
+    removeToken()
+    removeProjectFromLocal()
+    token.value = ''
+  }
+  /** 重置 Token */
+  const resetToken = () => {
+    removeToken()
+    token.value = ''
+  }
+
+  return {
+    token,
+    username,
+    login,
+    logout,
+    resetToken
+  }
+})
+
+/** 在 setup 外使用 */
+export function useUserStoreHook() {
+  return useUserStore(store)
+}

+ 33 - 0
src/utils/cache/cookies.ts

@@ -0,0 +1,33 @@
+/** 统一处理 Cookie */
+
+import CacheKey from '@/constants/cache-key'
+import Cookies from 'js-cookie'
+
+export const getToken = () => {
+  return Cookies.get(CacheKey.TOKEN)
+}
+export const setToken = (token: string) => {
+  Cookies.set(CacheKey.TOKEN, token)
+}
+export const removeToken = () => {
+  Cookies.remove(CacheKey.TOKEN)
+}
+
+export const getProjectFromLocal = () => {
+  return Cookies.get(CacheKey.PROJECT)
+}
+export const setProjectFromLocal = (val: string) => {
+  Cookies.set(CacheKey.PROJECT, val)
+}
+
+export const removeProjectFromLocal = () => {
+  Cookies.remove(CacheKey.PROJECT)
+}
+
+export const setSupportMul = (val: string) => {
+  Cookies.set(CacheKey.SUPPORT_MUL, val)
+}
+//获得是否开始测试状态
+export const getStartStatus = () => {
+  return Cookies.get(CacheKey.START_STATUS)
+}

+ 38 - 0
src/utils/cache/local-storage.ts

@@ -0,0 +1,38 @@
+/** 统一处理 localStorage */
+
+import CacheKey from '@/constants/cache-key'
+import { type SidebarOpened, type SidebarClosed } from '@/constants/app-key'
+import { type TagView } from '@/stores/modules/tags-view'
+
+//#region 侧边栏状态
+export const getSidebarStatus = () => {
+  return localStorage.getItem(CacheKey.SIDEBAR_STATUS)
+}
+export const setSidebarStatus = (
+  sidebarStatus: SidebarOpened | SidebarClosed
+) => {
+  localStorage.setItem(CacheKey.SIDEBAR_STATUS, sidebarStatus)
+}
+//#endregion
+
+//#region 标签栏
+export const getVisitedViews = () => {
+  const json = localStorage.getItem(CacheKey.VISITED_VIEWS)
+  return JSON.parse(json ?? '[]') as TagView[]
+}
+export const setVisitedViews = (views: TagView[]) => {
+  views.forEach((view) => {
+    // 删除不必要的属性,防止 JSON.stringify 处理到循环引用
+    delete view.matched
+    delete view.redirectedFrom
+  })
+  localStorage.setItem(CacheKey.VISITED_VIEWS, JSON.stringify(views))
+}
+export const getCachedViews = () => {
+  const json = localStorage.getItem(CacheKey.CACHED_VIEWS)
+  return JSON.parse(json ?? '[]') as string[]
+}
+export const setCachedViews = (views: string[]) => {
+  localStorage.setItem(CacheKey.CACHED_VIEWS, JSON.stringify(views))
+}
+//#endregion

+ 29 - 0
src/utils/index.ts

@@ -0,0 +1,29 @@
+/** 自动获取服务器地址 */
+export const getNetworkIp = (protocol: string, port?: number) => {
+  if (import.meta.env.VITE_AUTOMATIC_IP_FLAG === 'false') {
+    if (protocol === 'ws') {
+      return import.meta.env.VITE_WEBSOCKET_IP
+    }
+    return import.meta.env.VITE_BASE_API
+  } else {
+    let needHost: string = '' // 打开的 host
+    try {
+      // 使用 window.location 获取客户端的 IP 地址
+      needHost = window.location.hostname
+    } catch (e) {
+      needHost = '192.168.1.130/'
+    }
+    //本地环境判断
+    const devFlag = import.meta.env.DEV
+    if (devFlag) {
+      if (protocol === 'ws') {
+        return import.meta.env.VITE_WEBSOCKET_IP + port
+      }
+      port = 3333
+    }
+    if (port) {
+      return `${protocol}://${needHost}:${port}`
+    }
+    return `${protocol}://${needHost}`
+  }
+}

+ 44 - 0
src/utils/request.ts

@@ -0,0 +1,44 @@
+import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'
+import { get, merge } from 'lodash-es'
+import { getToken } from './cache/cookies'
+import { getNetworkIp } from './index'
+
+/** 创建请求实例 */
+function createService() {
+  // 创建一个 axios 实例命名为 service
+  const service = axios.create()
+  // 请求拦截
+  service.interceptors.request.use(
+    (config) => config,
+    // 发送失败
+    (error) => Promise.reject(error)
+  )
+  // 响应拦截(可根据具体业务作出相应的调整)
+  return service
+}
+
+/** 创建请求方法 */
+function createRequest(service: AxiosInstance) {
+  return function <T>(config: AxiosRequestConfig): Promise<T> {
+    const token = getToken()
+    const ip = getNetworkIp('http', 7000)
+    const defaultConfig = {
+      headers: {
+        // 携带 Token
+        Authorization: token ? `Bearer ${token}` : undefined,
+        'Content-Type': 'application/json'
+      },
+      timeout: 60000,
+      baseURL: ip,
+      data: {}
+    }
+    // 将默认配置 defaultConfig 和传入的自定义配置 config 进行合并成为 mergeConfig
+    const mergeConfig = merge(defaultConfig, config)
+    return service(mergeConfig)
+  }
+}
+
+/** 用于网络请求的实例 */
+const service = createService()
+/** 用于网络请求的方法 */
+export const request = createRequest(service)

+ 222 - 0
src/views/login/index.vue

@@ -0,0 +1,222 @@
+<template>
+  <div class="login-container">
+    <div class="login-card">
+      <div class="content">
+        <div class="title">
+          <h2>欢迎使用摄像机管理系统</h2>
+        </div>
+        <el-form
+          ref="loginFormRef"
+          :model="loginFormData"
+          :rules="loginFormRules"
+          @keyup.enter="handleLogin"
+        >
+          <el-form-item prop="username">
+            <el-input
+              v-model.trim="loginFormData.name"
+              placeholder="账号"
+              :prefix-icon="User"
+              size="large"
+              tabindex="1"
+              type="text"
+            />
+          </el-form-item>
+          <el-form-item prop="password" class="password-form-item">
+            <el-input
+              v-model.trim="loginFormData.password"
+              placeholder="密码"
+              :prefix-icon="Lock"
+              show-password
+              size="large"
+              tabindex="2"
+              type="password"
+            />
+            <div class="forget-password">
+              <el-link type="primary" @click="dialogVisible = true">忘记密码</el-link>
+            </div>
+          </el-form-item>
+          <el-button
+            :loading="loading"
+            size="large"
+            type="primary"
+            class="login-btn"
+            @click.prevent="handleLogin"
+          >
+            登 录
+          </el-button>
+          <div class="login-tip">
+            <span>初始密码请参阅说明书</span>
+          </div>
+        </el-form>
+      </div>
+    </div>
+    <!-- 忘记密码对话框 -->
+    <el-dialog
+      v-model="dialogVisible"
+      title="忘记密码"
+      width="500"
+    >
+      <span>请联系客服或管理员获取超级密码后在登录!</span>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="dialogVisible = false">
+            确认
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { reactive, ref } from 'vue'
+import { useRouter } from 'vue-router'
+import { useUserStore } from '@/stores/modules/user'
+import { ElMessage, ElDialog, type FormInstance, type FormRules } from 'element-plus'
+import { User, Lock } from '@element-plus/icons-vue'
+import { type LoginRequestData } from '@/api/types/login'
+import { setToken } from '@/utils/cache/cookies'
+import { setRouter } from '@/router'
+
+const router = useRouter()
+/** 登录表单元素的引用 */
+const loginFormRef = ref<FormInstance | null>(null)
+/** 登录按钮 Loading */
+const loading = ref(false)
+/** 忘记密码对话框是否可见 */
+const dialogVisible = ref(false)
+/** 登录表单数据 */
+const loginFormData: LoginRequestData = reactive({
+  name: '',
+  password: '',
+  code: ''
+})
+/** 登录表单校验规则 */
+const loginFormRules: FormRules = {
+  name: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
+  password: [
+    { required: true, message: '请输入密码', trigger: 'blur' },
+    { min: 1, max: 16, message: '长度在 1 到 16 个字符', trigger: 'blur' }
+  ]
+}
+
+/** 登录逻辑 */
+const handleLogin = () => {
+  loginFormRef.value?.validate((valid: boolean, fields) => {
+    if (valid) {
+      loading.value = true
+      useUserStore()
+        .login(loginFormData)
+        .then((res: string) => {
+          if (res == 'ok\n') {
+            ElMessage.success('登录成功')
+            const token = useUserStore().token
+            setToken(token)
+            setRouter()
+            router.push({ path: '/' })
+          } else {
+            ElMessage.error('用户名或密码错误')
+          }
+        })
+        .catch(() => {
+          loginFormData.password = ''
+          ElMessage.error('登录失败')
+        })
+        .finally(() => {
+          loading.value = false
+        })
+    } else {
+      console.error('表单校验不通过', fields)
+    }
+  })
+}
+</script>
+
+<style lang="scss" scoped>
+.login-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+  width: 100%;
+
+  .title {
+    text-align: center;
+    margin-bottom: 30px;
+
+    h2 {
+      color: #333;
+      font-size: 24px;
+      font-weight: 600;
+      margin: 0;
+    }
+  }
+
+  .login-card {
+    width: 480px;
+    border-radius: 12px;
+    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
+    background-color: #fff;
+    overflow: hidden;
+    transition: box-shadow 0.3s ease;
+
+    &:hover {
+      box-shadow: 0 12px 48px rgba(0, 0, 0, 0.2);
+    }
+
+    .content {
+      padding: 40px 50px;
+
+      :deep(.el-form-item) {
+        margin-bottom: 20px;
+      }
+
+      :deep(.el-input-group__append) {
+        padding: 0;
+        overflow: hidden;
+      }
+
+      .password-form-item {
+        position: relative;
+        margin-bottom: 8px;
+
+        .forget-password {
+          position: absolute;
+          right: 0;
+          top: 100%;
+          padding-top: 1px;
+          display: flex;
+          justify-content: flex-end;
+
+          :deep(.el-link) {
+            font-size: 13px;
+
+            &:hover {
+              text-decoration: underline;
+            }
+          }
+        }
+      }
+
+      .login-btn {
+        width: 100%;
+        margin-top: 32px;
+        font-weight: 500;
+        letter-spacing: 1px;
+
+        &:hover {
+          transform: translateY(-2px);
+          box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
+        }
+      }
+
+      .login-tip {
+        text-align: center;
+        font-size: 13px;
+        margin-top: 16px;
+        color: #999;
+      }
+    }
+  }
+}
+</style>

+ 281 - 0
src/views/preview/index.vue

@@ -0,0 +1,281 @@
+<template>
+  <div
+    ref="containerRef"
+    class="video-container"
+    :class="{ 'is-fullscreen': isFullscreen }"
+  >
+    <div class="video-wrapper">
+      <MyVideo
+        ref="videoRef"
+        :drag-flag="true"
+        @video-error="handleVideoError"
+      />
+      <!-- 全屏按钮 -->
+      <button
+        class="fullscreen-btn"
+        :title="isFullscreen ? '退出全屏 (F)' : '全屏 (F)'"
+        @click="toggleFullscreen"
+      >
+        <svg
+          v-if="!isFullscreen"
+          viewBox="0 0 24 24"
+          width="16"
+          height="16"
+        >
+          <path fill="currentColor" d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
+        </svg>
+        <svg
+          v-else
+          viewBox="0 0 24 24"
+          width="16"
+          height="16"
+        >
+          <path fill="currentColor" d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>
+        </svg>
+      </button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import {ref, onMounted, onUnmounted} from 'vue'
+import MyVideo from '@/components/myVideo.vue'
+
+const videoRef = ref()
+const isFullscreen = ref(false)
+const containerRef = ref<HTMLElement>()
+
+// 全屏切换
+const toggleFullscreen = () => {
+  if (!containerRef.value) return
+
+  if (!isFullscreen.value) {
+    // 进入全屏
+    if (containerRef.value.requestFullscreen) {
+      containerRef.value.requestFullscreen()
+    }
+  } else {
+    // 退出全屏
+    if (document.exitFullscreen) {
+      document.exitFullscreen()
+    }
+  }
+}
+
+// 监听全屏状态变化
+const handleFullscreenChange = () => {
+  isFullscreen.value = !!document.fullscreenElement
+}
+
+// 键盘快捷键
+const handleKeydown = (event: KeyboardEvent) => {
+  switch (event.code) {
+    case 'KeyF':
+    case 'F11':
+      event.preventDefault()
+      toggleFullscreen()
+      break
+    case 'Escape':
+      if (isFullscreen.value) {
+        toggleFullscreen()
+      }
+      break
+  }
+}
+
+// 处理视频错误
+const handleVideoError = (error: any) => {
+  console.error('视频播放错误:', error)
+  // 这里可以添加用户友好的错误提示
+}
+
+onMounted(() => {
+  document.addEventListener('fullscreenchange', handleFullscreenChange)
+  document.addEventListener('keydown', handleKeydown)
+})
+
+onUnmounted(() => {
+  document.removeEventListener('fullscreenchange', handleFullscreenChange)
+  document.removeEventListener('keydown', handleKeydown)
+})
+</script>
+
+<style lang="scss" scoped>
+.video-container {
+  width: 100%;
+  height: calc(100vh - 90px);
+  display: flex;
+  margin-top: 20px;
+  justify-content: center;
+  align-items: center;
+  position: relative;
+  overflow: hidden;
+
+  // 全屏状态
+  &.is-fullscreen {
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: 9999;
+    width: 100vw;
+    height: 100vh;
+  }
+}
+
+.video-wrapper {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  max-width: min(100vw, calc((100vh - 90px) * 16 / 9));
+  max-height: min(100vh - 90px, calc(100vw / 16 * 9));
+  aspect-ratio: 16/9;
+
+  // 确保视频组件填满容器
+  :deep(.preview-container) {
+    width: 100%;
+    height: 100%;
+  }
+
+  :deep(.video_container) {
+    width: 100%;
+    height: 100%;
+    background: #000;
+    border-radius: 5px;
+    overflow: hidden;
+    //box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
+
+    // 全屏时移除圆角和阴影
+    .video-container.is-fullscreen & {
+      border-radius: 0;
+      box-shadow: none;
+    }
+  }
+
+  :deep(#player) {
+    width: 100%;
+    height: 100%;
+    //object-fit: cover;
+    object-fit: contain;
+  }
+
+  // 优化控制条样式
+  :deep(.video-control) {
+    opacity: 0;
+    transition: opacity 0.3s ease;
+
+    &:hover {
+      opacity: 1;
+    }
+  }
+
+  // 鼠标悬停时显示控制条
+  &:hover :deep(.video-control) {
+    opacity: 1;
+  }
+}
+
+.fullscreen-btn {
+  position: absolute;
+  top: 16px;
+  right: 16px;
+  width: 40px;
+  height: 40px;
+  background: rgba(0, 0, 0, 0.6);
+  border: none;
+  border-radius: 8px;
+  color: white;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  opacity: 0;
+  transition: all 0.3s ease;
+  backdrop-filter: blur(8px);
+  z-index: 10;
+
+  &:hover {
+    background: rgba(0, 0, 0, 0.8);
+    transform: scale(1.05);
+  }
+
+  &:active {
+    transform: scale(0.95);
+  }
+
+  // 鼠标悬停在容器上时显示
+  .video-wrapper:hover & {
+    opacity: 1;
+  }
+
+  // 全屏状态下的样式调整
+  .video-container.is-fullscreen & {
+    top: 20px;
+    right: 20px;
+    width: 48px;
+    height: 48px;
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .video-container {
+    height: 100vh;
+    padding: 0;
+  }
+
+  .video-wrapper {
+    max-width: 100vw;
+    max-height: 100vh;
+
+    :deep(.video_container) {
+      border-radius: 0;
+    }
+  }
+
+  .fullscreen-btn {
+    width: 36px;
+    height: 36px;
+    top: 12px;
+    right: 12px;
+
+    svg {
+      width: 14px;
+      height: 14px;
+    }
+  }
+}
+
+@media (max-width: 480px) {
+  .video-wrapper {
+    :deep(.video-control) {
+      height: 48px;
+
+      .control-btn {
+        width: 40px;
+        height: 40px;
+        font-size: 20px;
+      }
+    }
+  }
+}
+
+// 减少动画以提升性能
+@media (prefers-reduced-motion: reduce) {
+  * {
+    transition: none !important;
+    animation: none !important;
+  }
+}
+
+// 高对比度模式支持
+@media (prefers-contrast: custom) {
+  .video-wrapper :deep(.video_container) {
+    border: 2px solid white;
+  }
+
+  .fullscreen-btn {
+    border: 1px solid white;
+    background: black;
+  }
+}
+</style>

+ 191 - 0
src/views/setting/netSetting/components/IPInputBox.vue

@@ -0,0 +1,191 @@
+<template>
+  <div class="ip-box" :class="{ disabled: props.disabled }">
+    <el-input
+      ref="input1Ref"
+      v-model="ip1"
+      :disabled="oDisabled"
+      maxlength="3"
+      name="ip"
+      onkeyup="value=value.replace(/[^\d]/g,'')"
+      @blur="submitIp"
+      @input="(val) => checkVal(val, 'ip1')"
+    />
+    <div class="ip-dot" />
+    <el-input
+      ref="input2Ref"
+      v-model="ip2"
+      :disabled="oDisabled"
+      maxlength="3"
+      name="ip"
+      onkeyup="value=value.replace(/[^\d]/g,'')"
+      @blur="submitIp"
+      @input="(val) => checkVal(val, 'ip2')"
+    />
+    <div class="ip-dot" />
+    <el-input
+      ref="input3Ref"
+      v-model="ip3"
+      :disabled="oDisabled"
+      maxlength="3"
+      name="ip"
+      onkeyup="value=value.replace(/[^\d]/g,'')"
+      @blur="submitIp"
+      @input="(val) => checkVal(val, 'ip3')"
+    />
+    <div class="ip-dot" />
+    <el-input
+      ref="input4Ref"
+      v-model="ip4"
+      :disabled="oDisabled"
+      maxlength="3"
+      name="ip"
+      onkeyup="value=value.replace(/[^\d]/g,'')"
+      @blur="submitIp"
+      @input="(val) => checkVal(val, 'ip4')"
+    />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue'
+import { ElInput } from 'element-plus'
+
+interface Props {
+  ipVal: string | undefined
+  disabled: number | undefined
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<{
+  'update:ipVal': [string]
+}>()
+
+const oDisabled = ref()
+
+watch(
+  () => props.disabled,
+  (newValue) => {
+    oDisabled.value = Number(newValue)
+  }
+)
+
+const ip1 = ref()
+const ip2 = ref()
+const ip3 = ref()
+const ip4 = ref()
+
+watch(
+  () => props.ipVal,
+  (newValue) => {
+    const ip = newValue?.match(/\d+/g)
+    if (ip) {
+      ip1.value = ip[0]
+      ip2.value = ip[1]
+      ip3.value = ip[2]
+      ip4.value = ip[3]
+    }
+  }
+)
+
+const input1Ref = ref<InstanceType<typeof ElInput>>()
+const input2Ref = ref<InstanceType<typeof ElInput>>()
+const input3Ref = ref<InstanceType<typeof ElInput>>()
+const input4Ref = ref<InstanceType<typeof ElInput>>()
+
+const submitIp = () => {
+  if (ip1.value && ip2.value && ip3.value && ip4.value) {
+    let ipVal = ip1.value + '.' + ip2.value + '.' + ip3.value + '.' + ip4.value
+    emit('update:ipVal', ipVal)
+  } else {
+    emit('update:ipVal', '')
+  }
+}
+const checkVal = (val: string, key: string) => {
+  if (parseInt(val) > 255) {
+    switch (key) {
+      case 'ip1':
+        ip1.value = 255
+        focusInput(2)
+        break
+      case 'ip2':
+        ip2.value = 255
+        focusInput(3)
+        break
+      case 'ip3':
+        ip3.value = 255
+        focusInput(4)
+        break
+      case 'ip4':
+        ip4.value = 255
+        break
+      default:
+        break
+    }
+  } else if (parseInt(val) <= 255 && parseInt(val) >= 100) {
+    //输入三位数自动跳到下一个输入值
+    switch (key) {
+      case 'ip1':
+        focusInput(2)
+        break
+      case 'ip2':
+        focusInput(3)
+        break
+      case 'ip3':
+        focusInput(4)
+        break
+      default:
+        break
+    }
+  }
+}
+const focusInput = (key: number) => {
+  switch (key) {
+    case 2:
+      input2Ref.value?.focus()
+      break
+    case 3:
+      input3Ref.value?.focus()
+      break
+    case 4:
+      input4Ref.value?.focus()
+      break
+    default:
+      break
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.ip-box {
+  width: 210px;
+  border: 1px solid #d9d9d9;
+  border-radius: 20px;
+  height: 32px;
+  display: flex;
+  :deep(.el-input) {
+    width: 50px;
+  }
+  :deep(.el-input__inner) {
+    border: 0 !important;
+  }
+  :deep(.el-input__wrapper) {
+    box-shadow: none;
+    background-color: unset;
+  }
+  :deep(.el-form-item.is-error) {
+    box-shadow: none;
+  }
+}
+.ip-dot {
+  display: inline-block;
+  width: 4px;
+  height: 4px;
+  border-radius: 50%;
+  background-color: #000000;
+  display: flex;
+  place-self: center;
+}
+.disabled {
+  background-color: #f5f7fa;
+}
+</style>

+ 377 - 0
src/views/setting/netSetting/index.vue

@@ -0,0 +1,377 @@
+
+<template>
+  <div class="setting-container">
+    <div class="setting-card">
+      <div class="card-header">
+        <h2 class="title">网络设置</h2>
+        <p class="subtitle">配置设备的网络连接参数</p>
+      </div>
+      <div class="content">
+        <el-form
+          ref="settingFormRef"
+          :hide-required-asterisk="true"
+          label-position="left"
+          label-width="100px"
+          :model="settingFormData"
+          :rules="settingFormRules"
+          @keyup.enter="handleSave"
+        >
+          <!-- 自动获取DHCP -->
+          <div class="dhcp-section">
+            <div class="dhcp-header">
+              <span class="dhcp-label">自动获取 (DHCP)</span>
+              <el-switch
+                v-model="settingFormData.enableDHCP"
+                :active-value="1"
+                :inactive-value="0"
+                active-color="#409eff"
+                inactive-color="#dcdfe4"
+                @change="switchChange($event)"
+              />
+            </div>
+            <p class="dhcp-tip">启用自动配置时,以下字段将被禁用</p>
+          </div>
+
+          <!-- IP版本 -->
+          <el-form-item label="IP版本" prop="IP_version" class="form-item-custom">
+            <el-input v-model="IP_version"   disabled></el-input>
+          </el-form-item>
+
+          <!-- IP地址 -->
+          <el-form-item label="IP地址" prop="IP" class="form-item-custom">
+            <IPInputBox
+              :disabled="settingFormData.enableDHCP"
+              :ip-val="settingFormData.ipAddress"
+              @update:ip-val="(updateVal) => (settingFormData.ipAddress = updateVal)"
+            />
+          </el-form-item>
+
+          <!-- 子网掩码 -->
+          <el-form-item label="子网掩码" prop="mask" class="form-item-custom">
+            <IPInputBox
+              :disabled="settingFormData.enableDHCP"
+              :ip-val="settingFormData.subNetAddress"
+              @update:ip-val="(updateVal) => (settingFormData.subNetAddress = updateVal)"
+            />
+          </el-form-item>
+
+          <!-- 默认网关 -->
+          <el-form-item label="默认网关" prop="gateway" class="form-item-custom">
+            <IPInputBox
+              :disabled="settingFormData.enableDHCP"
+              :ip-val="settingFormData.gateWayAddress"
+              @update:ip-val="(updateVal) => (settingFormData.gateWayAddress = updateVal)"
+            />
+          </el-form-item>
+
+          <!-- 物理地址 -->
+          <el-form-item label="物理地址" class="form-item-custom">
+            <el-input
+              v-model="settingFormData.deviceMac"
+              disabled
+              placeholder="MAC地址"
+              class="mac-input"
+            />
+          </el-form-item>
+
+          <!-- 操作按钮 -->
+          <div class="button-group">
+            <el-button
+              :loading="loading"
+              size="large"
+              type="primary"
+              @click="handleSave()"
+              class="save-button"
+            >
+              <span v-if="!loading">保 存</span>
+              <span v-else>保存中...</span>
+            </el-button>
+          </div>
+        </el-form>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {
+  type FormInstance,
+  type FormRules,
+  ElMessage,
+  ElMessageBox
+} from 'element-plus'
+import { getUserSettingApi, putUserSettingApi } from '@/api/setting'
+import { type UpdateSettingRequestData } from '@/api/types/setting'
+import IPInputBox from './components/IPInputBox.vue'
+
+const IP_version = ref('IPV4')
+const options = [
+  {
+    value: 'IPV4',
+    label: 'IPV4'
+  }
+]
+/** 按钮 Loading */
+const loading = ref(false)
+/** 表单数据 */
+const settingFormData: UpdateSettingRequestData = reactive({
+  NIC: 1,
+  enableDHCP: 0,
+  ipAddress: '',
+  subNetAddress: '',
+  gateWayAddress: '',
+  deviceMac: ''
+})
+/** 表单元素的引用 */
+const settingFormRef = ref<FormInstance | null>(null)
+/** 表单校验规则 */
+const settingFormRules: FormRules = {
+  IP: [
+    {
+      validator: (rule, value, callback) => {
+        if (settingFormData.ipAddress) {
+          callback()
+        } else {
+          callback(new Error('请输入IP'))
+        }
+      },
+      trigger: 'blur'
+    }
+  ],
+  mask: [
+    {
+      validator: (rule, value, callback) => {
+        if (settingFormData.subNetAddress) {
+          callback()
+        } else {
+          callback(new Error('请输入子网掩码'))
+        }
+      },
+      trigger: 'blur'
+    }
+  ],
+  gateway: [
+    {
+      validator: (rule, value, callback) => {
+        if (settingFormData.gateWayAddress) {
+          callback()
+        } else {
+          callback(new Error('请输入默认网关'))
+        }
+      },
+      trigger: 'blur'
+    }
+  ]
+}
+const switchChange = ($event: number) => {
+  if ($event === 1) {
+    settingFormRef.value?.clearValidate()
+  }
+}
+const param = {
+  NIC: 1
+}
+/** 获取表单数据 */
+const fetchData = () => {
+  getUserSettingApi(param.NIC).then((res) => {
+    settingFormData.enableDHCP = res.data.enableDHCP
+    settingFormData.ipAddress = res.data.ipAddress
+    settingFormData.subNetAddress = res.data.subNetAddress
+    settingFormData.gateWayAddress = res.data.gateWayAddress
+    settingFormData.deviceMac = res.data.deviceMac
+    settingFormData.firstDNSAddress = res.data.firstDNSAddress
+    settingFormData.secondDNSAddress = res.data.secondDNSAddress
+  })
+}
+fetchData()
+
+/** 保存逻辑 */
+const handleSave = () => {
+    settingFormRef.value?.validate((valid: boolean, fields) => {
+      if (valid) {
+        ElMessageBox.confirm('更改网络配置,设备将会重启', {
+          confirmButtonText: '确认',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(() => {
+          loading.value = true
+          putUserSettingApi(param.NIC, settingFormData)
+            .then(() => {
+              ElMessage.success('保存成功')
+            })
+            .finally(() => {
+              loading.value = false
+            })
+        })
+      }
+    })
+}
+</script>
+
+<style lang="scss" scoped>
+.setting-container {
+  padding: 20px;
+  //background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
+  min-height: 85vh;
+  display: flex;
+  //align-items: flex-start;
+  justify-content: center;
+  align-items: center;
+}
+
+.setting-card {
+  width: 100%;
+  max-width: 500px;
+  //background: white;
+  border-radius: 12px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+  overflow: hidden;
+  transition: all 0.3s ease;
+
+  &:hover {
+    box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
+  }
+}
+
+.card-header {
+  //background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #000104;
+  padding: 20px 20px 0 20px;
+  text-align: center;
+
+  .title {
+    margin: 0;
+    font-size: 24px;
+    font-weight: 600;
+    margin-bottom: 8px;
+  }
+
+  .subtitle {
+    margin: 0;
+    font-size: 14px;
+    opacity: 0.9;
+  }
+}
+
+.content {
+  padding: 30px;
+}
+
+.dhcp-section {
+  background: #f0f9ff;
+  border-left: 4px solid #409eff;
+  padding: 16px;
+  border-radius: 6px;
+  margin-bottom: 24px;
+
+  .dhcp-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 8px;
+
+    .dhcp-label {
+      font-size: 14px;
+      font-weight: 500;
+      color: #333;
+    }
+  }
+
+  .dhcp-tip {
+    margin: 0;
+    font-size: 12px;
+    color: #909399;
+  }
+}
+
+.form-item-custom {
+  margin-bottom: 20px;
+
+  :deep(.el-form-item__label) {
+    color: #606266;
+    font-weight: 500;
+    padding-right: 12px;
+  }
+}
+
+.mac-input {
+  :deep(.el-input__wrapper) {
+    background-color: #f5f7fa !important;
+  }
+}
+
+.button-group {
+  display: flex;
+  gap: 12px;
+  margin-top: 32px;
+  padding-top: 20px;
+  border-top: 1px solid #ebeef5;
+}
+
+.save-button {
+  flex: 1;
+  height: 40px;
+  font-size: 16px;
+  font-weight: 500;
+  border-radius: 6px;
+  transition: all 0.3s ease;
+
+  &:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 6px 16px rgba(64, 158, 255, 0.4);
+  }
+
+  &:active {
+    transform: translateY(0);
+  }
+}
+
+// 响应式设计
+@media (max-width: 600px) {
+  .setting-container {
+    padding: 12px;
+  }
+
+  .setting-card {
+    max-width: 100%;
+  }
+
+  .content {
+    padding: 20px;
+  }
+
+  .card-header {
+    padding: 20px 16px;
+
+    .title {
+      font-size: 20px;
+    }
+
+    .subtitle {
+      font-size: 12px;
+    }
+  }
+}
+
+// 深色主题支持(可选)
+@media (prefers-color-scheme: dark) {
+  .setting-card {
+    background: #1e1e1e;
+  }
+
+  .content {
+    color: #e0e0e0;
+  }
+
+  .form-item-custom {
+    :deep(.el-form-item__label) {
+      color: #b0b0b0;
+    }
+
+    :deep(.el-input__wrapper) {
+      background-color: #2a2a2a;
+      border-color: #404040;
+    }
+  }
+}
+</style>

+ 187 - 0
src/views/setting/systemSetting/components/alarm/index.vue

@@ -0,0 +1,187 @@
+<template>
+  <div class="alarm-container">
+    <div class="alarm-header">
+      <span class="alarm-title">手动报警控制</span>
+    </div>
+    <div class="alarm-buttons">
+      <el-button
+        type="primary"
+        size="large"
+        @click="handleAlarm"
+        class="btn-alarm"
+        :loading="loading"
+      >
+        开启手动报警
+      </el-button>
+      <el-button
+        type="info"
+        size="large"
+        @click="handleCloseAlarm"
+        class="btn-close"
+        :loading="loading"
+      >
+        关闭手动报警
+      </el-button>
+    </div>
+    <div class="alarm-tip">
+      <span class="tip-icon">ℹ️</span>
+      <span>报警开启后将持续1分钟</span>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { ElMessage } from 'element-plus'
+import { cameraAlarm } from '@/api/setting'
+
+const loading = ref(false)
+
+async function handleCloseAlarm() {
+  if (loading.value) return
+  loading.value = true
+  try {
+    const res = await cameraAlarm({
+      AlarmSettings: 0
+    })
+    if (res.data === 'ok\n') {
+      ElMessage.success('关闭手动报警成功!')
+    } else {
+      ElMessage.warning('关闭手动报警异常!')
+    }
+  } catch (error) {
+    ElMessage.error('关闭手动报警失败!')
+  } finally {
+    loading.value = false
+  }
+}
+
+async function handleAlarm() {
+  if (loading.value) return
+  loading.value = true
+  try {
+    const res = await cameraAlarm({
+      AlarmSettings: 1
+    })
+    if (res.data === 'ok\n') {
+      ElMessage.success('开启手动报警成功,持续时间为1分钟!')
+    } else {
+      ElMessage.warning('手动报警开启异常!')
+    }
+  } catch (error) {
+    ElMessage.error('手动报警开启失败!')
+  } finally {
+    loading.value = false
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.alarm-container {
+  background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
+  border-radius: 12px;
+  padding: 24px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+  border-left: 4px solid #ff6b6b;
+  transition: all 0.3s ease;
+
+  &:hover {
+    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
+    transform: translateY(-2px);
+  }
+}
+
+.alarm-header {
+  margin-bottom: 20px;
+  padding-bottom: 12px;
+  border-bottom: 2px solid rgba(255, 107, 107, 0.2);
+}
+
+.alarm-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #333;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+
+  &::before {
+    content: '';
+    width: 4px;
+    height: 4px;
+    background: #ff6b6b;
+    border-radius: 50%;
+    animation: pulse 2s ease-in-out infinite;
+  }
+}
+
+.alarm-buttons {
+  display: flex;
+  gap: 16px;
+  margin-bottom: 16px;
+  flex-wrap: wrap;
+
+  @media (max-width: 600px) {
+    flex-direction: column;
+    gap: 12px;
+  }
+}
+
+.btn-alarm,
+.btn-close {
+  flex: 1;
+  min-width: 160px;
+  height: 44px;
+  font-weight: 500;
+  letter-spacing: 0.5px;
+  border-radius: 8px;
+  transition: all 0.3s ease;
+  border: none;
+  font-size: 14px;
+
+  @media (max-width: 600px) {
+    width: 100%;
+  }
+
+  &:hover:not(:disabled) {
+    transform: translateY(-2px);
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
+  }
+
+  &:active:not(:disabled) {
+    transform: translateY(0);
+  }
+}
+
+.btn-icon {
+  margin-right: 6px;
+  font-size: 16px;
+}
+
+.alarm-tip {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 12px;
+  background: rgba(255, 193, 7, 0.1);
+  border-radius: 6px;
+  border-left: 3px solid #ffc107;
+  font-size: 13px;
+  color: #666;
+  line-height: 1.5;
+}
+
+.tip-icon {
+  font-size: 16px;
+  flex-shrink: 0;
+}
+
+@keyframes pulse {
+  0%, 100% {
+    opacity: 1;
+  }
+  50% {
+    opacity: 0.5;
+  }
+}
+</style>

+ 75 - 0
src/views/setting/systemSetting/components/cameraInfo/index.vue

@@ -0,0 +1,75 @@
+<template>
+  <div class="setting-container">
+    <div class="content">
+      <el-form
+        ref="formRef"
+        v-loading="loading"
+        label-position="left"
+        label-width="80px"
+        :model="formData"
+      >
+        <el-form-item label="项目名字" prop="DeviceName">
+          <el-input
+            v-model="formData.DeviceName"
+            class="el-input"
+            :disabled="true"
+            type="text"
+          />
+        </el-form-item>
+        <el-form-item label="软件版本" prop="SoftwareVer">
+          <el-input
+            v-model="formData.SoftwareVer"
+            :disabled="true"
+            type="text"
+          />
+        </el-form-item>
+        <el-form-item label="硬件版本" prop="HardwareVer">
+          <el-input
+            v-model="formData.HardwareVer"
+            :disabled="true"
+            type="text"
+          />
+        </el-form-item>
+        <el-form-item label="MAC地址" prop="MacAddr">
+          <el-input
+            v-model="formData.MacAddr"
+            :disabled="true"
+            type="text"
+          />
+        </el-form-item>
+
+      </el-form>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { type FormInstance } from 'element-plus'
+import { getCameraDeviceInfo } from '@/api/setting'
+
+/** 表单元素的引用 */
+const formRef = ref<FormInstance | null>(null)
+
+onMounted(() => {
+  deviceInfo()
+})
+const formData = ref({})
+const loading = ref(false)
+
+const deviceInfo = () => {
+  getCameraDeviceInfo().then((device) => {
+    formData.value = device.data
+  })
+}
+
+</script>
+
+<style lang="scss" scoped>
+.setting-container {
+  padding: 20px;
+}
+
+.el-input {
+  width: 210px;
+}
+</style>

+ 90 - 0
src/views/setting/systemSetting/components/nightVision/index.vue

@@ -0,0 +1,90 @@
+<template>
+  <div class="night-mode">
+    <div class="night-mode-content">
+      <span class="night-mode-label">夜视模式</span>
+      <el-select v-model="NightMode"  style="width: 160px">
+        <el-option
+          v-for="item in NightModeOptions"
+          :key="item.value"
+          :label="item.label"
+          :value="item.value"
+        />
+      </el-select>
+    </div>
+    <el-button
+      type="primary"
+      @click="handleNightMode"
+      style="width: 60px"
+    >
+      保存
+    </el-button>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { ElMessage } from 'element-plus'
+import { getCameraNightMode, cameraNightMode, getCameraAlarm } from '@/api/setting'
+
+const NightMode = ref(0)
+const NightModeOptions = [
+  {
+    value: 0,
+    label: '全彩夜视',
+  },
+  {
+    value: 1,
+    label: '黑白夜视',
+  },
+  {
+    value: 2,
+    label: '智能夜视',
+  }
+]
+
+async function getNightMode() {
+  const res = await getCameraNightMode()
+  if(res.data) {
+    NightMode.value = Number(res.data.NightMode)
+  }
+}
+
+onMounted( () => {
+  getNightMode()
+})
+
+async function handleNightMode() {
+  const res =await cameraNightMode({
+    NightMode: NightMode.value
+  })
+  if(res.data === 'ok\n'){
+    ElMessage.success('操作成功')
+  }else{
+    ElMessage.warning('操作失败')
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.night-mode {
+  display: flex;
+  gap: 20px;
+  flex-direction: column;
+  padding: 10px;
+  background: #f5f7fa;
+  border-radius: 8px;
+
+  &-content {
+    display: flex;
+    align-items: center;
+  }
+
+  &-label {
+    font-size: 16px;
+    font-weight: 500;
+    color: #303133;
+    min-width: 80px;
+  }
+
+}
+</style>

+ 237 - 0
src/views/setting/systemSetting/components/time/index.vue

@@ -0,0 +1,237 @@
+<template>
+  <div class="time-container">
+    <div class="time-card">
+      <div class="card-header">
+        <h3 class="card-title">时间设置</h3>
+      </div>
+
+      <div class="card-content">
+        <div class="form-group">
+          <label class="form-label">显示模式</label>
+          <el-select
+            v-model="timeMode"
+            class="mode-select"
+            @change="changeTimeMode"
+            placeholder="请选择时间格式"
+          >
+            <el-option
+              v-for="item in timeModeOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </div>
+
+        <div class="form-group">
+          <label class="form-label">本机时间</label>
+          <div class="time-info">
+            <span>{{ currentTime }}</span>
+          </div>
+        </div>
+        <div class="button-group">
+          <el-button
+            type="primary"
+            size="default"
+            @click="syncTime"
+            class="sync-button"
+          >
+            <span>同步至相机</span>
+          </el-button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, onBeforeUnmount } from 'vue'
+import { GetTimePara, PutTimePara } from '@/api/setting'
+import { format } from 'date-fns'
+import { ElMessage } from 'element-plus'
+import type { TimeParaData } from '@/api/types/setting'
+
+const timeMode = ref(0)
+const currentTime = ref('')
+let intervalId: number | null = null
+
+
+const timeModeOptions = [
+  {
+    value: 1,
+    label: '24小时制'
+  },
+  {
+    value: 0,
+    label: '12小时制'
+  }
+]
+
+// 更新时间函数
+function updateTime() {
+  const now = new Date()
+  currentTime.value = timeMode.value === 1
+    ? format(now, 'HH:mm:ss')
+    : format(now, 'hh:mm:ss a')
+}
+
+async function getTimeMode() {
+  try {
+    const res = await GetTimePara()
+    timeMode.value = res.data?.timeMode || 1
+    updateTime() // 初始更新
+  } catch (error) {
+    console.error('获取时间模式失败', error)
+  }
+}
+
+onMounted(() => {
+  getTimeMode()
+  // 每秒更新一次时间
+  intervalId = window.setInterval(updateTime, 1000)
+})
+
+onBeforeUnmount(() => {
+  // 组件卸载时清除定时器
+  if (intervalId !== null) {
+    clearInterval(intervalId)
+  }
+})
+
+async function changeTimeMode() {
+  updateTime() // 立即更新显示格式
+  const timeParaData: TimeParaData = {
+    timeMode: timeMode.value
+  }
+  try {
+    const res = await PutTimePara(timeParaData)
+    if (res.data === 'ok\n') {
+      ElMessage.success('修改成功')
+    } else {
+      ElMessage.error('修改失败')
+    }
+  } catch (error) {
+    ElMessage.error('修改失败')
+  }
+}
+
+function syncTime() {
+  const timeParaData: TimeParaData = {
+    time: format(new Date(), 'yyyy-MM-dd-HH-mm-ss'),
+    timeMode: timeMode.value
+  }
+  PutTimePara(timeParaData).then(res => {
+    if (res.data === 'ok\n') {
+      ElMessage.success('同步成功')
+    } else {
+      ElMessage.error('同步失败')
+    }
+  }).catch(() => {
+    ElMessage.error('同步失败')
+  })
+}
+</script>
+
+<style scoped lang="scss">
+.time-container {
+  padding: 0 16px;
+  background: #f5f7fa;
+  min-height: 100%;
+  display: flex;
+  align-items: flex-start;
+  //justify-content: center;
+}
+
+.time-card {
+  width: 100%;
+  max-width: 480px;
+  //background: white;
+  border-radius: 8px;
+  //box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  overflow: hidden;
+
+  .card-header {
+    //background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    padding: 20px;
+    color: #000104;
+
+    .card-title {
+      margin: 0;
+      font-size: 18px;
+      font-weight: 600;
+    }
+  }
+
+  .card-content {
+    padding: 24px;
+
+    .form-group {
+      margin-bottom: 24px;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+
+      .form-label {
+        display: block;
+        font-size: 14px;
+        font-weight: 500;
+        color: #303133;
+        margin-bottom: 8px;
+        line-height: 1;
+      }
+
+      .mode-select {
+        width: 100%;
+      }
+
+      .time-info {
+        display: flex;
+        align-items: center;
+        height: 40px;
+        padding: 0 12px;
+        background: #f5f7fa;
+        border: 1px solid #dcdfe6;
+        border-radius: 4px;
+        font-size: 14px;
+        color: #606266;
+        font-family: 'Courier New', monospace;
+      }
+    }
+
+    .button-group {
+      display: flex;
+      gap: 12px;
+      margin-top: 32px;
+
+      .sync-button {
+        flex: 1;
+        height: 40px;
+        font-size: 14px;
+        border-radius: 4px;
+      }
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 600px) {
+  .time-container {
+    padding: 12px;
+  }
+
+  .time-card {
+    .card-content {
+      padding: 16px;
+
+      .form-group {
+        margin-bottom: 16px;
+      }
+
+      .button-group {
+        margin-top: 24px;
+      }
+    }
+  }
+}
+</style>

+ 137 - 0
src/views/setting/systemSetting/components/user/index.vue

@@ -0,0 +1,137 @@
+<template>
+  <div v-loading="loading" class="user-password-form">
+    <h3>相机密码设置</h3>
+    <el-form
+      ref="formRef"
+      :model="userManagementFormData"
+      :rules="userManagementFormRules"
+      @keyup.enter="handleConfirm"
+    >
+      <el-form-item prop="oldPassword">
+        <el-input
+          v-model.trim="userManagementFormData.oldPassword"
+          placeholder="旧密码"
+          size="large"
+          tabindex="1"
+          type="password"
+          show-password
+          class="el-input"
+        />
+      </el-form-item>
+      <el-form-item prop="newPassword">
+        <el-input
+          v-model.trim="userManagementFormData.newPassword"
+          placeholder="新密码"
+          size="large"
+          tabindex="2"
+          type="password"
+          show-password
+        />
+      </el-form-item>
+      <el-form-item prop="newPasswordSc">
+        <el-input
+          v-model.trim="userManagementFormData.newPasswordSc"
+          placeholder="确认新密码"
+          size="large"
+          tabindex="3"
+          type="password"
+          show-password
+        />
+      </el-form-item>
+      <el-button size="large" type="primary" @click.prevent="handleConfirm">
+        保存
+      </el-button>
+    </el-form>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {
+  type FormInstance,
+  type FormRules,
+  ElMessage,
+  ElLoading
+} from 'element-plus'
+import { putPasswordApi } from '@/api/userManagement'
+import { useUserStore } from '@/stores/modules/user'
+import { ref } from 'vue'
+import { useRouter } from 'vue-router'
+
+/** 表单元素的引用 */
+const formRef = ref<FormInstance | null>(null)
+const version = import.meta.env.VITE_APP_TITLE
+
+const userManagementFormData = reactive({
+  name: useUserStore().username || 'admin',
+  oldPassword: '',
+  newPassword: '',
+  newPasswordSc: ''
+})
+/** 表单校验规则 */
+const userManagementFormRules: FormRules = {
+  oldPassword: [
+    { required: true, message: '请输入旧密码', trigger: 'blur' },
+    { min: 1, max: 16, message: '密码长度必须在1到16个字符之间', trigger: 'blur' }
+  ],
+  newPassword: [
+    { required: true, message: '请输入新密码', trigger: 'blur' },
+    { min: 1, max: 16, message: '密码长度必须在1到16个字符之间', trigger: 'blur' }
+  ],
+  newPasswordSc: [
+    {
+      validator: (rule, value, callback) => {
+        if (value !== userManagementFormData.newPassword) {
+          callback(new Error('两次密码不一致'))
+        } else {
+          callback()
+        }
+      },
+      trigger: 'blur'
+    }
+  ]
+}
+
+const router = useRouter()
+const loading = ref(false)
+
+/** 登录按钮 Loading */
+const handleConfirm = () => {
+  formRef.value?.validate((valid: boolean, fields) => {
+    if (valid) {
+      let loading = ElLoading.service()
+      putPasswordApi(userManagementFormData)
+        .then((res) => {
+          if (res.data == 'ok\n') {
+            ElMessage.success('修改成功')
+            useUserStore().logout() // 退出登录
+            router.push('/login')
+          } else {
+            ElMessage.error('修改失败')
+          }
+          loading.close()
+        })
+        .catch((error) => {
+          loading.close()
+          ElMessage.success('修改失败' + error)
+        })
+    } else {
+      ElMessage.error('请检查输入项')
+    }
+  })
+}
+</script>
+
+<style lang="scss" scoped>
+.user-password-form{
+  //margin-top: 20px;
+  margin-left: 10px;
+  display: flex;
+  flex-direction: column;
+  //align-items: center;
+  //justify-content: center;
+}
+.el-input{
+  width: 300px;
+}
+</style>
+

+ 296 - 0
src/views/setting/systemSetting/components/volume/index.vue

@@ -0,0 +1,296 @@
+<template>
+  <div class="volume-container">
+    <div class="volume-card">
+      <!-- 标题 -->
+      <div class="volume-header">
+        <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
+          <path d="M15.54 3.54a9 9 0 0 1 0 12.72M19.07 4.93a15 15 0 0 1 0 14.14"></path>
+        </svg>
+        <h2>喇叭音量</h2>
+      </div>
+
+      <!-- 音量显示 -->
+      <div class="volume-display">
+        <span class="label">当前音量</span>
+        <span class="value">{{ speakerVolume }}%</span>
+      </div>
+
+      <!-- 音量滑块 -->
+      <div class="slider-wrapper">
+        <el-slider
+          v-model="speakerVolume"
+          :max="100"
+          :min="0"
+          show-stops
+          :format-tooltip="formatTooltip"
+        />
+      </div>
+
+      <!-- 音量范围标记 -->
+      <div class="range-markers">
+        <span>0%</span>
+        <span>50%</span>
+        <span>100%</span>
+      </div>
+
+      <!-- 保存按钮 -->
+      <el-button
+        type="primary"
+        :loading="loading"
+        @click="saveVolume"
+        class="save-btn"
+      >
+        {{ loading ? '保存中...' : '保存设置' }}
+      </el-button>
+
+      <!-- 提示信息 -->
+      <p class="help-text">调整音量后点击保存按钮生效</p>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onUnmounted } from 'vue'
+import { ElSlider, ElButton, ElMessage } from 'element-plus'
+import { getCameraVolume, cameraVolume } from '@/api/setting'
+
+interface Feedback {
+  type: 'success' | 'error'
+  message: string
+}
+
+const speakerVolume = ref<number>(50)
+const loading = ref<boolean>(false)
+const feedback = ref<Feedback | null>(null)
+let feedbackTimer: ReturnType<typeof setTimeout> | null = null
+
+const formatTooltip = (val: number | number[]): string => {
+  const value = Array.isArray(val) ? val[0] : val
+  return `${value}%`
+}
+
+function getVolume() {
+  getCameraVolume().then(res => {
+    if(res.data) {
+      speakerVolume.value = Number(res.data.speakerVolume)
+    }
+  })
+}
+
+getVolume()
+
+const saveVolume = async () => {
+  loading.value = true
+  feedback.value = null
+  try {
+    const res = await cameraVolume({ speakerVolume: speakerVolume.value })
+    if(res.data == 'ok\n') {
+      ElMessage.success(`音量已保存`)
+    } else {
+      ElMessage.error('音量设置失败')
+    }
+  } catch (error) {
+    feedback.value = {
+      type: 'error',
+      message: '保存失败,请重试'
+    }
+    console.error('保存音量失败:', error)
+  } finally {
+    loading.value = false
+
+    // 3秒后自动清除反馈
+    if (feedbackTimer) clearTimeout(feedbackTimer)
+    feedbackTimer = setTimeout(() => {
+      feedback.value = null
+    }, 3000)
+  }
+}
+
+// 组件卸载时清理定时器
+onUnmounted(() => {
+  if (feedbackTimer) clearTimeout(feedbackTimer)
+})
+</script>
+
+<style scoped lang="scss">
+.volume-container {
+  //min-height: 100vh;
+  //background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
+  display: flex;
+  //align-items: center;
+  //justify-content: center;
+  padding: 20px;
+}
+
+.volume-card {
+  //background: white;
+  border-radius: 8px;
+  //box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
+  padding: 10px;
+  max-width: 360px;
+  width: 100%;
+}
+
+.volume-header {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 24px;
+
+  .icon {
+    width: 24px;
+    height: 24px;
+    color: #409eff;
+    stroke-width: 2;
+  }
+
+  h2 {
+    font-size: 18px;
+    font-weight: 600;
+    color: #333;
+    margin: 0;
+  }
+}
+
+.volume-display {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+
+  .label {
+    font-size: 14px;
+    font-weight: 500;
+    color: #606266;
+  }
+
+  .value {
+    font-size: 28px;
+    font-weight: bold;
+    color: #409eff;
+  }
+}
+
+.slider-wrapper {
+  margin-bottom: 12px;
+
+  :deep(.el-slider) {
+    .el-slider__runway {
+      height: 6px;
+      border-radius: 3px;
+    }
+
+    .el-slider__bar {
+      background-color: #409eff;
+      height: 6px;
+      border-radius: 3px;
+    }
+
+    .el-slider__button-wrapper {
+      .el-slider__button {
+        width: 18px;
+        height: 18px;
+        background-color: #409eff;
+
+        &:hover {
+          box-shadow: 0 0 8px rgba(64, 158, 255, 0.5);
+        }
+      }
+    }
+  }
+}
+
+.range-markers {
+  display: flex;
+  justify-content: space-between;
+  padding: 0 6px;
+  margin-bottom: 20px;
+  font-size: 12px;
+  color: #909399;
+}
+
+.save-btn {
+  width: 100%;
+  height: 40px;
+  font-size: 16px;
+  font-weight: 500;
+  border-radius: 4px;
+
+  &:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
+  }
+
+  &:active {
+    transform: translateY(0);
+  }
+}
+
+.feedback {
+  margin-top: 16px;
+  padding: 12px 16px;
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 14px;
+  animation: slideIn 0.3s ease-out;
+
+  .feedback-icon {
+    width: 20px;
+    height: 20px;
+    flex-shrink: 0;
+    stroke-width: 2;
+  }
+
+  &.feedback-success {
+    background-color: #f0f9ff;
+    color: #67c23a;
+    border-left: 4px solid #67c23a;
+
+    .feedback-icon {
+      color: #67c23a;
+    }
+  }
+
+  &.feedback-error {
+    background-color: #fef0f0;
+    color: #f56c6c;
+    border-left: 4px solid #f56c6c;
+
+    .feedback-icon {
+      color: #f56c6c;
+    }
+  }
+}
+
+.help-text {
+  margin-top: 16px;
+  font-size: 12px;
+  color: #909399;
+  text-align: center;
+  margin-bottom: 0;
+}
+
+@keyframes slideIn {
+  from {
+    opacity: 0;
+    transform: translateY(-10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.fade-enter-active,
+.fade-leave-active {
+  transition: all 0.3s ease;
+}
+
+.fade-enter-from,
+.fade-leave-to {
+  opacity: 0;
+}
+</style>

+ 181 - 0
src/views/setting/systemSetting/index.vue

@@ -0,0 +1,181 @@
+<template>
+  <div class="system-setting-container">
+    <el-tabs v-model="activeName" class="demo-tabs">
+      <el-tab-pane label="基本信息" name="first">
+        <CameraInfo />
+      </el-tab-pane>
+      <el-tab-pane label="系统维护" name="second">
+        <el-form
+          ref="formRef"
+          :model="FormData"
+          style="padding: 20px"
+        >
+          <el-form-item>
+            <el-radio-group
+              v-model="FormData.reset"
+              class="vertical-radio-group"
+            >
+              <el-radio :value="1">恢复出厂设置</el-radio>
+              <el-radio :value="2">手动重启相机</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-button  type="primary" @click.prevent="handle" :loading="loading">
+            执行
+          </el-button>
+        </el-form>
+
+      </el-tab-pane>
+      <el-tab-pane label="时间设置" name="third">
+        <TimeSetting />
+      </el-tab-pane>
+
+      <el-tab-pane label="音量设置" name="fourth">
+        <VolumeSetting />
+      </el-tab-pane>
+
+      <el-tab-pane label="报警设置" name="fifth">
+        <AlarmSetting />
+      </el-tab-pane>
+
+      <el-tab-pane label="夜视模式" name="sixth">
+        <NightVision />
+      </el-tab-pane>
+
+      <el-tab-pane label="密码管理" name="seventh">
+        <UserPassword />
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { ElLoading, ElMessage, ElMessageBox, type FormInstance } from 'element-plus'
+import CameraInfo from './components/cameraInfo/index.vue'
+import UserPassword from './components/user/index.vue'
+import TimeSetting from './components/time/index.vue'
+import VolumeSetting from './components/volume/index.vue'
+import AlarmSetting from './components/alarm/index.vue'
+import NightVision from './components/nightVision/index.vue'
+import { cameraReset } from '@/api/setting'
+
+const activeName = ref('first')
+const loading = ref(false)
+
+/** 表单元素的引用 */
+const formRef = ref<FormInstance | null>(null)
+
+// 定义复位选项的常量
+const FormData = reactive({ reset: 0 })
+
+const handleReset = async () => {
+  try {
+    await ElMessageBox.confirm(
+      '该操作将会修改相机配置并重启相机,是否继续?',
+      '警告',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+        confirmButtonClass: 'el-button--danger'
+      }
+    )
+
+    let loading = ElLoading.service()
+    formRef.value?.validate((valid: boolean) => {
+      if (valid) {
+        cameraReset(FormData)
+          .then((res) => {
+            if (res.data === 'ok\n') {
+              ElMessage.success('操作成功,请稍等片刻...')
+            } else {
+              ElMessage.error('操作失败')
+            }
+            loading.close()
+          })
+          .catch((error) => {
+            loading.close()
+            ElMessage.error('操作失败: ' + error)
+          })
+      } else {
+        loading.close()
+      }
+    })
+  } catch (error) {
+    console.log(error)
+    if (error !== 'cancel') {
+      ElMessage.error('操作失败')
+    }
+  }
+}
+
+
+const handleRestart = async () => {
+  try {
+    await ElMessageBox.confirm(
+      '该操作将会重启相机,是否继续?',
+      '提示',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+        confirmButtonClass: 'el-button--danger'
+      }
+    )
+    const resp = await cameraReset({ reboot: 1 })
+    if (resp.data === 'ok\n') {
+      ElMessage.success('相机重启成功,请稍等片刻...')
+    } else {
+      ElMessage.error('相机重启失败')
+    }
+  } catch (error) {
+    console.log(error)
+    if (error !== 'cancel') {
+      ElMessage.error('相机重启失败')
+    }
+  }
+}
+
+
+function handle() {
+  if (FormData.reset === 1) {
+    loading.value = true
+    handleReset()
+      .finally(() => {
+        loading.value = false
+      })
+  } else if (FormData.reset === 2) {
+    loading.value = true
+    handleRestart()
+      .finally(() => {
+        loading.value = false
+      })
+  }
+}
+</script>
+
+
+<style scoped lang="scss">
+.system-setting-container {
+  display: flex;
+  flex-direction: column;
+  padding: 20px;
+}
+
+// 添加垂直排列的radio样式
+.vertical-radio-group {
+  display: flex;
+  flex-direction: column;
+  gap: 10px; // 设置选项之间的间距
+  align-items: flex-start; // 左对齐
+}
+
+.el-radio {
+  margin-right: 0; // 移除默认的右边距
+  margin-bottom: 8px; // 添加底部间距
+}
+
+.el-button {
+  margin-top: 10px;
+}
+</style>

+ 14 - 0
tsconfig.app.json

@@ -0,0 +1,14 @@
+{
+  "extends": "@vue/tsconfig/tsconfig.dom.json",
+  "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "types/**/*.d.ts"],
+  "exclude": ["src/**/__tests__/*"],
+  "compilerOptions": {
+    "types": ["node"],
+    "composite": true,
+    "noEmit": true,
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  }
+}

+ 11 - 0
tsconfig.json

@@ -0,0 +1,11 @@
+{
+  "files": [],
+  "references": [
+    {
+      "path": "./tsconfig.node.json"
+    },
+    {
+      "path": "./tsconfig.app.json"
+    }
+  ],
+}

+ 17 - 0
tsconfig.node.json

@@ -0,0 +1,17 @@
+{
+//  "extends": "@tsconfig/node18/tsconfig.json",
+  "include": [
+    "vite.config.*",
+    "vitest.config.*",
+    "cypress.config.*",
+    "nightwatch.conf.*",
+    "playwright.config.*"
+  ],
+  "compilerOptions": {
+    "composite": true,
+    "noEmit": true,
+    "module": "ESNext",
+    "moduleResolution": "Bundler",
+    "types": ["node"],
+  }
+}

+ 6 - 0
types/api.d.ts

@@ -0,0 +1,6 @@
+/** 所有 api 接口的响应数据都应该准守该格式 */
+interface ApiResponseData<T> {
+  code: number
+  data: T
+  message: string
+}

+ 93 - 0
vite.config.ts

@@ -0,0 +1,93 @@
+import { fileURLToPath, URL } from 'node:url'
+import { type ConfigEnv, type UserConfigExport, loadEnv } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import { format } from 'date-fns'
+import AutoImport from 'unplugin-auto-import/vite'
+import Components from 'unplugin-vue-components/vite'
+import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
+
+export default (configEnv: ConfigEnv): UserConfigExport => {
+  const viteEnv = loadEnv(configEnv.mode, process.cwd()) as ImportMetaEnv
+  const { VITE_PUBLIC_PATH } = viteEnv
+  const isDevelopment = configEnv.mode === 'development'
+  return {
+    base: VITE_PUBLIC_PATH,
+    server: {
+      /** 设置 host: true 才可以使用 Network 的形式,以 IP 访问项目 */
+      host: true,
+      /** 端口号 */
+      port: 3333,
+      /** 是否自动打开浏览器 */
+      open: false,
+      /** 跨域设置允许 */
+      cors: true,
+      /** 端口被占用时,是否直接退出 */
+      strictPort: false,
+      /** 接口代理 */
+      proxy: isDevelopment
+        ? {
+            '/sports/': {
+              target: 'http://192.168.32.103/',
+              ws: true,
+              /** 是否允许跨域 */
+              changeOrigin: true
+            },
+            '/API/V1.0': {
+              target: 'http://192.168.32.103:7000',
+              ws: true,
+              /** 是否允许跨域 */
+              changeOrigin: true
+            },
+            '/XWAPI/V1.0': {
+              target: 'http://192.168.32.103:7000',
+              ws: true,
+              /** 是否允许跨域 */
+              changeOrigin: true
+            }
+          }
+        : {},
+      /** 预热常用文件,提高初始页面加载速度 */
+      warmup: {
+        clientFiles: ['./src/layouts/**/*.vue']
+      }
+    },
+    plugins: [
+      vue(),
+      AutoImport({
+        resolvers: [ElementPlusResolver()],
+        include: [
+          /\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
+          /\.vue$/,
+          /\.vue\?vue/, // .vue
+          /\.md$/ // .md
+        ],
+        imports: ['vue', 'vue-router'], // 自动导入
+        //这个一定要配置,会多出一个auto-import.d.ts文件,
+        dts: 'src/auto-import.d.ts'
+      }),
+      Components({
+        resolvers: [ElementPlusResolver(
+          {
+            importStyle: 'sass',
+            importIcons: true
+          }
+        )]
+      })
+    ],
+    css: {
+      preprocessorOptions: {
+        scss: {
+          api: 'modern-compiler'
+        }
+      }
+    },
+    resolve: {
+      alias: {
+        '@': fileURLToPath(new URL('./src', import.meta.url))
+      }
+    },
+    define: {
+      __BUILD_DATE__: JSON.stringify(format(new Date(), 'yyyy-MM-dd HH:mm:ss'))
+    }
+  }
+}

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff