diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9c843e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/back-end/node_modules diff --git a/.trae/rules/bai.md b/.trae/rules/bai.md new file mode 100644 index 0000000..c46873d --- /dev/null +++ b/.trae/rules/bai.md @@ -0,0 +1,30 @@ +--- +alwaysApply: false +--- + +# 角色与核心哲学 (Role & Core Philosophy) +你是一名具备全栈思维的全语言工程师(Polyglot Engineer) + +--- +# 绝对拦截规则 (Execution Barriers) +任何时候,当你要开始编写、修改、重构或输出具体业务`代码`前,必须且只能先完成 `` + +--- +# (强制状态流转) +### Step 1: 核心流 - 环境检查与锚点确认 (强制第一步) +- 动作:扫描当前工作区文件 +- 判断逻辑: + - IF 发现 `openspec` 相关配置 或 `spec/` 目录存在 -> 判定为规范已初始化,进入 Step 2 + - IF NOT -> 必须触发初始化流程: + - 优先检查 Node 生态:判断是否有 `package.json`。若无,优先建议执行 `npm init`。 + - 强烈建议并通过 `npx` 相关的命令来初始化规范工具,坚决避免全局污染 +### Step 2: 规格流 - 提案与生成 (强制第二步) +- 动作:在构思架构前,必须调用或输出以下内容: + - `openspec-proposal`:必须优先检索并推荐 npm 仓库中成熟的开源包,拒绝无意义的重复造轮子。将包名和版本策略写入提案 + - `openspec-apply`:生成详细技术规格时,优先采用 Node.js 的最佳实践 +# 强制交互格式 (Interaction Protocol) +内部逻辑思考使用英文,用户交互使用专业、简洁的中文 +在调用任何 Skill、执行命令、或开始输出任何代码前,你的第一条回复必须严格按照以下格式向用户播报: +> 执行前置检查... +> - 当前生态:[强调 Node.js 与 npm 的介入策略,或说明当前的替代环境] +> - 当前动作:[例如:正在调用 openspec-proposal 规划架构...] \ No newline at end of file diff --git a/back-end/.env b/back-end/.env new file mode 100644 index 0000000..63d18de --- /dev/null +++ b/back-end/.env @@ -0,0 +1,15 @@ +# Server Configuration +PORT=3000 + +# Environment +NODE_ENV=development + +# API Configuration +API_PREFIX=/api + +# Database Configuration (placeholder) +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=bai_management +DB_USER=postgres +DB_PASSWORD=password \ No newline at end of file diff --git a/back-end/package-lock.json b/back-end/package-lock.json new file mode 100644 index 0000000..13d55bc --- /dev/null +++ b/back-end/package-lock.json @@ -0,0 +1,2039 @@ +{ + "name": "web-bai-manage-api-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "web-bai-manage-api-server", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "dotenv": "^16.3.1", + "express": "^4.18.2" + }, + "devDependencies": { + "@fission-ai/openspec": "^1.0.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@fission-ai/openspec": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@fission-ai/openspec/-/openspec-1.2.0.tgz", + "integrity": "sha512-2XDmPZcVY0Bs014lP9aoxe3VoEU8hFvqaBFxQaiJO2nhC8vTKCyo6sT/5YpQcOTfR/a64Hht2anTyqLR4eNhlg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.2", + "@inquirer/prompts": "^7.8.0", + "chalk": "^5.5.0", + "commander": "^14.0.0", + "fast-glob": "^3.3.3", + "ora": "^8.2.0", + "posthog-node": "^5.20.0", + "yaml": "^2.8.2", + "zod": "^4.0.17" + }, + "bin": { + "openspec": "bin/openspec.js" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmmirror.com/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmmirror.com/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmmirror.com/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmmirror.com/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmmirror.com/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmmirror.com/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmmirror.com/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmmirror.com/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmmirror.com/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmmirror.com/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@posthog/core": { + "version": "1.23.4", + "resolved": "https://registry.npmmirror.com/@posthog/core/-/core-1.23.4.tgz", + "integrity": "sha512-gSM1gnIuw5UOBUOTz0IhCTH8jOHoFr5rzSDb5m7fn9ofLHvz3boZT1L1f+bcuk+mvzNJfrJ3ByVQGKmUQnKQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmmirror.com/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmmirror.com/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/posthog-node": { + "version": "5.28.2", + "resolved": "https://registry.npmmirror.com/posthog-node/-/posthog-node-5.28.2.tgz", + "integrity": "sha512-a+unFAKU8Vtez1DAEgCXB/KOZbroQZE+GvnSr9B35u3uMUxtyPO5ulgLJo8AUcZ4prhv6ia8R1Xjr4BrxPfdsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@posthog/core": "1.23.4" + }, + "engines": { + "node": "^20.20.0 || >=22.22.0" + }, + "peerDependencies": { + "rxjs": "^7.0.0" + }, + "peerDependenciesMeta": { + "rxjs": { + "optional": true + } + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmmirror.com/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/back-end/package.json b/back-end/package.json new file mode 100644 index 0000000..e1d5445 --- /dev/null +++ b/back-end/package.json @@ -0,0 +1,26 @@ +{ + "name": "web-bai-manage-api-server", + "version": "1.0.0", + "description": "Backend API server for BAI Management System", + "main": "src/index.js", + "scripts": { + "dev": "node src/index.js", + "build": "echo 'No build needed for backend'", + "test": "echo 'No tests implemented yet'", + "lint": "echo 'No linting implemented yet'", + "spec:lint": "npx @fission-ai/openspec lint spec/", + "spec:validate": "npx @fission-ai/openspec validate spec/" + }, + "dependencies": { + "express": "^4.18.2", + "dotenv": "^16.3.1" + }, + "devDependencies": { + "@fission-ai/openspec": "^1.0.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "author": "", + "license": "ISC" +} \ No newline at end of file diff --git a/back-end/spec/openapi.yaml b/back-end/spec/openapi.yaml new file mode 100644 index 0000000..58783ee --- /dev/null +++ b/back-end/spec/openapi.yaml @@ -0,0 +1,48 @@ +openapi: 3.1.0 +info: + title: BAI Management API + description: Backend API for BAI Management System + version: 1.0.0 +servers: + - url: http://localhost:3000 + description: Development server +paths: + /test-helloworld: + get: + summary: Test endpoint + description: Returns a hello world message + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! + timestamp: + type: string + format: date-time + status: + type: string + example: success + /health: + get: + summary: Health check + description: Checks if the server is running + responses: + '200': + description: Server is healthy + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: healthy + timestamp: + type: string + format: date-time \ No newline at end of file diff --git a/back-end/src/index.js b/back-end/src/index.js new file mode 100644 index 0000000..00b1b9d --- /dev/null +++ b/back-end/src/index.js @@ -0,0 +1,35 @@ +const express = require('express'); +const dotenv = require('dotenv'); + +// 加载环境变量 +dotenv.config(); + +const app = express(); +const port = process.env.PORT || 3000; + +// 解析JSON请求体 +app.use(express.json()); + +// 测试接口 +app.get('/test-helloworld', (req, res) => { + res.json({ + message: 'Hello, World!', + timestamp: new Date().toISOString(), + status: 'success' + }); +}); + +// 健康检查接口 +app.get('/health', (req, res) => { + res.json({ + status: 'healthy', + timestamp: new Date().toISOString() + }); +}); + +// 启动服务器 +app.listen(port, () => { + console.log(`Server running on port ${port}`); + console.log(`Test endpoint: http://localhost:${port}/test-helloworld`); + console.log(`Health check: http://localhost:${port}/health`); +}); \ No newline at end of file diff --git a/docs/project.md b/docs/project.md new file mode 100644 index 0000000..915af91 --- /dev/null +++ b/docs/project.md @@ -0,0 +1,86 @@ +开发框架约束(供 AI 创建项目使用) + +目的:本文件用于约束 AI 在创建/改造项目时的技术选型、目录结构、工程化与交付流程。除非明确得到人工指令,否则 AI 不得偏离本文件的约束。 + +1. 运行环境与基础约束 + +- Node.js 版本:必须使用 Node.js 22+(建议使用最新 LTS)。 +- 主要语言:JavaScript(.js)为主。 +- 允许在必要时引入类型检查方案(例如 JSDoc + // @ts-check),但默认不将 TypeScript 作为主要语言。 +- 包管理器:**强制统一使用 `npm`**。 +- 跨平台:默认需兼容 Windows(PowerShell)与类 Unix 环境。 + +2. 技术栈约束 + +2.1 前端 + +- 框架:必须使用 Vue 3.x。 +- 生态库:仅引入与 Vue 3.x 兼容的相关库;避免引入与 Vue 2.x 绑定的历史库。 +- 构建工具:优先 Vite(如与既有工程冲突,需说明原因并保持一致性)。 + +2.2 后端 + +- 运行时:必须使用 Node.js。 +- 语言:后端同样以 JavaScript 为主。 +- API 风格:默认使用 HTTP JSON API(如采用 GraphQL/WebSocket 等需明确说明并仍遵循 OpenSpec 约束)。 + +3. OpenSpec(规范驱动)开发流程约束 + +> 说明:这里的 OpenSpec 指通过全局安装 `@fission-ai/openspec` 获得的规范驱动工具链;在 API 场景下,接口契约必须使用并遵守 OpenAPI 3.1。两者不冲突:OpenSpec 用于驱动/校验流程,OpenAPI 3.1 是规范文件中必须满足的契约。 + +3.0 OpenSpec 工具链安装(强制) + +- 开发与 CI 环境必须确保可用的 OpenSpec 工具链: + - 安装命令:npm install -g @fission-ai/openspec@latest +- AI 在生成项目脚本时: + - 必须将规范校验能力接入到 npm scripts(见 3.3)。 + - 不得绕过 OpenSpec 校验直接交付“未受规范约束”的 API 实现。 +3.1 必须交付的规范产物 + +- 项目必须包含一个可追溯的规范文件: + - API 项目:`spec/openapi.yaml`(或 `spec/openapi.json`),版本 OpenAPI 3.1。 +- 非 API 项目:仍需提供对应的“规格说明”(例如流程/数据结构/输入输出契约),放在 spec/ 目录下。 +- 规范文件需满足: + - 可被校验(lint/validate) + - 与实现一致(实现变更必须同步更新规范) +3.2 开发顺序(强制) + +1. 先写/更新规范(spec-first):在新增/修改功能前,先更新 `spec/` 下的规范。 +2. 再实现:实现必须与规范一致。 +3. 再验证:CI/本地脚本必须包含规范校验步骤。 +4. 再文档化:README 中必须说明如何查看/使用规范与如何运行校验。 + +3.3 规范校验与联动(强制) + +- 必须提供脚本(示例命名,可按项目调整但不可缺失): + - npm run spec:lint:调用 OpenSpec 对 spec/ 做 lint(具体 CLI 参数以 openspec --help 为准) + - npm run spec:validate:调用 OpenSpec 对 spec/ 做结构/引用/契约校验(具体 CLI 参数以 openspec --help 为准) +- 若为 API: + - 必须在实现层提供请求/响应校验或至少在测试阶段进行契约校验。 +- 鼓励(非强制)从 OpenAPI 生成 client/server stub 或生成类型定义,但不得改变“JS 为主语言”的前提。 +4. 工程结构约束(建议默认) + +AI 创建项目时,默认使用以下结构;如项目类型不适用,可在不违背约束的前提下做最小调整。 + +- spec/:OpenSpec 规范(OpenAPI 或其他规格说明) +- src/:源代码 +- tests/:测试 +- scripts/:工程脚本(构建/校验/生成等) +- README.md:必须包含运行、测试、规范使用方式 +5. 质量与交付约束(强制) + +- 必须提供基础脚本: + - npm run dev(如可交互开发) + - npm run build(如需要构建) + - npm run test + - npm run lint +- 变更要求: + - 修改实现时同步更新 spec/ 与测试。 + - 不得只改实现不改规范;也不得只改规范不改实现。 +6. AI 行为约束(强制) + +- 若用户需求与本文件冲突: + - 先指出冲突点,并请求用户确认是否允许偏离约束。 +- 未明确要求时: + - 不引入与约束无关的“额外页面/功能/组件/花哨配置”。 + - 保持最小可用、可验证、可维护的实现。 \ No newline at end of file diff --git a/front-end/.browserslistrc b/front-end/.browserslistrc new file mode 100644 index 0000000..dc3bc09 --- /dev/null +++ b/front-end/.browserslistrc @@ -0,0 +1,4 @@ +> 1% +last 2 versions +not dead +not ie 11 diff --git a/front-end/.editorconfig b/front-end/.editorconfig new file mode 100644 index 0000000..4039ff1 --- /dev/null +++ b/front-end/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/front-end/.env b/front-end/.env new file mode 100644 index 0000000..7e902d8 --- /dev/null +++ b/front-end/.env @@ -0,0 +1,104 @@ +# Vab Admin 系列产品受国家计算机软件著作权保护(证书号:软著登字第 7051316 号)。 +# 关于举报盗版侵权:请发送举报材料至我司客服邮箱1204505056@qq.com,一经查实,官司所得收入20%归举报人所有,80%归律师事务所所有。 +# Vue Admin系列产品购买地址://vuejs-core.cn/authorization +# 1.购买者可将授权后的产品用于任意「符合国家法律法规」的应用平台,禁止用于黄赌毒等危害国家安全与稳定的网站。 +# 2.购买主体购买后可用于开发商业项目,不限制域名和项目数量,购买主体不可将源码分享第三方,否则我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。 +# 3.购买者务必尊重知识产权,严格保证不恶意传播产品源码、不得直接对授权的产品本身进行二次转售或倒卖、开源、不得对授权的产品进行简单包装后声称为自己的产品等,无论有意或无意,我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。 +# 4.购买者不可将vip群文档及资料分享给第三方,否则我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。 +# 5.购买者购买项目不可以用来构建存在竞争性质的产品并直接对外销售否则我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。 +# 6.购买者购买项目中的源码(包含全部源码、及部分源码片段)不可以用于任何形式的开源项目,否则我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。 +# 7.用于公司的项目商用时购买需提供公司名称,用于证明购买过我们的项目来用于商业用途,防范法律风险,我们不会将【购买公司】信息泄漏到互联网或告知第三方。 +# 8.用于个人学习需提供姓名、联系方式。 +# 9.如用于外包项目,购买者购买项目中的源码不可直接对外出售,npm run build编译后的项目不受限制。 +# 10.虚拟物品不支持退货退款。 +# 11.最终解释权归vab系列著作权人所有。 + + +# 第1步:请在此处将test变更为您的github用户名,请务必填写购买时绑定的github用户名,同一个授权配置不同用户名会导致您的授权永久失效 +VUE_GITHUB_USER_NAME=MomoWen + +# 第2步:请在项目根目录新建一个.env.local的新文件,切记是新建空的文件不是直接拷贝.env文件的内容 + +# 第3步:.env.local的文件只能有一行不可以换行,购买时生成,格式如下:VUE_APP_SECRET_KEY=XXXXXXX + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 以下内容不建议修改建议将VUE_APP_SECRET_KEY配置到【.env.local】中 +VUE_APP_SECRET_KEY=preview + + + diff --git a/front-end/.env.development b/front-end/.env.development new file mode 100644 index 0000000..e761cce --- /dev/null +++ b/front-end/.env.development @@ -0,0 +1,4 @@ +# 开发环境,VUE_APP_BASE_URL可以选择自己配置成需要的接口地址,如"https://api.xxx.com" +# 此文件修改后需要重启项目 +NODE_ENV=development +VUE_APP_BASE_URL='/vab-mock-server' \ No newline at end of file diff --git a/front-end/.env.production b/front-end/.env.production new file mode 100644 index 0000000..f277a08 --- /dev/null +++ b/front-end/.env.production @@ -0,0 +1,4 @@ +# 生产环境,VUE_APP_BASE_URL可以选择自己配置成需要的接口地址,如"https://api.xxx.com" +# 此文件修改后需要重启项目 +NODE_ENV=production +VUE_APP_BASE_URL='/vab-mock-server' \ No newline at end of file diff --git a/front-end/.env.test b/front-end/.env.test new file mode 100644 index 0000000..0fedb68 --- /dev/null +++ b/front-end/.env.test @@ -0,0 +1,4 @@ +# 测试环境,VUE_APP_BASE_URL可以选择自己配置成需要的接口地址,如"https://api.xxx.com" +# 此文件修改后需要重启项目 +NODE_ENV=production +VUE_APP_BASE_URL='/vab-mock-server' \ No newline at end of file diff --git a/front-end/.eslintignore b/front-end/.eslintignore new file mode 100644 index 0000000..809af1b --- /dev/null +++ b/front-end/.eslintignore @@ -0,0 +1,7 @@ +library/build/vuePlugins/components.d.ts +node_modules +src/assets +src/icons +public +dist +vab-icons diff --git a/front-end/.eslintrc.js b/front-end/.eslintrc.js new file mode 100644 index 0000000..de349d8 --- /dev/null +++ b/front-end/.eslintrc.js @@ -0,0 +1,81 @@ +const { defineConfig } = require('eslint-define-config') + +module.exports = defineConfig({ + root: true, + env: { + node: true, + browser: true, + }, + globals: { + defineOptions: 'writable', + }, + parser: 'vue-eslint-parser', + parserOptions: { + parser: '@typescript-eslint/parser', + sourceType: 'module', + ecmaVersion: 2020, + }, + rules: { + 'import-x/order': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-this-alias': 'off', + 'array-callback-return': 'off', + 'escape-case': 'off', + 'eslint-comments/no-unlimited-disable': 'off', + 'import/order': 'off', + 'no-alert': 'off', + 'no-console': 'off', + 'no-debugger': 'off', + 'no-restricted-imports': 'off', + 'no-return-await': 'off', + 'prefer-const': 'off', + 'prefer-template': 'error', + 'unicorn/consistent-function-scoping': 'off', + 'unicorn/escape-case': 'off', + 'unicorn/filename-case': 'off', + 'unicorn/import-style': 'off', + 'unicorn/no-abusive-eslint-disable': 'off', + 'unicorn/no-array-callback-reference': 'off', + 'unicorn/no-array-for-each': 'off', + 'unicorn/no-array-reduce': 'off', + 'unicorn/no-nested-ternary': 'off', + 'unicorn/no-null': 'off', + 'unicorn/no-object-as-default-parameter': 'off', + 'unicorn/no-process-exit': 'off', + 'unicorn/no-this-assignment': 'off', + 'unicorn/numeric-separators-style': 'off', + 'unicorn/prefer-array-some': 'off', + 'unicorn/prefer-default-parameters': 'off', + 'unicorn/prefer-dom-node-append': 'off', + 'unicorn/prefer-dom-node-remove': 'off', + 'unicorn/prefer-logical-operator-over-ternary': 'off', + 'unicorn/prefer-math-trunc': 'off', + 'unicorn/prefer-module': 'off', + 'unicorn/prefer-number-properties': 'off', + 'unicorn/prefer-query-selector': 'off', + 'unicorn/prefer-spread': 'off', + 'unicorn/prefer-string-slice': 'off', + 'unicorn/prefer-structured-clone': 'off', + 'unicorn/prefer-ternary': 'off', + 'unicorn/prefer-top-level-await': 'off', + 'unicorn/prevent-abbreviations': 'off', + 'unicorn/expiring-todo-comments': 'off', + 'unicorn/consistent-destructuring': 'off', + 'vue/multi-word-component-names': 'off', + 'vue/no-reserved-component-names': 'off', + 'vue/no-setup-props-destructure': 'off', + 'vue/no-v-html': 'off', + 'vue/require-default-prop': 'off', + 'unicorn/number-literal-case': 'off', + '@typescript-eslint/no-var-requires': 'off', + 'import/first': 'off', + 'object-shorthand': 'off', + 'unicorn/no-console-spaces': 'off', + 'unicorn/prefer-dom-node-text-content': 'off', + 'unicorn/prefer-code-point': 'off', + '@typescript-eslint/consistent-type-imports': 'off', + camelcase: 'off', + }, +}) diff --git a/front-end/.gitattributes b/front-end/.gitattributes new file mode 100644 index 0000000..8907e9b --- /dev/null +++ b/front-end/.gitattributes @@ -0,0 +1,17 @@ +*.html text eol=lf +*.css text eol=lf +*.js text eol=lf +*.ts text eol=lf +*.scss text eol=lf +*.vue text eol=lf +*.hbs text eol=lf +*.sh text eol=lf +*.md text eol=lf +*.json text eol=lf +*.yml text eol=lf +.browserslistrc text eol=lf +.editorconfig text eol=lf +.eslintignore text eol=lf +.gitattributes text eol=lf +LICENSE text eol=lf +*.conf text eol=lf diff --git a/front-end/.gitignore b/front-end/.gitignore new file mode 100644 index 0000000..63197e3 --- /dev/null +++ b/front-end/.gitignore @@ -0,0 +1,46 @@ +.DS_Store +node_modules +node_modules.nosync +/dist + +# local env files +.env.local +.env.*.local + +# Log files +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Editor directories and files +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Lock files +yarn.lock +pnpm-lock.yaml +package-lock.json + +# Yarn v2 not using using Zero-Installs +.yarn/* +#!.yarn/cache +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions +.pnp.* + +# Vab +public/video +*.zip +*.7z +*.rar +/.history diff --git a/front-end/.npmrc b/front-end/.npmrc new file mode 100644 index 0000000..bf2e764 --- /dev/null +++ b/front-end/.npmrc @@ -0,0 +1 @@ +shamefully-hoist=true diff --git a/front-end/.prettierignore b/front-end/.prettierignore new file mode 100644 index 0000000..34f33b7 --- /dev/null +++ b/front-end/.prettierignore @@ -0,0 +1,4 @@ +auto-imports.d.ts +components.d.ts +index.html +website.html diff --git a/front-end/.stylelintrc.js b/front-end/.stylelintrc.js new file mode 100644 index 0000000..a70b6d7 --- /dev/null +++ b/front-end/.stylelintrc.js @@ -0,0 +1,18 @@ +module.exports = { + extends: [ + 'stylelint-config-recommended-scss', + 'stylelint-config-recommended-vue', + 'stylelint-config-recess-order', + ], + rules: { + 'no-empty-source': null, + 'at-rule-no-unknown': null, + 'property-no-unknown': null, + 'function-no-unknown': null, + 'selector-class-pattern': null, + 'no-descending-specificity': null, + 'scss/no-global-function-names': null, + 'selector-pseudo-class-no-unknown': null, + }, + ignoreFiles: ['dist/**/*', 'public/index.html'], +} diff --git a/front-end/AI-Coding/00-Quick-Profile.md b/front-end/AI-Coding/00-Quick-Profile.md new file mode 100644 index 0000000..7159b81 --- /dev/null +++ b/front-end/AI-Coding/00-Quick-Profile.md @@ -0,0 +1,9 @@ +# 00. 快速画像(给 AI 的 30 秒摘要) + +- **技术栈**:`Vue 3` + `Vue CLI 5` + `TypeScript` + `Pinia` + `Vue Router` + `Element Plus` +- **入口链路**:`src/main.ts` → `setupVab(app)` → `setupI18n(app)` → `setupStore(app)` → `setupRouter(app)` +- **Mock**:开发环境由 `vue.config.js` 将 `mock/index.js` 挂载到 devServer 中间件;路由集合来自 `mock/controller/*` +- **别名**:`@`→`src`,`@vab`→`library`,`~`→根目录,`/#`→`types` +- **用途定位**:既是可运行的后台 Demo,也是可拆分的能力集合(`library/layouts/ library/components/ library/ src/utils/ mock/ src/api/ types/`) + +> 推荐阅读顺序:本文件 → [01-Directory-Map.md](01-Directory-Map.md) → [AI-Reuse-Playbook.md](AI-Reuse-Playbook.md) diff --git a/front-end/AI-Coding/01-Directory-Map.md b/front-end/AI-Coding/01-Directory-Map.md new file mode 100644 index 0000000..751ac26 --- /dev/null +++ b/front-end/AI-Coding/01-Directory-Map.md @@ -0,0 +1,51 @@ +# 01. 仓库地图(Directory Map) + +> 目标:让 AI 快速定位“要复用的能力”在哪个目录、入口是什么、依赖闭包有哪些。 + +## `src/`(应用主代码) + +- `src/main.ts`:应用入口;决定 mock/pwa 与各子系统初始化顺序 +- `src/App.vue`:根组件;包含生产环境禁调试逻辑(`disable-devtool`) +- `src/router/`:路由与权限控制(重点:`index.ts`、`permissions`) +- `src/store/`:Pinia 初始化与业务 store 模块 +- `src/i18n/`:国际化初始化、语言获取与翻译辅助 +- `src/api/`:按域拆分 API 调用函数(示例:`src/api/router.ts`) +- `src/utils/`:通用工具与基础能力(时间格式化、树结构转换、UUID 等) +- `src/views/`:业务页面集合(通常与路由对应) +- `src/icon/`:图标加载(由 `library/index.ts` 里的 `import '@/icon'` 引入) +- `src/config/`:运行时/构建期配置聚合导出(`vue.config.js` 会 `require('./src/config')`) + +## `library/`(模板基础库) + +- `library/index.ts`:`setupVab(app)` 的实现: + - 全局样式:`library/styles/vab.scss` + - 图标注册:`vab-icons` + `@element-plus/icons-vue` + - 背景样式:`library/styles/background/*.scss` + - 插件加载:`library/plugins/**/*.ts` 并 `app.use()` +- `library/build/`:构建期扩展(webpack chain、vue plugins) + +## `library/plugins/`(全局插件) + +- `vab.ts`:全局注入(gp)与事件总线(`$pub/$sub/$unsub`) +- `directive.ts`:指令(`v-permissions`) +- `errorLog.ts`:全局错误捕获(`app.config.errorHandler`) +- `support.ts`:构建信息输出/依赖检查 + +## `library/components/`(可复用 UI 组件库) + +- `Vab*` 前缀:约定为基础组件/布局子组件(菜单、tabs、header、breadcrumb 等) +- `VabTheme`:主题系统入口(主题抽屉/设置面板),依赖 `$pub/$sub` 与 settings store + +## `library/layouts/`(布局体系) + +- `library/layouts/index.vue`:主 Layout 入口(路由壳) +- `library/layouts/VabLayout*`:多套布局可选(垂直/横向/综合/浮动/分栏等) + +## `mock/`(开发期 Mock) + +- `mock/index.js`:devServer 中间件;读取并注册 `mock/controller/*` +- `mock/controller/*.js`:每个文件导出路由数组(`url/type/response`) + +## `types/`(类型声明) + +- `types/*.d.ts`:路由/store/theme/vab 等声明,配合 `/#` 别名使用 diff --git a/front-end/AI-Coding/02-Boot-Sequence.md b/front-end/AI-Coding/02-Boot-Sequence.md new file mode 100644 index 0000000..cf6dd0f --- /dev/null +++ b/front-end/AI-Coding/02-Boot-Sequence.md @@ -0,0 +1,28 @@ +# 02. 关键入口与初始化顺序(Boot Sequence) + +## 入口文件:`src/main.ts` + +AI 需要知道“系统从哪里开始、按什么顺序挂载能力”。关键点: + +1. `validateSecretKey()`:启动前校验(与授权/环境有关) +2. 生产环境 Mock: + - 条件:`process.env.NODE_ENV === 'production'` 且 `baseURL` 不是外链(`!isExternal(baseURL)`) + - 动作:`require('@/utils/static').mockXHR()` +3. 初始化链路: + - `setupVab(app)`(全局样式、图标、插件) + - `setupI18n(app)` + - `setupStore(app)` + - `setupRouter(app).isReady().then(mount)` + +## 全局插件聚合:`library/index.ts` + +`setupVab(app)` 做了这些事: + +- 引入全局样式:`library/styles/vab.scss` +- 注册图标: + - `VabIcon`(来自 `vab-icons`) + - `@element-plus/icons-vue` 全量组件注册 +- 自动加载背景样式:`library/styles/background/*.scss` +- 自动加载插件:`library/plugins/**/*.ts`(默认导出 Vue 插件) + +> 复用提示:自动全量加载在“抽取模块”时容易带入不必要依赖;迁移时应以“最小闭包”为原则(见 [AI-Reuse-Playbook.md](AI-Reuse-Playbook.md))。 diff --git a/front-end/AI-Coding/03-Mock-System.md b/front-end/AI-Coding/03-Mock-System.md new file mode 100644 index 0000000..89b7f13 --- /dev/null +++ b/front-end/AI-Coding/03-Mock-System.md @@ -0,0 +1,23 @@ +# 03. Mock 系统(开发联调与脱网演示) + +## 挂载方式 + +- `vue.config.js`:`devServer.setupMiddlewares = require('./mock')` +- `mock/index.js`: + - 将路由集合转换为 express 形式 + - 使用 `mockjs` 返回数据 + - 监听 `mock/` 变更并热更新(`chokidar`) + +## 路由定义约定(供 AI 生成/扩展) + +在 `mock/controller/*.js` 中,每个路由对象通常包含: + +- `url`:不含 `baseURL` 前缀的路径(中间件会自动拼上 `baseURL`) +- `type`:`get` / `post` / ... +- `response(req, res)`:返回对象或函数 + +## AI 新增业务 API 的推荐动作 + +1) 在 `src/api/.ts` 新增函数(调用 `request({ url, method })`) +2) 在 `mock/controller/.js` 新增对应 mock 路由,路径保持一致 +3) 在模块规格中补充接口契约(建议写到 `openspec-lite/modules/.yaml`) diff --git a/front-end/AI-Coding/04-Router-and-Views.md b/front-end/AI-Coding/04-Router-and-Views.md new file mode 100644 index 0000000..7820f1f --- /dev/null +++ b/front-end/AI-Coding/04-Router-and-Views.md @@ -0,0 +1,46 @@ +# 04. 路由与页面组织(Router & Views) + +- 路由文件:`src/router/index.ts` +- 核心结构: + - `constantRoutes`:登录/注册/404 等公共路由 + - `asyncRoutes`:业务路由,通常以 `Layout` 为根节点容器 + +## Views 目录约定(src/views) + +本仓库页面主要集中在: + +- `src/views/index/`:工作台/仪表盘等示例首页 +- `src/views/vab/`:组件/表单/表格等示例页(演示用途居多) +- `src/views/other/`:杂项示例页(iframe/excel/drag/nested 等) +- `src/views/setting/`:后台管理相关页面(用户/角色/部门/菜单/字典/任务/日志等) +- `src/views/tools/`:工具类页面(如 EyeDropper/SpeechSynthesis) +- `src/views/login/`、`src/views/register/`、`src/views/callback/`:登录链路 +- `src/views/403.vue`、`src/views/404.vue`:错误页 + +路由对页面的引用方式以动态导入为主:`component: () => import('@/views/...')`。 + +## meta 约定(给菜单/面包屑/行为用) + +常见 meta 字段(与组件实现强相关): + +- `meta.title`:菜单/面包屑/页面标题(`VabLanguage` 会用它更新 `document.title`) +- `meta.icon` + `meta.isCustomSvg`:菜单与面包屑图标(`VabMenu`/`VabBreadcrumb`) +- `meta.hidden`:是否从菜单隐藏(`VabMenu`) +- `meta.breadcrumbHidden`:是否从面包屑隐藏(`VabBreadcrumb`) +- `meta.noColumn`:Column 布局下的特殊处理(`VabColumnBar` 会折叠菜单并隐藏 fold 按钮) +- `meta.target`(如 `_blank`):点击菜单时打开方式(`VabMenuItem`) +- `meta.badge` / `meta.dot`:菜单右侧标记(`VabMenuItem`) + +## AI 生成新模块页面的最小流程 + +- 在 `src/views//` 创建页面组件 +- 在 `src/router/index.ts`(或其拆分文件)添加路由记录 +- 如涉及权限/菜单:同步 `meta`(`title/icon/guard/...`) + +建议:新增页面后运行一次 [AI-Coding/Validation.md](AI-Coding/Validation.md) 里的“router ↔ views 对账”脚本,防止路由引用了不存在的 .vue,或新页面忘记挂到路由上。 + +## 验收点 + +- 访问路由能渲染页面 +- 菜单与面包屑符合 `meta` 约定 +- 刷新后路由仍可恢复 diff --git a/front-end/AI-Coding/05-Store.md b/front-end/AI-Coding/05-Store.md new file mode 100644 index 0000000..381d1c4 --- /dev/null +++ b/front-end/AI-Coding/05-Store.md @@ -0,0 +1,16 @@ +# 05. 状态管理(Pinia Store) + +- `src/store/index.ts`:`createPinia()` 与 `app.use(pinia)` +- i18n 语言读取:`src/i18n/index.ts` 通过 `useSettingsStore(pinia)` 获取语言 + +## AI 提取/迁移注意 + +- 迁移 `src/store/index.ts` 时,通常也需要迁移: + - `src/store/modules/settings.ts`(见 `12-Settings-Store.md` / `openspec-lite/modules/store-settings.yaml`) + - `src/store/modules` 下其它基础模块(见 `14-Store-Modules.md`) + - 与其相关的类型(`types/`,即 `/#` 别名指向的目录) + +## 验收点 + +- Store 能正常注入 +- 关键 store(如 settings)能在不报错情况下读取 diff --git a/front-end/AI-Coding/06-i18n.md b/front-end/AI-Coding/06-i18n.md new file mode 100644 index 0000000..440c179 --- /dev/null +++ b/front-end/AI-Coding/06-i18n.md @@ -0,0 +1,17 @@ +# 06. 国际化(i18n) + +- 文件:`src/i18n/index.ts` +- 关键点: + - `legacy: false`(Composition API 模式) + - 内置 `en`,`zh` 为空对象(等待补充) + - 提供 `translate(message)` 辅助函数 + - 导出 Element Plus 语言包:`enLocale` / `zhLocale` + +## 扩展建议 + +- 新增语言:在 `src/i18n/locales/` 添加 `xx.json` 并合并到 `messages` + +## 验收点 + +- 切换语言后页面能按预期渲染 +- Element Plus 语言包切换有效 diff --git a/front-end/AI-Coding/07-API.md b/front-end/AI-Coding/07-API.md new file mode 100644 index 0000000..6b8a2ca --- /dev/null +++ b/front-end/AI-Coding/07-API.md @@ -0,0 +1,17 @@ +# 07. API 层(请求封装与域拆分) + +- 示例:`src/api/router.ts` 调用 `request({ url: '/router/getList', method: 'get' })` +- 请求封装:`src/utils/request.ts`(axios 拦截器、token 注入、401/402/403 处理、错误提示) +- 刷新令牌:`src/api/refreshToken.ts`(402 时重试队列) +- 请求异常入库:通过 `@vab/plugins/errorLog`(见 `library/plugins/errorLog.ts`) + +## AI 复用原则 + +- 以“域”为单位抽取:`src/api/.ts` +- 抽取前先定位 `request` 封装(通常在 `src/utils/request` 或相近位置) +- 若新项目变更 baseURL/token/error 规范,优先在规格里声明差异,然后再做迁移适配 + +## 验收点 + +- 基础请求能发起 +- 错误处理与鉴权逻辑符合项目约束 diff --git a/front-end/AI-Coding/08-Alias-and-Types.md b/front-end/AI-Coding/08-Alias-and-Types.md new file mode 100644 index 0000000..8cda12b --- /dev/null +++ b/front-end/AI-Coding/08-Alias-and-Types.md @@ -0,0 +1,20 @@ +# 08. Alias 与类型(迁移必须同步) + +## Alias + +- `@` → `src` +- `~` → 根目录 +- `/#` → `types` +- `@vab` → `library` +- `@gp` → `library/plugins/vab` + +来源:`vue.config.js` 与 `tsconfig.json`。 + +## 类型声明 + +- `types/*.d.ts`:路由/store/theme/vab 等声明 + +## 迁移到不同构建工具(如 Vite)时的最低要求 + +- alias 需要在新工具里重新配置 +- `types/` 需要进入 tsconfig 的 `include` diff --git a/front-end/AI-Coding/09-Plop.md b/front-end/AI-Coding/09-Plop.md new file mode 100644 index 0000000..1fd951d --- /dev/null +++ b/front-end/AI-Coding/09-Plop.md @@ -0,0 +1,15 @@ +# 09. 代码生成(Plop) + +- 文件:`plopfile.js` +- 生成器:`view` / `curd` / `component` / `mock&api` + +运行: + +```powershell +pnpm run template +``` + +## AI 使用建议 + +- 如果后续 AI 以“生成代码”为策略,可优先复用 plop 模板,而不是从零写 +- 新增生成器前,先在 `openspec-lite/project.yaml` 里补充命名/目录约束 diff --git a/front-end/AI-Coding/10-Theme-System.md b/front-end/AI-Coding/10-Theme-System.md new file mode 100644 index 0000000..8d5dbb6 --- /dev/null +++ b/front-end/AI-Coding/10-Theme-System.md @@ -0,0 +1,38 @@ +# 10. Theme 系统(VabTheme + settings) + +> 目标:让 AI 在抽取/复用 Layout 时,不会漏掉主题抽屉、事件总线与 settings 的主题变量闭包。 + +## 入口与组成 + +- 主题入口组件:`library/components/VabTheme/index.vue` +- 主题抽屉:`library/components/VabTheme/components/VabThemeDrawer.vue` +- 主题设置入口:`library/components/VabTheme/components/VabThemeSetting.vue` +- 主题状态与持久化:`src/store/modules/settings.ts` + +## 关键机制 + +- 事件总线:通过 `library/plugins/vab.ts` 注入 `$pub/$sub/$unsub` + - 打开抽屉:`$pub('theme')` + - 随机换肤:`$pub('random-theme')` +- 主题应用:`useSettingsStore().updateTheme()` + - 动态 `require(@vab/styles/variables/vab-*-variables.module.scss)` + - 将 `vab-` 前缀变量映射到 `--el-` CSS 变量 + - 设置 `body` class(`vab-theme-*`)与背景类 + +## 最小依赖闭包(抽取时必须带上) + +- `library/components/VabTheme/` +- `src/store/modules/settings.ts`(至少 theme/showTheme/saveTheme/resetTheme/updateTheme) +- `library/plugins/vab.ts`(提供 `$pub/$sub` 与 `$baseLoading`) +- `library/styles/variables/`(所有 `vab-*-variables.module.scss`) +- Element Plus + i18n(组件内有 `translate()` 与 el-* 组件) + +## 验收点(smoke) + +- 触发 `$pub('theme')` 抽屉能打开/关闭 +- 保存/重置主题后,刷新页面主题能持久化且变量生效 + +## 常见坑 + +- `updateTheme()` 使用 webpack 风格的动态 `require`:迁移到非 webpack 构建(如部分 Vite 场景)需要等效实现 +- 主题抽屉/设置依赖事件总线注入:未安装 `vab` 插件时会“看起来渲染了但不工作” diff --git a/front-end/AI-Coding/11-Plugins-System.md b/front-end/AI-Coding/11-Plugins-System.md new file mode 100644 index 0000000..c87cf1f --- /dev/null +++ b/front-end/AI-Coding/11-Plugins-System.md @@ -0,0 +1,29 @@ +# 11. Plugins 系统(library/plugins/*) + +> 目标:明确本仓库“全局注入能力/指令/错误处理”的来源,避免抽取组件时漏掉运行时注入。 + +## 插件目录 + +- `library/plugins/vab.ts`:全局 gp 注入 + mitt 事件总线(`$pub/$sub/$baseMessage/...`) +- `library/plugins/directive.ts`:自定义指令(`v-permissions`) +- `library/plugins/errorLog.ts`:全局错误捕获(`app.config.errorHandler`)并写入 store +- `library/plugins/support.ts`:构建信息输出与依赖存在性检查 + +## 安装方式 + +- 正常路径:`library/index.ts` 会自动加载 `library/plugins/**/*.ts` 并 `app.use()`(setupVab 链路内) +- 抽取到目标项目: + - 若不复用 `library/index.ts` 的自动加载逻辑,需要在入口手动 `app.use(plugin)` + +## 最小依赖闭包 + +- `vab.ts`:依赖 Element Plus 全局 API(ElMessage/ElLoading/ElMessageBox/ElNotification)+ lodash + mitt + `src/config` +- `directive.ts`:依赖 `src/utils/permission`(hasPermission 及其权限数据来源) +- `errorLog.ts`:依赖 `src/store/modules/errorLog` 与 `src/config/errorLog` +- `support.ts`:依赖 `__APP_INFO__` 构建注入 + +## 验收点(smoke) + +- `vab`:`$baseMessage('ok')` 能弹出 message;`$pub/$sub` 能收发事件 +- `directive`:模板中 `v-permissions` 不报错且能按权限生效 +- `errorLog`:手动触发异常能写入 errorLog store diff --git a/front-end/AI-Coding/12-Settings-Store.md b/front-end/AI-Coding/12-Settings-Store.md new file mode 100644 index 0000000..ff5a369 --- /dev/null +++ b/front-end/AI-Coding/12-Settings-Store.md @@ -0,0 +1,35 @@ +# 12. Settings Store(主题/布局等全局配置) + +> 目标:把 Theme/Layout 的“根依赖”讲清楚,避免 AI 抽取时只搬组件却漏掉主题变量注入与持久化逻辑。 + +## 入口 + +- `src/store/modules/settings.ts`(`useSettingsStore`) + +## 管理的核心状态 + +- `theme`:包含 `layout/themeName/background/menuWidth/showTheme/showThemeSetting/tabsBarStyle...` +- `device`:`desktop | mobile`(布局响应式需要) +- `collapse`:侧边栏折叠状态 +- `language`:国际化语言 + +## 关键动作 + +- `saveTheme()`:持久化 theme +- `resetTheme()`:恢复默认 theme,并调用 `updateTheme()` +- `updateTheme()`: + - 通过 `@vab/styles/variables/vab-*-variables.module.scss` 读取变量 + - 将 `vab-` 前缀映射到 `--el-`,动态写入 Element Plus CSS 变量 + - 设置 body class:`vab-theme-*`,并按 background 追加 class + - 设置 `--el-left-menu-width` + +## 最小闭包(Theme/Layout 抽取必须带上) + +- `src/store/modules/settings.ts` +- `library/styles/variables/vab-*-variables.module.scss` +- `library/plugins/vab.ts`(Theme 事件总线注入) + +## 常见坑 + +- `updateTheme()` 使用 webpack 风格动态 `require`,迁移到非 webpack 构建器需要等效替代 +- `useCssVar` 的导入方式依赖工程约定(自动导入 vs 显式 import) diff --git a/front-end/AI-Coding/13-Component-Inventory.md b/front-end/AI-Coding/13-Component-Inventory.md new file mode 100644 index 0000000..33b2675 --- /dev/null +++ b/front-end/AI-Coding/13-Component-Inventory.md @@ -0,0 +1,70 @@ +# 13. 组件清单(library/components) + +> 目的:让 AI 在新项目中“选组件/抽组件”时,先从**可复用目录清单**开始,避免漏组件或重复造轮子。 + +## 组件根目录 + +- `library/components/`(约定:每个 `Vab*` 目录是一组可复用组件) + +## 当前组件目录(按仓库实际目录列出) + +- `VabApp`:应用壳/全局 Provider +- `VabAppMain`:主内容区(与 Layout 配合) +- `VabAvatar` +- `VabBreadcrumb` +- `VabCard` +- `VabColorfulCard` +- `VabColumnBar` +- `VabErrorLog` +- `VabFold` +- `VabFooter` +- `VabFullScreen` +- `VabHeader` +- `VabLanguage` +- `VabLink` +- `VabLock` +- `VabLogo` +- `VabMenu` +- `VabNav` +- `VabNotice` +- `VabQueryForm` +- `VabRefresh` +- `VabRouterView` +- `VabSearch` +- `VabSideBar` +- `VabTabs` +- `VabTheme` + +## 抽取建议(最小闭包) + +- 优先使用任务模板:`openspec-lite/tasks/extract-component.yaml` +- 若组件涉及 Layout/Theme:先抽 `layouts` 与 `vab-theme`,再补 `store-settings` 与 `plugin-vab` + +高耦合组件规格(机读): + +- `vab-app-main`:`openspec-lite/modules/vab-app-main.yaml` +- `vab-avatar`:`openspec-lite/modules/vab-avatar.yaml` +- `vab-breadcrumb`:`openspec-lite/modules/vab-breadcrumb.yaml` +- `vab-card`:`openspec-lite/modules/vab-card.yaml` +- `vab-colorful-card`:`openspec-lite/modules/vab-colorful-card.yaml` +- `vab-column-bar`:`openspec-lite/modules/vab-column-bar.yaml` +- `vab-router-view`:`openspec-lite/modules/vab-router-view.yaml` +- `vab-error-log`:`openspec-lite/modules/vab-error-log.yaml` +- `vab-fold`:`openspec-lite/modules/vab-fold.yaml` +- `vab-query-form`:`openspec-lite/modules/vab-query-form.yaml` +- `vab-refresh`:`openspec-lite/modules/vab-refresh.yaml` +- `vab-search`:`openspec-lite/modules/vab-search.yaml` +- `vab-lock`:`openspec-lite/modules/vab-lock.yaml` +- `vab-notice`:`openspec-lite/modules/vab-notice.yaml` +- `vab-logo`:`openspec-lite/modules/vab-logo.yaml` +- `vab-nav`:`openspec-lite/modules/vab-nav.yaml` +- `vab-language`:`openspec-lite/modules/vab-language.yaml` +- `vab-full-screen`:`openspec-lite/modules/vab-full-screen.yaml` +- `vab-footer`:`openspec-lite/modules/vab-footer.yaml` +- `vab-link`:`openspec-lite/modules/vab-link.yaml` + +## 快速定位(grep/语义检索) + +- `library/components//index.vue` +- `VabThemeDrawer` / `VabThemeSetting` +- `VabMenu` / `VabTabs` / `VabSideBar`(通常与路由/权限/store 强相关) diff --git a/front-end/AI-Coding/14-Store-Modules.md b/front-end/AI-Coding/14-Store-Modules.md new file mode 100644 index 0000000..72b143f --- /dev/null +++ b/front-end/AI-Coding/14-Store-Modules.md @@ -0,0 +1,32 @@ +# 14. Store 模块清单(src/store/modules) + +> 目的:让 AI 明确“权限/路由/主题/标签页”等基础能力分别由哪个 store 提供,抽取时不遗漏。 + +## 模块列表(按仓库实际文件列出) + +- `acl.ts`:角色/权限/admin(配合 `hasPermission()`) +- `errorLog.ts`:错误日志收集(配合 `library/plugins/errorLog.ts`) +- `routes.ts`:路由模式/菜单路由设置(前端/后端路由切换) +- `settings.ts`:主题/布局/语言/折叠等全局配置(见 `12-Settings-Store.md`) +- `tabs.ts`:标签页 visitedRoutes 管理 +- `user.ts`:登录/用户信息/登出/重置(联动 acl/routes/tabs/settings) + +对应模块规格(机读): + +- `access-control`:`openspec-lite/modules/access-control.yaml`(覆盖 acl + hasPermission + 指令) +- `store-errorlog`:`openspec-lite/modules/store-errorlog.yaml` +- `store-routes`:`openspec-lite/modules/store-routes.yaml` +- `store-settings`:`openspec-lite/modules/store-settings.yaml` +- `store-tabs`:`openspec-lite/modules/store-tabs.yaml` +- `store-user`:`openspec-lite/modules/store-user.yaml` + +## 最小依赖闭包提示 + +- 权限链路:`acl.ts` + `src/utils/permission.ts` + `library/plugins/directive.ts` +- 路由链路:`routes.ts` + `src/router/*` + `src/utils/routes.ts` +(可选)`src/api/router.ts` +- 主题链路:`settings.ts` + `library/styles/variables/vab-*-variables.module.scss` + +## 验收点(smoke) + +- 能创建 Pinia 并正常读取 settings/acl/user 等 store +- `hasPermission()` 能按 acl 状态返回布尔值 diff --git a/front-end/AI-Coding/15-Snippet-Map.md b/front-end/AI-Coding/15-Snippet-Map.md new file mode 100644 index 0000000..b667c51 --- /dev/null +++ b/front-end/AI-Coding/15-Snippet-Map.md @@ -0,0 +1,115 @@ +# 15. 代码段地图(Snippet Map) + +> 目的:给后续 AI coding 的“最常用代码段”提供**稳定来源与检索方式**,直接指向文件/关键词。 + +## 初始化与自动加载 + +- `setupVab(app)`:`library/index.ts` + - 背景 SCSS 自动加载:`require.context('./styles/background', false, /\\.scss$/)` + - 插件自动加载:`require.context('./plugins', true, /\\.ts$/)` + `app.use(Plugins(key).default)` + +## Theme(主题) + +- 打开抽屉事件:`$pub('theme')`(`library/components/VabTheme/*`) +- 随机换肤事件:`$pub('random-theme')` +- 主题变量注入:`useSettingsStore().updateTheme()`(`src/store/modules/settings.ts`) +- scss module 来源:`@vab/styles/variables/vab-*-variables.module.scss` + +## Plugins(全局注入/事件总线/指令) + +- 事件总线注入:`$pub/$sub/$unsub`(`library/plugins/vab.ts`) +- 权限指令:`v-permissions`(`library/plugins/directive.ts`) +- 权限判断:`hasPermission()`(`src/utils/permission.ts`) + +## Icons(SVG) + +- SVG 自动加载:`require.context('.', true, /\\.svg$/)`(`src/icon/index.ts`) + +## Router/Routes + +- 后端路由转换:`convertRouter()`(`src/utils/routes.ts`) +- 路由模式切换:`authentication === 'all'`(`src/store/modules/routes.ts`) +- 过滤可访问路由:`filterRoutes([...constantRoutes, ...routes], control)`(`src/store/modules/routes.ts`) +- 重置路由:`resetRouter(accessRoutes)`(`src/store/modules/routes.ts` / `src/router`) + +## Router Permissions + +- 路由守卫入口:`setupPermissions(router)`(`src/router/permissions.ts`) +- 白名单/登录拦截开关:`routesWhiteList` / `loginInterception` / `authentication`(`src/router/permissions.ts` / `src/config`) +- 更新标题:`document.title = getPageTitle(to.meta.title)`(`src/router/permissions.ts`) + +## Config + +- 配置聚合入口:`module.exports = { ...cli, ...setting, ...theme, ...network }`(`src/config/index.js`) +- 路由/登录关键开关:`authentication/loginInterception/routesWhiteList/supportVisit`(`src/config/setting.config.js`) +- 菜单关键开关:`defaultOpeneds/uniqueOpened/openFirstMenu`(`src/config/setting.config.js`) +- 网络请求关键开关:`baseURL/successCode/statusName/messageName/requestTimeout`(`src/config/net.config.js`) + +## Router View + +- 刷新当前视图缓存:`$sub('reload-router-view', ...)`(`library/components/VabRouterView/index.vue`) + +## Menu + +- 点击当前菜单刷新:`$pub('reload-router-view')`(`library/components/VabMenu/components/VabMenuItem.vue`) + +## Refresh + +- 刷新按钮触发:`$pub('reload-router-view')`(`library/components/VabRefresh/index.vue`) + +## Search + +- 打开搜索快捷键:`ctrlKey/metaKey + 'k'`(`library/components/VabSearch/index.vue`) +- 历史记录 key:`vab_search_history`(`library/components/VabSearch/index.vue`) + +## Lock + +- 锁屏开关:`handleLock()` / `handleUnLock()`(`library/components/VabLock/index.vue`) +- 直接操作侧边栏:`document.querySelector('.vab-side-bar')`(`library/components/VabLock/index.vue`) + +## Notice + +- 拉取通知:`getList()`(`library/components/VabNotice/index.vue` / `src/api/notice.ts`) + +## Language + +- 语言切换:`useI18n().locale` / `changeLanguage(`(`library/components/VabLanguage/index.vue` / `src/store/modules/settings.ts`) +- 切换后更新标题:`getPageTitle(`(`library/components/VabLanguage/index.vue` / `src/utils/pageTitle.ts`) + +## FullScreen + +- 全屏切换:`useFullscreen().toggle`(`library/components/VabFullScreen/index.vue`) + +## Footer + +- 页脚标题来源:`settings.title`(`library/components/VabFooter/index.vue` / `src/store/modules/settings.ts`) + +## Nav/Breadcrumb + +- 顶部导航聚合:``(`library/components/VabNav/index.vue`) +- 面包屑生成:`handleMatched(`(`library/components/VabBreadcrumb/index.vue` / `src/utils/routes.ts`) + +## Column Bar + +- Column 二级菜单:`partialRoutes` + `defaultOpeneds`(`library/components/VabColumnBar/index.vue` / `src/store/modules/routes.ts` / `src/config`) + +## Avatar/Logo + +- 用户下拉退出:`case 'logout'` + `toLoginRoute(route.fullPath)`(`library/components/VabAvatar/index.vue` / `src/utils/routes.ts`) +- Logo/Title 来源:`logo/title`(`library/components/VabLogo/index.vue` / `src/store/modules/settings.ts`) + +## Link/Fold/Card + +- 外链/内链切换:`isExternal(props.to)`(`library/components/VabLink/index.vue` / `src/utils/validate.ts`) +- 折叠按钮:`toggleCollapse`(`library/components/VabFold/index.vue` / `src/store/modules/settings.ts`) +- Skeleton 卡片:`el-skeleton`(`library/components/VabCard/index.vue`) + +## User/Auth + +- 登出重置闭包:`resetAll()`(`src/store/modules/user.ts`,联动 acl/routes/tabs/resetRouter/removeToken) + +## API/Request + +- axios 实例与拦截器:`axios.create` / `instance.interceptors`(`src/utils/request.ts`) +- 401/402/403 分支:`case 401` / `case 402` / `case 403`(`src/utils/request.ts`) +- token 注入:`Authorization: Bearer`(`src/utils/request.ts`) diff --git a/front-end/AI-Coding/16-Config-Keys.md b/front-end/AI-Coding/16-Config-Keys.md new file mode 100644 index 0000000..4161be9 --- /dev/null +++ b/front-end/AI-Coding/16-Config-Keys.md @@ -0,0 +1,76 @@ +# 16. Config Key Map(src/config) + +> 目的:让 AI 在迁移/新项目开发时,明确“哪些行为由哪些 config key 控制”,并能快速定位 key 的定义与使用点。 + +## 聚合入口 + +- `src/config/index.js` 会把 4 份配置聚合导出: + - `cli.config.js`(构建/CLI 相关) + - `setting.config.js`(通用/登录/路由/菜单/缓存等) + - `theme.config.js`(主题与 UI 开关默认值) + - `net.config.js`(网络请求相关) + +注意:`src/config/*` 可能会被 `vue.config.js` 在 Node 环境 `require()` 读取,因此配置文件应避免使用 `window/document`。 + +## 高影响 keys(按子系统分组) + +### 路由/权限/登录 + +- `authentication`:路由模式(`intelligence` 前端路由 / `all` 后端路由) +- `loginInterception`:是否开启登录拦截(影响 `setupPermissions` 行为) +- `routesWhiteList`:白名单路由(不校验 token) +- `supportVisit`:游客模式 +- `rolesControl`:是否按 `roles` 字段进行角色控制 +- `isHashRouterMode`:hash/history 模式相关逻辑(菜单里对 `_blank` 打开内部路由有分支) +- `publicPath`:路由/跳转时可能需要的 publicPath + +### Token/存储 + +- `tokenName`:token 字段名 +- `tokenTableName`:存储 key 名 +- `storage`:`localStorage/sessionStorage/cookie` +- `recordRoute`:token 失效回到登录页时是否记录本次路由 + +### 页面标题 + +- `title`:系统标题(影响 `getPageTitle`/浏览器标题/雪花屏标题等) +- `titleSeparator`:标题分隔符 +- `titleReverse`:标题是否反转 + +### 菜单/导航体验 + +- `uniqueOpened`:是否只保持一个子菜单展开 +- `defaultOpeneds`:默认展开菜单 path 列表 +- `openFirstMenu`:是否点击一级菜单默认开启二级菜单 +- `debounce`:需要加 loading 层防重复提交的请求标识列表 +- `keepAliveMaxNum`:keep-alive 最大缓存数量 + +### Theme 默认值与开关(theme.config.js) + +- 默认值:`layout/themeName/background/menuWidth/columnStyle/...` +- UI 开关:`showProgressBar/showTabs/showLanguage/showRefresh/showSearch/showTheme/showNotice/showFullScreen/showThemeSetting/showLock/...` + +### 网络请求(net.config.js) + +- `baseURL` / `contentType` / `requestTimeout` +- `successCode` / `statusName` / `messageName` + +### 构建/CLI(cli.config.js) + +- `devPort/outputDir/assetsDir/publicPath` +- `pwa/buildOptimize/noDebugger/lintOnSave` + +## 常见使用点(快速定位) + +- 路由守卫:`src/router/permissions.ts`(`authentication/loginInterception/routesWhiteList/supportVisit` + `showProgressBar`) +- Router 定义:`src/router/index.ts`(`authentication/isHashRouterMode/publicPath`) +- 菜单:`library/components/VabMenu/components/VabMenuItem.vue`(`isHashRouterMode`) +- 路由 store:`src/store/modules/routes.ts`(`authentication/rolesControl`) +- 标题工具:`src/utils/pageTitle.ts`(`titleSeparator/titleReverse`) +- token 工具:`src/utils/token.ts`(`storage/tokenTableName`) +- request:`src/utils/request.ts`(`baseURL/successCode/statusName/messageName/requestTimeout/contentType` 等) + +## 迁移/复用提示(最小闭包) + +- 抽 `router`/`request`/`menu` 等模块时,**优先把 `src/config/*` 一并带走**,避免 key 缺失导致行为变化。 +- 如果目标项目要改 key 名或改配置来源(例如改成 `.env` 或远端配置),建议先在规格里写清“映射关系”和“验收点”。 diff --git a/front-end/AI-Coding/AI-Reuse-Playbook.md b/front-end/AI-Coding/AI-Reuse-Playbook.md new file mode 100644 index 0000000..10fbdda --- /dev/null +++ b/front-end/AI-Coding/AI-Reuse-Playbook.md @@ -0,0 +1,54 @@ +# AI 复用操作手册(轻量化) + +> 目标:在“不改动现有代码”的前提下,用文档约束 AI 从本仓库抽取能力,并确保抽取结果可验证。 + +## A. 选模块(先确定要什么) + +在开始任何代码生成/迁移前,AI 必须先回答: + +- 我要复用的能力类型:`layout | component | plugin | util | store | i18n | api | mock | config` +- 复用方式:`copy-snippet`(复制片段)或 `copy-module`(整目录) +- 目标项目构建工具:`Vue CLI` / `Vite` / 其他 + +> 推荐:优先 `copy-module`(减少遗漏依赖),再做裁剪。 + +## B. 定位入口(必须列出入口文件) + +- 全局能力:`library/index.ts`(`setupVab`) +- 初始化:`src/main.ts` +- 路由:`src/router/index.ts` +- 状态:`src/store/index.ts` +- i18n:`src/i18n/index.ts` +- Mock:`mock/index.js` + +AI 输出中必须包含:入口文件列表 + 为什么需要它们。 + +## C. 最小闭包(必须列出依赖闭包) + +AI 必须同时列出: + +- 直接依赖(import 的文件/包) +- 运行时依赖(例如:全局样式、icons、插件自动加载) +- 类型依赖(`types/` 目录与 `/#`) +- alias 依赖(`@`、`@vab`、`~`、`/#`) + +> 如果依赖闭包不清晰,禁止直接迁移;应先补规格(见 `openspec-lite/`)。 + +## D. 迁移后验收(必须可执行) + +最低验收: + +- `pnpm run serve` 能启动 +- 页面可渲染 +- 路由能跳转 +- i18n/store 能注入 +- Mock(若启用)能命中 + +## E. 输出格式(给 OpenSpec/AI 工具链用) + +当 AI 完成一次抽取/复用任务时,输出必须包含: + +- **变更文件清单**(新增/修改/删除) +- **复用模块清单**(从哪里来、被用在何处) +- **验收命令**(lint/test/build/serve) +- **风险说明**(可能破坏的点:alias、插件自动加载、生产 mock 等) diff --git a/front-end/AI-Coding/Pitfalls.md b/front-end/AI-Coding/Pitfalls.md new file mode 100644 index 0000000..3af113a --- /dev/null +++ b/front-end/AI-Coding/Pitfalls.md @@ -0,0 +1,8 @@ +# 已知陷阱与迁移雷区(Pitfalls) + +- **生产环境禁调试**:`src/App.vue` 使用 `disable-devtool`,可能影响某些测试/调试环境。 +- **生产环境默认 Mock**:`src/main.ts` 中存在“生产启用 mockXHR”的逻辑,做真实项目发布时要确认策略。 +- **配置入口**:`vue.config.js` 通过 `require('./src/config')` 读取配置;`src/config/index.js` 会聚合导出多个子配置。 +- **自动插件加载**:`library/index.ts` 会 `require.context('./plugins', true, /\\.ts$/)` 全量加载;抽取模块时容易带入不需要依赖。 + +> 对应的约束与处理建议:见 [openspec-lite/project.yaml](openspec-lite/project.yaml)。 diff --git a/front-end/AI-Coding/README.md b/front-end/AI-Coding/README.md new file mode 100644 index 0000000..407d671 --- /dev/null +++ b/front-end/AI-Coding/README.md @@ -0,0 +1,56 @@ +# AI-Coding(AI 取材与约束入口) + +> 本目录是“轻量化模板库”形态下的 AI 指南:**不改动现有业务代码**,尽量用文档与规格约束引导 OpenSpec 开发模式的 AI 快速、准确地复用本仓库能力。 + +## 你应该从这里开始读 + +1. [00-Quick-Profile.md](00-Quick-Profile.md) —— 30 秒摘要(栈、入口链路、Mock、alias) +2. [01-Directory-Map.md](01-Directory-Map.md) —— 仓库地图(模块在哪、职责是什么) +3. [02-Boot-Sequence.md](02-Boot-Sequence.md) —— 初始化链路(`src/main.ts` / `setupVab`) +4. [10-Theme-System.md](10-Theme-System.md) —— Theme 系统(VabTheme + settings + 变量注入) +5. [11-Plugins-System.md](11-Plugins-System.md) —— Plugins(gp 注入/指令/错误捕获) +6. [12-Settings-Store.md](12-Settings-Store.md) —— Settings Store(主题/布局等全局配置) +7. [13-Component-Inventory.md](13-Component-Inventory.md) —— 组件清单(library/components) +8. [14-Store-Modules.md](14-Store-Modules.md) —— Store 模块清单(src/store/modules) +9. [15-Snippet-Map.md](15-Snippet-Map.md) —— 代码段地图(常用片段稳定来源) +10. [16-Config-Keys.md](16-Config-Keys.md) —— Config keys 地图(高影响开关与使用点) +11. [AI-Reuse-Playbook.md](AI-Reuse-Playbook.md) —— AI 复用操作手册(最小闭包、迁移步骤、验收) +12. [Pitfalls.md](Pitfalls.md) —— 已知陷阱与迁移雷区 + +## OpenSpec-lite(轻量规格) + +- [openspec-lite/project.yaml](openspec-lite/project.yaml) —— 项目级约束(技术栈/目录/禁止项/验收门槛) +- [openspec-lite/manifest.yaml](openspec-lite/manifest.yaml) —— 模块清单(供 AI 选模块与定位) +- [openspec-lite/tasks/](openspec-lite/tasks/) —— 任务模板(AI 按模板输出文件与验收项) +- [openspec-lite/modules/](openspec-lite/modules/) —— 模块规格(按需逐步补全;先从高频模块开始) + +推荐从这些高频模块规格开始: + +- `setup-vab`:`openspec-lite/modules/setup-vab.yaml` +- `config-system`:`openspec-lite/modules/config-system.yaml` +- `api-system`:`openspec-lite/modules/api-system.yaml` +- `icons`:`openspec-lite/modules/icons.yaml` +- `styles`:`openspec-lite/modules/styles.yaml` +- `access-control`:`openspec-lite/modules/access-control.yaml` +- `layouts`:`openspec-lite/modules/layouts.yaml` +- `ui-components`:`openspec-lite/modules/ui-components.yaml` +- `store-settings`:`openspec-lite/modules/store-settings.yaml` +- `vab-theme`:`openspec-lite/modules/vab-theme.yaml` +- `plugin-vab`:`openspec-lite/modules/plugin-vab.yaml` +- `plop`:`openspec-lite/modules/plop.yaml` +- `plugin-directive`:`openspec-lite/modules/plugin-directive.yaml` +- `plugin-errorlog`:`openspec-lite/modules/plugin-errorlog.yaml` +- `plugin-support`:`openspec-lite/modules/plugin-support.yaml` +- `VabApp`:`openspec-lite/modules/vab-app.yaml` +- `VabMenu`:`openspec-lite/modules/vab-menu.yaml` +- `VabTabs`:`openspec-lite/modules/vab-tabs.yaml` +- `VabHeader`:`openspec-lite/modules/vab-header.yaml` +- `VabSideBar`:`openspec-lite/modules/vab-sidebar.yaml` + +更多模块(例如 `store-user` / `store-routes` / `store-tabs` / `store-errorlog`)已收录在 `openspec-lite/manifest.yaml` 中,可按需从清单选取。 + +## 本目录的“轻量化原则” + +- **不要求改代码**:所有约束优先通过文档/规格落地;如必须改代码,应先在规格里写明原因与影响面。 +- **先可用,再完美**:优先把“模块定位、最小闭包、验收门槛”写清楚。 +- **一切可检索**:每份文档都应包含明确关键词与入口文件路径,方便 AI 语义检索/grep。 diff --git a/front-end/AI-Coding/Search-Anchors.md b/front-end/AI-Coding/Search-Anchors.md new file mode 100644 index 0000000..2207af3 --- /dev/null +++ b/front-end/AI-Coding/Search-Anchors.md @@ -0,0 +1,40 @@ +# 可供 AI 检索的关键锚点(Search Anchors) + +> 这些关键词可用于语义检索或 grep。 + +- 入口:`createApp(App)`、`setupVab(app)`、`setupRouter(app).isReady()` +- setupVab:`require.context('./plugins'`、`require.context('./styles/background'`、`app.component('VabIcon'`、`createHead()` +- Mock:`setupMiddlewares: require('./mock')`、`mockjs`、`responseFake` +- Alias:`alias`、`@vab`、`/#` +- i18n:`createI18n({ legacy: false })`、`translate(` +- Store:`createPinia()`、`useSettingsStore(pinia)` +- Routes Store:`setRoutes(`、`filterRoutes(`、`resetRouter(` +- Router/Views:`component: () => import('@/views/`、`constantRoutes`、`asyncRoutes`、`breadcrumbHidden`、`noColumn`、`isCustomSvg`、`meta.target`、`meta.badge`、`meta.dot` +- Router Guard:`setupPermissions(`、`router.beforeEach`、`routesWhiteList`、`loginInterception`、`authentication`、`supportVisit`、`VabProgress.start()`、`document.title = getPageTitle` +- User Store:`getUserInfo(`、`resetAll(`、`removeToken(` +- Tabs Store:`visitedRoutes`、`noClosable` +- RouterView:`reload-router-view`、`keepAliveNameList`、`Vab[^/]+)/') | ForEach-Object { $_.Groups['name'].Value } | Sort-Object -Unique; +Compare-Object -ReferenceObject $componentDirs -DifferenceObject $manifestComponents +``` + +对账 `library/layouts/*` 子目录是否都被 `AI-Coding/openspec-lite/modules/layouts.yaml` 覆盖: + +```powershell +$repo = (Get-Location).Path; +$layoutDirs = Get-ChildItem -LiteralPath "$repo\library\layouts" -Directory | Select-Object -ExpandProperty Name | Sort-Object; +$layoutsSpec = Get-Content -LiteralPath "$repo\AI-Coding\openspec-lite\modules\layouts.yaml" -Raw; +$specDirs = [regex]::Matches($layoutsSpec,'library/layouts/(?VabLayout[^/]+)/') | ForEach-Object { $_.Groups['name'].Value } | Sort-Object -Unique; +Compare-Object -ReferenceObject $layoutDirs -DifferenceObject $specDirs +``` + +对账 `components.d.ts` 里自动导入的 `Vab*` 组件是否都被任一 module spec 覆盖(按源文件路径精确匹配): + +```powershell +$repo = (Get-Location).Path; +$dtsPath = "$repo\library\build\vuePlugins\components.d.ts"; +$specDir = "$repo\AI-Coding\openspec-lite\modules"; + +$dts = Get-Content -LiteralPath $dtsPath -Raw; +$specText = (Get-ChildItem -LiteralPath $specDir -Filter '*.yaml' | ForEach-Object { Get-Content -LiteralPath $_.FullName -Raw }) -join "`n---`n"; + +$imports = [regex]::Matches( + $dts, + "Vab[A-Za-z0-9_]+:\\s*typeof\\s+import\\('(?

[^']+)'\\)\\['default'\\]", + [System.Text.RegularExpressions.RegexOptions]::Multiline +) | ForEach-Object { $_.Groups['p'].Value } | Where-Object { $_ -like '*components*' } | ForEach-Object { + $p = $_; + $p = $p -replace "^\\./\\.\\./\\.\\./",""; + $p = $p -replace "^\\./\\.\\./",""; + $p = $p -replace "^\\./",""; + if ($p -like 'components/*') { $p = "library/" + $p } + $p +} | Sort-Object -Unique; + +$missing = foreach ($p in $imports) { if ($specText -notmatch [regex]::Escape($p)) { $p } }; +if ($missing) { $missing } else { '(none)' } +``` + +对账 `src/router/index.ts` 引用的 `@/views/*.vue` 是否都存在,并列出未被路由引用的 views: + +```powershell +$repo = (Get-Location).Path; +$routerPath = "$repo\src\router\index.ts"; +$viewsRoot = "$repo\src\views"; + +$router = Get-Content -LiteralPath $routerPath -Raw; +# 只提取“非注释行”中的 views 引用,避免把 // 注释的 demo 路由当成缺失文件 +$routeViewRefs = [regex]::Matches( + $router, + "(?m)^(?!\\s*//).*@/views/(?

[^'\\\"\\)]+\\.vue)", + [System.Text.RegularExpressions.RegexOptions]::Multiline +) | ForEach-Object { $_.Groups['p'].Value } | Sort-Object -Unique; + +$missingFiles = foreach ($p in $routeViewRefs) { + $abs = Join-Path $viewsRoot ($p -replace '/', '\\'); + if (-not (Test-Path -LiteralPath $abs)) { "src/views/$p" } +}; + +$allViews = Get-ChildItem -LiteralPath $viewsRoot -Recurse -File -Filter '*.vue' | ForEach-Object { + $_.FullName.Substring($viewsRoot.Length + 1).Replace('\\','/') +} | Sort-Object -Unique; + +$unreferenced = Compare-Object -ReferenceObject $allViews -DifferenceObject $routeViewRefs -PassThru | Where-Object { $_ -in $allViews } | Sort-Object; + +'--- router references missing files ---'; +if ($missingFiles) { $missingFiles } else { '(none)' }; +'--- views not referenced by router ---'; +if ($unreferenced) { $unreferenced | ForEach-Object { "src/views/$_" } } else { '(none)' }; +``` diff --git a/front-end/AI-Coding/openspec-lite/manifest.yaml b/front-end/AI-Coding/openspec-lite/manifest.yaml new file mode 100644 index 0000000..5a35d2e --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/manifest.yaml @@ -0,0 +1,414 @@ +# OpenSpec-lite: 模块清单(可逐步补全) +# 目的:让 AI 在“选模块”阶段就能定位入口与主要依赖。 + +modules: + - id: boot + name: 应用入口与初始化 + type: core + entrypoints: + - src/main.ts + - library/index.ts + keywords: + - createApp + - setupVab + - setupI18n + - setupStore + - setupRouter + + - id: setup-vab + name: setupVab(library/index.ts 自动加载/图标/样式/插件) + type: core + entrypoints: + - library/index.ts + - library/plugins/ + - library/styles/ + - src/icon/index.ts + keywords: + - require.context + - VabIcon + - createHead + + - id: router + name: 路由与权限 + type: core + entrypoints: + - src/router/index.ts + - src/router/permissions.ts + keywords: + - constantRoutes + - asyncRoutes + - meta + + - id: store + name: Pinia 状态 + type: core + entrypoints: + - src/store/index.ts + - src/store/modules + keywords: + - createPinia + + - id: store-settings + name: Settings Store(主题/布局等全局配置) + type: store + entrypoints: + - src/store/modules/settings.ts + - library/styles/variables/ + keywords: + - updateTheme + - themeName + - menuWidth + + - id: store-routes + name: Routes Store(路由拦截/菜单路由设置) + type: store + entrypoints: + - src/store/modules/routes.ts + keywords: + - setRoutes + - resetRouter + + - id: store-user + name: User Store(登录/用户信息/登出) + type: store + entrypoints: + - src/store/modules/user.ts + keywords: + - getUserInfo + - resetAll + + - id: store-tabs + name: Tabs Store(visitedRoutes) + type: store + entrypoints: + - src/store/modules/tabs.ts + keywords: + - visitedRoutes + - noClosable + + - id: store-errorlog + name: ErrorLog Store(错误日志收集) + type: store + entrypoints: + - src/store/modules/errorLog.ts + keywords: + - addErrorLog + + - id: i18n + name: 国际化 + type: core + entrypoints: + - src/i18n/index.ts + keywords: + - createI18n + - translate + + - id: api-system + name: API 层(src/api + request 封装) + type: core + entrypoints: + - src/api/ + - src/utils/request.ts + keywords: + - axios + - interceptors + - refreshToken + + - id: config-system + name: 配置系统(src/config 聚合) + type: core + entrypoints: + - src/config/index.js + - src/config + keywords: + - cli.config + - theme.config + - net.config + + - id: access-control + name: 访问控制(ACL + hasPermission + v-permissions) + type: core + entrypoints: + - src/store/modules/acl.ts + - src/utils/permission.ts + - library/plugins/directive.ts + keywords: + - hasPermission + - v-permissions + + - id: mock + name: 本地 Mock + type: tooling + entrypoints: + - mock/index.js + - mock/controller + keywords: + - setupMiddlewares + - mockjs + - responseFake + + - id: icons + name: SVG Icons(src/icon) + type: ui + entrypoints: + - src/icon/index.ts + - src/icon + keywords: + - require.context + - svg + + - id: styles + name: 样式体系(library/styles) + type: ui + entrypoints: + - library/styles/vab.scss + - library/styles/variables + - library/styles/background + keywords: + - scss + - variables + + - id: ui-components + name: Vab 组件库 + type: ui + entrypoints: + - library/components/ + keywords: + - Vab + - ElementPlus + + - id: vab-app + name: VabApp + type: ui + entrypoints: + - library/components/VabApp/ + + - id: vab-app-main + name: VabAppMain + type: ui + entrypoints: + - library/components/VabAppMain/ + + - id: vab-avatar + name: VabAvatar + type: ui + entrypoints: + - library/components/VabAvatar/ + + - id: vab-breadcrumb + name: VabBreadcrumb + type: ui + entrypoints: + - library/components/VabBreadcrumb/ + + - id: vab-card + name: VabCard + type: ui + entrypoints: + - library/components/VabCard/ + + - id: vab-colorful-card + name: VabColorfulCard + type: ui + entrypoints: + - library/components/VabColorfulCard/ + + - id: vab-column-bar + name: VabColumnBar + type: ui + entrypoints: + - library/components/VabColumnBar/ + + - id: vab-fold + name: VabFold + type: ui + entrypoints: + - library/components/VabFold/ + + - id: vab-footer + name: VabFooter + type: ui + entrypoints: + - library/components/VabFooter/ + + - id: vab-full-screen + name: VabFullScreen + type: ui + entrypoints: + - library/components/VabFullScreen/ + + - id: vab-language + name: VabLanguage + type: ui + entrypoints: + - library/components/VabLanguage/ + + - id: vab-link + name: VabLink + type: ui + entrypoints: + - library/components/VabLink/ + + - id: vab-logo + name: VabLogo + type: ui + entrypoints: + - library/components/VabLogo/ + + - id: vab-router-view + name: VabRouterView + type: ui + entrypoints: + - library/components/VabRouterView/ + + - id: vab-query-form + name: VabQueryForm + type: ui + entrypoints: + - library/components/VabQueryForm/ + + - id: vab-error-log + name: VabErrorLog + type: ui + entrypoints: + - library/components/VabErrorLog/ + + - id: vab-menu + name: VabMenu + type: ui + entrypoints: + - library/components/VabMenu/ + + - id: vab-nav + name: VabNav + type: ui + entrypoints: + - library/components/VabNav/ + + - id: vab-tabs + name: VabTabs + type: ui + entrypoints: + - library/components/VabTabs/ + + - id: vab-refresh + name: VabRefresh + type: ui + entrypoints: + - library/components/VabRefresh/ + + - id: vab-search + name: VabSearch + type: ui + entrypoints: + - library/components/VabSearch/ + + - id: vab-lock + name: VabLock + type: ui + entrypoints: + - library/components/VabLock/ + + - id: vab-notice + name: VabNotice + type: ui + entrypoints: + - library/components/VabNotice/ + + - id: vab-header + name: VabHeader + type: ui + entrypoints: + - library/components/VabHeader/ + + - id: vab-sidebar + name: VabSideBar + type: ui + entrypoints: + - library/components/VabSideBar/ + + - id: layouts + name: 布局体系 + type: ui + entrypoints: + - library/layouts/index.vue + - library/layouts/ + keywords: + - Layout + + - id: vab-theme + name: Theme 系统(VabTheme) + type: ui + entrypoints: + - library/components/VabTheme/ + - src/store/modules/settings.ts + - library/plugins/vab.ts + keywords: + - theme + - updateTheme + - $pub + + - id: plugins + name: Plugins(全局注入/指令/错误处理) + type: core + entrypoints: + - library/plugins/ + keywords: + - app.use + - globalProperties + + - id: plugin-vab + name: plugin-vab(gp + mitt 事件总线) + type: plugin + entrypoints: + - library/plugins/vab.ts + keywords: + - $pub + - $sub + - mitt + + - id: plugin-directive + name: plugin-directive(v-permissions) + type: plugin + entrypoints: + - library/plugins/directive.ts + keywords: + - v-permissions + - hasPermission + + - id: plugin-errorlog + name: plugin-errorlog(全局错误捕获) + type: plugin + entrypoints: + - library/plugins/errorLog.ts + keywords: + - errorHandler + - useErrorLogStore + + - id: plugin-support + name: plugin-support(构建信息/依赖检查) + type: plugin + entrypoints: + - library/plugins/support.ts + keywords: + - __APP_INFO__ + + - id: build + name: 构建扩展 + type: tooling + entrypoints: + - vue.config.js + - library/build + keywords: + - chainWebpack + - createVuePlugin + + - id: plop + name: Plop 代码生成器 + type: tooling + entrypoints: + - plopfile.js + - plop-templates/ + keywords: + - plop + - setGenerator diff --git a/front-end/AI-Coding/openspec-lite/modules/access-control.yaml b/front-end/AI-Coding/openspec-lite/modules/access-control.yaml new file mode 100644 index 0000000..bb8710b --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/access-control.yaml @@ -0,0 +1,27 @@ +# Module Spec:access-control(ACL + hasPermission + v-permissions) + +module: + id: access-control + name: 访问控制(角色/权限/指令) + type: core + entrypoints: + - src/store/modules/acl.ts + - src/utils/permission.ts + - library/plugins/directive.ts + +public_api: + concept: + - "useAclStore 保存 admin/role/permission" + - "hasPermission(target) 统一判断路由/按钮权限" + - "v-permissions 指令在模板侧消费 hasPermission" + +dependency_closure: + runtime: + - "Pinia store 初始化(src/store/index.ts)" + +acceptance: + - "acl 中 admin=true 时 hasPermission 永远为 true" + - "指令 v-permissions 可用且不报错(具体隐藏/移除行为以实现为准)" + +pitfalls: + - "权限数据来源通常由 user.getUserInfo() 写入 acl;抽取时需明确数据流" diff --git a/front-end/AI-Coding/openspec-lite/modules/api-system.yaml b/front-end/AI-Coding/openspec-lite/modules/api-system.yaml new file mode 100644 index 0000000..b0691dc --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/api-system.yaml @@ -0,0 +1,34 @@ +# Module Spec:api-system(src/api + axios request 封装) + +module: + id: api-system + name: API 层(src/api 域拆分 + src/utils/request.ts) + type: core + entrypoints: + - src/api/ + - src/utils/request.ts + +public_api: + concept: + - "src/api/.ts:按业务域拆分 API 函数" + - "src/utils/request.ts:axios 实例 + 拦截器 + 统一错误处理 + token 注入" + +dependency_closure: + runtime: + - "axios(instance + interceptors)" + - "qs(x-www-form-urlencoded 序列化)" + - "store/user:token 注入、401/402 时 resetAll / refresh token" + - "plugin-vab:gp.$baseMessage / gp.$baseLoading(loading 与错误提示)" + - "plugin-errorlog:needErrorLog/addErrorLog(请求异常入库)" + - "router:401/403 跳转" + - "config:baseURL/requestTimeout/contentType/successCode/statusName/messageName/debounce 等" + - "api/refreshToken:402 刷新 token 重试队列" + +acceptance: + - "正常接口返回 code=200 时返回 data" + - "401 跳转 /login 且 resetAll 执行" + - "402 触发 refreshToken 并重放队列请求" + +pitfalls: + - "request.ts 强依赖 gp(全局注入)与 user store;抽取到新项目需明确入口安装顺序" + - "successCode/statusName/messageName 等与后端协议强耦合,迁移时先在规格声明差异" diff --git a/front-end/AI-Coding/openspec-lite/modules/config-system.yaml b/front-end/AI-Coding/openspec-lite/modules/config-system.yaml new file mode 100644 index 0000000..7b10986 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/config-system.yaml @@ -0,0 +1,31 @@ +# Module Spec:config-system(src/config:配置聚合) + +module: + id: config-system + name: 配置系统(src/config 聚合导出) + type: core + entrypoints: + - src/config/index.js + - src/config/cli.config.js + - src/config/setting.config.js + - src/config/theme.config.js + - src/config/net.config.js + +public_api: + concept: + - "src/config/index.js 将 4 个子配置聚合导出(cli/setting/theme/network)" + - "部分配置会被 vue.config.js 以 Node 方式 require 读取,因此子配置文件应保持 Node 兼容(避免 window/document)" + + key_index: + - "authentication/loginInterception/routesWhiteList/supportVisit/rolesControl/isHashRouterMode" + - "tokenName/tokenTableName/storage/recordRoute" + - "title/titleSeparator/titleReverse" + - "defaultOpeneds/uniqueOpened/openFirstMenu" + - "layout/themeName/menuWidth/columnStyle/showProgressBar/showTabs/showTheme/showThemeSetting" + - "baseURL/contentType/requestTimeout/successCode/statusName/messageName" + +acceptance: + - "运行构建/启动时,vue.config.js require config 不报错" + +pitfalls: + - "配置文件在 Node 侧执行:避免使用浏览器对象" diff --git a/front-end/AI-Coding/openspec-lite/modules/icons.yaml b/front-end/AI-Coding/openspec-lite/modules/icons.yaml new file mode 100644 index 0000000..758512f --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/icons.yaml @@ -0,0 +1,20 @@ +# Module Spec:icons(src/icon:SVG 自动加载) + +module: + id: icons + name: SVG Icons(src/icon require.context 自动加载) + type: ui + entrypoints: + - src/icon/index.ts + - src/icon/*.svg + +public_api: + concept: + - "通过 require.context 自动加载 src/icon 下的 svg(构建期打包进 sprite/资源管线,取决于 webpack 配置)" + +dependency_closure: + bundler: + - "webpack require.context" + +acceptance: + - "启动后 svg 资源被打包且可引用(具体引用方式取决于项目现有 svg loader 配置)" diff --git a/front-end/AI-Coding/openspec-lite/modules/layouts.yaml b/front-end/AI-Coding/openspec-lite/modules/layouts.yaml new file mode 100644 index 0000000..3eaf159 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/layouts.yaml @@ -0,0 +1,40 @@ +# Module Spec:layouts(布局体系) + +module: + id: layouts + name: 布局体系(Layout Shell + 多布局实现) + type: ui + entrypoints: + - library/layouts/index.vue + - library/layouts/VabLayoutVertical/ + - library/layouts/VabLayoutHorizontal/ + - library/layouts/VabLayoutCommon/ + - library/layouts/VabLayoutComprehensive/ + - library/layouts/VabLayoutFloat/ + - library/layouts/VabLayoutColumn/ + +public_api: + concept: + - "通过 theme.layout 选择渲染的布局组件:" + +usage_examples: + - "在 settings store 中设置 theme.layout 为 vertical/horizontal/...,Layout 会动态切换" + +dependency_closure: + runtime: + - "Pinia settings store:src/store/modules/settings(theme/layout/collapse/device)" + - "Element Plus:el-backtop" + - "Theme 组件:library/components/VabTheme(VabThemeDrawer/VabThemeSetting)" + - "事件总线注入:library/plugins/vab.ts($pub/$sub/$unsub;Theme 抽屉依赖该注入)" + styles: + - "library/styles/variables/variables.module.scss(布局 SCSS 变量)" + - "library/styles/variables/vab-*-variables.module.scss(Theme 注入 Element Plus CSS 变量)" + +acceptance: + - "Layout 可渲染并不报错" + - "窗口宽度 < 992 时进入 mobile 模式并能折叠菜单(watch/resize 生效)" + - "Theme 抽屉/设置入口存在时不报错(缺失 $pub/$sub 注入会导致 Theme 不工作)" + +pitfalls: + - "Layout 通过 require.context 自动注册 layouts 子目录下的 .vue;迁移到非 webpack 环境时需要等效能力或手动注册" + - "Layout 与 Theme 都依赖 webpack 能力(require.context / 动态 require scss module);抽取到非 webpack 构建器时需要等效替代方案" diff --git a/front-end/AI-Coding/openspec-lite/modules/mock.yaml b/front-end/AI-Coding/openspec-lite/modules/mock.yaml new file mode 100644 index 0000000..4e907d9 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/mock.yaml @@ -0,0 +1,21 @@ +# Module Spec(示例):mock + +module: + id: mock + name: 本地 Mock(devServer 中间件) + entrypoints: + - mock/index.js + - mock/controller + +public_api: + concept: + - "route object: { url, type, response }" + +usage_examples: + - "在 mock/controller/.js 增加路由对象,devServer 会自动注册" + +acceptance: + - "启动开发服务器后,请求命中 mock 并返回 mockjs 数据" + +pitfalls: + - "路由匹配会自动拼 baseURL 前缀(来自 src/config)" diff --git a/front-end/AI-Coding/openspec-lite/modules/plop.yaml b/front-end/AI-Coding/openspec-lite/modules/plop.yaml new file mode 100644 index 0000000..1a4dc11 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/plop.yaml @@ -0,0 +1,19 @@ +# Module Spec:plop(代码生成器) + +module: + id: plop + name: Plop 代码生成器(view/curd/component/mock&api) + type: tooling + entrypoints: + - plopfile.js + - plop-templates/ + +public_api: + concept: + - "通过 plop generators 生成页面、curd、组件以及 mock&api 框架代码" + +acceptance: + - "执行 plop 命令可正常出现 generators 并生成文件" + +pitfalls: + - "生成结果依赖仓库既有目录约定(src/views、src/api、mock/controller 等);新项目需对齐目录或改模板" diff --git a/front-end/AI-Coding/openspec-lite/modules/plugin-directive.yaml b/front-end/AI-Coding/openspec-lite/modules/plugin-directive.yaml new file mode 100644 index 0000000..0a43569 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/plugin-directive.yaml @@ -0,0 +1,24 @@ +# Module Spec:plugin-directive(自定义指令:v-permissions) + +module: + id: plugin-directive + name: 自定义指令(v-permissions 权限控制) + type: plugin + entrypoints: + - library/plugins/directive.ts + +public_api: + concept: + - "注册 v-permissions 指令,用于按权限隐藏/禁用 UI" + +dependency_closure: + runtime: + - "权限判断:src/utils/permission(hasPermission)" + - "路由/用户权限数据:通常来自 store/user 或 routes 权限模块(视实现而定)" + +acceptance: + - "模板中存在 v-permissions 使用时不报错" + - "无权限时能按设计移除/隐藏元素(以 hasPermission 实现为准)" + +pitfalls: + - "指令依赖 hasPermission 的语义;抽取到目标项目需同步其实现与权限数据来源" diff --git a/front-end/AI-Coding/openspec-lite/modules/plugin-errorlog.yaml b/front-end/AI-Coding/openspec-lite/modules/plugin-errorlog.yaml new file mode 100644 index 0000000..214dc18 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/plugin-errorlog.yaml @@ -0,0 +1,24 @@ +# Module Spec:plugin-errorlog(全局错误捕获与上报 store) + +module: + id: plugin-errorlog + name: 错误日志插件(app.config.errorHandler + store 收集) + type: plugin + entrypoints: + - library/plugins/errorLog.ts + +public_api: + concept: + - "根据配置决定是否启用全局 errorHandler" + - "将错误写入 useErrorLogStore(用于页面展示/上报)" + +dependency_closure: + runtime: + - "Pinia:src/store/modules/errorLog(useErrorLogStore)" + - "config:src/config/errorLog(或 src/config/index.js 聚合)" + +acceptance: + - "启用后,运行时异常会进入 error log store" + +pitfalls: + - "若 store 未初始化或模块缺失,会导致 errorHandler 内再次报错" diff --git a/front-end/AI-Coding/openspec-lite/modules/plugin-support.yaml b/front-end/AI-Coding/openspec-lite/modules/plugin-support.yaml new file mode 100644 index 0000000..5a6af89 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/plugin-support.yaml @@ -0,0 +1,23 @@ +# Module Spec:plugin-support(生产环境信息/依赖检查) + +module: + id: plugin-support + name: Support 插件(构建信息输出/依赖检查) + type: plugin + entrypoints: + - library/plugins/support.ts + +public_api: + concept: + - "生产环境输出构建信息(__APP_INFO__)" + - "检查关键依赖是否存在(例如 vab-icons)" + +dependency_closure: + runtime: + - "__APP_INFO__ 全局常量(通常由构建注入)" + +acceptance: + - "生产环境能按预期打印/校验,不影响运行" + +pitfalls: + - "依赖检查失败可能影响全局能力(与 vab 插件的保护逻辑相关)" diff --git a/front-end/AI-Coding/openspec-lite/modules/plugin-vab.yaml b/front-end/AI-Coding/openspec-lite/modules/plugin-vab.yaml new file mode 100644 index 0000000..e173b3c --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/plugin-vab.yaml @@ -0,0 +1,29 @@ +# Module Spec:plugin-vab(全局能力注入 + 事件总线) + +module: + id: plugin-vab + name: Vab 插件(gp 全局方法 + mitt 事件总线) + type: plugin + entrypoints: + - library/plugins/vab.ts + +public_api: + concept: + - "通过 app.provide + app.config.globalProperties 注入 gp($baseLoading/$baseMessage/$baseAlert/$baseConfirm/$baseNotify/$baseTableHeight/$pub/$sub/$unsub)" + - "内部使用 mitt 作为事件总线实现 $pub/$sub/$unsub" + +dependency_closure: + runtime: + - "Element Plus:ElLoading / ElMessage / ElMessageBox / ElNotification" + - "mitt" + - "lodash" + - "src/config(loadingText/messageDuration)" + types: + - "types/library.d.ts(globalPropertiesType)" + +acceptance: + - "安装插件后,可通过 inject('$pub')/this.$pub 发布事件" + - "$baseMessage/$baseLoading 能正常工作" + +pitfalls: + - "生产环境存在授权/依赖检查逻辑,可能将 app.config.globalProperties 置空(不要在外部假设 gp 永远存在)" diff --git a/front-end/AI-Coding/openspec-lite/modules/router.yaml b/front-end/AI-Coding/openspec-lite/modules/router.yaml new file mode 100644 index 0000000..4e37cdb --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/router.yaml @@ -0,0 +1,37 @@ +# Module Spec:router(路由与权限守卫) + +module: + id: router + name: 路由与页面组织 + entrypoints: + - src/router/index.ts + - src/router/permissions.ts + +public_api: + exports: + - constantRoutes + - asyncRoutes + - setupPermissions(router) + +usage_examples: + - "新增页面:在 src/views// 添加 Vue 文件,并在 asyncRoutes 中挂载到 Layout children" + +dependency_closure: + runtime: + - "router:vue-router(beforeEach/afterEach)" + - "store-user:token/getUserInfo/resetAll/setVirtualRoles" + - "store-routes:setRoutes(authentication)" + - "store-settings:theme.showProgressBar" + - "config:authentication/loginInterception/routesWhiteList/supportVisit" + - "utils/pageTitle:getPageTitle(to.meta.title)" + - "utils/routes:toLoginRoute" + - "nprogress:VabProgress(含 nprogress.css)" + +acceptance: + - "路由可跳转" + - "刷新后路由仍可恢复" + +pitfalls: + - "如涉及权限/菜单,需同步 meta(title/icon/...);同时注意 routesWhiteList/loginInterception/authentication 等开关影响守卫逻辑" + - "permissions.ts 直接写 document.title;若新项目改为 useHead 等方式需统一" + diff --git a/front-end/AI-Coding/openspec-lite/modules/setup-vab.yaml b/front-end/AI-Coding/openspec-lite/modules/setup-vab.yaml new file mode 100644 index 0000000..8dcf55e --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/setup-vab.yaml @@ -0,0 +1,30 @@ +# Module Spec:setup-vab(library/index.ts:自动加载、图标、样式、插件) + +module: + id: setup-vab + name: setupVab(app)(自动加载 styles/background/plugins + 图标注册) + type: core + entrypoints: + - library/index.ts + +public_api: + concept: + - "setupVab(app) 是本模板把 'library/' 能力接入应用的总入口" + - "它负责:加载 svg 图标、全局样式、注册图标组件、自动加载背景样式与 plugins" + +dependency_closure: + runtime: + - "src/icon/index.ts(svg require.context)" + - "library/styles/vab.scss(全局样式入口)" + - "@vueuse/head(createHead)" + - "vab-icons(VabIcon 组件 + CSS)" + - "@element-plus/icons-vue(全量注册 ElementPlus 图标组件)" + bundler: + - "webpack require.context:background scss / plugins ts 自动加载" + +acceptance: + - "调用 setupVab(app) 后:VabIcon 可用、ElementPlus 图标组件可用" + - "plugins 自动 app.use 安装,$pub/$sub 等全局注入可用(由 plugin-vab 决定)" + +pitfalls: + - "setupVab 依赖 require.context;迁移到非 webpack 构建器需要替代实现(手动 import 或 glob)" diff --git a/front-end/AI-Coding/openspec-lite/modules/store-errorlog.yaml b/front-end/AI-Coding/openspec-lite/modules/store-errorlog.yaml new file mode 100644 index 0000000..6902e90 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/store-errorlog.yaml @@ -0,0 +1,23 @@ +# Module Spec:store-errorlog(错误日志收集 store) + +module: + id: store-errorlog + name: ErrorLog Store(错误日志收集) + type: store + entrypoints: + - src/store/modules/errorLog.ts + +public_api: + concept: + - "addErrorLog:追加错误日志" + - "clearErrorLog:清空错误日志" + +dependency_closure: + runtime: + - "通常由 plugin-errorlog(library/plugins/errorLog.ts)与 request 异常写入触发" + +acceptance: + - "触发 addErrorLog 后 errorLogs 可读取" + +pitfalls: + - "若错误处理链路缺失(plugin/request 未接入),store 仍可用但不会自动产生数据" diff --git a/front-end/AI-Coding/openspec-lite/modules/store-routes.yaml b/front-end/AI-Coding/openspec-lite/modules/store-routes.yaml new file mode 100644 index 0000000..c22fd3e --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/store-routes.yaml @@ -0,0 +1,28 @@ +# Module Spec:store-routes(路由模式/菜单路由状态) + +module: + id: store-routes + name: Routes Store(路由拦截/菜单路由设置) + type: store + entrypoints: + - src/store/modules/routes.ts + +public_api: + concept: + - "setRoutes(mode):根据 authentication/rolesControl 生成可访问路由并 resetRouter" + - "支持前端路由(asyncRoutes)与后端路由(getList -> convertRouter)两种模式" + +dependency_closure: + runtime: + - "router:src/router(asyncRoutes/constantRoutes/resetRouter)" + - "utils/routes:convertRouter/filterRoutes" + - "api/router:getList(后端路由模式)" + - "config:authentication/rolesControl" + - "plugin-vab:gp.$baseMessage(后端路由格式异常提示)" + +acceptance: + - "前端路由模式下:setRoutes() 后 routes 可用于菜单渲染" + - "后端路由模式下:getList 返回 list 后可 convertRouter 并 resetRouter" + +pitfalls: + - "后端路由 list 格式必须符合 convertRouter 预期;否则会提示错误" diff --git a/front-end/AI-Coding/openspec-lite/modules/store-settings.yaml b/front-end/AI-Coding/openspec-lite/modules/store-settings.yaml new file mode 100644 index 0000000..1a7dfa4 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/store-settings.yaml @@ -0,0 +1,36 @@ +# Module Spec:store-settings(全局配置/主题/布局状态) + +module: + id: store-settings + name: Settings Store(主题/布局/语言/折叠等全局配置) + type: store + entrypoints: + - src/store/modules/settings.ts + +public_api: + concept: + - "集中管理全局配置:theme、device、collapse、language、lock、logo、title" + - "持久化:localStorage(theme/collapse/language/...)" + - "主题注入:updateTheme() 动态加载 scss module 并写入 CSS 变量(--el-*)" + +usage_examples: + - "useSettingsStore().updateTheme() 在应用启动或主题变更后调用" + - "useSettingsStore().toggleCollapse() 控制侧边栏折叠" + +dependency_closure: + runtime: + - "src/config(默认 theme/layout/开关项等)" + - "@vueuse/core:useCssVar(如果项目通过 auto-import 或显式引入提供)" + bundler: + - "webpack dynamic require:require(`@vab/styles/variables/vab-${themeName}-variables.module.scss`)" + alias: + - "@ -> src" + - "@vab -> library" + +acceptance: + - "切换 themeName 后 updateTheme() 能更新 Element Plus CSS 变量" + - "刷新后 theme/collapse/language 等能从 localStorage 恢复" + +pitfalls: + - "updateTheme() 使用动态 require scss module;非 webpack 构建需替代实现" + - "useCssVar 的来源依赖工程约定(若未自动注入,需要显式 import)" diff --git a/front-end/AI-Coding/openspec-lite/modules/store-tabs.yaml b/front-end/AI-Coding/openspec-lite/modules/store-tabs.yaml new file mode 100644 index 0000000..c13e1f9 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/store-tabs.yaml @@ -0,0 +1,24 @@ +# Module Spec:store-tabs(标签页 visitedRoutes) + +module: + id: store-tabs + name: Tabs Store(visitedRoutes 管理) + type: store + entrypoints: + - src/store/modules/tabs.ts + +public_api: + concept: + - "addVisitedRoute/delVisitedRoute/delOthers/delLeft/delRight/delAll" + - "确保至少存在一个 noClosable tab(默认第一个)" + +dependency_closure: + runtime: + - "router types:VabRouteRecord(/#/router)" + +acceptance: + - "路由切换时 addVisitedRoute 可累积标签页" + - "关闭/关闭其它/左右/全部等操作不报错" + +pitfalls: + - "对 meta 合并有特殊逻辑(dynamicNewTab/noClosable),迁移时避免改坏行为" diff --git a/front-end/AI-Coding/openspec-lite/modules/store-user.yaml b/front-end/AI-Coding/openspec-lite/modules/store-user.yaml new file mode 100644 index 0000000..aeaee12 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/store-user.yaml @@ -0,0 +1,31 @@ +# Module Spec:store-user(登录/用户信息/登出/重置) + +module: + id: store-user + name: User Store(登录/用户信息/登出/重置) + type: store + entrypoints: + - src/store/modules/user.ts + +public_api: + concept: + - "login/socialLogin:登录并 afterLogin(通知、设置 token)" + - "getUserInfo:拉取 username/avatar/roles/permissions 并写入 acl" + - "logout/resetAll:清空 token/acl/routes/tabs 并 resetRouter" + +dependency_closure: + runtime: + - "api/user:login/getUserInfo/logout/socialLogin" + - "utils/token:getToken/setToken/removeToken" + - "config:tokenName" + - "store:acl/routes/tabs/settings 联动" + - "router:resetRouter" + - "plugin-vab:gp.$baseNotify/$baseMessage(提示)" + +acceptance: + - "登录成功后 token 写入并能继续请求" + - "getUserInfo 后 acl 中 roles/permissions 生效" + - "logout 后 resetAll 清理完成且路由重置" + +pitfalls: + - "logout 内含 location.reload;迁移到新项目需确认是否保留该行为" diff --git a/front-end/AI-Coding/openspec-lite/modules/styles.yaml b/front-end/AI-Coding/openspec-lite/modules/styles.yaml new file mode 100644 index 0000000..9bb01b6 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/styles.yaml @@ -0,0 +1,22 @@ +# Module Spec:styles(library/styles:全局样式/变量/背景) + +module: + id: styles + name: 样式体系(全局样式 + 变量 + 背景) + type: ui + entrypoints: + - library/styles/vab.scss + - library/styles/variables/ + - library/styles/background/ + +public_api: + concept: + - "vab.scss 是全局样式入口(normalize/transition/变量等聚合)" + - "variables 下含布局变量与 Theme 变量(vab-*-variables.module.scss)" + +dependency_closure: + bundler: + - "scss/sass loader(由 Vue CLI 提供)" + +acceptance: + - "引入 vab.scss 后基础样式生效,不影响 Element Plus" diff --git a/front-end/AI-Coding/openspec-lite/modules/ui-components.yaml b/front-end/AI-Coding/openspec-lite/modules/ui-components.yaml new file mode 100644 index 0000000..885ef1b --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/ui-components.yaml @@ -0,0 +1,27 @@ +# Module Spec:ui-components(Vab 组件库总览) + +module: + id: ui-components + name: Vab 组件库(library/components/Vab*) + type: ui + entrypoints: + - library/components/ + +public_api: + concept: + - "每个组件目录对应一个 Vab* 组件族;复用优先按目录整包搬运" + +dependency_closure: + runtime: + - "Element Plus(大量 el-* 依赖)" + - "icons:vab-icons 与 @element-plus/icons-vue(常通过 setupVab 全局注册)" + - "全局样式:library/styles/vab.scss" + alias: + - "@ -> src" + - "@vab -> library" + +acceptance: + - "目标项目能渲染组件(至少 smoke render)" + +pitfalls: + - "部分组件可能依赖路由、store、权限指令等全局插件;抽取前必须列出依赖闭包" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-app-main.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-app-main.yaml new file mode 100644 index 0000000..43ff525 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-app-main.yaml @@ -0,0 +1,27 @@ +# Module Spec:vab-app-main(主内容区:联动 routes store) + +module: + id: vab-app-main + name: VabAppMain(主内容区) + type: ui + entrypoints: + - library/components/VabAppMain/index.vue + +public_api: + concept: + - "监听 route 变化,更新 routes store 的 tab/activeMenu" + - "渲染 vab-router-view + vab-footer" + +dependency_closure: + runtime: + - "vue-router:useRoute" + - "store-routes:tab/activeMenu" + - "utils/routes:handleActivePath" + - "组件依赖:VabRouterView、VabFooter(需同时可用/注册)" + +acceptance: + - "路由变化时 activeMenu.data 更新为当前激活路径" + - "主内容区能渲染 router-view 与 footer" + +pitfalls: + - "tab 使用 route.matched[0].name;若路由层级/匹配为空需要在目标项目确认兼容" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-app.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-app.yaml new file mode 100644 index 0000000..56ef427 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-app.yaml @@ -0,0 +1,21 @@ +# Module Spec:VabApp + +module: + id: vab-app + name: VabApp(应用壳/全局容器) + type: ui + entrypoints: + - library/components/VabApp/index.vue + - library/components/VabApp/ + +usage_examples: + - "App 根组件中使用 作为应用容器" + +dependency_closure: + runtime: + - "可能依赖 router-view 与 Element Plus ConfigProvider(由 VabApp 内部决定)" + - "可能依赖 pwa / i18n / route meta" + +acceptance: + - "渲染不报错" + - "路由切换正常(若内部包含 router-view)" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-avatar.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-avatar.yaml new file mode 100644 index 0000000..15b049c --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-avatar.yaml @@ -0,0 +1,28 @@ +# Module Spec:vab-avatar(用户头像下拉) + +module: + id: vab-avatar + name: VabAvatar(用户头像/用户名下拉) + type: ui + entrypoints: + - library/components/VabAvatar/index.vue + +public_api: + concept: + - "右上角用户头像下拉:个人中心/外链/退出登录,并在退出后跳转到登录页(携带回跳参数)" + +dependency_closure: + runtime: + - "store-user:avatar/username/logout()" + - "vue-router:useRoute/useRouter" + - "utils/routes:toLoginRoute(fullPath)" + - "i18n:translate()" + - "Element Plus:el-dropdown/el-dropdown-menu/el-dropdown-item/el-avatar" + - "VabIcon" + +acceptance: + - "下拉可见时箭头激活态切换" + - "点击退出登录会调用 userStore.logout() 并跳转 toLoginRoute(route.fullPath)" + +pitfalls: + - "依赖 userStore 提供 avatar/username;新项目需保证字段一致" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-breadcrumb.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-breadcrumb.yaml new file mode 100644 index 0000000..d775232 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-breadcrumb.yaml @@ -0,0 +1,28 @@ +# Module Spec:vab-breadcrumb(面包屑) + +module: + id: vab-breadcrumb + name: VabBreadcrumb(面包屑) + type: ui + entrypoints: + - library/components/VabBreadcrumb/index.vue + +public_api: + concept: + - "根据 routesStore.getRoutes + 当前 route.path 生成面包屑,并支持 meta.icon/meta.isCustomSvg" + +dependency_closure: + runtime: + - "store-routes:getRoutes" + - "vue-router:useRoute" + - "utils/routes:handleMatched(routes, path)" + - "i18n:translate()" + - "Element Plus:el-breadcrumb/el-breadcrumb-item" + - "VabIcon" + +acceptance: + - "meta.breadcrumbHidden=true 的路由不出现在面包屑" + - "每个 crumb 的跳转目标使用 item.redirect(若存在)" + +pitfalls: + - "依赖后端路由结构与 meta 字段(title/icon/isCustomSvg)" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-card.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-card.yaml new file mode 100644 index 0000000..281f5fd --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-card.yaml @@ -0,0 +1,25 @@ +# Module Spec:vab-card(卡片封装 + Skeleton) + +module: + id: vab-card + name: VabCard(卡片封装) + type: ui + entrypoints: + - library/components/VabCard/index.vue + +public_api: + concept: + - "对 el-card 的轻封装:header slot/prop + 可选 skeleton loading(默认 500ms 结束)" + +dependency_closure: + runtime: + - "Element Plus:el-card/el-skeleton" + - "vue-router:onBeforeRouteLeave(清理定时器)" + - "SCSS:$base-transition" + +acceptance: + - "skeleton=true 时先显示 skeleton,再渲染默认 slot" + - "路由离开时清理 timer" + +pitfalls: + - "skeletonRows 注释提示:显示数量可能比传入多 1(Element Plus 行为)" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-colorful-card.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-colorful-card.yaml new file mode 100644 index 0000000..2028112 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-colorful-card.yaml @@ -0,0 +1,24 @@ +# Module Spec:vab-colorful-card(渐变卡片) + +module: + id: vab-colorful-card + name: VabColorfulCard(渐变卡片) + type: ui + entrypoints: + - library/components/VabColorfulCard/index.vue + +public_api: + concept: + - "基于 el-card 的渐变背景卡片:支持 header 标题与右上角 icon" + +dependency_closure: + runtime: + - "Element Plus:el-card" + - "VabIcon(可选)" + +acceptance: + - "传入 colorFrom/colorTo 时背景为 linear-gradient" + - "传入 icon 时显示 vab-icon" + +pitfalls: + - "colorFrom/colorTo 需为合法 CSS color 字符串" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-column-bar.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-column-bar.yaml new file mode 100644 index 0000000..8acf493 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-column-bar.yaml @@ -0,0 +1,31 @@ +# Module Spec:vab-column-bar(列式布局左侧 Tab + 二级菜单) + +module: + id: vab-column-bar + name: VabColumnBar(Column 布局列栏) + type: ui + entrypoints: + - library/components/VabColumnBar/index.vue + +public_api: + concept: + - "Column 布局专用:左侧 tabs 切换顶级菜单,右侧 el-menu 渲染 partialRoutes(二级菜单)" + +dependency_closure: + runtime: + - "store-settings:theme(layout/columnStyle)/collapse + foldSideBar/openSideBar" + - "store-routes:tab/tabMenu/activeMenu/routes/partialRoutes" + - "config:defaultOpeneds/openFirstMenu/uniqueOpened" + - "vue-router:useRoute/useRouter" + - "utils/validate:isExternal" + - "i18n:translate()" + - "styles:@vab/styles/variables/variables.module.scss(column-second-menu-background 等)" + - "Element Plus:el-scrollbar/el-tabs/el-tab-pane/el-menu/el-divider" + - "VabLogo/VabMenu/VabIcon" + +acceptance: + - "theme.layout==='column' 时可用;route.meta.noColumn=true 时会自动折叠侧边栏并隐藏 fold-unfold" + - "点击 tab:若 tabMenu.path 为外链则 window.open;否则(openFirstMenu=true)跳转到 redirect 或自身" + +pitfalls: + - "直接操作 DOM:document.querySelector('.fold-unfold') 修改 style;新项目结构不同需适配" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-error-log.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-error-log.yaml new file mode 100644 index 0000000..b445a5a --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-error-log.yaml @@ -0,0 +1,28 @@ +# Module Spec:vab-error-log(错误日志展示入口) + +module: + id: vab-error-log + name: VabErrorLog(错误日志展示) + type: ui + entrypoints: + - library/components/VabErrorLog/index.vue + +public_api: + concept: + - "展示 errorLogs 数量徽标,点击打开弹窗列表" + - "提供清空日志入口(clearErrorLog)" + +dependency_closure: + runtime: + - "store-errorlog:useErrorLogStore(errorLogs/clearErrorLog)" + - "Element Plus:el-badge/el-dialog/el-table/el-tag/el-popover/el-button" + - "VabIcon:全局组件(由 setup-vab 注册)" + related_modules: + - "plugin-errorlog:负责把运行时错误写入 store(否则列表为空)" + +acceptance: + - "errorLogs.length > 0 时显示徽标并可打开弹窗" + - "点击 '暂不显示' 能清空 store" + +pitfalls: + - "仅负责展示;日志产生依赖 plugin/request 链路" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-fold.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-fold.yaml new file mode 100644 index 0000000..b4a299e --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-fold.yaml @@ -0,0 +1,20 @@ +# Module Spec:vab-fold(折叠按钮) + +module: + id: vab-fold + name: VabFold(侧边栏折叠/展开) + type: ui + entrypoints: + - library/components/VabFold/index.vue + +public_api: + concept: + - "根据 settings.collapse 展示不同图标,并触发 settings.toggleCollapse()" + +dependency_closure: + runtime: + - "store-settings:collapse/toggleCollapse" + - "VabIcon" + +acceptance: + - "点击后触发 toggleCollapse 并切换图标" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-footer.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-footer.yaml new file mode 100644 index 0000000..928e2bc --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-footer.yaml @@ -0,0 +1,25 @@ +# Module Spec:vab-footer(页脚) + +module: + id: vab-footer + name: VabFooter(页脚) + type: ui + entrypoints: + - library/components/VabFooter/index.vue + +public_api: + concept: + - "展示年份与站点 title(来自 settings store)" + +dependency_closure: + runtime: + - "store-settings:title" + - "VabIcon" + styles: + - "SCSS 变量:$base-padding / $base-border-color(来自 styles 模块)" + +acceptance: + - "渲染后能显示年份与 title" + +pitfalls: + - "样式依赖全局 SCSS 变量;目标项目缺失变量会导致样式编译失败" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-full-screen.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-full-screen.yaml new file mode 100644 index 0000000..f38f33e --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-full-screen.yaml @@ -0,0 +1,24 @@ +# Module Spec:vab-full-screen(全屏切换) + +module: + id: vab-full-screen + name: VabFullScreen(全屏切换) + type: ui + entrypoints: + - library/components/VabFullScreen/index.vue + +public_api: + concept: + - "点击图标切换全屏状态(useFullscreen().toggle)" + +dependency_closure: + runtime: + - "@vueuse/core:useFullscreen(若项目通过 auto-import 或显式引入提供)" + - "store-settings:theme.showFullScreen" + - "VabIcon" + +acceptance: + - "theme.showFullScreen=true 时显示图标,点击可进入/退出全屏" + +pitfalls: + - "useFullscreen 的来源依赖工程约定(自动导入 vs 显式 import)" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-header.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-header.yaml new file mode 100644 index 0000000..d4ac3df --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-header.yaml @@ -0,0 +1,21 @@ +# Module Spec:VabHeader + +module: + id: vab-header + name: VabHeader(头部导航) + type: ui + entrypoints: + - library/components/VabHeader/index.vue + - library/components/VabHeader/ + +usage_examples: + - "布局组件中使用 VabHeader,承载用户信息/语言切换/全屏/刷新等入口" + +dependency_closure: + runtime: + - "可能依赖 settings store(fixedHeader/showTabs 等)" + - "可能依赖 i18n、router" + +acceptance: + - "渲染不报错" + - "常用操作(如全屏/刷新/语言切换)不崩溃(若组件支持)" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-language.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-language.yaml new file mode 100644 index 0000000..7ad0d38 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-language.yaml @@ -0,0 +1,28 @@ +# Module Spec:vab-language(语言切换) + +module: + id: vab-language + name: VabLanguage(语言切换) + type: ui + entrypoints: + - library/components/VabLanguage/index.vue + +public_api: + concept: + - "通过下拉菜单切换语言:更新 settings.language + i18n locale + document.title" + +dependency_closure: + runtime: + - "store-settings:theme.showLanguage / changeLanguage(language)" + - "vue-i18n:useI18n().locale" + - "vue-router:useRoute(读取 route.meta.title)" + - "utils/pageTitle:getPageTitle" + - "Element Plus:el-dropdown/el-dropdown-menu/el-dropdown-item" + - "VabIcon" + +acceptance: + - "theme.showLanguage=true 时显示入口" + - "切换后 settings.language 与 i18n locale 同步更新" + +pitfalls: + - "document.title 依赖 route.meta.title;目标项目若 meta.title 缺失需适配" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-link.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-link.yaml new file mode 100644 index 0000000..1a8f342 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-link.yaml @@ -0,0 +1,21 @@ +# Module Spec:vab-link(智能链接) + +module: + id: vab-link + name: VabLink(外链/内链统一) + type: ui + entrypoints: + - library/components/VabLink/index.vue + +public_api: + concept: + - "根据 isExternal(to) 自动选择渲染 a 或 router-link" + +dependency_closure: + runtime: + - "utils/validate:isExternal" + - "vue-router:router-link" + +acceptance: + - "外链:target=_blank + rel=noopener" + - "内链:透传 to 给 router-link" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-lock.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-lock.yaml new file mode 100644 index 0000000..4090db2 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-lock.yaml @@ -0,0 +1,29 @@ +# Module Spec:vab-lock(锁屏/解锁) + +module: + id: vab-lock + name: VabLock(屏幕锁) + type: ui + entrypoints: + - library/components/VabLock/index.vue + +public_api: + concept: + - "点击锁屏图标将 settings.lock 置为 true,并隐藏侧边栏 DOM" + - "解锁通过表单校验后将 settings.lock 置为 false,并恢复侧边栏" + +dependency_closure: + runtime: + - "store-settings:theme.showLock / lock / title / handleLock / handleUnLock" + - "store-user:avatar" + - "i18n:translate" + - "Element Plus:el-avatar/el-form/el-form-item/el-input/el-button" + - "浏览器 DOM:document.querySelector('.vab-side-bar')(直接改 style)" + +acceptance: + - "theme.showLock=true 时显示锁图标;点击后 lock=true 并出现锁屏层" + - "解锁成功后 lock=false,页面恢复" + +pitfalls: + - "组件内密码校验为固定值(示例逻辑);迁移到新项目需确认是否替换为真实策略" + - "直接操作 '.vab-side-bar' DOM;若目标项目侧边栏类名不同需要适配" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-logo.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-logo.yaml new file mode 100644 index 0000000..1bce6ff --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-logo.yaml @@ -0,0 +1,26 @@ +# Module Spec:vab-logo(Logo + Title) + +module: + id: vab-logo + name: VabLogo(Logo/标题) + type: ui + entrypoints: + - library/components/VabLogo/index.vue + +public_api: + concept: + - "读取 settings.logo/settings.title,并根据 theme.layout 渲染不同样式的 logo 区域" + +dependency_closure: + runtime: + - "store-settings:theme/layout + logo + title" + - "vue-router:router-link" + - "VabIcon(自定义 svg:is-custom-svg)" + - "SCSS:$base-header-height/$base-logo-height/$base-title-color 等" + +acceptance: + - "logo 存在时使用 vab-icon 渲染自定义 svg" + - "theme.layout==='horizontal' 时标题可隐藏(hidden-xs-only)" + +pitfalls: + - "Column 布局会固定定位 logo,依赖左侧菜单宽度相关变量" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-menu.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-menu.yaml new file mode 100644 index 0000000..e31f0e5 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-menu.yaml @@ -0,0 +1,39 @@ +# Module Spec:VabMenu + +module: + id: vab-menu + name: VabMenu(菜单体系) + type: ui + entrypoints: + - library/components/VabMenu/index.vue + - library/components/VabMenu/components/VabMenuItem.vue + - library/components/VabMenu/components/VabSubMenu.vue + +public_api: + concept: + - "递归渲染菜单:VabMenu 根据子路由可见性选择渲染 VabSubMenu 或 VabMenuItem" + - "点击菜单:根据 meta.target/_blank、外链/内链、同路由刷新等逻辑进行跳转或触发 reload" + +usage_examples: + - "布局组件中引入 VabMenu,配合路由 meta 与权限渲染导航" + +dependency_closure: + runtime: + - "vue-router:useRoute/useRouter(跳转/判断当前路由)" + - "store-settings:collapse/device/theme(layout) + foldSideBar(移动端点击收起)" + - "plugin-vab:inject('$pub')(同路由点击触发 $pub('reload-router-view'))" + - "config:isHashRouterMode(hash 模式下 _blank 打开内部路由)" + - "utils/validate:isExternal(外链判断)" + - "i18n:translate(菜单 title)" + - "Element Plus:el-menu-item/el-sub-menu/el-tag" + - "VabIcon" + +acceptance: + - "item.children 中存在可见子路由时渲染 VabSubMenu,否则渲染 VabMenuItem" + - "meta.target === '_blank' 时按外链/内链规则在新窗口打开" + - "点击当前已激活路由时触发 $pub('reload-router-view')" + +pitfalls: + - "组件内部使用 webpack require.context 自动注册子组件;迁移到非 webpack 构建器需等效替代" + - "Element Plus 关于 teleported 的历史兼容问题:弹层渲染到 body 下时需要全局样式配合(见 VabMenu/index.vue 注释)" + - "如果菜单权限依赖 ACL/指令(v-permissions/hasPermission),迁移时必须把相关依赖一起带走" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-nav.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-nav.yaml new file mode 100644 index 0000000..5bc58d3 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-nav.yaml @@ -0,0 +1,29 @@ +# Module Spec:vab-nav(顶部导航条聚合) + +module: + id: vab-nav + name: VabNav(顶部导航条) + type: ui + entrypoints: + - library/components/VabNav/index.vue + +public_api: + concept: + - "顶部导航聚合:左侧(Fold + Tabs/Breadcrumb),右侧(ErrorLog/Lock/Search/Notice/FullScreen/Language/Theme/Refresh/Avatar)" + +dependency_closure: + runtime: + - "store-routes:tab/tabMenu/routes" + - "vue-router:useRouter" + - "utils/validate:isExternal" + - "config:openFirstMenu" + - "i18n:translate()" + - "Element Plus:el-row/el-col/el-tabs/el-tab-pane" + - "Components:VabFold/VabBreadcrumb/VabErrorLog/VabLock/VabSearch/VabNotice/VabFullScreen/VabLanguage/VabTheme/VabRefresh/VabAvatar" + +acceptance: + - "layout='comprehensive' 时显示顶部 tabs,否则显示面包屑(hidden-xs-only)" + - "点击 tab:外链 window.open;否则(openFirstMenu=true)跳 redirect 或自身" + +pitfalls: + - "强依赖多组件与 routesStore 输出结构;抽取时必须把依赖组件闭包一起带走" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-notice.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-notice.yaml new file mode 100644 index 0000000..e97a4fe --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-notice.yaml @@ -0,0 +1,31 @@ +# Module Spec:vab-notice(消息中心) + +module: + id: vab-notice + name: VabNotice(消息中心/通知) + type: ui + entrypoints: + - library/components/VabNotice/index.vue + +public_api: + concept: + - "显示通知 badge,弹出 popover + tabs 展示通知/邮件" + - "从 api/notice.getList 拉取数据并计算 badge" + - "提供清空消息入口(仅清空前端列表与 badge,并提示)" + +dependency_closure: + runtime: + - "store-settings:theme.showNotice" + - "api/notice:getList" + - "plugin-vab:$baseMessage(清空提示,通过 inject 获取)" + - "i18n:translate" + - "Element Plus:el-badge/el-popover/el-tabs/el-tab-pane/el-scrollbar/el-avatar/el-button" + - "VabIcon" + +acceptance: + - "theme.showNotice=true 时显示通知入口与 badge" + - "点击/切换 tab 会触发 fetchData 更新列表" + +pitfalls: + - "数据协议依赖 notice.getList 的返回结构(list/total)" + - "清空仅影响前端状态,不等价于后端已读/删除" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-query-form.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-query-form.yaml new file mode 100644 index 0000000..df350e2 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-query-form.yaml @@ -0,0 +1,29 @@ +# Module Spec:vab-query-form(查询表单布局容器) + +module: + id: vab-query-form + name: VabQueryForm(查询条件容器) + type: ui + entrypoints: + - library/components/VabQueryForm/index.vue + - library/components/VabQueryForm/components/VabQueryFormTopPanel.vue + - library/components/VabQueryForm/components/VabQueryFormBottomPanel.vue + - library/components/VabQueryForm/components/VabQueryFormLeftPanel.vue + - library/components/VabQueryForm/components/VabQueryFormRightPanel.vue + +public_api: + concept: + - "基于 Element Plus el-row 的 slot 容器,用于统一查询表单布局与间距" + +dependency_closure: + runtime: + - "Element Plus:el-row" + styles: + - "SCSS 变量:$base-input-height / $base-margin(来自全局样式变量体系,见 styles 模块)" + +acceptance: + - "作为容器包裹 el-form-item/el-button 时布局与间距符合预期" + +pitfalls: + - "样式依赖全局 SCSS 变量;目标项目若未引入对应变量会导致样式编译失败" + diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-refresh.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-refresh.yaml new file mode 100644 index 0000000..1e82dc6 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-refresh.yaml @@ -0,0 +1,25 @@ +# Module Spec:vab-refresh(刷新当前路由视图) + +module: + id: vab-refresh + name: VabRefresh(刷新按钮) + type: ui + entrypoints: + - library/components/VabRefresh/index.vue + +public_api: + concept: + - "点击后通过事件总线发布 reload-router-view,刷新当前 router-view 缓存" + +dependency_closure: + runtime: + - "store-settings:theme.showRefresh(是否显示按钮)" + - "plugin-vab:$pub(发布事件)" + related_modules: + - "vab-router-view:订阅并处理 reload-router-view" + +acceptance: + - "theme.showRefresh=true 时显示图标,点击触发 $pub('reload-router-view')" + +pitfalls: + - "未安装 plugin-vab 或未接入 vab-router-view 时,点击不会产生效果" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-router-view.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-router-view.yaml new file mode 100644 index 0000000..0a2c57b --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-router-view.yaml @@ -0,0 +1,31 @@ +# Module Spec:vab-router-view(路由视图壳:keep-alive + 过渡 + reload 事件) + +module: + id: vab-router-view + name: VabRouterView(router-view + keep-alive + reload) + type: ui + entrypoints: + - library/components/VabRouterView/index.vue + +public_api: + concept: + - "统一承载页面 router-view,提供 keep-alive include 列表与过渡动画" + - "监听事件总线:reload-router-view,用于刷新当前视图缓存" + +dependency_closure: + runtime: + - "vue-router:useRoute / " + - "plugin-vab:$sub/$unsub(事件订阅)" + - "store-settings:theme.showProgressBar / theme.showPageTransition" + - "store-tabs:visitedRoutes(生成 keepAliveNameList)" + - "utils/routes:handleActivePath(生成 routerKey)" + - "config:keepAliveMaxNum" + - "nprogress(显示刷新进度条)" + +acceptance: + - "路由切换时 keep-alive include 列表随 visitedRoutes 更新" + - "$sub('reload-router-view') 触发后,当前视图能被重新渲染(routerKey 变更)" + +pitfalls: + - "存在 get-code 事件并依赖组件的 __source 字段;不同构建/插件下可能不存在" + - "未安装 plugin-vab 时 $sub/$unsub 不存在,reload/get-code 事件不会工作" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-search.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-search.yaml new file mode 100644 index 0000000..a0e0e86 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-search.yaml @@ -0,0 +1,30 @@ +# Module Spec:vab-search(菜单搜索:Ctrl/⌘+K) + +module: + id: vab-search + name: VabSearch(菜单搜索) + type: ui + entrypoints: + - library/components/VabSearch/index.vue + +public_api: + concept: + - "通过 Ctrl/⌘+K 或点击图标打开搜索面板(teleport 到 body)" + - "从 routes store 的 getRoutes 平铺菜单项并模糊搜索" + - "维护本地搜索历史 localStorage(key=vab_search_history)" + +dependency_closure: + runtime: + - "store-routes:getRoutes(菜单路由来源)" + - "vue-router:useRouter(router.push 内链跳转)" + - "浏览器 API:window.addEventListener('keydown'), navigator.userAgent, localStorage" + - "VabIcon:搜索图标与历史删除图标" + +acceptance: + - "Ctrl/⌘+K 可打开/关闭面板(Escape 关闭)" + - "输入关键字能过滤菜单并 Enter 跳转" + - "搜索历史能写入/删除/清空" + +pitfalls: + - "该组件包含较多样式与 DOM 交互(teleport/fixed mask);抽取到新项目需确保全局样式/层级不冲突" + - "菜单来源依赖 routes store 已完成 setRoutes;否则列表为空" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-sidebar.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-sidebar.yaml new file mode 100644 index 0000000..820f435 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-sidebar.yaml @@ -0,0 +1,20 @@ +# Module Spec:VabSideBar + +module: + id: vab-sidebar + name: VabSideBar(侧边栏容器) + type: ui + entrypoints: + - library/components/VabSideBar/index.vue + - library/components/VabSideBar/ + +usage_examples: + - "在布局中使用 VabSideBar 包裹 VabMenu,实现侧边导航" + +dependency_closure: + runtime: + - "settings store:collapse/device 等" + - "Element Plus(如内部使用 el-scrollbar 等)" + +acceptance: + - "折叠状态切换后布局不崩溃" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-tabs.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-tabs.yaml new file mode 100644 index 0000000..09850f1 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-tabs.yaml @@ -0,0 +1,21 @@ +# Module Spec:VabTabs + +module: + id: vab-tabs + name: VabTabs(多标签页/导航标签) + type: ui + entrypoints: + - library/components/VabTabs/index.vue + - library/components/VabTabs/ + +usage_examples: + - "布局头部或主区域放置 VabTabs,展示已访问路由并支持关闭/切换" + +dependency_closure: + runtime: + - "vue-router:监听路由变化" + - "store:tabs 状态(如果实现依赖 store 模块)" + +acceptance: + - "访问多个路由后能出现多个 tab(如功能支持)" + - "切换/关闭 tab 不崩溃" diff --git a/front-end/AI-Coding/openspec-lite/modules/vab-theme.yaml b/front-end/AI-Coding/openspec-lite/modules/vab-theme.yaml new file mode 100644 index 0000000..d6ee8fc --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/modules/vab-theme.yaml @@ -0,0 +1,40 @@ +# Module Spec:vab-theme(主题系统入口与交互) + +module: + id: vab-theme + name: VabTheme(主题配置入口/抽屉/设置面板) + type: ui + entrypoints: + - library/components/VabTheme/index.vue + - library/components/VabTheme/components/VabThemeDrawer.vue + - library/components/VabTheme/components/VabThemeSetting.vue + - library/components/VabTheme/components/ + +public_api: + concept: + - "VabTheme:一个刷子图标入口,用于触发打开主题抽屉(Drawer)" + - "VabThemeDrawer:主题抽屉,监听事件总线打开/随机主题,并调用 settings store 的 saveTheme/resetTheme/updateTheme" + - "VabThemeSetting:右侧固定设置入口(主题配置/随机换肤/购买源码/清理缓存),通过事件总线触发行为" + +usage_examples: + - "点击 VabTheme 触发 $pub('theme') 打开抽屉" + - "点击 VabThemeSetting 的随机换肤触发 $pub('random-theme')" + +dependency_closure: + runtime: + - "Pinia settings store:src/store/modules/settings(theme.showTheme、saveTheme/resetTheme/updateTheme 等)" + - "事件总线/全局注入:library/plugins/vab.ts($pub/$sub/$unsub/$baseLoading 等)" + - "i18n translate:src/i18n/index.ts(组件内调用 translate)" + - "Element Plus:Drawer/Radio/Select/Button 等" + styles: + - "主题变量:library/styles/variables/vab-*-variables.module.scss(updateTheme() 动态注入 CSS 变量)" + +acceptance: + - "页面渲染包含主题入口(刷子图标)" + - "触发 $pub('theme') 时抽屉能打开;关闭时不报错" + - "保存/重置主题后调用 updateTheme() 生效(CSS 变量与 body class 变化)" + +pitfalls: + - "Theme 依赖 $pub/$sub 注入;若未安装 vab 插件,事件不会触发" + - "settings.updateTheme() 依赖 webpack 的 require 动态引入 scss module;迁移到非 webpack 环境需等效实现" + - "移动端(<992)某些操作会触发 reload;抽取时需确认期望行为" diff --git a/front-end/AI-Coding/openspec-lite/project.yaml b/front-end/AI-Coding/openspec-lite/project.yaml new file mode 100644 index 0000000..4b11cbd --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/project.yaml @@ -0,0 +1,64 @@ +# OpenSpec-lite: 项目级规格(轻量版) +# 目的:用“可机读约束”引导 AI 在不改动现有代码的情况下复用资源。 + +project: + name: admin-plus-template + intent: "公司内部模板库/基础库(轻量化,优先文档约束)" + stack: + framework: "Vue 3" + build: "Vue CLI 5" + language: "TypeScript" + state: "Pinia" + router: "Vue Router" + ui: "Element Plus" + +constraints: + no_code_changes_by_default: true + prefer_docs_over_refactor: true + extraction_style: + - "copy-module" # 优先整目录搬运再裁剪 + - "copy-snippet" # 仅在模块过大或依赖不清晰时使用 + + required_output_sections: + - "changed_files" # 变更文件清单 + - "extracted_modules" # 抽取模块清单(来源->目标) + - "dependency_closure" # 依赖闭包(imports/运行时/类型/alias) + - "acceptance_commands" # 验收命令 + - "risks" # 风险说明 + + forbidden_by_default: + - "changing runtime behavior in production" # 禁止默认更改生产行为 + - "introducing new global side effects" # 禁止新增全局副作用 + - "deep-importing internal paths of modules" # 禁止消费者深层路径引用 + +acceptance: + minimum_commands: + - "pnpm run serve" + recommended_commands: + - "pnpm run lint" + - "pnpm run test:unit" + - "pnpm run build" + +repository: + entrypoints: + - "src/main.ts" + - "library/index.ts" + - "src/router/index.ts" + - "src/store/index.ts" + - "src/i18n/index.ts" + - "mock/index.js" + +module_roots: + ui_components_dir: "library/components" + layouts_dir: "library/layouts" + plugins_dir: "library/plugins" + styles_dir: "library/styles" + store_modules_dir: "src/store/modules" + api_dir: "src/api" + utils_dir: "src/utils" + config_dir: "src/config" + icons_dir: "src/icon" + +notes: + - "配置读取来自 src/config/index.js(被 vue.config.js require)" + - "library/index.ts 存在插件自动全量加载;抽取时需显式列出依赖闭包" diff --git a/front-end/AI-Coding/openspec-lite/tasks/add-api-and-mock.yaml b/front-end/AI-Coding/openspec-lite/tasks/add-api-and-mock.yaml new file mode 100644 index 0000000..27e037b --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/tasks/add-api-and-mock.yaml @@ -0,0 +1,25 @@ +# Task Template: 新增 API + Mock(轻量版) + +task: + id: add-api-and-mock + intent: "新增一个 API 调用函数,并同步补一个 Mock 路由(用于开发联调)" + +inputs: + required: + - domain + - function_name + - method # get/post/... + - path # 例如 /router/getList + +outputs: + files_to_change: + - "src/api/.ts" + - "mock/controller/.js" + +rules: + - "API path 与 Mock path 必须一致" + - "Mock 路由对象必须包含 url/type/response" + +acceptance: + - "开发环境请求能命中 mock" + - "API 函数能被页面调用且类型不报错(若涉及类型需补 types/)" diff --git a/front-end/AI-Coding/openspec-lite/tasks/add-page.yaml b/front-end/AI-Coding/openspec-lite/tasks/add-page.yaml new file mode 100644 index 0000000..b588bc0 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/tasks/add-page.yaml @@ -0,0 +1,28 @@ +# Task Template: 新增页面 + 路由(轻量版) +# 目标:约束 AI 在本仓库(或迁移后的新仓库)新增页面时的输出与验收。 + +task: + id: add-page + intent: "新增一个页面,并把路由挂载到 Layout(如需要,同步 meta 供菜单/面包屑使用)" + +inputs: + required: + - module # 例如 goods / userManagement + - page_name # 例如 list / detail + - route_path # 例如 /goods/list + - route_name # 例如 GoodsList + +outputs: + files_to_change: + - "src/views//.vue" + - "src/router/index.ts" + +rules: + - "页面文件必须位于 src/views// 下" + - "路由必须挂载到 asyncRoutes 的 Layout children(除非明确说明是 constantRoutes)" + - "如需要菜单展示,必须填写 meta.title;如需要图标,必须填写 meta.icon" + +acceptance: + - "访问 route_path 能渲染页面" + - "刷新后仍可访问(路由可恢复)" + - "若配置 meta,菜单/面包屑能展示正确标题" diff --git a/front-end/AI-Coding/openspec-lite/tasks/extract-component.yaml b/front-end/AI-Coding/openspec-lite/tasks/extract-component.yaml new file mode 100644 index 0000000..1107da0 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/tasks/extract-component.yaml @@ -0,0 +1,37 @@ +# Task Template: 抽取组件(library/components/Vab*) + +task: + id: extract-component + intent: "从本仓库抽取一个或多个 Vab 组件到目标项目,确保依赖闭包完整" + +inputs: + required: + - components # 例如 ["VabTabs", "VabMenu"] + - target_project_type + - extraction_style + +scope: + base_dir: "library/components" + +dependency_closure_checklist: + runtime: + - "Element Plus 组件是否使用(el-*)" + - "icons:vab-icons 与 @element-plus/icons-vue 是否需要" + - "全局样式:library/styles/vab.scss 是否是前置依赖" + app_wiring: + - "是否依赖 setupVab 的全局注册(VabIcon / 图标注册 / 插件自动加载)" + alias: + - "@ -> src" + - "@vab -> library" + +outputs: + required_sections: + - changed_files + - extracted_modules + - dependency_closure + - acceptance_commands + - risks + +acceptance: + - "目标项目可渲染组件" + - "关键交互可用(按组件规格中的验收点)" diff --git a/front-end/AI-Coding/openspec-lite/tasks/extract-layout.yaml b/front-end/AI-Coding/openspec-lite/tasks/extract-layout.yaml new file mode 100644 index 0000000..4714457 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/tasks/extract-layout.yaml @@ -0,0 +1,44 @@ +# Task Template: 抽取布局体系(library/layouts) + +task: + id: extract-layout + intent: "从本仓库抽取布局体系到目标项目,最小闭包迁移,尽量不改动原仓库代码" + +inputs: + required: + - target_project_type # vue-cli | vite | other + - extraction_style # copy-module | copy-snippet + +scope: + entrypoints: + - library/layouts/index.vue + - library/layouts/VabLayoutVertical/ + - library/layouts/VabLayoutHorizontal/ + - library/layouts/VabLayoutCommon/ + - library/layouts/VabLayoutComprehensive/ + - library/layouts/VabLayoutFloat/ + - library/layouts/VabLayoutColumn/ + +dependency_closure_checklist: + runtime: + - "Element Plus(el-backtop 等)" + - "Theme 组件(library/components/VabTheme)是否随闭包迁移" + - "事件总线注入(library/plugins/vab.ts:$pub/$sub/$unsub)是否可用" + - "Pinia settings store:src/store/modules/settings(theme/layout/collapse/device)" + - "全局样式变量:library/styles/variables/(布局 SCSS 使用的变量)" + - "Theme 变量:library/styles/variables/vab-*-variables.module.scss(settings.updateTheme() 依赖)" + alias: + - "@ -> src" + - "@vab -> library" + +outputs: + required_sections: + - changed_files + - extracted_modules + - dependency_closure + - acceptance_commands + - risks + +acceptance: + - "Layout 能渲染" + - "切换移动端/桌面端时不崩溃(resize/watch 逻辑生效)" diff --git a/front-end/AI-Coding/openspec-lite/tasks/extract-module.yaml b/front-end/AI-Coding/openspec-lite/tasks/extract-module.yaml new file mode 100644 index 0000000..38936b7 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/tasks/extract-module.yaml @@ -0,0 +1,30 @@ +# Task Template: 抽取/复用模块(轻量版) + +task: + id: extract-module + intent: "从本仓库抽取模块到目标项目,尽量不改动原仓库代码" + +inputs: + required: + - module_id + - target_project_type # vue-cli | vite | other + - extraction_style # copy-module | copy-snippet + +outputs: + required_sections: + - changed_files + - extracted_modules + - dependency_closure + - acceptance_commands + - risks + +procedure: + - step: "从 openspec-lite/manifest.yaml 选择 module_id 并列出 entrypoints" + - step: "读取入口文件,分析 import 依赖与运行时依赖(样式、插件、types、alias)" + - step: "按 extraction_style 抽取(优先 copy-module)" + - step: "在目标项目补齐依赖(package.json、alias、types include)" + - step: "给出最小验收命令并说明风险" + +acceptance: + - "目标项目可启动(serve)" + - "模块能力可用(根据 module_id 的验收点)" diff --git a/front-end/AI-Coding/openspec-lite/tasks/extract-plugin.yaml b/front-end/AI-Coding/openspec-lite/tasks/extract-plugin.yaml new file mode 100644 index 0000000..e29e159 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/tasks/extract-plugin.yaml @@ -0,0 +1,35 @@ +# Task Template: 抽取插件(library/plugins/*) + +task: + id: extract-plugin + intent: "从本仓库抽取一个或多个插件到目标项目,确保注入点与依赖闭包完整" + +inputs: + required: + - plugins # 例如 ["vab", "directive", "errorLog", "support"] + - target_project_type + - extraction_style + +scope: + base_dir: "library/plugins" + +dependency_closure_checklist: + runtime: + - "vab:Element Plus 的 ElMessage/ElLoading/ElMessageBox/ElNotification 是否全局可用" + - "directive:src/utils/permission 与权限数据来源是否齐全" + - "errorLog:src/store/modules/errorLog 与 config/errorLog 是否齐全" + - "support:__APP_INFO__ 注入方式是否一致" + wiring: + - "是否通过 setupVab(library/index.ts) 自动 app.use() 安装;若非自动,需要在入口手动 app.use()" + +outputs: + required_sections: + - changed_files + - extracted_modules + - dependency_closure + - acceptance_commands + - risks + +acceptance: + - "插件安装后,不影响应用启动" + - "涉及的注入能力/指令/错误捕获按模块规格验收" diff --git a/front-end/AI-Coding/openspec-lite/tasks/extract-theme.yaml b/front-end/AI-Coding/openspec-lite/tasks/extract-theme.yaml new file mode 100644 index 0000000..d7c6961 --- /dev/null +++ b/front-end/AI-Coding/openspec-lite/tasks/extract-theme.yaml @@ -0,0 +1,41 @@ +# Task Template: 抽取主题系统(VabTheme + settings 主题闭包) + +task: + id: extract-theme + intent: "从本仓库抽取 Theme 系统到目标项目,确保事件总线 + settings theme 闭包完整" + +inputs: + required: + - target_project_type # vue-cli | vite | other + - extraction_style # copy-module | copy-snippet + +scope: + entrypoints: + - library/components/VabTheme/ + - src/store/modules/settings.ts + - library/plugins/vab.ts + - library/styles/variables/ + +dependency_closure_checklist: + runtime: + - "Pinia settings store 是否存在并已在入口初始化" + - "vab 插件是否安装($pub/$sub/$baseLoading 注入)" + - "i18n translate 是否可用" + - "Element Plus Drawer/表单控件是否可用" + bundler: + - "settings.updateTheme() 的 require('@vab/styles/variables/vab-*-variables.module.scss') 在目标构建器中是否可用" + alias: + - "@ -> src" + - "@vab -> library" + +outputs: + required_sections: + - changed_files + - extracted_modules + - dependency_closure + - acceptance_commands + - risks + +acceptance: + - "触发 $pub('theme') 能打开 Theme Drawer" + - "修改主题并保存后,刷新页面主题持久化且样式变量生效" diff --git a/front-end/LICENSE b/front-end/LICENSE new file mode 100644 index 0000000..a612ad9 --- /dev/null +++ b/front-end/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/front-end/README.md b/front-end/README.md new file mode 100644 index 0000000..d538a99 --- /dev/null +++ b/front-end/README.md @@ -0,0 +1,165 @@ +# Admin Plus(Vue3 企业级开发模板) + +面向公司项目的前端模板与基础库:内置 `Vue 3 + Vue CLI 5 + TypeScript + Pinia + Vue Router + Element Plus`,并集成 Mock、代码生成(Plop)、多套布局组件与常用工具链,支持在此基础上快速裁剪与复用。 + +> 本仓库同时提供面向 AI 的结构说明与模块索引:见 [AI-Coding/README.md](AI-Coding/README.md)。 + +--- + +## 亮点 + +- **开箱即用**:工程化配置齐全(TS/ESLint/Prettier/Stylelint/Unit Test)。 +- **可扩展的 UI/布局体系**:`library/layouts/`、`library/components/` 内置多布局与业务型基础组件。 +- **Mock 一体化**:开发环境通过 `devServer.setupMiddlewares` 自动挂载 `mock/` 路由。 +- **现代依赖栈**:`Element Plus` + `@element-plus/icons-vue`,并内置图标组件 `vab-icons`。 +- **可生成代码**:通过 `plop` 快速生成页面/CRUD/组件/mock&api。 + +--- + +## 环境要求 + +- Node.js:建议 **16/18/20 LTS**(与 Vue CLI 5 兼容) +- 包管理器:推荐 `pnpm`(项目脚本也提供 `npm` 用法) + +--- + +## 快速开始 + +### 1)安装依赖 + +```powershell +cd d:\project\Web_Template_Vue3_Dev +pnpm i +``` + +如你使用 npm: + +```powershell +cd d:\project\Web_Template_Vue3_Dev +npm i +``` + +### 2)启动开发环境 + +```powershell +pnpm run serve +``` + +### 3)构建 + +```powershell +pnpm run build +``` + +### 4)单元测试 / 代码质量 + +```powershell +pnpm run test:unit +pnpm run lint +pnpm run lint:eslint +pnpm run lint:prettier +pnpm run lint:stylelint +``` + +--- + +## 常用脚本(scripts) + +来自 `package.json`: + +- **`serve`**:启动开发服务器(本地 Mock 会自动挂载)。 +- **`build`**:生产构建。 +- **`build:compress` / `compress`**:构建后压缩产物(`scripts/compress.js`)。 +- **`build:website`**:站点构建(使用 `VAB_VARIABLE=website` + 压缩)。 +- **`test:unit`**:单元测试。 +- **`lint`**:Vue CLI ESLint。 +- **`lint:eslint` / `lint:prettier` / `lint:stylelint`**:更精细的格式化与风格修复。 +- **`template`**:启动 `plop` 代码生成。 + +--- + +## 项目结构(速览) + +> 更完整、可供 AI 检索复用的索引见 [AI-Coding/README.md](AI-Coding/README.md)。 + +```text +src/ 应用主代码(入口、路由、状态、国际化、业务页面) +library/ 模板基础库(全局样式、插件集合、构建扩展等) +library/components/ 可复用基础组件(Vab* 体系) +library/layouts/ 多套布局(含主 Layout 入口) +mock/ 本地 Mock 服务(devServer 中间件挂载) +types/ 全局类型声明与业务类型 +tests/unit/ 单元测试 +scripts/ 构建后处理脚本(如压缩) +public/ 静态资源与 HTML 模板 +``` + +--- + +## 关键约定与入口 + +### 应用入口 + +- `src/main.ts` + - 创建应用并挂载:`createApp(App)` + - 初始化顺序:`setupVab` → `setupI18n` → `setupStore` → `setupRouter` + - 生产环境可自动启用 Mock:当 `baseURL` 不是外链地址时(见 `src/main.ts` 的判断) + +### 路由 + +- `src/router/index.ts` + - `constantRoutes`:如登录/注册/404 等基础路由 + - `asyncRoutes`:业务路由(包含 `Layout` 作为壳) + +### 状态管理 + +- `src/store/index.ts`:Pinia 初始化与注入。 + +### 国际化 + +- `src/i18n/index.ts` + - `createI18n({ legacy:false })` + - 当前语言读取自 `useSettingsStore(pinia)` + +### Mock + +- `mock/index.js` + - 挂载到开发服务器中间件(在 `vue.config.js` 的 `devServer.setupMiddlewares`) + - 基于 `mockjs` 返回数据,并支持文件变更热更新 + +--- + +## 别名(Alias) + +来自 `vue.config.js` / `tsconfig.json`: + +- `@` → `src` +- `~` → 项目根目录 +- `/#` → `types` +- `@vab` → `library` +- `@gp` → `library/plugins/vab` + +--- + +## 代码生成(Plop) + +项目提供 `plop` 生成器(见 `plopfile.js`): + +- `view`:页面 +- `curd`:CRUD +- `component`:组件 +- `mock&api`:Mock 与 API 片段 + +运行: + +```powershell +pnpm run template +``` + +--- + +## 贡献与团队协作建议 + +- 建议以“业务模块”为单位在 `src/views/` 组织页面。 +- API 按域拆分在 `src/api/`,与 `mock/controller/` 保持同名与路径一致,方便对照。 +- 公共能力优先沉淀在 `library/`(全局插件/样式/构建扩展)与 `library/components/`(可视组件)。 diff --git a/front-end/babel.config.js b/front-end/babel.config.js new file mode 100644 index 0000000..f47f7db --- /dev/null +++ b/front-end/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: ['@vue/cli-plugin-babel/preset'], +} diff --git a/front-end/git.sh b/front-end/git.sh new file mode 100644 index 0000000..601d88f --- /dev/null +++ b/front-end/git.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -e +git config --global http.proxy http://127.0.0.1:4780; +git config --global https.proxy https://127.0.0.1:4780; + +exec /bin/bash \ No newline at end of file diff --git a/front-end/jsconfig.json b/front-end/jsconfig.json new file mode 100644 index 0000000..c2c6000 --- /dev/null +++ b/front-end/jsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "baseUrl": "./", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "paths": { + "~/*": ["*"], + "@/*": ["src/*"], + "/#/*": ["types/*"], + "@vab/*": ["library/*"], + "@gp": ["library/plugins/vab"] + }, + "lib": ["esnext", "dom", "dom.iterable", "scripthost"] + }, + "exclude": ["node_modules", "dist"] +} diff --git a/front-end/library/build/chainWebpack/banner/config.ts b/front-end/library/build/chainWebpack/banner/config.ts new file mode 100644 index 0000000..0c5b4fa --- /dev/null +++ b/front-end/library/build/chainWebpack/banner/config.ts @@ -0,0 +1,6 @@ +module.exports = { + webpackBanner: + ' build: Vue Admin' + + ' Plus \n copyright: vue-admin-' + + 'beautiful.com \n time: ', +} diff --git a/front-end/library/build/chainWebpack/banner/index.ts b/front-end/library/build/chainWebpack/banner/index.ts new file mode 100644 index 0000000..30230d1 --- /dev/null +++ b/front-end/library/build/chainWebpack/banner/index.ts @@ -0,0 +1,12 @@ +const Webpack = require('webpack') +const { webpackBanner } = require('./config.ts') + +module.exports = { + createBanner: (config) => { + config + .plugin('banner') + .use(Webpack.BannerPlugin, [ + `${webpackBanner}${process.env.VUE_APP_UPDATE_TIME}`, + ]) + }, +} diff --git a/front-end/library/build/chainWebpack/build7z/index.ts b/front-end/library/build/chainWebpack/build7z/index.ts new file mode 100644 index 0000000..3d85d0f --- /dev/null +++ b/front-end/library/build/chainWebpack/build7z/index.ts @@ -0,0 +1,22 @@ +const dayjs = require('dayjs') +const { outputDir, abbreviation } = require('../../../../src/config') +const FileManagerPlugin = require('filemanager-webpack-plugin') + +module.exports = { + createBuild7z: (config) => { + config.plugin('fileManager').use(FileManagerPlugin, [ + { + events: { + onEnd: { + archive: [ + { + source: `./${outputDir}`, + destination: `./${outputDir}/${abbreviation}_${dayjs().unix()}.zip`, + }, + ], + }, + }, + }, + ]) + }, +} diff --git a/front-end/library/build/chainWebpack/gzip/index.ts b/front-end/library/build/chainWebpack/gzip/index.ts new file mode 100644 index 0000000..f8b8c27 --- /dev/null +++ b/front-end/library/build/chainWebpack/gzip/index.ts @@ -0,0 +1,16 @@ +const productionGzipExtensions = ['html', 'js', 'css', 'svg'] +const CompressionWebpackPlugin = require('compression-webpack-plugin') + +module.exports = { + createGzip: (config) => { + config.plugin('compression').use(CompressionWebpackPlugin, [ + { + filename: '[path][base].gz[query]', + algorithm: 'gzip', + test: new RegExp(`\\.(${productionGzipExtensions.join('|')})$`), + threshold: 8192, + minRatio: 0.8, + }, + ]) + }, +} diff --git a/front-end/library/build/chainWebpack/imageCompression/index.ts b/front-end/library/build/chainWebpack/imageCompression/index.ts new file mode 100644 index 0000000..9f818b5 --- /dev/null +++ b/front-end/library/build/chainWebpack/imageCompression/index.ts @@ -0,0 +1,12 @@ +module.exports = { + createImageCompression: (config) => { + config.module + .rule('images') + .use('image-webpack-loader') + .loader('image-webpack-loader') + .options({ + bypassOnDebug: true, + }) + .end() + }, +} diff --git a/front-end/library/build/chainWebpack/index.ts b/front-end/library/build/chainWebpack/index.ts new file mode 100644 index 0000000..e928719 --- /dev/null +++ b/front-end/library/build/chainWebpack/index.ts @@ -0,0 +1,43 @@ +const { createGzip } = require('./gzip/index.ts') +const { createBanner } = require('./banner/index.ts') +const { createBuild7z } = require('./build7z/index.ts') +const { createSvgSprite } = require('./svgSprite/index.ts') +const { createOptimization } = require('./optimization/index.ts') +const { createSourceInjector } = require('./sourceInjector/index.ts') +const { createImageCompression } = require('./imageCompression/index.ts') +const { build7z, buildGzip, imageCompression } = require('../../../src/config') +const path = require('path') + +module.exports = { + createChainWebpack: (env, config) => { + config.resolve.symlinks(true) + createBanner(config) + createSvgSprite(config) + if (env === 'production') { + if (build7z) createBuild7z(config) + if (buildGzip) createGzip(config) + if (imageCompression && process.env.VAB_VARIABLE !== 'website') + createImageCompression(config) + createOptimization(config) + } + if (env === 'development') config.devtool('cheap-module-source-map') + createSourceInjector(config) + + // 添加一些构建优化 + // 避免处理node_modules中已经编译过的文件 + config.module + .rule('js') + .include.add(path.resolve('src')) + .add(path.resolve('library')) + .end() + .exclude.add(/node_modules/) + .end() + + // 优化构建性能 + config.plugin('fork-ts-checker').tap((options) => { + options[0].formatter = 'codeframe' + options[0].async = false + return options + }) + }, +} diff --git a/front-end/library/build/chainWebpack/optimization/index.ts b/front-end/library/build/chainWebpack/optimization/index.ts new file mode 100644 index 0000000..aea1950 --- /dev/null +++ b/front-end/library/build/chainWebpack/optimization/index.ts @@ -0,0 +1,74 @@ +const rely = require('call-' + 'rely') +const { resolve } = require('path') + +module.exports = { + createOptimization: (config) => { + process.env['VUE_AP' + 'P_RELY'] = rely + config.performance.set('hints', false) + config.optimization.splitChunks({ + automaticNameDelimiter: '-', + chunks: 'all', + cacheGroups: { + // 默认缓存组 + default: { + minChunks: 2, + priority: -20, + reuseExistingChunk: true, + }, + // 公共chunk + common: { + name: 'vab-common', + minChunks: 2, + priority: -10, + chunks: 'initial', + maxInitialRequests: 5, + minSize: 0, + }, + chunk: { + name: 'vab-chunk', + test: /[\\/]node_modules[\\/]/, + minSize: 131072, + maxSize: 524288, + chunks: 'initial', + minChunks: 2, + priority: 10, + }, + vue: { + name: 'vue', + test: /[\\/]node_modules[\\/](vue(.*)|core-js)[\\/]/, + chunks: 'initial', + priority: 20, + }, + elementPlus: { + name: 'element-plus', + test: /[\\/]node_modules[\\/]_?element-plus(.*)/, + priority: 30, + chunks: 'all', + }, + extra: { + name: 'vab-plugins', + test: resolve('src/plugins'), + priority: 40, + }, + components: { + name: 'vab-components', + test: resolve('library/components'), + priority: 50, + }, + xlsx: { + name: 'xlsx', + test: /[\\/]node_modules[\\/]_?xlsx(.*)/, + priority: 60, + }, + echarts: { + name: 'echarts', + test: /[\\/]node_modules[\\/](echarts|zrender)[\\/]/, + priority: 65, + chunks: 'all', + }, + }, + }) + // 配置runtimeChunk + config.optimization.runtimeChunk('single') + }, +} diff --git a/front-end/library/build/chainWebpack/sourceInjector/index.ts b/front-end/library/build/chainWebpack/sourceInjector/index.ts new file mode 100644 index 0000000..a680994 --- /dev/null +++ b/front-end/library/build/chainWebpack/sourceInjector/index.ts @@ -0,0 +1,11 @@ +const injector = require.resolve('./injector.ts') + +module.exports = { + createSourceInjector: (config) => { + config.module + .rule('vue') + .use('vue-filename-injector') + .loader(injector) + .after('vue-loader') + }, +} diff --git a/front-end/library/build/chainWebpack/sourceInjector/injector.ts b/front-end/library/build/chainWebpack/sourceInjector/injector.ts new file mode 100644 index 0000000..ca817f6 --- /dev/null +++ b/front-end/library/build/chainWebpack/sourceInjector/injector.ts @@ -0,0 +1,14 @@ +const { relative } = require('path') + +const blockName = 'vue-filename-injector' +module.exports = function (content) { + const { rootContext, resourcePath } = this + const context = rootContext || process.cwd() + const filePath = relative(context, resourcePath).replace(/\\/g, '/') + content += `<${blockName}> + export default function (Component) { + Component.__source = ${JSON.stringify(filePath)} + } + ` + return content +} diff --git a/front-end/library/build/chainWebpack/svgSprite/index.ts b/front-end/library/build/chainWebpack/svgSprite/index.ts new file mode 100644 index 0000000..e4e4fec --- /dev/null +++ b/front-end/library/build/chainWebpack/svgSprite/index.ts @@ -0,0 +1,15 @@ +const { resolve } = require('path') + +module.exports = { + createSvgSprite: (config) => { + config.module.rule('svg').exclude.add(resolve('src/icon')) + config.module + .rule('vabIcon') + .test(/\.svg$/) + .include.add(resolve('src/icon')) + .end() + .use('svg-sprite-loader') + .loader('svg-sprite-loader') + .options({ symbolId: 'vab-icon-[name]' }) + }, +} diff --git a/front-end/library/build/index.ts b/front-end/library/build/index.ts new file mode 100644 index 0000000..46b6960 --- /dev/null +++ b/front-end/library/build/index.ts @@ -0,0 +1,7 @@ +const { createVuePlugin } = require('./vuePlugins/index.ts') +const { createChainWebpack } = require('./chainWebpack/index.ts') + +module.exports = { + createVuePlugin, + createChainWebpack, +} diff --git a/front-end/library/build/vuePlugins/auto-imports.d.ts b/front-end/library/build/vuePlugins/auto-imports.d.ts new file mode 100644 index 0000000..6b8ace5 --- /dev/null +++ b/front-end/library/build/vuePlugins/auto-imports.d.ts @@ -0,0 +1,322 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +export {} +declare global { + const EffectScope: typeof import('vue')['EffectScope'] + const ElLoading: typeof import('element-plus/es')['ElLoading'] + const ElMessage: typeof import('element-plus/es')['ElMessage'] + const ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] + const ElNotification: typeof import('element-plus/es')['ElNotification'] + const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] + const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] + const autoResetRef: typeof import('@vueuse/core')['autoResetRef'] + const axios: typeof import('axios')['default'] + const computed: typeof import('vue')['computed'] + const computedAsync: typeof import('@vueuse/core')['computedAsync'] + const computedEager: typeof import('@vueuse/core')['computedEager'] + const computedInject: typeof import('@vueuse/core')['computedInject'] + const computedWithControl: typeof import('@vueuse/core')['computedWithControl'] + const controlledComputed: typeof import('@vueuse/core')['controlledComputed'] + const controlledRef: typeof import('@vueuse/core')['controlledRef'] + const createApp: typeof import('vue')['createApp'] + const createEventHook: typeof import('@vueuse/core')['createEventHook'] + const createGlobalState: typeof import('@vueuse/core')['createGlobalState'] + const createInjectionState: typeof import('@vueuse/core')['createInjectionState'] + const createPinia: typeof import('pinia')['createPinia'] + const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn'] + const createRef: typeof import('@vueuse/core')['createRef'] + const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate'] + const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable'] + const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise'] + const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn'] + const customRef: typeof import('vue')['customRef'] + const debouncedRef: typeof import('@vueuse/core')['debouncedRef'] + const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const defineStore: typeof import('pinia')['defineStore'] + const eagerComputed: typeof import('@vueuse/core')['eagerComputed'] + const effectScope: typeof import('vue')['effectScope'] + const extendRef: typeof import('@vueuse/core')['extendRef'] + const getActivePinia: typeof import('pinia')['getActivePinia'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const h: typeof import('vue')['h'] + const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch'] + const inject: typeof import('vue')['inject'] + const injectLocal: typeof import('@vueuse/core')['injectLocal'] + const isDefined: typeof import('@vueuse/core')['isDefined'] + const isProxy: typeof import('vue')['isProxy'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] + const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable'] + const manualResetRef: typeof import('@vueuse/core')['manualResetRef'] + const mapActions: typeof import('pinia')['mapActions'] + const mapGetters: typeof import('pinia')['mapGetters'] + const mapState: typeof import('pinia')['mapState'] + const mapStores: typeof import('pinia')['mapStores'] + const mapWritableState: typeof import('pinia')['mapWritableState'] + const markRaw: typeof import('vue')['markRaw'] + const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] + const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onClickOutside: typeof import('@vueuse/core')['onClickOutside'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onElementRemoval: typeof import('@vueuse/core')['onElementRemoval'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke'] + const onLongPress: typeof import('@vueuse/core')['onLongPress'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onStartTyping: typeof import('@vueuse/core')['onStartTyping'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] + const pausableWatch: typeof import('@vueuse/core')['pausableWatch'] + const provide: typeof import('vue')['provide'] + const provideLocal: typeof import('@vueuse/core')['provideLocal'] + const reactify: typeof import('@vueuse/core')['reactify'] + const reactifyObject: typeof import('@vueuse/core')['reactifyObject'] + const reactive: typeof import('vue')['reactive'] + const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed'] + const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit'] + const reactivePick: typeof import('@vueuse/core')['reactivePick'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const refAutoReset: typeof import('@vueuse/core')['refAutoReset'] + const refDebounced: typeof import('@vueuse/core')['refDebounced'] + const refDefault: typeof import('@vueuse/core')['refDefault'] + const refManualReset: typeof import('@vueuse/core')['refManualReset'] + const refThrottled: typeof import('@vueuse/core')['refThrottled'] + const refWithControl: typeof import('@vueuse/core')['refWithControl'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const resolveRef: typeof import('@vueuse/core')['resolveRef'] + const resolveUnref: typeof import('@vueuse/core')['resolveUnref'] + const setActivePinia: typeof import('pinia')['setActivePinia'] + const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const storeToRefs: typeof import('pinia')['storeToRefs'] + const syncRef: typeof import('@vueuse/core')['syncRef'] + const syncRefs: typeof import('@vueuse/core')['syncRefs'] + const templateRef: typeof import('@vueuse/core')['templateRef'] + const throttledRef: typeof import('@vueuse/core')['throttledRef'] + const throttledWatch: typeof import('@vueuse/core')['throttledWatch'] + const toRaw: typeof import('vue')['toRaw'] + const toReactive: typeof import('@vueuse/core')['toReactive'] + const toRef: typeof import('vue')['toRef'] + const toRefs: typeof import('vue')['toRefs'] + const toValue: typeof import('vue')['toValue'] + const triggerRef: typeof import('vue')['triggerRef'] + const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount'] + const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount'] + const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted'] + const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose'] + const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted'] + const unref: typeof import('vue')['unref'] + const unrefElement: typeof import('@vueuse/core')['unrefElement'] + const until: typeof import('@vueuse/core')['until'] + const useActiveElement: typeof import('@vueuse/core')['useActiveElement'] + const useAnimate: typeof import('@vueuse/core')['useAnimate'] + const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference'] + const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery'] + const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter'] + const useArrayFind: typeof import('@vueuse/core')['useArrayFind'] + const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex'] + const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast'] + const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes'] + const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin'] + const useArrayMap: typeof import('@vueuse/core')['useArrayMap'] + const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce'] + const useArraySome: typeof import('@vueuse/core')['useArraySome'] + const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique'] + const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue'] + const useAsyncState: typeof import('@vueuse/core')['useAsyncState'] + const useAttrs: typeof import('vue')['useAttrs'] + const useBase64: typeof import('@vueuse/core')['useBase64'] + const useBattery: typeof import('@vueuse/core')['useBattery'] + const useBluetooth: typeof import('@vueuse/core')['useBluetooth'] + const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints'] + const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel'] + const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation'] + const useCached: typeof import('@vueuse/core')['useCached'] + const useClipboard: typeof import('@vueuse/core')['useClipboard'] + const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems'] + const useCloned: typeof import('@vueuse/core')['useCloned'] + const useColorMode: typeof import('@vueuse/core')['useColorMode'] + const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog'] + const useCountdown: typeof import('@vueuse/core')['useCountdown'] + const useCounter: typeof import('@vueuse/core')['useCounter'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVar: typeof import('@vueuse/core')['useCssVar'] + const useCssVars: typeof import('vue')['useCssVars'] + const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement'] + const useCycleList: typeof import('@vueuse/core')['useCycleList'] + const useDark: typeof import('@vueuse/core')['useDark'] + const useDateFormat: typeof import('@vueuse/core')['useDateFormat'] + const useDebounce: typeof import('@vueuse/core')['useDebounce'] + const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn'] + const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory'] + const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion'] + const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation'] + const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio'] + const useDevicesList: typeof import('@vueuse/core')['useDevicesList'] + const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia'] + const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility'] + const useDraggable: typeof import('@vueuse/core')['useDraggable'] + const useDropZone: typeof import('@vueuse/core')['useDropZone'] + const useElementBounding: typeof import('@vueuse/core')['useElementBounding'] + const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint'] + const useElementHover: typeof import('@vueuse/core')['useElementHover'] + const useElementSize: typeof import('@vueuse/core')['useElementSize'] + const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility'] + const useEventBus: typeof import('@vueuse/core')['useEventBus'] + const useEventListener: typeof import('@vueuse/core')['useEventListener'] + const useEventSource: typeof import('@vueuse/core')['useEventSource'] + const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper'] + const useFavicon: typeof import('@vueuse/core')['useFavicon'] + const useFetch: typeof import('@vueuse/core')['useFetch'] + const useFileDialog: typeof import('@vueuse/core')['useFileDialog'] + const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess'] + const useFocus: typeof import('@vueuse/core')['useFocus'] + const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin'] + const useFps: typeof import('@vueuse/core')['useFps'] + const useFullscreen: typeof import('@vueuse/core')['useFullscreen'] + const useGamepad: typeof import('@vueuse/core')['useGamepad'] + const useGeolocation: typeof import('@vueuse/core')['useGeolocation'] + const useI18n: typeof import('vue-i18n')['useI18n'] + const useId: typeof import('vue')['useId'] + const useIdle: typeof import('@vueuse/core')['useIdle'] + const useImage: typeof import('@vueuse/core')['useImage'] + const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll'] + const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver'] + const useInterval: typeof import('@vueuse/core')['useInterval'] + const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn'] + const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier'] + const useLastChanged: typeof import('@vueuse/core')['useLastChanged'] + const useLink: typeof import('vue-router')['useLink'] + const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage'] + const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys'] + const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory'] + const useMediaControls: typeof import('@vueuse/core')['useMediaControls'] + const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery'] + const useMemoize: typeof import('@vueuse/core')['useMemoize'] + const useMemory: typeof import('@vueuse/core')['useMemory'] + const useModel: typeof import('vue')['useModel'] + const useMounted: typeof import('@vueuse/core')['useMounted'] + const useMouse: typeof import('@vueuse/core')['useMouse'] + const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement'] + const useMousePressed: typeof import('@vueuse/core')['useMousePressed'] + const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver'] + const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage'] + const useNetwork: typeof import('@vueuse/core')['useNetwork'] + const useNow: typeof import('@vueuse/core')['useNow'] + const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl'] + const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination'] + const useOnline: typeof import('@vueuse/core')['useOnline'] + const usePageLeave: typeof import('@vueuse/core')['usePageLeave'] + const useParallax: typeof import('@vueuse/core')['useParallax'] + const useParentElement: typeof import('@vueuse/core')['useParentElement'] + const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver'] + const usePermission: typeof import('@vueuse/core')['usePermission'] + const usePointer: typeof import('@vueuse/core')['usePointer'] + const usePointerLock: typeof import('@vueuse/core')['usePointerLock'] + const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe'] + const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme'] + const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast'] + const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark'] + const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages'] + const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion'] + const usePreferredReducedTransparency: typeof import('@vueuse/core')['usePreferredReducedTransparency'] + const usePrevious: typeof import('@vueuse/core')['usePrevious'] + const useRafFn: typeof import('@vueuse/core')['useRafFn'] + const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] + const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] + const useRoute: typeof import('vue-router')['useRoute'] + const useRouter: typeof import('vue-router')['useRouter'] + const useSSRWidth: typeof import('@vueuse/core')['useSSRWidth'] + const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation'] + const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea'] + const useScriptTag: typeof import('@vueuse/core')['useScriptTag'] + const useScroll: typeof import('@vueuse/core')['useScroll'] + const useScrollLock: typeof import('@vueuse/core')['useScrollLock'] + const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage'] + const useShare: typeof import('@vueuse/core')['useShare'] + const useSlots: typeof import('vue')['useSlots'] + const useSorted: typeof import('@vueuse/core')['useSorted'] + const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition'] + const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis'] + const useStepper: typeof import('@vueuse/core')['useStepper'] + const useStorage: typeof import('@vueuse/core')['useStorage'] + const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync'] + const useStyleTag: typeof import('@vueuse/core')['useStyleTag'] + const useSupported: typeof import('@vueuse/core')['useSupported'] + const useSwipe: typeof import('@vueuse/core')['useSwipe'] + const useTemplateRef: typeof import('vue')['useTemplateRef'] + const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList'] + const useTextDirection: typeof import('@vueuse/core')['useTextDirection'] + const useTextSelection: typeof import('@vueuse/core')['useTextSelection'] + const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize'] + const useThrottle: typeof import('@vueuse/core')['useThrottle'] + const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn'] + const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory'] + const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo'] + const useTimeAgoIntl: typeof import('@vueuse/core')['useTimeAgoIntl'] + const useTimeout: typeof import('@vueuse/core')['useTimeout'] + const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn'] + const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll'] + const useTimestamp: typeof import('@vueuse/core')['useTimestamp'] + const useTitle: typeof import('@vueuse/core')['useTitle'] + const useToNumber: typeof import('@vueuse/core')['useToNumber'] + const useToString: typeof import('@vueuse/core')['useToString'] + const useToggle: typeof import('@vueuse/core')['useToggle'] + const useTransition: typeof import('@vueuse/core')['useTransition'] + const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams'] + const useUserMedia: typeof import('@vueuse/core')['useUserMedia'] + const useVModel: typeof import('@vueuse/core')['useVModel'] + const useVModels: typeof import('@vueuse/core')['useVModels'] + const useVibrate: typeof import('@vueuse/core')['useVibrate'] + const useVirtualList: typeof import('@vueuse/core')['useVirtualList'] + const useWakeLock: typeof import('@vueuse/core')['useWakeLock'] + const useWebNotification: typeof import('@vueuse/core')['useWebNotification'] + const useWebSocket: typeof import('@vueuse/core')['useWebSocket'] + const useWebWorker: typeof import('@vueuse/core')['useWebWorker'] + const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn'] + const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus'] + const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll'] + const useWindowSize: typeof import('@vueuse/core')['useWindowSize'] + const watch: typeof import('vue')['watch'] + const watchArray: typeof import('@vueuse/core')['watchArray'] + const watchAtMost: typeof import('@vueuse/core')['watchAtMost'] + const watchDebounced: typeof import('@vueuse/core')['watchDebounced'] + const watchDeep: typeof import('@vueuse/core')['watchDeep'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable'] + const watchImmediate: typeof import('@vueuse/core')['watchImmediate'] + const watchOnce: typeof import('@vueuse/core')['watchOnce'] + const watchPausable: typeof import('@vueuse/core')['watchPausable'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] + const watchThrottled: typeof import('@vueuse/core')['watchThrottled'] + const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable'] + const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter'] + const whenever: typeof import('@vueuse/core')['whenever'] +} +// for type re-export +declare global { + // @ts-ignore + export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' + import('vue') +} diff --git a/front-end/library/build/vuePlugins/components.d.ts b/front-end/library/build/vuePlugins/components.d.ts new file mode 100644 index 0000000..ee6da4e --- /dev/null +++ b/front-end/library/build/vuePlugins/components.d.ts @@ -0,0 +1,123 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +export {} + +declare module 'vue' { + export interface GlobalComponents { + ElAlert: typeof import('element-plus/es')['ElAlert'] + ElAvatar: typeof import('element-plus/es')['ElAvatar'] + ElBacktop: typeof import('element-plus/es')['ElBacktop'] + ElBadge: typeof import('element-plus/es')['ElBadge'] + ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'] + ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] + ElButton: typeof import('element-plus/es')['ElButton'] + ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] + ElCalendar: typeof import('element-plus/es')['ElCalendar'] + ElCard: typeof import('element-plus/es')['ElCard'] + ElCarousel: typeof import('element-plus/es')['ElCarousel'] + ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem'] + ElCascader: typeof import('element-plus/es')['ElCascader'] + ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] + ElCheckboxButton: typeof import('element-plus/es')['ElCheckboxButton'] + ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] + ElCol: typeof import('element-plus/es')['ElCol'] + ElCollapse: typeof import('element-plus/es')['ElCollapse'] + ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] + ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] + ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] + ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] + ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] + ElDialog: typeof import('element-plus/es')['ElDialog'] + ElDivider: typeof import('element-plus/es')['ElDivider'] + ElDrawer: typeof import('element-plus/es')['ElDrawer'] + ElDropdown: typeof import('element-plus/es')['ElDropdown'] + ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] + ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] + ElEmpty: typeof import('element-plus/es')['ElEmpty'] + ElForm: typeof import('element-plus/es')['ElForm'] + ElFormItem: typeof import('element-plus/es')['ElFormItem'] + ElIcon: typeof import('element-plus/es')['ElIcon'] + ElImage: typeof import('element-plus/es')['ElImage'] + ElInput: typeof import('element-plus/es')['ElInput'] + ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] + ElLink: typeof import('element-plus/es')['ElLink'] + ElMenu: typeof import('element-plus/es')['ElMenu'] + ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] + ElOption: typeof import('element-plus/es')['ElOption'] + ElPageHeader: typeof import('element-plus/es')['ElPageHeader'] + ElPagination: typeof import('element-plus/es')['ElPagination'] + ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm'] + ElPopover: typeof import('element-plus/es')['ElPopover'] + ElProgress: typeof import('element-plus/es')['ElProgress'] + ElRadio: typeof import('element-plus/es')['ElRadio'] + ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] + ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] + ElRate: typeof import('element-plus/es')['ElRate'] + ElResult: typeof import('element-plus/es')['ElResult'] + ElRow: typeof import('element-plus/es')['ElRow'] + ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] + ElSelect: typeof import('element-plus/es')['ElSelect'] + ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] + ElSlider: typeof import('element-plus/es')['ElSlider'] + ElStep: typeof import('element-plus/es')['ElStep'] + ElSteps: typeof import('element-plus/es')['ElSteps'] + ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] + ElSwitch: typeof import('element-plus/es')['ElSwitch'] + ElTable: typeof import('element-plus/es')['ElTable'] + ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] + ElTabPane: typeof import('element-plus/es')['ElTabPane'] + ElTabs: typeof import('element-plus/es')['ElTabs'] + ElTag: typeof import('element-plus/es')['ElTag'] + ElTimeline: typeof import('element-plus/es')['ElTimeline'] + ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] + ElTimePicker: typeof import('element-plus/es')['ElTimePicker'] + ElTimeSelect: typeof import('element-plus/es')['ElTimeSelect'] + ElTooltip: typeof import('element-plus/es')['ElTooltip'] + ElTransfer: typeof import('element-plus/es')['ElTransfer'] + ElTree: typeof import('element-plus/es')['ElTree'] + ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] + ElUpload: typeof import('element-plus/es')['ElUpload'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + VabApp: typeof import('./../../components/VabApp/index.vue')['default'] + VabAppMain: typeof import('./../../components/VabAppMain/index.vue')['default'] + VabAvatar: typeof import('./../../components/VabAvatar/index.vue')['default'] + VabBreadcrumb: typeof import('./../../components/VabBreadcrumb/index.vue')['default'] + VabCard: typeof import('./../../components/VabCard/index.vue')['default'] + VabColorfulCard: typeof import('./../../components/VabColorfulCard/index.vue')['default'] + VabColumnBar: typeof import('./../../components/VabColumnBar/index.vue')['default'] + VabErrorLog: typeof import('./../../components/VabErrorLog/index.vue')['default'] + VabFold: typeof import('./../../components/VabFold/index.vue')['default'] + VabFooter: typeof import('./../../components/VabFooter/index.vue')['default'] + VabFullScreen: typeof import('./../../components/VabFullScreen/index.vue')['default'] + VabHeader: typeof import('./../../components/VabHeader/index.vue')['default'] + VabLanguage: typeof import('./../../components/VabLanguage/index.vue')['default'] + VabLink: typeof import('./../../components/VabLink/index.vue')['default'] + VabLock: typeof import('./../../components/VabLock/index.vue')['default'] + VabLogo: typeof import('./../../components/VabLogo/index.vue')['default'] + VabMenu: typeof import('./../../components/VabMenu/index.vue')['default'] + VabMenuItem: typeof import('./../../components/VabMenu/components/VabMenuItem.vue')['default'] + VabNav: typeof import('./../../components/VabNav/index.vue')['default'] + VabNotice: typeof import('./../../components/VabNotice/index.vue')['default'] + VabQueryForm: typeof import('./../../components/VabQueryForm/index.vue')['default'] + VabQueryFormBottomPanel: typeof import('./../../components/VabQueryForm/components/VabQueryFormBottomPanel.vue')['default'] + VabQueryFormLeftPanel: typeof import('./../../components/VabQueryForm/components/VabQueryFormLeftPanel.vue')['default'] + VabQueryFormRightPanel: typeof import('./../../components/VabQueryForm/components/VabQueryFormRightPanel.vue')['default'] + VabQueryFormTopPanel: typeof import('./../../components/VabQueryForm/components/VabQueryFormTopPanel.vue')['default'] + VabRefresh: typeof import('./../../components/VabRefresh/index.vue')['default'] + VabRouterView: typeof import('./../../components/VabRouterView/index.vue')['default'] + VabSearch: typeof import('./../../components/VabSearch/index.vue')['default'] + VabSideBar: typeof import('./../../components/VabSideBar/index.vue')['default'] + VabSubMenu: typeof import('./../../components/VabMenu/components/VabSubMenu.vue')['default'] + VabTabs: typeof import('./../../components/VabTabs/index.vue')['default'] + VabTheme: typeof import('./../../components/VabTheme/index.vue')['default'] + VabThemeDrawer: typeof import('./../../components/VabTheme/components/VabThemeDrawer.vue')['default'] + VabThemeSetting: typeof import('./../../components/VabTheme/components/VabThemeSetting.vue')['default'] + } + export interface ComponentCustomProperties { + vLoading: typeof import('element-plus/es')['ElLoadingDirective'] + } +} diff --git a/front-end/library/build/vuePlugins/defineOptions/index.ts b/front-end/library/build/vuePlugins/defineOptions/index.ts new file mode 100644 index 0000000..c97547f --- /dev/null +++ b/front-end/library/build/vuePlugins/defineOptions/index.ts @@ -0,0 +1,5 @@ +module.exports = { + createDefineOptions: () => [ + require('unplugin-vue-define-options/webpack')(), + ], +} diff --git a/front-end/library/build/vuePlugins/definePlugin/index.ts b/front-end/library/build/vuePlugins/definePlugin/index.ts new file mode 100644 index 0000000..a3b3861 --- /dev/null +++ b/front-end/library/build/vuePlugins/definePlugin/index.ts @@ -0,0 +1,10 @@ +// @ts-ignore +const Webpack = require('webpack') + +module.exports = { + createDefinePlugin: () => [ + new Webpack.DefinePlugin({ + __APP_INFO__: process.env.VUE_APP_INFO, + }), + ], +} diff --git a/front-end/library/build/vuePlugins/index.ts b/front-end/library/build/vuePlugins/index.ts new file mode 100644 index 0000000..e1fb4fa --- /dev/null +++ b/front-end/library/build/vuePlugins/index.ts @@ -0,0 +1,19 @@ +const { createUnPlugin } = require('vue-' + 'unplugins') +const { createWebpackBar } = require('./webpack' + 'Bar/index.ts') +const { createDefineOptions } = require('./defineOptions/index.ts') +const { createDefinePlugin } = require('./definePlugin/index.ts') +const { createProvidePlugin } = require('./providePlugin/index.ts') +const { createMinChunkSizePlugin } = require('./minChunkSizePlugin/index.ts') + +const dev = process.env.NODE_ENV === 'development' +module.exports = { + createVuePlugin: () => [ + ...createDefineOptions(), + ...createUnPlugin(), + require('unplugin-element-plus/webpack')(), + ...createWebpackBar(), + ...createDefinePlugin(), + ...createProvidePlugin(), + ...(dev ? [] : createMinChunkSizePlugin()), + ], +} diff --git a/front-end/library/build/vuePlugins/minChunkSizePlugin/index.ts b/front-end/library/build/vuePlugins/minChunkSizePlugin/index.ts new file mode 100644 index 0000000..ed650a6 --- /dev/null +++ b/front-end/library/build/vuePlugins/minChunkSizePlugin/index.ts @@ -0,0 +1,14 @@ +// @ts-ignore +const Webpack = require('webpack') +const { buildOptimize } = require('../../../../src/config') + +module.exports = { + createMinChunkSizePlugin: () => + buildOptimize + ? [] + : [ + new Webpack.optimize.MinChunkSizePlugin({ + minChunkSize: 1024 * 300, + }), + ], +} diff --git a/front-end/library/build/vuePlugins/providePlugin/index.ts b/front-end/library/build/vuePlugins/providePlugin/index.ts new file mode 100644 index 0000000..42817e0 --- /dev/null +++ b/front-end/library/build/vuePlugins/providePlugin/index.ts @@ -0,0 +1,7 @@ +// @ts-ignore +const Webpack = require('webpack') +const { providePlugin } = require('../../../../src/config') + +module.exports = { + createProvidePlugin: () => [new Webpack.ProvidePlugin(providePlugin)], +} diff --git a/front-end/library/build/vuePlugins/webpackBar/index.ts b/front-end/library/build/vuePlugins/webpackBar/index.ts new file mode 100644 index 0000000..4b3b0aa --- /dev/null +++ b/front-end/library/build/vuePlugins/webpackBar/index.ts @@ -0,0 +1,10 @@ +const WebpackBar = require('webpackbar') +const { version } = require('../../../../package.json') + +module.exports = { + createWebpackBar: () => [ + new WebpackBar({ + name: `Vue-` + `Admin` + `-Plus ${version}`, + }), + ], +} diff --git a/front-end/library/components/VabApp/index.vue b/front-end/library/components/VabApp/index.vue new file mode 100644 index 0000000..9a51ebe --- /dev/null +++ b/front-end/library/components/VabApp/index.vue @@ -0,0 +1,45 @@ + + + diff --git a/front-end/library/components/VabAppMain/index.vue b/front-end/library/components/VabAppMain/index.vue new file mode 100644 index 0000000..5a523bd --- /dev/null +++ b/front-end/library/components/VabAppMain/index.vue @@ -0,0 +1,28 @@ + + + diff --git a/front-end/library/components/VabAvatar/index.vue b/front-end/library/components/VabAvatar/index.vue new file mode 100644 index 0000000..a977101 --- /dev/null +++ b/front-end/library/components/VabAvatar/index.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/front-end/library/components/VabBreadcrumb/index.vue b/front-end/library/components/VabBreadcrumb/index.vue new file mode 100644 index 0000000..88db40e --- /dev/null +++ b/front-end/library/components/VabBreadcrumb/index.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/front-end/library/components/VabCard/index.vue b/front-end/library/components/VabCard/index.vue new file mode 100644 index 0000000..50fa7e4 --- /dev/null +++ b/front-end/library/components/VabCard/index.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/front-end/library/components/VabColorfulCard/index.vue b/front-end/library/components/VabColorfulCard/index.vue new file mode 100644 index 0000000..bffb1f6 --- /dev/null +++ b/front-end/library/components/VabColorfulCard/index.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/front-end/library/components/VabColumnBar/index.vue b/front-end/library/components/VabColumnBar/index.vue new file mode 100644 index 0000000..c6ba066 --- /dev/null +++ b/front-end/library/components/VabColumnBar/index.vue @@ -0,0 +1,392 @@ + + + + + diff --git a/front-end/library/components/VabErrorLog/index.vue b/front-end/library/components/VabErrorLog/index.vue new file mode 100644 index 0000000..a2b1059 --- /dev/null +++ b/front-end/library/components/VabErrorLog/index.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/front-end/library/components/VabFold/index.vue b/front-end/library/components/VabFold/index.vue new file mode 100644 index 0000000..7640720 --- /dev/null +++ b/front-end/library/components/VabFold/index.vue @@ -0,0 +1,22 @@ + + + + + diff --git a/front-end/library/components/VabFooter/index.vue b/front-end/library/components/VabFooter/index.vue new file mode 100644 index 0000000..2a731a3 --- /dev/null +++ b/front-end/library/components/VabFooter/index.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/front-end/library/components/VabFullScreen/index.vue b/front-end/library/components/VabFullScreen/index.vue new file mode 100644 index 0000000..afc24ba --- /dev/null +++ b/front-end/library/components/VabFullScreen/index.vue @@ -0,0 +1,16 @@ + + + diff --git a/front-end/library/components/VabHeader/index.vue b/front-end/library/components/VabHeader/index.vue new file mode 100644 index 0000000..78327fe --- /dev/null +++ b/front-end/library/components/VabHeader/index.vue @@ -0,0 +1,233 @@ + + + + + diff --git a/front-end/library/components/VabLanguage/index.vue b/front-end/library/components/VabLanguage/index.vue new file mode 100644 index 0000000..35cf37e --- /dev/null +++ b/front-end/library/components/VabLanguage/index.vue @@ -0,0 +1,30 @@ + + + diff --git a/front-end/library/components/VabLink/index.vue b/front-end/library/components/VabLink/index.vue new file mode 100644 index 0000000..f400ed2 --- /dev/null +++ b/front-end/library/components/VabLink/index.vue @@ -0,0 +1,27 @@ + + + diff --git a/front-end/library/components/VabLock/index.vue b/front-end/library/components/VabLock/index.vue new file mode 100644 index 0000000..a0a71ec --- /dev/null +++ b/front-end/library/components/VabLock/index.vue @@ -0,0 +1,225 @@ + + + + + diff --git a/front-end/library/components/VabLogo/index.vue b/front-end/library/components/VabLogo/index.vue new file mode 100644 index 0000000..becdac3 --- /dev/null +++ b/front-end/library/components/VabLogo/index.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/front-end/library/components/VabMenu/components/VabMenuItem.vue b/front-end/library/components/VabMenu/components/VabMenuItem.vue new file mode 100644 index 0000000..fee9da3 --- /dev/null +++ b/front-end/library/components/VabMenu/components/VabMenuItem.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/front-end/library/components/VabMenu/components/VabSubMenu.vue b/front-end/library/components/VabMenu/components/VabSubMenu.vue new file mode 100644 index 0000000..45c3f9f --- /dev/null +++ b/front-end/library/components/VabMenu/components/VabSubMenu.vue @@ -0,0 +1,36 @@ + + + diff --git a/front-end/library/components/VabMenu/index.vue b/front-end/library/components/VabMenu/index.vue new file mode 100644 index 0000000..aa79dd3 --- /dev/null +++ b/front-end/library/components/VabMenu/index.vue @@ -0,0 +1,83 @@ + + + + + + + + diff --git a/front-end/library/components/VabNav/index.vue b/front-end/library/components/VabNav/index.vue new file mode 100644 index 0000000..4150ca1 --- /dev/null +++ b/front-end/library/components/VabNav/index.vue @@ -0,0 +1,165 @@ + + + + + diff --git a/front-end/library/components/VabNotice/index.vue b/front-end/library/components/VabNotice/index.vue new file mode 100644 index 0000000..b9bff9c --- /dev/null +++ b/front-end/library/components/VabNotice/index.vue @@ -0,0 +1,356 @@ + + + + + + + diff --git a/front-end/library/components/VabQueryForm/components/VabQueryFormBottomPanel.vue b/front-end/library/components/VabQueryForm/components/VabQueryFormBottomPanel.vue new file mode 100644 index 0000000..9f38ee6 --- /dev/null +++ b/front-end/library/components/VabQueryForm/components/VabQueryFormBottomPanel.vue @@ -0,0 +1,7 @@ + diff --git a/front-end/library/components/VabQueryForm/components/VabQueryFormLeftPanel.vue b/front-end/library/components/VabQueryForm/components/VabQueryFormLeftPanel.vue new file mode 100644 index 0000000..64f94e7 --- /dev/null +++ b/front-end/library/components/VabQueryForm/components/VabQueryFormLeftPanel.vue @@ -0,0 +1,16 @@ + + + diff --git a/front-end/library/components/VabQueryForm/components/VabQueryFormRightPanel.vue b/front-end/library/components/VabQueryForm/components/VabQueryFormRightPanel.vue new file mode 100644 index 0000000..2a2b781 --- /dev/null +++ b/front-end/library/components/VabQueryForm/components/VabQueryFormRightPanel.vue @@ -0,0 +1,16 @@ + + + diff --git a/front-end/library/components/VabQueryForm/components/VabQueryFormTopPanel.vue b/front-end/library/components/VabQueryForm/components/VabQueryFormTopPanel.vue new file mode 100644 index 0000000..e56c758 --- /dev/null +++ b/front-end/library/components/VabQueryForm/components/VabQueryFormTopPanel.vue @@ -0,0 +1,7 @@ + diff --git a/front-end/library/components/VabQueryForm/index.vue b/front-end/library/components/VabQueryForm/index.vue new file mode 100644 index 0000000..3ff6216 --- /dev/null +++ b/front-end/library/components/VabQueryForm/index.vue @@ -0,0 +1,60 @@ + + + diff --git a/front-end/library/components/VabRefresh/index.vue b/front-end/library/components/VabRefresh/index.vue new file mode 100644 index 0000000..dbea8e7 --- /dev/null +++ b/front-end/library/components/VabRefresh/index.vue @@ -0,0 +1,20 @@ + + + diff --git a/front-end/library/components/VabRouterView/index.vue b/front-end/library/components/VabRouterView/index.vue new file mode 100644 index 0000000..a517ee3 --- /dev/null +++ b/front-end/library/components/VabRouterView/index.vue @@ -0,0 +1,80 @@ + + + diff --git a/front-end/library/components/VabSearch/index.vue b/front-end/library/components/VabSearch/index.vue new file mode 100644 index 0000000..8feec2f --- /dev/null +++ b/front-end/library/components/VabSearch/index.vue @@ -0,0 +1,517 @@ + + + + + diff --git a/front-end/library/components/VabSideBar/index.vue b/front-end/library/components/VabSideBar/index.vue new file mode 100644 index 0000000..fbc88d9 --- /dev/null +++ b/front-end/library/components/VabSideBar/index.vue @@ -0,0 +1,214 @@ + + + + + + + + diff --git a/front-end/library/components/VabTabs/index.vue b/front-end/library/components/VabTabs/index.vue new file mode 100644 index 0000000..e621af2 --- /dev/null +++ b/front-end/library/components/VabTabs/index.vue @@ -0,0 +1,614 @@ + + + + + diff --git a/front-end/library/components/VabTheme/components/VabThemeDrawer.vue b/front-end/library/components/VabTheme/components/VabThemeDrawer.vue new file mode 100644 index 0000000..4af40a9 --- /dev/null +++ b/front-end/library/components/VabTheme/components/VabThemeDrawer.vue @@ -0,0 +1,515 @@ + + + + + diff --git a/front-end/library/components/VabTheme/components/VabThemeSetting.vue b/front-end/library/components/VabTheme/components/VabThemeSetting.vue new file mode 100644 index 0000000..b027fd8 --- /dev/null +++ b/front-end/library/components/VabTheme/components/VabThemeSetting.vue @@ -0,0 +1,168 @@ + + + + + diff --git a/front-end/library/components/VabTheme/index.vue b/front-end/library/components/VabTheme/index.vue new file mode 100644 index 0000000..c57614d --- /dev/null +++ b/front-end/library/components/VabTheme/index.vue @@ -0,0 +1,18 @@ + + + diff --git a/front-end/library/index.ts b/front-end/library/index.ts new file mode 100644 index 0000000..7c1c85f --- /dev/null +++ b/front-end/library/index.ts @@ -0,0 +1,39 @@ +import { App } from 'vue' + +// 加载雪碧图 +import '@/icon' +// 加载全局样式样式 +import './styles/vab.scss' + +import { createHead } from '@vueuse/head' + +// 加载Icon +import VabIcon from 'vab-icons' +import 'vab-icons/lib/vab-icons.css' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' + +const name = process['env']['VUE_' + 'APP_' + 'GITHUB_' + 'USER_' + 'NAME'] +const noTest = name !== 'test' +const noEmpty = name !== 'undefined' +const dev = process['env']['NODE_' + 'ENV'] === 'dev' + 'elop' + 'ment' + +export function setupVab(app: App) { + if ((noTest && noEmpty && !dev && VabIcon) || (dev && VabIcon)) { + app.use(createHead()) + + app.component('VabIcon', VabIcon) + for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) + } + + // 加载背景 + const Themes = require.context('./styles/background', false, /\.scss$/) + Themes.keys().map(Themes) + + // 加载插件 + const Plugins = require.context('./plugins', true, /\.ts$/) + Plugins.keys().forEach((key) => { + app.use(Plugins(key).default) + }) + } +} diff --git a/front-end/library/layouts/VabLayoutColumn/index.vue b/front-end/library/layouts/VabLayoutColumn/index.vue new file mode 100644 index 0000000..127937c --- /dev/null +++ b/front-end/library/layouts/VabLayoutColumn/index.vue @@ -0,0 +1,78 @@ + + + + + + diff --git a/front-end/library/layouts/VabLayoutCommon/index.vue b/front-end/library/layouts/VabLayoutCommon/index.vue new file mode 100644 index 0000000..1598883 --- /dev/null +++ b/front-end/library/layouts/VabLayoutCommon/index.vue @@ -0,0 +1,88 @@ + + + + + + diff --git a/front-end/library/layouts/VabLayoutComprehensive/index.vue b/front-end/library/layouts/VabLayoutComprehensive/index.vue new file mode 100644 index 0000000..7a35ecc --- /dev/null +++ b/front-end/library/layouts/VabLayoutComprehensive/index.vue @@ -0,0 +1,58 @@ + + + + diff --git a/front-end/library/layouts/VabLayoutFloat/index.vue b/front-end/library/layouts/VabLayoutFloat/index.vue new file mode 100644 index 0000000..c8aed38 --- /dev/null +++ b/front-end/library/layouts/VabLayoutFloat/index.vue @@ -0,0 +1,98 @@ + + + + + + + diff --git a/front-end/library/layouts/VabLayoutHorizontal/index.vue b/front-end/library/layouts/VabLayoutHorizontal/index.vue new file mode 100644 index 0000000..6db795b --- /dev/null +++ b/front-end/library/layouts/VabLayoutHorizontal/index.vue @@ -0,0 +1,83 @@ + + + + + + diff --git a/front-end/library/layouts/VabLayoutVertical/index.vue b/front-end/library/layouts/VabLayoutVertical/index.vue new file mode 100644 index 0000000..976a08f --- /dev/null +++ b/front-end/library/layouts/VabLayoutVertical/index.vue @@ -0,0 +1,68 @@ + + + + diff --git a/front-end/library/layouts/index.vue b/front-end/library/layouts/index.vue new file mode 100644 index 0000000..94f8820 --- /dev/null +++ b/front-end/library/layouts/index.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/front-end/library/plugins/directive.ts b/front-end/library/plugins/directive.ts new file mode 100644 index 0000000..d41a8d1 --- /dev/null +++ b/front-end/library/plugins/directive.ts @@ -0,0 +1,19 @@ +import type { App, DirectiveBinding } from 'vue' + +import { hasPermission } from '@/utils/permission' + +export default { + install(app: App) { + /** + * @description 自定义指令v-permissions + */ + app.directive('permissions', { + mounted(el: any, binding: DirectiveBinding) { + const { value } = binding + if (value) + if (!hasPermission(value)) + el.parentNode && el.parentNode.removeChild(el) + }, + }) + }, +} diff --git a/front-end/library/plugins/errorLog.ts b/front-end/library/plugins/errorLog.ts new file mode 100644 index 0000000..d064b07 --- /dev/null +++ b/front-end/library/plugins/errorLog.ts @@ -0,0 +1,30 @@ +import { App } from 'vue' +import pinia from '@/store' +import { useErrorLogStore } from '@/store/modules/errorLog' +import { errorLog } from '@/config' +import { isArray, isString } from '@/utils/validate' + +export const needErrorLog = () => { + const errorLogArray = isArray(errorLog) + ? [...errorLog] + : isString(errorLog) + ? [...[errorLog]] + : [] + return errorLogArray.includes(process.env.NODE_ENV as string) +} + +export const addErrorLog = (err: any) => { + // eslint-disable-next-line no-console + if (!err.isRequest) console.error('vue-admin-better错误拦截:', err) + const url = window.location.href + const { addErrorLog } = useErrorLogStore(pinia) + addErrorLog({ err, url }) +} + +export default { + install(app: App) { + if (needErrorLog()) { + app.config.errorHandler = addErrorLog + } + }, +} diff --git a/front-end/library/plugins/support.ts b/front-end/library/plugins/support.ts new file mode 100644 index 0000000..66b9dbb --- /dev/null +++ b/front-end/library/plugins/support.ts @@ -0,0 +1,26 @@ +import { App } from 'vue' +import pinia from '@/store' +import { useSettingsStore } from '@/store/modules/settings' + +export default { + install(app: App) { + if (process.env.NODE_ENV !== 'development') { + const { title } = useSettingsStore(pinia) + // eslint-disable-next-line no-console + console.log( + ` %c ${title} %c 基于admin-plus ${__APP_INFO__['version']} 构建 `, + 'color: #fadfa3; background: #030307; padding:5px 0;', + 'background: #fadfa3; padding:5px 0;' + ) + } + if (process.env.NODE_ENV !== 'development') { + const str = '\u0076\u0061\u0062\u002d\u0069\u0063\u006f\u006e\u0073' + const key = decodeURI(str.replace(/\\u/g, '%u')) + if (!__APP_INFO__['dependencies'][key]) { + // eslint-disable-next-line + // @ts-ignore + app.config.globalProperties = null + } + } + }, +} diff --git a/front-end/library/plugins/vab.ts b/front-end/library/plugins/vab.ts new file mode 100644 index 0000000..f8f9e7d --- /dev/null +++ b/front-end/library/plugins/vab.ts @@ -0,0 +1,172 @@ +import { App } from 'vue' +import mitt from 'mitt' +import _ from 'lodash' +import { loadingText, messageDuration } from '@/config' +import { globalPropertiesType } from '/#/library' + +export let gp: globalPropertiesType + +export default { + install(app: App) { + gp = { + /** + * @description 全局加载层 + * @param {number} index 自定义加载图标类名ID + * @param {string} text 显示在加载图标下方的加载文案 + */ + $baseLoading: (index = undefined, text = loadingText) => { + return ElLoading.service({ + lock: true, + text, + spinner: index ? `vab-loading-type${index}` : index, + background: 'hsla(0,0%,100%,.8)', + }) + }, + /** + * @description 全局Message + * @param {string} message 消息文字 + * @param {'success'|'warning'|'info'|'error'} type 主题 + * @param {string} customClass 自定义类名 + * @param {boolean} dangerouslyUseHTMLString 是否将message属性作为HTML片段处理 + */ + $baseMessage: ( + message, + type = 'info', + customClass, + dangerouslyUseHTMLString + ) => { + ElMessage({ + message, + type, + customClass, + duration: messageDuration, + dangerouslyUseHTMLString, + showClose: true, + }) + }, + /** + * @description 全局Alert + * @param {string|VNode} content 消息正文内容 + * @param {string} title 标题 + * @param {function} callback 若不使用Promise,可以使用此参数指定MessageBox关闭后的回调 + */ + $baseAlert: (content, title = '温馨提示', callback = undefined) => { + if (title && typeof title == 'function') { + callback = title + title = '温馨提示' + } + ElMessageBox.alert(content, title, { + confirmButtonText: '确定', + dangerouslyUseHTMLString: true, // 此处可能引起跨站攻击,建议配置为false + callback: () => { + if (callback) callback() + }, + }).then(() => {}) + }, + /** + * @description 全局Confirm + * @param {string|VNode} content 消息正文内容 + * @param {string} title 标题 + * @param {function} callback1 确认回调 + * @param {function} callback2 关闭或取消回调 + * @param {string} confirmButtonText 确定按钮的文本内容 + * @param {string} cancelButtonText 取消按钮的自定义类名 + */ + $baseConfirm: ( + content, + title, + callback1, + callback2, + confirmButtonText = '确定', + cancelButtonText = '取消' + ) => { + ElMessageBox.confirm(content, title || '温馨提示', { + confirmButtonText, + cancelButtonText, + closeOnClickModal: false, + type: 'warning', + lockScroll: false, + }) + .then(() => { + if (callback1) { + callback1() + } + }) + .catch(() => { + if (callback2) { + callback2() + } + }) + }, + /** + * @description 全局Notification + * @param {string} message 说明文字 + * @param {string} title 标题 + * @param {'success'|'warning'|'info'|'error'} type 主题样式,如果不在可选值内将被忽略 + * @param {'top-right'|'top-left'|'bottom-right'|'bottom-left'} position 自定义弹出位置 + * @param duration 显示时间,毫秒 + */ + $baseNotify: ( + message, + title, + type = 'success', + position = 'top-right', + duration = messageDuration + ) => { + ElNotification({ + title, + message, + type, + duration, + position, + }) + }, + /** + * @description 表格高度 + * @param {*} formType + */ + $baseTableHeight: (formType) => { + let height = window.innerHeight + const paddingHeight = 291 + const formHeight = 60 + + if ('number' === typeof formType) { + height = height - paddingHeight - formHeight * formType + } else { + height = height - paddingHeight + } + return height + }, + $pub: (...args: any[]) => { + _emitter.emit(_.head(args), args[1]) + }, + $sub: function () { + // eslint-disable-next-line prefer-rest-params + Reflect.apply(_emitter.on, _emitter, _.toArray(arguments)) + }, + $unsub: function () { + // eslint-disable-next-line prefer-rest-params + Reflect.apply(_emitter.off, _emitter, _.toArray(arguments)) + }, + } + + const _emitter = mitt() + Object.keys(gp).forEach((key) => { + app.provide(key, gp[key as keyof typeof gp]) + // 允许vue3下继续使用vue2中的this调用vab方法 + app.config.globalProperties[key] = gp[key as keyof typeof gp] + }) + + if (process.env['NODE_' + 'ENV'] !== `${'deve' + 'lopme' + 'nt'}`) { + const key = 'vab-' + 'icons' + if (!__APP_INFO__['dependencies'][key]) { + // @ts-ignore + app.config.globalProperties = null + } + if (!process.env['VUE_' + 'APP_' + 'SECRET_' + 'KEY']) { + // @ts-ignore + app.config.globalProperties = null + } + } + }, +} diff --git a/front-end/library/shims-vab.d.ts b/front-end/library/shims-vab.d.ts new file mode 100644 index 0000000..c8b3c92 --- /dev/null +++ b/front-end/library/shims-vab.d.ts @@ -0,0 +1,12 @@ +declare let __APP_INFO__: any + +// CSS +type CSSModuleClasses = { readonly [key: string]: string } +declare module '*.module.scss' { + const classes: CSSModuleClasses + export default classes +} +declare module '*.scss' { + const css: string + export default css +} diff --git a/front-end/library/styles/background/black.scss b/front-end/library/styles/background/black.scss new file mode 100644 index 0000000..1d95aef --- /dev/null +++ b/front-end/library/styles/background/black.scss @@ -0,0 +1,251 @@ +/** + * @description 黑 + */ + +body.vab-theme-black { + $base-menu-background: #282c34; + + @mixin container { + color: var(--el-color-white) !important; + background: $base-menu-background !important; + } + + @mixin active { + &:hover { + color: var(--el-color-white) !important; + background-color: var(--el-color-primary) !important; + } + + &.is-active { + color: var(--el-color-white) !important; + background-color: var(--el-color-primary) !important; + } + } + + .logo-container-vertical, + .logo-container-horizontal, + .logo-container-comprehensive, + .logo-container-float { + @include container; + } + + .logo-container-column { + .logo { + @include container; + } + } + + .vab-column-bar-container.el-scrollbar { + .el-tabs { + .el-tabs__nav-wrap.is-left { + @include container; + } + + .el-tabs__nav { + @include container; + } + + .el-tabs__item.is-active { + background: var(--el-color-primary) !important; + } + } + + .el-menu { + .el-menu-item.is-active, + .el-sub-menu__title.is-active, + .el-menu-item:hover, + .el-sub-menu__title:hover { + i { + color: var(--el-color-primary) !important; + } + + color: var(--el-color-primary) !important; + background-color: var(--el-color-primary-light-9) !important; + } + } + } + + .vab-column-bar-container-card.el-scrollbar { + .el-tabs { + .el-tabs__item.is-active { + background: transparent !important; + + .vab-column-grid { + background: var(--el-color-primary) !important; + } + } + } + } + + .vab-column-bar-container-arrow.el-scrollbar { + .el-tabs { + .el-tabs__item.is-active { + background: transparent !important; + + .vab-column-grid { + background: transparent !important; + } + } + } + } + + .vab-layout-float, + .vab-layout-common, + .vab-layout-vertical, + .vab-layout-horizontal, + .vab-layout-comprehensive { + .el-menu { + @include container; + + .el-sub-menu .el-sub-menu__title, + .el-menu-item { + @include container; + } + } + + .vab-side-bar, + .comprehensive-bar-container { + @include container; + + .el-menu-item { + @include active; + } + } + } + + .vab-layout-float { + .el-scrollbar__view + .el-menu--collapse.el-menu + li.el-sub-menu.is-active { + .el-sub-menu__title { + background-color: transparent !important; + } + + > .el-sub-menu__title { + background-color: var(--el-color-primary) !important; + } + } + } + + .vab-header { + @include container; + + .vab-main { + @include container; + + .right-panel { + .el-menu { + &--horizontal { + .el-sub-menu .el-sub-menu__title, + .el-menu-item { + @include active; + } + } + } + [role='menubar'].el-menu--horizontal { + > .el-sub-menu.is-active[tabindex='0'] { + > .el-sub-menu__title { + color: var(--el-color-white) !important; + background-color: var( + --el-color-primary + ) !important; + } + } + } + } + } + } + + .vab-tabs { + &-more { + &-active, + &:hover { + .vab-tabs-more-icon { + .box:before, + .box:after { + background: var(--el-color-primary) !important; + } + } + } + } + + .vab-tabs-content-card { + .el-tabs__header { + .el-tabs__item { + &.is-active { + color: var(--el-color-primary) !important; + background: var(--el-color-primary-light-9) !important; + border: 1px solid var(--el-color-primary) !important; + } + + &:hover { + border: 1px solid var(--el-color-primary) !important; + } + } + } + } + + .vab-tabs-content-smart { + .el-tabs__header { + .el-tabs__item { + &.is-active { + background: var(--el-color-primary-light-9) !important; + } + + &:after { + background-color: var(--el-color-primary) !important; + } + + &:hover { + background: var(--el-color-primary-light-9) !important; + } + } + } + } + + .vab-tabs-content-smooth { + .el-tabs__header { + .el-tabs__item { + &.is-active { + color: var(--el-color-primary) !important; + background: var(--el-color-primary-light-9) !important; + + &:hover { + color: var(--el-color-primary) !important; + background: var( + --el-color-primary-light-9 + ) !important; + } + } + + &:hover { + color: var(--el-color-black) !important; + } + } + } + } + } + + .vab-nav { + .el-tabs__item.is-active, + .el-tabs__item:hover { + color: var(--el-color-primary) !important; + } + + .el-tabs__active-bar { + background-color: var(--el-color-primary) !important; + } + } + + #nprogress { + .bar { + background: var(--el-color-primary) !important; + } + + .peg { + box-shadow: + 0 0 10px var(--el-color-primary), + 0 0 5px var(--el-color-primary) !important; + } + } +} diff --git a/front-end/library/styles/background/image.scss b/front-end/library/styles/background/image.scss new file mode 100644 index 0000000..2e460ca --- /dev/null +++ b/front-end/library/styles/background/image.scss @@ -0,0 +1,108 @@ +/** + * @description 菜单背景 + */ + +body.vab-background > #app { + $base-menu-background: url('~@/assets/theme_images/background-1.png') + no-repeat; + + @mixin container { + color: $base-color-white !important; + background: $base-menu-background !important; + background-size: auto 100% !important; + } + @mixin transparent { + color: $base-color-white !important; + background: transparent !important; + } + @mixin active { + span { + color: $base-color-white !important; + } + + &:hover { + color: $base-color-white !important; + background-color: rgba(0, 0, 0, 0.3) !important; + } + + &.is-active { + color: $base-color-white !important; + background-color: rgba(0, 0, 0, 0.3) !important; + } + } + + .vab-side-bar:not(.is-collapse), + .comprehensive-bar-container { + @include container; + + .el-menu { + @include transparent; + + .el-menu-item, + .el-sub-menu__title { + @include transparent; + @include active; + + i, + svg { + @include transparent; + } + } + } + + .logo-container-vertical, + .logo-container-comprehensive, + .logo-container-float { + @include transparent; + + .logo .vab-icon, + .title { + @include transparent; + } + } + } + + .vab-column-bar-container { + &.el-scrollbar { + .logo-container-column { + .logo { + @include container; + background: #034291 !important; + + .vab-icon { + @include transparent; + } + } + } + + .el-tabs { + .el-tabs__nav-wrap.is-left { + @include container; + } + + .el-tabs__nav, + .el-tabs__item { + @include transparent; + + &.is-active { + color: $base-color-white !important; + background-color: rgba(0, 0, 0, 0.3) !important; + } + } + } + + &.vab-column-bar-container-card { + .el-tabs { + .el-tabs__item { + &.is-active { + background: transparent !important; + .vab-column-grid { + background-color: rgba(0, 0, 0, 0.3) !important; + } + } + } + } + } + } + } +} diff --git a/front-end/library/styles/background/ocean.scss b/front-end/library/styles/background/ocean.scss new file mode 100644 index 0000000..db2eca8 --- /dev/null +++ b/front-end/library/styles/background/ocean.scss @@ -0,0 +1,181 @@ +/** + * @description 渐变 + */ + +body.vab-theme-ocean { + $base-color-blue: #1890ff; + $base-color-blue-active: #399efd; + + @mixin container { + background: linear-gradient(to right, #006cff, #399efd) !important; + } + + @mixin active { + &:hover { + color: $base-color-white; + background-color: $base-color-blue-active !important; + } + + &.is-active { + color: $base-color-white; + background-color: $base-color-blue-active !important; + } + } + + .logo-container-horizontal { + background: var(--el-color-primary) !important; + } + + .logo-container-vertical, + .logo-container-comprehensive, + .logo-container-float { + @include container; + } + + .logo-container-column { + .logo { + @include container; + } + } + + .vab-column-bar-container { + .el-tabs { + .el-tabs__nav-wrap.is-left { + @include container; + } + + .el-tabs__nav { + @include container; + } + } + + .el-menu { + .el-menu-item.is-active, + .el-sub-menu__title.is-active, + .el-menu-item:hover, + .el-sub-menu__title:hover { + i { + color: var(--el-color-primary) !important; + } + + color: var(--el-color-primary) !important; + background-color: var(--el-color-primary-light-9) !important; + } + } + + &-card { + .el-tabs { + .el-tabs__item { + &.is-active { + background: transparent !important; + } + } + } + } + } + + .vab-layout-horizontal { + .vab-header { + background: var(--el-color-primary) !important; + } + + .el-menu { + background: var(--el-color-primary) !important; + + .el-sub-menu__title { + background: var(--el-color-primary) !important; + } + + .el-menu-item { + background: var(--el-color-primary) !important; + } + } + + .vab-side-bar, + .comprehensive-bar-container { + background: var(--el-color-primary) !important; + + .el-menu-item { + @include active; + } + } + } + + .vab-layout-vertical, + .vab-layout-comprehensive, + .vab-layout-common, + .vab-layout-float { + .vab-side-bar, + .comprehensive-bar-container { + @include container; + + .el-menu { + @include container; + @include active; + + .el-sub-menu__title, + .el-menu-item { + background-color: transparent !important; + @include active; + + &.is-active { + background-color: $base-color-blue-active !important; + } + } + } + } + } + + .vab-layout-float { + .el-scrollbar__view + .el-menu--collapse.el-menu + li.el-sub-menu.is-active { + .el-sub-menu__title { + background-color: transparent !important; + } + + > .el-sub-menu__title { + background-color: var(--el-color-primary) !important; + } + } + } + + .vab-header { + background-color: var(--el-color-primary) !important; + + .vab-main { + .el-menu.el-menu { + background-color: var(--el-color-primary) !important; + + &--horizontal { + .el-sub-menu, + .el-menu-item { + background-color: var(--el-color-primary) !important; + + &.is-active { + color: $base-color-white !important; + background-color: $base-color-blue-active !important; + } + } + + > .el-menu-item, + .el-sub-menu__title, + > .el-menu-item:hover, + > .el-sub-menu__title:hover { + color: $base-color-white !important; + background-color: var(--el-color-primary) !important; + + i { + color: $base-color-white !important; + } + + &.is-active { + color: $base-color-white !important; + background-color: $base-color-blue-active !important; + } + } + } + } + } + } +} diff --git a/front-end/library/styles/background/white.scss b/front-end/library/styles/background/white.scss new file mode 100644 index 0000000..0634500 --- /dev/null +++ b/front-end/library/styles/background/white.scss @@ -0,0 +1,293 @@ +/** + * @description 白 + */ + +body.vab-theme-white { + $base-menu-background: #fff; + + @mixin container { + color: #515a6e !important; + background: $base-menu-background !important; + } + + @mixin active { + &:hover { + color: var(--el-color-primary) !important; + background-color: var(--el-color-primary-light-9) !important; + + i, + svg, + span[title] { + color: var(--el-color-primary) !important; + } + } + + &.is-active { + color: var(--el-color-primary) !important; + background-color: var(--el-color-primary-light-9) !important; + + i, + svg, + span[title] { + color: var(--el-color-primary) !important; + } + } + } + + .logo-container-common, + .logo-container-vertical, + .logo-container-horizontal, + .logo-container-comprehensive, + .logo-container-float { + @include container; + + .title, + .vab-icon { + @include container; + } + } + + .logo-container-column { + @include container; + + .title { + @include container; + } + + .logo, + .vab-icon { + @include container; + } + } + + .vab-column-bar-container { + .el-tabs { + @include container; + + .el-tabs__nav-wrap.is-left { + background: #f7faff !important; + } + + .el-tabs__item, + .el-tabs__nav { + @include container; + } + + .el-tabs__item.is-active { + color: var(--el-color-white) !important; + background: var(--el-color-primary) !important; + } + } + + .el-menu { + .el-menu-item.is-active, + .el-sub-menu__title.is-active, + .el-menu-item:hover, + .el-sub-menu__title:hover { + i { + color: var(--el-color-primary) !important; + } + + color: var(--el-color-primary) !important; + background-color: var(--el-color-primary-light-9) !important; + } + } + + &-card { + .el-tabs { + .el-tabs__item { + &.is-active { + background: transparent !important; + } + } + } + } + + &-arrow { + .el-tabs { + .el-tabs__item { + &.is-active { + color: var(--el-color-black) !important; + background: transparent !important; + + .vab-column-grid { + background: transparent !important; + } + } + } + } + } + } + + .vab-layout-float, + .vab-layout-common, + .vab-layout-vertical, + .vab-layout-horizontal, + .vab-layout-comprehensive { + .el-menu { + @include container; + + .el-sub-menu .el-sub-menu__title, + .el-menu-item { + @include container; + } + + .el-menu-item.is-active, + .el-sub-menu__title.is-active, + .el-menu-item:hover, + .el-sub-menu__title:hover { + i { + color: var(--el-color-primary) !important; + } + + color: var(--el-color-primary) !important; + background-color: var(--el-color-primary-light-9) !important; + } + } + + .vab-side-bar, + .comprehensive-bar-container { + @include container; + + .el-menu-item { + @include active; + } + } + } + + .vab-layout-float { + .el-scrollbar__view + .el-menu--collapse.el-menu + li.el-sub-menu.is-active { + .el-sub-menu__title { + background-color: transparent !important; + } + + > .el-sub-menu__title { + color: var(--el-color-primary) !important; + background-color: var(--el-color-primary-light-9) !important; + } + } + } + + .vab-header { + @include container; + + .vab-main { + @include container; + + .right-panel { + .user-name, + .user-name *, + > i, + > div > i, + > span > i, + > div > span > i, + > svg, + > div > svg, + > span > svg, + > div > span > svg, + .ri-notification-line, + .ri-translate, + .ri-bug-line { + @include container; + } + + .el-menu { + &--horizontal { + .el-sub-menu .el-sub-menu__title, + .el-menu-item { + @include active; + } + + .el-sub-menu, + .el-menu-item { + &.is-active { + @include active; + } + } + + > .el-sub-menu.is-active { + > .el-sub-menu__title { + background-color: var( + --el-color-primary-light-9 + ) !important; + @include active; + } + } + } + } + } + } + } + + .vab-tabs { + &-more { + &-active, + &:hover { + .vab-tabs-more-icon { + .box:before, + .box:after { + background: var(--el-color-primary) !important; + } + } + } + } + + .vab-tabs-content-card { + .el-tabs__header { + .el-tabs__item { + &.is-active { + color: var(--el-color-primary) !important; + background: var(--el-color-primary-light-9) !important; + border: 1px solid var(--el-color-primary) !important; + } + + &:hover { + border: 1px solid var(--el-color-primary) !important; + } + } + } + } + + .vab-tabs-content-smart { + .el-tabs__header { + .el-tabs__item { + &.is-active { + background: var(--el-color-primary-light-9) !important; + } + + &:after { + background-color: var(--el-color-primary) !important; + } + + &:hover { + background: var(--el-color-primary-light-9) !important; + } + } + } + } + + .vab-tabs-content-smooth { + .el-tabs__header { + .el-tabs__item { + &.is-active { + color: var(--el-color-primary) !important; + background: var(--el-color-primary-light-9) !important; + + &:hover { + color: var(--el-color-primary) !important; + background: var( + --el-color-primary-light-9 + ) !important; + } + } + + &:hover { + color: var(--el-color-black) !important; + } + } + } + } + } +} diff --git a/front-end/library/styles/loading/dots.css b/front-end/library/styles/loading/dots.css new file mode 100644 index 0000000..0049258 --- /dev/null +++ b/front-end/library/styles/loading/dots.css @@ -0,0 +1,124 @@ +.dots-loader:not(:required) { + position: relative; + display: inline-block; + width: 7px; + height: 7px; + margin-bottom: 30px; + overflow: hidden; + text-indent: -9999px; + background: transparent; + border-radius: 100%; + box-shadow: + #f86 -14px -14px 0 7px, + #fc6 14px -14px 0 7px, + #6d7 14px 14px 0 7px, + #4ae -14px 14px 0 7px; + transform-origin: 50% 50%; + animation: dots-loader 5s infinite ease-in-out; +} + +@keyframes dots-loader { + 0% { + box-shadow: + #f86 -14px -14px 0 7px, + #fc6 14px -14px 0 7px, + #6d7 14px 14px 0 7px, + #4ae -14px 14px 0 7px; + } + + 8.33% { + box-shadow: + #f86 14px -14px 0 7px, + #fc6 14px -14px 0 7px, + #6d7 14px 14px 0 7px, + #4ae -14px 14px 0 7px; + } + + 16.67% { + box-shadow: + #f86 14px 14px 0 7px, + #fc6 14px 14px 0 7px, + #6d7 14px 14px 0 7px, + #4ae -14px 14px 0 7px; + } + + 25% { + box-shadow: + #f86 -14px 14px 0 7px, + #fc6 -14px 14px 0 7px, + #6d7 -14px 14px 0 7px, + #4ae -14px 14px 0 7px; + } + + 33.33% { + box-shadow: + #f86 -14px -14px 0 7px, + #fc6 -14px 14px 0 7px, + #6d7 -14px -14px 0 7px, + #4ae -14px -14px 0 7px; + } + + 41.67% { + box-shadow: + #f86 14px -14px 0 7px, + #fc6 -14px 14px 0 7px, + #6d7 -14px -14px 0 7px, + #4ae 14px -14px 0 7px; + } + + 50% { + box-shadow: + #f86 14px 14px 0 7px, + #fc6 -14px 14px 0 7px, + #6d7 -14px -14px 0 7px, + #4ae 14px -14px 0 7px; + } + + 58.33% { + box-shadow: + #f86 -14px 14px 0 7px, + #fc6 -14px 14px 0 7px, + #6d7 -14px -14px 0 7px, + #4ae 14px -14px 0 7px; + } + + 66.67% { + box-shadow: + #f86 -14px -14px 0 7px, + #fc6 -14px -14px 0 7px, + #6d7 -14px -14px 0 7px, + #4ae 14px -14px 0 7px; + } + + 75% { + box-shadow: + #f86 14px -14px 0 7px, + #fc6 14px -14px 0 7px, + #6d7 14px -14px 0 7px, + #4ae 14px -14px 0 7px; + } + + 83.33% { + box-shadow: + #f86 14px 14px 0 7px, + #fc6 14px -14px 0 7px, + #6d7 14px 14px 0 7px, + #4ae 14px 14px 0 7px; + } + + 91.67% { + box-shadow: + #f86 -14px 14px 0 7px, + #fc6 14px -14px 0 7px, + #6d7 14px 14px 0 7px, + #4ae -14px 14px 0 7px; + } + + 100% { + box-shadow: + #f86 -14px -14px 0 7px, + #fc6 14px -14px 0 7px, + #6d7 14px 14px 0 7px, + #4ae -14px 14px 0 7px; + } +} diff --git a/front-end/library/styles/loading/gauge.css b/front-end/library/styles/loading/gauge.css new file mode 100644 index 0000000..401f8d8 --- /dev/null +++ b/front-end/library/styles/loading/gauge.css @@ -0,0 +1,104 @@ +.gauge-loader:not(:required) { + position: relative; + display: inline-block; + width: 64px; + height: 32px; + margin-bottom: 10px; + overflow: hidden; + text-indent: -9999px; + background: #6ca; + border-top-left-radius: 32px; + border-top-right-radius: 32px; +} + +.gauge-loader:not(:required)::before { + position: absolute; + top: 5px; + left: 30px; + width: 4px; + height: 27px; + content: ''; + background: white; + border-radius: 2px; + transform-origin: 50% 100%; + animation: gauge-loader 4000ms infinite ease; +} + +.gauge-loader:not(:required)::after { + position: absolute; + top: 26px; + left: 26px; + width: 13px; + height: 13px; + content: ''; + background: white; + -moz-border-radius: 8px; + -webkit-border-radius: 8px; + border-radius: 8px; +} + +@keyframes gauge-loader { + 0% { + transform: rotate(-50deg); + } + + 10% { + transform: rotate(20deg); + } + + 20% { + transform: rotate(60deg); + } + + 24% { + transform: rotate(60deg); + } + + 40% { + transform: rotate(-20deg); + } + + 54% { + transform: rotate(70deg); + } + + 56% { + transform: rotate(78deg); + } + + 58% { + transform: rotate(73deg); + } + + 60% { + transform: rotate(75deg); + } + + 62% { + transform: rotate(70deg); + } + + 70% { + transform: rotate(-20deg); + } + + 80% { + transform: rotate(20deg); + } + + 83% { + transform: rotate(25deg); + } + + 86% { + transform: rotate(20deg); + } + + 89% { + transform: rotate(25deg); + } + + 100% { + transform: rotate(-50deg); + } +} diff --git a/front-end/library/styles/loading/inner-circles.css b/front-end/library/styles/loading/inner-circles.css new file mode 100644 index 0000000..d753d66 --- /dev/null +++ b/front-end/library/styles/loading/inner-circles.css @@ -0,0 +1,51 @@ +.inner-circles-loader:not(:required) { + position: relative; + display: inline-block; + width: 50px; + height: 50px; + margin-bottom: 10px; + overflow: hidden; + text-indent: -9999px; + background: rgba(25, 165, 152, 0.5); + border-radius: 50%; + transform: translate3d(0, 0, 0); +} + +.inner-circles-loader:not(:required)::before, +.inner-circles-loader:not(:required)::after { + position: absolute; + top: 0; + display: inline-block; + width: 50px; + height: 50px; + content: ''; + border-radius: 50%; +} + +.inner-circles-loader:not(:required)::before { + left: 0; + background: #c7efcf; + transform-origin: 0 50%; + animation: inner-circles-loader 3s infinite; +} + +.inner-circles-loader:not(:required)::after { + right: 0; + background: #eef5db; + transform-origin: 100% 50%; + animation: inner-circles-loader 3s 0.2s reverse infinite; +} + +@keyframes inner-circles-loader { + 0% { + transform: rotate(0deg); + } + + 50% { + transform: rotate(360deg); + } + + 100% { + transform: rotate(0deg); + } +} diff --git a/front-end/library/styles/loading/plus.css b/front-end/library/styles/loading/plus.css new file mode 100644 index 0000000..28b6951 --- /dev/null +++ b/front-end/library/styles/loading/plus.css @@ -0,0 +1,341 @@ +.plus-loader:not(:required) { + position: relative; + display: inline-block; + width: 48px; + height: 48px; + margin-bottom: 10px; + overflow: hidden; + text-indent: -9999px; + background: #f86; + -moz-border-radius: 24px; + -webkit-border-radius: 24px; + border-radius: 24px; + -moz-transform: rotateZ(90deg); + -ms-transform: rotateZ(90deg); + -webkit-transform: rotateZ(90deg); + transform: rotateZ(90deg); + -moz-transform-origin: 50% 50%; + -ms-transform-origin: 50% 50%; + -webkit-transform-origin: 50% 50%; + transform-origin: 50% 50%; + -moz-animation: plus-loader-background 3s infinite ease-in-out; + -webkit-animation: plus-loader-background 3s infinite ease-in-out; + animation: plus-loader-background 3s infinite ease-in-out; +} + +.plus-loader:not(:required)::after { + position: absolute; + top: 0; + right: 50%; + width: 50%; + height: 100%; + content: ''; + background: #f86; + -moz-border-radius: 24px 0 0 24px; + -webkit-border-radius: 24px; + border-radius: 24px 0 0 24px; + -moz-transform-origin: 100% 50%; + -ms-transform-origin: 100% 50%; + -webkit-transform-origin: 100% 50%; + transform-origin: 100% 50%; + -moz-animation: plus-loader-top 3s infinite linear; + -webkit-animation: plus-loader-top 3s infinite linear; + animation: plus-loader-top 3s infinite linear; +} + +.plus-loader:not(:required)::before { + position: absolute; + top: 0; + right: 50%; + width: 50%; + height: 100%; + content: ''; + background: #fc6; + -moz-border-radius: 24px 0 0 24px; + -webkit-border-radius: 24px; + border-radius: 24px 0 0 24px; + -moz-transform-origin: 100% 50%; + -ms-transform-origin: 100% 50%; + -webkit-transform-origin: 100% 50%; + transform-origin: 100% 50%; + -moz-animation: plus-loader-bottom 3s infinite linear; + -webkit-animation: plus-loader-bottom 3s infinite linear; + animation: plus-loader-bottom 3s infinite linear; +} + +@keyframes plus-loader-top { + 2.5% { + background: #f86; + -moz-transform: rotateY(0deg); + -ms-transform: rotateY(0deg); + -webkit-transform: rotateY(0deg); + transform: rotateY(0deg); + -moz-animation-timing-function: ease-in; + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 13.75% { + background: #ff430d; + -moz-transform: rotateY(90deg); + -ms-transform: rotateY(90deg); + -webkit-transform: rotateY(90deg); + transform: rotateY(90deg); + -moz-animation-timing-function: step-start; + -webkit-animation-timing-function: step-start; + animation-timing-function: step-start; + } + + 13.76% { + background: #ffae0d; + -moz-transform: rotateY(90deg); + -ms-transform: rotateY(90deg); + -webkit-transform: rotateY(90deg); + transform: rotateY(90deg); + -moz-animation-timing-function: ease-out; + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 25% { + background: #fc6; + -moz-transform: rotateY(180deg); + -ms-transform: rotateY(180deg); + -webkit-transform: rotateY(180deg); + transform: rotateY(180deg); + } + + 27.5% { + background: #fc6; + -moz-transform: rotateY(180deg); + -ms-transform: rotateY(180deg); + -webkit-transform: rotateY(180deg); + transform: rotateY(180deg); + -moz-animation-timing-function: ease-in; + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 41.25% { + background: #ffae0d; + -moz-transform: rotateY(90deg); + -ms-transform: rotateY(90deg); + -webkit-transform: rotateY(90deg); + transform: rotateY(90deg); + -moz-animation-timing-function: step-start; + -webkit-animation-timing-function: step-start; + animation-timing-function: step-start; + } + + 41.26% { + background: #2cc642; + -moz-transform: rotateY(90deg); + -ms-transform: rotateY(90deg); + -webkit-transform: rotateY(90deg); + transform: rotateY(90deg); + -moz-animation-timing-function: ease-out; + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 50% { + background: #6d7; + -moz-transform: rotateY(0deg); + -ms-transform: rotateY(0deg); + -webkit-transform: rotateY(0deg); + transform: rotateY(0deg); + } + + 52.5% { + background: #6d7; + -moz-transform: rotateY(0deg); + -ms-transform: rotateY(0deg); + -webkit-transform: rotateY(0deg); + transform: rotateY(0deg); + -moz-animation-timing-function: ease-in; + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 63.75% { + background: #2cc642; + -moz-transform: rotateY(90deg); + -ms-transform: rotateY(90deg); + -webkit-transform: rotateY(90deg); + transform: rotateY(90deg); + -moz-animation-timing-function: step-start; + -webkit-animation-timing-function: step-start; + animation-timing-function: step-start; + } + + 63.76% { + background: #1386d2; + -moz-transform: rotateY(90deg); + -ms-transform: rotateY(90deg); + -webkit-transform: rotateY(90deg); + transform: rotateY(90deg); + -moz-animation-timing-function: ease-out; + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 75% { + background: #4ae; + -moz-transform: rotateY(180deg); + -ms-transform: rotateY(180deg); + -webkit-transform: rotateY(180deg); + transform: rotateY(180deg); + } + + 77.5% { + background: #4ae; + -moz-transform: rotateY(180deg); + -ms-transform: rotateY(180deg); + -webkit-transform: rotateY(180deg); + transform: rotateY(180deg); + -moz-animation-timing-function: ease-in; + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 91.25% { + background: #1386d2; + -moz-transform: rotateY(90deg); + -ms-transform: rotateY(90deg); + -webkit-transform: rotateY(90deg); + transform: rotateY(90deg); + -moz-animation-timing-function: step-start; + -webkit-animation-timing-function: step-start; + animation-timing-function: step-start; + } + + 91.26% { + background: #ff430d; + -moz-transform: rotateY(90deg); + -ms-transform: rotateY(90deg); + -webkit-transform: rotateY(90deg); + transform: rotateY(90deg); + -moz-animation-timing-function: ease-in; + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 100% { + background: #f86; + -moz-transform: rotateY(0deg); + -ms-transform: rotateY(0deg); + -webkit-transform: rotateY(0deg); + transform: rotateY(0deg); + -moz-animation-timing-function: step-start; + -webkit-animation-timing-function: step-start; + animation-timing-function: step-start; + } +} + +@keyframes plus-loader-bottom { + 0% { + background: #fc6; + -moz-animation-timing-function: step-start; + -webkit-animation-timing-function: step-start; + animation-timing-function: step-start; + } + + 50% { + background: #fc6; + -moz-animation-timing-function: step-start; + -webkit-animation-timing-function: step-start; + animation-timing-function: step-start; + } + + 75% { + background: #4ae; + -moz-animation-timing-function: step-start; + -webkit-animation-timing-function: step-start; + animation-timing-function: step-start; + } + + 100% { + background: #4ae; + -moz-animation-timing-function: step-start; + -webkit-animation-timing-function: step-start; + animation-timing-function: step-start; + } +} + +@keyframes plus-loader-background { + 0% { + background: #f86; + -moz-transform: rotateZ(180deg); + -ms-transform: rotateZ(180deg); + -webkit-transform: rotateZ(180deg); + transform: rotateZ(180deg); + } + + 25% { + background: #f86; + -moz-transform: rotateZ(180deg); + -ms-transform: rotateZ(180deg); + -webkit-transform: rotateZ(180deg); + transform: rotateZ(180deg); + -moz-animation-timing-function: step-start; + -webkit-animation-timing-function: step-start; + animation-timing-function: step-start; + } + + 27.5% { + background: #6d7; + -moz-transform: rotateZ(90deg); + -ms-transform: rotateZ(90deg); + -webkit-transform: rotateZ(90deg); + transform: rotateZ(90deg); + } + + 50% { + background: #6d7; + -moz-transform: rotateZ(90deg); + -ms-transform: rotateZ(90deg); + -webkit-transform: rotateZ(90deg); + transform: rotateZ(90deg); + -moz-animation-timing-function: step-start; + -webkit-animation-timing-function: step-start; + animation-timing-function: step-start; + } + + 52.5% { + background: #6d7; + -moz-transform: rotateZ(0deg); + -ms-transform: rotateZ(0deg); + -webkit-transform: rotateZ(0deg); + transform: rotateZ(0deg); + } + + 75% { + background: #6d7; + -moz-transform: rotateZ(0deg); + -ms-transform: rotateZ(0deg); + -webkit-transform: rotateZ(0deg); + transform: rotateZ(0deg); + -moz-animation-timing-function: step-start; + -webkit-animation-timing-function: step-start; + animation-timing-function: step-start; + } + + 77.5% { + background: #f86; + -moz-transform: rotateZ(270deg); + -ms-transform: rotateZ(270deg); + -webkit-transform: rotateZ(270deg); + transform: rotateZ(270deg); + } + + 100% { + background: #f86; + -moz-transform: rotateZ(270deg); + -ms-transform: rotateZ(270deg); + -webkit-transform: rotateZ(270deg); + transform: rotateZ(270deg); + -moz-animation-timing-function: step-start; + -webkit-animation-timing-function: step-start; + animation-timing-function: step-start; + } +} diff --git a/front-end/library/styles/normalize.scss b/front-end/library/styles/normalize.scss new file mode 100644 index 0000000..f4c364d --- /dev/null +++ b/front-end/library/styles/normalize.scss @@ -0,0 +1,377 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + +html { + line-height: 1.15; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers. + */ + +body { + margin: 0; +} + +/** + * Render the `main` element consistently in IE. + */ + +main { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + margin: 0.67em 0; + font-size: 2em; +} + +/* Grouping content + ========================================================================== */ + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; + /* 1 */ + height: 0; + /* 1 */ + overflow: visible; + /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Remove the gray background on active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + text-decoration: underline dotted; + /* 2 */ + border-bottom: none; + /* 1 */ +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove the border on images inside links in IE 10. + */ + +img { + border-style: none; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + margin: 0; + /* 2 */ + font-family: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + line-height: 1.15; + /* 1 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { + /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { + /* 1 */ + text-transform: none; +} + +/** + * Correct the inability to style clickable types in iOS and Safari. + */ + +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type='button']::-moz-focus-inner, +[type='reset']::-moz-focus-inner, +[type='submit']::-moz-focus-inner { + padding: 0; + border-style: none; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type='button']:-moz-focusring, +[type='reset']:-moz-focusring, +[type='submit']:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; + /* 1 */ + display: table; + /* 1 */ + max-width: 100%; + /* 1 */ + padding: 0; + /* 3 */ + color: inherit; + /* 2 */ + white-space: normal; + /* 1 */ +} + +/** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + vertical-align: baseline; +} + +/** + * Remove the default vertical scrollbar in IE 10+. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + +[type='checkbox'], +[type='radio'] { + box-sizing: border-box; + /* 1 */ + padding: 0; + /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type='number']::-webkit-inner-spin-button, +[type='number']::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/** + * Remove the inner padding in Chrome and Safari on macOS. + */ + +[type='search']::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + /* 1 */ + font: inherit; + -webkit-appearance: button; + /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + +details { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Misc + ========================================================================== */ + +/** + * Add the correct display in IE 10+. + */ + +template { + display: none; +} + +/** + * Add the correct display in IE 10. + */ + +[hidden] { + display: none; +} diff --git a/front-end/library/styles/transition.scss b/front-end/library/styles/transition.scss new file mode 100644 index 0000000..54ec6f9 --- /dev/null +++ b/front-end/library/styles/transition.scss @@ -0,0 +1,39 @@ +/** + * @description vue过渡动画 + */ + +.fade-transform { + &-leave-active, + &-enter-active { + transition: $base-transition; + } + + &-enter, + &-leave-to { + opacity: 0; + } +} + +.no-transform { + &-leave-active, + &-enter-active { + transition: none; + } + + &-enter, + &-leave-to { + opacity: 0; + } +} + +/** + * @description 旋转动画 + */ +@keyframes rotate { + 0% { + transform: rotate(0); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/front-end/library/styles/vab.scss b/front-end/library/styles/vab.scss new file mode 100644 index 0000000..71f222e --- /dev/null +++ b/front-end/library/styles/vab.scss @@ -0,0 +1,633 @@ +/** + * @description 全局样式 + */ +@import 'element-plus/theme-chalk/display.css'; +@import './normalize'; +@import './transition'; + +@mixin base-scrollbar { + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + &::-webkit-scrollbar-thumb { + background-color: mix($base-color-white, $base-menu-background, 90%); + border: 3px solid transparent; + border-radius: 7px; + } + + &::-webkit-scrollbar-thumb:hover { + background-color: mix($base-color-white, $base-menu-background, 80%); + } +} + +.vab-layout-header, +[class*='-bar-container'] { + transition: $base-transition; + + * { + transition: $base-transition; + } +} + +html { + + body, + body[class*='vab-theme-'] { + position: relative; + box-sizing: border-box; + height: 100vh; + padding: 0; + overflow: hidden; + font-family: 'PingFang SC', Arial, 'Microsoft YaHei', sans-serif; + font-size: $base-font-size-default; + color: var(--el-color-black); + background: $base-color-background; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + #app { + height: 100vh; + overflow: auto; + @include base-scrollbar; + + .vab-main { + transition: $base-transition; + + .vab-app-main { + width: 100%; + padding: $base-padding; + overflow: hidden; + transition: $base-transition; + + >section { + background: var(--el-color-white); + transition: $base-transition; + + >[class*='-container'] { + min-height: $base-keep-alive-height; + padding: $base-padding; + background: var(--el-color-white); + transition: $base-transition; + } + } + } + } + } + + * { + box-sizing: border-box; + outline: none !important; + @include base-scrollbar; + } + + [class*='ri-'] { + vertical-align: -3px !important; + } + + .vab-icon { + margin: 0 3px 0 0 !important; + } + + /*a标签 */ + a { + color: var(--el-color-primary); + text-decoration: none; + } + + /*图片 */ + img { + object-fit: cover; + + &[src=''], + &:not([src]) { + opacity: 0; + } + } + + /* vab-fullscreen全屏 */ + .vab-fullscreen { + position: fixed !important; + top: 0 !important; + left: 0 !important; + z-index: $base-z-index + 3 !important; + box-sizing: border-box !important; + width: 100vw !important; + height: 100vh !important; + padding-bottom: 15px !important; + overflow: auto !important; + } + + /* vab-dropdown下拉动画 */ + .vab-dropdown { + transition: $base-transition; + + &-active { + transform: rotateZ(180deg); + } + } + + /* vab-dot圆点动画 */ + .vab-dot { + position: relative; + display: inline-block; + width: 6px; + height: 6px; + margin-right: 3px; + vertical-align: middle; + border-radius: 50%; + + span { + position: absolute; + top: 0; + left: 0; + box-sizing: border-box; + display: block; + width: 100%; + height: 100%; + border-radius: 50%; + animation: vabDot 1.2s ease-in-out infinite; + + @keyframes vabDot { + 0% { + opacity: 0.6; + transform: scale(0.8); + } + + to { + opacity: 0; + transform: scale(2.4); + } + } + } + + &-success { + background: var(--el-color-success); + + span { + background: var(--el-color-success); + } + } + + &-error { + background: var(--el-color-error); + + span { + background: var(--el-color-error); + } + } + } + + /* vab-data-empty占位图 */ + .vab-data-empty { + display: flex; + align-items: center; + justify-content: center; + min-height: 600px; + margin: auto; + } + + /* el-descriptions */ + .el-descriptions { + &__title { + padding-left: 10px; + border-left: 5px solid var(--el-color-primary); + } + } + + /* el-button按钮 */ + .el-button { + border-radius: var(--el-border-radius-base); + + &:hover, + &:focus, + &:active, + &.is-disabled { + background-clip: padding-box; + } + + &.is-round { + border-radius: var(--el-border-radius-round); + } + + &.is-circle { + border-radius: var(--el-border-radius-circle); + } + + [class*='el-icon-']+span, + span+[class*='el-icon-'], + [class*='ri-']+span, + span+[class*='ri-'] { + margin-left: 3px; + } + } + + /* el-tag */ + .el-tag { + border-radius: var(--el-border-radius-base); + + &+.el-tag { + margin-left: 10px; + } + + &--light:not(&--success, &--info, &--warning, &--danger) { + --el-tag-bg-color: var(--el-color-primary-light-9); + --el-tag-border-color: var(--el-color-primary-light-8); + --el-tag-text-color: var(--el-color-primary); + --el-tag-hover-color: var(--el-color-primary); + } + + &--dark:not(&--success, &--info, &--warning, &--danger) { + --el-tag-bg-color: var(--el-color-primary); + --el-tag-border-color: var(--el-color-primary); + --el-tag-hover-color: var(--el-color-primary-2); + } + + &.is-round { + border-radius: var(--el-border-radius-round); + } + } + + /* .el-select-tags */ + .el-select-tags-wrapper { + .el-tag.el-tag { + margin-left: 0px; + } + } + + /* el-select */ + .el-select { + min-width: 115px; + } + + a+a, + /* span + span, */ + a+.el-button, + .el-button+a { + margin-left: 10px; + } + + .el-drawer__wrapper { + outline: none !important; + + * { + outline: none !important; + } + } + + /* el-overlay遮罩 */ + .el-overlay { + background-color: rgba(0, 0, 0, 0.1); + backdrop-filter: blur(3px); + } + + /* el-image-viewer遮罩 */ + .el-image-viewer__mask { + background-color: rgba(0, 0, 0, 0.1); + backdrop-filter: blur(3px); + } + + /* v-modal遮罩 */ + .v-modal { + z-index: $base-z-index; + background-color: rgba(0, 0, 0, 0.5); + opacity: 0.6; + //backdrop-filter: blur(10px); + } + + /* el-loading-mask遮罩 */ + .el-loading-mask { + z-index: $base-z-index - 10 !important; + + &.is-fullscreen { + z-index: $base-z-index + 99 !important; + } + } + + /* el-scrollbar滚动条 */ + .el-scrollbar { + height: 100%; + + &__bar { + z-index: 999; + } + + &__thumb { + background-color: mix($base-color-white, + $base-menu-background, + 90%); + + &:hover { + background-color: mix($base-color-white, + $base-menu-background, + 80%); + } + } + } + + /* el-form表单 */ + .el-form--label-top { + .el-form-item__label { + padding: 0; + } + } + + .el-form-item__label { + padding: 0 10px 0 0; + } + + .el-range-editor--small { + + .el-range__icon, + .el-range__close-icon { + line-height: 23.5px; + } + } + + /* el-badge */ + .el-badge__content { + border: 0; + } + + /* .el-page-header */ + .el-page-header { + margin: 0 0 $base-margin 0; + } + + /* el-alert */ + .el-alert { + margin: 0 0 $base-margin 0; + + &__closebtn { + position: absolute !important; + } + + &--success.is-light { + color: var(--el-color-success); + background-color: var(--el-color-success-lighter); + border: 1px solid var(--el-color-success); + + i { + color: var(--el-color-success); + } + } + + &--info.is-light { + color: var(--el-color-primary); + background-color: var(--el-color-primary-light-9); + border: 1px solid var(--el-color-primary); + + i { + color: var(--el-color-primary); + } + } + + &--warning.is-light { + color: var(--el-color-warning); + background-color: var(--el-color-warning-lighter); + border: 1px solid var(--el-color-warning); + + i { + color: var(--el-color-warning); + } + } + + &--error.is-light { + color: var(--el-color-error); + background-color: var(--el-color-error-lighter); + border: 1px solid var(--el-color-error); + + i { + color: var(--el-color-error); + } + } + } + + /* el-divider间隔线 */ + .el-divider--horizontal { + margin: 8px 0 $base-margin + 8px 0; + + .el-divider__text { + display: -webkit-box; + overflow: hidden; + text-overflow: ellipsis; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + } + } + + /* nprogress进度条 */ + #nprogress { + position: fixed; + z-index: $base-z-index + 3; + + .bar { + background: var(--el-color-primary); + } + + .peg { + box-shadow: + 0 0 10px var(--el-color-primary), + 0 0 5px var(--el-color-primary); + } + } + + /* el-table表格 */ + .el-table { + .el-table__body-wrapper { + @include base-scrollbar; + } + + th { + background: #f5f7fa !important; + } + + td, + th { + position: relative; + box-sizing: border-box; + + .cell { + font-size: $base-font-size-default; + font-weight: normal; + color: #606266; + + .el-image { + width: 50px; + height: 50px; + border-radius: $base-border-radius; + } + } + } + } + + /* el-pagination分页 */ + .el-pagination { + justify-content: center; + margin: $base-margin 0 0 0; + font-weight: normal; + color: var(--el-color-black); + } + + /* el-menu菜单开始 */ + .el-menu, + .vab-column-grid { + user-select: none; + + /* plus处理图标间距 */ + div, + li, + span { + i+span { + margin-left: 3px; + } + } + + &.vab-column-grid-card, + &.vab-column-grid-vertical { + div { + i+span { + margin-left: 0; + } + } + } + } + + .el-dialog__body { + padding-right: 30px; + } + + /* el-dialog、el-message-box、el-popover、el-button、el-tag */ + @media (max-width: 576px) { + + .el-dialog, + .el-message-box, + .el-popover.el-popper { + width: 95% !important; + } + + + .el-button { + margin-bottom: 10px; + } + } + + /* el-card卡片 */ + .el-card { + margin-bottom: $base-margin; + border-radius: var(--el-border-radius-base); + + &__header { + position: relative; + + .card-header-tag { + position: absolute; + top: 15px; + right: 20px; + } + } + + &__body { + padding: $base-padding; + } + } + + /* .vab-hey-message */ + .vab-hey-message { + @mixin vab-hey-message { + padding: 15px; + background-color: var(--el-color-white); + border-color: var(--el-color-white); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.15); + + .el-message__content { + padding-right: $base-padding; + color: #34495e; + } + + .el-icon-close { + color: #34495e; + + &:hover { + opacity: 0.8; + } + } + } + + &-info { + @include vab-hey-message; + + i { + color: $base-color-grey; + } + } + + &-success { + @include vab-hey-message; + + i { + color: var(--el-color-success); + } + } + + &-warning { + @include vab-hey-message; + + i { + color: var(--el-color-warning); + } + } + + &-error { + @include vab-hey-message; + + i { + color: var(--el-color-error); + } + } + } + + /* vab-table-expand */ + .vab-table-expand { + padding: $base-padding; + line-height: 30px; + + &-title { + display: inline-block; + width: 80px; + font-weight: bold; + } + } + + :not(.no-background-container).auto-height-container { + display: flex; + flex-direction: column; + height: var(--el-container-height); + + .el-table { + flex: 1; + } + + .el-scrollbar { + //margin-right: -20px; + + .vab-auto-box { + flex: 1; + width: 100%; + padding: 0 var(--el-padding) 0 0; + } + } + + @media (max-width: 1024px) { + height: auto; + } + } + + + .el-card__body { + overflow: hidden; + flex: none; + } + } +} \ No newline at end of file diff --git a/front-end/library/styles/variables/vab-blue-variables.module.scss b/front-end/library/styles/variables/vab-blue-variables.module.scss new file mode 100644 index 0000000..3ac22ee --- /dev/null +++ b/front-end/library/styles/variables/vab-blue-variables.module.scss @@ -0,0 +1,43 @@ +$base-color-primary: #1890ff; +$base-color-success: #13ce66; +$base-color-warning: #ffba00; +$base-color-danger: #ff4d4f; +$base-color-error: #ff4d4f; +$base-color-transition: #77e19d; + +:export { + vab-color-grey: $base-color-grey; + vab-color-black: $base-color-black; + vab-color-primary: $base-color-primary; + vab-color-primary-light-1: mix($base-color-white, $base-color-primary, 10%); + vab-color-primary-light-2: mix($base-color-white, $base-color-primary, 20%); + vab-color-primary-light-3: mix($base-color-white, $base-color-primary, 30%); + vab-color-primary-light-4: mix($base-color-white, $base-color-primary, 40%); + vab-color-primary-light-5: mix($base-color-white, $base-color-primary, 50%); + vab-color-primary-light-6: mix($base-color-white, $base-color-primary, 60%); + vab-color-primary-light-7: mix($base-color-white, $base-color-primary, 70%); + vab-color-primary-light-8: mix($base-color-white, $base-color-primary, 80%); + vab-color-primary-light-9: mix($base-color-white, $base-color-primary, 90%); + vab-color-success: $base-color-success; + vab-color-success-light: mix($base-color-white, $base-color-success, 80%); + vab-color-success-lighter: mix($base-color-white, $base-color-success, 90%); + vab-color-warning: $base-color-warning; + vab-color-warning-light: mix($base-color-white, $base-color-warning, 80%); + vab-color-warning-lighter: mix($base-color-white, $base-color-warning, 90%); + vab-color-danger: $base-color-danger; + vab-color-danger-light: mix($base-color-white, $base-color-danger, 80%); + vab-color-danger-lighter: mix($base-color-white, $base-color-danger, 90%); + vab-color-error: $base-color-error; + vab-color-error-light: mix($base-color-white, $base-color-error, 80%); + vab-color-error-lighter: mix($base-color-white, $base-color-error, 90%); + vab-color-info: $base-color-text-secondary; + vab-color-info-light: mix($base-color-white, + $base-color-text-secondary, + 80%); + vab-color-info-lighter: mix($base-color-white, + $base-color-text-secondary, + 90%); + vab-border-radius-base: 5px; + vab-color-transition: $base-color-transition; + vab-left-menu-width: $base-left-menu-width; +} \ No newline at end of file diff --git a/front-end/library/styles/variables/vab-green-variables.module.scss b/front-end/library/styles/variables/vab-green-variables.module.scss new file mode 100644 index 0000000..dae104d --- /dev/null +++ b/front-end/library/styles/variables/vab-green-variables.module.scss @@ -0,0 +1,43 @@ +$base-color-primary: #41b584; +$base-color-success: #13ce66; +$base-color-warning: #ffba00; +$base-color-danger: #ff4d4f; +$base-color-error: #ff4d4f; +$base-color-transition: #1890ff; + +:export { + vab-color-grey: $base-color-grey; + vab-color-black: $base-color-black; + vab-color-primary: $base-color-primary; + vab-color-primary-light-1: mix($base-color-white, $base-color-primary, 10%); + vab-color-primary-light-2: mix($base-color-white, $base-color-primary, 20%); + vab-color-primary-light-3: mix($base-color-white, $base-color-primary, 30%); + vab-color-primary-light-4: mix($base-color-white, $base-color-primary, 40%); + vab-color-primary-light-5: mix($base-color-white, $base-color-primary, 50%); + vab-color-primary-light-6: mix($base-color-white, $base-color-primary, 60%); + vab-color-primary-light-7: mix($base-color-white, $base-color-primary, 70%); + vab-color-primary-light-8: mix($base-color-white, $base-color-primary, 80%); + vab-color-primary-light-9: mix($base-color-white, $base-color-primary, 90%); + vab-color-success: $base-color-success; + vab-color-success-light: mix($base-color-white, $base-color-success, 80%); + vab-color-success-lighter: mix($base-color-white, $base-color-success, 90%); + vab-color-warning: $base-color-warning; + vab-color-warning-light: mix($base-color-white, $base-color-warning, 80%); + vab-color-warning-lighter: mix($base-color-white, $base-color-warning, 90%); + vab-color-danger: $base-color-danger; + vab-color-danger-light: mix($base-color-white, $base-color-danger, 80%); + vab-color-danger-lighter: mix($base-color-white, $base-color-danger, 90%); + vab-color-error: $base-color-error; + vab-color-error-light: mix($base-color-white, $base-color-error, 80%); + vab-color-error-lighter: mix($base-color-white, $base-color-error, 90%); + vab-color-info: $base-color-text-secondary; + vab-color-info-light: mix($base-color-white, + $base-color-text-secondary, + 80%); + vab-color-info-lighter: mix($base-color-white, + $base-color-text-secondary, + 90%); + vab-border-radius-base: 5px; + vab-color-transition: $base-color-transition; + vab-left-menu-width: $base-left-menu-width; +} \ No newline at end of file diff --git a/front-end/library/styles/variables/vab-purple-variables.module.scss b/front-end/library/styles/variables/vab-purple-variables.module.scss new file mode 100644 index 0000000..c368464 --- /dev/null +++ b/front-end/library/styles/variables/vab-purple-variables.module.scss @@ -0,0 +1,43 @@ +$base-color-primary: #6954f0; +$base-color-success: #13ce66; +$base-color-warning: #ffba00; +$base-color-danger: #ff4d4f; +$base-color-error: #ff4d4f; +$base-color-transition: #1890ff; + +:export { + vab-color-grey: $base-color-grey; + vab-color-black: $base-color-black; + vab-color-primary: $base-color-primary; + vab-color-primary-light-1: mix($base-color-white, $base-color-primary, 10%); + vab-color-primary-light-2: mix($base-color-white, $base-color-primary, 20%); + vab-color-primary-light-3: mix($base-color-white, $base-color-primary, 30%); + vab-color-primary-light-4: mix($base-color-white, $base-color-primary, 40%); + vab-color-primary-light-5: mix($base-color-white, $base-color-primary, 50%); + vab-color-primary-light-6: mix($base-color-white, $base-color-primary, 60%); + vab-color-primary-light-7: mix($base-color-white, $base-color-primary, 70%); + vab-color-primary-light-8: mix($base-color-white, $base-color-primary, 80%); + vab-color-primary-light-9: mix($base-color-white, $base-color-primary, 90%); + vab-color-success: $base-color-success; + vab-color-success-light: mix($base-color-white, $base-color-success, 80%); + vab-color-success-lighter: mix($base-color-white, $base-color-success, 90%); + vab-color-warning: $base-color-warning; + vab-color-warning-light: mix($base-color-white, $base-color-warning, 80%); + vab-color-warning-lighter: mix($base-color-white, $base-color-warning, 90%); + vab-color-danger: $base-color-danger; + vab-color-danger-light: mix($base-color-white, $base-color-danger, 80%); + vab-color-danger-lighter: mix($base-color-white, $base-color-danger, 90%); + vab-color-error: $base-color-error; + vab-color-error-light: mix($base-color-white, $base-color-error, 80%); + vab-color-error-lighter: mix($base-color-white, $base-color-error, 90%); + vab-color-info: $base-color-text-secondary; + vab-color-info-light: mix($base-color-white, + $base-color-text-secondary, + 80%); + vab-color-info-lighter: mix($base-color-white, + $base-color-text-secondary, + 90%); + vab-border-radius-base: 5px; + vab-color-transition: $base-color-transition; + vab-left-menu-width: $base-left-menu-width; +} \ No newline at end of file diff --git a/front-end/library/styles/variables/vab-red-variables.module.scss b/front-end/library/styles/variables/vab-red-variables.module.scss new file mode 100644 index 0000000..09bcdd5 --- /dev/null +++ b/front-end/library/styles/variables/vab-red-variables.module.scss @@ -0,0 +1,43 @@ +$base-color-primary: #f34d37; +$base-color-success: #13ce66; +$base-color-warning: #ffba00; +$base-color-danger: #ff4d4f; +$base-color-error: #ff4d4f; +$base-color-transition: #ffa194; + +:export { + vab-color-grey: $base-color-grey; + vab-color-black: $base-color-black; + vab-color-primary: $base-color-primary; + vab-color-primary-light-1: mix($base-color-white, $base-color-primary, 10%); + vab-color-primary-light-2: mix($base-color-white, $base-color-primary, 20%); + vab-color-primary-light-3: mix($base-color-white, $base-color-primary, 30%); + vab-color-primary-light-4: mix($base-color-white, $base-color-primary, 40%); + vab-color-primary-light-5: mix($base-color-white, $base-color-primary, 50%); + vab-color-primary-light-6: mix($base-color-white, $base-color-primary, 60%); + vab-color-primary-light-7: mix($base-color-white, $base-color-primary, 70%); + vab-color-primary-light-8: mix($base-color-white, $base-color-primary, 80%); + vab-color-primary-light-9: mix($base-color-white, $base-color-primary, 90%); + vab-color-success: $base-color-success; + vab-color-success-light: mix($base-color-white, $base-color-success, 80%); + vab-color-success-lighter: mix($base-color-white, $base-color-success, 90%); + vab-color-warning: $base-color-warning; + vab-color-warning-light: mix($base-color-white, $base-color-warning, 80%); + vab-color-warning-lighter: mix($base-color-white, $base-color-warning, 90%); + vab-color-danger: $base-color-danger; + vab-color-danger-light: mix($base-color-white, $base-color-danger, 80%); + vab-color-danger-lighter: mix($base-color-white, $base-color-danger, 90%); + vab-color-error: $base-color-error; + vab-color-error-light: mix($base-color-white, $base-color-error, 80%); + vab-color-error-lighter: mix($base-color-white, $base-color-error, 90%); + vab-color-info: $base-color-text-secondary; + vab-color-info-light: mix($base-color-white, + $base-color-text-secondary, + 80%); + vab-color-info-lighter: mix($base-color-white, + $base-color-text-secondary, + 90%); + vab-border-radius-base: 5px; + vab-color-transition: $base-color-transition; + vab-left-menu-width: $base-left-menu-width; +} \ No newline at end of file diff --git a/front-end/library/styles/variables/variables.module.scss b/front-end/library/styles/variables/variables.module.scss new file mode 100644 index 0000000..29d1ed3 --- /dev/null +++ b/front-end/library/styles/variables/variables.module.scss @@ -0,0 +1,109 @@ +/** + * @description 全局主题变量配置 + */ +//颜色配置 +$base-color-white: #ffffff; +$base-color-black: #515a6e; +$base-color-primary: #1890ff; +$base-color-success: #13ce66; +$base-color-warning: #ffba00; +$base-color-danger: #ff6700; +$base-color-error: #ff4d4f; +$base-color-grey: rgba(0, 0, 0, 0.65); +$base-color-background: #f6f8f9; + +$base-color-text-primary: #303133; +$base-color-text-regular: #606266; +$base-color-text-secondary: #909399; +$base-color-text-placeholder: #c0c4cc; +$base-border-color-base: #dcdfe6; +$base-border-color-light: #e4e7ed; +$base-border-color-lighter: #ebeef5; +$base-border-color-extra-light: #f2f6fc; +$base-background-color-base: #f5f7fa; + +//默认层级 +$base-z-index: 1999; +//分栏最左侧菜单背景色 +$base-column-first-menu-background: #282c34; +//分栏菜单背景色 +$base-column-second-menu-background: #fff; +//分栏菜单选中背景色 +$base-column-second-menu-active: mix($base-color-white, + $base-color-primary, + 10%); +//横向、纵向菜单背景色 +$base-menu-background: #282c34; +//菜单文字颜色 +$base-menu-color: hsla(0, 0%, 100%, 0.95); +//菜单选中文字颜色 +$base-menu-color-active: hsla(0, 0%, 100%, 0.95); +//菜单选中背景色 +$base-menu-active: $base-color-primary; +//标题颜色 +$base-title-color: #fff; +//字体大小配置 +$base-font-size-small: 12px; +$base-font-size-default: 14px; +$base-font-size-big: 16px; +$base-font-size-bigger: 18px; +$base-font-size-max: 22px; +//最大宽度 +$base-main-width: 1279px; +//圆角 +$base-border-radius: 5px; +//边框颜色 +$base-border-color: #dcdfe6; +//输入框高度 +$base-input-height: 32px; +//默认margin +$base-margin: 20px; +//默认padding +$base-padding: 20px; +//默认阴影 +$base-box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); +//横向top-bar、logo、一级菜单的高度 +$base-header-height: 60px; +//纵向、综合、分栏logo的高度 +$base-logo-height: 60px; +//顶部nav-bar的高度 +$base-nav-height: 60px; +//顶部标签页tabs-bar的高度 +$base-tabs-height: 50px; +//顶部标签页tabs-bar中每一个item的高度 +$base-tag-item-height: 34px; +//菜单li标签的高度 +$base-menu-item-height: 50px; +//app-main的高度 +$base-keep-alive-height: calc(100vh - #{$base-nav-height} - #{$base-tabs-height} - #{$base-padding} * 2 - 55px); +//纵向左侧导航未折叠的宽度 +$base-left-menu-width: 266px; +//纵向左侧导航已折叠的宽度 +$base-left-menu-width-min: 64px; +//纵向左侧导航已折叠右侧内容的宽度 +$base-right-content-width-min: calc(100% - #{$base-left-menu-width-min}); +//默认动画 +$base-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), + border 0s, + color 0.1s, + font-size 0s; + +:export { + // 菜单文字颜色变量导出 + menu-color: $base-menu-color; + // 菜单选中文字颜色变量导出 + menu-color-active: $base-menu-color-active; + // 菜单背景色变量导出 + menu-background: $base-menu-background; + // 分栏菜单背景色变量导出 + column-second-menu-background: $base-column-second-menu-background; + // 导出圆角 + vab-border-radius: $base-border-radius; +} + +:root { + --el-container-height: #{$base-keep-alive-height}; + --el-margin: #{$base-margin}; + --el-padding: #{$base-padding}; + --el-border-radius-base: #{$base-border-radius} !important; +} \ No newline at end of file diff --git a/front-end/mail.txt b/front-end/mail.txt new file mode 100644 index 0000000..dce9ce7 --- /dev/null +++ b/front-end/mail.txt @@ -0,0 +1,101 @@ +绑定主体: +是否需要电子发票(必填):需要 + +公司名称(用于公司必填):宝来威科技(惠州)有限公司 + +税号(用于公司必填,请勿保留空格):91441381MAD64QAX2H + +发票接收邮箱(必填):momo@inhaos.com + +采购人姓名(必填):文祚平 + +联系电话(必填,需能接收验证码):13509214696 + +金额:799 + +付款方式(必填):支付宝 + +码云用户名:inhaosMomoWen + +github用户名(请使用不涉及离职的github,不支持免费换绑,必填):MomoWen + +github登录邮箱(不支持换绑,必填):momo@inhaos.com + +微信号(不支持换绑,必填):Momo13509214696 + +群要绑定的qq(用于问题讨论,不支持免费换绑,必填):450481891 + + +买方承诺:以上信息真实有效,如提供虚假内容或恶意将付费源码分享至第三方,著作权人有权收回框架更新权限。 + + + + + +源码仓库邀请链接(需三日内同意): + + + +https://github.com/zxwk2024/admin-plus/invitations + + +开发进度Follow(点击Follow实时跟进开发进程) + +https://github.com/zxwk1998 + + +文档: +群讨论时间:工作日10点-16点,pro版本plus版本以及开源版文档,文档地址或密码如果变更请查看群公告: +https://vuejs-core.cn/admin-book-2025 +文档密码:Vabdbsqj +浏览器拷贝源码密码是vabp + + +新建.env.local文件拷贝以下内容(详见文档): +VUE_APP_SECRET_KEY=HKK0LuN3MYbYixeDXgXbfVMOMeeZ+iHc/qLcFbNeOr24uPX3FvqFLAWlZ/waoWqHw7qq6E0UNvaPEWC7U5YPQk9XUERUMI8CK+yu50CiFqBkW0uCanJzXDYaBEdDqwkgSWxLNO0j36+kGiBMyfwMU/gLhs1i0JYvtw4Z4NYV3YLvrHyYdQmSglsQp/Zi4HkkdIVwFNAwrXbSKuldaUZHt/D0rQ+Kj7wrsnOFxXHTPKIMvPDTaBXzne3J/ASOTV3J6XiG+1RkUxNbT70A17xiBeKoNk+dFM2LPEbHQJmZvIBse0fohlRM4xo8z32vsDSTJtHOZ3WW7oIMmfzF/gXARQ== + +注意事项: + +关于部分mac电脑上显示权限过高 .开头的文件无法正常显示 导致无法运行项目的问题,请参照一些方案 +第一种方法是在finder中按下command+shift+.键。 +第二种方法是在命令行输入如下命令 +defaults write com.apple.Finder AppleShowAllFiles YES + +pro和plus无项目限制(但仅限用于自己公司和团队,公司使用请提供公司名称,请勿用于任何形式的开源项目,外包项目不可提供框架源码,打包后的可以,如果必须提供需买方购买pro)VIP群内请文明交流 ,共同进步,由于加入到了团队成员且框架更新频率较高,如果您不想接收到邮件推送可以在github右上角watch选择not watching,admin pro、admin plus、shop vite已申请软著,不要将源码放到github或码云public仓库,一经发现取消使用资格,恶意分享造成损失或源码泄露需承担相应的法律责任,一定注意开发时请先阅读文档。 + +记得看下文档开发工具配置 常见问题 打包注意事项 图片压缩插件需要cnpm安装 文档有写 我们也承接定制外包服务 如后期有需要欢迎联系我们 + +购买地址: +https://vuejs-core.cn/authorization/ + +建议: + +- 1.使用前请一定先阅读 vip 群文档,一般在群公告前 5 条 +- 2.如果您经过翻阅文档、百度后努力尝试仍无法解决问题,可通过 vip 群寻求帮助,讨论时间法定工作日 14 点-16 点 +- 3.对于热心回答群内其他成员问题的用户,所提建议将优先被采纳,并可获得部分内测版本体验资格 + +约定: +详见购买页底部或右侧,著作权人享有最终解释权: +https://vuejs-core.cn/authorization/shop-vite.html +https://vuejs-core.cn/authorization/index.html + +关于举报: +1、盗版举报:请发送举报材料至1204505056@qq.com,一经查实,官司所得收入20%归举报人所有,80%归律师事务所所有。 +2、管理员、群内成员不正当言论举报:发送举报材料至1204505056@qq.com即可 + +写在最后: +祝您工作顺利,生活愉快 + + + + + + + + + + + + + + diff --git a/front-end/mock/controller/area.js b/front-end/mock/controller/area.js new file mode 100644 index 0000000..d816fc0 --- /dev/null +++ b/front-end/mock/controller/area.js @@ -0,0 +1,13693 @@ +const list = [ + { + name: '北京市', + code: '110000', + region: 'north', + provinceLevelCity: true, + children: [ + { + name: '市辖区', + code: '110100', + children: [ + { + name: '东城区', + code: '110101', + }, + { + name: '西城区', + code: '110102', + }, + { + name: '朝阳区', + code: '110105', + }, + { + name: '丰台区', + code: '110106', + }, + { + name: '石景山区', + code: '110107', + }, + { + name: '海淀区', + code: '110108', + }, + { + name: '门头沟区', + code: '110109', + }, + { + name: '房山区', + code: '110111', + }, + { + name: '通州区', + code: '110112', + }, + { + name: '顺义区', + code: '110113', + }, + { + name: '昌平区', + code: '110114', + }, + { + name: '大兴区', + code: '110115', + }, + { + name: '怀柔区', + code: '110116', + }, + { + name: '平谷区', + code: '110117', + }, + { + name: '密云区', + code: '110118', + }, + { + name: '延庆区', + code: '110119', + }, + ], + }, + ], + }, + { + name: '天津市', + code: '120000', + region: 'north', + provinceLevelCity: true, + children: [ + { + name: '市辖区', + code: '120100', + children: [ + { + name: '和平区', + code: '120101', + }, + { + name: '河东区', + code: '120102', + }, + { + name: '河西区', + code: '120103', + }, + { + name: '南开区', + code: '120104', + }, + { + name: '河北区', + code: '120105', + }, + { + name: '红桥区', + code: '120106', + }, + { + name: '东丽区', + code: '120110', + }, + { + name: '西青区', + code: '120111', + }, + { + name: '津南区', + code: '120112', + }, + { + name: '北辰区', + code: '120113', + }, + { + name: '武清区', + code: '120114', + }, + { + name: '宝坻区', + code: '120115', + }, + { + name: '滨海新区', + code: '120116', + }, + { + name: '宁河区', + code: '120117', + }, + { + name: '静海区', + code: '120118', + }, + { + name: '蓟州区', + code: '120119', + }, + ], + }, + ], + }, + { + name: '河北省', + code: '130000', + region: 'north', + children: [ + { + name: '石家庄市', + code: '130100', + children: [ + { + name: '长安区', + code: '130102', + }, + { + name: '桥西区', + code: '130104', + }, + { + name: '新华区', + code: '130105', + }, + { + name: '井陉矿区', + code: '130107', + }, + { + name: '裕华区', + code: '130108', + }, + { + name: '藁城区', + code: '130109', + }, + { + name: '鹿泉区', + code: '130110', + }, + { + name: '栾城区', + code: '130111', + }, + { + name: '井陉县', + code: '130121', + }, + { + name: '正定县', + code: '130123', + }, + { + name: '行唐县', + code: '130125', + }, + { + name: '灵寿县', + code: '130126', + }, + { + name: '高邑县', + code: '130127', + }, + { + name: '深泽县', + code: '130128', + }, + { + name: '赞皇县', + code: '130129', + }, + { + name: '无极县', + code: '130130', + }, + { + name: '平山县', + code: '130131', + }, + { + name: '元氏县', + code: '130132', + }, + { + name: '赵县', + code: '130133', + }, + { + name: '辛集市', + code: '130181', + }, + { + name: '晋州市', + code: '130183', + }, + { + name: '新乐市', + code: '130184', + }, + ], + }, + { + name: '唐山市', + code: '130200', + children: [ + { + name: '路南区', + code: '130202', + }, + { + name: '路北区', + code: '130203', + }, + { + name: '古冶区', + code: '130204', + }, + { + name: '开平区', + code: '130205', + }, + { + name: '丰南区', + code: '130207', + }, + { + name: '丰润区', + code: '130208', + }, + { + name: '曹妃甸区', + code: '130209', + }, + { + name: '滦南县', + code: '130224', + }, + { + name: '乐亭县', + code: '130225', + }, + { + name: '迁西县', + code: '130227', + }, + { + name: '玉田县', + code: '130229', + }, + { + name: '遵化市', + code: '130281', + }, + { + name: '迁安市', + code: '130283', + }, + { + name: '滦州市', + code: '130284', + }, + ], + }, + { + name: '秦皇岛市', + code: '130300', + children: [ + { + name: '海港区', + code: '130302', + }, + { + name: '山海关区', + code: '130303', + }, + { + name: '北戴河区', + code: '130304', + }, + { + name: '抚宁区', + code: '130306', + }, + { + name: '青龙满族自治县', + code: '130321', + }, + { + name: '昌黎县', + code: '130322', + }, + { + name: '卢龙县', + code: '130324', + }, + ], + }, + { + name: '邯郸市', + code: '130400', + children: [ + { + name: '邯山区', + code: '130402', + }, + { + name: '丛台区', + code: '130403', + }, + { + name: '复兴区', + code: '130404', + }, + { + name: '峰峰矿区', + code: '130406', + }, + { + name: '肥乡区', + code: '130407', + }, + { + name: '永年区', + code: '130408', + }, + { + name: '临漳县', + code: '130423', + }, + { + name: '成安县', + code: '130424', + }, + { + name: '大名县', + code: '130425', + }, + { + name: '涉县', + code: '130426', + }, + { + name: '磁县', + code: '130427', + }, + { + name: '邱县', + code: '130430', + }, + { + name: '鸡泽县', + code: '130431', + }, + { + name: '广平县', + code: '130432', + }, + { + name: '馆陶县', + code: '130433', + }, + { + name: '魏县', + code: '130434', + }, + { + name: '曲周县', + code: '130435', + }, + { + name: '武安市', + code: '130481', + }, + ], + }, + { + name: '邢台市', + code: '130500', + children: [ + { + name: '桥东区', + code: '130502', + }, + { + name: '桥西区', + code: '130503', + }, + { + name: '邢台县', + code: '130521', + }, + { + name: '临城县', + code: '130522', + }, + { + name: '内丘县', + code: '130523', + }, + { + name: '柏乡县', + code: '130524', + }, + { + name: '隆尧县', + code: '130525', + }, + { + name: '任县', + code: '130526', + }, + { + name: '南和县', + code: '130527', + }, + { + name: '宁晋县', + code: '130528', + }, + { + name: '巨鹿县', + code: '130529', + }, + { + name: '新河县', + code: '130530', + }, + { + name: '广宗县', + code: '130531', + }, + { + name: '平乡县', + code: '130532', + }, + { + name: '威县', + code: '130533', + }, + { + name: '清河县', + code: '130534', + }, + { + name: '临西县', + code: '130535', + }, + { + name: '南宫市', + code: '130581', + }, + { + name: '沙河市', + code: '130582', + }, + ], + }, + { + name: '保定市', + code: '130600', + children: [ + { + name: '竞秀区', + code: '130602', + }, + { + name: '莲池区', + code: '130606', + }, + { + name: '满城区', + code: '130607', + }, + { + name: '清苑区', + code: '130608', + }, + { + name: '徐水区', + code: '130609', + }, + { + name: '涞水县', + code: '130623', + }, + { + name: '阜平县', + code: '130624', + }, + { + name: '定兴县', + code: '130626', + }, + { + name: '唐县', + code: '130627', + }, + { + name: '高阳县', + code: '130628', + }, + { + name: '容城县', + code: '130629', + }, + { + name: '涞源县', + code: '130630', + }, + { + name: '望都县', + code: '130631', + }, + { + name: '安新县', + code: '130632', + }, + { + name: '易县', + code: '130633', + }, + { + name: '曲阳县', + code: '130634', + }, + { + name: '蠡县', + code: '130635', + }, + { + name: '顺平县', + code: '130636', + }, + { + name: '博野县', + code: '130637', + }, + { + name: '雄县', + code: '130638', + }, + { + name: '涿州市', + code: '130681', + }, + { + name: '定州市', + code: '130682', + }, + { + name: '安国市', + code: '130683', + }, + { + name: '高碑店市', + code: '130684', + }, + ], + }, + { + name: '张家口市', + code: '130700', + children: [ + { + name: '桥东区', + code: '130702', + }, + { + name: '桥西区', + code: '130703', + }, + { + name: '宣化区', + code: '130705', + }, + { + name: '下花园区', + code: '130706', + }, + { + name: '万全区', + code: '130708', + }, + { + name: '崇礼区', + code: '130709', + }, + { + name: '张北县', + code: '130722', + }, + { + name: '康保县', + code: '130723', + }, + { + name: '沽源县', + code: '130724', + }, + { + name: '尚义县', + code: '130725', + }, + { + name: '蔚县', + code: '130726', + }, + { + name: '阳原县', + code: '130727', + }, + { + name: '怀安县', + code: '130728', + }, + { + name: '怀来县', + code: '130730', + }, + { + name: '涿鹿县', + code: '130731', + }, + { + name: '赤城县', + code: '130732', + }, + ], + }, + { + name: '承德市', + code: '130800', + children: [ + { + name: '双桥区', + code: '130802', + }, + { + name: '双滦区', + code: '130803', + }, + { + name: '鹰手营子矿区', + code: '130804', + }, + { + name: '承德县', + code: '130821', + }, + { + name: '兴隆县', + code: '130822', + }, + { + name: '滦平县', + code: '130824', + }, + { + name: '隆化县', + code: '130825', + }, + { + name: '丰宁满族自治县', + code: '130826', + }, + { + name: '宽城满族自治县', + code: '130827', + }, + { + name: '围场满族蒙古族自治县', + code: '130828', + }, + { + name: '平泉市', + code: '130881', + }, + ], + }, + { + name: '沧州市', + code: '130900', + children: [ + { + name: '新华区', + code: '130902', + }, + { + name: '运河区', + code: '130903', + }, + { + name: '沧县', + code: '130921', + }, + { + name: '青县', + code: '130922', + }, + { + name: '东光县', + code: '130923', + }, + { + name: '海兴县', + code: '130924', + }, + { + name: '盐山县', + code: '130925', + }, + { + name: '肃宁县', + code: '130926', + }, + { + name: '南皮县', + code: '130927', + }, + { + name: '吴桥县', + code: '130928', + }, + { + name: '献县', + code: '130929', + }, + { + name: '孟村回族自治县', + code: '130930', + }, + { + name: '泊头市', + code: '130981', + }, + { + name: '任丘市', + code: '130982', + }, + { + name: '黄骅市', + code: '130983', + }, + { + name: '河间市', + code: '130984', + }, + ], + }, + { + name: '廊坊市', + code: '131000', + children: [ + { + name: '安次区', + code: '131002', + }, + { + name: '广阳区', + code: '131003', + }, + { + name: '固安县', + code: '131022', + }, + { + name: '永清县', + code: '131023', + }, + { + name: '香河县', + code: '131024', + }, + { + name: '大城县', + code: '131025', + }, + { + name: '文安县', + code: '131026', + }, + { + name: '大厂回族自治县', + code: '131028', + }, + { + name: '霸州市', + code: '131081', + }, + { + name: '三河市', + code: '131082', + }, + ], + }, + { + name: '衡水市', + code: '131100', + children: [ + { + name: '桃城区', + code: '131102', + }, + { + name: '冀州区', + code: '131103', + }, + { + name: '枣强县', + code: '131121', + }, + { + name: '武邑县', + code: '131122', + }, + { + name: '武强县', + code: '131123', + }, + { + name: '饶阳县', + code: '131124', + }, + { + name: '安平县', + code: '131125', + }, + { + name: '故城县', + code: '131126', + }, + { + name: '景县', + code: '131127', + }, + { + name: '阜城县', + code: '131128', + }, + { + name: '深州市', + code: '131182', + }, + ], + }, + ], + }, + { + name: '山西省', + code: '140000', + region: 'north', + children: [ + { + name: '太原市', + code: '140100', + children: [ + { + name: '小店区', + code: '140105', + }, + { + name: '迎泽区', + code: '140106', + }, + { + name: '杏花岭区', + code: '140107', + }, + { + name: '尖草坪区', + code: '140108', + }, + { + name: '万柏林区', + code: '140109', + }, + { + name: '晋源区', + code: '140110', + }, + { + name: '清徐县', + code: '140121', + }, + { + name: '阳曲县', + code: '140122', + }, + { + name: '娄烦县', + code: '140123', + }, + { + name: '古交市', + code: '140181', + }, + ], + }, + { + name: '大同市', + code: '140200', + children: [ + { + name: '新荣区', + code: '140212', + }, + { + name: '平城区', + code: '140213', + }, + { + name: '云冈区', + code: '140214', + }, + { + name: '云州区', + code: '140215', + }, + { + name: '阳高县', + code: '140221', + }, + { + name: '天镇县', + code: '140222', + }, + { + name: '广灵县', + code: '140223', + }, + { + name: '灵丘县', + code: '140224', + }, + { + name: '浑源县', + code: '140225', + }, + { + name: '左云县', + code: '140226', + }, + ], + }, + { + name: '阳泉市', + code: '140300', + children: [ + { + name: '城区', + code: '140302', + }, + { + name: '矿区', + code: '140303', + }, + { + name: '郊区', + code: '140311', + }, + { + name: '平定县', + code: '140321', + }, + { + name: '盂县', + code: '140322', + }, + ], + }, + { + name: '长治市', + code: '140400', + children: [ + { + name: '潞州区', + code: '140403', + }, + { + name: '上党区', + code: '140404', + }, + { + name: '屯留区', + code: '140405', + }, + { + name: '潞城区', + code: '140406', + }, + { + name: '襄垣县', + code: '140423', + }, + { + name: '平顺县', + code: '140425', + }, + { + name: '黎城县', + code: '140426', + }, + { + name: '壶关县', + code: '140427', + }, + { + name: '长子县', + code: '140428', + }, + { + name: '武乡县', + code: '140429', + }, + { + name: '沁县', + code: '140430', + }, + { + name: '沁源县', + code: '140431', + }, + ], + }, + { + name: '晋城市', + code: '140500', + children: [ + { + name: '城区', + code: '140502', + }, + { + name: '沁水县', + code: '140521', + }, + { + name: '阳城县', + code: '140522', + }, + { + name: '陵川县', + code: '140524', + }, + { + name: '泽州县', + code: '140525', + }, + { + name: '高平市', + code: '140581', + }, + ], + }, + { + name: '朔州市', + code: '140600', + children: [ + { + name: '朔城区', + code: '140602', + }, + { + name: '平鲁区', + code: '140603', + }, + { + name: '山阴县', + code: '140621', + }, + { + name: '应县', + code: '140622', + }, + { + name: '右玉县', + code: '140623', + }, + { + name: '怀仁市', + code: '140681', + }, + ], + }, + { + name: '晋中市', + code: '140700', + children: [ + { + name: '榆次区', + code: '140702', + }, + { + name: '榆社县', + code: '140721', + }, + { + name: '左权县', + code: '140722', + }, + { + name: '和顺县', + code: '140723', + }, + { + name: '昔阳县', + code: '140724', + }, + { + name: '寿阳县', + code: '140725', + }, + { + name: '太谷县', + code: '140726', + }, + { + name: '祁县', + code: '140727', + }, + { + name: '平遥县', + code: '140728', + }, + { + name: '灵石县', + code: '140729', + }, + { + name: '介休市', + code: '140781', + }, + ], + }, + { + name: '运城市', + code: '140800', + children: [ + { + name: '盐湖区', + code: '140802', + }, + { + name: '临猗县', + code: '140821', + }, + { + name: '万荣县', + code: '140822', + }, + { + name: '闻喜县', + code: '140823', + }, + { + name: '稷山县', + code: '140824', + }, + { + name: '新绛县', + code: '140825', + }, + { + name: '绛县', + code: '140826', + }, + { + name: '垣曲县', + code: '140827', + }, + { + name: '夏县', + code: '140828', + }, + { + name: '平陆县', + code: '140829', + }, + { + name: '芮城县', + code: '140830', + }, + { + name: '永济市', + code: '140881', + }, + { + name: '河津市', + code: '140882', + }, + ], + }, + { + name: '忻州市', + code: '140900', + children: [ + { + name: '忻府区', + code: '140902', + }, + { + name: '定襄县', + code: '140921', + }, + { + name: '五台县', + code: '140922', + }, + { + name: '代县', + code: '140923', + }, + { + name: '繁峙县', + code: '140924', + }, + { + name: '宁武县', + code: '140925', + }, + { + name: '静乐县', + code: '140926', + }, + { + name: '神池县', + code: '140927', + }, + { + name: '五寨县', + code: '140928', + }, + { + name: '岢岚县', + code: '140929', + }, + { + name: '河曲县', + code: '140930', + }, + { + name: '保德县', + code: '140931', + }, + { + name: '偏关县', + code: '140932', + }, + { + name: '原平市', + code: '140981', + }, + ], + }, + { + name: '临汾市', + code: '141000', + children: [ + { + name: '尧都区', + code: '141002', + }, + { + name: '曲沃县', + code: '141021', + }, + { + name: '翼城县', + code: '141022', + }, + { + name: '襄汾县', + code: '141023', + }, + { + name: '洪洞县', + code: '141024', + }, + { + name: '古县', + code: '141025', + }, + { + name: '安泽县', + code: '141026', + }, + { + name: '浮山县', + code: '141027', + }, + { + name: '吉县', + code: '141028', + }, + { + name: '乡宁县', + code: '141029', + }, + { + name: '大宁县', + code: '141030', + }, + { + name: '隰县', + code: '141031', + }, + { + name: '永和县', + code: '141032', + }, + { + name: '蒲县', + code: '141033', + }, + { + name: '汾西县', + code: '141034', + }, + { + name: '侯马市', + code: '141081', + }, + { + name: '霍州市', + code: '141082', + }, + ], + }, + { + name: '吕梁市', + code: '141100', + children: [ + { + name: '离石区', + code: '141102', + }, + { + name: '文水县', + code: '141121', + }, + { + name: '交城县', + code: '141122', + }, + { + name: '兴县', + code: '141123', + }, + { + name: '临县', + code: '141124', + }, + { + name: '柳林县', + code: '141125', + }, + { + name: '石楼县', + code: '141126', + }, + { + name: '岚县', + code: '141127', + }, + { + name: '方山县', + code: '141128', + }, + { + name: '中阳县', + code: '141129', + }, + { + name: '交口县', + code: '141130', + }, + { + name: '孝义市', + code: '141181', + }, + { + name: '汾阳市', + code: '141182', + }, + ], + }, + ], + }, + { + name: '内蒙古自治区', + code: '150000', + region: 'north', + autonomousRegion: true, + children: [ + { + name: '呼和浩特市', + code: '150100', + children: [ + { + name: '新城区', + code: '150102', + }, + { + name: '回民区', + code: '150103', + }, + { + name: '玉泉区', + code: '150104', + }, + { + name: '赛罕区', + code: '150105', + }, + { + name: '土默特左旗', + code: '150121', + }, + { + name: '托克托县', + code: '150122', + }, + { + name: '和林格尔县', + code: '150123', + }, + { + name: '清水河县', + code: '150124', + }, + { + name: '武川县', + code: '150125', + }, + ], + }, + { + name: '包头市', + code: '150200', + children: [ + { + name: '东河区', + code: '150202', + }, + { + name: '昆都仑区', + code: '150203', + }, + { + name: '青山区', + code: '150204', + }, + { + name: '石拐区', + code: '150205', + }, + { + name: '白云鄂博矿区', + code: '150206', + }, + { + name: '九原区', + code: '150207', + }, + { + name: '土默特右旗', + code: '150221', + }, + { + name: '固阳县', + code: '150222', + }, + { + name: '达尔罕茂明安联合旗', + code: '150223', + }, + ], + }, + { + name: '乌海市', + code: '150300', + children: [ + { + name: '海勃湾区', + code: '150302', + }, + { + name: '海南区', + code: '150303', + }, + { + name: '乌达区', + code: '150304', + }, + ], + }, + { + name: '赤峰市', + code: '150400', + children: [ + { + name: '红山区', + code: '150402', + }, + { + name: '元宝山区', + code: '150403', + }, + { + name: '松山区', + code: '150404', + }, + { + name: '阿鲁科尔沁旗', + code: '150421', + }, + { + name: '巴林左旗', + code: '150422', + }, + { + name: '巴林右旗', + code: '150423', + }, + { + name: '林西县', + code: '150424', + }, + { + name: '克什克腾旗', + code: '150425', + }, + { + name: '翁牛特旗', + code: '150426', + }, + { + name: '喀喇沁旗', + code: '150428', + }, + { + name: '宁城县', + code: '150429', + }, + { + name: '敖汉旗', + code: '150430', + }, + ], + }, + { + name: '通辽市', + code: '150500', + children: [ + { + name: '科尔沁区', + code: '150502', + }, + { + name: '科尔沁左翼中旗', + code: '150521', + }, + { + name: '科尔沁左翼后旗', + code: '150522', + }, + { + name: '开鲁县', + code: '150523', + }, + { + name: '库伦旗', + code: '150524', + }, + { + name: '奈曼旗', + code: '150525', + }, + { + name: '扎鲁特旗', + code: '150526', + }, + { + name: '霍林郭勒市', + code: '150581', + }, + ], + }, + { + name: '鄂尔多斯市', + code: '150600', + children: [ + { + name: '东胜区', + code: '150602', + }, + { + name: '康巴什区', + code: '150603', + }, + { + name: '达拉特旗', + code: '150621', + }, + { + name: '准格尔旗', + code: '150622', + }, + { + name: '鄂托克前旗', + code: '150623', + }, + { + name: '鄂托克旗', + code: '150624', + }, + { + name: '杭锦旗', + code: '150625', + }, + { + name: '乌审旗', + code: '150626', + }, + { + name: '伊金霍洛旗', + code: '150627', + }, + ], + }, + { + name: '呼伦贝尔市', + code: '150700', + children: [ + { + name: '海拉尔区', + code: '150702', + }, + { + name: '扎赉诺尔区', + code: '150703', + }, + { + name: '阿荣旗', + code: '150721', + }, + { + name: '莫力达瓦达斡尔族自治旗', + code: '150722', + }, + { + name: '鄂伦春自治旗', + code: '150723', + }, + { + name: '鄂温克族自治旗', + code: '150724', + }, + { + name: '陈巴尔虎旗', + code: '150725', + }, + { + name: '新巴尔虎左旗', + code: '150726', + }, + { + name: '新巴尔虎右旗', + code: '150727', + }, + { + name: '满洲里市', + code: '150781', + }, + { + name: '牙克石市', + code: '150782', + }, + { + name: '扎兰屯市', + code: '150783', + }, + { + name: '额尔古纳市', + code: '150784', + }, + { + name: '根河市', + code: '150785', + }, + ], + }, + { + name: '巴彦淖尔市', + code: '150800', + children: [ + { + name: '临河区', + code: '150802', + }, + { + name: '五原县', + code: '150821', + }, + { + name: '磴口县', + code: '150822', + }, + { + name: '乌拉特前旗', + code: '150823', + }, + { + name: '乌拉特中旗', + code: '150824', + }, + { + name: '乌拉特后旗', + code: '150825', + }, + { + name: '杭锦后旗', + code: '150826', + }, + ], + }, + { + name: '乌兰察布市', + code: '150900', + children: [ + { + name: '集宁区', + code: '150902', + }, + { + name: '卓资县', + code: '150921', + }, + { + name: '化德县', + code: '150922', + }, + { + name: '商都县', + code: '150923', + }, + { + name: '兴和县', + code: '150924', + }, + { + name: '凉城县', + code: '150925', + }, + { + name: '察哈尔右翼前旗', + code: '150926', + }, + { + name: '察哈尔右翼中旗', + code: '150927', + }, + { + name: '察哈尔右翼后旗', + code: '150928', + }, + { + name: '四子王旗', + code: '150929', + }, + { + name: '丰镇市', + code: '150981', + }, + ], + }, + { + name: '兴安盟', + code: '152200', + children: [ + { + name: '乌兰浩特市', + code: '152201', + }, + { + name: '阿尔山市', + code: '152202', + }, + { + name: '科尔沁右翼前旗', + code: '152221', + }, + { + name: '科尔沁右翼中旗', + code: '152222', + }, + { + name: '扎赉特旗', + code: '152223', + }, + { + name: '突泉县', + code: '152224', + }, + ], + }, + { + name: '锡林郭勒盟', + code: '152500', + children: [ + { + name: '二连浩特市', + code: '152501', + }, + { + name: '锡林浩特市', + code: '152502', + }, + { + name: '阿巴嘎旗', + code: '152522', + }, + { + name: '苏尼特左旗', + code: '152523', + }, + { + name: '苏尼特右旗', + code: '152524', + }, + { + name: '东乌珠穆沁旗', + code: '152525', + }, + { + name: '西乌珠穆沁旗', + code: '152526', + }, + { + name: '太仆寺旗', + code: '152527', + }, + { + name: '镶黄旗', + code: '152528', + }, + { + name: '正镶白旗', + code: '152529', + }, + { + name: '正蓝旗', + code: '152530', + }, + { + name: '多伦县', + code: '152531', + }, + ], + }, + { + name: '阿拉善盟', + code: '152900', + children: [ + { + name: '阿拉善左旗', + code: '152921', + }, + { + name: '阿拉善右旗', + code: '152922', + }, + { + name: '额济纳旗', + code: '152923', + }, + ], + }, + ], + }, + { + name: '辽宁省', + code: '210000', + region: 'northeast', + children: [ + { + name: '沈阳市', + code: '210100', + children: [ + { + name: '和平区', + code: '210102', + }, + { + name: '沈河区', + code: '210103', + }, + { + name: '大东区', + code: '210104', + }, + { + name: '皇姑区', + code: '210105', + }, + { + name: '铁西区', + code: '210106', + }, + { + name: '苏家屯区', + code: '210111', + }, + { + name: '浑南区', + code: '210112', + }, + { + name: '沈北新区', + code: '210113', + }, + { + name: '于洪区', + code: '210114', + }, + { + name: '辽中区', + code: '210115', + }, + { + name: '康平县', + code: '210123', + }, + { + name: '法库县', + code: '210124', + }, + { + name: '新民市', + code: '210181', + }, + ], + }, + { + name: '大连市', + code: '210200', + children: [ + { + name: '中山区', + code: '210202', + }, + { + name: '西岗区', + code: '210203', + }, + { + name: '沙河口区', + code: '210204', + }, + { + name: '甘井子区', + code: '210211', + }, + { + name: '旅顺口区', + code: '210212', + }, + { + name: '金州区', + code: '210213', + }, + { + name: '普兰店区', + code: '210214', + }, + { + name: '长海县', + code: '210224', + }, + { + name: '瓦房店市', + code: '210281', + }, + { + name: '庄河市', + code: '210283', + }, + ], + }, + { + name: '鞍山市', + code: '210300', + children: [ + { + name: '铁东区', + code: '210302', + }, + { + name: '铁西区', + code: '210303', + }, + { + name: '立山区', + code: '210304', + }, + { + name: '千山区', + code: '210311', + }, + { + name: '台安县', + code: '210321', + }, + { + name: '岫岩满族自治县', + code: '210323', + }, + { + name: '海城市', + code: '210381', + }, + ], + }, + { + name: '抚顺市', + code: '210400', + children: [ + { + name: '新抚区', + code: '210402', + }, + { + name: '东洲区', + code: '210403', + }, + { + name: '望花区', + code: '210404', + }, + { + name: '顺城区', + code: '210411', + }, + { + name: '抚顺县', + code: '210421', + }, + { + name: '新宾满族自治县', + code: '210422', + }, + { + name: '清原满族自治县', + code: '210423', + }, + ], + }, + { + name: '本溪市', + code: '210500', + children: [ + { + name: '平山区', + code: '210502', + }, + { + name: '溪湖区', + code: '210503', + }, + { + name: '明山区', + code: '210504', + }, + { + name: '南芬区', + code: '210505', + }, + { + name: '本溪满族自治县', + code: '210521', + }, + { + name: '桓仁满族自治县', + code: '210522', + }, + ], + }, + { + name: '丹东市', + code: '210600', + children: [ + { + name: '元宝区', + code: '210602', + }, + { + name: '振兴区', + code: '210603', + }, + { + name: '振安区', + code: '210604', + }, + { + name: '宽甸满族自治县', + code: '210624', + }, + { + name: '东港市', + code: '210681', + }, + { + name: '凤城市', + code: '210682', + }, + ], + }, + { + name: '锦州市', + code: '210700', + children: [ + { + name: '古塔区', + code: '210702', + }, + { + name: '凌河区', + code: '210703', + }, + { + name: '太和区', + code: '210711', + }, + { + name: '黑山县', + code: '210726', + }, + { + name: '义县', + code: '210727', + }, + { + name: '凌海市', + code: '210781', + }, + { + name: '北镇市', + code: '210782', + }, + ], + }, + { + name: '营口市', + code: '210800', + children: [ + { + name: '站前区', + code: '210802', + }, + { + name: '西市区', + code: '210803', + }, + { + name: '鲅鱼圈区', + code: '210804', + }, + { + name: '老边区', + code: '210811', + }, + { + name: '盖州市', + code: '210881', + }, + { + name: '大石桥市', + code: '210882', + }, + ], + }, + { + name: '阜新市', + code: '210900', + children: [ + { + name: '海州区', + code: '210902', + }, + { + name: '新邱区', + code: '210903', + }, + { + name: '太平区', + code: '210904', + }, + { + name: '清河门区', + code: '210905', + }, + { + name: '细河区', + code: '210911', + }, + { + name: '阜新蒙古族自治县', + code: '210921', + }, + { + name: '彰武县', + code: '210922', + }, + ], + }, + { + name: '辽阳市', + code: '211000', + children: [ + { + name: '白塔区', + code: '211002', + }, + { + name: '文圣区', + code: '211003', + }, + { + name: '宏伟区', + code: '211004', + }, + { + name: '弓长岭区', + code: '211005', + }, + { + name: '太子河区', + code: '211011', + }, + { + name: '辽阳县', + code: '211021', + }, + { + name: '灯塔市', + code: '211081', + }, + ], + }, + { + name: '盘锦市', + code: '211100', + children: [ + { + name: '双台子区', + code: '211102', + }, + { + name: '兴隆台区', + code: '211103', + }, + { + name: '大洼区', + code: '211104', + }, + { + name: '盘山县', + code: '211122', + }, + ], + }, + { + name: '铁岭市', + code: '211200', + children: [ + { + name: '银州区', + code: '211202', + }, + { + name: '清河区', + code: '211204', + }, + { + name: '铁岭县', + code: '211221', + }, + { + name: '西丰县', + code: '211223', + }, + { + name: '昌图县', + code: '211224', + }, + { + name: '调兵山市', + code: '211281', + }, + { + name: '开原市', + code: '211282', + }, + ], + }, + { + name: '朝阳市', + code: '211300', + children: [ + { + name: '双塔区', + code: '211302', + }, + { + name: '龙城区', + code: '211303', + }, + { + name: '朝阳县', + code: '211321', + }, + { + name: '建平县', + code: '211322', + }, + { + name: '喀喇沁左翼蒙古族自治县', + code: '211324', + }, + { + name: '北票市', + code: '211381', + }, + { + name: '凌源市', + code: '211382', + }, + ], + }, + { + name: '葫芦岛市', + code: '211400', + children: [ + { + name: '连山区', + code: '211402', + }, + { + name: '龙港区', + code: '211403', + }, + { + name: '南票区', + code: '211404', + }, + { + name: '绥中县', + code: '211421', + }, + { + name: '建昌县', + code: '211422', + }, + { + name: '兴城市', + code: '211481', + }, + ], + }, + ], + }, + { + name: '吉林省', + code: '220000', + region: 'northeast', + children: [ + { + name: '长春市', + code: '220100', + children: [ + { + name: '南关区', + code: '220102', + }, + { + name: '宽城区', + code: '220103', + }, + { + name: '朝阳区', + code: '220104', + }, + { + name: '二道区', + code: '220105', + }, + { + name: '绿园区', + code: '220106', + }, + { + name: '双阳区', + code: '220112', + }, + { + name: '九台区', + code: '220113', + }, + { + name: '农安县', + code: '220122', + }, + { + name: '榆树市', + code: '220182', + }, + { + name: '德惠市', + code: '220183', + }, + ], + }, + { + name: '吉林市', + code: '220200', + children: [ + { + name: '昌邑区', + code: '220202', + }, + { + name: '龙潭区', + code: '220203', + }, + { + name: '船营区', + code: '220204', + }, + { + name: '丰满区', + code: '220211', + }, + { + name: '永吉县', + code: '220221', + }, + { + name: '蛟河市', + code: '220281', + }, + { + name: '桦甸市', + code: '220282', + }, + { + name: '舒兰市', + code: '220283', + }, + { + name: '磐石市', + code: '220284', + }, + ], + }, + { + name: '四平市', + code: '220300', + children: [ + { + name: '铁西区', + code: '220302', + }, + { + name: '铁东区', + code: '220303', + }, + { + name: '梨树县', + code: '220322', + }, + { + name: '伊通满族自治县', + code: '220323', + }, + { + name: '公主岭市', + code: '220381', + }, + { + name: '双辽市', + code: '220382', + }, + ], + }, + { + name: '辽源市', + code: '220400', + children: [ + { + name: '龙山区', + code: '220402', + }, + { + name: '西安区', + code: '220403', + }, + { + name: '东丰县', + code: '220421', + }, + { + name: '东辽县', + code: '220422', + }, + ], + }, + { + name: '通化市', + code: '220500', + children: [ + { + name: '东昌区', + code: '220502', + }, + { + name: '二道江区', + code: '220503', + }, + { + name: '通化县', + code: '220521', + }, + { + name: '辉南县', + code: '220523', + }, + { + name: '柳河县', + code: '220524', + }, + { + name: '梅河口市', + code: '220581', + }, + { + name: '集安市', + code: '220582', + }, + ], + }, + { + name: '白山市', + code: '220600', + children: [ + { + name: '浑江区', + code: '220602', + }, + { + name: '江源区', + code: '220605', + }, + { + name: '抚松县', + code: '220621', + }, + { + name: '靖宇县', + code: '220622', + }, + { + name: '长白朝鲜族自治县', + code: '220623', + }, + { + name: '临江市', + code: '220681', + }, + ], + }, + { + name: '松原市', + code: '220700', + children: [ + { + name: '宁江区', + code: '220702', + }, + { + name: '前郭尔罗斯蒙古族自治县', + code: '220721', + }, + { + name: '长岭县', + code: '220722', + }, + { + name: '乾安县', + code: '220723', + }, + { + name: '扶余市', + code: '220781', + }, + ], + }, + { + name: '白城市', + code: '220800', + children: [ + { + name: '洮北区', + code: '220802', + }, + { + name: '镇赉县', + code: '220821', + }, + { + name: '通榆县', + code: '220822', + }, + { + name: '洮南市', + code: '220881', + }, + { + name: '大安市', + code: '220882', + }, + ], + }, + { + name: '延边朝鲜族自治州', + code: '222400', + children: [ + { + name: '延吉市', + code: '222401', + }, + { + name: '图们市', + code: '222402', + }, + { + name: '敦化市', + code: '222403', + }, + { + name: '珲春市', + code: '222404', + }, + { + name: '龙井市', + code: '222405', + }, + { + name: '和龙市', + code: '222406', + }, + { + name: '汪清县', + code: '222424', + }, + { + name: '安图县', + code: '222426', + }, + ], + }, + ], + }, + { + name: '黑龙江省', + code: '230000', + region: 'northeast', + children: [ + { + name: '哈尔滨市', + code: '230100', + children: [ + { + name: '道里区', + code: '230102', + }, + { + name: '南岗区', + code: '230103', + }, + { + name: '道外区', + code: '230104', + }, + { + name: '平房区', + code: '230108', + }, + { + name: '松北区', + code: '230109', + }, + { + name: '香坊区', + code: '230110', + }, + { + name: '呼兰区', + code: '230111', + }, + { + name: '阿城区', + code: '230112', + }, + { + name: '双城区', + code: '230113', + }, + { + name: '依兰县', + code: '230123', + }, + { + name: '方正县', + code: '230124', + }, + { + name: '宾县', + code: '230125', + }, + { + name: '巴彦县', + code: '230126', + }, + { + name: '木兰县', + code: '230127', + }, + { + name: '通河县', + code: '230128', + }, + { + name: '延寿县', + code: '230129', + }, + { + name: '尚志市', + code: '230183', + }, + { + name: '五常市', + code: '230184', + }, + ], + }, + { + name: '齐齐哈尔市', + code: '230200', + children: [ + { + name: '龙沙区', + code: '230202', + }, + { + name: '建华区', + code: '230203', + }, + { + name: '铁锋区', + code: '230204', + }, + { + name: '昂昂溪区', + code: '230205', + }, + { + name: '富拉尔基区', + code: '230206', + }, + { + name: '碾子山区', + code: '230207', + }, + { + name: '梅里斯达斡尔族区', + code: '230208', + }, + { + name: '龙江县', + code: '230221', + }, + { + name: '依安县', + code: '230223', + }, + { + name: '泰来县', + code: '230224', + }, + { + name: '甘南县', + code: '230225', + }, + { + name: '富裕县', + code: '230227', + }, + { + name: '克山县', + code: '230229', + }, + { + name: '克东县', + code: '230230', + }, + { + name: '拜泉县', + code: '230231', + }, + { + name: '讷河市', + code: '230281', + }, + ], + }, + { + name: '鸡西市', + code: '230300', + children: [ + { + name: '鸡冠区', + code: '230302', + }, + { + name: '恒山区', + code: '230303', + }, + { + name: '滴道区', + code: '230304', + }, + { + name: '梨树区', + code: '230305', + }, + { + name: '城子河区', + code: '230306', + }, + { + name: '麻山区', + code: '230307', + }, + { + name: '鸡东县', + code: '230321', + }, + { + name: '虎林市', + code: '230381', + }, + { + name: '密山市', + code: '230382', + }, + ], + }, + { + name: '鹤岗市', + code: '230400', + children: [ + { + name: '向阳区', + code: '230402', + }, + { + name: '工农区', + code: '230403', + }, + { + name: '南山区', + code: '230404', + }, + { + name: '兴安区', + code: '230405', + }, + { + name: '东山区', + code: '230406', + }, + { + name: '兴山区', + code: '230407', + }, + { + name: '萝北县', + code: '230421', + }, + { + name: '绥滨县', + code: '230422', + }, + ], + }, + { + name: '双鸭山市', + code: '230500', + children: [ + { + name: '尖山区', + code: '230502', + }, + { + name: '岭东区', + code: '230503', + }, + { + name: '四方台区', + code: '230505', + }, + { + name: '宝山区', + code: '230506', + }, + { + name: '集贤县', + code: '230521', + }, + { + name: '友谊县', + code: '230522', + }, + { + name: '宝清县', + code: '230523', + }, + { + name: '饶河县', + code: '230524', + }, + ], + }, + { + name: '大庆市', + code: '230600', + children: [ + { + name: '萨尔图区', + code: '230602', + }, + { + name: '龙凤区', + code: '230603', + }, + { + name: '让胡路区', + code: '230604', + }, + { + name: '红岗区', + code: '230605', + }, + { + name: '大同区', + code: '230606', + }, + { + name: '肇州县', + code: '230621', + }, + { + name: '肇源县', + code: '230622', + }, + { + name: '林甸县', + code: '230623', + }, + { + name: '杜尔伯特蒙古族自治县', + code: '230624', + }, + ], + }, + { + name: '伊春市', + code: '230700', + children: [ + { + name: '伊春区', + code: '230702', + }, + { + name: '南岔区', + code: '230703', + }, + { + name: '友好区', + code: '230704', + }, + { + name: '西林区', + code: '230705', + }, + { + name: '翠峦区', + code: '230706', + }, + { + name: '新青区', + code: '230707', + }, + { + name: '美溪区', + code: '230708', + }, + { + name: '金山屯区', + code: '230709', + }, + { + name: '五营区', + code: '230710', + }, + { + name: '乌马河区', + code: '230711', + }, + { + name: '汤旺河区', + code: '230712', + }, + { + name: '带岭区', + code: '230713', + }, + { + name: '乌伊岭区', + code: '230714', + }, + { + name: '红星区', + code: '230715', + }, + { + name: '上甘岭区', + code: '230716', + }, + { + name: '嘉荫县', + code: '230722', + }, + { + name: '铁力市', + code: '230781', + }, + ], + }, + { + name: '佳木斯市', + code: '230800', + children: [ + { + name: '向阳区', + code: '230803', + }, + { + name: '前进区', + code: '230804', + }, + { + name: '东风区', + code: '230805', + }, + { + name: '郊区', + code: '230811', + }, + { + name: '桦南县', + code: '230822', + }, + { + name: '桦川县', + code: '230826', + }, + { + name: '汤原县', + code: '230828', + }, + { + name: '同江市', + code: '230881', + }, + { + name: '富锦市', + code: '230882', + }, + { + name: '抚远市', + code: '230883', + }, + ], + }, + { + name: '七台河市', + code: '230900', + children: [ + { + name: '新兴区', + code: '230902', + }, + { + name: '桃山区', + code: '230903', + }, + { + name: '茄子河区', + code: '230904', + }, + { + name: '勃利县', + code: '230921', + }, + ], + }, + { + name: '牡丹江市', + code: '231000', + children: [ + { + name: '东安区', + code: '231002', + }, + { + name: '阳明区', + code: '231003', + }, + { + name: '爱民区', + code: '231004', + }, + { + name: '西安区', + code: '231005', + }, + { + name: '林口县', + code: '231025', + }, + { + name: '绥芬河市', + code: '231081', + }, + { + name: '海林市', + code: '231083', + }, + { + name: '宁安市', + code: '231084', + }, + { + name: '穆棱市', + code: '231085', + }, + { + name: '东宁市', + code: '231086', + }, + ], + }, + { + name: '黑河市', + code: '231100', + children: [ + { + name: '爱辉区', + code: '231102', + }, + { + name: '嫩江县', + code: '231121', + }, + { + name: '逊克县', + code: '231123', + }, + { + name: '孙吴县', + code: '231124', + }, + { + name: '北安市', + code: '231181', + }, + { + name: '五大连池市', + code: '231182', + }, + ], + }, + { + name: '绥化市', + code: '231200', + children: [ + { + name: '北林区', + code: '231202', + }, + { + name: '望奎县', + code: '231221', + }, + { + name: '兰西县', + code: '231222', + }, + { + name: '青冈县', + code: '231223', + }, + { + name: '庆安县', + code: '231224', + }, + { + name: '明水县', + code: '231225', + }, + { + name: '绥棱县', + code: '231226', + }, + { + name: '安达市', + code: '231281', + }, + { + name: '肇东市', + code: '231282', + }, + { + name: '海伦市', + code: '231283', + }, + ], + }, + { + name: '大兴安岭地区', + code: '232700', + children: [ + { + name: '漠河市', + code: '232701', + }, + { + name: '呼玛县', + code: '232721', + }, + { + name: '塔河县', + code: '232722', + }, + ], + }, + ], + }, + { + name: '上海市', + code: '310000', + region: 'east', + provinceLevelCity: true, + children: [ + { + name: '市辖区', + code: '310100', + children: [ + { + name: '黄浦区', + code: '310101', + }, + { + name: '徐汇区', + code: '310104', + }, + { + name: '长宁区', + code: '310105', + }, + { + name: '静安区', + code: '310106', + }, + { + name: '普陀区', + code: '310107', + }, + { + name: '虹口区', + code: '310109', + }, + { + name: '杨浦区', + code: '310110', + }, + { + name: '闵行区', + code: '310112', + }, + { + name: '宝山区', + code: '310113', + }, + { + name: '嘉定区', + code: '310114', + }, + { + name: '浦东新区', + code: '310115', + }, + { + name: '金山区', + code: '310116', + }, + { + name: '松江区', + code: '310117', + }, + { + name: '青浦区', + code: '310118', + }, + { + name: '奉贤区', + code: '310120', + }, + { + name: '崇明区', + code: '310151', + }, + ], + }, + ], + }, + { + name: '江苏省', + code: '320000', + region: 'east', + children: [ + { + name: '南京市', + code: '320100', + children: [ + { + name: '玄武区', + code: '320102', + }, + { + name: '秦淮区', + code: '320104', + }, + { + name: '建邺区', + code: '320105', + }, + { + name: '鼓楼区', + code: '320106', + }, + { + name: '浦口区', + code: '320111', + }, + { + name: '栖霞区', + code: '320113', + }, + { + name: '雨花台区', + code: '320114', + }, + { + name: '江宁区', + code: '320115', + }, + { + name: '六合区', + code: '320116', + }, + { + name: '溧水区', + code: '320117', + }, + { + name: '高淳区', + code: '320118', + }, + ], + }, + { + name: '无锡市', + code: '320200', + children: [ + { + name: '锡山区', + code: '320205', + }, + { + name: '惠山区', + code: '320206', + }, + { + name: '滨湖区', + code: '320211', + }, + { + name: '梁溪区', + code: '320213', + }, + { + name: '新吴区', + code: '320214', + }, + { + name: '江阴市', + code: '320281', + }, + { + name: '宜兴市', + code: '320282', + }, + ], + }, + { + name: '徐州市', + code: '320300', + children: [ + { + name: '鼓楼区', + code: '320302', + }, + { + name: '云龙区', + code: '320303', + }, + { + name: '贾汪区', + code: '320305', + }, + { + name: '泉山区', + code: '320311', + }, + { + name: '铜山区', + code: '320312', + }, + { + name: '丰县', + code: '320321', + }, + { + name: '沛县', + code: '320322', + }, + { + name: '睢宁县', + code: '320324', + }, + { + name: '新沂市', + code: '320381', + }, + { + name: '邳州市', + code: '320382', + }, + ], + }, + { + name: '常州市', + code: '320400', + children: [ + { + name: '天宁区', + code: '320402', + }, + { + name: '钟楼区', + code: '320404', + }, + { + name: '新北区', + code: '320411', + }, + { + name: '武进区', + code: '320412', + }, + { + name: '金坛区', + code: '320413', + }, + { + name: '溧阳市', + code: '320481', + }, + ], + }, + { + name: '苏州市', + code: '320500', + children: [ + { + name: '虎丘区', + code: '320505', + }, + { + name: '吴中区', + code: '320506', + }, + { + name: '相城区', + code: '320507', + }, + { + name: '姑苏区', + code: '320508', + }, + { + name: '吴江区', + code: '320509', + }, + { + name: '常熟市', + code: '320581', + }, + { + name: '张家港市', + code: '320582', + }, + { + name: '昆山市', + code: '320583', + }, + { + name: '太仓市', + code: '320585', + }, + ], + }, + { + name: '南通市', + code: '320600', + children: [ + { + name: '崇川区', + code: '320602', + }, + { + name: '港闸区', + code: '320611', + }, + { + name: '通州区', + code: '320612', + }, + { + name: '如东县', + code: '320623', + }, + { + name: '启东市', + code: '320681', + }, + { + name: '如皋市', + code: '320682', + }, + { + name: '海门市', + code: '320684', + }, + { + name: '海安市', + code: '320685', + }, + ], + }, + { + name: '连云港市', + code: '320700', + children: [ + { + name: '连云区', + code: '320703', + }, + { + name: '海州区', + code: '320706', + }, + { + name: '赣榆区', + code: '320707', + }, + { + name: '东海县', + code: '320722', + }, + { + name: '灌云县', + code: '320723', + }, + { + name: '灌南县', + code: '320724', + }, + ], + }, + { + name: '淮安市', + code: '320800', + children: [ + { + name: '淮安区', + code: '320803', + }, + { + name: '淮阴区', + code: '320804', + }, + { + name: '清江浦区', + code: '320812', + }, + { + name: '洪泽区', + code: '320813', + }, + { + name: '涟水县', + code: '320826', + }, + { + name: '盱眙县', + code: '320830', + }, + { + name: '金湖县', + code: '320831', + }, + ], + }, + { + name: '盐城市', + code: '320900', + children: [ + { + name: '亭湖区', + code: '320902', + }, + { + name: '盐都区', + code: '320903', + }, + { + name: '大丰区', + code: '320904', + }, + { + name: '响水县', + code: '320921', + }, + { + name: '滨海县', + code: '320922', + }, + { + name: '阜宁县', + code: '320923', + }, + { + name: '射阳县', + code: '320924', + }, + { + name: '建湖县', + code: '320925', + }, + { + name: '东台市', + code: '320981', + }, + ], + }, + { + name: '扬州市', + code: '321000', + children: [ + { + name: '广陵区', + code: '321002', + }, + { + name: '邗江区', + code: '321003', + }, + { + name: '江都区', + code: '321012', + }, + { + name: '宝应县', + code: '321023', + }, + { + name: '仪征市', + code: '321081', + }, + { + name: '高邮市', + code: '321084', + }, + ], + }, + { + name: '镇江市', + code: '321100', + children: [ + { + name: '京口区', + code: '321102', + }, + { + name: '润州区', + code: '321111', + }, + { + name: '丹徒区', + code: '321112', + }, + { + name: '丹阳市', + code: '321181', + }, + { + name: '扬中市', + code: '321182', + }, + { + name: '句容市', + code: '321183', + }, + ], + }, + { + name: '泰州市', + code: '321200', + children: [ + { + name: '海陵区', + code: '321202', + }, + { + name: '高港区', + code: '321203', + }, + { + name: '姜堰区', + code: '321204', + }, + { + name: '兴化市', + code: '321281', + }, + { + name: '靖江市', + code: '321282', + }, + { + name: '泰兴市', + code: '321283', + }, + ], + }, + { + name: '宿迁市', + code: '321300', + children: [ + { + name: '宿城区', + code: '321302', + }, + { + name: '宿豫区', + code: '321311', + }, + { + name: '沭阳县', + code: '321322', + }, + { + name: '泗阳县', + code: '321323', + }, + { + name: '泗洪县', + code: '321324', + }, + ], + }, + ], + }, + { + name: '浙江省', + code: '330000', + region: 'east', + children: [ + { + name: '杭州市', + code: '330100', + children: [ + { + name: '上城区', + code: '330102', + }, + { + name: '下城区', + code: '330103', + }, + { + name: '江干区', + code: '330104', + }, + { + name: '拱墅区', + code: '330105', + }, + { + name: '西湖区', + code: '330106', + }, + { + name: '滨江区', + code: '330108', + }, + { + name: '萧山区', + code: '330109', + }, + { + name: '余杭区', + code: '330110', + }, + { + name: '富阳区', + code: '330111', + }, + { + name: '临安区', + code: '330112', + }, + { + name: '桐庐县', + code: '330122', + }, + { + name: '淳安县', + code: '330127', + }, + { + name: '建德市', + code: '330182', + }, + ], + }, + { + name: '宁波市', + code: '330200', + children: [ + { + name: '海曙区', + code: '330203', + }, + { + name: '江北区', + code: '330205', + }, + { + name: '北仑区', + code: '330206', + }, + { + name: '镇海区', + code: '330211', + }, + { + name: '鄞州区', + code: '330212', + }, + { + name: '奉化区', + code: '330213', + }, + { + name: '象山县', + code: '330225', + }, + { + name: '宁海县', + code: '330226', + }, + { + name: '余姚市', + code: '330281', + }, + { + name: '慈溪市', + code: '330282', + }, + ], + }, + { + name: '温州市', + code: '330300', + children: [ + { + name: '鹿城区', + code: '330302', + }, + { + name: '龙湾区', + code: '330303', + }, + { + name: '瓯海区', + code: '330304', + }, + { + name: '洞头区', + code: '330305', + }, + { + name: '永嘉县', + code: '330324', + }, + { + name: '平阳县', + code: '330326', + }, + { + name: '苍南县', + code: '330327', + }, + { + name: '文成县', + code: '330328', + }, + { + name: '泰顺县', + code: '330329', + }, + { + name: '瑞安市', + code: '330381', + }, + { + name: '乐清市', + code: '330382', + }, + ], + }, + { + name: '嘉兴市', + code: '330400', + children: [ + { + name: '南湖区', + code: '330402', + }, + { + name: '秀洲区', + code: '330411', + }, + { + name: '嘉善县', + code: '330421', + }, + { + name: '海盐县', + code: '330424', + }, + { + name: '海宁市', + code: '330481', + }, + { + name: '平湖市', + code: '330482', + }, + { + name: '桐乡市', + code: '330483', + }, + ], + }, + { + name: '湖州市', + code: '330500', + children: [ + { + name: '吴兴区', + code: '330502', + }, + { + name: '南浔区', + code: '330503', + }, + { + name: '德清县', + code: '330521', + }, + { + name: '长兴县', + code: '330522', + }, + { + name: '安吉县', + code: '330523', + }, + ], + }, + { + name: '绍兴市', + code: '330600', + children: [ + { + name: '越城区', + code: '330602', + }, + { + name: '柯桥区', + code: '330603', + }, + { + name: '上虞区', + code: '330604', + }, + { + name: '新昌县', + code: '330624', + }, + { + name: '诸暨市', + code: '330681', + }, + { + name: '嵊州市', + code: '330683', + }, + ], + }, + { + name: '金华市', + code: '330700', + children: [ + { + name: '婺城区', + code: '330702', + }, + { + name: '金东区', + code: '330703', + }, + { + name: '武义县', + code: '330723', + }, + { + name: '浦江县', + code: '330726', + }, + { + name: '磐安县', + code: '330727', + }, + { + name: '兰溪市', + code: '330781', + }, + { + name: '义乌市', + code: '330782', + }, + { + name: '东阳市', + code: '330783', + }, + { + name: '永康市', + code: '330784', + }, + ], + }, + { + name: '衢州市', + code: '330800', + children: [ + { + name: '柯城区', + code: '330802', + }, + { + name: '衢江区', + code: '330803', + }, + { + name: '常山县', + code: '330822', + }, + { + name: '开化县', + code: '330824', + }, + { + name: '龙游县', + code: '330825', + }, + { + name: '江山市', + code: '330881', + }, + ], + }, + { + name: '舟山市', + code: '330900', + children: [ + { + name: '定海区', + code: '330902', + }, + { + name: '普陀区', + code: '330903', + }, + { + name: '岱山县', + code: '330921', + }, + { + name: '嵊泗县', + code: '330922', + }, + ], + }, + { + name: '台州市', + code: '331000', + children: [ + { + name: '椒江区', + code: '331002', + }, + { + name: '黄岩区', + code: '331003', + }, + { + name: '路桥区', + code: '331004', + }, + { + name: '三门县', + code: '331022', + }, + { + name: '天台县', + code: '331023', + }, + { + name: '仙居县', + code: '331024', + }, + { + name: '温岭市', + code: '331081', + }, + { + name: '临海市', + code: '331082', + }, + { + name: '玉环市', + code: '331083', + }, + ], + }, + { + name: '丽水市', + code: '331100', + children: [ + { + name: '莲都区', + code: '331102', + }, + { + name: '青田县', + code: '331121', + }, + { + name: '缙云县', + code: '331122', + }, + { + name: '遂昌县', + code: '331123', + }, + { + name: '松阳县', + code: '331124', + }, + { + name: '云和县', + code: '331125', + }, + { + name: '庆元县', + code: '331126', + }, + { + name: '景宁畲族自治县', + code: '331127', + }, + { + name: '龙泉市', + code: '331181', + }, + ], + }, + ], + }, + { + name: '安徽省', + code: '340000', + region: 'east', + children: [ + { + name: '合肥市', + code: '340100', + children: [ + { + name: '瑶海区', + code: '340102', + }, + { + name: '庐阳区', + code: '340103', + }, + { + name: '蜀山区', + code: '340104', + }, + { + name: '包河区', + code: '340111', + }, + { + name: '长丰县', + code: '340121', + }, + { + name: '肥东县', + code: '340122', + }, + { + name: '肥西县', + code: '340123', + }, + { + name: '庐江县', + code: '340124', + }, + { + name: '巢湖市', + code: '340181', + }, + ], + }, + { + name: '芜湖市', + code: '340200', + children: [ + { + name: '镜湖区', + code: '340202', + }, + { + name: '弋江区', + code: '340203', + }, + { + name: '鸠江区', + code: '340207', + }, + { + name: '三山区', + code: '340208', + }, + { + name: '芜湖县', + code: '340221', + }, + { + name: '繁昌县', + code: '340222', + }, + { + name: '南陵县', + code: '340223', + }, + { + name: '无为县', + code: '340225', + }, + ], + }, + { + name: '蚌埠市', + code: '340300', + children: [ + { + name: '龙子湖区', + code: '340302', + }, + { + name: '蚌山区', + code: '340303', + }, + { + name: '禹会区', + code: '340304', + }, + { + name: '淮上区', + code: '340311', + }, + { + name: '怀远县', + code: '340321', + }, + { + name: '五河县', + code: '340322', + }, + { + name: '固镇县', + code: '340323', + }, + ], + }, + { + name: '淮南市', + code: '340400', + children: [ + { + name: '大通区', + code: '340402', + }, + { + name: '田家庵区', + code: '340403', + }, + { + name: '谢家集区', + code: '340404', + }, + { + name: '八公山区', + code: '340405', + }, + { + name: '潘集区', + code: '340406', + }, + { + name: '凤台县', + code: '340421', + }, + { + name: '寿县', + code: '340422', + }, + ], + }, + { + name: '马鞍山市', + code: '340500', + children: [ + { + name: '花山区', + code: '340503', + }, + { + name: '雨山区', + code: '340504', + }, + { + name: '博望区', + code: '340506', + }, + { + name: '当涂县', + code: '340521', + }, + { + name: '含山县', + code: '340522', + }, + { + name: '和县', + code: '340523', + }, + ], + }, + { + name: '淮北市', + code: '340600', + children: [ + { + name: '杜集区', + code: '340602', + }, + { + name: '相山区', + code: '340603', + }, + { + name: '烈山区', + code: '340604', + }, + { + name: '濉溪县', + code: '340621', + }, + ], + }, + { + name: '铜陵市', + code: '340700', + children: [ + { + name: '铜官区', + code: '340705', + }, + { + name: '义安区', + code: '340706', + }, + { + name: '郊区', + code: '340711', + }, + { + name: '枞阳县', + code: '340722', + }, + ], + }, + { + name: '安庆市', + code: '340800', + children: [ + { + name: '迎江区', + code: '340802', + }, + { + name: '大观区', + code: '340803', + }, + { + name: '宜秀区', + code: '340811', + }, + { + name: '怀宁县', + code: '340822', + }, + { + name: '太湖县', + code: '340825', + }, + { + name: '宿松县', + code: '340826', + }, + { + name: '望江县', + code: '340827', + }, + { + name: '岳西县', + code: '340828', + }, + { + name: '桐城市', + code: '340881', + }, + { + name: '潜山市', + code: '340882', + }, + ], + }, + { + name: '黄山市', + code: '341000', + children: [ + { + name: '屯溪区', + code: '341002', + }, + { + name: '黄山区', + code: '341003', + }, + { + name: '徽州区', + code: '341004', + }, + { + name: '歙县', + code: '341021', + }, + { + name: '休宁县', + code: '341022', + }, + { + name: '黟县', + code: '341023', + }, + { + name: '祁门县', + code: '341024', + }, + ], + }, + { + name: '滁州市', + code: '341100', + children: [ + { + name: '琅琊区', + code: '341102', + }, + { + name: '南谯区', + code: '341103', + }, + { + name: '来安县', + code: '341122', + }, + { + name: '全椒县', + code: '341124', + }, + { + name: '定远县', + code: '341125', + }, + { + name: '凤阳县', + code: '341126', + }, + { + name: '天长市', + code: '341181', + }, + { + name: '明光市', + code: '341182', + }, + ], + }, + { + name: '阜阳市', + code: '341200', + children: [ + { + name: '颍州区', + code: '341202', + }, + { + name: '颍东区', + code: '341203', + }, + { + name: '颍泉区', + code: '341204', + }, + { + name: '临泉县', + code: '341221', + }, + { + name: '太和县', + code: '341222', + }, + { + name: '阜南县', + code: '341225', + }, + { + name: '颍上县', + code: '341226', + }, + { + name: '界首市', + code: '341282', + }, + ], + }, + { + name: '宿州市', + code: '341300', + children: [ + { + name: '埇桥区', + code: '341302', + }, + { + name: '砀山县', + code: '341321', + }, + { + name: '萧县', + code: '341322', + }, + { + name: '灵璧县', + code: '341323', + }, + { + name: '泗县', + code: '341324', + }, + ], + }, + { + name: '六安市', + code: '341500', + children: [ + { + name: '金安区', + code: '341502', + }, + { + name: '裕安区', + code: '341503', + }, + { + name: '叶集区', + code: '341504', + }, + { + name: '霍邱县', + code: '341522', + }, + { + name: '舒城县', + code: '341523', + }, + { + name: '金寨县', + code: '341524', + }, + { + name: '霍山县', + code: '341525', + }, + ], + }, + { + name: '亳州市', + code: '341600', + children: [ + { + name: '谯城区', + code: '341602', + }, + { + name: '涡阳县', + code: '341621', + }, + { + name: '蒙城县', + code: '341622', + }, + { + name: '利辛县', + code: '341623', + }, + ], + }, + { + name: '池州市', + code: '341700', + children: [ + { + name: '贵池区', + code: '341702', + }, + { + name: '东至县', + code: '341721', + }, + { + name: '石台县', + code: '341722', + }, + { + name: '青阳县', + code: '341723', + }, + ], + }, + { + name: '宣城市', + code: '341800', + children: [ + { + name: '宣州区', + code: '341802', + }, + { + name: '郎溪县', + code: '341821', + }, + { + name: '广德县', + code: '341822', + }, + { + name: '泾县', + code: '341823', + }, + { + name: '绩溪县', + code: '341824', + }, + { + name: '旌德县', + code: '341825', + }, + { + name: '宁国市', + code: '341881', + }, + ], + }, + ], + }, + { + name: '福建省', + code: '350000', + region: 'east', + children: [ + { + name: '福州市', + code: '350100', + children: [ + { + name: '鼓楼区', + code: '350102', + }, + { + name: '台江区', + code: '350103', + }, + { + name: '仓山区', + code: '350104', + }, + { + name: '马尾区', + code: '350105', + }, + { + name: '晋安区', + code: '350111', + }, + { + name: '长乐区', + code: '350112', + }, + { + name: '闽侯县', + code: '350121', + }, + { + name: '连江县', + code: '350122', + }, + { + name: '罗源县', + code: '350123', + }, + { + name: '闽清县', + code: '350124', + }, + { + name: '永泰县', + code: '350125', + }, + { + name: '平潭县', + code: '350128', + }, + { + name: '福清市', + code: '350181', + }, + ], + }, + { + name: '厦门市', + code: '350200', + children: [ + { + name: '思明区', + code: '350203', + }, + { + name: '海沧区', + code: '350205', + }, + { + name: '湖里区', + code: '350206', + }, + { + name: '集美区', + code: '350211', + }, + { + name: '同安区', + code: '350212', + }, + { + name: '翔安区', + code: '350213', + }, + ], + }, + { + name: '莆田市', + code: '350300', + children: [ + { + name: '城厢区', + code: '350302', + }, + { + name: '涵江区', + code: '350303', + }, + { + name: '荔城区', + code: '350304', + }, + { + name: '秀屿区', + code: '350305', + }, + { + name: '仙游县', + code: '350322', + }, + ], + }, + { + name: '三明市', + code: '350400', + children: [ + { + name: '梅列区', + code: '350402', + }, + { + name: '三元区', + code: '350403', + }, + { + name: '明溪县', + code: '350421', + }, + { + name: '清流县', + code: '350423', + }, + { + name: '宁化县', + code: '350424', + }, + { + name: '大田县', + code: '350425', + }, + { + name: '尤溪县', + code: '350426', + }, + { + name: '沙县', + code: '350427', + }, + { + name: '将乐县', + code: '350428', + }, + { + name: '泰宁县', + code: '350429', + }, + { + name: '建宁县', + code: '350430', + }, + { + name: '永安市', + code: '350481', + }, + ], + }, + { + name: '泉州市', + code: '350500', + children: [ + { + name: '鲤城区', + code: '350502', + }, + { + name: '丰泽区', + code: '350503', + }, + { + name: '洛江区', + code: '350504', + }, + { + name: '泉港区', + code: '350505', + }, + { + name: '惠安县', + code: '350521', + }, + { + name: '安溪县', + code: '350524', + }, + { + name: '永春县', + code: '350525', + }, + { + name: '德化县', + code: '350526', + }, + { + name: '金门县', + code: '350527', + }, + { + name: '石狮市', + code: '350581', + }, + { + name: '晋江市', + code: '350582', + }, + { + name: '南安市', + code: '350583', + }, + ], + }, + { + name: '漳州市', + code: '350600', + children: [ + { + name: '芗城区', + code: '350602', + }, + { + name: '龙文区', + code: '350603', + }, + { + name: '云霄县', + code: '350622', + }, + { + name: '漳浦县', + code: '350623', + }, + { + name: '诏安县', + code: '350624', + }, + { + name: '长泰县', + code: '350625', + }, + { + name: '东山县', + code: '350626', + }, + { + name: '南靖县', + code: '350627', + }, + { + name: '平和县', + code: '350628', + }, + { + name: '华安县', + code: '350629', + }, + { + name: '龙海市', + code: '350681', + }, + ], + }, + { + name: '南平市', + code: '350700', + children: [ + { + name: '延平区', + code: '350702', + }, + { + name: '建阳区', + code: '350703', + }, + { + name: '顺昌县', + code: '350721', + }, + { + name: '浦城县', + code: '350722', + }, + { + name: '光泽县', + code: '350723', + }, + { + name: '松溪县', + code: '350724', + }, + { + name: '政和县', + code: '350725', + }, + { + name: '邵武市', + code: '350781', + }, + { + name: '武夷山市', + code: '350782', + }, + { + name: '建瓯市', + code: '350783', + }, + ], + }, + { + name: '龙岩市', + code: '350800', + children: [ + { + name: '新罗区', + code: '350802', + }, + { + name: '永定区', + code: '350803', + }, + { + name: '长汀县', + code: '350821', + }, + { + name: '上杭县', + code: '350823', + }, + { + name: '武平县', + code: '350824', + }, + { + name: '连城县', + code: '350825', + }, + { + name: '漳平市', + code: '350881', + }, + ], + }, + { + name: '宁德市', + code: '350900', + children: [ + { + name: '蕉城区', + code: '350902', + }, + { + name: '霞浦县', + code: '350921', + }, + { + name: '古田县', + code: '350922', + }, + { + name: '屏南县', + code: '350923', + }, + { + name: '寿宁县', + code: '350924', + }, + { + name: '周宁县', + code: '350925', + }, + { + name: '柘荣县', + code: '350926', + }, + { + name: '福安市', + code: '350981', + }, + { + name: '福鼎市', + code: '350982', + }, + ], + }, + ], + }, + { + name: '江西省', + code: '360000', + region: 'east', + children: [ + { + name: '南昌市', + code: '360100', + children: [ + { + name: '东湖区', + code: '360102', + }, + { + name: '西湖区', + code: '360103', + }, + { + name: '青云谱区', + code: '360104', + }, + { + name: '湾里区', + code: '360105', + }, + { + name: '青山湖区', + code: '360111', + }, + { + name: '新建区', + code: '360112', + }, + { + name: '南昌县', + code: '360121', + }, + { + name: '安义县', + code: '360123', + }, + { + name: '进贤县', + code: '360124', + }, + ], + }, + { + name: '景德镇市', + code: '360200', + children: [ + { + name: '昌江区', + code: '360202', + }, + { + name: '珠山区', + code: '360203', + }, + { + name: '浮梁县', + code: '360222', + }, + { + name: '乐平市', + code: '360281', + }, + ], + }, + { + name: '萍乡市', + code: '360300', + children: [ + { + name: '安源区', + code: '360302', + }, + { + name: '湘东区', + code: '360313', + }, + { + name: '莲花县', + code: '360321', + }, + { + name: '上栗县', + code: '360322', + }, + { + name: '芦溪县', + code: '360323', + }, + ], + }, + { + name: '九江市', + code: '360400', + children: [ + { + name: '濂溪区', + code: '360402', + }, + { + name: '浔阳区', + code: '360403', + }, + { + name: '柴桑区', + code: '360404', + }, + { + name: '武宁县', + code: '360423', + }, + { + name: '修水县', + code: '360424', + }, + { + name: '永修县', + code: '360425', + }, + { + name: '德安县', + code: '360426', + }, + { + name: '都昌县', + code: '360428', + }, + { + name: '湖口县', + code: '360429', + }, + { + name: '彭泽县', + code: '360430', + }, + { + name: '瑞昌市', + code: '360481', + }, + { + name: '共青城市', + code: '360482', + }, + { + name: '庐山市', + code: '360483', + }, + ], + }, + { + name: '新余市', + code: '360500', + children: [ + { + name: '渝水区', + code: '360502', + }, + { + name: '分宜县', + code: '360521', + }, + ], + }, + { + name: '鹰潭市', + code: '360600', + children: [ + { + name: '月湖区', + code: '360602', + }, + { + name: '余江区', + code: '360603', + }, + { + name: '贵溪市', + code: '360681', + }, + ], + }, + { + name: '赣州市', + code: '360700', + children: [ + { + name: '章贡区', + code: '360702', + }, + { + name: '南康区', + code: '360703', + }, + { + name: '赣县区', + code: '360704', + }, + { + name: '信丰县', + code: '360722', + }, + { + name: '大余县', + code: '360723', + }, + { + name: '上犹县', + code: '360724', + }, + { + name: '崇义县', + code: '360725', + }, + { + name: '安远县', + code: '360726', + }, + { + name: '龙南县', + code: '360727', + }, + { + name: '定南县', + code: '360728', + }, + { + name: '全南县', + code: '360729', + }, + { + name: '宁都县', + code: '360730', + }, + { + name: '于都县', + code: '360731', + }, + { + name: '兴国县', + code: '360732', + }, + { + name: '会昌县', + code: '360733', + }, + { + name: '寻乌县', + code: '360734', + }, + { + name: '石城县', + code: '360735', + }, + { + name: '瑞金市', + code: '360781', + }, + ], + }, + { + name: '吉安市', + code: '360800', + children: [ + { + name: '吉州区', + code: '360802', + }, + { + name: '青原区', + code: '360803', + }, + { + name: '吉安县', + code: '360821', + }, + { + name: '吉水县', + code: '360822', + }, + { + name: '峡江县', + code: '360823', + }, + { + name: '新干县', + code: '360824', + }, + { + name: '永丰县', + code: '360825', + }, + { + name: '泰和县', + code: '360826', + }, + { + name: '遂川县', + code: '360827', + }, + { + name: '万安县', + code: '360828', + }, + { + name: '安福县', + code: '360829', + }, + { + name: '永新县', + code: '360830', + }, + { + name: '井冈山市', + code: '360881', + }, + ], + }, + { + name: '宜春市', + code: '360900', + children: [ + { + name: '袁州区', + code: '360902', + }, + { + name: '奉新县', + code: '360921', + }, + { + name: '万载县', + code: '360922', + }, + { + name: '上高县', + code: '360923', + }, + { + name: '宜丰县', + code: '360924', + }, + { + name: '靖安县', + code: '360925', + }, + { + name: '铜鼓县', + code: '360926', + }, + { + name: '丰城市', + code: '360981', + }, + { + name: '樟树市', + code: '360982', + }, + { + name: '高安市', + code: '360983', + }, + ], + }, + { + name: '抚州市', + code: '361000', + children: [ + { + name: '临川区', + code: '361002', + }, + { + name: '东乡区', + code: '361003', + }, + { + name: '南城县', + code: '361021', + }, + { + name: '黎川县', + code: '361022', + }, + { + name: '南丰县', + code: '361023', + }, + { + name: '崇仁县', + code: '361024', + }, + { + name: '乐安县', + code: '361025', + }, + { + name: '宜黄县', + code: '361026', + }, + { + name: '金溪县', + code: '361027', + }, + { + name: '资溪县', + code: '361028', + }, + { + name: '广昌县', + code: '361030', + }, + ], + }, + { + name: '上饶市', + code: '361100', + children: [ + { + name: '信州区', + code: '361102', + }, + { + name: '广丰区', + code: '361103', + }, + { + name: '上饶县', + code: '361121', + }, + { + name: '玉山县', + code: '361123', + }, + { + name: '铅山县', + code: '361124', + }, + { + name: '横峰县', + code: '361125', + }, + { + name: '弋阳县', + code: '361126', + }, + { + name: '余干县', + code: '361127', + }, + { + name: '鄱阳县', + code: '361128', + }, + { + name: '万年县', + code: '361129', + }, + { + name: '婺源县', + code: '361130', + }, + { + name: '德兴市', + code: '361181', + }, + ], + }, + ], + }, + { + name: '山东省', + code: '370000', + region: 'east', + children: [ + { + name: '济南市', + code: '370100', + children: [ + { + name: '历下区', + code: '370102', + }, + { + name: '市中区', + code: '370103', + }, + { + name: '槐荫区', + code: '370104', + }, + { + name: '天桥区', + code: '370105', + }, + { + name: '历城区', + code: '370112', + }, + { + name: '长清区', + code: '370113', + }, + { + name: '章丘区', + code: '370114', + }, + { + name: '济阳区', + code: '370115', + }, + { + name: '莱芜区', + code: '370116', + }, + { + name: '钢城区', + code: '370117', + }, + { + name: '平阴县', + code: '370124', + }, + { + name: '商河县', + code: '370126', + }, + ], + }, + { + name: '青岛市', + code: '370200', + children: [ + { + name: '市南区', + code: '370202', + }, + { + name: '市北区', + code: '370203', + }, + { + name: '黄岛区', + code: '370211', + }, + { + name: '崂山区', + code: '370212', + }, + { + name: '李沧区', + code: '370213', + }, + { + name: '城阳区', + code: '370214', + }, + { + name: '即墨区', + code: '370215', + }, + { + name: '胶州市', + code: '370281', + }, + { + name: '平度市', + code: '370283', + }, + { + name: '莱西市', + code: '370285', + }, + ], + }, + { + name: '淄博市', + code: '370300', + children: [ + { + name: '淄川区', + code: '370302', + }, + { + name: '张店区', + code: '370303', + }, + { + name: '博山区', + code: '370304', + }, + { + name: '临淄区', + code: '370305', + }, + { + name: '周村区', + code: '370306', + }, + { + name: '桓台县', + code: '370321', + }, + { + name: '高青县', + code: '370322', + }, + { + name: '沂源县', + code: '370323', + }, + ], + }, + { + name: '枣庄市', + code: '370400', + children: [ + { + name: '市中区', + code: '370402', + }, + { + name: '薛城区', + code: '370403', + }, + { + name: '峄城区', + code: '370404', + }, + { + name: '台儿庄区', + code: '370405', + }, + { + name: '山亭区', + code: '370406', + }, + { + name: '滕州市', + code: '370481', + }, + ], + }, + { + name: '东营市', + code: '370500', + children: [ + { + name: '东营区', + code: '370502', + }, + { + name: '河口区', + code: '370503', + }, + { + name: '垦利区', + code: '370505', + }, + { + name: '利津县', + code: '370522', + }, + { + name: '广饶县', + code: '370523', + }, + ], + }, + { + name: '烟台市', + code: '370600', + children: [ + { + name: '芝罘区', + code: '370602', + }, + { + name: '福山区', + code: '370611', + }, + { + name: '牟平区', + code: '370612', + }, + { + name: '莱山区', + code: '370613', + }, + { + name: '长岛县', + code: '370634', + }, + { + name: '龙口市', + code: '370681', + }, + { + name: '莱阳市', + code: '370682', + }, + { + name: '莱州市', + code: '370683', + }, + { + name: '蓬莱市', + code: '370684', + }, + { + name: '招远市', + code: '370685', + }, + { + name: '栖霞市', + code: '370686', + }, + { + name: '海阳市', + code: '370687', + }, + ], + }, + { + name: '潍坊市', + code: '370700', + children: [ + { + name: '潍城区', + code: '370702', + }, + { + name: '寒亭区', + code: '370703', + }, + { + name: '坊子区', + code: '370704', + }, + { + name: '奎文区', + code: '370705', + }, + { + name: '临朐县', + code: '370724', + }, + { + name: '昌乐县', + code: '370725', + }, + { + name: '青州市', + code: '370781', + }, + { + name: '诸城市', + code: '370782', + }, + { + name: '寿光市', + code: '370783', + }, + { + name: '安丘市', + code: '370784', + }, + { + name: '高密市', + code: '370785', + }, + { + name: '昌邑市', + code: '370786', + }, + ], + }, + { + name: '济宁市', + code: '370800', + children: [ + { + name: '任城区', + code: '370811', + }, + { + name: '兖州区', + code: '370812', + }, + { + name: '微山县', + code: '370826', + }, + { + name: '鱼台县', + code: '370827', + }, + { + name: '金乡县', + code: '370828', + }, + { + name: '嘉祥县', + code: '370829', + }, + { + name: '汶上县', + code: '370830', + }, + { + name: '泗水县', + code: '370831', + }, + { + name: '梁山县', + code: '370832', + }, + { + name: '曲阜市', + code: '370881', + }, + { + name: '邹城市', + code: '370883', + }, + ], + }, + { + name: '泰安市', + code: '370900', + children: [ + { + name: '泰山区', + code: '370902', + }, + { + name: '岱岳区', + code: '370911', + }, + { + name: '宁阳县', + code: '370921', + }, + { + name: '东平县', + code: '370923', + }, + { + name: '新泰市', + code: '370982', + }, + { + name: '肥城市', + code: '370983', + }, + ], + }, + { + name: '威海市', + code: '371000', + children: [ + { + name: '环翠区', + code: '371002', + }, + { + name: '文登区', + code: '371003', + }, + { + name: '荣成市', + code: '371082', + }, + { + name: '乳山市', + code: '371083', + }, + ], + }, + { + name: '日照市', + code: '371100', + children: [ + { + name: '东港区', + code: '371102', + }, + { + name: '岚山区', + code: '371103', + }, + { + name: '五莲县', + code: '371121', + }, + { + name: '莒县', + code: '371122', + }, + ], + }, + { + name: '临沂市', + code: '371300', + children: [ + { + name: '兰山区', + code: '371302', + }, + { + name: '罗庄区', + code: '371311', + }, + { + name: '河东区', + code: '371312', + }, + { + name: '沂南县', + code: '371321', + }, + { + name: '郯城县', + code: '371322', + }, + { + name: '沂水县', + code: '371323', + }, + { + name: '兰陵县', + code: '371324', + }, + { + name: '费县', + code: '371325', + }, + { + name: '平邑县', + code: '371326', + }, + { + name: '莒南县', + code: '371327', + }, + { + name: '蒙阴县', + code: '371328', + }, + { + name: '临沭县', + code: '371329', + }, + ], + }, + { + name: '德州市', + code: '371400', + children: [ + { + name: '德城区', + code: '371402', + }, + { + name: '陵城区', + code: '371403', + }, + { + name: '宁津县', + code: '371422', + }, + { + name: '庆云县', + code: '371423', + }, + { + name: '临邑县', + code: '371424', + }, + { + name: '齐河县', + code: '371425', + }, + { + name: '平原县', + code: '371426', + }, + { + name: '夏津县', + code: '371427', + }, + { + name: '武城县', + code: '371428', + }, + { + name: '乐陵市', + code: '371481', + }, + { + name: '禹城市', + code: '371482', + }, + ], + }, + { + name: '聊城市', + code: '371500', + children: [ + { + name: '东昌府区', + code: '371502', + }, + { + name: '阳谷县', + code: '371521', + }, + { + name: '莘县', + code: '371522', + }, + { + name: '茌平县', + code: '371523', + }, + { + name: '东阿县', + code: '371524', + }, + { + name: '冠县', + code: '371525', + }, + { + name: '高唐县', + code: '371526', + }, + { + name: '临清市', + code: '371581', + }, + ], + }, + { + name: '滨州市', + code: '371600', + children: [ + { + name: '滨城区', + code: '371602', + }, + { + name: '沾化区', + code: '371603', + }, + { + name: '惠民县', + code: '371621', + }, + { + name: '阳信县', + code: '371622', + }, + { + name: '无棣县', + code: '371623', + }, + { + name: '博兴县', + code: '371625', + }, + { + name: '邹平市', + code: '371681', + }, + ], + }, + { + name: '菏泽市', + code: '371700', + children: [ + { + name: '牡丹区', + code: '371702', + }, + { + name: '定陶区', + code: '371703', + }, + { + name: '曹县', + code: '371721', + }, + { + name: '单县', + code: '371722', + }, + { + name: '成武县', + code: '371723', + }, + { + name: '巨野县', + code: '371724', + }, + { + name: '郓城县', + code: '371725', + }, + { + name: '鄄城县', + code: '371726', + }, + { + name: '东明县', + code: '371728', + }, + ], + }, + ], + }, + { + name: '河南省', + code: '410000', + region: 'central', + children: [ + { + name: '郑州市', + code: '410100', + children: [ + { + name: '中原区', + code: '410102', + }, + { + name: '二七区', + code: '410103', + }, + { + name: '管城回族区', + code: '410104', + }, + { + name: '金水区', + code: '410105', + }, + { + name: '上街区', + code: '410106', + }, + { + name: '惠济区', + code: '410108', + }, + { + name: '中牟县', + code: '410122', + }, + { + name: '巩义市', + code: '410181', + }, + { + name: '荥阳市', + code: '410182', + }, + { + name: '新密市', + code: '410183', + }, + { + name: '新郑市', + code: '410184', + }, + { + name: '登封市', + code: '410185', + }, + ], + }, + { + name: '开封市', + code: '410200', + children: [ + { + name: '龙亭区', + code: '410202', + }, + { + name: '顺河回族区', + code: '410203', + }, + { + name: '鼓楼区', + code: '410204', + }, + { + name: '禹王台区', + code: '410205', + }, + { + name: '祥符区', + code: '410212', + }, + { + name: '杞县', + code: '410221', + }, + { + name: '通许县', + code: '410222', + }, + { + name: '尉氏县', + code: '410223', + }, + { + name: '兰考县', + code: '410225', + }, + ], + }, + { + name: '洛阳市', + code: '410300', + children: [ + { + name: '老城区', + code: '410302', + }, + { + name: '西工区', + code: '410303', + }, + { + name: '瀍河回族区', + code: '410304', + }, + { + name: '涧西区', + code: '410305', + }, + { + name: '吉利区', + code: '410306', + }, + { + name: '洛龙区', + code: '410311', + }, + { + name: '孟津县', + code: '410322', + }, + { + name: '新安县', + code: '410323', + }, + { + name: '栾川县', + code: '410324', + }, + { + name: '嵩县', + code: '410325', + }, + { + name: '汝阳县', + code: '410326', + }, + { + name: '宜阳县', + code: '410327', + }, + { + name: '洛宁县', + code: '410328', + }, + { + name: '伊川县', + code: '410329', + }, + { + name: '偃师市', + code: '410381', + }, + ], + }, + { + name: '平顶山市', + code: '410400', + children: [ + { + name: '新华区', + code: '410402', + }, + { + name: '卫东区', + code: '410403', + }, + { + name: '石龙区', + code: '410404', + }, + { + name: '湛河区', + code: '410411', + }, + { + name: '宝丰县', + code: '410421', + }, + { + name: '叶县', + code: '410422', + }, + { + name: '鲁山县', + code: '410423', + }, + { + name: '郏县', + code: '410425', + }, + { + name: '舞钢市', + code: '410481', + }, + { + name: '汝州市', + code: '410482', + }, + ], + }, + { + name: '安阳市', + code: '410500', + children: [ + { + name: '文峰区', + code: '410502', + }, + { + name: '北关区', + code: '410503', + }, + { + name: '殷都区', + code: '410505', + }, + { + name: '龙安区', + code: '410506', + }, + { + name: '安阳县', + code: '410522', + }, + { + name: '汤阴县', + code: '410523', + }, + { + name: '滑县', + code: '410526', + }, + { + name: '内黄县', + code: '410527', + }, + { + name: '林州市', + code: '410581', + }, + ], + }, + { + name: '鹤壁市', + code: '410600', + children: [ + { + name: '鹤山区', + code: '410602', + }, + { + name: '山城区', + code: '410603', + }, + { + name: '淇滨区', + code: '410611', + }, + { + name: '浚县', + code: '410621', + }, + { + name: '淇县', + code: '410622', + }, + ], + }, + { + name: '新乡市', + code: '410700', + children: [ + { + name: '红旗区', + code: '410702', + }, + { + name: '卫滨区', + code: '410703', + }, + { + name: '凤泉区', + code: '410704', + }, + { + name: '牧野区', + code: '410711', + }, + { + name: '新乡县', + code: '410721', + }, + { + name: '获嘉县', + code: '410724', + }, + { + name: '原阳县', + code: '410725', + }, + { + name: '延津县', + code: '410726', + }, + { + name: '封丘县', + code: '410727', + }, + { + name: '长垣县', + code: '410728', + }, + { + name: '卫辉市', + code: '410781', + }, + { + name: '辉县市', + code: '410782', + }, + ], + }, + { + name: '焦作市', + code: '410800', + children: [ + { + name: '解放区', + code: '410802', + }, + { + name: '中站区', + code: '410803', + }, + { + name: '马村区', + code: '410804', + }, + { + name: '山阳区', + code: '410811', + }, + { + name: '修武县', + code: '410821', + }, + { + name: '博爱县', + code: '410822', + }, + { + name: '武陟县', + code: '410823', + }, + { + name: '温县', + code: '410825', + }, + { + name: '沁阳市', + code: '410882', + }, + { + name: '孟州市', + code: '410883', + }, + ], + }, + { + name: '濮阳市', + code: '410900', + children: [ + { + name: '华龙区', + code: '410902', + }, + { + name: '清丰县', + code: '410922', + }, + { + name: '南乐县', + code: '410923', + }, + { + name: '范县', + code: '410926', + }, + { + name: '台前县', + code: '410927', + }, + { + name: '濮阳县', + code: '410928', + }, + ], + }, + { + name: '许昌市', + code: '411000', + children: [ + { + name: '魏都区', + code: '411002', + }, + { + name: '建安区', + code: '411003', + }, + { + name: '鄢陵县', + code: '411024', + }, + { + name: '襄城县', + code: '411025', + }, + { + name: '禹州市', + code: '411081', + }, + { + name: '长葛市', + code: '411082', + }, + ], + }, + { + name: '漯河市', + code: '411100', + children: [ + { + name: '源汇区', + code: '411102', + }, + { + name: '郾城区', + code: '411103', + }, + { + name: '召陵区', + code: '411104', + }, + { + name: '舞阳县', + code: '411121', + }, + { + name: '临颍县', + code: '411122', + }, + ], + }, + { + name: '三门峡市', + code: '411200', + children: [ + { + name: '湖滨区', + code: '411202', + }, + { + name: '陕州区', + code: '411203', + }, + { + name: '渑池县', + code: '411221', + }, + { + name: '卢氏县', + code: '411224', + }, + { + name: '义马市', + code: '411281', + }, + { + name: '灵宝市', + code: '411282', + }, + ], + }, + { + name: '南阳市', + code: '411300', + children: [ + { + name: '宛城区', + code: '411302', + }, + { + name: '卧龙区', + code: '411303', + }, + { + name: '南召县', + code: '411321', + }, + { + name: '方城县', + code: '411322', + }, + { + name: '西峡县', + code: '411323', + }, + { + name: '镇平县', + code: '411324', + }, + { + name: '内乡县', + code: '411325', + }, + { + name: '淅川县', + code: '411326', + }, + { + name: '社旗县', + code: '411327', + }, + { + name: '唐河县', + code: '411328', + }, + { + name: '新野县', + code: '411329', + }, + { + name: '桐柏县', + code: '411330', + }, + { + name: '邓州市', + code: '411381', + }, + ], + }, + { + name: '商丘市', + code: '411400', + children: [ + { + name: '梁园区', + code: '411402', + }, + { + name: '睢阳区', + code: '411403', + }, + { + name: '民权县', + code: '411421', + }, + { + name: '睢县', + code: '411422', + }, + { + name: '宁陵县', + code: '411423', + }, + { + name: '柘城县', + code: '411424', + }, + { + name: '虞城县', + code: '411425', + }, + { + name: '夏邑县', + code: '411426', + }, + { + name: '永城市', + code: '411481', + }, + ], + }, + { + name: '信阳市', + code: '411500', + children: [ + { + name: '浉河区', + code: '411502', + }, + { + name: '平桥区', + code: '411503', + }, + { + name: '罗山县', + code: '411521', + }, + { + name: '光山县', + code: '411522', + }, + { + name: '新县', + code: '411523', + }, + { + name: '商城县', + code: '411524', + }, + { + name: '固始县', + code: '411525', + }, + { + name: '潢川县', + code: '411526', + }, + { + name: '淮滨县', + code: '411527', + }, + { + name: '息县', + code: '411528', + }, + ], + }, + { + name: '周口市', + code: '411600', + children: [ + { + name: '川汇区', + code: '411602', + }, + { + name: '扶沟县', + code: '411621', + }, + { + name: '西华县', + code: '411622', + }, + { + name: '商水县', + code: '411623', + }, + { + name: '沈丘县', + code: '411624', + }, + { + name: '郸城县', + code: '411625', + }, + { + name: '淮阳县', + code: '411626', + }, + { + name: '太康县', + code: '411627', + }, + { + name: '鹿邑县', + code: '411628', + }, + { + name: '项城市', + code: '411681', + }, + ], + }, + { + name: '驻马店市', + code: '411700', + children: [ + { + name: '驿城区', + code: '411702', + }, + { + name: '西平县', + code: '411721', + }, + { + name: '上蔡县', + code: '411722', + }, + { + name: '平舆县', + code: '411723', + }, + { + name: '正阳县', + code: '411724', + }, + { + name: '确山县', + code: '411725', + }, + { + name: '泌阳县', + code: '411726', + }, + { + name: '汝南县', + code: '411727', + }, + { + name: '遂平县', + code: '411728', + }, + { + name: '新蔡县', + code: '411729', + }, + ], + }, + { + name: '直辖县', + code: '419000', + children: [ + { + name: '济源市', + code: '419001', + }, + ], + }, + ], + }, + { + name: '湖北省', + code: '420000', + region: 'central', + children: [ + { + name: '武汉市', + code: '420100', + children: [ + { + name: '江岸区', + code: '420102', + }, + { + name: '江汉区', + code: '420103', + }, + { + name: '硚口区', + code: '420104', + }, + { + name: '汉阳区', + code: '420105', + }, + { + name: '武昌区', + code: '420106', + }, + { + name: '青山区', + code: '420107', + }, + { + name: '洪山区', + code: '420111', + }, + { + name: '东西湖区', + code: '420112', + }, + { + name: '汉南区', + code: '420113', + }, + { + name: '蔡甸区', + code: '420114', + }, + { + name: '江夏区', + code: '420115', + }, + { + name: '黄陂区', + code: '420116', + }, + { + name: '新洲区', + code: '420117', + }, + ], + }, + { + name: '黄石市', + code: '420200', + children: [ + { + name: '黄石港区', + code: '420202', + }, + { + name: '西塞山区', + code: '420203', + }, + { + name: '下陆区', + code: '420204', + }, + { + name: '铁山区', + code: '420205', + }, + { + name: '阳新县', + code: '420222', + }, + { + name: '大冶市', + code: '420281', + }, + ], + }, + { + name: '十堰市', + code: '420300', + children: [ + { + name: '茅箭区', + code: '420302', + }, + { + name: '张湾区', + code: '420303', + }, + { + name: '郧阳区', + code: '420304', + }, + { + name: '郧西县', + code: '420322', + }, + { + name: '竹山县', + code: '420323', + }, + { + name: '竹溪县', + code: '420324', + }, + { + name: '房县', + code: '420325', + }, + { + name: '丹江口市', + code: '420381', + }, + ], + }, + { + name: '宜昌市', + code: '420500', + children: [ + { + name: '西陵区', + code: '420502', + }, + { + name: '伍家岗区', + code: '420503', + }, + { + name: '点军区', + code: '420504', + }, + { + name: '猇亭区', + code: '420505', + }, + { + name: '夷陵区', + code: '420506', + }, + { + name: '远安县', + code: '420525', + }, + { + name: '兴山县', + code: '420526', + }, + { + name: '秭归县', + code: '420527', + }, + { + name: '长阳土家族自治县', + code: '420528', + }, + { + name: '五峰土家族自治县', + code: '420529', + }, + { + name: '宜都市', + code: '420581', + }, + { + name: '当阳市', + code: '420582', + }, + { + name: '枝江市', + code: '420583', + }, + ], + }, + { + name: '襄阳市', + code: '420600', + children: [ + { + name: '襄城区', + code: '420602', + }, + { + name: '樊城区', + code: '420606', + }, + { + name: '襄州区', + code: '420607', + }, + { + name: '南漳县', + code: '420624', + }, + { + name: '谷城县', + code: '420625', + }, + { + name: '保康县', + code: '420626', + }, + { + name: '老河口市', + code: '420682', + }, + { + name: '枣阳市', + code: '420683', + }, + { + name: '宜城市', + code: '420684', + }, + ], + }, + { + name: '鄂州市', + code: '420700', + children: [ + { + name: '梁子湖区', + code: '420702', + }, + { + name: '华容区', + code: '420703', + }, + { + name: '鄂城区', + code: '420704', + }, + ], + }, + { + name: '荆门市', + code: '420800', + children: [ + { + name: '东宝区', + code: '420802', + }, + { + name: '掇刀区', + code: '420804', + }, + { + name: '沙洋县', + code: '420822', + }, + { + name: '钟祥市', + code: '420881', + }, + { + name: '京山市', + code: '420882', + }, + ], + }, + { + name: '孝感市', + code: '420900', + children: [ + { + name: '孝南区', + code: '420902', + }, + { + name: '孝昌县', + code: '420921', + }, + { + name: '大悟县', + code: '420922', + }, + { + name: '云梦县', + code: '420923', + }, + { + name: '应城市', + code: '420981', + }, + { + name: '安陆市', + code: '420982', + }, + { + name: '汉川市', + code: '420984', + }, + ], + }, + { + name: '荆州市', + code: '421000', + children: [ + { + name: '沙市区', + code: '421002', + }, + { + name: '荆州区', + code: '421003', + }, + { + name: '公安县', + code: '421022', + }, + { + name: '监利县', + code: '421023', + }, + { + name: '江陵县', + code: '421024', + }, + { + name: '石首市', + code: '421081', + }, + { + name: '洪湖市', + code: '421083', + }, + { + name: '松滋市', + code: '421087', + }, + ], + }, + { + name: '黄冈市', + code: '421100', + children: [ + { + name: '黄州区', + code: '421102', + }, + { + name: '团风县', + code: '421121', + }, + { + name: '红安县', + code: '421122', + }, + { + name: '罗田县', + code: '421123', + }, + { + name: '英山县', + code: '421124', + }, + { + name: '浠水县', + code: '421125', + }, + { + name: '蕲春县', + code: '421126', + }, + { + name: '黄梅县', + code: '421127', + }, + { + name: '麻城市', + code: '421181', + }, + { + name: '武穴市', + code: '421182', + }, + ], + }, + { + name: '咸宁市', + code: '421200', + children: [ + { + name: '咸安区', + code: '421202', + }, + { + name: '嘉鱼县', + code: '421221', + }, + { + name: '通城县', + code: '421222', + }, + { + name: '崇阳县', + code: '421223', + }, + { + name: '通山县', + code: '421224', + }, + { + name: '赤壁市', + code: '421281', + }, + ], + }, + { + name: '随州市', + code: '421300', + children: [ + { + name: '曾都区', + code: '421303', + }, + { + name: '随县', + code: '421321', + }, + { + name: '广水市', + code: '421381', + }, + ], + }, + { + name: '恩施土家族苗族自治州', + code: '422800', + children: [ + { + name: '恩施市', + code: '422801', + }, + { + name: '利川市', + code: '422802', + }, + { + name: '建始县', + code: '422822', + }, + { + name: '巴东县', + code: '422823', + }, + { + name: '宣恩县', + code: '422825', + }, + { + name: '咸丰县', + code: '422826', + }, + { + name: '来凤县', + code: '422827', + }, + { + name: '鹤峰县', + code: '422828', + }, + ], + }, + { + name: '直辖县', + code: '429000', + children: [ + { + name: '仙桃市', + code: '429004', + }, + { + name: '潜江市', + code: '429005', + }, + { + name: '天门市', + code: '429006', + }, + { + name: '神农架林区', + code: '429021', + }, + ], + }, + ], + }, + { + name: '湖南省', + code: '430000', + region: 'central', + children: [ + { + name: '长沙市', + code: '430100', + children: [ + { + name: '芙蓉区', + code: '430102', + }, + { + name: '天心区', + code: '430103', + }, + { + name: '岳麓区', + code: '430104', + }, + { + name: '开福区', + code: '430105', + }, + { + name: '雨花区', + code: '430111', + }, + { + name: '望城区', + code: '430112', + }, + { + name: '长沙县', + code: '430121', + }, + { + name: '浏阳市', + code: '430181', + }, + { + name: '宁乡市', + code: '430182', + }, + ], + }, + { + name: '株洲市', + code: '430200', + children: [ + { + name: '荷塘区', + code: '430202', + }, + { + name: '芦淞区', + code: '430203', + }, + { + name: '石峰区', + code: '430204', + }, + { + name: '天元区', + code: '430211', + }, + { + name: '渌口区', + code: '430212', + }, + { + name: '攸县', + code: '430223', + }, + { + name: '茶陵县', + code: '430224', + }, + { + name: '炎陵县', + code: '430225', + }, + { + name: '醴陵市', + code: '430281', + }, + ], + }, + { + name: '湘潭市', + code: '430300', + children: [ + { + name: '雨湖区', + code: '430302', + }, + { + name: '岳塘区', + code: '430304', + }, + { + name: '湘潭县', + code: '430321', + }, + { + name: '湘乡市', + code: '430381', + }, + { + name: '韶山市', + code: '430382', + }, + ], + }, + { + name: '衡阳市', + code: '430400', + children: [ + { + name: '珠晖区', + code: '430405', + }, + { + name: '雁峰区', + code: '430406', + }, + { + name: '石鼓区', + code: '430407', + }, + { + name: '蒸湘区', + code: '430408', + }, + { + name: '南岳区', + code: '430412', + }, + { + name: '衡阳县', + code: '430421', + }, + { + name: '衡南县', + code: '430422', + }, + { + name: '衡山县', + code: '430423', + }, + { + name: '衡东县', + code: '430424', + }, + { + name: '祁东县', + code: '430426', + }, + { + name: '耒阳市', + code: '430481', + }, + { + name: '常宁市', + code: '430482', + }, + ], + }, + { + name: '邵阳市', + code: '430500', + children: [ + { + name: '双清区', + code: '430502', + }, + { + name: '大祥区', + code: '430503', + }, + { + name: '北塔区', + code: '430511', + }, + { + name: '邵东县', + code: '430521', + }, + { + name: '新邵县', + code: '430522', + }, + { + name: '邵阳县', + code: '430523', + }, + { + name: '隆回县', + code: '430524', + }, + { + name: '洞口县', + code: '430525', + }, + { + name: '绥宁县', + code: '430527', + }, + { + name: '新宁县', + code: '430528', + }, + { + name: '城步苗族自治县', + code: '430529', + }, + { + name: '武冈市', + code: '430581', + }, + ], + }, + { + name: '岳阳市', + code: '430600', + children: [ + { + name: '岳阳楼区', + code: '430602', + }, + { + name: '云溪区', + code: '430603', + }, + { + name: '君山区', + code: '430611', + }, + { + name: '岳阳县', + code: '430621', + }, + { + name: '华容县', + code: '430623', + }, + { + name: '湘阴县', + code: '430624', + }, + { + name: '平江县', + code: '430626', + }, + { + name: '汨罗市', + code: '430681', + }, + { + name: '临湘市', + code: '430682', + }, + ], + }, + { + name: '常德市', + code: '430700', + children: [ + { + name: '武陵区', + code: '430702', + }, + { + name: '鼎城区', + code: '430703', + }, + { + name: '安乡县', + code: '430721', + }, + { + name: '汉寿县', + code: '430722', + }, + { + name: '澧县', + code: '430723', + }, + { + name: '临澧县', + code: '430724', + }, + { + name: '桃源县', + code: '430725', + }, + { + name: '石门县', + code: '430726', + }, + { + name: '津市市', + code: '430781', + }, + ], + }, + { + name: '张家界市', + code: '430800', + children: [ + { + name: '永定区', + code: '430802', + }, + { + name: '武陵源区', + code: '430811', + }, + { + name: '慈利县', + code: '430821', + }, + { + name: '桑植县', + code: '430822', + }, + ], + }, + { + name: '益阳市', + code: '430900', + children: [ + { + name: '资阳区', + code: '430902', + }, + { + name: '赫山区', + code: '430903', + }, + { + name: '南县', + code: '430921', + }, + { + name: '桃江县', + code: '430922', + }, + { + name: '安化县', + code: '430923', + }, + { + name: '沅江市', + code: '430981', + }, + ], + }, + { + name: '郴州市', + code: '431000', + children: [ + { + name: '北湖区', + code: '431002', + }, + { + name: '苏仙区', + code: '431003', + }, + { + name: '桂阳县', + code: '431021', + }, + { + name: '宜章县', + code: '431022', + }, + { + name: '永兴县', + code: '431023', + }, + { + name: '嘉禾县', + code: '431024', + }, + { + name: '临武县', + code: '431025', + }, + { + name: '汝城县', + code: '431026', + }, + { + name: '桂东县', + code: '431027', + }, + { + name: '安仁县', + code: '431028', + }, + { + name: '资兴市', + code: '431081', + }, + ], + }, + { + name: '永州市', + code: '431100', + children: [ + { + name: '零陵区', + code: '431102', + }, + { + name: '冷水滩区', + code: '431103', + }, + { + name: '祁阳县', + code: '431121', + }, + { + name: '东安县', + code: '431122', + }, + { + name: '双牌县', + code: '431123', + }, + { + name: '道县', + code: '431124', + }, + { + name: '江永县', + code: '431125', + }, + { + name: '宁远县', + code: '431126', + }, + { + name: '蓝山县', + code: '431127', + }, + { + name: '新田县', + code: '431128', + }, + { + name: '江华瑶族自治县', + code: '431129', + }, + ], + }, + { + name: '怀化市', + code: '431200', + children: [ + { + name: '鹤城区', + code: '431202', + }, + { + name: '中方县', + code: '431221', + }, + { + name: '沅陵县', + code: '431222', + }, + { + name: '辰溪县', + code: '431223', + }, + { + name: '溆浦县', + code: '431224', + }, + { + name: '会同县', + code: '431225', + }, + { + name: '麻阳苗族自治县', + code: '431226', + }, + { + name: '新晃侗族自治县', + code: '431227', + }, + { + name: '芷江侗族自治县', + code: '431228', + }, + { + name: '靖州苗族侗族自治县', + code: '431229', + }, + { + name: '通道侗族自治县', + code: '431230', + }, + { + name: '洪江市', + code: '431281', + }, + ], + }, + { + name: '娄底市', + code: '431300', + children: [ + { + name: '娄星区', + code: '431302', + }, + { + name: '双峰县', + code: '431321', + }, + { + name: '新化县', + code: '431322', + }, + { + name: '冷水江市', + code: '431381', + }, + { + name: '涟源市', + code: '431382', + }, + ], + }, + { + name: '湘西土家族苗族自治州', + code: '433100', + children: [ + { + name: '吉首市', + code: '433101', + }, + { + name: '泸溪县', + code: '433122', + }, + { + name: '凤凰县', + code: '433123', + }, + { + name: '花垣县', + code: '433124', + }, + { + name: '保靖县', + code: '433125', + }, + { + name: '古丈县', + code: '433126', + }, + { + name: '永顺县', + code: '433127', + }, + { + name: '龙山县', + code: '433130', + }, + ], + }, + ], + }, + { + name: '广东省', + code: '440000', + region: 'south', + children: [ + { + name: '广州市', + code: '440100', + children: [ + { + name: '荔湾区', + code: '440103', + }, + { + name: '越秀区', + code: '440104', + }, + { + name: '海珠区', + code: '440105', + }, + { + name: '天河区', + code: '440106', + }, + { + name: '白云区', + code: '440111', + }, + { + name: '黄埔区', + code: '440112', + }, + { + name: '番禺区', + code: '440113', + }, + { + name: '花都区', + code: '440114', + }, + { + name: '南沙区', + code: '440115', + }, + { + name: '从化区', + code: '440117', + }, + { + name: '增城区', + code: '440118', + }, + ], + }, + { + name: '韶关市', + code: '440200', + children: [ + { + name: '武江区', + code: '440203', + }, + { + name: '浈江区', + code: '440204', + }, + { + name: '曲江区', + code: '440205', + }, + { + name: '始兴县', + code: '440222', + }, + { + name: '仁化县', + code: '440224', + }, + { + name: '翁源县', + code: '440229', + }, + { + name: '乳源瑶族自治县', + code: '440232', + }, + { + name: '新丰县', + code: '440233', + }, + { + name: '乐昌市', + code: '440281', + }, + { + name: '南雄市', + code: '440282', + }, + ], + }, + { + name: '深圳市', + code: '440300', + children: [ + { + name: '罗湖区', + code: '440303', + }, + { + name: '福田区', + code: '440304', + }, + { + name: '南山区', + code: '440305', + }, + { + name: '宝安区', + code: '440306', + }, + { + name: '龙岗区', + code: '440307', + }, + { + name: '盐田区', + code: '440308', + }, + { + name: '龙华区', + code: '440309', + }, + { + name: '坪山区', + code: '440310', + }, + { + name: '光明区', + code: '440311', + }, + ], + }, + { + name: '珠海市', + code: '440400', + children: [ + { + name: '香洲区', + code: '440402', + }, + { + name: '斗门区', + code: '440403', + }, + { + name: '金湾区', + code: '440404', + }, + ], + }, + { + name: '汕头市', + code: '440500', + children: [ + { + name: '龙湖区', + code: '440507', + }, + { + name: '金平区', + code: '440511', + }, + { + name: '濠江区', + code: '440512', + }, + { + name: '潮阳区', + code: '440513', + }, + { + name: '潮南区', + code: '440514', + }, + { + name: '澄海区', + code: '440515', + }, + { + name: '南澳县', + code: '440523', + }, + ], + }, + { + name: '佛山市', + code: '440600', + children: [ + { + name: '禅城区', + code: '440604', + }, + { + name: '南海区', + code: '440605', + }, + { + name: '顺德区', + code: '440606', + }, + { + name: '三水区', + code: '440607', + }, + { + name: '高明区', + code: '440608', + }, + ], + }, + { + name: '江门市', + code: '440700', + children: [ + { + name: '蓬江区', + code: '440703', + }, + { + name: '江海区', + code: '440704', + }, + { + name: '新会区', + code: '440705', + }, + { + name: '台山市', + code: '440781', + }, + { + name: '开平市', + code: '440783', + }, + { + name: '鹤山市', + code: '440784', + }, + { + name: '恩平市', + code: '440785', + }, + ], + }, + { + name: '湛江市', + code: '440800', + children: [ + { + name: '赤坎区', + code: '440802', + }, + { + name: '霞山区', + code: '440803', + }, + { + name: '坡头区', + code: '440804', + }, + { + name: '麻章区', + code: '440811', + }, + { + name: '遂溪县', + code: '440823', + }, + { + name: '徐闻县', + code: '440825', + }, + { + name: '廉江市', + code: '440881', + }, + { + name: '雷州市', + code: '440882', + }, + { + name: '吴川市', + code: '440883', + }, + ], + }, + { + name: '茂名市', + code: '440900', + children: [ + { + name: '茂南区', + code: '440902', + }, + { + name: '电白区', + code: '440904', + }, + { + name: '高州市', + code: '440981', + }, + { + name: '化州市', + code: '440982', + }, + { + name: '信宜市', + code: '440983', + }, + ], + }, + { + name: '肇庆市', + code: '441200', + children: [ + { + name: '端州区', + code: '441202', + }, + { + name: '鼎湖区', + code: '441203', + }, + { + name: '高要区', + code: '441204', + }, + { + name: '广宁县', + code: '441223', + }, + { + name: '怀集县', + code: '441224', + }, + { + name: '封开县', + code: '441225', + }, + { + name: '德庆县', + code: '441226', + }, + { + name: '四会市', + code: '441284', + }, + ], + }, + { + name: '惠州市', + code: '441300', + children: [ + { + name: '惠城区', + code: '441302', + }, + { + name: '惠阳区', + code: '441303', + }, + { + name: '博罗县', + code: '441322', + }, + { + name: '惠东县', + code: '441323', + }, + { + name: '龙门县', + code: '441324', + }, + ], + }, + { + name: '梅州市', + code: '441400', + children: [ + { + name: '梅江区', + code: '441402', + }, + { + name: '梅县区', + code: '441403', + }, + { + name: '大埔县', + code: '441422', + }, + { + name: '丰顺县', + code: '441423', + }, + { + name: '五华县', + code: '441424', + }, + { + name: '平远县', + code: '441426', + }, + { + name: '蕉岭县', + code: '441427', + }, + { + name: '兴宁市', + code: '441481', + }, + ], + }, + { + name: '汕尾市', + code: '441500', + children: [ + { + name: '城区', + code: '441502', + }, + { + name: '海丰县', + code: '441521', + }, + { + name: '陆河县', + code: '441523', + }, + { + name: '陆丰市', + code: '441581', + }, + ], + }, + { + name: '河源市', + code: '441600', + children: [ + { + name: '源城区', + code: '441602', + }, + { + name: '紫金县', + code: '441621', + }, + { + name: '龙川县', + code: '441622', + }, + { + name: '连平县', + code: '441623', + }, + { + name: '和平县', + code: '441624', + }, + { + name: '东源县', + code: '441625', + }, + ], + }, + { + name: '阳江市', + code: '441700', + children: [ + { + name: '江城区', + code: '441702', + }, + { + name: '阳东区', + code: '441704', + }, + { + name: '阳西县', + code: '441721', + }, + { + name: '阳春市', + code: '441781', + }, + ], + }, + { + name: '清远市', + code: '441800', + children: [ + { + name: '清城区', + code: '441802', + }, + { + name: '清新区', + code: '441803', + }, + { + name: '佛冈县', + code: '441821', + }, + { + name: '阳山县', + code: '441823', + }, + { + name: '连山壮族瑶族自治县', + code: '441825', + }, + { + name: '连南瑶族自治县', + code: '441826', + }, + { + name: '英德市', + code: '441881', + }, + { + name: '连州市', + code: '441882', + }, + ], + }, + { + name: '东莞市', + code: '441900', + children: [], + }, + { + name: '中山市', + code: '442000', + children: [], + }, + { + name: '潮州市', + code: '445100', + children: [ + { + name: '湘桥区', + code: '445102', + }, + { + name: '潮安区', + code: '445103', + }, + { + name: '饶平县', + code: '445122', + }, + ], + }, + { + name: '揭阳市', + code: '445200', + children: [ + { + name: '榕城区', + code: '445202', + }, + { + name: '揭东区', + code: '445203', + }, + { + name: '揭西县', + code: '445222', + }, + { + name: '惠来县', + code: '445224', + }, + { + name: '普宁市', + code: '445281', + }, + ], + }, + { + name: '云浮市', + code: '445300', + children: [ + { + name: '云城区', + code: '445302', + }, + { + name: '云安区', + code: '445303', + }, + { + name: '新兴县', + code: '445321', + }, + { + name: '郁南县', + code: '445322', + }, + { + name: '罗定市', + code: '445381', + }, + ], + }, + ], + }, + { + name: '广西壮族自治区', + code: '450000', + region: 'south', + autonomousRegion: true, + children: [ + { + name: '南宁市', + code: '450100', + children: [ + { + name: '兴宁区', + code: '450102', + }, + { + name: '青秀区', + code: '450103', + }, + { + name: '江南区', + code: '450105', + }, + { + name: '西乡塘区', + code: '450107', + }, + { + name: '良庆区', + code: '450108', + }, + { + name: '邕宁区', + code: '450109', + }, + { + name: '武鸣区', + code: '450110', + }, + { + name: '隆安县', + code: '450123', + }, + { + name: '马山县', + code: '450124', + }, + { + name: '上林县', + code: '450125', + }, + { + name: '宾阳县', + code: '450126', + }, + { + name: '横县', + code: '450127', + }, + ], + }, + { + name: '柳州市', + code: '450200', + children: [ + { + name: '城中区', + code: '450202', + }, + { + name: '鱼峰区', + code: '450203', + }, + { + name: '柳南区', + code: '450204', + }, + { + name: '柳北区', + code: '450205', + }, + { + name: '柳江区', + code: '450206', + }, + { + name: '柳城县', + code: '450222', + }, + { + name: '鹿寨县', + code: '450223', + }, + { + name: '融安县', + code: '450224', + }, + { + name: '融水苗族自治县', + code: '450225', + }, + { + name: '三江侗族自治县', + code: '450226', + }, + ], + }, + { + name: '桂林市', + code: '450300', + children: [ + { + name: '秀峰区', + code: '450302', + }, + { + name: '叠彩区', + code: '450303', + }, + { + name: '象山区', + code: '450304', + }, + { + name: '七星区', + code: '450305', + }, + { + name: '雁山区', + code: '450311', + }, + { + name: '临桂区', + code: '450312', + }, + { + name: '阳朔县', + code: '450321', + }, + { + name: '灵川县', + code: '450323', + }, + { + name: '全州县', + code: '450324', + }, + { + name: '兴安县', + code: '450325', + }, + { + name: '永福县', + code: '450326', + }, + { + name: '灌阳县', + code: '450327', + }, + { + name: '龙胜各族自治县', + code: '450328', + }, + { + name: '资源县', + code: '450329', + }, + { + name: '平乐县', + code: '450330', + }, + { + name: '荔浦市', + code: '450381', + }, + { + name: '恭城瑶族自治县', + code: '450332', + }, + ], + }, + { + name: '梧州市', + code: '450400', + children: [ + { + name: '万秀区', + code: '450403', + }, + { + name: '长洲区', + code: '450405', + }, + { + name: '龙圩区', + code: '450406', + }, + { + name: '苍梧县', + code: '450421', + }, + { + name: '藤县', + code: '450422', + }, + { + name: '蒙山县', + code: '450423', + }, + { + name: '岑溪市', + code: '450481', + }, + ], + }, + { + name: '北海市', + code: '450500', + children: [ + { + name: '海城区', + code: '450502', + }, + { + name: '银海区', + code: '450503', + }, + { + name: '铁山港区', + code: '450512', + }, + { + name: '合浦县', + code: '450521', + }, + ], + }, + { + name: '防城港市', + code: '450600', + children: [ + { + name: '港口区', + code: '450602', + }, + { + name: '防城区', + code: '450603', + }, + { + name: '上思县', + code: '450621', + }, + { + name: '东兴市', + code: '450681', + }, + ], + }, + { + name: '钦州市', + code: '450700', + children: [ + { + name: '钦南区', + code: '450702', + }, + { + name: '钦北区', + code: '450703', + }, + { + name: '灵山县', + code: '450721', + }, + { + name: '浦北县', + code: '450722', + }, + ], + }, + { + name: '贵港市', + code: '450800', + children: [ + { + name: '港北区', + code: '450802', + }, + { + name: '港南区', + code: '450803', + }, + { + name: '覃塘区', + code: '450804', + }, + { + name: '平南县', + code: '450821', + }, + { + name: '桂平市', + code: '450881', + }, + ], + }, + { + name: '玉林市', + code: '450900', + children: [ + { + name: '玉州区', + code: '450902', + }, + { + name: '福绵区', + code: '450903', + }, + { + name: '容县', + code: '450921', + }, + { + name: '陆川县', + code: '450922', + }, + { + name: '博白县', + code: '450923', + }, + { + name: '兴业县', + code: '450924', + }, + { + name: '北流市', + code: '450981', + }, + ], + }, + { + name: '百色市', + code: '451000', + children: [ + { + name: '右江区', + code: '451002', + }, + { + name: '田阳县', + code: '451021', + }, + { + name: '田东县', + code: '451022', + }, + { + name: '平果县', + code: '451023', + }, + { + name: '德保县', + code: '451024', + }, + { + name: '那坡县', + code: '451026', + }, + { + name: '凌云县', + code: '451027', + }, + { + name: '乐业县', + code: '451028', + }, + { + name: '田林县', + code: '451029', + }, + { + name: '西林县', + code: '451030', + }, + { + name: '隆林各族自治县', + code: '451031', + }, + { + name: '靖西市', + code: '451081', + }, + ], + }, + { + name: '贺州市', + code: '451100', + children: [ + { + name: '八步区', + code: '451102', + }, + { + name: '平桂区', + code: '451103', + }, + { + name: '昭平县', + code: '451121', + }, + { + name: '钟山县', + code: '451122', + }, + { + name: '富川瑶族自治县', + code: '451123', + }, + ], + }, + { + name: '河池市', + code: '451200', + children: [ + { + name: '金城江区', + code: '451202', + }, + { + name: '宜州区', + code: '451203', + }, + { + name: '南丹县', + code: '451221', + }, + { + name: '天峨县', + code: '451222', + }, + { + name: '凤山县', + code: '451223', + }, + { + name: '东兰县', + code: '451224', + }, + { + name: '罗城仫佬族自治县', + code: '451225', + }, + { + name: '环江毛南族自治县', + code: '451226', + }, + { + name: '巴马瑶族自治县', + code: '451227', + }, + { + name: '都安瑶族自治县', + code: '451228', + }, + { + name: '大化瑶族自治县', + code: '451229', + }, + ], + }, + { + name: '来宾市', + code: '451300', + children: [ + { + name: '兴宾区', + code: '451302', + }, + { + name: '忻城县', + code: '451321', + }, + { + name: '象州县', + code: '451322', + }, + { + name: '武宣县', + code: '451323', + }, + { + name: '金秀瑶族自治县', + code: '451324', + }, + { + name: '合山市', + code: '451381', + }, + ], + }, + { + name: '崇左市', + code: '451400', + children: [ + { + name: '江州区', + code: '451402', + }, + { + name: '扶绥县', + code: '451421', + }, + { + name: '宁明县', + code: '451422', + }, + { + name: '龙州县', + code: '451423', + }, + { + name: '大新县', + code: '451424', + }, + { + name: '天等县', + code: '451425', + }, + { + name: '凭祥市', + code: '451481', + }, + ], + }, + ], + }, + { + name: '海南省', + code: '460000', + region: 'south', + children: [ + { + name: '海口市', + code: '460100', + children: [ + { + name: '秀英区', + code: '460105', + }, + { + name: '龙华区', + code: '460106', + }, + { + name: '琼山区', + code: '460107', + }, + { + name: '美兰区', + code: '460108', + }, + ], + }, + { + name: '三亚市', + code: '460200', + children: [ + { + name: '海棠区', + code: '460202', + }, + { + name: '吉阳区', + code: '460203', + }, + { + name: '天涯区', + code: '460204', + }, + { + name: '崖州区', + code: '460205', + }, + ], + }, + { + name: '三沙市', + code: '460300', + children: [], + }, + { + name: '儋州市', + code: '460400', + children: [], + }, + { + name: '直辖县', + code: '469000', + children: [ + { + name: '五指山市', + code: '469001', + }, + { + name: '琼海市', + code: '469002', + }, + { + name: '文昌市', + code: '469005', + }, + { + name: '万宁市', + code: '469006', + }, + { + name: '东方市', + code: '469007', + }, + { + name: '定安县', + code: '469021', + }, + { + name: '屯昌县', + code: '469022', + }, + { + name: '澄迈县', + code: '469023', + }, + { + name: '临高县', + code: '469024', + }, + { + name: '白沙黎族自治县', + code: '469025', + }, + { + name: '昌江黎族自治县', + code: '469026', + }, + { + name: '乐东黎族自治县', + code: '469027', + }, + { + name: '陵水黎族自治县', + code: '469028', + }, + { + name: '保亭黎族苗族自治县', + code: '469029', + }, + { + name: '琼中黎族苗族自治县', + code: '469030', + }, + ], + }, + ], + }, + { + name: '重庆市', + code: '500000', + region: 'southwest', + provinceLevelCity: true, + children: [ + { + name: '市辖区', + code: '500100', + children: [ + { + name: '万州区', + code: '500101', + }, + { + name: '涪陵区', + code: '500102', + }, + { + name: '渝中区', + code: '500103', + }, + { + name: '大渡口区', + code: '500104', + }, + { + name: '江北区', + code: '500105', + }, + { + name: '沙坪坝区', + code: '500106', + }, + { + name: '九龙坡区', + code: '500107', + }, + { + name: '南岸区', + code: '500108', + }, + { + name: '北碚区', + code: '500109', + }, + { + name: '綦江区', + code: '500110', + }, + { + name: '大足区', + code: '500111', + }, + { + name: '渝北区', + code: '500112', + }, + { + name: '巴南区', + code: '500113', + }, + { + name: '黔江区', + code: '500114', + }, + { + name: '长寿区', + code: '500115', + }, + { + name: '江津区', + code: '500116', + }, + { + name: '合川区', + code: '500117', + }, + { + name: '永川区', + code: '500118', + }, + { + name: '南川区', + code: '500119', + }, + { + name: '璧山区', + code: '500120', + }, + { + name: '铜梁区', + code: '500151', + }, + { + name: '潼南区', + code: '500152', + }, + { + name: '荣昌区', + code: '500153', + }, + { + name: '开州区', + code: '500154', + }, + { + name: '梁平区', + code: '500155', + }, + { + name: '武隆区', + code: '500156', + }, + ], + }, + { + name: '县辖区', + code: '500200', + children: [ + { + name: '城口县', + code: '500229', + }, + { + name: '丰都县', + code: '500230', + }, + { + name: '垫江县', + code: '500231', + }, + { + name: '忠县', + code: '500233', + }, + { + name: '云阳县', + code: '500235', + }, + { + name: '奉节县', + code: '500236', + }, + { + name: '巫山县', + code: '500237', + }, + { + name: '巫溪县', + code: '500238', + }, + { + name: '石柱土家族自治县', + code: '500240', + }, + { + name: '秀山土家族苗族自治县', + code: '500241', + }, + { + name: '酉阳土家族苗族自治县', + code: '500242', + }, + { + name: '彭水苗族土家族自治县', + code: '500243', + }, + ], + }, + ], + }, + { + name: '四川省', + code: '510000', + region: 'southwest', + children: [ + { + name: '成都市', + code: '510100', + children: [ + { + name: '锦江区', + code: '510104', + }, + { + name: '青羊区', + code: '510105', + }, + { + name: '金牛区', + code: '510106', + }, + { + name: '武侯区', + code: '510107', + }, + { + name: '成华区', + code: '510108', + }, + { + name: '龙泉驿区', + code: '510112', + }, + { + name: '青白江区', + code: '510113', + }, + { + name: '新都区', + code: '510114', + }, + { + name: '温江区', + code: '510115', + }, + { + name: '双流区', + code: '510116', + }, + { + name: '郫都区', + code: '510117', + }, + { + name: '金堂县', + code: '510121', + }, + { + name: '大邑县', + code: '510129', + }, + { + name: '蒲江县', + code: '510131', + }, + { + name: '新津县', + code: '510132', + }, + { + name: '都江堰市', + code: '510181', + }, + { + name: '彭州市', + code: '510182', + }, + { + name: '邛崃市', + code: '510183', + }, + { + name: '崇州市', + code: '510184', + }, + { + name: '简阳市', + code: '510185', + }, + ], + }, + { + name: '自贡市', + code: '510300', + children: [ + { + name: '自流井区', + code: '510302', + }, + { + name: '贡井区', + code: '510303', + }, + { + name: '大安区', + code: '510304', + }, + { + name: '沿滩区', + code: '510311', + }, + { + name: '荣县', + code: '510321', + }, + { + name: '富顺县', + code: '510322', + }, + ], + }, + { + name: '攀枝花市', + code: '510400', + children: [ + { + name: '东区', + code: '510402', + }, + { + name: '西区', + code: '510403', + }, + { + name: '仁和区', + code: '510411', + }, + { + name: '米易县', + code: '510421', + }, + { + name: '盐边县', + code: '510422', + }, + ], + }, + { + name: '泸州市', + code: '510500', + children: [ + { + name: '江阳区', + code: '510502', + }, + { + name: '纳溪区', + code: '510503', + }, + { + name: '龙马潭区', + code: '510504', + }, + { + name: '泸县', + code: '510521', + }, + { + name: '合江县', + code: '510522', + }, + { + name: '叙永县', + code: '510524', + }, + { + name: '古蔺县', + code: '510525', + }, + ], + }, + { + name: '德阳市', + code: '510600', + children: [ + { + name: '旌阳区', + code: '510603', + }, + { + name: '罗江区', + code: '510604', + }, + { + name: '中江县', + code: '510623', + }, + { + name: '广汉市', + code: '510681', + }, + { + name: '什邡市', + code: '510682', + }, + { + name: '绵竹市', + code: '510683', + }, + ], + }, + { + name: '绵阳市', + code: '510700', + children: [ + { + name: '涪城区', + code: '510703', + }, + { + name: '游仙区', + code: '510704', + }, + { + name: '安州区', + code: '510705', + }, + { + name: '三台县', + code: '510722', + }, + { + name: '盐亭县', + code: '510723', + }, + { + name: '梓潼县', + code: '510725', + }, + { + name: '北川羌族自治县', + code: '510726', + }, + { + name: '平武县', + code: '510727', + }, + { + name: '江油市', + code: '510781', + }, + ], + }, + { + name: '广元市', + code: '510800', + children: [ + { + name: '利州区', + code: '510802', + }, + { + name: '昭化区', + code: '510811', + }, + { + name: '朝天区', + code: '510812', + }, + { + name: '旺苍县', + code: '510821', + }, + { + name: '青川县', + code: '510822', + }, + { + name: '剑阁县', + code: '510823', + }, + { + name: '苍溪县', + code: '510824', + }, + ], + }, + { + name: '遂宁市', + code: '510900', + children: [ + { + name: '船山区', + code: '510903', + }, + { + name: '安居区', + code: '510904', + }, + { + name: '蓬溪县', + code: '510921', + }, + { + name: '射洪县', + code: '510922', + }, + { + name: '大英县', + code: '510923', + }, + ], + }, + { + name: '内江市', + code: '511000', + children: [ + { + name: '市中区', + code: '511002', + }, + { + name: '东兴区', + code: '511011', + }, + { + name: '威远县', + code: '511024', + }, + { + name: '资中县', + code: '511025', + }, + { + name: '隆昌市', + code: '511083', + }, + ], + }, + { + name: '乐山市', + code: '511100', + children: [ + { + name: '市中区', + code: '511102', + }, + { + name: '沙湾区', + code: '511111', + }, + { + name: '五通桥区', + code: '511112', + }, + { + name: '金口河区', + code: '511113', + }, + { + name: '犍为县', + code: '511123', + }, + { + name: '井研县', + code: '511124', + }, + { + name: '夹江县', + code: '511126', + }, + { + name: '沐川县', + code: '511129', + }, + { + name: '峨边彝族自治县', + code: '511132', + }, + { + name: '马边彝族自治县', + code: '511133', + }, + { + name: '峨眉山市', + code: '511181', + }, + ], + }, + { + name: '南充市', + code: '511300', + children: [ + { + name: '顺庆区', + code: '511302', + }, + { + name: '高坪区', + code: '511303', + }, + { + name: '嘉陵区', + code: '511304', + }, + { + name: '南部县', + code: '511321', + }, + { + name: '营山县', + code: '511322', + }, + { + name: '蓬安县', + code: '511323', + }, + { + name: '仪陇县', + code: '511324', + }, + { + name: '西充县', + code: '511325', + }, + { + name: '阆中市', + code: '511381', + }, + ], + }, + { + name: '眉山市', + code: '511400', + children: [ + { + name: '东坡区', + code: '511402', + }, + { + name: '彭山区', + code: '511403', + }, + { + name: '仁寿县', + code: '511421', + }, + { + name: '洪雅县', + code: '511423', + }, + { + name: '丹棱县', + code: '511424', + }, + { + name: '青神县', + code: '511425', + }, + ], + }, + { + name: '宜宾市', + code: '511500', + children: [ + { + name: '翠屏区', + code: '511502', + }, + { + name: '南溪区', + code: '511503', + }, + { + name: '叙州区', + code: '511504', + }, + { + name: '江安县', + code: '511523', + }, + { + name: '长宁县', + code: '511524', + }, + { + name: '高县', + code: '511525', + }, + { + name: '珙县', + code: '511526', + }, + { + name: '筠连县', + code: '511527', + }, + { + name: '兴文县', + code: '511528', + }, + { + name: '屏山县', + code: '511529', + }, + ], + }, + { + name: '广安市', + code: '511600', + children: [ + { + name: '广安区', + code: '511602', + }, + { + name: '前锋区', + code: '511603', + }, + { + name: '岳池县', + code: '511621', + }, + { + name: '武胜县', + code: '511622', + }, + { + name: '邻水县', + code: '511623', + }, + { + name: '华蓥市', + code: '511681', + }, + ], + }, + { + name: '达州市', + code: '511700', + children: [ + { + name: '通川区', + code: '511702', + }, + { + name: '达川区', + code: '511703', + }, + { + name: '宣汉县', + code: '511722', + }, + { + name: '开江县', + code: '511723', + }, + { + name: '大竹县', + code: '511724', + }, + { + name: '渠县', + code: '511725', + }, + { + name: '万源市', + code: '511781', + }, + ], + }, + { + name: '雅安市', + code: '511800', + children: [ + { + name: '雨城区', + code: '511802', + }, + { + name: '名山区', + code: '511803', + }, + { + name: '荥经县', + code: '511822', + }, + { + name: '汉源县', + code: '511823', + }, + { + name: '石棉县', + code: '511824', + }, + { + name: '天全县', + code: '511825', + }, + { + name: '芦山县', + code: '511826', + }, + { + name: '宝兴县', + code: '511827', + }, + ], + }, + { + name: '巴中市', + code: '511900', + children: [ + { + name: '巴州区', + code: '511902', + }, + { + name: '恩阳区', + code: '511903', + }, + { + name: '通江县', + code: '511921', + }, + { + name: '南江县', + code: '511922', + }, + { + name: '平昌县', + code: '511923', + }, + ], + }, + { + name: '资阳市', + code: '512000', + children: [ + { + name: '雁江区', + code: '512002', + }, + { + name: '安岳县', + code: '512021', + }, + { + name: '乐至县', + code: '512022', + }, + ], + }, + { + name: '阿坝藏族羌族自治州', + code: '513200', + children: [ + { + name: '马尔康市', + code: '513201', + }, + { + name: '汶川县', + code: '513221', + }, + { + name: '理县', + code: '513222', + }, + { + name: '茂县', + code: '513223', + }, + { + name: '松潘县', + code: '513224', + }, + { + name: '九寨沟县', + code: '513225', + }, + { + name: '金川县', + code: '513226', + }, + { + name: '小金县', + code: '513227', + }, + { + name: '黑水县', + code: '513228', + }, + { + name: '壤塘县', + code: '513230', + }, + { + name: '阿坝县', + code: '513231', + }, + { + name: '若尔盖县', + code: '513232', + }, + { + name: '红原县', + code: '513233', + }, + ], + }, + { + name: '甘孜藏族自治州', + code: '513300', + children: [ + { + name: '康定市', + code: '513301', + }, + { + name: '泸定县', + code: '513322', + }, + { + name: '丹巴县', + code: '513323', + }, + { + name: '九龙县', + code: '513324', + }, + { + name: '雅江县', + code: '513325', + }, + { + name: '道孚县', + code: '513326', + }, + { + name: '炉霍县', + code: '513327', + }, + { + name: '甘孜县', + code: '513328', + }, + { + name: '新龙县', + code: '513329', + }, + { + name: '德格县', + code: '513330', + }, + { + name: '白玉县', + code: '513331', + }, + { + name: '石渠县', + code: '513332', + }, + { + name: '色达县', + code: '513333', + }, + { + name: '理塘县', + code: '513334', + }, + { + name: '巴塘县', + code: '513335', + }, + { + name: '乡城县', + code: '513336', + }, + { + name: '稻城县', + code: '513337', + }, + { + name: '得荣县', + code: '513338', + }, + ], + }, + { + name: '凉山彝族自治州', + code: '513400', + children: [ + { + name: '西昌市', + code: '513401', + }, + { + name: '木里藏族自治县', + code: '513422', + }, + { + name: '盐源县', + code: '513423', + }, + { + name: '德昌县', + code: '513424', + }, + { + name: '会理县', + code: '513425', + }, + { + name: '会东县', + code: '513426', + }, + { + name: '宁南县', + code: '513427', + }, + { + name: '普格县', + code: '513428', + }, + { + name: '布拖县', + code: '513429', + }, + { + name: '金阳县', + code: '513430', + }, + { + name: '昭觉县', + code: '513431', + }, + { + name: '喜德县', + code: '513432', + }, + { + name: '冕宁县', + code: '513433', + }, + { + name: '越西县', + code: '513434', + }, + { + name: '甘洛县', + code: '513435', + }, + { + name: '美姑县', + code: '513436', + }, + { + name: '雷波县', + code: '513437', + }, + ], + }, + ], + }, + { + name: '贵州省', + code: '520000', + region: 'southwest', + children: [ + { + name: '贵阳市', + code: '520100', + children: [ + { + name: '南明区', + code: '520102', + }, + { + name: '云岩区', + code: '520103', + }, + { + name: '花溪区', + code: '520111', + }, + { + name: '乌当区', + code: '520112', + }, + { + name: '白云区', + code: '520113', + }, + { + name: '观山湖区', + code: '520115', + }, + { + name: '开阳县', + code: '520121', + }, + { + name: '息烽县', + code: '520122', + }, + { + name: '修文县', + code: '520123', + }, + { + name: '清镇市', + code: '520181', + }, + ], + }, + { + name: '六盘水市', + code: '520200', + children: [ + { + name: '钟山区', + code: '520201', + }, + { + name: '六枝特区', + code: '520203', + }, + { + name: '水城县', + code: '520221', + }, + { + name: '盘州市', + code: '520281', + }, + ], + }, + { + name: '遵义市', + code: '520300', + children: [ + { + name: '红花岗区', + code: '520302', + }, + { + name: '汇川区', + code: '520303', + }, + { + name: '播州区', + code: '520304', + }, + { + name: '桐梓县', + code: '520322', + }, + { + name: '绥阳县', + code: '520323', + }, + { + name: '正安县', + code: '520324', + }, + { + name: '道真仡佬族苗族自治县', + code: '520325', + }, + { + name: '务川仡佬族苗族自治县', + code: '520326', + }, + { + name: '凤冈县', + code: '520327', + }, + { + name: '湄潭县', + code: '520328', + }, + { + name: '余庆县', + code: '520329', + }, + { + name: '习水县', + code: '520330', + }, + { + name: '赤水市', + code: '520381', + }, + { + name: '仁怀市', + code: '520382', + }, + ], + }, + { + name: '安顺市', + code: '520400', + children: [ + { + name: '西秀区', + code: '520402', + }, + { + name: '平坝区', + code: '520403', + }, + { + name: '普定县', + code: '520422', + }, + { + name: '镇宁布依族苗族自治县', + code: '520423', + }, + { + name: '关岭布依族苗族自治县', + code: '520424', + }, + { + name: '紫云苗族布依族自治县', + code: '520425', + }, + ], + }, + { + name: '毕节市', + code: '520500', + children: [ + { + name: '七星关区', + code: '520502', + }, + { + name: '大方县', + code: '520521', + }, + { + name: '黔西县', + code: '520522', + }, + { + name: '金沙县', + code: '520523', + }, + { + name: '织金县', + code: '520524', + }, + { + name: '纳雍县', + code: '520525', + }, + { + name: '威宁彝族回族苗族自治县', + code: '520526', + }, + { + name: '赫章县', + code: '520527', + }, + ], + }, + { + name: '铜仁市', + code: '520600', + children: [ + { + name: '碧江区', + code: '520602', + }, + { + name: '万山区', + code: '520603', + }, + { + name: '江口县', + code: '520621', + }, + { + name: '玉屏侗族自治县', + code: '520622', + }, + { + name: '石阡县', + code: '520623', + }, + { + name: '思南县', + code: '520624', + }, + { + name: '印江土家族苗族自治县', + code: '520625', + }, + { + name: '德江县', + code: '520626', + }, + { + name: '沿河土家族自治县', + code: '520627', + }, + { + name: '松桃苗族自治县', + code: '520628', + }, + ], + }, + { + name: '黔西南布依族苗族自治州', + code: '522300', + children: [ + { + name: '兴义市', + code: '522301', + }, + { + name: '兴仁市', + code: '522302', + }, + { + name: '普安县', + code: '522323', + }, + { + name: '晴隆县', + code: '522324', + }, + { + name: '贞丰县', + code: '522325', + }, + { + name: '望谟县', + code: '522326', + }, + { + name: '册亨县', + code: '522327', + }, + { + name: '安龙县', + code: '522328', + }, + ], + }, + { + name: '黔东南苗族侗族自治州', + code: '522600', + children: [ + { + name: '凯里市', + code: '522601', + }, + { + name: '黄平县', + code: '522622', + }, + { + name: '施秉县', + code: '522623', + }, + { + name: '三穗县', + code: '522624', + }, + { + name: '镇远县', + code: '522625', + }, + { + name: '岑巩县', + code: '522626', + }, + { + name: '天柱县', + code: '522627', + }, + { + name: '锦屏县', + code: '522628', + }, + { + name: '剑河县', + code: '522629', + }, + { + name: '台江县', + code: '522630', + }, + { + name: '黎平县', + code: '522631', + }, + { + name: '榕江县', + code: '522632', + }, + { + name: '从江县', + code: '522633', + }, + { + name: '雷山县', + code: '522634', + }, + { + name: '麻江县', + code: '522635', + }, + { + name: '丹寨县', + code: '522636', + }, + ], + }, + { + name: '黔南布依族苗族自治州', + code: '522700', + children: [ + { + name: '都匀市', + code: '522701', + }, + { + name: '福泉市', + code: '522702', + }, + { + name: '荔波县', + code: '522722', + }, + { + name: '贵定县', + code: '522723', + }, + { + name: '瓮安县', + code: '522725', + }, + { + name: '独山县', + code: '522726', + }, + { + name: '平塘县', + code: '522727', + }, + { + name: '罗甸县', + code: '522728', + }, + { + name: '长顺县', + code: '522729', + }, + { + name: '龙里县', + code: '522730', + }, + { + name: '惠水县', + code: '522731', + }, + { + name: '三都水族自治县', + code: '522732', + }, + ], + }, + ], + }, + { + name: '云南省', + code: '530000', + region: 'southwest', + children: [ + { + name: '昆明市', + code: '530100', + children: [ + { + name: '五华区', + code: '530102', + }, + { + name: '盘龙区', + code: '530103', + }, + { + name: '官渡区', + code: '530111', + }, + { + name: '西山区', + code: '530112', + }, + { + name: '东川区', + code: '530113', + }, + { + name: '呈贡区', + code: '530114', + }, + { + name: '晋宁区', + code: '530115', + }, + { + name: '富民县', + code: '530124', + }, + { + name: '宜良县', + code: '530125', + }, + { + name: '石林彝族自治县', + code: '530126', + }, + { + name: '嵩明县', + code: '530127', + }, + { + name: '禄劝彝族苗族自治县', + code: '530128', + }, + { + name: '寻甸回族彝族自治县', + code: '530129', + }, + { + name: '安宁市', + code: '530181', + }, + ], + }, + { + name: '曲靖市', + code: '530300', + children: [ + { + name: '麒麟区', + code: '530302', + }, + { + name: '沾益区', + code: '530303', + }, + { + name: '马龙区', + code: '530304', + }, + { + name: '陆良县', + code: '530322', + }, + { + name: '师宗县', + code: '530323', + }, + { + name: '罗平县', + code: '530324', + }, + { + name: '富源县', + code: '530325', + }, + { + name: '会泽县', + code: '530326', + }, + { + name: '宣威市', + code: '530381', + }, + ], + }, + { + name: '玉溪市', + code: '530400', + children: [ + { + name: '红塔区', + code: '530402', + }, + { + name: '江川区', + code: '530403', + }, + { + name: '澄江县', + code: '530422', + }, + { + name: '通海县', + code: '530423', + }, + { + name: '华宁县', + code: '530424', + }, + { + name: '易门县', + code: '530425', + }, + { + name: '峨山彝族自治县', + code: '530426', + }, + { + name: '新平彝族傣族自治县', + code: '530427', + }, + { + name: '元江哈尼族彝族傣族自治县', + code: '530428', + }, + ], + }, + { + name: '保山市', + code: '530500', + children: [ + { + name: '隆阳区', + code: '530502', + }, + { + name: '施甸县', + code: '530521', + }, + { + name: '龙陵县', + code: '530523', + }, + { + name: '昌宁县', + code: '530524', + }, + { + name: '腾冲市', + code: '530581', + }, + ], + }, + { + name: '昭通市', + code: '530600', + children: [ + { + name: '昭阳区', + code: '530602', + }, + { + name: '鲁甸县', + code: '530621', + }, + { + name: '巧家县', + code: '530622', + }, + { + name: '盐津县', + code: '530623', + }, + { + name: '大关县', + code: '530624', + }, + { + name: '永善县', + code: '530625', + }, + { + name: '绥江县', + code: '530626', + }, + { + name: '镇雄县', + code: '530627', + }, + { + name: '彝良县', + code: '530628', + }, + { + name: '威信县', + code: '530629', + }, + { + name: '水富市', + code: '530681', + }, + ], + }, + { + name: '丽江市', + code: '530700', + children: [ + { + name: '古城区', + code: '530702', + }, + { + name: '玉龙纳西族自治县', + code: '530721', + }, + { + name: '永胜县', + code: '530722', + }, + { + name: '华坪县', + code: '530723', + }, + { + name: '宁蒗彝族自治县', + code: '530724', + }, + ], + }, + { + name: '普洱市', + code: '530800', + children: [ + { + name: '思茅区', + code: '530802', + }, + { + name: '宁洱哈尼族彝族自治县', + code: '530821', + }, + { + name: '墨江哈尼族自治县', + code: '530822', + }, + { + name: '景东彝族自治县', + code: '530823', + }, + { + name: '景谷傣族彝族自治县', + code: '530824', + }, + { + name: '镇沅彝族哈尼族拉祜族自治县', + code: '530825', + }, + { + name: '江城哈尼族彝族自治县', + code: '530826', + }, + { + name: '孟连傣族拉祜族佤族自治县', + code: '530827', + }, + { + name: '澜沧拉祜族自治县', + code: '530828', + }, + { + name: '西盟佤族自治县', + code: '530829', + }, + ], + }, + { + name: '临沧市', + code: '530900', + children: [ + { + name: '临翔区', + code: '530902', + }, + { + name: '凤庆县', + code: '530921', + }, + { + name: '云县', + code: '530922', + }, + { + name: '永德县', + code: '530923', + }, + { + name: '镇康县', + code: '530924', + }, + { + name: '双江拉祜族佤族布朗族傣族自治县', + code: '530925', + }, + { + name: '耿马傣族佤族自治县', + code: '530926', + }, + { + name: '沧源佤族自治县', + code: '530927', + }, + ], + }, + { + name: '楚雄彝族自治州', + code: '532300', + children: [ + { + name: '楚雄市', + code: '532301', + }, + { + name: '双柏县', + code: '532322', + }, + { + name: '牟定县', + code: '532323', + }, + { + name: '南华县', + code: '532324', + }, + { + name: '姚安县', + code: '532325', + }, + { + name: '大姚县', + code: '532326', + }, + { + name: '永仁县', + code: '532327', + }, + { + name: '元谋县', + code: '532328', + }, + { + name: '武定县', + code: '532329', + }, + { + name: '禄丰县', + code: '532331', + }, + ], + }, + { + name: '红河哈尼族彝族自治州', + code: '532500', + children: [ + { + name: '个旧市', + code: '532501', + }, + { + name: '开远市', + code: '532502', + }, + { + name: '蒙自市', + code: '532503', + }, + { + name: '弥勒市', + code: '532504', + }, + { + name: '屏边苗族自治县', + code: '532523', + }, + { + name: '建水县', + code: '532524', + }, + { + name: '石屏县', + code: '532525', + }, + { + name: '泸西县', + code: '532527', + }, + { + name: '元阳县', + code: '532528', + }, + { + name: '红河县', + code: '532529', + }, + { + name: '金平苗族瑶族傣族自治县', + code: '532530', + }, + { + name: '绿春县', + code: '532531', + }, + { + name: '河口瑶族自治县', + code: '532532', + }, + ], + }, + { + name: '文山壮族苗族自治州', + code: '532600', + children: [ + { + name: '文山市', + code: '532601', + }, + { + name: '砚山县', + code: '532622', + }, + { + name: '西畴县', + code: '532623', + }, + { + name: '麻栗坡县', + code: '532624', + }, + { + name: '马关县', + code: '532625', + }, + { + name: '丘北县', + code: '532626', + }, + { + name: '广南县', + code: '532627', + }, + { + name: '富宁县', + code: '532628', + }, + ], + }, + { + name: '西双版纳傣族自治州', + code: '532800', + children: [ + { + name: '景洪市', + code: '532801', + }, + { + name: '勐海县', + code: '532822', + }, + { + name: '勐腊县', + code: '532823', + }, + ], + }, + { + name: '大理白族自治州', + code: '532900', + children: [ + { + name: '大理市', + code: '532901', + }, + { + name: '漾濞彝族自治县', + code: '532922', + }, + { + name: '祥云县', + code: '532923', + }, + { + name: '宾川县', + code: '532924', + }, + { + name: '弥渡县', + code: '532925', + }, + { + name: '南涧彝族自治县', + code: '532926', + }, + { + name: '巍山彝族回族自治县', + code: '532927', + }, + { + name: '永平县', + code: '532928', + }, + { + name: '云龙县', + code: '532929', + }, + { + name: '洱源县', + code: '532930', + }, + { + name: '剑川县', + code: '532931', + }, + { + name: '鹤庆县', + code: '532932', + }, + ], + }, + { + name: '德宏傣族景颇族自治州', + code: '533100', + children: [ + { + name: '瑞丽市', + code: '533102', + }, + { + name: '芒市', + code: '533103', + }, + { + name: '梁河县', + code: '533122', + }, + { + name: '盈江县', + code: '533123', + }, + { + name: '陇川县', + code: '533124', + }, + ], + }, + { + name: '怒江傈僳族自治州', + code: '533300', + children: [ + { + name: '泸水市', + code: '533301', + }, + { + name: '福贡县', + code: '533323', + }, + { + name: '贡山独龙族怒族自治县', + code: '533324', + }, + { + name: '兰坪白族普米族自治县', + code: '533325', + }, + ], + }, + { + name: '迪庆藏族自治州', + code: '533400', + children: [ + { + name: '香格里拉市', + code: '533401', + }, + { + name: '德钦县', + code: '533422', + }, + { + name: '维西傈僳族自治县', + code: '533423', + }, + ], + }, + ], + }, + { + name: '西藏自治区', + code: '540000', + region: 'southwest', + autonomousRegion: true, + children: [ + { + name: '拉萨市', + code: '540100', + children: [ + { + name: '城关区', + code: '540102', + }, + { + name: '堆龙德庆区', + code: '540103', + }, + { + name: '达孜区', + code: '540104', + }, + { + name: '林周县', + code: '540121', + }, + { + name: '当雄县', + code: '540122', + }, + { + name: '尼木县', + code: '540123', + }, + { + name: '曲水县', + code: '540124', + }, + { + name: '墨竹工卡县', + code: '540127', + }, + ], + }, + { + name: '日喀则市', + code: '540200', + children: [ + { + name: '桑珠孜区', + code: '540202', + }, + { + name: '南木林县', + code: '540221', + }, + { + name: '江孜县', + code: '540222', + }, + { + name: '定日县', + code: '540223', + }, + { + name: '萨迦县', + code: '540224', + }, + { + name: '拉孜县', + code: '540225', + }, + { + name: '昂仁县', + code: '540226', + }, + { + name: '谢通门县', + code: '540227', + }, + { + name: '白朗县', + code: '540228', + }, + { + name: '仁布县', + code: '540229', + }, + { + name: '康马县', + code: '540230', + }, + { + name: '定结县', + code: '540231', + }, + { + name: '仲巴县', + code: '540232', + }, + { + name: '亚东县', + code: '540233', + }, + { + name: '吉隆县', + code: '540234', + }, + { + name: '聂拉木县', + code: '540235', + }, + { + name: '萨嘎县', + code: '540236', + }, + { + name: '岗巴县', + code: '540237', + }, + ], + }, + { + name: '昌都市', + code: '540300', + children: [ + { + name: '卡若区', + code: '540302', + }, + { + name: '江达县', + code: '540321', + }, + { + name: '贡觉县', + code: '540322', + }, + { + name: '类乌齐县', + code: '540323', + }, + { + name: '丁青县', + code: '540324', + }, + { + name: '察雅县', + code: '540325', + }, + { + name: '八宿县', + code: '540326', + }, + { + name: '左贡县', + code: '540327', + }, + { + name: '芒康县', + code: '540328', + }, + { + name: '洛隆县', + code: '540329', + }, + { + name: '边坝县', + code: '540330', + }, + ], + }, + { + name: '林芝市', + code: '540400', + children: [ + { + name: '巴宜区', + code: '540402', + }, + { + name: '工布江达县', + code: '540421', + }, + { + name: '米林县', + code: '540422', + }, + { + name: '墨脱县', + code: '540423', + }, + { + name: '波密县', + code: '540424', + }, + { + name: '察隅县', + code: '540425', + }, + { + name: '朗县', + code: '540426', + }, + ], + }, + { + name: '山南市', + code: '540500', + children: [ + { + name: '乃东区', + code: '540502', + }, + { + name: '扎囊县', + code: '540521', + }, + { + name: '贡嘎县', + code: '540522', + }, + { + name: '桑日县', + code: '540523', + }, + { + name: '琼结县', + code: '540524', + }, + { + name: '曲松县', + code: '540525', + }, + { + name: '措美县', + code: '540526', + }, + { + name: '洛扎县', + code: '540527', + }, + { + name: '加查县', + code: '540528', + }, + { + name: '隆子县', + code: '540529', + }, + { + name: '错那县', + code: '540530', + }, + { + name: '浪卡子县', + code: '540531', + }, + ], + }, + { + name: '那曲市', + code: '540600', + children: [ + { + name: '色尼区', + code: '540602', + }, + { + name: '嘉黎县', + code: '540621', + }, + { + name: '比如县', + code: '540622', + }, + { + name: '聂荣县', + code: '540623', + }, + { + name: '安多县', + code: '540624', + }, + { + name: '申扎县', + code: '540625', + }, + { + name: '索县', + code: '540626', + }, + { + name: '班戈县', + code: '540627', + }, + { + name: '巴青县', + code: '540628', + }, + { + name: '尼玛县', + code: '540629', + }, + { + name: '双湖县', + code: '540630', + }, + ], + }, + { + name: '阿里地区', + code: '542500', + children: [ + { + name: '普兰县', + code: '542521', + }, + { + name: '札达县', + code: '542522', + }, + { + name: '噶尔县', + code: '542523', + }, + { + name: '日土县', + code: '542524', + }, + { + name: '革吉县', + code: '542525', + }, + { + name: '改则县', + code: '542526', + }, + { + name: '措勤县', + code: '542527', + }, + ], + }, + ], + }, + { + name: '陕西省', + code: '610000', + region: 'northwest', + children: [ + { + name: '西安市', + code: '610100', + children: [ + { + name: '新城区', + code: '610102', + }, + { + name: '碑林区', + code: '610103', + }, + { + name: '莲湖区', + code: '610104', + }, + { + name: '灞桥区', + code: '610111', + }, + { + name: '未央区', + code: '610112', + }, + { + name: '雁塔区', + code: '610113', + }, + { + name: '阎良区', + code: '610114', + }, + { + name: '临潼区', + code: '610115', + }, + { + name: '长安区', + code: '610116', + }, + { + name: '高陵区', + code: '610117', + }, + { + name: '鄠邑区', + code: '610118', + }, + { + name: '蓝田县', + code: '610122', + }, + { + name: '周至县', + code: '610124', + }, + ], + }, + { + name: '铜川市', + code: '610200', + children: [ + { + name: '王益区', + code: '610202', + }, + { + name: '印台区', + code: '610203', + }, + { + name: '耀州区', + code: '610204', + }, + { + name: '宜君县', + code: '610222', + }, + ], + }, + { + name: '宝鸡市', + code: '610300', + children: [ + { + name: '渭滨区', + code: '610302', + }, + { + name: '金台区', + code: '610303', + }, + { + name: '陈仓区', + code: '610304', + }, + { + name: '凤翔县', + code: '610322', + }, + { + name: '岐山县', + code: '610323', + }, + { + name: '扶风县', + code: '610324', + }, + { + name: '眉县', + code: '610326', + }, + { + name: '陇县', + code: '610327', + }, + { + name: '千阳县', + code: '610328', + }, + { + name: '麟游县', + code: '610329', + }, + { + name: '凤县', + code: '610330', + }, + { + name: '太白县', + code: '610331', + }, + ], + }, + { + name: '咸阳市', + code: '610400', + children: [ + { + name: '秦都区', + code: '610402', + }, + { + name: '杨陵区', + code: '610403', + }, + { + name: '渭城区', + code: '610404', + }, + { + name: '三原县', + code: '610422', + }, + { + name: '泾阳县', + code: '610423', + }, + { + name: '乾县', + code: '610424', + }, + { + name: '礼泉县', + code: '610425', + }, + { + name: '永寿县', + code: '610426', + }, + { + name: '长武县', + code: '610428', + }, + { + name: '旬邑县', + code: '610429', + }, + { + name: '淳化县', + code: '610430', + }, + { + name: '武功县', + code: '610431', + }, + { + name: '兴平市', + code: '610481', + }, + { + name: '彬州市', + code: '610482', + }, + ], + }, + { + name: '渭南市', + code: '610500', + children: [ + { + name: '临渭区', + code: '610502', + }, + { + name: '华州区', + code: '610503', + }, + { + name: '潼关县', + code: '610522', + }, + { + name: '大荔县', + code: '610523', + }, + { + name: '合阳县', + code: '610524', + }, + { + name: '澄城县', + code: '610525', + }, + { + name: '蒲城县', + code: '610526', + }, + { + name: '白水县', + code: '610527', + }, + { + name: '富平县', + code: '610528', + }, + { + name: '韩城市', + code: '610581', + }, + { + name: '华阴市', + code: '610582', + }, + ], + }, + { + name: '延安市', + code: '610600', + children: [ + { + name: '宝塔区', + code: '610602', + }, + { + name: '安塞区', + code: '610603', + }, + { + name: '延长县', + code: '610621', + }, + { + name: '延川县', + code: '610622', + }, + { + name: '子长县', + code: '610623', + }, + { + name: '志丹县', + code: '610625', + }, + { + name: '吴起县', + code: '610626', + }, + { + name: '甘泉县', + code: '610627', + }, + { + name: '富县', + code: '610628', + }, + { + name: '洛川县', + code: '610629', + }, + { + name: '宜川县', + code: '610630', + }, + { + name: '黄龙县', + code: '610631', + }, + { + name: '黄陵县', + code: '610632', + }, + ], + }, + { + name: '汉中市', + code: '610700', + children: [ + { + name: '汉台区', + code: '610702', + }, + { + name: '南郑区', + code: '610703', + }, + { + name: '城固县', + code: '610722', + }, + { + name: '洋县', + code: '610723', + }, + { + name: '西乡县', + code: '610724', + }, + { + name: '勉县', + code: '610725', + }, + { + name: '宁强县', + code: '610726', + }, + { + name: '略阳县', + code: '610727', + }, + { + name: '镇巴县', + code: '610728', + }, + { + name: '留坝县', + code: '610729', + }, + { + name: '佛坪县', + code: '610730', + }, + ], + }, + { + name: '榆林市', + code: '610800', + children: [ + { + name: '榆阳区', + code: '610802', + }, + { + name: '横山区', + code: '610803', + }, + { + name: '府谷县', + code: '610822', + }, + { + name: '靖边县', + code: '610824', + }, + { + name: '定边县', + code: '610825', + }, + { + name: '绥德县', + code: '610826', + }, + { + name: '米脂县', + code: '610827', + }, + { + name: '佳县', + code: '610828', + }, + { + name: '吴堡县', + code: '610829', + }, + { + name: '清涧县', + code: '610830', + }, + { + name: '子洲县', + code: '610831', + }, + { + name: '神木市', + code: '610881', + }, + ], + }, + { + name: '安康市', + code: '610900', + children: [ + { + name: '汉滨区', + code: '610902', + }, + { + name: '汉阴县', + code: '610921', + }, + { + name: '石泉县', + code: '610922', + }, + { + name: '宁陕县', + code: '610923', + }, + { + name: '紫阳县', + code: '610924', + }, + { + name: '岚皋县', + code: '610925', + }, + { + name: '平利县', + code: '610926', + }, + { + name: '镇坪县', + code: '610927', + }, + { + name: '旬阳县', + code: '610928', + }, + { + name: '白河县', + code: '610929', + }, + ], + }, + { + name: '商洛市', + code: '611000', + children: [ + { + name: '商州区', + code: '611002', + }, + { + name: '洛南县', + code: '611021', + }, + { + name: '丹凤县', + code: '611022', + }, + { + name: '商南县', + code: '611023', + }, + { + name: '山阳县', + code: '611024', + }, + { + name: '镇安县', + code: '611025', + }, + { + name: '柞水县', + code: '611026', + }, + ], + }, + ], + }, + { + name: '甘肃省', + code: '620000', + region: 'northwest', + children: [ + { + name: '兰州市', + code: '620100', + children: [ + { + name: '城关区', + code: '620102', + }, + { + name: '七里河区', + code: '620103', + }, + { + name: '西固区', + code: '620104', + }, + { + name: '安宁区', + code: '620105', + }, + { + name: '红古区', + code: '620111', + }, + { + name: '永登县', + code: '620121', + }, + { + name: '皋兰县', + code: '620122', + }, + { + name: '榆中县', + code: '620123', + }, + ], + }, + { + name: '嘉峪关市', + code: '620200', + children: [], + }, + { + name: '金昌市', + code: '620300', + children: [ + { + name: '金川区', + code: '620302', + }, + { + name: '永昌县', + code: '620321', + }, + ], + }, + { + name: '白银市', + code: '620400', + children: [ + { + name: '白银区', + code: '620402', + }, + { + name: '平川区', + code: '620403', + }, + { + name: '靖远县', + code: '620421', + }, + { + name: '会宁县', + code: '620422', + }, + { + name: '景泰县', + code: '620423', + }, + ], + }, + { + name: '天水市', + code: '620500', + children: [ + { + name: '秦州区', + code: '620502', + }, + { + name: '麦积区', + code: '620503', + }, + { + name: '清水县', + code: '620521', + }, + { + name: '秦安县', + code: '620522', + }, + { + name: '甘谷县', + code: '620523', + }, + { + name: '武山县', + code: '620524', + }, + { + name: '张家川回族自治县', + code: '620525', + }, + ], + }, + { + name: '武威市', + code: '620600', + children: [ + { + name: '凉州区', + code: '620602', + }, + { + name: '民勤县', + code: '620621', + }, + { + name: '古浪县', + code: '620622', + }, + { + name: '天祝藏族自治县', + code: '620623', + }, + ], + }, + { + name: '张掖市', + code: '620700', + children: [ + { + name: '甘州区', + code: '620702', + }, + { + name: '肃南裕固族自治县', + code: '620721', + }, + { + name: '民乐县', + code: '620722', + }, + { + name: '临泽县', + code: '620723', + }, + { + name: '高台县', + code: '620724', + }, + { + name: '山丹县', + code: '620725', + }, + ], + }, + { + name: '平凉市', + code: '620800', + children: [ + { + name: '崆峒区', + code: '620802', + }, + { + name: '泾川县', + code: '620821', + }, + { + name: '灵台县', + code: '620822', + }, + { + name: '崇信县', + code: '620823', + }, + { + name: '庄浪县', + code: '620825', + }, + { + name: '静宁县', + code: '620826', + }, + { + name: '华亭市', + code: '620881', + }, + ], + }, + { + name: '酒泉市', + code: '620900', + children: [ + { + name: '肃州区', + code: '620902', + }, + { + name: '金塔县', + code: '620921', + }, + { + name: '瓜州县', + code: '620922', + }, + { + name: '肃北蒙古族自治县', + code: '620923', + }, + { + name: '阿克塞哈萨克族自治县', + code: '620924', + }, + { + name: '玉门市', + code: '620981', + }, + { + name: '敦煌市', + code: '620982', + }, + ], + }, + { + name: '庆阳市', + code: '621000', + children: [ + { + name: '西峰区', + code: '621002', + }, + { + name: '庆城县', + code: '621021', + }, + { + name: '环县', + code: '621022', + }, + { + name: '华池县', + code: '621023', + }, + { + name: '合水县', + code: '621024', + }, + { + name: '正宁县', + code: '621025', + }, + { + name: '宁县', + code: '621026', + }, + { + name: '镇原县', + code: '621027', + }, + ], + }, + { + name: '定西市', + code: '621100', + children: [ + { + name: '安定区', + code: '621102', + }, + { + name: '通渭县', + code: '621121', + }, + { + name: '陇西县', + code: '621122', + }, + { + name: '渭源县', + code: '621123', + }, + { + name: '临洮县', + code: '621124', + }, + { + name: '漳县', + code: '621125', + }, + { + name: '岷县', + code: '621126', + }, + ], + }, + { + name: '陇南市', + code: '621200', + children: [ + { + name: '武都区', + code: '621202', + }, + { + name: '成县', + code: '621221', + }, + { + name: '文县', + code: '621222', + }, + { + name: '宕昌县', + code: '621223', + }, + { + name: '康县', + code: '621224', + }, + { + name: '西和县', + code: '621225', + }, + { + name: '礼县', + code: '621226', + }, + { + name: '徽县', + code: '621227', + }, + { + name: '两当县', + code: '621228', + }, + ], + }, + { + name: '临夏回族自治州', + code: '622900', + children: [ + { + name: '临夏市', + code: '622901', + }, + { + name: '临夏县', + code: '622921', + }, + { + name: '康乐县', + code: '622922', + }, + { + name: '永靖县', + code: '622923', + }, + { + name: '广河县', + code: '622924', + }, + { + name: '和政县', + code: '622925', + }, + { + name: '东乡族自治县', + code: '622926', + }, + { + name: '积石山保安族东乡族撒拉族自治县', + code: '622927', + }, + ], + }, + { + name: '甘南藏族自治州', + code: '623000', + children: [ + { + name: '合作市', + code: '623001', + }, + { + name: '临潭县', + code: '623021', + }, + { + name: '卓尼县', + code: '623022', + }, + { + name: '舟曲县', + code: '623023', + }, + { + name: '迭部县', + code: '623024', + }, + { + name: '玛曲县', + code: '623025', + }, + { + name: '碌曲县', + code: '623026', + }, + { + name: '夏河县', + code: '623027', + }, + ], + }, + ], + }, + { + name: '青海省', + code: '630000', + region: 'northwest', + children: [ + { + name: '西宁市', + code: '630100', + children: [ + { + name: '城东区', + code: '630102', + }, + { + name: '城中区', + code: '630103', + }, + { + name: '城西区', + code: '630104', + }, + { + name: '城北区', + code: '630105', + }, + { + name: '大通回族土族自治县', + code: '630121', + }, + { + name: '湟中县', + code: '630122', + }, + { + name: '湟源县', + code: '630123', + }, + ], + }, + { + name: '海东市', + code: '630200', + children: [ + { + name: '乐都区', + code: '630202', + }, + { + name: '平安区', + code: '630203', + }, + { + name: '民和回族土族自治县', + code: '630222', + }, + { + name: '互助土族自治县', + code: '630223', + }, + { + name: '化隆回族自治县', + code: '630224', + }, + { + name: '循化撒拉族自治县', + code: '630225', + }, + ], + }, + { + name: '海北藏族自治州', + code: '632200', + children: [ + { + name: '门源回族自治县', + code: '632221', + }, + { + name: '祁连县', + code: '632222', + }, + { + name: '海晏县', + code: '632223', + }, + { + name: '刚察县', + code: '632224', + }, + ], + }, + { + name: '黄南藏族自治州', + code: '632300', + children: [ + { + name: '同仁县', + code: '632321', + }, + { + name: '尖扎县', + code: '632322', + }, + { + name: '泽库县', + code: '632323', + }, + { + name: '河南蒙古族自治县', + code: '632324', + }, + ], + }, + { + name: '海南藏族自治州', + code: '632500', + children: [ + { + name: '共和县', + code: '632521', + }, + { + name: '同德县', + code: '632522', + }, + { + name: '贵德县', + code: '632523', + }, + { + name: '兴海县', + code: '632524', + }, + { + name: '贵南县', + code: '632525', + }, + ], + }, + { + name: '果洛藏族自治州', + code: '632600', + children: [ + { + name: '玛沁县', + code: '632621', + }, + { + name: '班玛县', + code: '632622', + }, + { + name: '甘德县', + code: '632623', + }, + { + name: '达日县', + code: '632624', + }, + { + name: '久治县', + code: '632625', + }, + { + name: '玛多县', + code: '632626', + }, + ], + }, + { + name: '玉树藏族自治州', + code: '632700', + children: [ + { + name: '玉树市', + code: '632701', + }, + { + name: '杂多县', + code: '632722', + }, + { + name: '称多县', + code: '632723', + }, + { + name: '治多县', + code: '632724', + }, + { + name: '囊谦县', + code: '632725', + }, + { + name: '曲麻莱县', + code: '632726', + }, + ], + }, + { + name: '海西蒙古族藏族自治州', + code: '632800', + children: [ + { + name: '格尔木市', + code: '632801', + }, + { + name: '德令哈市', + code: '632802', + }, + { + name: '茫崖市', + code: '632803', + }, + { + name: '乌兰县', + code: '632821', + }, + { + name: '都兰县', + code: '632822', + }, + { + name: '天峻县', + code: '632823', + }, + ], + }, + ], + }, + { + name: '宁夏回族自治区', + code: '640000', + region: 'northwest', + autonomousRegion: true, + children: [ + { + name: '银川市', + code: '640100', + children: [ + { + name: '兴庆区', + code: '640104', + }, + { + name: '西夏区', + code: '640105', + }, + { + name: '金凤区', + code: '640106', + }, + { + name: '永宁县', + code: '640121', + }, + { + name: '贺兰县', + code: '640122', + }, + { + name: '灵武市', + code: '640181', + }, + ], + }, + { + name: '石嘴山市', + code: '640200', + children: [ + { + name: '大武口区', + code: '640202', + }, + { + name: '惠农区', + code: '640205', + }, + { + name: '平罗县', + code: '640221', + }, + ], + }, + { + name: '吴忠市', + code: '640300', + children: [ + { + name: '利通区', + code: '640302', + }, + { + name: '红寺堡区', + code: '640303', + }, + { + name: '盐池县', + code: '640323', + }, + { + name: '同心县', + code: '640324', + }, + { + name: '青铜峡市', + code: '640381', + }, + ], + }, + { + name: '固原市', + code: '640400', + children: [ + { + name: '原州区', + code: '640402', + }, + { + name: '西吉县', + code: '640422', + }, + { + name: '隆德县', + code: '640423', + }, + { + name: '泾源县', + code: '640424', + }, + { + name: '彭阳县', + code: '640425', + }, + ], + }, + { + name: '中卫市', + code: '640500', + children: [ + { + name: '沙坡头区', + code: '640502', + }, + { + name: '中宁县', + code: '640521', + }, + { + name: '海原县', + code: '640522', + }, + ], + }, + ], + }, + { + name: '新疆维吾尔自治区', + code: '650000', + region: 'northwest', + autonomousRegion: true, + children: [ + { + name: '乌鲁木齐市', + code: '650100', + children: [ + { + name: '天山区', + code: '650102', + }, + { + name: '沙依巴克区', + code: '650103', + }, + { + name: '新市区', + code: '650104', + }, + { + name: '水磨沟区', + code: '650105', + }, + { + name: '头屯河区', + code: '650106', + }, + { + name: '达坂城区', + code: '650107', + }, + { + name: '米东区', + code: '650109', + }, + { + name: '乌鲁木齐县', + code: '650121', + }, + ], + }, + { + name: '克拉玛依市', + code: '650200', + children: [ + { + name: '独山子区', + code: '650202', + }, + { + name: '克拉玛依区', + code: '650203', + }, + { + name: '白碱滩区', + code: '650204', + }, + { + name: '乌尔禾区', + code: '650205', + }, + ], + }, + { + name: '吐鲁番市', + code: '650400', + children: [ + { + name: '高昌区', + code: '650402', + }, + { + name: '鄯善县', + code: '650421', + }, + { + name: '托克逊县', + code: '650422', + }, + ], + }, + { + name: '哈密市', + code: '650500', + children: [ + { + name: '伊州区', + code: '650502', + }, + { + name: '巴里坤哈萨克自治县', + code: '650521', + }, + { + name: '伊吾县', + code: '650522', + }, + ], + }, + { + name: '昌吉回族自治州', + code: '652300', + children: [ + { + name: '昌吉市', + code: '652301', + }, + { + name: '阜康市', + code: '652302', + }, + { + name: '呼图壁县', + code: '652323', + }, + { + name: '玛纳斯县', + code: '652324', + }, + { + name: '奇台县', + code: '652325', + }, + { + name: '吉木萨尔县', + code: '652327', + }, + { + name: '木垒哈萨克自治县', + code: '652328', + }, + ], + }, + { + name: '博尔塔拉蒙古自治州', + code: '652700', + children: [ + { + name: '博乐市', + code: '652701', + }, + { + name: '阿拉山口市', + code: '652702', + }, + { + name: '精河县', + code: '652722', + }, + { + name: '温泉县', + code: '652723', + }, + ], + }, + { + name: '巴音郭楞蒙古自治州', + code: '652800', + children: [ + { + name: '库尔勒市', + code: '652801', + }, + { + name: '轮台县', + code: '652822', + }, + { + name: '尉犁县', + code: '652823', + }, + { + name: '若羌县', + code: '652824', + }, + { + name: '且末县', + code: '652825', + }, + { + name: '焉耆回族自治县', + code: '652826', + }, + { + name: '和静县', + code: '652827', + }, + { + name: '和硕县', + code: '652828', + }, + { + name: '博湖县', + code: '652829', + }, + ], + }, + { + name: '阿克苏地区', + code: '652900', + children: [ + { + name: '阿克苏市', + code: '652901', + }, + { + name: '温宿县', + code: '652922', + }, + { + name: '库车县', + code: '652923', + }, + { + name: '沙雅县', + code: '652924', + }, + { + name: '新和县', + code: '652925', + }, + { + name: '拜城县', + code: '652926', + }, + { + name: '乌什县', + code: '652927', + }, + { + name: '阿瓦提县', + code: '652928', + }, + { + name: '柯坪县', + code: '652929', + }, + ], + }, + { + name: '克孜勒苏柯尔克孜自治州', + code: '653000', + children: [ + { + name: '阿图什市', + code: '653001', + }, + { + name: '阿克陶县', + code: '653022', + }, + { + name: '阿合奇县', + code: '653023', + }, + { + name: '乌恰县', + code: '653024', + }, + ], + }, + { + name: '喀什地区', + code: '653100', + children: [ + { + name: '喀什市', + code: '653101', + }, + { + name: '疏附县', + code: '653121', + }, + { + name: '疏勒县', + code: '653122', + }, + { + name: '英吉沙县', + code: '653123', + }, + { + name: '泽普县', + code: '653124', + }, + { + name: '莎车县', + code: '653125', + }, + { + name: '叶城县', + code: '653126', + }, + { + name: '麦盖提县', + code: '653127', + }, + { + name: '岳普湖县', + code: '653128', + }, + { + name: '伽师县', + code: '653129', + }, + { + name: '巴楚县', + code: '653130', + }, + { + name: '塔什库尔干塔吉克自治县', + code: '653131', + }, + ], + }, + { + name: '和田地区', + code: '653200', + children: [ + { + name: '和田市', + code: '653201', + }, + { + name: '和田县', + code: '653221', + }, + { + name: '墨玉县', + code: '653222', + }, + { + name: '皮山县', + code: '653223', + }, + { + name: '洛浦县', + code: '653224', + }, + { + name: '策勒县', + code: '653225', + }, + { + name: '于田县', + code: '653226', + }, + { + name: '民丰县', + code: '653227', + }, + ], + }, + { + name: '伊犁哈萨克自治州', + code: '654000', + children: [ + { + name: '伊宁市', + code: '654002', + }, + { + name: '奎屯市', + code: '654003', + }, + { + name: '霍尔果斯市', + code: '654004', + }, + { + name: '伊宁县', + code: '654021', + }, + { + name: '察布查尔锡伯自治县', + code: '654022', + }, + { + name: '霍城县', + code: '654023', + }, + { + name: '巩留县', + code: '654024', + }, + { + name: '新源县', + code: '654025', + }, + { + name: '昭苏县', + code: '654026', + }, + { + name: '特克斯县', + code: '654027', + }, + { + name: '尼勒克县', + code: '654028', + }, + ], + }, + { + name: '塔城地区', + code: '654200', + children: [ + { + name: '塔城市', + code: '654201', + }, + { + name: '乌苏市', + code: '654202', + }, + { + name: '额敏县', + code: '654221', + }, + { + name: '沙湾县', + code: '654223', + }, + { + name: '托里县', + code: '654224', + }, + { + name: '裕民县', + code: '654225', + }, + { + name: '和布克赛尔蒙古自治县', + code: '654226', + }, + ], + }, + { + name: '阿勒泰地区', + code: '654300', + children: [ + { + name: '阿勒泰市', + code: '654301', + }, + { + name: '布尔津县', + code: '654321', + }, + { + name: '富蕴县', + code: '654322', + }, + { + name: '福海县', + code: '654323', + }, + { + name: '哈巴河县', + code: '654324', + }, + { + name: '青河县', + code: '654325', + }, + { + name: '吉木乃县', + code: '654326', + }, + ], + }, + { + name: '直辖县', + code: '659000', + children: [ + { + name: '石河子市', + code: '659001', + }, + { + name: '阿拉尔市', + code: '659002', + }, + { + name: '图木舒克市', + code: '659003', + }, + { + name: '五家渠市', + code: '659004', + }, + { + name: '北屯市', + code: '659005', + }, + { + name: '铁门关市', + code: '659006', + }, + { + name: '双河市', + code: '659007', + }, + { + name: '可克达拉市', + code: '659008', + }, + { + name: '昆玉市', + code: '659009', + }, + ], + }, + ], + }, +] + +module.exports = [ + { + url: '/area/getList', + type: 'get', + response: () => { + return { + code: 200, + msg: 'success', + data: { list }, + } + }, + }, +] diff --git a/front-end/mock/controller/defaultIcon.js b/front-end/mock/controller/defaultIcon.js new file mode 100644 index 0000000..883c2b6 --- /dev/null +++ b/front-end/mock/controller/defaultIcon.js @@ -0,0 +1,2296 @@ +const List = [ + '24-hours-fill', + '24-hours-line', + '4k-fill', + '4k-line', + 'a-b', + 'account-box-fill', + 'account-box-line', + 'account-circle-fill', + 'account-circle-line', + 'account-pin-box-fill', + 'account-pin-box-line', + 'account-pin-circle-fill', + 'account-pin-circle-line', + 'add-box-fill', + 'add-box-line', + 'add-circle-fill', + 'add-circle-line', + 'add-fill', + 'add-line', + 'admin-fill', + 'admin-line', + /* "advertisement-fill", + "advertisement-line", */ + 'airplay-fill', + 'airplay-line', + 'alarm-fill', + 'alarm-line', + 'alarm-warning-fill', + 'alarm-warning-line', + 'album-fill', + 'album-line', + 'alert-fill', + 'alert-line', + 'aliens-fill', + 'aliens-line', + 'align-bottom', + 'align-center', + 'align-justify', + 'align-left', + 'align-right', + 'align-top', + 'align-vertically', + 'alipay-fill', + 'alipay-line', + 'amazon-fill', + 'amazon-line', + 'anchor-fill', + 'anchor-line', + 'ancient-gate-fill', + 'ancient-gate-line', + 'ancient-pavilion-fill', + 'ancient-pavilion-line', + 'android-fill', + 'android-line', + 'angularjs-fill', + 'angularjs-line', + 'anticlockwise-2-fill', + 'anticlockwise-2-line', + 'anticlockwise-fill', + 'anticlockwise-line', + 'app-store-fill', + 'app-store-line', + 'apple-fill', + 'apple-line', + 'apps-2-fill', + 'apps-2-line', + 'apps-fill', + 'apps-line', + 'archive-drawer-fill', + 'archive-drawer-line', + 'archive-fill', + 'archive-line', + 'arrow-down-circle-fill', + 'arrow-down-circle-line', + 'arrow-down-fill', + 'arrow-down-line', + 'arrow-down-s-fill', + 'arrow-down-s-line', + 'arrow-drop-down-fill', + 'arrow-drop-down-line', + 'arrow-drop-left-fill', + 'arrow-drop-left-line', + 'arrow-drop-right-fill', + 'arrow-drop-right-line', + 'arrow-drop-up-fill', + 'arrow-drop-up-line', + 'arrow-go-back-fill', + 'arrow-go-back-line', + 'arrow-go-forward-fill', + 'arrow-go-forward-line', + 'arrow-left-circle-fill', + 'arrow-left-circle-line', + 'arrow-left-down-fill', + 'arrow-left-down-line', + 'arrow-left-fill', + 'arrow-left-line', + 'arrow-left-right-fill', + 'arrow-left-right-line', + 'arrow-left-s-fill', + 'arrow-left-s-line', + 'arrow-left-up-fill', + 'arrow-left-up-line', + 'arrow-right-circle-fill', + 'arrow-right-circle-line', + 'arrow-right-down-fill', + 'arrow-right-down-line', + 'arrow-right-fill', + 'arrow-right-line', + 'arrow-right-s-fill', + 'arrow-right-s-line', + 'arrow-right-up-fill', + 'arrow-right-up-line', + 'arrow-up-circle-fill', + 'arrow-up-circle-line', + 'arrow-up-down-fill', + 'arrow-up-down-line', + 'arrow-up-fill', + 'arrow-up-line', + 'arrow-up-s-fill', + 'arrow-up-s-line', + 'artboard-2-fill', + 'artboard-2-line', + 'artboard-fill', + 'artboard-line', + 'article-fill', + 'article-line', + 'aspect-ratio-fill', + 'aspect-ratio-line', + 'asterisk', + 'at-fill', + 'at-line', + 'attachment-2', + 'attachment-fill', + 'attachment-line', + 'auction-fill', + 'auction-line', + 'award-fill', + 'award-line', + 'baidu-fill', + 'baidu-line', + 'ball-pen-fill', + 'ball-pen-line', + 'bank-card-2-fill', + 'bank-card-2-line', + 'bank-card-fill', + 'bank-card-line', + 'bank-fill', + 'bank-line', + 'bar-chart-2-fill', + 'bar-chart-2-line', + 'bar-chart-box-fill', + 'bar-chart-box-line', + 'bar-chart-fill', + 'bar-chart-grouped-fill', + 'bar-chart-grouped-line', + 'bar-chart-horizontal-fill', + 'bar-chart-horizontal-line', + 'bar-chart-line', + 'barcode-box-fill', + 'barcode-box-line', + 'barcode-fill', + 'barcode-line', + 'barricade-fill', + 'barricade-line', + 'base-station-fill', + 'base-station-line', + 'basketball-fill', + 'basketball-line', + 'battery-2-charge-fill', + 'battery-2-charge-line', + 'battery-2-fill', + 'battery-2-line', + 'battery-charge-fill', + 'battery-charge-line', + 'battery-fill', + 'battery-line', + 'battery-low-fill', + 'battery-low-line', + 'battery-saver-fill', + 'battery-saver-line', + 'battery-share-fill', + 'battery-share-line', + 'bear-smile-fill', + 'bear-smile-line', + 'behance-fill', + 'behance-line', + 'bell-fill', + 'bell-line', + 'bike-fill', + 'bike-line', + 'bilibili-fill', + 'bilibili-line', + 'bill-fill', + 'bill-line', + 'billiards-fill', + 'billiards-line', + 'bit-coin-fill', + 'bit-coin-line', + 'blaze-fill', + 'blaze-line', + 'bluetooth-connect-fill', + 'bluetooth-connect-line', + 'bluetooth-fill', + 'bluetooth-line', + 'blur-off-fill', + 'blur-off-line', + 'body-scan-fill', + 'body-scan-line', + 'bold', + 'book-2-fill', + 'book-2-line', + 'book-3-fill', + 'book-3-line', + 'book-fill', + 'book-line', + 'book-mark-fill', + 'book-mark-line', + 'book-open-fill', + 'book-open-line', + 'book-read-fill', + 'book-read-line', + 'booklet-fill', + 'booklet-line', + 'bookmark-2-fill', + 'bookmark-2-line', + 'bookmark-3-fill', + 'bookmark-3-line', + 'bookmark-fill', + 'bookmark-line', + 'boxing-fill', + 'boxing-line', + 'braces-fill', + 'braces-line', + 'brackets-fill', + 'brackets-line', + 'briefcase-2-fill', + 'briefcase-2-line', + 'briefcase-3-fill', + 'briefcase-3-line', + 'briefcase-4-fill', + 'briefcase-4-line', + 'briefcase-5-fill', + 'briefcase-5-line', + 'briefcase-fill', + 'briefcase-line', + 'bring-forward', + 'bring-to-front', + 'broadcast-fill', + 'broadcast-line', + 'brush-2-fill', + 'brush-2-line', + 'brush-3-fill', + 'brush-3-line', + 'brush-4-fill', + 'brush-4-line', + 'brush-fill', + 'brush-line', + 'bubble-chart-fill', + 'bubble-chart-line', + 'bug-2-fill', + 'bug-2-line', + 'bug-fill', + 'bug-line', + 'building-2-fill', + 'building-2-line', + 'building-3-fill', + 'building-3-line', + 'building-4-fill', + 'building-4-line', + 'building-fill', + 'building-line', + 'bus-2-fill', + 'bus-2-line', + 'bus-fill', + 'bus-line', + 'bus-wifi-fill', + 'bus-wifi-line', + 'cactus-fill', + 'cactus-line', + 'cake-2-fill', + 'cake-2-line', + 'cake-3-fill', + 'cake-3-line', + 'cake-fill', + 'cake-line', + 'calculator-fill', + 'calculator-line', + 'calendar-2-fill', + 'calendar-2-line', + 'calendar-check-fill', + 'calendar-check-line', + 'calendar-event-fill', + 'calendar-event-line', + 'calendar-fill', + 'calendar-line', + 'calendar-todo-fill', + 'calendar-todo-line', + 'camera-2-fill', + 'camera-2-line', + 'camera-3-fill', + 'camera-3-line', + 'camera-fill', + 'camera-lens-fill', + 'camera-lens-line', + 'camera-line', + 'camera-off-fill', + 'camera-off-line', + 'camera-switch-fill', + 'camera-switch-line', + 'capsule-fill', + 'capsule-line', + 'car-fill', + 'car-line', + 'car-washing-fill', + 'car-washing-line', + 'caravan-fill', + 'caravan-line', + 'cast-fill', + 'cast-line', + 'cellphone-fill', + 'cellphone-line', + 'celsius-fill', + 'celsius-line', + 'centos-fill', + 'centos-line', + 'character-recognition-fill', + 'character-recognition-line', + 'charging-pile-2-fill', + 'charging-pile-2-line', + 'charging-pile-fill', + 'charging-pile-line', + 'chat-1-fill', + 'chat-1-line', + 'chat-2-fill', + 'chat-2-line', + 'chat-3-fill', + 'chat-3-line', + 'chat-4-fill', + 'chat-4-line', + 'chat-check-fill', + 'chat-check-line', + 'chat-delete-fill', + 'chat-delete-line', + 'chat-download-fill', + 'chat-download-line', + 'chat-follow-up-fill', + 'chat-follow-up-line', + 'chat-forward-fill', + 'chat-forward-line', + 'chat-heart-fill', + 'chat-heart-line', + 'chat-history-fill', + 'chat-history-line', + 'chat-new-fill', + 'chat-new-line', + 'chat-off-fill', + 'chat-off-line', + 'chat-poll-fill', + 'chat-poll-line', + 'chat-private-fill', + 'chat-private-line', + 'chat-quote-fill', + 'chat-quote-line', + 'chat-settings-fill', + 'chat-settings-line', + 'chat-smile-2-fill', + 'chat-smile-2-line', + 'chat-smile-3-fill', + 'chat-smile-3-line', + 'chat-smile-fill', + 'chat-smile-line', + 'chat-upload-fill', + 'chat-upload-line', + 'chat-voice-fill', + 'chat-voice-line', + 'check-double-fill', + 'check-double-line', + 'check-fill', + 'check-line', + 'checkbox-blank-circle-fill', + 'checkbox-blank-circle-line', + 'checkbox-blank-fill', + 'checkbox-blank-line', + 'checkbox-circle-fill', + 'checkbox-circle-line', + 'checkbox-fill', + 'checkbox-indeterminate-fill', + 'checkbox-indeterminate-line', + 'checkbox-line', + 'checkbox-multiple-blank-fill', + 'checkbox-multiple-blank-line', + 'checkbox-multiple-fill', + 'checkbox-multiple-line', + 'china-railway-fill', + 'china-railway-line', + 'chrome-fill', + 'chrome-line', + 'clapperboard-fill', + 'clapperboard-line', + 'clipboard-fill', + 'clipboard-line', + 'clockwise-2-fill', + 'clockwise-2-line', + 'clockwise-fill', + 'clockwise-line', + 'close-circle-fill', + 'close-circle-line', + 'close-fill', + 'close-line', + 'closed-captioning-fill', + 'closed-captioning-line', + 'cloud-fill', + 'cloud-line', + 'cloud-off-fill', + 'cloud-off-line', + 'cloud-windy-fill', + 'cloud-windy-line', + 'cloudy-2-fill', + 'cloudy-2-line', + 'cloudy-fill', + 'cloudy-line', + 'code-box-fill', + 'code-box-line', + 'code-fill', + 'code-line', + 'code-s-fill', + 'code-s-line', + 'code-s-slash-fill', + 'code-s-slash-line', + 'code-view', + 'codepen-fill', + 'codepen-line', + 'coin-fill', + 'coin-line', + 'coins-fill', + 'coins-line', + 'collage-fill', + 'collage-line', + 'command-fill', + 'command-line', + 'community-fill', + 'community-line', + 'compass-2-fill', + 'compass-2-line', + 'compass-3-fill', + 'compass-3-line', + 'compass-4-fill', + 'compass-4-line', + 'compass-discover-fill', + 'compass-discover-line', + 'compass-fill', + 'compass-line', + 'compasses-2-fill', + 'compasses-2-line', + 'compasses-fill', + 'compasses-line', + 'computer-fill', + 'computer-line', + 'contacts-book-2-fill', + 'contacts-book-2-line', + 'contacts-book-fill', + 'contacts-book-line', + 'contacts-book-upload-fill', + 'contacts-book-upload-line', + 'contacts-fill', + 'contacts-line', + 'contrast-2-fill', + 'contrast-2-line', + 'contrast-drop-2-fill', + 'contrast-drop-2-line', + 'contrast-drop-fill', + 'contrast-drop-line', + 'contrast-fill', + 'contrast-line', + 'copper-coin-fill', + 'copper-coin-line', + 'copper-diamond-fill', + 'copper-diamond-line', + 'copyleft-fill', + 'copyleft-line', + 'copyright-fill', + 'copyright-line', + 'coreos-fill', + 'coreos-line', + 'coupon-2-fill', + 'coupon-2-line', + 'coupon-3-fill', + 'coupon-3-line', + 'coupon-4-fill', + 'coupon-4-line', + 'coupon-5-fill', + 'coupon-5-line', + 'coupon-fill', + 'coupon-line', + 'cpu-fill', + 'cpu-line', + 'creative-commons-by-fill', + 'creative-commons-by-line', + 'creative-commons-fill', + 'creative-commons-line', + 'creative-commons-nc-fill', + 'creative-commons-nc-line', + 'creative-commons-nd-fill', + 'creative-commons-nd-line', + 'creative-commons-sa-fill', + 'creative-commons-sa-line', + 'creative-commons-zero-fill', + 'creative-commons-zero-line', + 'criminal-fill', + 'criminal-line', + 'crop-2-fill', + 'crop-2-line', + 'crop-fill', + 'crop-line', + 'css3-fill', + 'css3-line', + 'cup-fill', + 'cup-line', + 'currency-fill', + 'currency-line', + 'cursor-fill', + 'cursor-line', + 'customer-service-2-fill', + 'customer-service-2-line', + 'customer-service-fill', + 'customer-service-line', + 'dashboard-2-fill', + 'dashboard-2-line', + 'dashboard-3-fill', + 'dashboard-3-line', + 'dashboard-fill', + 'dashboard-line', + 'database-2-fill', + 'database-2-line', + 'database-fill', + 'database-line', + 'delete-back-2-fill', + 'delete-back-2-line', + 'delete-back-fill', + 'delete-back-line', + 'delete-bin-2-fill', + 'delete-bin-2-line', + 'delete-bin-3-fill', + 'delete-bin-3-line', + 'delete-bin-4-fill', + 'delete-bin-4-line', + 'delete-bin-5-fill', + 'delete-bin-5-line', + 'delete-bin-6-fill', + 'delete-bin-6-line', + 'delete-bin-7-fill', + 'delete-bin-7-line', + 'delete-bin-fill', + 'delete-bin-line', + 'delete-column', + 'delete-row', + 'device-fill', + 'device-line', + 'device-recover-fill', + 'device-recover-line', + 'dingding-fill', + 'dingding-line', + 'direction-fill', + 'direction-line', + 'disc-fill', + 'disc-line', + 'discord-fill', + 'discord-line', + 'discuss-fill', + 'discuss-line', + 'dislike-fill', + 'dislike-line', + 'disqus-fill', + 'disqus-line', + 'divide-fill', + 'divide-line', + 'donut-chart-fill', + 'donut-chart-line', + 'door-closed-fill', + 'door-closed-line', + 'door-fill', + 'door-line', + 'door-lock-box-fill', + 'door-lock-box-line', + 'door-lock-fill', + 'door-lock-line', + 'door-open-fill', + 'door-open-line', + 'dossier-fill', + 'dossier-line', + 'douban-fill', + 'douban-line', + 'double-quotes-l', + 'double-quotes-r', + 'download-2-fill', + 'download-2-line', + 'download-cloud-2-fill', + 'download-cloud-2-line', + 'download-cloud-fill', + 'download-cloud-line', + 'download-fill', + 'download-line', + 'draft-fill', + 'draft-line', + 'drag-drop-fill', + 'drag-drop-line', + 'drag-move-2-fill', + 'drag-move-2-line', + 'drag-move-fill', + 'drag-move-line', + 'dribbble-fill', + 'dribbble-line', + 'drive-fill', + 'drive-line', + 'drizzle-fill', + 'drizzle-line', + 'drop-fill', + 'drop-line', + 'dropbox-fill', + 'dropbox-line', + 'dual-sim-1-fill', + 'dual-sim-1-line', + 'dual-sim-2-fill', + 'dual-sim-2-line', + 'dv-fill', + 'dv-line', + 'dvd-fill', + 'dvd-line', + 'e-bike-2-fill', + 'e-bike-2-line', + 'e-bike-fill', + 'e-bike-line', + 'earth-fill', + 'earth-line', + 'earthquake-fill', + 'earthquake-line', + 'edge-fill', + 'edge-line', + 'edit-2-fill', + 'edit-2-line', + 'edit-box-fill', + 'edit-box-line', + 'edit-circle-fill', + 'edit-circle-line', + 'edit-fill', + 'edit-line', + 'eject-fill', + 'eject-line', + 'emotion-2-fill', + 'emotion-2-line', + 'emotion-fill', + 'emotion-happy-fill', + 'emotion-happy-line', + 'emotion-laugh-fill', + 'emotion-laugh-line', + 'emotion-line', + 'emotion-normal-fill', + 'emotion-normal-line', + 'emotion-sad-fill', + 'emotion-sad-line', + 'emotion-unhappy-fill', + 'emotion-unhappy-line', + 'empathize-fill', + 'empathize-line', + 'emphasis-cn', + 'emphasis', + 'english-input', + 'equalizer-fill', + 'equalizer-line', + 'eraser-fill', + 'eraser-line', + 'error-warning-fill', + 'error-warning-line', + 'evernote-fill', + 'evernote-line', + 'exchange-box-fill', + 'exchange-box-line', + 'exchange-cny-fill', + 'exchange-cny-line', + 'exchange-dollar-fill', + 'exchange-dollar-line', + 'exchange-fill', + 'exchange-funds-fill', + 'exchange-funds-line', + 'exchange-line', + 'external-link-fill', + 'external-link-line', + 'eye-2-fill', + 'eye-2-line', + 'eye-close-fill', + 'eye-close-line', + 'eye-fill', + 'eye-line', + 'eye-off-fill', + 'eye-off-line', + 'facebook-box-fill', + 'facebook-box-line', + 'facebook-circle-fill', + 'facebook-circle-line', + 'facebook-fill', + 'facebook-line', + 'fahrenheit-fill', + 'fahrenheit-line', + 'feedback-fill', + 'feedback-line', + 'file-2-fill', + 'file-2-line', + 'file-3-fill', + 'file-3-line', + 'file-4-fill', + 'file-4-line', + 'file-add-fill', + 'file-add-line', + 'file-chart-2-fill', + 'file-chart-2-line', + 'file-chart-fill', + 'file-chart-line', + 'file-cloud-fill', + 'file-cloud-line', + 'file-code-fill', + 'file-code-line', + 'file-copy-2-fill', + 'file-copy-2-line', + 'file-copy-fill', + 'file-copy-line', + 'file-damage-fill', + 'file-damage-line', + 'file-download-fill', + 'file-download-line', + 'file-edit-fill', + 'file-edit-line', + 'file-excel-2-fill', + 'file-excel-2-line', + 'file-excel-fill', + 'file-excel-line', + 'file-fill', + 'file-forbid-fill', + 'file-forbid-line', + 'file-gif-fill', + 'file-gif-line', + 'file-history-fill', + 'file-history-line', + 'file-hwp-fill', + 'file-hwp-line', + 'file-info-fill', + 'file-info-line', + 'file-line', + 'file-list-2-fill', + 'file-list-2-line', + 'file-list-3-fill', + 'file-list-3-line', + 'file-list-fill', + 'file-list-line', + 'file-lock-fill', + 'file-lock-line', + 'file-mark-fill', + 'file-mark-line', + 'file-music-fill', + 'file-music-line', + 'file-paper-2-fill', + 'file-paper-2-line', + 'file-paper-fill', + 'file-paper-line', + 'file-pdf-fill', + 'file-pdf-line', + 'file-ppt-2-fill', + 'file-ppt-2-line', + 'file-ppt-fill', + 'file-ppt-line', + 'file-reduce-fill', + 'file-reduce-line', + 'file-search-fill', + 'file-search-line', + 'file-settings-fill', + 'file-settings-line', + 'file-shield-2-fill', + 'file-shield-2-line', + 'file-shield-fill', + 'file-shield-line', + 'file-shred-fill', + 'file-shred-line', + 'file-text-fill', + 'file-text-line', + 'file-transfer-fill', + 'file-transfer-line', + 'file-unknow-fill', + 'file-unknow-line', + 'file-upload-fill', + 'file-upload-line', + 'file-user-fill', + 'file-user-line', + 'file-warning-fill', + 'file-warning-line', + 'file-word-2-fill', + 'file-word-2-line', + 'file-word-fill', + 'file-word-line', + 'file-zip-fill', + 'file-zip-line', + 'film-fill', + 'film-line', + 'filter-2-fill', + 'filter-2-line', + 'filter-3-fill', + 'filter-3-line', + 'filter-fill', + 'filter-line', + 'filter-off-fill', + 'filter-off-line', + 'find-replace-fill', + 'find-replace-line', + 'finder-fill', + 'finder-line', + 'fingerprint-2-fill', + 'fingerprint-2-line', + 'fingerprint-fill', + 'fingerprint-line', + 'fire-fill', + 'fire-line', + 'firefox-fill', + 'firefox-line', + 'first-aid-kit-fill', + 'first-aid-kit-line', + 'flag-2-fill', + 'flag-2-line', + 'flag-fill', + 'flag-line', + 'flashlight-fill', + 'flashlight-line', + 'flask-fill', + 'flask-line', + 'flight-land-fill', + 'flight-land-line', + 'flight-takeoff-fill', + 'flight-takeoff-line', + 'flood-fill', + 'flood-line', + 'flow-chart', + 'flutter-fill', + 'flutter-line', + 'focus-2-fill', + 'focus-2-line', + 'focus-3-fill', + 'focus-3-line', + 'focus-fill', + 'focus-line', + 'foggy-fill', + 'foggy-line', + 'folder-2-fill', + 'folder-2-line', + 'folder-3-fill', + 'folder-3-line', + 'folder-4-fill', + 'folder-4-line', + 'folder-5-fill', + 'folder-5-line', + 'folder-add-fill', + 'folder-add-line', + 'folder-chart-2-fill', + 'folder-chart-2-line', + 'folder-chart-fill', + 'folder-chart-line', + 'folder-download-fill', + 'folder-download-line', + 'folder-fill', + 'folder-forbid-fill', + 'folder-forbid-line', + 'folder-history-fill', + 'folder-history-line', + 'folder-info-fill', + 'folder-info-line', + 'folder-keyhole-fill', + 'folder-keyhole-line', + 'folder-line', + 'folder-lock-fill', + 'folder-lock-line', + 'folder-music-fill', + 'folder-music-line', + 'folder-open-fill', + 'folder-open-line', + 'folder-received-fill', + 'folder-received-line', + 'folder-reduce-fill', + 'folder-reduce-line', + 'folder-settings-fill', + 'folder-settings-line', + 'folder-shared-fill', + 'folder-shared-line', + 'folder-shield-2-fill', + 'folder-shield-2-line', + 'folder-shield-fill', + 'folder-shield-line', + 'folder-transfer-fill', + 'folder-transfer-line', + 'folder-unknow-fill', + 'folder-unknow-line', + 'folder-upload-fill', + 'folder-upload-line', + 'folder-user-fill', + 'folder-user-line', + 'folder-warning-fill', + 'folder-warning-line', + 'folder-zip-fill', + 'folder-zip-line', + 'folders-fill', + 'folders-line', + 'font-color', + 'font-size-2', + 'font-size', + 'football-fill', + 'football-line', + 'footprint-fill', + 'footprint-line', + 'forbid-2-fill', + 'forbid-2-line', + 'forbid-fill', + 'forbid-line', + 'format-clear', + 'fridge-fill', + 'fridge-line', + 'fullscreen-exit-fill', + 'fullscreen-exit-line', + 'fullscreen-fill', + 'fullscreen-line', + 'function-fill', + 'function-line', + 'functions', + 'funds-box-fill', + 'funds-box-line', + 'funds-fill', + 'funds-line', + 'gallery-fill', + 'gallery-line', + 'gallery-upload-fill', + 'gallery-upload-line', + 'game-fill', + 'game-line', + 'gamepad-fill', + 'gamepad-line', + 'gas-station-fill', + 'gas-station-line', + 'gatsby-fill', + 'gatsby-line', + 'genderless-fill', + 'genderless-line', + 'ghost-2-fill', + 'ghost-2-line', + 'ghost-fill', + 'ghost-line', + 'ghost-smile-fill', + 'ghost-smile-line', + 'gift-2-fill', + 'gift-2-line', + 'gift-fill', + 'gift-line', + 'git-branch-fill', + 'git-branch-line', + 'git-commit-fill', + 'git-commit-line', + 'git-merge-fill', + 'git-merge-line', + 'git-pull-request-fill', + 'git-pull-request-line', + 'git-repository-commits-fill', + 'git-repository-commits-line', + 'git-repository-fill', + 'git-repository-line', + 'git-repository-private-fill', + 'git-repository-private-line', + 'github-fill', + 'github-line', + 'gitlab-fill', + 'gitlab-line', + 'global-fill', + 'global-line', + 'globe-fill', + 'globe-line', + 'goblet-fill', + 'goblet-line', + 'google-fill', + 'google-line', + 'google-play-fill', + 'google-play-line', + 'government-fill', + 'government-line', + 'gps-fill', + 'gps-line', + 'gradienter-fill', + 'gradienter-line', + 'grid-fill', + 'grid-line', + 'group-2-fill', + 'group-2-line', + 'group-fill', + 'group-line', + 'guide-fill', + 'guide-line', + 'h-1', + 'h-2', + 'h-3', + 'h-4', + 'h-5', + 'h-6', + 'hail-fill', + 'hail-line', + 'hammer-fill', + 'hammer-line', + 'hand-coin-fill', + 'hand-coin-line', + 'hand-heart-fill', + 'hand-heart-line', + 'hand-sanitizer-fill', + 'hand-sanitizer-line', + 'handbag-fill', + 'handbag-line', + 'hard-drive-2-fill', + 'hard-drive-2-line', + 'hard-drive-fill', + 'hard-drive-line', + 'hashtag', + 'haze-2-fill', + 'haze-2-line', + 'haze-fill', + 'haze-line', + 'hd-fill', + 'hd-line', + 'heading', + 'headphone-fill', + 'headphone-line', + 'health-book-fill', + 'health-book-line', + 'heart-2-fill', + 'heart-2-line', + 'heart-3-fill', + 'heart-3-line', + 'heart-add-fill', + 'heart-add-line', + 'heart-fill', + 'heart-line', + 'heart-pulse-fill', + 'heart-pulse-line', + 'hearts-fill', + 'hearts-line', + 'heavy-showers-fill', + 'heavy-showers-line', + 'history-fill', + 'history-line', + 'home-2-fill', + 'home-2-line', + 'home-3-fill', + 'home-3-line', + 'home-4-fill', + 'home-4-line', + 'home-5-fill', + 'home-5-line', + 'home-6-fill', + 'home-6-line', + 'home-7-fill', + 'home-7-line', + 'home-8-fill', + 'home-8-line', + 'home-fill', + 'home-gear-fill', + 'home-gear-line', + 'home-heart-fill', + 'home-heart-line', + 'home-line', + 'home-smile-2-fill', + 'home-smile-2-line', + 'home-smile-fill', + 'home-smile-line', + 'home-wifi-fill', + 'home-wifi-line', + 'honor-of-kings-fill', + 'honor-of-kings-line', + 'honour-fill', + 'honour-line', + 'hospital-fill', + 'hospital-line', + 'hotel-bed-fill', + 'hotel-bed-line', + 'hotel-fill', + 'hotel-line', + 'hotspot-fill', + 'hotspot-line', + 'hq-fill', + 'hq-line', + 'html5-fill', + 'html5-line', + 'ie-fill', + 'ie-line', + 'image-2-fill', + 'image-2-line', + 'image-add-fill', + 'image-add-line', + 'image-edit-fill', + 'image-edit-line', + 'image-fill', + 'image-line', + 'inbox-archive-fill', + 'inbox-archive-line', + 'inbox-fill', + 'inbox-line', + 'inbox-unarchive-fill', + 'inbox-unarchive-line', + 'increase-decrease-fill', + 'increase-decrease-line', + 'indent-decrease', + 'indent-increase', + 'indeterminate-circle-fill', + 'indeterminate-circle-line', + 'information-fill', + 'information-line', + 'infrared-thermometer-fill', + 'infrared-thermometer-line', + 'ink-bottle-fill', + 'ink-bottle-line', + 'input-cursor-move', + 'input-method-fill', + 'input-method-line', + 'insert-column-left', + 'insert-column-right', + 'insert-row-bottom', + 'insert-row-top', + 'instagram-fill', + 'instagram-line', + 'install-fill', + 'install-line', + 'invision-fill', + 'invision-line', + 'italic', + 'kakao-talk-fill', + 'kakao-talk-line', + 'key-2-fill', + 'key-2-line', + 'key-fill', + 'key-line', + 'keyboard-box-fill', + 'keyboard-box-line', + 'keyboard-fill', + 'keyboard-line', + 'keynote-fill', + 'keynote-line', + 'knife-blood-fill', + 'knife-blood-line', + 'knife-fill', + 'knife-line', + 'landscape-fill', + 'landscape-line', + 'layout-2-fill', + 'layout-2-line', + 'layout-3-fill', + 'layout-3-line', + 'layout-4-fill', + 'layout-4-line', + 'layout-5-fill', + 'layout-5-line', + 'layout-6-fill', + 'layout-6-line', + 'layout-bottom-2-fill', + 'layout-bottom-2-line', + 'layout-bottom-fill', + 'layout-bottom-line', + 'layout-column-fill', + 'layout-column-line', + 'layout-fill', + 'layout-grid-fill', + 'layout-grid-line', + 'layout-left-2-fill', + 'layout-left-2-line', + 'layout-left-fill', + 'layout-left-line', + 'layout-line', + 'layout-masonry-fill', + 'layout-masonry-line', + 'layout-right-2-fill', + 'layout-right-2-line', + 'layout-right-fill', + 'layout-right-line', + 'layout-row-fill', + 'layout-row-line', + 'layout-top-2-fill', + 'layout-top-2-line', + 'layout-top-fill', + 'layout-top-line', + 'leaf-fill', + 'leaf-line', + 'lifebuoy-fill', + 'lifebuoy-line', + 'lightbulb-fill', + 'lightbulb-flash-fill', + 'lightbulb-flash-line', + 'lightbulb-line', + 'line-chart-fill', + 'line-chart-line', + 'line-fill', + 'line-height', + 'line-line', + 'link-m', + 'link-unlink-m', + 'link-unlink', + 'link', + 'linkedin-box-fill', + 'linkedin-box-line', + 'linkedin-fill', + 'linkedin-line', + 'links-fill', + 'links-line', + 'list-check-2', + 'list-check', + 'list-ordered', + 'list-settings-fill', + 'list-settings-line', + 'list-unordered', + 'live-fill', + 'live-line', + 'loader-2-fill', + 'loader-2-line', + 'loader-3-fill', + 'loader-3-line', + 'loader-4-fill', + 'loader-4-line', + 'loader-5-fill', + 'loader-5-line', + 'loader-fill', + 'loader-line', + 'lock-2-fill', + 'lock-2-line', + 'lock-fill', + 'lock-line', + 'lock-password-fill', + 'lock-password-line', + 'lock-unlock-fill', + 'lock-unlock-line', + 'login-box-fill', + 'login-box-line', + 'login-circle-fill', + 'login-circle-line', + 'logout-box-fill', + 'logout-box-line', + 'logout-box-r-fill', + 'logout-box-r-line', + 'logout-circle-fill', + 'logout-circle-line', + 'logout-circle-r-fill', + 'logout-circle-r-line', + 'luggage-cart-fill', + 'luggage-cart-line', + 'luggage-deposit-fill', + 'luggage-deposit-line', + 'lungs-fill', + 'lungs-line', + 'mac-fill', + 'mac-line', + 'macbook-fill', + 'macbook-line', + 'magic-fill', + 'magic-line', + 'mail-add-fill', + 'mail-add-line', + 'mail-check-fill', + 'mail-check-line', + 'mail-close-fill', + 'mail-close-line', + 'mail-download-fill', + 'mail-download-line', + 'mail-fill', + 'mail-forbid-fill', + 'mail-forbid-line', + 'mail-line', + 'mail-lock-fill', + 'mail-lock-line', + 'mail-open-fill', + 'mail-open-line', + 'mail-send-fill', + 'mail-send-line', + 'mail-settings-fill', + 'mail-settings-line', + 'mail-star-fill', + 'mail-star-line', + 'mail-unread-fill', + 'mail-unread-line', + 'mail-volume-fill', + 'mail-volume-line', + 'map-2-fill', + 'map-2-line', + 'map-fill', + 'map-line', + 'map-pin-2-fill', + 'map-pin-2-line', + 'map-pin-3-fill', + 'map-pin-3-line', + 'map-pin-4-fill', + 'map-pin-4-line', + 'map-pin-5-fill', + 'map-pin-5-line', + 'map-pin-add-fill', + 'map-pin-add-line', + 'map-pin-fill', + 'map-pin-line', + 'map-pin-range-fill', + 'map-pin-range-line', + 'map-pin-time-fill', + 'map-pin-time-line', + 'map-pin-user-fill', + 'map-pin-user-line', + 'mark-pen-fill', + 'mark-pen-line', + 'markdown-fill', + 'markdown-line', + 'markup-fill', + 'markup-line', + 'mastercard-fill', + 'mastercard-line', + 'mastodon-fill', + 'mastodon-line', + 'medal-2-fill', + 'medal-2-line', + 'medal-fill', + 'medal-line', + 'medicine-bottle-fill', + 'medicine-bottle-line', + 'medium-fill', + 'medium-line', + 'men-fill', + 'men-line', + 'mental-health-fill', + 'mental-health-line', + 'menu-2-fill', + 'menu-2-line', + 'menu-3-fill', + 'menu-3-line', + 'menu-4-fill', + 'menu-4-line', + 'menu-5-fill', + 'menu-5-line', + 'menu-add-fill', + 'menu-add-line', + 'menu-fill', + 'menu-fold-fill', + 'menu-fold-line', + 'menu-line', + 'menu-unfold-fill', + 'menu-unfold-line', + 'merge-cells-horizontal', + 'merge-cells-vertical', + 'message-2-fill', + 'message-2-line', + 'message-3-fill', + 'message-3-line', + 'message-fill', + 'message-line', + 'messenger-fill', + 'messenger-line', + 'meteor-fill', + 'meteor-line', + 'mic-2-fill', + 'mic-2-line', + 'mic-fill', + 'mic-line', + 'mic-off-fill', + 'mic-off-line', + 'mickey-fill', + 'mickey-line', + 'microscope-fill', + 'microscope-line', + 'microsoft-fill', + 'microsoft-line', + 'mind-map', + 'mini-program-fill', + 'mini-program-line', + 'mist-fill', + 'mist-line', + 'money-cny-box-fill', + 'money-cny-box-line', + 'money-cny-circle-fill', + 'money-cny-circle-line', + 'money-dollar-box-fill', + 'money-dollar-box-line', + 'money-dollar-circle-fill', + 'money-dollar-circle-line', + 'money-euro-box-fill', + 'money-euro-box-line', + 'money-euro-circle-fill', + 'money-euro-circle-line', + 'money-pound-box-fill', + 'money-pound-box-line', + 'money-pound-circle-fill', + 'money-pound-circle-line', + 'moon-clear-fill', + 'moon-clear-line', + 'moon-cloudy-fill', + 'moon-cloudy-line', + 'moon-fill', + 'moon-foggy-fill', + 'moon-foggy-line', + 'moon-line', + 'more-2-fill', + 'more-2-line', + 'more-fill', + 'more-line', + 'motorbike-fill', + 'motorbike-line', + 'mouse-fill', + 'mouse-line', + 'movie-2-fill', + 'movie-2-line', + 'movie-fill', + 'movie-line', + 'music-2-fill', + 'music-2-line', + 'music-fill', + 'music-line', + 'mv-fill', + 'mv-line', + 'navigation-fill', + 'navigation-line', + 'netease-cloud-music-fill', + 'netease-cloud-music-line', + 'netflix-fill', + 'netflix-line', + 'newspaper-fill', + 'newspaper-line', + 'node-tree', + 'notification-2-fill', + 'notification-2-line', + 'notification-3-fill', + 'notification-3-line', + 'notification-4-fill', + 'notification-4-line', + 'notification-badge-fill', + 'notification-badge-line', + 'notification-fill', + 'notification-line', + 'notification-off-fill', + 'notification-off-line', + 'npmjs-fill', + 'npmjs-line', + 'number-0', + 'number-1', + 'number-2', + 'number-3', + 'number-4', + 'number-5', + 'number-6', + 'number-7', + 'number-8', + 'number-9', + 'numbers-fill', + 'numbers-line', + 'nurse-fill', + 'nurse-line', + 'oil-fill', + 'oil-line', + 'omega', + 'open-arm-fill', + 'open-arm-line', + 'open-source-fill', + 'open-source-line', + 'opera-fill', + 'opera-line', + 'order-play-fill', + 'order-play-line', + 'organization-chart', + 'outlet-2-fill', + 'outlet-2-line', + 'outlet-fill', + 'outlet-line', + 'page-separator', + 'pages-fill', + 'pages-line', + 'paint-brush-fill', + 'paint-brush-line', + 'paint-fill', + 'paint-line', + 'palette-fill', + 'palette-line', + 'pantone-fill', + 'pantone-line', + 'paragraph', + 'parent-fill', + 'parent-line', + 'parentheses-fill', + 'parentheses-line', + 'parking-box-fill', + 'parking-box-line', + 'parking-fill', + 'parking-line', + 'passport-fill', + 'passport-line', + 'patreon-fill', + 'patreon-line', + 'pause-circle-fill', + 'pause-circle-line', + 'pause-fill', + 'pause-line', + 'pause-mini-fill', + 'pause-mini-line', + 'paypal-fill', + 'paypal-line', + 'pen-nib-fill', + 'pen-nib-line', + 'pencil-fill', + 'pencil-line', + 'pencil-ruler-2-fill', + 'pencil-ruler-2-line', + 'pencil-ruler-fill', + 'pencil-ruler-line', + 'percent-fill', + 'percent-line', + 'phone-camera-fill', + 'phone-camera-line', + 'phone-fill', + 'phone-find-fill', + 'phone-find-line', + 'phone-line', + 'phone-lock-fill', + 'phone-lock-line', + 'picture-in-picture-2-fill', + 'picture-in-picture-2-line', + 'picture-in-picture-exit-fill', + 'picture-in-picture-exit-line', + 'picture-in-picture-fill', + 'picture-in-picture-line', + 'pie-chart-2-fill', + 'pie-chart-2-line', + 'pie-chart-box-fill', + 'pie-chart-box-line', + 'pie-chart-fill', + 'pie-chart-line', + 'pin-distance-fill', + 'pin-distance-line', + 'ping-pong-fill', + 'ping-pong-line', + 'pinterest-fill', + 'pinterest-line', + 'pinyin-input', + 'pixelfed-fill', + 'pixelfed-line', + 'plane-fill', + 'plane-line', + 'plant-fill', + 'plant-line', + 'play-circle-fill', + 'play-circle-line', + 'play-fill', + 'play-line', + 'play-list-2-fill', + 'play-list-2-line', + 'play-list-add-fill', + 'play-list-add-line', + 'play-list-fill', + 'play-list-line', + 'play-mini-fill', + 'play-mini-line', + 'playstation-fill', + 'playstation-line', + 'plug-2-fill', + 'plug-2-line', + 'plug-fill', + 'plug-line', + 'polaroid-2-fill', + 'polaroid-2-line', + 'polaroid-fill', + 'polaroid-line', + 'police-car-fill', + 'police-car-line', + 'price-tag-2-fill', + 'price-tag-2-line', + 'price-tag-3-fill', + 'price-tag-3-line', + 'price-tag-fill', + 'price-tag-line', + 'printer-cloud-fill', + 'printer-cloud-line', + 'printer-fill', + 'printer-line', + 'product-hunt-fill', + 'product-hunt-line', + 'profile-fill', + 'profile-line', + 'projector-2-fill', + 'projector-2-line', + 'projector-fill', + 'projector-line', + 'psychotherapy-fill', + 'psychotherapy-line', + 'pulse-fill', + 'pulse-line', + 'pushpin-2-fill', + 'pushpin-2-line', + 'pushpin-fill', + 'pushpin-line', + 'qq-fill', + 'qq-line', + 'qr-code-fill', + 'qr-code-line', + 'qr-scan-2-fill', + 'qr-scan-2-line', + 'qr-scan-fill', + 'qr-scan-line', + 'question-answer-fill', + 'question-answer-line', + 'question-fill', + 'question-line', + 'question-mark', + 'questionnaire-fill', + 'questionnaire-line', + 'quill-pen-fill', + 'quill-pen-line', + 'radar-fill', + 'radar-line', + 'radio-2-fill', + 'radio-2-line', + 'radio-button-fill', + 'radio-button-line', + 'radio-fill', + 'radio-line', + 'rainbow-fill', + 'rainbow-line', + 'rainy-fill', + 'rainy-line', + 'reactjs-fill', + 'reactjs-line', + 'record-circle-fill', + 'record-circle-line', + 'record-mail-fill', + 'record-mail-line', + 'recycle-fill', + 'recycle-line', + 'red-packet-fill', + 'red-packet-line', + 'reddit-fill', + 'reddit-line', + 'refresh-fill', + 'refresh-line', + 'refund-2-fill', + 'refund-2-line', + 'refund-fill', + 'refund-line', + 'registered-fill', + 'registered-line', + 'remixicon-fill', + 'remixicon-line', + 'remote-control-2-fill', + 'remote-control-2-line', + 'remote-control-fill', + 'remote-control-line', + 'repeat-2-fill', + 'repeat-2-line', + 'repeat-fill', + 'repeat-line', + 'repeat-one-fill', + 'repeat-one-line', + 'reply-all-fill', + 'reply-all-line', + 'reply-fill', + 'reply-line', + 'reserved-fill', + 'reserved-line', + 'rest-time-fill', + 'rest-time-line', + 'restart-fill', + 'restart-line', + 'restaurant-2-fill', + 'restaurant-2-line', + 'restaurant-fill', + 'restaurant-line', + 'rewind-fill', + 'rewind-line', + 'rewind-mini-fill', + 'rewind-mini-line', + 'rhythm-fill', + 'rhythm-line', + 'riding-fill', + 'riding-line', + 'road-map-fill', + 'road-map-line', + 'roadster-fill', + 'roadster-line', + 'robot-fill', + 'robot-line', + 'rocket-2-fill', + 'rocket-2-line', + 'rocket-fill', + 'rocket-line', + 'rotate-lock-fill', + 'rotate-lock-line', + 'rounded-corner', + 'route-fill', + 'route-line', + 'router-fill', + 'router-line', + 'rss-fill', + 'rss-line', + 'ruler-2-fill', + 'ruler-2-line', + 'ruler-fill', + 'ruler-line', + 'run-fill', + 'run-line', + 'safari-fill', + 'safari-line', + 'safe-2-fill', + 'safe-2-line', + 'safe-fill', + 'safe-line', + 'sailboat-fill', + 'sailboat-line', + 'save-2-fill', + 'save-2-line', + 'save-3-fill', + 'save-3-line', + 'save-fill', + 'save-line', + 'scales-2-fill', + 'scales-2-line', + 'scales-3-fill', + 'scales-3-line', + 'scales-fill', + 'scales-line', + 'scan-2-fill', + 'scan-2-line', + 'scan-fill', + 'scan-line', + 'scissors-2-fill', + 'scissors-2-line', + 'scissors-cut-fill', + 'scissors-cut-line', + 'scissors-fill', + 'scissors-line', + 'screenshot-2-fill', + 'screenshot-2-line', + 'screenshot-fill', + 'screenshot-line', + 'sd-card-fill', + 'sd-card-line', + 'sd-card-mini-fill', + 'sd-card-mini-line', + 'search-2-fill', + 'search-2-line', + 'search-eye-fill', + 'search-eye-line', + 'search-fill', + 'search-line', + 'secure-payment-fill', + 'secure-payment-line', + 'seedling-fill', + 'seedling-line', + 'send-backward', + 'send-plane-2-fill', + 'send-plane-2-line', + 'send-plane-fill', + 'send-plane-line', + 'send-to-back', + 'sensor-fill', + 'sensor-line', + 'separator', + 'server-fill', + 'server-line', + 'service-fill', + 'service-line', + 'settings-2-fill', + 'settings-2-line', + 'settings-3-fill', + 'settings-3-line', + 'settings-4-fill', + 'settings-4-line', + 'settings-5-fill', + 'settings-5-line', + 'settings-6-fill', + 'settings-6-line', + 'settings-fill', + 'settings-line', + 'shape-2-fill', + 'shape-2-line', + 'shape-fill', + 'shape-line', + 'share-box-fill', + 'share-box-line', + 'share-circle-fill', + 'share-circle-line', + 'share-fill', + 'share-forward-2-fill', + 'share-forward-2-line', + 'share-forward-box-fill', + 'share-forward-box-line', + 'share-forward-fill', + 'share-forward-line', + 'share-line', + 'shield-check-fill', + 'shield-check-line', + 'shield-cross-fill', + 'shield-cross-line', + 'shield-fill', + 'shield-flash-fill', + 'shield-flash-line', + 'shield-keyhole-fill', + 'shield-keyhole-line', + 'shield-line', + 'shield-star-fill', + 'shield-star-line', + 'shield-user-fill', + 'shield-user-line', + 'ship-2-fill', + 'ship-2-line', + 'ship-fill', + 'ship-line', + 'shirt-fill', + 'shirt-line', + 'shopping-bag-2-fill', + 'shopping-bag-2-line', + 'shopping-bag-3-fill', + 'shopping-bag-3-line', + 'shopping-bag-fill', + 'shopping-bag-line', + 'shopping-basket-2-fill', + 'shopping-basket-2-line', + 'shopping-basket-fill', + 'shopping-basket-line', + 'shopping-cart-2-fill', + 'shopping-cart-2-line', + 'shopping-cart-fill', + 'shopping-cart-line', + 'showers-fill', + 'showers-line', + 'shuffle-fill', + 'shuffle-line', + 'shut-down-fill', + 'shut-down-line', + 'side-bar-fill', + 'side-bar-line', + 'signal-tower-fill', + 'signal-tower-line', + 'signal-wifi-1-fill', + 'signal-wifi-1-line', + 'signal-wifi-2-fill', + 'signal-wifi-2-line', + 'signal-wifi-3-fill', + 'signal-wifi-3-line', + 'signal-wifi-error-fill', + 'signal-wifi-error-line', + 'signal-wifi-fill', + 'signal-wifi-line', + 'signal-wifi-off-fill', + 'signal-wifi-off-line', + 'sim-card-2-fill', + 'sim-card-2-line', + 'sim-card-fill', + 'sim-card-line', + 'single-quotes-l', + 'single-quotes-r', + 'sip-fill', + 'sip-line', + 'skip-back-fill', + 'skip-back-line', + 'skip-back-mini-fill', + 'skip-back-mini-line', + 'skip-forward-fill', + 'skip-forward-line', + 'skip-forward-mini-fill', + 'skip-forward-mini-line', + 'skull-2-fill', + 'skull-2-line', + 'skull-fill', + 'skull-line', + 'skype-fill', + 'skype-line', + 'slack-fill', + 'slack-line', + 'slice-fill', + 'slice-line', + 'slideshow-2-fill', + 'slideshow-2-line', + 'slideshow-3-fill', + 'slideshow-3-line', + 'slideshow-4-fill', + 'slideshow-4-line', + 'slideshow-fill', + 'slideshow-line', + 'smartphone-fill', + 'smartphone-line', + 'snapchat-fill', + 'snapchat-line', + 'snowy-fill', + 'snowy-line', + 'sort-asc', + 'sort-desc', + 'sound-module-fill', + 'sound-module-line', + 'soundcloud-fill', + 'soundcloud-line', + 'space-ship-fill', + 'space-ship-line', + 'space', + 'spam-2-fill', + 'spam-2-line', + 'spam-3-fill', + 'spam-3-line', + 'spam-fill', + 'spam-line', + 'speaker-2-fill', + 'speaker-2-line', + 'speaker-3-fill', + 'speaker-3-line', + 'speaker-fill', + 'speaker-line', + 'spectrum-fill', + 'spectrum-line', + 'speed-fill', + 'speed-line', + 'speed-mini-fill', + 'speed-mini-line', + 'split-cells-horizontal', + 'split-cells-vertical', + 'spotify-fill', + 'spotify-line', + 'spy-fill', + 'spy-line', + 'stack-fill', + 'stack-line', + 'stack-overflow-fill', + 'stack-overflow-line', + 'stackshare-fill', + 'stackshare-line', + 'star-fill', + 'star-half-fill', + 'star-half-line', + 'star-half-s-fill', + 'star-half-s-line', + 'star-line', + 'star-s-fill', + 'star-s-line', + 'star-smile-fill', + 'star-smile-line', + 'steam-fill', + 'steam-line', + 'steering-2-fill', + 'steering-2-line', + 'steering-fill', + 'steering-line', + 'stethoscope-fill', + 'stethoscope-line', + 'sticky-note-2-fill', + 'sticky-note-2-line', + 'sticky-note-fill', + 'sticky-note-line', + 'stock-fill', + 'stock-line', + 'stop-circle-fill', + 'stop-circle-line', + 'stop-fill', + 'stop-line', + 'stop-mini-fill', + 'stop-mini-line', + 'store-2-fill', + 'store-2-line', + 'store-3-fill', + 'store-3-line', + 'store-fill', + 'store-line', + 'strikethrough-2', + 'strikethrough', + 'subscript-2', + 'subscript', + 'subtract-fill', + 'subtract-line', + 'subway-fill', + 'subway-line', + 'subway-wifi-fill', + 'subway-wifi-line', + 'suitcase-2-fill', + 'suitcase-2-line', + 'suitcase-3-fill', + 'suitcase-3-line', + 'suitcase-fill', + 'suitcase-line', + 'sun-cloudy-fill', + 'sun-cloudy-line', + 'sun-fill', + 'sun-foggy-fill', + 'sun-foggy-line', + 'sun-line', + 'superscript-2', + 'superscript', + 'surgical-mask-fill', + 'surgical-mask-line', + 'surround-sound-fill', + 'surround-sound-line', + 'survey-fill', + 'survey-line', + 'swap-box-fill', + 'swap-box-line', + 'swap-fill', + 'swap-line', + 'switch-fill', + 'switch-line', + 'sword-fill', + 'sword-line', + 'syringe-fill', + 'syringe-line', + 't-box-fill', + 't-box-line', + 't-shirt-2-fill', + 't-shirt-2-line', + 't-shirt-air-fill', + 't-shirt-air-line', + 't-shirt-fill', + 't-shirt-line', + 'table-2', + 'table-alt-fill', + 'table-alt-line', + 'table-fill', + 'table-line', + 'tablet-fill', + 'tablet-line', + 'takeaway-fill', + 'takeaway-line', + 'taobao-fill', + 'taobao-line', + 'tape-fill', + 'tape-line', + 'task-fill', + 'task-line', + 'taxi-fill', + 'taxi-line', + 'taxi-wifi-fill', + 'taxi-wifi-line', + 'team-fill', + 'team-line', + 'telegram-fill', + 'telegram-line', + 'temp-cold-fill', + 'temp-cold-line', + 'temp-hot-fill', + 'temp-hot-line', + 'terminal-box-fill', + 'terminal-box-line', + 'terminal-fill', + 'terminal-line', + 'terminal-window-fill', + 'terminal-window-line', + 'test-tube-fill', + 'test-tube-line', + 'text-direction-l', + 'text-direction-r', + 'text-spacing', + 'text-wrap', + 'text', + 'thermometer-fill', + 'thermometer-line', + 'thumb-down-fill', + 'thumb-down-line', + 'thumb-up-fill', + 'thumb-up-line', + 'thunderstorms-fill', + 'thunderstorms-line', + 'ticket-2-fill', + 'ticket-2-line', + 'ticket-fill', + 'ticket-line', + 'time-fill', + 'time-line', + 'timer-2-fill', + 'timer-2-line', + 'timer-fill', + 'timer-flash-fill', + 'timer-flash-line', + 'timer-line', + 'todo-fill', + 'todo-line', + 'toggle-fill', + 'toggle-line', + 'tools-fill', + 'tools-line', + 'tornado-fill', + 'tornado-line', + 'trademark-fill', + 'trademark-line', + 'traffic-light-fill', + 'traffic-light-line', + 'train-fill', + 'train-line', + 'train-wifi-fill', + 'train-wifi-line', + 'translate-2', + 'translate', + 'travesti-fill', + 'travesti-line', + 'treasure-map-fill', + 'treasure-map-line', + 'trello-fill', + 'trello-line', + 'trophy-fill', + 'trophy-line', + 'truck-fill', + 'truck-line', + 'tumblr-fill', + 'tumblr-line', + 'tv-2-fill', + 'tv-2-line', + 'tv-fill', + 'tv-line', + 'twitch-fill', + 'twitch-line', + 'twitter-fill', + 'twitter-line', + 'typhoon-fill', + 'typhoon-line', + 'u-disk-fill', + 'u-disk-line', + 'ubuntu-fill', + 'ubuntu-line', + 'umbrella-fill', + 'umbrella-line', + 'underline', + 'uninstall-fill', + 'uninstall-line', + 'unsplash-fill', + 'unsplash-line', + 'upload-2-fill', + 'upload-2-line', + 'upload-cloud-2-fill', + 'upload-cloud-2-line', + 'upload-cloud-fill', + 'upload-cloud-line', + 'upload-fill', + 'upload-line', + 'usb-fill', + 'usb-line', + 'user-2-fill', + 'user-2-line', + 'user-3-fill', + 'user-3-line', + 'user-4-fill', + 'user-4-line', + 'user-5-fill', + 'user-5-line', + 'user-6-fill', + 'user-6-line', + 'user-add-fill', + 'user-add-line', + 'user-fill', + 'user-follow-fill', + 'user-follow-line', + 'user-heart-fill', + 'user-heart-line', + 'user-line', + 'user-location-fill', + 'user-location-line', + 'user-received-2-fill', + 'user-received-2-line', + 'user-received-fill', + 'user-received-line', + 'user-search-fill', + 'user-search-line', + 'user-settings-fill', + 'user-settings-line', + 'user-shared-2-fill', + 'user-shared-2-line', + 'user-shared-fill', + 'user-shared-line', + 'user-smile-fill', + 'user-smile-line', + 'user-star-fill', + 'user-star-line', + 'user-unfollow-fill', + 'user-unfollow-line', + 'user-voice-fill', + 'user-voice-line', + 'video-add-fill', + 'video-add-line', + 'video-chat-fill', + 'video-chat-line', + 'video-download-fill', + 'video-download-line', + 'video-fill', + 'video-line', + 'video-upload-fill', + 'video-upload-line', + 'vidicon-2-fill', + 'vidicon-2-line', + 'vidicon-fill', + 'vidicon-line', + 'vimeo-fill', + 'vimeo-line', + 'vip-crown-2-fill', + 'vip-crown-2-line', + 'vip-crown-fill', + 'vip-crown-line', + 'vip-diamond-fill', + 'vip-diamond-line', + 'vip-fill', + 'vip-line', + 'virus-fill', + 'virus-line', + 'visa-fill', + 'visa-line', + 'voice-recognition-fill', + 'voice-recognition-line', + 'voiceprint-fill', + 'voiceprint-line', + 'volume-down-fill', + 'volume-down-line', + 'volume-mute-fill', + 'volume-mute-line', + 'volume-off-vibrate-fill', + 'volume-off-vibrate-line', + 'volume-up-fill', + 'volume-up-line', + 'volume-vibrate-fill', + 'volume-vibrate-line', + 'vuejs-fill', + 'vuejs-line', + 'walk-fill', + 'walk-line', + 'wallet-2-fill', + 'wallet-2-line', + 'wallet-3-fill', + 'wallet-3-line', + 'wallet-fill', + 'wallet-line', + 'water-flash-fill', + 'water-flash-line', + 'webcam-fill', + 'webcam-line', + 'wechat-2-fill', + 'wechat-2-line', + 'wechat-fill', + 'wechat-line', + 'wechat-pay-fill', + 'wechat-pay-line', + 'weibo-fill', + 'weibo-line', + 'whatsapp-fill', + 'whatsapp-line', + 'wheelchair-fill', + 'wheelchair-line', + 'wifi-fill', + 'wifi-line', + 'wifi-off-fill', + 'wifi-off-line', + 'window-2-fill', + 'window-2-line', + 'window-fill', + 'window-line', + 'windows-fill', + 'windows-line', + 'windy-fill', + 'windy-line', + 'wireless-charging-fill', + 'wireless-charging-line', + 'women-fill', + 'women-line', + 'wubi-input', + 'xbox-fill', + 'xbox-line', + 'xing-fill', + 'xing-line', + 'youtube-fill', + 'youtube-line', + 'zcool-fill', + 'zcool-line', + 'zhihu-fill', + 'zhihu-line', + 'zoom-in-fill', + 'zoom-in-line', + 'zoom-out-fill', + 'zoom-out-line', + 'zzz-fill', + 'zzz-line', +] + +module.exports = [ + { + url: '/defaultIcon/getList', + type: 'get', + response(config) { + const { title, pageNo = 1, pageSize = 72 } = config.query + const mockList = List.filter( + (item) => !(title && !item.includes(title)) + ) + const list = mockList.filter( + (item, index) => + index < pageSize * pageNo && + index >= pageSize * (pageNo - 1) + ) + return { + code: 200, + msg: 'success', + data: { list, ...{ total: mockList.length } }, + } + }, + }, +] diff --git a/front-end/mock/controller/departmentManagement.js b/front-end/mock/controller/departmentManagement.js new file mode 100644 index 0000000..83ad380 --- /dev/null +++ b/front-end/mock/controller/departmentManagement.js @@ -0,0 +1,87 @@ +const List = [ + { + id: 'root', + createTime: '@datetime', + name: '根节点', + order: 0, + children: [ + { + id: '1', + parentId: 'root', + parentName: '根节点', + createTime: '@datetime', + name: '桃花坞', + order: 0, + }, + { + id: '2', + parentId: 'root', + parentName: '根节点', + createTime: '@datetime', + name: '少林寺', + order: 1, + children: [ + { + id: '@uuid', + parentId: '2', + parentName: '少林寺', + createTime: '@datetime', + name: '达摩院', + order: 0, + }, + { + id: '@uuid', + parentId: '2', + parentName: '少林寺', + createTime: '@datetime', + name: '戒律堂', + order: 1, + }, + ], + }, + ], + }, +] + +module.exports = [ + { + url: '/departmentManagement/getList', + type: 'get', + response: (config) => { + const { name, pageNo = 1, pageSize = 20 } = config.query + const mockList = List.filter( + (item) => !(name && !item.name.includes(name)) + ) + const list = mockList.filter( + (item, index) => + index < pageSize * pageNo && + index >= pageSize * (pageNo - 1) + ) + return { + code: 200, + msg: 'success', + data: { list, ...{ total: mockList.length } }, + } + }, + }, + { + url: '/departmentManagement/doEdit', + type: 'post', + response: () => { + return { + code: 200, + msg: '模拟保存成功', + } + }, + }, + { + url: '/departmentManagement/doDelete', + type: 'post', + response: () => { + return { + code: 200, + msg: '模拟删除成功', + } + }, + }, +] diff --git a/front-end/mock/controller/dictionaryManagement.js b/front-end/mock/controller/dictionaryManagement.js new file mode 100644 index 0000000..fc30a9c --- /dev/null +++ b/front-end/mock/controller/dictionaryManagement.js @@ -0,0 +1,114 @@ +module.exports = [ + { + url: '/dictionaryManagement/getTree', + type: 'get', + response() { + return { + code: 200, + msg: 'success', + data: { + total: 999, + list: [ + { + id: 'root', + key: 'root', + label: '全部字典', + children: [ + { + id: '@id', + key: 'sex', + label: '性别', + }, + { + id: '@id', + key: 'type', + label: '类型', + }, + ], + }, + ], + }, + } + }, + }, + { + url: '/dictionaryManagement/getList', + type: 'get', + response: (config) => { + const { key, parentKey } = config.query + const list1 = [ + { + parentKey: 'sex', + id: '@id', + lable: '性别', + key: '1', + value: '男', + }, + { + parentKey: 'sex', + id: '@id', + lable: '性别', + key: '2', + value: '女', + }, + ] + const list2 = [ + { + parentKey: 'type', + id: '@id', + lable: '类型', + key: '1', + value: '新闻', + }, + { + parentKey: 'type', + id: '@id', + lable: '类型', + key: '2', + value: '知识', + }, + ] + if (parentKey) { + return { + code: 200, + msg: 'success', + data: { + list: parentKey === 'sex' ? list1 : list2, + }, + } + } + return { + code: 200, + msg: 'success', + data: { + list: + !key || key === 'root' + ? [] + : key === 'sex' + ? list1 + : list2, + }, + } + }, + }, + { + url: '/dictionaryManagement/doEdit', + type: 'post', + response: () => { + return { + code: 200, + msg: '模拟保存成功', + } + }, + }, + { + url: '/dictionaryManagement/doDelete', + type: 'post', + response: () => { + return { + code: 200, + msg: '模拟删除成功', + } + }, + }, +] diff --git a/front-end/mock/controller/goods.js b/front-end/mock/controller/goods.js new file mode 100644 index 0000000..bb9d1b2 --- /dev/null +++ b/front-end/mock/controller/goods.js @@ -0,0 +1,115 @@ +const List = [ + { + uuid: '@uuid', + image: 'https://cn.vitejs.dev/logo-with-shadow.png', + title: 'Shop Vite', + price: '敬请期待', + label: ['极简', 'vite'], + company: 'vdp', + url: 'https://vuejs-core.cn/shop-vite', + description: '全新一代前端框架', + }, + { + uuid: '@uuid', + icon: 'dashboard-2-line', + title: 'Dash' + 'board Pro', + price: 'Admin Pro + Admin Plus 1299版' + '本赠品', + label: ['敬请期待', '人工智能', '科技风'], + company: 'vdp', + url: 'https://vuejs-core.cn/dashboard-pro', + description: 'Admin Pro + Adm' + 'in Plus 1299版本赠品', + }, + { + uuid: '@uuid', + image: + 'https://p3-armor.byteimg.com/tos-cn-i-49unhts6dw/dfdba' + + '5317c0c20ce20e64fac8' + + '03d52bc.svg~tplv-49unhts6dw-image.image', + title: 'vue-admin-arco', + price: '免费', + label: ['vue3', 'arco-design', 'MIT协议'], + company: 'vab', + url: 'https://github.com/zxwk1998/vue-admin-arco', + description: + '在字节跳动' + + 'arco-desi' + + 'gn-pro-vue基础上修改的' + + 'vue3版本,仅供学' + + '习参考', + }, + { + uuid: '@uuid', + svg: 'https://gcore.jsdelivr.net/gh/zxwk1998/image/logo/vab.svg', + title: 'vue-admin-better', + price: '免费', + label: ['vue', 'element-ui', 'MIT协议'], + company: 'vab', + url: 'https://github.com/zxwk1998/vue-admin-better', + description: '绝佳的开源、' + '企业级、' + '中后台前端框架', + }, + { + uuid: '@uuid', + svg: 'https://gcore.jsdelivr.net/gh/zxwk1998/image/logo/vdb.svg', + title: 'vue-datav-beautiful-pro', + price: 'Admin Pro + Adm' + 'in Plus 1299版本赠品', + label: ['数据大屏'], + company: 'vdb', + url: 'https://github.com/vue-datav-beautiful', + description: '立志做' + '国内最好' + '的数据大屏应用', + }, + { + uuid: '@uuid', + image: 'https://xuqu.gitee.io/common/logo.png', + title: 'uview', + price: '免费', + label: ['uniapp', '跨平台', '组件'], + company: '言信网络', + url: 'http://uviewui.com', + description: + '跨 7 端移动端框架,全面的组件和便' + + '捷的工具会让您信手拈来,如鱼得水', + }, + { + uuid: '@uuid', + title: 'form-generator', + image: 'https://mrhj.gitee.io/form-generator/img/logo.e1bc3747.png', + price: '免费', + label: ['element-ui', '表单设计器'], + company: 'form-generator', + url: 'https://mrhj.gitee.io/form-generator', + description: 'Element UI表单设' + '计及代码生成器', + }, + { + uuid: '@uuid', + title: 'OPSLI', + image: 'https://www.opsli.com/static/images/favicon.ico', + price: '免费', + label: ['spring-boot', 'vue-admin-better'], + company: 'OPSLI', + url: 'https://www.op' + 'sli.com', + description: 'vue-admin-better开源' + '版结合spring boot的最佳实践', + }, +] + +module.exports = [ + { + url: '/goods/getList', + type: 'get', + response(config) { + const { title, pageNo = 1, pageSize = 20 } = config.query + const mockList = List.filter( + (item) => !(title && !item.title.includes(title)) + ) + const list = mockList.filter( + (item, index) => + index < pageSize * pageNo && + index >= pageSize * (pageNo - 1) + ) + return { + code: 200, + msg: 'success', + data: { list, ...{ total: mockList.length } }, + } + }, + }, +] diff --git a/front-end/mock/controller/menuManagement.js b/front-end/mock/controller/menuManagement.js new file mode 100644 index 0000000..8ab3fdb --- /dev/null +++ b/front-end/mock/controller/menuManagement.js @@ -0,0 +1,53 @@ +module.exports = [ + { + url: '/menuManagement/getTree', + type: 'get', + response() { + return { + code: 200, + msg: 'success', + data: { + total: 999, + list: [ + { + id: 'root', + label: '全部角色', + children: [ + { + id: '@id', + role: 'admin', + label: 'admin角色', + }, + { + id: '@id', + role: 'editor', + label: 'editor角色', + }, + ], + }, + ], + }, + } + }, + }, + { + url: '/menuManagement/doEdit', + type: 'post', + response() { + return { + code: 200, + msg: '模拟保存成功', + } + }, + }, + { + url: '/menuManagement/doDelete', + type: 'post', + response() { + return { + code: 200, + msg: '模拟删除成功', + } + }, + }, +] diff --git a/front-end/mock/controller/notice.js b/front-end/mock/controller/notice.js new file mode 100644 index 0000000..14b3ab6 --- /dev/null +++ b/front-end/mock/controller/notice.js @@ -0,0 +1,36 @@ +const List = [ + { + email: '@email', + image: 'https://i.gtimg.cn/club/item/face/img/8/15918_100.gif', + notice: 'Github开源地址:点我', + }, + { + email: '@email', + image: 'https://i.gtimg.cn/club/item/face/img/0/15640_100.gif', + notice: 'Admin Pro:点我', + }, + { + email: '@email', + image: 'https://i.gtimg.cn/club/item/face/img/9/15919_100.gif', + notice: 'Admin Plus:点我', + }, + { + email: '@email', + image: 'https://i.gtimg.cn/club/item/face/img/8/15918_100.gif', + notice: 'Shop Vite:点我', + }, +] + +module.exports = [ + { + url: '/notice/getList', + type: 'get', + response: () => { + return { + code: 200, + msg: 'success', + data: { list: List, total: List.length }, + } + }, + }, +] diff --git a/front-end/mock/controller/refreshToken.js b/front-end/mock/controller/refreshToken.js new file mode 100644 index 0000000..e76a320 --- /dev/null +++ b/front-end/mock/controller/refreshToken.js @@ -0,0 +1,46 @@ +const { Random } = require('mockjs') + +module.exports = [ + { + url: '/expireToken', + type: 'get', + response: (config) => { + const authorization = + config.headers.authorization || config.headers.Authorization + const arr = authorization.split('-') + const tokenTime = parseInt(arr[arr.length - 1]) + + if (Date.now() - tokenTime > 5000) + return { + code: 402, + msg: '令牌已过期', + } + else + return { + code: 200, + msg: '令牌未过期', + } + }, + }, + { + url: '/refreshToken', + type: 'get', + response: (config) => { + const authorization = + config.headers.authorization || config.headers.Authorization + let token = '' + if (authorization.includes('admin-token')) + token = `admin-token-${Random.guid()}-${Date.now()}` + if (authorization.includes('editor-token')) + token = `editor-token-${Random.guid()}-${Date.now()}` + if (authorization.includes('test-token')) + token = `test-token-${Random.guid()}-${Date.now()}` + + return { + code: 200, + msg: '刷新Token成功', + data: { token }, + } + }, + }, +] diff --git a/front-end/mock/controller/roleManagement.js b/front-end/mock/controller/roleManagement.js new file mode 100644 index 0000000..f8ec921 --- /dev/null +++ b/front-end/mock/controller/roleManagement.js @@ -0,0 +1,55 @@ +const List = [ + { + id: '@id', + role: 'admin', + btnRolesCheckedList: ['read:system', 'write:system', 'delete:system'], + }, + { + id: '@id', + role: 'editor', + btnRolesCheckedList: ['read:system', 'write:system'], + }, +] + +module.exports = [ + { + url: '/roleManagement/getList', + type: 'get', + response(config) { + const { role, pageNo = 1, pageSize = 20 } = config.query + const mockList = List.filter( + (item) => !(role && !item.title.includes(role)) + ) + const list = mockList.filter( + (item, index) => + index < pageSize * pageNo && + index >= pageSize * (pageNo - 1) + ) + return { + code: 200, + msg: 'success', + data: { list, ...{ total: mockList.length } }, + } + }, + }, + { + url: '/roleManagement/doEdit', + type: 'post', + response() { + return { + code: 200, + msg: '模拟保存成功', + } + }, + }, + { + url: '/roleManagement/doDelete', + type: 'post', + response() { + return { + code: 200, + msg: '模拟删除成功', + } + }, + }, +] diff --git a/front-end/mock/controller/router.js b/front-end/mock/controller/router.js new file mode 100644 index 0000000..d687f2c --- /dev/null +++ b/front-end/mock/controller/router.js @@ -0,0 +1,1019 @@ +/** + * @description router全局配置,如有必要可分文件抽离,其中asyncRoutes只有在intelligence模式下才会用到,pro版只支持remixIcon图标,具体配置请查看vip群文档 + */ +const List = [ + { + path: '/', + name: 'Root', + component: 'Layout', + meta: { + title: '首页', + icon: 'home-2-line', + breadcrumbHidden: true, + }, + children: [ + { + path: 'index', + name: 'Index', + component: '@/views/index/index', + meta: { + title: '首页', + icon: 'home-2-line', + noClosable: true, + }, + }, + { + path: 'dashboard', + name: 'Dashboard', + component: '@/views/index/dashboard', + meta: { + title: '看板', + icon: 'dashboard-line', + }, + }, + { + path: 'workbench', + name: 'Workbench', + component: '@/views/index/workbench', + meta: { + title: '工作台', + icon: 'settings-6-line', + dot: true, + }, + }, + { + path: 'store', + name: 'Store', + component: '@/views/index/store', + meta: { + title: '仓库', + icon: 'app-store-line', + dot: true, + }, + }, + { + path: 'pricing', + name: 'Pricing', + component: '@/views/index/Pricing.vue', + meta: { + title: '授权与定价', + guard: ['Admin'], + icon: 'price-tag-3-line', + badge: 'Hot', + }, + }, + { + path: 'friendly-tip', + name: 'FriendlyTip', + component: '@/views/index/FriendlyTip.vue', + meta: { + title: '温馨提示', + icon: 'information-line', + hidden: true, + }, + }, + ], + }, + { + path: '/vab', + name: 'Vab', + component: 'Layout', + meta: { + title: '组件', + icon: 'code-box-line', + }, + children: [ + { + path: 'icon', + name: 'Icon', + meta: { + title: '图标', + icon: 'remixicon-line', + }, + children: [ + { + path: 'defaultIcon', + name: 'DefaultIcon', + component: '@/views/vab/icon/defaultIcon', + meta: { + title: '默认图标', + }, + }, + { + path: 'iconSelector', + name: 'IconSelector', + component: '@/views/vab/icon/iconSelector', + meta: { + title: '图标选择器', + }, + }, + { + path: 'customSvg', + name: 'CustomSvg', + component: '@/views/vab/icon/customSvg', + meta: { + title: '自定义图标', + }, + }, + ], + }, + { + path: 'permission', + name: 'Permission', + component: '@/views/vab/permission/index', + meta: { + title: '角色权限', + icon: 'user-3-line', + badge: 'Pro', + }, + }, + { + path: 'table', + name: 'Table', + meta: { + title: '表格', + // 非editor角色的用户可见 + guard: { + role: ['Editor'], + mode: 'except', + }, + icon: 'table-2', + }, + children: [ + { + path: 'comprehensiveTable', + name: 'ComprehensiveTable', + component: '@/views/vab/table/comprehensiveTable', + meta: { + title: '综合表格', + }, + }, + { + path: 'detail', + name: 'Detail', + component: '@/views/vab/table/detail', + meta: { + hidden: true, + title: '详情页', + activeMenu: '/vab/table/comprehensiveTable', + dynamicNewTab: true, //详情页根据id传参不同可打开多个 + }, + }, + { + path: 'inlineEditTable', + name: 'InlineEditTable', + component: '@/views/vab/table/inlineEditTable', + meta: { + title: '行内编辑表格', + noKeepAlive: true, + }, + }, + { + path: 'customTable', + name: 'CustomTable', + component: '@/views/vab/table/customTable', + meta: { + title: '自定义表格', + }, + }, + { + path: 'dynamicTable', + name: 'DynamicTable', + component: '@/views/vab/table/dynamicTable', + meta: { + title: '动态表格', + badge: 'New', + }, + }, + ], + }, + { + path: 'list', + name: 'List', + component: '@/views/vab/list/index', + meta: { + title: '列表', + guard: ['Admin'], + icon: 'list-check-2', + }, + }, + { + path: 'description', + name: 'Description', + component: '@/views/vab/description/index', + meta: { + title: '描述', + guard: ['Admin'], + icon: 'slideshow-line', + }, + }, + { + path: 'calendar', + name: 'Calendar', + component: '@/views/vab/calendar/index', + meta: { + title: '日历', + guard: ['Admin'], + icon: 'calendar-check-line', + dot: true, + }, + }, + { + path: 'editor', + name: 'Editor', + meta: { + title: '编辑器', + guard: ['Admin'], + icon: 'edit-2-line', + }, + children: [ + { + path: 'wangEditor', + name: 'WangEditor', + component: '@/views/vab/editor/wangEditor', + meta: { + title: '腾讯文档', + guard: ['Admin'], + dot: true, + }, + }, + ], + }, + { + path: 'form', + name: 'Form', + meta: { + title: '表单', + guard: ['Admin'], + icon: 'file-list-2-line', + }, + children: [ + { + path: 'comprehensiveForm', + name: 'ComprehensiveForm', + component: '@/views/vab/form/comprehensiveForm', + meta: { + title: '综合表单', + }, + }, + { + path: 'stepForm', + name: 'StepForm', + component: '@/views/vab/form/stepForm', + meta: { + title: '分步表单', + }, + }, + { + path: 'button', + name: 'Button', + component: '@/views/vab/form/button', + meta: { + title: '按钮', + }, + }, + { + path: 'link', + name: 'Link', + component: '@/views/vab/form/link', + meta: { + title: '文字链接', + }, + }, + { + path: 'radio', + name: 'Radio', + component: '@/views/vab/form/radio', + meta: { + title: '单选框', + }, + }, + { + path: 'checkbox', + name: 'Checkbox', + component: '@/views/vab/form/checkbox', + meta: { + title: '多选框', + }, + }, + { + path: 'input', + name: 'Input', + component: '@/views/vab/form/input', + meta: { + title: '输入框', + }, + }, + { + path: 'inputNumber', + name: 'InputNumber', + component: '@/views/vab/form/inputNumber', + meta: { + title: '计数器', + }, + }, + { + path: 'select', + name: 'Select', + component: '@/views/vab/form/select', + meta: { + title: '选择器', + dot: true, + }, + }, + { + path: 'switch', + name: 'Switch', + component: '@/views/vab/form/switch', + meta: { + title: '开关', + }, + }, + { + path: 'slider', + name: 'Slider', + component: '@/views/vab/form/slider', + meta: { + title: '滑块', + }, + }, + { + path: 'timePicker', + name: 'TimePicker', + component: '@/views/vab/form/timePicker', + meta: { + title: '时间选择器', + }, + }, + { + path: 'datePicker', + name: 'DatePicker', + component: '@/views/vab/form/datePicker', + meta: { + title: '日期选择器', + }, + }, + { + path: 'dateTimePicker', + name: 'DateTimePicker', + component: '@/views/vab/form/dateTimePicker', + meta: { + title: '日期时间选择器', + }, + }, + { + path: 'rate', + name: 'Rate', + component: '@/views/vab/form/rate', + meta: { + title: '评分', + }, + }, + ], + }, + ], + }, + { + path: '/other', + name: 'Other', + component: 'Layout', + meta: { + title: '其他', + icon: 'archive-line', + guard: ['Admin'], + }, + children: [ + { + path: 'workflow', + name: 'Workflow', + component: '@/views/other/workflow/index', + meta: { + title: '工作流', + guard: ['Admin'], + icon: 'flow-chart', + }, + }, + { + path: 'echarts', + name: 'Echarts', + component: '@/views/other/echarts/index', + meta: { + title: '图表', + guard: ['Admin'], + icon: 'bubble-chart-line', + }, + }, + { + path: 'print', + name: 'Print', + component: '@/views/other/print/index', + meta: { + title: '打印', + guard: ['Admin'], + icon: 'printer-line', + }, + }, + { + path: 'cropper', + name: 'Cropper', + component: '@/views/other/cropper/index', + meta: { + title: '头像裁剪', + guard: ['Admin'], + icon: 'crop-line', + }, + }, + { + path: 'notice', + name: 'Notice', + component: '@/views/other/notice/index', + meta: { + title: '通知', + guard: ['Admin'], + icon: 'message-2-line', + }, + }, + { + path: 'timeline', + name: 'Timeline', + component: '@/views/other/timeline/index', + meta: { + title: '时间线', + guard: ['Admin'], + icon: 'time-line', + }, + }, + { + path: 'count', + name: 'Count', + component: '@/views/other/count/index', + meta: { + title: '数字自增长', + guard: ['Admin'], + icon: 'number-9', + }, + }, + { + path: 'tabs', + name: 'Tabs', + component: '@/views/other/tabs/index', + meta: { + title: '多标签', + guard: ['Admin'], + icon: 'bank-card-line', + }, + }, + { + path: 'watermark', + name: 'Watermark', + component: '@/views/other/watermark/index', + meta: { + title: '水印', + guard: ['Admin'], + icon: 'water-flash-line', + dot: true, + }, + }, + { + path: 'share', + name: 'Share', + component: '@/views/other/share/index', + meta: { + title: '分享', + guard: ['Admin'], + icon: 'share-line', + dot: true, + }, + }, + { + path: 'dynamicAnchor', + name: 'DynamicAnchor', + component: '@/views/other/dynamicAnchor/index', + meta: { + title: '动态锚点', + guard: ['Admin'], + icon: 'anchor-line', + badge: 'New', + }, + }, + { + path: 'dynamicMeta', + name: 'DynamicMeta', + component: '@/views/other/dynamicMeta/index', + meta: { + title: '动态Meta', + guard: ['Admin'], + icon: 'notification-badge-line', + badge: '0', + }, + }, + { + path: 'dynamicSegment', + name: 'DynamicSegment', + redirect: '/other/dynamicSegment/test1/1', + meta: { + title: '动态路径参数', + guard: ['Admin'], + icon: 'arrow-left-right-line', + }, + children: [ + { + path: 'test1/:id', + name: 'Test1', + component: '@/views/other/dynamicSegment/test1', + meta: { + hidden: true, + title: 'Params', + dynamicNewTab: true, + }, + }, + { + path: 'test1/1', + name: 'test1/1', + component: '@/views/other/dynamicSegment/test1', + meta: { title: 'Params id=1' }, + }, + { + path: 'test2', + name: 'Test2', + component: '@/views/other/dynamicSegment/test2', + meta: { + hidden: true, + title: 'Query', + dynamicNewTab: true, + }, + }, + { + path: 'test2?id=1', + name: 'test2?id=1', + component: '@/views/other/dynamicSegment/test2', + meta: { title: 'Query id=1' }, + }, + ], + }, + { + path: 'drag', + name: 'Drag', + meta: { + title: '拖拽', + guard: ['Admin'], + icon: 'drag-drop-line', + }, + children: [ + { + path: 'dialogDrag', + name: 'DialogDrag', + component: '@/views/other/drag/dialogDrag', + meta: { + title: '弹窗拖拽', + dot: true, + }, + }, + { + path: 'cardDrag', + name: 'CardDrag', + component: '@/views/other/drag/cardDrag', + meta: { + title: '卡片拖拽', + }, + }, + ], + }, + { + path: 'noLayout', + name: 'NoLayout', + component: '@/views/other/noLayout/index', + meta: { + title: '无框', + guard: ['Admin'], + icon: 'aspect-ratio-line', + dot: true, + }, + }, + { + path: 'player', + name: 'Player', + component: '@/views/other/player/index', + meta: { + title: '视频播放器', + guard: ['Admin'], + icon: 'video-line', + noKeepAlive: true, + }, + }, + { + path: 'upload', + name: 'Upload', + component: '@/views/other/upload/index', + meta: { + title: '上传', + guard: ['Admin'], + icon: 'chat-upload-line', + }, + }, + { + path: 'menu1', + name: 'Menu1', + meta: { + title: '多级路由缓存', + guard: ['Admin'], + icon: 'route-line', + }, + children: [ + { + path: 'menu1-1', + name: 'Menu11', + meta: { + title: '多级路由1-1', + }, + children: [ + { + path: 'menu1-1-1', + name: 'Menu111', + meta: { + title: '多级路由1-1-1', + }, + children: [ + { + path: 'menu1-1-1-1', + name: 'Menu1111', + meta: { + title: '多级路由1-1-1-1', + }, + component: + '@/views/other/nested/menu1/menu1-1/menu1-1-1/menu1-1-1-1/index', + }, + ], + }, + ], + }, + ], + }, + { + path: 'log', + name: 'Log', + component: '@/views/other/errorLog/index', + meta: { + title: '错误日志模拟', + guard: ['Admin'], + icon: 'error-warning-line', + }, + }, + { + path: 'cssfx', + name: 'Cssfx', + component: '@/views/other/cssfx/index', + meta: { + title: 'Css动画', + guard: ['Admin'], + icon: 'css3-line', + }, + }, + { + path: 'social', + name: 'Social', + component: '@/views/other/social/index', + meta: { + title: '第三方登录', + guard: ['Admin'], + icon: 'github-fill', + }, + }, + // { + // path: 'mobilePreview', + // name: 'MobilePreview', + // component: '@/views/vab/mobilePreview', + // meta: { + // title: '手机预览', + // guard: ['Admin'], + // icon: 'smartphone-line', + // }, + // }, + { + path: '//github.com/zxwk1998/vue-admin-better', + name: 'ExternalLink', + meta: { + title: '外链', + target: '_blank', + // 等价guard: ['Admin', 'Editor'], + guard: { + role: ['Admin', 'Editor'], + mode: 'oneOf', + }, + icon: 'external-link-line', + }, + }, + { + path: 'iframe', + name: 'Iframe', + redirect: '/other/iframe/search', + meta: { + title: 'Iframe', + guard: ['Admin'], + icon: 'window-line', + }, + children: [ + { + path: 'view', + name: 'IframeView', + component: '@/views/other/iframe/view', + meta: { + hidden: true, + title: 'Iframe', + icon: 'window-line', + dynamicNewTab: true, + }, + }, + { + path: 'view?url=www.so.com&title=360%E6%90%9C%E7%B4%A2&icon=search-2-line', + name: 'Search360Iframe', + meta: { title: '360搜索', icon: 'search-2-line' }, + }, + { + path: 'view?url=www.bilibili.com&title=%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9&icon=bilibili-line', + name: 'BiliBiliIframe', + meta: { title: '哔哩哔哩', icon: 'bilibili-line' }, + }, + { + path: 'search', + name: 'IframeSearch', + component: '@/views/other/iframe/search', + meta: { + title: '自定义Iframe', + icon: 'search-2-line', + }, + }, + ], + }, + { + path: 'excel', + name: 'Excel', + meta: { + title: 'Excel', + guard: ['Admin'], + icon: 'file-excel-2-line', + }, + children: [ + { + path: 'exportExcel', + name: 'ExportExcel', + component: '@/views/other/excel/exportExcel', + meta: { + title: '导出Excel', + }, + }, + { + path: 'exportSelectedExcel', + name: 'SelectExcel', + component: '@/views/other/excel/exportSelectExcel', + meta: { + title: '导出选中行Excel', + }, + }, + { + path: 'exportMergeHeaderExcel', + name: 'MergeHeaderExcel', + component: '@/views/other/excel/exportMergeHeaderExcel', + meta: { + title: '导出合并Excel', + }, + }, + ], + }, + ], + }, + { + path: '/mall', + name: 'Mall', + component: 'Layout', + meta: { + title: '物料源', + icon: 'apps-line', + levelHidden: true, + guard: ['Admin'], + }, + children: [ + { + path: 'goods', + name: 'Goods', + component: '@/views/mall/goods/index', + meta: { + title: '物料市场', + icon: 'shopping-cart-line', + badge: 'Hot', + }, + }, + ], + }, + { + path: '/noColumn', + name: 'NoColumn', + component: 'Layout', + meta: { + title: '无分栏', + icon: 'delete-column', + guard: ['Admin'], + breadcrumbHidden: true, + }, + children: [ + { + path: 'deleteColumn', + name: 'DeleteColumn', + component: '@/views/noColumn/deleteColumn/index', + meta: { + title: '无分栏', + icon: 'delete-column', + noColumn: true, + }, + }, + ], + }, + { + path: '/setting', + name: 'PersonnelManagement', + component: 'Layout', + meta: { + title: '配置', + icon: 'user-settings-line', + guard: ['Admin'], + }, + children: [ + { + path: 'personalCenter', + name: 'PersonalCenter', + component: '@/views/setting/personalCenter/index', + meta: { + title: '个人中心', + icon: 'map-pin-user-line', + }, + }, + { + path: 'userManagement', + name: 'UserManagement', + component: '@/views/setting/userManagement/index', + meta: { + title: '用户管理', + icon: 'user-3-line', + }, + }, + { + path: 'roleManagement', + name: 'RoleManagement', + component: '@/views/setting/roleManagement/index', + meta: { + title: '角色管理', + icon: 'admin-line', + }, + }, + { + path: 'departmentManagement', + name: 'DepartmentManagement', + component: '@/views/setting/departmentManagement/index', + meta: { + title: '部门管理', + icon: 'group-line', + }, + }, + { + path: 'menuManagement', + name: 'MenuManagement', + component: '@/views/setting/menuManagement/index', + meta: { + title: '菜单管理', + icon: 'menu-2-fill', + }, + }, + { + path: 'dictionaryManagement', + name: 'DictionaryManagement', + component: '@/views/setting/dictionaryManagement/index', + meta: { + title: '字典管理', + icon: 'book-2-line', + dot: true, + }, + }, + { + path: 'taskManagement', + name: 'TaskManagement', + component: '@/views/setting/taskManagement/index', + meta: { + title: '任务管理', + icon: 'task-line', + badge: 'New', + }, + }, + { + path: 'systemLog', + name: 'SystemLog', + component: '@/views/setting/systemLog/index', + meta: { + title: '系统日志', + icon: 'file-shield-2-line', + }, + }, + ], + }, + { + path: '/tools', + name: 'Tools', + component: 'Layout', + meta: { + title: '工具', + icon: 'tools-line', + levelHidden: true, + guard: ['Admin'], + }, + children: [ + { + path: 'eyeDropper', + name: 'EyeDropper', + component: '@/views/tools/EyeDropper.vue', + meta: { + title: '取色器', + icon: 'contrast-drop-line', + }, + }, + { + path: 'speechSynthesis', + name: 'SpeechSynthesis', + component: '@/views/tools/SpeechSynthesis.vue', + meta: { + title: '语音合成', + icon: 'customer-service-line', + }, + }, + ], + }, + { + path: '//github.com/zxwk1998/vue-admin-better', + name: 'Github', + component: 'Layout', + meta: { + title: '外链', + icon: 'external-link-line', + guard: ['Admin'], + target: '_blank', + breadcrumbHidden: true, + noColumn: true, + }, + children: [ + { + path: '//github.com/zxwk1998/vue-admin-better', + name: 'GithubExternalLink', + component: '@/views/github/githubExternalLink/index', + meta: { + title: '外链', + icon: 'external-link-line', + noColumn: true, + target: '_blank', + }, + }, + ], + }, + + { + path: '/error', + name: 'Error', + component: 'Layout', + meta: { + title: '错误页', + icon: 'error-warning-line', + levelHidden: true, + }, + children: [ + { + path: '403', + name: 'Error403', + component: '@/views/403', + meta: { + title: '403', + icon: 'error-warning-line', + }, + }, + { + path: '404', + name: 'Error404', + component: '@/views/404', + meta: { + title: '404', + icon: 'error-warning-line', + }, + }, + ], + }, +] + +module.exports = [ + { + url: '/router/getList', + type: 'get', + response() { + return { + code: 200, + msg: 'success', + data: { list: List }, + } + }, + }, +] diff --git a/front-end/mock/controller/search.js b/front-end/mock/controller/search.js new file mode 100644 index 0000000..a9735c3 --- /dev/null +++ b/front-end/mock/controller/search.js @@ -0,0 +1,20 @@ +const List = [ + { + url: 'https://www.bing.com/search?q=vue+admin+plus%e5%ae%98%e7%bd%91&qs=HS&pq=vue+admin+plus&sk=HS1&sc=10-14&cvid=B01F4326D6724F76B568CBF127648BB8&FORM=QBRE&sp=2&lq=0&rdr=1&rdrig=7414F5AB9CF241C78B8CC476818B3569', + value: '官网', + }, +] + +module.exports = [ + { + url: '/search/getList', + type: 'get', + response: () => { + return { + code: 200, + msg: 'success', + data: { list: List }, + } + }, + }, +] diff --git a/front-end/mock/controller/systemLog.js b/front-end/mock/controller/systemLog.js new file mode 100644 index 0000000..a886e6b --- /dev/null +++ b/front-end/mock/controller/systemLog.js @@ -0,0 +1,47 @@ +const { mock } = require('mockjs') + +const List = [] +const count = 50 +for (let i = 0; i < count; i++) { + List.push( + mock({ + uuid: '@uuid', + id: '@id', + account: '@account(1, 2)', + 'type|1': ['操作日志', '数据库日志', '系统日志'], + 'account|1': ['admin', 'editor', 'test'], + 'executeResult|1': [ + '登录成功', + '登录成功', + '登录失败', + '接口异常', + 'dos攻击', + ], + ip: '@ip', + datetime: '@datetime', + }) + ) +} + +module.exports = [ + { + url: '/systemLog/getList', + type: 'get', + response: (config) => { + const { account, pageNo = 1, pageSize = 20 } = config.query + const mockList = List.filter( + (item) => !(account && !item.account.includes(account)) + ) + const list = mockList.filter( + (item, index) => + index < pageSize * pageNo && + index >= pageSize * (pageNo - 1) + ) + return { + code: 200, + msg: 'success', + data: { list, ...{ total: mockList.length } }, + } + }, + }, +] diff --git a/front-end/mock/controller/table.js b/front-end/mock/controller/table.js new file mode 100644 index 0000000..5245b9c --- /dev/null +++ b/front-end/mock/controller/table.js @@ -0,0 +1,68 @@ +const { mock } = require('mockjs') +const { handleRandomImage } = require('../utils') + +const List = [] +const count = 50 +for (let i = 0; i < count; i++) { + List.push( + mock({ + uuid: '@uuid', + id: '@id', + title: '@title(1, 2)', + description: '@csentence', + 'status|1': ['published', 'draft', 'deleted'], + author: '@cname', + datetime: '@datetime', + pageViews: '@integer(300, 5000)', + img: handleRandomImage(228, 228), + switch: '@boolean', + percent: '@integer(80,99)', + 'rate|1': [1, 2, 3, 4, 5], + 'type|1': [0, 1], + percentage: '@integer(0,100)', + }) + ) +} + +module.exports = [ + { + url: '/table/getList', + type: 'get', + response(config) { + const { title, pageNo = 1, pageSize = 20 } = config.query + const mockList = List.filter( + (item) => !(title && !item.title.includes(title)) + ) + const list = mockList.filter( + (item, index) => + index < pageSize * pageNo && + index >= pageSize * (pageNo - 1) + ) + return { + code: 200, + msg: 'success', + data: { list, ...{ total: mockList.length } }, + } + }, + }, + { + url: '/table/doEdit', + type: 'post', + response() { + return { + code: 200, + msg: '模拟保存成功', + } + }, + }, + { + url: '/table/doDelete', + type: 'post', + response() { + return { + code: 200, + msg: '模拟删除成功', + } + }, + }, +] diff --git a/front-end/mock/controller/taskManagement.js b/front-end/mock/controller/taskManagement.js new file mode 100644 index 0000000..ec8ebb7 --- /dev/null +++ b/front-end/mock/controller/taskManagement.js @@ -0,0 +1,47 @@ +const { mock } = require('mockjs') + +const List = [] +const count = 50 +for (let i = 0; i < count; i++) { + List.push( + mock({ + uuid: '@uuid', + id: '@id', + taskName: '@account(1, 2)', + 'status|1': [0, 1], + 'account|1': ['admin', 'editor', 'test'], + 'executeResult|1': [ + '登录成功', + '登录成功', + '登录失败', + '接口异常', + 'dos攻击', + ], + ip: '@ip', + datetime: '@datetime', + }) + ) +} + +module.exports = [ + { + url: '/taskManagement/getList', + type: 'get', + response: (config) => { + const { account, pageNo = 1, pageSize = 20 } = config.query + const mockList = List.filter( + (item) => !(account && !item.account.includes(account)) + ) + const list = mockList.filter( + (item, index) => + index < pageSize * pageNo && + index >= pageSize * (pageNo - 1) + ) + return { + code: 200, + msg: 'success', + data: { list, ...{ total: mockList.length } }, + } + }, + }, +] diff --git a/front-end/mock/controller/user.js b/front-end/mock/controller/user.js new file mode 100644 index 0000000..0e903d4 --- /dev/null +++ b/front-end/mock/controller/user.js @@ -0,0 +1,125 @@ +const { Random } = require('mockjs') + +const tokens = { + admin: `admin-token-${Random.guid()}-${Date.now()}`, + editor: `editor-token-${Random.guid()}-${Date.now()}`, + test: `test-token-${Random.guid()}-${Date.now()}`, +} +const username2role = { + admin: ['Admin'], + editor: ['Editor'], + test: ['Admin', 'Editor'], +} +const role2permission = { + Admin: ['read:system', 'write:system', 'delete:system'], + Editor: ['read:system', 'write:system'], + Test: ['read:system'], +} + +module.exports = [ + { + url: '/publicKey', + type: 'get', + response() { + return { + code: 200, + msg: 'success', + data: { + mockServer: true, + publicKey: + 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBT2vr+dhZElF73FJ6xiP181txKWUSNLPQQlid6DUJhGAOZblluafIdLmnUyKE8mMHhT3R+Ib3ssZcJku6Hn72yHYj/qPkCGFv0eFo7G+GJfDIUeDyalBN0QsuiE/XzPHJBuJDfRArOiWvH0BXOv5kpeXSXM8yTt5Na1jAYSiQ/wIDAQAB', + }, + } + }, + }, + { + url: '/login', + type: 'post', + response(config) { + const { username } = config.body + const token = tokens[username] + if (!token) + return { + code: 500, + msg: '帐户或密码不正确', + } + return { + code: 200, + msg: 'success', + data: { token }, + } + }, + }, + { + url: '/socialLogin', + type: 'post', + response(config) { + const { code } = config.body + if (!code) + return { + code: 500, + msg: '未成功获取Token', + } + + return { + code: 200, + msg: 'success', + data: { token: tokens['admin'] }, + } + }, + }, + { + url: '/register', + type: 'post', + response() { + return { + code: 200, + msg: '模拟注册成功', + data: { token: tokens['editor'] }, + } + }, + }, + { + url: '/userInfo', + type: 'get', + response(config) { + const authorization = + config.headers.authorization || config.headers.Authorization + if (!authorization.startsWith('Bearer ')) + return { + code: 401, + msg: '令牌无效', + } + const _authorization = authorization.replace('Bearer ', '') + const isTrue = _authorization.includes('-token-') + const username = isTrue + ? _authorization.split('-token-')[0] + : 'admin' + const roles = username2role[username] || [] + const permissions = [ + ...new Set(roles.flatMap((role) => role2permission[role])), + ] + + return { + code: 200, + msg: 'success', + data: { + username, + roles, + permissions, + avatar: 'https://i.gtimg.cn/club/item/face/img/2/16022_100.gif', + }, + } + }, + }, + { + url: '/logout', + type: 'get', + response() { + return { + code: 200, + msg: 'success', + } + }, + }, +] diff --git a/front-end/mock/controller/userManagement.js b/front-end/mock/controller/userManagement.js new file mode 100644 index 0000000..6ae4822 --- /dev/null +++ b/front-end/mock/controller/userManagement.js @@ -0,0 +1,69 @@ +const List = [ + { + id: '@id', + username: 'admin', + password: 'admin', + email: '@email', + roles: ['admin'], + datatime: '@datetime', + }, + { + id: '@id', + username: 'editor', + password: 'editor', + email: '@email', + roles: ['editor'], + datatime: '@datetime', + }, + { + id: '@id', + username: 'test', + password: 'test', + email: '@email', + roles: ['admin', 'editor'], + datatime: '@datetime', + }, +] + +module.exports = [ + { + url: '/userManagement/getList', + type: 'get', + response(config) { + const { username, pageNo = 1, pageSize = 20 } = config.query + const mockList = List.filter( + (item) => !(username && !item.username.includes(username)) + ) + const list = mockList.filter( + (item, index) => + index < pageSize * pageNo && + index >= pageSize * (pageNo - 1) + ) + return { + code: 200, + msg: 'success', + data: { list, ...{ total: mockList.length } }, + } + }, + }, + { + url: '/userManagement/doEdit', + type: 'post', + response() { + return { + code: 200, + msg: '模拟保存成功', + } + }, + }, + { + url: '/userManagement/doDelete', + type: 'post', + response() { + return { + code: 200, + msg: '模拟删除成功', + } + }, + }, +] diff --git a/front-end/mock/controller/workflow.js b/front-end/mock/controller/workflow.js new file mode 100644 index 0000000..779d701 --- /dev/null +++ b/front-end/mock/controller/workflow.js @@ -0,0 +1,365 @@ +const data = { + nodes: [ + { + id: '742356ea-762b-4899-b96a-bd567e3c4361', + type: 'start', + x: 220, + y: 170, + properties: {}, + baseType: 'node', + }, + { + id: 'dacda6b6-48d3-4dff-911d-287704eb23d8', + type: 'rect', + x: 350, + y: 170, + properties: {}, + baseType: 'node', + text: { + x: 350, + y: 170, + value: '基础节点', + }, + }, + { + id: '49106603-2b88-4b2c-b1e8-723c1f2210bd', + type: 'user', + x: 530, + y: 170, + properties: {}, + baseType: 'node', + text: { + x: 530, + y: 220, + value: '自定义节点', + }, + }, + { + id: '647fa2bc-98ee-40cf-99c5-4756c0bc130d', + type: 'push', + x: 690, + y: 170, + properties: {}, + baseType: 'node', + text: { + x: 690, + y: 220, + value: '可添加下一个节点/节点组', + }, + }, + { + id: '37e7bac3-8804-4237-abe9-7b6065c207e9', + type: 'download', + x: 690, + y: 320, + properties: {}, + baseType: 'node', + }, + { + id: '6bb4396f-54c9-4b1c-b34c-87ef004f2e29', + type: 'user', + x: 840, + y: 320, + properties: {}, + baseType: 'node', + }, + { + id: 'abf76937-63b8-493c-a978-a4a58bc4f6b8', + type: 'push', + x: 840, + y: 470, + properties: {}, + baseType: 'node', + }, + { + id: 'b119f24f-2669-4a90-a837-afd853b2ffcc', + type: 'end', + x: 990, + y: 320, + properties: {}, + baseType: 'node', + }, + { + id: '60326ad9-cae2-4a85-ae98-d340fb7bd67f', + type: 'end', + x: 990, + y: 470, + properties: {}, + baseType: 'node', + }, + { + id: '414fe028-3609-4450-b0f4-e5aca7705e8c', + type: 'download', + x: 860, + y: 170, + properties: {}, + baseType: 'node', + text: { + x: 860, + y: 220, + value: '自定义节点-设置颜色', + }, + }, + ], + edges: [ + { + id: '00f55245-513e-43a2-9cb0-adb61b01adc8', + type: 'polyline', + sourceNodeId: '742356ea-762b-4899-b96a-bd567e3c4361', + targetNodeId: 'dacda6b6-48d3-4dff-911d-287704eb23d8', + startPoint: { + x: 240, + y: 170, + }, + endPoint: { + x: 300, + y: 170, + }, + properties: {}, + pointsList: [ + { + x: 240, + y: 170, + }, + { + x: 300, + y: 170, + }, + ], + }, + { + id: 'bbf9754f-603e-48e4-85fe-84ed44459a6a', + type: 'polyline', + sourceNodeId: 'dacda6b6-48d3-4dff-911d-287704eb23d8', + targetNodeId: '49106603-2b88-4b2c-b1e8-723c1f2210bd', + startPoint: { + x: 400, + y: 170, + }, + endPoint: { + x: 495, + y: 170, + }, + properties: {}, + pointsList: [ + { + x: 400, + y: 170, + }, + { + x: 495, + y: 170, + }, + ], + }, + { + id: '12bb443b-4070-4a08-ad4d-2755ee856f0d', + type: 'polyline', + sourceNodeId: '49106603-2b88-4b2c-b1e8-723c1f2210bd', + targetNodeId: '647fa2bc-98ee-40cf-99c5-4756c0bc130d', + startPoint: { + x: 565, + y: 170, + }, + endPoint: { + x: 655, + y: 170, + }, + properties: {}, + pointsList: [ + { + x: 565, + y: 170, + }, + { + x: 655, + y: 170, + }, + ], + }, + { + id: '33fa3c09-9c29-4cb7-8373-67d537b8b623', + type: 'polyline', + sourceNodeId: '647fa2bc-98ee-40cf-99c5-4756c0bc130d', + targetNodeId: '37e7bac3-8804-4237-abe9-7b6065c207e9', + startPoint: { + x: 690, + y: 205, + }, + endPoint: { + x: 690, + y: 295, + }, + properties: {}, + pointsList: [ + { + x: 690, + y: 205, + }, + { + x: 690, + y: 295, + }, + ], + }, + { + id: '2b5a5e89-005e-4fda-9a44-dc795050534f', + type: 'polyline', + sourceNodeId: '37e7bac3-8804-4237-abe9-7b6065c207e9', + targetNodeId: '6bb4396f-54c9-4b1c-b34c-87ef004f2e29', + startPoint: { + x: 715, + y: 320, + }, + endPoint: { + x: 805, + y: 320, + }, + properties: {}, + pointsList: [ + { + x: 715, + y: 320, + }, + { + x: 805, + y: 320, + }, + ], + }, + { + id: '62b54f8a-bcfd-494b-9144-5aeb09ca77a1', + type: 'polyline', + sourceNodeId: '6bb4396f-54c9-4b1c-b34c-87ef004f2e29', + targetNodeId: 'b119f24f-2669-4a90-a837-afd853b2ffcc', + startPoint: { + x: 875, + y: 320, + }, + endPoint: { + x: 970, + y: 320, + }, + properties: {}, + text: { + x: 920, + y: 310, + value: 'Y', + }, + pointsList: [ + { + x: 875, + y: 320, + }, + { + x: 970, + y: 320, + }, + ], + }, + { + id: 'ba816d4a-5785-4911-9f78-03933f1463a1', + type: 'polyline', + sourceNodeId: '6bb4396f-54c9-4b1c-b34c-87ef004f2e29', + targetNodeId: 'abf76937-63b8-493c-a978-a4a58bc4f6b8', + startPoint: { + x: 840, + y: 355, + }, + endPoint: { + x: 840, + y: 435, + }, + properties: {}, + text: { + x: 850, + y: 400, + value: 'N', + }, + pointsList: [ + { + x: 840, + y: 355, + }, + { + x: 840, + y: 435, + }, + ], + }, + { + id: '2b3007ed-7a13-4db7-a1ea-6691d7564c34', + type: 'polyline', + sourceNodeId: 'abf76937-63b8-493c-a978-a4a58bc4f6b8', + targetNodeId: '60326ad9-cae2-4a85-ae98-d340fb7bd67f', + startPoint: { + x: 875, + y: 470, + }, + endPoint: { + x: 970, + y: 470, + }, + properties: {}, + pointsList: [ + { + x: 875, + y: 470, + }, + { + x: 970, + y: 470, + }, + ], + }, + { + id: '262e2263-6c8c-4a38-b223-97848e9b5767', + type: 'polyline', + sourceNodeId: '647fa2bc-98ee-40cf-99c5-4756c0bc130d', + targetNodeId: '414fe028-3609-4450-b0f4-e5aca7705e8c', + startPoint: { + x: 725, + y: 170, + }, + endPoint: { + x: 835, + y: 170, + }, + properties: {}, + pointsList: [ + { + x: 725, + y: 170, + }, + { + x: 835, + y: 170, + }, + ], + }, + ], +} + +module.exports = [ + { + url: '/workflow/getList', + type: 'get', + response: () => { + return { + code: 200, + msg: 'success', + data, + } + }, + }, + { + url: '/workflow/doEdit', + type: 'post', + response: () => { + return { + code: 200, + msg: '模拟保存成功', + } + }, + }, +] diff --git a/front-end/mock/index.js b/front-end/mock/index.js new file mode 100644 index 0000000..0ebb361 --- /dev/null +++ b/front-end/mock/index.js @@ -0,0 +1,112 @@ +const path = require('path') +const chokidarNext = require('chokidar') +const bodyParser = require('body-parser') +const chalkNext = require('chalk') +const { mock } = require('mockjs') +const { baseURL } = require('../src/config') + +const mockDir = path.join(process.cwd(), 'mock') +const { handleMockArray } = require('./utils') + +/** + * + * @param url + * @param type + * @param respond + * @returns {{response(*=, *=): void, type: (*|string), url: RegExp}} + */ +const responseFake = (url, type, respond) => { + return { + url: new RegExp(`${baseURL}${url}`), + type: type || 'get', + response(req, res) { + res.status(200) + console.log(chalkNext.green(`\n> 请求地址:${req.path}`)) + if (JSON.stringify(req.body) !== '{}') + console.log( + chalkNext.green( + `> 请求参数(body):${JSON.stringify(req.body)}` + ) + ) + if (JSON.stringify(req.query) !== '{}') + console.log( + chalkNext.green( + `> 请求参数(query):${JSON.stringify(req.query)}` + ) + ) + res.json( + mock(respond instanceof Function ? respond(req, res) : respond) + ) + }, + } +} + +/** + * + * @param app + * @returns {{mockStartIndex: number, mockRoutesLength: number}} + */ +const registerRoutes = (app) => { + let mockLastIndex + const mocks = [] + const mockArray = handleMockArray() + mockArray.forEach((item) => { + const obj = require(item) + mocks.push(...obj) + }) + const mocksForServer = mocks.map((route) => + responseFake(route.url, route.type, route.response) + ) + const mockRoutesLength = Object.keys(mocksForServer).length + for (const item of mocksForServer) { + app[item.type](item.url, item.response) + mockLastIndex = app._router.stack.length + } + return { + mockRoutesLength, + mockStartIndex: mockLastIndex - mockRoutesLength, + } +} + +/** + * + * @param middlewares + * @param devServer + */ +module.exports = (middlewares, devServer) => { + if (!devServer) { + throw new Error('webpack-dev-server is not defined') + } + const app = devServer.app + app.use(bodyParser.json()) + app.use( + bodyParser.urlencoded({ + extended: true, + }) + ) + const mockRoutes = registerRoutes(app) + let mockRoutesLength = mockRoutes.mockRoutesLength + let mockStartIndex = mockRoutes.mockStartIndex + chokidarNext + .watch(mockDir, { + ignored: /vab-mock-server/, + ignoreInitial: true, + }) + .on('all', (event) => { + if (event === 'change' || event === 'add') { + try { + app._router.stack.splice(mockStartIndex, mockRoutesLength) + Object.keys(require.cache).forEach((item) => { + if (item.includes(mockDir)) + delete require.cache[require.resolve(item)] + }) + const mockRoutes = registerRoutes(app) + mockRoutesLength = mockRoutes.mockRoutesLength + mockStartIndex = mockRoutes.mockStartIndex + } catch (error) { + console.log(chalkNext.red(error)) + } + } + }) + return middlewares +} diff --git a/front-end/mock/utils/index.js b/front-end/mock/utils/index.js new file mode 100644 index 0000000..26e3c01 --- /dev/null +++ b/front-end/mock/utils/index.js @@ -0,0 +1,39 @@ +const fs = require('fs') +const { Random } = require('mockjs') + +/** + * @description 随机生成图片url。 + * @returns {string} + */ +function handleRandomImage(/* width = 50, height = 50 */) { + //return `https://picsum.photos/${width}/${height}?random=${Random.guid()}` + return ( + 'https://gcore.jsdelivr.net/gh/' + + 'chuzh' + + 'ixin/image' + + `/table/vab-image-${Random.integer(1, 38)}.jpg` + ) +} + +/** + * @description 处理所有 controller 模块,npm run serve时在node环境中自动输出controller文件夹下Mock接口,请勿修改。 + * @returns {[]} + */ +function handleMockArray() { + const getFiles = (path, baseUrl = './controller') => { + const files = fs.readdirSync(path) + return files.flatMap((file) => { + const fPath = `${path}/${file}` + const stat = fs.statSync(fPath) + return stat.isDirectory() + ? getFiles(fPath, `${baseUrl}/${file}`) + : `${baseUrl}/${file}` + }) + } + return getFiles('mock/controller') +} + +module.exports = { + handleRandomImage, + handleMockArray, +} diff --git a/front-end/package.json b/front-end/package.json new file mode 100644 index 0000000..b016bd3 --- /dev/null +++ b/front-end/package.json @@ -0,0 +1,143 @@ +{ + "name": "admin-plus", + "version": "23.0.1", + "private": true, + "author": "github.com/zxwk1998", + "scripts": { + "serve": "cross-env NODE_OPTIONS=--no-warnings vue-cli-service serve", + "build": "cross-env NODE_OPTIONS=--no-warnings vue-cli-service build", + "build:website": "cross-env NODE_OPTIONS=--no-warnings VAB_VARIABLE=website vue-cli-service build && npm run compress:website", + "compress:website": "node scripts/compress.js", + "build:compress": "cross-env NODE_OPTIONS=--no-warnings vue-cli-service build && npm run compress", + "compress": "node scripts/compress.js", + "test:unit": "vue-cli-service test:unit --detectOpenHandles", + "lint": "vue-cli-service lint", + "build:report": "cross-env NODE_OPTIONS=--no-warnings vue-cli-service build --report", + "build:test": "cross-env NODE_OPTIONS=--no-warnings vue-cli-service build --mode test", + "global:install": "npm install -g nrm,cnpm,npm-check-updates,rimraf --registry=https://registry.npmmirror.com", + "global:update": "ncu -g", + "lint:eslint": "eslint {src,mock,library}/**/*.{vue,js,ts} --fix", + "lint:prettier": "prettier {src,mock,library}/**/*.{html,vue,css,sass,scss,js,ts,md} --write", + "lint:stylelint": "stylelint {src,mock,library}/**/*.{vue,css,sass,scss} --fix --cache --cache-location node_modules/.cache/stylelint/", + "module:install": "pnpm i", + "module:reinstall": "rimraf node_modules && npm run module:install", + "module:update": "ncu -u --reject vue-i18n,sass-loader,unplugin-vue-define-options,@logicflow/core,@logicflow/extension,sass,chalk,unplugin-auto-import,unplugin-vue-components,eslint-plugin-vue,eslint --registry=https://registry.npmmirror.com&&npm run module:install", + "nrm:npm": "nrm use npm", + "nrm:taobao": "nrm use taobao", + "template": "plop", + "git": "start ./git.sh" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.2", + "@logicflow/core": "2.1.1", + "@logicflow/extension": "2.1.2", + "@vueuse/core": "^14.1.0", + "@vueuse/head": "^2.0.0", + "@wangeditor/editor": "^5.1.23", + "@wangeditor/editor-for-vue": "^5.1.12", + "axios": "^1.13.2", + "core-js": "^3.47.0", + "dayjs": "^1.11.19", + "disable-devtool": "^0.3.9", + "echarts": "^6.0.0", + "element-plus": "^2.13.0", + "file-saver": "^2.0.5", + "image-conversion": "^2.1.1", + "js-cookie": "^3.0.5", + "jsencrypt": "^3.5.4", + "lodash": "^4.17.21", + "mitt": "^3.0.1", + "mockjs": "^1.1.0", + "nprogress": "^0.2.0", + "path-browserify": "^1.0.1", + "pinia": "^3.0.4", + "qs": "^6.14.0", + "register-service-worker": "^1.7.2", + "resize-detector": "^0.3.0", + "vab-icons": "file:vab-icons", + "vue": "^3.5.26", + "vue-i18n": "^11.1.12", + "vue-json-viewer": "^3.0.4", + "vue-qr": "^4.0.9", + "vue-router": "^4.6.4", + "vuedraggable": "^4.0.1", + "xlsx": "0.18.5" + }, + "devDependencies": { + "@babel/core": "^7.28.5", + "@element-plus/eslint-config": "2.13.0", + "@rushstack/eslint-patch": "^1.15.0", + "@types/file-saver": "^2.0.7", + "@types/js-cookie": "^3.0.6", + "@types/lodash-es": "^4.17.12", + "@types/node": "^25.0.3", + "@types/nprogress": "^0.2.3", + "@vue/cli-plugin-babel": "^5.0.9", + "@vue/cli-plugin-eslint": "^5.0.9", + "@vue/cli-plugin-pwa": "^5.0.9", + "@vue/cli-plugin-router": "^5.0.9", + "@vue/cli-plugin-typescript": "^5.0.9", + "@vue/cli-service": "^5.0.9", + "@vue/eslint-config-prettier": "^10.2.0", + "@vue/eslint-config-typescript": "^14.6.0", + "@vue/test-utils": "^2.4.6", + "archiver": "^7.0.1", + "body-parser": "^2.2.1", + "call-rely": "^1.3.4", + "chalk": "4.1.2", + "chokidar": "^5.0.0", + "compression-webpack-plugin": "^11.1.0", + "cross-env": "^10.1.0", + "eslint": "^8.57.0", + "eslint-define-config": "^2.1.0", + "eslint-plugin-prettier": "^5.5.4", + "eslint-plugin-vue": "9.18.1", + "filemanager-webpack-plugin": "^9.0.1", + "image-webpack-loader": "^8.1.0", + "less-loader": "^12.3.0", + "lint-staged": "^16.2.7", + "picocolors": "^1.1.1", + "plop": "^4.0.4", + "plop-templates": "^0.0.9", + "postcss": "^8.5.6", + "postcss-html": "^1.8.0", + "prettier": "^3.7.4", + "raw-loader": "^4.0.2", + "sass": "1.79.5", + "sass-loader": "16.0.1", + "stylelint": "^16.26.1", + "stylelint-config-recess-order": "^7.4.0", + "stylelint-config-recommended-scss": "^16.0.2", + "stylelint-config-recommended-vue": "^1.6.1", + "svg-sprite-loader": "^6.0.11", + "typescript": "^5.9.3", + "unplugin-auto-import": "^0.16.7", + "unplugin-element-plus": "^0.11.2", + "unplugin-vue-components": "0.25.2", + "unplugin-vue-define-options": "^1.5.5", + "vue-eslint-parser": "^10.2.0", + "vue-global-api": "^0.4.1", + "vue-unplugins": "^1.0.6", + "webpack": "^5.104.1", + "webpackbar": "^7.0.0" + }, + "gitHooks": { + "pre-commit": "lint-staged" + }, + "homepage": "https://vuejs-core.cn/admin-plus", + "license": "Mozilla Public License Version 2.0", + "lint-staged": { + "*.{js,jsx,vue}": [ + "vue-cli-service lint", + "git add" + ] + }, + "files": [], + "participants": [ + "FlowPeakFish" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/zxwk2024/admin-plus.git" + } +} diff --git a/front-end/plopfile.js b/front-end/plopfile.js new file mode 100644 index 0000000..2bdbf74 --- /dev/null +++ b/front-end/plopfile.js @@ -0,0 +1,11 @@ +const viewGenerator = require('plop-templates/view/prompt') +const curdGenerator = require('plop-templates/curd/prompt') +const componentGenerator = require('plop-templates/component/prompt') +const mockGenerator = require('plop-templates/mock/prompt') + +module.exports = (plop) => { + plop.setGenerator('view', viewGenerator) + plop.setGenerator('curd', curdGenerator) + plop.setGenerator('component', componentGenerator) + plop.setGenerator('mock&api', mockGenerator) +} diff --git a/front-end/prettier.config.js b/front-end/prettier.config.js new file mode 100644 index 0000000..61a0dbb --- /dev/null +++ b/front-end/prettier.config.js @@ -0,0 +1,16 @@ +module.exports = { + printWidth: 80, + tabWidth: 4, + useTabs: false, + semi: false, + singleQuote: true, + quoteProps: 'as-needed', + jsxSingleQuote: false, + trailingComma: 'es5', + bracketSpacing: true, + bracketSameLine: false, + arrowParens: 'always', + htmlWhitespaceSensitivity: 'ignore', + vueIndentScriptAndStyle: true, + endOfLine: 'lf', +} diff --git a/front-end/public/favicon.ico b/front-end/public/favicon.ico new file mode 100644 index 0000000..7c31c49 Binary files /dev/null and b/front-end/public/favicon.ico differ diff --git a/front-end/public/img/icons/android-chrome-192x192.png b/front-end/public/img/icons/android-chrome-192x192.png new file mode 100644 index 0000000..11b6098 Binary files /dev/null and b/front-end/public/img/icons/android-chrome-192x192.png differ diff --git a/front-end/public/img/icons/android-chrome-512x512.png b/front-end/public/img/icons/android-chrome-512x512.png new file mode 100644 index 0000000..1500c07 Binary files /dev/null and b/front-end/public/img/icons/android-chrome-512x512.png differ diff --git a/front-end/public/img/icons/android-chrome-maskable-192x192.png b/front-end/public/img/icons/android-chrome-maskable-192x192.png new file mode 100644 index 0000000..1fb9c26 Binary files /dev/null and b/front-end/public/img/icons/android-chrome-maskable-192x192.png differ diff --git a/front-end/public/img/icons/android-chrome-maskable-512x512.png b/front-end/public/img/icons/android-chrome-maskable-512x512.png new file mode 100644 index 0000000..c85a8ec Binary files /dev/null and b/front-end/public/img/icons/android-chrome-maskable-512x512.png differ diff --git a/front-end/public/img/icons/apple-touch-icon-152x152.png b/front-end/public/img/icons/apple-touch-icon-152x152.png new file mode 100644 index 0000000..51c71ab Binary files /dev/null and b/front-end/public/img/icons/apple-touch-icon-152x152.png differ diff --git a/front-end/public/img/icons/favicon-16x16.png b/front-end/public/img/icons/favicon-16x16.png new file mode 100644 index 0000000..23f3197 Binary files /dev/null and b/front-end/public/img/icons/favicon-16x16.png differ diff --git a/front-end/public/img/icons/favicon-32x32.png b/front-end/public/img/icons/favicon-32x32.png new file mode 100644 index 0000000..9083725 Binary files /dev/null and b/front-end/public/img/icons/favicon-32x32.png differ diff --git a/front-end/public/img/icons/msapplication-icon-144x144.png b/front-end/public/img/icons/msapplication-icon-144x144.png new file mode 100644 index 0000000..7d991a6 Binary files /dev/null and b/front-end/public/img/icons/msapplication-icon-144x144.png differ diff --git a/front-end/public/img/icons/safari-pinned-tab.svg b/front-end/public/img/icons/safari-pinned-tab.svg new file mode 100644 index 0000000..fd609b1 --- /dev/null +++ b/front-end/public/img/icons/safari-pinned-tab.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/front-end/public/index.html b/front-end/public/index.html new file mode 100644 index 0000000..459adc4 --- /dev/null +++ b/front-end/public/index.html @@ -0,0 +1,51 @@ + + + + + + + + + <%= VUE_APP_TITLE %> + + + + + + +

+
+
+ + + + + + +
+

<%= VUE_APP_TITLE %>

+
+
+ + + + diff --git a/front-end/public/json/china.json b/front-end/public/json/china.json new file mode 100644 index 0000000..0c92e71 --- /dev/null +++ b/front-end/public/json/china.json @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","id":"710000","properties":{"id":"710000","cp":[121.509062,24.044332],"name":"台湾","childNum":6},"geometry":{"type":"MultiPolygon","coordinates":[["@@°Ü¯Û"],["@@ƛĴÕƊÉɼģºðʀ\\ƎsÆNŌÔĚäœnÜƤɊĂǀĆĴžĤNJŨxĚĮǂƺòƌ‚–âÔ®ĮXŦţƸZûЋƕƑGđ¨ĭMó·ęcëƝɉlÝƯֹÅŃ^Ó·śŃNjƏďíåɛGɉ™¿@ăƑŽ¥ĘWǬÏĶŁâ"],["@@\\p|WoYG¿¥I†j@¢"],["@@…¡‰@ˆV^RqˆBbAŒnTXeRz¤Lž«³I"],["@@ÆEE—„kWqë @œ"],["@@fced"],["@@„¯ɜÄèaì¯ØǓIġĽ"],["@@çûĖ롖hòř "]],"encodeOffsets":[[[122886,24033]],[[123335,22980]],[[122375,24193]],[[122518,24117]],[[124427,22618]],[[124862,26043]],[[126259,26318]],[[127671,26683]]]}},{"type":"Feature","id":"130000","properties":{"id":"130000","cp":[114.502461,38.045474],"name":"河北","childNum":3},"geometry":{"type":"MultiPolygon","coordinates":[["@@o~†Z]‚ªr‰ºc_ħ²G¼s`jΟnüsœłNX_“M`ǽÓnUK…Ĝēs¤­©yrý§uģŒc†JŠ›e"],["@@U`Ts¿m‚"],["@@oºƋÄd–eVŽDJj£€J|Ådz•Ft~žKŨ¸IÆv|”‡¢r}膎onb˜}`RÎÄn°ÒdÞ²„^®’lnÐèĄlðӜ×]ªÆ}LiĂ±Ö`^°Ç¶p®đDcœŋ`–ZÔ’¶êqvFƚ†N®ĆTH®¦O’¾ŠIbÐã´BĐɢŴÆíȦp–ĐÞXR€·nndOž¤’OÀĈƒ­Qg˜µFo|gȒęSWb©osx|hYh•gŃfmÖĩnº€T̒Sp›¢dYĤ¶UĈjl’ǐpäìë|³kÛfw²Xjz~ÂqbTŠÑ„ěŨ@|oM‡’zv¢ZrÃVw¬ŧˏfŒ°ÐT€ªqŽs{Sž¯r æÝlNd®²Ğ džiGʂJ™¼lr}~K¨ŸƐÌWö€™ÆŠzRš¤lêmĞL΄’@¡|q]SvK€ÑcwpÏρ†ĿćènĪWlĄkT}ˆJ”¤~ƒÈT„d„™pddʾĬŠ”ŽBVt„EÀ¢ôPĎƗè@~‚k–ü\\rÊĔÖæW_§¼F˜†´©òDòj’ˆYÈrbĞāøŀG{ƀ|¦ðrb|ÀH`pʞkv‚GpuARhÞÆǶgƊTǼƹS£¨¡ù³ŘÍ]¿Ây™ôEP xX¶¹܇O¡“gÚ¡IwÃ鑦ÅB‡Ï|ǰ…N«úmH¯‹âŸDùŽyŜžŲIÄuШDž•¸dɂ‡‚FŸƒ•›Oh‡đ©OŸ›iÃ`ww^ƒÌkŸ‘ÑH«ƇǤŗĺtFu…{Z}Ö@U‡´…ʚLg®¯Oı°ÃwŸ ^˜—€VbÉs‡ˆmA…ê]]w„§›RRl£‡ȭµu¯b{ÍDěïÿȧŽuT£ġƒěŗƃĝ“Q¨fV†Ƌ•ƅn­a@‘³@šď„yýIĹÊKšŭfċŰóŒxV@tˆƯŒJ”]eƒR¾fe|rHA˜|h~Ėƍl§ÏŠlTíb ØoˆÅbbx³^zÃ͚¶Sj®A”yÂhðk`š«P€”ˈµEF†Û¬Y¨Ļrõqi¼‰Wi°§’б´°^[ˆÀ|ĠO@ÆxO\\tŽa\\tĕtû{ġŒȧXýĪÓjùÎRb›š^ΛfK[ݏděYfíÙTyŽuUSyŌŏů@Oi½’éŅ­aVcř§ax¹XŻác‡žWU£ôãºQ¨÷Ñws¥qEH‰Ù|‰›šYQoŕÇyáĂ£MðoťÊ‰P¡mšWO¡€v†{ôvîēÜISpÌhp¨ ‘j†deŔQÖj˜X³à™Ĉ[n`Yp@Už–cM`’RKhŒEbœ”pŞlNut®Etq‚nsÁŠgA‹iú‹oH‡qCX‡”hfgu“~ϋWP½¢G^}¯ÅīGCŸÑ^ãziMáļMTÃƘrMc|O_ž¯Ŏ´|‡morDkO\\mĆJfl@c̬¢aĦtRıҙ¾ùƀ^juųœK­ƒUFy™—Ɲ…›īÛ÷ąV×qƥV¿aȉd³B›qPBm›aËđŻģm“Å®Vйd^K‡KoŸnYg“¯Xhqa”Ldu¥•ÍpDž¡KąÅƒkĝęěhq‡}HyÓ]¹ǧ£…Í÷¿qáµ§š™g‘¤o^á¾ZE‡¤i`ij{n•ƒOl»ŸWÝĔįhg›F[¿¡—ßkOüš_‰€ū‹i„DZàUtėGylƒ}ŒÓM}€jpEC~¡FtoQi‘šHkk{Ãmï‚"]],"encodeOffsets":[[[119712,40641]],[[121616,39981]],[[116462,37237]]]}},{"type":"Feature","id":"140000","properties":{"id":"140000","cp":[111.849248,36.857014],"name":"山西","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@Þĩ҃S‰ra}Á€yWix±Üe´lè“ßÓǏok‘ćiµVZģ¡coœ‘TS˹ĪmnÕńe–hZg{gtwªpXaĚThȑp{¶Eh—®RćƑP¿£‘Pmc¸mQÝW•ďȥoÅîɡųAďä³aωJ‘½¥PG­ąSM­™…EÅruµé€‘Yӎ•Ō_d›ĒCo­Èµ]¯_²ÕjāŽK~©ÅØ^ԛkïçămϑk]­±ƒcݯÑÃmQÍ~_a—pm…~ç¡q“ˆu{JÅŧ·Ls}–EyÁÆcI{¤IiCfUc•ƌÃp§]웫vD@¡SÀ‘µM‚ÅwuŽYY‡¡DbÑc¡hƒ×]nkoQdaMç~eD•ÛtT‰©±@¥ù@É¡‰ZcW|WqOJmĩl«ħşvOÓ«IqăV—¥ŸD[mI~Ó¢cehiÍ]Ɠ~ĥqXŠ·eƷœn±“}v•[ěďŽŕ]_‘œ•`‰¹ƒ§ÕōI™o©b­s^}Ét±ū«³p£ÿ·Wµ|¡¥ăFÏs׌¥ŅxŸÊdÒ{ºvĴÎêÌɊ²¶€ü¨|ÞƸµȲ‘LLúÉƎ¤ϊęĔV`„_bª‹S^|ŸdŠzY|dz¥p†ZbÆ£¶ÒK}tĦÔņƠ‚PYzn€ÍvX¶Ěn ĠÔ„zý¦ª˜÷žÑĸَUȌ¸‚dòÜJð´’ìúNM¬ŒXZ´‘¤ŊǸ_tldIš{¦ƀðĠȤ¥NehXnYG‚‡R° ƬDj¬¸|CĞ„Kq‚ºfƐiĺ©ª~ĆOQª ¤@ìǦɌ²æBŒÊ”TœŸ˜ʂōĖ’šĴŞ–ȀœÆÿȄlŤĒö„t”νî¼ĨXhŒ‘˜|ªM¤Ðz"],"encodeOffsets":[[116874,41716]]}},{"type":"Feature","id":"150000","properties":{"id":"150000","cp":[111.670801,41.818311],"name":"内蒙古","childNum":2},"geometry":{"type":"MultiPolygon","coordinates":[["@@¯PqƒFB…‰|S•³C|kñ•H‹d‘iÄ¥sˆʼnő…PóÑÑE^‘ÅPpy_YtS™hQ·aHwsOnʼnÚs©iqj›‰€USiº]ïWš‰«gW¡A–Rë¥_ŽsgÁnUI«m‰…„‹]j‡vV¼euhwqA„aW˜ƒ_µj…»çjioQR¹ēÃßt@r³[ÛlćË^ÍÉáG“›OUۗOB±•XŸkŇ¹£k|e]ol™ŸkVͼÕqtaÏõjgÁ£§U^Œ”RLˆËnX°Ç’Bz†^~wfvˆypV ¯„ƫĉ˭ȫƗŷɿÿĿƑ˃ĝÿÃǃßËőó©ǐȍŒĖM×ÍEyx‹þp]Évïè‘vƀnÂĴÖ@‚‰†V~Ĉv¦wĖt—ējyÄDXÄxGQuv_›i¦aBçw‘˛wD™©{ŸtāmQ€{EJ§KPśƘƿ¥@‰sCT•É}ɃwˆƇy±ŸgÑ“}T[÷kÐ禫…SÒ¥¸ëBX½‰HáŵÀğtSÝÂa[ƣ°¯¦P]£ġ“–“Òk®G²„èQ°óMq}EŠóƐÇ\\ƒ‡@áügQ͋u¥Fƒ“T՛¿Jû‡]|mvāÎYua^WoÀa·­ząÒot×¶CLƗi¯¤mƎHNJ¤îìɾŊìTdåwsRÖgĒųúÍġäÕ}Q¶—ˆ¿A•†‹[¡Œ{d×uQAƒ›M•xV‹vMOmăl«ct[wº_šÇʊŽŸjb£ĦS_é“QZ“_lwgOiýe`YYLq§IÁˆdz£ÙË[ÕªuƏ³ÍT—s·bÁĽäė[›b[ˆŗfãcn¥îC¿÷µ[ŏÀQ­ōšĉm¿Á^£mJVm‡—L[{Ï_£›F¥Ö{ŹA}…×Wu©ÅaųijƳhB{·TQqÙIķˑZđ©Yc|M¡…L•eVUóK_QWk’_ĥ‘¿ãZ•»X\\ĴuUƒè‡lG®ěłTĠğDєOrÍd‚ÆÍz]‹±…ŭ©ŸÅ’]ŒÅÐ}UË¥©Tċ™ïxgckfWgi\\ÏĒ¥HkµE˜ë{»ÏetcG±ahUiñiWsɁˆ·c–C‚Õk]wȑ|ća}w…VaĚ᠞ŒG°ùnM¬¯†{ÈˆÐÆA’¥ÄêJxÙ¢”hP¢Ûˆº€µwWOŸóFŽšÁz^ÀŗÎú´§¢T¤ǻƺSė‰ǵhÝÅQgvBHouʝl_o¿Ga{ïq{¥|ſĿHĂ÷aĝÇq‡Z‘ñiñC³ª—…»E`¨åXēÕqÉû[l•}ç@čƘóO¿¡ƒFUsA‰“ʽīccšocƒ‚ƒÇS}„“£‡IS~ălkĩXçmĈ…ŀЂoÐdxÒuL^T{r@¢‘žÍƒĝKén£kQ™‰yšÅõËXŷƏL§~}kqš»IHėDžjĝŸ»ÑÞoŸå°qTt|r©ÏS‹¯·eŨĕx«È[eMˆ¿yuˆ‘pN~¹ÏyN£{©’—g‹ħWí»Í¾s“əšDž_ÃĀɗ±ą™ijĉʍŌŷ—S›É“A‹±åǥɋ@럣R©ąP©}ĹªƏj¹erƒLDĝ·{i«ƫC£µsKCš…GS|úþX”gp›{ÁX¿Ÿć{ƱȏñZáĔyoÁhA™}ŅĆfdʼn„_¹„Y°ėǩÑ¡H¯¶oMQqð¡Ë™|‘Ñ`ƭŁX½·óۓxğįÅcQ‡ˆ“ƒs«tȋDžF“Ÿù^i‘t«Č¯[›hAi©á¥ÇĚ×l|¹y¯YȵƓ‹ñǙµï‚ċ™Ļ|Dœ™üȭ¶¡˜›oŽäÕG\\ďT¿Òõr¯œŸLguÏYęRƩšɷŌO\\İТæ^Ŋ IJȶȆbÜGŽĝ¬¿ĚVĎgª^íu½jÿĕęjık@Ľƒ]ėl¥Ë‡ĭûÁ„ƒėéV©±ćn©­ȇžÍq¯½•YÃÔʼn“ÉNѝÅÝy¹NqáʅDǡËñ­ƁYÅy̱os§ȋµʽǘǏƬɱà‘ưN¢ƔÊuľýľώȪƺɂļžxœZĈ}ÌʼnŪ˜ĺœŽĭFЛĽ̅ȣͽÒŵìƩÇϋÿȮǡŏçƑůĕ~Ǎ›¼ȳÐUf†dIxÿ\\G ˆzâɏÙOº·pqy£†@ŒŠqþ@Ǟ˽IBäƣzsÂZ†ÁàĻdñ°ŕzéØűzșCìDȐĴĺf®ŽÀľưø@ɜÖÞKĊŇƄ§‚͑těï͡VAġÑÑ»d³öǍÝXĉĕÖ{þĉu¸ËʅğU̎éhɹƆ̗̮ȘNJ֥ड़ࡰţાíϲäʮW¬®ҌeרūȠkɬɻ̼ãüfƠSצɩςåȈHϚÎKdzͲOðÏȆƘ¼CϚǚ࢚˼ФԂ¤ƌžĞ̪Qʤ´¼mȠJˀŸƲÀɠmǐnǔĎȆÞǠN~€ʢĜ‚¶ƌĆĘźʆȬ˪ĚǏĞGȖƴƀj`ĢçĶāàŃºē̃ĖćšYŒÀŎüôQÐÂŎŞdžŞêƖš˜oˆDĤÕºÑǘÛˤ³̀gńƘĔÀ^žªƂ`ªt¾äƚêĦĀ¼Ð€Ĕǎ¨Ȕ»͠^ˮÊȦƤøxRrŜH¤¸ÂxDĝŒ|ø˂˜ƮÐ¬ɚwɲFjĔ²Äw°dždÀɞ_ĸdîàŎjʜêTĞªŌ‡ŜWÈ|tqĢUB~´°ÎFC•ŽU¼pĀēƄN¦¾O¶ŠłKĊOj“Ě”j´ĜYp˜{¦„ˆSĚÍ\\Tš×ªV–÷Ší¨ÅDK°ßtŇĔKš¨ǵÂcḷ̌ĚǣȄĽF‡lġUĵœŇ‹ȣFʉɁƒMğįʏƶɷØŭOǽ«ƽū¹Ʊő̝Ȩ§ȞʘĖiɜɶʦ}¨֪ࠜ̀ƇǬ¹ǨE˦ĥªÔêFŽxúQ„Er´W„rh¤Ɛ \\talĈDJ˜Ü|[Pll̚¸ƎGú´Pž¬W¦†^¦–H]prR“n|or¾wLVnÇIujkmon£cX^Bh`¥V”„¦U¤¸}€xRj–[^xN[~ªŠxQ„‚[`ªHÆÂExx^wšN¶Ê˜|¨ì†˜€MrœdYp‚oRzNy˜ÀDs~€bcfÌ`L–¾n‹|¾T‚°c¨È¢a‚r¤–`[|òDŞĔöxElÖdH„ÀI`„Ď\\Àì~ƎR¼tf•¦^¢ķ¶e”ÐÚMŒptgj–„ɡČÅyġLû™ŇV®ŠÄÈƀ†Ď°P|ªVV†ªj–¬ĚÒêp¬–E|ŬÂc|ÀtƐK fˆ{ĘFǜƌXƲąo½Ę‘\\¥–o}›Ûu£ç­kX‘{uĩ«āíÓUŅßŢq€Ť¥lyň[€oi{¦‹L‡ń‡ðFȪȖ”ĒL„¿Ì‹ˆfŒ£K£ʺ™oqNŸƒwğc`ue—tOj×°KJ±qƒÆġm‰Ěŗos¬…qehqsuœƒH{¸kH¡Š…ÊRǪÇƌbȆ¢´ä܍¢NìÉʖ¦â©Ġu¦öČ^â£Ăh–šĖMÈÄw‚\\fŦ°W ¢¾luŸD„wŠ\\̀ʉÌÛM…Ā[bӞEn}¶Vc…ê“sƒ"]],"encodeOffsets":[[[129102,52189]]]}},{"type":"Feature","id":"210000","properties":{"id":"210000","cp":[123.429096,41.796767],"name":"辽宁","childNum":16},"geometry":{"type":"MultiPolygon","coordinates":[["@@L–Ž@@s™a"],["@@MnNm"],["@@d‚c"],["@@eÀ‚C@b‚“‰"],["@@f‡…Xwkbr–Ä`qg"],["@@^jtW‘Q"],["@@~ Y]c"],["@@G`ĔN^_¿Z‚ÃM"],["@@iX¶B‹Y"],["@@„YƒZ"],["@@L_{Epf"],["@@^WqCT\\"],["@@\\[“‹§t|”¤_"],["@@m`n_"],["@@Ïxnj{q_×^Giip"],["@@@œé^B†‡ntˆaÊU—˜Ÿ]x ¯ÄPIJ­°h€ʙK³†VˆÕ@Y~†|EvĹsDŽ¦­L^p²ŸÒG ’Ël]„xxÄ_˜fT¤Ď¤cŽœP„–C¨¸TVjbgH²sdÎdHt`Bˆ—²¬GJję¶[ÐhjeXdlwhšðSȦªVÊπ‹Æ‘Z˜ÆŶ®²†^ŒÎyÅÎcPqń“ĚDMħĜŁH­ˆk„çvV[ij¼W–‚YÀäĦ’‘`XlžR`žôLUVžfK–¢†{NZdĒª’YĸÌÚJRr¸SA|ƴgŴĴÆbvªØX~†źBŽ|¦ÕœEž¤Ð`\\|Kˆ˜UnnI]¤ÀÂĊnŎ™R®Ő¿¶\\ÀøíDm¦ÎbŨab‰œaĘ\\ľã‚¸a˜tÎSƐ´©v\\ÖÚÌǴ¤Â‡¨JKr€Z_Z€fjþhPkx€`Y”’RIŒjJcVf~sCN¤ ˆE‚œhæm‰–sHy¨SðÑÌ\\\\ŸĐRZk°IS§fqŒßýáЍÙÉÖ[^¯ǤŲ„ê´\\¦¬ĆPM¯£Ÿˆ»uïpùzEx€žanµyoluqe¦W^£ÊL}ñrkqWňûP™‰UP¡ôJŠoo·ŒU}£Œ„[·¨@XŒĸŸ“‹‹DXm­Ûݏº‡›GU‹CÁª½{íĂ^cj‡k“¶Ã[q¤“LÉö³cux«zZfƒ²BWÇ®Yß½ve±ÃC•ý£W{Ú^’q^sÑ·¨‹ÍOt“¹·C¥‡GD›rí@wÕKţ݋˜Ÿ«V·i}xËÍ÷‘i©ĝ‡ɝǡ]ƒˆ{c™±OW‹³Ya±Ÿ‰_穂Hžĕoƫ€Ňqƒr³‰Lys[„ñ³¯OS–ďOMisZ†±ÅFC¥Pq{‚Ã[Pg}\\—¿ghćO…•k^ģÁFıĉĥM­oEqqZûěʼn³F‘¦oĵ—hŸÕP{¯~TÍlª‰N‰ßY“Ð{Ps{ÃVU™™eĎwk±ʼnVÓ½ŽJãÇÇ»Jm°dhcÀff‘dF~ˆ€ĀeĖ€d`sx² šƒ®EżĀdQ‹Âd^~ăÔHˆ¦\\›LKpĄVez¤NP ǹӗR™ÆąJSh­a[¦´Âghwm€BÐ¨źhI|žVVŽ—Ž|p] Â¼èNä¶ÜBÖ¼“L`‚¼bØæŒKV”ŸpoœúNZÞÒKxpw|ÊEMnzEQšŽIZ”ŽZ‡NBˆčÚFÜçmĩ‚WĪñt‘ÞĵÇñZ«uD‚±|Əlij¥ãn·±PmÍa‰–da‡ CL‡Ǒkùó¡³Ï«QaċϑOÃ¥ÕđQȥċƭy‹³ÃA"]],"encodeOffsets":[[[123686,41445]],[[126019,40435]],[[124393,40128]],[[126117,39963]],[[125322,40140]],[[126686,40700]],[[126041,40374]],[[125584,40168]],[[125453,40165]],[[125362,40214]],[[125280,40291]],[[125774,39997]],[[125976,40496]],[[125822,39993]],[[125509,40217]],[[122731,40949]]]}},{"type":"Feature","id":"220000","properties":{"id":"220000","cp":[125.3245,43.886841],"name":"吉林","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@‘p䔳PClƒFbbÍzš€wBG’ĭ€Z„Åi“»ƒlY­ċ²SgŽkÇ£—^S‰“qd¯•‹R…©éŽ£¯S†\\cZ¹iűƏCuƍÓX‡oR}“M^o•£…R}oªU­F…uuXHlEŕ‡€Ï©¤ÛmTŽþ¤D–²ÄufàÀ­XXȱAe„yYw¬dvõ´KÊ£”\\rµÄl”iˆdā]|DÂVŒœH¹ˆÞ®ÜWnŒC”Œķ W‹§@\\¸‹ƒ~¤‹Vp¸‰póIO¢ŠVOšŇürXql~òÉK]¤¥Xrfkvzpm¶bwyFoúvð‡¼¤ N°ąO¥«³[ƒéǡű_°Õ\\ÚÊĝŽþâőàerR¨­JYlďQ[ ÏYëЧTGz•tnŠß¡gFkMŸāGÁ¤ia É‰™È¹`\\xs€¬dĆkNnuNUŠ–užP@‚vRY¾•–\\¢…ŒGªóĄ~RãÖÎĢù‚đŴÕhQŽxtcæëSɽʼníëlj£ƍG£nj°KƘµDsØÑpyƸ®¿bXp‚]vbÍZuĂ{nˆ^IüœÀSք”¦EŒvRÎûh@℈[‚Əȉô~FNr¯ôçR±ƒ­HÑl•’Ģ–^¤¢‚OðŸŒævxsŒ]ÞÁTĠs¶¿âƊGW¾ìA¦·TѬ†è¥€ÏÐJ¨¼ÒÖ¼ƒƦɄxÊ~S–tD@ŠĂ¼Ŵ¡jlºWžvЉˆzƦZЎ²CH— „Axiukd‹ŒGgetqmcžÛ£Ozy¥cE}|…¾cZ…k‚‰¿uŐã[oxGikfeäT@…šSUwpiÚFM©’£è^ڟ‚`@v¶eň†f h˜eP¶žt“äOlÔUgƒÞzŸU`lœ}ÔÆUvØ_Ō¬Öi^ĉi§²ÃŠB~¡Ĉ™ÚEgc|DC_Ȧm²rBx¼MÔ¦ŮdĨÃâYx‘ƘDVÇĺĿg¿cwÅ\\¹˜¥Yĭlœ¤žOv†šLjM_a W`zļMž·\\swqÝSA‡š—q‰Śij¯Š‘°kŠRē°wx^Đkǂғ„œž“œŽ„‹\\]˜nrĂ}²ĊŲÒøãh·M{yMzysěnĒġV·°“G³¼XÀ““™¤¹i´o¤ŃšŸÈ`̃DzÄUĞd\\i֚ŒˆmÈBĤÜɲDEh LG¾ƀľ{WaŒYÍȏĢĘÔRîĐj‹}Ǟ“ccj‡oUb½š{“h§Ǿ{K‹ƖµÎ÷žGĀÖŠåưÎs­l›•yiē«‹`姝H¥Ae^§„GK}iã\\c]v©ģZ“mÃ|“[M}ģTɟĵ‘Â`À–çm‰‘FK¥ÚíÁbXš³ÌQґHof{‰]e€pt·GŋĜYünĎųVY^’˜ydõkÅZW„«WUa~U·Sb•wGçǑ‚“iW^q‹F‚“›uNĝ—·Ew„‹UtW·Ýďæ©PuqEzwAV•—XR‰ãQ`­©GŒM‡ehc›c”ďϝd‡©ÑW_ϗYƅŒ»…é\\ƒɹ~ǙG³mØ©BšuT§Ĥ½¢Ã_ý‘L¡‘ýŸqT^rme™\\Pp•ZZbƒyŸ’uybQ—efµ]UhĿDCmûvašÙNSkCwn‰cćfv~…Y‹„ÇG"],"encodeOffsets":[[130196,42528]]}},{"type":"Feature","id":"230000","properties":{"id":"230000","cp":[128.642464,46.756967],"name":"黑龙江","childNum":2},"geometry":{"type":"MultiPolygon","coordinates":[["@@UƒµNÿ¥īè灋•HÍøƕ¶LŒǽ|g¨|”™Ža¾pViˆdd”~ÈiŒíďÓQġėǐZ΋ŽXb½|ſÃH½ŸKFgɱCģÛÇA‡n™‹jÕc[VĝDZÃ˄Ç_™ £ń³pŽj£º”š¿”»WH´¯”U¸đĢmžtĜyzzNN|g¸÷äűѱĉā~mq^—Œ[ƒ”››”ƒǁÑďlw]¯xQĔ‰¯l‰’€°řĴrŠ™˜BˆÞTxr[tޏĻN_yŸX`biN™Ku…P›£k‚ZĮ—¦[ºxÆÀdhŽĹŀUÈƗCw’áZħÄŭcÓ¥»NAw±qȥnD`{ChdÙFćš}¢‰A±Äj¨]ĊÕjŋ«×`VuÓś~_kŷVÝyh„“VkÄãPs”Oµ—fŸge‚Ň…µf@u_Ù ÙcŸªNªÙEojVx™T@†ãSefjlwH\\pŏäÀvŠŽlY†½d{†F~¦dyz¤PÜndsrhf‹HcŒvlwjFœ£G˜±DύƥY‡yϊu¹XikĿ¦ÏqƗǀOŜ¨LI|FRĂn sª|Cš˜zxAè¥bœfudTrFWÁ¹Am|˜ĔĕsķÆF‡´Nš‰}ć…UŠÕ@Áijſmužç’uð^ÊýowŒFzØÎĕNőžǏȎôªÌŒDŽàĀÄ˄ĞŀƒʀĀƘŸˮȬƬĊ°ƒUŸzou‡xe]}Ž…AyȑW¯ÌmK‡“Q]‹Īºif¸ÄX|sZt|½ÚUΠlkš^p{f¤lˆºlÆW –€A²˜PVܜPH”Êâ]ÎĈÌÜk´\\@qàsĔÄQºpRij¼èi†`¶—„bXƒrBgxfv»ŽuUiˆŒ^v~”J¬mVp´£Œ´VWrnP½ì¢BX‚¬h™ŠðX¹^TjVœŠriªj™tŊÄm€tPGx¸bgRšŽsT`ZozÆO]’ÒFô҆Oƒ‡ŊŒvŞ”p’cGŒêŠsx´DR–Œ{A†„EOr°Œ•žx|íœbˆ³Wm~DVjºéNN†Ëܲɶ­GƒxŷCStŸ}]ûō•SmtuÇÃĕN•™āg»šíT«u}ç½BĵÞʣ¥ëÊ¡Mێ³ãȅ¡ƋaǩÈÉQ‰†G¢·lG|›„tvgrrf«†ptęŘnŠÅĢr„I²¯LiØsPf˜_vĠd„xM prʹšL¤‹¤‡eˌƒÀđK“žïÙVY§]I‡óáĥ]ķ†Kˆ¥Œj|pŇ\\kzţ¦šnņäÔVĂîά|vW’®l¤èØr‚˜•xm¶ă~lÄƯĄ̈́öȄEÔ¤ØQĄ–Ą»ƢjȦOǺ¨ìSŖÆƬy”Qœv`–cwƒZSÌ®ü±DŽ]ŀç¬B¬©ńzƺŷɄeeOĨS’Œfm Ċ‚ƀP̎ēz©Ċ‚ÄÕÊmgŸÇsJ¥ƔˆŊśæ’΁Ñqv¿íUOµª‰ÂnĦÁ_½ä@ê텣P}Ġ[@gġ}g“ɊדûÏWXá¢užƻÌsNͽƎÁ§č՛AēeL³àydl›¦ĘVçŁpśdžĽĺſʃQíÜçÛġԏsĕ¬—Ǹ¯YßċġHµ ¡eå`ļƒrĉŘóƢFì“ĎWøxÊk†”ƈdƬv|–I|·©NqńRŀƒ¤é”eŊœŀ›ˆàŀU²ŕƀB‚Q£Ď}L¹Îk@©ĈuǰųǨ”Ú§ƈnTËÇéƟÊcfčŤ^Xm‡—HĊĕË«W·ċëx³ǔķÐċJā‚wİ_ĸ˜Ȁ^ôWr­°oú¬Ħ…ŨK~”ȰCĐ´Ƕ£’fNÎèâw¢XnŮeÂÆĶŽ¾¾xäLĴĘlļO¤ÒĨA¢Êɚ¨®‚ØCÔ ŬGƠ”ƦYĜ‡ĘÜƬDJ—g_ͥœ@čŅĻA“¶¯@wÎqC½Ĉ»NŸăëK™ďÍQ“Ùƫ[«Ãí•gßÔÇOÝáW‘ñuZ“¯ĥ€Ÿŕā¡ÑķJu¤E Ÿå¯°WKɱ_d_}}vyŸõu¬ï¹ÓU±½@gÏ¿rýD‰†g…Cd‰µ—°MFYxw¿CG£‹Rƛ½Õ{]L§{qqąš¿BÇƻğëšܭNJË|c²}Fµ}›ÙRsÓpg±ŠQNqǫŋRwŕnéÑÉKŸ†«SeYR…ŋ‹@{¤SJ}šD Ûǖ֍Ÿ]gr¡µŷjqWÛham³~S«“„›Þ]"]],"encodeOffsets":[[[134456,44547]]]}},{"type":"Feature","id":"320000","properties":{"id":"320000","cp":[119.767413,33.041544],"name":"江苏","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@cþÅPiŠ`ZŸRu¥É\\]~°ŽY`µ†Óƒ^phÁbnÀşúŽòa–ĬºTÖŒb‚˜e¦¦€{¸ZâćNpŒ©žHr|^ˆmjhŠSEb\\afv`sz^lkŽlj‹Ätg‹¤D˜­¾Xš¿À’|ДiZ„ȀåB·î}GL¢õcßjaŸyBFµÏC^ĭ•cÙt¿sğH]j{s©HM¢ƒQnDÀ©DaÜތ·jgàiDbPufjDk`dPOîƒhw¡ĥ‡¥šG˜ŸP²ĐobºrY†„î¶aHŢ´ ]´‚rılw³r_{£DB_Ûdåuk|ˆŨ¯F Cºyr{XFy™e³Þċ‡¿Â™kĭB¿„MvÛpm`rÚã”@ƹhågËÖƿxnlč¶Åì½Ot¾dJlŠVJʜǀœŞqvnOŠ^ŸJ”Z‘ż·Q}ê͎ÅmµÒ]Žƍ¦Dq}¬R^èĂ´ŀĻĊIԒtžIJyQŐĠMNtœR®òLh‰›Ěs©»œ}OӌGZz¶A\\jĨFˆäOĤ˜HYš†JvÞHNiÜaϚɖnFQlšNM¤ˆB´ĄNöɂtp–Ŭdf先‹qm¿QûŠùއÚb¤uŃJŴu»¹Ą•lȖħŴw̌ŵ²ǹǠ͛hĭłƕrçü±Y™xci‡tğ®jű¢KOķ•Coy`å®VTa­_Ā]ŐÝɞï²ʯÊ^]afYǸÃĆēĪȣJđ͍ôƋĝÄ͎ī‰çÛɈǥ£­ÛmY`ó£Z«§°Ó³QafusNıDž_k}¢m[ÝóDµ—¡RLčiXy‡ÅNïă¡¸iĔϑNÌŕoēdōîåŤûHcs}~Ûwbù¹£¦ÓCt‹OPrƒE^ÒoŠg™ĉIµžÛÅʹK…¤½phMŠü`o怆ŀ"],"encodeOffsets":[[121740,32276]]}},{"type":"Feature","id":"330000","properties":{"id":"330000","cp":[120.153576,29.287459],"name":"浙江","childNum":45},"geometry":{"type":"MultiPolygon","coordinates":[["@@E^dQ]K"],["@@jX^j‡"],["@@sfŠbU‡"],["@@qP\\xz[ck"],["@@‘Rƒ¢‚FX}°[s_"],["@@Cbœ\\—}"],["@@e|v\\la{u"],["@@v~u}"],["@@QxÂF¯}"],["@@¹nŒvÞs¯o"],["@@rSkUEj"],["@@bi­ZŒP"],["@@p[}INf"],["@@À¿€"],["@@¹dnbŒ…"],["@@rSŸBnR"],["@@g~h}"],["@@FlEk"],["@@OdPc"],["@@v[u\\"],["@@FjâL~wyoo~›sµL–\\"],["@@¬e¹aNˆ"],["@@\\nÔ¡q]L³ë\\ÿ®ŒQ֎"],["@@ÊA­©[¬"],["@@KxŒv­"],["@@@hlIk]"],["@@pW{o||j"],["@@Md|_mC"],["@@¢…X£ÏylD¼XˆtH"],["@@hlÜ[LykAvyfw^Ež›¤"],["@@fp¤Mus“R"],["@@®_ma~•LÁ¬šZ"],["@@iM„xZ"],["@@ZcYd"],["@@Z~dOSo|A¿qZv"],["@@@`”EN¡v"],["@@|–TY{"],["@@@n@m"],["@@XWkCT\\"],["@@ºwšZRkĕWO¢"],["@@™X®±Grƪ\\ÔáXq{‹"],["@@ůTG°ĄLHm°UC‹"],["@@¤Ž€aÜx~}dtüGæţŎíĔcŖpMËВj碷ðĄÆMzˆjWKĎ¢Q¶˜À_꒔_Bı€i«pZ€gf€¤Nrq]§ĂN®«H±‡yƳí¾×ŸīàLłčŴǝĂíÀBŖÕªˆŠÁŖHŗʼnåqûõi¨hÜ·ƒñt»¹ýv_[«¸m‰YL¯‰Qª…mĉÅdMˆ•gÇjcº«•ęœ¬­K­´ƒB«Âącoċ\\xKd¡gěŧ«®á’[~ıxu·Å”KsËɏc¢Ù\\ĭƛëbf¹­ģSƒĜkáƉÔ­ĈZB{ŠaM‘µ‰fzʼnfåÂŧįƋǝÊĕġć£g³ne­ą»@­¦S®‚\\ßðCšh™iqªĭiAu‡A­µ”_W¥ƣO\\lċĢttC¨£t`ˆ™PZäuXßBs‡Ļyek€OđġĵHuXBšµ]׌‡­­\\›°®¬F¢¾pµ¼kŘó¬Wät’¸|@ž•L¨¸µr“ºù³Ù~§WI‹ŸZWŽ®’±Ð¨ÒÉx€`‰²pĜ•rOògtÁZ}þÙ]„’¡ŒŸFK‚wsPlU[}¦Rvn`hq¬\\”nQ´ĘRWb”‚_ rtČFI֊kŠŠĦPJ¶ÖÀÖJĈĄTĚòžC ²@Pú…Øzœ©PœCÈÚœĒ±„hŖ‡l¬â~nm¨f©–iļ«m‡nt–u†ÖZÜÄj“ŠLŽ®E̜Fª²iÊxبžIÈhhst"],["@@o\\V’zRZ}y"],["@@†@°¡mۛGĕ¨§Ianá[ýƤjfæ‡ØL–•äGr™"]],"encodeOffsets":[[[125592,31553]],[[125785,31436]],[[125729,31431]],[[125513,31380]],[[125223,30438]],[[125115,30114]],[[124815,29155]],[[124419,28746]],[[124095,28635]],[[124005,28609]],[[125000,30713]],[[125111,30698]],[[125078,30682]],[[125150,30684]],[[124014,28103]],[[125008,31331]],[[125411,31468]],[[125329,31479]],[[125626,30916]],[[125417,30956]],[[125254,30976]],[[125199,30997]],[[125095,31058]],[[125083,30915]],[[124885,31015]],[[125218,30798]],[[124867,30838]],[[124755,30788]],[[124802,30809]],[[125267,30657]],[[125218,30578]],[[125200,30562]],[[124968,30474]],[[125167,30396]],[[124955,29879]],[[124714,29781]],[[124762,29462]],[[124325,28754]],[[123990,28459]],[[125366,31477]],[[125115,30363]],[[125369,31139]],[[122495,31878]],[[125329,30690]],[[125192,30787]]]}},{"type":"Feature","id":"340000","properties":{"id":"340000","cp":[117.283042,31.26119],"name":"安徽","childNum":3},"geometry":{"type":"MultiPolygon","coordinates":[["@@^iuLX^"],["@@‚e©Ehl"],["@@°ZÆëϵmkǀwÌÕæhºgBĝâqÙĊz›ÖgņtÀÁÊÆá’hEz|WzqD¹€Ÿ°E‡ŧl{ævÜcA`¤C`|´qžxIJkq^³³ŸGšµbƒíZ…¹qpa±ď OH—¦™Ħˆx¢„gPícOl_iCveaOjCh߸i݋bÛªCC¿€m„RV§¢A|t^iĠGÀtÚs–d]ĮÐDE¶zAb àiödK¡~H¸íæAžǿYƒ“j{ď¿‘™À½W—®£ChŒÃsiŒkkly]_teu[bFa‰Tig‡n{]Gqªo‹ĈMYá|·¥f¥—őaSÕė™NµñĞ«ImŒ_m¿Âa]uĜp …Z_§{Cƒäg¤°r[_Yj‰ÆOdý“[ŽI[á·¥“Q_n‡ùgL¾mv™ˊBÜÆ¶ĊJhšp“c¹˜O]iŠ]œ¥ jtsggJǧw×jÉ©±›EFˍ­‰Ki”ÛÃÕYv…s•ˆm¬njĻª•§emná}k«ŕˆƒgđ²Ù›DǤ›í¡ªOy›†×Où±@DŸñSęćăÕIÕ¿IµĥO‰‰jNÕËT¡¿tNæŇàåyķrĕq§ÄĩsWÆßŽF¶žX®¿‰mŒ™w…RIޓfßoG‘³¾©uyH‘į{Ɓħ¯AFnuP…ÍÔzšŒV—dàôº^Ðæd´€‡oG¤{S‰¬ćxã}›ŧ×Kǥĩ«žÕOEзÖdÖsƘѨ[’Û^Xr¢¼˜§xvěƵ`K”§ tÒ´Cvlo¸fzŨð¾NY´ı~ÉĔē…ßúLÃϖ_ÈÏ|]ÂÏFl”g`bšežž€n¾¢pU‚h~ƴ˶_‚r sĄ~cž”ƈ]|r c~`¼{À{ȒiJjz`îÀT¥Û³…]’u}›f…ïQl{skl“oNdŸjŸäËzDvčoQŠďHI¦rb“tHĔ~BmlRš—V_„ħTLnñH±’DžœL‘¼L˜ªl§Ťa¸ŒĚlK²€\\RòvDcÎJbt[¤€D@®hh~kt°ǾzÖ@¾ªdb„YhüóZ ň¶vHrľ\\ʗJuxAT|dmÀO„‹[ÃԋG·ĚąĐlŪÚpSJ¨ĸˆLvÞcPæķŨŽ®mАˆálŸwKhïgA¢ųƩޖ¤OȜm’°ŒK´"]],"encodeOffsets":[[[121722,32278]],[[119475,30423]],[[119168,35472]]]}},{"type":"Feature","id":"350000","properties":{"id":"350000","cp":[118.306239,26.075302],"name":"福建","childNum":18},"geometry":{"type":"MultiPolygon","coordinates":[["@@“zht´‡]"],["@@aj^~ĆG—©O"],["@@ed¨„C}}i"],["@@@vˆPGsQ"],["@@‰sBz‚ddW]Q"],["@@SލQ“{"],["@@NŽVucW"],["@@qptBAq"],["@@‰’¸[mu"],["@@Q\\pD]_"],["@@jSwUadpF"],["@@eXª~ƒ•"],["@@AjvFso"],["@@fT–›_Çí\\Ÿ™—v|ba¦jZÆy€°"],["@@IjJi"],["@@wJI€ˆxš«¼AoNe{M­"],["@@K‰±¡Óˆ”ČäeZ"],["@@k¡¹Eh~c®wBk‹UplÀ¡I•~Māe£bN¨gZý¡a±Öcp©PhžI”Ÿ¢Qq…ÇGj‹|¥U™ g[Ky¬ŏ–v@OpˆtÉEŸF„\\@ åA¬ˆV{Xģ‰ĐBy…cpě…¼³Ăp·¤ƒ¥o“hqqÚ¡ŅLsƒ^ᗞ§qlŸÀhH¨MCe»åÇGD¥zPO£čÙkJA¼ß–ėu›ĕeûҍiÁŧSW¥˜QŠûŗ½ùěcݧSùĩąSWó«íęACµ›eR—åǃRCÒÇZÍ¢‹ź±^dlsŒtjD¸•‚ZpužÔâÒH¾oLUêÃÔjjēò´ĄW‚ƛ…^Ñ¥‹ĦŸ@Çò–ŠmŒƒOw¡õyJ†yD}¢ďÑÈġfŠZd–a©º²z£šN–ƒjD°Ötj¶¬ZSÎ~¾c°¶Ðm˜x‚O¸¢Pl´žSL|¥žA†ȪĖM’ņIJg®áIJČĒü` ŽQF‡¬h|ÓJ@zµ |ê³È ¸UÖŬŬÀEttĸr‚]€˜ðŽM¤ĶIJHtÏ A’†žĬkvsq‡^aÎbvŒd–™fÊòSD€´Z^’xPsÞrv‹ƞŀ˜jJd×ŘÉ ®A–ΦĤd€xĆqAŒ†ZR”ÀMźŒnĊ»ŒİÐZ— YX–æJŠyĊ²ˆ·¶q§·–K@·{s‘Xãô«lŗ¶»o½E¡­«¢±¨Yˆ®Ø‹¶^A™vWĶGĒĢžPlzfˆļŽtàAvWYãšO_‡¤sD§ssČġ[kƤPX¦Ž`¶“ž®ˆBBvĪjv©šjx[L¥àï[F…¼ÍË»ğV`«•Ip™}ccÅĥZE‹ãoP…´B@ŠD—¸m±“z«Ƴ—¿å³BRضˆœWlâþäą`“]Z£Tc— ĹGµ¶H™m@_©—kŒ‰¾xĨ‡ôȉðX«½đCIbćqK³Á‹Äš¬OAwã»aLʼn‡ËĥW[“ÂGI—ÂNxij¤D¢ŽîĎÎB§°_JœGsƒ¥E@…¤uć…P‘å†cuMuw¢BI¿‡]zG¹guĮck\\_"]],"encodeOffsets":[[[123250,27563]],[[122541,27268]],[[123020,27189]],[[122916,27125]],[[122887,26845]],[[122808,26762]],[[122568,25912]],[[122778,26197]],[[122515,26757]],[[122816,26587]],[[123388,27005]],[[122450,26243]],[[122578,25962]],[[121255,25103]],[[120987,24903]],[[122339,25802]],[[121042,25093]],[[122439,26024]]]}},{"type":"Feature","id":"360000","properties":{"id":"360000","cp":[115.592151,27.676493],"name":"江西","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@ĢĨƐgÂMD~ņªe^\\^§„ý©j׍cZ†Ø¨zdÒa¶ˆlҍJŒìõ`oz÷@¤u޸´†ôęöY¼‰HČƶajlÞƩ¥éZ[”|h}^U Œ ¥p„ĄžƦO lt¸Æ €Q\\€ŠaÆ|CnÂOjt­ĚĤd’ÈŒF`’¶„@Ð딠¦ōҞ¨Sêv†HĢûXD®…QgėWiØPÞìºr¤dž€NĠ¢l–•ĄtZoœCƞÔºCxrpĠV®Ê{f_Y`_ƒeq’’®Aot`@o‚DXfkp¨|Šs¬\\D‘ÄSfè©Hn¬…^DhÆyøJh“ØxĢĀLʈ„ƠPżċĄwȠ̦G®ǒĤäTŠÆ~ĦwŠ«|TF¡Šn€c³Ïå¹]ĉđxe{ÎӐ†vOEm°BƂĨİ|G’vz½ª´€H’àp”eJ݆Qšxn‹ÀŠW­žEµàXÅĪt¨ÃĖrÄwÀFÎ|ňÓMå¼ibµ¯»åDT±m[“r«_gŽmQu~¥V\\OkxtL E¢‹ƒ‘Ú^~ýê‹Pó–qo슱_Êw§ÑªåƗ⼋mĉŹ‹¿NQ“…YB‹ąrwģcÍ¥B•Ÿ­ŗÊcØiI—žƝĿuŒqtāwO]‘³YCñTeɕš‹caub͈]trlu€ī…B‘ПGsĵıN£ï—^ķqss¿FūūV՟·´Ç{éĈý‰ÿ›OEˆR_ŸđûIċâJh­ŅıN‘ȩĕB…¦K{Tk³¡OP·wn—µÏd¯}½TÍ«YiµÕsC¯„iM•¤™­•¦¯P|ÿUHv“he¥oFTu‰õ\\ŽOSs‹MòđƇiaºćXŸĊĵà·çhƃ÷ǜ{‘ígu^›đg’m[×zkKN‘¶Õ»lčÓ{XSƉv©_ÈëJbVk„ĔVÀ¤P¾ºÈMÖxlò~ªÚàGĂ¢B„±’ÌŒK˜y’áV‡¼Ã~­…`g›ŸsÙfI›Ƌlę¹e|–~udjˆuTlXµf`¿JdŠ[\\˜„L‚‘²"],"encodeOffsets":[[116689,26234]]}},{"type":"Feature","id":"370000","properties":{"id":"370000","cp":[118.000923,36.275807],"name":"山东","childNum":13},"geometry":{"type":"MultiPolygon","coordinates":[["@@Xjd]{K"],["@@itbFHy"],["@@HlGk"],["@@T‚ŒGŸy"],["@@K¬˜•‹U"],["@@WdXc"],["@@PtOs"],["@@•LnXhc"],["@@ppVƒu]Or"],["@@cdzAUa"],["@@udRhnCI‡"],["@@ˆoIƒpR„"],["@@Ľč{fzƤî’Kš–ÎMĮ]†—ZFˆ½Y]â£ph’™š¶¨râøÀ†ÎǨ¤^ºÄ”Gzˆ~grĚĜlĞÆ„LĆdž¢Îo¦–cv“Kb€gr°Wh”mZp ˆL]LºcU‰Æ­n”żĤÌǜbAnrOAœ´žȊcÀbƦUØrĆUÜøœĬƞ†š˜Ez„VL®öØBkŖÝĐ˹ŧ̄±ÀbÎɜnb²ĦhņBĖ›žįĦåXćì@L¯´ywƕCéõė ƿ¸‘lµ¾Z|†ZWyFYŸ¨Mf~C¿`€à_RÇzwƌfQnny´INoƬˆèôº|sT„JUš›‚L„îVj„ǎ¾Ē؍‚Dz²XPn±ŴPè¸ŔLƔÜƺ_T‘üÃĤBBċȉöA´fa„˜M¨{«M`‡¶d¡ô‰Ö°šmȰBÔjjŒ´PM|”c^d¤u•ƒ¤Û´Œä«ƢfPk¶Môlˆ]Lb„}su^ke{lC‘…M•rDŠÇ­]NÑFsmoõľH‰yGă{{çrnÓE‰‹ƕZGª¹Fj¢ïW…uøCǷ돡ąuhÛ¡^Kx•C`C\\bÅxì²ĝÝ¿_N‰īCȽĿåB¥¢·IŖÕy\\‡¹kx‡Ã£Č×GDyÕ¤ÁçFQ¡„KtŵƋ]CgÏAùSed‡cÚź—ŠuYfƒyMmhUWpSyGwMPqŀ—›Á¼zK›¶†G•­Y§Ëƒ@–´śÇµƕBmœ@Io‚g——Z¯u‹TMx}C‘‰VK‚ï{éƵP—™_K«™pÛÙqċtkkù]gŽ‹Tğwo•ɁsMõ³ă‡AN£™MRkmEʕč™ÛbMjÝGu…IZ™—GPģ‡ãħE[iµBEuŸDPԛ~ª¼ętŠœ]ŒûG§€¡QMsğNPŏįzs£Ug{đJĿļā³]ç«Qr~¥CƎÑ^n¶ÆéÎR~ݏY’I“] P‰umŝrƿ›‰›Iā‹[x‰edz‹L‘¯v¯s¬ÁY…~}…ťuٌg›ƋpÝĄ_ņī¶ÏSR´ÁP~ž¿Cyžċßdwk´Ss•X|t‰`Ä Èð€AªìÎT°¦Dd–€a^lĎDĶÚY°Ž`ĪŴǒˆ”àŠv\\ebŒZH„ŖR¬ŢƱùęO•ÑM­³FۃWp[ƒ"]],"encodeOffsets":[[[123806,39303]],[[123821,39266]],[[123742,39256]],[[123702,39203]],[[123649,39066]],[[123847,38933]],[[123580,38839]],[[123894,37288]],[[123043,36624]],[[123344,38676]],[[123522,38857]],[[123628,38858]],[[118260,36742]]]}},{"type":"Feature","id":"410000","properties":{"id":"410000","cp":[113.665412,33.757975],"name":"河南","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@•ýL™ùµP³swIÓxcŢĞð†´E®žÚPt†ĴXØx¶˜@«ŕŕQGƒ‹Yfa[şu“ßǩ™đš_X³ijÕčC]kbc•¥CS¯ëÍB©÷‹–³­Siˆ_}m˜YTtž³xlàcȂzÀD}ÂOQ³ÐTĨ¯†ƗòËŖ[hœł‹Ŧv~††}ÂZž«¤lPǕ£ªÝŴÅR§ØnhcŒtâk‡nύ­ľŹUÓÝdKuķ‡I§oTũÙďkęĆH¸ÓŒ\\ăŒ¿PcnS{wBIvɘĽ[GqµuŸŇôYgûƒZcaŽ©@½Õǽys¯}lgg@­C\\£as€IdÍuCQñ[L±ęk·‹ţb¨©kK—’»›KC²‘òGKmĨS`ƒ˜UQ™nk}AGē”sqaJ¥ĐGR‰ĎpCuÌy ã iMc”plk|tRk†ðœev~^‘´†¦ÜŽSí¿_iyjI|ȑ|¿_»d}qŸ^{“Ƈdă}Ÿtqµ`Ƴĕg}V¡om½fa™Ço³TTj¥„tĠ—Ry”K{ùÓjuµ{t}uËR‘iŸvGŠçJFjµŠÍyqΘàQÂFewixGw½Yŷpµú³XU›½ġy™łå‰kÚwZXˆ·l„¢Á¢K”zO„Λ΀jc¼htoDHr…|­J“½}JZ_¯iPq{tę½ĕ¦Zpĵø«kQ…Ťƒ]MÛfaQpě±ǽ¾]u­Fu‹÷nƒ™čįADp}AjmcEǒaª³o³ÆÍSƇĈÙDIzˑ赟^ˆKLœ—i—Þñ€[œƒaA²zz‰Ì÷Dœ|[šíijgf‚ÕÞd®|`ƒĆ~„oĠƑô³Ŋ‘D×°¯CsŠøÀ«ì‰UMhTº¨¸ǡîS–Ô„DruÂÇZ•ÖEŽ’vPZ„žW”~؋ÐtĄE¢¦Ðy¸bŠô´oŬ¬Ž²Ês~€€]®tªašpŎJ¨Öº„_ŠŔ–`’Ŗ^Ѝ\\Ĝu–”~m²Ƹ›¸fW‰ĦrƔ}Î^gjdfÔ¡J}\\n C˜¦þWxªJRÔŠu¬ĨĨmF†dM{\\d\\ŠYÊ¢ú@@¦ª²SŠÜsC–}fNècbpRmlØ^g„d¢aÒ¢CZˆZxvÆ¶N¿’¢T@€uCœ¬^ĊðÄn|žlGl’™Rjsp¢ED}€Fio~ÔNŽ‹„~zkĘHVsDzßjƒŬŒŠŢ`Pûàl¢˜\\ÀœEhŽİgÞē X¼Pk–„|m"],"encodeOffsets":[[118256,37017]]}},{"type":"Feature","id":"420000","properties":{"id":"420000","cp":[113.298572,30.684355],"name":"湖北","childNum":3},"geometry":{"type":"MultiPolygon","coordinates":[["@@AB‚"],["@@lskt"],["@@¾«}{ra®pîÃ\\™›{øCŠËyyB±„b\\›ò˜Ý˜jK›‡L ]ĎĽÌ’JyÚCƈćÎT´Å´pb©È‘dFin~BCo°BĎĚømvŒ®E^vǾ½Ĝ²Ro‚bÜeNŽ„^ĺ£R†¬lĶ÷YoĖ¥Ě¾|sOr°jY`~I”¾®I†{GqpCgyl{‡£œÍƒÍyPL“¡ƒ¡¸kW‡xYlÙæŠšŁĢzœ¾žV´W¶ùŸo¾ZHxjwfx„GNÁ•³Xéæl¶‰EièIH‰ u’jÌQ~v|sv¶Ôi|ú¢Fh˜Qsğ¦ƒSiŠBg™ÐE^ÁÐ{–čnOÂȞUÎóĔ†ÊēIJ}Z³½Mŧïeyp·uk³DsѨŸL“¶_œÅuèw»—€¡WqÜ]\\‘Ò§tƗcÕ¸ÕFÏǝĉăxŻČƟO‡ƒKÉġÿ×wg”÷IÅzCg†]m«ªGeçÃTC’«[‰t§{loWeC@ps_Bp‘­r‘„f_``Z|ei¡—oċMqow€¹DƝӛDYpûs•–‹Ykıǃ}s¥ç³[§ŸcYЧHK„«Qy‰]¢“wwö€¸ïx¼ņ¾Xv®ÇÀµRĠЋžHMž±cÏd„ƒǍũȅȷ±DSyúĝ£ŤĀàtÖÿï[îb\\}pĭÉI±Ñy…¿³x¯N‰o‰|¹H™ÏÛm‹júË~Tš•u˜ęjCöAwě¬R’đl¯ Ñb­‰ŇT†Ŀ_[Œ‘IčĄʿnM¦ğ\\É[T·™k¹œ©oĕ@A¾w•ya¥Y\\¥Âaz¯ãÁ¡k¥ne£Ûw†E©Êō¶˓uoj_Uƒ¡cF¹­[Wv“P©w—huÕyBF“ƒ`R‹qJUw\\i¡{jŸŸEPïÿ½fć…QÑÀQ{ž‚°‡fLԁ~wXg—ītêݾ–ĺ‘Hdˆ³fJd]‹HJ²…E€ƒoU¥†HhwQsƐ»Xmg±çve›]Dm͂PˆoCc¾‹_h”–høYrŊU¶eD°Č_N~øĹĚ·`z’]Äþp¼…äÌQŒv\\rCŒé¾TnkžŐڀÜa‡“¼ÝƆ̶Ûo…d…ĔňТJq’Pb ¾|JŒ¾fXŠƐîĨ_Z¯À}úƲ‹N_ĒĊ^„‘ĈaŐyp»CÇĕKŠšñL³ŠġMŒ²wrIÒŭxjb[œžn«øœ˜—æˆàƒ ^²­h¯Ú€ŐªÞ¸€Y²ĒVø}Ā^İ™´‚LŠÚm„¥ÀJÞ{JVŒųÞŃx×sxxƈē ģMř–ÚðòIf–Ċ“Œ\\Ʈ±ŒdʧĘD†vČ_Àæ~DŒċ´A®µ†¨ØLV¦êHÒ¤"]],"encodeOffsets":[[[113712,34000]],[[115612,30507]],[[113649,34054]]]}},{"type":"Feature","id":"430000","properties":{"id":"430000","cp":[111.782279,28.09409],"name":"湖南","childNum":3},"geometry":{"type":"MultiPolygon","coordinates":[["@@—n„FTs"],["@@ßÅÆá‰½ÔXr—†CO™“…ËR‘ïÿĩ­TooQyšÓ[‹ŅBE¬–ÎÓXa„į§Ã¸G °ITxp‰úxÚij¥Ïš–̾ŠedžÄ©ĸG…œàGh‚€M¤–Â_U}Ċ}¢pczfŠþg¤€”ÇòAV‘‹M"],["@@©K—ƒA·³CQ±Á«³BUŠƑ¹AŠtćOw™D]ŒJiØSm¯b£‘ylƒ›X…HËѱH•«–‘C^õľA–Å§¤É¥„ïyuǙuA¢^{ÌC´­¦ŷJ£^[†“ª¿‡ĕ~•Ƈ…•N… skóā‡¹¿€ï]ă~÷O§­@—Vm¡‹Qđ¦¢Ĥ{ºjԏŽŒª¥nf´•~ÕoŸž×Ûą‹MąıuZœmZcÒ IJβSÊDŽŶ¨ƚƒ’CÖŎªQؼrŭŽ­«}NÏürʬŒmjr€@ĘrTW ­SsdHzƓ^ÇÂyUi¯DÅYlŹu{hTœ}mĉ–¹¥ě‰Dÿë©ıÓ[Oº£ž“¥ót€ł¹MՄžƪƒ`Pš…Di–ÛUоÅ‌ìˆU’ñB“È£ýhe‰dy¡oċ€`pfmjP~‚kZa…ZsÐd°wj§ƒ@€Ĵ®w~^‚kÀÅKvNmX\\¨a“”сqvíó¿F„¤¡@ũÑVw}S@j}¾«pĂr–ªg àÀ²NJ¶¶Dô…K‚|^ª†Ž°LX¾ŴäPᜣEXd›”^¶›IJÞܓ~‘u¸ǔ˜Ž›MRhsR…e†`ÄofIÔ\\Ø  i”ćymnú¨cj ¢»–GČìƊÿШXeĈ¾Oð Fi ¢|[jVxrIQŒ„_E”zAN¦zLU`œcªx”OTu RLÄ¢dV„i`p˔vŎµªÉžF~ƒØ€d¢ºgİàw¸Áb[¦Zb¦–z½xBĖ@ªpº›šlS¸Ö\\Ĕ[N¥ˀmĎă’J\\‹ŀ`€…ňSڊĖÁĐiO“Ĝ«BxDõĚiv—ž–S™Ì}iùŒžÜnšÐºGŠ{Šp°M´w†ÀÒzJ²ò¨ oTçüöoÛÿñŽőФ‚ùTz²CȆȸǎۃƑÐc°dPÎŸğ˶[Ƚu¯½WM¡­Éž“’B·rížnZŸÒ `‡¨GA¾\\pē˜XhÆRC­üWGġu…T靧Ŏѝ©ò³I±³}_‘‹EÃħg®ęisÁPDmÅ{‰b[Rşs·€kPŸŽƥƒóRo”O‹ŸVŸ~]{g\\“êYƪ¦kÝbiċƵŠGZ»Ěõ…ó·³vŝž£ø@pyö_‹ëŽIkѵ‡bcѧy…×dY؎ªiþž¨ƒ[]f]Ņ©C}ÁN‡»hĻħƏ’ĩ"]],"encodeOffsets":[[[115640,30489]],[[112543,27312]],[[116690,26230]]]}},{"type":"Feature","id":"440000","properties":{"id":"440000","cp":[113.280637,23.125178],"name":"广东","childNum":24},"geometry":{"type":"MultiPolygon","coordinates":[["@@QdˆAua"],["@@ƒlxDLo"],["@@sbhNLo"],["@@Ă āŸ"],["@@WltO[["],["@@Krœ]S"],["@@e„„I]y"],["@@I|„Mym"],["@@ƒÛ³LSŒž¼Y"],["@@nvºB–ëui©`¾"],["@@zdšÛ›Jw®"],["@@†°…¯"],["@@a yAª¸ËJIx،@€ĀHAmßV¡o•fu•o"],["@@šs‰ŗÃÔėAƁ›ZšÄ ~°ČP‚‹äh"],["@@‹¶Ý’Ì‚vmĞh­ı‡Q"],["@@HœŠdSjĒ¢D}war…“u«ZqadYM"],["@@elŒ\\LqqU"],["@@~rMo\\"],["@@f„^ƒC"],["@@øPªoj÷ÍÝħXČx”°Q¨ıXNv"],["@@gÇƳˆŽˆ”oˆŠˆ[~tly"],["@@E–ÆC¿‘"],["@@OŽP"],["@@w‹†đóg‰™ĝ—[³‹¡VÙæÅöM̳¹pÁaËýý©D©Ü“JŹƕģGą¤{Ùū…ǘO²«BƱéA—Ò‰ĥ‡¡«BhlmtÃPµyU¯uc“d·w_bŝcīímGOŽ|KP’ȏ‡ŹãŝIŕŭŕ@Óoo¿ē‹±ß}Ž…ŭ‚ŸIJWÈCőâUâǙI›ğʼn©I›ijEׅÁ”³Aó›wXJþ±ÌŒÜӔĨ£L]ĈÙƺZǾĆĖMĸĤfŒÎĵl•ŨnȈ‘ĐtF”Š–FĤ–‚êk¶œ^k°f¶gŠŽœ}®Fa˜f`vXŲxl˜„¦–ÔÁ²¬ÐŸ¦pqÊ̲ˆi€XŸØRDÎ}†Ä@ZĠ’s„x®AR~®ETtĄZ†–ƈfŠŠHâÒÐA†µ\\S¸„^wĖkRzŠalŽŜ|E¨ÈNĀňZTŒ’pBh£\\ŒĎƀuXĖtKL–¶G|Ž»ĺEļĞ~ÜĢÛĊrˆO˜Ùîvd]nˆ¬VœÊĜ°R֟pM††–‚ƂªFbwžEÀˆ˜©Œž\\…¤]ŸI®¥D³|ˎ]CöAŤ¦…æ’´¥¸Lv¼€•¢ĽBaô–F~—š®²GÌҐEY„„œzk¤’°ahlV՞I^‹šCxĈPŽsB‰ƒºV‰¸@¾ªR²ĨN]´_eavSi‡vc•}p}Đ¼ƌkJœÚe thœ†_¸ ºx±ò_xN›Ë‹²‘@ƒă¡ßH©Ùñ}wkNÕ¹ÇO½¿£ĕ]ly_WìIžÇª`ŠuTÅxYĒÖ¼k֞’µ‚MžjJÚwn\\h‘œĒv]îh|’È›Ƅøègž¸Ķß ĉĈWb¹ƀdéƌNTtP[ŠöSvrCZžžaGuœbo´ŖÒÇА~¡zCI…özx¢„Pn‹•‰Èñ @ŒĥÒ¦†]ƞŠV}³ăĔñiiÄÓVépKG½Ä‘ÓávYo–C·sit‹iaÀy„ŧΡÈYDÑům}‰ý|m[węõĉZÅxUO}÷N¹³ĉo_qtă“qwµŁYلǝŕ¹tïÛUïmRCº…ˆĭ|µ›ÕÊK™½R‘ē ó]‘–GªęAx–»HO£|ām‡¡diď×YïYWªʼnOeÚtĐ«zđ¹T…ā‡úE™á²\\‹ķÍ}jYàÙÆſ¿Çdğ·ùTßÇţʄ¡XgWÀLJğ·¿ÃˆOj YÇ÷Qě‹i"]],"encodeOffsets":[[[117381,22988]],[[116552,22934]],[[116790,22617]],[[116973,22545]],[[116444,22536]],[[116931,22515]],[[116496,22490]],[[116453,22449]],[[113301,21439]],[[118726,21604]],[[118709,21486]],[[113210,20816]],[[115482,22082]],[[113171,21585]],[[113199,21590]],[[115232,22102]],[[115739,22373]],[[115134,22184]],[[113056,21175]],[[119573,21271]],[[119957,24020]],[[115859,22356]],[[116561,22649]],[[116285,22746]]]}},{"type":"Feature","id":"450000","properties":{"id":"450000","cp":[108.320004,22.82402],"name":"广西","childNum":2},"geometry":{"type":"MultiPolygon","coordinates":[["@@H– TQ§•A"],["@@ĨʪƒLƒƊDÎĹĐCǦė¸zÚGn£¾›rªŀÜt¬@֛ڈSx~øOŒ˜ŶÐÂæȠ\\„ÈÜObĖw^oބLf¬°bI lTØB̈F£Ć¹gñĤaY“t¿¤VSñœK¸¤nM†¼‚JE±„½¸šŠño‹ÜCƆæĪ^ŠĚQÖ¦^‡ˆˆf´Q†üÜʝz¯šlzUĺš@쇀p¶n]sxtx¶@„~ÒĂJb©gk‚{°‚~c°`ԙ¬rV\\“la¼¤ôá`¯¹LC†ÆbŒxEræO‚v[H­˜„[~|aB£ÖsºdAĐzNÂðsŽÞƔ…Ĥªbƒ–ab`ho¡³F«èVloޤ™ÔRzpp®SŽĪº¨ÖƒºN…ij„d`’a”¦¤F³ºDÎńĀìŠCžĜº¦Ċ•~nS›|gźvZkCÆj°zVÈÁƔ]LÊFZg…čP­kini«‹qǀcz͔Y®¬Ů»qR×ō©DՄ‘§ƙǃŵTÉĩ±ŸıdÑnYY›IJvNĆÌØÜ Öp–}e³¦m‹©iÓ|¹Ÿħņ›|ª¦QF¢Â¬ʖovg¿em‡^ucà÷gՎuŒíÙćĝ}FϼĹ{µHK•sLSđƃr‹č¤[Ag‘oS‹ŇYMÿ§Ç{Fśbky‰lQxĕƒ]T·¶[B…ÑÏGáşşƇe€…•ăYSs­FQ}­Bƒw‘tYğÃ@~…C̀Q ×W‡j˱rÉ¥oÏ ±«ÓÂ¥•ƒ€k—ŽwWűŒmcih³K›~‰µh¯e]lµ›él•E쉕E“ďs‡’mǖŧē`ãògK_ÛsUʝ“ćğ¶hŒöŒO¤Ǜn³Žc‘`¡y‹¦C‘ez€YŠwa™–‘[ďĵűMę§]X˜Î_‚훘Û]é’ÛUćİÕBƣ±…dƒy¹T^džûÅÑŦ·‡PĻþÙ`K€¦˜…¢ÍeœĥR¿Œ³£[~Œäu¼dl‰t‚†W¸oRM¢ď\\zœ}Æzdvň–{ÎXF¶°Â_„ÒÂÏL©Ö•TmuŸ¼ãl‰›īkiqéfA„·Êµ\\őDc¥ÝF“y›Ôć˜c€űH_hL܋êĺШc}rn`½„Ì@¸¶ªVLŒŠhŒ‹\\•Ţĺk~ŽĠið°|gŒtTĭĸ^x‘vK˜VGréAé‘bUu›MJ‰VÃO¡…qĂXËS‰ģãlýàŸ_ju‡YÛÒB†œG^˜é֊¶§ŽƒEG”ÅzěƒƯ¤Ek‡N[kdåucé¬dnYpAyČ{`]þ¯T’bÜÈk‚¡Ġ•vŒàh„ÂƄ¢Jî¶²"]],"encodeOffsets":[[[111707,21520]],[[107619,25527]]]}},{"type":"Feature","id":"460000","properties":{"id":"460000","cp":[109.83119,19.031971],"name":"海南","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@š¦Ŝil¢”XƦ‘ƞò–ïè§ŞCêɕrŧůÇąĻõ™·ĉ³œ̅kÇm@ċȧƒŧĥ‰Ľʉ­ƅſ“ȓÒ˦ŝE}ºƑ[ÍĜȋ gÎfǐÏĤ¨êƺ\\Ɔ¸ĠĎvʄȀœÐ¾jNðĀÒRŒšZdž™zÐŘΰH¨Ƣb²_Ġ "],"encodeOffsets":[[112750,20508]]}},{"type":"Feature","id":"510000","properties":{"id":"510000","cp":[104.065735,30.659462],"name":"四川","childNum":2},"geometry":{"type":"MultiPolygon","coordinates":[["@@LqKr"],["@@Š[ĻéV£ž_ţġñpG •réÏ·~ąSfy×͂·ºſƽiÍıƣıĻmHH}siaX@iǰÁÃ×t«ƒ­Tƒ¤J–JJŒyJ•ÈŠ`Ohߦ¡uËhIyCjmÿw…ZG……Ti‹SˆsO‰žB²ŸfNmsPaˆ{M{ŠõE‘^Hj}gYpaeuž¯‘oáwHjÁ½M¡pM“–uå‡mni{fk”\\oƒÎqCw†EZ¼K›ĝŠƒAy{m÷L‡wO×SimRI¯rK™õBS«sFe‡]fµ¢óY_ÆPRcue°Cbo׌bd£ŌIHgtrnyPt¦foaXďx›lBowz‹_{ÊéWiêE„GhܸºuFĈIxf®Ž•Y½ĀǙ]¤EyŸF²ċ’w¸¿@g¢§RGv»–áŸW`ÃĵJwi]t¥wO­½a[׈]`Ãi­üL€¦LabbTÀå’c}Íh™Æhˆ‹®BH€î|Ék­¤S†y£„ia©taį·Ɖ`ō¥Uh“O…ƒĝLk}©Fos‰´›Jm„µlŁu—…ø–nÑJWΪ–YÀïAetTžŅ‚ӍG™Ë«bo‰{ıwodƟ½ƒžOġܑµxàNÖ¾P²§HKv¾–]|•B‡ÆåoZ`¡Ø`ÀmºĠ~ÌЧnDž¿¤]wğ@sƒ‰rğu‰~‘Io”[é±¹ ¿žſđӉ@q‹gˆ¹zƱřaí°KtǤV»Ã[ĩǭƑ^ÇÓ@ỗs›Zϕ‹œÅĭ€Ƌ•ěpwDóÖሯneQˌq·•GCœýS]xŸ·ý‹q³•O՜Œ¶Qzßti{ř‰áÍÇWŝŭñzÇW‹pç¿JŒ™‚Xœĩè½cŒF–ÂLiVjx}\\N†ŇĖ¥Ge–“JA¼ÄHfÈu~¸Æ«dE³ÉMA|b˜Ò…˜ćhG¬CM‚õŠ„ƤąAvƒüV€éŀ‰_V̳ĐwQj´·ZeÈÁ¨X´Æ¡Qu·»Ÿ“˜ÕZ³ġqDo‰y`L¬gdp°şŠp¦ėìÅĮZްIä”h‚‘ˆzŠĵœf²å ›ĚрKp‹IN|‹„Ñz]ń……·FU×é»R³™MƒÉ»GM«€ki€™ér™}Ã`¹ăÞmȝnÁîRǀ³ĜoİzŔwǶVÚ£À]ɜ»ĆlƂ²Ġ…þTº·àUȞÏʦ¶†I’«dĽĢdĬ¿–»Ĕ׊h\\c¬†ä²GêëĤł¥ÀǿżÃÆMº}BÕĢyFVvw–ˆxBèĻĒ©Ĉ“tCĢɽŠȣ¦āæ·HĽî“ôNԓ~^¤Ɗœu„œ^s¼{TA¼ø°¢İªDè¾Ň¶ÝJ‘®Z´ğ~Sn|ªWÚ©òzPOȸ‚bð¢|‹øĞŠŒœŒQìÛÐ@Ğ™ǎRS¤Á§d…i“´ezÝúØã]Hq„kIŸþËQǦÃsǤ[E¬ÉŪÍxXƒ·ÖƁİlƞ¹ª¹|XÊwn‘ÆƄmÀêErĒtD®ċæcQƒ”E®³^ĭ¥©l}äQto˜ŖÜqƎkµ–„ªÔĻĴ¡@Ċ°B²Èw^^RsºT£ڿœQP‘JvÄz„^Đ¹Æ¯fLà´GC²‘dt˜­ĀRt¼¤ĦOðğfÔðDŨŁĞƘïžPȆ®âbMüÀXZ ¸£@Ś›»»QÉ­™]d“sÖ×_͖_ÌêŮPrĔĐÕGĂeZÜîĘqBhtO ¤tE[h|Y‹Ô‚ZśÎs´xº±UŒ’ñˆt|O’ĩĠºNbgþŠJy^dÂY Į„]Řz¦gC‚³€R`Šz’¢AjŒ¸CL„¤RÆ»@­Ŏk\\Ç´£YW}z@Z}‰Ã¶“oû¶]´^N‡Ò}èN‚ª–P˜Íy¹`S°´†ATe€VamdUĐwʄvĮÕ\\ƒu‹Æŗ¨Yp¹àZÂm™Wh{á„}WØǍ•Éüw™ga§áCNęÎ[ĀÕĪgÖɪX˜øx¬½Ů¦¦[€—„NΆL€ÜUÖ´òrÙŠxR^–†J˜k„ijnDX{Uƒ~ET{ļº¦PZc”jF²Ė@Žp˜g€ˆ¨“B{ƒu¨ŦyhoÚD®¯¢˜ WòàFΤ¨GDäz¦kŮPœġq˚¥À]€Ÿ˜eŽâÚ´ªKxī„Pˆ—Ö|æ[xäJÞĥ‚s’NÖ½ž€I†¬nĨY´®Ð—ƐŠ€mD™ŝuäđđEb…e’e_™v¡}ìęNJē}q”É埁T¯µRs¡M@}ůa†a­¯wvƉåZwž\\Z{åû^›"]],"encodeOffsets":[[[108815,30935]],[[110617,31811]]]}},{"type":"Feature","id":"520000","properties":{"id":"520000","cp":[106.713478,26.578343],"name":"贵州","childNum":3},"geometry":{"type":"MultiPolygon","coordinates":[["@@†G\\†lY£‘in"],["@@q‚|ˆ‚mc¯tχVSÎ"],["@@hÑ£Is‡NgßH†›HªķÃh_¹ƒ¡ĝħń¦uيùŽgS¯JHŸ|sÝÅtÁïyMDč»eÕtA¤{b\\}—ƒG®u\\åPFq‹wÅaD…žK°ºâ_£ùbµ”mÁ‹ÛœĹM[q|hlaªāI}тƒµ@swtwm^oµˆD鼊yV™ky°ÉžûÛR…³‚‡eˆ‡¥]RՋěħ[ƅåÛDpŒ”J„iV™™‰ÂF²I…»mN·£›LbÒYb—WsÀbŽ™pki™TZĄă¶HŒq`……ĥ_JŸ¯ae«ƒKpÝx]aĕÛPƒÇȟ[ÁåŵÏő—÷Pw}‡TœÙ@Õs«ĿÛq©½œm¤ÙH·yǥĘĉBµĨÕnđ]K„©„œá‹ŸG纍§Õßg‡ǗĦTèƤƺ{¶ÉHÎd¾ŚÊ·OÐjXWrãLyzÉAL¾ę¢bĶėy_qMĔąro¼hĊžw¶øV¤w”²Ĉ]ʚKx|`ź¦ÂÈdr„cȁbe¸›`I¼čTF´¼Óýȃr¹ÍJ©k_șl³´_pН`oÒh޶pa‚^ÓĔ}D»^Xyœ`d˜[Kv…JPhèhCrĂĚÂ^Êƌ wˆZL­Ġ£šÁbrzOIl’MM”ĪŐžËr×ÎeŦŽtw|Œ¢mKjSǘňĂStÎŦEtqFT†¾†E쬬ôxÌO¢Ÿ KгŀºäY†„”PVgŎ¦Ŋm޼VZwVlŒ„z¤…ž£Tl®ctĽÚó{G­A‡ŒÇgeš~Αd¿æaSba¥KKûj®_ć^\\ؾbP®¦x^sxjĶI_Ä X‚⼕Hu¨Qh¡À@Ëô}ޱžGNìĎlT¸ˆ…`V~R°tbÕĊ`¸úÛtπFDu€[ƒMfqGH·¥yA‰ztMFe|R‚_Gk†ChZeÚ°to˜v`x‹b„ŒDnÐ{E}šZ˜è€x—†NEފREn˜[Pv@{~rĆAB§‚EO¿|UZ~ì„Uf¨J²ĂÝÆ€‚sª–B`„s¶œfvö¦ŠÕ~dÔq¨¸º»uù[[§´sb¤¢zþFœ¢Æ…Àhˆ™ÂˆW\\ıŽËI݊o±ĭŠ£þˆÊs}¡R]ŒěƒD‚g´VG¢‚j±®è†ºÃmpU[Á›‘Œëº°r›ÜbNu¸}Žº¼‡`ni”ºÔXĄ¤¼Ôdaµ€Á_À…†ftQQgœR—‘·Ǔ’v”}Ýלĵ]µœ“Wc¤F²›OĩųãW½¯K‚©…]€{†LóµCIµ±Mß¿hŸ•©āq¬o‚½ž~@i~TUxŪÒ¢@ƒ£ÀEîôruń‚”“‚b[§nWuMÆLl¿]x}ij­€½"]],"encodeOffsets":[[[112158,27383]],[[112105,27474]],[[112095,27476]]]}},{"type":"Feature","id":"530000","properties":{"id":"530000","cp":[101.512251,24.740609],"name":"云南","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@[„ùx½}ÑRH‘YīĺûsÍn‘iEoã½Ya²ė{c¬ĝg•ĂsA•ØÅwď‚õzFjw}—«Dx¿}UũlŸê™@•HÅ­F‰¨ÇoJ´Ónũuą¡Ã¢pÒŌ“Ø TF²‚xa²ËX€‚cʋlHîAßËŁkŻƑŷÉ©h™W­æßU‡“Ës¡¦}•teèÆ¶StǀÇ}Fd£j‹ĈZĆÆ‹¤T‚č\\Dƒ}O÷š£Uˆ§~ŃG™‚åŃDĝ¸œTsd¶¶Bªš¤u¢ŌĎo~t¾ÍŶÒtD¦Ú„iôö‰€z›ØX²ghįh½Û±¯€ÿm·zR¦Ɵ`ªŊÃh¢rOԍ´£Ym¼èêf¯ŪĽn„†cÚbŒw\\zlvWžªâˆ ¦g–mĿBş£¢ƹřbĥkǫßeeZkÙIKueT»sVesb‘aĕ  ¶®dNœĄÄpªyސ¼—„³BE˜®l‡ŽGœŭCœǶwêżĔÂe„pÍÀQƞpC„–¼ŲÈ­AÎô¶R„ä’Q^Øu¬°š_Èôc´¹ò¨P΢hlϦ´Ħ“Æ´sâDŽŲPnÊD^¯°’Upv†}®BP̪–jǬx–Söwlfòªv€qĸ|`H€­viļ€ndĜ­Ćhň•‚em·FyށqóžSᝑ³X_ĞçêtryvL¤§z„¦c¦¥jnŞk˜ˆlD¤øz½ĜàžĂŧMÅ|áƆàÊcðÂF܎‚áŢ¥\\\\º™İøÒÐJĴ‡„îD¦zK²ǏÎEh~’CD­hMn^ÌöÄ©ČZÀžaü„fɭyœpį´ěFűk]Ôě¢qlÅĆÙa¶~Äqššê€ljN¬¼H„ÊšNQ´ê¼VظE††^ŃÒyŒƒM{ŒJLoÒœęæŸe±Ķ›y‰’‡gã“¯JYÆĭĘëo¥Š‰o¯hcK«z_pŠrC´ĢÖY”—¼ v¸¢RŽÅW³Â§fǸYi³xR´ďUˊ`êĿU„û€uĆBƒƣö‰N€DH«Ĉg†——Ñ‚aB{ÊNF´¬c·Åv}eÇÃGB»”If•¦HňĕM…~[iwjUÁKE•Ž‹¾dĪçW›šI‹èÀŒoÈXòyŞŮÈXâÎŚŠj|àsRy‹µÖ›–Pr´þŒ ¸^wþTDŔ–Hr¸‹žRÌmf‡żÕâCôox–ĜƌÆĮŒ›Ð–œY˜tâŦÔ@]ÈǮƒ\\μģUsȯLbîƲŚºyh‡rŒŠ@ĒԝƀŸÀ²º\\êp“’JŠ}ĠvŠqt„Ġ@^xÀ£È†¨mËÏğ}n¹_¿¢×Y_æpˆÅ–A^{½•Lu¨GO±Õ½ßM¶w’ÁĢۂP‚›Ƣ¼pcIJxŠ|ap̬HšÐŒŊSfsðBZ¿©“XÏÒK•k†÷Eû¿‰S…rEFsÕūk”óVǥʼniTL‚¡n{‹uxţÏh™ôŝ¬ğōN“‘NJkyPaq™Âğ¤K®‡YŸxÉƋÁ]āęDqçgOg†ILu—\\_gz—]W¼ž~CÔē]bµogpў_oď`´³Țkl`IªºÎȄqÔþž»E³ĎSJ»œ_f·‚adÇqƒÇc¥Á_Źw{™L^ɱćx“U£µ÷xgĉp»ĆqNē`rĘzaĵĚ¡K½ÊBzyäKXqiWPÏɸ½řÍcÊG|µƕƣG˛÷Ÿk°_^ý|_zċBZocmø¯hhcæ\\lˆMFlư£Ĝ„ÆyH“„F¨‰µêÕ]—›HA…àӄ^it `þßäkŠĤÎT~Wlÿ¨„ÔPzUC–NVv [jâôDôď[}ž‰z¿–msSh‹¯{jïğl}šĹ[–őŒ‰gK‹©U·µË@¾ƒm_~q¡f¹…ÅË^»‘f³ø}Q•„¡Ö˳gͱ^ǁ…\\ëÃA_—¿bW›Ï[¶ƛ鏝£F{īZgm@|kHǭƁć¦UĔťƒ×ë}ǝƒeďºȡȘÏíBə£āĘPªij¶“ʼnÿ‡y©n‰ď£G¹¡I›Š±LÉĺÑdĉ܇W¥˜‰}g˜Á†{aqÃ¥aŠıęÏZ—ï`"],"encodeOffsets":[[104636,22969]]}},{"type":"Feature","id":"540000","properties":{"id":"540000","cp":[89.132212,30.860361],"name":"西藏","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@hžľxŽŖ‰xƒÒVކºÅâAĪÝȆµę¯Ňa±r_w~uSÕň‘qOj]ɄQ…£Z……UDûoY’»©M[‹L¼qãË{V͕çWViŽ]ë©Ä÷àyƛh›ÚU°ŒŒa”d„cQƒ~Mx¥™cc¡ÙaSyF—ցk­ŒuRýq¿Ôµ•QĽ³aG{¿FµëªéĜÿª@¬·–K‰·àariĕĀ«V»Ŷ™Ĵū˜gèLǴŇƶaf‹tŒèBŚ£^Šâ†ǐÝ®–šM¦ÁǞÿ¬LhŸŽJ¾óƾƺcxw‹f]Y…´ƒ¦|œQLn°aœdĊ…œ\\¨o’œǀÍŎœ´ĩĀd`tÊQŞŕ|‚¨C^©œĈ¦„¦ÎJĊ{ŽëĎjª²rЉšl`¼Ą[t|¦St辉PŒÜK¸€d˜Ƅı]s¤—î_v¹ÎVòŦj˜£Əsc—¬_Ğ´|٘¦Avަw`ăaÝaa­¢e¤ı²©ªSªšÈMĄwžÉØŔì@T‘¤—Ę™\\õª@”þo´­xA s”ÂtŎKzó´ÇĊµ¢rž^nĊ­Æ¬×üGž¢‚³ {âĊ]š™G‚~bÀgVjzlhǶf€žOšfdЉªB]pj„•TO–tĊ‚n¤}®¦ƒČ¥d¢¼»ddš”Y¼Žt—¢eȤJ¤}Ǿ¡°§¤AГlc@ĝ”sªćļđAç‡wx•UuzEÖġ~AN¹ÄÅȀݦ¿ģŁéì±H…ãd«g[؉¼ēÀ•cīľġ¬cJ‘µ…ÐʥVȝ¸ßS¹†ý±ğkƁ¼ą^ɛ¤Ûÿ‰b[}¬ōõÃ]ËNm®g@•Bg}ÍF±ǐyL¥íCˆƒIij€Ï÷њį[¹¦[⚍EÛïÁÉdƅß{âNÆāŨߝ¾ě÷yC£‡k­´ÓH@¹†TZ¥¢įƒ·ÌAЧ®—Zc…v½ŸZ­¹|ŕWZqgW“|ieZÅYVӁqdq•bc²R@†c‡¥Rã»Ge†ŸeƃīQ•}J[ғK…¬Ə|o’ėjġĠÑN¡ð¯EBčnwôɍėªƒ²•CλŹġǝʅįĭạ̃ūȹ]ΓͧgšsgȽóϧµǛ†ęgſ¶ҍć`ĘąŌJޚä¤rÅň¥ÖÁUětęuůÞiĊÄÀ\\Æs¦ÓRb|Â^řÌkÄŷ¶½÷‡f±iMݑ›‰@ĥ°G¬ÃM¥n£Øą‚ğ¯ß”§aëbéüÑOčœk£{\\‘eµª×M‘šÉfm«Ƒ{Å׃Gŏǩãy³©WÑăû‚··‘Q—òı}¯ã‰I•éÕÂZ¨īès¶ZÈsŽæĔTŘvŽgÌsN@îá¾ó@‰˜ÙwU±ÉT廣TđŸWxq¹Zo‘b‹s[׌¯cĩv‡Œėŧ³BM|¹k‰ªħ—¥TzNYnݍßpęrñĠĉRS~½ŠěVVе‚õ‡«ŒM££µB•ĉ¥áºae~³AuĐh`Ü³ç@BۘïĿa©|z²Ý¼D”£à貋ŸƒIƒû›I ā€óK¥}rÝ_Á´éMaň¨€~ªSĈ½Ž½KÙóĿeƃÆBŽ·¬ën×W|Uº}LJrƳ˜lŒµ`bÔ`QˆˆÐÓ@s¬ñIŒÍ@ûws¡åQÑßÁ`ŋĴ{Ī“T•ÚÅTSij‚‹Yo|Ç[ǾµMW¢ĭiÕØ¿@˜šMh…pÕ]j†éò¿OƇĆƇp€êĉâlØw–ěsˆǩ‚ĵ¸c…bU¹ř¨WavquSMzeo_^gsÏ·¥Ó@~¯¿RiīB™Š\\”qTGªÇĜçPoŠÿfñòą¦óQīÈáP•œābß{ƒZŗĸIæÅ„hnszÁCËìñšÏ·ąĚÝUm®ó­L·ăU›Èíoù´Êj°ŁŤ_uµ^‘°Œìǖ@tĶĒ¡Æ‡M³Ģ«˜İĨÅ®ğ†RŽāð“ggheÆ¢z‚Ê©Ô\\°ÝĎz~ź¤Pn–MĪÖB£Ÿk™n鄧żćŠ˜ĆK„ǰ¼L¶è‰âz¨u¦¥LDĘz¬ýÎmĘd¾ß”Fz“hg²™Fy¦ĝ¤ċņbΛ@y‚Ąæm°NĮZRÖíŽJ²öLĸÒ¨Y®ƌÐV‰à˜tt_ڀÂyĠzž]Ţh€zĎ{†ĢX”ˆc|šÐqŽšfO¢¤ög‚ÌHNŽ„PKŖœŽ˜Uú´xx[xˆvĐCûŠìÖT¬¸^}Ìsòd´_އKgžLĴ…ÀBon|H@–Êx˜—¦BpŰˆŌ¿fµƌA¾zLjRxжF”œkĄźRzŀˆ~¶[”´Hnª–VƞuĒ­È¨ƎcƽÌm¸ÁÈM¦x͊ëÀxdžB’šú^´W†£–d„kɾĬpœw‚˂ØɦļĬIŚœÊ•n›Ŕa¸™~J°î”lɌxĤÊÈðhÌ®‚g˜T´øŽàCˆŽÀ^ªerrƘdž¢İP|Ė ŸWœªĦ^¶´ÂL„aT±üWƜ˜ǀRšŶUńšĖ[QhlLüA†‹Ü\\†qR›Ą©"],"encodeOffsets":[[90849,37210]]}},{"type":"Feature","id":"610000","properties":{"id":"610000","cp":[108.948024,34.263161],"name":"陕西","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@˜p¢—ȮµšûG™Ħ}Ħšðǚ¶òƄ€jɂz°{ºØkÈęâ¦jª‚Bg‚\\œċ°s¬Ž’]jžú ‚E”Ȍdž¬s„t‡”RˆÆdĠݎwܔ¸ôW¾ƮłÒ_{’Ìšû¼„jº¹¢GǪÒ¯ĘƒZ`ºŊƒecņąš~BÂgzpâēòYǠȰÌTΨÂWœ|fcŸă§uF—Œ@NŸ¢XLƒŠRMº[ğȣſï|¥J™kc`sʼnǷ’Y¹‹W@µ÷K…ãï³ÛIcñ·VȋڍÒķø©—þ¥ƒy‚ÓŸğęmWµÎumZyOŅƟĥÓ~sÑL¤µaŅY¦ocyZ{‰y c]{ŒTa©ƒ`U_Ěē£ωÊƍKù’K¶ȱÝƷ§{û»ÅÁȹÍéuij|¹cÑd‘ŠìUYƒŽO‘uF–ÕÈYvÁCqӃT•Ǣí§·S¹NgŠV¬ë÷Át‡°Dد’C´ʼnƒópģ}„ċcE˅FŸŸéGU¥×K…§­¶³B‹Č}C¿åċ`wġB·¤őcƭ²ő[Å^axwQO…ÿEËߌ•ĤNĔŸwƇˆÄŠńwĪ­Šo[„_KÓª³“ÙnK‰Çƒěœÿ]ď€ă_d©·©Ýŏ°Ù®g]±„Ÿ‡ß˜å›—¬÷m\\›iaǑkěX{¢|ZKlçhLt€Ňîŵ€œè[€É@ƉĄEœ‡tƇÏ˜³­ħZ«mJ…›×¾‘MtÝĦ£IwÄå\\Õ{‡˜ƒOwĬ©LÙ³ÙgBƕŀr̛ĢŭO¥lãyC§HÍ£ßEñŸX¡—­°ÙCgpťz‘ˆb`wI„vA|§”‡—hoĕ@E±“iYd¥OϹS|}F@¾oAO²{tfžÜ—¢Fǂ҈W²°BĤh^Wx{@„¬‚­F¸¡„ķn£P|ŸªĴ@^ĠĈæb–Ôc¶l˜Yi…–^Mi˜cϰÂ[ä€vï¶gv@À“Ĭ·lJ¸sn|¼u~a]’ÆÈtŌºJp’ƒþ£KKf~ЦUbyäIšĺãn‡Ô¿^­žŵMT–hĠܤko¼Ŏìąǜh`[tŒRd²IJ_œXPrɲ‰l‘‚XžiL§àƒ–¹ŽH˜°Ȧqº®QC—bA†„ŌJ¸ĕÚ³ĺ§ `d¨YjžiZvRĺ±öVKkjGȊĐePОZmļKÀ€‚[ŠŽ`ösìh†ïÎoĬdtKÞ{¬èÒÒBŒÔpIJÇĬJŊ¦±J«ˆY§‹@·pH€µàåVKe›pW†ftsAÅqC·¬ko«pHÆuK@oŸHĆۄķhx“e‘n›S³àǍrqƶRbzy€¸ËАl›¼EºpĤ¼Œx¼½~Ğ’”à@†ÚüdK^ˆmÌSj"],"encodeOffsets":[[110234,38774]]}},{"type":"Feature","id":"620000","properties":{"id":"620000","cp":[103.823557,36.058039],"name":"甘肃","childNum":2},"geometry":{"type":"MultiPolygon","coordinates":[["@@VuUv"],["@@ũ‹EĠtt~nkh`Q‰¦ÅÄÜdw˜Ab×ĠąJˆ¤DüègĺqBqœj°lI¡ĨÒ¤úSHbš‡ŠjΑBаaZˆ¢KJŽ’O[|A£žDx}Nì•HUnrk„ kp€¼Y kMJn[aG‚áÚÏ[½rc†}aQxOgsPMnUs‡nc‹Z…ž–sKúvA›t„Þġ’£®ĀYKdnFwš¢JE°”Latf`¼h¬we|€Æ‡šbj}GA€·~WŽ”—`†¢MC¤tL©IJ°qdf”O‚“bÞĬ¹ttu`^ZúE`Œ[@„Æsîz®¡’C„ƳƜG²“R‘¢R’m”fŽwĸg܃‚ą G@pzJM½mŠhVy¸uÈÔO±¨{LfæU¶ßGĂq\\ª¬‡²I‚¥IʼnÈīoı‹ÓÑAçÑ|«LÝcspīðÍg…të_õ‰\\ĉñLYnĝg’ŸRǡÁiHLlõUĹ²uQjYi§Z_c¨Ÿ´ĹĖÙ·ŋI…ƒaBD˜­R¹ȥr—¯G•ºß„K¨jWk’ɱŠOq›Wij\\a­‹Q\\sg_ĆǛōëp»£lğۀgS•ŶN®À]ˆÓäm™ĹãJaz¥V}‰Le¤L„ýo‘¹IsŋÅÇ^‘Žbz…³tmEÁ´aйcčecÇN•ĊãÁ\\蝗dNj•]j†—ZµkÓda•ćå]ğij@ ©O{¤ĸm¢ƒE·®ƒ«|@Xwg]A챝‡XǁÑdzªc›wQÚŝñsÕ³ÛV_ýƒ˜¥\\ů¥©¾÷w—Ž©WÕÊĩhÿÖÁRo¸V¬âDb¨šhûx–Ê×nj~Zâƒg|šXÁnßYoº§ZÅŘvŒ[„ĭÖʃuďxcVbnUSf…B¯³_Tzº—ΕO©çMÑ~Mˆ³]µ^püµ”ŠÄY~y@X~¤Z³€[Èōl@®Å¼£QKƒ·Di‹¡By‘ÿ‰Q_´D¥hŗyƒ^ŸĭÁZ]cIzý‰ah¹MĪğP‘s{ò‡‹‘²Vw¹t³Ŝˁ[ŽÑ}X\\gsFŸ£sPAgěp×ëfYHāďÖqēŭOÏë“dLü•\\iŒ”t^c®šRʺ¶—¢H°mˆ‘rYŸ£BŸ¹čIoľu¶uI]vģSQ{ƒUŻ”Å}QÂ|̋°ƅ¤ĩŪU ęĄžÌZҞ\\v˜²PĔ»ƢNHƒĂyAmƂwVmž`”]ȏb•”H`‰Ì¢²ILvĜ—H®¤Dlt_„¢JJÄämèÔDëþgºƫ™”aʎÌrêYi~ ÎݤNpÀA¾Ĕ¼b…ð÷’Žˆ‡®‚”üs”zMzÖĖQdȨý†v§Tè|ªH’þa¸|šÐ ƒwKĢx¦ivr^ÿ ¸l öæfƟĴ·PJv}n\\h¹¶v†·À|\\ƁĚN´Ĝ€çèÁz]ġ¤²¨QÒŨTIl‡ªťØ}¼˗ƦvÄùØE‹’«Fï˛Iq”ōŒTvāÜŏ‚íÛߜÛV—j³âwGăÂíNOŠˆŠPìyV³ʼnĖýZso§HіiYw[߆\\X¦¥c]ÔƩÜ·«j‡ÐqvÁ¦m^ċ±R™¦΋ƈťĚgÀ»IïĨʗƮްƝ˜ĻþÍAƉſ±tÍEÕÞāNU͗¡\\ſčåÒʻĘm ƭÌŹöʥ’ëQ¤µ­ÇcƕªoIýˆ‰Iɐ_mkl³ă‰Ɠ¦j—¡Yz•Ňi–}Msßõ–īʋ —}ƒÁVmŸ_[n}eı­Uĥ¼‘ª•I{ΧDӜƻėoj‘qYhĹT©oūĶ£]ďxĩ‹ǑMĝ‰q`B´ƃ˺Ч—ç~™²ņj@”¥@đ´ί}ĥtPńǾV¬ufӃÉC‹tÓ̻‰…¹£G³€]ƖƾŎĪŪĘ̖¨ʈĢƂlɘ۪üºňUðǜȢƢż̌ȦǼ‚ĤŊɲĖ­Kq´ï¦—ºĒDzņɾªǀÞĈĂD†½ĄĎÌŗĞrôñnŽœN¼â¾ʄľԆ|DŽŽ֦ज़ȗlj̘̭ɺƅêgV̍ʆĠ·ÌĊv|ýĖÕWĊǎÞ´õ¼cÒÒBĢ͢UĜð͒s¨ňƃLĉÕÝ@ɛƯ÷¿Ľ­ĹeȏijëCȚDŲyê×Ŗyò¯ļcÂßY…tÁƤyAã˾J@ǝrý‹‰@¤…rz¸oP¹ɐÚyᐇHŸĀ[Jw…cVeȴϜ»ÈŽĖ}ƒŰŐèȭǢόĀƪÈŶë;Ñ̆ȤМľĮEŔ—ĹŊũ~ËUă{ŸĻƹɁύȩþĽvĽƓÉ@ē„ĽɲßǐƫʾǗĒpäWÐxnsÀ^ƆwW©¦cÅ¡Ji§vúF¶Ž¨c~c¼īŒeXǚ‹\\đ¾JŽwÀďksãA‹fÕ¦L}wa‚o”Z’‹D½†Ml«]eÒÅaɲáo½FõÛ]ĻÒ¡wYR£¢rvÓ®y®LF‹LzĈ„ôe]gx}•|KK}xklL]c¦£fRtív¦†PĤoH{tK"]],"encodeOffsets":[[[108619,36299]],[[108589,36341]]]}},{"type":"Feature","id":"630000","properties":{"id":"630000","cp":[96.778916,35.623178],"name":"青海","childNum":2},"geometry":{"type":"MultiPolygon","coordinates":[["@@InJm"],["@@CƒÆ½OŃĦsΰ~dz¦@@“Ņiš±è}ؘƄ˹A³r_ĞŠǒNΌĐw¤^ŬĵªpĺSZg’rpiƼĘԛ¨C|͖J’©Ħ»®VIJ~f\\m `Un„˜~ʌŸ•ĬàöNt•~ňjy–¢Zi˜Ɣ¥ĄŠk´nl`JʇŠJþ©pdƖ®È£¶ìRʦ‘źõƮËnŸʼėæÑƀĎ[‚˜¢VÎĂMÖÝÎF²sƊƀÎBļýƞ—¯ʘƭðħ¼Jh¿ŦęΌƇš¥²Q]Č¥nuÂÏriˆ¸¬ƪÛ^Ó¦d€¥[Wà…x\\ZŽjҕ¨GtpþYŊĕ´€zUO뇉P‰îMĄÁxH´á˜iÜUà›îÜՁĂÛSuŎ‹r“œJð̬EŒ‘FÁú×uÃÎkr“Ē{V}İ«O_ÌËĬ©ŽÓŧSRѱ§Ģ£^ÂyèçěM³Ƃę{[¸¿u…ºµ[gt£¸OƤĿéYŸõ·kŸq]juw¥Dĩƍ€õÇPéĽG‘ž©ã‡¤G…uȧþRcÕĕNy“yût“ˆ­‡ø‘†ï»a½ē¿BMoᣟÍj}éZËqbʍš“Ƭh¹ìÿÓAçãnIáI`ƒks£CG­ě˜Uy×Cy•…’Ÿ@¶ʡÊBnāzG„ơMē¼±O÷õJËĚăVŸĪũƆ£Œ¯{ËL½Ìzż“„VR|ĠTbuvJvµhĻĖH”Aëáa…­OÇðñęNw‡…œľ·L›mI±íĠĩPÉ×®ÿs—’cB³±JKßĊ«`…ađ»·QAmO’‘Vţéÿ¤¹SQt]]Çx€±¯A@ĉij¢Ó祖•ƒl¶ÅÛr—ŕspãRk~¦ª]Į­´“FR„åd­ČsCqđéFn¿Åƃm’Éx{W©ºƝºįkÕƂƑ¸wWūЩÈFž£\\tÈ¥ÄRÈýÌJ ƒlGr^×äùyÞ³fj”c†€¨£ÂZ|ǓMĝšÏ@ëÜőR‹›ĝ‰Œ÷¡{aïȷPu°ËXÙ{©TmĠ}Y³’­ÞIňµç½©C¡į÷¯B»|St»›]vƒųƒs»”}MÓ ÿʪƟǭA¡fs˜»PY¼c¡»¦c„ċ­¥£~msĉP•–Siƒ^o©A‰Šec‚™PeǵŽkg‚yUi¿h}aH™šĉ^|ᴟ¡HØûÅ«ĉ®]m€¡qĉ¶³ÈyôōLÁst“BŸ®wn±ă¥HSò뚣˜S’ë@לÊăxÇN©™©T±ª£IJ¡fb®ÞbŽb_Ą¥xu¥B—ž{łĝ³«`d˜Ɛt—¤ťiñžÍUuºí`£˜^tƃIJc—·ÛLO‹½Šsç¥Ts{ă\\_»™kϊ±q©čiìĉ|ÍIƒ¥ć¥›€]ª§D{ŝŖÉR_sÿc³Īō›ƿΑ›§p›[ĉ†›c¯bKm›R¥{³„Z†e^ŽŒwx¹dƽŽôIg §Mĕ ƹĴ¿—ǣÜ̓]‹Ý–]snåA{‹eŒƭ`ǻŊĿ\\ijŬű”YÂÿ¬jĖqŽßbЏ•L«¸©@ěĀ©ê¶ìÀEH|´bRľž–Ó¶rÀQþ‹vl®Õ‚E˜TzÜdb ˜hw¤{LR„ƒd“c‹b¯‹ÙVgœ‚ƜßzÃô쮍^jUèXΖ|UäÌ»rKŽ\\ŒªN‘¼pZCü†VY††¤ɃRi^rPҒTÖ}|br°qňb̰ªiƶGQ¾²„x¦PœmlŜ‘[Ĥ¡ΞsĦŸÔÏâ\\ªÚŒU\\f…¢N²§x|¤§„xĔsZPòʛ²SÐqF`ª„VƒÞŜĶƨVZŒÌL`ˆ¢dŐIqr\\oäõ–F礻Ŷ×h¹]Clـ\\¦ďÌį¬řtTӺƙgQÇÓHţĒ”´ÃbEÄlbʔC”|CˆŮˆk„Ʈ[ʼ¬ňœ´KŮÈΰÌζƶlð”ļA†TUvdTŠG†º̼ŠÔ€ŒsÊDԄveOg"]],"encodeOffsets":[[[105308,37219]],[[95370,40081]]]}},{"type":"Feature","id":"640000","properties":{"id":"640000","cp":[106.278179,37.26637],"name":"宁夏","childNum":2},"geometry":{"type":"MultiPolygon","coordinates":[["@@KëÀęĞ«OęȿȕŸı]ʼn¡åįÕÔ«Ǵõƪ™ĚQÐZhv K°›öqÀѐS[ÃÖHƖčË‡nL]ûc…Ùß@‚“ĝ‘¾}w»»‹oģF¹œ»kÌÏ·{zPƒ§B­¢íyÅt@ƒ@áš]Yv_ssģ¼i߁”ĻL¾ġsKD£¡N_…“˜X¸}B~Haiˆ™Åf{«x»ge_bs“KF¯¡Ix™mELcÿZ¤­Ģ‘ƒÝœsuBLù•t†ŒYdˆmVtNmtOPhRw~bd…¾qÐ\\âÙH\\bImlNZŸ»loƒŸqlVm–Gā§~QCw¤™{A\\‘PKŸNY‡¯bF‡kC¥’sk‹Šs_Ã\\ă«¢ħkJi¯r›rAhĹûç£CU‡ĕĊ_ԗBixÅُĄnªÑaM~ħpOu¥sîeQ¥¤^dkKwlL~{L~–hw^‚ófćƒKyEŒ­K­zuÔ¡qQ¤xZÑ¢^ļöܾEpž±âbÊÑÆ^fk¬…NC¾‘Œ“YpxbK~¥Že֎ŒäBlt¿Đx½I[ĒǙŒWž‹f»Ĭ}d§dµùEuj¨‚IÆ¢¥dXªƅx¿]mtÏwßR͌X¢͎vÆzƂZò®ǢÌʆCrâºMÞzžÆMҔÊÓŊZľ–r°Î®Ȉmª²ĈUªĚøºˆĮ¦ÌĘk„^FłĬhĚiĀ˾iİbjÕ"],["@@mfwěwMrŢªv@G‰"]],"encodeOffsets":[[[109366,40242]],[[108600,36303]]]}},{"type":"Feature","id":"650000","properties":{"id":"650000","cp":[85.617733,40.792818],"name":"新疆","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@QØĔ²X¨”~ǘBºjʐߨvK”ƔX¨vĊOžÃƒ·¢i@~c—‡ĝe_«”Eš“}QxgɪëÏÃ@sÅyXoŖ{ô«ŸuX…ê•Îf`œC‚¹ÂÿÐGĮÕĞXŪōŸMźÈƺQèĽôe|¿ƸJR¤ĘEjcUóº¯Ĩ_ŘÁMª÷Ð¥Oéȇ¿ÖğǤǷÂF҇zÉx[]­Ĥĝ‰œ¦EP}ûƥé¿İƷTėƫœŕƅ™ƱB»Đ±’ēO…¦E–•}‘`cȺrĦáŖuҞª«IJ‡πdƺÏØZƴwʄ¤ĖGЙǂZ̓èH¶}ÚZצʥĪï|ÇĦMŔ»İĝLj‹ì¥Βœba­¯¥ǕǚkĆŵĦɑĺƯxūД̵nơʃĽá½M»›òmqóŘĝč˾ăC…ćāƿÝɽ©DZŅ¹đ¥˜³ðLrÁ®ɱĕģʼnǻ̋ȥơŻǛȡVï¹Ň۩ûkɗġƁ§ʇė̕ĩũƽō^ƕŠUv£ƁQï“Ƶkŏ½ΉÃŭdzLқʻ«ƭ\\lƒ‡ŭD‡“{ʓDkaFÃÄa“³ŤđÔGRÈƚhSӹŚsİ«ĐË[¥ÚDkº^Øg¼ŵ¸£EÍö•€ůʼnT¡c_‡ËKY‹ƧUśĵ„݃U_©rETÏʜ±OñtYw獃{£¨uM³x½şL©Ùá[ÓÐĥ Νtģ¢\\‚ś’nkO›w¥±ƒT»ƷFɯàĩÞáB¹Æ…ÑUw„੍žĽw[“mG½Èå~‡Æ÷QyŠěCFmĭZī—ŵVÁ™ƿQƛ—ûXS²‰b½KϽĉS›©ŷXĕŸ{ŽĕK·¥Ɨcqq©f¿]‡ßDõU³h—­gËÇïģÉɋw“k¯í}I·šœbmœÉ–ř›īJɥĻˁ×xo›ɹī‡l•c…¤³Xù]‘™DžA¿w͉ì¥wÇN·ÂËnƾƍdǧđ®Ɲv•Um©³G\\“}µĿ‡QyŹl㓛µEw‰LJQ½yƋBe¶ŋÀů‡ož¥A—˜Éw@•{Gpm¿Aij†ŽKLhˆ³`ñcËtW‚±»ÕS‰ëüÿďD‡u\\wwwù³—V›LŕƒOMËGh£õP¡™er™Ïd{“‡ġWÁ…č|yšg^ğyÁzÙs`—s|ÉåªÇ}m¢Ń¨`x¥’ù^•}ƒÌ¥H«‰Yªƅ”Aйn~Ꝛf¤áÀz„gŠÇDIԝ´AňĀ҄¶ûEYospõD[{ù°]u›Jq•U•|Soċxţ[õÔĥkŋÞŭZ˺óYËüċrw €ÞkrťË¿XGÉbřaDü·Ē÷Aê[Ää€I®BÕИÞ_¢āĠpŠÛÄȉĖġDKwbm‡ÄNô‡ŠfœƫVÉvi†dz—H‘‹QµâFšù­Âœ³¦{YGžƒd¢ĚÜO „€{Ö¦ÞÍÀPŒ^b–ƾŠlŽ[„vt×ĈÍE˨¡Đ~´î¸ùÎh€uè`¸ŸHÕŔVºwĠââWò‡@{œÙNÝ´ə²ȕn{¿¥{l—÷eé^e’ďˆXj©î\\ªÑò˜Üìc\\üqˆÕ[Č¡xoÂċªbØ­Œø|€¶ȴZdÆÂšońéŒGš\\”¼C°ÌƁn´nxšÊOĨ’ہƴĸ¢¸òTxÊǪMīИÖŲÃɎOvˆʦƢ~FއRěò—¿ġ~åŊœú‰Nšžš¸qŽ’Ę[Ĕ¶ÂćnÒPĒÜvúĀÊbÖ{Äî¸~Ŕünp¤ÂH¾œĄYÒ©ÊfºmԈĘcDoĬMŬ’˜S¤„s²‚”ʘچžȂVŦ –ŽèW°ªB|IJXŔþÈJĦÆæFĚêŠYĂªĂ]øªŖNÞüA€’fɨJ€˜¯ÎrDDšĤ€`€mz\\„§~D¬{vJÂ˜«lµĂb–¤p€ŌŰNĄ¨ĊXW|ų ¿¾ɄĦƐMT”‡òP˜÷fØĶK¢ȝ˔Sô¹òEð­”`Ɩ½ǒÂň×äı–§ĤƝ§C~¡‚hlå‚ǺŦŞkâ’~}ŽFøàIJaĞ‚fƠ¥Ž„Ŕdž˜®U¸ˆźXœv¢aƆúŪtŠųƠjd•ƺŠƺÅìnrh\\ĺ¯äɝĦ]èpĄ¦´LƞĬŠ´ƤǬ˼Ēɸ¤rºǼ²¨zÌPðŀbþ¹ļD¢¹œ\\ĜÑŚŸ¶ZƄ³àjĨoâŠȴLʉȮŒĐ­ĚăŽÀêZǚŐ¤qȂ\\L¢ŌİfÆs|zºeªÙæ§΢{Ā´ƐÚ¬¨Ĵà²łhʺKÞºÖTŠiƢ¾ªì°`öøu®Ê¾ãØ"],"encodeOffsets":[[88824,50096]]}},{"type":"Feature","id":"110000","properties":{"id":"110000","cp":[116.405285,39.904989],"name":"北京","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@ĽOÁ›ûtŷmiÍt_H»Ĩ±d`й­{bw…Yr“³S]§§o¹€qGtm_Sŧ€“oa›‹FLg‘QN_•dV€@Zom_ć\\ߚc±x¯oœRcfe…£’o§ËgToÛJíĔóu…|wP¤™XnO¢ÉˆŦ¯rNÄā¤zâŖÈRpŢZŠœÚ{GŠrFt¦Òx§ø¹RóäV¤XdˆżâºWbwڍUd®bêņ¾‘jnŎGŃŶŠnzÚSeîĜZczî¾i]͜™QaúÍÔiþĩȨWĢ‹ü|Ėu[qb[swP@ÅğP¿{\\‡¥A¨Ï‘Ѩj¯ŠX\\¯œMK‘pA³[H…īu}}"],"encodeOffsets":[[120023,41045]]}},{"type":"Feature","id":"120000","properties":{"id":"120000","cp":[117.190182,39.125596],"name":"天津","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@ŬgX§Ü«E…¶Ḟ“¬O_™ïlÁg“z±AXe™µÄĵ{¶]gitgšIj·›¥îakS€‰¨ÐƎk}ĕ{gB—qGf{¿a†U^fI“ư‹³õ{YƒıëNĿžk©ïËZŏ‘R§òoY×Ógc…ĥs¡bġ«@dekąI[nlPqCnp{ˆō³°`{PNdƗqSÄĻNNâyj]äžÒD ĬH°Æ]~¡HO¾ŒX}ÐxŒgp“gWˆrDGˆŒpù‚Š^L‚ˆrzWxˆZ^¨´T\\|~@I‰zƒ–bĤ‹œjeĊªz£®Ĕvě€L†mV¾Ô_ȔNW~zbĬvG†²ZmDM~”~"],"encodeOffsets":[[120237,41215]]}},{"type":"Feature","id":"310000","properties":{"id":"310000","cp":[121.472644,31.231706],"name":"上海","childNum":6},"geometry":{"type":"MultiPolygon","coordinates":[["@@ɧư¬EpƸÁxc‡"],["@@©„ªƒ"],["@@”MA‹‘š"],["@@Qp݁E§ÉC¾"],["@@bŝՕÕEȣÚƥêImɇǦèÜĠŒÚžÃƌÃ͎ó"],["@@ǜûȬɋŠŭ™×^‰sYŒɍDŋ‘ŽąñCG²«ªč@h–_p¯A{‡oloY€¬j@IJ`•gQڛhr|ǀ^MIJvtbe´R¯Ô¬¨YŽô¤r]ì†Ƭį"]],"encodeOffsets":[[[124702,32062]],[[124547,32200]],[[124808,31991]],[[124726,32110]],[[124903,32376]],[[124438,32149]]]}},{"type":"Feature","id":"500000","properties":{"id":"500000","cp":[107.304962,29.533155],"name":"重庆","childNum":2},"geometry":{"type":"MultiPolygon","coordinates":[["@@vjG~nGŘŬĶȂƀƾ¹¸ØÎezĆT¸}êЖqHŸðqĖ䒊¥^CƒIj–²p…\\_ æüY|[YxƊæuž°xb®…Űb@~¢NQt°¶‚S栓Ê~rljĔëĚ¢~šuf`‘‚†fa‚ĔJåĊ„nÖ]„jƎćÊ@Š£¾a®£Ű{ŶĕF‹ègLk{Y|¡ĜWƔtƬJÑxq‹±ĢN´‰òK‰™–LÈüD|s`ŋ’ć]ƒÃ‰`đŒMûƱ½~Y°ħ`ƏíW‰½eI‹½{aŸ‘OIrÏ¡ĕŇa†p†µÜƅġ‘œ^ÖÛbÙŽŏml½S‹êqDu[R‹ãË»†ÿw`»y‘¸_ĺę}÷`M¯ċfCVµqʼn÷Z•gg“Œ`d½pDO‡ÎCnœ^uf²ènh¼WtƏxRGg¦…pV„†FI±ŽG^ŒIc´ec‡’G•ĹÞ½sëĬ„h˜xW‚}Kӈe­Xsbk”F¦›L‘ØgTkïƵNï¶}Gy“w\\oñ¡nmĈzjŸ•@™Óc£»Wă¹Ój“_m»ˆ¹·~MvÛaqœ»­‰êœ’\\ÂoVnŽÓØÍ™²«‹bq¿efE „€‹Ĝ^Qž~ Évý‡ş¤²Į‰pEİ}zcĺƒL‹½‡š¿gņ›¡ýE¡ya£³t\\¨\\vú»¼§·Ñr_oÒý¥u‚•_n»_ƒ•At©Þűā§IVeëƒY}{VPÀFA¨ąB}q@|Ou—\\Fm‰QF݅Mw˜å}]•€|FmϋCaƒwŒu_p—¯sfÙgY…DHl`{QEfNysBЦzG¸rHe‚„N\\CvEsÐùÜ_·ÖĉsaQ¯€}_U‡†xÃđŠq›NH¬•Äd^ÝŰR¬ã°wećJEž·vÝ·Hgƒ‚éFXjÉê`|yŒpxkAwœWĐpb¥eOsmzwqChóUQl¥F^laf‹anòsr›EvfQdÁUVf—ÎvÜ^efˆtET¬ôA\\œ¢sJŽnQTjP؈xøK|nBz‰„œĞ»LY‚…FDxӄvr“[ehľš•vN”¢o¾NiÂxGp⬐z›bfZo~hGi’]öF|‰|Nb‡tOMn eA±ŠtPT‡LjpYQ|†SH††YĀxinzDJ€Ìg¢và¥Pg‰_–ÇzII‹€II•„£®S¬„Øs쐣ŒN"],["@@ifjN@s"]],"encodeOffsets":[[[109628,30765]],[[111725,31320]]]}},{"type":"Feature","id":"810000","properties":{"id":"810000","cp":[114.173355,22.320048],"name":"香港","childNum":5},"geometry":{"type":"MultiPolygon","coordinates":[["@@AlBk"],["@@mŽn"],["@@EpFo"],["@@ea¢pl¸Eõ¹‡hj[ƒ]ÔCΖ@lj˜¡uBXŸ…•´‹AI¹…[‹yDUˆ]W`çwZkmc–…M›žp€Åv›}I‹oJlcaƒfёKްä¬XJmРđhI®æÔtSHn€Eˆ„ÒrÈc"],["@@rMUw‡AS®€e"]],"encodeOffsets":[[[117111,23002]],[[117072,22876]],[[117045,22887]],[[116975,23082]],[[116882,22747]]]}},{"type":"Feature","id":"820000","properties":{"id":"820000","cp":[113.54909,22.198951],"name":"澳门","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@kÊd°å§s"],"encodeOffsets":[[116279,22639]]}}],"UTF8Encoding":true} \ No newline at end of file diff --git a/front-end/public/static/css/loading.css b/front-end/public/static/css/loading.css new file mode 100644 index 0000000..a9ee0fb --- /dev/null +++ b/front-end/public/static/css/loading.css @@ -0,0 +1,96 @@ +.first-loading-wrp { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 90vh; + min-height: 90vh; +} + +.first-loading-wrp > h1 { + font-size: 28px; + font-weight: bolder; +} + +.first-loading-wrp .loading-wrp { + display: flex; + align-items: center; + justify-content: center; + padding: 98px; +} + +.dot { + position: relative; + box-sizing: border-box; + display: inline-block; + width: 64px; + height: 64px; + font-size: 64px; + transform: rotate(45deg); + animation: antRotate 1.2s infinite linear; +} + +.dot i { + position: absolute; + display: block; + width: 28px; + height: 28px; + background-color: #1890ff; + border-radius: 100%; + opacity: 0.3; + transform: scale(0.75); + transform-origin: 50% 50%; + animation: antSpinMove 1s infinite linear alternate; +} + +.dot i:nth-child(1) { + top: 0; + left: 0; +} + +.dot i:nth-child(2) { + top: 0; + right: 0; + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} + +.dot i:nth-child(3) { + right: 0; + bottom: 0; + -webkit-animation-delay: 0.8s; + animation-delay: 0.8s; +} + +.dot i:nth-child(4) { + bottom: 0; + left: 0; + -webkit-animation-delay: 1.2s; + animation-delay: 1.2s; +} + +@keyframes antRotate { + to { + -webkit-transform: rotate(405deg); + transform: rotate(405deg); + } +} + +@-webkit-keyframes antRotate { + to { + -webkit-transform: rotate(405deg); + transform: rotate(405deg); + } +} + +@keyframes antSpinMove { + to { + opacity: 1; + } +} + +@-webkit-keyframes antSpinMove { + to { + opacity: 1; + } +} diff --git a/front-end/scripts/compress.js b/front-end/scripts/compress.js new file mode 100644 index 0000000..c69960b --- /dev/null +++ b/front-end/scripts/compress.js @@ -0,0 +1,87 @@ +const fs = require('fs') +const path = require('path') +const archiver = require('archiver') + +// 压缩文件名 +const zipFileName = `dist.zip` +const zipFilePath = path.join(__dirname, '..', 'dist', zipFileName) + +// 创建输出流 +const output = fs.createWriteStream(zipFilePath) +const archive = archiver('zip', { + zlib: { level: 9 }, // 设置压缩级别 +}) + +// 监听错误事件 +archive.on('error', (err) => { + throw err +}) + +// 监听关闭事件 +output.on('close', () => { + const sizeInMB = (archive.pointer() / 1024 / 1024).toFixed(2) + console.log(`✅ 压缩完成!`) + console.log(`📦 文件名: ${zipFileName}`) + console.log(`📏 文件大小: ${sizeInMB} MB`) + console.log(`📍 文件路径: ${zipFilePath}`) +}) + +// 监听警告事件 +archive.on('warning', (err) => { + if (err.code === 'ENOENT') { + console.warn('⚠️ 警告:', err.message) + } else { + throw err + } +}) + +// 管道输出 +archive.pipe(output) + +// 检查dist目录是否存在 +const distPath = path.join(__dirname, '..', 'dist') +if (!fs.existsSync(distPath)) { + console.error('❌ 错误: dist目录不存在,请先运行构建命令') + process.exit(1) +} + +console.log('🚀 开始压缩构建文件...') + +// 递归添加dist目录内的所有文件和文件夹到压缩包根目录 +function addDirectoryToArchive(dirPath, archivePath = '') { + const items = fs.readdirSync(dirPath) + + items.forEach((item) => { + const fullPath = path.join(dirPath, item) + const relativePath = archivePath ? path.join(archivePath, item) : item + const stat = fs.statSync(fullPath) + + if (stat.isDirectory()) { + // 递归添加子目录 + addDirectoryToArchive(fullPath, relativePath) + } else { + // 跳过压缩包文件本身,避免自包含 + if (item !== zipFileName) { + // 添加文件到压缩包根目录 + archive.file(fullPath, { name: relativePath }) + } + } + }) +} + +// 添加dist目录内的所有文件到压缩包根目录 +addDirectoryToArchive(distPath) + +// 添加package.json到压缩包(可选) +archive.file(path.join(__dirname, '..', 'package.json'), { + name: 'package.json', +}) + +// 添加README.md到压缩包(如果存在) +const readmePath = path.join(__dirname, '..', 'README.md') +if (fs.existsSync(readmePath)) { + archive.file(readmePath, { name: 'README.md' }) +} + +// 完成压缩 +archive.finalize() diff --git a/front-end/src/App.vue b/front-end/src/App.vue new file mode 100644 index 0000000..89c212f --- /dev/null +++ b/front-end/src/App.vue @@ -0,0 +1,34 @@ + + + diff --git a/front-end/src/api/area.ts b/front-end/src/api/area.ts new file mode 100644 index 0000000..37c3dd8 --- /dev/null +++ b/front-end/src/api/area.ts @@ -0,0 +1,25 @@ +import request from '@/utils/request' + +export function getList(params?: any) { + return request({ + url: '/area/getList', + method: 'get', + params, + }) +} + +export function doEdit(data: any) { + return request({ + url: '/area/doEdit', + method: 'post', + data, + }) +} + +export function doDelete(data: any) { + return request({ + url: '/area/doDelete', + method: 'post', + data, + }) +} diff --git a/front-end/src/api/defaultIcon.ts b/front-end/src/api/defaultIcon.ts new file mode 100644 index 0000000..5fe7654 --- /dev/null +++ b/front-end/src/api/defaultIcon.ts @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +export function getIconList(params?: any) { + return request({ + url: '/defaultIcon/getList', + method: 'get', + params, + }) +} diff --git a/front-end/src/api/departmentManagement.ts b/front-end/src/api/departmentManagement.ts new file mode 100644 index 0000000..7f4e25c --- /dev/null +++ b/front-end/src/api/departmentManagement.ts @@ -0,0 +1,25 @@ +import request from '@/utils/request' + +export function getList(params?: any) { + return request({ + url: '/departmentManagement/getList', + method: 'get', + params, + }) +} + +export function doEdit(data: any) { + return request({ + url: '/departmentManagement/doEdit', + method: 'post', + data, + }) +} + +export function doDelete(data: any) { + return request({ + url: '/departmentManagement/doDelete', + method: 'post', + data, + }) +} diff --git a/front-end/src/api/description.ts b/front-end/src/api/description.ts new file mode 100644 index 0000000..4fc7b7d --- /dev/null +++ b/front-end/src/api/description.ts @@ -0,0 +1,13 @@ +import request from '@/utils/request' + +export function getList() { + const params: any = {} + if (process.env.NODE_ENV === 'production') + params.u = btoa(process.env['VUE_A' + 'PP_GIT' + 'HUB_US' + 'ER_NAME']) + + return request({ + url: 'https://api.vuejs-core.cn/getDescription', + method: 'get', + params, + }) +} diff --git a/front-end/src/api/dictionaryManagement.ts b/front-end/src/api/dictionaryManagement.ts new file mode 100644 index 0000000..1469b73 --- /dev/null +++ b/front-end/src/api/dictionaryManagement.ts @@ -0,0 +1,33 @@ +import request from '@/utils/request' + +export function getTree(params?: any) { + return request({ + url: '/dictionaryManagement/getTree', + method: 'get', + params, + }) +} + +export function getList(params?: any) { + return request({ + url: '/dictionaryManagement/getList', + method: 'get', + params, + }) +} + +export function doEdit(data: any) { + return request({ + url: '/dictionaryManagement/doEdit', + method: 'post', + data, + }) +} + +export function doDelete(data: any) { + return request({ + url: '/dictionaryManagement/doDelete', + method: 'post', + data, + }) +} diff --git a/front-end/src/api/goods.ts b/front-end/src/api/goods.ts new file mode 100644 index 0000000..b97336e --- /dev/null +++ b/front-end/src/api/goods.ts @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +export function getList(params?: any) { + return request({ + url: '/goods/getList', + method: 'get', + params, + }) +} diff --git a/front-end/src/api/menuManagement.ts b/front-end/src/api/menuManagement.ts new file mode 100644 index 0000000..93641b0 --- /dev/null +++ b/front-end/src/api/menuManagement.ts @@ -0,0 +1,25 @@ +import request from '@/utils/request' + +export function getTree(params?: any) { + return request({ + url: '/menuManagement/getTree', + method: 'get', + params, + }) +} + +export function doEdit(data: any) { + return request({ + url: '/menuManagement/doEdit', + method: 'post', + data, + }) +} + +export function doDelete(data: any) { + return request({ + url: '/menuManagement/doDelete', + method: 'post', + data, + }) +} diff --git a/front-end/src/api/notice.ts b/front-end/src/api/notice.ts new file mode 100644 index 0000000..9c3792d --- /dev/null +++ b/front-end/src/api/notice.ts @@ -0,0 +1,8 @@ +import request from '@/utils/request' + +export function getList() { + return request({ + url: '/notice/getList', + method: 'get', + }) +} diff --git a/front-end/src/api/publicKey.ts b/front-end/src/api/publicKey.ts new file mode 100644 index 0000000..289001a --- /dev/null +++ b/front-end/src/api/publicKey.ts @@ -0,0 +1,8 @@ +import request from '@/utils/request' + +export function getPublicKey() { + return request({ + url: '/publicKey', + method: 'get', + }) +} diff --git a/front-end/src/api/refreshToken.ts b/front-end/src/api/refreshToken.ts new file mode 100644 index 0000000..bdd6ae0 --- /dev/null +++ b/front-end/src/api/refreshToken.ts @@ -0,0 +1,15 @@ +import request from '@/utils/request' + +export function expireToken() { + return request({ + url: '/expireToken', + method: 'get', + }) +} + +export function refreshToken() { + return request({ + url: '/refreshToken', + method: 'get', + }) +} diff --git a/front-end/src/api/roleManagement.ts b/front-end/src/api/roleManagement.ts new file mode 100644 index 0000000..600de61 --- /dev/null +++ b/front-end/src/api/roleManagement.ts @@ -0,0 +1,25 @@ +import request from '@/utils/request' + +export function getList(params?: any) { + return request({ + url: '/roleManagement/getList', + method: 'get', + params, + }) +} + +export function doEdit(data: any) { + return request({ + url: '/roleManagement/doEdit', + method: 'post', + data, + }) +} + +export function doDelete(data: any) { + return request({ + url: '/roleManagement/doDelete', + method: 'post', + data, + }) +} diff --git a/front-end/src/api/router.ts b/front-end/src/api/router.ts new file mode 100644 index 0000000..c0e0bf5 --- /dev/null +++ b/front-end/src/api/router.ts @@ -0,0 +1,8 @@ +import request from '@/utils/request' + +export function getList() { + return request({ + url: '/router/getList', + method: 'get', + }) +} diff --git a/front-end/src/api/search.ts b/front-end/src/api/search.ts new file mode 100644 index 0000000..a0aee8a --- /dev/null +++ b/front-end/src/api/search.ts @@ -0,0 +1,8 @@ +import request from '@/utils/request' + +export function getList() { + return request({ + url: '/search/getList', + method: 'get', + }) +} diff --git a/front-end/src/api/systemLog.ts b/front-end/src/api/systemLog.ts new file mode 100644 index 0000000..ba808fd --- /dev/null +++ b/front-end/src/api/systemLog.ts @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +export function getList(params?: any) { + return request({ + url: '/systemLog/getList', + method: 'get', + params, + }) +} diff --git a/front-end/src/api/table.ts b/front-end/src/api/table.ts new file mode 100644 index 0000000..539019f --- /dev/null +++ b/front-end/src/api/table.ts @@ -0,0 +1,25 @@ +import request from '@/utils/request' + +export function getList(params?: any) { + return request({ + url: '/table/getList', + method: 'get', + params, + }) +} + +export function doEdit(data: any) { + return request({ + url: '/table/doEdit', + method: 'post', + data, + }) +} + +export function doDelete(data: any) { + return request({ + url: '/table/doDelete', + method: 'post', + data, + }) +} diff --git a/front-end/src/api/taskManagement.ts b/front-end/src/api/taskManagement.ts new file mode 100644 index 0000000..1e23dfe --- /dev/null +++ b/front-end/src/api/taskManagement.ts @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +export function getList(params?: any) { + return request({ + url: '/taskManagement/getList', + method: 'get', + params, + }) +} diff --git a/front-end/src/api/user.ts b/front-end/src/api/user.ts new file mode 100644 index 0000000..48bc5bc --- /dev/null +++ b/front-end/src/api/user.ts @@ -0,0 +1,47 @@ +import request from '@/utils/request' +import { encryptedData } from '@/utils/encrypt' +import { loginRSA } from '@/config' + +export async function login(data: any) { + if (loginRSA) { + data = await encryptedData(data) + } + return request({ + url: '/login', + method: 'post', + data, + }) +} + +export async function socialLogin(data: any) { + if (loginRSA) { + data = await encryptedData(data) + } + return request({ + url: '/socialLogin', + method: 'post', + data, + }) +} + +export function getUserInfo() { + return request({ + url: '/userInfo', + method: 'get', + }) +} + +export function logout() { + return request({ + url: '/logout', + method: 'get', + }) +} + +export function register(data: any) { + return request({ + url: '/register', + method: 'post', + data, + }) +} diff --git a/front-end/src/api/userManagement.ts b/front-end/src/api/userManagement.ts new file mode 100644 index 0000000..6a91cc6 --- /dev/null +++ b/front-end/src/api/userManagement.ts @@ -0,0 +1,25 @@ +import request from '@/utils/request' + +export function getList(params?: any) { + return request({ + url: '/userManagement/getList', + method: 'get', + params, + }) +} + +export function doEdit(data: any) { + return request({ + url: '/userManagement/doEdit', + method: 'post', + data, + }) +} + +export function doDelete(data: any) { + return request({ + url: '/userManagement/doDelete', + method: 'post', + data, + }) +} diff --git a/front-end/src/api/workflow.ts b/front-end/src/api/workflow.ts new file mode 100644 index 0000000..118703b --- /dev/null +++ b/front-end/src/api/workflow.ts @@ -0,0 +1,17 @@ +import request from '@/utils/request' + +export function getList(params?: any) { + return request({ + url: '/workflow/getList', + method: 'get', + params, + }) +} + +export function doEdit(data: any) { + return request({ + url: '/workflow/doEdit', + method: 'post', + data, + }) +} diff --git a/front-end/src/assets/cropper_images/user.gif b/front-end/src/assets/cropper_images/user.gif new file mode 100644 index 0000000..ee994f6 Binary files /dev/null and b/front-end/src/assets/cropper_images/user.gif differ diff --git a/front-end/src/assets/empty_images/data_empty.png b/front-end/src/assets/empty_images/data_empty.png new file mode 100644 index 0000000..fccccf2 Binary files /dev/null and b/front-end/src/assets/empty_images/data_empty.png differ diff --git a/front-end/src/assets/error_images/403.png b/front-end/src/assets/error_images/403.png new file mode 100644 index 0000000..b1dfc1f Binary files /dev/null and b/front-end/src/assets/error_images/403.png differ diff --git a/front-end/src/assets/error_images/404.png b/front-end/src/assets/error_images/404.png new file mode 100644 index 0000000..135d629 Binary files /dev/null and b/front-end/src/assets/error_images/404.png differ diff --git a/front-end/src/assets/error_images/cloud.png b/front-end/src/assets/error_images/cloud.png new file mode 100644 index 0000000..247c06b Binary files /dev/null and b/front-end/src/assets/error_images/cloud.png differ diff --git a/front-end/src/assets/index_images/image.jpg b/front-end/src/assets/index_images/image.jpg new file mode 100644 index 0000000..4294d6d Binary files /dev/null and b/front-end/src/assets/index_images/image.jpg differ diff --git a/front-end/src/assets/login_images/background.jpg b/front-end/src/assets/login_images/background.jpg new file mode 100644 index 0000000..79b0433 Binary files /dev/null and b/front-end/src/assets/login_images/background.jpg differ diff --git a/front-end/src/assets/login_images/login_form.png b/front-end/src/assets/login_images/login_form.png new file mode 100644 index 0000000..faa0569 Binary files /dev/null and b/front-end/src/assets/login_images/login_form.png differ diff --git a/front-end/src/assets/logo.png b/front-end/src/assets/logo.png new file mode 100644 index 0000000..cb67440 Binary files /dev/null and b/front-end/src/assets/logo.png differ diff --git a/front-end/src/assets/mobile_images/mobile.png b/front-end/src/assets/mobile_images/mobile.png new file mode 100644 index 0000000..49d4be7 Binary files /dev/null and b/front-end/src/assets/mobile_images/mobile.png differ diff --git a/front-end/src/assets/rank_images/rank.png b/front-end/src/assets/rank_images/rank.png new file mode 100644 index 0000000..efc448d Binary files /dev/null and b/front-end/src/assets/rank_images/rank.png differ diff --git a/front-end/src/assets/skm.jpg b/front-end/src/assets/skm.jpg new file mode 100644 index 0000000..4760e7c Binary files /dev/null and b/front-end/src/assets/skm.jpg differ diff --git a/front-end/src/assets/skm1.jpg b/front-end/src/assets/skm1.jpg new file mode 100644 index 0000000..ff10604 Binary files /dev/null and b/front-end/src/assets/skm1.jpg differ diff --git a/front-end/src/assets/skm2.jpg b/front-end/src/assets/skm2.jpg new file mode 100644 index 0000000..b3725e0 Binary files /dev/null and b/front-end/src/assets/skm2.jpg differ diff --git a/front-end/src/assets/tabs_images/vab-tab.png b/front-end/src/assets/tabs_images/vab-tab.png new file mode 100644 index 0000000..9cabaa7 Binary files /dev/null and b/front-end/src/assets/tabs_images/vab-tab.png differ diff --git a/front-end/src/assets/task_image/task.png b/front-end/src/assets/task_image/task.png new file mode 100644 index 0000000..aa0b897 Binary files /dev/null and b/front-end/src/assets/task_image/task.png differ diff --git a/front-end/src/assets/theme_images/background-1.png b/front-end/src/assets/theme_images/background-1.png new file mode 100644 index 0000000..e20be5b Binary files /dev/null and b/front-end/src/assets/theme_images/background-1.png differ diff --git a/front-end/src/config/cli.config.js b/front-end/src/config/cli.config.js new file mode 100644 index 0000000..c2c6685 --- /dev/null +++ b/front-end/src/config/cli.config.js @@ -0,0 +1,33 @@ +/** + * @description 导出vue/cli配置,以下所有配置修改需要重启项目 + */ +module.exports = { + // 开发以及部署时的URL + // hash模式时在不确定二级目录名称的情况下建议使用""代表相对路径或者"/二级目录/" + // history模式默认使用"/"或者"/二级目录/",记住只有hash时publicPath可以为空!!! + publicPath: '', + // 生产环境构建文件的目录名 + outputDir: 'dist', + // 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。 + assetsDir: 'static', + // 开发环境每次保存时是否输出为eslint编译警告 + lintOnSave: true, + // 进行编译的依赖 + transpileDependencies: [], + // 开发环境端口号 + devPort: 15000, + // 需要自动注入并加载的模块 + providePlugin: {}, + // npm run build时是否自动生成7z压缩包 + build7z: false, + // npm run build时是否生成gzip + buildGzip: false, + // npm run build时是否开启图片压缩,由于国内网路原因image-webpack-loader必须使用cnpm安装,如无法使用cnpm,请配置false + imageCompression: false, + // pwa + pwa: true, + // 打包优化,如需实现服务器快速部署请配置false,如需提升网页加载速度请配置true + buildOptimize: true, + // 禁止在生产环境下使用调试 + noDebugger: true, +} diff --git a/front-end/src/config/index.js b/front-end/src/config/index.js new file mode 100644 index 0000000..412d7cf --- /dev/null +++ b/front-end/src/config/index.js @@ -0,0 +1,15 @@ +/** + * @description 4个子配置,vue/cli配置|通用配置|主题配置|网络配置导出 + * config中的部分配置由vue.config.js读取,本质是node,故不可使用window等浏览器对象 + */ +const cli = require('./cli.config') +const setting = require('./setting.config') +const theme = require('./theme.config') +const network = require('./net.config') + +module.exports = { + ...cli, + ...setting, + ...theme, + ...network, +} diff --git a/front-end/src/config/net.config.js b/front-end/src/config/net.config.js new file mode 100644 index 0000000..f067221 --- /dev/null +++ b/front-end/src/config/net.config.js @@ -0,0 +1,27 @@ +/** + * @description 导出网络配置 + **/ +module.exports = { + // 默认的接口地址,开发环境和生产环境都会走/vab-mock-server + // 正式项目可以选择自己配置成需要的接口地址,如"https://api.xxx.com" + // 问号后边代表开发环境,冒号后边代表生产环境 + // 如果不需要测试环境解除以下注释即可 + // baseURL: + // process.env.NODE_ENV === 'development' + // ? '/vab-mock-server' + // : '/vab-mock-server', + + // 支持多环境接口地址配置的方法 + // 开发环境去.env.development改,生产环境去.env.production改,测试环境去.env.test改 + baseURL: `${process.env.VUE_APP_BASE_URL}`, + // 配后端数据的接收方式application/json;charset=UTF-8 或 application/x-www-form-urlencoded;charset=UTF-8 + contentType: 'application/json;charset=UTF-8', + // 最长请求时间 + requestTimeout: 10000, + // 操作正常code,支持String、Array、int多种类型 + successCode: [200, 0, '200', '0'], + // 数据状态的字段名称 + statusName: 'code', + // 状态信息的字段名称 + messageName: 'msg', +} diff --git a/front-end/src/config/setting.config.js b/front-end/src/config/setting.config.js new file mode 100644 index 0000000..b502381 --- /dev/null +++ b/front-end/src/config/setting.config.js @@ -0,0 +1,67 @@ +/** + * @description 导出通用配置 + */ +module.exports = { + // 标题,此项修改后需要重启项目!!! (包括初次加载雪花屏的标题 页面的标题 浏览器的标题) + title: 'Vue Admin Plus', + // 标题分隔符 + titleSeparator: ' - ', + // 标题是否反转 + // 如果为false: "page - title" + // 如果为true : "title - page" + titleReverse: false, + // 简写 + abbreviation: 'vab-admin-plus', + // pro版本copyright可随意修改 + copyright: 'zxwk1998', + // 缓存路由的最大数量 + keepAliveMaxNum: 20, + // 路由模式,是否为hash模式 + isHashRouterMode: true, + // 不经过token校验的路由,白名单路由建议配置到与login页面同级,如果需要放行带传参的页面,请使用query传参,配置时只配置path即可 + routesWhiteList: ['/login', '/register', '/callback', '/404', '/403'], + // 加载时显示文字 + loadingText: '正在加载中...', + // token名称 + tokenName: 'token', + // token在localStorage、sessionStorage、cookie存储的key的名称 + tokenTableName: 'admin-plus-token', + // token存储位置localStorage sessionStorage cookie + storage: 'localStorage', + // token失效回退到登录页时是否记录本次的路由(是否记录当前tab页) + recordRoute: true, + // 是否开启logo,不显示时设置false,请填写src/icon路径下的图标名称 + // 如需使用内置RemixIcon图标,请自行去logo组件切换注释代码(内置svg雪碧图较大,对性能有一定影响) + logo: 'vuejs-fill', + // 语言类型zh、en + i18n: 'zh', + // 消息框消失时间 + messageDuration: 3000, + // 在哪些环境下显示高亮错误 ['development', 'production'] + errorLog: 'development', + // 是否开启登录拦截 + loginInterception: true, + // 是否开启登录RSA加密 + loginRSA: false, + // intelligence(前端导出路由)和 all(后端导出路由)两种方式 + authentication: 'intelligence', + // 是否支持游客模式,支持情况下,访问白名单,可查看所有asyncRoutes + supportVisit: false, + // 是否开启roles字段进行角色权限控制(如果是all模式后端完全处理角色并进行json组装,可设置false不处理路由中的roles字段) + rolesControl: true, + // vertical column comprehensive common布局时是否只保持一个子菜单的展开 + uniqueOpened: false, + // vertical column comprehensive common布局时默认展开的菜单path,使用逗号隔开建议只展开一个,true全部展开,false/[]不展开 + defaultOpeneds: [ + '/vab', + '/vab/table', + '/vab/icon', + '/vab/form', + '/vab/editor', + '/other/drag', + ], + // 需要加loading层的请求,防止重复提交 + debounce: ['doEdit'], + // 分栏布局和综合布局时,是否点击一级菜单默认开启二级菜单(默认第一个,可通过redirect自定义) + openFirstMenu: true, +} diff --git a/front-end/src/config/theme.config.js b/front-end/src/config/theme.config.js new file mode 100644 index 0000000..8245244 --- /dev/null +++ b/front-end/src/config/theme.config.js @@ -0,0 +1,45 @@ +/** + * @description 导出主题配置,注意事项:此配置下的项修改后需清理浏览器缓存!!! + */ +module.exports = { + // 布局种类:横向布局horizontal、纵向布局vertical、分栏布局column、综合布局comprehensive、常规布局common、浮动布局float + layout: 'column', + // 主题名称:默认blue-black、blue-white、green-black、green-white、渐变ocean、red-white、red-black + themeName: 'blue-black', + // 菜单背景 none、vab-background + background: 'none', + // 菜单宽度,仅支持px,建议大小:266px、277px、288px,其余尺寸会影响美观 + menuWidth: '266px', + // 分栏风格(仅针对分栏布局column时生效):横向风格horizontal、纵向风格vertical、卡片风格card、箭头风格arrow + columnStyle: 'card', + // 是否固定头部固定 + fixedHeader: true, + // 是否开启顶部进度条 + showProgressBar: true, + // 是否开启标签页 + showTabs: true, + // 显示标签页时标签页样式:卡片风格card、灵动风格smart、圆滑风格smooth + tabsBarStyle: 'smooth', + // 是否标签页图标 + showTabsIcon: true, + // 是否开启语言选择组件 + showLanguage: true, + // 是否开启刷新组件 + showRefresh: true, + // 是否开启搜索组件 + showSearch: true, + // 是否开启主题组件 + showTheme: true, + // 是否开启通知组件 + showNotice: true, + // 是否开启全屏组件 + showFullScreen: true, + // 是否开启右侧悬浮窗 + showThemeSetting: true, + //纵向布局、常规布局、综合布局时是否默认收起左侧菜单(不支持分栏布局、横向布局) + foldSidebar: false, + // 是否开启页面动画 + showPageTransition: true, + // 是否开启锁屏 + showLock: true, +} diff --git a/front-end/src/i18n/index.ts b/front-end/src/i18n/index.ts new file mode 100644 index 0000000..0900143 --- /dev/null +++ b/front-end/src/i18n/index.ts @@ -0,0 +1,44 @@ +import { createI18n } from 'vue-i18n' +import en from './locales/en.json' +import pinia from '@/store' +import { useSettingsStore } from '@/store/modules/settings' +import type { LanguageType } from '/#/store' + +const messages: Record = { + en: { + ...en, + }, + zh: {}, +} + +function getLanguage() { + const { getLanguage } = useSettingsStore(pinia) + return getLanguage +} + +export const i18n = createI18n({ + legacy: false, + locale: getLanguage(), + fallbackLocale: 'zh', + messages, +}) + +export function setupI18n(app: any) { + app.use(i18n) + return i18n +} + +export function translate(message: string | undefined) { + if (!message) { + return '' + } + return ( + [getLanguage(), 'vabI18n', message].reduce( + (o, k) => (o || {})[k], + messages as any + ) || message + ) +} + +export { default as enLocale } from 'element-plus/dist/locale/en' +export { default as zhLocale } from 'element-plus/dist/locale/zh-cn' diff --git a/front-end/src/i18n/locales/en.json b/front-end/src/i18n/locales/en.json new file mode 100644 index 0000000..2002e65 --- /dev/null +++ b/front-end/src/i18n/locales/en.json @@ -0,0 +1,172 @@ +{ + "vabI18n": { + "403": "403", + "404": "404", + "Css动画": "Cssfx", + "Excel": "Excel", + "按钮": "Button", + "保存": "Save", + "编辑器": "Editor", + "标签": "Tabs", + "标签风格": "Tabs style", + "标签开启时生效": "Effective when the label is opened", + "标签图标": "Tabs icon", + "表单": "Form", + "表格": "Table", + "不固定": "No fixed", + "布局": "Layouts", + "布局配置仅在电脑视窗下生效,手机视窗时将默认锁定为纵向布局": "The layout configuration only takes effect in the computer window,the vertical layout will be locked in the mobile window by default", + "部门管理": "Department management", + "菜单背景": "Background", + "菜单管理": "Menu management", + "菜单宽度": "Menu width", + "仓库": "Store", + "常规": "Common", + "常规图标": "Awesome icon", + "常用设置": "Common settings", + "错误日志模拟": "Log", + "错误页": "Error", + "打印": "Print", + "单选框": "Radip", + "导出Excel": "Export excel", + "导出合并Excel": "Export merge header excel", + "导出选中行Excel": "Export selected excel", + "登录": "Login", + "第三方登录": "Social login", + "动态Meta": "Dynamic meta", + "动态表格": "Dynamic table", + "动态路径参数": "Dynamic segment", + "动态锚点": "Dynamic anchor", + "多标签": "Tabs", + "'多级路由1-1'": "Menu1-1", + "'多级路由1-1-1'": "Menu1-1-1", + "多级路由缓存": "Menu1", + "多选框": "Checkbox", + "分步表单": "Step form", + "分栏": "Column", + "分栏布局时生效": "Column layout takes effect", + "分栏风格": "Column style", + "分享": "Share", + "浮动": "Float", + "富文本编辑器": "Rich text editor", + "个人中心": "User center", + "更多": "More", + "更新日志": "Change log", + "工具": "Tools", + "工作流": "Workflow", + "工作台": "Workbench", + "购买源码": "Buy", + "固定": "Fixed", + "关闭": "Close", + "关闭其他": "Close other", + "关闭全部": "Close all", + "关闭右侧": "Close right", + "关闭左侧": "Close left", + "国际化": "Language", + "海洋之心": "Ocean", + "横向": "Horizontal", + "红白": "Red white", + "红黑": "Red black", + "滑块": "Slider", + "欢迎来到": "Welcome to", + "恢复默认": "Defalut", + "获取验证码": "Get captcha", + "计数器": "Input number", + "加载": "Loading", + "渐变": "Ocean", + "箭头": "Arrow", + "角色管理": "Role management", + "角色权限": "Roles", + "解锁": "Unlock", + "进度条": "Progress", + "卡片": "Card", + "卡片拖拽": "Card drag", + "开关": "Switch", + "开启": "Open", + "看板": "Dashboard", + "拷贝源码": "Code", + "蓝白": "Blue white", + "蓝黑": "Blue black", + "列表": "List", + "灵动": "Smart", + "绿白": "Green white", + "绿黑": "Green black", + "绿荫草场": "Green", + "密码不能少于6位": "The password cannot be less than 6 digits", + "描述": "Description", + "默认": "Default", + "配置": "Settings", + "碰触纯白": "White", + "评分": "rate", + "屏幕已锁定": "Screen already locked", + "其他": "Other", + "其它设置": "Other settings", + "切换壁纸": "Switch wallpaper", + "清空消息": "Clear message", + "清理缓存": "Claer", + "请输入密码": "Please input a password", + "请输入手机号": "Please enter your mobile phone number", + "请输入手机验证码": "Please input the mobile phone verification code", + "请输入用户名": "Please enter one user name", + "请输入正确的手机号": "Please enter the correct mobile phone number", + "全屏": "Full screen", + "取色器": "Color picker", + "任务管理": "Task management", + "日历": "Calendar", + "日期时间选择器": "Date time picker", + "日期选择器": "Date picker", + "上传": "Upload", + "时间线": "Timeline", + "时间选择器": "Time picker", + "视频播放器": "Player", + "手机预览": "Mobile preview", + "首页": "Home", + "输入框": "Input", + "数字自增长": "Count", + "刷新": "Refresh", + "搜索": "Search", + "随机换肤": "Random", + "锁屏": "Lock screen", + "水印": "Watermark", + "弹窗拖拽": "Diaglog Drag", + "腾讯文档": "Wang editor", + "通知": "Notice", + "头部固定": "Header", + "头像裁剪": "Head cropper", + "图标": "Icon", + "图标选择器": "Icon selector", + "图表": "Echarts", + "退出登录": "Logout", + "拖拽": "Drag", + "外链": "External links", + "文字链接": "Link", + "无分栏": "No column", + "无框": "No layout", + "物料市场": "Material market", + "物料源": "Material", + "系统日志": "System log", + "默认图标": "Default icon", + "行内编辑表格": "Inline edit table", + "选择器": "Select", + "验证码": "Verification code", + "页面动画": "Page transition", + "用户管理": "User management", + "用户名不能为空": "The user name cannot be empty", + "邮件": "Email", + "语音合成": "Speech synthesis", + "圆滑": "Smooth", + "月上重火": "Red", + "支持纵向布局、分栏布局、综合布局、常规布局,不支持横向布局、浮动布局": "Vertical layout, column layout, comprehensive layout and general layout are supported, while horizontal layout and floating layout are not supported", + "主题": "Theme", + "主题配置": "Theme", + "注册": "Register", + "字典管理": "Dictionary management", + "自定义表格": "Custom table", + "自定义图标": "Custom svg", + "综合": "Comprehensive", + "综合表单": "Comprehensive form", + "综合表格": "Comprehensive table", + "纵向": "Vertical", + "组件": "Part" + } +} diff --git a/front-end/src/icon/index.ts b/front-end/src/icon/index.ts new file mode 100644 index 0000000..739dfe2 --- /dev/null +++ b/front-end/src/icon/index.ts @@ -0,0 +1,2 @@ +const icons = require.context('.', true, /\.svg$/) +icons.keys().map(icons) diff --git a/front-end/src/icon/vab.svg b/front-end/src/icon/vab.svg new file mode 100644 index 0000000..fd609b1 --- /dev/null +++ b/front-end/src/icon/vab.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/front-end/src/icon/vuejs-fill.svg b/front-end/src/icon/vuejs-fill.svg new file mode 100644 index 0000000..0e6787a --- /dev/null +++ b/front-end/src/icon/vuejs-fill.svg @@ -0,0 +1,4 @@ + + + + diff --git a/front-end/src/main.ts b/front-end/src/main.ts new file mode 100644 index 0000000..b2d2f24 --- /dev/null +++ b/front-end/src/main.ts @@ -0,0 +1,36 @@ +import { createApp } from 'vue' +import App from './App.vue' +import { baseURL, pwa } from './config' +import { setupVab } from '~/library' +import { setupI18n } from '@/i18n' +import { setupStore } from '@/store' +import { setupRouter } from '@/router' +import { validateSecretKey } from '@/utils' + +/** + * @description 正式环境默认使用mock,正式项目记得注释后再打包 + */ +import { isExternal } from '@/utils/validate' + +validateSecretKey() + +const app = createApp(App) + +if (process.env.NODE_ENV === 'production' && !isExternal(baseURL)) { + const { mockXHR } = require('@/utils/static') + mockXHR() +} + +/** + * @description 生产环境启用组件初始化,编译,渲染和补丁性能跟踪。仅在开发模式和支持 Performance.mark API的浏览器中工作。 + */ +//if (process.env.NODE_ENV === 'development') app.config.performance = true + +if (pwa) require('./registerServiceWorker') + +setupVab(app) +setupI18n(app) +setupStore(app) +setupRouter(app) + .isReady() + .then(() => app.mount('#app')) diff --git a/front-end/src/plugins/VabAnchor/index.vue b/front-end/src/plugins/VabAnchor/index.vue new file mode 100644 index 0000000..41ff186 --- /dev/null +++ b/front-end/src/plugins/VabAnchor/index.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/front-end/src/plugins/VabAvatarList/index.vue b/front-end/src/plugins/VabAvatarList/index.vue new file mode 100644 index 0000000..06ad3db --- /dev/null +++ b/front-end/src/plugins/VabAvatarList/index.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/front-end/src/plugins/VabCalendar.ts b/front-end/src/plugins/VabCalendar.ts new file mode 100644 index 0000000..6a8b18b --- /dev/null +++ b/front-end/src/plugins/VabCalendar.ts @@ -0,0 +1,1122 @@ +/** @type {*} + * //github.com/jjonline/calendar.js/blob/master/calendar.js + */ + +const VabCalendar: any = { + /** + * 农历1900-2100的润大小信息表 + * @Array Of Property + * @return Hex + */ + lunarInfo: [ + 0x04bd8, + 0x04ae0, + 0x0a570, + 0x054d5, + 0x0d260, + 0x0d950, + 0x16554, + 0x056a0, + 0x09ad0, + 0x055d2, //1900-1909 + 0x04ae0, + 0x0a5b6, + 0x0a4d0, + 0x0d250, + 0x1d255, + 0x0b540, + 0x0d6a0, + 0x0ada2, + 0x095b0, + 0x14977, //1910-1919 + 0x04970, + 0x0a4b0, + 0x0b4b5, + 0x06a50, + 0x06d40, + 0x1ab54, + 0x02b60, + 0x09570, + 0x052f2, + 0x04970, //1920-1929 + 0x06566, + 0x0d4a0, + 0x0ea50, + 0x16a95, + 0x05ad0, + 0x02b60, + 0x186e3, + 0x092e0, + 0x1c8d7, + 0x0c950, //1930-1939 + 0x0d4a0, + 0x1d8a6, + 0x0b550, + 0x056a0, + 0x1a5b4, + 0x025d0, + 0x092d0, + 0x0d2b2, + 0x0a950, + 0x0b557, //1940-1949 + 0x06ca0, + 0x0b550, + 0x15355, + 0x04da0, + 0x0a5b0, + 0x14573, + 0x052b0, + 0x0a9a8, + 0x0e950, + 0x06aa0, //1950-1959 + 0x0aea6, + 0x0ab50, + 0x04b60, + 0x0aae4, + 0x0a570, + 0x05260, + 0x0f263, + 0x0d950, + 0x05b57, + 0x056a0, //1960-1969 + 0x096d0, + 0x04dd5, + 0x04ad0, + 0x0a4d0, + 0x0d4d4, + 0x0d250, + 0x0d558, + 0x0b540, + 0x0b6a0, + 0x195a6, //1970-1979 + 0x095b0, + 0x049b0, + 0x0a974, + 0x0a4b0, + 0x0b27a, + 0x06a50, + 0x06d40, + 0x0af46, + 0x0ab60, + 0x09570, //1980-1989 + 0x04af5, + 0x04970, + 0x064b0, + 0x074a3, + 0x0ea50, + 0x06b58, + 0x05ac0, + 0x0ab60, + 0x096d5, + 0x092e0, //1990-1999 + 0x0c960, + 0x0d954, + 0x0d4a0, + 0x0da50, + 0x07552, + 0x056a0, + 0x0abb7, + 0x025d0, + 0x092d0, + 0x0cab5, //2000-2009 + 0x0a950, + 0x0b4a0, + 0x0baa4, + 0x0ad50, + 0x055d9, + 0x04ba0, + 0x0a5b0, + 0x15176, + 0x052b0, + 0x0a930, //2010-2019 + 0x07954, + 0x06aa0, + 0x0ad50, + 0x05b52, + 0x04b60, + 0x0a6e6, + 0x0a4e0, + 0x0d260, + 0x0ea65, + 0x0d530, //2020-2029 + 0x05aa0, + 0x076a3, + 0x096d0, + 0x04afb, + 0x04ad0, + 0x0a4d0, + 0x1d0b6, + 0x0d250, + 0x0d520, + 0x0dd45, //2030-2039 + 0x0b5a0, + 0x056d0, + 0x055b2, + 0x049b0, + 0x0a577, + 0x0a4b0, + 0x0aa50, + 0x1b255, + 0x06d20, + 0x0ada0, //2040-2049 + /**Add By JJonline@JJonline.Cn**/ + 0x14b63, + 0x09370, + 0x049f8, + 0x04970, + 0x064b0, + 0x168a6, + 0x0ea50, + 0x06b20, + 0x1a6c4, + 0x0aae0, //2050-2059 + 0x092e0, + 0x0d2e3, + 0x0c960, + 0x0d557, + 0x0d4a0, + 0x0da50, + 0x05d55, + 0x056a0, + 0x0a6d0, + 0x055d4, //2060-2069 + 0x052d0, + 0x0a9b8, + 0x0a950, + 0x0b4a0, + 0x0b6a6, + 0x0ad50, + 0x055a0, + 0x0aba4, + 0x0a5b0, + 0x052b0, //2070-2079 + 0x0b273, + 0x06930, + 0x07337, + 0x06aa0, + 0x0ad50, + 0x14b55, + 0x04b60, + 0x0a570, + 0x054e4, + 0x0d160, //2080-2089 + 0x0e968, + 0x0d520, + 0x0daa0, + 0x16aa6, + 0x056d0, + 0x04ae0, + 0x0a9d4, + 0x0a2d0, + 0x0d150, + 0x0f252, //2090-2099 + 0x0d520, + ], //2100 + + /** + * 公历每个月份的天数普通表 + * @Array Of Property + * @return Number + */ + solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], + + /** + * 天干地支之天干速查表 + * @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"] + * @return Cn string + */ + Gan: [ + '\u7532', + '\u4E59', + '\u4E19', + '\u4E01', + '\u620A', + '\u5DF1', + '\u5E9A', + '\u8F9B', + '\u58EC', + '\u7678', + ], + + /** + * 天干地支之地支速查表 + * @Array Of Property + * @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"] + * @return Cn string + */ + Zhi: [ + '\u5B50', + '\u4E11', + '\u5BC5', + '\u536F', + '\u8FB0', + '\u5DF3', + '\u5348', + '\u672A', + '\u7533', + '\u9149', + '\u620C', + '\u4EA5', + ], + + /** + * 天干地支之地支速查表<=>生肖 + * @Array Of Property + * @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"] + * @return Cn string + */ + Animals: [ + '\u9F20', + '\u725B', + '\u864E', + '\u5154', + '\u9F99', + '\u86C7', + '\u9A6C', + '\u7F8A', + '\u7334', + '\u9E21', + '\u72D7', + '\u732A', + ], + + /** + * 阳历节日 + */ + festival: { + '1-1': { title: '元旦节' }, + '2-14': { title: '情人节' }, + '5-1': { title: '劳动节' }, + '5-4': { title: '青年节' }, + '6-1': { title: '儿童节' }, + '9-10': { title: '教师节' }, + '10-1': { title: '国庆节' }, + '12-25': { title: '圣诞节' }, + + '3-8': { title: '妇女节' }, + '3-12': { title: '植树节' }, + '4-1': { title: '愚人节' }, + '5-12': { title: '护士节' }, + '7-1': { title: '建党节' }, + '8-1': { title: '建军节' }, + '12-24': { title: '平安夜' }, + }, + + /** + * 农历节日 + */ + lFestival: { + '12-30': { title: '除夕' }, + '1-1': { title: '春节' }, + '1-15': { title: '元宵节' }, + '2-2': { title: '龙抬头' }, + '5-5': { title: '端午节' }, + '7-7': { title: '七夕节' }, + '7-15': { title: '中元节' }, + '8-15': { title: '中秋节' }, + '9-9': { title: '重阳节' }, + '10-1': { title: '寒衣节' }, + '10-15': { title: '下元节' }, + '12-8': { title: '腊八节' }, + '12-23': { title: '北方小年' }, + '12-24': { title: '南方小年' }, + }, + + /** + * 返回默认定义的阳历节日 + */ + getFestival() { + return this.festival + }, + + /** + * 返回默认定义的内容里节日 + */ + getLunarFestival() { + return this.lFestival + }, + + /** + * + * @param param {Object} 按照festival的格式输入数据,设置阳历节日 + */ + setFestival(param = {}) { + this.festival = param + }, + + /** + * + * @param param {Object} 按照lFestival的格式输入数据,设置农历节日 + */ + setLunarFestival(param = {}) { + this.lFestival = param + }, + + /** + * 24节气速查表 + * @Array Of Property + * @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"] + * @return Cn string + */ + solarTerm: [ + '\u5C0F\u5BD2', + '\u5927\u5BD2', + '\u7ACB\u6625', + '\u96E8\u6C34', + '\u60CA\u86F0', + '\u6625\u5206', + '\u6E05\u660E', + '\u8C37\u96E8', + '\u7ACB\u590F', + '\u5C0F\u6EE1', + '\u8292\u79CD', + '\u590F\u81F3', + '\u5C0F\u6691', + '\u5927\u6691', + '\u7ACB\u79CB', + '\u5904\u6691', + '\u767D\u9732', + '\u79CB\u5206', + '\u5BD2\u9732', + '\u971C\u964D', + '\u7ACB\u51AC', + '\u5C0F\u96EA', + '\u5927\u96EA', + '\u51AC\u81F3', + ], + + /** + * 1900-2100各年的24节气日期速查表 + * @Array Of Property + * @return 0x string For splice + */ + sTermInfo: [ + '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', + '97bcf97c3598082c95f8c965cc920f', + '97bd0b06bdb0722c965ce1cfcc920f', + 'b027097bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', + '97bcf97c359801ec95f8c965cc920f', + '97bd0b06bdb0722c965ce1cfcc920f', + 'b027097bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', + '97bcf97c359801ec95f8c965cc920f', + '97bd0b06bdb0722c965ce1cfcc920f', + 'b027097bd097c36b0b6fc9274c91aa', + '9778397bd19801ec9210c965cc920e', + '97b6b97bd19801ec95f8c965cc920f', + '97bd09801d98082c95f8e1cfcc920f', + '97bd097bd097c36b0b6fc9210c8dc2', + '9778397bd197c36c9210c9274c91aa', + '97b6b97bd19801ec95f8c965cc920e', + '97bd09801d98082c95f8e1cfcc920f', + '97bd097bd097c36b0b6fc9210c8dc2', + '9778397bd097c36c9210c9274c91aa', + '97b6b97bd19801ec95f8c965cc920e', + '97bcf97c3598082c95f8e1cfcc920f', + '97bd097bd097c36b0b6fc9210c8dc2', + '9778397bd097c36c9210c9274c91aa', + '97b6b97bd19801ec9210c965cc920e', + '97bcf97c3598082c95f8c965cc920f', + '97bd097bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', + '97bcf97c3598082c95f8c965cc920f', + '97bd097bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', + '97bcf97c359801ec95f8c965cc920f', + '97bd097bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', + '97bcf97c359801ec95f8c965cc920f', + '97bd097bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', + '97bcf97c359801ec95f8c965cc920f', + '97bd097bd07f595b0b6fc920fb0722', + '9778397bd097c36b0b6fc9210c8dc2', + '9778397bd19801ec9210c9274c920e', + '97b6b97bd19801ec95f8c965cc920f', + '97bd07f5307f595b0b0bc920fb0722', + '7f0e397bd097c36b0b6fc9210c8dc2', + '9778397bd097c36c9210c9274c920e', + '97b6b97bd19801ec95f8c965cc920f', + '97bd07f5307f595b0b0bc920fb0722', + '7f0e397bd097c36b0b6fc9210c8dc2', + '9778397bd097c36c9210c9274c91aa', + '97b6b97bd19801ec9210c965cc920e', + '97bd07f1487f595b0b0bc920fb0722', + '7f0e397bd097c36b0b6fc9210c8dc2', + '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', + '97bcf7f1487f595b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', + '97bcf7f1487f595b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', + '97bcf7f1487f531b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', + '97bcf7f1487f531b0b0bb0b6fb0722', + '7f0e397bd07f595b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c9274c920e', + '97bcf7f0e47f531b0b0bb0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', + '9778397bd097c36b0b6fc9210c91aa', + '97b6b97bd197c36c9210c9274c920e', + '97bcf7f0e47f531b0b0bb0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', + '9778397bd097c36b0b6fc9210c8dc2', + '9778397bd097c36c9210c9274c920e', + '97b6b7f0e47f531b0723b0b6fb0722', + '7f0e37f5307f595b0b0bc920fb0722', + '7f0e397bd097c36b0b6fc9210c8dc2', + '9778397bd097c36b0b70c9274c91aa', + '97b6b7f0e47f531b0723b0b6fb0721', + '7f0e37f1487f595b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc9210c8dc2', + '9778397bd097c36b0b6fc9274c91aa', + '97b6b7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f595b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', + '97b6b7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', + '97b6b7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', + '97b6b7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', + '97b6b7f0e47f531b0723b0787b0721', + '7f0e27f0e47f531b0b0bb0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', + '9778397bd097c36b0b6fc9210c91aa', + '97b6b7f0e47f149b0723b0787b0721', + '7f0e27f0e47f531b0723b0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', + '9778397bd097c36b0b6fc9210c8dc2', + '977837f0e37f149b0723b0787b0721', + '7f07e7f0e47f531b0723b0b6fb0722', + '7f0e37f5307f595b0b0bc920fb0722', + '7f0e397bd097c35b0b6fc9210c8dc2', + '977837f0e37f14998082b0787b0721', + '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e37f1487f595b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc9210c8dc2', + '977837f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc920fb0722', + '977837f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc920fb0722', + '977837f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', + '977837f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', + '977837f0e37f14998082b0787b06bd', + '7f07e7f0e47f149b0723b0787b0721', + '7f0e27f0e47f531b0b0bb0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', + '977837f0e37f14998082b0723b06bd', + '7f07e7f0e37f149b0723b0787b0721', + '7f0e27f0e47f531b0723b0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', + '977837f0e37f14898082b0723b02d5', + '7ec967f0e37f14998082b0787b0721', + '7f07e7f0e47f531b0723b0b6fb0722', + '7f0e37f1487f595b0b0bb0b6fb0722', + '7f0e37f0e37f14898082b0723b02d5', + '7ec967f0e37f14998082b0787b0721', + '7f07e7f0e47f531b0723b0b6fb0722', + '7f0e37f1487f531b0b0bb0b6fb0722', + '7f0e37f0e37f14898082b0723b02d5', + '7ec967f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e37f1487f531b0b0bb0b6fb0722', + '7f0e37f0e37f14898082b072297c35', + '7ec967f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e37f0e37f14898082b072297c35', + '7ec967f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e37f0e366aa89801eb072297c35', + '7ec967f0e37f14998082b0787b06bd', + '7f07e7f0e47f149b0723b0787b0721', + '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e37f0e366aa89801eb072297c35', + '7ec967f0e37f14998082b0723b06bd', + '7f07e7f0e47f149b0723b0787b0721', + '7f0e27f0e47f531b0723b0b6fb0722', + '7f0e37f0e366aa89801eb072297c35', + '7ec967f0e37f14998082b0723b06bd', + '7f07e7f0e37f14998083b0787b0721', + '7f0e27f0e47f531b0723b0b6fb0722', + '7f0e37f0e366aa89801eb072297c35', + '7ec967f0e37f14898082b0723b02d5', + '7f07e7f0e37f14998082b0787b0721', + '7f07e7f0e47f531b0723b0b6fb0722', + '7f0e36665b66aa89801e9808297c35', + '665f67f0e37f14898082b0723b02d5', + '7ec967f0e37f14998082b0787b0721', + '7f07e7f0e47f531b0723b0b6fb0722', + '7f0e36665b66a449801e9808297c35', + '665f67f0e37f14898082b0723b02d5', + '7ec967f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e36665b66a449801e9808297c35', + '665f67f0e37f14898082b072297c35', + '7ec967f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e26665b66a449801e9808297c35', + '665f67f0e37f1489801eb072297c35', + '7ec967f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', + ], + + /** + * 数字转中文速查表 + * @Array Of Property + * @trans ['日','一','二','三','四','五','六','七','八','九','十'] + * @return Cn string + */ + nStr1: [ + '\u65E5', + '\u4E00', + '\u4E8C', + '\u4E09', + '\u56DB', + '\u4E94', + '\u516D', + '\u4E03', + '\u516B', + '\u4E5D', + '\u5341', + ], + + /** + * 日期转农历称呼速查表 + * @Array Of Property + * @trans ['初','十','廿','卅'] + * @return Cn string + */ + nStr2: ['\u521D', '\u5341', '\u5EFF', '\u5345'], + + /** + * 月份转农历称呼速查表 + * @Array Of Property + * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊'] + * @return Cn string + */ + nStr3: [ + '\u6B63', + '\u4E8C', + '\u4E09', + '\u56DB', + '\u4E94', + '\u516D', + '\u4E03', + '\u516B', + '\u4E5D', + '\u5341', + '\u51AC', + '\u814A', + ], + + /** + * 返回农历y年一整年的总天数 + * @param y lunar Year + * @return Number + * @eg:var count = calendar.lYearDays(1987) ;//count=387 + */ + lYearDays(y: number) { + let i, + sum = 348 + for (i = 0x8000; i > 0x8; i >>= 1) { + sum += this.lunarInfo[y - 1900] & i ? 1 : 0 + } + return sum + this.leapDays(y) + }, + + /** + * 返回农历y年闰月是哪个月;若y年没有闰月 则返回0 + * @param y lunar Year + * @return Number (0-12) + * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6 + */ + leapMonth(y: number) { + //闰字编码 \u95f0 + return this.lunarInfo[y - 1900] & 0xf + }, + + /** + * 返回农历y年闰月的天数 若该年没有闰月则返回0 + * @param y lunar Year + * @return Number (0、29、30) + * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29 + */ + leapDays(y: number) { + if (this.leapMonth(y)) { + return this.lunarInfo[y - 1900] & 0x10000 ? 30 : 29 + } + return 0 + }, + + /** + * 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法 + * @param y lunar Year + * @param m lunar Month + * @return Number (-1、29、30) + * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29 + */ + monthDays(y: number, m: number) { + if (m > 12 || m < 1) { + return -1 + } //月份参数从1至12,参数错误返回-1 + return this.lunarInfo[y - 1900] & (0x10000 >> m) ? 30 : 29 + }, + + /** + * 返回公历(!)y年m月的天数 + * @param y solar Year + * @param m solar Month + * @return Number (-1、28、29、30、31) + * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30 + */ + solarDays(y: number, m: number) { + if (m > 12 || m < 1) { + return -1 + } //若参数错误 返回-1 + const ms = m - 1 + if (ms === 1) { + //2月份的闰平规律测算后确认返回28或29 + return (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0 ? 29 : 28 + } else { + return this.solarMonth[ms] + } + }, + + /** + * 农历年份转换为干支纪年 + * @param lYear 农历年的年份数 + * @return Cn string + */ + toGanZhiYear(lYear: number) { + let ganKey = (lYear - 3) % 10 + let zhiKey = (lYear - 3) % 12 + if (ganKey === 0) ganKey = 10 //如果余数为0则为最后一个天干 + if (zhiKey === 0) zhiKey = 12 //如果余数为0则为最后一个地支 + return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1] + }, + + /** + * 公历月、日判断所属星座 + * @param cMonth [description] + * @param cDay [description] + * @return Cn string + */ + toAstro(cMonth: number, cDay: number) { + const s = + '\u9B54\u7FAF\u6C34\u74F6\u53CC\u9C7C\u767D\u7F8A\u91D1\u725B\u53CC\u5B50\u5DE8\u87F9\u72EE\u5B50\u5904\u5973\u5929\u79E4\u5929\u874E\u5C04\u624B\u9B54\u7FAF' + const arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22] + return `${s.substring(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2)}\u5EA7` //座 + }, + + /** + * 传入offset偏移量返回干支 + * @param offset 相对甲子的偏移量 + * @return Cn string + */ + toGanZhi(offset: number) { + return this.Gan[offset % 10] + this.Zhi[offset % 12] + }, + + /** + * 传入公历(!)y年获得该年第n个节气的公历日期 + * @param y y公历年(1900-2100) + * @param n n二十四节气中的第几个节气(1~24);从n=1(小寒)算起 + * @return day Number + * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春 + */ + getTerm(y: number, n: number) { + if (y < 1900 || y > 2100) { + return -1 + } + if (n < 1 || n > 24) { + return -1 + } + const _table = this.sTermInfo[y - 1900] + const _info = [ + Number.parseInt(`0x${_table.slice(0, 5)}`).toString(), + Number.parseInt(`0x${_table.slice(5, 5)}`).toString(), + Number.parseInt(`0x${_table.slice(5, 10)}`).toString(), + Number.parseInt(`0x${_table.slice(5, 15)}`).toString(), + Number.parseInt(`0x${_table.slice(5, 20)}`).toString(), + Number.parseInt(`0x${_table.slice(5, 25)}`).toString(), + ] + const _calcDay = [ + _info[0].slice(0, 1), + _info[0].slice(1, 2), + _info[0].slice(1, 3), + _info[0].slice(2, 4), + + _info[1].slice(0, 1), + _info[1].slice(1, 2), + _info[1].slice(1, 3), + _info[1].slice(2, 4), + + _info[2].slice(0, 1), + _info[2].slice(1, 2), + _info[2].slice(1, 3), + _info[2].slice(2, 4), + + _info[3].slice(0, 1), + _info[3].slice(1, 2), + _info[3].slice(1, 3), + _info[3].slice(2, 4), + + _info[4].slice(0, 1), + _info[4].slice(1, 2), + _info[4].slice(1, 3), + _info[4].slice(2, 4), + + _info[5].slice(0, 1), + _info[5].slice(1, 2), + _info[5].slice(1, 3), + _info[5].slice(2, 4), + ] + return Number.parseInt(_calcDay[n - 1]) + }, + + /** + * 传入农历数字月份返回汉语通俗表示法 + * @param m lunar month + * @return Cn string + * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月' + */ + toChinaMonth(m: number) { + // 月 => \u6708 + if (m > 12 || m < 1) { + return -1 + } //若参数错误 返回-1 + let s = this.nStr3[m - 1] + s += '\u6708' //加上月字 + return s + }, + + /** + * 传入农历日期数字返回汉字表示法 + * @param d lunar day + * @return Cn string + * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一' + */ + toChinaDay(d: number) { + //日 => \u65e5 + let s + switch (d) { + case 10: + s = '\u521D\u5341' + break + case 20: + s = '\u4E8C\u5341' + break + case 30: + s = '\u4E09\u5341' + break + default: + s = this.nStr2[Math.floor(d / 10)] + s += this.nStr1[d % 10] + } + return s + }, + + /** + * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春” + * @param y year + * @return Cn string + * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔' + */ + getAnimal(y: number) { + return this.Animals[(y - 4) % 12] + }, + + /** + * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON + * !important! 公历参数区间1900.1.31~2100.12.31 + * @param yPara solar year + * @param mPara solar month + * @param dPara solar day + * @return JSON object + * @eg:console.log(calendar.solar2lunar(1987,11,01)); + */ + solar2lunar(yPara: string, mPara: string, dPara: string) { + let y: any = Number.parseInt(yPara) + let m: any = Number.parseInt(mPara) + let d: any = Number.parseInt(dPara) + //年份限定、上限 + if (y < 1900 || y > 2100) { + return -1 // undefined转换为数字变为NaN + } + //公历传参最下限 + if (y === 1900 && m === 1 && d < 31) { + return -1 + } + + //未传参 获得当天 + let objDate + if (!y) { + objDate = new Date() + } else { + objDate = new Date(y, Number.parseInt(m) - 1, d) + } + let i, + leap = 0, + temp = 0 + //修正ymd参数 + y = objDate.getFullYear() + m = objDate.getMonth() + 1 + d = objDate.getDate() + let offset = + (Date.UTC( + objDate.getFullYear(), + objDate.getMonth(), + objDate.getDate() + ) - + Date.UTC(1900, 0, 31)) / + 86400000 + for (i = 1900; i < 2101 && offset > 0; i++) { + temp = this.lYearDays(i) + offset -= temp + } + if (offset < 0) { + offset += temp + i-- + } + + //是否今天 + const isTodayObj: any = new Date() + let isToday: any = false + if ( + isTodayObj.getFullYear() === y && + isTodayObj.getMonth() + 1 === m && + isTodayObj.getDate() === d + ) { + isToday = true + } + //星期几 + let nWeek: any = objDate.getDay() + const cWeek: any = this.nStr1[nWeek] + //数字表示周几顺应天朝周一开始的惯例 + if (nWeek === 0) { + nWeek = 7 + } + //农历年 + const year = i + leap = this.leapMonth(i) //闰哪个月 + let isLeap = false + + //效验闰月 + for (i = 1; i < 13 && offset > 0; i++) { + //闰月 + if (leap > 0 && i === leap + 1 && isLeap === false) { + --i + isLeap = true + temp = this.leapDays(year) //计算农历闰月天数 + } else { + temp = this.monthDays(year, i) //计算农历普通月天数 + } + //解除闰月 + if (isLeap === true && i === leap + 1) { + isLeap = false + } + offset -= temp + } + // 闰月导致数组下标重叠取反 + if (offset === 0 && leap > 0 && i === leap + 1) { + if (isLeap) { + isLeap = false + } else { + isLeap = true + --i + } + } + if (offset < 0) { + offset += temp + --i + } + //农历月 + const month = i + //农历日 + const day = offset + 1 + //天干地支处理 + const sm = m - 1 + const gzY = this.toGanZhiYear(year) + + // 当月的两个节气 + // bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year` + const firstNode = this.getTerm(y, m * 2 - 1) //返回当月「节」为几日开始 + const secondNode = this.getTerm(y, m * 2) //返回当月「节」为几日开始 + + // 依据12节气修正干支月 + let gzM = this.toGanZhi((y - 1900) * 12 + m + 11) + if (d >= firstNode) { + gzM = this.toGanZhi((y - 1900) * 12 + m + 12) + } + + //传入的日期的节气与否 + let isTerm = false + let Term = null + if (firstNode === d) { + isTerm = true + Term = this.solarTerm[m * 2 - 2] + } + if (secondNode === d) { + isTerm = true + Term = this.solarTerm[m * 2 - 1] + } + //日柱 当月一日与 1900/1/1 相差天数 + const dayCyclical = + Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10 + const gzD = this.toGanZhi(dayCyclical + d - 1) + //该日期所属的星座 + const astro = this.toAstro(m, d) + + const solarDate = `${y}-${m}-${d}` + const lunarDate = `${year}-${month}-${day}` + + const festival = this.festival + const lFestival = this.lFestival + + const festivalDate = `${m}-${d}` + const lunarFestivalDate = `${month}-${day}` + + return { + date: solarDate, + lunarDate, + festival: festival[festivalDate] + ? festival[festivalDate].title + : null, + lunarFestival: lFestival[lunarFestivalDate] + ? lFestival[lunarFestivalDate].title + : null, + lYear: year, + lMonth: month, + lDay: day, + Animal: this.getAnimal(year), + IMonthCn: (isLeap ? '\u95F0' : '') + this.toChinaMonth(month), + IDayCn: this.toChinaDay(day), + cYear: y, + cMonth: m, + cDay: d, + gzYear: gzY, + gzMonth: gzM, + gzDay: gzD, + isToday, + isLeap, + nWeek, + ncWeek: `\u661F\u671F${cWeek}`, + isTerm, + Term, + astro, + } + }, + + /** + * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON + * !important! 参数区间1900.1.31~2100.12.1 + * @param y lunar year + * @param m lunar month + * @param d lunar day + * @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可] + * @return JSON object + * @eg:console.log(calendar.lunar2solar(1987,9,10)); + */ + lunar2solar(y: any, m: any, d: any, isLeapMonth: boolean) { + y = Number.parseInt(y) + m = Number.parseInt(m) + d = Number.parseInt(d) + isLeapMonth = !!isLeapMonth + const leapMonth = this.leapMonth(y) + if (isLeapMonth && leapMonth !== m) { + return -1 + } //传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同 + if ( + (y === 2100 && m === 12 && d > 1) || + (y === 1900 && m === 1 && d < 31) + ) { + return -1 + } //超出了最大极限值 + const day = this.monthDays(y, m) + let _day = day + //bugFix 2016-9-25 + //if month is leap, _day use leapDays method + if (isLeapMonth) { + _day = this.leapDays(y, m) + } + if (y < 1900 || y > 2100 || d > _day) { + return -1 + } //参数合法性效验 + + //计算农历的时间差 + let offset = 0 + let i + for (i = 1900; i < y; i++) { + offset += this.lYearDays(i) + } + let leap = 0, + isAdd = false + for (i = 1; i < m; i++) { + leap = this.leapMonth(y) + if ( + !isAdd && //处理闰月 + leap <= i && + leap > 0 + ) { + offset += this.leapDays(y) + isAdd = true + } + offset += this.monthDays(y, i) + } + //转换闰月农历 需补充该年闰月的前一个月的时差 + if (isLeapMonth) { + offset += day + } + //1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点) + const strap = Date.UTC(1900, 1, 30, 0, 0, 0) + const calObj = new Date((offset + d - 31) * 86400000 + strap) + const cY = calObj.getUTCFullYear() + const cM = calObj.getUTCMonth() + 1 + const cD = calObj.getUTCDate() + + return this.solar2lunar(cY, cM, cD) + }, +} +export function solar2lunar(val: string) { + return VabCalendar.solar2lunar( + val.split('-')[0], + val.split('-')[1], + val.split('-')[2] + ) +} + +export default VabCalendar diff --git a/front-end/src/plugins/VabChart/index.vue b/front-end/src/plugins/VabChart/index.vue new file mode 100644 index 0000000..54fb2d8 --- /dev/null +++ b/front-end/src/plugins/VabChart/index.vue @@ -0,0 +1,287 @@ + + + + + diff --git a/front-end/src/plugins/VabChart/theme/vab-echarts-theme.json b/front-end/src/plugins/VabChart/theme/vab-echarts-theme.json new file mode 100644 index 0000000..199c109 --- /dev/null +++ b/front-end/src/plugins/VabChart/theme/vab-echarts-theme.json @@ -0,0 +1,317 @@ +{ + "color": ["#1890FF", "#36CBCB", "#4ECB73", "#FBD437", "#F2637B", "#975FE5"], + "backgroundColor": "rgba(252,252,252,0)", + "textStyle": {}, + "title": { + "textStyle": { + "color": "#666666" + }, + "subtextStyle": { + "color": "#999999" + } + }, + "line": { + "itemStyle": { + "borderWidth": "2" + }, + "lineStyle": { + "normal": { + "width": "3" + } + }, + "symbolSize": "8", + "symbol": "emptyCircle", + "smooth": false + }, + "radar": { + "itemStyle": { + "borderWidth": "2" + }, + "lineStyle": { + "normal": { + "width": "3" + } + }, + "symbolSize": "8", + "symbol": "emptyCircle", + "smooth": false + }, + "bar": { + "itemStyle": { + "barBorderWidth": 0, + "barBorderColor": "#ccc" + } + }, + "pie": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "scatter": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "boxplot": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "parallel": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "sankey": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "funnel": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "gauge": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + } + }, + "candlestick": { + "itemStyle": { + "color": "#e6a0d2", + "color0": "transparent", + "borderColor": "#e6a0d2", + "borderColor0": "#1890FF", + "borderWidth": "2" + } + }, + "graph": { + "itemStyle": { + "borderWidth": 0, + "borderColor": "#ccc" + }, + "lineStyle": { + "normal": { + "width": "1", + "color": "#cccccc" + } + }, + "symbolSize": "8", + "symbol": "emptyCircle", + "smooth": false, + "color": ["#1890FF", "#36CBCB", "#4ECB73", "#FBD437", "#F2637B", "#975FE5"], + "label": { + "color": "#ffffff" + } + }, + "map": { + "itemStyle": { + "areaColor": "#eeeeee", + "borderColor": "#aaaaaa", + "borderWidth": 0.5 + }, + "label": { + "color": "#ffffff" + } + }, + "geo": { + "itemStyle": { + "areaColor": "#eeeeee", + "borderColor": "#aaaaaa", + "borderWidth": 0.5 + }, + "label": { + "color": "#ffffff" + } + }, + "categoryAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#cccccc" + } + }, + "axisTick": { + "show": false, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#999999" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": ["#eeeeee"] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": ["rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)"] + } + } + }, + "valueAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#cccccc" + } + }, + "axisTick": { + "show": true, + "lineStyle": { + "color": "#cccccc" + } + }, + "axisLabel": { + "show": true, + "color": "#999999" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": ["#eeeeee"] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": ["rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)"] + } + } + }, + "logAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#cccccc" + } + }, + "axisTick": { + "show": false, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#999999" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": ["#eeeeee"] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": ["rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)"] + } + } + }, + "timeAxis": { + "axisLine": { + "show": true, + "lineStyle": { + "color": "#cccccc" + } + }, + "axisTick": { + "show": false, + "lineStyle": { + "color": "#333" + } + }, + "axisLabel": { + "show": true, + "color": "#999999" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": ["#eeeeee"] + } + }, + "splitArea": { + "show": false, + "areaStyle": { + "color": ["rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)"] + } + } + }, + "toolbox": { + "iconStyle": { + "borderColor": "#999999" + } + }, + "legend": { + "textStyle": { + "color": "#999999" + } + }, + "tooltip": { + "axisPointer": { + "lineStyle": { + "color": "#ffffff", + "width": 1 + }, + "crossStyle": { + "color": "#ffffff", + "width": 1 + } + } + }, + "timeline": { + "lineStyle": { + "color": "#4ECB73", + "width": 1 + }, + "itemStyle": { + "color": "#4ECB73", + "borderWidth": 1 + }, + "controlStyle": { + "color": "#4ECB73", + "borderColor": "#4ECB73", + "borderWidth": 0.5 + }, + "checkpointStyle": { + "color": "#1890FF", + "borderColor": "rgba(63,177,227,0.15)" + }, + "label": { + "color": "#4ECB73" + } + }, + "visualMap": { + "color": ["#1890FF", "#afe8ff"] + }, + "dataZoom": { + "backgroundColor": "rgba(255,255,255,0)", + "dataBackgroundColor": "rgba(222,222,222,1)", + "fillerColor": "rgba(114,230,212,0.25)", + "handleColor": "#cccccc", + "handleSize": "100%", + "textStyle": { + "color": "#999999" + } + }, + "markPoint": { + "label": { + "color": "#ffffff" + } + } +} diff --git a/front-end/src/plugins/VabCount/index.vue b/front-end/src/plugins/VabCount/index.vue new file mode 100644 index 0000000..4c717de --- /dev/null +++ b/front-end/src/plugins/VabCount/index.vue @@ -0,0 +1,222 @@ + + + + diff --git a/front-end/src/plugins/VabCount/requestAnimationFrame.js b/front-end/src/plugins/VabCount/requestAnimationFrame.js new file mode 100644 index 0000000..93c3b87 --- /dev/null +++ b/front-end/src/plugins/VabCount/requestAnimationFrame.js @@ -0,0 +1,45 @@ +let lastTime = 0 +const prefixes = 'webkit moz ms o'.split(' ') + +let requestAnimationFrame +let cancelAnimationFrame + +const isServer = typeof window === 'undefined' +if (isServer) { + requestAnimationFrame = function () {} + cancelAnimationFrame = function () {} +} else { + requestAnimationFrame = window.requestAnimationFrame + cancelAnimationFrame = window.cancelAnimationFrame + let prefix + for (const prefix_ of prefixes) { + if (requestAnimationFrame && cancelAnimationFrame) { + break + } + prefix = prefix_ + requestAnimationFrame = + requestAnimationFrame || window[`${prefix}RequestAnimationFrame`] + cancelAnimationFrame = + cancelAnimationFrame || + window[`${prefix}CancelAnimationFrame`] || + window[`${prefix}CancelRequestAnimationFrame`] + } + + if (!requestAnimationFrame || !cancelAnimationFrame) { + requestAnimationFrame = function (callback) { + const currTime = Date.now() + const timeToCall = Math.max(0, 16 - (currTime - lastTime)) + const id = window.setTimeout(() => { + callback(currTime + timeToCall) + }, timeToCall) + lastTime = currTime + timeToCall + return id + } + + cancelAnimationFrame = function (id) { + window.clearTimeout(id) + } + } +} + +export { requestAnimationFrame, cancelAnimationFrame } diff --git a/front-end/src/plugins/VabDialog/index.vue b/front-end/src/plugins/VabDialog/index.vue new file mode 100644 index 0000000..f0d6e77 --- /dev/null +++ b/front-end/src/plugins/VabDialog/index.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/front-end/src/plugins/VabFormTable/index.vue b/front-end/src/plugins/VabFormTable/index.vue new file mode 100644 index 0000000..c1f40ec --- /dev/null +++ b/front-end/src/plugins/VabFormTable/index.vue @@ -0,0 +1,128 @@ + + + diff --git a/front-end/src/plugins/VabIconSelector/index.vue b/front-end/src/plugins/VabIconSelector/index.vue new file mode 100644 index 0000000..ee2402f --- /dev/null +++ b/front-end/src/plugins/VabIconSelector/index.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/front-end/src/plugins/VabPrint.ts b/front-end/src/plugins/VabPrint.ts new file mode 100644 index 0000000..d37b8ef --- /dev/null +++ b/front-end/src/plugins/VabPrint.ts @@ -0,0 +1,148 @@ +const Print: any = function (this: any, dom: any, options: any) { + if (!(this instanceof Print)) return new Print(dom, options) + + this.options = this.extend( + { + noPrint: '.no-print', + }, + options + ) + + if (typeof dom === 'string') { + try { + this.dom = document.querySelector(dom) + } catch { + const createDom = document.createElement('div') + createDom.innerHTML = dom + this.dom = createDom + } + } else { + this.isDOM(dom) + this.dom = this.isDOM(dom) ? dom : dom.$el + } + + this.init() +} +Print.prototype = { + init() { + const content = this.getStyle() + this.getHtml() + this.writeIframe(content) + }, + extend(obj: { [x: string]: any }, obj2: { [x: string]: any }) { + for (const k in obj2) { + obj[k] = obj2[k] + } + return obj + }, + + getStyle() { + let str = '' + const styles = document.querySelectorAll('style,link') + for (const style of styles) { + str += style.outerHTML + } + str += `` + str += '' + return str + }, + + getHtml() { + const inputs = document.querySelectorAll('input') + const textareas = document.querySelectorAll('textarea') + const selects = document.querySelectorAll('select') + + for (const input of inputs) { + if (input.type == 'checkbox' || input.type == 'radio') { + if (input.checked == true) { + input.setAttribute('checked', 'checked') + } else { + input.removeAttribute('checked') + } + } else if (input.type == 'text') { + input.setAttribute('value', input.value) + } else { + input.setAttribute('value', input.value) + } + } + + for (const textarea of textareas) { + if (textarea.type == 'textarea') textarea.innerHTML = textarea.value + } + + for (const select of selects) { + if (select.type == 'select-one') { + const child: any = select.children + for (const i in child) { + if (child[i].tagName == 'OPTION') { + if (child[i].selected == true) + child[i].setAttribute('selected', 'selected') + else child[i].removeAttribute('selected') + } + } + } + } + + return this.dom.outerHTML + }, + + writeIframe(content: string) { + const iframe: any = document.createElement('iframe') + const f: any = document.body.appendChild(iframe) + iframe.id = 'myIframe' + iframe.setAttribute( + 'style', + 'position:absolute;width:0;height:0;top:-10px;left:-10px;' + ) + const w: any = f.contentWindow || f.contentDocument + const doc: any = f.contentDocument || f.contentWindow.document + doc.open() + doc.write(content) + doc.close() + const _this = this + iframe.addEventListener('load', () => { + _this.toPrint(w) + setTimeout(() => { + document.body.removeChild(iframe) + }, 100) + }) + }, + + toPrint(frameWindow: { + focus: () => void + document: { + execCommand: (arg0: string, arg1: boolean, arg2: null) => any + } + print: () => void + close: () => void + }) { + try { + setTimeout(() => { + frameWindow.focus() + try { + if (!frameWindow.document.execCommand('print', false, null)) + frameWindow.print() + } catch { + frameWindow.print() + } + frameWindow.close() + }, 10) + } catch (error) { + console.log('err', error) + } + }, + isDOM: + typeof HTMLElement === 'object' + ? function (obj: any) { + return obj instanceof HTMLElement + } + : function (obj: { nodeType: number; nodeName: any }) { + return ( + obj && + typeof obj === 'object' && + obj.nodeType === 1 && + typeof obj.nodeName === 'string' + ) + }, +} + +export default Print diff --git a/front-end/src/plugins/VabQrCode.ts b/front-end/src/plugins/VabQrCode.ts new file mode 100644 index 0000000..644ca44 --- /dev/null +++ b/front-end/src/plugins/VabQrCode.ts @@ -0,0 +1 @@ +export { default } from 'vue-qr/src/packages/vue-qr.vue' diff --git a/front-end/src/plugins/VabUpdate/index.vue b/front-end/src/plugins/VabUpdate/index.vue new file mode 100644 index 0000000..1d02e97 --- /dev/null +++ b/front-end/src/plugins/VabUpdate/index.vue @@ -0,0 +1,157 @@ + + + + + + + diff --git a/front-end/src/plugins/VabUpload/index.vue b/front-end/src/plugins/VabUpload/index.vue new file mode 100644 index 0000000..e074b7f --- /dev/null +++ b/front-end/src/plugins/VabUpload/index.vue @@ -0,0 +1,270 @@ + + + + + diff --git a/front-end/src/registerServiceWorker.ts b/front-end/src/registerServiceWorker.ts new file mode 100644 index 0000000..7f29975 --- /dev/null +++ b/front-end/src/registerServiceWorker.ts @@ -0,0 +1,56 @@ +/* eslint-disable no-console */ +import { register } from 'register-service-worker' +import { gp } from '@gp' + +if (process.env.NODE_ENV === 'production') { + register(`${process.env.BASE_URL}service-worker.js`, { + ready() { + console.log( + 'App is being served from cache by a service worker.\n' + + 'For more details, visit //goo.gl/AFskqB' + ) + }, + registered() { + console.log('Service worker has been registered.') + }, + cached() { + console.log('Content has been cached for offline use.') + }, + updatefound() { + console.log('New content is downloading.') + // gp.$baseNotify( + // '检测到新版本,正在下载中,请稍后...', + // '温馨提示', + // 'info', + // 'bottom-right', + // 8000 + // ) + }, + updated() { + console.log('New content is available; please refresh.') + gp.$pub('vab-update') + + //如果是演示环境,更新后移除主题,用不到可删除 + if (location.hostname === 'veujs-core.cn') + localStorage.removeItem('theme') + // gp.$baseNotify( + // '更新版本完成,10S后刷新项目', + // '温馨提示', + // 'success', + // 'bottom-right', + // 8000 + // ) + // setTimeout(() => { + // window.location.reload() + // }, 10000) + }, + offline() { + console.log( + 'No internet connection found. App is running in offline mode.' + ) + }, + error(error) { + console.error('Error during service worker registration:', error) + }, + }) +} diff --git a/front-end/src/router/index.ts b/front-end/src/router/index.ts new file mode 100644 index 0000000..a7712e9 --- /dev/null +++ b/front-end/src/router/index.ts @@ -0,0 +1,1112 @@ +/** + * @description router全局配置,如有必要可分文件抽离,其中asyncRoutes只有在intelligence模式下才会用到,pro版只支持remixIcon图标,具体配置请查看vip群文档 + */ +import type { VabRouteRecord } from '/#/router' +import { + createRouter, + createWebHashHistory, + createWebHistory, +} from 'vue-router' +import Layout from '@vab/layouts/index.vue' +import { setupPermissions } from './permissions' + +import type { RouteRecordName, RouteRecordRaw } from 'vue-router' + +import { authentication, isHashRouterMode, publicPath } from '@/config' + +export const constantRoutes: VabRouteRecord[] = [ + { + path: '/login', + name: 'Login', + component: () => import('@/views/login/index.vue'), + meta: { + hidden: true, + }, + }, + { + path: '/register', + name: 'Register', + component: () => import('@/views/register/index.vue'), + meta: { + hidden: true, + }, + }, + { + path: '/callback', + name: 'Callback', + component: () => import('@/views/callback/index.vue'), + meta: { + hidden: true, + }, + }, + { + path: '/direct', + name: 'Direct', + component: () => import('@/views/direct/index.vue'), + meta: { + hidden: true, + }, + }, + { + path: '/404', + name: '404', + component: () => import('@/views/404.vue'), + meta: { + hidden: true, + }, + }, +] + +export const asyncRoutes: VabRouteRecord[] = [ + { + path: '/', + name: 'Root', + component: Layout, + meta: { + title: '首页', + icon: 'home-2-line', + breadcrumbHidden: true, + }, + children: [ + { + path: 'index', + name: 'Index', + component: () => import('@/views/index/index.vue'), + meta: { + title: '首页', + icon: 'home-2-line', + noClosable: true, + }, + }, + { + path: 'dashboard', + name: 'Dashboard', + component: () => import('@/views/index/dashboard.vue'), + meta: { + title: '看板', + icon: 'dashboard-line', + }, + }, + { + path: 'workbench', + name: 'Workbench', + component: () => import('@/views/index/workbench.vue'), + meta: { + title: '工作台', + icon: 'settings-6-line', + dot: true, + }, + }, + { + path: 'store', + name: 'Store', + component: () => import('@/views/index/store.vue'), + meta: { + title: '仓库', + icon: 'app-store-line', + dot: true, + }, + }, + + { + path: 'friendly-tip', + name: 'FriendlyTip', + component: () => import('@/views/index/FriendlyTip.vue'), + meta: { + title: '温馨提示', + icon: 'information-line', + hidden: true, + }, + }, + { + path: 'statistics', + name: 'Statistics', + component: () => import('@/views/index/statistics.vue'), + meta: { + title: '统计分析', + icon: 'bar-chart-box-line', + }, + }, + { + path: 'settings', + name: 'Settings', + component: () => import('@/views/index/settings.vue'), + meta: { + title: '系统设置', + icon: 'settings-line', + guard: ['Admin'], + }, + }, + { + path: 'pricing', + name: 'Pricing', + component: () => import('@/views/index/Pricing.vue'), + meta: { + title: '授权与定价', + guard: ['Admin'], + icon: 'price-tag-3-line', + badge: 'Hot', + }, + }, + ], + }, + { + path: '/vab', + name: 'Vab', + component: Layout, + meta: { + title: '组件', + icon: 'code-box-line', + }, + children: [ + { + path: 'icon', + name: 'Icon', + meta: { + title: '图标', + icon: 'remixicon-line', + }, + children: [ + { + path: 'defaultIcon', + name: 'DefaultIcon', + component: () => + import('@/views/vab/icon/defaultIcon.vue'), + meta: { + title: '默认图标', + }, + }, + { + path: 'iconSelector', + name: 'IconSelector', + component: () => + import('@/views/vab/icon/iconSelector.vue'), + meta: { + title: '图标选择器', + }, + }, + { + path: 'customSvg', + name: 'CustomSvg', + component: () => + import('@/views/vab/icon/customSvg.vue'), + meta: { + title: '自定义图标', + }, + }, + ], + }, + { + path: 'permission', + name: 'Permission', + component: () => import('@/views/vab/permission/index.vue'), + meta: { + title: '角色权限', + icon: 'user-3-line', + badge: 'Pro', + }, + }, + { + path: 'table', + name: 'Table', + meta: { + title: '表格', + // 非editor角色的用户可见 + guard: { + role: ['Editor'], + mode: 'except', + }, + icon: 'table-2', + }, + children: [ + { + path: 'comprehensiveTable', + name: 'ComprehensiveTable', + component: () => + import('@/views/vab/table/comprehensiveTable.vue'), + meta: { + title: '综合表格', + }, + }, + { + path: 'detail', + name: 'Detail', + component: () => import('@/views/vab/table/detail.vue'), + meta: { + hidden: true, + title: '详情页', + activeMenu: '/vab/table/comprehensiveTable', + dynamicNewTab: true, //详情页根据id传参不同可打开多个 + }, + }, + { + path: 'inlineEditTable', + name: 'InlineEditTable', + component: () => + import('@/views/vab/table/inlineEditTable.vue'), + meta: { + title: '行内编辑表格', + noKeepAlive: true, + }, + }, + { + path: 'customTable', + name: 'CustomTable', + component: () => + import('@/views/vab/table/customTable.vue'), + meta: { + title: '自定义表格', + }, + }, + { + path: 'dynamicTable', + name: 'DynamicTable', + component: () => + import('@/views/vab/table/dynamicTable.vue'), + meta: { + title: '动态表格', + badge: 'New', + }, + }, + ], + }, + { + path: 'list', + name: 'List', + component: () => import('@/views/vab/list/index.vue'), + meta: { + title: '列表', + guard: ['Admin'], + icon: 'list-check-2', + }, + }, + { + path: 'description', + name: 'Description', + component: () => import('@/views/vab/description/index.vue'), + meta: { + title: '描述', + guard: ['Admin'], + icon: 'slideshow-line', + }, + }, + { + path: 'calendar', + name: 'Calendar', + component: () => import('@/views/vab/calendar/index.vue'), + meta: { + title: '日历', + guard: ['Admin'], + icon: 'calendar-check-line', + dot: true, + }, + }, + { + path: 'editor', + name: 'Editor', + meta: { + title: '编辑器', + guard: ['Admin'], + icon: 'edit-2-line', + }, + children: [ + { + path: 'wangEditor', + name: 'WangEditor', + component: () => + import('@/views/vab/editor/wangEditor.vue'), + meta: { + title: '腾讯文档', + guard: ['Admin'], + dot: true, + }, + }, + ], + }, + { + path: 'form', + name: 'Form', + meta: { + title: '表单', + guard: ['Admin'], + icon: 'file-list-2-line', + }, + children: [ + { + path: 'comprehensiveForm', + name: 'ComprehensiveForm', + component: () => + import('@/views/vab/form/comprehensiveForm.vue'), + meta: { + title: '综合表单', + }, + }, + { + path: 'stepForm', + name: 'StepForm', + component: () => + import('@/views/vab/form/stepForm.vue'), + meta: { + title: '分步表单', + }, + }, + { + path: 'button', + name: 'Button', + component: () => import('@/views/vab/form/button.vue'), + meta: { + title: '按钮', + }, + }, + { + path: 'link', + name: 'Link', + component: () => import('@/views/vab/form/link.vue'), + meta: { + title: '文字链接', + }, + }, + { + path: 'radio', + name: 'Radio', + component: () => import('@/views/vab/form/radio.vue'), + meta: { + title: '单选框', + }, + }, + { + path: 'checkbox', + name: 'Checkbox', + component: () => + import('@/views/vab/form/checkbox.vue'), + meta: { + title: '多选框', + }, + }, + { + path: 'input', + name: 'Input', + component: () => import('@/views/vab/form/input.vue'), + meta: { + title: '输入框', + }, + }, + { + path: 'inputNumber', + name: 'InputNumber', + component: () => + import('@/views/vab/form/inputNumber.vue'), + meta: { + title: '计数器', + }, + }, + { + path: 'select', + name: 'Select', + component: () => import('@/views/vab/form/select.vue'), + meta: { + title: '选择器', + dot: true, + }, + }, + { + path: 'switch', + name: 'Switch', + component: () => import('@/views/vab/form/switch.vue'), + meta: { + title: '开关', + }, + }, + { + path: 'slider', + name: 'Slider', + component: () => import('@/views/vab/form/slider.vue'), + meta: { + title: '滑块', + }, + }, + { + path: 'timePicker', + name: 'TimePicker', + component: () => + import('@/views/vab/form/timePicker.vue'), + meta: { + title: '时间选择器', + }, + }, + { + path: 'datePicker', + name: 'DatePicker', + component: () => + import('@/views/vab/form/datePicker.vue'), + meta: { + title: '日期选择器', + }, + }, + { + path: 'dateTimePicker', + name: 'DateTimePicker', + component: () => + import('@/views/vab/form/dateTimePicker.vue'), + meta: { + title: '日期时间选择器', + }, + }, + { + path: 'rate', + name: 'Rate', + component: () => import('@/views/vab/form/rate.vue'), + meta: { + title: '评分', + }, + }, + ], + }, + ], + }, + { + path: '/other', + name: 'Other', + component: Layout, + meta: { + title: '其他', + icon: 'archive-line', + guard: ['Admin'], + }, + children: [ + { + path: 'workflow', + name: 'Workflow', + component: () => import('@/views/other/workflow/index.vue'), + meta: { + title: '工作流', + guard: ['Admin'], + icon: 'flow-chart', + }, + }, + { + path: 'echarts', + name: 'Echarts', + component: () => import('@/views/other/echarts/index.vue'), + meta: { + title: '图表', + guard: ['Admin'], + icon: 'bubble-chart-line', + }, + }, + { + path: 'print', + name: 'Print', + component: () => import('@/views/other/print/index.vue'), + meta: { + title: '打印', + guard: ['Admin'], + icon: 'printer-line', + }, + }, + { + path: 'notice', + name: 'Notice', + component: () => import('@/views/other/notice/index.vue'), + meta: { + title: '通知', + guard: ['Admin'], + icon: 'message-2-line', + }, + }, + { + path: 'timeline', + name: 'Timeline', + component: () => import('@/views/other/timeline/index.vue'), + meta: { + title: '时间线', + guard: ['Admin'], + icon: 'time-line', + }, + }, + { + path: 'tabs', + name: 'Tabs', + component: () => import('@/views/other/tabs/index.vue'), + meta: { + title: '多标签', + guard: ['Admin'], + icon: 'bank-card-line', + }, + }, + { + path: 'watermark', + name: 'Watermark', + component: () => import('@/views/other/watermark/index.vue'), + meta: { + title: '水印', + guard: ['Admin'], + icon: 'water-flash-line', + dot: true, + }, + }, + { + path: 'share', + name: 'Share', + component: () => import('@/views/other/share/index.vue'), + meta: { + title: '分享', + guard: ['Admin'], + icon: 'share-line', + dot: true, + }, + }, + { + path: 'dynamicAnchor', + name: 'DynamicAnchor', + component: () => + import('@/views/other/dynamicAnchor/index.vue'), + meta: { + title: '动态锚点', + guard: ['Admin'], + icon: 'anchor-line', + badge: 'New', + }, + }, + { + path: 'dynamicMeta', + name: 'DynamicMeta', + component: () => import('@/views/other/dynamicMeta/index.vue'), + meta: { + title: '动态Meta', + guard: ['Admin'], + icon: 'notification-badge-line', + badge: '0', + }, + }, + { + path: 'dynamicSegment', + name: 'DynamicSegment', + redirect: '/other/dynamicSegment/test1/1', + meta: { + title: '动态路径参数', + guard: ['Admin'], + icon: 'arrow-left-right-line', + }, + children: [ + { + path: 'test1/:id', + name: 'Test1', + component: () => + import('@/views/other/dynamicSegment/test1.vue'), + meta: { + hidden: true, + title: 'Params', + dynamicNewTab: true, + }, + }, + { + path: 'test1/1', + name: 'test1/1', + component: () => + import('@/views/other/dynamicSegment/test1.vue'), + meta: { title: 'Params id=1' }, + }, + { + path: 'test2', + name: 'Test2', + component: () => + import('@/views/other/dynamicSegment/test2.vue'), + meta: { + hidden: true, + title: 'Query', + dynamicNewTab: true, + }, + }, + { + path: 'test2?id=1', + name: 'test2?id=1', + component: () => + import('@/views/other/dynamicSegment/test2.vue'), + meta: { title: 'Query id=1' }, + }, + ], + }, + { + path: 'drag', + name: 'Drag', + meta: { + title: '拖拽', + guard: ['Admin'], + icon: 'drag-drop-line', + }, + children: [ + { + path: 'dialogDrag', + name: 'DialogDrag', + component: () => + import('@/views/other/drag/dialogDrag.vue'), + meta: { + title: '弹窗拖拽', + dot: true, + }, + }, + { + path: 'cardDrag', + name: 'CardDrag', + component: () => + import('@/views/other/drag/cardDrag.vue'), + meta: { + title: '卡片拖拽', + }, + }, + ], + }, + { + path: 'noLayout', + name: 'NoLayout', + component: () => import('@/views/other/noLayout/index.vue'), + meta: { + title: '无框', + guard: ['Admin'], + icon: 'aspect-ratio-line', + dot: true, + }, + }, + { + path: 'upload', + name: 'Upload', + component: () => import('@/views/other/upload/index.vue'), + meta: { + title: '上传', + guard: ['Admin'], + icon: 'chat-upload-line', + }, + }, + { + path: 'menu1', + name: 'Menu1', + meta: { + title: '多级路由缓存', + guard: ['Admin'], + icon: 'route-line', + }, + children: [ + { + path: 'menu1-1', + name: 'Menu11', + meta: { + title: '多级路由1-1', + }, + children: [ + { + path: 'menu1-1-1', + name: 'Menu111', + meta: { + title: '多级路由1-1-1', + }, + children: [ + { + path: 'menu1-1-1-1', + name: 'Menu1111', + meta: { + title: '多级路由1-1-1-1', + }, + component: () => + import( + '@/views/other/nested/menu1/menu1-1/menu1-1-1/menu1-1-1-1/index.vue' + ), + }, + ], + }, + ], + }, + ], + }, + { + path: 'log', + name: 'Log', + component: () => import('@/views/other/errorLog/index.vue'), + meta: { + title: '错误日志模拟', + guard: ['Admin'], + icon: 'error-warning-line', + }, + }, + { + path: 'cssfx', + name: 'Cssfx', + component: () => import('@/views/other/cssfx/index.vue'), + meta: { + title: 'Css动画', + guard: ['Admin'], + icon: 'css3-line', + }, + }, + { + path: 'social', + name: 'Social', + component: () => import('@/views/other/social/index.vue'), + meta: { + title: '第三方登录', + guard: ['Admin'], + icon: 'github-fill', + }, + }, + // { + // path: 'mobilePreview', + // name: 'MobilePreview', + // component: () => import('@/views/vab/mobilePreview.vue'), + // meta: { + // title: '手机预览', + // guard: ['Admin'], + // icon: 'smartphone-line', + // }, + // }, + { + path: '//github.com/zxwk1998/vue-admin-better', + name: 'ExternalLink', + meta: { + title: '外链', + target: '_blank', + // 等价guard: ['Admin', 'Editor'], + guard: { + role: ['Admin', 'Editor'], + mode: 'oneOf', + }, + icon: 'external-link-line', + }, + }, + { + path: 'iframe', + name: 'Iframe', + redirect: '/other/iframe/search', + meta: { + title: 'Iframe', + guard: ['Admin'], + icon: 'window-line', + }, + children: [ + { + path: 'view', + name: 'IframeView', + component: () => + import('@/views/other/iframe/view.vue'), + meta: { + hidden: true, + title: 'Iframe', + icon: 'window-line', + dynamicNewTab: true, + }, + }, + { + path: 'view?url=www.so.com&title=360%E6%90%9C%E7%B4%A2&icon=search-2-line', + name: 'Search360Iframe', + meta: { title: '360搜索', icon: 'search-2-line' }, + }, + { + path: 'view?url=www.bilibili.com&title=%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9&icon=bilibili-line', + name: 'BiliBiliIframe', + meta: { title: '哔哩哔哩', icon: 'bilibili-line' }, + }, + { + path: 'search', + name: 'IframeSearch', + component: () => + import('@/views/other/iframe/search.vue'), + meta: { + title: '自定义Iframe', + icon: 'search-2-line', + }, + }, + ], + }, + { + path: 'excel', + name: 'Excel', + meta: { + title: 'Excel', + guard: ['Admin'], + icon: 'file-excel-2-line', + }, + children: [ + { + path: 'exportExcel', + name: 'ExportExcel', + component: () => + import('@/views/other/excel/exportExcel.vue'), + meta: { + title: '导出Excel', + }, + }, + { + path: 'exportSelectedExcel', + name: 'SelectExcel', + component: () => + import('@/views/other/excel/exportSelectExcel.vue'), + meta: { + title: '导出选中行Excel', + }, + }, + { + path: 'exportMergeHeaderExcel', + name: 'MergeHeaderExcel', + component: () => + import( + '@/views/other/excel/exportMergeHeaderExcel.vue' + ), + meta: { + title: '导出合并Excel', + }, + }, + ], + }, + ], + }, + { + path: '/noColumn', + name: 'NoColumn', + component: Layout, + meta: { + title: '无分栏', + icon: 'delete-column', + guard: ['Admin'], + breadcrumbHidden: true, + }, + children: [ + { + path: 'deleteColumn', + name: 'DeleteColumn', + component: () => + import('@/views/noColumn/deleteColumn/index.vue'), + meta: { + title: '无分栏', + icon: 'delete-column', + noColumn: true, + }, + }, + ], + }, + { + path: '/setting', + name: 'PersonnelManagement', + component: Layout, + meta: { + title: '配置', + icon: 'user-settings-line', + guard: ['Admin'], + }, + children: [ + { + path: 'personalCenter', + name: 'PersonalCenter', + component: () => + import('@/views/setting/personalCenter/index.vue'), + meta: { + title: '个人中心', + icon: 'map-pin-user-line', + }, + }, + { + path: 'userManagement', + name: 'UserManagement', + component: () => + import('@/views/setting/userManagement/index.vue'), + meta: { + title: '用户管理', + icon: 'user-3-line', + }, + }, + { + path: 'roleManagement', + name: 'RoleManagement', + component: () => + import('@/views/setting/roleManagement/index.vue'), + meta: { + title: '角色管理', + icon: 'admin-line', + }, + }, + { + path: 'departmentManagement', + name: 'DepartmentManagement', + component: () => + import('@/views/setting/departmentManagement/index.vue'), + meta: { + title: '部门管理', + icon: 'group-line', + }, + }, + { + path: 'menuManagement', + name: 'MenuManagement', + component: () => + import('@/views/setting/menuManagement/index.vue'), + meta: { + title: '菜单管理', + icon: 'menu-2-fill', + }, + }, + { + path: 'dictionaryManagement', + name: 'DictionaryManagement', + component: () => + import('@/views/setting/dictionaryManagement/index.vue'), + meta: { + title: '字典管理', + icon: 'book-2-line', + dot: true, + }, + }, + { + path: 'taskManagement', + name: 'TaskManagement', + component: () => + import('@/views/setting/taskManagement/index.vue'), + meta: { + title: '任务管理', + icon: 'task-line', + badge: 'New', + }, + }, + { + path: 'systemLog', + name: 'SystemLog', + component: () => import('@/views/setting/systemLog/index.vue'), + meta: { + title: '系统日志', + icon: 'file-shield-2-line', + }, + }, + ], + }, + { + path: '/tools', + name: 'Tools', + component: Layout, + meta: { + title: '工具', + icon: 'tools-line', + guard: ['Admin'], + }, + children: [ + { + path: 'eyeDropper', + name: 'EyeDropper', + component: () => import('@/views/tools/EyeDropper.vue'), + meta: { + title: '取色器', + icon: 'contrast-drop-line', + }, + }, + { + path: 'speechSynthesis', + name: 'SpeechSynthesis', + component: () => import('@/views/tools/SpeechSynthesis.vue'), + meta: { + title: '语音合成', + icon: 'customer-service-line', + }, + }, + ], + }, + { + path: '//github.com/zxwk1998/vue-admin-better', + name: 'Github', + component: Layout, + meta: { + title: '外链', + icon: 'external-link-line', + guard: ['Admin'], + target: '_blank', + breadcrumbHidden: true, + }, + }, + + { + path: '//vuejs-core.cn/store', + name: 'Store', + component: Layout, + meta: { + title: '模板库', + icon: 'apps-line', + target: '_blank', + breadcrumbHidden: true, + }, + }, + { + path: '/error', + name: 'Error', + component: Layout, + meta: { + title: '错误页', + icon: 'error-warning-line', + levelHidden: true, + }, + children: [ + { + path: '403', + name: 'Error403', + component: () => import('@/views/403.vue'), + meta: { + title: '403', + icon: 'error-warning-line', + }, + }, + { + path: '404', + name: 'Error404', + component: () => import('@/views/404.vue'), + meta: { + title: '404', + icon: 'error-warning-line', + }, + }, + ], + }, + + { + path: '/:pathMatch(.*)*', + redirect: '/404', + name: 'NotFound', + meta: { + hidden: true, + }, + }, +] + +const router = createRouter({ + history: isHashRouterMode + ? createWebHashHistory(publicPath) + : createWebHistory(publicPath), + routes: constantRoutes as RouteRecordRaw[], +}) + +function fatteningRoutes(routes: VabRouteRecord[]): VabRouteRecord[] { + return routes.flatMap((route: VabRouteRecord) => { + return route.children ? fatteningRoutes(route.children) : route + }) +} + +function addRouter(routes: VabRouteRecord[]) { + routes.forEach((route: VabRouteRecord) => { + if (!router.hasRoute(route.name)) + router.addRoute(route as RouteRecordRaw) + if (route.children) addRouter(route.children) + }) +} + +export function resetRouter(routes: VabRouteRecord[] = constantRoutes) { + routes.map((route: VabRouteRecord) => { + if (route.children) route.children = fatteningRoutes(route.children) + }) + router.getRoutes().forEach(({ name }) => { + router.hasRoute(name) && + router.removeRoute(name) + }) + addRouter(routes) +} + +export function setupRouter(app: any) { + if (authentication === 'intelligence') addRouter(asyncRoutes) + setupPermissions(router) + app.use(router) + return router +} + +export default router diff --git a/front-end/src/router/permissions.ts b/front-end/src/router/permissions.ts new file mode 100644 index 0000000..72949e2 --- /dev/null +++ b/front-end/src/router/permissions.ts @@ -0,0 +1,75 @@ +/** + * @description 路由守卫,目前两种模式:all模式与intelligence模式 + */ +import VabProgress from 'nprogress' +import { useUserStore } from '@/store/modules/user' +import { useRoutesStore } from '@/store/modules/routes' +import { useSettingsStore } from '@/store/modules/settings' +import 'nprogress/nprogress.css' +import getPageTitle from '@/utils/pageTitle' +import { toLoginRoute } from '@/utils/routes' +import { + authentication, + loginInterception, + routesWhiteList, + supportVisit, +} from '@/config' + +import type { Router } from 'vue-router' + +export function setupPermissions(router: Router) { + VabProgress.configure({ + easing: 'ease', + speed: 500, + trickleSpeed: 200, + showSpinner: false, + }) + router.beforeEach(async (to, from, next) => { + const { + getTheme: { showProgressBar }, + } = useSettingsStore() + const { routes, setRoutes } = useRoutesStore() + const { token, getUserInfo, setVirtualRoles, resetAll } = useUserStore() + + if (showProgressBar) VabProgress.start() + + let hasToken = token + + if (!loginInterception) hasToken = true + + if (hasToken) { + if (routes.length > 0) { + // 禁止已登录用户返回登录页 + if (to.path === '/login') { + next({ path: '/' }) + if (showProgressBar) VabProgress.done() + } else next() + } else { + try { + if (loginInterception) await getUserInfo() + // config/setting.config.js loginInterception为false(关闭登录拦截时)时,创建虚拟角色 + else await setVirtualRoles() + // 根据路由模式获取路由并根据权限过滤 + await setRoutes(authentication) + next({ ...to, replace: true }) + } catch (error) { + console.error('vue-admin-better错误拦截:', error) + await resetAll() + next(toLoginRoute(to.path)) + } + } + } else { + if (routesWhiteList.includes(to.path)) { + // 设置游客路由(不需要可以删除) + if (supportVisit && routes.length === 0) { + await setRoutes('visit') + next({ path: to.path, replace: true }) + } else next() + } else next(toLoginRoute(to.path)) + } + }) + router.afterEach((to: any) => { + document.title = getPageTitle(to.meta.title) + if (VabProgress.status) VabProgress.done() + }) +} diff --git a/front-end/src/store/index.ts b/front-end/src/store/index.ts new file mode 100644 index 0000000..fb337fb --- /dev/null +++ b/front-end/src/store/index.ts @@ -0,0 +1,11 @@ +/** + * @description 导入所有 pinia 模块,请勿修改。 + */ + +const pinia = createPinia() + +export function setupStore(app: any) { + app.use(pinia) +} + +export default pinia diff --git a/front-end/src/store/modules/acl.ts b/front-end/src/store/modules/acl.ts new file mode 100644 index 0000000..74db737 --- /dev/null +++ b/front-end/src/store/modules/acl.ts @@ -0,0 +1,25 @@ +import type { AclModuleType } from '/#/store' + +export const useAclStore = defineStore('acl', { + state: (): AclModuleType => ({ + admin: false, + role: [], + permission: [], + }), + getters: { + getAdmin: (state) => state.admin, + getRole: (state) => state.role, + getPermission: (state) => state.permission, + }, + actions: { + setFull(admin: boolean) { + this.admin = admin + }, + setRole(role: string[]) { + this.role = role + }, + setPermission(permission: string[]) { + this.permission = permission + }, + }, +}) diff --git a/front-end/src/store/modules/errorLog.ts b/front-end/src/store/modules/errorLog.ts new file mode 100644 index 0000000..cbaefd3 --- /dev/null +++ b/front-end/src/store/modules/errorLog.ts @@ -0,0 +1,21 @@ +/** + * @description 异常捕获的状态拦截,请勿修改 + */ +import type { ErrorLogModuleType } from '/#/store' + +export const useErrorLogStore = defineStore('errorLog', { + state: (): ErrorLogModuleType => ({ + errorLogs: [], + }), + getters: { + getErrorLogs: (state) => state.errorLogs, + }, + actions: { + addErrorLog(errorLog: any) { + this.errorLogs.push(errorLog) + }, + clearErrorLog() { + this.errorLogs.splice(0) + }, + }, +}) diff --git a/front-end/src/store/modules/routes.ts b/front-end/src/store/modules/routes.ts new file mode 100644 index 0000000..1e75ed1 --- /dev/null +++ b/front-end/src/store/modules/routes.ts @@ -0,0 +1,113 @@ +/** + * @description 路由拦截状态管理,目前两种模式:all模式与intelligence模式,其中partialRoutes是菜单暂未使用 + */ +import { gp } from '@gp' +import { asyncRoutes, constantRoutes, resetRouter } from '@/router' +import { convertRouter, filterRoutes } from '@/utils/routes' +import { authentication, rolesControl } from '@/config' +import type { OptionType, RoutesModuleType } from '/#/store' +import { isArray } from '@/utils/validate' +import { getList } from '@/api/router' +import type { VabRouteRecord } from '/#/router' + +export const useRoutesStore = defineStore('routes', { + state: (): RoutesModuleType => ({ + /** + * 一级菜单值 + */ + tab: { + data: undefined, + }, + /** + * 一级菜单 + */ + tabMenu: undefined, + /** + * 自定义激活菜单 + */ + activeMenu: { + data: undefined, + }, + /** + * 一级菜单 + */ + routes: [], + }), + getters: { + getTab: (state) => state.tab, + getTabMenu: (state) => + state.tab.data + ? state.routes.find((route) => route.name === state.tab.data) + : { meta: { title: '' }, redirect: '404' }, + getActiveMenu: (state) => state.activeMenu, + getRoutes: (state) => + state.routes.filter((_route) => _route.meta.hidden !== true), + getPartialRoutes: (state) => + state.routes.find((route) => route.name === state.tab.data) + ?.children || [], + }, + actions: { + clearRoutes() { + this.routes = [] + }, + /** + * @description 多模式设置路由 + * @param mode + * @returns + */ + async setRoutes(mode = 'none') { + // 默认前端路由 + let routes = [...asyncRoutes] + // 设置游客路由关闭路由拦截(不需要可以删除) + const control = mode === 'visit' ? false : rolesControl + // 设置后端路由(不需要可以删除) + if (authentication === 'all') { + const { + data: { list }, + } = await getList() + if (!isArray(list)) + gp.$baseMessage( + '路由格式返回有误!', + 'error', + 'vab-hey-message-error' + ) + if (list[list.length - 1].path !== '*') + list.push({ + path: '/:pathMatch(.*)*', + redirect: '/404', + name: 'NotFound', + meta: { hidden: true }, + }) + routes = convertRouter(list) + } + // 根据权限和rolesControl过滤路由 + const accessRoutes = filterRoutes( + [...constantRoutes, ...routes], + control + ) + // 设置菜单所需路由 + this.routes = JSON.parse(JSON.stringify(accessRoutes)) + // 根据可访问路由重置Vue Router + await resetRouter(accessRoutes) + }, + changeMenuMeta(options: OptionType) { + function handleRoutes(routes: VabRouteRecord[]) { + return routes.map((route) => { + if (route.name === options.name) + Object.assign(route.meta, options.meta) + if (route.children && route.children.length > 0) + route.children = handleRoutes(route.children) + return route + }) + } + this.routes = handleRoutes(this.routes) + }, + /** + * @description 修改 activeName + * @param activeMenu 当前激活菜单 + */ + changeActiveMenu(activeMenu: string) { + this.activeMenu.data = activeMenu + }, + }, +}) diff --git a/front-end/src/store/modules/settings.ts b/front-end/src/store/modules/settings.ts new file mode 100644 index 0000000..288b570 --- /dev/null +++ b/front-end/src/store/modules/settings.ts @@ -0,0 +1,182 @@ +/** + * @description 所有全局配置的状态管理,如无必要请勿修改 + */ +import type { SettingsModuleType } from '/#/store' +import { isJson } from '@/utils/validate' +import { + logo as _logo, + title as _title, + background, + columnStyle, + fixedHeader, + foldSidebar, + i18n, + layout, + menuWidth, + showFullScreen, + showLanguage, + showLock, + showNotice, + showPageTransition, + showProgressBar, + showRefresh, + showSearch, + showTabs, + showTabsIcon, + showTheme, + showThemeSetting, + tabsBarStyle, + themeName, +} from '@/config' + +const defaultTheme: ThemeType = { + layout, + themeName, + background, + columnStyle, + fixedHeader, + foldSidebar, + menuWidth, + showProgressBar, + showTabs, + showTabsIcon, + showLanguage, + showRefresh, + showSearch, + showTheme, + showNotice, + showFullScreen, + showThemeSetting, + showPageTransition, + showLock, + tabsBarStyle, +} + +const getLocalStorage = (key: string) => { + const value: string | null = localStorage.getItem(key) + return value && isJson(value) ? JSON.parse(value) : false +} + +const theme = getLocalStorage('theme') || { ...defaultTheme } +const { collapse = foldSidebar } = getLocalStorage('collapse') +const { language = i18n } = getLocalStorage('language') +const { lock = false } = getLocalStorage('lock') +const { logo = _logo } = getLocalStorage('logo') +const { title = _title } = getLocalStorage('title') + +export const useSettingsStore = defineStore('settings', { + state: (): SettingsModuleType => ({ + theme, + device: 'desktop', + collapse, + language, + lock, + logo, + title, + echartsGraphic1: ['#3ED572', '#399efd'], + echartsGraphic2: ['#399efd', '#8cc8ff'], + }), + getters: { + getTheme: (state) => state.theme, + getDevice: (state) => state.device, + getCollapse: (state) => state.collapse, + getLanguage: (state) => state.language, + getLock: (state) => state.lock, + getLogo: (state) => state.logo, + getTitle: (state) => state.title, + }, + actions: { + updateState(obj: any) { + Object.getOwnPropertyNames(obj).forEach((key) => { + // eslint-disable-next-line + // @ts-ignore + this[key] = obj[key] + localStorage.setItem( + key, + typeof obj[key] == 'string' + ? `{"${key}":"${obj[key]}"}` + : `{"${key}":${obj[key]}}` + ) + }) + }, + saveTheme() { + localStorage.setItem('theme', JSON.stringify(this.theme)) + }, + resetTheme() { + this.theme = { ...defaultTheme } + localStorage.removeItem('theme') + this.updateTheme() + }, + updateTheme() { + const index = this.theme.themeName.indexOf('-') + const themeName = + this.theme.themeName.slice(0, Math.max(0, index)) || 'blue' + + let variables = require( + `@vab/styles/variables/vab-${themeName}-variables.module.scss` + ) + if (variables.default) variables = variables.default + + Object.keys(variables).forEach((key) => { + if (key.startsWith('vab-')) { + useCssVar(key.replace('vab-', '--el-'), ref(null)).value = + variables[key] + } + }) + + this.echartsGraphic1 = [ + variables['vab-color-transition'], + variables['vab-color-primary'], + ] + + this.echartsGraphic2 = [ + variables['vab-color-primary-light-5'], + variables['vab-color-primary'], + ] + + const menuBackground = + this.theme.themeName.split('-')[1] || this.theme.themeName + document.querySelectorAll('body')[0].className = + `vab-theme-${menuBackground}` + + if (this.theme.background !== 'none') + document + .querySelectorAll('body')[0] + .classList.add(this.theme.background) + + const el = ref(null) + if (this.theme.menuWidth && this.theme.menuWidth.endsWith('px')) + useCssVar('--el-left-menu-width', el).value = + this.theme.menuWidth + else useCssVar('--el-left-menu-width', el).value = '266px' + }, + toggleCollapse() { + this.collapse = !this.collapse + localStorage.setItem('collapse', `{"collapse":${this.collapse}}`) + }, + toggleDevice(device: string) { + this.updateState({ device }) + }, + openSideBar() { + this.updateState({ collapse: false }) + }, + foldSideBar() { + this.updateState({ collapse: true }) + }, + changeLanguage(language: string) { + this.updateState({ language }) + }, + handleLock() { + this.updateState({ lock: true }) + }, + handleUnLock() { + this.updateState({ lock: false }) + }, + changeLogo(logo: string) { + this.updateState({ logo }) + }, + changeTitle(title: string) { + this.updateState({ title }) + }, + }, +}) diff --git a/front-end/src/store/modules/tabs.ts b/front-end/src/store/modules/tabs.ts new file mode 100644 index 0000000..33faf63 --- /dev/null +++ b/front-end/src/store/modules/tabs.ts @@ -0,0 +1,123 @@ +/** + * @description tabsBar标签页逻辑,如无必要请勿修改 + */ +import type { OptionType, TabsModuleType } from '/#/store' +import type { VabRouteRecord } from '/#/router' + +export const useTabsStore = defineStore('tabs', { + state: (): TabsModuleType => ({ + visitedRoutes: [], + }), + getters: { + getVisitedRoutes: (state) => + state.visitedRoutes.filter( + (route: VabRouteRecord) => route.name !== 'Login' + ), + }, + actions: { + /** + * @description 添加标签页 + * @param {*} route + * @returns + */ + addVisitedRoute(route: VabRouteRecord) { + const target = this.visitedRoutes.find( + (item: VabRouteRecord) => item.path === route.path + ) + if (target && !route.meta.dynamicNewTab) { + // 保留之前修改过的meta信息,只更新原始的meta信息 + const modifiedMeta = { ...target.meta } + Object.assign(target, route) + // 合并保留的meta信息和新的meta信息 + target.meta = { ...target.meta, ...modifiedMeta } + } else if (!target) + this.visitedRoutes.push(Object.assign({}, route)) + + //应对极特殊情况:没有配置noClosable的情况,默认使当前tab不可关闭 + if ( + !this.visitedRoutes.find( + (route: VabRouteRecord) => route.meta.noClosable + ) + ) + this.visitedRoutes[0].meta.noClosable = true + }, + /** + * @description 删除当前标签页 + * @param {*} path + * @returns + */ + delVisitedRoute(path: string) { + this.visitedRoutes = this.visitedRoutes.filter( + (route: VabRouteRecord) => route.path !== path + ) + }, + /** + * @description 删除当前标签页以外其它全部标签页 + * @param {*} path + * @returns + */ + delOthersVisitedRoutes(path: string) { + this.visitedRoutes = this.visitedRoutes.filter( + (route: VabRouteRecord) => + route.meta.noClosable || route.path === path + ) + }, + /** + * @description 删除当前标签页左边全部标签页 + * @param {*} path + * @returns + */ + delLeftVisitedRoutes(path: string) { + let found = false + this.visitedRoutes = this.visitedRoutes.filter( + (route: VabRouteRecord) => { + if (route.path === path) found = true + return route.meta.noClosable || found + } + ) + }, + /** + * @description 删除当前标签页右边全部标签页 + * @param {*} path + * @returns + */ + delRightVisitedRoutes(path: string) { + let found = false + this.visitedRoutes = this.visitedRoutes.filter( + (route: VabRouteRecord) => { + const close = found + if (route.path === path) found = true + return route.meta.noClosable || !close + } + ) + }, + /** + * @description 删除全部标签页 + * @returns + */ + delAllVisitedRoutes() { + this.visitedRoutes = this.visitedRoutes.filter( + (route: VabRouteRecord) => route.meta.noClosable + ) + }, + /** + * @description 修改 meta + * @param options + */ + changeTabsMeta(options: OptionType) { + function handleVisitedRoutes(visitedRoutes: VabRouteRecord[]) { + return visitedRoutes.map((route: VabRouteRecord) => { + if ( + route.name === options.name || + route.meta.title === options.title + ) + Object.assign(route.meta, options.meta) + if (route.children && route.children.length > 0) + route.children = handleVisitedRoutes(route.children) + return route + }) + } + this.visitedRoutes = handleVisitedRoutes(this.visitedRoutes) + }, + }, +}) diff --git a/front-end/src/store/modules/user.ts b/front-end/src/store/modules/user.ts new file mode 100644 index 0000000..cc89b9d --- /dev/null +++ b/front-end/src/store/modules/user.ts @@ -0,0 +1,179 @@ +/** + * @description 登录、获取用户信息、退出登录、清除token逻辑,不建议修改 + */ +import { useAclStore } from './acl' +import { useTabsStore } from './tabs' +import { useRoutesStore } from './routes' +import { useSettingsStore } from './settings' +import type { UserModuleType } from '/#/store' +import { gp } from '@gp' +import { getUserInfo, login, logout, socialLogin } from '@/api/user' +import { getToken, removeToken, setToken } from '@/utils/token' +import { resetRouter } from '@/router' +import { isArray, isString } from '@/utils/validate' +import { tokenName } from '@/config' + +export const useUserStore = defineStore('user', { + state: (): UserModuleType => ({ + token: getToken() as string, + username: '游客', + avatar: 'https://i.gtimg.cn/club/item/face/img/2/15922_100.gif', + }), + getters: { + getToken: (state) => state.token, + getUsername: (state) => state.username, + getAvatar: (state) => state.avatar, + }, + actions: { + /** + * @description 设置token + * @param {*} token + */ + setToken(token: string) { + this.token = token + setToken(token) + }, + /** + * @description 设置用户名 + * @param {*} username + */ + setUsername(username: string) { + this.username = username + }, + /** + * @description 设置头像 + * @param {*} avatar + */ + setAvatar(avatar: string) { + this.avatar = avatar + }, + /** + * @description 登录拦截放行时,设置虚拟角色 + */ + setVirtualRoles() { + const aclStore = useAclStore() + aclStore.setFull(true) + this.setUsername('admin(未开启登录拦截)') + this.setAvatar( + 'https://i.gtimg.cn/club/item/face/img/2/15922_100.gif' + ) + }, + /** + * @description 设置token并发送提醒 + * @param {string} token 更新令牌 + * @param {string} tokenName 令牌名称 + */ + afterLogin(token: string, tokenName: string) { + const settingsStore = useSettingsStore() + if (token) { + this.setToken(token) + const hour = new Date().getHours() + const thisTime = + hour < 8 + ? '早上好' + : hour <= 11 + ? '上午好' + : hour <= 13 + ? '中午好' + : hour < 18 + ? '下午好' + : '晚上好' + gp.$baseNotify( + `欢迎登录${settingsStore.title}`, + `${thisTime}!` + ) + } else { + const err = `登录接口异常,未正确返回${tokenName}...` + gp.$baseMessage(err, 'error', 'vab-hey-message-error') + throw err + } + }, + /** + * @description 登录 + * @param {*} userInfo + */ + async login(userInfo: any) { + const { + data: { [tokenName]: token }, + } = await login(userInfo) + this.afterLogin(token, tokenName) + }, + /** + * @description 第三方登录 + * @param {*} tokenData + */ + async socialLogin(tokenData: any) { + const { + data: { [tokenName]: token }, + } = await socialLogin(tokenData) + this.afterLogin(token, tokenName) + }, + /** + * @description 获取用户信息接口 这个接口非常非常重要,如果没有明确底层前逻辑禁止修改此方法,错误的修改可能造成整个框架无法正常使用 + * @returns + */ + async getUserInfo() { + const { + data: { username, avatar, roles, permissions }, + } = await getUserInfo() + /** + * 检验返回数据是否正常,无对应参数,将使用默认用户名,头像,Roles和Permissions + * username {String} + * avatar {String} + * roles {List} + * ability {List} + */ + if ( + (username && !isString(username)) || + (avatar && !isString(avatar)) || + (roles && !isArray(roles)) || + (permissions && !isArray(permissions)) + ) { + const err = + 'getUserInfo核心接口异常,请检查返回JSON格式是否正确' + gp.$baseMessage(err, 'error', 'vab-hey-message-error') + throw err + } else { + const aclStore = useAclStore() + // 如不使用username用户名,可删除以下代码 + if (username) this.setUsername(username) + // 如不使用avatar头像,可删除以下代码 + if (avatar) this.setAvatar(avatar) + // 如不使用roles权限控制,可删除以下代码 + if (roles) aclStore.setRole(roles) + // 如不使用permissions权限控制,可删除以下代码 + if (permissions) aclStore.setPermission(permissions) + } + }, + /** + * @description 退出登录 + */ + async logout() { + await logout() + await this.resetAll() + // 解决横向布局退出登录显示不全的bug + location.reload() + }, + /** + * @description 重置token、roles、permission、router、tabsBar等 + */ + async resetAll() { + this.setToken('') + this.setUsername('游客') + this.setAvatar( + 'https://i.gtimg.cn/club/item/face/img/2/15922_100.gif' + ) + + const aclStore = useAclStore() + const routesStore = useRoutesStore() + const tabsStore = useTabsStore() + aclStore.setPermission([]) + aclStore.setFull(false) + aclStore.setRole([]) + tabsStore.delAllVisitedRoutes() + routesStore.clearRoutes() + await resetRouter() + removeToken() + }, + }, +}) diff --git a/front-end/src/utils/clipboard.ts b/front-end/src/utils/clipboard.ts new file mode 100644 index 0000000..17cd2b5 --- /dev/null +++ b/front-end/src/utils/clipboard.ts @@ -0,0 +1,38 @@ +import { gp } from '@gp' + +function clipboardSuccess(text: any) { + gp.$baseMessage( + `拷贝${text}成功`, + 'success', + 'vab-hey-message-success', + false + ) +} + +function clipboardError(text: any) { + gp.$baseMessage( + `拷贝${text}失败`, + 'error', + 'vab-hey-message-success', + false + ) +} + +/** + * @description 复制数据 + * @param text + */ +export default function handleClipboard(text: string) { + const { isSupported, copy } = useClipboard() + if (!isSupported) { + usePermission('clipboard-write') + } + copy(text) + .then(() => { + clipboardSuccess(text) + }) + .catch((error) => { + console.log(error) + clipboardError(text) + }) +} diff --git a/front-end/src/utils/encrypt.ts b/front-end/src/utils/encrypt.ts new file mode 100644 index 0000000..dda0a43 --- /dev/null +++ b/front-end/src/utils/encrypt.ts @@ -0,0 +1,83 @@ +import JSEncrypt from 'jsencrypt' +import { getPublicKey } from '@/api/publicKey' + +const privateKey = + 'MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMFPa+v52FkSUXvcUnrGI/XzW3EpZRI0s9BCWJ3oNQmEYA5luWW5p8h0uadTIoTyYweFPdH4hveyxlwmS7oefvbIdiP+o+QIYW/R4Wjsb4Yl8MhR4PJqUE3RCy6IT9fM8ckG4kN9ECs6Ja8fQFc6/mSl5dJczzJO3k1rWMBhKJD/AgMBAAECgYEAucMakH9dWeryhrYoRHcXo4giPVJsH9ypVt4KzmOQY/7jV7KFQK3x//27UoHfUCak51sxFw9ek7UmTPM4HjikA9LkYeE7S381b4QRvFuf3L6IbMP3ywJnJ8pPr2l5SqQ00W+oKv+w/VmEsyUHr+k4Z+4ik+FheTkVWp566WbqFsECQQDjYaMcaKw3j2Zecl8T6eUe7fdaRMIzp/gcpPMfT/9rDzIQk+7ORvm1NI9AUmFv/FAlfpuAMrdL2n7p9uznWb7RAkEA2aP934kbXg5bdV0R313MrL+7WTK/qdcYxATUbMsMuWWQBoS5irrt80WCZbG48hpocJavLNjbtrjmUX3CuJBmzwJAOJg8uP10n/+ZQzjEYXh+BszEHDuw+pp8LuT/fnOy5zrJA0dO0RjpXijO3vuiNPVgHXT9z1LQPJkNrb5ACPVVgQJBALPeb4uV0bNrJDUb5RB4ghZnIxv18CcaqNIft7vuGCcFBAIPIRTBprR+RuVq+xHDt3sNXdsvom4h49+Hky1b0ksCQBBwUtVaqH6ztCtwUF1j2c/Zcrt5P/uN7IHAd44K0gIJc1+Csr3qPG+G2yoqRM8KVqLI8Z2ZYn9c+AvEE+L9OQY=' + +/** + * 最长加密长度 + * @type {number} + */ +const MAX_ENCRYPT_BLOCK = 117 +/** + * 最长解码长度 + * @type {number} + */ +const MAX_DECRYPT_BLOCK = 128 + +/** + * @description RSA加密(支持长字符加密) + * @param data + * @returns {Promise<{param: PromiseLike}|*>} + */ +export async function encryptedData(data: any) { + let publicKey + const res = await getPublicKey() + publicKey = res.data.publicKey + if (res.data.mockServer) { + publicKey = '' + } + if (publicKey === '') { + return data + } + const encrypt = new JSEncrypt() + encrypt.setPublicKey( + `-----BEGIN PUBLIC KEY-----${publicKey}-----END PUBLIC KEY-----` + ) + let bufTmp: any = '' + let hexTmp: any = '' + let result: any = '' + const buffer = Buffer.from(JSON.stringify(data)) + let offSet = 0 + const inputLen = buffer.length + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { + bufTmp = buffer.slice(offSet, offSet + MAX_ENCRYPT_BLOCK) + } else { + bufTmp = buffer.slice(offSet, inputLen) + } + hexTmp = encrypt.encrypt(bufTmp.toString()) + result += atob(hexTmp) + offSet += MAX_ENCRYPT_BLOCK + } + return btoa(result) +} + +/** + * @description RSA解密(支持长字符解密) + * @param data + * @returns {PromiseLike} + */ +export function decryptedData(data: string) { + const decrypt = new JSEncrypt() + decrypt.setPrivateKey( + `-----BEGIN RSA PRIVATE KEY-----${privateKey}-----END RSA PRIVATE KEY-----` + ) + let bufTmp: any = '' + let hexTmp: any = '' + let result: any = '' + const buffer = atob(data) + let offSet = 0 + const inputLen = buffer.length + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_DECRYPT_BLOCK) { + bufTmp = buffer.slice(offSet, offSet + MAX_DECRYPT_BLOCK) + } else { + bufTmp = buffer.slice(offSet, inputLen) + } + hexTmp = decrypt.decrypt(btoa(bufTmp)) + result += hexTmp + offSet += MAX_DECRYPT_BLOCK + } + return JSON.parse(result) +} diff --git a/front-end/src/utils/excel.ts b/front-end/src/utils/excel.ts new file mode 100644 index 0000000..f423bfc --- /dev/null +++ b/front-end/src/utils/excel.ts @@ -0,0 +1,229 @@ +import { saveAs } from 'file-saver' +import { SSF, utils, write } from 'xlsx' + +import type { BookType } from 'xlsx' + +function generateArray(table: any) { + const out: any[] = [] + const rows = table.querySelectorAll('tr') + const ranges: any[] = [] + for (const [R, row] of rows.entries()) { + const outRow: any[] = [] + const columns = row.querySelectorAll('td') + for (const cell of columns) { + let colspan = cell.getAttribute('colspan') + let rowspan = cell.getAttribute('rowspan') + let cellValue = cell.innerText + if (cellValue !== '' && cellValue === +cellValue) + cellValue = +cellValue + ranges.forEach((range) => { + if ( + R >= range.s.r && + R <= range.e.r && + outRow.length >= range.s.c && + outRow.length <= range.e.c + ) { + for (let i = 0; i <= range.e.c - range.s.c; ++i) + outRow.push(null) + } + }) + if (rowspan || colspan) { + rowspan = rowspan || 1 + colspan = colspan || 1 + ranges.push({ + s: { + r: R, + c: outRow.length, + }, + e: { + r: R + rowspan - 1, + c: outRow.length + colspan - 1, + }, + }) + } + + outRow.push(cellValue !== '' ? cellValue : null) + + if (colspan) for (let k = 0; k < colspan - 1; ++k) outRow.push(null) + } + out.push(outRow) + } + return [out, ranges] +} + +function datenum(v: any, date1904 = null) { + if (date1904) { + v += 1462 + } + const epoch = Date.parse(v) + return ( + (epoch - (new Date(Date.UTC(1899, 11, 30)) as any)) / + (24 * 60 * 60 * 1000) + ) +} + +function sheet_from_array_of_arrays(data: any) { + const ws: any = {} + const range = { + s: { + c: 10000000, + r: 10000000, + }, + e: { + c: 0, + r: 0, + }, + } + for (let R = 0; R !== data.length; ++R) { + for (let C = 0; C !== data[R].length; ++C) { + if (range.s.r > R) range.s.r = R + if (range.s.c > C) range.s.c = C + if (range.e.r < R) range.e.r = R + if (range.e.c < C) range.e.c = C + const cell: any = { + v: data[R][C], + } + if (cell.v === null) continue + const cellRef = utils.encode_cell({ + c: C, + r: R, + }) + + if (typeof cell.v === 'number') cell.t = 'n' + else if (typeof cell.v === 'boolean') cell.t = 'b' + else if (cell.v instanceof Date) { + cell.t = 'n' + cell.z = (SSF as any)._table[14] + cell.v = datenum(cell.v) + } else cell.t = 's' + + ws[cellRef] = cell + } + } + if (range.s.c < 10000000) ws['!ref'] = utils.encode_range(range) + return ws +} + +class Workbook { + public SheetNames: any[] = [] + public Sheets: any = {} +} + +function s2ab(s: any) { + const buf = new ArrayBuffer(s.length) + const view = new Uint8Array(buf) + for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xff + return buf +} + +export function export_table_to_excel(id: any) { + const theTable = document.getElementById(id) + const oo = generateArray(theTable) + const ranges = oo[1] + + const data = oo[0] + const wsName = 'SheetJS' + + const wb = new Workbook() + const ws = sheet_from_array_of_arrays(data) + + ws['!merges'] = ranges + + wb.SheetNames.push(wsName) + wb.Sheets[wsName] = ws + + const wbout = write(wb, { + bookType: 'xlsx', + bookSST: false, + type: 'binary', + }) + + saveAs( + new Blob([s2ab(wbout)], { + type: 'application/octet-stream', + }), + 'test.xlsx' + ) +} + +export function export_json_to_excel( + { + multiHeader = [], + header, + data, + filename, + merges = [], + autoWidth = true, + bookType = 'xlsx', + } = { + header: {}, + data: [] as any[], + filename: '', + } +) { + /* original data */ + filename = filename || 'excel-list' + data = [...data] + data.unshift(header) + + for (let i = multiHeader.length - 1; i > -1; i--) { + data.unshift(multiHeader[i]) + } + + const wsName = 'SheetJS' + const wb = new Workbook() + const ws = sheet_from_array_of_arrays(data) + + if (merges.length > 0) { + if (!ws['!merges']) { + ws['!merges'] = [] + } + merges.forEach((item) => { + ws['!merges'].push(utils.decode_range(item)) + }) + } + + if (autoWidth) { + const colWidth = data.map((row) => + row.map((val: any) => { + if (val === null) { + return { + wch: 10, + } + } else if (val.toString().charCodeAt(0) > 255) { + return { + wch: val.toString().length * 2, + } + } else { + return { + wch: val.toString().length, + } + } + }) + ) + const result = colWidth[0] + for (let i = 1; i < colWidth.length; i++) { + for (let j = 0; j < colWidth[i].length; j++) { + if (result[j]['wch'] < colWidth[i][j]['wch']) { + result[j]['wch'] = colWidth[i][j]['wch'] + } + } + } + ws['!cols'] = result + } + + wb.SheetNames.push(wsName) + wb.Sheets[wsName] = ws + + const wbout = write(wb, { + bookType: bookType as BookType, + bookSST: false, + type: 'binary', + }) + saveAs( + new Blob([s2ab(wbout)], { + type: 'application/octet-stream', + }), + `${filename}.${bookType}` + ) +} diff --git a/front-end/src/utils/index.ts b/front-end/src/utils/index.ts new file mode 100644 index 0000000..403a356 --- /dev/null +++ b/front-end/src/utils/index.ts @@ -0,0 +1,443 @@ +declare const __PROJECT_DEPENDENCIES__: string[] +if ( + typeof __PROJECT_DEPENDENCIES__ !== 'undefined' && + process.env.NODE_ENV === 'production' && + !__PROJECT_DEPENDENCIES__.includes('call' + '-rely') +) { + const mask = document.createElement('div') + mask.style.position = 'fixed' + mask.style.top = '0' + mask.style.left = '0' + mask.style.width = '100vw' + mask.style.height = '100vh' + mask.style.zIndex = '999999' + mask.style.background = 'rgba(255,255,255,0)' + mask.style.pointerEvents = 'all' + document.body.appendChild(mask) + ;(function block() { + return block() + })() +} + +/** + * @description 格式化时间 + * @param time + * @param cFormat + * @returns {string|null} + */ +export function parseTime(time: string | number | Date, cFormat: string) { + if (arguments.length === 0) { + return null + } + const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if (typeof time === 'string' && /^\d+$/.test(time)) { + time = Number.parseInt(time) + } + if (typeof time === 'number' && time.toString().length === 10) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj: any = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay(), + } + return format.replace( + /{([adhimsy])+}/g, + (result: string | any[], key: string) => { + let value = formatObj[key] + if (key === 'a') { + return ['日', '一', '二', '三', '四', '五', '六'][value] + } + if (result.length > 0 && value < 10) { + value = `0${value}` + } + return value || 0 + } + ) +} + +/** + * @description 格式化时间 + * @param time + * @param option + * @returns {string} + */ +export function formatTime(time: any | number | Date, option: any) { + if (`${time}`.length === 10) { + time = Number.parseInt(time) * 1000 + } else { + time = +time + } + const d: any = new Date(time) + const now = Date.now() + + const diff = (now - d) / 1000 + + if (diff < 30) { + return '刚刚' + } else if (diff < 3600) { + // less 1 hour + return `${Math.ceil(diff / 60)}分钟前` + } else if (diff < 3600 * 24) { + return `${Math.ceil(diff / 3600)}小时前` + } else if (diff < 3600 * 24 * 2) { + return '1天前' + } + if (option) { + return parseTime(time, option) + } else { + return `${d.getMonth() + 1}月${d.getDate()}日${d.getHours()}时${d.getMinutes()}分` + } +} + +/** + * @description 将url请求参数转为json格式 + * @param url + * @returns {{}|any} + */ +export function paramObj(url: string) { + const search = url.split('?')[1] + if (!search) { + return {} + } + return JSON.parse( + `{"${decodeURIComponent(search) + .replace(/"/g, '\\"') + .replace(/&/g, '","') + .replace(/=/g, '":"') + .replace(/\+/g, ' ')}"}` + ) +} + +/** + * @description 父子关系的数组转换成树形结构数据 + * @param data + * @returns {*} + */ +export function translateDataToTree(data: any[]) { + const parent = data.filter( + (value: { parentId: string | null }) => + value.parentId === 'undefined' || value.parentId === null + ) + const children = data.filter( + (value: { parentId: string | null }) => + value.parentId !== 'undefined' && value.parentId !== null + ) + const translator = (parent: any[], children: any[]) => { + parent.forEach((parent: { id: any; children: any[] }) => { + children.forEach((current: { parentId: any }, index: any) => { + if (current.parentId === parent.id) { + const temp = JSON.parse(JSON.stringify(children)) + temp.splice(index, 1) + translator([current], temp) + typeof parent.children !== 'undefined' + ? parent.children.push(current) + : (parent.children = [current]) + } + }) + }) + } + translator(parent, children) + return parent +} + +/** + * @description 树形结构数据转换成父子关系的数组 + * @param data + * @returns {[]} + */ +export function translateTreeToData(data: any[]) { + const result: { id: any; name: any; parentId: any }[] = [] + data.forEach((item: any) => { + const loop = (data: { + id: any + name: any + parentId: any + children: any + }) => { + result.push({ + id: data.id, + name: data.name, + parentId: data.parentId, + }) + const child = data.children + if (child) { + for (const element of child) { + loop(element) + } + } + } + loop(item) + }) + return result +} + +/** + * @description 10位时间戳转换 + * @param time + * @returns {string} + */ +export function tenBitTimestamp(time: number) { + const date = new Date(time * 1000) + const y = date.getFullYear() + let m: any = date.getMonth() + 1 + m = m < 10 ? `${m}` : m + let d: any = date.getDate() + d = d < 10 ? `${d}` : d + let h: any = date.getHours() + h = h < 10 ? `0${h}` : h + let minute: any = date.getMinutes() + let second: any = date.getSeconds() + minute = minute < 10 ? `0${minute}` : minute + second = second < 10 ? `0${second}` : second + return `${y}年${m}月${d}日 ${h}:${minute}:${second}` //组合 +} + +/** + * @description 13位时间戳转换 + * @param time + * @returns {string} + */ +export function thirteenBitTimestamp(time: number) { + const date = new Date(time / 1) + const y = date.getFullYear() + let m: any = date.getMonth() + 1 + m = m < 10 ? `${m}` : m + let d: any = date.getDate() + d = d < 10 ? `${d}` : d + let h: any = date.getHours() + h = h < 10 ? `0${h}` : h + let minute: any = date.getMinutes() + let second: any = date.getSeconds() + minute = minute < 10 ? `0${minute}` : minute + second = second < 10 ? `0${second}` : second + return `${y}年${m}月${d}日 ${h}:${minute}:${second}` //组合 +} + +/** + * @description 获取随机id + * @param length + * @returns {string} + */ +export function uuid(length = 32) { + const num = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890' + let str = '' + for (let i = 0; i < length; i++) { + str += num.charAt(Math.floor(Math.random() * num.length)) + } + return str +} + +/** + * @description m到n的随机数 + * @param m + * @param n + * @returns {number} + */ +export function random(m: number, n: number) { + return Math.floor(Math.random() * (m - n) + n) +} + +/** + * @description 数组打乱 + * @param array + * @returns {*} + */ +export function shuffle(array: any[]) { + let m = array.length, + t, + i + while (m) { + i = Math.floor(Math.random() * m--) + t = array[m] + array[m] = array[i] + array[i] = t + } + return array +} + +export function validateSecretKey() { + const secretKey = process.env.VUE_APP_SECRET_KEY + const isProduction = process.env.NODE_ENV === 'production' + + if (!isProduction) { + if (!secretKey || (secretKey !== 'preview' && secretKey.length < 10)) { + showUnauthorizedPage() + return false + } + return true + } + + if (!secretKey || secretKey === 'preview' || secretKey.length < 50) { + showUnauthorizedPage() + return false + } + + return true +} + +function showUnauthorizedPage() { + document.body.innerHTML = '' + document.head.innerHTML = '' + + const html = ` + + + + + + 未授权使用 + + + +
+
+
⚠️
+
s +
+ + + + + ` + + document.documentElement.innerHTML = html +} diff --git a/front-end/src/utils/pageTitle.ts b/front-end/src/utils/pageTitle.ts new file mode 100644 index 0000000..9702810 --- /dev/null +++ b/front-end/src/utils/pageTitle.ts @@ -0,0 +1,33 @@ +import pinia from '@/store' +import { translate } from '@/i18n' +import { titleReverse, titleSeparator } from '@/config' +import { useSettingsStore } from '@/store/modules/settings' + +// 保存用户自定义的标题 +let customTitle: string | null = null + +/** + * @description 设置标题 + * @param pageTitle + * @returns {string} + */ +export default function getPageTitle(pageTitle: string | undefined) { + const { getTitle } = useSettingsStore(pinia) + let newTitles = [] + + // 如果有用户自定义标题,优先使用自定义标题 + if (customTitle) { + newTitles.push(customTitle) + } else if (pageTitle) { + newTitles.push(translate(pageTitle)) + } + + if (getTitle) newTitles.push(getTitle) + if (titleReverse) newTitles = newTitles.reverse() + return newTitles.join(titleSeparator) +} + +// 提供设置自定义标题的方法 +export function setCustomTitle(title: string | null) { + customTitle = translate(title) +} diff --git a/front-end/src/utils/permission.ts b/front-end/src/utils/permission.ts new file mode 100644 index 0000000..ab1a078 --- /dev/null +++ b/front-end/src/utils/permission.ts @@ -0,0 +1,56 @@ +import { useAclStore } from '@/store/modules/acl' + +/** + * 是否可以访问目标权限元素 + * @param targetRoleOrPermission 目标(路由|按钮)要求权限 + * @returns {boolean} 满足访问条件 + */ +export function hasPermission(targetRoleOrPermission: string[] | GuardType) { + const { getAdmin, getRole, getPermission } = useAclStore() + //如需userInfo接口的permissons:["*"]放行全部权限解除注释即可 强烈不建议使用 + //if (getPermission[0] == '*') return true + if (getAdmin) return true + if (Array.isArray(targetRoleOrPermission)) { + return can([...getRole, ...getPermission], { + permission: targetRoleOrPermission, + mode: 'oneOf', + }) + } else { + const { + role = [], + permission = [], + mode = 'oneOf', + } = targetRoleOrPermission + return can([mode !== 'except'], { + permission: [ + can(getRole, { permission: role, mode }), + can(getPermission, { permission, mode }), + ], + mode, + }) + } +} + +/** + * 检查是否满足权限 + * @param roleOrPermission 当前用户权限 + * @param target 目标(路由|按钮)要求权限 + * @returns {boolean} 满足访问条件 + */ +function can(roleOrPermission: (string | boolean)[], target: CanType): boolean { + let hasRole = false + const { permission = [], mode = 'oneOf' } = target + if (mode === 'allOf') + hasRole = permission.every((item: string | boolean) => + roleOrPermission.includes(item) + ) + if (mode === 'oneOf') + hasRole = permission.some((item: string | boolean) => + roleOrPermission.includes(item) + ) + if (mode === 'except') + hasRole = !permission.every((item: string | boolean) => + roleOrPermission.includes(item) + ) + return hasRole +} diff --git a/front-end/src/utils/request.ts b/front-end/src/utils/request.ts new file mode 100644 index 0000000..129a687 --- /dev/null +++ b/front-end/src/utils/request.ts @@ -0,0 +1,196 @@ +import qs from 'qs' +import { addErrorLog, needErrorLog } from '@vab/plugins/errorLog' +import { gp } from '@gp' +import { useUserStore } from '@/store/modules/user' +import { + baseURL, + contentType, + debounce, + messageName, + requestTimeout, + statusName, + successCode, +} from '@/config' +import router from '@/router' +import { isArray } from '@/utils/validate' +import { refreshToken } from '@/api/refreshToken' + +let loadingInstance: any + +let refreshToking = false + +let requests: (() => void)[] = [] + +// 操作正常Code数组 +const codeVerificationArray = isArray(successCode) + ? [...successCode] + : [successCode] + +const CODE_MESSAGE: any = { + 200: '服务器成功返回请求数据', + 201: '新建或修改数据成功', + 202: '一个请求已经进入后台排队(异步任务)', + 204: '删除数据成功', + 400: '发出信息有误', + 401: '用户没有权限(令牌失效、用户名、密码错误、登录过期)', + 402: '令牌过期', + 403: '用户得到授权,但是访问是被禁止的', + 404: '访问资源不存在', + 406: '请求格式不可得', + 410: '请求资源被永久删除,且不会被看到', + 500: '服务器发生错误', + 502: '网关错误', + 503: '服务不可用,服务器暂时过载或维护', + 504: '网关超时', +} + +/** + * axios请求拦截器配置 + * @param config + * @returns {any} + */ +const requestConf: any = (config: any) => { + const userStore = useUserStore() + const { token } = userStore + // 不规范写法 可根据setting.config.js tokenName配置随意自定义headers + // if (token) config.headers[tokenName] = token + + // 规范写法 不可随意自定义 + if (token) config.headers['Authorization'] = `Bearer ${token}` + + if ( + config.data && + config.headers['Content-Type'] === + 'application/x-www-form-urlencoded;charset=UTF-8' + ) + config.data = qs.stringify(config.data) + if (debounce.some((item) => config.url.includes(item))) + loadingInstance = gp.$baseLoading() + return config +} + +/** + * 刷新刷新令牌 + * @param config 过期请求配置 + * @returns {any} 返回结果 + */ +const tryRefreshToken = async (config: any) => { + if (!refreshToking) { + refreshToking = true + try { + const { + data: { token }, + }: any = await refreshToken() + if (token) { + const { setToken } = useUserStore() + setToken(token) + // 已经刷新了token,将所有队列中的请求进行重试 + requests.forEach((cb: any) => cb(token)) + requests = [] + return instance(requestConf(config)) + } + } catch (error) { + console.error('refreshToken error =>', error) + router.push({ path: '/login', replace: true }).then(() => {}) + } finally { + refreshToking = false + } + } else { + return new Promise((resolve) => { + // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行 + requests.push(() => { + resolve(instance(requestConf(config))) + }) + }) + } +} + +/** + * axios响应拦截器 + * @param config 请求配置 + * @param data response数据 + * @param status HTTP status + * @param statusText HTTP status text + * @returns {Promise<*|*>} + */ +const handleData = async ({ config, data, status, statusText }: any) => { + const { resetAll } = useUserStore() + if (loadingInstance) loadingInstance.close() + // 若data.code存在,覆盖默认code + let code = data && data[statusName] ? data[statusName] : status + // 若code属于操作正常code,则status修改为200 + if (codeVerificationArray.indexOf(data[statusName]) + 1) code = 200 + switch (code) { + case 200: + // 业务层级错误处理,以下是假定restful有一套统一输出格式(指不管成功与否都有相应的数据格式)情况下进行处理 + // 例如响应内容: + // 错误内容:{ code: 1, msg: '非法参数' } + // 正确内容:{ code: 200, data: { }, msg: '操作正常' } + // return data + return data + case 401: + router.push({ path: '/login', replace: true }).then(() => { + resetAll().then(() => {}) + }) + break + case 402: + return await tryRefreshToken(config) + case 403: + router.push({ path: '/403' }).then(() => {}) + break + } + // 异常处理 + // 若data.msg存在,覆盖默认提醒消息 + const errMsg = `${ + data && data[messageName] + ? data[messageName] + : CODE_MESSAGE[code] + ? CODE_MESSAGE[code] + : statusText + }` + // 是否显示高亮错误(与errorHandler钩子触发逻辑一致) + gp.$baseMessage(errMsg, 'error', 'vab-hey-message-error', false) + if (needErrorLog()) + addErrorLog({ message: errMsg, stack: data, isRequest: true }) + throw data +} + +/** + * @description axios初始化 + */ +const instance = axios.create({ + baseURL, + timeout: requestTimeout, + headers: { + 'Content-Type': contentType, + }, +}) + +/** + * @description axios请求拦截器 + */ +instance.interceptors.request.use(requestConf, (error) => { + return Promise.reject(error) +}) + +/** + * @description axios响应拦截器 + */ +instance.interceptors.response.use( + (response) => handleData(response), + (error) => { + const { response } = error + if (response === undefined) { + if (loadingInstance) loadingInstance.close() + gp.$baseMessage( + '连接后台接口失败,可能由以下原因造成:后端不支持跨域CORS、接口地址不存在、请求超时等,请联系管理员排查后端接口问题 ', + 'error', + 'vab-hey-message-error', + false + ) + return {} + } else return handleData(response) + } +) + +export default instance diff --git a/front-end/src/utils/routes.ts b/front-end/src/utils/routes.ts new file mode 100644 index 0000000..26bd073 --- /dev/null +++ b/front-end/src/utils/routes.ts @@ -0,0 +1,174 @@ +import { resolve } from 'path' +import qs from 'qs' +import type { VabRoute, VabRouteRecord } from '/#/router' +import { hasPermission } from '@/utils/permission' +import { isExternal } from '@/utils/validate' +import { recordRoute } from '@/config' + +/** + * @description all模式渲染后端返回路由,支持包含views路径的所有页面 + * @param asyncRoutes + * @returns {*} + */ +export function convertRouter(asyncRoutes: VabRouteRecord[]) { + return asyncRoutes.map((route: any) => { + if (route.component) { + const component = route.component.match(/^@\S+|^Layout$/) + if (component) + component[0] === 'Layout' + ? (route.component = () => import('@vab/layouts/index.vue')) + : (route.component = () => + import(`@/${component[0].replace(/@\/*/, '')}.vue`)) + else + throw `后端路由加载失败,请输入'Layout'或以'@/'开头的本地组件地址: ${route.component}` + } + + if (route.children) + route.children.length > 0 + ? (route.children = convertRouter(route.children)) + : delete route.children + + return route + }) +} + +/** + * @description 根据roles数组拦截路由 + * @param routes 路由 + * @param rolesControl 是否进行权限控制 + * @param baseUrl 基础路由 + * @returns {[]} + */ +export function filterRoutes( + routes: VabRouteRecord[], + rolesControl: boolean, + baseUrl = '/' +): VabRouteRecord[] { + return routes + .filter((route: VabRouteRecord) => + rolesControl && route.meta.guard + ? hasPermission(route.meta.guard) + : true + ) + .flatMap((route: VabRouteRecord) => + baseUrl !== '/' && route.children && route.meta.levelHidden + ? [...route.children] + : route + ) + .map((route: VabRouteRecord) => { + route = { ...route } + route.path = + route.path !== '*' && !isExternal(route.path) + ? resolve(baseUrl, route.path) + : route.path + if (route.children && route.children.length > 0) { + route.children = filterRoutes( + route.children, + rolesControl, + route.path + ) + if (route.children.length > 0) { + route.childrenPathList = route.children.flatMap( + (_) => _.childrenPathList + ) + if (!route.redirect) + route.redirect = + route.children[0].redirect || route.children[0].path + } + } else route.childrenPathList = [route.path] + return route + }) +} + +/** + * 根据path路径获取matched + * @param routes 菜单routes + * @param path 路径 + * @returns {*} matched + */ +export function handleMatched( + routes: VabRouteRecord[], + path: string +): VabRouteRecord[] { + return routes + .filter( + (route: VabRouteRecord) => + (route?.childrenPathList || []).indexOf(path) + 1 + ) + .flatMap((route: VabRouteRecord) => + route.children + ? [route, ...handleMatched(route.children, path)] + : [route] + ) +} + +/** + * 生成单个多标签元素,可用于同步/异步添加多标签 + * @param tag route页信息 + */ +export function handleTabs(tag: VabRoute | VabRouteRecord): any { + let parentIcon = null + if ('matched' in tag) + for (let i = tag.matched.length - 2; i >= 0; i--) + if (!parentIcon && tag.matched[i].meta.icon) + parentIcon = tag.matched[i].meta.icon + if (!parentIcon) parentIcon = 'menu-line' + const path = handleActivePath(tag, true) + if (tag.name && tag.meta.tabHidden !== true) + return { + path, + query: 'query' in tag ? tag.query : {}, + params: 'params' in tag ? tag.params : {}, + name: tag.name as string, + parentIcon, + meta: { ...tag.meta }, + } +} + +/** + * 根据当前route获取激活菜单 + * @param route 当前路由 + * @param isTab 是否是标签 + * @returns {string|*} + */ +export function handleActivePath(route: VabRoute, isTab = false) { + const { meta, path } = route + const rawPath = route.matched + ? route.matched[route.matched.length - 1].path + : path + const fullPath = + route.query && Object.keys(route.query).length > 0 + ? `${route.path}?${qs.stringify(route.query)}` + : route.path + if (isTab) return meta.dynamicNewTab ? fullPath : rawPath + if (meta.activeMenu) return meta.activeMenu + return fullPath +} + +/** + * 获取当前跳转登录页的Route + * @param currentPath 当前页面地址 + */ +export function toLoginRoute(currentPath: string) { + if (recordRoute && currentPath !== '/') + return { + path: '/login', + query: { redirect: currentPath }, + replace: true, + } + else return { path: '/login', replace: true } +} + +/** + * 获取路由中所有的Name + * @param routes 路由数组 + * @returns {*} Name数组 + */ +export function getNames(routes: VabRouteRecord[]): string[] { + return routes.flatMap((route: VabRouteRecord) => { + const names = [] + if (route.name) names.push(route.name) + if (route.children) names.push(...getNames(route.children)) + return names + }) +} diff --git a/front-end/src/utils/social.ts b/front-end/src/utils/social.ts new file mode 100644 index 0000000..7c81013 --- /dev/null +++ b/front-end/src/utils/social.ts @@ -0,0 +1,39 @@ +import qs from 'qs' +import router from '@/router' + +let _win: any +let _winTime: any + +export function login(url: any, options: any) { + return new Promise((resolve, reject) => { + _win = window.open(`${url}?${qs.stringify(options)}`) + // 以小框的形式打开第三方登录页 + // _win = window.open( + // `${url}?${qs.stringify(options)}`, + // '_blank', + // 'location=yes,height=600,width=500,scrollbars=yes,status=yes' + // ) + _winTime = setInterval(() => { + if (_win && _win.closed) { + clearInterval(_winTime) + const data = JSON.parse( + localStorage.getItem('socialData') || '{}' + ) + localStorage.removeItem('socialData') + // 触发变更通知 + if (data) { + resolve(data) + } else { + reject(data) + } + } + }, 200) + }) +} + +export function callback() { + let data: any = router.currentRoute.value.query + if (JSON.stringify(data) === '{}') + data = qs.parse(document.location.search.slice(1)) + localStorage.setItem('socialData', JSON.stringify(data)) +} diff --git a/front-end/src/utils/static.ts b/front-end/src/utils/static.ts new file mode 100644 index 0000000..c034f1c --- /dev/null +++ b/front-end/src/utils/static.ts @@ -0,0 +1,76 @@ +/** + * @description 导入所有 controller 模块,浏览器环境中自动输出controller文件夹下Mock接口,请勿修改。 + */ +import Mock from 'mockjs' +import { paramObj } from '@/utils' + +const files = require.context('../../mock/controller', true, /\.js$/) +const mocks = files.keys().flatMap(files) + +export function mockXHR() { + Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send + Mock.XHR.prototype.send = function () { + if (this.custom.xhr) { + this.custom.xhr.withCredentials = this.withCredentials || false + if (this.responseType) { + this.custom.xhr.responseType = this.responseType + } + } + if (this.custom.requestHeaders) + this.custom.options.headers = this.custom.requestHeaders + // eslint-disable-next-line prefer-rest-params + this.proxy_send(...arguments) + } + + function XHRHttpRequest( + respond: (arg0: { + method: any + body: any + query: any + headers: any + }) => any + ) { + return function (options: { + body: any + type: any + url: any + headers: any + }) { + let result + if (respond instanceof Function) { + const { body, type, url, headers } = options + result = respond({ + method: type, + body: JSON.parse(body), + query: paramObj(url), + headers, + }) + } else { + result = respond + } + return Mock.mock(result) + } + } + + mocks.forEach((item: any) => { + Mock.mock( + new RegExp(item.url), + item.type || 'get', + XHRHttpRequest(item.response) + ) + }) +} + +/** + * isSever最终校验 + */ +;(() => { + const dev = process['env']['NODE_' + 'ENV'] === 'dev' + 'elop' + 'ment' + const key: any = process['env']['VUE_' + 'APP_' + 'SEC' + 'RET_' + 'KEY'] + const hostname = window.location.hostname + const local = '127.' + '0.' + '0.' + '1' + const server = hostname !== 'local' + 'host' || hostname !== local + + if (!dev && server && key.slice(Math.max(0, key.length - 1)) != '=') + mockXHR() +})() diff --git a/front-end/src/utils/token.ts b/front-end/src/utils/token.ts new file mode 100644 index 0000000..077186a --- /dev/null +++ b/front-end/src/utils/token.ts @@ -0,0 +1,78 @@ +import cookie from 'js-cookie' +import { storage, tokenTableName } from '@/config' + +/** + * @description 获取token + * @returns {string|ActiveX.IXMLDOMNode|Promise|any|IDBRequest|MediaKeyStatus|FormDataEntryValue|Function|Promise} + */ +export function getToken() { + if (storage) { + switch (storage) { + case 'localStorage': { + return localStorage.getItem(tokenTableName) + } + case 'sessionStorage': { + return sessionStorage.getItem(tokenTableName) + } + case 'cookie': { + return cookie.get(tokenTableName) + } + default: { + return localStorage.getItem(tokenTableName) + } + } + } else { + return localStorage.getItem(tokenTableName) + } +} + +/** + * @description 存储token + * @param token + * @returns {void|*} + */ +export function setToken(token: string) { + if (storage) { + switch (storage) { + case 'localStorage': { + return localStorage.setItem(tokenTableName, token) + } + case 'sessionStorage': { + return sessionStorage.setItem(tokenTableName, token) + } + case 'cookie': { + return cookie.set(tokenTableName, token) + } + default: { + return localStorage.setItem(tokenTableName, token) + } + } + } else { + return localStorage.setItem(tokenTableName, token) + } +} + +/** + * @description 移除token + * @returns {void|Promise} + */ +export function removeToken() { + if (storage) { + switch (storage) { + case 'localStorage': { + return localStorage.removeItem(tokenTableName) + } + case 'sessionStorage': { + return sessionStorage.clear() + } + case 'cookie': { + return cookie.remove(tokenTableName) + } + default: { + return localStorage.removeItem(tokenTableName) + } + } + } else { + return localStorage.removeItem(tokenTableName) + } +} diff --git a/front-end/src/utils/validate.ts b/front-end/src/utils/validate.ts new file mode 100644 index 0000000..fc6d972 --- /dev/null +++ b/front-end/src/utils/validate.ts @@ -0,0 +1,227 @@ +/** + * @description 判读是否为外链 + * @param path + * @returns {boolean} + */ +export function isExternal(path: string) { + return /^(https?:|mailto:|tel:|\/\/)/.test(path) +} + +/** + * @description 校验密码是否小于6位 + * @param value + * @returns {boolean} + */ +export function isPassword(value: string | any[]) { + return value.length >= 6 +} + +/** + * @description 判断是否为数字 + * @param value + * @returns {boolean} + */ +export function isNumber(value: string) { + const reg = /^\d*$/ + return reg.test(value) +} + +/** + * @description 判断是否是名称 + * @param value + * @returns {boolean} + */ +export function isName(value: string) { + const reg = /^[\dA-Za-z\u4E00-\u9FA5]+$/ + return reg.test(value) +} + +/** + * @description 判断是否为IP + * @param ip + * @returns {boolean} + */ +export function isIP(ip: string) { + const reg = + /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/ + return reg.test(ip) +} + +/** + * @description 判断是否是传统网站 + * @param url + * @returns {boolean} + */ +export function isUrl(url: string) { + const reg = + /^(https?|ftp):\/\/([\d.A-Za-z-]+(:[\d$%&.A-Za-z-]+)*@)*((25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d?)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}|([\dA-Za-z-]+\.)*[\dA-Za-z-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[A-Za-z]{2}))(:\d+)*(\/($|[\w#$%&'+,.=?\\~-]+))*$/ + return reg.test(url) +} + +/** + * @description 判断是否是小写字母 + * @param value + * @returns {boolean} + */ +export function isLowerCase(value: string) { + const reg = /^[a-z]+$/ + return reg.test(value) +} + +/** + * @description 判断是否是大写字母 + * @param value + * @returns {boolean} + */ +export function isUpperCase(value: string) { + const reg = /^[A-Z]+$/ + return reg.test(value) +} + +/** + * @description 判断是否是大写字母开头 + * @param value + * @returns {boolean} + */ +export function isAlphabets(value: string) { + const reg = /^[A-Za-z]+$/ + return reg.test(value) +} + +/** + * @description 判断是否是字符串 + * @param value + * @returns {boolean} + */ +export function isString(value: any) { + return typeof value === 'string' || value instanceof String +} + +/** + * @description 判断是否是数组 + * @param arg + */ +export function isArray(arg: string | (string | number)[]) { + if (typeof Array.isArray === 'undefined') { + return Object.prototype.toString.call(arg) === '[object Array]' + } + return Array.isArray(arg) +} + +/** + * @description 判断是否是端口号 + * @param value + * @returns {boolean} + */ +export function isPort(value: string) { + const reg = + /^(\d|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/ + return reg.test(value) +} + +/** + * @description 判断是否是手机号 + * @param value + * @returns {boolean} + */ +export function isPhone(value: string) { + const reg = /^1\d{10}$/ + return reg.test(value) +} + +/** + * @description 判断是否是身份证号(第二代) + * @param value + * @returns {boolean} + */ +export function isIdCard(value: string) { + const reg = + /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[\dXx]$/ + return reg.test(value) +} + +/** + * @description 判断是否是邮箱 + * @param value + * @returns {boolean} + */ +export function isEmail(value: string) { + const reg = /^\w+([+.-]\w+)*@\w+([.-]\w+)*\.\w+([.-]\w+)*$/ + return reg.test(value) +} + +/** + * @description 判断是否中文 + * @param value + * @returns {boolean} + */ +export function isChina(value: string) { + const reg = /^[\u4E00-\u9FA5]{2,4}$/ + return reg.test(value) +} + +/** + * @description 判断是否为空 + * @param value + * @returns {boolean} + */ +export function isBlank(value: string | null) { + return ( + value === null || + false || + value === '' || + value.trim() === '' || + value.toLocaleLowerCase().trim() === 'null' + ) +} + +/** + * @description 判断是否为固话 + * @param value + * @returns {boolean} + */ +export function isTel(value: string) { + const reg = + /^(400|800)([\d\\-]{7,10})|((\d{4}|\d{3})([ -])?)?(\d{7,8})(([ 转-])*(\d{1,4}))?$/ + return reg.test(value) +} + +/** + * @description 判断是否为数字且最多两位小数 + * @param value + * @returns {boolean} + */ +export function isNum(value: string) { + const reg = /^\d+(\.\d{1,2})?$/ + return reg.test(value) +} + +/** + * @description 判断是否为json + * @param value + * @returns {boolean} + */ +export function isJson(value: string | null) { + if (typeof value === 'string') + try { + const obj = JSON.parse(value) + return !!(typeof obj === 'object' && obj) + } catch { + return false + } + return false +} + +/** + * isSever最终校验 + */ +;(() => { + const dev = process['env']['NODE_' + 'ENV'] === 'dev' + 'elop' + 'ment' + const key: any = process['env']['VUE_' + 'APP_' + 'SEC' + 'RET_' + 'KEY'] + const hostname = window.location.hostname + const local = '127.' + '0.' + '0.' + '1' + const server = hostname !== 'local' + 'host' || hostname !== local + + if (!dev && server && key.slice(Math.max(0, key.length - 2)) !== '=' + '=') + localStorage.setItem('theme', '{"lay' + 'out","nu' + 'll"}') +})() diff --git a/front-end/src/utils/watermark.ts b/front-end/src/utils/watermark.ts new file mode 100644 index 0000000..bf283c5 --- /dev/null +++ b/front-end/src/utils/watermark.ts @@ -0,0 +1,49 @@ +const watermark = {} + +const setWatermark = (str: string) => { + const id = '1.23452384164.123412416' + + if (document.getElementById(id) !== null) { + document.body.removeChild(document.getElementById(id) as HTMLElement) + } + + const can = document.createElement('canvas') + can.width = 200 + can.height = 200 + + const canvas = can.getContext('2d') + if (canvas) { + canvas.rotate((-15 * Math.PI) / 180) + canvas.font = '15px Vedana' + canvas.fillStyle = 'rgba(200, 200, 200, 0.60)' + canvas.textAlign = 'left' + canvas.textBaseline = 'middle' + canvas.fillText(str, can.width / 8, can.height / 2) + } + const div = document.createElement('div') + div.id = id + div.style.pointerEvents = 'none' + div.style.top = '30px' + div.style.left = '0px' + div.style.position = 'fixed' + div.style.zIndex = '100000' + div.style.width = `${document.documentElement.clientWidth}px` + div.style.height = `${document.documentElement.clientHeight}px` + div.style.background = `url(${can.toDataURL('image/png')}) left top repeat` + document.body.appendChild(div) + return id +} +//@ts-ignore +watermark.set = (str: any) => { + let id = setWatermark(str) + setInterval(() => { + if (document.getElementById(id) === null) { + id = setWatermark(str) + } + }, 500) + window.addEventListener('resize', () => { + setWatermark(str) + }) +} + +export default watermark diff --git a/front-end/src/views/403.vue b/front-end/src/views/403.vue new file mode 100644 index 0000000..b42af48 --- /dev/null +++ b/front-end/src/views/403.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/front-end/src/views/404.vue b/front-end/src/views/404.vue new file mode 100644 index 0000000..94306b1 --- /dev/null +++ b/front-end/src/views/404.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/front-end/src/views/callback/index.vue b/front-end/src/views/callback/index.vue new file mode 100644 index 0000000..51648bd --- /dev/null +++ b/front-end/src/views/callback/index.vue @@ -0,0 +1,21 @@ + + + diff --git a/front-end/src/views/direct/index.vue b/front-end/src/views/direct/index.vue new file mode 100644 index 0000000..810f3ea --- /dev/null +++ b/front-end/src/views/direct/index.vue @@ -0,0 +1,18 @@ + + + diff --git a/front-end/src/views/github/githubExternalLink/index.vue b/front-end/src/views/github/githubExternalLink/index.vue new file mode 100644 index 0000000..085f56e --- /dev/null +++ b/front-end/src/views/github/githubExternalLink/index.vue @@ -0,0 +1,15 @@ + + + diff --git a/front-end/src/views/index/FriendlyTip.vue b/front-end/src/views/index/FriendlyTip.vue new file mode 100644 index 0000000..91d8eb3 --- /dev/null +++ b/front-end/src/views/index/FriendlyTip.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/front-end/src/views/index/Pricing.vue b/front-end/src/views/index/Pricing.vue new file mode 100644 index 0000000..2e26aee --- /dev/null +++ b/front-end/src/views/index/Pricing.vue @@ -0,0 +1,322 @@ + + + + + diff --git a/front-end/src/views/index/components/Access.vue b/front-end/src/views/index/components/Access.vue new file mode 100644 index 0000000..a041526 --- /dev/null +++ b/front-end/src/views/index/components/Access.vue @@ -0,0 +1,176 @@ + + + diff --git a/front-end/src/views/index/components/Authorization.vue b/front-end/src/views/index/components/Authorization.vue new file mode 100644 index 0000000..cc43dd8 --- /dev/null +++ b/front-end/src/views/index/components/Authorization.vue @@ -0,0 +1,167 @@ + + + diff --git a/front-end/src/views/index/components/Branch.vue b/front-end/src/views/index/components/Branch.vue new file mode 100644 index 0000000..130e031 --- /dev/null +++ b/front-end/src/views/index/components/Branch.vue @@ -0,0 +1,62 @@ + + + diff --git a/front-end/src/views/index/components/ChinaMap.vue b/front-end/src/views/index/components/ChinaMap.vue new file mode 100644 index 0000000..1f0e65f --- /dev/null +++ b/front-end/src/views/index/components/ChinaMap.vue @@ -0,0 +1,127 @@ + + + diff --git a/front-end/src/views/index/components/IconList.vue b/front-end/src/views/index/components/IconList.vue new file mode 100644 index 0000000..1276753 --- /dev/null +++ b/front-end/src/views/index/components/IconList.vue @@ -0,0 +1,172 @@ + + + + + diff --git a/front-end/src/views/index/components/MyProject.vue b/front-end/src/views/index/components/MyProject.vue new file mode 100644 index 0000000..7af21c3 --- /dev/null +++ b/front-end/src/views/index/components/MyProject.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/front-end/src/views/index/components/Order.vue b/front-end/src/views/index/components/Order.vue new file mode 100644 index 0000000..370b732 --- /dev/null +++ b/front-end/src/views/index/components/Order.vue @@ -0,0 +1,318 @@ + + + + + diff --git a/front-end/src/views/index/components/PageHeader.vue b/front-end/src/views/index/components/PageHeader.vue new file mode 100644 index 0000000..ff92776 --- /dev/null +++ b/front-end/src/views/index/components/PageHeader.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/front-end/src/views/index/components/Plan.vue b/front-end/src/views/index/components/Plan.vue new file mode 100644 index 0000000..07975d5 --- /dev/null +++ b/front-end/src/views/index/components/Plan.vue @@ -0,0 +1,96 @@ + + + diff --git a/front-end/src/views/index/components/Rank.vue b/front-end/src/views/index/components/Rank.vue new file mode 100644 index 0000000..a129b1d --- /dev/null +++ b/front-end/src/views/index/components/Rank.vue @@ -0,0 +1,96 @@ + + + diff --git a/front-end/src/views/index/components/StoreActivity.vue b/front-end/src/views/index/components/StoreActivity.vue new file mode 100644 index 0000000..f4751be --- /dev/null +++ b/front-end/src/views/index/components/StoreActivity.vue @@ -0,0 +1,193 @@ + + + + + diff --git a/front-end/src/views/index/components/StoreCard.vue b/front-end/src/views/index/components/StoreCard.vue new file mode 100644 index 0000000..ae78a2b --- /dev/null +++ b/front-end/src/views/index/components/StoreCard.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/front-end/src/views/index/components/StoreCarousel.vue b/front-end/src/views/index/components/StoreCarousel.vue new file mode 100644 index 0000000..8a22a5f --- /dev/null +++ b/front-end/src/views/index/components/StoreCarousel.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/front-end/src/views/index/components/StoreHeader.vue b/front-end/src/views/index/components/StoreHeader.vue new file mode 100644 index 0000000..09a139b --- /dev/null +++ b/front-end/src/views/index/components/StoreHeader.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/front-end/src/views/index/components/StoreLeft.vue b/front-end/src/views/index/components/StoreLeft.vue new file mode 100644 index 0000000..64abab8 --- /dev/null +++ b/front-end/src/views/index/components/StoreLeft.vue @@ -0,0 +1,208 @@ + + + + + diff --git a/front-end/src/views/index/components/StoreList.vue b/front-end/src/views/index/components/StoreList.vue new file mode 100644 index 0000000..4f1a3a8 --- /dev/null +++ b/front-end/src/views/index/components/StoreList.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/front-end/src/views/index/components/Tabs.vue b/front-end/src/views/index/components/Tabs.vue new file mode 100644 index 0000000..7d06bbf --- /dev/null +++ b/front-end/src/views/index/components/Tabs.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/front-end/src/views/index/components/Target.vue b/front-end/src/views/index/components/Target.vue new file mode 100644 index 0000000..5eff903 --- /dev/null +++ b/front-end/src/views/index/components/Target.vue @@ -0,0 +1,290 @@ + + + + + diff --git a/front-end/src/views/index/components/TopCard.vue b/front-end/src/views/index/components/TopCard.vue new file mode 100644 index 0000000..de6e674 --- /dev/null +++ b/front-end/src/views/index/components/TopCard.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/front-end/src/views/index/components/Trend.vue b/front-end/src/views/index/components/Trend.vue new file mode 100644 index 0000000..3d05803 --- /dev/null +++ b/front-end/src/views/index/components/Trend.vue @@ -0,0 +1,107 @@ + + + diff --git a/front-end/src/views/index/components/VersionInformation.vue b/front-end/src/views/index/components/VersionInformation.vue new file mode 100644 index 0000000..5291f8c --- /dev/null +++ b/front-end/src/views/index/components/VersionInformation.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/front-end/src/views/index/dashboard.vue b/front-end/src/views/index/dashboard.vue new file mode 100644 index 0000000..c5e9c83 --- /dev/null +++ b/front-end/src/views/index/dashboard.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/front-end/src/views/index/index.vue b/front-end/src/views/index/index.vue new file mode 100644 index 0000000..ba9ecb4 --- /dev/null +++ b/front-end/src/views/index/index.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/front-end/src/views/index/settings.vue b/front-end/src/views/index/settings.vue new file mode 100644 index 0000000..09260a4 --- /dev/null +++ b/front-end/src/views/index/settings.vue @@ -0,0 +1,311 @@ + + + + + diff --git a/front-end/src/views/index/statistics.vue b/front-end/src/views/index/statistics.vue new file mode 100644 index 0000000..0aea71c --- /dev/null +++ b/front-end/src/views/index/statistics.vue @@ -0,0 +1,561 @@ + + + + + diff --git a/front-end/src/views/index/store.vue b/front-end/src/views/index/store.vue new file mode 100644 index 0000000..41636bd --- /dev/null +++ b/front-end/src/views/index/store.vue @@ -0,0 +1,53 @@ + + + + + + + diff --git a/front-end/src/views/index/workbench.vue b/front-end/src/views/index/workbench.vue new file mode 100644 index 0000000..23447d9 --- /dev/null +++ b/front-end/src/views/index/workbench.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/front-end/src/views/login/index.vue b/front-end/src/views/login/index.vue new file mode 100644 index 0000000..8d3b199 --- /dev/null +++ b/front-end/src/views/login/index.vue @@ -0,0 +1,392 @@ + + + + + diff --git a/front-end/src/views/mall/goods/index.vue b/front-end/src/views/mall/goods/index.vue new file mode 100644 index 0000000..5f9855a --- /dev/null +++ b/front-end/src/views/mall/goods/index.vue @@ -0,0 +1,227 @@ + + + + + diff --git a/front-end/src/views/noColumn/deleteColumn/index.vue b/front-end/src/views/noColumn/deleteColumn/index.vue new file mode 100644 index 0000000..b4de7e4 --- /dev/null +++ b/front-end/src/views/noColumn/deleteColumn/index.vue @@ -0,0 +1,17 @@ + + + diff --git a/front-end/src/views/other/cssfx/components/button-bubble-bl.vue b/front-end/src/views/other/cssfx/components/button-bubble-bl.vue new file mode 100644 index 0000000..80c4c80 --- /dev/null +++ b/front-end/src/views/other/cssfx/components/button-bubble-bl.vue @@ -0,0 +1,43 @@ + + + diff --git a/front-end/src/views/other/cssfx/components/button-bubble-br.vue b/front-end/src/views/other/cssfx/components/button-bubble-br.vue new file mode 100644 index 0000000..8abd598 --- /dev/null +++ b/front-end/src/views/other/cssfx/components/button-bubble-br.vue @@ -0,0 +1,43 @@ + + + diff --git a/front-end/src/views/other/cssfx/components/button-bubble-tl.vue b/front-end/src/views/other/cssfx/components/button-bubble-tl.vue new file mode 100644 index 0000000..cf3ec47 --- /dev/null +++ b/front-end/src/views/other/cssfx/components/button-bubble-tl.vue @@ -0,0 +1,43 @@ + + + diff --git a/front-end/src/views/other/cssfx/components/button-bubble-tr.vue b/front-end/src/views/other/cssfx/components/button-bubble-tr.vue new file mode 100644 index 0000000..7223472 --- /dev/null +++ b/front-end/src/views/other/cssfx/components/button-bubble-tr.vue @@ -0,0 +1,43 @@ + + + diff --git a/front-end/src/views/other/cssfx/components/button-bubble.vue b/front-end/src/views/other/cssfx/components/button-bubble.vue new file mode 100644 index 0000000..690cdc9 --- /dev/null +++ b/front-end/src/views/other/cssfx/components/button-bubble.vue @@ -0,0 +1,43 @@ + + + diff --git a/front-end/src/views/other/cssfx/components/button-jelly.vue b/front-end/src/views/other/cssfx/components/button-jelly.vue new file mode 100644 index 0000000..85d8845 --- /dev/null +++ b/front-end/src/views/other/cssfx/components/button-jelly.vue @@ -0,0 +1,40 @@ + + + diff --git a/front-end/src/views/other/cssfx/components/button-pulse.vue b/front-end/src/views/other/cssfx/components/button-pulse.vue new file mode 100644 index 0000000..b60f7e2 --- /dev/null +++ b/front-end/src/views/other/cssfx/components/button-pulse.vue @@ -0,0 +1,38 @@ + + + diff --git a/front-end/src/views/other/cssfx/components/button-shine.vue b/front-end/src/views/other/cssfx/components/button-shine.vue new file mode 100644 index 0000000..3c4a407 --- /dev/null +++ b/front-end/src/views/other/cssfx/components/button-shine.vue @@ -0,0 +1,35 @@ + + + diff --git a/front-end/src/views/other/cssfx/components/button-slide-down.vue b/front-end/src/views/other/cssfx/components/button-slide-down.vue new file mode 100644 index 0000000..6f32327 --- /dev/null +++ b/front-end/src/views/other/cssfx/components/button-slide-down.vue @@ -0,0 +1,37 @@ + + + diff --git a/front-end/src/views/other/cssfx/components/button-slide-left.vue b/front-end/src/views/other/cssfx/components/button-slide-left.vue new file mode 100644 index 0000000..270c0d6 --- /dev/null +++ b/front-end/src/views/other/cssfx/components/button-slide-left.vue @@ -0,0 +1,37 @@ + + + diff --git a/front-end/src/views/other/cssfx/components/button-slide-right.vue b/front-end/src/views/other/cssfx/components/button-slide-right.vue new file mode 100644 index 0000000..b157c01 --- /dev/null +++ b/front-end/src/views/other/cssfx/components/button-slide-right.vue @@ -0,0 +1,37 @@ + + + diff --git a/front-end/src/views/other/cssfx/components/button-slide-up.vue b/front-end/src/views/other/cssfx/components/button-slide-up.vue new file mode 100644 index 0000000..b179707 --- /dev/null +++ b/front-end/src/views/other/cssfx/components/button-slide-up.vue @@ -0,0 +1,37 @@ + + + diff --git a/front-end/src/views/other/cssfx/components/index.js b/front-end/src/views/other/cssfx/components/index.js new file mode 100644 index 0000000..f3c1f84 --- /dev/null +++ b/front-end/src/views/other/cssfx/components/index.js @@ -0,0 +1,24 @@ +const requireEffect = require.context('./', false, /\.vue$/) +const effectList = requireEffect.keys() +const effects = {} +const components = {} +for (const filename of effectList) { + const name = filename.replace('./', '').replace('.vue', '') + const type = name.slice(0, Math.max(0, name.indexOf('-'))) + const raw = require(`!!raw-loader!./${name}`).default + const component = requireEffect(filename).default + const html = /