请提供文件的di

main
zonawayne 11 months ago
commit c04d95f9ea

42
.gitignore vendored

@ -0,0 +1,42 @@
# Node
node_modules/
dist/
*.zip
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor
.vscode/
.idea/
*.suo
*.ntvs*
*.njsproj
*.sln
# OS
.DS_Store
Thumbs.db
# Env
.env*
# Local build
/build/
# Others
coverage/
*.local
*.log
# Mac
.AppleDouble
.LSOverride
# npm
package-lock.json
# Optional: ignore test snapshots
__snapshots__/

@ -0,0 +1,15 @@
{
"semi": true,
"tabWidth": 2,
"printWidth": 100,
"trailingComma": "all",
"singleQuote": false,
"jsxSingleQuote": false,
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"endOfLine": "lf",
"quoteProps": "as-needed",
"plugins": ["prettier-plugin-tailwindcss"],
"embeddedLanguageFormatting": "auto"
}

@ -0,0 +1,90 @@
# Digital Lighting Dashboard
基于 Vue 3 + TypeScript + Vite 构建的数字照明仪表板项目。
## 技术栈
- Vue 3.5.13
- TypeScript
- Vite 6.2.0
- Element Plus 2.9.7
- ECharts 5.6.0
- Vue Router 4
- Axios 1.8.4
## 系统要求
- Node.js >= 18.0.0
- npm >= 8.0.0
## 开发环境设置
1. 克隆项目
```bash
git clone [项目地址]
cd digital-lighting-dashboard
```
2. 安装依赖
```bash
npm install
```
3. 启动开发服务器
```bash
npm run dev
```
开发服务器将在以下地址启动:
- 本地访问http://localhost:5173
- 局域网访问http://[你的IP]:5173
## 可用的命令
- `npm run dev` - 启动开发服务器
- `npm run build` - 构建生产版本
- `npm run preview` - 预览生产构建
- `npm run lint` - 运行代码检查
- `npm run format` - 格式化代码
## 项目结构
```
digital-lighting-dashboard/
├── src/ # 源代码目录
├── public/ # 静态资源
├── dist/ # 构建输出目录
├── vite.config.ts # Vite 配置
├── tsconfig.json # TypeScript 配置
└── package.json # 项目依赖和脚本
```
## 代码规范
项目使用 ESLint 和 Prettier 进行代码规范和格式化:
- ESLint 用于代码质量检查
- Prettier 用于代码格式化
## 构建部署
构建生产版本:
```bash
npm run build
```
构建完成后,`dist` 目录中包含可部署的文件。
## 浏览器支持
- Chrome >= 87
- Firefox >= 78
- Safari >= 14
- Edge >= 88
## 许可证
[添加许可证信息]

