Browse Source

项目初始化

markc1d 2 months ago
commit
06954aff59
26 changed files with 4070 additions and 0 deletions
  1. 24 0
      .gitignore
  2. 3 0
      .vscode/extensions.json
  3. 81 0
      README.md
  4. 13 0
      index.html
  5. 1819 0
      package-lock.json
  6. 21 0
      package.json
  7. 1 0
      public/vite.svg
  8. 21 0
      src/App.vue
  9. 26 0
      src/api/user.js
  10. 1 0
      src/assets/vue.svg
  11. 43 0
      src/components/HelloWorld.vue
  12. 48 0
      src/layout/components/Breadcrumb.vue
  13. 37 0
      src/layout/components/Link.vue
  14. 86 0
      src/layout/components/SidebarItem.vue
  15. 219 0
      src/layout/index.vue
  16. 19 0
      src/main.js
  17. 89 0
      src/router/index.js
  18. 63 0
      src/style.css
  19. 66 0
      src/utils/request.js
  20. 61 0
      src/utils/validate.js
  21. 47 0
      src/views/404.vue
  22. 337 0
      src/views/dashboard/index.vue
  23. 168 0
      src/views/login/index.vue
  24. 375 0
      src/views/system/role.vue
  25. 389 0
      src/views/system/user.vue
  26. 13 0
      vite.config.js

+ 24 - 0
.gitignore
View File

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

+ 3 - 0
.vscode/extensions.json
View File

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar"]
+}

+ 81 - 0
README.md
View File

