Compare commits

15 Commits

Author SHA1 Message Date
edcc8611e5 feat(用户资料): 完善用户资料页面功能
- 在用户类型定义中新增userPhone、unionId和mpOpenId字段
- 重构验证码输入组件,使用a-input-group替代a-input-search
- 实现用户资料更新、密码修改和邮箱绑定功能
- 添加验证码发送倒计时功能
- 优化表单验证和错误处理
- 清理定时器防止内存泄漏
2026-01-18 17:29:03 +08:00
783ea21d55 feat(用户中心): 重构用户个人中心页面并添加HTTP常量
重构用户个人中心页面,新增HTTP请求方法常量模块
- 创建constants/http.ts定义HTTP方法常量
- 在API模块中使用HTTP常量替代字符串
- 重写用户中心页面,增加编辑资料和账号安全功能
- 添加头像上传、密码修改、邮箱绑定等功能
2026-01-18 16:59:15 +08:00
13b320ca93 feat(头像): 实现用户头像加载和显示功能
在全局头部和用户资料页面添加头像加载逻辑,当用户有头像时显示图片,否则显示默认图标。通过监听用户头像变化实时更新显示,并添加样式处理圆形头像和图片适应。
2026-01-18 16:16:49 +08:00
1a82dfab35 feat(auth): 实现用户登出功能并增强错误处理
添加用户登出方法到用户store,替换多处手动更新登录状态的代码
新增错误码枚举和映射,完善axios拦截器中的错误处理逻辑
重构token刷新逻辑为独立函数,支持401错误自动刷新token
2026-01-12 02:06:13 +08:00
3b6fb0cae1 feat(profile): 实现用户个人中心页面及头像上传功能
添加用户个人中心页面,包含基本信息展示和头像上传功能。主要修改包括:
1. 新增 UserProfileView 页面组件
2. 扩展用户信息接口和类型定义
3. 添加文件上传和头像更新API
4. 配置Vite代理以支持文件服务
5. 添加相关依赖(spark-md5, json-bigint)
2026-01-12 01:41:22 +08:00
f18c9cdc8d feat(auth): 添加用户注册功能及相关接口
实现用户注册功能,包括:
1. 在auth模块中添加register接口及相关类型定义
2. 在UserRegisterView中实现注册表单提交逻辑
3. 修改FileListItem的id类型为string以保持一致性
2026-01-12 00:33:55 +08:00
c77d6855a9 Merge pull request #1 from meowrain/features/file-upload-component
feat(api): 新增文件上传相关API及用户邮箱字段
2026-01-11 21:59:05 +08:00
8b09394295 Update src/views/user/UserLoginView.vue
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-11 21:58:43 +08:00
82a2acbdbe Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-11 21:58:19 +08:00
2dfb891465 Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-11 21:58:01 +08:00
4f679e8eb6 Update src/api/file/file.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-11 21:57:39 +08:00
67db6e8d53 Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-11 21:57:25 +08:00
d4c2572cf3 Update src/api/file/file.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-11 21:56:49 +08:00
9d18d05a58 feat(api): 新增文件上传相关API及用户邮箱字段
refactor(auth): 使用isApiSuccess统一校验API响应
refactor(store): 更新用户信息获取逻辑以适配新响应格式

chore: 添加eslint和prettier配置及脚本
style: 调整vite代理配置端口号

新增文件上传相关API接口及类型定义
扩展用户信息接口添加邮箱相关字段
统一API响应校验逻辑
更新package.json添加代码格式化工具
2026-01-11 21:50:38 +08:00
d01117c6ea feat(router): 添加根路径重定向到首页的路由配置
添加根路径("/")的重定向配置,使其自动跳转到首页("/home"),并设置该路由不在菜单中显示
2026-01-10 19:37:39 +08:00
21 changed files with 3643 additions and 167 deletions

275
bun.lock
View File