@ -0,0 +1,660 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "vue-vite",
"dependencies": {
"axios": "^1.8.4",
"echarts": "^5.6.0",
"element-plus": "^2.9.7",
"pinia": "^3.0.2",
"vue": "^3.5.13",
"vue-router": "4",
"vue3-slide-verify": "^1.1.7",
},
"devDependencies": {
"@eslint/js": "^9.22.0",
"@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.7.0",
"eslint": "^9.22.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-vue": "^10.0.0",
"globals": "^16.0.0",
"prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"sass-embedded": "^1.87.0",
"typescript": "~5.7.2",
"vite": "^6.2.0",
"vue-eslint-parser": "^9.4.2",
"vue-tsc": "^2.2.4",
},
},
},
"packages": {
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
"@babel/parser": ["@babel/parser@7.26.10", "", { "dependencies": { "@babel/types": "^7.26.10" }, "bin": "./bin/babel-parser.js" }, "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA=="],
"@babel/types": ["@babel/types@7.26.10", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ=="],
"@bufbuild/protobuf": ["@bufbuild/protobuf@2.2.5", "https://registry.npmmirror.com/@bufbuild/protobuf/-/protobuf-2.2.5.tgz", {}, "sha512-/g5EzJifw5GF8aren8wZ/G5oMuPoGeS6MQD3ca8ddcvdXR5UELUfdTZITCGNhNXynY/AYl3Z4plmxdj/tRl/hQ=="],
"@ctrl/tinycolor": ["@ctrl/tinycolor@3.6.1", "", {}, "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA=="],
"@element-plus/icons-vue": ["@element-plus/icons-vue@2.3.1", "", { "peerDependencies": { "vue": "^3.2.0" } }, "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.1", "", { "os": "android", "cpu": "arm" }, "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.1", "", { "os": "android", "cpu": "arm64" }, "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.1", "", { "os": "android", "cpu": "x64" }, "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.1", "", { "os": "linux", "cpu": "arm" }, "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.1", "", { "os": "none", "cpu": "arm64" }, "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.1", "", { "os": "none", "cpu": "x64" }, "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg=="],
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.5.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w=="],
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
"@eslint/config-array": ["@eslint/config-array@0.19.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w=="],
"@eslint/config-helpers": ["@eslint/config-helpers@0.1.0", "", {}, "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA=="],
"@eslint/core": ["@eslint/core@0.12.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg=="],
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.0", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ=="],
"@eslint/js": ["@eslint/js@9.22.0", "", {}, "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ=="],
"@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="],
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.7", "", { "dependencies": { "@eslint/core": "^0.12.0", "levn": "^0.4.1" } }, "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g=="],
"@floating-ui/core": ["@floating-ui/core@1.6.9", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw=="],
"@floating-ui/dom": ["@floating-ui/dom@1.6.13", "", { "dependencies": { "@floating-ui/core": "^1.6.0", "@floating-ui/utils": "^0.2.9" } }, "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w=="],
"@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="],
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
"@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="],
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.2", "", {}, "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"@popperjs/core": ["@sxzz/popperjs-es@2.11.7", "", {}, "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.36.0", "", { "os": "android", "cpu": "arm" }, "sha512-jgrXjjcEwN6XpZXL0HUeOVGfjXhPyxAbbhD0BlXUB+abTOpbPiN5Wb3kOT7yb+uEtATNYF5x5gIfwutmuBA26w=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.36.0", "", { "os": "android", "cpu": "arm64" }, "sha512-NyfuLvdPdNUfUNeYKUwPwKsE5SXa2J6bCt2LdB/N+AxShnkpiczi3tcLJrm5mA+eqpy0HmaIY9F6XCa32N5yzg=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.36.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JQ1Jk5G4bGrD4pWJQzWsD8I1n1mgPXq33+/vP4sk8j/z/C2siRuxZtaUA7yMTf71TCZTZl/4e1bfzwUmFb3+rw=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.36.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-6c6wMZa1lrtiRsbDziCmjE53YbTkxMYhhnWnSW8R/yqsM7a6mSJ3uAVT0t8Y/DGt7gxUWYuFM4bwWk9XCJrFKA=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.36.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-KXVsijKeJXOl8QzXTsA+sHVDsFOmMCdBRgFmBb+mfEb/7geR7+C8ypAml4fquUt14ZyVXaw2o1FWhqAfOvA4sg=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.36.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dVeWq1ebbvByI+ndz4IJcD4a09RJgRYmLccwlQ8bPd4olz3Y213uf1iwvc7ZaxNn2ab7bjc08PrtBgMu6nb4pQ=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.36.0", "", { "os": "linux", "cpu": "arm" }, "sha512-bvXVU42mOVcF4le6XSjscdXjqx8okv4n5vmwgzcmtvFdifQ5U4dXFYaCB87namDRKlUL9ybVtLQ9ztnawaSzvg=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.36.0", "", { "os": "linux", "cpu": "arm" }, "sha512-JFIQrDJYrxOnyDQGYkqnNBtjDwTgbasdbUiQvcU8JmGDfValfH1lNpng+4FWlhaVIR4KPkeddYjsVVbmJYvDcg=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.36.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-KqjYVh3oM1bj//5X7k79PSCZ6CvaVzb7Qs7VMWS+SlWB5M8p3FqufLP9VNp4CazJ0CsPDLwVD9r3vX7Ci4J56A=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.36.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-QiGnhScND+mAAtfHqeT+cB1S9yFnNQ/EwCg5yE3MzoaZZnIV0RV9O5alJAoJKX/sBONVKeZdMfO8QSaWEygMhw=="],
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.36.0", "", { "os": "linux", "cpu": "none" }, "sha512-1ZPyEDWF8phd4FQtTzMh8FQwqzvIjLsl6/84gzUxnMNFBtExBtpL51H67mV9xipuxl1AEAerRBgBwFNpkw8+Lg=="],
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.36.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-VMPMEIUpPFKpPI9GZMhJrtu8rxnp6mJR3ZzQPykq4xc2GmdHj3Q4cA+7avMyegXy4n1v+Qynr9fR88BmyO74tg=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.36.0", "", { "os": "linux", "cpu": "none" }, "sha512-ttE6ayb/kHwNRJGYLpuAvB7SMtOeQnVXEIpMtAvx3kepFQeowVED0n1K9nAdraHUPJ5hydEMxBpIR7o4nrm8uA=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.36.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-4a5gf2jpS0AIe7uBjxDeUMNcFmaRTbNv7NxI5xOCs4lhzsVyGR/0qBXduPnoWf6dGC365saTiwag8hP1imTgag=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.36.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5KtoW8UWmwFKQ96aQL3LlRXX16IMwyzMq/jSSVIIyAANiE1doaQsx/KRyhAvpHlPjPiSU/AYX/8m+lQ9VToxFQ=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.36.0", "", { "os": "linux", "cpu": "x64" }, "sha512-sycrYZPrv2ag4OCvaN5js+f01eoZ2U+RmT5as8vhxiFz+kxwlHrsxOwKPSA8WyS+Wc6Epid9QeI/IkQ9NkgYyQ=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.36.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-qbqt4N7tokFwwSVlWDsjfoHgviS3n/vZ8LK0h1uLG9TYIRuUTJC88E1xb3LM2iqZ/WTqNQjYrtmtGmrmmawB6A=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.36.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-t+RY0JuRamIocMuQcfwYSOkmdX9dtkr1PbhKW42AMvaDQa+jOdpUYysroTF/nuPpAaQMWp7ye+ndlmmthieJrQ=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.36.0", "", { "os": "win32", "cpu": "x64" }, "sha512-aRXd7tRZkWLqGbChgcMMDEHjOKudo1kChb1Jt1IfR8cY/KIpgNviLeJy5FUb9IpSuQj8dU2fAYNMPW/hLKOSTw=="],
"@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/lodash": ["@types/lodash@4.17.16", "", {}, "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g=="],
"@types/lodash-es": ["@types/lodash-es@4.17.12", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ=="],
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.16", "", {}, "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.26.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.26.1", "@typescript-eslint/type-utils": "8.26.1", "@typescript-eslint/utils": "8.26.1", "@typescript-eslint/visitor-keys": "8.26.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.26.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.26.1", "@typescript-eslint/types": "8.26.1", "@typescript-eslint/typescript-estree": "8.26.1", "@typescript-eslint/visitor-keys": "8.26.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.26.1", "", { "dependencies": { "@typescript-eslint/types": "8.26.1", "@typescript-eslint/visitor-keys": "8.26.1" } }, "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.26.1", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.26.1", "@typescript-eslint/utils": "8.26.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.26.1", "", {}, "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.26.1", "", { "dependencies": { "@typescript-eslint/types": "8.26.1", "@typescript-eslint/visitor-keys": "8.26.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.26.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.26.1", "@typescript-eslint/types": "8.26.1", "@typescript-eslint/typescript-estree": "8.26.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.26.1", "", { "dependencies": { "@typescript-eslint/types": "8.26.1", "eslint-visitor-keys": "^4.2.0" } }, "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg=="],
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@5.2.3", "", { "peerDependencies": { "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg=="],
"@volar/language-core": ["@volar/language-core@2.4.12", "", { "dependencies": { "@volar/source-map": "2.4.12" } }, "sha512-RLrFdXEaQBWfSnYGVxvR2WrO6Bub0unkdHYIdC31HzIEqATIuuhRRzYu76iGPZ6OtA4Au1SnW0ZwIqPP217YhA=="],
"@volar/source-map": ["@volar/source-map@2.4.12", "", {}, "sha512-bUFIKvn2U0AWojOaqf63ER0N/iHIBYZPpNGogfLPQ68F5Eet6FnLlyho7BS0y2HJ1jFhSif7AcuTx1TqsCzRzw=="],
"@volar/typescript": ["@volar/typescript@2.4.12", "", { "dependencies": { "@volar/language-core": "2.4.12", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-HJB73OTJDgPc80K30wxi3if4fSsZZAOScbj2fcicMuOPoOkcf9NNAINb33o+DzhBdF9xTKC1gnPmIRDous5S0g=="],
"@vue/compiler-core": ["@vue/compiler-core@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q=="],
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.13", "", { "dependencies": { "@vue/compiler-core": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA=="],
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/compiler-core": "3.5.13", "@vue/compiler-dom": "3.5.13", "@vue/compiler-ssr": "3.5.13", "@vue/shared": "3.5.13", "estree-walker": "^2.0.2", "magic-string": "^0.30.11", "postcss": "^8.4.48", "source-map-js": "^1.2.0" } }, "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ=="],
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA=="],
"@vue/compiler-vue2": ["@vue/compiler-vue2@2.7.16", "", { "dependencies": { "de-indent": "^1.0.2", "he": "^1.2.0" } }, "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A=="],
"@vue/devtools-api": ["@vue/devtools-api@7.7.6", "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.6.tgz", { "dependencies": { "@vue/devtools-kit": "^7.7.6" } }, "sha512-b2Xx0KvXZObePpXPYHvBRRJLDQn5nhKjXh7vUhMEtWxz1AYNFOVIsh5+HLP8xDGL7sy+Q7hXeUxPHB/KgbtsPw=="],
"@vue/devtools-kit": ["@vue/devtools-kit@7.7.6", "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.6.tgz", { "dependencies": { "@vue/devtools-shared": "^7.7.6", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-geu7ds7tem2Y7Wz+WgbnbZ6T5eadOvozHZ23Atk/8tksHMFOFylKi1xgGlQlVn0wlkEf4hu+vd5ctj1G4kFtwA=="],
"@vue/devtools-shared": ["@vue/devtools-shared@7.7.6", "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.6.tgz", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-yFEgJZ/WblEsojQQceuyK6FzpFDx4kqrz2ohInxNj5/DnhoX023upTv4OD6lNPLAA5LLkbwPVb10o/7b+Y4FVA=="],
"@vue/language-core": ["@vue/language-core@2.2.8", "", { "dependencies": { "@volar/language-core": "~2.4.11", "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", "alien-signals": "^1.0.3", "minimatch": "^9.0.3", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-rrzB0wPGBvcwaSNRriVWdNAbHQWSf0NlGqgKHK5mEkXpefjUlVRP62u03KvwZpvKVjRnBIQ/Lwre+Mx9N6juUQ=="],
"@vue/reactivity": ["@vue/reactivity@3.5.13", "", { "dependencies": { "@vue/shared": "3.5.13" } }, "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg=="],
"@vue/runtime-core": ["@vue/runtime-core@3.5.13", "", { "dependencies": { "@vue/reactivity": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw=="],
"@vue/runtime-dom": ["@vue/runtime-dom@3.5.13", "", { "dependencies": { "@vue/reactivity": "3.5.13", "@vue/runtime-core": "3.5.13", "@vue/shared": "3.5.13", "csstype": "^3.1.3" } }, "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog=="],
"@vue/server-renderer": ["@vue/server-renderer@3.5.13", "", { "dependencies": { "@vue/compiler-ssr": "3.5.13", "@vue/shared": "3.5.13" }, "peerDependencies": { "vue": "3.5.13" } }, "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA=="],
"@vue/shared": ["@vue/shared@3.5.13", "", {}, "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ=="],
"@vue/tsconfig": ["@vue/tsconfig@0.7.0", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg=="],
"@vueuse/core": ["@vueuse/core@9.13.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.16", "@vueuse/metadata": "9.13.0", "@vueuse/shared": "9.13.0", "vue-demi": "*" } }, "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw=="],
"@vueuse/metadata": ["@vueuse/metadata@9.13.0", "", {}, "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ=="],
"@vueuse/shared": ["@vueuse/shared@9.13.0", "", { "dependencies": { "vue-demi": "*" } }, "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw=="],
"acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"alien-signals": ["alien-signals@1.0.4", "", {}, "sha512-DJqqQD3XcsaQcQ1s+iE2jDUZmmQpXwHiR6fCAim/w87luaW+vmLY8fMlrdkmRwzaFXhkxf3rqPCR59tKVv1MDw=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"async-validator": ["async-validator@4.2.5", "", {}, "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"axios": ["axios@1.8.4", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"birpc": ["birpc@2.3.0", "https://registry.npmmirror.com/birpc/-/birpc-2.3.0.tgz", {}, "sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g=="],
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
"brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"buffer-builder": ["buffer-builder@0.2.0", "https://registry.npmmirror.com/buffer-builder/-/buffer-builder-0.2.0.tgz", {}, "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"colorjs.io": ["colorjs.io@0.5.2", "https://registry.npmmirror.com/colorjs.io/-/colorjs.io-0.5.2.tgz", {}, "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"copy-anything": ["copy-anything@3.0.5", "https://registry.npmmirror.com/copy-anything/-/copy-anything-3.0.5.tgz", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"dayjs": ["dayjs@1.11.13", "", {}, "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="],
"de-indent": ["de-indent@1.0.2", "", {}, "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg=="],
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"echarts": ["echarts@5.6.0", "", { "dependencies": { "tslib": "2.3.0", "zrender": "5.6.1" } }, "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA=="],
"element-plus": ["element-plus@2.9.7", "", { "dependencies": { "@ctrl/tinycolor": "^3.4.1", "@element-plus/icons-vue": "^2.3.1", "@floating-ui/dom": "^1.0.1", "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", "@types/lodash": "^4.14.182", "@types/lodash-es": "^4.17.6", "@vueuse/core": "^9.1.0", "async-validator": "^4.2.5", "dayjs": "^1.11.13", "escape-html": "^1.0.3", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "lodash-unified": "^1.0.2", "memoize-one": "^6.0.0", "normalize-wheel-es": "^1.2.0" }, "peerDependencies": { "vue": "^3.2.0" } }, "sha512-6vjZh5SXBncLhUwJGTVKS5oDljfgGMh6J4zVTeAZK3YdMUN76FgpvHkwwFXocpJpMbii6rDYU3sgie64FyPerQ=="],
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
"esbuild": ["esbuild@0.25.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.1", "@esbuild/android-arm": "0.25.1", "@esbuild/android-arm64": "0.25.1", "@esbuild/android-x64": "0.25.1", "@esbuild/darwin-arm64": "0.25.1", "@esbuild/darwin-x64": "0.25.1", "@esbuild/freebsd-arm64": "0.25.1", "@esbuild/freebsd-x64": "0.25.1", "@esbuild/linux-arm": "0.25.1", "@esbuild/linux-arm64": "0.25.1", "@esbuild/linux-ia32": "0.25.1", "@esbuild/linux-loong64": "0.25.1", "@esbuild/linux-mips64el": "0.25.1", "@esbuild/linux-ppc64": "0.25.1", "@esbuild/linux-riscv64": "0.25.1", "@esbuild/linux-s390x": "0.25.1", "@esbuild/linux-x64": "0.25.1", "@esbuild/netbsd-arm64": "0.25.1", "@esbuild/netbsd-x64": "0.25.1", "@esbuild/openbsd-arm64": "0.25.1", "@esbuild/openbsd-x64": "0.25.1", "@esbuild/sunos-x64": "0.25.1", "@esbuild/win32-arm64": "0.25.1", "@esbuild/win32-ia32": "0.25.1", "@esbuild/win32-x64": "0.25.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ=="],
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"eslint": ["eslint@9.22.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.2", "@eslint/config-helpers": "^0.1.0", "@eslint/core": "^0.12.0", "@eslint/eslintrc": "^3.3.0", "@eslint/js": "9.22.0", "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ=="],
"eslint-config-prettier": ["eslint-config-prettier@10.1.1", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw=="],
"eslint-plugin-vue": ["eslint-plugin-vue@10.0.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^6.0.15", "semver": "^7.6.3", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "vue-eslint-parser": "^10.0.0" } }, "sha512-XKckedtajqwmaX6u1VnECmZ6xJt+YvlmMzBPZd+/sI3ub2lpYZyFnsyWo7c3nMOQKJQudeyk1lw/JxdgeKT64w=="],
"eslint-scope": ["eslint-scope@8.3.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ=="],
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
"espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="],
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-glob": ["fast-glob@3.3.3", "", { "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" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
"follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="],
"form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"globals": ["globals@16.0.0", "", {}, "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
"hookable": ["hookable@5.5.3", "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"immutable": ["immutable@5.1.1", "https://registry.npmmirror.com/immutable/-/immutable-5.1.1.tgz", {}, "sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-what": ["is-what@4.1.16", "https://registry.npmmirror.com/is-what/-/is-what-4.1.16.tgz", {}, "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
"lodash-unified": ["lodash-unified@1.0.3", "", { "peerDependencies": { "@types/lodash-es": "*", "lodash": "*", "lodash-es": "*" } }, "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ=="],
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"mitt": ["mitt@3.0.1", "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
"nanoid": ["nanoid@3.3.10", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-vSJJTG+t/dIKAUhUDw/dLdZ9s//5OxcHqLaDWWrW4Cdq7o6tdLIczUkMXt2MBNmk6sJRZBZRXVixs7URY1CmIg=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
"normalize-wheel-es": ["normalize-wheel-es@1.2.0", "", {}, "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw=="],
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"perfect-debounce": ["perfect-debounce@1.0.0", "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"pinia": ["pinia@3.0.2", "https://registry.npmmirror.com/pinia/-/pinia-3.0.2.tgz", { "dependencies": { "@vue/devtools-api": "^7.7.2" }, "peerDependencies": { "typescript": ">=4.4.4", "vue": "^2.7.0 || ^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-sH2JK3wNY809JOeiiURUR0wehJ9/gd9qFN2Y828jCbxEzKEmEt0pzCXwqiSTfuRsK9vQsOflSdnbdBOGrhtn+g=="],
"postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
"postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
"prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.6.11", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-import-sort": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-style-order": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-import-sort", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-style-order", "prettier-plugin-svelte"] }, "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA=="],
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"rfdc": ["rfdc@1.4.1", "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
"rollup": ["rollup@4.36.0", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.36.0", "@rollup/rollup-android-arm64": "4.36.0", "@rollup/rollup-darwin-arm64": "4.36.0", "@rollup/rollup-darwin-x64": "4.36.0", "@rollup/rollup-freebsd-arm64": "4.36.0", "@rollup/rollup-freebsd-x64": "4.36.0", "@rollup/rollup-linux-arm-gnueabihf": "4.36.0", "@rollup/rollup-linux-arm-musleabihf": "4.36.0", "@rollup/rollup-linux-arm64-gnu": "4.36.0", "@rollup/rollup-linux-arm64-musl": "4.36.0", "@rollup/rollup-linux-loongarch64-gnu": "4.36.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.36.0", "@rollup/rollup-linux-riscv64-gnu": "4.36.0", "@rollup/rollup-linux-s390x-gnu": "4.36.0", "@rollup/rollup-linux-x64-gnu": "4.36.0", "@rollup/rollup-linux-x64-musl": "4.36.0", "@rollup/rollup-win32-arm64-msvc": "4.36.0", "@rollup/rollup-win32-ia32-msvc": "4.36.0", "@rollup/rollup-win32-x64-msvc": "4.36.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-zwATAXNQxUcd40zgtQG0ZafcRK4g004WtEl7kbuhTWPvf07PsfohXl39jVUvPF7jvNAIkKPQ2XrsDlWuxBd++Q=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"rxjs": ["rxjs@7.8.2", "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.2.tgz", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
"sass-embedded": ["sass-embedded@1.87.0", "https://registry.npmmirror.com/sass-embedded/-/sass-embedded-1.87.0.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.0.0", "buffer-builder": "^0.2.0", "colorjs.io": "^0.5.0", "immutable": "^5.0.2", "rxjs": "^7.4.0", "supports-color": "^8.1.1", "sync-child-process": "^1.0.2", "varint": "^6.0.0" }, "optionalDependencies": { "sass-embedded-android-arm": "1.87.0", "sass-embedded-android-arm64": "1.87.0", "sass-embedded-android-ia32": "1.87.0", "sass-embedded-android-riscv64": "1.87.0", "sass-embedded-android-x64": "1.87.0", "sass-embedded-darwin-arm64": "1.87.0", "sass-embedded-darwin-x64": "1.87.0", "sass-embedded-linux-arm": "1.87.0", "sass-embedded-linux-arm64": "1.87.0", "sass-embedded-linux-ia32": "1.87.0", "sass-embedded-linux-musl-arm": "1.87.0", "sass-embedded-linux-musl-arm64": "1.87.0", "sass-embedded-linux-musl-ia32": "1.87.0", "sass-embedded-linux-musl-riscv64": "1.87.0", "sass-embedded-linux-musl-x64": "1.87.0", "sass-embedded-linux-riscv64": "1.87.0", "sass-embedded-linux-x64": "1.87.0", "sass-embedded-win32-arm64": "1.87.0", "sass-embedded-win32-ia32": "1.87.0", "sass-embedded-win32-x64": "1.87.0" }, "bin": { "sass": "dist/bin/sass.js" } }, "sha512-1IA3iTJNh4BkkA/nidKiVwbmkxr9o6LsPegycHMX/JYs255zpocN5GdLF1+onohQCJxbs5ldr8osKV7qNaNBjg=="],
"sass-embedded-android-arm": ["sass-embedded-android-arm@1.87.0", "https://registry.npmmirror.com/sass-embedded-android-arm/-/sass-embedded-android-arm-1.87.0.tgz", { "os": "android", "cpu": "arm" }, "sha512-Z20u/Y1kFDpMbgiloR5YPLxNuMVeKQRC8e/n68oAAxf3u7rDSmNn2msi7USqgT1f2zdBBNawn/ifbFEla6JiHw=="],
"sass-embedded-android-arm64": ["sass-embedded-android-arm64@1.87.0", "https://registry.npmmirror.com/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.87.0.tgz", { "os": "android", "cpu": "arm64" }, "sha512-uqeZoBuXm3W2KhxolScAAfWOLHL21e50g7AxlLmG0he7WZsWw6e9kSnmq301iLIFp4kvmXYXbXbNKAeu9ItRYA=="],
"sass-embedded-android-ia32": ["sass-embedded-android-ia32@1.87.0", "https://registry.npmmirror.com/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.87.0.tgz", { "os": "android", "cpu": "ia32" }, "sha512-hSWTqo2Igdig528cUb1W1+emw9d1J4+nqOoR4tERS04zcwRRFNDiuBT0o5meV7nkEwE982F+h57YdcRXj8gTtg=="],
"sass-embedded-android-riscv64": ["sass-embedded-android-riscv64@1.87.0", "https://registry.npmmirror.com/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.87.0.tgz", { "os": "android", "cpu": "none" }, "sha512-kBAPSjiTBLy5ua/0LRNAJwOAARhzFU7gP35fYORJcdBuz1lkIVPVnid1lh9qQ6Ce9MOJcr7VKFtGnTuqVeig5A=="],
"sass-embedded-android-x64": ["sass-embedded-android-x64@1.87.0", "https://registry.npmmirror.com/sass-embedded-android-x64/-/sass-embedded-android-x64-1.87.0.tgz", { "os": "android", "cpu": "x64" }, "sha512-ZHMrNdtdMSpJUYco2MesnlPwDTZftD3pqkkOMI2pbqarPoFUKJtP5k80nwCM0sJGtqfNE+O16w9yPght0CMiJg=="],
"sass-embedded-darwin-arm64": ["sass-embedded-darwin-arm64@1.87.0", "https://registry.npmmirror.com/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.87.0.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-7TK1JWJdCIRSdZv5CJv/HpDz/wIfwUy2FoPz9sVOEj1pDTH0N+VfJd5VutCddIdoQN9jr0ap8vwkc65FbAxV2A=="],
"sass-embedded-darwin-x64": ["sass-embedded-darwin-x64@1.87.0", "https://registry.npmmirror.com/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.87.0.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-2JiQzt7FmgUC4MYT2QvbeH/Bi3e76WEhaYoc5P3WyTW8unsHksyTdMuTuYe0Qf9usIyt6bmm5no/4BBw7c8Cig=="],
"sass-embedded-linux-arm": ["sass-embedded-linux-arm@1.87.0", "https://registry.npmmirror.com/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.87.0.tgz", { "os": "linux", "cpu": "arm" }, "sha512-z5P6INMsGXiUcq1sRRbksyQUhalFFYjTEexuxfSYdK3U2YQMADHubQh8pGzkWvFRPOpnh83RiGuwvpaARYHnsw=="],
"sass-embedded-linux-arm64": ["sass-embedded-linux-arm64@1.87.0", "https://registry.npmmirror.com/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.87.0.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-5z+mwJCbGZcg+q+MwdEVSh0ogFK7OSAe175Gsozzr/Izw34Q+RGUw9O82jsV2c4YNuTAQvzEHgIO5cvNvt3Quw=="],
"sass-embedded-linux-ia32": ["sass-embedded-linux-ia32@1.87.0", "https://registry.npmmirror.com/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.87.0.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-Xzcp+YPp0iakGL148Jl57CO+MxLuj2jsry3M+rc1cSnDlvkjNVs6TMxaL70GFeV5HdU2V60voYcgE7adDUtJjw=="],
"sass-embedded-linux-musl-arm": ["sass-embedded-linux-musl-arm@1.87.0", "https://registry.npmmirror.com/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.87.0.tgz", { "os": "linux", "cpu": "arm" }, "sha512-4PyqOWhRzyu06RRmpCCBOJdF4BOv7s446wrV6yODtEyyfSIDx3MJabo3KT0oJ1lTWSI/aU3R89bKx0JFXcIHHw=="],
"sass-embedded-linux-musl-arm64": ["sass-embedded-linux-musl-arm64@1.87.0", "https://registry.npmmirror.com/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.87.0.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-HWE5eTRCoKzFZWsxOjDMTF5m4DDTQ0n7NJxSYiUXPBDydr9viPXbGOMYG7WVJLjiF7upr7DYo/mfp/SNTMlZyg=="],
"sass-embedded-linux-musl-ia32": ["sass-embedded-linux-musl-ia32@1.87.0", "https://registry.npmmirror.com/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.87.0.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-aQaPvlRn3kh93PLQvl6BcFKu8Ji92+42blFEkg6nMVvmugD5ZwH2TGFrX25ibx4CYxRpMS4ssF7a0i7vy5HB1Q=="],
"sass-embedded-linux-musl-riscv64": ["sass-embedded-linux-musl-riscv64@1.87.0", "https://registry.npmmirror.com/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.87.0.tgz", { "os": "linux", "cpu": "none" }, "sha512-o5DxcqiFzET3KRWo+futHr/lhAMBP3tJGGx8YIgpHQYfvDMbsvE0hiFC+nZ/GF9dbcGd+ceIQwfvE5mcc7Gsjw=="],
"sass-embedded-linux-musl-x64": ["sass-embedded-linux-musl-x64@1.87.0", "https://registry.npmmirror.com/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.87.0.tgz", { "os": "linux", "cpu": "x64" }, "sha512-dKxWsu9Wu/CyfzQmHdeiGqrRSzJ85VUjbSx+aP1/7ttmps3SSg+YW95PuqnCOa7GSuSreC3dKKpXHTywUxMLQA=="],
"sass-embedded-linux-riscv64": ["sass-embedded-linux-riscv64@1.87.0", "https://registry.npmmirror.com/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.87.0.tgz", { "os": "linux", "cpu": "none" }, "sha512-Sy3ESZ4FwBiijvmTA9n+0p0w3MNCue1AgINVPzpAY27EFi0h49eqQm9SWfOkFqmkFS2zFRYowdQOr5Bbr2gOXA=="],
"sass-embedded-linux-x64": ["sass-embedded-linux-x64@1.87.0", "https://registry.npmmirror.com/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.87.0.tgz", { "os": "linux", "cpu": "x64" }, "sha512-+UfjakOcHHKTnEqB3EZ+KqzezQOe1emvy4Rs+eQhLyfekpYuNze/qlRvYxfKTmrtvDiUrIto8MXsyZfMLzkuMA=="],
"sass-embedded-win32-arm64": ["sass-embedded-win32-arm64@1.87.0", "https://registry.npmmirror.com/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.87.0.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-m1DS6FYUE0/fv+vt38uQB/kxR4UjnyD+2zcSc298pFmA0aYh/XZIPWw7RxG1HL3KLE1ZrGyu3254MPoxRhs3ig=="],
"sass-embedded-win32-ia32": ["sass-embedded-win32-ia32@1.87.0", "https://registry.npmmirror.com/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.87.0.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-JztXLo59GMe2E6g+kCsyiERYhtZgkcyDYx6CrXoSTE5WaE+RbxRiCCCv8/1+hf406f08pUxJ8G0Ody7M5urtBA=="],
"sass-embedded-win32-x64": ["sass-embedded-win32-x64@1.87.0", "https://registry.npmmirror.com/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.87.0.tgz", { "os": "win32", "cpu": "x64" }, "sha512-4nQErpauvhgSo+7ClumGdjdf9sGx+U9yBgvhI0+zUw+D5YvraVgvA0Lk8Wuwntx2PqnvKUk8YDr/vxHJostv4Q=="],
"semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"speakingurl": ["speakingurl@14.0.1", "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"superjson": ["superjson@2.2.2", "https://registry.npmmirror.com/superjson/-/superjson-2.2.2.tgz", { "dependencies": { "copy-anything": "^3.0.2" } }, "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q=="],
"supports-color": ["supports-color@8.1.1", "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
"sync-child-process": ["sync-child-process@1.0.2", "https://registry.npmmirror.com/sync-child-process/-/sync-child-process-1.0.2.tgz", { "dependencies": { "sync-message-port": "^1.0.0" } }, "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA=="],
"sync-message-port": ["sync-message-port@1.1.3", "https://registry.npmmirror.com/sync-message-port/-/sync-message-port-1.1.3.tgz", {}, "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"ts-api-utils": ["ts-api-utils@2.0.1", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w=="],
"tslib": ["tslib@2.3.0", "", {}, "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"varint": ["varint@6.0.0", "https://registry.npmmirror.com/varint/-/varint-6.0.0.tgz", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="],
"vite": ["vite@6.2.2", "", { "dependencies": { "esbuild": "^0.25.0", "postcss": "^8.5.3", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ=="],
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
"vue": ["vue@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/compiler-sfc": "3.5.13", "@vue/runtime-dom": "3.5.13", "@vue/server-renderer": "3.5.13", "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ=="],
"vue-demi": ["vue-demi@0.14.10", "", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="],
"vue-eslint-parser": ["vue-eslint-parser@9.4.3", "", { "dependencies": { "debug": "^4.3.4", "eslint-scope": "^7.1.1", "eslint-visitor-keys": "^3.3.0", "espree": "^9.3.1", "esquery": "^1.4.0", "lodash": "^4.17.21", "semver": "^7.3.6" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg=="],
"vue-router": ["vue-router@4.5.0", "", { "dependencies": { "@vue/devtools-api": "^6.6.4" }, "peerDependencies": { "vue": "^3.2.0" } }, "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w=="],
"vue-tsc": ["vue-tsc@2.2.8", "", { "dependencies": { "@volar/typescript": "~2.4.11", "@vue/language-core": "2.2.8" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "./bin/vue-tsc.js" } }, "sha512-jBYKBNFADTN+L+MdesNX/TB3XuDSyaWynKMDgR+yCSln0GQ9Tfb7JS2lr46s2LiFUT1WsmfWsSvIElyxzOPqcQ=="],
"vue3-slide-verify": ["vue3-slide-verify@1.1.7", "", { "dependencies": { "vue": "^3.2.25" } }, "sha512-EP1Ddr5N8wiF5HIEeF6+pKyu3jqWdBkhM0tB1gr/qU6J40IgklcqBBg9YAVaHv7uV4OoRmE5hlhrobPZArvzkw=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"xml-name-validator": ["xml-name-validator@4.0.0", "", {}, "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="],
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"zrender": ["zrender@5.6.1", "", { "dependencies": { "tslib": "2.3.0" } }, "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@vue/language-core/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"vue-eslint-parser/eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="],
"vue-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"vue-eslint-parser/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="],
"vue-router/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"@vue/language-core/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
}
}

@ -0,0 +1,266 @@
# 故障管理 API 文档
## 接口说明
本文档描述了数字灯网AI平台故障管理相关的 API 接口。所有接口都遵循 JSON-RPC 2.0 规范。
## 基础信息
- 基础URL: `/api`
- 请求方式: POST
- 数据格式: JSON
- 认证方式: Token (在请求头中携带)
## 通用响应格式
```json
{
"jsonrpc": "2.0",
"result": {
// 具体数据
},
"error": {
"code": 200,
"message": "success"
}
}
```
## 错误码说明
| 错误码 | 说明 |
|--------|------|
| 200 | 成功 |
| 400 | 请求参数错误 |
| 401 | 未授权 |
| 403 | 权限不足 |
| 404 | 资源不存在 |
| 500 | 服务器内部错误 |
## API 列表
### 1. 获取故障列表
#### 请求
```json
{
"jsonrpc": "2.0",
"method": "fault.list",
"params": {
"page": 1,
"pageSize": 10,
"status": "all", // all, pending, processing, resolved
"startTime": "2024-04-01 00:00:00",
"endTime": "2024-04-26 23:59:59",
"deviceId": "optional_device_id"
}
}
```
#### 响应
```json
{
"jsonrpc": "2.0",
"result": {
"total": 100,
"items": [
{
"id": "fault_001",
"deviceId": "device_001",
"deviceName": "路灯-001",
"type": "power_failure",
"level": "high",
"status": "pending",
"description": "设备断电",
"location": {
"longitude": 120.123456,
"latitude": 30.123456
},
"createTime": "2024-04-26 10:00:00",
"updateTime": "2024-04-26 10:00:00"
}
]
},
"error": {
"code": 200,
"message": "success"
}
}
```
### 2. 获取故障详情
#### 请求
```json
{
"jsonrpc": "2.0",
"method": "fault.detail",
"params": {
"id": "fault_001"
}
}
```
#### 响应
```json
{
"jsonrpc": "2.0",
"result": {
"id": "fault_001",
"deviceId": "device_001",
"deviceName": "路灯-001",
"type": "power_failure",
"level": "high",
"status": "pending",
"description": "设备断电",
"location": {
"longitude": 120.123456,
"latitude": 30.123456
},
"createTime": "2024-04-26 10:00:00",
"updateTime": "2024-04-26 10:00:00",
"history": [
{
"time": "2024-04-26 10:00:00",
"action": "create",
"operator": "system",
"comment": "系统自动检测到故障"
}
]
},
"error": {
"code": 200,
"message": "success"
}
}
```
### 3. 更新故障状态
#### 请求
```json
{
"jsonrpc": "2.0",
"method": "fault.update",
"params": {
"id": "fault_001",
"status": "processing",
"comment": "开始处理故障"
}
}
```
#### 响应
```json
{
"jsonrpc": "2.0",
"result": {
"id": "fault_001",
"status": "processing",
"updateTime": "2024-04-26 10:30:00"
},
"error": {
"code": 200,
"message": "success"
}
}
```
### 4. 获取故障统计
#### 请求
```json
{
"jsonrpc": "2.0",
"method": "fault.statistics",
"params": {
"startTime": "2024-04-01 00:00:00",
"endTime": "2024-04-26 23:59:59",
"type": "all" // all, daily, weekly, monthly
}
}
```
#### 响应
```json
{
"jsonrpc": "2.0",
"result": {
"total": 100,
"pending": 20,
"processing": 30,
"resolved": 50,
"byType": {
"power_failure": 40,
"communication_error": 30,
"hardware_failure": 20,
"other": 10
},
"byLevel": {
"high": 30,
"medium": 50,
"low": 20
},
"trend": [
{
"date": "2024-04-26",
"count": 10
}
]
},
"error": {
"code": 200,
"message": "success"
}
}
```
## 故障类型说明
| 类型 | 说明 |
|------|------|
| power_failure | 电源故障 |
| communication_error | 通信故障 |
| hardware_failure | 硬件故障 |
| sensor_error | 传感器故障 |
| other | 其他故障 |
## 故障等级说明
| 等级 | 说明 |
|------|------|
| high | 高优先级 |
| medium | 中优先级 |
| low | 低优先级 |
## 故障状态说明
| 状态 | 说明 |
|------|------|
| pending | 待处理 |
| processing | 处理中 |
| resolved | 已解决 |
| closed | 已关闭 |
## 注意事项
1. 所有时间字段使用 ISO 8601 格式
2. 分页参数 page 从 1 开始
3. 坐标使用 WGS84 坐标系
4. 故障 ID 格式为 "fault_" + 6位数字
5. 设备 ID 格式为 "device_" + 6位数字
## 更新日志
### v1.0.0 (2024-04-26)
- 初始版本发布
- 实现基础故障管理功能
- 添加故障统计功能

@ -0,0 +1,325 @@
# 维护耗材模块接口文档
## 创建耗材项接口
#### 消息协议
- JSONRPC-2.0
#### Method
- /material/create
#### 请求参数示例:
```json
{
"jsonrpc": "2.0",
"method": "/material/create",
"params": {
"title": "LED灯泡",
"type": "照明设备",
"unit": "个",
"quantity": 2,
"price": 50.00
},
"session": "viFuv4G2XJMxO4GIIsVfbJ2eKwy6QyuWLXmqZY69dy0G9iTY",
"timestampin": "1706325301436"
}
```
#### 参数说明:
| 参数名 | 类型 | 说明 | 必填 | 备注 |
|-------------|---------|-----------------------|------|-------------------------------|
| title | string | 耗材名称 | 是 | 最大长度50字符 |
| type | string | 耗材类型 | 是 | 照明设备/网络设备/其他 |
| unit | string | 单位 | 是 | 个/件/米/套等 |
| quantity | int | 数量 | 是 | 必须大于0 |
| price | float64 | 单价 | 是 | 必须大于等于0 |
#### 成功响应示例:
```json
{
"jsonrpc": "2.0",
"result": {
"id": 1,
"title": "LED灯泡",
"type": "照明设备",
"unit": "个",
"quantity": 2,
"price": 50.00,
"total_price": 100.00,
"created_at": "2024-03-20T10:00:00Z"
},
"error": {
"message": "ok",
"code": 200
}
}
```
#### 错误响应示例:
```json
{
"jsonrpc": "2.0",
"result": null,
"error": {
"message": "数量必须大于0",
"code": 400,
"data": {
"field": "quantity"
}
}
}
```
## 获取耗材项详情接口
#### Method
- /material/detail
#### 请求参数示例:
```json
{
"jsonrpc": "2.0",
"method": "/material/detail",
"params": {
"id": 1
},
"session": "viFuv4G2XJMxO4GIIsVfbJ2eKwy6QyuWLXmqZY69dy0G9iTY",
"timestampin": "1706325301436"
}
```
#### 参数说明:
| 参数名 | 类型 | 说明 | 必填 | 备注 |
|-------------|---------|-----------------------|------|-------------------------------|
| id | uint | 耗材项ID | 是 | |
#### 成功响应示例:
```json
{
"jsonrpc": "2.0",
"result": {
"id": 1,
"title": "LED灯泡",
"type": "照明设备",
"unit": "个",
"quantity": 2,
"price": 50.00,
"total_price": 100.00,
"created_at": "2024-03-20T10:00:00Z"
},
"error": {
"message": "ok",
"code": 200
}
}
```
#### 错误响应示例:
```json
{
"jsonrpc": "2.0",
"result": null,
"error": {
"message": "耗材项不存在",
"code": 400,
"data": {
"field": "id"
}
}
}
```
## 更新耗材项接口
#### Method
- /material/update
#### 请求参数示例:
```json
{
"jsonrpc": "2.0",
"method": "/material/update",
"params": {
"id": 1,
"quantity": 3,
"price": 55.00
},
"session": "viFuv4G2XJMxO4GIIsVfbJ2eKwy6QyuWLXmqZY69dy0G9iTY",
"timestampin": "1706325301436"
}
```
#### 参数说明:
| 参数名 | 类型 | 说明 | 必填 | 备注 |
|-------------|---------|-----------------------|------|-------------------------------|
| id | uint | 耗材项ID | 是 | |
| title | string | 耗材名称 | 否 | 最大长度50字符 |
| type | string | 耗材类型 | 否 | 照明设备/网络设备/其他 |
| unit | string | 单位 | 否 | 个/件/米/套等 |
| quantity | int | 数量 | 否 | 必须大于0 |
| price | float64 | 单价 | 否 | 必须大于等于0 |
#### 成功响应示例:
```json
{
"jsonrpc": "2.0",
"result": {
"id": 1,
"title": "LED灯泡",
"type": "照明设备",
"unit": "个",
"quantity": 3,
"price": 55.00,
"total_price": 165.00,
"created_at": "2024-03-20T10:00:00Z"
},
"error": {
"message": "ok",
"code": 200
}
}
```
#### 错误响应示例:
```json
{
"jsonrpc": "2.0",
"result": null,
"error": {
"message": "耗材项不存在",
"code": 400,
"data": {
"field": "id"
}
}
}
```
## 删除耗材项接口
#### Method
- /material/delete
#### 请求参数示例:
```json
{
"jsonrpc": "2.0",
"method": "/material/delete",
"params": {
"id": 1
},
"session": "viFuv4G2XJMxO4GIIsVfbJ2eKwy6QyuWLXmqZY69dy0G9iTY",
"timestampin": "1706325301436"
}
```
#### 参数说明:
| 参数名 | 类型 | 说明 | 必填 | 备注 |
|-------------|---------|-----------------------|------|-------------------------------|
| id | uint | 耗材项ID | 是 | |
#### 成功响应示例:
```json
{
"jsonrpc": "2.0",
"result": null,
"error": {
"message": "ok",
"code": 200
}
}
```
#### 错误响应示例:
```json
{
"jsonrpc": "2.0",
"result": null,
"error": {
"message": "耗材项不存在",
"code": 400,
"data": {
"field": "id"
}
}
}
```
## 获取耗材列表接口
#### Method
- /material/list
#### 请求参数示例:
```json
{
"jsonrpc": "2.0",
"method": "/material/list",
"params": {
"page": 1,
"page_size": 20,
"type": "照明设备"
},
"session": "viFuv4G2XJMxO4GIIsVfbJ2eKwy6QyuWLXmqZY69dy0G9iTY",
"timestampin": "1706325301436"
}
```
#### 参数说明:
| 参数名 | 类型 | 说明 | 必填 | 备注 |
|-------------|---------|-----------------------|------|-------------------------------|
| page | integer | 当前页码 | 否 | 默认1 |
| page_size | integer | 每页记录数 | 否 | 默认20最大100 |
| type | string | 耗材类型 | 否 | 照明设备/网络设备/其他 |
#### 成功响应示例:
```json
{
"jsonrpc": "2.0",
"result": {
"count": 1,
"rows": [
{
"id": 1,
"title": "LED灯泡",
"type": "照明设备",
"unit": "个",
"quantity": 2,
"price": 50.00,
"total_price": 100.00,
"created_at": "2024-03-20T10:00:00Z"
}
]
},
"error": {
"message": "ok",
"code": 200
}
}
```
#### 错误响应示例:
```json
{
"jsonrpc": "2.0",
"result": null,
"error": {
"message": "无效的耗材类型",
"code": 400,
"data": {
"field": "type"
}
}
}
```
## 业务规则说明
1. 耗材名称、类型、单位、数量、单价为必填项
2. 数量必须大于0
3. 单价不能为负数
4. 总价自动计算(单价 × 数量)
5. 耗材项创建后不可修改名称
6. 耗材项删除后不可恢复
7. 列表查询支持按类型过滤
8. 分页查询默认每页20条记录最大100条

@ -0,0 +1,416 @@
# 工单模块接口文档
## 工单列表接口
#### Method
- /ticket/list
#### 请求参数示例:
```json
{
"jsonrpc": "2.0",
"method": "/ticket/list",
"params": {
"page": 1,
"page_size": 20,
"status": ["new", "in_progress"],
"start_time": "2024-03-01T00:00:00Z",
"end_time": "2024-03-15T23:59:59Z"
},
"session": "viFuv4G2XJMxO4GIIsVfbJ2eKwy6QyuWLXmqZY69dy0G9iTY",
"timestampin": "1706325301436"
}
```
#### 参数说明:
| 参数名 | 类型 | 说明 | 必填 | 备注 |
|-------------|---------|-----------------------|------|-------------------------------|
| page | integer | 当前页码 | 否 | 默认1 |
| page_size | integer | 每页记录数 | 否 | 默认20最大100 |
| status | string[]| 过滤状态列表 | 否 | 多个状态组合查询 |
| priority | string[]| 过滤优先级列表 | 否 | low/medium/high |
| start_time | string | 创建时间范围-开始时间 | 否 | RFC3339格式 |
| end_time | string | 创建时间范围-结束时间 | 否 | RFC3339格式 |
#### 成功响应示例:
```json
{
"jsonrpc": "2.0",
"result": {
"count": 15,
"rows": [
{
"id": 123,
"title": "网络故障报修",
"status": "in_progress",
"priority": "high",
"creator_id": 1001,
"created_name": "张三",
"updated_name": "李四",
"created_at": "2024-03-13T09:15:00Z",
"updated_at": "2024-03-14T10:30:00Z",
"contact_info": "13800138000",
"fault_time": "2024-03-13T09:00:00Z",
"expected_completion_time": "2024-03-13T10:00:00Z",
"handler": "王五",
"images": ["http://example.com/image1.jpg", "http://example.com/image2.jpg"],
"dispatch_time": "2024-03-13T09:10:00Z",
"process_time": "2024-03-13T09:30:00Z"
}
]
},
"error": {
"message": "ok",
"code": 200
}
}
```
#### 返回参数说明:
| 参数名 | 类型 | 说明 | 备注 |
|-------------|----------|--------------|-----------------------------|
| count | int | 总记录数 | |
| rows | object[] | 工单记录列表 | |
**rows字段说明**
| 参数名 | 类型 | 说明 | 备注 |
|---------------|---------|-----------------------|-------------------------------|
| id | int | 工单ID | |
| title | string | 工单标题 | 最大长度100字符 |
| status | string | 当前状态 | 字符串枚举值 |
| priority | string | 优先级 | low/medium/high |
| creator_id | uint | 创建人ID | 关联账户系统 |
| created_name | string | 创建人姓名 | 从账户系统获取 |
| updated_name | string | 最后修改人姓名 | 从账户系统获取 |
| created_at | string | 创建时间 | RFC3339格式 |
| updated_at | string | 最后更新时间 | RFC3339格式 |
| contact_info | string | 联系方式 | 需格式校验 |
| fault_time | string | 故障发生时间 | RFC3339格式 |
| expected_completion_time | string | 预计完成时间 | RFC3339格式 |
| handler | string | 处理人员 | 最大长度100字符 |
| images | string[]| 故障图片列表 | 图片URL数组 |
| dispatch_time | string | 派单时间 | RFC3339格式 |
| process_time | string | 处理时间 | RFC3339格式 |
#### 错误响应示例:
```json
{
"jsonrpc": "2.0",
"result": null,
"error": {
"message": "无效的时间格式",
"code": 400,
"data": {
"field": "start_time"
}
}
}
```
## 创建工单接口
#### 消息协议
- JSONRPC-2.0
#### Method
- /ticket/create
#### 请求参数示例:
```json
{
"jsonrpc": "2.0",
"method": "/ticket/create",
"params": {
"title": "网络故障报修",
"description": "<p>办公室A区网络中断</p>",
"source": "市民举报",
"priority": "high",
"status": "new",
"creator_id": 1001,
"device_id": 5001,
"contact_info": "13800138000",
"fault_time": "2024-03-13T09:00:00Z",
"expected_completion_time": "2024-03-13T10:00:00Z",
"handler": "王五",
"images": ["http://example.com/image1.jpg", "http://example.com/image2.jpg"],
"dispatch_time": "2024-03-13T09:10:00Z",
"process_time": "2024-03-13T09:30:00Z"
},
"session": "viFuv4G2XJMxO4GIIsVfbJ2eKwy6QyuWLXmqZY69dy0G9iTY",
"timestampin": "2024-03-13T09:15:00Z"
}
```
#### 参数说明:
| 参数名 | 类型 | 说明 | 必填 | 备注 |
|---------------|---------|--------------------|------|--------------------|
| title | string | 工单标题 | 是 | 最大长度100字符 |
| description | string | 工单描述(富文本) | 是 | 需进行HTML消毒处理|
| source | string | 来源渠道 | 是 | 市民举报/巡检发现/监控系统 |
| priority | string | 优先级 | 是 | low/medium/high字符串枚举 |
| status | string | 工单状态 | 是 | new/reviewed/dispatched/assigned/in_progress/completed/closed/rejected |
| creator_id | uint | 提交用户ID | 是 | 对应账户系统的用户ID |
| device_id | uint | 关联设备ID | 是 | 设备管理系统中的设备ID |
| contact_info | string | 联系方式 | 是 | 根据类型格式校验 |
| fault_time | string | 故障发生时间 | 是 | RFC3339格式 |
| expected_completion_time | string | 预计完成时间 | 否 | RFC3339格式 |
| handler | string | 处理人员 | 否 | 最大长度100字符 |
| images | string[]| 故障图片列表 | 否 | 图片URL数组 |
| dispatch_time | string | 派单时间 | 否 | RFC3339格式 |
| process_time | string | 处理时间 | 否 | RFC3339格式 |
#### 成功响应示例:
```json
{
"jsonrpc": "2.0",
"result": {
"id": 123,
"title": "网络故障报修",
"description": "<p>办公室A区网络中断</p>",
"source": "市民举报",
"priority": "high",
"creator_id": 1001,
"status": "new",
"fault_time": "2024-03-13T09:00:00Z",
"expected_completion_time": "2024-03-13T10:00:00Z",
"handler": "王五",
"images": ["http://example.com/image1.jpg", "http://example.com/image2.jpg"],
"dispatch_time": "2024-03-13T09:10:00Z",
"process_time": "2024-03-13T09:30:00Z",
"created_at": "2024-03-13T09:15:00Z",
"updated_at": "2024-03-13T09:15:00Z"
},
"error": {
"message": "ok",
"code": 200
}
}
```
#### 错误响应示例:
```json
{
"jsonrpc": "2.0",
"result": null,
"error": {
"message": "缺少必填参数: title",
"code": 400
}
}
```
#### 状态流转规则:
1. 新建(new) → 已派发(dispatched) | 已驳回(rejected)
2. 已派发(dispatched) → 已分配(assigned)
3. 已分配(assigned) → 处理中(in_progress)
4. 处理中(in_progress) → 已完成(completed)
5. 已完成(completed) → 已关闭(closed)
6. 所有状态均可变更为已关闭(closed)
## 修改工单接口
#### Method
- /ticket/update
#### 请求参数示例:
```json
{
"jsonrpc": "2.0",
"method": "/ticket/update",
"params": {
"id": 123,
"title": "更新后的故障描述",
"description": "<p>网络故障已定位到交换机</p>",
"status": "in_progress",
"priority": "medium",
"updater_id": 1002,
"fault_time": "2024-03-13T09:00:00Z",
"expected_completion_time": "2024-03-13T10:00:00Z",
"handler": "王五",
"images": ["http://example.com/image1.jpg", "http://example.com/image2.jpg"],
"dispatch_time": "2024-03-13T09:10:00Z",
"process_time": "2024-03-13T09:30:00Z"
},
"session": "viFuv4G2XJMxO4GIIsVfbJ2eKwy6QyuWLXmqZY69dy0G9iTY",
"timestampin": "1706325301436"
}
```
#### 成功响应示例:
```json
{
"jsonrpc": "2.0",
"result": {
"id": 123,
"title": "更新后的故障描述",
"description": "<p>网络故障已定位到交换机</p>",
"status": "in_progress",
"priority": "medium",
"created_name": "张三",
"updated_name": "王五",
"created_at": "2024-03-13T09:15:00Z",
"updated_at": "2024-03-14T14:20:00Z",
"contact_info": "13800138000",
"fault_time": "2024-03-13T09:00:00Z",
"expected_completion_time": "2024-03-13T10:00:00Z",
"handler": "王五",
"images": ["http://example.com/image1.jpg", "http://example.com/image2.jpg"],
"dispatch_time": "2024-03-13T09:10:00Z",
"process_time": "2024-03-13T09:30:00Z"
},
"error": {
"message": "ok",
"code": 200
}
}
```
#### 错误响应示例:
```json
{
"jsonrpc": "2.0",
"result": null,
"error": {
"message": "无效的状态变更: new → closed",
"code": 400,
"data": {
"current_status": "new",
"target_status": "closed"
}
}
}
```
## 删除工单接口
#### Method
- /ticket/delete
#### 请求参数示例:
```json
{
"jsonrpc": "2.0",
"method": "/ticket/delete",
"params": {
"id": 123,
"operator_id": 1002
},
"session": "viFuv4G2XJMxO4GIIsVfbJ2eKwy6QyuWLXmqZY69dy0G9iTY",
"timestampin": "1706325301436"
}
```
#### 参数说明:
| 参数名 | 类型 | 说明 | 必填 | 备注 |
|-------------|---------|-----------------------|------|-------------------------------|
| id | int | 工单ID | 是 | |
| operator_id | uint | 操作人ID | 是 | 需校验删除权限 |
#### 成功响应示例:
```json
{
"jsonrpc": "2.0",
"result": {
"id": 123,
"deleted_at": "2024-03-15T14:30:00Z"
},
"error": {
"message": "ok",
"code": 200
}
}
```
#### 返回参数说明:
| 参数名 | 类型 | 说明 | 备注 |
|-------------|----------|--------------|-----------------------------|
| id | int | 被删除工单ID | |
#### 错误响应示例:
```json
{
"jsonrpc": "2.0",
"result": null,
"error": {
"message": "无权删除该工单",
"code": 403,
"data": {
"required_role": "admin"
}
}
}
```
#### 特殊说明:
1. 采用软删除机制仅标记deleted_at字段
2. 需校验操作人权限:
- 工单创建人
- 系统管理员角色
3. 已关闭工单不可删除
4. 返回204状态码时表示物理删除成功
## 工单流转接口
#### Method
- /ticket/transition
#### 请求参数示例:
```json
{
"jsonrpc": "2.0",
"method": "/ticket/transition",
"params": {
"id": 123,
"status": "in_progress",
"operator_id": 1002,
"comment": "开始处理故障",
"handler": "王五"
},
"session": "viFuv4G2XJMxO4GIIsVfbJ2eKwy6QyuWLXmqZY69dy0G9iTY",
"timestampin": "1706325301436"
}
```
#### 参数说明:
| 参数名 | 类型 | 说明 | 必填 | 备注 |
|---------------|---------|--------------------|------|--------------------|
| id | int | 工单ID | 是 | |
| status | string | 目标状态 | 否 | 不指定则自动流转到下一状态 |
| operator_id | uint | 操作人ID | 否 | 需校验操作权限 |
| comment | string | 状态变更说明 | 否 | 最大长度500字符 |
| handler | string | 处理人员 | 否 | 状态变更为assigned/in_progress时必填 |
#### 自动流转规则:
1. 新建(new) → 已派发(dispatched)
2. 已派发(dispatched) → 已分配(assigned)
3. 已分配(assigned) → 处理中(in_progress)
4. 处理中(in_progress) → 已完成(completed)
5. 已完成(completed) → 已关闭(closed)
#### 状态流转规则:
1. 新建(new) → 已派发(dispatched) | 已驳回(rejected)
2. 已派发(dispatched) → 已分配(assigned)
3. 已分配(assigned) → 处理中(in_progress)
4. 处理中(in_progress) → 已完成(completed)
5. 已完成(completed) → 已关闭(closed)
6. 所有状态均可变更为已关闭(closed)
#### 状态变更条件:
1. 完成状态(completed)条件:
- 必须已分配处理人员
- 必须已开始处理(有处理时间)
2. 关闭状态(closed)条件:
- 必须处于完成状态
- 不能超过预计完成时间
#### 特殊说明:
1. 状态变更必须符合流转规则
2. 操作人必须具有相应权限
3. 状态变更为assigned或in_progress时必须指定处理人员
4. 状态变更为in_progress时系统会自动记录处理时间
5. 状态变更为dispatched时系统会自动记录派单时间
6. 每次状态变更都会记录操作日志
7. 不指定目标状态时,系统会自动流转到下一状态

@ -0,0 +1,76 @@
# 通用列表查询方法
请求参数:
```json
{
"method":"xxx/xxx/list",
"params":{
"page":1,
"limit":10,
"field":[
"id","name"
],
"and":{
"id":["1","2"],
"name":["name1","name2"]
},
"or":{
"id":["1","2"],
"name":["name1","name2"]
},
"like":{
"id":["1","2"],
"name":["name1","name2"]
},
"orderby":[
{"id":"asc"},
{"name":"desc"},
{"created_at":"desc"}
],
"where":[{
"field":"job_id",
"operator":"=",
"value":"2"
},{
"field":"departments",
"operator":"find_in_set",
"value":[1,2]
},]
}
}
```
参数说明:
- page: 分页记录默认不填为1page不能小于1小于1的数均会改写成1
- limit: 每页显示的记录数默认不填为10,当limit小于0时 会返回所有数据记录
- field: 自定义需要查询的字段,不填默认返回所有记录
- and: 与运算条件。示例中会生成如下sql查询语句: select ..... where id in(1,2) and name in ("name1","name2")
- or: 或运算条件。示例中会生成如下sql查询语句: select ..... where id in(1,2) or name in ("name1","name2")
- like: 模糊查询条件。
- orderby: 排序 根据数组顺序区分 首序、次序
- where: 自定义where条件 传入condition对象 的数组
- condition.field 指定字段
- condition.operator 操作符 例:= != > >= < <= in notin 等
<font color="red">特殊操作符: find_in_set一对多关联查询</font> 例如:
```json
{
"field": "departments",
"operator": "find_in_set",
"value": [1]
}
```
- condition.value 值
成功返回参数:
```json
{
"result":{
"count":100,
"rows":[
{},{},{}
]
}
}
```
参数说明:
- count: 总记录数
- rows: 查询出的记录数组

@ -0,0 +1,84 @@
// @ts-check
import js from "@eslint/js";
import vuePlugin from "eslint-plugin-vue";
import globals from "globals";
import * as tseslint from "@typescript-eslint/eslint-plugin";
import tseslintParser from "@typescript-eslint/parser";
import vueParser from "vue-eslint-parser";
import prettierConfig from "eslint-config-prettier";
export default [
{ ignores: ["dist"] },
js.configs.recommended,
prettierConfig,
{
files: ["**/*.{js,mjs,cjs}"],
languageOptions: {
ecmaVersion: 2020,
globals: {
...globals.browser,
...globals.node,
},
},
},
{
files: ["**/*.{ts,mts,cts}"],
languageOptions: {
ecmaVersion: 2020,
globals: {
...globals.browser,
...globals.node,
},
parser: tseslintParser,
parserOptions: {
projectService: true,
},
},
plugins: {
"@typescript-eslint": tseslint,
},
rules: {
...tseslint.configs.recommended.rules,
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/ban-ts-comment": "warn",
},
},
{
files: ["**/*.vue"],
languageOptions: {
ecmaVersion: 2020,
globals: {
...globals.browser,
...globals.node,
},
parser: vueParser,
parserOptions: {
parser: tseslintParser,
extraFileExtensions: [".vue"],
projectService: true,
},
},
plugins: {
vue: vuePlugin,
"@typescript-eslint": tseslint,
},
rules: {
// Vue specific rules
"vue/multi-word-component-names": "off",
"vue/no-v-html": "warn",
"vue/require-default-prop": "off",
"vue/max-attributes-per-line": "off",
"vue/html-indent": ["error", 2],
"vue/html-self-closing": "error",
"vue/component-name-in-template-casing": ["error", "PascalCase"],
"vue/no-unused-components": "warn",
// TypeScript
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/ban-ts-comment": "warn",
},
},
];

@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
</head>
<body>
<div id="app"></div>
<script type="text/javascript">
window._AMapSecurityConfig = {
securityJsCode: "33ae5910cb2d9ccabc9c1a2ed887181b",
};
</script>
<script src="https://webapi.amap.com/loader.js"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

@ -0,0 +1,41 @@
{
"name": "vue-vite",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 0.0.0.0",
"build": "vite build --outDir dist",
"lint": "eslint --ext .vue,.js,.cjs,.mjs,.ts,.cts,.mts . && tsc --noEmit",
"format": "prettier --write \"src/**/*.{vue,js,ts,cjs,mjs,json,css,scss,md}\"",
"preview": "vite preview"
},
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"axios": "^1.8.4",
"echarts": "^5.6.0",
"element-plus": "^2.9.7",
"pinia": "^3.0.2",
"vue": "^3.5.13",
"vue-router": "4",
"vue3-slide-verify": "^1.1.7"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
"@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.7.0",
"eslint": "^9.22.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-vue": "^10.0.0",
"globals": "^16.0.0",
"prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"sass-embedded": "^1.87.0",
"typescript": "~5.7.2",
"vite": "^6.2.0",
"vue-eslint-parser": "^9.4.2",
"vue-tsc": "^2.2.4"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- 背景圆 -->
<circle cx="16" cy="16" r="16" fill="#041236" />
<!-- 外圈 -->
<circle cx="16" cy="16" r="14" stroke="#00c3ff" stroke-width="1.5" />
<!-- 路灯杆 -->
<path d="M16 8 L16 24" stroke="#00c3ff" stroke-width="2" />
<!-- 路灯灯头 -->
<path d="M16 8 L20 12 L16 16 L12 12 Z" fill="#00c3ff" />
<!-- 发光效果 -->
<path d="M16 8 L20 12 L16 16 L12 12 Z" fill="#00c3ff" opacity="0.3" transform="scale(1.2) translate(-2.7 -2.7)" />
<!-- 科技环 -->
<circle cx="16" cy="16" r="10" stroke="#00c3ff" stroke-width="1" stroke-dasharray="2 2" />
<!-- 中心点 -->
<circle cx="16" cy="16" r="1.5" fill="#00c3ff" />
</svg>

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

@ -0,0 +1,85 @@
<template>
<router-view v-slot="{ Component }">
<component :is="Component" />
</router-view>
<!-- 全站通用Footer -->
<div class="footer">
<div class="footer-inner">
<div class="footer-left">
<span>© 2024 智慧照明系统</span>
<span class="footer-divider">|</span>
<span>技术支持上海蕊鑫信息科技有限公司</span>
<span class="footer-divider">|</span>
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" class="beian-link">沪ICP备2022011794号</a>
</div>
</div>
</div>
</template>
<script setup lang="ts">
// No imports needed
</script>
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
}
#app {
height: 100%;
}
</style>
<style scoped>
.footer {
width: 100%;
background: #0a1a2f;
color: #b0b8c7;
font-size: 14px;
padding: 18px 0 12px 0;
position: fixed;
left: 0;
bottom: 0;
z-index: 100;
border-top: 1px solid #1a2a3f;
}
.footer-inner {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
padding: 0 24px;
}
.footer-left, .footer-right {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
.footer-divider {
margin: 0 8px;
color: #3a4a5f;
}
.beian-link {
color: #b0b8c7;
text-decoration: underline;
}
.beian-link:hover {
color: #409EFF;
}
@media (max-width: 700px) {
.footer-inner {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.footer {
font-size: 12px;
padding: 12px 0 8px 0;
}
}
</style>

@ -0,0 +1,31 @@
import rpc from '../utils/rpc'
// 获取工单列表
export const fetchTicketList = (params) => {
return rpc.post('/ticket/list', params)
}
// 获取工单详情
export const getTicketDetail = (id) => {
return rpc.post(`/ticket/detail`, { id })
}
// 创建工单
export const createTicket = (params) => {
return rpc.post('/ticket/create', params)
}
// 更新工单
export const updateTicket = (params) => {
return rpc.post('/ticket/update', params)
}
// 删除工单
export const deleteTicket = (params) => {
return rpc.post('/ticket/delete', params)
}
// 工单流转
export const transitionTicket = (params) => {
return rpc.post('/ticket/transition', params)
}

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#00C3FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2L2 7l10 5 10-5-10-5z"></path>
<path d="M2 17l10 5 10-5"></path>
<path d="M2 12l10 5 10-5"></path>
</svg>

After

Width:  |  Height:  |  Size: 302 B

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#00C3FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path>
<circle cx="12" cy="13" r="4"></circle>
</svg>

After

Width:  |  Height:  |  Size: 328 B

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="#00FF00" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 16.9A5 5 0 0 0 18 7h-1.26a8 8 0 1 0-11.62 9"></path>
<path d="M13 11l-4 6h6l-4 6"></path>
</svg>

After

Width:  |  Height:  |  Size: 291 B

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#00C3FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<path d="M12 6v6l4 4"></path>
</svg>

After

Width:  |  Height:  |  Size: 259 B

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>

After

Width:  |  Height:  |  Size: 276 B

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path>
</svg>

After

Width:  |  Height:  |  Size: 250 B

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#00C3FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="7" r="4"></circle>
<path d="M6 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2"></path>
</svg>

After

Width:  |  Height:  |  Size: 286 B

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="7" height="7"></rect>
<rect x="14" y="3" width="7" height="7"></rect>
<rect x="14" y="14" width="7" height="7"></rect>
<rect x="3" y="14" width="7" height="7"></rect>
</svg>

After

Width:  |  Height:  |  Size: 384 B

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
<line x1="1" y1="1" x2="23" y2="23" stroke="rgba(255,255,255,0.5)"></line>
</svg>

After

Width:  |  Height:  |  Size: 723 B

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#00C3FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>

After

Width:  |  Height:  |  Size: 646 B

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="8" y1="6" x2="21" y2="6"></line>
<line x1="8" y1="12" x2="21" y2="12"></line>
<line x1="8" y1="18" x2="21" y2="18"></line>
<line x1="3" y1="6" x2="3.01" y2="6"></line>
<line x1="3" y1="12" x2="3.01" y2="12"></line>
<line x1="3" y1="18" x2="3.01" y2="18"></line>
</svg>

After

Width:  |  Height:  |  Size: 468 B

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#00C3FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path>
</svg>

After

Width:  |  Height:  |  Size: 357 B

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#00C3FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
<line x1="8" y1="21" x2="16" y2="21"></line>
<line x1="12" y1="17" x2="12" y2="21"></line>
</svg>

After

Width:  |  Height:  |  Size: 344 B

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>

After

Width:  |  Height:  |  Size: 287 B

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#00C3FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="6" width="16" height="12" rx="2" ry="2"></rect>
<polygon points="22 8.5 22 15.5 16 12 22 8.5"></polygon>
</svg>

After

Width:  |  Height:  |  Size: 308 B

@ -0,0 +1,242 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="title"
width="800px"
:close-on-click-modal="false"
@closed="handleClose"
>
<div class="map-search-box">
<el-input
v-model="searchKeyword"
placeholder="请输入地名搜索"
class="search-input"
@keyup.enter="handleSearch"
>
<template #append>
<el-button @click="handleSearch"></el-button>
</template>
</el-input>
</div>
<div id="mapContainer" class="map-container"></div>
<template #footer>
<el-button @click="handleCancel"></el-button>
<el-button type="primary" @click="handleConfirm"></el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, onMounted, watch, nextTick } from 'vue'
import AMapLoader from '@amap/amap-jsapi-loader'
// AMap
declare const AMap: any;
// Window
declare global {
interface Window {
_AMapSecurityConfig: {
securityJsCode: string;
};
}
}
const props = defineProps<{
modelValue: boolean
title?: string
lng?: string | number
lat?: string | number
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'confirm', location: { lng: string, lat: string }): void
}>()
const dialogVisible = ref(false)
const searchKeyword = ref('')
const map = ref<any>(null)
const marker = ref<any>(null)
const currentLocation = ref<{ lng: string, lat: string }>({
lng: props.lng?.toString() || '',
lat: props.lat?.toString() || ''
})
//
watch(() => props.modelValue, (val) => {
dialogVisible.value = val
if (val) {
nextTick(() => {
initMap()
})
}
})
//
watch(() => [props.lng, props.lat], ([newLng, newLat]) => {
if (newLng && newLat) {
currentLocation.value = {
lng: newLng.toString(),
lat: newLat.toString()
}
//
if (map.value) {
updateMarker(newLng, newLat)
}
}
}, { immediate: true })
watch(dialogVisible, (val) => {
emit('update:modelValue', val)
})
//
const initMap = async () => {
try {
window._AMapSecurityConfig = {
securityJsCode: '33ae5910cb2d9ccabc9c1a2ed887181b'
}
const AMap = await AMapLoader.load({
key: '2467ab6c994eb7d244ed68a830d35cde',
version: '2.0',
plugins: ['AMap.PlaceSearch', 'AMap.Geocoder']
} as any)
//
if (map.value) {
map.value.destroy()
map.value = null
}
map.value = new AMap.Map('mapContainer', {
zoom: 15,
center: [currentLocation.value.lng || 121.4737, currentLocation.value.lat || 31.23037],
viewMode: '3D',
mapStyle: 'amap://styles/dark',
features: ['bg', 'building', 'point'],
buildingAnimation: true,
pitch: 50,
skyColor: '#1E1E1E',
})
//
map.value.on('click', (e: any) => {
const { lng, lat } = e.lnglat
updateMarker(lng, lat)
//
currentLocation.value = {
lng: lng.toString(),
lat: lat.toString()
}
})
//
map.value.on('complete', () => {
if (currentLocation.value.lng && currentLocation.value.lat) {
updateMarker(currentLocation.value.lng, currentLocation.value.lat)
}
})
} catch (error) {
console.error('地图初始化失败:', error)
}
}
//
const updateMarker = (lng: number | string, lat: number | string) => {
if (!map.value) return
//
if (marker.value) {
marker.value.setMap(null)
marker.value = null
}
//
marker.value = new AMap.Marker({
position: [lng, lat],
map: map.value,
animation: 'AMAP_ANIMATION_DROP'
})
//
currentLocation.value = {
lng: lng.toString(),
lat: lat.toString()
}
//
map.value.setCenter([lng, lat])
}
//
const handleSearch = async () => {
if (!searchKeyword.value || !map.value) return
try {
const placeSearch = new AMap.PlaceSearch({
city: '全国'
})
placeSearch.search(searchKeyword.value, (status: string, result: any) => {
if (status === 'complete' && result.poiList?.pois?.length > 0) {
const poi = result.poiList.pois[0]
const { location } = poi
map.value.setCenter([location.lng, location.lat])
updateMarker(location.lng, location.lat)
}
})
} catch (error) {
console.error('搜索失败:', error)
}
}
//
const handleConfirm = () => {
emit('confirm', currentLocation.value)
dialogVisible.value = false
}
//
const handleCancel = () => {
dialogVisible.value = false
}
//
const handleClose = () => {
if (map.value) {
map.value.destroy()
map.value = null
}
if (marker.value) {
marker.value = null
}
}
</script>
<style scoped>
.map-container {
width: 100%;
height: 400px;
margin: 16px 0;
}
.map-search-box {
margin-bottom: 16px;
}
.search-input {
width: 100%;
}
:deep(.el-input-group__append) {
padding: 0;
}
:deep(.el-input-group__append .el-button) {
margin: 0;
border: none;
height: 100%;
padding: 0 15px;
}
</style>

@ -0,0 +1,156 @@
<template>
<div class="module-icon-wrapper" :class="{ active }" @click="$emit('click')">
<div class="module-icon">
<div class="hexagon">
<div class="hexagon-inner">
<div class="icon" :class="icon" />
</div>
</div>
<div class="title">{{ title }}</div>
</div>
<div class="platform" />
</div>
</template>
<script setup lang="ts">
defineProps<{
title: string;
icon: string;
active: boolean;
}>();
defineEmits(['click']);
</script>
<style scoped>
.module-icon-wrapper {
display: flex;
flex-direction: column;
align-items: center;
margin: 0 15px;
transform-origin: center bottom;
transition: transform 0.3s ease;
position: relative;
cursor: pointer;
}
.active {
transform: scale(1.15);
z-index: 2;
}
.module-icon {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 10px;
}
.hexagon {
width: 80px;
height: 90px;
background: linear-gradient(135deg, #0A3677 0%, #041A47 100%);
position: relative;
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 15px;
transition: box-shadow 0.3s ease, transform 0.3s ease;
border: 1px solid #0066ff;
}
.active .hexagon {
box-shadow: 0 0 20px rgba(0, 195, 255, 0.8);
}
.hexagon::before {
content: '';
position: absolute;
width: calc(100% - 4px);
height: calc(100% - 4px);
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
background: linear-gradient(135deg, #0c3b7e 0%, #062052 100%);
z-index: 1;
}
.hexagon-inner {
position: relative;
z-index: 2;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.icon {
width: 40px;
height: 40px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
filter: drop-shadow(0 0 5px rgba(0, 195, 255, 0.8));
}
.title {
color: white;
font-size: 13px;
text-align: center;
margin-top: 5px;
text-shadow: 0 0 5px rgba(0, 195, 255, 0.5);
}
.platform {
width: 100px;
height: 10px;
background: radial-gradient(ellipse at center, rgba(0, 102, 255, 0.5) 0%, transparent 70%);
border-radius: 50%;
margin-top: -5px;
opacity: 0;
transition: opacity 0.3s ease;
}
.active .platform {
opacity: 1;
}
/* Hover effects */
.module-icon-wrapper:hover:not(.active) .hexagon {
box-shadow: 0 0 10px rgba(0, 195, 255, 0.4);
transform: translateY(-5px);
}
.module-icon-wrapper:hover:not(.active) .platform {
opacity: 0.3;
}
/* Icon styles */
.camera {
background-image: url('../assets/camera-icon.svg');
}
.video {
background-image: url('../assets/video-icon.svg');
}
.chart {
background-image: url('../assets/chart-icon.svg');
}
.screen {
background-image: url('../assets/screen-icon.svg');
}
.asset {
background-image: url('../assets/asset-icon.svg');
}
.command {
background-image: url('../assets/command-icon.svg');
}
.maintenance {
background-image: url('../assets/maintenance-icon.svg');
}
</style>

@ -0,0 +1,196 @@
<template>
<div class="layout-container">
<header class="header">
<div class="logo">
<router-link to="/dashboard" class="logo-link">智慧照明系统</router-link>
</div>
<div class="user-info">
<span class="nickname">{{ userStore.nickname }}</span>
<el-dropdown @command="handleCommand">
<span class="user-dropdown">
<el-avatar :size="32" :src="avatarUrl">{{ userStore.nickname.charAt(0) }}</el-avatar>
<span class="dropdown-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</span>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</header>
<!-- <div class="divider">
<div class="divider-line"></div>
<div class="divider-dots">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</div> -->
<main class="main-content">
<router-view></router-view>
</main>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '../stores/user'
import { ElMessage } from 'element-plus'
const router = useRouter()
const userStore = useUserStore()
const avatarUrl = ref('')
const handleCommand = (command: string) => {
if (command === 'logout') {
userStore.clearUserInfo()
router.push('/login')
ElMessage.success('已退出登录')
}
}
</script>
<style scoped>
.layout-container {
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: #020b1f;
}
.header {
height: 60px;
background: linear-gradient(to right, #041236, #0A1B48);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.logo {
color: #00c3ff;
font-size: 20px;
font-weight: bold;
text-shadow: 0 0 10px rgba(0, 195, 255, 0.5);
}
.user-info {
display: flex;
align-items: center;
gap: 10px;
}
.nickname {
color: white;
font-size: 14px;
}
.user-dropdown {
display: flex;
align-items: center;
gap: 5px;
cursor: pointer;
color: white;
}
.dropdown-icon {
display: flex;
align-items: center;
}
.divider {
position: relative;
height: 2px;
background: linear-gradient(90deg, transparent, #00c3ff, transparent);
margin: 0 20px;
}
.divider-line {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, #00c3ff, transparent);
animation: scan 2s linear infinite;
}
.divider-dots {
position: absolute;
top: 50%;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
transform: translateY(-50%);
}
.dot {
width: 4px;
height: 4px;
background-color: #00c3ff;
border-radius: 50%;
box-shadow: 0 0 10px #00c3ff;
animation: pulse 2s infinite;
}
.dot:nth-child(2) {
animation-delay: 0.5s;
}
.dot:nth-child(3) {
animation-delay: 1s;
}
@keyframes scan {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
@keyframes pulse {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.5);
opacity: 0.5;
}
100% {
transform: scale(1);
opacity: 1;
}
}
.main-content {
flex: 1;
padding: 20px;
overflow: auto;
}
.logo-link {
color: #00c3ff;
font-size: 20px;
font-weight: bold;
text-shadow: 0 0 10px #00c3ff, 0 0 2px #6eeaff;
text-decoration: none;
transition: color 0.2s, text-shadow 0.2s;
}
.logo-link:hover {
color: #6eeaff;
text-shadow: 0 0 16px #6eeaff, 0 0 4px #00c3ff;
}
</style>

@ -0,0 +1,19 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import App from './App.vue'
import router from './router'
import 'element-plus/dist/index.css';
import './style.css'
import 'element-plus/theme-chalk/dark/css-vars.css'
import './styles/element-dark.scss'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(router)
app.use(ElementPlus, { locale: zhCn })
app.mount('#app')

@ -0,0 +1,72 @@
import { createRouter, createWebHistory } from 'vue-router'
import DashboardView from '../views/DashboardView.vue'
import MaintenanceView from '../views/MaintenanceView.vue'
import LightingView from '../views/LightingView.vue'
import LoginView from '../views/LoginView.vue'
import DataScreenView from '../views/DataScreenView.vue'
import DefaultLayout from '../layouts/DefaultLayout.vue'
import { useUserStore } from '../stores/user'
import AssetTreeView from '../views/AssetTreeView.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/login',
name: 'login',
component: LoginView
},
{
path: '/',
component: DefaultLayout,
children: [
{
path: '',
redirect: '/dashboard'
},
{
path: 'dashboard',
name: 'dashboard',
component: DashboardView
},
{
path: 'maintenance',
name: 'maintenance',
component: MaintenanceView
},
{
path: 'lighting',
name: 'lighting',
component: LightingView
},
{
path: 'datascreen',
name: 'datascreen',
component: DataScreenView
},
{
path: '/asset-tree',
name: 'AssetTree',
component: AssetTreeView
}
]
}
]
})
// 路由守卫
router.beforeEach((to, from, next) => {
const userStore = useUserStore()
// 如果用户未登录且访问的不是登录页,重定向到登录页
if (!userStore.token && to.path !== '/login') {
next('/login')
} else if (userStore.token && to.path === '/login') {
// 如果用户已登录且访问登录页,重定向到仪表板
next('/dashboard')
} else {
next()
}
})
export default router

@ -0,0 +1,119 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { FaultItem, FaultDetail, FaultFilter, FaultStatistics } from '../types/fault'
import { ElMessage } from 'element-plus'
import axios from 'axios'
export const useFaultStore = defineStore('fault', () => {
const faultList = ref<FaultItem[]>([])
const currentFault = ref<FaultDetail | null>(null)
const statistics = ref<FaultStatistics | null>(null)
const total = ref(0)
const loading = ref(false)
// 获取故障列表
const fetchFaultList = async (params: {
page: number
pageSize: number
filter?: FaultFilter
}) => {
loading.value = true
try {
const response = await axios.post('/api', {
jsonrpc: '2.0',
method: 'fault.list',
params: {
page: params.page,
pageSize: params.pageSize,
...params.filter
}
})
if (response.data.error.code === 200) {
faultList.value = response.data.result.items
total.value = response.data.result.total
} else {
throw new Error(response.data.error.message)
}
} catch (error) {
ElMessage.error('获取故障列表失败')
faultList.value = []
total.value = 0
} finally {
loading.value = false
}
}
// 获取故障详情
const fetchFaultDetail = async (id: string) => {
try {
const response = await axios.post('/api', {
jsonrpc: '2.0',
method: 'fault.detail',
params: { id }
})
if (response.data.error.code === 200) {
currentFault.value = response.data.result
} else {
throw new Error(response.data.error.message)
}
} catch (error) {
ElMessage.error('获取故障详情失败')
currentFault.value = null
}
}
// 更新故障状态
const updateFaultStatus = async (id: string, status: string, comment: string) => {
try {
const response = await axios.post('/api', {
jsonrpc: '2.0',
method: 'fault.update',
params: { id, status, comment }
})
if (response.data.error.code === 200) {
ElMessage.success('更新状态成功')
return response.data.result
} else {
throw new Error(response.data.error.message)
}
} catch (error) {
ElMessage.error('更新状态失败')
throw error
}
}
// 获取故障统计
const fetchFaultStatistics = async (params: {
startTime: string
endTime: string
type: 'all' | 'daily' | 'weekly' | 'monthly'
}) => {
try {
const response = await axios.post('/api', {
jsonrpc: '2.0',
method: 'fault.statistics',
params
})
if (response.data.error.code === 200) {
statistics.value = response.data.result
} else {
throw new Error(response.data.error.message)
}
} catch (error) {
ElMessage.error('获取故障统计失败')
statistics.value = null
}
}
return {
faultList,
currentFault,
statistics,
total,
loading,
fetchFaultList,
fetchFaultDetail,
updateFaultStatus,
fetchFaultStatistics
}
})

@ -0,0 +1,85 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { TicketItem, TicketDetail, TicketListResponse } from '../types/ticket'
import * as ticketApi from '../api/ticket'
export const useTicketStore = defineStore('ticket', () => {
const ticketList = ref<TicketItem[]>([])
const currentTicket = ref<TicketDetail | null>(null)
const total = ref(0)
const loading = ref(false)
// 获取工单列表
const fetchTicketList = async (params: any) => {
loading.value = true
try {
const res = await ticketApi.fetchTicketList(params)
ticketList.value = res.rows
total.value = res.count
} finally {
loading.value = false
}
}
// 获取工单详情
const fetchTicketDetail = async (id: number) => {
loading.value = true
try {
const res = await ticketApi.getTicketDetail(id)
currentTicket.value = res
} finally {
loading.value = false
}
}
// 创建工单
const createTicket = async (params: any) => {
loading.value = true
try {
await ticketApi.createTicket(params)
// 刷新列表
await fetchTicketList({})
} finally {
loading.value = false
}
}
// 更新工单
const updateTicket = async (params: any) => {
loading.value = true
try {
await ticketApi.updateTicket(params)
// 刷新列表和详情
await Promise.all([
fetchTicketList({}),
params.id && fetchTicketDetail(params.id)
])
} finally {
loading.value = false
}
}
// 删除工单
const deleteTicket = async (id: number) => {
loading.value = true
try {
await ticketApi.deleteTicket(id)
// 刷新列表
await fetchTicketList({})
} finally {
loading.value = false
}
}
return {
ticketList,
currentTicket,
total,
loading,
fetchTicketList,
fetchTicketDetail,
createTicket,
updateTicket,
deleteTicket
}
})

@ -0,0 +1,31 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useUserStore = defineStore('user', () => {
// 从本地存储获取初始值
const nickname = ref(localStorage.getItem('nickname') || '')
const token = ref(localStorage.getItem('token') || '')
const setUserInfo = (userInfo: { nickname: string; token: string }) => {
nickname.value = userInfo.nickname
token.value = userInfo.token
// 保存到本地存储
localStorage.setItem('nickname', userInfo.nickname)
localStorage.setItem('token', userInfo.token)
}
const clearUserInfo = () => {
nickname.value = ''
token.value = ''
// 清除本地存储
localStorage.removeItem('nickname')
localStorage.removeItem('token')
}
return {
nickname,
token,
setUserInfo,
clearUserInfo
}
})

@ -0,0 +1,204 @@
/* 全局深蓝科技风样式 (Element-UI) */
/* 基础背景及文字色 */
body {
background-color: #001529 !important;
color: rgba(255, 255, 255, 0.85) !important;
}
/* 表格定制 */
.el-table {
background-color: #001529 !important;
border: 1px solid #003a8c !important;
}
.el-table th {
background-color: #002140 !important;
color: #1890ff !important;
}
.el-table tr {
background-color: #001529 !important;
color: rgba(255, 255, 255, 0.85) !important;
}
.el-table__body tr:hover > td {
background-color: rgba(24, 144, 255, 0.08) !important;
}
/* 按钮玻璃拟态风格 */
/* .el-button {
background: transparent !important;
border: 1px solid rgba(255, 255, 255, 0.3) !important;
color: rgba(255, 255, 255, 0.85) !important;
}
.el-button:hover {
background: rgba(24, 144, 255, 0.1) !important;
} */
/* 表单项深蓝科技风优化 */
/* 表单项标签 */
.el-form-item__label {
color: #1890ff !important; /* 标签使用科技蓝 */
font-size: 13px !important; /* 紧凑字号 */
padding-right: 8px !important; /* 标签与输入框间距 */
}
/* 输入框容器增强 */
.el-form-item {
margin-bottom: 16px !important; /* 统一垂直间距 */
}
/* 输入框聚焦状态 */
.el-input.is-active .el-input__inner,
.el-input__inner:focus {
border-color: #1890ff !important; /* 科技蓝聚焦边框 */
box-shadow: 0 0 3px rgba(24,144,255,0.3) !important; /* 科技感光效 */
}
/* 分页器深度定制 */
.el-pagination {
/* 基础样式 */
padding: 8px 12px !important;
background: #002140 !important;
border-radius: 4px;
/* 页码按钮 */
.btn-prev,
.btn-next,
.el-pager li {
background: transparent !important;
border: 1px solid #003a8c !important;
color: rgba(255,255,255,0.7) !important;
min-width: 28px !important;
height: 28px !important;
margin: 0 3px !important;
&:hover {
border-color: #1890ff !important;
color: #1890ff !important;
}
}
/* 当前页码 */
.el-pager li.active {
background: #1890ff !important;
border-color: #1890ff !important;
color: #fff !important;
font-weight: normal;
}
/* 页数选择器 */
.el-pagination__sizes {
.el-input__inner {
background: #001529 !important;
border-color: #003a8c !important;
}
}
/* 总条数文字 */
.el-pagination__total {
color: rgba(255,255,255,0.6) !important;
margin-right: 15px !important;
}
}
/* 表单元素 */
.el-input__inner {
background-color: #002140 !important;
border-color: #003a8c !important;
color: rgba(255, 255, 255, 0.85) !important;
}
.el-range-input {
background-color: #002140 !important;
color: rgba(255, 255, 255, 0.85) !important;
}
/* 分页组件 */
.el-pagination {
color: rgba(255, 255, 255, 0.85) !important;
}
.el-pager li {
background: #002140 !important;
color: rgba(255, 255, 255, 0.85) !important;
}
.el-pager li.active {
color: #1890ff !important;
}
/* 状态标识色 */
.el-tag {
background-color: #001529 !important;
border-color: #13c2c2 !important;
color: #13c2c2 !important;
}
/* 空数据提示 */
.el-empty__description p {
color: rgba(255, 255, 255, 0.6) !important;
}
/* 弹框专用深蓝科技风样式补充 */
/* 弹框容器 */
.el-dialog {
background: #001529 !important;
border: 1px solid #003a8c !important;
border-radius: 4px !important;
}
/* 弹框标题 */
.el-dialog__title {
color: #1890ff !important;
font-size: 16px !important;
}
/* 弹框头部装饰线 */
.el-dialog__header {
border-bottom: 1px solid #003a8c !important;
}
/* 弹框内容 */
.el-dialog__body {
padding-top: var(--el-dialog-padding-primary) !important;
background: #001529 !important;
}
/* 筛选条件容器 */
.filter-container {
background: #002140 !important;
padding: 12px;
margin-bottom: 16px;
border-radius: 2px;
}
/* 时间选择器组 */
.el-date-editor--daterange {
width: 320px !important;
background: #002140 !important;
}
/* 操作按钮组布局 */
.action-buttons {
display: flex;
gap: 12px;
margin-top: 16px;
}
/* 紧凑型表格 */
.compact-table {
.el-table__body td {
padding: 8px 0 !important;
}
.el-table__header th {
padding: 12px 0 !important;
}
}
/* 分页底部定位 */
.dialog-pagination {
margin-top: 16px !important;
display: flex;
justify-content: flex-end;
background: #002140 !important;
padding: 12px;
}

@ -0,0 +1,83 @@
// Element Plus
@use "../../node_modules/element-plus/theme-chalk/src/index.scss" as *;
//
:root {
--el-box-shadow: 0 0 20px rgba(0, 102, 255, 0.2);
--el-font-size-large: 18px;
--el-text-color-primary: #fff;
--el-color-primary: #00c3ff; //
--el-color-success: #13c2c2; //
--el-color-warning: #faad14; //
--el-color-danger: #f5222d; //
--el-bg-color: #041236; //
--el-text-color-regular: #b3e5ff; //
--el-fill-color-blank: hsl(209, 100%, 13%);
--el-input-bg-color: var(--el-fill-color-blank);
--el-border-color: #1a3a7c;
--el-border-color-hover: rgba(24, 144, 255, 0.7);
--el-input-focus-border-color: #1890ff;
--el-input-text-color: rgba(255, 255, 255, 0.85);
--el-input-placeholder-color: rgba(255, 255, 255, 0.5);
--el-input-disabled-bg-color: #001529;
--el-input-disabled-border-color: rgba(255, 255, 255, 0.1);
--el-input-disabled-text-color: rgba(255, 255, 255, 0.3);
--el-border-color-lighter: #3a5aac;
--el-border-color-light: #2a4a9c;
--el-border-color-extra-light: rgba(24, 144, 255, 0.1);
--el-table-border-color: var(--el-border-color-lighter);
--el-table-border: 1px solid var(--el-table-border-color);
--el-table-header-bg-color: #002140;
--el-table-row-hover-bg-color: rgba(24, 144, 255, 0.08);
--el-fill-color-light: #188fffd5;
--el-color-primary-light-3: #1a9fff;
--el-color-primary-light-5: #41cfff;
--el-color-primary-light-7: #6eeaff;
--el-color-primary-light-8: #b3e5ff;
--el-color-primary-light-9: #e6f7ff;
--el-color-primary-dark-2: #0052cc;
--el-bg-color-page: #020b1f;
--el-bg-color-overlay: #0a1a3c;
--el-text-color-secondary: #6eeaff;
--el-text-color-placeholder: #41cfff;
--el-box-shadow-light: 0 0 12px #00c3ff44;
--el-box-shadow-dark: 0 0 24px #00c3ff88;
--el-button-hover-text-color: var(--el-text-color-primary);
--el-button-hover-border-color: #188fffd5;
--el-button-hover-bg-color: #188fffd5;
--el-button-divide-border-color: var(--el-fill-color-blank);
}
.el-button{
--el-button-hover-text-color: var(--el-text-color-primary);
--el-button-hover-border-color: #5da9f0d5;
--el-button-hover-bg-color: #5da9f0d5;
--el-button-divide-border-color: var(--el-fill-color-blank);
}
.el-dropdown__popper {
--el-dropdown-menu-box-shadow: var(--el-box-shadow-light);
--el-dropdown-menuItem-hover-fill: var(--el-color-primary-light-9);
--el-dropdown-menuItem-hover-color: var(--el-text-color-primary);
}
//
$--menu-background-color: var(--el-bg-color); //
$--table-header-background-color: #002140; //
$--table-row-hover-background-color: rgba(24, 144, 255, 0.08); //
$--button-default-background-color: transparent; //
$--button-default-border-color: rgba(255,255,255,0.3); //
.el-tag,
.el-button--primary {
box-shadow: 0 0 12px #00c3ff88, 0 0 2px #6eeaff;
text-shadow: 0 0 6px #6eeaff88;
border-color: #00c3ff !important;
}

@ -0,0 +1,69 @@
export enum FaultStatus {
PENDING = 'pending',
PROCESSING = 'processing',
RESOLVED = 'resolved',
CLOSED = 'closed'
}
export enum FaultLevel {
HIGH = 'high',
MEDIUM = 'medium',
LOW = 'low'
}
export enum FaultType {
POWER_FAILURE = 'power_failure',
COMMUNICATION_ERROR = 'communication_error',
HARDWARE_FAILURE = 'hardware_failure',
SENSOR_ERROR = 'sensor_error',
OTHER = 'other'
}
export interface Location {
longitude: number
latitude: number
}
export interface FaultItem {
id: string
deviceId: string
deviceName: string
type: FaultType
level: FaultLevel
status: FaultStatus
description: string
location: Location
createTime: string
updateTime: string
}
export interface FaultHistory {
time: string
action: string
operator: string
comment: string
}
export interface FaultDetail extends FaultItem {
history: FaultHistory[]
}
export interface FaultStatistics {
total: number
pending: number
processing: number
resolved: number
byType: Record<FaultType, number>
byLevel: Record<FaultLevel, number>
trend: Array<{
date: string
count: number
}>
}
export interface FaultFilter {
status?: FaultStatus | 'all'
startTime?: string
endTime?: string
deviceId?: string
}

@ -0,0 +1,86 @@
// 工单状态枚举
export enum TicketStatus {
NEW = 'new',
REVIEWED = 'reviewed',
DISPATCHED = 'dispatched',
ASSIGNED = 'assigned',
IN_PROGRESS = 'in_progress',
COMPLETED = 'completed',
CLOSED = 'closed',
REJECTED = 'rejected'
}
// 工单优先级枚举
export enum TicketPriority {
LOW = 'low',
MEDIUM = 'medium',
HIGH = 'high'
}
// 工单来源枚举
export enum TicketSource {
CITIZEN = '市民举报',
INSPECTION = '巡检发现',
MONITOR = '监控系统'
}
// 工单列表项接口
export interface TicketItem {
id: number
title: string
description: string
status: TicketStatus
priority: TicketPriority
source: TicketSource
creator_id: number
created_name: string
updated_name: string
created_at: string
updated_at: string
contact_info: string
expected_completion_time?: string
}
// 工单列表响应接口
export interface TicketListResponse {
count: number
rows: TicketItem[]
}
// 工单详情接口
export interface TicketDetail extends TicketItem {
device_id: number
}
// 创建工单请求参数
export interface CreateTicketParams {
title: string
description: string
source: TicketSource
priority: TicketPriority
status: TicketStatus
creator_id: number
device_id: number
contact_info: string
expected_completion_time?: string
}
// 更新工单请求参数
export interface UpdateTicketParams {
id: number
title?: string
description?: string
status?: TicketStatus
priority?: TicketPriority
updater_id: number
}
// 工单列表查询参数
export interface TicketQueryParams {
page?: number
page_size?: number
status?: TicketStatus[]
priority?: TicketPriority[]
start_time?: string
end_time?: string
}

@ -0,0 +1,17 @@
const isLogin = () => {
return !!localStorage.getItem('token')
}
const getToken = () => {
return localStorage.getItem('token')
}
const setToken = (token: string) => {
localStorage.setItem('token', token)
}
const clearToken = () => {
localStorage.removeItem('token')
}
export { isLogin, getToken, setToken, clearToken }

@ -0,0 +1,3 @@
const debug = true
export const baseUrl = '/jsonrpc'
export default debug

@ -0,0 +1,35 @@
import axios from 'axios'
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
// 请求拦截器
request.interceptors.request.use(
config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截器
request.interceptors.response.use(
response => {
return response.data
},
error => {
return Promise.reject(error)
}
)
export default request

@ -0,0 +1,71 @@
import axios from 'axios';
import {baseUrl} from './env';
import {getToken} from "./auth";
import {ElMessage} from 'element-plus';
const instance = axios.create({
baseURL: '/',
timeout: 100000,
responseType: 'json',
});
// HTTP request拦截
instance.interceptors.request.use(
(config) => {
// console.log(config)
const method = config.url;
const params = config.data;
// sso登录方式下/account/myinfo 跨域请求用户中心对应服务
config.url = baseUrl;
config.data = {
jsonrpc: '2.0',
method,
params,
session: getToken(),
timestampin: Date.now().toString(),
};
return config;
},
(error) => {
return Promise.reject(error);
}
);
// HTTP response拦截
instance.interceptors.response.use(
(response) => {
// console.log(response)
const { error, result } = response.data;
// 如果没有error对象说明是成功的响应
if (!error) {
return result;
}
// 处理错误情况
switch (error.code) {
case 200:
return result;
case 401:
ElMessage.error(error.message || '未授权,请重新登录');
// 可以在这里处理登出逻辑
return Promise.reject(error);
case 403:
ElMessage.error(error.message || '没有权限访问');
return Promise.reject(error);
case 601:
ElMessage.warning(error.message);
return Promise.reject(error);
default:
ElMessage.error(error.message || '请求失败');
return Promise.reject(error);
}
},
(error) => {
ElMessage.error(error.message || '网络请求失败');
return Promise.reject(error);
}
);
export default instance;

@ -0,0 +1,184 @@
<template>
<div class="asset-category-page">
<!-- 顶部筛选区 -->
<el-card class="table-card">
<el-form :inline="true" :model="filters" size="small" class="filter-form">
<el-form-item label="关键词">
<el-input v-model="filters.keyword" placeholder="请输入分类名称" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="fetchList"></el-button>
</el-form-item>
<el-form-item>
<el-button type="success" @click="openAddDialog"></el-button>
</el-form-item>
</el-form>
<el-table
:data="tableData"
border
style="width: 100%;"
>
<el-table-column prop="markTypeId" label="分类ID" min-width="80" />
<el-table-column prop="markTypeName" label="分类名称" min-width="160" />
<el-table-column label="操作" min-width="180">
<template #default="{ row }">
<el-button size="small" type="warning" @click.stop="openEditDialog(row)">编辑</el-button>
<el-button size="small" type="danger" @click.stop="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
style="margin: 16px 0 0; text-align: right"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
:page-size="pageSize"
:current-page="currentPage"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:page-sizes="[10, 20, 50, 100]"
/>
</el-card>
<!-- 新增/编辑弹窗 -->
<el-dialog v-model="showDialog" :title="dialogType==='add'?'新增分类':'编辑分类'" width="400px">
<el-form :model="form" label-width="100px">
<el-form-item label="分类名称" required>
<el-input v-model="form.markTypeName" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showDialog=false"></el-button>
<el-button type="primary" @click="submitForm"></el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import rpc from '../utils/rpc'
const tableData = ref<any[]>([])
const showDialog = ref(false)
const dialogType = ref<'add'|'edit'>('add')
const form = reactive<any>({})
const filters = reactive({ keyword: '' })
const total = ref(0)
const pageSize = ref(20)
const currentPage = ref(1)
const allData = ref<any[]>([])
const fetchList = async () => {
const res = await rpc.post('/markType/list', {})
const rows = (res as any)?.rows || []
allData.value = rows
let data = rows
if (filters.keyword) {
data = data.filter((item: any) => item.markTypeName?.includes(filters.keyword))
}
total.value = data.length
tableData.value = data.slice((currentPage.value-1)*pageSize.value, currentPage.value*pageSize.value)
}
const openAddDialog = () => {
dialogType.value = 'add'
showDialog.value = true
Object.assign(form, { markTypeName: '' })
}
const openEditDialog = (row: any) => {
dialogType.value = 'edit'
showDialog.value = true
Object.assign(form, { ...row })
}
const handleDelete = (row: any) => {
ElMessageBox.confirm('确定删除该分类吗?', '提示', { type: 'warning' })
.then(async () => {
await rpc.post('/markType/delete', { markTypeId: row.markTypeId })
ElMessage.success('删除成功')
fetchList()
})
.catch(() => {})
}
const submitForm = async () => {
if (!form.markTypeName) {
ElMessage.error('请输入分类名称')
return
}
if (dialogType.value === 'add') {
await rpc.post('/markType/create', { markTypeName: form.markTypeName })
ElMessage.success('新增成功')
} else {
await rpc.post('/markType/update', { markTypeId: form.markTypeId, markTypeName: form.markTypeName })
ElMessage.success('编辑成功')
}
showDialog.value = false
fetchList()
}
const handleSizeChange = (size: number) => {
pageSize.value = size
fetchList()
}
const handleCurrentChange = (page: number) => {
currentPage.value = page
fetchList()
}
onMounted(() => {
fetchList()
})
</script>
<style scoped>
.asset-category-page {
width: 100%;
height: 100%;
background: linear-gradient(180deg, #041236 0%, #02081A 100%);
display: flex;
flex-direction: column;
padding: 20px;
box-sizing: border-box;
}
.filter-card {
background: transparent;
border: none;
box-shadow: none;
margin-bottom: 16px;
padding: 0;
}
.filter-form {
padding: 0 0 8px 0;
}
.table-card {
flex: 1;
display: flex;
flex-direction: column;
background: rgba(4, 26, 71, 0.7);
border-radius: 8px;
box-shadow: 0 0 20px #0003;
padding: 0 0 16px 0;
}
.el-table {
background: transparent;
color: #e6f7ff;
}
.el-table th {
background: #002140;
color: #6eeaff;
font-weight: bold;
font-size: 15px;
}
.el-table .el-button {
box-shadow: 0 0 8px #00c3ff88;
border-radius: 4px;
}
.el-pagination {
margin-top: 16px;
}
</style>

@ -0,0 +1,406 @@
<template>
<div class="asset-tree-page">
<div class="asset-container">
<!-- 左侧资产类型树 -->
<div class="asset-tree">
<el-card class="tree-card">
<template #header>
<div class="tree-header">
<span>资产类型</span>
<el-button type="primary" size="small" @click="showAssetTypeDialog = true">管理</el-button>
</div>
</template>
<el-input
v-model="treeKeyword"
placeholder="搜索资产类型"
clearable
class="tree-search"
/>
<el-tree
ref="treeRef"
:data="typeTreeData"
:props="{ label: 'assetTypeName', children: 'childrenList' }"
node-key="id"
highlight-current
:filter-node-method="filterNode"
@node-click="handleNodeClick"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<span>{{ node.label }}</span>
<span class="node-count" v-if="data.count">({{ data.count }})</span>
</span>
</template>
</el-tree>
</el-card>
</div>
<!-- 右侧资产列表 -->
<div class="asset-list">
<el-card class="table-card">
<el-form :inline="true" :model="filters" size="small" class="filter-form">
<el-form-item label="关键词">
<el-input v-model="filters.keyword" placeholder="请输入关键词" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="fetchList"></el-button>
</el-form-item>
<el-form-item>
<el-button type="success" @click="openAddDialog(null)"></el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="showMarkTypeDialog = true">资产分类管理</el-button>
</el-form-item>
</el-form>
<el-table
:data="tableData"
row-key="id"
border
style="width: 100%;"
height="calc(100% - 120px)"
>
<el-table-column prop="assetName" label="资产名称" min-width="120" />
<el-table-column prop="assetNumber" label="资产编号" min-width="100" />
<el-table-column
prop="markType"
label="资产分类"
min-width="100"
>
<template #default="{ row }">
{{ markTypeOptions.find(opt => opt.value === row.markType)?.label || row.markType }}
</template>
</el-table-column>
<el-table-column
prop="assetTypeId"
label="资产类型"
min-width="100"
>
<template #default="{ row }">
{{ assetTypeOptions.find(opt => opt.value == row.assetTypeId)?.label || row.assetTypeId }}
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="120" />
<el-table-column label="操作" min-width="180" fixed="right">
<template #default="{ row }">
<el-button size="small" type="warning" @click="openEditDialog(row)"></el-button>
<el-button size="small" type="danger" @click="handleDelete(row)"></el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
style="margin: 16px 0 0; text-align: right"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
:page-size="pageSize"
:current-page="currentPage"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:page-sizes="[10, 20, 50, 100]"
/>
</el-card>
</div>
</div>
<!-- 弹窗集成 -->
<el-dialog v-model="showMarkTypeDialog" title="资产分类管理" width="900px" top="5vh" :close-on-click-modal="false">
<AssetCategoryView />
</el-dialog>
<!-- 弹窗集成 -->
<el-dialog v-model="showAssetTypeDialog" title="资产类型管理" width="900px" top="5vh" :close-on-click-modal="false" @closed="fetchTypeTree">
<AssetTypeView />
</el-dialog>
<!-- 新增/编辑弹窗 -->
<el-dialog v-model="showDialog" :title="dialogType==='add'?'新增资产':'编辑资产'" width="500px">
<el-form :model="form" label-width="100px">
<el-form-item label="资产名称" required>
<el-input v-model="form.assetName" />
</el-form-item>
<el-form-item label="资产编号">
<el-input v-model="form.assetNumber" />
</el-form-item>
<el-form-item label="资产分类">
<el-select v-model="form.markType" placeholder="请选择资产分类" filterable clearable>
<el-option v-for="item in markTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="资产类型">
<el-select v-model="form.assetTypeId" placeholder="请选择资产类型" filterable clearable>
<el-option v-for="item in assetTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showDialog=false"></el-button>
<el-button type="primary" @click="submitForm"></el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import rpc from '../utils/rpc'
import AssetTypeView from './AssetTypeView.vue'
import AssetCategoryView from './AssetCategoryView.vue'
const treeRef = ref()
const treeKeyword = ref('')
const typeTreeData = ref<any[]>([])
const tableData = ref<any[]>([])
const showDialog = ref(false)
const dialogType = ref<'add'|'edit'>('add')
const form = reactive<any>({})
const filters = reactive({
keyword: '',
assetTypeId: null as number | null
})
const total = ref(0)
const pageSize = ref(20)
const currentPage = ref(1)
const markTypeOptions = ref<{label:string,value:number}[]>([])
const assetTypeOptions = ref<{label:string,value:number}[]>([])
const showMarkTypeDialog = ref(false)
const showAssetTypeDialog = ref(false)
//
watch(treeKeyword, (val) => {
treeRef.value?.filter(val)
})
//
const filterNode = (value: string, data: any) => {
if (!value) return true
return data.assetTypeName.includes(value)
}
//
const fetchTypeTree = async () => {
const res = await rpc.post('/assetType/tree', {})
typeTreeData.value = (res as any) || []
}
//
const fetchList = async () => {
const res = await rpc.post('/asset/list', {
...filters,
pageSize: pageSize.value,
currentPage: currentPage.value
})
const data = res as any
tableData.value = data.rows || []
total.value = data.total || 0
}
//
const fetchMarkTypeOptions = async () => {
const res = await rpc.post('/markType/list', {})
const rows = (res as any)?.rows || []
markTypeOptions.value = rows.map((item:any) => ({ label: item.markTypeName, value: item.markTypeId }))
}
//
const fetchAssetTypeOptions = async () => {
const res = await rpc.post('/assetType/list', {})
const rows = (res as any)?.rows || []
assetTypeOptions.value = rows.map((item:any) => ({ label: item.assetTypeName, value: item.id }))
}
//
const handleNodeClick = (data: any) => {
filters.assetTypeId = data.id
currentPage.value = 1
fetchList()
}
const openAddDialog = (parent: any) => {
dialogType.value = 'add'
showDialog.value = true
Object.assign(form, {
id: null,
assetName: '',
assetNumber: '',
markType: '',
assetTypeId: filters.assetTypeId || '',
remark: ''
})
}
const openEditDialog = (data: any) => {
dialogType.value = 'edit'
showDialog.value = true
Object.assign(form, { ...data })
}
const handleDelete = (data: any) => {
ElMessageBox.confirm('确定删除该资产吗?', '提示', { type: 'warning' })
.then(async () => {
await rpc.post('/asset/delete', { id: data.id })
ElMessage.success('删除成功')
fetchList()
})
.catch(() => {})
}
const submitForm = async () => {
if (!form.assetName) {
ElMessage.error('请输入资产名称')
return
}
if (dialogType.value === 'add') {
await rpc.post('/asset/create', form)
ElMessage.success('新增成功')
} else {
await rpc.post('/asset/update', form)
ElMessage.success('编辑成功')
}
showDialog.value = false
fetchList()
}
const handleSizeChange = (size: number) => {
pageSize.value = size
fetchList()
}
const handleCurrentChange = (page: number) => {
currentPage.value = page
fetchList()
}
onMounted(() => {
fetchTypeTree()
fetchMarkTypeOptions()
fetchAssetTypeOptions()
fetchList()
})
</script>
<style scoped>
.asset-tree-page {
width: 100%;
height: 100%;
background: linear-gradient(180deg, #041236 0%, #02081A 100%);
display: flex;
flex-direction: column;
padding: 20px;
box-sizing: border-box;
}
.asset-container {
display: flex;
gap: 20px;
height: 100%;
}
.asset-tree {
width: 280px;
flex-shrink: 0;
}
.asset-list {
flex: 1;
min-width: 0;
}
.tree-card {
height: 100%;
background: rgba(4, 26, 71, 0.7);
border-radius: 8px;
box-shadow: 0 0 20px #0003;
}
.tree-header {
display: flex;
justify-content: space-between;
align-items: center;
color: #6eeaff;
font-size: 16px;
font-weight: bold;
}
.tree-search {
margin-bottom: 16px;
}
.custom-tree-node {
display: flex;
align-items: center;
gap: 8px;
}
.node-count {
color: #00c3ff;
font-size: 12px;
}
.table-card {
height: 100%;
background: rgba(4, 26, 71, 0.7);
border-radius: 8px;
box-shadow: 0 0 20px #0003;
display: flex;
flex-direction: column;
}
.filter-form {
padding: 0 0 8px 0;
}
.el-table {
background: transparent;
color: #e6f7ff;
}
.el-table th {
background: #002140;
color: #6eeaff;
font-weight: bold;
font-size: 15px;
}
.el-table .el-button {
box-shadow: 0 0 8px #00c3ff88;
border-radius: 4px;
}
.el-pagination {
margin-top: 16px;
}
:deep(.el-tree) {
background: transparent;
color: #e6f7ff;
}
:deep(.el-tree-node__content:hover) {
background: rgba(0, 195, 255, 0.1);
}
:deep(.el-tree-node.is-current > .el-tree-node__content) {
background: rgba(0, 195, 255, 0.2);
color: #00c3ff;
}
:deep(.el-input__wrapper) {
background: rgba(0, 33, 64, 0.5);
box-shadow: 0 0 0 1px rgba(0, 195, 255, 0.2);
}
:deep(.el-input__wrapper:hover) {
box-shadow: 0 0 0 1px rgba(0, 195, 255, 0.4);
}
:deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 1px #00c3ff;
}
:deep(.el-select .el-input__wrapper) {
background: rgba(0, 33, 64, 0.5);
}
</style>

@ -0,0 +1,250 @@
<template>
<div class="asset-type-page">
<!-- 顶部筛选区 -->
<!-- 主表格区 -->
<el-card class="table-card">
<el-form :inline="true" :model="filters" size="small" class="filter-form">
<el-form-item label="关键词">
<el-input v-model="filters.keyword" placeholder="请输入资产类型名称" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="fetchList"></el-button>
</el-form-item>
<el-form-item>
<el-button type="success" @click="openAddDialog"></el-button>
</el-form-item>
</el-form>
<el-table
:data="tableData"
row-key="id"
border
style="width: 100%;"
>
<el-table-column prop="assetTypeName" label="类型名称" min-width="120" />
<el-table-column prop="parentId" label="父类型" min-width="120">
<template #default="{ row }">
{{ row.parentId ? parentOptions.find(item => item.id === row.parentId)?.assetTypeName || '-' : '-' }}
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="120" />
<el-table-column label="操作" min-width="180">
<template #default="{ row }">
<el-button size="small" type="warning" @click.stop="openEditDialog(row)">编辑</el-button>
<el-button size="small" type="danger" @click.stop="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
style="margin: 16px 0 0; text-align: right"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
:page-size="pageSize"
:current-page="currentPage"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:page-sizes="[10, 20, 50, 100]"
/>
</el-card>
<!-- 新增/编辑弹窗 -->
<el-dialog v-model="showDialog" :title="dialogType==='add'?'新增资产类型':'编辑资产类型'" width="500px">
<el-form :model="form" label-width="100px">
<el-form-item label="类型名称" required>
<el-input v-model="form.assetTypeName" />
</el-form-item>
<el-form-item label="父类型">
<el-select v-model="form.parentId" placeholder="请选择父类型" clearable>
<el-option
v-for="item in parentOptions"
:key="item.id"
:label="item.assetTypeName"
:value="item.id"
:disabled="dialogType === 'edit' && item.id === form.id"
/>
</el-select>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" />
</el-form-item>
<!-- 可根据接口补充attrs/assetAttrs等字段 -->
</el-form>
<template #footer>
<el-button @click="showDialog=false"></el-button>
<el-button type="primary" @click="submitForm"></el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import rpc from '../utils/rpc'
const tableData = ref<any[]>([])
const parentOptions = ref<any[]>([])
const showDialog = ref(false)
const dialogType = ref<'add'|'edit'>('add')
const form = reactive<any>({})
const filters = reactive({ keyword: '' })
const total = ref(0)
const pageSize = ref(20)
const currentPage = ref(1)
//
const fetchParentOptions = async () => {
const res = await rpc.post('/assetType/list', {})
parentOptions.value = (res as any)?.rows || []
}
const fetchList = async () => {
const res = await rpc.post('/assetType/list', {})
let rows = (res as any)?.rows || []
//
if (filters.keyword) {
rows = rows.filter((item:any) =>
item.assetTypeName?.includes(filters.keyword) ||
parentOptions.value.find(p => p.id === item.parentId)?.assetTypeName?.includes(filters.keyword)
)
}
total.value = rows.length
tableData.value = rows.slice((currentPage.value-1)*pageSize.value, currentPage.value*pageSize.value)
}
const openAddDialog = () => {
dialogType.value = 'add'
showDialog.value = true
Object.assign(form, {
assetTypeName: '',
parentId: '',
remark: ''
})
}
const openEditDialog = (row: any) => {
dialogType.value = 'edit'
showDialog.value = true
Object.assign(form, { ...row })
}
const handleDelete = (row: any) => {
ElMessageBox.confirm('确定删除该资产类型吗?', '提示', { type: 'warning' })
.then(async () => {
await rpc.post('/assetType/delete', { id: row.id })
ElMessage.success('删除成功')
fetchList()
})
.catch(() => {})
}
const submitForm = async () => {
if (!form.assetTypeName) {
ElMessage.error('请输入类型名称')
return
}
if (dialogType.value === 'add') {
await rpc.post('/assetType/create', form)
ElMessage.success('新增成功')
} else {
await rpc.post('/assetType/update', form)
ElMessage.success('编辑成功')
}
showDialog.value = false
fetchList()
}
const handleSizeChange = (size: number) => {
pageSize.value = size
fetchList()
}
const handleCurrentChange = (page: number) => {
currentPage.value = page
fetchList()
}
onMounted(() => {
fetchList()
fetchParentOptions()
})
</script>
<style scoped>
.asset-type-page {
width: 100%;
height: 100%;
background: linear-gradient(180deg, #041236 0%, #02081A 100%);
display: flex;
flex-direction: column;
padding: 20px;
box-sizing: border-box;
}
.filter-card {
background: transparent;
border: none;
box-shadow: none;
margin-bottom: 16px;
padding: 0;
}
.filter-form {
padding: 0 0 8px 0;
}
.table-card {
flex: 1;
display: flex;
flex-direction: column;
background: rgba(4, 26, 71, 0.7);
border-radius: 8px;
box-shadow: 0 0 20px #0003;
padding: 0 0 16px 0;
}
.el-table {
background: transparent;
color: #e6f7ff;
}
.el-table th {
background: #002140;
color: #6eeaff;
font-weight: bold;
font-size: 15px;
}
.el-table .el-button {
box-shadow: 0 0 8px #00c3ff88;
border-radius: 4px;
}
.el-pagination {
margin-top: 16px;
}
:deep(.el-select .el-input__wrapper) {
background: rgba(0, 33, 64, 0.5);
box-shadow: 0 0 0 1px rgba(0, 195, 255, 0.2);
}
:deep(.el-select .el-input__wrapper:hover) {
box-shadow: 0 0 0 1px rgba(0, 195, 255, 0.4);
}
:deep(.el-select .el-input__wrapper.is-focus) {
box-shadow: 0 0 0 1px #00c3ff;
}
:deep(.el-select-dropdown) {
background: #002140;
border: 1px solid #00c3ff;
}
:deep(.el-select-dropdown__item) {
color: #e6f7ff;
}
:deep(.el-select-dropdown__item.hover, .el-select-dropdown__item:hover) {
background: rgba(0, 195, 255, 0.1);
}
:deep(.el-select-dropdown__item.selected) {
color: #00c3ff;
background: rgba(0, 195, 255, 0.2);
}
</style>

@ -0,0 +1,208 @@
<template>
<div class="dashboard-container">
<!-- 动画层 -->
<div class="bg-animate">
<div class="bg-circle"></div>
<div class="bg-circle bg-circle2"></div>
<div class="bg-light"></div>
</div>
<!-- Main content -->
<div class="main-content">
<div class="module-container">
<ModuleIcon
v-for="(module, index) in modules"
:key="index"
:title="module.title"
:icon="module.icon"
:active="activeModuleIndex === index"
@click="handleModuleClick(index)"
/>
</div>
</div>
<!-- Footer -->
<div class="footer">
<div class="footer-button" @click="router.push('/datascreen')">
数据可视大屏
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import ModuleIcon from '../components/ModuleIcon.vue';
const router = useRouter();
const activeModuleIndex = ref(3); // Data visualization screen is active by default
const modules = [
{ title: '照明监控系统', icon: 'camera', route: '/lighting' },
{ title: '视频监控系统', icon: 'video', route: '/' },
{ title: '分析决策系统', icon: 'chart', route: '/' },
{ title: '数据可视大屏', icon: 'screen', route: '/datascreen' },
{ title: '资产管理系统', icon: 'asset', route: '/asset-tree' },
{ title: '指挥调度系统', icon: 'command', route: '/' },
{ title: '维护工单系统', icon: 'maintenance', route: '/maintenance' }
];
const handleModuleClick = (index: number) => {
activeModuleIndex.value = index;
// Navigate to the corresponding route if available
const route = modules[index].route;
if (route) {
router.push(route);
}
};
</script>
<style scoped>
.dashboard-container {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
background: linear-gradient(180deg, #041236 0%, #02081A 100%);
position: relative;
overflow: hidden;
}
/* 动画层样式 */
.bg-animate {
position: absolute;
left: 50%; top: 50%;
transform: translate(-50%, -50%);
width: 120vw; height: 120vh;
pointer-events: none;
z-index: 0;
}
.bg-circle {
position: absolute;
left: 50%; top: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
border: 2px solid #00c3ff55;
width: 60vw; height: 60vw;
animation: rotate 16s linear infinite;
box-shadow: 0 0 80px 20px #00c3ff33, 0 0 200px 40px #00c3ff22 inset;
}
.bg-circle2 {
width: 40vw; height: 40vw;
border: 2px dashed #00c3ff33;
animation: rotate-rev 24s linear infinite;
opacity: 0.7;
}
.bg-light {
position: absolute;
left: 50%; top: 50%;
transform: translate(-50%, -50%);
width: 100vw; height: 100vw;
background: conic-gradient(from 0deg, #00c3ff33 0deg 60deg, transparent 60deg 360deg);
border-radius: 50%;
filter: blur(40px);
animation: rotate 10s linear infinite;
opacity: 0.5;
}
@keyframes rotate {
0% { transform: translate(-50%, -50%) rotate(0deg);}
100% { transform: translate(-50%, -50%) rotate(360deg);}
}
@keyframes rotate-rev {
0% { transform: translate(-50%, -50%) rotate(0deg);}
100% { transform: translate(-50%, -50%) rotate(-360deg);}
}
.header {
height: 60px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
border-bottom: 2px solid #0a3677;
position: relative;
z-index: 1;
background: linear-gradient(90deg, transparent 0%, #0b2560 50%, transparent 100%);
}
.title {
color: white;
font-size: 24px;
font-weight: bold;
text-shadow: 0 0 10px rgba(0, 195, 255, 0.8);
}
.user-info {
display: flex;
align-items: center;
gap: 20px;
}
.grid-icon img, .user img {
width: 24px;
height: 24px;
}
.user {
display: flex;
align-items: center;
gap: 5px;
color: white;
}
.main-content {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
position: relative;
z-index: 1;
min-height: 0;
}
.module-container {
display: flex;
justify-content: center;
align-items: center;
gap: 30px;
flex-wrap: wrap;
max-width: 1200px;
padding: 20px;
margin: 0 auto;
}
.footer {
height: 80px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
z-index: 1;
flex-shrink: 0;
}
.footer-button {
background: linear-gradient(180deg, #0A3677 0%, #041A47 100%);
color: white;
padding: 10px 40px;
border-radius: 0;
font-size: 18px;
position: relative;
clip-path: polygon(15% 0, 85% 0, 100% 50%, 85% 100%, 15% 100%, 0 50%);
display: flex;
align-items: center;
justify-content: center;
width: 200px;
height: 50px;
border: 1px solid #0066ff;
box-shadow: 0 0 15px rgba(0, 102, 255, 0.5);
cursor: pointer;
transition: box-shadow 0.3s;
}
.footer-button:hover {
box-shadow: 0 0 20px rgba(0, 102, 255, 0.8);
}
</style>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,432 @@
<template>
<div class="fault-management">
<!-- 统计卡片 -->
<div class="statistics-cards">
<el-card v-for="(value, key) in statisticsData" :key="key">
<template #header>
<div class="card-header">
{{ getStatisticsLabel(key) }}
</div>
</template>
<div class="card-value">{{ value }}</div>
</el-card>
</div>
<!-- 筛选器 -->
<div class="filter-section">
<el-form :inline="true" :model="filters">
<el-form-item label="故障状态">
<el-select v-model="filters.status" placeholder="所有状态">
<el-option label="全部" value="all" />
<el-option
v-for="status in statusOptions"
:key="status.value"
:label="status.label"
:value="status.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-date-picker
v-model="dateRange"
type="datetimerange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
@change="handleDateRangeChange"
/>
</el-form-item>
<el-form-item label="设备ID">
<el-input v-model="filters.deviceId" placeholder="输入设备ID" />
</el-form-item>
<el-button type="primary" @click="handleSearch"></el-button>
<el-button @click="resetFilters"></el-button>
</el-form>
</div>
<!-- 故障列表 -->
<el-table
v-loading="faultStore.loading"
:data="faultStore.faultList"
style="width: 100%"
>
<el-table-column prop="id" label="故障ID" width="120" />
<el-table-column prop="deviceName" label="设备名称" width="150" />
<el-table-column prop="type" label="故障类型" width="120">
<template #default="{ row }">
{{ getFaultTypeLabel(row.type) }}
</template>
</el-table-column>
<el-table-column prop="level" label="优先级" width="100">
<template #default="{ row }">
<el-tag :type="getLevelType(row.level)">
{{ getLevelLabel(row.level) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)">
{{ getStatusLabel(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="description" label="故障描述" />
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="handleView(row)"></el-button>
<el-button
v-if="canUpdateStatus(row.status)"
link
type="primary"
@click="handleUpdateStatus(row)"
>
更新状态
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:total="faultStore.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
<!-- 故障详情对话框 -->
<el-dialog
v-model="showDetailDialog"
title="故障详情"
width="60%"
>
<div v-if="currentFault" class="fault-detail">
<el-descriptions :column="2" border>
<el-descriptions-item label="故障ID">{{ currentFault.id }}</el-descriptions-item>
<el-descriptions-item label="设备名称">{{ currentFault.deviceName }}</el-descriptions-item>
<el-descriptions-item label="故障类型">{{ getFaultTypeLabel(currentFault.type) }}</el-descriptions-item>
<el-descriptions-item label="优先级">
<el-tag :type="getLevelType(currentFault.level)">
{{ getLevelLabel(currentFault.level) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="getStatusType(currentFault.status)">
{{ getStatusLabel(currentFault.status) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ currentFault.createTime }}</el-descriptions-item>
<el-descriptions-item label="故障描述" :span="2">{{ currentFault.description }}</el-descriptions-item>
<el-descriptions-item label="位置信息" :span="2">
经度: {{ currentFault.location.longitude }}, 纬度: {{ currentFault.location.latitude }}
</el-descriptions-item>
</el-descriptions>
<div class="history-section">
<h3>处理历史</h3>
<el-timeline>
<el-timeline-item
v-for="(history, index) in currentFault.history"
:key="index"
:timestamp="history.time"
:type="getHistoryType(history.action)"
>
{{ history.operator }}: {{ history.comment }}
</el-timeline-item>
</el-timeline>
</div>
</div>
</el-dialog>
<!-- 更新状态对话框 -->
<el-dialog
v-model="showStatusDialog"
title="更新故障状态"
width="40%"
>
<el-form :model="statusForm" label-width="100px">
<el-form-item label="当前状态">
<el-tag :type="getStatusType(currentFault?.status)">
{{ getStatusLabel(currentFault?.status) }}
</el-tag>
</el-form-item>
<el-form-item label="目标状态">
<el-select v-model="statusForm.status" placeholder="选择状态">
<el-option
v-for="status in getNextStatuses(currentFault?.status)"
:key="status.value"
:label="status.label"
:value="status.value"
/>
</el-select>
</el-form-item>
<el-form-item label="处理说明">
<el-input
v-model="statusForm.comment"
type="textarea"
rows="3"
placeholder="请输入处理说明"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showStatusDialog = false">取消</el-button>
<el-button type="primary" @click="handleStatusSubmit"></el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useFaultStore } from '../stores/fault'
import { FaultStatus, FaultLevel, FaultType } from '../types/fault'
import type { FaultItem, FaultDetail, FaultFilter } from '../types/fault'
import { ElMessage } from 'element-plus'
const faultStore = useFaultStore()
//
const statusOptions = [
{ label: '待处理', value: FaultStatus.PENDING },
{ label: '处理中', value: FaultStatus.PROCESSING },
{ label: '已解决', value: FaultStatus.RESOLVED },
{ label: '已关闭', value: FaultStatus.CLOSED }
]
//
const filters = ref<FaultFilter>({
status: 'all',
deviceId: '',
startTime: '',
endTime: ''
})
const dateRange = ref<[Date, Date] | null>(null)
//
const currentPage = ref(1)
const pageSize = ref(10)
//
const showDetailDialog = ref(false)
const showStatusDialog = ref(false)
const currentFault = ref<FaultDetail | null>(null)
//
const statusForm = ref({
status: '',
comment: ''
})
//
const statisticsData = computed(() => {
if (!faultStore.statistics) return {}
return {
total: faultStore.statistics.total,
pending: faultStore.statistics.pending,
processing: faultStore.statistics.processing,
resolved: faultStore.statistics.resolved
}
})
//
const fetchFaultList = async () => {
await faultStore.fetchFaultList({
page: currentPage.value,
pageSize: pageSize.value,
filter: filters.value
})
}
//
const fetchStatistics = async () => {
const now = new Date()
const startTime = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
await faultStore.fetchFaultStatistics({
startTime: startTime.toISOString(),
endTime: now.toISOString(),
type: 'all'
})
}
//
const handleSearch = () => {
currentPage.value = 1
fetchFaultList()
}
//
const resetFilters = () => {
filters.value = {
status: 'all',
deviceId: '',
startTime: '',
endTime: ''
}
dateRange.value = null
handleSearch()
}
//
const handleDateRangeChange = (val: [Date, Date] | null) => {
if (val) {
filters.value.startTime = val[0].toISOString()
filters.value.endTime = val[1].toISOString()
} else {
filters.value.startTime = ''
filters.value.endTime = ''
}
}
//
const handleSizeChange = (val: number) => {
pageSize.value = val
fetchFaultList()
}
const handleCurrentChange = (val: number) => {
currentPage.value = val
fetchFaultList()
}
//
const handleView = async (row: FaultItem) => {
await faultStore.fetchFaultDetail(row.id)
currentFault.value = faultStore.currentFault
showDetailDialog.value = true
}
//
const handleUpdateStatus = (row: FaultItem) => {
currentFault.value = row as FaultDetail
statusForm.value = {
status: '',
comment: ''
}
showStatusDialog.value = true
}
//
const handleStatusSubmit = async () => {
if (!currentFault.value) return
try {
await faultStore.updateFaultStatus(
currentFault.value.id,
statusForm.value.status,
statusForm.value.comment
)
showStatusDialog.value = false
fetchFaultList()
} catch (error) {
ElMessage.error('更新状态失败')
}
}
//
const getNextStatuses = (currentStatus?: FaultStatus) => {
if (!currentStatus) return []
const statusFlow: Record<FaultStatus, FaultStatus[]> = {
[FaultStatus.PENDING]: [FaultStatus.PROCESSING],
[FaultStatus.PROCESSING]: [FaultStatus.RESOLVED],
[FaultStatus.RESOLVED]: [FaultStatus.CLOSED],
[FaultStatus.CLOSED]: []
}
return statusOptions.filter(s => statusFlow[currentStatus]?.includes(s.value as FaultStatus))
}
//
const canUpdateStatus = (status: FaultStatus) => {
return status !== FaultStatus.CLOSED
}
//
const getLevelType = (level: FaultLevel) => {
const types: Record<FaultLevel, string> = {
high: 'danger',
medium: 'warning',
low: 'info'
}
return types[level]
}
const getStatusType = (status?: FaultStatus) => {
if (!status) return ''
const types: Record<FaultStatus, string> = {
[FaultStatus.PENDING]: 'info',
[FaultStatus.PROCESSING]: 'warning',
[FaultStatus.RESOLVED]: 'success',
[FaultStatus.CLOSED]: ''
}
return types[status]
}
const getHistoryType = (action: string) => {
const types: Record<string, string> = {
create: 'primary',
update: 'warning',
resolve: 'success',
close: 'info'
}
return types[action] || 'info'
}
//
const getLevelLabel = (level: FaultLevel) => {
const labels: Record<FaultLevel, string> = {
high: '高优先级',
medium: '中优先级',
low: '低优先级'
}
return labels[level]
}
const getStatusLabel = (status?: FaultStatus) => {
if (!status) return ''
const labels: Record<FaultStatus, string> = {
[FaultStatus.PENDING]: '待处理',
[FaultStatus.PROCESSING]: '处理中',
[FaultStatus.RESOLVED]: '已解决',
[FaultStatus.CLOSED]: '已关闭'
}
return labels[status]
}
const getFaultTypeLabel = (type: FaultType) => {
const labels: Record<FaultType, string> = {
power_failure: '电源故障',
communication_error: '通信故障',
hardware_failure: '硬件故障',
sensor_error: '传感器故障',
other: '其他故障'
}
return labels[type]
}
const getStatisticsLabel = (key: string) => {
const labels: Record<string, string> = {
total: '总故障数',
pending: '待处理',
processing: '处理中',
resolved: '已解决'
}
return labels[key] || key
}
//
onMounted(() => {
fetchFaultList()
fetchStatistics()
})
</script>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,263 @@
<template>
<div class="login-bg">
<!-- 星空背景 -->
<div class="star-field"></div>
<!-- 竖直流光动画 -->
<div class="vertical-lights">
<div v-for="i in 18" :key="i" class="v-light" :style="getLightStyle(i)"></div>
</div>
<!-- 城市剪影 -->
<div class="city-silhouette"></div>
<!-- 发光网格 -->
<div class="grid-floor"></div>
<!-- 登录面板 -->
<div class="login-panel">
<div class="login-title">智慧照明系统</div>
<el-form>
<el-form-item>
<el-input v-model="username" prefix-icon="el-icon-user" placeholder="请输入用户名称" size="large" />
</el-form-item>
<el-form-item>
<el-input v-model="passwordVisible" :type="showPassword ? 'text' : 'password'" prefix-icon="el-icon-lock" placeholder="请输入登录密码" size="large">
<template #suffix>
<el-icon @click="togglePasswordVisibility" style="cursor:pointer;">
<View v-if="showPassword" />
<Hide v-else />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item style="margin-bottom: 0;">
<el-checkbox v-model="rememberMe"></el-checkbox>
<span class="forget-link" @click="onForget">?</span>
</el-form-item>
<el-form-item>
<el-button class="login-btn" type="primary" size="large" @click="handleLogin" :loading="loading">登录</el-button>
</el-form-item>
</el-form>
<div class="login-tips">温馨提示建议使用最新版Edge/Chrome浏览器360浏览器请使用极速模式</div>
</div>
<!-- 验证码弹窗 -->
<el-dialog
v-model="showVerify"
title="安全验证"
width="400px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
class="verify-dialog"
>
<div class="verify-container">
<slide-verify
ref="block"
:w="360"
:imgs="['/verify-bg.jpg']"
@refresh="onRefresh"
@success="onVerifySuccess"
slider-text="向右滑动验证"
/>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { View, Hide } from '@element-plus/icons-vue';
import { useUserStore } from '../stores/user';
import rpc from '../utils/rpc';
import SlideVerify from 'vue3-slide-verify';
import "vue3-slide-verify/dist/style.css";
const router = useRouter();
const userStore = useUserStore();
const username = ref('');
const passwordVisible = ref('');
const showPassword = ref(false);
const rememberMe = ref(false);
const loading = ref(false);
const showVerify = ref(false);
const block = ref<any>(null);
const code = ref('');
const togglePasswordVisibility = () => {
showPassword.value = !showPassword.value;
};
const onForget = () => {
ElMessage.info('请联系管理员重置密码');
};
const handleLogin = () => {
if (!username.value) {
ElMessage.error('请输入用户名');
return;
}
if (!passwordVisible.value) {
ElMessage.error('请输入密码');
return;
}
//
showVerify.value = true;
};
const onVerifySuccess = () => {
code.value = '1';
showVerify.value = false;
doLogin();
};
const onRefresh = () => {
code.value = '';
block.value?.refresh();
};
//
const doLogin = async () => {
loading.value = true;
try {
const res = await rpc.post('/account/login', {
username: username.value,
password: passwordVisible.value,
code: code.value
});
userStore.setUserInfo({
nickname: (res as any).nickname || username.value,
token: (res as any).token
});
ElMessage.success('登录成功');
await router.push('/dashboard');
} catch (error: any) {
ElMessage.error(error.message || '登录失败');
block.value?.refresh();
code.value = '';
} finally {
loading.value = false;
}
};
//
function getLightStyle(i: number) {
const left = 5 + i * 5 + Math.random() * 2;
const duration = 2.5 + Math.random() * 2;
const delay = Math.random() * 2;
return {
left: `${left}%`,
animationDuration: `${duration}s`,
animationDelay: `${delay}s`
};
}
</script>
<style scoped>
.login-bg {
width: 100vw;
height: 100vh;
background: linear-gradient(180deg, #0a1a3c 0%, #0a1a3c 60%, #0a1a3c 100%);
position: relative;
overflow: hidden;
}
.star-field {
position: absolute; inset: 0;
background: url('@/assets/bg-stars.png') repeat top center;
z-index: 1;
opacity: 0.7;
pointer-events: none;
}
.vertical-lights {
position: absolute; inset: 0;
z-index: 2;
pointer-events: none;
}
.v-light {
position: absolute;
top: 0; width: 2px; height: 100%;
background: linear-gradient(180deg, #6eeaff 0%, transparent 80%);
opacity: 0.25;
filter: blur(1px);
animation: vlight-move 3.5s linear infinite;
}
@keyframes vlight-move {
0% { opacity: 0; top: -30%; height: 0; }
10% { opacity: 0.5; }
50% { opacity: 1; height: 100%; }
100% { opacity: 0; top: 100%; height: 0; }
}
.city-silhouette {
position: absolute; left: 0; right: 0; bottom: 0; height: 180px;
background: url('@/assets/bg-city.png') no-repeat bottom center/cover;
z-index: 3;
opacity: 0.8;
pointer-events: none;
}
.grid-floor {
position: absolute; left: 0; right: 0; bottom: 0; height: 320px;
background: url('@/assets/bg-grid.png') no-repeat bottom center/cover;
z-index: 4;
opacity: 0.7;
pointer-events: none;
}
.login-panel {
position: absolute;
left: 50%; top: 50%;
transform: translate(-50%, -50%);
width: 480px;
background: rgba(10, 26, 60, 0.85);
border-radius: 18px;
box-shadow: 0 0 40px #00c3ff55;
backdrop-filter: blur(12px);
padding: 48px 40px 32px 40px;
z-index: 10;
display: flex;
flex-direction: column;
align-items: center;
}
.login-title {
color: #6eeaff;
font-size: 32px;
font-weight: bold;
letter-spacing: 6px;
margin-bottom: 32px;
text-shadow: 0 0 16px #00c3ff99;
}
.login-btn {
width: 100%;
height: 48px;
font-size: 20px;
background: linear-gradient(90deg, #00c3ff 0%, #0052cc 100%);
border-radius: 8px;
box-shadow: 0 0 16px #00c3ff55;
margin-top: 8px;
}
.login-tips {
color: #b3e5ff;
font-size: 14px;
margin-top: 24px;
text-align: center;
opacity: 0.8;
}
.forget-link {
color: #6eeaff;
margin-left: 24px;
cursor: pointer;
font-size: 14px;
transition: text-shadow 0.2s;
}
.forget-link:hover {
text-shadow: 0 0 8px #00c3ff;
}
.verify-dialog :deep(.el-dialog__body) {
padding: 20px;
}
.verify-container {
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
</style>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,39 @@
<template>
<div class="profile-container">
<h1>个人信息</h1>
<div class="profile-content">
<el-form label-width="100px">
<el-form-item label="用户名">
<el-input v-model="username" disabled />
</el-form-item>
<el-form-item label="昵称">
<el-input v-model="nickname" />
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useUserStore } from '../stores/user'
const userStore = useUserStore()
const username = ref('')
const nickname = ref(userStore.nickname)
</script>
<style scoped>
.profile-container {
padding: 20px;
}
.profile-content {
max-width: 600px;
margin: 20px auto;
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
}
</style>

1
src/vite-env.d.ts vendored

@ -0,0 +1 @@
/// <reference types="vite/client" />

@ -0,0 +1,14 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}

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

@ -0,0 +1,24 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
vite.config.d.ts vendored

@ -0,0 +1,2 @@
declare const _default: import("vite").UserConfig;
export default _default;

@ -0,0 +1,23 @@
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
// https://vite.dev/config/
export default defineConfig({
server: {
open: true,
host: '0.0.0.0',
fs: {
strict: true,
},
proxy: {
"/jsonrpc": {
secure: false,
headers: {
Referer: 'https://lamp.ruixininfo.com'
},
target: 'https://lamp.ruixininfo.com',
changeOrigin: true
},
}
},
plugins: [vue()],
});

@ -0,0 +1,26 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/
export default defineConfig({
server: {
open: true,
host: '0.0.0.0',
fs: {
strict: true,
},
proxy:
{
"/jsonrpc": {
secure: false,
headers: {
Referer: 'https://lamp.ruixininfo.com'
},
target: 'https://lamp.ruixininfo.com',
changeOrigin: true
},
}
},
plugins: [vue()],
})
Loading…
Cancel
Save