@@ -0,0 +1,81 @@
+# Vite Vue3 Admin Template
+
+一个基于 Vue 3、Vite、Element Plus 和 Vue Router 4 的轻量级后台管理系统模板。
+
+## 特性
+
+- 📦 基于 [Vue 3](https://v3.vuejs.org/)、[Vite](https://vitejs.dev/)、[Element Plus](https://element-plus.org/) 和 [Vue Router 4](https://router.vuejs.org/)
+- 🚀 使用 Vue 3 的 `<script setup>` 语法
+- 🎨 内置响应式布局
+- 🔒 基本的路由权限控制
+- 📊 示例页面(仪表盘、用户管理、角色管理等)
+- 🔄 Axios 封装与接口管理
+- 📱 移动端适配
+
+## 目录结构
+
+```
+├── public/             # 静态资源
+├── src/                # 源代码
+│   ├── api/            # API 请求
+│   ├── assets/         # 图片等静态资源
+│   ├── components/     # 全局组件
+│   ├── layout/         # 布局组件
+│   ├── router/         # 路由配置
+│   ├── store/          # 状态管理(预留)
+│   ├── utils/          # 工具函数
+│   ├── views/          # 页面
+│   ├── App.vue         # 根组件
+│   ├── main.js         # 入口文件
+│   └── style.css       # 全局样式
+├── index.html          # HTML 模板
+├── package.json        # 依赖管理
+└── vite.config.js      # Vite 配置
+```
+
+## 快速开始
+
+### 安装依赖
+
+```bash
+npm install
+```
+
+### 开发环境运行
+
+```bash
+npm run dev
+```
+
+### 生产环境构建
+
+```bash
+npm run build
+```
+
+### 预览生产环境构建
+
+```bash
+npm run preview
+```
+
+## 登录信息
+
+- 用户名:admin
+- 密码:123456
+
+## 技术栈
+
+- [Vue 3](https://v3.vuejs.org/)
+- [Vite](https://vitejs.dev/)
+- [Vue Router 4](https://router.vuejs.org/)
+- [Element Plus](https://element-plus.org/)
+- [Axios](https://axios-http.com/)
+
+## 浏览器支持
+
+推荐使用现代浏览器如 Chrome、Firefox、Safari、Edge 等。
+
+## 许可证
+
+[MIT](./LICENSE)

+ 13 - 0
index.html
View File

@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + Vue</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 1819 - 0
package-lock.json
View File

@@ -0,0 +1,1819 @@
+{
+  "name": "vite-vue3-admin-template",
+  "version": "0.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "vite-vue3-admin-template",
+      "version": "0.0.0",
+      "dependencies": {
+        "axios": "^1.12.2",
+        "element-plus": "^2.11.2",
+        "vue": "^3.5.21",
+        "vue-router": "^4.5.1"
+      },
+      "devDependencies": {
+        "@vitejs/plugin-vue": "^6.0.1",
+        "vite": "^7.1.6"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+      "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.28.4",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
+      "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.28.4"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.28.4",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
+      "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@ctrl/tinycolor": {
+      "version": "3.6.1",
+      "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
+      "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@element-plus/icons-vue": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
+      "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
+      "license": "MIT",
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz",
+      "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz",
+      "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz",
+      "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz",
+      "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz",
+      "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz",
+      "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz",
+      "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz",
+      "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz",
+      "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz",
+      "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz",
+      "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz",
+      "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz",
+      "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz",
+      "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz",
+      "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz",
+      "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz",
+      "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-arm64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz",
+      "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz",
+      "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-arm64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz",
+      "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz",
+      "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openharmony-arm64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz",
+      "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz",
+      "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz",
+      "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz",
+      "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz",
+      "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@floating-ui/core": {
+      "version": "1.7.3",
+      "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
+      "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
+      "license": "MIT",
+      "dependencies": {
+        "@floating-ui/utils": "^0.2.10"
+      }
+    },
+    "node_modules/@floating-ui/dom": {
+      "version": "1.7.4",
+      "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
+      "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
+      "license": "MIT",
+      "dependencies": {
+        "@floating-ui/core": "^1.7.3",
+        "@floating-ui/utils": "^0.2.10"
+      }
+    },
+    "node_modules/@floating-ui/utils": {
+      "version": "0.2.10",
+      "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
+      "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+      "license": "MIT"
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+      "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+      "license": "MIT"
+    },
+    "node_modules/@popperjs/core": {
+      "name": "@sxzz/popperjs-es",
+      "version": "2.11.7",
+      "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
+      "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/popperjs"
+      }
+    },
+    "node_modules/@rolldown/pluginutils": {
+      "version": "1.0.0-beta.29",
+      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz",
+      "integrity": "sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.2.tgz",
+      "integrity": "sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.2.tgz",
+      "integrity": "sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.2.tgz",
+      "integrity": "sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.2.tgz",
+      "integrity": "sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.2.tgz",
+      "integrity": "sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.2.tgz",
+      "integrity": "sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.2.tgz",
+      "integrity": "sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.2.tgz",
+      "integrity": "sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.2.tgz",
+      "integrity": "sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.2.tgz",
+      "integrity": "sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loong64-gnu": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.50.2.tgz",
+      "integrity": "sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.2.tgz",
+      "integrity": "sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.2.tgz",
+      "integrity": "sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-musl": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.2.tgz",
+      "integrity": "sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.2.tgz",
+      "integrity": "sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.2.tgz",
+      "integrity": "sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.2.tgz",
+      "integrity": "sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-openharmony-arm64": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.2.tgz",
+      "integrity": "sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.2.tgz",
+      "integrity": "sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.2.tgz",
+      "integrity": "sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.2.tgz",
+      "integrity": "sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+      "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/lodash": {
+      "version": "4.17.20",
+      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
+      "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
+      "license": "MIT"
+    },
+    "node_modules/@types/lodash-es": {
+      "version": "4.17.12",
+      "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+      "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/lodash": "*"
+      }
+    },
+    "node_modules/@types/web-bluetooth": {
+      "version": "0.0.16",
+      "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
+      "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==",
+      "license": "MIT"
+    },
+    "node_modules/@vitejs/plugin-vue": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz",
+      "integrity": "sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@rolldown/pluginutils": "1.0.0-beta.29"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      },
+      "peerDependencies": {
+        "vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
+        "vue": "^3.2.25"
+      }
+    },
+    "node_modules/@vue/compiler-core": {
+      "version": "3.5.21",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.21.tgz",
+      "integrity": "sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.28.3",
+        "@vue/shared": "3.5.21",
+        "entities": "^4.5.0",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.2.1"
+      }
+    },
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.5.21",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.21.tgz",
+      "integrity": "sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-core": "3.5.21",
+        "@vue/shared": "3.5.21"
+      }
+    },
+    "node_modules/@vue/compiler-sfc": {
+      "version": "3.5.21",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.21.tgz",
+      "integrity": "sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.28.3",
+        "@vue/compiler-core": "3.5.21",
+        "@vue/compiler-dom": "3.5.21",
+        "@vue/compiler-ssr": "3.5.21",
+        "@vue/shared": "3.5.21",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.18",
+        "postcss": "^8.5.6",
+        "source-map-js": "^1.2.1"
+      }
+    },
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.5.21",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.21.tgz",
+      "integrity": "sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.21",
+        "@vue/shared": "3.5.21"
+      }
+    },
+    "node_modules/@vue/devtools-api": {
+      "version": "6.6.4",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+      "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
+      "license": "MIT"
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.5.21",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.21.tgz",
+      "integrity": "sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/shared": "3.5.21"
+      }
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.5.21",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.21.tgz",
+      "integrity": "sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/reactivity": "3.5.21",
+        "@vue/shared": "3.5.21"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.5.21",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.21.tgz",
+      "integrity": "sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/reactivity": "3.5.21",
+        "@vue/runtime-core": "3.5.21",
+        "@vue/shared": "3.5.21",
+        "csstype": "^3.1.3"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.5.21",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.21.tgz",
+      "integrity": "sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.5.21",
+        "@vue/shared": "3.5.21"
+      },
+      "peerDependencies": {
+        "vue": "3.5.21"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.5.21",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.21.tgz",
+      "integrity": "sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==",
+      "license": "MIT"
+    },
+    "node_modules/@vueuse/core": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.13.0.tgz",
+      "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/web-bluetooth": "^0.0.16",
+        "@vueuse/metadata": "9.13.0",
+        "@vueuse/shared": "9.13.0",
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/core/node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vueuse/metadata": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.13.0.tgz",
+      "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.13.0.tgz",
+      "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
+      "license": "MIT",
+      "dependencies": {
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared/node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/async-validator": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
+      "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
+      "license": "MIT"
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "license": "MIT"
+    },
+    "node_modules/axios": {
+      "version": "1.12.2",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
+      "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.15.6",
+        "form-data": "^4.0.4",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/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/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "license": "MIT",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/csstype": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+      "license": "MIT"
+    },
+    "node_modules/dayjs": {
+      "version": "1.11.18",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz",
+      "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==",
+      "license": "MIT"
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/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/element-plus": {
+      "version": "2.11.2",
+      "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.11.2.tgz",
+      "integrity": "sha512-sTMDXtgeqy17TUsBSH/DL3h1/5sqIOVUUgXFoVbdD8lWWYssKrDX50CEYy4k29tYJhPHKZyFSwcLJsIajr+dDA==",
+      "license": "MIT",
+      "dependencies": {
+        "@ctrl/tinycolor": "^3.4.1",
+        "@element-plus/icons-vue": "^2.3.1",
+        "@floating-ui/dom": "^1.0.1",
+        "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
+        "@types/lodash": "^4.14.182",
+        "@types/lodash-es": "^4.17.6",
+        "@vueuse/core": "^9.1.0",
+        "async-validator": "^4.2.5",
+        "dayjs": "^1.11.13",
+        "escape-html": "^1.0.3",
+        "lodash": "^4.17.21",
+        "lodash-es": "^4.17.21",
+        "lodash-unified": "^1.0.2",
+        "memoize-one": "^6.0.0",
+        "normalize-wheel-es": "^1.2.0"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/entities": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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/es-set-tostringtag": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.6",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz",
+      "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.25.10",
+        "@esbuild/android-arm": "0.25.10",
+        "@esbuild/android-arm64": "0.25.10",
+        "@esbuild/android-x64": "0.25.10",
+        "@esbuild/darwin-arm64": "0.25.10",
+        "@esbuild/darwin-x64": "0.25.10",
+        "@esbuild/freebsd-arm64": "0.25.10",
+        "@esbuild/freebsd-x64": "0.25.10",
+        "@esbuild/linux-arm": "0.25.10",
+        "@esbuild/linux-arm64": "0.25.10",
+        "@esbuild/linux-ia32": "0.25.10",
+        "@esbuild/linux-loong64": "0.25.10",
+        "@esbuild/linux-mips64el": "0.25.10",
+        "@esbuild/linux-ppc64": "0.25.10",
+        "@esbuild/linux-riscv64": "0.25.10",
+        "@esbuild/linux-s390x": "0.25.10",
+        "@esbuild/linux-x64": "0.25.10",
+        "@esbuild/netbsd-arm64": "0.25.10",
+        "@esbuild/netbsd-x64": "0.25.10",
+        "@esbuild/openbsd-arm64": "0.25.10",
+        "@esbuild/openbsd-x64": "0.25.10",
+        "@esbuild/openharmony-arm64": "0.25.10",
+        "@esbuild/sunos-x64": "0.25.10",
+        "@esbuild/win32-arm64": "0.25.10",
+        "@esbuild/win32-ia32": "0.25.10",
+        "@esbuild/win32-x64": "0.25.10"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "license": "MIT"
+    },
+    "node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+      "license": "MIT"
+    },
+    "node_modules/fdir": {
+      "version": "6.5.0",
+      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+      "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "peerDependencies": {
+        "picomatch": "^3 || ^4"
+      },
+      "peerDependenciesMeta": {
+        "picomatch": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.11",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+      "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+      "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+      "license": "MIT",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "hasown": "^2.0.2",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/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-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/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.npmjs.org/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/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/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.npmjs.org/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/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "license": "MIT",
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "license": "MIT"
+    },
+    "node_modules/lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
+      "license": "MIT"
+    },
+    "node_modules/lodash-unified": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz",
+      "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/lodash-es": "*",
+        "lodash": "*",
+        "lodash-es": "*"
+      }
+    },
+    "node_modules/magic-string": {
+      "version": "0.30.19",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
+      "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.5"
+      }
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/memoize-one": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
+      "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
+      "license": "MIT"
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/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.npmjs.org/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/nanoid": {
+      "version": "3.3.11",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/normalize-wheel-es": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
+      "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "license": "ISC"
+    },
+    "node_modules/picomatch": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+      "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.5.6",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+      "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "nanoid": "^3.3.11",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+      "license": "MIT"
+    },
+    "node_modules/rollup": {
+      "version": "4.50.2",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.2.tgz",
+      "integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "1.0.8"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.50.2",
+        "@rollup/rollup-android-arm64": "4.50.2",
+        "@rollup/rollup-darwin-arm64": "4.50.2",
+        "@rollup/rollup-darwin-x64": "4.50.2",
+        "@rollup/rollup-freebsd-arm64": "4.50.2",
+        "@rollup/rollup-freebsd-x64": "4.50.2",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.50.2",
+        "@rollup/rollup-linux-arm-musleabihf": "4.50.2",
+        "@rollup/rollup-linux-arm64-gnu": "4.50.2",
+        "@rollup/rollup-linux-arm64-musl": "4.50.2",
+        "@rollup/rollup-linux-loong64-gnu": "4.50.2",
+        "@rollup/rollup-linux-ppc64-gnu": "4.50.2",
+        "@rollup/rollup-linux-riscv64-gnu": "4.50.2",
+        "@rollup/rollup-linux-riscv64-musl": "4.50.2",
+        "@rollup/rollup-linux-s390x-gnu": "4.50.2",
+        "@rollup/rollup-linux-x64-gnu": "4.50.2",
+        "@rollup/rollup-linux-x64-musl": "4.50.2",
+        "@rollup/rollup-openharmony-arm64": "4.50.2",
+        "@rollup/rollup-win32-arm64-msvc": "4.50.2",
+        "@rollup/rollup-win32-ia32-msvc": "4.50.2",
+        "@rollup/rollup-win32-x64-msvc": "4.50.2",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/tinyglobby": {
+      "version": "0.2.15",
+      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+      "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "fdir": "^6.5.0",
+        "picomatch": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/SuperchupuDev"
+      }
+    },
+    "node_modules/vite": {
+      "version": "7.1.6",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.6.tgz",
+      "integrity": "sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "esbuild": "^0.25.0",
+        "fdir": "^6.5.0",
+        "picomatch": "^4.0.3",
+        "postcss": "^8.5.6",
+        "rollup": "^4.43.0",
+        "tinyglobby": "^0.2.15"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^20.19.0 || >=22.12.0",
+        "jiti": ">=1.21.0",
+        "less": "^4.0.0",
+        "lightningcss": "^1.21.0",
+        "sass": "^1.70.0",
+        "sass-embedded": "^1.70.0",
+        "stylus": ">=0.54.8",
+        "sugarss": "^5.0.0",
+        "terser": "^5.16.0",
+        "tsx": "^4.8.1",
+        "yaml": "^2.4.2"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "jiti": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "lightningcss": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        },
+        "tsx": {
+          "optional": true
+        },
+        "yaml": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue": {
+      "version": "3.5.21",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.21.tgz",
+      "integrity": "sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.21",
+        "@vue/compiler-sfc": "3.5.21",
+        "@vue/runtime-dom": "3.5.21",
+        "@vue/server-renderer": "3.5.21",
+        "@vue/shared": "3.5.21"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue-router": {
+      "version": "4.5.1",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz",
+      "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/devtools-api": "^6.6.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    }
+  }
+}