@@ -6,20 +6,31 @@
"name": "aioj_frontend",
"dependencies": {
"@arco-design/web-vue": "^2.57.0",
"@types/spark-md5": "^3.0.5",
"axios": "^1.13.2",
"json-bigint": "^1.0.0",
"pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"spark-md5": "^3.0.2",
"vue": "^3.5.24",
"vue-router": "4",
},
"devDependencies": {
"@types/json-bigint": "^1.0.4",
"@types/node": "^24.10.0",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-vue": "^9.23.0",
"openapi-typescript-codegen": "^0.29.0",
"prettier": "^3.2.5",
"sass-embedded": "^1.93.3",
"typescript": "~5.9.3",
"vite": "npm:rolldown-vite@7.2.2",
"vue-eslint-parser": "^9.4.3",
"vue-tsc": "^3.1.3",
},
},
@@ -50,12 +61,32 @@
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
"@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="],
"@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="],
"@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="],
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
"@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jsdevtools/ono": ["@jsdevtools/ono@7.1.3", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"@oxc-project/runtime": ["@oxc-project/runtime@0.96.0", "", {}, "sha512-34lh4o9CcSw09Hx6fKihPu85+m+4pmDlkXwJrLvN5nMq5JrcGhhihVM415zDqT8j8IixO1PYYdQZRN4SwQCncg=="],
"@oxc-project/types": ["@oxc-project/types@0.96.0", "", {}, "sha512-r/xkmoXA0xEpU6UGtn18CNVjXH6erU3KCpCDbpLmbVxBFor1U9MqN5Z2uMmCHJuXjJzlnDR+hWY+yPoLo8oHDw=="],
@@ -120,10 +151,32 @@
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@types/json-bigint": ["@types/json-bigint@1.0.4", "", {}, "sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"@types/spark-md5": ["@types/spark-md5@3.0.5", "", {}, "sha512-lWf05dnD42DLVKQJZrDHtWFidcLrHuip01CtnC2/S6AMhX4t9ZlEUj4iuRlAnts0PQk7KESOqKxeGE/b6sIPGg=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@7.18.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/type-utils": "7.18.0", "@typescript-eslint/utils": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.56.0" } }, "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@7.18.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0" } }, "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@7.18.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA=="],
"@typescript-eslint/types": ["@typescript-eslint/types@7.18.0", "", {}, "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^1.3.0" } }, "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@7.18.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" } }, "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg=="],
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.29" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", "vue": "^3.2.25" } }, "sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw=="],
"@volar/language-core": ["@volar/language-core@2.4.23", "", { "dependencies": { "@volar/source-map": "2.4.23" } }, "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ=="],
@@ -160,10 +213,22 @@
"@vue/tsconfig": ["@vue/tsconfig@0.8.1", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"alien-signals": ["alien-signals@3.1.0", "", {}, "sha512-yufC6VpSy8tK3I0lO67pjumo5JvDQVQyr38+3OHqe6CHl1t2VZekKZ7EKKZSqk0cRmE7U7tfZbpXiKNzuc+ckg=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"axios": ["axios@1.13.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA=="],
@@ -172,23 +237,35 @@
"b-validate": ["b-validate@1.5.3", "", {}, "sha512-iCvCkGFskbaYtfQ0a3GmcQCHl/Sv1GufXFGuUQ+FE+WJa7A/espLOuFIn09B944V8/ImPj71T4+rTASxO2PAuA=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
"birpc": ["birpc@2.8.0", "", {}, "sha512-Bz2a4qD/5GRhiHSwj30c/8kC8QGj12nNDwz3D4ErQ4Xhy35dsSDvF+RA/tWpjyU0pdGtSDiEk6B5fBGE1qNVhw=="],
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"buffer-builder": ["buffer-builder@0.2.0", "", {}, "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
"camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"color": ["color@3.2.1", "", { "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" } }, "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA=="],
"color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
@@ -200,18 +277,32 @@
"compute-scroll-into-view": ["compute-scroll-into-view@1.0.20", "", {}, "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"csstype": ["csstype@3.2.0", "", {}, "sha512-si++xzRAY9iPp60roQiFta7OFbhrgvcthrhlNAGeQptSY25uJjkfUV8OArC3KLocB8JT8ohz+qgxWCmz8RhjIg=="],
"dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
"defu": ["defu@6.1.4", "https://registry.npmmirror.com/defu/-/defu-6.1.4.tgz", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
"doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
@@ -224,18 +315,60 @@
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="],
"eslint-config-prettier": ["eslint-config-prettier@9.1.2", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ=="],
"eslint-plugin-vue": ["eslint-plugin-vue@9.33.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "globals": "^13.24.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^6.0.15", "semver": "^7.6.3", "vue-eslint-parser": "^9.4.3", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, "sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw=="],
"eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="],
"eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="],
"esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
"flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="],
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
"form-data": ["form-data@4.0.4", "", { "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" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
"fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="],
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
@@ -244,10 +377,20 @@
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="],
"globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
"handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
@@ -260,8 +403,18 @@
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"immutable": ["immutable@5.1.4", "", {}, "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
@@ -270,12 +423,28 @@
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
"is-what": ["is-what@5.5.0", "", {}, "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
"jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
@@ -300,34 +469,68 @@
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
"neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
"number-precision": ["number-precision@1.6.0", "", {}, "sha512-05OLPgbgmnixJw+VvEh18yNPUo3iyp4BEWJcrLu4X9W05KmMifN7Mu5exYvQXqxxeNWhvIF+j3Rij+HmddM/hQ=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"openapi-typescript-codegen": ["openapi-typescript-codegen@0.29.0", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.5.4", "camelcase": "^6.3.0", "commander": "^12.0.0", "fs-extra": "^11.2.0", "handlebars": "^4.7.8" }, "bin": { "openapi": "bin/index.js" } }, "sha512-/wC42PkD0LGjDTEULa/XiWQbv4E9NwLjwLjsaJ/62yOsoYhwvmBR31kPttn1DzQ2OlGe5stACcF/EIkZk43M6w=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
"perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
@@ -340,16 +543,34 @@
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="],
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"resize-observer-polyfill": ["resize-observer-polyfill@1.5.1", "", {}, "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="],
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
"rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
"rolldown": ["rolldown@1.0.0-beta.47", "", { "dependencies": { "@oxc-project/types": "=0.96.0", "@rolldown/pluginutils": "1.0.0-beta.47" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.47", "@rolldown/binding-darwin-arm64": "1.0.0-beta.47", "@rolldown/binding-darwin-x64": "1.0.0-beta.47", "@rolldown/binding-freebsd-x64": "1.0.0-beta.47", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.47", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.47", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.47", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.47", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.47", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.47", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.47", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.47", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.47", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.47" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Mid74GckX1OeFAOYz9KuXeWYhq3xkXbMziYIC+ULVdUzPTG9y70OBSBQDQn9hQP8u/AfhuYw1R0BSg15nBI4Dg=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
"sass": ["sass@1.93.3", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg=="],
@@ -394,14 +615,28 @@
"scroll-into-view-if-needed": ["scroll-into-view-if-needed@2.2.31", "", { "dependencies": { "compute-scroll-into-view": "^1.0.20" } }, "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA=="],
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="],
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"spark-md5": ["spark-md5@3.0.2", "", {}, "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw=="],
"speakingurl": ["speakingurl@14.0.1", "", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="],
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"superjson": ["superjson@2.2.5", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w=="],
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
@@ -410,12 +645,20 @@
"sync-message-port": ["sync-message-port@1.1.3", "", {}, "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg=="],
"text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
@@ -424,6 +667,10 @@
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="],
"vite": ["rolldown-vite@7.2.2", "", { "dependencies": { "@oxc-project/runtime": "0.96.0", "fdir": "^6.5.0", "lightningcss": "^1.30.2", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-beta.47", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "esbuild": "^0.25.0", "jiti": ">=1.21.0", "less": "^4.0.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" }, "optionalPeers": ["@types/node", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Fl3ZdmJhDMJGcqrr342pPVrhugXdOcuNBRBauz4S7QGSRXbQy7y8q5QYJtgkcrG8XjY0EENSZeTk58c3m20FxA=="],
@@ -432,20 +679,42 @@
"vue": ["vue@3.5.24", "", { "dependencies": { "@vue/compiler-dom": "3.5.24", "@vue/compiler-sfc": "3.5.24", "@vue/runtime-dom": "3.5.24", "@vue/server-renderer": "3.5.24", "@vue/shared": "3.5.24" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg=="],
"vue-eslint-parser": ["vue-eslint-parser@9.4.3", "", { "dependencies": { "debug": "^4.3.4", "eslint-scope": "^7.1.1", "eslint-visitor-keys": "^3.3.0", "espree": "^9.3.1", "esquery": "^1.4.0", "lodash": "^4.17.21", "semver": "^7.3.6" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg=="],
"vue-router": ["vue-router@4.6.3", "", { "dependencies": { "@vue/devtools-api": "^6.6.4" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg=="],
"vue-tsc": ["vue-tsc@3.1.3", "", { "dependencies": { "@volar/typescript": "2.4.23", "@vue/language-core": "3.1.3" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "./bin/vue-tsc.js" } }, "sha512-StMNfZHwPIXQgY3KxPKM0Jsoc8b46mDV3Fn2UlHCBIwRJApjqrSwqeMYgWf0zpN+g857y74pv7GWuBm+UqQe1w=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"xml-name-validator": ["xml-name-validator@4.0.0", "", {}, "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="],
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"@parcel/watcher/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
"color-string/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"color/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.47", "", {}, "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw=="],
"vue-router/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"color/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
}
}

1802
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,25 +10,59 @@
"build:prod": "vue-tsc -b && vite build --mode prod",
"preview": "vite preview",
"lint": "eslint . --ext .ts,.vue",
"lint:fix": "eslint . --ext .ts,.vue --fix",
"format": "prettier . --write",
"format:check": "prettier . --check",
"type-check": "vue-tsc --noEmit",
"build:types": "vue-tsc --declaration --emitDeclarationOnly"
},
"eslintConfig": {
"root": true,
"env": {
"browser": true,
"node": true,
"es2022": true
},
"extends": [
"eslint:recommended",
"plugin:vue/vue3-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
"ecmaVersion": "latest",
"sourceType": "module"
}
},
"dependencies": {
"@arco-design/web-vue": "^2.57.0",
"@types/spark-md5": "^3.0.5",
"axios": "^1.13.2",
"json-bigint": "^1.0.0",
"pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"spark-md5": "^3.0.2",
"vue": "^3.5.24",
"vue-router": "4"
},
"devDependencies": {
"@types/json-bigint": "^1.0.4",
"@types/node": "^24.10.0",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-vue": "^9.23.0",
"openapi-typescript-codegen": "^0.29.0",
"prettier": "^3.2.5",
"sass-embedded": "^1.93.3",
"typescript": "~5.9.3",
"vite": "npm:rolldown-vite@7.2.2",
"vue-eslint-parser": "^9.4.3",
"vue-tsc": "^3.1.3"
},
"overrides": {

View File

@@ -1,12 +1,11 @@
import request from "@/plugins/axios";
import type { ApiResponse } from "../response";
import { HttpMethod } from "@/constants";
/**
* 认证服务 API 类型定义
*/
// 登录请求参数
export interface LoginRequest {
userAccount: string;
@@ -24,6 +23,20 @@ export interface LoginResponse {
refreshTokenExpireTime: number | null;
}
/**
* 注册请求参数
*/
export interface RegisterRequest {
userAccount: string;
userPassword: string;
checkPassword: string;
}
/**
* 注册响应数据
*/
export type RegisterResponse = string;
// 刷新令牌响应数据
export interface RefreshTokenResponse {
id: number | null;
@@ -44,8 +57,29 @@ export interface RefreshTokenResponse {
* @param data 登录请求参数
* @returns 登录响应数据
*/
export const login = (data: LoginRequest): Promise<ApiResponse<LoginResponse>> => {
return request.post("/v1/auth/login", data);
export const login = (
data: LoginRequest
): Promise<ApiResponse<LoginResponse>> => {
return request({
url: "/v1/auth/login",
method: HttpMethod.POST,
data,
});
};
/**
* 用户注册
* @param data 注册请求参数
* @returns 注册响应数据
*/
export const register = (
data: RegisterRequest
): Promise<ApiResponse<RegisterResponse>> => {
return request({
url: "/v1/user/register",
method: HttpMethod.POST,
data,
});
};
/**
@@ -53,8 +87,12 @@ export const login = (data: LoginRequest): Promise<ApiResponse<LoginResponse>> =
* @param refreshToken 刷新令牌
* @returns 新的令牌信息
*/
export const refreshToken = (refreshToken: string): Promise<ApiResponse<RefreshTokenResponse>> => {
return request.post("/v1/auth/refresh", null, {
export const refreshToken = (
refreshToken: string
): Promise<ApiResponse<RefreshTokenResponse>> => {
return request({
url: "/v1/auth/refresh",
method: HttpMethod.POST,
params: {
refreshToken,
},
@@ -66,8 +104,14 @@ export const refreshToken = (refreshToken: string): Promise<ApiResponse<RefreshT
* @param data 登录请求参数
* @returns 访问令牌字符串
*/
export const getAccessToken = (data: LoginRequest): Promise<ApiResponse<string>> => {
return request.post("/v1/auth/auth", data);
export const getAccessToken = (
data: LoginRequest
): Promise<ApiResponse<string>> => {
return request({
url: "/v1/auth/auth",
method: HttpMethod.POST,
data,
});
};
/**
@@ -75,5 +119,8 @@ export const getAccessToken = (data: LoginRequest): Promise<ApiResponse<string>>
* @returns 验证结果
*/
export const validateToken = (): Promise<ApiResponse<boolean>> => {
return request.post("/v1/auth/validate");
return request({
url: "/v1/auth/validate",
method: HttpMethod.POST,
});
};

View File

@@ -3,10 +3,13 @@ export { chatAPI, type ChatMessage, type ChatRequest } from '../aiChat'
// 认证 API
export {
login,
register,
refreshToken,
getAccessToken,
validateToken,
type LoginRequest,
type LoginResponse,
type RegisterRequest,
type RegisterResponse,
type RefreshTokenResponse,
} from './auth'

View File

@@ -1,70 +1,190 @@
import request from "@/plugins/axios";
import type { ApiResponse } from "../response";
import { HttpMethod } from "@/constants";
/**
*
*/
/**
* 用户信息
*/
* 用户信息
*/
export interface UserInfo {
/**
* id
*/
id: number;
/**
* id
*/
id: number;
/**
* 用户账号
*/
userAccount: string;
/**
* 用户账号
*/
userAccount: string;
/**
* 开放平台id
*/
unionId?: string;
/**
* 开放平台id
*/
unionId?: string;
/**
* 公众号openId
*/
mpOpenId?: string;
/**
* 公众号openId
*/
mpOpenId?: string;
/**
* 用户昵称
*/
userName: string;
/**
* 用户昵称
*/
userName: string;
/**
* 用户头像 是一个文件id需要去文件服务查询文件详情拿到文件url和当前前端url + “/file/” 拼接起来,才能访问到文件
*/
userAvatar?: string;
/**
* 用户头像
*/
userAvatar?: string;
/**
* 用户邮箱
*/
userEmail?: string;
/**
* 用户简介
*/
userProfile?: string;
/**
* 用户邮箱是否验证
*/
userEmailVerified?: boolean;
/**
* 用户角色user/admin/ban
*/
userRole: 'user' | 'admin' | 'ban';
/**
* 用户简介
*/
userProfile?: string;
/**
* 创建时间
*/
createTime: string;
/**
* 用户角色user/admin/ban
*/
userRole: "user" | "admin" | "ban";
/**
* 更新时间
*/
updateTime: string;
/**
* 创建时间
*/
createTime: string;
/**
* 更新时间
*/
updateTime: string;
}
/**
* 获取当前登录用户信息
* @returns 当前登录用户信息
*/
export const getUserInfoByToken = () => {
return request<ApiResponse<UserInfo>>({
url: "/v1/auth/getUserInfo",
method: "GET",
})
}
export const getUserInfoByToken = (): Promise<ApiResponse<UserInfo>> => {
return request({
url: "/v1/auth/getUserInfo",
method: HttpMethod.GET,
});
};
/**
* 更新用户头像
* @param fileId 文件id
* @returns
*/
export const updateUserAvatar = (
fileId: string,
): Promise<ApiResponse<void>> => {
return request({
url: "/v1/user/avatar",
method: HttpMethod.PUT,
data: {
fileId: fileId,
},
});
};
/**
* 发送邮箱验证码
* @param email 邮箱
* @returns
*/
export const sendEmailVerifyCode = (
email: string,
): Promise<ApiResponse<void>> => {
return request({
url: "/v1/user/email/send-code",
method: HttpMethod.GET,
params: {
email: email,
},
});
};
/**
* 绑定邮箱
* @param email 邮箱
* @param verifyCode 验证码
* @returns
*/
export const bindEmail = (
email: string,
verifyCode: string,
): Promise<ApiResponse<void>> => {
return request({
url: "/v1/user/email/bind",
method: HttpMethod.POST,
data: {
email: email,
verifyCode: verifyCode,
},
});
};
/**
* 解绑邮箱
* @param email 邮箱
* @returns
*/
export const unbindEmail = (email: string): Promise<ApiResponse<void>> => {
return request({
url: "/v1/user/email/unbind",
method: HttpMethod.POST,
data: {
email: email,
},
});
};
/**
* 修改用户密码
* @param oldPassword 旧密码
* @param newPassword 新密码
* @returns
*/
export const updateUserPassword = (
oldPassword: string,
newPassword: string,
): Promise<ApiResponse<void>> => {
return request({
url: "/v1/user/password",
method: HttpMethod.PUT,
data: {
oldPassword: oldPassword,
newPassword: newPassword,
},
});
};
// 更新请求体对象
export interface UpdateUserProfileRequest {
userName?: string;
userProfile?: string;
}
/**
* 更新用户个人信息
* @param userProfile 更新用户个人信息请求体
* @returns
*/
export const updateUserProfile = (
userProfile: UpdateUserProfileRequest,
): Promise<ApiResponse<void>> => {
return request({
url: "/v1/user/profile",
method: HttpMethod.PUT,
data: userProfile,
});
};

125
src/api/file/file.ts Normal file
View File

@@ -0,0 +1,125 @@
import request from "@/plugins/axios";
import type { ApiResponse } from "../response";
import { HttpMethod } from "@/constants";
/**
* 分页查询文件列表
*/
export const getFileList = async(current: number,size: number) =>{
return request({
url: "/v1/file/page",
method: HttpMethod.GET,
params: {
current,
size,
},
});
}
/**
* 根据ID查询文件详情
* @param id 文件id
* @returns
*/
export const getFileById = async (id: string): Promise<ApiResponse<FileListItem>> => {
return request({
url: "/v1/file/" + id,
method: HttpMethod.GET,
});
}
/**
* 检查文件是否已经上传,本地计算文件哈希
* @param hash
* @returns
*/
export const checkHash = async (hash: string) => {
return request({
url: "/v1/file/check",
method: HttpMethod.GET,
params: {
hash,
},
});
};
/**
* 上传文件
* @param file
* @returns
*/
export const uploadFile = async (file: File, hash: string) : Promise<ApiResponse<FileListItem>> => {
const formData = new FormData();
formData.append("file", file);
formData.append("hash", hash);
return request({
url: "/v1/file/upload",
method: HttpMethod.POST,
data: formData,
});
}
/**
* 删除文件
* @param id
* @returns
*/
export const deleteFile = async (id: number) => {
return request({
url: "/v1/file/" + id,
method: HttpMethod.DELETE,
});
}
export interface FileListItem {
/* 文件记录的唯一标识 ID */
id: string;
/* 文件的原始名称(不包含扩展名) */
fileName: string;
/* 文件扩展名,例如 "png"、"jpg"、"pdf" 等 */
fileExtension: string;
/* 文件大小单位为字节Byte */
fileSize: number;
/* 文件内容的哈希值(例如 MD5 或 SHA-256用于校验与秒传 */
fileHash: string;
/* 文件的 MIME 类型,例如 "image/png"、"application/pdf" 等 */
mimeType: string;
/* 文件存储方式类型,例如本地存储、对象存储等 */
storageType: string;
/* 文件在存储系统中的物理或逻辑路径 */
storagePath: string;
/* 业务类型标识,用于区分文件所属的业务场景 */
businessType: string;
/* 关联的业务主键 ID例如订单 ID、用户资料 ID 等 */
businessId: number;
/* 上传该文件的用户 ID */
userId: number;
/* 图片文件的附加信息,例如宽高、分辨率等(非图片可为空) */
imageInfo: string;
/* 逻辑删除标记0 表示未删除1 表示已删除 */
isDeleted: number;
/* 记录创建时间信息 */
createdAt: Record<string, unknown>;
/* 记录最近一次更新时间信息 */
updatedAt: Record<string, unknown>;
}
export type FileListArray = FileListItem[];

View File

@@ -1,6 +1,11 @@
// 统一响应格式
export interface ApiResponse<T = any> {
success: boolean;
message: string;
code?: string | number;
success?: boolean;
message?: string | null;
data: T;
}
}
export const isApiSuccess = (response: ApiResponse<unknown>): boolean => {
return response?.success === true || response?.code === 0 || response?.code === "0";
};

View File

@@ -41,7 +41,12 @@
<a-dropdown v-else trigger="hover" position="br">
<div class="user-info">
<a-avatar :size="36" class="user-avatar">
<IconUser />
<img
v-if="avatarUrl"
:src="avatarUrl"
alt="avatar"
/>
<IconUser v-else />
</a-avatar>
<span class="username">{{ userStore.loginUser.userName }}</span>
<icon-down class="dropdown-icon" />
@@ -70,7 +75,7 @@
<script setup lang="ts">
import { routes } from "../router/index.ts";
import { useRouter } from "vue-router";
import { computed, ref } from "vue";
import { computed, ref, watch } from "vue";
import { Message } from "@arco-design/web-vue";
import {
IconUser,
@@ -82,13 +87,42 @@ import checkAccess from "../access/checkAccess.ts";
import { useUserStore } from "../store/user.ts";
import { clearTokens } from "@/utils/token";
import ACCESS_ENUM from "@/access/accessEnum";
import { getFileById } from "@/api/file/file";
import { isApiSuccess } from "@/api/response";
const router = useRouter();
const userStore = useUserStore();
const avatarUrl = ref("");
// 默认主页
const selectedKeys = ref(["/"]);
// 加载用户头像
const loadAvatarUrl = async () => {
if (!userStore.loginUser.userAvatar) {
avatarUrl.value = "";
return;
}
try {
const res = await getFileById(userStore.loginUser.userAvatar);
if (isApiSuccess(res) && res.data && res.data.storagePath) {
// 拼接 /file/ 前缀,通过 vite 代理访问后端静态资源
avatarUrl.value = `/api/file/${res.data.storagePath}`;
}
} catch (error) {
console.error("加载头像失败", error);
}
};
// 监听用户头像变化
watch(
() => userStore.loginUser.userAvatar,
() => {
loadAvatarUrl();
},
{ immediate: true }
);
const doMenuItemClick = (path: string) => {
router.push({ path });
};
@@ -120,7 +154,7 @@ const goToRegister = () => {
// 跳转到个人中心
const goToProfile = () => {
Message.info("个人中心功能开发中");
router.push("/user/profile");
};
// 跳转到设置页
@@ -131,10 +165,7 @@ const goToSettings = () => {
// 退出登录
const handleLogout = () => {
clearTokens();
userStore.updateUserLoginStatus({
userName: "未登录",
userRole: ACCESS_ENUM.NOT_LOGIN,
});
userStore.logout();
Message.success("已退出登录");
router.push("/home");
};
@@ -308,6 +339,11 @@ router.afterEach((to) => {
.user-avatar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
:deep(img) {
border-radius: 50%;
object-fit: cover;
}
}
.username {

15
src/constants/http.ts Normal file
View File

@@ -0,0 +1,15 @@
/**
* HTTP 请求方法常量
*/
export const HttpMethod = {
GET: 'GET',
POST: 'POST',
PUT: 'PUT',
DELETE: 'DELETE',
PATCH: 'PATCH',
HEAD: 'HEAD',
OPTIONS: 'OPTIONS',
} as const;
// 导出类型以供使用
export type HttpMethod = (typeof HttpMethod)[keyof typeof HttpMethod];

1
src/constants/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './http';

View File

@@ -10,12 +10,98 @@ import {
clearTokens,
} from "@/utils/token";
import { Message } from "@arco-design/web-vue";
import { isApiSuccess } from "@/api/response";
import {
ErrorCode,
ErrorMessages,
isNotLoginError,
isNoAuthError,
isParamsError,
isServerError,
} from "@/types/errorCode";
// 扩展 Axios 请求配置类型
declare module "axios" {
interface InternalAxiosRequestConfig {
_retry?: boolean;
}
}
// 是否正在刷新 token
let isRefreshing = false;
// 存储因为 token 过期而挂起的请求
let requests: ((token: string) => void)[] = [];
// 统一的刷新 token 并重试请求的函数
const refreshTokenAndRetry = async (originalRequest: InternalAxiosRequestConfig) => {
// 如果正在刷新,将当前请求加入队列等待
if (isRefreshing) {
return new Promise((resolve) => {
requests.push((token: string) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
resolve(request(originalRequest));
});
});
}
originalRequest._retry = true;
isRefreshing = true;
try {
const refreshToken = getRefreshToken();
if (!refreshToken) {
throw new Error("No refresh token available");
}
// 使用原生 axios 发送刷新请求,避免拦截器循环
const response = await axios.post(
`${ENV.API_BASE_URL}/v1/auth/refresh`,
null,
{
params: { refreshToken },
}
);
if (isApiSuccess(response.data)) {
const { accessToken, refreshToken: newRefreshToken } =
response.data.data;
// 更新本地存储
setTokens(accessToken, newRefreshToken);
// 执行队列中的请求
requests.forEach((cb) => cb(accessToken));
requests = [];
// 重试当前请求
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
return request(originalRequest);
} else {
throw new Error("Refresh token failed");
}
} catch (refreshError) {
console.error("Token 刷新失败:", refreshError);
// 清除过期 token
clearTokens();
// 动态导入 userStore确保 Pinia 已初始化
import("@/store/user").then(({ useUserStore }) => {
const userStore = useUserStore();
userStore.logout();
});
Message.error("登录已过期,请重新登录");
// 跳转到登录页
setTimeout(() => {
window.location.href = "/user/login";
}, 1000);
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
}
};
const request: AxiosInstance = axios.create({
baseURL: ENV.API_BASE_URL,
timeout: 10000,
@@ -43,80 +129,58 @@ request.interceptors.request.use(
// =======================
request.interceptors.response.use(
(response) => {
// console.log("响应: ", response);
const data = response.data;
/**TODO: 增加响应码处理 */
const originalRequest = response.config;
const code = data?.code;
// 未登录错误 (40100)
if (isNotLoginError(code)) {
clearTokens();
import("@/store/user").then(({ useUserStore }) => {
const userStore = useUserStore();
userStore.logout();
});
Message.error(data?.message || ErrorMessages[ErrorCode.NOT_LOGIN_ERROR]);
setTimeout(() => {
window.location.href = "/user/login";
}, 1000);
return Promise.reject(new Error(data?.message || ErrorMessages[ErrorCode.NOT_LOGIN_ERROR]));
}
// 无权限/token 过期错误 (40101) - 尝试刷新 token
if (isNoAuthError(code) && !originalRequest._retry) {
return refreshTokenAndRetry(originalRequest);
}
// 参数错误 (40000)
if (isParamsError(code)) {
Message.warning(data?.message || ErrorMessages[ErrorCode.PARAMS_ERROR]);
return data;
}
// 服务端错误 (5xxxx)
if (isServerError(code)) {
Message.error(data?.message || ErrorMessages[ErrorCode.SYSTEM_ERROR]);
return data;
}
return data;
},
async (error) => {
const originalRequest = error.config;
// 处理 401 未授权情况 (Token 过期)
// 处理 HTTP 401 未授权情况 (Token 过期)
// 确保不是刷新 token 的请求本身 (避免死循环)
if (
error.response?.status === 401 &&
!originalRequest._retry &&
!originalRequest.url.includes("/auth/refresh")
) {
// 如果正在刷新,将当前请求加入队列等待
if (isRefreshing) {
return new Promise((resolve) => {
requests.push((token: string) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
resolve(request(originalRequest));
});
});
}
originalRequest._retry = true;
isRefreshing = true;
try {
const refreshToken = getRefreshToken();
if (!refreshToken) {
throw new Error("No refresh token available");
}
// 使用原生 axios 发送刷新请求,避免拦截器循环
const response = await axios.post(
`${ENV.API_BASE_URL}/v1/auth/refresh`,
null,
{
params: { refreshToken },
}
);
if (response.data?.success) {
const { accessToken, refreshToken: newRefreshToken } =
response.data.data;
// 更新本地存储
setTokens(accessToken, newRefreshToken);
// 执行队列中的请求
requests.forEach((cb) => cb(accessToken));
requests = [];
// 重试当前请求
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
return request(originalRequest);
} else {
throw new Error("Refresh token failed");
}
} catch (refreshError) {
console.error("Token 刷新失败:", refreshError);
// 清除过期 token
clearTokens();
Message.error("登录已过期,请重新登录");
// 这里可以选择跳转到登录页,例如 window.location.href = '/user/login'
// 建议让路由守卫或者页面自行处理未登录状态
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
}
return refreshTokenAndRetry(originalRequest);
}
/**
* Promise.reject(error) 用来 返回一个状态为 rejected 的 Promise相当于主动抛出错误
* Promise.reject(error) 用来 返回一个状态为 rejected 的 Promise相当于"主动抛出错误"
*
* 让调用者能够在 .catch() 或 try/catch 中捕获这个错误。
* 它在 异步流程、拦截器、错误处理 中非常常见。

View File

@@ -4,11 +4,18 @@ import AboutView from "../views/AboutView.vue";
import ACCESS_ENUM from "../access/accessEnum";
import UserLoginView from "../views/user/UserLoginView.vue";
import UserRegisterView from "../views/user/UserRegisterView.vue";
import UserProfileView from "../views/user/UserProfileView.vue";
import AiChatView from "../views/ai/AiChatView.vue";
/**
* 路由配置
*/
export const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "root",
redirect: "/home",
meta: { hideInMenu: true },
},
{
path: "/home",
name: "HomeView",
@@ -39,4 +46,10 @@ export const routes: Array<RouteRecordRaw> = [
component: UserRegisterView,
meta: { hideInMenu: true, access: ACCESS_ENUM.NOT_LOGIN },
},
{
path: "/user/profile",
name: "UserProfileView",
component: UserProfileView,
meta: { hideInMenu: true, access: ACCESS_ENUM.USER },
},
];

15
src/store/types.d.ts vendored
View File

@@ -1,5 +1,16 @@
export interface LoginUesr {
userName: string;
id?: number;
userName?: string;
userAccount?: string;
userAvatar?: string;
userRole?: string;
}
userProfile?: string;
userEmail?: string;
userPhone?: string;
unionId?: string;
mpOpenId?: string;
createTime?: string;
updateTime?: string;
[key: string]: any;
}

View File

@@ -2,6 +2,7 @@ import { defineStore } from "pinia";
import ACCESS_ENUM from "../access/accessEnum";
import type { LoginUesr } from "../store/types";
import { getUserInfoByToken } from "@/api/auth/user";
import { isApiSuccess } from "@/api/response";
/**
*
*/
@@ -23,10 +24,10 @@ export const useUserStore = defineStore("user", {
try {
const res = await getUserInfoByToken();
console.log("获取登录用户成功", res);
if(res.data.success === true){
this.updateUserLoginStatus(res.data.data);
if (isApiSuccess(res)) {
this.updateUserLoginStatus(res.data);
}
}catch(e) {
} catch (e) {
console.error("获取登录用户失败", e);
// 网络错误情况也视为未登录
this.loginUser = {
@@ -38,6 +39,13 @@ export const useUserStore = defineStore("user", {
// 手动更新用户状态
updateUserLoginStatus(user: LoginUesr) {
this.loginUser = user;
},
// 退出登录(清除登录状态)
logout() {
this.loginUser = {
userName: "未登录",
userRole: ACCESS_ENUM.NOT_LOGIN,
};
}
},
});

80
src/types/errorCode.ts Normal file
View File

@@ -0,0 +1,80 @@
/**
* 错误码常量
* 与后端保持一致
*/
export const ErrorCode = {
// 成功
SUCCESS: "0",
// 客户端错误 4xxxx
PARAMS_ERROR: "40000", // 请求参数错误
NOT_LOGIN_ERROR: "40100", // 未登录
NO_AUTH_ERROR: "40101", // 无权限
NOT_FOUND_ERROR: "40400", // 请求数据不存在
FORBIDDEN_ERROR: "40300", // 禁止访问
// 服务端错误 5xxxx
SYSTEM_ERROR: "50000", // 系统内部异常
OPERATION_ERROR: "50001", // 操作失败
API_REQUEST_ERROR: "50010", // 接口调用失败
} as const;
/**
* 错误码类型
*/
export type ErrorCodeType = (typeof ErrorCode)[keyof typeof ErrorCode];
/**
* 错误码对应的消息映射
* 用于当后端未返回 message 时使用
*/
export const ErrorMessages: Record<string, string> = {
[ErrorCode.SUCCESS]: "操作成功",
[ErrorCode.PARAMS_ERROR]: "请求参数错误",
[ErrorCode.NOT_LOGIN_ERROR]: "未登录,请先登录",
[ErrorCode.NO_AUTH_ERROR]: "无权限访问",
[ErrorCode.NOT_FOUND_ERROR]: "请求的数据不存在",
[ErrorCode.FORBIDDEN_ERROR]: "禁止访问",
[ErrorCode.SYSTEM_ERROR]: "系统内部异常,请稍后重试",
[ErrorCode.OPERATION_ERROR]: "操作失败,请稍后重试",
[ErrorCode.API_REQUEST_ERROR]: "接口调用失败,请稍后重试",
};
/**
* 判断是否为成功错误码
*/
export const isSuccessCode = (code: string | number | undefined): boolean => {
return code === ErrorCode.SUCCESS || code === 0;
};
/**
* 判断是否为未登录错误码
*/
export const isNotLoginError = (code: string | number | undefined): boolean => {
return code === ErrorCode.NOT_LOGIN_ERROR;
};
/**
* 判断是否为无权限错误码
*/
export const isNoAuthError = (code: string | number | undefined): boolean => {
return code === ErrorCode.NO_AUTH_ERROR;
};
/**
* 判断是否为参数错误
*/
export const isParamsError = (code: string | number | undefined): boolean => {
return code === ErrorCode.PARAMS_ERROR;
};
/**
* 判断是否为服务端错误 (5xxxx)
*/
export const isServerError = (code: string | number | undefined): boolean => {
return (
code === ErrorCode.SYSTEM_ERROR ||
code === ErrorCode.OPERATION_ERROR ||
code === ErrorCode.API_REQUEST_ERROR
);
};

View File

@@ -78,6 +78,7 @@ import { login } from "@/api/auth/auth";
import { setTokens } from "@/utils/token";
import { useUserStore } from "@/store/user";
import ACCESS_ENUM from "@/access/accessEnum";
import { isApiSuccess } from "@/api/response";
const router = useRouter();
const userStore = useUserStore();
@@ -114,7 +115,7 @@ const handleSubmit = async (data: any) => {
userPassword: form.userPassword,
});
if (response.success) {
if (isApiSuccess(response)) {
Message.success("登录成功!");
// 存储 token

View File

@@ -0,0 +1,860 @@
<template>
<div id="userProfileView">
<a-row :gutter="24">
<!-- 左侧个人信息展示 -->
<a-col :span="8" :xs="24" :sm="24" :md="8">
<a-card title="个人名片" :bordered="false" class="profile-card">
<div class="profile-left">
<a-upload
action="/"
:custom-request="customUpload"
:show-file-list="false"
:auto-upload="true"
accept="image/*"
>
<template #upload-button>
<div class="avatar-wrapper">
<a-avatar :size="120" trigger-type="mask">
<img
v-if="avatarUrl"
:src="avatarUrl"
alt="avatar"
/>
<span v-else class="avatar-text">{{
loginUser.userName?.charAt(0)?.toUpperCase()
}}</span>
<template #trigger-icon>
<icon-camera />
</template>
</a-avatar>
<div class="avatar-hint">点击更换头像</div>
</div>
</template>
</a-upload>
<div class="user-name">{{ loginUser.userName }}</div>
<div class="user-role">
<a-tag :color="roleColor">{{ roleText }}</a-tag>
</div>
<a-divider />
<a-descriptions :data="staticData" :column="1" size="medium" />
</div>
</a-card>
</a-col>
<!-- 右侧编辑表单和账号安全 -->
<a-col :span="16" :xs="24" :sm="24" :md="16">
<a-space direction="vertical" size="large" fill>
<!-- 编辑资料卡片 -->
<a-card title="编辑资料" :bordered="false" class="edit-card">
<a-form
:model="formData"
:label-col-props="{ span: 4 }"
:wrapper-col-props="{ span: 20 }"
layout="horizontal"
@submit="handleSubmit"
>
<a-form-item label="昵称" field="userName">
<a-input
v-model="formData.userName"
placeholder="请输入昵称"
max-length="20"
show-word-limit
/>
</a-form-item>
<a-form-item label="个人简介" field="userProfile">
<a-textarea
v-model="formData.userProfile"
placeholder="介绍一下自己..."
:max-length="200"
show-word-limit
:auto-size="{ minRows: 4, maxRows: 8 }"
/>
</a-form-item>
<a-form-item :wrapper-col-props="{ offset: 4, span: 20 }">
<a-space>
<a-button type="primary" html-type="submit" :loading="loading">
保存修改
</a-button>
<a-button @click="handleReset">重置</a-button>
</a-space>
</a-form-item>
</a-form>
</a-card>
<!-- 账号安全卡片 -->
<a-card title="账号安全" :bordered="false" class="security-card">
<div class="security-list">
<!-- 修改密码 -->
<div class="security-item">
<div class="security-item-left">
<icon-lock class="security-icon" />
<span class="security-label">登录密码</span>
</div>
<span class="security-status">已设置</span>
<a class="security-action" @click="showPasswordModal = true">修改密码</a>
</div>
<!-- 绑定手机 -->
<div class="security-item">
<div class="security-item-left">
<icon-phone class="security-icon" />
<span class="security-label">手机号</span>
</div>
<span class="security-status">
{{ loginUser.userPhone ? maskPhone(loginUser.userPhone) : '未绑定' }}
</span>
<a class="security-action" @click="showPhoneModal = true">
{{ loginUser.userPhone ? '更换手机' : '绑定手机' }}
</a>
</div>
<!-- 绑定邮箱 -->
<div class="security-item">
<div class="security-item-left">
<icon-email class="security-icon" />
<span class="security-label">邮箱</span>
</div>
<span class="security-status">
{{ loginUser.userEmail ? maskEmail(loginUser.userEmail) : '未绑定' }}
</span>
<a class="security-action" @click="showEmailModal = true">
{{ loginUser.userEmail ? '更换邮箱' : '绑定邮箱' }}
</a>
</div>
<!-- 微信绑定 -->
<div class="security-item">
<div class="security-item-left">
<icon-wechat class="security-icon wechat-color" />
<span class="security-label">微信</span>
</div>
<span class="security-status">
{{ loginUser.mpOpenId ? '已授权绑定微信账号' : '未绑定' }}
</span>
<a
v-if="!loginUser.mpOpenId"
class="security-action"
@click="handleBindWechat"
>
绑定
</a>
<a v-else class="security-action" @click="handleUnbindWechat">解除绑定</a>
</div>
</div>
</a-card>
</a-space>
</a-col>
</a-row>
<!-- 修改密码弹窗 -->
<a-modal
v-model:visible="showPasswordModal"
title="修改密码"
@before-ok="handlePasswordSubmit"
@cancel="handlePasswordReset"
:ok-loading="passwordLoading"
>
<a-form :model="passwordForm" layout="vertical">
<a-form-item label="当前密码" field="oldPassword">
<a-input-password
v-model="passwordForm.oldPassword"
placeholder="请输入当前密码"
max-length="20"
/>
</a-form-item>
<a-form-item label="新密码" field="newPassword">
<a-input-password
v-model="passwordForm.newPassword"
placeholder="请输入新密码6-20位"
max-length="20"
/>
</a-form-item>
<a-form-item label="确认密码" field="confirmPassword">
<a-input-password
v-model="passwordForm.confirmPassword"
placeholder="请再次输入新密码"
max-length="20"
/>
</a-form-item>
</a-form>
</a-modal>
<!-- 绑定手机弹窗 -->
<a-modal
v-model:visible="showPhoneModal"
:title="loginUser.userPhone ? '更换手机号' : '绑定手机号'"
@before-ok="handlePhoneSubmit"
@cancel="handlePhoneReset"
:ok-loading="phoneLoading"
>
<a-form :model="phoneForm" layout="vertical">
<a-form-item label="手机号" field="phone">
<a-input
v-model="phoneForm.phone"
placeholder="请输入手机号"
max-length="11"
/>
</a-form-item>
<a-form-item label="验证码" field="code">
<a-input-group style="width: 100%">
<a-input
v-model="phoneForm.code"
placeholder="请输入验证码"
:max-length="6"
/>
<a-button
type="outline"
:loading="codeSending"
:disabled="phoneCountdown > 0 || !phoneForm.phone"
@click="handleSendPhoneCode"
>
{{ phoneCountdown > 0 ? `${phoneCountdown}秒后重试` : '发送验证码' }}
</a-button>
</a-input-group>
</a-form-item>
</a-form>
</a-modal>
<!-- 绑定邮箱弹窗 -->
<a-modal
v-model:visible="showEmailModal"
:title="loginUser.userEmail ? '更换邮箱' : '绑定邮箱'"
@before-ok="handleEmailSubmit"
@cancel="handleEmailReset"
:ok-loading="emailLoading"
>
<a-form :model="emailForm" layout="vertical">
<a-form-item label="邮箱地址" field="email">
<a-input
v-model="emailForm.email"
placeholder="请输入邮箱地址"
type="email"
/>
</a-form-item>
<a-form-item label="验证码" field="code">
<a-input-group style="width: 100%">
<a-input
v-model="emailForm.code"
placeholder="请输入验证码"
:max-length="6"
/>
<a-button
type="outline"
:loading="codeSending"
:disabled="emailCountdown > 0 || !emailForm.email"
@click="handleSendEmailCode"
>
{{ emailCountdown > 0 ? `${emailCountdown}秒后重试` : '发送验证码' }}
</a-button>
</a-input-group>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch, reactive } from 'vue';
import { useUserStore } from '@/store/user';
import { uploadFile, getFileById } from '@/api/file/file';
import {
updateUserAvatar,
updateUserPassword,
updateUserProfile,
sendEmailVerifyCode,
bindEmail,
} from '@/api/auth/user';
import { isApiSuccess } from '@/api/response';
import { Message } from '@arco-design/web-vue';
import {
IconCamera,
IconLock,
IconPhone,
IconEmail,
IconWechat
} from '@arco-design/web-vue/es/icon';
import * as SparkMD5 from 'spark-md5';
import type { RequestOption } from '@arco-design/web-vue/es/upload/interfaces';
const userStore = useUserStore();
const loginUser = computed(() => userStore.loginUser);
const avatarUrl = ref('');
const loading = ref(false);
const passwordLoading = ref(false);
const phoneLoading = ref(false);
const emailLoading = ref(false);
const codeSending = ref(false);
// 验证码倒计时
const emailCountdown = ref(0);
const phoneCountdown = ref(0);
let emailTimer: number | null = null;
let phoneTimer: number | null = null;
// 弹窗显示状态
const showPasswordModal = ref(false);
const showPhoneModal = ref(false);
const showEmailModal = ref(false);
// 表单数据
const formData = reactive({
userName: '',
userProfile: '',
});
// 修改密码表单
const passwordForm = reactive({
oldPassword: '',
newPassword: '',
confirmPassword: '',
});
// 手机号表单
const phoneForm = reactive({
phone: '',
code: '',
});
// 邮箱表单
const emailForm = reactive({
email: '',
code: '',
});
// 角色颜色
const roleColor = computed(() => {
const role = loginUser.value.userRole;
if (role === 'admin') return 'red';
if (role === 'user') return 'arcoblue';
return 'gray';
});
// 角色文字
const roleText = computed(() => {
const role = loginUser.value.userRole;
if (role === 'admin') return '管理员';
if (role === 'user') return '普通用户';
if (role === 'ban') return '被封禁';
return '未知';
});
// 静态数据(不可编辑)
const staticData = computed(() => [
{ label: '用户ID', value: loginUser.value.id?.toString() || '-' },
{ label: '账号', value: loginUser.value.userAccount || '-' },
{ label: '注册时间', value: loginUser.value.createTime || '-' },
]);
// 手机号脱敏
const maskPhone = (phone: string) => {
if (!phone) return '';
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
};
// 邮箱脱敏
const maskEmail = (email: string) => {
if (!email) return '';
const parts = email.split('@');
if (parts.length !== 2 || !parts[0] || !parts[1]) return email;
const [name = '', domain = ''] = parts;
if (name.length <= 2) return email;
return `${name.slice(0, 2)}***@${domain}`;
};
// 加载用户头像
const loadAvatarUrl = async () => {
if (!loginUser.value.userAvatar) {
avatarUrl.value = '';
return;
}
try {
const res = await getFileById(loginUser.value.userAvatar);
if (isApiSuccess(res) && res.data && res.data.storagePath) {
avatarUrl.value = `/api/file/${res.data.storagePath}`;
}
} catch (error) {
console.error('加载头像失败', error);
}
};
// 初始化表单数据
const initFormData = () => {
formData.userName = loginUser.value.userName || '';
formData.userProfile = loginUser.value.userProfile || '';
};
// 监听用户头像变化
watch(() => loginUser.value.userAvatar, () => {
loadAvatarUrl();
}, { immediate: true });
// 监听用户信息变化,更新表单
watch(() => loginUser.value, () => {
initFormData();
}, { immediate: true, deep: true });
// 头像上传
const customUpload = async (option: RequestOption) => {
const { fileItem, onSuccess, onError } = option;
if (!fileItem.file) return;
try {
const hash = await computeFileHash(fileItem.file);
const res = await uploadFile(fileItem.file, hash);
if (isApiSuccess(res) && res.data) {
const fileId: string = String(res.data.id);
if (!fileId) {
throw new Error('上传返回数据缺少文件ID');
}
const updateRes = await updateUserAvatar(fileId);
if (isApiSuccess(updateRes)) {
Message.success('头像更新成功');
await userStore.getLoginUser();
onSuccess(res);
} else {
throw new Error(updateRes.message || '更新头像失败');
}
} else {
throw new Error(res.message || '上传失败');
}
} catch (err: any) {
Message.error('上传出错: ' + err.message);
onError(err);
}
};
const computeFileHash = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
const spark = new SparkMD5.ArrayBuffer();
fileReader.onload = (e) => {
if (e.target?.result) {
spark.append(e.target.result as ArrayBuffer);
resolve(spark.end());
} else {
reject(new Error('File read failed'));
}
};
fileReader.onerror = () => reject(new Error('File read failed'));
fileReader.readAsArrayBuffer(file);
});
};
// 提交资料表单
const handleSubmit = async () => {
try {
loading.value = true;
const res = await updateUserProfile({
userName: formData.userName,
userProfile: formData.userProfile,
});
if (isApiSuccess(res)) {
Message.success('资料更新成功');
await userStore.getLoginUser();
} else {
Message.error(res.message || '更新失败');
}
} catch (err: any) {
Message.error('更新出错: ' + (err.message || '未知错误'));
} finally {
loading.value = false;
}
};
// 重置资料表单
const handleReset = () => {
initFormData();
Message.info('已重置');
};
// 修改密码
const handlePasswordSubmit = async () => {
if (!passwordForm.oldPassword) {
Message.warning('请输入当前密码');
return false;
}
if (!passwordForm.newPassword) {
Message.warning('请输入新密码');
return false;
}
if (passwordForm.newPassword.length < 6 || passwordForm.newPassword.length > 20) {
Message.warning('新密码长度应为6-20位');
return false;
}
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
Message.warning('两次输入的密码不一致');
return false;
}
if (passwordForm.oldPassword === passwordForm.newPassword) {
Message.warning('新密码不能与当前密码相同');
return false;
}
try {
passwordLoading.value = true;
const res = await updateUserPassword(
passwordForm.oldPassword,
passwordForm.newPassword
);
if (isApiSuccess(res)) {
Message.success('密码修改成功,请重新登录');
handlePasswordReset();
// 修改密码后跳转到登录页
setTimeout(() => {
window.location.href = '/user/login';
}, 1500);
return true;
} else {
Message.error(res.message || '修改密码失败');
return false;
}
} catch (err: any) {
Message.error('修改密码出错: ' + (err.message || '未知错误'));
return false;
} finally {
passwordLoading.value = false;
}
};
// 重置密码表单
const handlePasswordReset = () => {
passwordForm.oldPassword = '';
passwordForm.newPassword = '';
passwordForm.confirmPassword = '';
};
// 绑定手机号
const handlePhoneSubmit = async () => {
if (!phoneForm.phone) {
Message.warning('请输入手机号');
return false;
}
if (!/^1[3-9]\d{9}$/.test(phoneForm.phone)) {
Message.warning('请输入正确的手机号');
return false;
}
if (!phoneForm.code) {
Message.warning('请输入验证码');
return false;
}
// TODO: 等后端接口提供后对接
Message.info('绑定手机号接口开发中...');
console.log('绑定手机号:', phoneForm);
return false;
};
// 发送手机验证码
const handleSendPhoneCode = async () => {
if (!phoneForm.phone) {
Message.warning('请先输入手机号');
return;
}
if (!/^1[3-9]\d{9}$/.test(phoneForm.phone)) {
Message.warning('请输入正确的手机号');
return;
}
// TODO: 等后端接口提供后对接
Message.info('发送验证码接口开发中...');
// 模拟倒计时(实际对接后端后移到成功回调中)
phoneCountdown.value = 60;
if (phoneTimer) clearInterval(phoneTimer);
phoneTimer = window.setInterval(() => {
phoneCountdown.value--;
if (phoneCountdown.value <= 0) {
if (phoneTimer) clearInterval(phoneTimer);
phoneTimer = null;
}
}, 1000);
};
// 重置手机表单
const handlePhoneReset = () => {
phoneForm.phone = '';
phoneForm.code = '';
// 清理倒计时
if (phoneTimer) {
clearInterval(phoneTimer);
phoneTimer = null;
}
phoneCountdown.value = 0;
};
// 绑定邮箱
const handleEmailSubmit = async () => {
if (!emailForm.email) {
Message.warning('请输入邮箱');
return false;
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailForm.email)) {
Message.warning('请输入正确的邮箱地址');
return false;
}
if (!emailForm.code) {
Message.warning('请输入验证码');
return false;
}
try {
emailLoading.value = true;
const res = await bindEmail(emailForm.email, emailForm.code);
if (isApiSuccess(res)) {
Message.success('邮箱绑定成功');
handleEmailReset();
await userStore.getLoginUser();
return true;
} else {
Message.error(res.message || '绑定失败');
return false;
}
} catch (err: any) {
Message.error('绑定邮箱出错: ' + (err.message || '未知错误'));
return false;
} finally {
emailLoading.value = false;
}
};
// 发送邮箱验证码
const handleSendEmailCode = async () => {
if (!emailForm.email) {
Message.warning('请先输入邮箱');
return;
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailForm.email)) {
Message.warning('请输入正确的邮箱地址');
return;
}
try {
codeSending.value = true;
const res = await sendEmailVerifyCode(emailForm.email);
if (isApiSuccess(res)) {
Message.success('验证码已发送,请查收邮箱');
// 开始倒计时
emailCountdown.value = 60;
if (emailTimer) clearInterval(emailTimer);
emailTimer = window.setInterval(() => {
emailCountdown.value--;
if (emailCountdown.value <= 0) {
if (emailTimer) clearInterval(emailTimer);
emailTimer = null;
}
}, 1000);
} else {
Message.error(res.message || '发送失败');
}
} catch (err: any) {
Message.error('发送验证码出错: ' + (err.message || '未知错误'));
} finally {
codeSending.value = false;
}
};
// 重置邮箱表单
const handleEmailReset = () => {
emailForm.email = '';
emailForm.code = '';
// 清理倒计时
if (emailTimer) {
clearInterval(emailTimer);
emailTimer = null;
}
emailCountdown.value = 0;
};
// 绑定微信
const handleBindWechat = () => {
// TODO: 等后端接口提供后对接
Message.info('微信绑定接口开发中...');
};
// 解绑微信
const handleUnbindWechat = () => {
// TODO: 等后端接口提供后对接
Message.info('微信解绑接口开发中...');
};
onMounted(() => {
userStore.getLoginUser();
});
// 清理定时器
onUnmounted(() => {
if (emailTimer) {
clearInterval(emailTimer);
emailTimer = null;
}
if (phoneTimer) {
clearInterval(phoneTimer);
phoneTimer = null;
}
});
</script>
<style scoped lang="scss">
#userProfileView {
max-width: 1200px;
margin: 0 auto;
padding: 24px;
}
.profile-card {
height: 100%;
.profile-left {
display: flex;
flex-direction: column;
align-items: center;
}
.avatar-wrapper {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
transition: all 0.3s;
&:hover {
opacity: 0.8;
.avatar-hint {
opacity: 1;
}
}
:deep(img) {
border-radius: 50%;
object-fit: cover;
}
.avatar-text {
font-size: 48px;
font-weight: bold;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.avatar-hint {
margin-top: 8px;
font-size: 12px;
color: #86909c;
opacity: 0;
transition: opacity 0.3s;
}
}
.user-name {
margin-top: 20px;
font-size: 22px;
font-weight: 600;
color: #1d2129;
}
.user-role {
margin-top: 8px;
}
}
.edit-card,
.security-card {
:deep(.arco-form-item) {
margin-bottom: 24px;
}
:deep(.arco-input-wrapper),
:deep(.arco-textarea) {
border-radius: 8px;
}
// 验证码输入组样式
:deep(.arco-input-group) {
.arco-input {
flex: 1;
}
.arco-btn {
flex-shrink: 0;
min-width: 100px;
}
}
}
.security-card {
.security-list {
padding: 0;
}
.security-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.security-item-left {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.security-icon {
font-size: 20px;
color: #4e5969;
&.wechat-color {
color: #07c160;
}
}
.security-label {
font-size: 15px;
color: #1d2129;
font-weight: 500;
}
.security-status {
font-size: 14px;
color: #86909c;
margin-right: 24px;
}
.security-action {
font-size: 14px;
color: #165dff;
cursor: pointer;
white-space: nowrap;
&:hover {
color: #4080ff;
text-decoration: underline;
}
}
}
}
// 响应式设计
@media (max-width: 768px) {
#userProfileView {
padding: 16px;
}
.profile-card,
.edit-card,
.security-card {
margin-bottom: 16px;
}
}
</style>

View File

@@ -94,6 +94,8 @@ import { reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { Message } from "@arco-design/web-vue";
import { IconUser, IconLock } from "@arco-design/web-vue/es/icon";
import { register } from "@/api/auth/auth";
import { isApiSuccess } from "@/api/response";
const router = useRouter();
@@ -151,27 +153,18 @@ const handleSubmit = async (data: any) => {
loading.value = true;
try {
// TODO: 调用注册 API
// 目前后端文档中没有注册接口,这里模拟注册成功
await new Promise((resolve) => setTimeout(resolve, 1000));
const res = await register({
userAccount: form.userAccount,
userPassword: form.userPassword,
checkPassword: form.confirmPassword,
});
Message.success("注册成功!请登录");
// 跳转到登录页
router.push("/user/login");
// 实际实现时应该调用后端注册接口:
// const response = await register({
// userAccount: form.userAccount,
// userPassword: form.userPassword,
// });
//
// if (response.success) {
// Message.success("注册成功!请登录");
// router.push("/user/login");
// } else {
// Message.error(response.message || "注册失败");
// }
if (isApiSuccess(res)) {
Message.success("注册成功!请登录");
router.push("/user/login");
} else {
Message.error(res.message || "注册失败");
}
} catch (error: any) {
console.error("注册失败:", error);
Message.error(error.response?.data?.message || "注册失败,请稍后重试");

View File

@@ -28,12 +28,17 @@ export default defineConfig(({ command, mode }) => {
proxy: {
// 代理所有 /api 开头的请求
'/api': {
target: 'http://localhost:8085', // 后端服务器地址
target: 'http://localhost:18085', // 后端服务器地址
changeOrigin: true, // 改变请求头中的 origin
secure: false, // 支持 https
// 如果后端 API 路径不包含 /api可以重写路径
// rewrite: (path) => path.replace(/^\/api/, ''),
},
'/file': {
target: 'http://localhost:18085',
changeOrigin: true,
secure: false,
},
},
},
};