+ 21 - 0
package.json
View File

@@ -0,0 +1,21 @@
+{
+  "name": "vite-vue3-admin-template",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "axios": "^1.12.2",
+    "element-plus": "^2.11.2",
+    "vue": "^3.5.21",
+    "vue-router": "^4.5.1"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^6.0.1",
+    "vite": "^7.1.6"
+  }
+}

+ 1 - 0
public/vite.svg
View File

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

+ 21 - 0
src/App.vue
View File

@@ -0,0 +1,21 @@
+<script setup>
+// 这里不需要引入HelloWorld组件了
+</script>
+
+<template>
+  <router-view />
+</template>
+
+<style>
+html,
+body {
+  margin: 0;
+  padding: 0;
+  height: 100%;
+  font-family: Arial, sans-serif;
+}
+
+#app {
+  height: 100%;
+}
+</style>

+ 26 - 0
src/api/user.js
View File

@@ -0,0 +1,26 @@
+import request from "@/utils/request";
+
+// 用户登录
+export function login(data) {
+  return request({
+    url: "/user/login",
+    method: "post",
+    data,
+  });
+}
+
+// 获取用户信息
+export function getUserInfo() {
+  return request({
+    url: "/user/info",
+    method: "get",
+  });
+}
+
+// 用户登出
+export function logout() {
+  return request({
+    url: "/user/logout",
+    method: "post",
+  });
+}

+ 1 - 0
src/assets/vue.svg
View File

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

+ 43 - 0
src/components/HelloWorld.vue
View File

@@ -0,0 +1,43 @@
+<script setup>
+import { ref } from 'vue'
+
+defineProps({
+  msg: String,
+})
+
+const count = ref(0)
+</script>
+
+<template>
+  <h1>{{ msg }}</h1>
+
+  <div class="card">
+    <button type="button" @click="count++">count is {{ count }}</button>
+    <p>
+      Edit
+      <code>components/HelloWorld.vue</code> to test HMR
+    </p>
+  </div>
+
+  <p>
+    Check out
+    <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
+      >create-vue</a
+    >, the official Vue + Vite starter
+  </p>
+  <p>
+    Learn more about IDE Support for Vue in the
+    <a
+      href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
+      target="_blank"
+      >Vue Docs Scaling up Guide</a
+    >.
+  </p>
+  <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
+</template>
+
+<style scoped>
+.read-the-docs {
+  color: #888;
+}
+</style>

+ 48 - 0
src/layout/components/Breadcrumb.vue
View File

@@ -0,0 +1,48 @@
+<template>
+  <div class="breadcrumb-container">
+    <el-breadcrumb separator="/">
+      <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
+      <el-breadcrumb-item
+        v-for="(item, index) in breadcrumbs"
+        :key="index"
+        :to="item.path"
+      >
+        {{ item.meta.title }}
+      </el-breadcrumb-item>
+    </el-breadcrumb>
+  </div>
+</template>
+
+<script setup>
+import { ref, watch } from "vue";
+import { useRoute } from "vue-router";
+
+const route = useRoute();
+const breadcrumbs = ref([]);
+
+// 生成面包屑导航
+const getBreadcrumbs = () => {
+  let matched = route.matched.filter((item) => item.meta && item.meta.title);
+  // 如果第一个是首页,则过滤掉
+  if (matched[0]?.path === "/") {
+    matched.shift();
+  }
+  breadcrumbs.value = matched;
+};
+
+// 监听路由变化,更新面包屑
+watch(
+  () => route.path,
+  () => {
+    getBreadcrumbs();
+  },
+  { immediate: true }
+);
+</script>
+
+<style scoped>
+.breadcrumb-container {
+  display: inline-block;
+  line-height: 50px;
+}
+</style>

+ 37 - 0
src/layout/components/Link.vue
View File

@@ -0,0 +1,37 @@
+<template>
+  <component :is="type" v-bind="linkProps(to)">
+    <slot />
+  </component>
+</template>
+
+<script setup>
+import { computed } from "vue";
+import { isExternal } from "@/utils/validate";
+
+// 定义组件属性
+const props = defineProps({
+  to: {
+    type: String,
+    required: true,
+  },
+});
+
+// 根据to属性判断是渲染a标签还是router-link
+const type = computed(() => {
+  return isExternal(props.to) ? "a" : "router-link";
+});
+
+// 根据类型设置链接属性
+const linkProps = (to) => {
+  if (isExternal(to)) {
+    return {
+      href: to,
+      target: "_blank",
+      rel: "noopener",
+    };
+  }
+  return {
+    to: to,
+  };
+};
+</script>

+ 86 - 0
src/layout/components/SidebarItem.vue
View File

@@ -0,0 +1,86 @@
+<template>
+  <div v-if="!item.meta?.hidden">
+    <!-- 如果没有子路由或者只有一个显示的子路由 -->
+    <template v-if="hasOneShowingChild(item.children, item)">
+      <app-link :to="resolvePath(onlyOneChild.path)">
+        <el-menu-item :index="resolvePath(onlyOneChild.path)">
+          <el-icon v-if="onlyOneChild.meta?.icon">
+            <component :is="onlyOneChild.meta.icon" />
+          </el-icon>
+          <template #title>{{ onlyOneChild.meta?.title }}</template>
+        </el-menu-item>
+      </app-link>
+    </template>
+
+    <!-- 如果有多个子路由 -->
+    <el-sub-menu v-else :index="resolvePath(item.path)">
+      <template #title>
+        <el-icon v-if="item.meta?.icon">
+          <component :is="item.meta.icon" />
+        </el-icon>
+        <span>{{ item.meta?.title }}</span>
+      </template>
+
+      <sidebar-item
+        v-for="child in item.children"
+        :key="child.path"
+        :item="child"
+        :base-path="resolvePath(child.path)"
+      />
+    </el-sub-menu>
+  </div>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { isExternal } from "@/utils/validate";
+import AppLink from "./Link.vue";
+
+// 定义组件属性
+const props = defineProps({
+  item: {
+    type: Object,
+    required: true,
+  },
+  basePath: {
+    type: String,
+    default: "",
+  },
+});
+
+// 用于存储唯一子项
+const onlyOneChild = ref(null);
+
+// 判断是否只有一个可显示的子路由
+const hasOneShowingChild = (children = [], parent) => {
+  // 筛选出没有hidden属性的子路由
+  const showingChildren = children.filter((item) => {
+    return !item.meta?.hidden;
+  });
+
+  // 如果只有一个子路由,则直接显示这个子路由
+  if (showingChildren.length === 1) {
+    onlyOneChild.value = showingChildren[0];
+    return true;
+  }
+
+  // 如果没有子路由,则显示父路由本身
+  if (showingChildren.length === 0) {
+    onlyOneChild.value = { ...parent, path: "", noShowingChildren: true };
+    return true;
+  }
+
+  return false;
+};
+
+// 解析路径
+const resolvePath = (routePath) => {
+  if (isExternal(routePath)) {
+    return routePath;
+  }
+  if (isExternal(props.basePath)) {
+    return props.basePath;
+  }
+  return props.basePath ? `${props.basePath}/${routePath}` : routePath;
+};
+</script>

+ 219 - 0
src/layout/index.vue
View File

@@ -0,0 +1,219 @@
+<template>
+  <div class="app-wrapper">
+    <!-- 侧边栏 -->
+    <div class="sidebar-container" :class="{ 'is-collapsed': isCollapse }">
+      <div class="logo-container">
+        <h1 class="logo">Admin</h1>
+      </div>
+      <el-scrollbar>
+        <el-menu
+          :default-active="activeMenu"
+          :collapse="isCollapse"
+          background-color="#304156"
+          text-color="#bfcbd9"
+          active-text-color="#409EFF"
+          unique-opened
+          router
+        >
+          <sidebar-item
+            v-for="route in routes"
+            :key="route.path"
+            :item="route"
+            :base-path="route.path"
+          />
+        </el-menu>
+      </el-scrollbar>
+    </div>
+
+    <!-- 主要内容区 -->
+    <div class="main-container">
+      <!-- 顶部导航栏 -->
+      <div class="navbar">
+        <div class="left-menu">
+          <el-icon class="hamburger" @click="toggleSideBar">
+            <component :is="isCollapse ? 'Expand' : 'Fold'" />
+          </el-icon>
+          <breadcrumb />
+        </div>
+        <div class="right-menu">
+          <el-dropdown trigger="click">
+            <span class="user-dropdown-link">
+              管理员
+              <el-icon><ArrowDown /></el-icon>
+            </span>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item>个人中心</el-dropdown-item>
+                <el-dropdown-item divided @click="handleLogout"
+                  >退出登录</el-dropdown-item
+                >
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </div>
+      </div>
+
+      <!-- 内容区 -->
+      <div class="app-main">
+        <router-view v-slot="{ Component }">
+          <transition name="fade-transform" mode="out-in">
+            <keep-alive>
+              <component :is="Component" />
+            </keep-alive>
+          </transition>
+        </router-view>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { computed, ref } from "vue";
+import { useRouter, useRoute } from "vue-router";
+import { ElMessageBox } from "element-plus";
+import { ArrowDown, Expand, Fold } from "@element-plus/icons-vue";
+import Breadcrumb from "./components/Breadcrumb.vue";
+import SidebarItem from "./components/SidebarItem.vue";
+
+const router = useRouter();
+const route = useRoute();
+
+// 侧边栏展开收起状态
+const isCollapse = ref(false);
+
+// 切换侧边栏展开/收起
+const toggleSideBar = () => {
+  isCollapse.value = !isCollapse.value;
+};
+
+// 计算当前激活的菜单项
+const activeMenu = computed(() => {
+  const { meta, path } = route;
+  if (meta.activeMenu) {
+    return meta.activeMenu;
+  }
+  return path;
+});
+
+// 获取路由列表,过滤掉 hidden 的路由
+const routes = computed(() => {
+  return router.options.routes.filter((route) => {
+    return !route.meta?.hidden;
+  });
+});
+
+// 退出登录
+const handleLogout = () => {
+  ElMessageBox.confirm("确认退出登录吗?", "提示", {
+    confirmButtonText: "确认",
+    cancelButtonText: "取消",
+    type: "warning",
+  })
+    .then(() => {
+      localStorage.removeItem("token");
+      router.push("/login");
+    })
+    .catch(() => {});
+};
+</script>
+
+<style scoped>
+.app-wrapper {
+  position: relative;
+  height: 100vh;
+  width: 100%;
+  display: flex;
+}
+
+.sidebar-container {
+  width: 210px;
+  height: 100%;
+  background-color: #304156;
+  transition: width 0.28s;
+  overflow: hidden;
+}
+
+.sidebar-container.is-collapsed {
+  width: 64px;
+}
+
+.logo-container {
+  height: 50px;
+  line-height: 50px;
+  text-align: center;
+  background-color: #2b2f3a;
+}
+
+.logo {
+  margin: 0;
+  color: #fff;
+  font-size: 18px;
+  font-weight: bold;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.main-container {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.navbar {
+  height: 50px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 15px;
+  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
+  background-color: #fff;
+}
+
+.left-menu {
+  display: flex;
+  align-items: center;
+}
+
+.hamburger {
+  font-size: 20px;
+  cursor: pointer;
+  margin-right: 15px;
+}
+
+.right-menu {
+  display: flex;
+  align-items: center;
+}
+
+.user-dropdown-link {
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+  color: #606266;
+}
+
+.app-main {
+  flex: 1;
+  padding: 15px;
+  overflow: auto;
+  background-color: #f0f2f5;
+}
+
+/* 页面切换动画 */
+.fade-transform-enter-active,
+.fade-transform-leave-active {
+  transition: all 0.3s;
+}
+
+.fade-transform-enter-from {
+  opacity: 0;
+  transform: translateX(-30px);
+}
+
+.fade-transform-leave-to {
+  opacity: 0;
+  transform: translateX(30px);
+}
+</style>

+ 19 - 0
src/main.js
View File

@@ -0,0 +1,19 @@
+import { createApp } from "vue";
+import ElementPlus from "element-plus";
+import * as ElementPlusIconsVue from "@element-plus/icons-vue";
+import "element-plus/dist/index.css";
+import "./style.css";
+import App from "./App.vue";
+import router from "./router";
+
+const app = createApp(App);
+
+// 注册所有图标
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+  app.component(key, component);
+}
+
+app.use(ElementPlus);
+app.use(router);
+
+app.mount("#app");

+ 89 - 0
src/router/index.js
View File

@@ -0,0 +1,89 @@
+import { createRouter, createWebHashHistory } from "vue-router";
+
+// 布局组件
+const Layout = () => import("@/layout/index.vue");
+
+// 路由配置
+const routes = [
+  {
+    path: "/login",
+    name: "Login",
+    component: () => import("@/views/login/index.vue"),
+    meta: { title: "登录", hidden: true },
+  },
+  {
+    path: "/",
+    component: Layout,
+    redirect: "/dashboard",
+    children: [
+      {
+        path: "dashboard",
+        name: "Dashboard",
+        component: () => import("@/views/dashboard/index.vue"),
+        meta: { title: "控制台", icon: "el-icon-menu" },
+      },
+    ],
+  },
+  {
+    path: "/system",
+    component: Layout,
+    name: "System",
+    meta: { title: "系统管理", icon: "el-icon-setting" },
+    children: [
+      {
+        path: "user",
+        name: "User",
+        component: () => import("@/views/system/user.vue"),
+        meta: { title: "用户管理", icon: "el-icon-user" },
+      },
+      {
+        path: "role",
+        name: "Role",
+        component: () => import("@/views/system/role.vue"),
+        meta: { title: "角色管理", icon: "el-icon-s-check" },
+      },
+    ],
+  },
+  // 404页面
+  {
+    path: "/:pathMatch(.*)*",
+    name: "NotFound",
+    component: () => import("@/views/404.vue"),
+    meta: { hidden: true },
+  },
+];
+
+const router = createRouter({
+  history: createWebHashHistory(),
+  routes,
+});
+
+// 路由守卫
+router.beforeEach((to, from, next) => {
+  // 设置页面标题
+  document.title = to.meta.title
+    ? `${to.meta.title} - 后台管理系统`
+    : "后台管理系统";
+
+  // 检查用户是否已登录
+  const hasToken = localStorage.getItem("token");
+
+  if (to.path === "/login") {
+    // 如果已登录且要去登录页,重定向到首页
+    if (hasToken) {
+      next({ path: "/" });
+    } else {
+      next();
+    }
+  } else {
+    // 其他页面需要登录权限
+    if (hasToken) {
+      next();
+    } else {
+      // 未登录则跳转到登录页
+      next(`/login?redirect=${to.path}`);
+    }
+  }
+});
+
+export default router;

+ 63 - 0
src/style.css
View File

@@ -0,0 +1,63 @@
+/* 基础样式重置 */
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
+html,
+body {
+  height: 100%;
+  font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
+    "Microsoft YaHei", Arial, sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  font-size: 14px;
+  color: #303133;
+  background-color: #f0f2f5;
+}
+
+/* 清除浮动 */
+.clearfix:after {
+  content: "";
+  display: table;
+  clear: both;
+}
+
+/* 超出省略 */
+.ellipsis {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+/* 自定义滚动条 */
+::-webkit-scrollbar {
+  width: 6px;
+  height: 6px;
+}
+
+::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 3px;
+}
+
+::-webkit-scrollbar-thumb {
+  background: #c1c1c1;
+  border-radius: 3px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+  background: #a8a8a8;
+}
+
+/* 通用过渡动画 */
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.3s;
+}
+
+.fade-enter-from,
+.fade-leave-to {
+  opacity: 0;
+}

+ 66 - 0
src/utils/request.js
View File

@@ -0,0 +1,66 @@
+import axios from 'axios'
+import { ElMessage } from 'element-plus'
+
+// 创建axios实例
+const service = axios.create({
+  baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // 设置默认请求前缀
+  timeout: 15000, // 请求超时时间
+  headers: {
+    'Content-Type': 'application/json;charset=utf-8'
+  }
+})
+
+// 请求拦截器
+service.interceptors.request.use(
+  config => {
+    // 在请求发送前做一些处理,比如添加token
+    const token = localStorage.getItem('token')
+    if (token) {
+      config.headers['Authorization'] = `Bearer ${token}`
+    }
+    return config
+  },
+  error => {
+    // 请求错误处理
+    console.error('Request error:', error)
+    return Promise.reject(error)
+  }
+)
+
+// 响应拦截器
+service.interceptors.response.use(
+  response => {
+    const res = response.data
+    
+    // 根据自己的后端API规范来判断请求是否成功
+    if (res.code === 200 || res.code === 0) {
+      return res.data
+    } else {
+      ElMessage({
+        message: res.message || '接口请求失败',
+        type: 'error',
+        duration: 5 * 1000
+      })
+      
+      // 处理特定错误,例如token过期
+      if (res.code === 401) {
+        // 可以在此处理登出逻辑
+        localStorage.removeItem('token')
+        window.location.href = '/login'
+      }
+      
+      return Promise.reject(new Error(res.message || '接口请求失败'))
+    }
+  },
+  error => {
+    console.error('Response error:', error)
+    ElMessage({
+      message: error.message || '网络请求错误',
+      type: 'error',
+      duration: 5 * 1000
+    })
+    return Promise.reject(error)
+  }
+)
+
+export default service

+ 61 - 0
src/utils/validate.js
View File

@@ -0,0 +1,61 @@
+/**
+ * 判断是否是外部链接
+ * @param {string} path 路径
+ * @returns {boolean}
+ */
+export function isExternal(path) {
+  return /^(https?:|mailto:|tel:)/.test(path);
+}
+
+/**
+ * 格式化时间
+ * @param {Date|string|number} time 时间
+ * @param {string} format 格式
+ * @returns {string}
+ */
+export function formatDate(time, format = "YYYY-MM-DD HH:mm:ss") {
+  if (!time) {
+    return "";
+  }
+
+  const date = new Date(time);
+
+  const year = date.getFullYear();
+  const month = date.getMonth() + 1;
+  const day = date.getDate();
+  const hour = date.getHours();
+  const minute = date.getMinutes();
+  const second = date.getSeconds();
+
+  const formatMap = {
+    YYYY: year.toString(),
+    MM: month.toString().padStart(2, "0"),
+    DD: day.toString().padStart(2, "0"),
+    HH: hour.toString().padStart(2, "0"),
+    mm: minute.toString().padStart(2, "0"),
+    ss: second.toString().padStart(2, "0"),
+  };
+
+  return format.replace(/(YYYY|MM|DD|HH|mm|ss)/g, (match) => formatMap[match]);
+}
+
+/**
+ * 深拷贝
+ * @param {Object} obj 要拷贝的对象
+ * @returns {Object}
+ */
+export function deepClone(obj) {
+  if (obj === null || typeof obj !== "object") {
+    return obj;
+  }
+
+  const result = Array.isArray(obj) ? [] : {};
+
+  for (const key in obj) {
+    if (Object.prototype.hasOwnProperty.call(obj, key)) {
+      result[key] = deepClone(obj[key]);
+    }
+  }
+
+  return result;
+}

+ 47 - 0
src/views/404.vue
View File

@@ -0,0 +1,47 @@
+<template>
+  <div class="not-found-container">
+    <div class="not-found-content">
+      <h1 class="not-found-title">404</h1>
+      <div class="not-found-desc">抱歉,您访问的页面不存在</div>
+      <el-button type="primary" @click="goHome">返回首页</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { useRouter } from "vue-router";
+
+const router = useRouter();
+
+const goHome = () => {
+  router.push("/");
+};
+</script>
+
+<style scoped>
+.not-found-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100vh;
+  background-color: #f0f2f5;
+}
+
+.not-found-content {
+  text-align: center;
+}
+
+.not-found-title {
+  font-size: 144px;
+  font-weight: bold;
+  color: #409eff;
+  margin: 0;
+  line-height: 1.2;
+}
+
+.not-found-desc {
+  font-size: 20px;
+  color: #606266;
+  margin: 20px 0 30px;
+}
+</style>

+ 337 - 0
src/views/dashboard/index.vue
View File

@@ -0,0 +1,337 @@
+<template>
+  <div class="dashboard-container">
+    <el-row :gutter="20">
+      <!-- 统计卡片 -->
+      <el-col
+        :xs="24"
+        :sm="12"
+        :md="12"
+        :lg="6"
+        :xl="6"
+        v-for="(item, index) in statisticsData"
+        :key="index"
+      >
+        <el-card class="statistics-card" shadow="hover">
+          <div class="statistics-card-header">
+            <div class="statistics-card-title">{{ item.title }}</div>
+            <el-icon
+              class="statistics-card-icon"
+              :style="{ backgroundColor: item.color }"
+            >
+              <component :is="item.icon" />
+            </el-icon>
+          </div>
+          <div class="statistics-card-content">
+            <div class="statistics-card-value">{{ item.value }}</div>
+            <div class="statistics-card-subtitle">{{ item.subtitle }}</div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <el-row :gutter="20" style="margin-top: 20px">
+      <!-- 图表展示 -->
+      <el-col :xs="24" :sm="24" :md="24" :lg="16" :xl="16">
+        <el-card shadow="hover">
+          <template #header>
+            <div class="card-header">
+              <span>系统访问统计</span>
+            </div>
+          </template>
+          <div class="chart-placeholder">
+            这里可以放置Echarts图表<br />
+            (需要另外安装Echarts依赖)
+          </div>
+        </el-card>
+      </el-col>
+
+      <!-- 快捷入口 -->
+      <el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
+        <el-card shadow="hover">
+          <template #header>
+            <div class="card-header">
+              <span>快捷入口</span>
+            </div>
+          </template>
+          <div class="quick-entry">
+            <div
+              v-for="(item, index) in quickEntryList"
+              :key="index"
+              class="quick-entry-item"
+              @click="handleQuickEntry(item)"
+            >
+              <el-icon :style="{ color: item.color }">
+                <component :is="item.icon" />
+              </el-icon>
+              <span>{{ item.title }}</span>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <el-row :gutter="20" style="margin-top: 20px">
+      <!-- 待办事项 -->
+      <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
+        <el-card shadow="hover">
+          <template #header>
+            <div class="card-header">
+              <span>待办事项</span>
+              <el-button class="button" text>查看更多</el-button>
+            </div>
+          </template>
+          <el-table :data="todoList" style="width: 100%">
+            <el-table-column prop="title" label="标题" />
+            <el-table-column prop="date" label="日期" width="180" />
+            <el-table-column label="操作" width="120">
+              <template #default>
+                <el-button type="primary" link>完成</el-button>
+                <el-button type="danger" link>删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-card>
+      </el-col>
+
+      <!-- 系统公告 -->
+      <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
+        <el-card shadow="hover">
+          <template #header>
+            <div class="card-header">
+              <span>系统公告</span>
+              <el-button class="button" text>查看更多</el-button>
+            </div>
+          </template>
+          <div class="notice-list">
+            <div
+              v-for="(item, index) in noticeList"
+              :key="index"
+              class="notice-item"
+            >
+              <div class="notice-title">{{ item.title }}</div>
+              <div class="notice-time">{{ item.time }}</div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { useRouter } from "vue-router";
+import {
+  User,
+  Tickets,
+  View,
+  Picture,
+  Files,
+  Setting,
+  Notification,
+  ChatDotRound,
+} from "@element-plus/icons-vue";
+
+const router = useRouter();
+
+// 统计数据
+const statisticsData = [
+  {
+    title: "用户总数",
+    value: "1,234",
+    subtitle: "较昨日 +12%",
+    icon: "User",
+    color: "#409EFF",
+  },
+  {
+    title: "订单总数",
+    value: "3,456",
+    subtitle: "较昨日 +5%",
+    icon: "Tickets",
+    color: "#67C23A",
+  },
+  {
+    title: "页面浏览量",
+    value: "58,954",
+    subtitle: "较昨日 +8%",
+    icon: "View",
+    color: "#E6A23C",
+  },
+  {
+    title: "内容总数",
+    value: "587",
+    subtitle: "较昨日 +2%",
+    icon: "Picture",
+    color: "#F56C6C",
+  },
+];
+
+// 快捷入口
+const quickEntryList = [
+  { title: "用户管理", icon: "User", color: "#409EFF", path: "/system/user" },
+  { title: "角色管理", icon: "Files", color: "#67C23A", path: "/system/role" },
+  {
+    title: "系统设置",
+    icon: "Setting",
+    color: "#E6A23C",
+    path: "/system/setting",
+  },
+  {
+    title: "消息通知",
+    icon: "Notification",
+    color: "#F56C6C",
+    path: "/message",
+  },
+  {
+    title: "在线客服",
+    icon: "ChatDotRound",
+    color: "#909399",
+    path: "/service",
+  },
+];
+
+// 待办事项
+const todoList = [
+  { title: "完成系统权限设计", date: "2025-09-20" },
+  { title: "用户管理模块开发", date: "2025-09-22" },
+  { title: "数据统计报表设计", date: "2025-09-25" },
+  { title: "系统安全性测试", date: "2025-09-28" },
+];
+
+// 系统公告
+const noticeList = [
+  {
+    title: "系统将于9月30日晚上23:00-次日01:00进行升级维护",
+    time: "2025-09-18 10:00",
+  },
+  { title: "用户管理模块新增批量导入功能", time: "2025-09-15 14:30" },
+  { title: "系统优化了数据加载速度,提升了50%", time: "2025-09-10 09:15" },
+  { title: "欢迎使用后台管理系统,祝您使用愉快", time: "2025-09-01 08:00" },
+];
+
+// 处理快捷入口点击
+const handleQuickEntry = (item) => {
+  router.push(item.path);
+};
+</script>
+
+<style scoped>
+.dashboard-container {
+  padding: 10px;
+}
+
+.statistics-card {
+  margin-bottom: 20px;
+  height: 130px;
+}
+
+.statistics-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10px;
+}
+
+.statistics-card-title {
+  font-size: 16px;
+  color: #606266;
+}
+
+.statistics-card-icon {
+  padding: 8px;
+  border-radius: 4px;
+  color: #fff;
+  font-size: 20px;
+}
+
+.statistics-card-content {
+  display: flex;
+  flex-direction: column;
+}
+
+.statistics-card-value {
+  font-size: 24px;
+  font-weight: bold;
+  color: #303133;
+  margin-bottom: 5px;
+}
+
+.statistics-card-subtitle {
+  font-size: 12px;
+  color: #67c23a;
+}
+
+.chart-placeholder {
+  height: 350px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: #909399;
+  border: 1px dashed #dcdfe6;
+  border-radius: 4px;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.quick-entry {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 15px;
+}
+
+.quick-entry-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 15px;
+  border-radius: 4px;
+  cursor: pointer;
+  transition: all 0.3s;
+  border: 1px solid #ebeef5;
+}
+
+.quick-entry-item:hover {
+  background-color: #f5f7fa;
+  transform: translateY(-2px);
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+}
+
+.quick-entry-item .el-icon {
+  font-size: 24px;
+  margin-bottom: 8px;
+}
+
+.notice-list {
+  padding: 5px 0;
+}
+
+.notice-item {
+  padding: 10px 0;
+  border-bottom: 1px solid #ebeef5;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.notice-item:last-child {
+  border-bottom: none;
+}
+
+.notice-title {
+  font-size: 14px;
+  color: #303133;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 70%;
+}
+
+.notice-time {
+  font-size: 12px;
+  color: #909399;
+}
+</style>

+ 168 - 0
src/views/login/index.vue
View File

@@ -0,0 +1,168 @@
+<template>
+  <div class="login-container">
+    <div class="login-form-wrapper">
+      <div class="login-title">后台管理系统</div>
+      <el-form
+        ref="loginFormRef"
+        :model="loginForm"
+        :rules="loginRules"
+        class="login-form"
+      >
+        <el-form-item prop="username">
+          <el-input
+            v-model="loginForm.username"
+            placeholder="用户名"
+            prefix-icon="User"
+          />
+        </el-form-item>
+        <el-form-item prop="password">
+          <el-input
+            v-model="loginForm.password"
+            placeholder="密码"
+            prefix-icon="Lock"
+            type="password"
+            show-password
+            @keyup.enter="handleLogin"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button
+            :loading="loading"
+            type="primary"
+            class="login-button"
+            @click="handleLogin"
+          >
+            登录
+          </el-button>
+        </el-form-item>
+      </el-form>
+      <div class="login-tips">
+        <p>默认用户名: admin</p>
+        <p>默认密码: 123456</p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive } from "vue";
+import { useRouter, useRoute } from "vue-router";
+import { ElMessage } from "element-plus";
+import { User, Lock } from "@element-plus/icons-vue";
+import { login } from "@/api/user";
+
+const router = useRouter();
+const route = useRoute();
+
+// 表单引用
+const loginFormRef = ref(null);
+
+// 加载状态
+const loading = ref(false);
+
+// 表单数据
+const loginForm = reactive({
+  username: "admin",
+  password: "123456",
+});
+
+// 表单验证规则
+const loginRules = {
+  username: [
+    { required: true, message: "请输入用户名", trigger: "blur" },
+    { min: 3, max: 20, message: "长度在 3 到 20 个字符", trigger: "blur" },
+  ],
+  password: [
+    { required: true, message: "请输入密码", trigger: "blur" },
+    { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" },
+  ],
+};
+
+// 登录处理
+const handleLogin = () => {
+  loginFormRef.value.validate((valid) => {
+    if (!valid) return;
+
+    loading.value = true;
+
+    // 调用登录接口
+    login(loginForm)
+      .then((res) => {
+        // 正常情况下,这里应该从后端获取token
+        // 这里为了演示,直接使用一个假的token
+        const token = "admin-token";
+        localStorage.setItem("token", token);
+
+        ElMessage({
+          message: "登录成功",
+          type: "success",
+        });
+
+        // 获取重定向地址
+        const redirect = route.query.redirect || "/";
+        router.push(redirect);
+      })
+      .catch((error) => {
+        console.error("登录失败:", error);
+        // 这里为了演示,即使接口调用失败也让登录成功
+        const token = "admin-token";
+        localStorage.setItem("token", token);
+
+        ElMessage({
+          message: "登录成功(模拟)",
+          type: "success",
+        });
+
+        const redirect = route.query.redirect || "/";
+        router.push(redirect);
+      })
+      .finally(() => {
+        loading.value = false;
+      });
+  });
+};
+</script>
+
+<style scoped>
+.login-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100vh;
+  background-color: #f0f2f5;
+  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzY2IiBoZWlnaHQ9IjY4MyIgdmlld0JveD0iMCAwIDEzNjYgNjgzIj48ZGVmcz48c3R5bGU+LmNscy0xe29wYWNpdHk6MC4xNjt9LmNscy0ye2ZpbGw6IzAwNzJmZjt9PC9zdHlsZT48L2RlZnM+PGcgaWQ9IuWbvuWxgl8xIiBkYXRhLW5hbWU9IuWbvuWxgiAxIiBjbGFzcz0iY2xzLTEiPjxwYXRoIGlkPSLot6/lvoRfNTIwIiBkYXRhLW5hbWU9Iui3r+W+hCA1MjAiIGNsYXNzPSJjbHMtMiIgZD0iTTEzNjYsMTQwLjc1NUExMzM5LjUyLDEzMzkuNTIsMCwwLDAsOTgsNjU3LjI0NVYwSDEzNjZWMTQwLjc1NVoiLz48L2c+PC9zdmc+");
+  background-repeat: no-repeat;
+  background-position: center 110px;
+  background-size: 100%;
+}
+
+.login-form-wrapper {
+  width: 360px;
+  padding: 30px;
+  background: #fff;
+  border-radius: 4px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+.login-title {
+  text-align: center;
+  font-size: 24px;
+  font-weight: bold;
+  margin-bottom: 30px;
+  color: #303133;
+}
+
+.login-form {
+  margin-bottom: 20px;
+}
+
+.login-button {
+  width: 100%;
+}
+
+.login-tips {
+  font-size: 12px;
+  color: #909399;
+  line-height: 1.5;
+}
+</style>

+ 375 - 0
src/views/system/role.vue
View File

@@ -0,0 +1,375 @@
+<template>
+  <div class="role-container">
+    <div class="filter-container">
+      <el-form :inline="true" :model="queryParams" class="form-inline">
+        <el-form-item label="角色名称">
+          <el-input
+            v-model="queryParams.roleName"
+            placeholder="请输入角色名称"
+            clearable
+          />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select
+            v-model="queryParams.status"
+            placeholder="请选择状态"
+            clearable
+          >
+            <el-option label="启用" value="1" />
+            <el-option label="禁用" value="0" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">查询</el-button>
+          <el-button @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="action-container">
+      <el-button type="primary" @click="handleAdd">新增角色</el-button>
+      <el-button
+        type="danger"
+        :disabled="selectedRows.length === 0"
+        @click="handleBatchDelete"
+        >批量删除</el-button
+      >
+    </div>
+
+    <el-table
+      v-loading="loading"
+      :data="roleList"
+      @selection-change="handleSelectionChange"
+      border
+      style="width: 100%; margin-top: 15px"
+    >
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="角色ID" prop="id" width="80" align="center" />
+      <el-table-column label="角色名称" prop="roleName" />
+      <el-table-column label="角色编码" prop="roleKey" />
+      <el-table-column
+        label="显示顺序"
+        prop="roleSort"
+        width="80"
+        align="center"
+      />
+      <el-table-column label="状态" align="center">
+        <template #default="scope">
+          <el-tag :type="scope.row.status === '1' ? 'success' : 'danger'">
+            {{ scope.row.status === "1" ? "启用" : "禁用" }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" prop="createTime" />
+      <el-table-column label="操作" width="180" align="center">
+        <template #default="scope">
+          <el-button type="primary" link @click="handleEdit(scope.row)"
+            >编辑</el-button
+          >
+          <el-button type="success" link @click="handlePermission(scope.row)"
+            >权限</el-button
+          >
+          <el-button type="danger" link @click="handleDelete(scope.row)"
+            >删除</el-button
+          >
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div class="pagination-container">
+      <el-pagination
+        v-model:current-page="queryParams.pageNum"
+        v-model:page-size="queryParams.pageSize"
+        :page-sizes="[10, 20, 50, 100]"
+        :total="total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      />
+    </div>
+
+    <!-- 角色表单对话框 -->
+    <el-dialog
+      :title="dialogTitle"
+      v-model="dialogVisible"
+      width="500px"
+      append-to-body
+    >
+      <el-form
+        ref="roleFormRef"
+        :model="roleForm"
+        :rules="roleRules"
+        label-width="80px"
+      >
+        <el-form-item label="角色名称" prop="roleName">
+          <el-input v-model="roleForm.roleName" placeholder="请输入角色名称" />
+        </el-form-item>
+        <el-form-item label="角色编码" prop="roleKey">
+          <el-input v-model="roleForm.roleKey" placeholder="请输入角色编码" />
+        </el-form-item>
+        <el-form-item label="显示顺序" prop="roleSort">
+          <el-input-number v-model="roleForm.roleSort" :min="1" :max="999" />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-radio-group v-model="roleForm.status">
+            <el-radio label="1">启用</el-radio>
+            <el-radio label="0">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注">
+          <el-input
+            v-model="roleForm.remark"
+            type="textarea"
+            placeholder="请输入备注"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="submitForm">确定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+// 加载状态
+const loading = ref(false);
+
+// 选中行数据
+const selectedRows = ref([]);
+
+// 角色列表总数
+const total = ref(0);
+
+// 查询参数
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  roleName: "",
+  status: "",
+});
+
+// 重置查询参数
+const resetQuery = () => {
+  queryParams.roleName = "";
+  queryParams.status = "";
+  handleQuery();
+};
+
+// 角色列表数据
+const roleList = ref([
+  {
+    id: 1,
+    roleName: "管理员",
+    roleKey: "admin",
+    roleSort: 1,
+    status: "1",
+    remark: "系统管理员",
+    createTime: "2025-09-01 10:00:00",
+  },
+  {
+    id: 2,
+    roleName: "普通用户",
+    roleKey: "user",
+    roleSort: 2,
+    status: "1",
+    remark: "普通用户",
+    createTime: "2025-09-02 11:00:00",
+  },
+  {
+    id: 3,
+    roleName: "游客",
+    roleKey: "visitor",
+    roleSort: 3,
+    status: "0",
+    remark: "只有查看权限",
+    createTime: "2025-09-03 12:00:00",
+  },
+]);
+
+// 对话框相关
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const roleFormRef = ref(null);
+
+// 角色表单
+const roleForm = reactive({
+  id: undefined,
+  roleName: "",
+  roleKey: "",
+  roleSort: 1,
+  status: "1",
+  remark: "",
+});
+
+// 表单验证规则
+const roleRules = {
+  roleName: [{ required: true, message: "请输入角色名称", trigger: "blur" }],
+  roleKey: [{ required: true, message: "请输入角色编码", trigger: "blur" }],
+  roleSort: [{ required: true, message: "请输入显示顺序", trigger: "blur" }],
+};
+
+// 获取角色列表
+const getRoleList = () => {
+  loading.value = true;
+
+  // 这里应该调用API,这里模拟一下
+  setTimeout(() => {
+    total.value = roleList.value.length;
+    loading.value = false;
+  }, 500);
+};
+
+// 查询
+const handleQuery = () => {
+  queryParams.pageNum = 1;
+  getRoleList();
+};
+
+// 页面大小变更
+const handleSizeChange = (val) => {
+  queryParams.pageSize = val;
+  getRoleList();
+};
+
+// 页码变更
+const handleCurrentChange = (val) => {
+  queryParams.pageNum = val;
+  getRoleList();
+};
+
+// 表格选择项变更
+const handleSelectionChange = (selection) => {
+  selectedRows.value = selection;
+};
+
+// 新增角色
+const handleAdd = () => {
+  dialogTitle.value = "新增角色";
+  dialogVisible.value = true;
+
+  // 重置表单
+  Object.keys(roleForm).forEach((key) => {
+    if (key === "roleSort") {
+      roleForm[key] = 1;
+    } else if (key === "status") {
+      roleForm[key] = "1";
+    } else {
+      roleForm[key] = undefined;
+    }
+  });
+};
+
+// 编辑角色
+const handleEdit = (row) => {
+  dialogTitle.value = "编辑角色";
+  dialogVisible.value = true;
+
+  // 填充表单
+  Object.keys(roleForm).forEach((key) => {
+    roleForm[key] = row[key];
+  });
+};
+
+// 删除角色
+const handleDelete = (row) => {
+  ElMessageBox.confirm(`确认删除角色"${row.roleName}"吗?`, "警告", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "warning",
+  })
+    .then(() => {
+      // 这里应该调用API,这里模拟一下
+      ElMessage({
+        type: "success",
+        message: "删除成功",
+      });
+      getRoleList();
+    })
+    .catch(() => {});
+};
+
+// 批量删除
+const handleBatchDelete = () => {
+  if (selectedRows.value.length === 0) {
+    return;
+  }
+
+  ElMessageBox.confirm(
+    `确认删除选中的${selectedRows.value.length}个角色吗?`,
+    "警告",
+    {
+      confirmButtonText: "确定",
+      cancelButtonText: "取消",
+      type: "warning",
+    }
+  )
+    .then(() => {
+      // 这里应该调用API,这里模拟一下
+      ElMessage({
+        type: "success",
+        message: "批量删除成功",
+      });
+      getRoleList();
+    })
+    .catch(() => {});
+};
+
+// 权限设置
+const handlePermission = (row) => {
+  ElMessage({
+    type: "info",
+    message: `为角色"${row.roleName}"设置权限功能待实现`,
+  });
+};
+
+// 提交表单
+const submitForm = () => {
+  roleFormRef.value.validate((valid) => {
+    if (!valid) return;
+
+    // 这里应该调用API,这里模拟一下
+    ElMessage({
+      type: "success",
+      message: roleForm.id ? "修改成功" : "新增成功",
+    });
+
+    dialogVisible.value = false;
+    getRoleList();
+  });
+};
+
+// 初始化
+onMounted(() => {
+  getRoleList();
+});
+</script>
+
+<style scoped>
+.role-container {
+  padding: 10px;
+}
+
+.filter-container {
+  margin-bottom: 15px;
+  padding: 15px;
+  background-color: #fff;
+  border-radius: 4px;
+  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
+}
+
+.action-container {
+  margin-bottom: 15px;
+}
+
+.pagination-container {
+  margin-top: 15px;
+  text-align: right;
+}
+</style>

+ 389 - 0
src/views/system/user.vue
View File

@@ -0,0 +1,389 @@
+<template>
+  <div class="user-container">
+    <div class="filter-container">
+      <el-form :inline="true" :model="queryParams" class="form-inline">
+        <el-form-item label="用户名">
+          <el-input
+            v-model="queryParams.username"
+            placeholder="请输入用户名"
+            clearable
+          />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select
+            v-model="queryParams.status"
+            placeholder="请选择状态"
+            clearable
+          >
+            <el-option label="启用" value="1" />
+            <el-option label="禁用" value="0" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">查询</el-button>
+          <el-button @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="action-container">
+      <el-button type="primary" @click="handleAdd">新增用户</el-button>
+      <el-button
+        type="danger"
+        :disabled="selectedRows.length === 0"
+        @click="handleBatchDelete"
+        >批量删除</el-button
+      >
+      <el-button type="success">导出</el-button>
+    </div>
+
+    <el-table
+      v-loading="loading"
+      :data="userList"
+      @selection-change="handleSelectionChange"
+      border
+      style="width: 100%; margin-top: 15px"
+    >
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="用户ID" prop="id" width="80" align="center" />
+      <el-table-column label="用户名" prop="username" />
+      <el-table-column label="昵称" prop="nickname" />
+      <el-table-column label="邮箱" prop="email" />
+      <el-table-column label="手机号码" prop="phone" />
+      <el-table-column label="状态" align="center">
+        <template #default="scope">
+          <el-tag :type="scope.row.status === '1' ? 'success' : 'danger'">
+            {{ scope.row.status === "1" ? "启用" : "禁用" }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" prop="createTime" />
+      <el-table-column label="操作" width="180" align="center">
+        <template #default="scope">
+          <el-button type="primary" link @click="handleEdit(scope.row)"
+            >编辑</el-button
+          >
+          <el-button type="success" link @click="handleAssignRole(scope.row)"
+            >分配角色</el-button
+          >
+          <el-button type="danger" link @click="handleDelete(scope.row)"
+            >删除</el-button
+          >
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div class="pagination-container">
+      <el-pagination
+        v-model:current-page="queryParams.pageNum"
+        v-model:page-size="queryParams.pageSize"
+        :page-sizes="[10, 20, 50, 100]"
+        :total="total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      />
+    </div>
+
+    <!-- 用户表单对话框 -->
+    <el-dialog
+      :title="dialogTitle"
+      v-model="dialogVisible"
+      width="500px"
+      append-to-body
+    >
+      <el-form
+        ref="userFormRef"
+        :model="userForm"
+        :rules="userRules"
+        label-width="80px"
+      >
+        <el-form-item label="用户名" prop="username">
+          <el-input v-model="userForm.username" placeholder="请输入用户名" />
+        </el-form-item>
+        <el-form-item label="昵称" prop="nickname">
+          <el-input v-model="userForm.nickname" placeholder="请输入昵称" />
+        </el-form-item>
+        <el-form-item label="邮箱" prop="email">
+          <el-input v-model="userForm.email" placeholder="请输入邮箱" />
+        </el-form-item>
+        <el-form-item label="手机号码" prop="phone">
+          <el-input v-model="userForm.phone" placeholder="请输入手机号码" />
+        </el-form-item>
+        <el-form-item label="密码" prop="password" v-if="!userForm.id">
+          <el-input
+            v-model="userForm.password"
+            placeholder="请输入密码"
+            type="password"
+            show-password
+          />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-radio-group v-model="userForm.status">
+            <el-radio label="1">启用</el-radio>
+            <el-radio label="0">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="submitForm">确定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+// 加载状态
+const loading = ref(false);
+
+// 选中行数据
+const selectedRows = ref([]);
+
+// 用户列表总数
+const total = ref(0);
+
+// 查询参数
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  username: "",
+  status: "",
+});
+
+// 重置查询参数
+const resetQuery = () => {
+  queryParams.username = "";
+  queryParams.status = "";
+  handleQuery();
+};
+
+// 用户列表数据
+const userList = ref([
+  {
+    id: 1,
+    username: "admin",
+    nickname: "管理员",
+    email: "admin@example.com",
+    phone: "13800138000",
+    status: "1",
+    createTime: "2025-09-01 10:00:00",
+  },
+  {
+    id: 2,
+    username: "test",
+    nickname: "测试用户",
+    email: "test@example.com",
+    phone: "13900139000",
+    status: "1",
+    createTime: "2025-09-02 11:00:00",
+  },
+  {
+    id: 3,
+    username: "disabled",
+    nickname: "禁用用户",
+    email: "disabled@example.com",
+    phone: "13700137000",
+    status: "0",
+    createTime: "2025-09-03 12:00:00",
+  },
+]);
+
+// 对话框相关
+const dialogVisible = ref(false);
+const dialogTitle = ref("");
+const userFormRef = ref(null);
+
+// 用户表单
+const userForm = reactive({
+  id: undefined,
+  username: "",
+  nickname: "",
+  email: "",
+  phone: "",
+  password: "",
+  status: "1",
+});
+
+// 表单验证规则
+const userRules = {
+  username: [
+    { required: true, message: "请输入用户名", trigger: "blur" },
+    { min: 3, max: 20, message: "长度在 3 到 20 个字符", trigger: "blur" },
+  ],
+  nickname: [{ required: true, message: "请输入昵称", trigger: "blur" }],
+  email: [
+    { required: true, message: "请输入邮箱", trigger: "blur" },
+    { type: "email", message: "请输入正确的邮箱地址", trigger: "blur" },
+  ],
+  phone: [
+    { required: true, message: "请输入手机号码", trigger: "blur" },
+    {
+      pattern: /^1[3-9]\d{9}$/,
+      message: "请输入正确的手机号码",
+      trigger: "blur",
+    },
+  ],
+  password: [
+    { required: true, message: "请输入密码", trigger: "blur" },
+    { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" },
+  ],
+};
+
+// 获取用户列表
+const getUserList = () => {
+  loading.value = true;
+
+  // 这里应该调用API,这里模拟一下
+  setTimeout(() => {
+    total.value = userList.value.length;
+    loading.value = false;
+  }, 500);
+};
+
+// 查询
+const handleQuery = () => {
+  queryParams.pageNum = 1;
+  getUserList();
+};
+
+// 页面大小变更
+const handleSizeChange = (val) => {
+  queryParams.pageSize = val;
+  getUserList();
+};
+
+// 页码变更
+const handleCurrentChange = (val) => {
+  queryParams.pageNum = val;
+  getUserList();
+};
+
+// 表格选择项变更
+const handleSelectionChange = (selection) => {
+  selectedRows.value = selection;
+};
+
+// 新增用户
+const handleAdd = () => {
+  dialogTitle.value = "新增用户";
+  dialogVisible.value = true;
+
+  // 重置表单
+  Object.keys(userForm).forEach((key) => {
+    userForm[key] = key === "status" ? "1" : undefined;
+  });
+};
+
+// 编辑用户
+const handleEdit = (row) => {
+  dialogTitle.value = "编辑用户";
+  dialogVisible.value = true;
+
+  // 填充表单
+  Object.keys(userForm).forEach((key) => {
+    userForm[key] = row[key];
+  });
+};
+
+// 删除用户
+const handleDelete = (row) => {
+  ElMessageBox.confirm(`确认删除用户"${row.username}"吗?`, "警告", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "warning",
+  })
+    .then(() => {
+      // 这里应该调用API,这里模拟一下
+      ElMessage({
+        type: "success",
+        message: "删除成功",
+      });
+      getUserList();
+    })
+    .catch(() => {});
+};
+
+// 批量删除
+const handleBatchDelete = () => {
+  if (selectedRows.value.length === 0) {
+    return;
+  }
+
+  ElMessageBox.confirm(
+    `确认删除选中的${selectedRows.value.length}个用户吗?`,
+    "警告",
+    {
+      confirmButtonText: "确定",
+      cancelButtonText: "取消",
+      type: "warning",
+    }
+  )
+    .then(() => {
+      // 这里应该调用API,这里模拟一下
+      ElMessage({
+        type: "success",
+        message: "批量删除成功",
+      });
+      getUserList();
+    })
+    .catch(() => {});
+};
+
+// 分配角色
+const handleAssignRole = (row) => {
+  ElMessage({
+    type: "info",
+    message: `为用户"${row.username}"分配角色功能待实现`,
+  });
+};
+
+// 提交表单
+const submitForm = () => {
+  userFormRef.value.validate((valid) => {
+    if (!valid) return;
+
+    // 这里应该调用API,这里模拟一下
+    ElMessage({
+      type: "success",
+      message: userForm.id ? "修改成功" : "新增成功",
+    });
+
+    dialogVisible.value = false;
+    getUserList();
+  });
+};
+
+// 初始化
+onMounted(() => {
+  getUserList();
+});
+</script>
+
+<style scoped>
+.user-container {
+  padding: 10px;
+}
+
+.filter-container {
+  margin-bottom: 15px;
+  padding: 15px;
+  background-color: #fff;
+  border-radius: 4px;
+  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
+}
+
+.action-container {
+  margin-bottom: 15px;
+}
+
+.pagination-container {
+  margin-top: 15px;
+  text-align: right;
+}
+</style>

+ 13 - 0
vite.config.js
View File

@@ -0,0 +1,13 @@
+import { defineConfig } from "vite";
+import vue from "@vitejs/plugin-vue";
+import { resolve } from "path";
+
+// https://vite.dev/config/
+export default defineConfig({
+  plugins: [vue()],
+  resolve: {
+    alias: {
+      "@": resolve(__dirname, "src"),
+    },
+  },
+});