From 5457ce7cf4d979387a81104917c93b1b5c20e04d Mon Sep 17 00:00:00 2001 From: super Date: Sun, 28 Dec 2025 22:09:44 +0800 Subject: [PATCH] first commit --- .env.development | 2 + .env.production | 2 + .gitignore | 24 + README.md | 80 + index.html | 13 + package-lock.json | 2947 +++++++++++++++++ package.json | 38 + public/vite.svg | 1 + src/App.vue | 17 + src/assets/vue.svg | 1 + src/auto-imports.d.ts | 91 + src/components.d.ts | 76 + src/components/ChatConversation.vue | 385 +++ src/components/HelloWorld.vue | 41 + src/components/RichTextEditor.vue | 144 + src/config/index.ts | 4 + src/config/project.ts | 324 ++ src/layouts/EmbeddedLayout.vue | 23 + src/layouts/MainLayout.vue | 368 ++ src/main.ts | 15 + src/mock/article.ts | 178 + src/mock/certification.ts | 256 ++ src/mock/cityCircle.ts | 546 +++ src/mock/comment.ts | 164 + src/mock/conversation.ts | 335 ++ src/mock/index.ts | 14 + src/mock/post.ts | 236 ++ src/mock/project.ts | 183 + src/mock/projectSession.ts | 211 ++ src/mock/recruitment.ts | 162 + src/mock/signedProject.ts | 346 ++ src/mock/talent.ts | 457 +++ src/mock/user.ts | 386 +++ src/router/index.ts | 299 ++ src/stores/index.ts | 10 + src/stores/user.ts | 116 + src/style.css | 50 + src/types/article.ts | 83 + src/types/certification.ts | 134 + src/types/cityCircle.ts | 122 + src/types/comment.ts | 72 + src/types/conversation.ts | 92 + src/types/index.ts | 17 + src/types/post.ts | 58 + src/types/project.ts | 78 + src/types/projectSession.ts | 84 + src/types/recruitment.ts | 101 + src/types/response.ts | 32 + src/types/signedProject.ts | 251 ++ src/types/talent.ts | 236 ++ src/types/user.ts | 162 + src/utils/common.ts | 76 + src/utils/request.ts | 111 + src/views/community/circles/index.vue | 1515 +++++++++ src/views/community/comments/index.vue | 566 ++++ src/views/community/posts/index.vue | 835 +++++ src/views/community/tags/index.vue | 848 +++++ src/views/content/articles/index.vue | 769 +++++ src/views/dashboard/index.vue | 495 +++ src/views/error/404.vue | 65 + src/views/login/index.vue | 428 +++ src/views/project/contract/index.vue | 578 ++++ src/views/project/list/index.vue | 575 ++++ src/views/project/recruitment/index.vue | 570 ++++ .../session/components/InviteModal.vue | 134 + src/views/project/session/detail.vue | 217 ++ src/views/project/session/index.vue | 175 + src/views/project/signed/index.vue | 571 ++++ src/views/support/conversations/index.vue | 832 +++++ src/views/support/session/index.vue | 513 +++ src/views/talent/components/OnlineResume.vue | 250 ++ src/views/talent/detail.vue | 663 ++++ src/views/talent/index.vue | 483 +++ src/views/talent/resume-templates.vue | 466 +++ src/views/user/certification/index.vue | 670 ++++ src/views/user/levels/index.vue | 291 ++ src/views/user/list/index.vue | 1235 +++++++ src/views/user/positions/index.vue | 266 ++ src/views/user/roles/index.vue | 265 ++ tsconfig.app.json | 21 + tsconfig.json | 7 + tsconfig.node.json | 26 + vite.config.ts | 57 + 83 files changed, 24640 insertions(+) create mode 100644 .env.development create mode 100644 .env.production create mode 100644 .gitignore create mode 100644 README.md create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/vite.svg create mode 100644 src/App.vue create mode 100644 src/assets/vue.svg create mode 100644 src/auto-imports.d.ts create mode 100644 src/components.d.ts create mode 100644 src/components/ChatConversation.vue create mode 100644 src/components/HelloWorld.vue create mode 100644 src/components/RichTextEditor.vue create mode 100644 src/config/index.ts create mode 100644 src/config/project.ts create mode 100644 src/layouts/EmbeddedLayout.vue create mode 100644 src/layouts/MainLayout.vue create mode 100644 src/main.ts create mode 100644 src/mock/article.ts create mode 100644 src/mock/certification.ts create mode 100644 src/mock/cityCircle.ts create mode 100644 src/mock/comment.ts create mode 100644 src/mock/conversation.ts create mode 100644 src/mock/index.ts create mode 100644 src/mock/post.ts create mode 100644 src/mock/project.ts create mode 100644 src/mock/projectSession.ts create mode 100644 src/mock/recruitment.ts create mode 100644 src/mock/signedProject.ts create mode 100644 src/mock/talent.ts create mode 100644 src/mock/user.ts create mode 100644 src/router/index.ts create mode 100644 src/stores/index.ts create mode 100644 src/stores/user.ts create mode 100644 src/style.css create mode 100644 src/types/article.ts create mode 100644 src/types/certification.ts create mode 100644 src/types/cityCircle.ts create mode 100644 src/types/comment.ts create mode 100644 src/types/conversation.ts create mode 100644 src/types/index.ts create mode 100644 src/types/post.ts create mode 100644 src/types/project.ts create mode 100644 src/types/projectSession.ts create mode 100644 src/types/recruitment.ts create mode 100644 src/types/response.ts create mode 100644 src/types/signedProject.ts create mode 100644 src/types/talent.ts create mode 100644 src/types/user.ts create mode 100644 src/utils/common.ts create mode 100644 src/utils/request.ts create mode 100644 src/views/community/circles/index.vue create mode 100644 src/views/community/comments/index.vue create mode 100644 src/views/community/posts/index.vue create mode 100644 src/views/community/tags/index.vue create mode 100644 src/views/content/articles/index.vue create mode 100644 src/views/dashboard/index.vue create mode 100644 src/views/error/404.vue create mode 100644 src/views/login/index.vue create mode 100644 src/views/project/contract/index.vue create mode 100644 src/views/project/list/index.vue create mode 100644 src/views/project/recruitment/index.vue create mode 100644 src/views/project/session/components/InviteModal.vue create mode 100644 src/views/project/session/detail.vue create mode 100644 src/views/project/session/index.vue create mode 100644 src/views/project/signed/index.vue create mode 100644 src/views/support/conversations/index.vue create mode 100644 src/views/support/session/index.vue create mode 100644 src/views/talent/components/OnlineResume.vue create mode 100644 src/views/talent/detail.vue create mode 100644 src/views/talent/index.vue create mode 100644 src/views/talent/resume-templates.vue create mode 100644 src/views/user/certification/index.vue create mode 100644 src/views/user/levels/index.vue create mode 100644 src/views/user/list/index.vue create mode 100644 src/views/user/positions/index.vue create mode 100644 src/views/user/roles/index.vue create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..c85cd7c --- /dev/null +++ b/.env.development @@ -0,0 +1,2 @@ +VITE_APP_TITLE=CodePort 码头 +VITE_API_BASE_URL=http://localhost:3000/api diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..73871e9 --- /dev/null +++ b/.env.production @@ -0,0 +1,2 @@ +VITE_APP_TITLE=CodePort 码头 +VITE_API_BASE_URL=/api diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/README.md b/README.md new file mode 100644 index 0000000..edc39e0 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# CodePort 码头 - 管理后台 + +人才外包平台管理后台系统 + +## 项目简介 + +CodePort 码头是一个人才外包平台的管理后台系统,提供完整的业务管理功能。 + +## 功能模块 + +- **控制台** - 数据总览和工作台 +- **社区管理** - 帖子、评论、标签、城市圈子管理 +- **内容管理** - 文章管理 +- **客服管理** - 接入会话、会话列表 +- **项目管理** - 项目列表、招募管理、已成交项目、合同管理、会话管理 +- **人才管理** - 人才列表、简历模板管理 +- **用户管理** - 用户列表、认证管理、角色管理、岗位管理、等级配置 + +## 技术栈 + +- **Vue 3** - 渐进式 JavaScript 框架 +- **TypeScript** - 类型安全的 JavaScript 超集 +- **Vite** - 下一代前端构建工具 +- **Ant Design Vue 4** - 企业级 UI 组件库 +- **Vue Router 4** - 官方路由管理器 +- **Pinia** - 轻量级状态管理 +- **Axios** - HTTP 客户端 + +## 开发环境 + +```bash +# 安装依赖 +npm install + +# 启动开发服务器 +npm run dev + +# 构建生产版本 +npm run build + +# 预览构建结果 +npm run preview +``` + +## 项目结构 + +``` +codePort/ +├── public/ # 静态资源 +├── src/ +│ ├── assets/ # 资源文件 +│ ├── components/ # 公共组件 +│ ├── config/ # 项目配置 +│ ├── layouts/ # 布局组件 +│ ├── mock/ # Mock 数据 +│ ├── router/ # 路由配置 +│ ├── stores/ # 状态管理 +│ ├── types/ # TypeScript 类型 +│ ├── utils/ # 工具函数 +│ ├── views/ # 页面视图 +│ ├── App.vue # 根组件 +│ ├── main.ts # 入口文件 +│ └── style.css # 全局样式 +├── index.html # HTML 模板 +├── package.json # 依赖配置 +├── tsconfig.json # TypeScript 配置 +└── vite.config.ts # Vite 配置 +``` + +## 环境变量 + +创建 `.env.development` 或 `.env.production` 文件配置环境变量: + +```env +VITE_API_BASE_URL=/api +``` + +## 许可证 + +MIT License diff --git a/index.html b/index.html new file mode 100644 index 0000000..fd364c1 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + CodePort 码头 - 管理后台 + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b043069 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2947 @@ +{ + "name": "codeport-admin", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "codeport-admin", + "version": "1.0.0", + "dependencies": { + "@ant-design/icons-vue": "^7.0.1", + "@types/echarts": "^4.9.22", + "@vue-flow/background": "^1.3.2", + "@vue-flow/controls": "^1.1.3", + "@vue-flow/core": "^1.48.0", + "@vue-flow/minimap": "^1.5.4", + "ant-design-vue": "^4.2.6", + "axios": "^1.13.2", + "echarts": "^6.0.0", + "pinia": "^3.0.4", + "vue": "^3.5.24", + "vue-router": "^4.6.4" + }, + "devDependencies": { + "@iconify/json": "^2.2.412", + "@types/node": "^24.10.1", + "@vitejs/plugin-vue": "^6.0.1", + "@vue/tsconfig": "^0.8.1", + "typescript": "~5.9.3", + "unplugin-auto-import": "^20.3.0", + "unplugin-icons": "^22.5.0", + "unplugin-vue-components": "^30.0.0", + "vite": "^7.2.4", + "vue-tsc": "^3.1.4" + } + }, + "node_modules/@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT" + }, + "node_modules/@ant-design/icons-vue": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons-vue/-/icons-vue-7.0.1.tgz", + "integrity": "sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-svg": "^4.2.1" + }, + "peerDependencies": { + "vue": ">=3.0.3" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@iconify/json": { + "version": "2.2.417", + "resolved": "https://registry.npmmirror.com/@iconify/json/-/json-2.2.417.tgz", + "integrity": "sha512-/MzthgckJ4vEwdHmAbAn6Bph5WnR4tzVcHMs/nZl3v5hOVRw80SK28UPnG7jjsCB41WWjWPnWdMEdOZfUMZS5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@iconify/types": "*", + "pathe": "^2.0.3" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@iconify/utils/-/utils-3.1.0.tgz", + "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "mlly": "^1.8.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", + "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", + "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", + "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", + "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", + "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", + "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", + "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", + "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", + "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", + "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", + "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", + "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", + "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", + "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", + "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", + "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", + "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", + "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", + "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", + "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", + "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", + "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@simonwep/pickr": { + "version": "1.8.2", + "resolved": "https://registry.npmmirror.com/@simonwep/pickr/-/pickr-1.8.2.tgz", + "integrity": "sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==", + "license": "MIT", + "dependencies": { + "core-js": "^3.15.1", + "nanopop": "^2.1.0" + } + }, + "node_modules/@types/echarts": { + "version": "4.9.22", + "resolved": "https://registry.npmmirror.com/@types/echarts/-/echarts-4.9.22.tgz", + "integrity": "sha512-7Fo6XdWpoi8jxkwP7BARUOM7riq8bMhmsCtSG8gzUcJmFhLo387tihoBYS/y5j7jl3PENT5RxeWZdN9RiwO7HQ==", + "license": "MIT", + "dependencies": { + "@types/zrender": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.4", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.10.4.tgz", + "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/@types/zrender": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/@types/zrender/-/zrender-4.0.6.tgz", + "integrity": "sha512-1jZ9bJn2BsfmYFPBHtl5o3uV+ILejAtGrDcYSpT4qaVKEI/0YY+arw3XHU04Ebd8Nca3SQ7uNcLaqiL+tTFVMg==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.3.tgz", + "integrity": "sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.53" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.27", + "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.27.tgz", + "integrity": "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.27" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.27", + "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.27.tgz", + "integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.27", + "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.27.tgz", + "integrity": "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.27", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue-flow/background": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/@vue-flow/background/-/background-1.3.2.tgz", + "integrity": "sha512-eJPhDcLj1wEo45bBoqTXw1uhl0yK2RaQGnEINqvvBsAFKh/camHJd5NPmOdS1w+M9lggc9igUewxaEd3iCQX2w==", + "license": "MIT", + "peerDependencies": { + "@vue-flow/core": "^1.23.0", + "vue": "^3.3.0" + } + }, + "node_modules/@vue-flow/controls": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@vue-flow/controls/-/controls-1.1.3.tgz", + "integrity": "sha512-XCf+G+jCvaWURdFlZmOjifZGw3XMhN5hHlfMGkWh9xot+9nH9gdTZtn+ldIJKtarg3B21iyHU8JjKDhYcB6JMw==", + "license": "MIT", + "peerDependencies": { + "@vue-flow/core": "^1.23.0", + "vue": "^3.3.0" + } + }, + "node_modules/@vue-flow/core": { + "version": "1.48.1", + "resolved": "https://registry.npmmirror.com/@vue-flow/core/-/core-1.48.1.tgz", + "integrity": "sha512-3IxaMBLvWRbznZ4CuK0kVhp4Y4lCDQx9nhi48Swp6PwPw29KNhmiKd2kaBogYeWjGLb/tLjlE9V0s3jEmKCYWw==", + "license": "MIT", + "dependencies": { + "@vueuse/core": "^10.5.0", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + }, + "peerDependencies": { + "vue": "^3.3.0" + } + }, + "node_modules/@vue-flow/minimap": { + "version": "1.5.4", + "resolved": "https://registry.npmmirror.com/@vue-flow/minimap/-/minimap-1.5.4.tgz", + "integrity": "sha512-l4C+XTAXnRxsRpUdN7cAVFBennC1sVRzq4bDSpVK+ag7tdMczAnhFYGgbLkUw3v3sY6gokyWwMl8CDonp8eB2g==", + "license": "MIT", + "dependencies": { + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + }, + "peerDependencies": { + "@vue-flow/core": "^1.23.0", + "vue": "^3.3.0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.26.tgz", + "integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.26", + "entities": "^7.0.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz", + "integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.26", + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz", + "integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/compiler-core": "3.5.26", + "@vue/compiler-dom": "3.5.26", + "@vue/compiler-ssr": "3.5.26", + "@vue/shared": "3.5.26", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz", + "integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.26", + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.2.1.tgz", + "integrity": "sha512-g6oSenpnGMtpxHGAwKuu7HJJkNZpemK/zg3vZzZbJ6cnnXq1ssxuNrXSsAHYM3NvH8p4IkTw+NLmuxyeYz4r8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.27", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.0.0", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.2" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.26.tgz", + "integrity": "sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.26.tgz", + "integrity": "sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.26", + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.26.tgz", + "integrity": "sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.26", + "@vue/runtime-core": "3.5.26", + "@vue/shared": "3.5.26", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.26.tgz", + "integrity": "sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.26", + "@vue/shared": "3.5.26" + }, + "peerDependencies": { + "vue": "3.5.26" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.26.tgz", + "integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.8.1", + "resolved": "https://registry.npmmirror.com/@vue/tsconfig/-/tsconfig-0.8.1.tgz", + "integrity": "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "5.x", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/alien-signals": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-3.1.1.tgz", + "integrity": "sha512-ogkIWbVrLwKtHY6oOAXaYkAxP+cTH7V5FZ5+Tm4NZFd8VDZ6uNMDrfzqctTZ42eTMCSR3ne3otpcxmqSnFfPYA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ant-design-vue": { + "version": "4.2.6", + "resolved": "https://registry.npmmirror.com/ant-design-vue/-/ant-design-vue-4.2.6.tgz", + "integrity": "sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-vue": "^7.0.0", + "@babel/runtime": "^7.10.5", + "@ctrl/tinycolor": "^3.5.0", + "@emotion/hash": "^0.9.0", + "@emotion/unitless": "^0.8.0", + "@simonwep/pickr": "~1.8.0", + "array-tree-filter": "^2.1.0", + "async-validator": "^4.0.0", + "csstype": "^3.1.1", + "dayjs": "^1.10.5", + "dom-align": "^1.12.1", + "dom-scroll-into-view": "^2.0.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.15", + "resize-observer-polyfill": "^1.5.1", + "scroll-into-view-if-needed": "^2.2.25", + "shallow-equal": "^1.0.0", + "stylis": "^4.1.3", + "throttle-debounce": "^5.0.0", + "vue-types": "^3.0.0", + "warning": "^4.0.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design-vue" + }, + "peerDependencies": { + "vue": ">=3.2.0" + } + }, + "node_modules/array-tree-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz", + "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==", + "license": "MIT" + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/core-js": { + "version": "3.47.0", + "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.47.0.tgz", + "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dom-align": { + "version": "1.12.4", + "resolved": "https://registry.npmmirror.com/dom-align/-/dom-align-1.12.4.tgz", + "integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==", + "license": "MIT" + }, + "node_modules/dom-scroll-into-view": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz", + "integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==", + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/echarts": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/echarts/-/echarts-6.0.0.tgz", + "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "6.0.0" + } + }, + "node_modules/entities": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.0.tgz", + "integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.22", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.22.tgz", + "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loose-envify/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanopop": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/nanopop/-/nanopop-2.4.2.tgz", + "integrity": "sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==", + "license": "MIT" + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.7" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.5.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.54.0", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.54.0.tgz", + "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.54.0", + "@rollup/rollup-android-arm64": "4.54.0", + "@rollup/rollup-darwin-arm64": "4.54.0", + "@rollup/rollup-darwin-x64": "4.54.0", + "@rollup/rollup-freebsd-arm64": "4.54.0", + "@rollup/rollup-freebsd-x64": "4.54.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", + "@rollup/rollup-linux-arm-musleabihf": "4.54.0", + "@rollup/rollup-linux-arm64-gnu": "4.54.0", + "@rollup/rollup-linux-arm64-musl": "4.54.0", + "@rollup/rollup-linux-loong64-gnu": "4.54.0", + "@rollup/rollup-linux-ppc64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-musl": "4.54.0", + "@rollup/rollup-linux-s390x-gnu": "4.54.0", + "@rollup/rollup-linux-x64-gnu": "4.54.0", + "@rollup/rollup-linux-x64-musl": "4.54.0", + "@rollup/rollup-openharmony-arm64": "4.54.0", + "@rollup/rollup-win32-arm64-msvc": "4.54.0", + "@rollup/rollup-win32-ia32-msvc": "4.54.0", + "@rollup/rollup-win32-x64-gnu": "4.54.0", + "@rollup/rollup-win32-x64-msvc": "4.54.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.31", + "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", + "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^1.0.20" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unimport": { + "version": "5.6.0", + "resolved": "https://registry.npmmirror.com/unimport/-/unimport-5.6.0.tgz", + "integrity": "sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "local-pkg": "^1.1.2", + "magic-string": "^0.30.21", + "mlly": "^1.8.0", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "pkg-types": "^2.3.0", + "scule": "^1.3.0", + "strip-literal": "^3.1.0", + "tinyglobby": "^0.2.15", + "unplugin": "^2.3.11", + "unplugin-utils": "^0.3.1" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-auto-import": { + "version": "20.3.0", + "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-20.3.0.tgz", + "integrity": "sha512-RcSEQiVv7g0mLMMXibYVKk8mpteKxvyffGuDKqZZiFr7Oq3PB1HwgHdK5O7H4AzbhzHoVKG0NnMnsk/1HIVYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "local-pkg": "^1.1.2", + "magic-string": "^0.30.21", + "picomatch": "^4.0.3", + "unimport": "^5.5.0", + "unplugin": "^2.3.11", + "unplugin-utils": "^0.3.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^4.0.0", + "@vueuse/core": "*" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@vueuse/core": { + "optional": true + } + } + }, + "node_modules/unplugin-icons": { + "version": "22.5.0", + "resolved": "https://registry.npmmirror.com/unplugin-icons/-/unplugin-icons-22.5.0.tgz", + "integrity": "sha512-MBlMtT5RuMYZy4TZgqUL2OTtOdTUVsS1Mhj6G1pEzMlFJlEnq6mhUfoIt45gBWxHcsOdXJDWLg3pRZ+YmvAVWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/utils": "^3.0.2", + "debug": "^4.4.3", + "local-pkg": "^1.1.2", + "unplugin": "^2.3.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@svgr/core": ">=7.0.0", + "@svgx/core": "^1.0.1", + "@vue/compiler-sfc": "^3.0.2 || ^2.7.0", + "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0", + "vue-template-compiler": "^2.6.12", + "vue-template-es2015-compiler": "^1.9.0" + }, + "peerDependenciesMeta": { + "@svgr/core": { + "optional": true + }, + "@svgx/core": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + }, + "vue-template-es2015-compiler": { + "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.3.1.tgz", + "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/unplugin-vue-components": { + "version": "30.0.0", + "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-30.0.0.tgz", + "integrity": "sha512-4qVE/lwCgmdPTp6h0qsRN2u642tt4boBQtcpn4wQcWZAsr8TQwq+SPT3NDu/6kBFxzo/sSEK4ioXhOOBrXc3iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "debug": "^4.4.3", + "local-pkg": "^1.1.2", + "magic-string": "^0.30.19", + "mlly": "^1.8.0", + "tinyglobby": "^0.2.15", + "unplugin": "^2.3.10", + "unplugin-utils": "^0.3.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2 || ^4.0.0", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.26.tgz", + "integrity": "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.26", + "@vue/compiler-sfc": "3.5.26", + "@vue/runtime-dom": "3.5.26", + "@vue/server-renderer": "3.5.26", + "@vue/shared": "3.5.26" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-tsc": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.2.1.tgz", + "integrity": "sha512-I23Rk8dkQfmcSbxDO0dmg9ioMLjKA1pjlU3Lz6Jfk2pMGu3Uryu9810XkcZH24IzPbhzPCnkKo2rEMRX0skSrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.27", + "@vue/language-core": "3.2.1" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/vue-types": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/vue-types/-/vue-types-3.0.2.tgz", + "integrity": "sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==", + "license": "MIT", + "dependencies": { + "is-plain-object": "3.0.1" + }, + "engines": { + "node": ">=10.15.0" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/zrender": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/zrender/-/zrender-6.0.0.tgz", + "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ebd39bc --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "codeport-admin", + "private": true, + "version": "1.0.0", + "type": "module", + "description": "CodePort 码头 - 人才外包平台管理后台", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@ant-design/icons-vue": "^7.0.1", + "@types/echarts": "^4.9.22", + "@vue-flow/background": "^1.3.2", + "@vue-flow/controls": "^1.1.3", + "@vue-flow/core": "^1.48.0", + "@vue-flow/minimap": "^1.5.4", + "ant-design-vue": "^4.2.6", + "axios": "^1.13.2", + "echarts": "^6.0.0", + "pinia": "^3.0.4", + "vue": "^3.5.24", + "vue-router": "^4.6.4" + }, + "devDependencies": { + "@iconify/json": "^2.2.412", + "@types/node": "^24.10.1", + "@vitejs/plugin-vue": "^6.0.1", + "@vue/tsconfig": "^0.8.1", + "typescript": "~5.9.3", + "unplugin-auto-import": "^20.3.0", + "unplugin-icons": "^22.5.0", + "unplugin-vue-components": "^30.0.0", + "vite": "^7.2.4", + "vue-tsc": "^3.1.4" + } +} diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..1b9c701 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,17 @@ + + + + + diff --git a/src/assets/vue.svg b/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts new file mode 100644 index 0000000..71e70ef --- /dev/null +++ b/src/auto-imports.d.ts @@ -0,0 +1,91 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +// biome-ignore lint: disable +export {} +declare global { + const EffectScope: typeof import('vue').EffectScope + const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate + const axios: typeof import('axios').default + const computed: typeof import('vue').computed + const createApp: typeof import('vue').createApp + const createPinia: typeof import('pinia').createPinia + const customRef: typeof import('vue').customRef + const defineAsyncComponent: typeof import('vue').defineAsyncComponent + const defineComponent: typeof import('vue').defineComponent + const defineStore: typeof import('pinia').defineStore + const effectScope: typeof import('vue').effectScope + const getActivePinia: typeof import('pinia').getActivePinia + const getCurrentInstance: typeof import('vue').getCurrentInstance + const getCurrentScope: typeof import('vue').getCurrentScope + const getCurrentWatcher: typeof import('vue').getCurrentWatcher + const h: typeof import('vue').h + const inject: typeof import('vue').inject + const isProxy: typeof import('vue').isProxy + const isReactive: typeof import('vue').isReactive + const isReadonly: typeof import('vue').isReadonly + const isRef: typeof import('vue').isRef + const isShallow: typeof import('vue').isShallow + const mapActions: typeof import('pinia').mapActions + const mapGetters: typeof import('pinia').mapGetters + const mapState: typeof import('pinia').mapState + const mapStores: typeof import('pinia').mapStores + const mapWritableState: typeof import('pinia').mapWritableState + const markRaw: typeof import('vue').markRaw + const nextTick: typeof import('vue').nextTick + const onActivated: typeof import('vue').onActivated + const onBeforeMount: typeof import('vue').onBeforeMount + const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave + const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate + const onBeforeUnmount: typeof import('vue').onBeforeUnmount + const onBeforeUpdate: typeof import('vue').onBeforeUpdate + const onDeactivated: typeof import('vue').onDeactivated + const onErrorCaptured: typeof import('vue').onErrorCaptured + const onMounted: typeof import('vue').onMounted + const onRenderTracked: typeof import('vue').onRenderTracked + const onRenderTriggered: typeof import('vue').onRenderTriggered + const onScopeDispose: typeof import('vue').onScopeDispose + const onServerPrefetch: typeof import('vue').onServerPrefetch + const onUnmounted: typeof import('vue').onUnmounted + const onUpdated: typeof import('vue').onUpdated + const onWatcherCleanup: typeof import('vue').onWatcherCleanup + const provide: typeof import('vue').provide + const reactive: typeof import('vue').reactive + const readonly: typeof import('vue').readonly + const ref: typeof import('vue').ref + const resolveComponent: typeof import('vue').resolveComponent + const setActivePinia: typeof import('pinia').setActivePinia + const setMapStoreSuffix: typeof import('pinia').setMapStoreSuffix + const shallowReactive: typeof import('vue').shallowReactive + const shallowReadonly: typeof import('vue').shallowReadonly + const shallowRef: typeof import('vue').shallowRef + const storeToRefs: typeof import('pinia').storeToRefs + const toRaw: typeof import('vue').toRaw + const toRef: typeof import('vue').toRef + const toRefs: typeof import('vue').toRefs + const toValue: typeof import('vue').toValue + const triggerRef: typeof import('vue').triggerRef + const unref: typeof import('vue').unref + const useAttrs: typeof import('vue').useAttrs + const useCssModule: typeof import('vue').useCssModule + const useCssVars: typeof import('vue').useCssVars + const useId: typeof import('vue').useId + const useLink: typeof import('vue-router').useLink + const useModel: typeof import('vue').useModel + const useRoute: typeof import('vue-router').useRoute + const useRouter: typeof import('vue-router').useRouter + const useSlots: typeof import('vue').useSlots + const useTemplateRef: typeof import('vue').useTemplateRef + const watch: typeof import('vue').watch + const watchEffect: typeof import('vue').watchEffect + const watchPostEffect: typeof import('vue').watchPostEffect + const watchSyncEffect: typeof import('vue').watchSyncEffect +} +// for type re-export +declare global { + // @ts-ignore + export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' + import('vue') +} diff --git a/src/components.d.ts b/src/components.d.ts new file mode 100644 index 0000000..40eb56e --- /dev/null +++ b/src/components.d.ts @@ -0,0 +1,76 @@ +/* eslint-disable */ +// @ts-nocheck +// biome-ignore lint: disable +// oxlint-disable +// ------ +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 + +export {} + +/* prettier-ignore */ +declare module 'vue' { + export interface GlobalComponents { + AAlert: typeof import('ant-design-vue/es')['Alert'] + AAvatar: typeof import('ant-design-vue/es')['Avatar'] + ABadge: typeof import('ant-design-vue/es')['Badge'] + ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb'] + ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem'] + AButton: typeof import('ant-design-vue/es')['Button'] + ACard: typeof import('ant-design-vue/es')['Card'] + ACheckbox: typeof import('ant-design-vue/es')['Checkbox'] + ACol: typeof import('ant-design-vue/es')['Col'] + ADescriptions: typeof import('ant-design-vue/es')['Descriptions'] + ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem'] + ADivider: typeof import('ant-design-vue/es')['Divider'] + ADrawer: typeof import('ant-design-vue/es')['Drawer'] + ADropdown: typeof import('ant-design-vue/es')['Dropdown'] + AEmpty: typeof import('ant-design-vue/es')['Empty'] + AForm: typeof import('ant-design-vue/es')['Form'] + AFormItem: typeof import('ant-design-vue/es')['FormItem'] + AInput: typeof import('ant-design-vue/es')['Input'] + AInputNumber: typeof import('ant-design-vue/es')['InputNumber'] + AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] + AInputSearch: typeof import('ant-design-vue/es')['InputSearch'] + ALayout: typeof import('ant-design-vue/es')['Layout'] + ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent'] + ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader'] + ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider'] + AList: typeof import('ant-design-vue/es')['List'] + AListItem: typeof import('ant-design-vue/es')['ListItem'] + AMenu: typeof import('ant-design-vue/es')['Menu'] + AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider'] + AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] + AMenuItemGroup: typeof import('ant-design-vue/es')['MenuItemGroup'] + AModal: typeof import('ant-design-vue/es')['Modal'] + APageHeader: typeof import('ant-design-vue/es')['PageHeader'] + APopconfirm: typeof import('ant-design-vue/es')['Popconfirm'] + AProgress: typeof import('ant-design-vue/es')['Progress'] + ARadio: typeof import('ant-design-vue/es')['Radio'] + ARadioButton: typeof import('ant-design-vue/es')['RadioButton'] + ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] + ARangePicker: typeof import('ant-design-vue/es')['RangePicker'] + ARow: typeof import('ant-design-vue/es')['Row'] + ASelect: typeof import('ant-design-vue/es')['Select'] + ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] + ASpace: typeof import('ant-design-vue/es')['Space'] + ASpin: typeof import('ant-design-vue/es')['Spin'] + AStatistic: typeof import('ant-design-vue/es')['Statistic'] + ASubMenu: typeof import('ant-design-vue/es')['SubMenu'] + ASwitch: typeof import('ant-design-vue/es')['Switch'] + ATable: typeof import('ant-design-vue/es')['Table'] + ATabPane: typeof import('ant-design-vue/es')['TabPane'] + ATabs: typeof import('ant-design-vue/es')['Tabs'] + ATag: typeof import('ant-design-vue/es')['Tag'] + ATextarea: typeof import('ant-design-vue/es')['Textarea'] + ATimeline: typeof import('ant-design-vue/es')['Timeline'] + ATimelineItem: typeof import('ant-design-vue/es')['TimelineItem'] + ATooltip: typeof import('ant-design-vue/es')['Tooltip'] + AUpload: typeof import('ant-design-vue/es')['Upload'] + ChatConversation: typeof import('./components/ChatConversation.vue')['default'] + HelloWorld: typeof import('./components/HelloWorld.vue')['default'] + RichTextEditor: typeof import('./components/RichTextEditor.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + } +} diff --git a/src/components/ChatConversation.vue b/src/components/ChatConversation.vue new file mode 100644 index 0000000..03cec71 --- /dev/null +++ b/src/components/ChatConversation.vue @@ -0,0 +1,385 @@ + + + + + diff --git a/src/components/HelloWorld.vue b/src/components/HelloWorld.vue new file mode 100644 index 0000000..b58e52b --- /dev/null +++ b/src/components/HelloWorld.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/components/RichTextEditor.vue b/src/components/RichTextEditor.vue new file mode 100644 index 0000000..851e94e --- /dev/null +++ b/src/components/RichTextEditor.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/src/config/index.ts b/src/config/index.ts new file mode 100644 index 0000000..c4019a9 --- /dev/null +++ b/src/config/index.ts @@ -0,0 +1,4 @@ +/** + * 配置模块统一导出 + */ +export * from './project' diff --git a/src/config/project.ts b/src/config/project.ts new file mode 100644 index 0000000..9de0675 --- /dev/null +++ b/src/config/project.ts @@ -0,0 +1,324 @@ +/** + * CodePort 码头 - 项目配置 + * + * 人才外包平台管理后台 + */ + +import type { Component } from 'vue' +import { + DashboardOutlined, + TeamOutlined, + ReadOutlined, + CustomerServiceOutlined, + ProjectOutlined, + IdcardOutlined, + UserOutlined +} from '@ant-design/icons-vue' + +// 菜单项类型 +export interface MenuItem { + key: string + label: string + icon?: Component + path?: string + children?: MenuItem[] +} + +// 模块配置 +export interface ModuleConfig { + id: string + name: string + icon?: Component + menus: MenuItem[] + routes: RouteConfig[] +} + +// 路由配置 +export interface RouteConfig { + path: string + name: string + component: string + title: string +} + +// 项目配置 +export interface ProjectConfig { + id: string + name: string + logo: string + shortName: string + description: string + modules: ModuleConfig[] +} + +// ==================== CodePort 业务模块 ==================== + +// 控制台模块 +export const dashboardModule: ModuleConfig = { + id: 'dashboard', + name: '控制台', + icon: DashboardOutlined, + menus: [ + { key: 'dashboard', label: '控制台', path: '/dashboard' } + ], + routes: [ + { path: 'dashboard', name: 'Dashboard', component: '@/views/dashboard/index.vue', title: '控制台' } + ] +} + +// 社区管理模块 +export const communityModule: ModuleConfig = { + id: 'community', + name: '社区管理', + icon: TeamOutlined, + menus: [ + { + key: 'community', + label: '社区管理', + children: [ + { key: 'posts', label: '帖子管理', path: '/community/posts' }, + { key: 'comments', label: '评论管理', path: '/community/comments' }, + { key: 'tags', label: '标签管理', path: '/community/tags' }, + { key: 'circles', label: '城市圈子', path: '/community/circles' } + ] + } + ], + routes: [ + { path: 'community/posts', name: 'Posts', component: '@/views/community/posts/index.vue', title: '帖子管理' }, + { path: 'community/comments', name: 'Comments', component: '@/views/community/comments/index.vue', title: '评论管理' }, + { path: 'community/tags', name: 'Tags', component: '@/views/community/tags/index.vue', title: '标签管理' }, + { path: 'community/circles', name: 'CityCircles', component: '@/views/community/circles/index.vue', title: '城市圈子' } + ] +} + +// 内容管理模块 +export const contentModule: ModuleConfig = { + id: 'content', + name: '内容管理', + icon: ReadOutlined, + menus: [ + { + key: 'content', + label: '内容管理', + children: [ + { key: 'articles', label: '文章管理', path: '/content/articles' } + ] + } + ], + routes: [ + { path: 'content/articles', name: 'Articles', component: '@/views/content/articles/index.vue', title: '文章管理' } + ] +} + +// 客服管理模块 +export const supportModule: ModuleConfig = { + id: 'support', + name: '客服管理', + icon: CustomerServiceOutlined, + menus: [ + { + key: 'support', + label: '客服管理', + children: [ + { key: 'support-console', label: '接入会话', path: '/support/console' }, + { key: 'support-conversations', label: '会话列表', path: '/support/conversations' } + ] + } + ], + routes: [ + { path: 'support/console', name: 'SupportConsole', component: '@/views/support/session/index.vue', title: '接入会话' }, + { path: 'support/conversations', name: 'SupportConversations', component: '@/views/support/conversations/index.vue', title: '会话列表' } + ] +} + +// 项目管理模块 +export const projectModule: ModuleConfig = { + id: 'project', + name: '项目管理', + icon: ProjectOutlined, + menus: [ + { + key: 'project', + label: '项目管理', + children: [ + { key: 'projects', label: '项目列表', path: '/project/list' }, + { key: 'recruitment', label: '招募管理', path: '/project/recruitment' }, + { key: 'signed-projects', label: '已成交项目', path: '/project/signed' }, + { key: 'contracts', label: '合同管理', path: '/project/contract' }, + { key: 'sessions', label: '会话管理', path: '/project/sessions' } + ] + } + ], + routes: [ + { path: 'project/list', name: 'Projects', component: '@/views/project/list/index.vue', title: '项目列表' }, + { path: 'project/recruitment', name: 'Recruitment', component: '@/views/project/recruitment/index.vue', title: '招募管理' }, + { path: 'project/signed', name: 'SignedProjects', component: '@/views/project/signed/index.vue', title: '已成交项目' }, + { path: 'project/contract', name: 'ContractManagement', component: '@/views/project/contract/index.vue', title: '合同管理' }, + { path: 'project/sessions', name: 'ProjectSessions', component: '@/views/project/session/index.vue', title: '会话管理' }, + { path: 'project/sessions/:id', name: 'ProjectSessionDetail', component: '@/views/project/session/detail.vue', title: '会话详情' } + ] +} + +// 人才管理模块 +export const talentModule: ModuleConfig = { + id: 'talent', + name: '人才管理', + icon: IdcardOutlined, + menus: [ + { + key: 'talent', + label: '人才管理', + children: [ + { key: 'talent-list', label: '人才列表', path: '/talent' }, + { key: 'resume-templates', label: '简历模板', path: '/talent/resume-templates' } + ] + } + ], + routes: [ + { path: 'talent', name: 'Talent', component: '@/views/talent/index.vue', title: '人才管理' }, + { path: 'talent/resume-templates', name: 'ResumeTemplates', component: '@/views/talent/resume-templates.vue', title: '简历模板管理' }, + { path: 'talent/:id', name: 'TalentDetail', component: '@/views/talent/detail.vue', title: '人才详情' } + ] +} + +// 用户管理模块 +export const userModule: ModuleConfig = { + id: 'user', + name: '用户管理', + icon: UserOutlined, + menus: [ + { + key: 'user', + label: '用户管理', + children: [ + { key: 'users', label: '用户列表', path: '/user/list' }, + { key: 'certification', label: '认证管理', path: '/user/certification' }, + { key: 'roles', label: '角色管理', path: '/user/roles' }, + { key: 'positions', label: '岗位管理', path: '/user/positions' }, + { key: 'levels', label: '等级配置', path: '/user/levels' } + ] + } + ], + routes: [ + { path: 'user/list', name: 'Users', component: '@/views/user/list/index.vue', title: '用户列表' }, + { path: 'user/roles', name: 'Roles', component: '@/views/user/roles/index.vue', title: '角色管理' }, + { path: 'user/certification', name: 'UserCertification', component: '@/views/user/certification/index.vue', title: '认证管理' }, + { path: 'user/positions', name: 'Positions', component: '@/views/user/positions/index.vue', title: '岗位管理' }, + { path: 'user/levels', name: 'Levels', component: '@/views/user/levels/index.vue', title: '等级配置' } + ] +} + +// ==================== 项目配置 ==================== + +// CodePort 项目配置 +export const codePortProject: ProjectConfig = { + id: 'codePort', + name: 'CodePort 码头', + logo: '码', + shortName: 'CodePort', + description: '人才外包平台管理后台', + modules: [ + dashboardModule, + communityModule, + contentModule, + supportModule, + projectModule, + talentModule, + userModule + ] +} + +// 当前项目配置 +export const currentProject = codePortProject + +// ==================== 工具函数 ==================== + +/** + * 获取当前项目配置 + */ +export function getCurrentProject(): ProjectConfig { + return currentProject +} + +/** + * 获取所有模块 + */ +export function getAllModules(): ModuleConfig[] { + return currentProject.modules +} + +/** + * 获取菜单配置 + */ +export function getMenuConfig(): MenuItem[] { + const menus: MenuItem[] = [] + + currentProject.modules.forEach(module => { + module.menus.forEach(menu => { + if (!menu.children) { + menus.push({ + ...menu, + icon: module.icon + }) + } else { + menus.push({ + ...menu, + icon: module.icon + }) + } + }) + }) + + return menus +} + +/** + * 获取菜单项到路由的映射 + */ +export function getMenuRouteMap(): Record { + const map: Record = {} + + currentProject.modules.forEach(module => { + module.menus.forEach(menu => { + if (menu.path) { + map[menu.key] = menu.path + } + if (menu.children) { + menu.children.forEach(child => { + if (child.path) { + map[child.key] = child.path + } + }) + } + }) + }) + + return map +} + +/** + * 获取所有路由配置 + */ +export function getRouteConfigs(): RouteConfig[] { + const routes: RouteConfig[] = [] + + currentProject.modules.forEach(module => { + routes.push(...module.routes) + }) + + return routes +} + +/** + * 获取业务菜单(兼容原框架接口) + */ +export function getBusinessMenus(): MenuItem[] { + return getMenuConfig() +} + +/** + * 获取框架菜单(CodePort 独立项目无框架菜单) + */ +export function getFrameworkMenus(): MenuItem[] { + return [] +} diff --git a/src/layouts/EmbeddedLayout.vue b/src/layouts/EmbeddedLayout.vue new file mode 100644 index 0000000..061459a --- /dev/null +++ b/src/layouts/EmbeddedLayout.vue @@ -0,0 +1,23 @@ + + + + + diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue new file mode 100644 index 0000000..24e855b --- /dev/null +++ b/src/layouts/MainLayout.vue @@ -0,0 +1,368 @@ + + + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..3716f87 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,15 @@ +import { createApp } from 'vue' +import App from './App.vue' +import router from './router' +import pinia from './stores' + +// Ant Design Vue 全局样式 +import 'ant-design-vue/dist/reset.css' +import './style.css' + +const app = createApp(App) + +app.use(pinia) +app.use(router) + +app.mount('#app') diff --git a/src/mock/article.ts b/src/mock/article.ts new file mode 100644 index 0000000..52ac147 --- /dev/null +++ b/src/mock/article.ts @@ -0,0 +1,178 @@ +/** + * 文章管理 mock + */ +import type { + ArticleInfo, + ArticleCategory, + ArticleListResult, + ArticleQueryParams, + ArticleCreatePayload, + RuleSubCategoryKey +} from '@/types' + +export const ArticleCategories: ArticleCategory[] = [ + { key: 'announcement', name: '平台通告', description: '面向所有用户的公告与提醒', color: 'blue' }, + { key: 'rule', name: '规则政策', description: '平台规则、合作协议及更新说明', color: 'purple' }, + { key: 'activity', name: '活动细则', description: '活动玩法、报名及细则说明', color: 'orange' }, + { key: 'guide', name: '使用指南', description: '功能介绍与操作手册', color: 'green' }, + { key: 'other', name: '其他内容', description: '无法归类的文章', color: 'default' } +] + +// 规则政策相关的预设标签 +export const RulePolicyTags: Record = { + withdraw: ['提现', '银行卡', '到账时间', '手续费'], + settlement: ['结算', '账单', '收益', '分成'], + cooperation: ['合同', '协议', '合作', '条款'], + violation: ['违规', '处罚', '封禁', '申诉'], + privacy: ['隐私', '数据', '安全', '授权'], + other: ['规则', '政策'] +} + +const authors = [ + { id: 1, name: '阿岚', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=alan' }, + { id: 2, name: '林舟', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=linzhou' }, + { id: 3, name: '南鸢', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=nanyuan' } +] + +function randomItem(items: T[]): T { + return items[Math.floor(Math.random() * items.length)]! +} + +function generateArticles(): ArticleInfo[] { + const articles: ArticleInfo[] = [] + for (let i = 1; i <= 36; i++) { + const category = ArticleCategories[i % ArticleCategories.length]!.key + const author = randomItem(authors) + const isPublished = Math.random() > 0.2 + const createdAt = new Date(Date.now() - i * 24 * 60 * 60 * 1000) + const publishedAt = isPublished + ? new Date(createdAt.getTime() + 6 * 60 * 60 * 1000) + : undefined + + articles.push({ + id: i, + title: `【${ArticleCategories[i % ArticleCategories.length]!.name}】第 ${i} 期内容`, + summary: + '围绕最新的活动安排及平台规范进行了说明,请项目方与人才密切关注,按照流程执行后续操作。', + content: + '为确保平台运营秩序与活动顺利开展,本次公告重点覆盖:\n' + + '1. 活动报名流程与时间节点;\n2. 新增安全风控策略;\n3. 针对人才服务的评分制度更新;\n4. 常见问题处理 FAQ。\n' + + '请项目方结合自身安排,按时完成信息填报,如有疑问可联系平台客服通道。', + cover: `https://picsum.photos/seed/article-${i}/600/320`, + category, + tags: ['平台', '更新', i % 2 === 0 ? '活动' : '规则'], + publishStatus: isPublished ? 'published' : 'draft', + pinned: i % 7 === 0, + createdAt: createdAt.toISOString(), + updatedAt: new Date(createdAt.getTime() + 2 * 60 * 60 * 1000).toISOString(), + publishedAt: publishedAt?.toISOString(), + author, + viewCount: 200 + Math.floor(Math.random() * 2000), + linkUrl: i % 5 === 0 ? 'https://nanxiislet.com/articles/' + i : undefined + }) + } + return articles +} + +let articleList = generateArticles() + +function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +export async function mockGetArticleCategories(): Promise { + await delay(100) + return ArticleCategories +} + +export async function mockGetArticleList(params: ArticleQueryParams = {}): Promise { + await delay(300) + const { keyword, category, status, pinned, page = 1, pageSize = 10 } = params + let filtered = [...articleList] + + if (keyword) { + const kw = keyword.toLowerCase() + filtered = filtered.filter( + item => item.title.toLowerCase().includes(kw) || item.summary.toLowerCase().includes(kw) + ) + } + + if (category) { + filtered = filtered.filter(item => item.category === category) + } + + if (status) { + filtered = filtered.filter(item => item.publishStatus === status) + } + + if (pinned !== undefined) { + filtered = filtered.filter(item => item.pinned === pinned) + } + + const start = (page - 1) * pageSize + const list = filtered.slice(start, start + pageSize) + + return { + list, + total: filtered.length, + page, + pageSize + } +} + +export async function mockUpdateArticleStatus( + id: number, + status: ArticleInfo['publishStatus'] +): Promise { + await delay(200) + const target = articleList.find(item => item.id === id) + if (target) { + target.publishStatus = status + if (status === 'published') { + target.publishedAt = new Date().toISOString() + } + target.updatedAt = new Date().toISOString() + } + return target +} + +export async function mockToggleArticlePin(id: number, pinned: boolean): Promise { + await delay(150) + const target = articleList.find(item => item.id === id) + if (target) { + target.pinned = pinned + target.updatedAt = new Date().toISOString() + } +} + +export async function mockDeleteArticle(id: number): Promise { + await delay(150) + articleList = articleList.filter(item => item.id !== id) +} + +export async function mockCreateArticle(payload: ArticleCreatePayload): Promise { + await delay(300) + const maxId = articleList.reduce((max, item) => Math.max(max, item.id), 0) + const id = maxId + 1 + const now = new Date().toISOString() + const article: ArticleInfo = { + id, + title: payload.title, + summary: payload.summary, + content: payload.content, + cover: payload.cover, + category: payload.category, + subCategory: payload.subCategory, + tags: payload.tags, + publishStatus: payload.publishStatus, + pinned: payload.pinned ?? false, + createdAt: now, + updatedAt: now, + publishedAt: payload.publishStatus === 'published' ? now : undefined, + author: payload.author, + viewCount: 0, + linkUrl: payload.linkUrl + } + articleList = [article, ...articleList] + return article +} diff --git a/src/mock/certification.ts b/src/mock/certification.ts new file mode 100644 index 0000000..d3a5125 --- /dev/null +++ b/src/mock/certification.ts @@ -0,0 +1,256 @@ +/** + * 用户认证模拟数据 + */ +import type { + CertificationRecord, + CertificationStats, + CertificationQueryParams, + CertificationListResult, + CertificationStatus +} from '@/types/certification' + +// 第三方认证提供商 +const providers = ['阿里云实名认证', '腾讯云身份核验', '学信网', '天眼查', '企查查'] + +// 模拟身份证号脱敏 +function maskIdCard(idCard: string): string { + return idCard.substring(0, 6) + '********' + idCard.substring(14) +} + +// 模拟手机号脱敏 +function maskPhone(phone: string): string { + return phone.substring(0, 3) + '****' + phone.substring(7) +} + +// 生成模拟认证数据 +function generateCertifications(): CertificationRecord[] { + const names = ['张三', '李四', '王五', '赵六', '钱七', '孙八', '周九', '吴十', '郑十一', '冯十二'] + const schools = ['北京大学', '清华大学', '浙江大学', '复旦大学', '上海交通大学', '南京大学', '武汉大学', '中山大学'] + const majors = ['计算机科学与技术', '软件工程', '人工智能', '数据科学', '信息安全', '电子信息工程'] + const degrees = ['本科', '硕士', '博士'] + const certificates = ['PMP项目管理', '系统架构设计师', 'AWS认证', 'CPA注册会计师', '一级建造师', '高级工程师'] + const companies = ['阿里巴巴', '腾讯科技', '字节跳动', '华为技术', '京东集团', '美团点评'] + + const records: CertificationRecord[] = [] + let id = 1 + + // 生成实名认证记录 + for (let i = 0; i < 15; i++) { + const name = names[i % names.length]! + const status: CertificationStatus = i < 12 ? 'verified' : i < 14 ? 'pending' : 'expired' + const submittedAt = new Date(Date.now() - (i + 1) * 3 * 24 * 60 * 60 * 1000).toISOString() + + records.push({ + id: id++, + userId: 1000 + i, + userName: name, + userAvatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=cert_${i}`, + userPhone: maskPhone(`138${String(10000000 + i).padStart(8, '0')}`), + userEmail: `user${i + 1}@example.com`, + type: 'identity', + status, + identityInfo: { + realName: name, + idCardNumber: maskIdCard(`110101199${i}0101${1234 + i}`), + faceVerified: status === 'verified', + verifiedAt: status === 'verified' ? submittedAt : '' + }, + thirdPartyProvider: providers[i % providers.length]!, + thirdPartyOrderId: `ID${Date.now()}${i}`, + submittedAt, + verifiedAt: status === 'verified' ? submittedAt : undefined, + createdAt: submittedAt, + updatedAt: submittedAt + }) + } + + // 生成学历认证记录 + for (let i = 0; i < 10; i++) { + const name = names[(i + 3) % names.length]! + const status: CertificationStatus = i < 8 ? 'verified' : 'pending' + const submittedAt = new Date(Date.now() - (i + 1) * 5 * 24 * 60 * 60 * 1000).toISOString() + + records.push({ + id: id++, + userId: 1000 + i + 3, + userName: name, + userAvatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=edu_${i}`, + userPhone: maskPhone(`139${String(10000000 + i).padStart(8, '0')}`), + userEmail: `edu${i + 1}@example.com`, + type: 'education', + status, + educationInfo: { + school: schools[i % schools.length]!, + major: majors[i % majors.length]!, + degree: degrees[i % degrees.length]!, + graduationYear: 2018 + (i % 6), + certificateNumber: `学位证书编号***${1000 + i}`, + verifiedAt: status === 'verified' ? submittedAt : '' + }, + thirdPartyProvider: '学信网', + thirdPartyOrderId: `EDU${Date.now()}${i}`, + submittedAt, + verifiedAt: status === 'verified' ? submittedAt : undefined, + createdAt: submittedAt, + updatedAt: submittedAt + }) + } + + // 生成职业资格认证记录 + for (let i = 0; i < 8; i++) { + const name = names[(i + 5) % names.length]! + const status: CertificationStatus = i < 6 ? 'verified' : i < 7 ? 'rejected' : 'pending' + const submittedAt = new Date(Date.now() - (i + 1) * 7 * 24 * 60 * 60 * 1000).toISOString() + + records.push({ + id: id++, + userId: 1000 + i + 5, + userName: name, + userAvatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=pro_${i}`, + userPhone: maskPhone(`137${String(10000000 + i).padStart(8, '0')}`), + userEmail: `pro${i + 1}@example.com`, + type: 'professional', + status, + professionalInfo: { + certificateName: certificates[i % certificates.length]!, + certificateNumber: `资格证书编号***${2000 + i}`, + issuer: '人力资源和社会保障部', + issueDate: `${2020 + (i % 4)}-0${1 + (i % 9)}-15`, + expiryDate: i % 2 === 0 ? `${2025 + (i % 3)}-0${1 + (i % 9)}-15` : undefined, + verifiedAt: status === 'verified' ? submittedAt : '' + }, + thirdPartyProvider: providers[(i + 2) % providers.length]!, + thirdPartyOrderId: `PRO${Date.now()}${i}`, + rejectReason: status === 'rejected' ? '证书信息与系统记录不符,请核实后重新提交' : undefined, + submittedAt, + verifiedAt: status === 'verified' ? submittedAt : undefined, + createdAt: submittedAt, + updatedAt: submittedAt + }) + } + + // 生成企业认证记录 + for (let i = 0; i < 5; i++) { + const name = names[(i + 7) % names.length]! + const status: CertificationStatus = i < 4 ? 'verified' : 'pending' + const submittedAt = new Date(Date.now() - (i + 1) * 10 * 24 * 60 * 60 * 1000).toISOString() + + records.push({ + id: id++, + userId: 1000 + i + 7, + userName: name, + userAvatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=ent_${i}`, + userPhone: maskPhone(`136${String(10000000 + i).padStart(8, '0')}`), + userEmail: `ent${i + 1}@example.com`, + type: 'enterprise', + status, + enterpriseInfo: { + companyName: companies[i % companies.length]! + '(' + ['北京', '上海', '深圳', '杭州', '广州'][i % 5] + ')有限公司', + unifiedSocialCreditCode: `91110000***${3000 + i}`, + legalRepresentative: name, + registeredCapital: `${1000 + i * 500}万人民币`, + businessScope: '软件开发、技术咨询、技术服务、技术转让', + verifiedAt: status === 'verified' ? submittedAt : '' + }, + thirdPartyProvider: '天眼查', + thirdPartyOrderId: `ENT${Date.now()}${i}`, + submittedAt, + verifiedAt: status === 'verified' ? submittedAt : undefined, + createdAt: submittedAt, + updatedAt: submittedAt + }) + } + + return records +} + +const mockCertifications = generateCertifications() + +function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +// 获取认证统计 +export async function mockGetCertificationStats(): Promise { + await delay(150) + + const today = new Date() + today.setHours(0, 0, 0, 0) + + return { + total: mockCertifications.length, + verified: mockCertifications.filter(c => c.status === 'verified').length, + pending: mockCertifications.filter(c => c.status === 'pending').length, + rejected: mockCertifications.filter(c => c.status === 'rejected').length, + expired: mockCertifications.filter(c => c.status === 'expired').length, + todayNew: mockCertifications.filter(c => new Date(c.createdAt) >= today).length + } +} + +// 获取认证列表 +export async function mockGetCertificationList( + params: CertificationQueryParams = {} +): Promise { + await delay(200) + + const { page = 1, pageSize = 10, keyword, type, status, startDate, endDate } = params + + let filtered = [...mockCertifications] + + // 按关键词筛选 + if (keyword) { + const kw = keyword.toLowerCase() + filtered = filtered.filter(c => + c.userName.toLowerCase().includes(kw) || + c.userEmail.toLowerCase().includes(kw) || + c.userPhone.includes(kw) || + c.thirdPartyOrderId.toLowerCase().includes(kw) + ) + } + + // 按类型筛选 + if (type) { + filtered = filtered.filter(c => c.type === type) + } + + // 按状态筛选 + if (status) { + filtered = filtered.filter(c => c.status === status) + } + + // 按日期范围筛选 + if (startDate) { + const start = new Date(startDate) + filtered = filtered.filter(c => new Date(c.submittedAt) >= start) + } + if (endDate) { + const end = new Date(endDate) + end.setHours(23, 59, 59, 999) + filtered = filtered.filter(c => new Date(c.submittedAt) <= end) + } + + // 按提交时间倒序排列 + filtered.sort((a, b) => new Date(b.submittedAt).getTime() - new Date(a.submittedAt).getTime()) + + const start = (page - 1) * pageSize + const list = filtered.slice(start, start + pageSize) + + return { + list, + total: filtered.length, + page, + pageSize + } +} + +// 获取认证详情 +export async function mockGetCertificationById(id: number): Promise { + await delay(150) + return mockCertifications.find(c => c.id === id) || null +} + +// 获取用户的所有认证记录 +export async function mockGetUserCertifications(userId: number): Promise { + await delay(150) + return mockCertifications.filter(c => c.userId === userId) +} diff --git a/src/mock/cityCircle.ts b/src/mock/cityCircle.ts new file mode 100644 index 0000000..21ee6ac --- /dev/null +++ b/src/mock/cityCircle.ts @@ -0,0 +1,546 @@ +/** + * 城市圈子 Mock 数据 + */ +import type { + CityCircle, + CityCircleQueryParams, + CityCircleListResult, + CircleTopic, + CircleMember, + HotDiscussion, + CircleTag, + CircleStatus +} from '@/types/cityCircle' + +// 模拟标签数据 +const mockCircleTags: CircleTag[] = [ + { id: 1, name: '互联网', color: '#1677ff' }, + { id: 2, name: 'AI', color: '#722ed1' }, + { id: 3, name: '金融科技', color: '#13c2c2' }, + { id: 4, name: '新能源', color: '#52c41a' }, + { id: 5, name: '电商', color: '#fa541c' }, + { id: 6, name: '游戏', color: '#eb2f96' }, + { id: 7, name: '教育', color: '#faad14' }, + { id: 8, name: '医疗健康', color: '#2f54eb' } +] + +// 模拟成员数据 +const mockMembers: CircleMember[] = [ + { + id: 1, + userId: 101, + nickname: 'Nova', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Nova', + role: 'operator', + title: '圈子主持人', + tags: ['互联网'], + operatorType: '全职坐班', + joinedAt: '2024-01-15' + }, + { + id: 2, + userId: 102, + nickname: 'Vega', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Vega', + role: 'operator', + title: '招聘合伙人', + tags: ['AI'], + operatorType: '开放推广', + joinedAt: '2024-02-20' + }, + { + id: 3, + userId: 103, + nickname: 'CloudEdge', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=CloudEdge', + role: 'operator', + title: '技术顾问', + tags: ['运营', '架构'], + operatorType: '周五 AMA', + joinedAt: '2024-03-10' + }, + { + id: 4, + userId: 104, + nickname: 'Iris', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Iris', + role: 'operator', + title: '数据科学', + tags: ['金融科技'], + operatorType: '寻找合作', + joinedAt: '2024-04-05' + }, + { + id: 5, + userId: 105, + nickname: 'Kite', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Kite', + role: 'operator', + title: '运营志愿者', + tags: ['活动组织'], + operatorType: '可协作', + joinedAt: '2024-04-15' + }, + { + id: 6, + userId: 106, + nickname: '张三', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=zhangsan', + role: 'member', + tags: ['前端开发'], + joinedAt: '2024-05-01' + }, + { + id: 7, + userId: 107, + nickname: '李四', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=lisi', + role: 'member', + tags: ['后端开发'], + joinedAt: '2024-05-10' + } +] + +// 模拟话题数据 +const mockTopics: CircleTopic[] = [ + { id: 1, name: '互联网 讨论', description: '关注互联网的案例、岗位与技术交流', postCount: 120, isHot: true, createdAt: '2024-01-01' }, + { id: 2, name: 'AI 讨论', description: '关注 AI 的案例、岗位与技术交流', postCount: 102, isNew: true, createdAt: '2024-02-15' }, + { id: 3, name: '金融科技 讨论', description: '探讨金融科技的案例、岗位与技术交流', postCount: 84, isHot: true, createdAt: '2024-03-01' }, + { id: 4, name: '产品经理交流', description: '产品经理相关话题讨论', postCount: 65, createdAt: '2024-03-20' }, + { id: 5, name: '远程工作', description: '远程办公经验分享', postCount: 48, createdAt: '2024-04-01' } +] + +// 模拟热门讨论 +const mockHotDiscussions: HotDiscussion[] = [ + { id: 1, title: '互联网 讨论', topicName: '互联网', discussionCount: 120, tags: ['互联网', 'AI'] }, + { id: 2, title: '金融科技 讨论', topicName: '金融科技', discussionCount: 84, tags: ['金融科技'] } +] + +// 模拟城市圈子数据 +const mockCityCircles: CityCircle[] = [ + { + id: 1, + cityId: 1, + cityName: '北京', + cityNameEn: 'BEIJING', + cityCode: '110000', + description: '中国的首都和科技创新中心,聚集了大量互联网公司和AI研究机构。', + tags: [mockCircleTags[0]!, mockCircleTags[1]!, mockCircleTags[2]!], + icon: '北', + coverImage: '', + stats: { + developerCount: 3456, + openPositionCount: 892, + avgSalary: 35, + activeTopicCount: 3, + hotTopicCount: 2, + coreMemberCount: 8 + }, + topics: mockTopics, + hotDiscussions: mockHotDiscussions, + members: mockMembers, + operators: mockMembers.filter(m => m.role === 'operator'), + status: 'active', + sort: 1, + createdAt: '2024-01-01', + updatedAt: '2024-12-01' + }, + { + id: 2, + cityId: 2, + cityName: '上海', + cityNameEn: 'SHANGHAI', + cityCode: '310000', + description: '国际金融中心,聚集大量金融科技和外资企业。', + tags: [mockCircleTags[2]!, mockCircleTags[4]!], + icon: '沪', + stats: { + developerCount: 2890, + openPositionCount: 756, + avgSalary: 32, + activeTopicCount: 2, + hotTopicCount: 1, + coreMemberCount: 6 + }, + topics: mockTopics.slice(0, 3), + hotDiscussions: mockHotDiscussions.slice(0, 1), + members: mockMembers.slice(0, 5), + operators: mockMembers.filter(m => m.role === 'operator').slice(0, 3), + status: 'active', + sort: 2, + createdAt: '2024-01-05', + updatedAt: '2024-12-01' + }, + { + id: 3, + cityId: 3, + cityName: '杭州', + cityNameEn: 'HANGZHOU', + cityCode: '330100', + description: '电商之都,阿里巴巴总部所在地,创业氛围浓厚。', + tags: [mockCircleTags[4]!, mockCircleTags[0]!], + icon: '杭', + stats: { + developerCount: 2100, + openPositionCount: 520, + avgSalary: 28, + activeTopicCount: 2, + hotTopicCount: 1, + coreMemberCount: 5 + }, + topics: mockTopics.slice(0, 2), + hotDiscussions: [], + members: mockMembers.slice(0, 4), + operators: mockMembers.filter(m => m.role === 'operator').slice(0, 2), + status: 'active', + sort: 3, + createdAt: '2024-02-01', + updatedAt: '2024-11-20' + }, + { + id: 4, + cityId: 5, + cityName: '深圳', + cityNameEn: 'SHENZHEN', + cityCode: '440300', + description: '创新之城,硬件创业和互联网企业云集。', + tags: [mockCircleTags[0]!, mockCircleTags[3]!], + icon: '深', + stats: { + developerCount: 2650, + openPositionCount: 680, + avgSalary: 30, + activeTopicCount: 3, + hotTopicCount: 2, + coreMemberCount: 7 + }, + topics: mockTopics.slice(0, 4), + hotDiscussions: mockHotDiscussions, + members: mockMembers, + operators: mockMembers.filter(m => m.role === 'operator').slice(0, 4), + status: 'active', + sort: 4, + createdAt: '2024-02-10', + updatedAt: '2024-11-25' + }, + { + id: 5, + cityId: 6, + cityName: '成都', + cityNameEn: 'CHENGDU', + cityCode: '510100', + description: '西南科技重镇,游戏和互联网企业快速发展。', + tags: [mockCircleTags[5]!, mockCircleTags[0]!], + icon: '蓉', + stats: { + developerCount: 1560, + openPositionCount: 380, + avgSalary: 22, + activeTopicCount: 2, + hotTopicCount: 1, + coreMemberCount: 4 + }, + topics: mockTopics.slice(0, 2), + hotDiscussions: mockHotDiscussions.slice(0, 1), + members: mockMembers.slice(0, 3), + operators: mockMembers.filter(m => m.role === 'operator').slice(0, 2), + status: 'active', + sort: 5, + createdAt: '2024-03-01', + updatedAt: '2024-11-15' + }, + { + id: 6, + cityId: 7, + cityName: '广州', + cityNameEn: 'GUANGZHOU', + cityCode: '440100', + description: '华南商业中心,传统与新兴产业并重。', + tags: [mockCircleTags[4]!, mockCircleTags[6]!], + icon: '穗', + stats: { + developerCount: 1890, + openPositionCount: 450, + avgSalary: 25, + activeTopicCount: 2, + hotTopicCount: 1, + coreMemberCount: 5 + }, + topics: mockTopics.slice(0, 3), + hotDiscussions: mockHotDiscussions.slice(0, 1), + members: mockMembers.slice(0, 4), + operators: mockMembers.filter(m => m.role === 'operator').slice(0, 3), + status: 'pending', + sort: 6, + createdAt: '2024-03-15', + updatedAt: '2024-11-10' + } +] + +// 延迟模拟 +function delay(ms: number = 300): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +// 获取城市圈子列表 +export async function mockGetCityCircleList(params: CityCircleQueryParams = {}): Promise { + await delay() + + let list = [...mockCityCircles] + + // 关键词筛选 + if (params.keyword) { + const keyword = params.keyword.toLowerCase() + list = list.filter(item => + item.cityName.toLowerCase().includes(keyword) || + item.cityNameEn?.toLowerCase().includes(keyword) || + item.description.toLowerCase().includes(keyword) + ) + } + + // 城市ID筛选 + if (params.cityId) { + list = list.filter(item => item.cityId === params.cityId) + } + + // 状态筛选 + if (params.status) { + list = list.filter(item => item.status === params.status) + } + + // 排序 + list.sort((a, b) => a.sort - b.sort) + + // 分页 + const page = params.page || 1 + const pageSize = params.pageSize || 10 + const start = (page - 1) * pageSize + const pageList = list.slice(start, start + pageSize) + + return { + list: pageList, + total: list.length, + page, + pageSize + } +} + +// 获取城市圈子详情 +export async function mockGetCityCircleDetail(id: number): Promise { + await delay() + return mockCityCircles.find(item => item.id === id) || null +} + +// 创建城市圈子 +export async function mockCreateCityCircle(data: Partial): Promise { + await delay(500) + + const newCircle: CityCircle = { + id: mockCityCircles.length + 1, + cityId: data.cityId || 0, + cityName: data.cityName || '', + cityNameEn: data.cityNameEn, + cityCode: data.cityCode || '', + description: data.description || '', + tags: data.tags || [], + icon: data.icon, + coverImage: data.coverImage, + stats: { + developerCount: 0, + openPositionCount: 0, + avgSalary: 0, + activeTopicCount: 0, + hotTopicCount: 0, + coreMemberCount: 0 + }, + topics: [], + hotDiscussions: [], + members: [], + operators: [], + status: data.status || 'pending', + sort: data.sort || mockCityCircles.length + 1, + createdAt: new Date().toISOString().split('T')[0]!, + updatedAt: new Date().toISOString().split('T')[0]! + } + + mockCityCircles.push(newCircle) + return newCircle +} + +// 更新城市圈子 +export async function mockUpdateCityCircle(id: number, data: Partial): Promise { + await delay(500) + + const index = mockCityCircles.findIndex(item => item.id === id) + if (index === -1) return false + + mockCityCircles[index] = { + ...mockCityCircles[index]!, + ...data, + updatedAt: new Date().toISOString().split('T')[0]! + } + + return true +} + +// 删除城市圈子 +export async function mockDeleteCityCircle(id: number): Promise { + await delay(500) + + const index = mockCityCircles.findIndex(item => item.id === id) + if (index === -1) return false + + mockCityCircles.splice(index, 1) + return true +} + +// 更新圈子状态 +export async function mockUpdateCircleStatus(id: number, status: CircleStatus): Promise { + await delay(300) + + const circle = mockCityCircles.find(item => item.id === id) + if (!circle) return false + + circle.status = status + circle.updatedAt = new Date().toISOString().split('T')[0]! + return true +} + +// 添加话题 +export async function mockAddCircleTopic(circleId: number, topic: Omit): Promise { + await delay(300) + + const circle = mockCityCircles.find(item => item.id === circleId) + if (!circle) return null + + const newTopic: CircleTopic = { + id: circle.topics.length + 100, + name: topic.name, + description: topic.description, + postCount: 0, + isHot: topic.isHot, + isNew: true, + createdAt: new Date().toISOString().split('T')[0]! + } + + circle.topics.push(newTopic) + circle.stats.activeTopicCount = circle.topics.length + return newTopic +} + +// 删除话题 +export async function mockDeleteCircleTopic(circleId: number, topicId: number): Promise { + await delay(300) + + const circle = mockCityCircles.find(item => item.id === circleId) + if (!circle) return false + + const index = circle.topics.findIndex(t => t.id === topicId) + if (index === -1) return false + + circle.topics.splice(index, 1) + circle.stats.activeTopicCount = circle.topics.length + return true +} + +// 添加成员 +export async function mockAddCircleMember(circleId: number, member: Omit): Promise { + await delay(300) + + const circle = mockCityCircles.find(item => item.id === circleId) + if (!circle) return null + + const newMember: CircleMember = { + ...member, + id: circle.members.length + 200, + joinedAt: new Date().toISOString().split('T')[0]! + } + + circle.members.push(newMember) + + if (newMember.role === 'operator') { + circle.operators.push(newMember) + circle.stats.coreMemberCount = circle.operators.length + } + + return newMember +} + +// 移除成员 +export async function mockRemoveCircleMember(circleId: number, memberId: number): Promise { + await delay(300) + + const circle = mockCityCircles.find(item => item.id === circleId) + if (!circle) return false + + const memberIndex = circle.members.findIndex(m => m.id === memberId) + if (memberIndex === -1) return false + + const member = circle.members[memberIndex] + circle.members.splice(memberIndex, 1) + + if (member?.role === 'operator') { + const opIndex = circle.operators.findIndex(o => o.id === memberId) + if (opIndex !== -1) { + circle.operators.splice(opIndex, 1) + circle.stats.coreMemberCount = circle.operators.length + } + } + + return true +} + +// 更新成员角色 +export async function mockUpdateMemberRole(circleId: number, memberId: number, role: 'operator' | 'member', operatorType?: string): Promise { + await delay(300) + + const circle = mockCityCircles.find(item => item.id === circleId) + if (!circle) return false + + const member = circle.members.find(m => m.id === memberId) + if (!member) return false + + const wasOperator = member.role === 'operator' + member.role = role + + if (role === 'operator') { + member.operatorType = operatorType + if (!wasOperator) { + circle.operators.push(member) + } + } else { + member.operatorType = undefined + if (wasOperator) { + const opIndex = circle.operators.findIndex(o => o.id === memberId) + if (opIndex !== -1) { + circle.operators.splice(opIndex, 1) + } + } + } + + circle.stats.coreMemberCount = circle.operators.length + return true +} + +// 获取可用标签列表 +export async function mockGetCircleTags(): Promise { + await delay(200) + return [...mockCircleTags] +} + +// 获取可绑定的城市列表(从城市管理) +export async function mockGetAvailableCities(): Promise> { + await delay(200) + return [ + { id: 1, name: '北京市', code: '110000' }, + { id: 2, name: '上海市', code: '310000' }, + { id: 3, name: '杭州市', code: '330100' }, + { id: 4, name: '南京市', code: '320100' }, + { id: 5, name: '深圳市', code: '440300' }, + { id: 6, name: '成都市', code: '510100' }, + { id: 7, name: '广州市', code: '440100' }, + { id: 8, name: '武汉市', code: '420100' }, + { id: 9, name: '西安市', code: '610100' }, + { id: 10, name: '苏州市', code: '320500' } + ] +} diff --git a/src/mock/comment.ts b/src/mock/comment.ts new file mode 100644 index 0000000..4653e0f --- /dev/null +++ b/src/mock/comment.ts @@ -0,0 +1,164 @@ +/** + * 评论管理模拟数据 + */ +import type { CommentRecord, CommentUser, CommentPost, CommentQueryParams, CommentListResult, CommentStatus, CommentStats } from '@/types' + +// 模拟帖子 +const mockPosts: CommentPost[] = [ + { id: 1, title: 'Vue3 组合式API最佳实践分享', authorName: '前端小王' }, + { id: 2, title: '如何高效学习TypeScript', authorName: '码农老张' }, + { id: 3, title: 'React vs Vue 深度对比', authorName: '全栈开发者' }, + { id: 4, title: 'Node.js性能优化技巧', authorName: '后端大神' }, + { id: 5, title: '程序员如何提升软技能', authorName: '技术管理者' }, + { id: 6, title: '2024年前端发展趋势', authorName: '前端架构师' } +] + +// 模拟用户 +const mockUsers: CommentUser[] = [ + { id: 101, username: 'coder_li', nickname: '程序员小李', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=li' }, + { id: 102, username: 'dev_wang', nickname: '开发者小王', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=wang' }, + { id: 103, username: 'tech_zhang', nickname: '技术张', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=zhang' }, + { id: 104, username: 'front_zhao', nickname: '前端赵', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=zhao' }, + { id: 105, username: 'back_sun', nickname: '后端孙', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=sun' }, + { id: 106, username: 'full_zhou', nickname: '全栈周', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=zhou' } +] + +// 评论内容模板 +const commentContents = [ + '写得太好了,学到了很多!', + '感谢分享,正好需要这个', + '这个观点我不太同意,我觉得...', + '楼主能详细说一下具体实现吗?', + '已收藏,以后慢慢看', + '代码示例很清晰,赞一个', + '有没有相关的项目案例推荐?', + '这个问题我也遇到过,当时是这样解决的...', + '期待楼主更多的分享', + '干货满满,建议加精' +] + +const replyContents = [ + '同意你的观点', + '补充一点,还可以这样做...', + '谢谢回复,明白了', + '这个解释很清楚', + '我试了一下,确实可行' +] + +const statuses: CommentStatus[] = ['normal', 'normal', 'normal', 'normal', 'hidden'] + +// 生成模拟评论 +function generateMockComments(): CommentRecord[] { + const records: CommentRecord[] = [] + let id = 1 + + for (let i = 0; i < 80; i++) { + const post = mockPosts[i % mockPosts.length]! + const user = mockUsers[i % mockUsers.length]! + const isReply = i % 4 === 3 + const createdAt = new Date(Date.now() - Math.floor(Math.random() * 30 * 24 * 60 * 60 * 1000)) + + records.push({ + id: id++, + post, + user, + content: isReply ? replyContents[i % replyContents.length]! : commentContents[i % commentContents.length]!, + likeCount: Math.floor(Math.random() * 100), + replyCount: isReply ? 0 : Math.floor(Math.random() * 10), + parentId: isReply ? Math.floor(Math.random() * (id - 1)) + 1 : undefined, + parentContent: isReply ? commentContents[Math.floor(Math.random() * commentContents.length)] : undefined, + status: statuses[i % statuses.length]!, + createdAt: createdAt.toISOString() + }) + } + + return records.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) +} + +let mockComments = generateMockComments() + +function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +// 获取评论列表 +export async function mockGetCommentList(params: CommentQueryParams = {}): Promise { + await delay(300) + const { page = 1, pageSize = 10, keyword, postId, status, isReply } = params + + let filtered = [...mockComments] + + if (keyword) { + filtered = filtered.filter(c => c.content.includes(keyword) || c.user.nickname.includes(keyword)) + } + if (postId) { + filtered = filtered.filter(c => c.post.id === postId) + } + if (status) { + filtered = filtered.filter(c => c.status === status) + } + if (isReply !== undefined) { + filtered = filtered.filter(c => isReply ? c.parentId !== undefined : c.parentId === undefined) + } + + const start = (page - 1) * pageSize + return { list: filtered.slice(start, start + pageSize), total: filtered.length, page, pageSize } +} + +// 获取评论统计 +export async function mockGetCommentStats(): Promise { + await delay(100) + const today = new Date().toDateString() + return { + total: mockComments.length, + todayNew: mockComments.filter(c => new Date(c.createdAt).toDateString() === today).length, + normal: mockComments.filter(c => c.status === 'normal').length, + hidden: mockComments.filter(c => c.status === 'hidden').length + } +} + +// 隐藏评论 +export async function mockHideComment(id: number): Promise { + await delay(200) + const comment = mockComments.find(c => c.id === id) + if (comment) comment.status = 'hidden' +} + +// 恢复评论 +export async function mockRestoreComment(id: number): Promise { + await delay(200) + const comment = mockComments.find(c => c.id === id) + if (comment) comment.status = 'normal' +} + +// 删除评论 +export async function mockDeleteComment(id: number): Promise { + await delay(200) + mockComments = mockComments.filter(c => c.id !== id) +} + +// 批量删除 +export async function mockBatchDeleteComments(ids: number[]): Promise { + await delay(300) + mockComments = mockComments.filter(c => !ids.includes(c.id)) +} + +// 获取帖子列表(用于筛选) +export async function mockGetCommentPosts(): Promise { + await delay(100) + return mockPosts +} + +// 编辑评论 +export async function mockUpdateComment(id: number, data: { content: string; likeCount?: number; replyCount?: number }): Promise { + await delay(200) + const comment = mockComments.find(c => c.id === id) + if (comment) { + comment.content = data.content + if (data.likeCount !== undefined) comment.likeCount = data.likeCount + if (data.replyCount !== undefined) comment.replyCount = data.replyCount + return comment + } + return null +} + diff --git a/src/mock/conversation.ts b/src/mock/conversation.ts new file mode 100644 index 0000000..4e07c66 --- /dev/null +++ b/src/mock/conversation.ts @@ -0,0 +1,335 @@ +/** + * 客服会话相关模拟数据 + */ +import type { + ConversationAgent, + ConversationListQuery, + ConversationListResult, + ConversationMessage, + ConversationMetrics, + ConversationPriority, + ConversationSession, + ConversationStatus, + ConversationTag +} from '@/types' + +const supportAgents: ConversationAgent[] = [ + { + id: 11, + name: '林清', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=agent_lin', + title: '资深客服', + workload: 8, + expertise: ['会员权益', '支付问题'] + }, + { + id: 12, + name: '周晚', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=agent_zhou', + title: '体验顾问', + workload: 5, + expertise: ['产品咨询', '活动政策'] + }, + { + id: 13, + name: '程斐', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=agent_cheng', + title: '资深专家', + workload: 6, + expertise: ['订单售后', '履约异常'] + }, + { + id: 14, + name: '白凝', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=agent_bai', + title: '运营支持', + workload: 3, + expertise: ['社群转接', '投诉升级'] + } +] + +const sessionTags: ConversationTag[] = [ + { id: 1, name: '支付', color: '#1890ff' }, + { id: 2, name: '权益', color: '#722ed1' }, + { id: 3, name: '功能', color: '#13c2c2' }, + { id: 4, name: '投诉', color: '#ff4d4f' }, + { id: 5, name: '活动', color: '#fa8c16' }, + { id: 6, name: 'BUG', color: '#a0d911' } +] + +const intents = ['开票需求', '会员续费', '提现异常', '功能咨询', '活动规则', '体验反馈'] +const sources = ['App 内反馈', '官网咨询', '小程序客服', '社群转接'] +const customerLevels = ['L1', 'L2', 'L3', 'L4', 'L5'] +const cities = ['杭州', '上海', '成都', '武汉', '广州', '深圳'] +const customerNames = ['南鸢', '时年', '望舒', '青禾', '阿岚', '林舟', '潮生', '江迟', '枝夏', '安意'] + +const channelPool: Array = ['app', 'web', 'wechat', 'miniapp'] +const priorityPool: ConversationPriority[] = ['normal', 'normal', 'high', 'vip'] +const statusPool: ConversationStatus[] = ['waiting', 'active', 'pending', 'resolved', 'active', 'pending'] + +function randomItem(arr: T[]): T { + return arr[Math.floor(Math.random() * arr.length)]! +} + +function randomInt(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1)) + min +} + +function pickTags(): ConversationTag[] { + const count = randomInt(1, 3) + const shuffled = [...sessionTags].sort(() => Math.random() - 0.5) + return shuffled.slice(0, count) +} + +function buildMessages( + customerName: string, + customerAvatar: string, + agent?: ConversationAgent, + startTime?: Date +): ConversationMessage[] { + const userMessages = [ + '你好,我想咨询一下昨天支付的订单还没有到账是怎么回事?', + '我看到权益说明里有些不一致,麻烦帮我核实下。', + '现在打开功能的时候会提示网络错误,可以帮忙看看吗?', + '关于新的社群活动我有些疑问,规则是不是已经更新了?' + ] + const agentMessages = [ + '您好,已经为您核实到订单,目前支付渠道反馈处理中,大约 10 分钟内到账。', + '权益说明我们刚刚做了更新,我发一份最新版给您,您稍等查看。', + '收到,我们正在排查这个异常,稍后会第一时间同步处理进度。', + '关于活动规则我帮您确认了,新的版本中需要满足邀请条件才可报名。' + ] + + const messages: ConversationMessage[] = [] + const base = startTime ? new Date(startTime) : new Date(Date.now() - randomInt(10, 72) * 60 * 1000) + let pointer = base.getTime() + const rounds = agent ? randomInt(3, 6) : randomInt(2, 4) + + for (let i = 0; i < rounds; i++) { + const userMessage: ConversationMessage = { + id: Number(`${pointer}${i}`), + sender: 'user', + senderName: customerName, + content: randomItem(userMessages), + timestamp: new Date(pointer).toISOString(), + avatar: customerAvatar + } + messages.push(userMessage) + pointer += randomInt(1, 6) * 60 * 1000 + if (agent) { + const agentMessage: ConversationMessage = { + id: Number(`${pointer}${i}`), + sender: 'agent', + senderName: agent.name, + content: randomItem(agentMessages), + timestamp: new Date(pointer).toISOString(), + avatar: agent.avatar + } + messages.push(agentMessage) + pointer += randomInt(2, 8) * 60 * 1000 + } + } + + return messages +} + +function generateMockConversations(): ConversationSession[] { + const sessions: ConversationSession[] = [] + for (let i = 1; i <= 36; i++) { + const customer = { + id: 2000 + i, + nickname: customerNames[i % customerNames.length]!, + avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=session_${i}`, + city: randomItem(cities), + vip: Math.random() > 0.7, + level: randomItem(customerLevels), + intents: [randomItem(intents)] + } + const assignedAgent = Math.random() > 0.2 ? randomItem(supportAgents) : undefined + let status = randomItem(statusPool) + if (!assignedAgent && status !== 'waiting') { + status = 'waiting' + } + const createdAt = new Date(Date.now() - randomInt(1, 7) * 24 * 60 * 60 * 1000) + const messages = buildMessages(customer.nickname, customer.avatar, assignedAgent, createdAt) + const lastMessage = messages[messages.length - 1]! + const firstAgentMessage = messages.find(msg => msg.sender === 'agent') + const priority = randomItem(priorityPool) + + sessions.push({ + id: i, + sessionCode: `KF${202400 + i}`, + channel: randomItem(channelPool), + status, + priority, + createdAt: messages[0]!.timestamp, + lastMessageAt: lastMessage.timestamp, + lastMessage: lastMessage.content, + unreadCount: status === 'waiting' ? randomInt(1, 6) : Math.max(0, randomInt(0, 3) - 1), + totalMessages: messages.length, + waitingTime: status === 'waiting' ? randomInt(5, 25) : randomInt(1, 10), + firstResponseAt: firstAgentMessage?.timestamp, + resolvedAt: status === 'resolved' ? lastMessage.timestamp : undefined, + satisfaction: status === 'resolved' ? Number((4.2 + Math.random() * 0.7).toFixed(1)) : undefined, + autoDetectedIntent: randomItem(intents), + source: randomItem(sources), + tags: pickTags(), + customer, + assignedAgent: assignedAgent ? { ...assignedAgent } : undefined, + messages + }) + } + + return sessions.sort((a, b) => new Date(b.lastMessageAt).getTime() - new Date(a.lastMessageAt).getTime()) +} + +let conversationSessions: ConversationSession[] = generateMockConversations() + +function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +function buildMetrics(sessions: ConversationSession[]): ConversationMetrics { + const waitingCount = sessions.filter(session => session.status === 'waiting').length + const activeCount = sessions.filter(session => session.status === 'active').length + const pendingCount = sessions.filter(session => session.status === 'pending').length + const resolvedToday = sessions.filter(session => { + if (!session.resolvedAt) return false + const resolvedDate = new Date(session.resolvedAt) + const now = new Date() + return ( + resolvedDate.getFullYear() === now.getFullYear() && + resolvedDate.getMonth() === now.getMonth() && + resolvedDate.getDate() === now.getDate() + ) + }).length + + const responseDiffs = sessions + .filter(session => session.firstResponseAt) + .map(session => new Date(session.firstResponseAt!).getTime() - new Date(session.createdAt).getTime()) + + const satisfactionScores = sessions + .filter(session => session.satisfaction) + .map(session => session.satisfaction!) + + const avgFirstResponse = + responseDiffs.length > 0 ? Math.round(responseDiffs.reduce((a, b) => a + b, 0) / responseDiffs.length / 60000) : 0 + const satisfaction = + satisfactionScores.length > 0 + ? Number((satisfactionScores.reduce((a, b) => a + b, 0) / satisfactionScores.length).toFixed(1)) + : 0 + + return { + waitingCount, + activeCount, + pendingCount, + resolvedToday, + satisfaction, + avgFirstResponse + } +} + +export async function mockGetConversationList( + params: ConversationListQuery = {} +): Promise { + await delay(250) + const { + page = 1, + pageSize = 10, + keyword, + status = 'all', + channel = 'all', + priority = 'all', + vipOnly, + dateRange + } = params + + let filtered = [...conversationSessions] + + if (keyword) { + filtered = filtered.filter(session => + [session.sessionCode, session.customer.nickname, session.lastMessage, session.assignedAgent?.name || ''] + .join(' ') + .toLowerCase() + .includes(keyword.toLowerCase()) + ) + } + + if (status !== 'all') { + filtered = filtered.filter(session => session.status === status) + } + + if (channel !== 'all') { + filtered = filtered.filter(session => session.channel === channel) + } + + if (priority !== 'all') { + filtered = filtered.filter(session => session.priority === priority) + } + + if (vipOnly) { + filtered = filtered.filter(session => session.customer.vip) + } + + if (dateRange && dateRange.length === 2) { + const [start, end] = dateRange + const startTime = new Date(start).getTime() + const endTime = new Date(end).getTime() + filtered = filtered.filter(session => { + const created = new Date(session.createdAt).getTime() + return created >= startTime && created <= endTime + }) + } + + const start = (page - 1) * pageSize + const list = filtered.slice(start, start + pageSize) + + return { + list, + total: filtered.length, + page, + pageSize, + metrics: buildMetrics(conversationSessions) + } +} + +export async function mockUpdateConversationStatus( + sessionId: number, + status: ConversationStatus +): Promise { + await delay(200) + const target = conversationSessions.find(session => session.id === sessionId) + if (!target) return undefined + target.status = status + if (status === 'resolved' || status === 'closed') { + target.resolvedAt = new Date().toISOString() + target.unreadCount = 0 + target.waitingTime = 0 + } + if (status !== 'waiting' && !target.firstResponseAt) { + target.firstResponseAt = new Date().toISOString() + } + return target +} + +export async function mockAssignConversationAgent( + sessionId: number, + agentId: number +): Promise { + await delay(200) + const target = conversationSessions.find(session => session.id === sessionId) + const agent = supportAgents.find(item => item.id === agentId) + if (!target || !agent) return undefined + target.assignedAgent = { ...agent } + if (target.status === 'waiting') { + target.status = 'active' + } + if (!target.firstResponseAt) { + target.firstResponseAt = new Date().toISOString() + } + return target +} + +export function mockGetSupportAgents(): ConversationAgent[] { + return supportAgents.map(agent => ({ ...agent })) +} diff --git a/src/mock/index.ts b/src/mock/index.ts new file mode 100644 index 0000000..a29934d --- /dev/null +++ b/src/mock/index.ts @@ -0,0 +1,14 @@ +/** + * Mock数据统一导出 + */ +export * from './user' +export * from './post' +export * from './project' +export * from './recruitment' +export * from './comment' +export * from './talent' +export * from './conversation' +export * from './article' +export * from './cityCircle' +export * from './certification' +export * from './signedProject' diff --git a/src/mock/post.ts b/src/mock/post.ts new file mode 100644 index 0000000..c0fa49c --- /dev/null +++ b/src/mock/post.ts @@ -0,0 +1,236 @@ +/** + * 帖子相关模拟数据 + */ +import type { PostInfo, PostTag, PostAuthor, PostQueryParams, PostListResult } from '@/types' + +// 模拟标签数据 +const mockTags: PostTag[] = [ + { id: 1, name: 'Vue', color: '#42b883' }, + { id: 2, name: 'React', color: '#61dafb' }, + { id: 3, name: 'TypeScript', color: '#3178c6' }, + { id: 4, name: 'JavaScript', color: '#f7df1e' }, + { id: 5, name: 'Node.js', color: '#339933' }, + { id: 6, name: 'Python', color: '#3776ab' }, + { id: 7, name: 'Go', color: '#00add8' }, + { id: 8, name: '求助', color: '#ff4d4f' }, + { id: 9, name: '分享', color: '#722ed1' }, + { id: 10, name: '讨论', color: '#fa8c16' }, + { id: 11, name: '官方', color: '#1890ff' } // 官方标签 +] + +// 模拟用户数据 +const mockAuthors: PostAuthor[] = [ + { id: 0, username: 'official', nickname: '官方', avatar: 'https://api.dicebear.com/7.x/bottts/svg?seed=official' }, // 官方账号 + { id: 1, username: 'zhangsan', nickname: '张三', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=zhangsan' }, + { id: 2, username: 'lisi', nickname: '李四', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=lisi' }, + { id: 3, username: 'wangwu', nickname: '王五', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=wangwu' }, + { id: 4, username: 'zhaoliu', nickname: '赵六', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=zhaoliu' }, + { id: 5, username: 'coder001', nickname: '代码狂人', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=coder001' }, + { id: 6, username: 'devmaster', nickname: '开发大神', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=devmaster' } +] + +// 生成随机帖子内容 +const postTitles = [ + 'Vue3 Composition API 最佳实践总结', + 'TypeScript 高级类型体操指南', + 'React Hooks 深入理解与应用', + '前端性能优化的 10 个关键点', + 'Node.js 微服务架构设计', + 'Go 语言并发编程详解', + 'Python 数据分析入门到精通', + '如何成为一名优秀的全栈工程师', + '求助:Webpack 打包优化问题', + '分享我的开源项目经验', + 'CSS Grid 布局完全指南', + 'Docker 容器化部署实战', + 'MongoDB 数据库设计最佳实践', + 'Git 工作流程规范建议', + 'REST API 设计原则讨论' +] + +const postContents = [ + '最近在项目中使用了这个技术,遇到了一些问题,经过研究后总结了一些经验分享给大家...', + '这是一篇关于技术实践的文章,希望能够帮助到正在学习的同学们...', + '在实际开发中,我们经常会遇到这类问题,今天来详细讲解一下解决方案...', + '作为一名开发者,我认为掌握这些技能是非常重要的,下面是我的一些心得体会...', + '经过长时间的实践和总结,我整理了这份详细的教程,涵盖了从入门到进阶的所有内容...' +] + +// 生成模拟帖子数据 +function generateMockPosts(): PostInfo[] { + const posts: PostInfo[] = [] + for (let i = 1; i <= 50; i++) { + const viewCount = Math.floor(Math.random() * 3000) + const likeCount = Math.floor(Math.random() * 1000) + const isHot = viewCount > 1000 || likeCount > 500 + const tag1 = mockTags[i % mockTags.length]! + const tag2 = mockTags[(i + 3) % mockTags.length]! + + posts.push({ + id: i, + title: postTitles[i % postTitles.length]!, + content: postContents[i % postContents.length]!, + author: mockAuthors[(i % (mockAuthors.length - 1)) + 1]!, // 跳过官方账号 + tags: tag1.id === tag2.id ? [tag1] : [tag1, tag2], + viewCount, + likeCount, + commentCount: Math.floor(Math.random() * 200), + isHot, + isOfficial: false, // 普通帖子都不是官方 + status: Math.random() > 0.1 ? 'published' : 'hidden', + createdAt: new Date(Date.now() - Math.floor(Math.random() * 30 * 24 * 60 * 60 * 1000)).toISOString(), + updatedAt: new Date(Date.now() - Math.floor(Math.random() * 7 * 24 * 60 * 60 * 1000)).toISOString() + }) + } + return posts.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) +} + +let mockPosts = generateMockPosts() + +// 模拟延迟 +function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +// 获取帖子列表 +export async function mockGetPostList(params: PostQueryParams = {}): Promise { + await delay(300) + const { page = 1, pageSize = 10, keyword, tagId, status, isHot, isOfficial } = params + + let filtered = [...mockPosts] + + if (keyword) { + filtered = filtered.filter(p => p.title.includes(keyword) || p.author.nickname.includes(keyword)) + } + if (tagId) { + filtered = filtered.filter(p => p.tags.some(t => t.id === tagId)) + } + if (status) { + filtered = filtered.filter(p => p.status === status) + } + if (isHot !== undefined) { + filtered = filtered.filter(p => p.isHot === isHot) + } + if (isOfficial !== undefined) { + filtered = filtered.filter(p => p.isOfficial === isOfficial) + } + + const start = (page - 1) * pageSize + const list = filtered.slice(start, start + pageSize) + + return { list, total: filtered.length, page, pageSize } +} + +// 删除帖子 +export async function mockDeletePost(id: number): Promise { + await delay(200) + mockPosts = mockPosts.filter(p => p.id !== id) +} + +// 批量删除帖子 +export async function mockBatchDeletePosts(ids: number[]): Promise { + await delay(300) + mockPosts = mockPosts.filter(p => !ids.includes(p.id)) +} + +// 编辑帖子 +export async function mockUpdatePost(id: number, data: { + title?: string + content?: string + tagIds?: number[] + viewCount?: number + likeCount?: number + commentCount?: number +}): Promise { + await delay(200) + const post = mockPosts.find(p => p.id === id) + if (post) { + if (data.title !== undefined) post.title = data.title + if (data.content !== undefined) post.content = data.content + if (data.tagIds !== undefined) { + post.tags = mockTags.filter(t => data.tagIds!.includes(t.id)) + } + if (data.viewCount !== undefined) post.viewCount = data.viewCount + if (data.likeCount !== undefined) post.likeCount = data.likeCount + if (data.commentCount !== undefined) post.commentCount = data.commentCount + post.updatedAt = new Date().toISOString() + return post + } + return null +} + +// 设置/取消热门 +export async function mockToggleHot(id: number, isHot: boolean): Promise { + await delay(200) + const post = mockPosts.find(p => p.id === id) + if (post) { + post.isHot = isHot + post.updatedAt = new Date().toISOString() + return post + } + return null +} + +// 获取标签列表 +export async function mockGetTags(): Promise { + await delay(100) + return mockTags +} + +// 创建帖子(发布官方帖子) +export async function mockCreatePost(data: { + title: string + content: string + tagIds: number[] + isOfficial?: boolean +}): Promise { + await delay(300) + const officialTag = mockTags.find(t => t.name === '官方')! + const officialAuthor = mockAuthors.find(a => a.username === 'official')! + + const newPost: PostInfo = { + id: Math.max(...mockPosts.map(p => p.id)) + 1, + title: data.title, + content: data.content, + author: data.isOfficial ? officialAuthor : mockAuthors[1]!, + tags: data.isOfficial + ? [officialTag, ...mockTags.filter(t => data.tagIds.includes(t.id) && t.id !== officialTag.id)] + : mockTags.filter(t => data.tagIds.includes(t.id)), + viewCount: 0, + likeCount: 0, + commentCount: 0, + isHot: false, + isOfficial: data.isOfficial ?? false, + status: 'published', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + } + + mockPosts.unshift(newPost) // 添加到列表开头 + return newPost +} + +// 设置/取消官方 +export async function mockToggleOfficial(id: number, isOfficial: boolean): Promise { + await delay(200) + const post = mockPosts.find(p => p.id === id) + const officialTag = mockTags.find(t => t.name === '官方')! + const officialAuthor = mockAuthors.find(a => a.username === 'official')! + + if (post) { + post.isOfficial = isOfficial + if (isOfficial) { + // 设为官方时,添加官方标签并修改作者 + if (!post.tags.some(t => t.id === officialTag.id)) { + post.tags.unshift(officialTag) + } + post.author = officialAuthor + } else { + // 取消官方时,移除官方标签 + post.tags = post.tags.filter(t => t.id !== officialTag.id) + } + post.updatedAt = new Date().toISOString() + return post + } + return null +} diff --git a/src/mock/project.ts b/src/mock/project.ts new file mode 100644 index 0000000..e8cfb22 --- /dev/null +++ b/src/mock/project.ts @@ -0,0 +1,183 @@ +/** + * 项目相关模拟数据 + */ +import type { ProjectInfo, ProjectTag, ProjectPublisher, ProjectQueryParams, ProjectListResult, WorkType } from '@/types' + +// 模拟项目标签 +const mockProjectTags: ProjectTag[] = [ + { id: 1, name: 'Vue', color: '#42b883' }, + { id: 2, name: 'React', color: '#61dafb' }, + { id: 3, name: 'Node.js', color: '#339933' }, + { id: 4, name: 'Python', color: '#3776ab' }, + { id: 5, name: 'Java', color: '#007396' }, + { id: 6, name: 'Go', color: '#00add8' }, + { id: 7, name: '小程序', color: '#07c160' }, + { id: 8, name: 'APP开发', color: '#ff6b6b' }, + { id: 9, name: '数据分析', color: '#845ef7' }, + { id: 10, name: 'AI/ML', color: '#ff922b' } +] + +// 模拟发布人 +const mockPublishers: ProjectPublisher[] = [ + { id: 1, username: 'techcorp', nickname: '科技有限公司', avatar: 'https://api.dicebear.com/7.x/identicon/svg?seed=techcorp', company: '科技有限公司' }, + { id: 2, username: 'startup', nickname: '创业团队', avatar: 'https://api.dicebear.com/7.x/identicon/svg?seed=startup', company: '创新科技' }, + { id: 3, username: 'bigcompany', nickname: '大厂HR', avatar: 'https://api.dicebear.com/7.x/identicon/svg?seed=bigcompany', company: '某大厂' }, + { id: 4, username: 'freelancer', nickname: '独立开发者', avatar: 'https://api.dicebear.com/7.x/identicon/svg?seed=freelancer' }, + { id: 5, username: 'agency', nickname: '外包公司', avatar: 'https://api.dicebear.com/7.x/identicon/svg?seed=agency', company: '软件外包' } +] + +const projectNames = [ + '企业官网开发项目', '电商小程序开发', '数据可视化平台', 'CRM系统定制开发', + 'APP后端接口开发', '在线教育平台开发', '社交应用开发', '物流管理系统', + 'AI智能客服系统', '区块链应用开发', '医疗健康APP', '金融风控系统' +] + +const locations = ['北京', '上海', '深圳', '杭州', '广州', '成都', '武汉', '南京', '不限'] +const workTypes: WorkType[] = ['fulltime', 'parttime', 'remote', 'internship'] + +const descriptions = [ + '我们正在寻找有经验的开发者加入我们的项目,参与核心功能的开发和优化。项目采用最新的技术栈,有良好的代码规范和开发流程。', + '这是一个具有挑战性的项目,需要您具备扎实的编程基础和良好的学习能力。我们提供灵活的工作时间和有竞争力的薪资待遇。', + '加入我们的团队,您将有机会参与大型项目的架构设计和技术选型,与优秀的工程师一起工作,不断提升自己的技术能力。' +] + +const requirements = [ + '1. 3年以上相关开发经验\n2. 熟悉主流开发框架\n3. 良好的代码规范意识\n4. 具备团队协作精神\n5. 有大型项目经验优先', + '1. 本科及以上学历\n2. 熟练掌握至少一门编程语言\n3. 了解常用设计模式\n4. 具备独立解决问题的能力\n5. 有开源项目经验优先', + '1. 2年以上工作经验\n2. 熟悉敏捷开发流程\n3. 具备良好的沟通能力\n4. 能够承受一定的工作压力\n5. 对技术有热情' +] + +const benefits = [ + '五险一金、带薪年假、弹性工作、免费午餐、定期团建、技术分享会', + '远程办公、股权激励、年终奖金、技术培训、健身房、零食饮料', + '双休、节日福利、项目奖金、晋升通道清晰、技术氛围好' +] + + + +// 生成模拟项目数据 +function generateMockProjects(): ProjectInfo[] { + const projects: ProjectInfo[] = [] + for (let i = 1; i <= 40; i++) { + const salaryMin = Math.floor(Math.random() * 20 + 5) * 1000 + const salaryMax = salaryMin + Math.floor(Math.random() * 15 + 5) * 1000 + const deadlineDate = new Date(Date.now() + Math.floor(Math.random() * 60 + 10) * 24 * 60 * 60 * 1000) + const isExpired = Math.random() > 0.9 + const tag1 = mockProjectTags[i % mockProjectTags.length]! + const tag2 = mockProjectTags[(i + 3) % mockProjectTags.length]! + + const isPinned = Math.random() > 0.8 + const pinnedDays = [3, 7, 14, 30][Math.floor(Math.random() * 4)] + + projects.push({ + id: i, + name: projectNames[i % projectNames.length]!, + salaryMin, + salaryMax, + salaryUnit: Math.random() > 0.7 ? '项目' : '月', + publisher: mockPublishers[i % mockPublishers.length]!, + location: locations[i % locations.length]!, + workType: workTypes[i % workTypes.length]!, + description: descriptions[i % descriptions.length]!, + requirements: requirements[i % requirements.length]!, + benefits: benefits[i % benefits.length]!, + tags: tag1.id === tag2.id ? [tag1] : [tag1, tag2], + contactEmail: `hr${i}@example.com`, + deadline: deadlineDate.toISOString(), + status: isExpired ? 'expired' : (Math.random() > 0.2 ? 'active' : 'closed'), + createdAt: new Date(Date.now() - Math.floor(Math.random() * 30 * 24 * 60 * 60 * 1000)).toISOString(), + updatedAt: new Date(Date.now() - Math.floor(Math.random() * 7 * 24 * 60 * 60 * 1000)).toISOString(), + isPinned, + pinnedUntil: isPinned ? new Date(Date.now() + pinnedDays! * 24 * 60 * 60 * 1000).toISOString() : undefined, + exposureCount: Math.floor(Math.random() * 500), + lastExposureAt: Math.random() > 0.5 ? new Date(Date.now() - Math.floor(Math.random() * 3 * 24 * 60 * 60 * 1000)).toISOString() : undefined + }) + } + return projects.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) +} + +let mockProjects = generateMockProjects() + +// 模拟延迟 +function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +// 获取项目列表 +export async function mockGetProjectList(params: ProjectQueryParams = {}): Promise { + await delay(300) + const { page = 1, pageSize = 10, keyword, workType, tagId, status, location } = params + + let filtered = [...mockProjects] + + if (keyword) { + filtered = filtered.filter(p => p.name.includes(keyword) || p.publisher.nickname.includes(keyword)) + } + if (workType) { + filtered = filtered.filter(p => p.workType === workType) + } + if (tagId) { + filtered = filtered.filter(p => p.tags.some(t => t.id === tagId)) + } + if (status) { + filtered = filtered.filter(p => p.status === status) + } + if (location) { + filtered = filtered.filter(p => p.location === location || p.location === '不限') + } + + const start = (page - 1) * pageSize + const list = filtered.slice(start, start + pageSize) + + return { list, total: filtered.length, page, pageSize } +} + +// 删除项目 +export async function mockDeleteProject(id: number): Promise { + await delay(200) + mockProjects = mockProjects.filter(p => p.id !== id) +} + +// 批量删除项目 +export async function mockBatchDeleteProjects(ids: number[]): Promise { + await delay(300) + mockProjects = mockProjects.filter(p => !ids.includes(p.id)) +} + +// 获取项目标签列表 +export async function mockGetProjectTags(): Promise { + await delay(100) + return mockProjectTags +} + +// 获取工作地点列表 +export async function mockGetLocations(): Promise { + await delay(100) + return locations +} + +// 设置项目置顶 +export async function mockSetProjectPin(id: number, isPinned: boolean, days?: number): Promise { + await delay(200) + const project = mockProjects.find(p => p.id === id) + if (project) { + project.isPinned = isPinned + project.pinnedUntil = isPinned && days ? new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString() : undefined + project.updatedAt = new Date().toISOString() + return project + } + return null +} + +// 手动曝光项目 +export async function mockExposeProject(id: number): Promise { + await delay(200) + const project = mockProjects.find(p => p.id === id) + if (project) { + project.exposureCount += 1 + project.lastExposureAt = new Date().toISOString() + project.updatedAt = new Date().toISOString() + return project + } + return null +} diff --git a/src/mock/projectSession.ts b/src/mock/projectSession.ts new file mode 100644 index 0000000..3ad76f9 --- /dev/null +++ b/src/mock/projectSession.ts @@ -0,0 +1,211 @@ +/** + * 项目会话管理模拟数据 + */ +import type { + ProjectSession, + ProjectMessage, + ProjectMember, + ProjectProgressNode, + ProjectMaterial, + ProjectRole +} from '@/types' +import { mockGetRecruitmentList } from './recruitment' +import { mockGetRecruitmentProjects } from './recruitment' + +// 模拟项目会话数据 +const mockSessions: Map = new Map() + +// 初始化模拟数据 +async function initMockData() { + const projects = await mockGetRecruitmentProjects() + for (const project of projects) { + mockSessions.set(project.id, { + id: project.id, + projectId: project.id, + projectInfo: { + ...project, + workType: project.workType as any, // 模拟数据类型兼容处理 + tags: [], // 简化 + id: project.id, + status: 'active', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + isPinned: false, + exposureCount: 0, + salaryMin: project.salaryMin, + salaryMax: project.salaryMax, + salaryUnit: project.salaryUnit, + description: '这是一个模拟的项目描述。', + requirements: '熟悉Vue3, TS...', + benefits: '远程办公', + contactEmail: 'contact@example.com', + deadline: new Date().toISOString(), + publisher: { id: 1, username: 'admin', nickname: '管理员', avatar: '' } + }, + members: generateMockMembers(project.id), + messages: generateMockMessages(project.id), + progressNodes: [ + { id: 1, title: '立项', status: 'completed', completedAt: '2023-10-01' }, + { id: 2, title: '需求对齐', status: 'completed', completedAt: '2023-10-05' }, + { id: 3, title: '开发中', status: 'processing', description: '前端页面开发进行中...' }, + { id: 4, title: '联调', status: 'pending' }, + { id: 5, title: '验收', status: 'pending' }, + { id: 6, title: '结项', status: 'pending' } + ], + materials: [ + { id: 1, name: '需求文档 (v1.0)', url: '#', type: 'doc', size: '2.4MB', uploadedBy: 'Elaine', uploadedAt: '10-01 10:00' }, + { id: 2, name: '接口说明', url: '#', type: 'doc', size: '1.2MB', uploadedBy: 'Mia', uploadedAt: '10-02 14:30' }, + { id: 3, name: '设计稿链接', url: '#', type: 'other', size: '-', uploadedBy: 'Leo', uploadedAt: '10-02 16:00' } + ], + onlineCount: 3 + }) + } +} + +// 生成模拟成员 +function generateMockMembers(projectId: number): ProjectMember[] { + // 这里写死一些数据,实际应从招募中获取approved的用户 + return [ + { + id: 1, + userId: 201, + role: 'leader', + isLeader: true, + status: 'online', + joinedAt: '2023-10-01', + info: { id: 201, username: 'zhangsan', nickname: '张三', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=zhang', email: '', skills: [], experience: '5年', introduction: '' } + }, + { + id: 2, + userId: 101, + role: 'product', + isLeader: false, + status: 'online', + joinedAt: '2023-10-02', + info: { id: 101, username: 'elaine', nickname: 'Elaine', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=elaine', email: '', skills: [], experience: '3年', introduction: '' } + }, + { + id: 3, + userId: 102, + role: 'frontend', + isLeader: false, + status: 'offline', + joinedAt: '2023-10-03', + info: { id: 102, username: 'leo', nickname: 'Leo', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=leo', email: '', skills: [], experience: '2年', introduction: '' } + }, + { + id: 4, + userId: 103, + role: 'backend', + isLeader: false, + status: 'online', + joinedAt: '2023-10-03', + info: { id: 103, username: 'mia', nickname: 'Mia', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=mia', email: '', skills: [], experience: '4年', introduction: '' } + }, + { + id: 5, + userId: 104, + role: 'test', + isLeader: false, + status: 'offline', + joinedAt: '2023-10-04', + info: { id: 104, username: 'noah', nickname: 'Noah', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=noah', email: '', skills: [], experience: '3年', introduction: '' } + } + ] +} + +// 生成模拟消息 +function generateMockMessages(projectId: number): ProjectMessage[] { + const now = new Date() + return [ + { + id: 1, + sessionId: projectId, + senderId: 101, + senderName: 'Elaine', + senderAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=elaine', + senderRole: 'product', + content: '今天先把需求边界对齐一下,重点是交付节奏。', + sentAt: new Date(now.getTime() - 1000 * 60 * 30).toISOString(), + type: 'text' + }, + { + id: 2, + sessionId: projectId, + senderId: 201, + senderName: '张三', + senderAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=zhang', + senderRole: 'leader', + content: '收到,我先同步一个可落地的里程碑拆解。', + sentAt: new Date(now.getTime() - 1000 * 60 * 29).toISOString(), + type: 'text' + } + ] +} + +// 确保数据已初始化 +initMockData() + +export async function mockGetProjectSession(projectId: number): Promise { + // 模拟网络延迟 + await new Promise(resolve => setTimeout(resolve, 500)) + if (mockSessions.size === 0) await initMockData() + const session = mockSessions.get(projectId) + if (!session) throw new Error('Session not found') + return session +} + +export async function mockSendProjectMessage(projectId: number, content: string): Promise { + await new Promise(resolve => setTimeout(resolve, 300)) + const msg: ProjectMessage = { + id: Date.now(), + sessionId: projectId, + senderId: 999, // 当前用户 + senderName: 'Luna', // 当前用户 + senderAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=luna', + senderRole: 'leader', // 假设当前用户是负责人 + content, + sentAt: new Date().toISOString(), + type: 'text' + } + const session = mockSessions.get(projectId) + if (session) { + session.messages.push(msg) + } + return msg +} + +// 获取可邀请的候选人列表(已通过招募申请的人员) +export async function mockGetInvitableCandidates(projectId: number) { + await new Promise(resolve => setTimeout(resolve, 400)) + // 获取该项目的approved的申请记录 + const recruitmentResult = await mockGetRecruitmentList({ projectId, status: 'approved' }) + // 排除已经是成员的人(简化逻辑:这里直接返回所有approved的) + return recruitmentResult.list.map(record => ({ + id: record.applicant.id, + applicantId: record.applicant.id, + name: record.applicant.nickname || record.applicant.username, + role: record.publisher.position || '候选人', // 这里简化,实际可能是申请时的意向岗位 + avatar: record.applicant.avatar, + email: record.applicant.email + })) +} + +// 邀请成员 +export async function mockInviteMember(projectId: number, candidateId: number, role: ProjectRole) { + await new Promise(resolve => setTimeout(resolve, 500)) + const session = mockSessions.get(projectId) + if (session) { + // 简化:直接添加一个模拟成员 + const newMember: ProjectMember = { + id: Date.now(), + userId: candidateId, + role, + isLeader: false, + status: 'offline', + joinedAt: new Date().toISOString(), + info: { id: candidateId, username: 'new_member', nickname: '新成员', avatar: '', email: '', skills: [], experience: '', introduction: '' } + } + session.members.push(newMember) + } +} diff --git a/src/mock/recruitment.ts b/src/mock/recruitment.ts new file mode 100644 index 0000000..3d677f2 --- /dev/null +++ b/src/mock/recruitment.ts @@ -0,0 +1,162 @@ +/** + * 招募管理模拟数据 + */ +import type { RecruitmentRecord, ApplicantInfo, RecruitmentProject, RecruitmentQueryParams, RecruitmentListResult, RecruitmentStatus, RecruitmentStats, PublisherInfo } from '@/types' + +// 模拟项目数据 +const mockProjects: RecruitmentProject[] = [ + { id: 1, name: '企业官网开发项目', workType: 'remote', location: '不限', salaryMin: 15000, salaryMax: 25000, salaryUnit: '月', publisherName: '科技有限公司' }, + { id: 2, name: '电商小程序开发', workType: 'fulltime', location: '上海', salaryMin: 20000, salaryMax: 35000, salaryUnit: '月', publisherName: '创业团队' }, + { id: 3, name: 'CRM系统定制开发', workType: 'freelance', location: '北京', salaryMin: 50000, salaryMax: 80000, salaryUnit: '项目', publisherName: '某大厂' }, + { id: 4, name: 'AI智能客服系统', workType: 'fulltime', location: '深圳', salaryMin: 25000, salaryMax: 40000, salaryUnit: '月', publisherName: '独立开发者' }, + { id: 5, name: '数据可视化平台', workType: 'parttime', location: '杭州', salaryMin: 8000, salaryMax: 15000, salaryUnit: '月', publisherName: '外包公司' } +] + +// 模拟申请人 +const mockApplicants: ApplicantInfo[] = [ + { id: 101, username: 'dev_zhang', nickname: '张开发', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=zhang', email: 'zhang@example.com', phone: '13800001111', skills: ['Vue', 'TypeScript', 'Node.js'], experience: '5年', introduction: '全栈开发工程师,擅长Vue生态和Node.js后端开发,有多个大型项目经验。', portfolioUrl: 'https://github.com/zhangdev' }, + { id: 102, username: 'code_li', nickname: '李程序', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=li', email: 'li@example.com', skills: ['React', 'Python', 'Docker'], experience: '3年', introduction: '前端开发为主,熟悉React技术栈,有一定后端开发能力。' }, + { id: 103, username: 'wang_coder', nickname: '王码农', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=wang', email: 'wang@example.com', phone: '13900002222', skills: ['Java', 'MySQL', 'K8s'], experience: '7年', introduction: '资深Java开发,精通微服务架构,有大厂背景。', resumeUrl: 'https://resume.example.com/wang' }, + { id: 104, username: 'zhao_dev', nickname: '赵工程', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=zhao', email: 'zhao@example.com', skills: ['Go', 'Python', 'AWS'], experience: '4年', introduction: '后端开发工程师,专注于Go语言和云原生开发。' }, + { id: 105, username: 'sun_tech', nickname: '孙技术', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=sun', email: 'sun@example.com', phone: '13700003333', skills: ['Vue', 'React', 'TypeScript'], experience: '2年', introduction: '前端开发新人,学习能力强,对技术有热情。' }, + { id: 106, username: 'zhou_full', nickname: '周全栈', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=zhou', email: 'zhou@example.com', skills: ['Node.js', 'MongoDB', 'Docker'], experience: '6年', introduction: '全栈工程师,前后端通吃,有独立完成项目的能力。', portfolioUrl: 'https://zhoudev.com' } +] + +// 模拟发布人数据 +const mockPublishers: PublisherInfo[] = [ + { id: 201, username: 'hr_tech', nickname: '陈HR', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=hr1', email: 'hr@techcompany.com', phone: '13600001111', company: '科技有限公司', position: '人事经理', verified: true }, + { id: 202, username: 'cto_startup', nickname: '刘CTO', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=cto1', email: 'cto@startup.com', phone: '13600002222', company: '创业团队', position: '技术总监', verified: true }, + { id: 203, username: 'pm_bigtech', nickname: '李产品', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=pm1', email: 'pm@bigtech.com', company: '某大厂', position: '项目经理', verified: true }, + { id: 204, username: 'indie_dev', nickname: '独立开发者小明', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=indie1', email: 'indie@dev.com', position: '独立开发者', verified: false }, + { id: 205, username: 'outsource_mgr', nickname: '外包管理小周', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=out1', email: 'mgr@outsource.com', phone: '13600003333', company: '外包公司', position: '项目经理', verified: true } +] + +const coverLetters = [ + '您好,我对贵公司的项目非常感兴趣。我有丰富的相关经验,相信能够胜任这份工作。期待与您进一步沟通!', + '看到招聘信息后非常激动,这正是我一直想参与的项目类型。我的技能和经验与岗位要求高度匹配,希望能有机会加入团队。', + '我是一名有多年经验的开发者,对技术有持续的热情。贵项目的技术栈正是我擅长的领域,相信我能为项目带来价值。', + '非常期待能够参与这个项目。我之前有类似项目的开发经验,可以快速上手。薪资可以商议,更看重项目本身的发展前景。' +] + +const statuses: RecruitmentStatus[] = ['pending', 'approved', 'rejected', 'withdrawn', 'expired'] + +// 生成模拟招募记录 +function generateMockRecruitments(): RecruitmentRecord[] { + const records: RecruitmentRecord[] = [] + for (let i = 1; i <= 60; i++) { + const project = mockProjects[i % mockProjects.length]! + const applicant = mockApplicants[i % mockApplicants.length]! + const status = statuses[i % statuses.length]! + const appliedAt = new Date(Date.now() - Math.floor(Math.random() * 30 * 24 * 60 * 60 * 1000)) + const coverLetter = coverLetters[i % coverLetters.length]! + + records.push({ + id: i, + project, + applicant, + publisher: mockPublishers[i % mockPublishers.length]!, + expectedSalary: Math.floor(Math.random() * 20 + 10) * 1000, + availableDate: new Date(Date.now() + Math.floor(Math.random() * 30 + 7) * 24 * 60 * 60 * 1000).toISOString(), + coverLetter, + status, + appliedAt: appliedAt.toISOString(), + processedAt: status !== 'pending' ? new Date(appliedAt.getTime() + Math.floor(Math.random() * 3 * 24 * 60 * 60 * 1000)).toISOString() : undefined, + processedBy: status !== 'pending' ? '管理员' : undefined, + rejectReason: status === 'rejected' ? '技能不匹配项目需求' : undefined, + remark: Math.random() > 0.7 ? '候选人沟通态度良好' : undefined + }) + } + return records.sort((a, b) => new Date(b.appliedAt).getTime() - new Date(a.appliedAt).getTime()) +} + +let mockRecruitments = generateMockRecruitments() + +function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +// 获取招募列表 +export async function mockGetRecruitmentList(params: RecruitmentQueryParams = {}): Promise { + await delay(300) + const { page = 1, pageSize = 10, keyword, projectId, status, startDate, endDate } = params + + let filtered = [...mockRecruitments] + + if (keyword) { + filtered = filtered.filter(r => + r.project.name.includes(keyword) || + r.applicant.nickname.includes(keyword) || + r.applicant.username.includes(keyword) + ) + } + if (projectId) { + filtered = filtered.filter(r => r.project.id === projectId) + } + if (status) { + filtered = filtered.filter(r => r.status === status) + } + if (startDate) { + filtered = filtered.filter(r => new Date(r.appliedAt) >= new Date(startDate)) + } + if (endDate) { + filtered = filtered.filter(r => new Date(r.appliedAt) <= new Date(endDate)) + } + + const start = (page - 1) * pageSize + return { list: filtered.slice(start, start + pageSize), total: filtered.length, page, pageSize } +} + +// 获取招募统计 +export async function mockGetRecruitmentStats(): Promise { + await delay(100) + const today = new Date().toDateString() + return { + total: mockRecruitments.length, + pending: mockRecruitments.filter(r => r.status === 'pending').length, + approved: mockRecruitments.filter(r => r.status === 'approved').length, + rejected: mockRecruitments.filter(r => r.status === 'rejected').length, + todayNew: mockRecruitments.filter(r => new Date(r.appliedAt).toDateString() === today).length + } +} + +// 审批通过 +export async function mockApproveRecruitment(id: number): Promise { + await delay(200) + const record = mockRecruitments.find(r => r.id === id) + if (record) { + record.status = 'approved' + record.processedAt = new Date().toISOString() + record.processedBy = '管理员' + } +} + +// 审批拒绝 +export async function mockRejectRecruitment(id: number, reason: string): Promise { + await delay(200) + const record = mockRecruitments.find(r => r.id === id) + if (record) { + record.status = 'rejected' + record.processedAt = new Date().toISOString() + record.processedBy = '管理员' + record.rejectReason = reason + } +} + +// 删除招募记录 +export async function mockDeleteRecruitment(id: number): Promise { + await delay(200) + mockRecruitments = mockRecruitments.filter(r => r.id !== id) +} + +// 批量删除 +export async function mockBatchDeleteRecruitments(ids: number[]): Promise { + await delay(300) + mockRecruitments = mockRecruitments.filter(r => !ids.includes(r.id)) +} + +// 获取项目列表(用于筛选) +export async function mockGetRecruitmentProjects(): Promise { + await delay(100) + return mockProjects +} + diff --git a/src/mock/signedProject.ts b/src/mock/signedProject.ts new file mode 100644 index 0000000..b143bb9 --- /dev/null +++ b/src/mock/signedProject.ts @@ -0,0 +1,346 @@ +/** + * 已成交项目和合同管理模拟数据 + */ +import type { + SignedProjectRecord, + SignedProjectStats, + SignedProjectQueryParams, + SignedProjectListResult, + ProjectProgressStatus, + ProjectMilestone, + ContractPartyInfo, + ContractInfo, + ContractRecord, + ContractStats, + ContractQueryParams, + ContractListResult, + ContractType, + ContractStatus +} from '@/types/signedProject' + +// 模拟发布人数据 +const mockPublishers: ContractPartyInfo[] = [ + { id: 201, username: 'hr_tech', nickname: '陈HR', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=pub1', email: 'hr@techcompany.com', phone: '13600001111', company: '科技有限公司', position: '人事经理', verified: true }, + { id: 202, username: 'cto_startup', nickname: '刘CTO', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=pub2', email: 'cto@startup.com', phone: '13600002222', company: '创业团队', position: '技术总监', verified: true }, + { id: 203, username: 'pm_bigtech', nickname: '李产品', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=pub3', email: 'pm@bigtech.com', company: '某大厂', position: '项目经理', verified: true } +] + +// 模拟签约人数据 +const mockContractors: ContractPartyInfo[] = [ + { id: 101, username: 'dev_zhang', nickname: '张开发', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=con1', email: 'zhang@example.com', phone: '13800001111', position: '全栈开发工程师', verified: true }, + { id: 102, username: 'code_li', nickname: '李程序', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=con2', email: 'li@example.com', position: '前端开发工程师', verified: true }, + { id: 103, username: 'wang_coder', nickname: '王码农', avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=con3', email: 'wang@example.com', phone: '13900002222', position: '后端开发工程师', verified: true } +] + +const projectNames = [ + '企业官网开发项目', + '电商小程序开发', + 'CRM系统定制开发', + 'AI智能客服系统', + '数据可视化平台', + '移动端APP开发', + 'OA办公系统', + '在线教育平台' +] + +const workTypes = ['remote', 'fulltime', 'parttime', 'freelance'] +const locations = ['北京', '上海', '深圳', '杭州', '广州', '远程'] + +// 生成里程碑 +function generateMilestones(projectId: number, progressPercent: number): ProjectMilestone[] { + const milestoneTemplates = [ + { title: '需求确认', description: '完成需求文档和原型确认' }, + { title: '设计完成', description: '完成UI/UX设计和技术方案' }, + { title: '开发阶段一', description: '完成核心功能开发' }, + { title: '开发阶段二', description: '完成辅助功能开发' }, + { title: '测试验收', description: '完成测试和问题修复' }, + { title: '项目交付', description: '完成部署上线和文档交付' } + ] + + return milestoneTemplates.map((template, index) => { + const plannedDaysFromStart = (index + 1) * 15 + const baseDate = new Date(Date.now() - 60 * 24 * 60 * 60 * 1000) + const plannedDate = new Date(baseDate.getTime() + plannedDaysFromStart * 24 * 60 * 60 * 1000) + + let status: ProjectMilestone['status'] = 'pending' + let actualDate: string | undefined + + const milestoneProgress = ((index + 1) / milestoneTemplates.length) * 100 + if (progressPercent >= milestoneProgress) { + status = 'completed' + actualDate = new Date(plannedDate.getTime() + (Math.random() - 0.5) * 5 * 24 * 60 * 60 * 1000).toISOString() + } else if (progressPercent >= milestoneProgress - 15) { + status = 'in_progress' + } + + return { + id: projectId * 100 + index, + title: template.title, + description: template.description, + plannedDate: plannedDate.toISOString(), + actualDate, + status, + deliverables: index < 3 ? ['文档', '源代码'] : undefined + } + }) +} + +// 生成已成交项目 +function generateSignedProjects(): SignedProjectRecord[] { + const records: SignedProjectRecord[] = [] + const progressStatuses: ProjectProgressStatus[] = ['signed', 'in_progress', 'delivered', 'accepted', 'completed', 'disputed'] + + for (let i = 1; i <= 30; i++) { + const publisher = mockPublishers[i % mockPublishers.length]! + const contractor = mockContractors[i % mockContractors.length]! + const projectName = projectNames[i % projectNames.length]! + const progressStatus = progressStatuses[i % progressStatuses.length]! + + // 根据状态计算进度百分比 + let progressPercent = 0 + switch (progressStatus) { + case 'signed': progressPercent = 0; break + case 'in_progress': progressPercent = 20 + Math.floor(Math.random() * 50); break + case 'delivered': progressPercent = 85 + Math.floor(Math.random() * 10); break + case 'accepted': progressPercent = 95; break + case 'completed': progressPercent = 100; break + case 'disputed': progressPercent = 50 + Math.floor(Math.random() * 30); break + } + + const contractAmount = (20 + Math.floor(Math.random() * 80)) * 1000 + const paidPercent = progressStatus === 'completed' ? 1 : progressPercent / 100 * 0.8 + const paidAmount = Math.floor(contractAmount * paidPercent) + + const signedAt = new Date(Date.now() - (60 + i * 5) * 24 * 60 * 60 * 1000) + + const contract: ContractInfo = { + id: i * 10, + contractNo: `HT${2024}${String(i).padStart(6, '0')}`, + title: `${projectName}服务合同`, + type: 'project', + amount: contractAmount, + currency: 'CNY', + signedAt: signedAt.toISOString(), + startDate: new Date(signedAt.getTime() + 3 * 24 * 60 * 60 * 1000).toISOString(), + endDate: new Date(signedAt.getTime() + 90 * 24 * 60 * 60 * 1000).toISOString(), + status: progressStatus === 'completed' ? 'completed' : 'active', + attachmentUrl: '/contracts/sample.pdf', + attachmentName: `${projectName}-合同.pdf` + } + + records.push({ + id: i, + projectName, + projectDescription: `${projectName}的定制开发服务,包含需求分析、设计、开发、测试和部署。`, + workType: workTypes[i % workTypes.length]!, + location: locations[i % locations.length]!, + contractAmount, + paidAmount, + pendingAmount: contractAmount - paidAmount, + currency: 'CNY', + publisher, + contractor, + contract, + progressStatus, + progressPercent, + milestones: generateMilestones(i, progressPercent), + signedAt: signedAt.toISOString(), + startedAt: progressStatus !== 'signed' ? new Date(signedAt.getTime() + 3 * 24 * 60 * 60 * 1000).toISOString() : undefined, + deliveredAt: ['delivered', 'accepted', 'completed'].includes(progressStatus) ? new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString() : undefined, + completedAt: progressStatus === 'completed' ? new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString() : undefined, + publisherRating: progressStatus === 'completed' ? 80 + Math.floor(Math.random() * 20) : undefined, + contractorRating: progressStatus === 'completed' ? 80 + Math.floor(Math.random() * 20) : undefined, + createdAt: signedAt.toISOString(), + updatedAt: new Date().toISOString() + }) + } + + return records.sort((a, b) => new Date(b.signedAt).getTime() - new Date(a.signedAt).getTime()) +} + +// 生成合同记录 +function generateContracts(): ContractRecord[] { + const records: ContractRecord[] = [] + const contractTypes: ContractType[] = ['service', 'project', 'freelance', 'nda', 'other'] + const contractStatuses: ContractStatus[] = ['draft', 'pending_sign', 'active', 'completed', 'terminated', 'expired'] + + for (let i = 1; i <= 40; i++) { + const partyA = mockPublishers[i % mockPublishers.length]! + const partyB = mockContractors[i % mockContractors.length]! + const projectName = projectNames[i % projectNames.length]! + const type = contractTypes[i % contractTypes.length]! + const status = contractStatuses[i % contractStatuses.length]! + + const amount = type === 'nda' ? 0 : (10 + Math.floor(Math.random() * 90)) * 1000 + const createdAt = new Date(Date.now() - (30 + i * 3) * 24 * 60 * 60 * 1000) + const effectiveDate = new Date(createdAt.getTime() + 5 * 24 * 60 * 60 * 1000) + const expiryDate = new Date(effectiveDate.getTime() + 365 * 24 * 60 * 60 * 1000) + + records.push({ + id: i, + contractNo: `HT${2024}${String(i).padStart(6, '0')}`, + title: type === 'nda' ? `${projectName}保密协议` : `${projectName}${ContractTypeMap[type]}`, + type, + partyA, + partyB, + relatedProjectId: type !== 'nda' && type !== 'other' ? i : undefined, + relatedProjectName: type !== 'nda' && type !== 'other' ? projectName : undefined, + amount, + currency: 'CNY', + signedAt: ['active', 'completed'].includes(status) ? new Date(effectiveDate.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString() : undefined, + effectiveDate: effectiveDate.toISOString(), + expiryDate: expiryDate.toISOString(), + status, + attachments: status !== 'draft' ? [{ + id: i * 10, + name: `合同-${projectName}.pdf`, + url: '/contracts/sample.pdf', + size: 1024 * 500 + Math.floor(Math.random() * 1024 * 500), + uploadedAt: createdAt.toISOString() + }] : [], + signRecords: ['active', 'completed'].includes(status) ? [ + { partyType: 'A' as const, signedBy: partyA.nickname, signedAt: new Date(effectiveDate.getTime() - 3 * 24 * 60 * 60 * 1000).toISOString(), signMethod: 'electronic' as const }, + { partyType: 'B' as const, signedBy: partyB.nickname, signedAt: new Date(effectiveDate.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(), signMethod: 'electronic' as const } + ] : [], + remark: i % 5 === 0 ? '重点项目合同' : undefined, + createdAt: createdAt.toISOString(), + updatedAt: new Date().toISOString() + }) + } + + return records.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) +} + +const mockSignedProjects = generateSignedProjects() +const mockContracts = generateContracts() + +function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +// ================== 已成交项目 API ================== + +export async function mockGetSignedProjectStats(): Promise { + await delay(150) + return { + total: mockSignedProjects.length, + inProgress: mockSignedProjects.filter(p => ['signed', 'in_progress', 'delivered'].includes(p.progressStatus)).length, + completed: mockSignedProjects.filter(p => p.progressStatus === 'completed').length, + disputed: mockSignedProjects.filter(p => p.progressStatus === 'disputed').length, + totalAmount: mockSignedProjects.reduce((sum, p) => sum + p.contractAmount, 0), + paidAmount: mockSignedProjects.reduce((sum, p) => sum + p.paidAmount, 0) + } +} + +export async function mockGetSignedProjectList( + params: SignedProjectQueryParams = {} +): Promise { + await delay(200) + + const { page = 1, pageSize = 10, keyword, progressStatus, startDate, endDate } = params + + let filtered = [...mockSignedProjects] + + if (keyword) { + const kw = keyword.toLowerCase() + filtered = filtered.filter(p => + p.projectName.toLowerCase().includes(kw) || + p.publisher.nickname.toLowerCase().includes(kw) || + p.contractor.nickname.toLowerCase().includes(kw) || + p.contract.contractNo.toLowerCase().includes(kw) + ) + } + + if (progressStatus) { + filtered = filtered.filter(p => p.progressStatus === progressStatus) + } + + if (startDate) { + filtered = filtered.filter(p => new Date(p.signedAt) >= new Date(startDate)) + } + if (endDate) { + const end = new Date(endDate) + end.setHours(23, 59, 59, 999) + filtered = filtered.filter(p => new Date(p.signedAt) <= end) + } + + const start = (page - 1) * pageSize + return { + list: filtered.slice(start, start + pageSize), + total: filtered.length, + page, + pageSize + } +} + +export async function mockGetSignedProjectById(id: number): Promise { + await delay(150) + return mockSignedProjects.find(p => p.id === id) || null +} + +// ================== 合同管理 API ================== + +export async function mockGetContractStats(): Promise { + await delay(150) + return { + total: mockContracts.length, + active: mockContracts.filter(c => c.status === 'active').length, + pendingSign: mockContracts.filter(c => c.status === 'pending_sign').length, + completed: mockContracts.filter(c => c.status === 'completed').length, + expired: mockContracts.filter(c => c.status === 'expired').length, + totalAmount: mockContracts.reduce((sum, c) => sum + c.amount, 0) + } +} + +export async function mockGetContractList( + params: ContractQueryParams = {} +): Promise { + await delay(200) + + const { page = 1, pageSize = 10, keyword, type, status, startDate, endDate } = params + + let filtered = [...mockContracts] + + if (keyword) { + const kw = keyword.toLowerCase() + filtered = filtered.filter(c => + c.title.toLowerCase().includes(kw) || + c.contractNo.toLowerCase().includes(kw) || + c.partyA.nickname.toLowerCase().includes(kw) || + c.partyB.nickname.toLowerCase().includes(kw) + ) + } + + if (type) { + filtered = filtered.filter(c => c.type === type) + } + + if (status) { + filtered = filtered.filter(c => c.status === status) + } + + if (startDate) { + filtered = filtered.filter(c => new Date(c.createdAt) >= new Date(startDate)) + } + if (endDate) { + const end = new Date(endDate) + end.setHours(23, 59, 59, 999) + filtered = filtered.filter(c => new Date(c.createdAt) <= end) + } + + const start = (page - 1) * pageSize + return { + list: filtered.slice(start, start + pageSize), + total: filtered.length, + page, + pageSize + } +} + +export async function mockGetContractById(id: number): Promise { + await delay(150) + return mockContracts.find(c => c.id === id) || null +} + +// 导入合同类型映射 +import { ContractTypeMap } from '@/types/signedProject' diff --git a/src/mock/talent.ts b/src/mock/talent.ts new file mode 100644 index 0000000..eafda35 --- /dev/null +++ b/src/mock/talent.ts @@ -0,0 +1,457 @@ +/** + * 人才管理模拟数据 + */ +import type { TalentProfile, TalentQueryParams, TalentListResult, TalentStatus, TalentProjectRecord, TalentTag, TalentResume, ResumeTemplate, TalentIncomeRecord, TalentIncomeType } from '@/types' + +const skillTagPool: TalentTag[] = [ + { id: 201, name: 'Vue3', color: '#42b883' }, + { id: 202, name: 'React18', color: '#61dafb' }, + { id: 203, name: 'Node.js', color: '#339933' }, + { id: 204, name: 'NestJS', color: '#d97706' }, + { id: 205, name: 'Nuxt', color: '#00DC82' }, + { id: 206, name: 'TypeScript', color: '#3178c6' }, + { id: 207, name: '数据可视化', color: '#845ef7' }, + { id: 208, name: 'AI 算法', color: '#ff922b' }, + { id: 209, name: '低代码', color: '#13c2c2' }, + { id: 210, name: '移动端', color: '#ff6b6b' } +] + +const cityTagPool: TalentTag[] = [ + { id: 301, name: '上海', color: '#2f54eb' }, + { id: 302, name: '北京', color: '#9254de' }, + { id: 303, name: '深圳', color: '#13c2c2' }, + { id: 304, name: '杭州', color: '#52c41a' }, + { id: 305, name: '成都', color: '#fa8c16' }, + { id: 306, name: '远程', color: '#a0aec0' } +] + +const projectTemplates: Array> = [ + { name: '智能客服平台', role: '前端负责人' }, + { name: '跨境电商系统', role: '全栈交付' }, + { name: 'IoT 设备中台', role: '架构设计' }, + { name: '实景互动平台', role: '可视化开发' }, + { name: '企业私有化部署', role: '技术顾问' }, + { name: 'AI 训练标注平台', role: '交互设计' } +] + +const talentNames = ['林晓', '周渝', '宋岚', '杜言', '霍启', '唐叶', '江澄', '顾宁', '温槿', '秦若', '程恺', '沈律'] +const positionPool = ['高级前端工程师', '全栈技术专家', '可视化工程师', '后端架构师', '低代码顾问', 'AI 产品工程师'] +const statusPool: TalentStatus[] = ['available', 'busy', 'onboarding', 'resting'] +const introTemplates = [ + '聚焦企业级前端体系建设,擅长性能优化与大型项目的组件化拆分,拥有多行业交付经验。', + '熟悉从需求分析到上线的全链路流程,习惯以目标导向来拆解项目,具备优秀的沟通协调能力。', + '擅长数据可视化和实时互动场景,对前后端协作、部署流程有完整认知,能够独立承担复杂任务。' +] + +const companies = ['字节跳动', '腾讯科技', '阿里巴巴', '美团点评', '蚂蚁金服', '快手科技', '京东集团', '网易游戏'] +const universities = ['浙江大学', '复旦大学', '上海交通大学', '同济大学', '武汉大学', '华中科技大学', '南京大学', '电子科技大学'] +const majors = ['计算机科学与技术', '软件工程', '信息管理与信息系统', '电子信息工程', '通信工程', '数学与应用数学'] +const degrees = ['本科', '硕士'] + +function pickTags(pool: TalentTag[], count: number): TalentTag[] { + const shuffled = [...pool].sort(() => Math.random() - 0.5) + return shuffled.slice(0, count) +} + +function buildRecentProjects(seed: number): TalentProjectRecord[] { + return Array.from({ length: 3 }, (_, index) => { + const template = projectTemplates[(seed + index) % projectTemplates.length]! + const deliveredAt = new Date(Date.now() - (seed * 10 + index * 5) * 24 * 60 * 60 * 1000) + return { + id: seed * 100 + index, + name: template.name, + role: template.role, + deliveryDate: deliveredAt.toISOString(), + score: Math.floor(75 + Math.random() * 25), // 75-100分 + income: Math.floor(5000 + Math.random() * 30000) // 5000-35000元 + } + }) +} + +// 构建收益记录列表 +function buildIncomeRecords(seed: number, projects: TalentProjectRecord[]): TalentIncomeRecord[] { + const records: TalentIncomeRecord[] = [] + let recordId = seed * 1000 + + // 1. 项目收益记录(从项目数据生成) + projects.forEach(project => { + if (project.income) { + records.push({ + id: recordId++, + type: 'project' as TalentIncomeType, + title: project.name, + amount: project.income, + isIncome: true, + relatedProjectId: project.id, + createdAt: project.deliveryDate, + remark: `${project.role} - 项目已交付并完成验收` + }) + } + }) + + // 2. 任务收益记录 + const taskTitles = ['开发电商首页', '完成API接口开发', 'Bug修复', '功能优化', '代码审查'] + for (let i = 0; i < 3; i++) { + const daysAgo = 10 + i * 8 + Math.floor(Math.random() * 5) + records.push({ + id: recordId++, + type: 'task' as TalentIncomeType, + title: taskTitles[i % taskTitles.length]!, + amount: Math.floor(100 + Math.random() * 400), + isIncome: true, + createdAt: new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000).toISOString(), + remark: '任务完成奖励' + }) + } + + // 3. 奖励记录 + const rewardTitles = ['发布优质帖子获得奖励', '被评为月度优秀人才', '推荐新用户奖励'] + for (let i = 0; i < 2; i++) { + const daysAgo = 15 + i * 12 + records.push({ + id: recordId++, + type: 'reward' as TalentIncomeType, + title: rewardTitles[i % rewardTitles.length]!, + amount: Math.floor(50 + Math.random() * 200), + isIncome: true, + createdAt: new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000).toISOString() + }) + } + + // 4. 支出记录 + const expenseTitles = ['兑换技术书籍', '购买会员服务', '提现手续费'] + for (let i = 0; i < 2; i++) { + const daysAgo = 20 + i * 10 + records.push({ + id: recordId++, + type: 'other' as TalentIncomeType, + title: expenseTitles[i % expenseTitles.length]!, + amount: Math.floor(20 + Math.random() * 100), + isIncome: false, + createdAt: new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000).toISOString() + }) + } + + // 按时间倒序排列 + return records.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) +} + +function buildWorkExperiences(seed: number, years: number): TalentProfile['workExperiences'] { + const count = Math.min(Math.floor(years / 2) + 1, 3) + return Array.from({ length: count }, (_, index) => { + const isCurrent = index === 0 + const startYear = 2024 - (index + 1) * 2 + return { + id: seed * 1000 + index, + company: companies[(seed + index) % companies.length]!, + position: positionPool[(seed + index) % positionPool.length]!, + industry: '互联网', + department: '研发部', + startTime: `${startYear}-03`, + endTime: isCurrent ? null : `${startYear + 2}-02`, + description: '负责核心业务系统的架构设计与开发,主导了技术栈升级,通过引入新技术提升了团队30%的开发效率。', + tags: ['Vue3', 'React', 'Node.js', '架构设计'].slice(0, 3) + } + }) +} + +function buildEducationExperiences(seed: number): TalentProfile['educationExperiences'] { + const degree = degrees[seed % degrees.length] + const endYear = 2024 - (3 + (seed % 8)) + return [ + { + id: seed * 2000 + 1, + school: universities[seed % universities.length]!, + major: majors[seed % majors.length]!, + degree: degree!, + startTime: `${endYear - 4}-09`, + endTime: `${endYear}-06`, + description: '在校期间多次获得奖学金,担任学生会技术部长,组织过多次校园黑客马拉松活动。' + } + ] +} + +function createTalent(index: number): TalentProfile { + const seed = index + 1 + const status = statusPool[index % statusPool.length]! + const skillTags = pickTags(skillTagPool, 3) + const cityTag = cityTagPool[index % cityTagPool.length]! + const recentProjects = buildRecentProjects(seed) + const experienceYears = 3 + (index % 8) + const projectCount = 8 + (index * 2) + Math.floor(Math.random() * 5) + const unit = seed % 2 === 0 ? 'day' : 'month' + const amount = unit === 'day' ? 1800 + seed * 120 : 28000 + seed * 1500 + + // 信誉评分和项目评分 - 100分制 + const creditRating = Math.floor(70 + (projectCount / 30) * 15 + Math.random() * 15) + const projectRating = Math.floor(75 + Math.random() * 20) + const avgRating = (creditRating + projectRating) / 2 + + // 领域方向 + const domainPool = ['Web3 / SaaS', 'AI / 机器学习', '企业服务', '电商零售', 'ToB 平台', '金融科技', '物联网'] + + // 生成附件简历 + const resumeAttachments: TalentResume[] = Math.random() > 0.3 ? [ + { + id: seed * 10 + 1, + fileName: `${talentNames[index % talentNames.length]}_简历.pdf`, + fileUrl: `https://example.com/resumes/${seed}_resume.pdf`, + fileSize: 1024 * 512 + Math.floor(Math.random() * 1024 * 256), + uploadedAt: new Date(Date.now() - Math.floor(Math.random() * 30 * 24 * 60 * 60 * 1000)).toISOString() + } + ] : [] + + const workExperiences = buildWorkExperiences(seed, experienceYears) + const educationExperiences = buildEducationExperiences(seed) + const city = cityTag.name + + // 社交统计 + const completedTaskCount = projectCount + Math.floor(Math.random() * 20) + const ongoingTaskCount = status === 'busy' ? 1 + Math.floor(Math.random() * 3) : Math.floor(Math.random() * 2) + const socialStats = { + completedTaskCount, + ongoingTaskCount, + followingCount: 50 + Math.floor(Math.random() * 200), + followerCount: 500 + Math.floor(Math.random() * 3000), + starCount: 100 + Math.floor(Math.random() * 500), + likeCount: 200 + Math.floor(Math.random() * 1000) + } + + // 签到信息 + const today = new Date() + const consecutiveDays = Math.floor(Math.random() * 30) + const totalDays = consecutiveDays + Math.floor(Math.random() * 100) + const checkedInDates: string[] = [] + for (let i = 0; i < Math.min(consecutiveDays, 15); i++) { + const date = new Date(today) + date.setDate(date.getDate() - i) + if (date.getMonth() === today.getMonth()) { + checkedInDates.push(date.toISOString().split('T')[0]!) + } + } + + const checkInInfo = { + consecutiveDays, + totalDays, + lastCheckIn: consecutiveDays > 0 ? today.toISOString().split('T')[0] : undefined, + todayCheckedIn: consecutiveDays > 0 && Math.random() > 0.3, + checkedInDates + } + + return { + id: seed, + realName: talentNames[index % talentNames.length]!, + avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=talent_${seed}`, + gender: Math.random() > 0.3 ? 'male' : 'female', + age: 22 + experienceYears, + phone: `138${String(seed).padStart(8, '0')}`, + email: `talent${seed}@example.com`, + positionTitle: positionPool[index % positionPool.length]!, + domain: domainPool[index % domainPool.length], + verified: avgRating >= 80 || projectCount >= 15, + skillTags, + cityTag, + projectCount, + experienceYears, + degree: educationExperiences[0]!.degree, + fee: { + amount, + unit, + currency: 'CNY' + }, + rating: { + credit: creditRating, + project: projectRating + }, + level: projectCount > 30 ? 5 : projectCount > 15 ? 4 : projectCount > 8 ? 3 : projectCount > 3 ? 2 : 1, + status, + introduction: introTemplates[index % introTemplates.length]!, + recentProjects, + availableFrom: new Date( + Date.now() + (status === 'busy' ? 12 : status === 'onboarding' ? 5 : status === 'resting' ? 8 : 0) * 24 * 60 * 60 * 1000 + ).toISOString(), + hot: avgRating >= 90, + socialStats, + checkInInfo, + // 收益钱包 + wallet: { + balance: Math.floor(5000 + Math.random() * 50000), + totalIncome: Math.floor(20000 + Math.random() * 200000), + totalExpense: Math.floor(1000 + Math.random() * 10000), + currency: 'CNY' + }, + // 收益记录列表 + incomeRecords: buildIncomeRecords(seed, recentProjects), + resumeAttachments, + selectedTemplateId: Math.random() > 0.5 ? (index % 3) + 1 : undefined, + + // 详细简历 + workExperiences, + educationExperiences, + jobIntention: { + jobStatus: status === 'available' ? '离职-随时到岗' : '在职-考虑机会', + jobType: '全职', + expectedSalary: `${Math.floor(amount / 1000)}K-${Math.floor(amount / 1000) + 5}K`, + expectedCity: [city], + expectedIndustry: ['互联网', '人工智能'], + expectedPosition: [positionPool[index % positionPool.length]!] + } + } +} + +const mockTalents: TalentProfile[] = Array.from({ length: 12 }, (_, index) => createTalent(index)) + +export function getAllTalentProfiles(): TalentProfile[] { + return mockTalents +} + +function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +export async function mockGetTalentList(params: TalentQueryParams = {}): Promise { + await delay(200) + const { + page = 1, + pageSize = 8, + keyword, + status, + tagId, + cityTagId, + hotOnly + } = params + + let filtered = [...mockTalents] + + if (keyword) { + filtered = filtered.filter( + talent => + talent.realName.includes(keyword) || + talent.positionTitle.includes(keyword) || + talent.introduction.includes(keyword) + ) + } + + if (status) { + filtered = filtered.filter(talent => talent.status === status) + } + + if (tagId) { + filtered = filtered.filter(talent => talent.skillTags.some(tag => tag.id === tagId) || talent.cityTag.id === tagId) + } + + if (cityTagId) { + filtered = filtered.filter(talent => talent.cityTag.id === cityTagId) + } + + if (hotOnly) { + filtered = filtered.filter(talent => talent.hot) + } + + const start = (page - 1) * pageSize + const list = filtered.slice(start, start + pageSize) + + return { + list, + total: filtered.length, + page, + pageSize + } +} + +/** + * 根据ID获取人才详情 + */ +export async function mockGetTalentById(id: number): Promise { + await delay(150) + return mockTalents.find(talent => talent.id === id) || null +} + +// 在线简历模板 +const mockResumeTemplates: ResumeTemplate[] = [ + { + id: 1, + name: '简约专业型', + thumbnail: 'https://picsum.photos/seed/resume1/200/280', + description: '简洁大方,突出专业技能和项目经验,适合技术岗位', + isDefault: true, + createdAt: '2024-01-15T10:00:00Z', + tags: ['简约', '技术', '蓝色'], + useCount: 12503 + }, + { + id: 2, + name: '创意设计型', + thumbnail: 'https://picsum.photos/seed/resume2/200/280', + description: '突出个人特色与作品集,适合设计创意岗位', + isDefault: false, + createdAt: '2024-02-20T14:30:00Z', + tags: ['创意', '设计', '渐变'], + useCount: 8462 + }, + { + id: 3, + name: '全栈展示型', + thumbnail: 'https://picsum.photos/seed/resume3/200/280', + description: '全面展示前后端技术能力,适合全栈开发者', + isDefault: false, + createdAt: '2024-03-10T09:15:00Z', + tags: ['全栈', '丰富', '深色'], + useCount: 10234 + }, + { + id: 4, + name: '项目经理型', + thumbnail: 'https://picsum.photos/seed/resume4/200/280', + description: '突出项目管理能力和团队协作经验', + isDefault: false, + createdAt: '2024-04-05T16:45:00Z', + tags: ['管理', '商务', '灰色'], + useCount: 5678 + } +] + +/** + * 获取简历模板列表 + */ +export async function mockGetResumeTemplates(): Promise { + await delay(150) + return mockResumeTemplates +} + +/** + * 更新简历模板 + */ +export async function mockUpdateResumeTemplate(id: number, data: Partial): Promise { + await delay(200) + const template = mockResumeTemplates.find(t => t.id === id) + if (template) { + Object.assign(template, data) + return template + } + return null +} + +/** + * 设置默认模板 + */ +export async function mockSetDefaultTemplate(id: number): Promise { + await delay(200) + mockResumeTemplates.forEach(t => { + t.isDefault = t.id === id + }) + return true +} + +/** + * 删除简历模板 + */ +export async function mockDeleteResumeTemplate(id: number): Promise { + await delay(200) + const index = mockResumeTemplates.findIndex(t => t.id === id) + if (index > -1 && !mockResumeTemplates[index]?.isDefault) { + mockResumeTemplates.splice(index, 1) + return true + } + return false +} diff --git a/src/mock/user.ts b/src/mock/user.ts new file mode 100644 index 0000000..9513a8a --- /dev/null +++ b/src/mock/user.ts @@ -0,0 +1,386 @@ +/** + * 用户相关模拟数据 + */ +import type { + LoginParams, + LoginResult, + CaptchaResult, + UserInfo, + PlatformUserItem, + UserListQueryParams, + UserListResult, + PublishedRecruitmentSummary +} from '@/types' +import { getAllTalentProfiles } from './talent' + +// 模拟验证码 +const captchaCodes: Map = new Map() + +// 模拟用户数据 +const mockUsers: Array<{ username: string; password: string; userInfo: UserInfo, [key: number]: any }> = [ + { + username: 'admin', + password: '123456', + userInfo: { + id: 1, + username: 'admin', + nickname: '超级管理员', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=admin', + email: 'admin@nanxiislet.com', + phone: '13800138000', + role: 'admin', + permissions: ['*'], + createTime: '2024-01-01 00:00:00', + lastLoginTime: new Date().toLocaleString() + } + }, + { + username: 'user', + password: '123456', + userInfo: { + id: 2, + username: 'user', + nickname: '普通用户', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=user', + email: 'user@nanxiislet.com', + phone: '13900139000', + role: 'user', + permissions: ['read'], + createTime: '2024-06-01 00:00:00', + lastLoginTime: new Date().toLocaleString() + } + } +] + +// 生成随机验证码 +function generateCaptcha(): string { + const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' + let code = '' + for (let i = 0; i < 4; i++) { + code += chars.charAt(Math.floor(Math.random() * chars.length)) + } + return code +} + +// 生成SVG验证码图片 +function generateCaptchaSvg(code: string): string { + const colors = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae', '#1890ff'] + let svg = `` + svg += `` + + // 添加干扰线 + for (let i = 0; i < 4; i++) { + const x1 = Math.random() * 120 + const y1 = Math.random() * 40 + const x2 = Math.random() * 120 + const y2 = Math.random() * 40 + const color = colors[Math.floor(Math.random() * colors.length)] + svg += `` + } + + // 添加验证码文字 + for (let i = 0; i < code.length; i++) { + const x = 15 + i * 25 + const y = 28 + Math.random() * 6 - 3 + const rotate = Math.random() * 30 - 15 + const color = colors[Math.floor(Math.random() * colors.length)] + svg += `${code[i]}` + } + + svg += `` + return `data:image/svg+xml;base64,${btoa(svg)}` +} + +// 模拟延迟 +function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +// 获取验证码 +export async function mockGetCaptcha(): Promise { + await delay(300) + const captchaKey = 'captcha_' + Date.now() + const code = generateCaptcha() + captchaCodes.set(captchaKey, code) + + // 5分钟后过期 + setTimeout(() => captchaCodes.delete(captchaKey), 5 * 60 * 1000) + + return { + captchaKey, + captchaImage: generateCaptchaSvg(code) + } +} + +// 登录 +export async function mockLogin(params: LoginParams): Promise { + await delay(500) + + // 验证验证码 + const storedCode = captchaCodes.get(params.captchaKey) + if (!storedCode || storedCode.toUpperCase() !== params.captcha.toUpperCase()) { + throw new Error('验证码错误') + } + + // 验证用户 + const user = mockUsers.find( + u => u.username === params.username && u.password === params.password + ) + + if (!user) { + throw new Error('用户名或密码错误') + } + + // 删除已使用的验证码 + captchaCodes.delete(params.captchaKey) + + return { + token: 'mock_token_' + Date.now(), + refreshToken: 'mock_refresh_token_' + Date.now(), + expires: Date.now() + 24 * 60 * 60 * 1000, + userInfo: user.userInfo + } +} + +// 获取用户信息 +export async function mockGetUserInfo(): Promise { + await delay(200) + return mockUsers[0]!.userInfo +} + +function generatePhone(seed: number): string { + const base = String(10000000 + ((seed * 7919) % 90000000)) + return '138' + base.slice(-8) +} + +function normalizeDateTime(input?: string): string { + if (!input) return new Date().toISOString() + const safe = input.includes('T') ? input : input.replace(' ', 'T') + const date = new Date(safe) + return Number.isNaN(date.getTime()) ? new Date().toISOString() : date.toISOString() +} + +// 构建发布招募统计 +function buildPublishStats(userId: number) { + const totalPublished = Math.floor(2 + (userId % 5)) + const activePublished = Math.floor(totalPublished * 0.6) + return { + totalPublished, + activePublished, + closedPublished: totalPublished - activePublished, + totalApplicants: totalPublished * Math.floor(3 + Math.random() * 10) + } +} + +// 构建发布的招募记录 +function buildPublishedRecruitments(userId: number): PublishedRecruitmentSummary[] { + const projectNames = [ + '企业官网开发项目', + '移动端APP开发', + 'CRM系统定制开发', + '电商小程序开发', + '数据可视化平台', + 'AI智能客服系统' + ] + const workTypes = ['remote', 'fulltime', 'parttime', 'freelance'] + const locations = ['北京', '上海', '深圳', '杭州', '广州', '不限'] + + const count = 2 + (userId % 4) + return Array.from({ length: count }, (_, index) => { + const salaryMin = 10 + Math.floor(Math.random() * 15) + const salaryMax = salaryMin + 5 + Math.floor(Math.random() * 10) + const daysAgo = 5 + index * 10 + Math.floor(Math.random() * 5) + + return { + id: userId * 100 + index, + projectName: projectNames[(userId + index) % projectNames.length]!, + workType: workTypes[(userId + index) % workTypes.length]!, + location: locations[(userId + index) % locations.length]!, + salaryRange: `${salaryMin}k-${salaryMax}k`, + applicantCount: Math.floor(3 + Math.random() * 15), + status: (index === 0 ? 'active' : Math.random() > 0.5 ? 'active' : 'closed') as 'active' | 'closed', + publishedAt: new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000).toISOString() + } + }) +} + +function mapAuthUsersToPlatformUsers(): PlatformUserItem[] { + return mockUsers.map(user => ({ + id: user.userInfo.id, + realName: user.userInfo.nickname, + username: user.userInfo.username, + nickname: user.userInfo.nickname, + avatar: user.userInfo.avatar || '', + email: user.userInfo.email || `${user.userInfo.username}@nanxiislet.com`, + phone: user.userInfo.phone || generatePhone(user.userInfo.id), + role: user.userInfo.role, + joinedAt: normalizeDateTime(user.userInfo.createTime), + lastActiveAt: normalizeDateTime(user.userInfo.lastLoginTime), + tags: user.userInfo.permissions || [], + isTalent: false, + cityTag: { id: 0, name: '总部', color: '#8c8c8c' } + })) +} + +function buildPlatformUsersFromTalents(): PlatformUserItem[] { + const talents = getAllTalentProfiles() + return talents.map((talent, index) => { + const joinedAt = new Date(Date.now() - (index + 3) * 12 * 24 * 60 * 60 * 1000).toISOString() + const lastActiveAt = new Date(Date.now() - Math.floor(Math.random() * 5) * 24 * 60 * 60 * 1000).toISOString() + const BANK_NAMES = ['中国工商银行', '中国建设银行', '中国农业银行', '招商银行', '中国银行'] + + return { + id: 1000 + talent.id, + realName: talent.realName, + username: `talent_${talent.id}`, + nickname: talent.realName, + avatar: talent.avatar, + email: `talent${talent.id}@nanxiislet.com`, + phone: generatePhone(talent.id + 100), + role: 'talent', + joinedAt, + lastActiveAt, + tags: talent.skillTags.map(tag => tag.name), + isTalent: true, + cityTag: talent.cityTag, + introduction: talent.introduction, + paymentMethods: { + bankCard: { + bankName: BANK_NAMES[index % BANK_NAMES.length]!, + accountName: talent.realName, + accountNo: `621226${(1000000000 + index).toString()}` + }, + alipay: Math.random() > 0.5 ? { + accountName: talent.realName, + accountNo: generatePhone(talent.id + 100) + } : undefined + }, + talentSummary: { + status: talent.status, + projectCount: talent.projectCount, + experienceYears: talent.experienceYears, + fee: talent.fee, + rating: talent.rating, + level: talent.level, + hot: talent.hot, + availableFrom: talent.availableFrom, + skillTags: talent.skillTags, + cityTag: talent.cityTag, + // 新增字段 + verified: talent.verified, + domain: talent.domain, + socialStats: talent.socialStats, + wallet: talent.wallet, + incomeRecords: talent.incomeRecords, + // 发布招募数据 + publishStats: buildPublishStats(talent.id), + publishedRecruitments: buildPublishedRecruitments(talent.id) + }, + signInStats: buildSignInStats(talent.id), + contacts: buildContacts() // 新增联系人 + } + }) +} + +// 模拟生成联系人数据 +const companies = ['TechFlow', 'DesignCo', 'Freelance', 'MetaWorks', 'PixelStudio'] +const roles = ['Product Manager', 'Frontend Developer', 'Backend Engineer', 'QA Engineer', 'UI/UX Designer', 'Full Stack Dev'] +const statuses = ['Online Now', 'Active 1h ago', 'Active 5m ago', 'Offline', 'Active 1d ago'] + +function buildContacts(): any[] { + const count = Math.floor(Math.random() * 8) + 2 // 2-10个联系人 + const contacts = [] + for (let i = 0; i < count; i++) { + const id = 1000 + i + const gender = Math.random() > 0.5 ? 'women' : 'men' + const name = ['Elaine', 'Leo', 'Mia', 'Noah', 'Sarah', 'David', 'James', 'Lucas'][i % 8] + + contacts.push({ + id, + name, + avatar: `https://randomuser.me/api/portraits/${gender}/${Math.floor(Math.random() * 50)}.jpg`, + company: Math.random() > 0.3 ? companies[Math.floor(Math.random() * companies.length)] : undefined, + role: roles[Math.floor(Math.random() * roles.length)], + status: statuses[Math.floor(Math.random() * statuses.length)], + isMutal: Math.random() > 0.5 + }) + } + return contacts +} + +// 生成随机签到数据 +function buildSignInStats(userId: number) { + const currentMonthDays = Math.floor(Math.random() * 22) // 随机本月签到天数 + const rewardPerDay = 10 // 假设每日签到奖励 10 code币 + const totalReward = currentMonthDays * rewardPerDay + + const history = [] + const today = new Date() + for (let i = 0; i < currentMonthDays; i++) { + const date = new Date(today) + date.setDate(date.getDate() - i) + history.push({ + date: date.toISOString().split('T')[0], + reward: rewardPerDay, + isMakeup: Math.random() > 0.9 // 10% 概率补签 + }) + } + + return { + currentMonthDays, + totalReward, + lastSignInDate: history.length > 0 ? history[0].date : undefined, + history + } +} + +let platformUsers: PlatformUserItem[] = [ + ...mapAuthUsersToPlatformUsers().map(user => ({ + ...user, + signInStats: buildSignInStats(user.id) + })), + ...buildPlatformUsersFromTalents() +] + +export async function mockGetPlatformUserList( + params: UserListQueryParams = {} +): Promise { + await delay(200) + const { page = 1, pageSize = 10, keyword, status, hotOnly, cityTagId } = params + let filtered = [...platformUsers] + + if (keyword) { + filtered = filtered.filter(user => + [user.realName, user.nickname, user.username, user.email].some(field => + field.toLowerCase().includes(keyword.toLowerCase()) + ) + ) + } + + if (status) { + filtered = filtered.filter(user => user.talentSummary?.status === status) + } + + if (hotOnly) { + filtered = filtered.filter(user => user.talentSummary?.hot) + } + + if (cityTagId) { + filtered = filtered.filter(user => user.cityTag.id === cityTagId) + } + + const start = (page - 1) * pageSize + const list = filtered.slice(start, start + pageSize) + + return { + list, + total: filtered.length, + page, + pageSize + } +} + +export async function mockDeletePlatformUser(id: number): Promise { + await delay(150) + platformUsers = platformUsers.filter(user => user.id !== id) +} diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..3a606f0 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,299 @@ +/** + * CodePort 路由配置 + * + * 支持两种模式: + * 1. 独立模式 - 使用 MainLayout,包含菜单栏 + * 2. 嵌入模式 - 使用 EmbeddedLayout,不包含菜单栏(被父框架 iframe 嵌套时) + */ +import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' + +// 检测是否为嵌入模式 +function isEmbeddedMode(): boolean { + // 检查 URL 参数 + const urlParams = new URLSearchParams(window.location.search) + if (urlParams.get('__embedded') === 'true') { + return true + } + // 检查是否在 iframe 中 + try { + return window.self !== window.top + } catch { + return true + } +} + +// 页面路由配置(共用) +const pageRoutes: RouteRecordRaw[] = [ + { + path: 'dashboard', + name: 'Dashboard', + component: () => import('@/views/dashboard/index.vue'), + meta: { + title: '控制台', + requiresAuth: true + } + }, + // 社区管理 + { + path: 'community/posts', + name: 'Posts', + component: () => import('@/views/community/posts/index.vue'), + meta: { + title: '帖子管理', + requiresAuth: true + } + }, + { + path: 'community/comments', + name: 'Comments', + component: () => import('@/views/community/comments/index.vue'), + meta: { + title: '评论管理', + requiresAuth: true + } + }, + { + path: 'community/tags', + name: 'Tags', + component: () => import('@/views/community/tags/index.vue'), + meta: { + title: '标签管理', + requiresAuth: true + } + }, + { + path: 'community/circles', + name: 'CityCircles', + component: () => import('@/views/community/circles/index.vue'), + meta: { + title: '城市圈子', + requiresAuth: true + } + }, + // 客服管理 + { + path: 'support/console', + name: 'SupportConsole', + component: () => import('@/views/support/session/index.vue'), + meta: { + title: '接入会话', + requiresAuth: true + } + }, + { + path: 'support/conversations', + name: 'SupportConversations', + component: () => import('@/views/support/conversations/index.vue'), + meta: { + title: '会话列表', + requiresAuth: true + } + }, + // 内容/文章管理 + { + path: 'content/articles', + name: 'Articles', + component: () => import('@/views/content/articles/index.vue'), + meta: { + title: '文章管理', + requiresAuth: true + } + }, + // 项目管理 + { + path: 'project/list', + name: 'Projects', + component: () => import('@/views/project/list/index.vue'), + meta: { + title: '项目列表', + requiresAuth: true + } + }, + { + path: 'project/recruitment', + name: 'Recruitment', + component: () => import('@/views/project/recruitment/index.vue'), + meta: { + title: '招募管理', + requiresAuth: true + } + }, + { + path: 'project/signed', + name: 'SignedProjects', + component: () => import('@/views/project/signed/index.vue'), + meta: { + title: '已成交项目', + requiresAuth: true + } + }, + { + path: 'project/contract', + name: 'ContractManagement', + component: () => import('@/views/project/contract/index.vue'), + meta: { + title: '合同管理', + requiresAuth: true + } + }, + { + path: 'project/sessions', + name: 'ProjectSessions', + component: () => import('@/views/project/session/index.vue'), + meta: { + title: '会话管理', + requiresAuth: true + } + }, + { + path: 'project/sessions/:id', + name: 'ProjectSessionDetail', + component: () => import('@/views/project/session/detail.vue'), + meta: { + title: '会话详情', + requiresAuth: true, + hideInMenu: true + } + }, + { + path: 'talent', + name: 'Talent', + component: () => import('@/views/talent/index.vue'), + meta: { + title: '人才管理', + requiresAuth: true + } + }, + { + path: 'talent/resume-templates', + name: 'ResumeTemplates', + component: () => import('@/views/talent/resume-templates.vue'), + meta: { + title: '简历模板管理', + requiresAuth: true + } + }, + { + path: 'talent/:id', + name: 'TalentDetail', + component: () => import('@/views/talent/detail.vue'), + meta: { + title: '人才详情', + requiresAuth: true + } + }, + + // 用户管理 + { + path: 'user/list', + name: 'Users', + component: () => import('@/views/user/list/index.vue'), + meta: { + title: '用户列表', + requiresAuth: true + } + }, + { + path: 'user/roles', + name: 'Roles', + component: () => import('@/views/user/roles/index.vue'), + meta: { + title: '角色管理', + requiresAuth: true + } + }, + { + path: 'user/certification', + name: 'UserCertification', + component: () => import('@/views/user/certification/index.vue'), + meta: { + title: '认证管理', + requiresAuth: true + } + }, + { + path: 'user/positions', + name: 'Positions', + component: () => import('@/views/user/positions/index.vue'), + meta: { + title: '岗位管理', + requiresAuth: true + } + }, + { + path: 'user/levels', + name: 'Levels', + component: () => import('@/views/user/levels/index.vue'), + meta: { + title: '等级配置', + requiresAuth: true + } + } +] + +// 根据模式选择布局 +const isEmbedded = isEmbeddedMode() + +const routes: RouteRecordRaw[] = [ + // 登录页面 - 仅在独立模式下使用 + ...(!isEmbedded ? [{ + path: '/login', + name: 'Login', + component: () => import('@/views/login/index.vue'), + meta: { + title: '登录', + requiresAuth: false + } + }] : []), + + { + path: '/', + // 根据模式选择布局 + component: isEmbedded + ? () => import('@/layouts/EmbeddedLayout.vue') + : () => import('@/layouts/MainLayout.vue'), + redirect: '/dashboard', + children: pageRoutes + }, + + // 404页面 + { + path: '/:pathMatch(.*)*', + name: 'NotFound', + component: () => import('@/views/error/404.vue'), + meta: { + title: '页面不存在' + } + } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +// 路由守卫 +router.beforeEach((to, _from, next) => { + // 设置页面标题 + document.title = `${to.meta.title || 'CodePort'} - 码头管理后台` + + // 嵌入模式下跳过登录检查 + if (isEmbedded) { + next() + return + } + + const token = localStorage.getItem('token') + + if (to.meta.requiresAuth && !token) { + // 需要登录但未登录,跳转到登录页 + next({ path: '/login', query: { redirect: to.fullPath } }) + } else if (to.path === '/login' && token) { + // 已登录访问登录页,跳转到首页 + next({ path: '/' }) + } else { + next() + } +}) + +export default router diff --git a/src/stores/index.ts b/src/stores/index.ts new file mode 100644 index 0000000..10afe6c --- /dev/null +++ b/src/stores/index.ts @@ -0,0 +1,10 @@ +/** + * Pinia Store 统一导出 + */ +import { createPinia } from 'pinia' + +const pinia = createPinia() + +export default pinia + +export * from './user' diff --git a/src/stores/user.ts b/src/stores/user.ts new file mode 100644 index 0000000..fc828eb --- /dev/null +++ b/src/stores/user.ts @@ -0,0 +1,116 @@ +/** + * 用户状态管理 + */ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import type { UserInfo, LoginParams, LoginResult, CaptchaResult } from '@/types' +import { mockGetCaptcha, mockLogin, mockGetUserInfo } from '@/mock' + +// 是否使用Mock数据 +const USE_MOCK = true + +export const useUserStore = defineStore('user', () => { + // 状态 + const token = ref(localStorage.getItem('token') || '') + const userInfo = ref(null) + + // 计算属性 + const isLoggedIn = computed(() => !!token.value) + const username = computed(() => userInfo.value?.username || '') + const nickname = computed(() => userInfo.value?.nickname || userInfo.value?.username || '') + const avatar = computed(() => userInfo.value?.avatar || '') + + // 获取验证码 + async function getCaptcha(): Promise { + if (USE_MOCK) { + return await mockGetCaptcha() + } + // 真实API调用 + const { request } = await import('@/utils/request') + const res = await request.get('/auth/captcha') + return res.data.data + } + + // 登录 + async function login(params: LoginParams): Promise { + let data: LoginResult + + if (USE_MOCK) { + data = await mockLogin(params) + } else { + // 真实API调用 + const { request } = await import('@/utils/request') + const res = await request.post('/auth/login', params) + data = res.data.data + } + + // 保存token + token.value = data.token + localStorage.setItem('token', data.token) + + // 保存用户信息 + userInfo.value = data.userInfo + + return data + } + + // 登出 + async function logout(): Promise { + try { + if (!USE_MOCK) { + const { request } = await import('@/utils/request') + await request.post('/auth/logout') + } + } catch (error) { + console.error('登出请求失败:', error) + } finally { + // 清除本地状态 + token.value = '' + userInfo.value = null + localStorage.removeItem('token') + } + } + + // 获取用户信息 + async function getUserInfo(): Promise { + if (USE_MOCK) { + const info = await mockGetUserInfo() + userInfo.value = info + return info + } + const { request } = await import('@/utils/request') + const res = await request.get('/user/info') + userInfo.value = res.data.data + return res.data.data + } + + // 设置用户信息 + function setUserInfo(info: UserInfo) { + userInfo.value = info + } + + // 重置状态 + function resetState() { + token.value = '' + userInfo.value = null + localStorage.removeItem('token') + } + + return { + // 状态 + token, + userInfo, + // 计算属性 + isLoggedIn, + username, + nickname, + avatar, + // 方法 + getCaptcha, + login, + logout, + getUserInfo, + setUserInfo, + resetState + } +}) diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..14b5e2b --- /dev/null +++ b/src/style.css @@ -0,0 +1,50 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, +body { + width: 100%; + height: 100%; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, sans-serif; + font-size: 14px; + line-height: 1.5; + color: #333; + background-color: #f0f2f5; +} + +#app { + width: 100%; + height: 100%; +} + +a { + color: #1890ff; + text-decoration: none; +} + +a:hover { + color: #40a9ff; +} + +/* 滚动条样式 */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; +} diff --git a/src/types/article.ts b/src/types/article.ts new file mode 100644 index 0000000..e28b4b1 --- /dev/null +++ b/src/types/article.ts @@ -0,0 +1,83 @@ +/** + * 文章/通告管理相关类型 + */ + +export type ArticleCategoryKey = 'announcement' | 'rule' | 'activity' | 'guide' | 'other' + +export interface ArticleCategory { + key: ArticleCategoryKey + name: string + description?: string + color: string +} + +// 规则政策二级分类 +export type RuleSubCategoryKey = 'withdraw' | 'settlement' | 'cooperation' | 'violation' | 'privacy' | 'other' + +export interface RuleSubCategory { + key: RuleSubCategoryKey + name: string +} + +// 规则政策二级分类列表 +export const RuleSubCategories: RuleSubCategory[] = [ + { key: 'withdraw', name: '提现规则' }, + { key: 'settlement', name: '结算规则' }, + { key: 'cooperation', name: '合作协议' }, + { key: 'violation', name: '违规处理' }, + { key: 'privacy', name: '隐私政策' }, + { key: 'other', name: '其他规则' } +] + +export interface ArticleInfo { + id: number + title: string + summary: string + content: string + cover?: string + category: ArticleCategoryKey + subCategory?: RuleSubCategoryKey // 规则政策的二级分类 + tags: string[] + publishStatus: 'draft' | 'published' + pinned: boolean + createdAt: string + updatedAt: string + publishedAt?: string + author: { + id: number + name: string + avatar?: string + } + viewCount: number + linkUrl?: string +} + +export interface ArticleQueryParams { + keyword?: string + category?: ArticleCategoryKey + status?: ArticleInfo['publishStatus'] + pinned?: boolean + page?: number + pageSize?: number +} + +export interface ArticleListResult { + list: ArticleInfo[] + total: number + page: number + pageSize: number +} + +export interface ArticleCreatePayload { + title: string + summary: string + content: string + category: ArticleCategoryKey + subCategory?: RuleSubCategoryKey // 规则政策的二级分类 + tags: string[] + publishStatus: ArticleInfo['publishStatus'] + pinned?: boolean + cover?: string + linkUrl?: string + author: ArticleInfo['author'] +} diff --git a/src/types/certification.ts b/src/types/certification.ts new file mode 100644 index 0000000..6383db1 --- /dev/null +++ b/src/types/certification.ts @@ -0,0 +1,134 @@ +/** + * 用户认证相关类型定义 + */ + +// 认证类型 +export type CertificationType = 'identity' | 'education' | 'professional' | 'enterprise' + +// 认证类型映射 +export const CertificationTypeMap: Record = { + identity: '实名认证', + education: '学历认证', + professional: '职业资格认证', + enterprise: '企业认证' +} + +// 认证状态 +export type CertificationStatus = 'pending' | 'verified' | 'rejected' | 'expired' + +// 认证状态映射 +export const CertificationStatusMap: Record = { + pending: '审核中', + verified: '已认证', + rejected: '已拒绝', + expired: '已过期' +} + +// 认证状态徽章映射 +export const CertificationStatusBadgeMap: Record = { + pending: 'processing', + verified: 'success', + rejected: 'error', + expired: 'default' +} + +// 实名认证信息 +export interface IdentityCertification { + realName: string // 真实姓名 + idCardNumber: string // 身份证号(脱敏显示) + idCardFrontUrl?: string // 身份证正面照片URL + idCardBackUrl?: string // 身份证背面照片URL + faceVerified: boolean // 是否通过人脸识别 + verifiedAt: string // 认证时间 +} + +// 学历认证信息 +export interface EducationCertification { + school: string // 学校名称 + major: string // 专业 + degree: string // 学位(本科、硕士、博士等) + graduationYear: number // 毕业年份 + certificateNumber: string // 学位证书编号(脱敏) + verifiedAt: string // 认证时间 +} + +// 职业资格认证信息 +export interface ProfessionalCertification { + certificateName: string // 证书名称 + certificateNumber: string // 证书编号(脱敏) + issuer: string // 发证机构 + issueDate: string // 发证日期 + expiryDate?: string // 有效期(可选,部分证书永久有效) + verifiedAt: string // 认证时间 +} + +// 企业认证信息 +export interface EnterpriseCertification { + companyName: string // 企业名称 + unifiedSocialCreditCode: string // 统一社会信用代码(脱敏) + legalRepresentative: string // 法定代表人 + registeredCapital: string // 注册资本 + businessScope: string // 经营范围 + businessLicenseUrl?: string // 营业执照照片URL + verifiedAt: string // 认证时间 +} + +// 认证记录 +export interface CertificationRecord { + id: number + userId: number + userName: string + userAvatar: string + userPhone: string + userEmail: string + type: CertificationType + status: CertificationStatus + + // 认证详情(根据类型不同包含不同内容) + identityInfo?: IdentityCertification + educationInfo?: EducationCertification + professionalInfo?: ProfessionalCertification + enterpriseInfo?: EnterpriseCertification + + // 第三方认证信息 + thirdPartyProvider: string // 第三方认证提供商 + thirdPartyOrderId: string // 第三方订单号 + + // 审核信息 + rejectReason?: string // 拒绝原因 + submittedAt: string // 提交时间 + verifiedAt?: string // 认证通过时间 + expiredAt?: string // 过期时间 + + createdAt: string + updatedAt: string +} + +// 认证统计 +export interface CertificationStats { + total: number + verified: number + pending: number + rejected: number + expired: number + todayNew: number +} + +// 查询参数 +export interface CertificationQueryParams { + page?: number + pageSize?: number + keyword?: string + type?: CertificationType + status?: CertificationStatus + startDate?: string + endDate?: string +} + +// 查询结果 +export interface CertificationListResult { + list: CertificationRecord[] + total: number + page: number + pageSize: number +} diff --git a/src/types/cityCircle.ts b/src/types/cityCircle.ts new file mode 100644 index 0000000..c611077 --- /dev/null +++ b/src/types/cityCircle.ts @@ -0,0 +1,122 @@ +/** + * 城市圈子相关类型定义 + */ + +// 圈子标签 +export interface CircleTag { + id: number + name: string + color?: string +} + +// 帖子话题 +export interface CircleTopic { + id: number + name: string + description?: string + postCount: number + isHot?: boolean // 热门话题 + isNew?: boolean // 最新话题 + createdAt: string +} + +// 热门讨论 +export interface HotDiscussion { + id: number + title: string + topicName: string + discussionCount: number + tags?: string[] +} + +// 圈子成员角色 +export type CircleMemberRole = 'owner' | 'operator' | 'member' + +// 圈子成员 +export interface CircleMember { + id: number + userId: number + nickname: string + avatar?: string + role: CircleMemberRole + title?: string // 职位描述,如 "圈子主持人"、"招聘合伙人" + tags?: string[] // 成员标签,如 "互联网"、"AI" + operatorType?: string // 运营人员类型,如 "全职运营"、"开放推广" + joinedAt: string +} + +// 城市圈子状态 +export type CircleStatus = 'active' | 'inactive' | 'pending' + +// 城市圈子统计数据 +export interface CircleStats { + developerCount: number // 开发者数量 + openPositionCount: number // 开放岗位 + avgSalary: number // 平均薪资 (K) + activeTopicCount: number // 活跃话题数 + hotTopicCount: number // 热门讨论数 + coreMemberCount: number // 核心成员数 +} + +// 城市圈子 +export interface CityCircle { + id: number + cityId: number // 关联城市管理的城市ID + cityName: string + cityNameEn?: string // 城市英文名 + cityCode: string + description: string // 城市简介 + tags: CircleTag[] // 城市标签 + icon?: string // 城市图标/logo + coverImage?: string // 封面图 + stats: CircleStats + topics: CircleTopic[] + hotDiscussions: HotDiscussion[] + members: CircleMember[] + operators: CircleMember[] // 主要运营人员 + status: CircleStatus + sort: number + createdAt: string + updatedAt: string +} + +// 城市圈子列表查询参数 +export interface CityCircleQueryParams { + page?: number + pageSize?: number + keyword?: string + cityId?: number + status?: CircleStatus +} + +// 城市圈子列表结果 +export interface CityCircleListResult { + list: CityCircle[] + total: number + page: number + pageSize: number +} + +// 创建/编辑城市圈子表单 +export interface CityCircleFormData { + id?: number + cityId: number + description: string + tags: number[] // 标签ID列表 + status: CircleStatus + sort: number +} + +// 添加成员表单 +export interface AddMemberFormData { + userId: number + role: CircleMemberRole + title?: string + operatorType?: string +} + +// 创建话题表单 +export interface CreateTopicFormData { + name: string + description?: string +} diff --git a/src/types/comment.ts b/src/types/comment.ts new file mode 100644 index 0000000..ba37863 --- /dev/null +++ b/src/types/comment.ts @@ -0,0 +1,72 @@ +/** + * 评论管理相关类型定义 + */ + +// 评论状态 +export type CommentStatus = 'normal' | 'hidden' | 'deleted' + +// 评论状态映射 +export const CommentStatusMap: Record = { + normal: '正常', + hidden: '已隐藏', + deleted: '已删除' +} + +// 评论者信息 +export interface CommentUser { + id: number + username: string + nickname: string + avatar: string +} + +// 关联的帖子简要信息 +export interface CommentPost { + id: number + title: string + authorName: string +} + +// 评论记录 +export interface CommentRecord { + id: number + post: CommentPost // 关联帖子 + user: CommentUser // 评论者 + content: string // 评论内容 + likeCount: number // 点赞数 + replyCount: number // 回复数(一级评论才有) + parentId?: number // 父评论ID(回复才有) + parentContent?: string // 父评论内容摘要 + status: CommentStatus // 状态 + createdAt: string // 评论时间 +} + +// 评论查询参数 +export interface CommentQueryParams { + page?: number + pageSize?: number + keyword?: string // 搜索评论内容 + postId?: number // 帖子ID + userId?: number // 用户ID + status?: CommentStatus // 状态 + isReply?: boolean // 是否为回复 + startDate?: string + endDate?: string +} + +// 评论列表响应 +export interface CommentListResult { + list: CommentRecord[] + total: number + page: number + pageSize: number +} + +// 评论统计 +export interface CommentStats { + total: number + todayNew: number + normal: number + hidden: number +} + diff --git a/src/types/conversation.ts b/src/types/conversation.ts new file mode 100644 index 0000000..14cf2d9 --- /dev/null +++ b/src/types/conversation.ts @@ -0,0 +1,92 @@ +/** + * 客服会话相关类型定义 + */ + +export type ConversationChannel = 'app' | 'web' | 'wechat' | 'miniapp' +export type ConversationStatus = 'waiting' | 'active' | 'pending' | 'resolved' | 'closed' +export type ConversationPriority = 'normal' | 'high' | 'vip' + +export interface ConversationTag { + id: number + name: string + color: string +} + +export interface ConversationCustomer { + id: number + nickname: string + avatar: string + city: string + vip: boolean + level: string + intents: string[] +} + +export interface ConversationAgent { + id: number + name: string + avatar: string + title: string + workload: number + expertise: string[] +} + +export interface ConversationMessage { + id: number + sender: 'user' | 'agent' | 'system' + senderName: string + content: string + timestamp: string + avatar?: string +} + +export interface ConversationSession { + id: number + sessionCode: string + channel: ConversationChannel + status: ConversationStatus + priority: ConversationPriority + createdAt: string + lastMessageAt: string + lastMessage: string + unreadCount: number + totalMessages: number + waitingTime: number + firstResponseAt?: string + resolvedAt?: string + satisfaction?: number + autoDetectedIntent: string + source: string + tags: ConversationTag[] + customer: ConversationCustomer + assignedAgent?: ConversationAgent + messages: ConversationMessage[] +} + +export interface ConversationListQuery { + page?: number + pageSize?: number + keyword?: string + status?: ConversationStatus | 'all' + channel?: ConversationChannel | 'all' + priority?: ConversationPriority | 'all' + vipOnly?: boolean + dateRange?: [string, string] +} + +export interface ConversationMetrics { + waitingCount: number + activeCount: number + pendingCount: number + resolvedToday: number + satisfaction: number + avgFirstResponse: number +} + +export interface ConversationListResult { + list: ConversationSession[] + total: number + page: number + pageSize: number + metrics: ConversationMetrics +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..7e96707 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,17 @@ +/** + * 类型统一导出 + */ + +export * from './user' +export * from './response' +export * from './post' +export * from './project' +export * from './recruitment' +export * from './comment' +export * from './talent' +export * from './conversation' +export * from './article' +export * from './cityCircle' +export * from './certification' +export * from './signedProject' +export * from './projectSession' diff --git a/src/types/post.ts b/src/types/post.ts new file mode 100644 index 0000000..a2003d8 --- /dev/null +++ b/src/types/post.ts @@ -0,0 +1,58 @@ +/** + * 帖子相关类型定义 + */ + +// 帖子标签 +export interface PostTag { + id: number + name: string + color: string +} + +// 帖子作者信息 +export interface PostAuthor { + id: number + username: string + nickname: string + avatar: string +} + +// 帖子信息 +export interface PostInfo { + id: number + title: string + content: string + author: PostAuthor + tags: PostTag[] + viewCount: number + likeCount: number + commentCount: number + isHot: boolean // 是否热门:查看>1000 或 点赞>500 + isOfficial: boolean // 是否官方帖子 + status: 'published' | 'hidden' | 'deleted' + createdAt: string + updatedAt: string +} + +// 帖子查询参数 +export interface PostQueryParams { + page?: number + pageSize?: number + keyword?: string + tagId?: number + authorId?: number + status?: string + isHot?: boolean + isOfficial?: boolean + startDate?: string + endDate?: string +} + +// 帖子列表响应 +export interface PostListResult { + list: PostInfo[] + total: number + page: number + pageSize: number +} + diff --git a/src/types/project.ts b/src/types/project.ts new file mode 100644 index 0000000..a57dc4f --- /dev/null +++ b/src/types/project.ts @@ -0,0 +1,78 @@ +/** + * 项目相关类型定义 + */ + +// 项目标签 +export interface ProjectTag { + id: number + name: string + color: string +} + +// 项目发布人 +export interface ProjectPublisher { + id: number + username: string + nickname: string + avatar: string + company?: string +} + +// 工作类型 +export type WorkType = 'fulltime' | 'parttime' | 'remote' | 'internship' | 'freelance' + +// 工作类型映射 +export const WorkTypeMap: Record = { + fulltime: '全职', + parttime: '兼职', + remote: '远程', + internship: '实习', + freelance: '自由接单' +} + +// 项目信息 +export interface ProjectInfo { + id: number + name: string // 项目名称 + salaryMin: number // 最低金额 + salaryMax: number // 最高金额 + salaryUnit: string // 金额单位(月/次/项目) + publisher: ProjectPublisher // 发布人 + location: string // 工作地点 + workType: WorkType // 工作类型 + description: string // 项目描述 + requirements: string // 任职要求 + benefits: string // 福利待遇 + tags: ProjectTag[] // 项目标签 + contactEmail: string // 联系邮箱 + deadline: string // 招聘截止日期 + status: 'active' | 'closed' | 'expired' // 项目状态 + createdAt: string // 发布时间 + updatedAt: string // 更新时间 + // 置顶相关 + isPinned: boolean // 是否置顶 + pinnedUntil?: string // 置顶到期时间 + // 曝光相关 + exposureCount: number // 曝光次数 + lastExposureAt?: string // 最后曝光时间 +} + +// 项目查询参数 +export interface ProjectQueryParams { + page?: number + pageSize?: number + keyword?: string + workType?: WorkType + tagId?: number + status?: string + location?: string +} + +// 项目列表响应 +export interface ProjectListResult { + list: ProjectInfo[] + total: number + page: number + pageSize: number +} + diff --git a/src/types/projectSession.ts b/src/types/projectSession.ts new file mode 100644 index 0000000..bf5ea08 --- /dev/null +++ b/src/types/projectSession.ts @@ -0,0 +1,84 @@ +/** + * 项目会话管理相关类型定义 + */ +import type { ApplicantInfo } from './recruitment' +import type { ProjectInfo } from './project' + +// 项目成员角色 +export type ProjectRole = 'leader' | 'product' | 'frontend' | 'backend' | 'test' | 'design' + +// 角色的中文映射 +export const ProjectRoleMap: Record = { + leader: '负责人', + product: '产品经理', + frontend: '前端开发', + backend: '后端开发', + test: '测试工程师', + design: 'UI设计师' +} + +// 项目成员信息 +export interface ProjectMember { + id: number // 成员记录ID + userId: number // 用户ID (对应ApplicantInfo.id) + role: ProjectRole // 担任角色 + info: ApplicantInfo // 详细信息 + isLeader: boolean // 是否负责人 + status: 'online' | 'offline' // 在线状态 + joinedAt: string // 加入时间 +} + +// 项目流程节点 +export interface ProjectProgressNode { + id: number + title: string + status: 'pending' | 'processing' | 'completed' // 待开始 | 进行中 | 已完成 + completedAt?: string + description?: string +} + +// 项目文件资料 +export interface ProjectMaterial { + id: number + name: string + url: string + type: 'doc' | 'image' | 'archive' | 'other' + size: string + uploadedBy: string + uploadedAt: string +} + +// 会话消息 +export interface ProjectMessage { + id: number + sessionId: number + senderId: number + senderName: string + senderAvatar: string + senderRole: ProjectRole + content: string + sentAt: string + type: 'text' | 'file' | 'image' | 'system' +} + +// 项目会话详情 +export interface ProjectSession { + id: number // 会话ID + projectId: number // 关联项目ID + projectInfo: ProjectInfo // 项目基本信息 + members: ProjectMember[] // 团队成员 + progressNodes: ProjectProgressNode[] // 项目进度 + materials: ProjectMaterial[] // 文件资料 + messages: ProjectMessage[] // 聊天记录 (通常只加载最近的) + onlineCount: number // 在线人数 +} + +// 会话列表项 (用于左侧导航或列表页) +export interface ProjectSessionListItem { + id: number + projectId: number + projectName: string + lastMessage?: string + lastMessageTime?: string + unreadCount: number +} diff --git a/src/types/recruitment.ts b/src/types/recruitment.ts new file mode 100644 index 0000000..7faa212 --- /dev/null +++ b/src/types/recruitment.ts @@ -0,0 +1,101 @@ +/** + * 招募管理相关类型定义 + */ + +// 申请状态 +export type RecruitmentStatus = 'pending' | 'approved' | 'rejected' | 'withdrawn' | 'expired' + +// 申请状态映射(社区招募场景:发布者-申请者之间的状态) +export const RecruitmentStatusMap: Record = { + pending: '待回复', + approved: '已接受', + rejected: '已拒绝', + withdrawn: '已撤回', + expired: '已过期' +} + +// 申请人信息 +export interface ApplicantInfo { + id: number + username: string + nickname: string + avatar: string + email: string + phone?: string + skills: string[] // 技能标签 + experience: string // 工作经验(如:3年) + introduction: string // 个人简介 + portfolioUrl?: string // 作品集链接 + resumeUrl?: string // 简历链接 +} + +// 关联的项目简要信息 +export interface RecruitmentProject { + id: number + name: string + workType: string + location: string + salaryMin: number + salaryMax: number + salaryUnit: string + publisherName: string +} + +// 发布人信息 +export interface PublisherInfo { + id: number + username: string + nickname: string + avatar: string + email: string + phone?: string + company?: string // 公司名称 + position?: string // 职位 + verified: boolean // 是否认证 +} + +// 招募申请记录 +export interface RecruitmentRecord { + id: number + project: RecruitmentProject // 关联项目 + applicant: ApplicantInfo // 申请人 + publisher: PublisherInfo // 发布人 + expectedSalary: number // 期望薪资 + availableDate: string // 可到岗日期 + coverLetter: string // 申请说明/求职信 + status: RecruitmentStatus // 申请状态 + appliedAt: string // 申请时间 + processedAt?: string // 处理时间 + processedBy?: string // 处理人 + rejectReason?: string // 拒绝原因 + remark?: string // 备注 +} + +// 招募查询参数 +export interface RecruitmentQueryParams { + page?: number + pageSize?: number + keyword?: string // 搜索项目名/申请人 + projectId?: number // 项目ID + status?: RecruitmentStatus // 状态 + startDate?: string // 申请开始日期 + endDate?: string // 申请结束日期 +} + +// 招募列表响应 +export interface RecruitmentListResult { + list: RecruitmentRecord[] + total: number + page: number + pageSize: number +} + +// 招募统计 +export interface RecruitmentStats { + total: number + pending: number + approved: number + rejected: number + todayNew: number +} + diff --git a/src/types/response.ts b/src/types/response.ts new file mode 100644 index 0000000..3c42aff --- /dev/null +++ b/src/types/response.ts @@ -0,0 +1,32 @@ +/** + * 响应相关类型定义 + */ + +// 基础响应结构 +export interface ApiResponse { + code: number + message: string + data: T + success: boolean +} + +// 分页请求参数 +export interface PageParams { + page: number + pageSize: number +} + +// 分页响应数据 +export interface PageResult { + list: T[] + total: number + page: number + pageSize: number + totalPages: number +} + +// 列表响应 +export interface ListResult { + list: T[] + total: number +} diff --git a/src/types/signedProject.ts b/src/types/signedProject.ts new file mode 100644 index 0000000..a3b2092 --- /dev/null +++ b/src/types/signedProject.ts @@ -0,0 +1,251 @@ +/** + * 已成交项目相关类型定义 + */ + +// 项目进度状态 +export type ProjectProgressStatus = 'signed' | 'in_progress' | 'delivered' | 'accepted' | 'completed' | 'disputed' + +// 项目进度状态映射 +export const ProjectProgressStatusMap: Record = { + signed: '已签约', + in_progress: '进行中', + delivered: '已交付', + accepted: '已验收', + completed: '已完成', + disputed: '争议处理' +} + +// 项目进度状态徽章映射 +export const ProjectProgressStatusBadgeMap: Record = { + signed: 'default', + in_progress: 'processing', + delivered: 'warning', + accepted: 'success', + completed: 'success', + disputed: 'error' +} + +// 签约人信息 +export interface ContractPartyInfo { + id: number + username: string + nickname: string + avatar: string + email: string + phone?: string + company?: string + position?: string + verified: boolean +} + +// 合同信息 +export interface ContractInfo { + id: number + contractNo: string // 合同编号 + title: string // 合同标题 + type: 'service' | 'project' | 'freelance' // 合同类型 + amount: number // 合同金额 + currency: 'CNY' + signedAt: string // 签约时间 + startDate: string // 开始日期 + endDate: string // 结束日期 + status: 'active' | 'completed' | 'terminated' | 'expired' // 合同状态 + attachmentUrl?: string // 合同附件URL + attachmentName?: string // 附件名称 + remark?: string // 备注 +} + +// 项目进度里程碑 +export interface ProjectMilestone { + id: number + title: string // 里程碑标题 + description?: string // 描述 + plannedDate: string // 计划完成日期 + actualDate?: string // 实际完成日期 + status: 'pending' | 'in_progress' | 'completed' | 'delayed' + deliverables?: string[] // 交付物 +} + +// 已成交项目记录 +export interface SignedProjectRecord { + id: number + // 项目基本信息 + projectName: string + projectDescription: string + workType: string + location: string + + // 金额信息 + contractAmount: number // 签约金额 + paidAmount: number // 已支付金额 + pendingAmount: number // 待支付金额 + currency: 'CNY' + + // 发布人和签约人 + publisher: ContractPartyInfo // 发布人/甲方 + contractor: ContractPartyInfo // 签约人/乙方 + + // 合同信息 + contract: ContractInfo + + // 项目进度 + progressStatus: ProjectProgressStatus + progressPercent: number // 进度百分比 0-100 + milestones: ProjectMilestone[] // 里程碑列表 + + // 时间信息 + signedAt: string // 签约时间 + startedAt?: string // 项目开始时间 + deliveredAt?: string // 交付时间 + completedAt?: string // 完成时间 + + // 评价信息 + publisherRating?: number // 发布人给的评分 + contractorRating?: number // 签约人给的评分 + + createdAt: string + updatedAt: string +} + +// 已成交项目统计 +export interface SignedProjectStats { + total: number + inProgress: number + completed: number + disputed: number + totalAmount: number + paidAmount: number +} + +// 查询参数 +export interface SignedProjectQueryParams { + page?: number + pageSize?: number + keyword?: string + progressStatus?: ProjectProgressStatus + startDate?: string + endDate?: string +} + +// 查询结果 +export interface SignedProjectListResult { + list: SignedProjectRecord[] + total: number + page: number + pageSize: number +} + +// ================== 合同管理类型 ================== + +// 合同类型 +export type ContractType = 'service' | 'project' | 'freelance' | 'nda' | 'other' + +// 合同类型映射 +export const ContractTypeMap: Record = { + service: '服务合同', + project: '项目合同', + freelance: '自由职业合同', + nda: '保密协议', + other: '其他' +} + +// 合同状态 +export type ContractStatus = 'draft' | 'pending_sign' | 'active' | 'completed' | 'terminated' | 'expired' + +// 合同状态映射 +export const ContractStatusMap: Record = { + draft: '草稿', + pending_sign: '待签署', + active: '生效中', + completed: '已完成', + terminated: '已终止', + expired: '已过期' +} + +// 合同状态徽章 +export const ContractStatusBadgeMap: Record = { + draft: 'default', + pending_sign: 'processing', + active: 'success', + completed: 'success', + terminated: 'error', + expired: 'warning' +} + +// 完整合同记录 +export interface ContractRecord { + id: number + contractNo: string // 合同编号 + title: string // 合同标题 + type: ContractType // 合同类型 + + // 合同双方 + partyA: ContractPartyInfo // 甲方 + partyB: ContractPartyInfo // 乙方 + + // 关联项目 + relatedProjectId?: number + relatedProjectName?: string + + // 金额 + amount: number + currency: 'CNY' + + // 时间 + signedAt?: string // 签署时间 + effectiveDate: string // 生效日期 + expiryDate: string // 到期日期 + + // 状态 + status: ContractStatus + + // 附件 + attachments: { + id: number + name: string + url: string + size: number + uploadedAt: string + }[] + + // 签署记录 + signRecords: { + partyType: 'A' | 'B' + signedBy: string + signedAt: string + signMethod: 'electronic' | 'manual' + }[] + + remark?: string + createdAt: string + updatedAt: string +} + +// 合同统计 +export interface ContractStats { + total: number + active: number + pendingSign: number + completed: number + expired: number + totalAmount: number +} + +// 合同查询参数 +export interface ContractQueryParams { + page?: number + pageSize?: number + keyword?: string + type?: ContractType + status?: ContractStatus + startDate?: string + endDate?: string +} + +// 合同列表结果 +export interface ContractListResult { + list: ContractRecord[] + total: number + page: number + pageSize: number +} diff --git a/src/types/talent.ts b/src/types/talent.ts new file mode 100644 index 0000000..f6f937b --- /dev/null +++ b/src/types/talent.ts @@ -0,0 +1,236 @@ +/** + * 人才管理类型定义 + */ +import type { PostTag } from './post' + +// 人员状态 +export type TalentStatus = 'available' | 'busy' | 'onboarding' | 'resting' + +// 状态文本映射 +export const TalentStatusMap: Record = { + available: '可约', + busy: '忙碌中', + onboarding: '即将空闲', + resting: '休整中' +} + +// 状态徽章映射 +export const TalentStatusBadgeMap: Record< + TalentStatus, + 'success' | 'processing' | 'error' | 'default' | 'warning' +> = { + available: 'success', + busy: 'error', + onboarding: 'processing', + resting: 'default' +} + +// 状态色值映射 +export const TalentStatusColorMap: Record = { + available: '#52c41a', + busy: '#fa541c', + onboarding: '#1890ff', + resting: '#d9d9d9' +} + +export type TalentTag = PostTag + +export type TalentFeeUnit = 'day' | 'month' + +export interface TalentFee { + amount: number + unit: TalentFeeUnit + currency: 'CNY' +} + +// 评分信息(信誉评分由后台计算)- 100分制 +export interface TalentRating { + credit: number // 信誉评分 0-100(后台计算:基于履约、评价、投诉等) + project: number // 项目评分 0-100 +} + +// 评分等级映射 +export function getRatingGrade(score: number): { grade: string; color: string } { + if (score >= 90) return { grade: 'A+', color: '#52c41a' } + if (score >= 80) return { grade: 'A', color: '#73d13d' } + if (score >= 70) return { grade: 'B+', color: '#1890ff' } + if (score >= 60) return { grade: 'B', color: '#40a9ff' } + if (score >= 50) return { grade: 'C', color: '#faad14' } + return { grade: 'D', color: '#ff4d4f' } +} + +// 用户社交统计 +export interface UserSocialStats { + completedTaskCount: number // 已完成任务数 + ongoingTaskCount: number // 进行中任务数 + followingCount: number // 关注数 + followerCount: number // 粉丝数 + starCount: number // 星标/收藏数 + likeCount: number // 点赞数 +} + +// 签到信息 +export interface CheckInInfo { + consecutiveDays: number // 连续签到天数 + totalDays: number // 累计签到天数 + lastCheckIn?: string // 最后签到日期 + todayCheckedIn: boolean // 今日是否已签到 + checkedInDates: string[] // 本月已签到日期列表 (格式: 'YYYY-MM-DD') +} + +export interface TalentProjectRecord { + id: number + name: string + role: string + deliveryDate: string + score: number // 项目评分 0-100 + income?: number // 项目收益(元) +} + +// 收益记录类型 (人才收益) +export type TalentIncomeType = 'project' | 'task' | 'reward' | 'other' + +// 收益记录类型映射 +export const TalentIncomeTypeMap: Record = { + project: '项目结算', + task: '完成任务', + reward: '奖励', + other: '其他' +} + +// 收益记录 (人才收益) +export interface TalentIncomeRecord { + id: number + type: TalentIncomeType + title: string // 收益标题,如项目名称、任务名称 + amount: number // 金额(正数为收入,负数为支出) + isIncome: boolean // 是否为收入(用于区分收入/支出) + relatedProjectId?: number // 关联项目ID + createdAt: string // 创建时间 + remark?: string // 备注 +} + +// 附件简历 +export interface TalentResume { + id: number + fileName: string + fileUrl: string + fileSize: number // 文件大小(字节) + uploadedAt: string +} + +// 在线简历模板 +export interface ResumeTemplate { + id: number + name: string + thumbnail: string // 模板缩略图 + description: string + isDefault: boolean + createdAt: string + tags?: string[] + useCount?: number +} + +// 工作经历 +export interface WorkExperience { + id: number + company: string + industry?: string + department?: string + position: string + startTime: string + endTime: string | null // null 表示至今 + description: string + tags?: string[] +} + +// 教育经历 +export interface EducationExperience { + id: number + school: string + major: string + degree: string // 本科, 硕士, 博士... + startTime: string + endTime: string + description?: string +} + +// 求职意向 +export interface JobIntention { + jobStatus: string // 离职-随时到岗, 在职-月内到岗... + jobType: string // 全职, 兼职, 外包... + expectedSalary: string + expectedCity: string[] + expectedIndustry: string[] + expectedPosition: string[] +} + +export interface TalentProfile { + id: number + realName: string + avatar: string + gender: 'male' | 'female' + age: number + phone?: string + email?: string + wechat?: string + positionTitle: string + domain?: string // 领域/方向,如 "Web3 / SaaS" + verified: boolean // 是否认证 + skillTags: TalentTag[] + cityTag: TalentTag + projectCount: number + experienceYears: number + degree: string // 最高学历 + fee: TalentFee + rating: TalentRating + level: number + status: TalentStatus + introduction: string + recentProjects: TalentProjectRecord[] + availableFrom: string + hot: boolean + + // 社交统计 + socialStats: UserSocialStats + + // 签到信息 + checkInInfo: CheckInInfo + + // 收益钱包 + wallet: { + balance: number // 当前余额 + totalIncome: number // 累计收入 + totalExpense: number // 累计支出 + currency: 'CNY' // 币种 + } + + // 收益记录列表 + incomeRecords: TalentIncomeRecord[] + + // 简历相关 + resumeAttachments: TalentResume[] // 附件简历列表 + selectedTemplateId?: number // 使用的在线简历模板ID + + // 详细简历数据 + workExperiences: WorkExperience[] + educationExperiences: EducationExperience[] + jobIntention: JobIntention +} + +export interface TalentQueryParams { + page?: number + pageSize?: number + keyword?: string + status?: TalentStatus + tagId?: number + cityTagId?: number + hotOnly?: boolean +} + +export interface TalentListResult { + list: TalentProfile[] + total: number + page: number + pageSize: number +} diff --git a/src/types/user.ts b/src/types/user.ts new file mode 100644 index 0000000..4a1dbbb --- /dev/null +++ b/src/types/user.ts @@ -0,0 +1,162 @@ +/** + * 用户相关类型定义 + */ +import type { TalentStatus, TalentFee, TalentRating, TalentTag, TalentIncomeRecord } from './talent' + +// 用户信息(用于鉴权等场景) +export interface UserInfo { + id: number + username: string + nickname: string + avatar?: string + email?: string + phone?: string + role: string + permissions?: string[] + createTime?: string + lastLoginTime?: string +} + +// 登录请求参数 +export interface LoginParams { + username: string + password: string + captcha: string + captchaKey: string +} + +// 登录响应数据 +export interface LoginResult { + token: string + refreshToken?: string + expires: number + userInfo: UserInfo +} + +// 验证码响应 +export interface CaptchaResult { + captchaKey: string + captchaImage: string +} + +// 用户与人才数据关联摘要 +export interface UserTalentSummary { + status: TalentStatus + projectCount: number + experienceYears: number + fee: TalentFee + rating: TalentRating + level: number + hot: boolean + availableFrom: string + skillTags: TalentTag[] + cityTag: TalentTag + // 新增字段 + verified?: boolean // 是否认证 + domain?: string // 领域/方向 + socialStats?: { + completedTaskCount: number + ongoingTaskCount: number + followingCount: number + followerCount: number + starCount: number + likeCount: number + } + // 收益钱包 + wallet?: { + balance: number // 当前余额 + totalIncome: number // 累计收入 + totalExpense: number // 累计支出 + currency: 'CNY' // 币种 + } + // 收益记录列表 + incomeRecords?: TalentIncomeRecord[] + // 发布招募统计 + publishStats?: { + totalPublished: number // 总发布数 + activePublished: number // 进行中 + closedPublished: number // 已关闭 + totalApplicants: number // 总申请人数 + } + // 发布的招募记录(简要) + publishedRecruitments?: PublishedRecruitmentSummary[] +} + +// 发布的招募摘要 +export interface PublishedRecruitmentSummary { + id: number + projectName: string // 项目名称 + workType: string // 工作类型 + location: string // 工作地点 + salaryRange: string // 薪资范围 + applicantCount: number // 申请人数 + status: 'active' | 'closed' // 状态 + publishedAt: string // 发布时间 +} + +// 平台用户列表项(为人才衍生的数据源) +export interface PlatformUserItem { + id: number + realName: string + username: string + nickname: string + avatar: string + email: string + phone: string + role: string + joinedAt: string + lastActiveAt: string + tags: string[] + isTalent: boolean + cityTag: TalentTag + talentSummary?: UserTalentSummary + introduction?: string + paymentMethods?: { + bankCard?: { + bankName: string + accountName: string + accountNo: string + } + alipay?: { + accountName: string + accountNo: string + } + } + // 签到数据 + signInStats?: { + currentMonthDays: number // 本月签到天数 + totalReward: number // 本月累计获得奖励 + lastSignInDate?: string // 最后签到时间 + history: { + date: string // YYYY-MM-DD + reward: number // 获得奖励 + isMakeup?: boolean // 是否补签 + }[] + } + // 联系人列表 + contacts?: { + id: number + name: string + avatar: string + company?: string + role: string + status: string + isMutal: boolean + }[] +} + +export interface UserListQueryParams { + page?: number + pageSize?: number + keyword?: string + status?: TalentStatus + hotOnly?: boolean + cityTagId?: number +} + +export interface UserListResult { + list: PlatformUserItem[] + total: number + page: number + pageSize: number +} diff --git a/src/utils/common.ts b/src/utils/common.ts new file mode 100644 index 0000000..241ec97 --- /dev/null +++ b/src/utils/common.ts @@ -0,0 +1,76 @@ +/** + * 公共工具函数 + */ +import type { Key } from 'ant-design-vue/es/table/interface' +import type { TablePaginationConfig } from 'ant-design-vue' + +/** + * 格式化日期 + */ +export function formatDate(str: string): string { + return new Date(str).toLocaleDateString('zh-CN') +} + +/** + * 格式化日期时间 + */ +export function formatDateTime(str: string): string { + return new Date(str).toLocaleString('zh-CN') +} + +/** + * 格式化数字(超过1000显示为x.xk) + */ +export function formatCount(num: number): string { + if (num >= 1000) { + return (num / 1000).toFixed(1) + 'k' + } + return num.toString() +} + +/** + * 从数组中随机取一个元素(带类型安全) + */ +export function randomItem(arr: T[]): T { + return arr[Math.floor(Math.random() * arr.length)]! +} + +/** + * 模拟延迟 + */ +export function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +/** + * 表格选择变更处理器类型 + */ +export type TableSelectChange = (keys: Key[]) => void + +/** + * 表格分页变更处理器类型 + */ +export type TablePaginationChange = (pagination: TablePaginationConfig) => void + +/** + * 创建表格分页变更处理函数 + */ +export function createTableChangeHandler( + paginationRef: { current: number; pageSize: number }, + loadFn: () => void +) { + return (pagination: TablePaginationConfig) => { + paginationRef.current = pagination.current || 1 + paginationRef.pageSize = pagination.pageSize || 10 + loadFn() + } +} + +/** + * 创建表格选择变更处理函数 + */ +export function createSelectChangeHandler(selectedKeysRef: { value: Key[] }) { + return (keys: Key[]) => { + selectedKeysRef.value = keys + } +} diff --git a/src/utils/request.ts b/src/utils/request.ts new file mode 100644 index 0000000..717b13a --- /dev/null +++ b/src/utils/request.ts @@ -0,0 +1,111 @@ +/** + * Axios 基础配置 + */ +import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse, type InternalAxiosRequestConfig } from 'axios' +import { message } from 'ant-design-vue' +import type { ApiResponse } from '@/types' + +// 创建axios实例 +const service: AxiosInstance = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL || '/api', + timeout: 30000, + headers: { + 'Content-Type': 'application/json' + } +}) + +// 请求拦截器 +service.interceptors.request.use( + (config: InternalAxiosRequestConfig) => { + // 从localStorage获取token + const token = localStorage.getItem('token') + if (token && config.headers) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => { + console.error('请求错误:', error) + return Promise.reject(error) + } +) + +// 响应拦截器 +service.interceptors.response.use( + (response: AxiosResponse) => { + const res = response.data + + // 根据业务状态码判断请求是否成功 + if (res.code === 200 || res.success) { + return response + } + + // 处理业务错误 + message.error(res.message || '请求失败') + + // 处理特定错误码 + if (res.code === 401) { + // token过期或未授权,跳转登录 + localStorage.removeItem('token') + window.location.href = '/login' + } + + return Promise.reject(new Error(res.message || '请求失败')) + }, + (error) => { + console.error('响应错误:', error) + + // 处理HTTP错误 + let errorMessage = '网络错误,请稍后重试' + if (error.response) { + const { status } = error.response + switch (status) { + case 400: + errorMessage = '请求参数错误' + break + case 401: + errorMessage = '未授权,请重新登录' + localStorage.removeItem('token') + window.location.href = '/login' + break + case 403: + errorMessage = '拒绝访问' + break + case 404: + errorMessage = '请求资源不存在' + break + case 500: + errorMessage = '服务器内部错误' + break + default: + errorMessage = `请求失败(${status})` + } + } else if (error.code === 'ECONNABORTED') { + errorMessage = '请求超时,请稍后重试' + } + + message.error(errorMessage) + return Promise.reject(error) + } +) + +// 封装请求方法 +export const request = { + get(url: string, config?: AxiosRequestConfig): Promise>> { + return service.get(url, config) + }, + + post(url: string, data?: unknown, config?: AxiosRequestConfig): Promise>> { + return service.post(url, data, config) + }, + + put(url: string, data?: unknown, config?: AxiosRequestConfig): Promise>> { + return service.put(url, data, config) + }, + + delete(url: string, config?: AxiosRequestConfig): Promise>> { + return service.delete(url, config) + } +} + +export default service diff --git a/src/views/community/circles/index.vue b/src/views/community/circles/index.vue new file mode 100644 index 0000000..c2c96b6 --- /dev/null +++ b/src/views/community/circles/index.vue @@ -0,0 +1,1515 @@ + + + + + diff --git a/src/views/community/comments/index.vue b/src/views/community/comments/index.vue new file mode 100644 index 0000000..3607fc6 --- /dev/null +++ b/src/views/community/comments/index.vue @@ -0,0 +1,566 @@ + + + + + + diff --git a/src/views/community/posts/index.vue b/src/views/community/posts/index.vue new file mode 100644 index 0000000..c3fe261 --- /dev/null +++ b/src/views/community/posts/index.vue @@ -0,0 +1,835 @@ + + + + + + diff --git a/src/views/community/tags/index.vue b/src/views/community/tags/index.vue new file mode 100644 index 0000000..ca1d041 --- /dev/null +++ b/src/views/community/tags/index.vue @@ -0,0 +1,848 @@ + + + + + diff --git a/src/views/content/articles/index.vue b/src/views/content/articles/index.vue new file mode 100644 index 0000000..2cfd918 --- /dev/null +++ b/src/views/content/articles/index.vue @@ -0,0 +1,769 @@ + + + + + diff --git a/src/views/dashboard/index.vue b/src/views/dashboard/index.vue new file mode 100644 index 0000000..3a536e3 --- /dev/null +++ b/src/views/dashboard/index.vue @@ -0,0 +1,495 @@ + + + + + diff --git a/src/views/error/404.vue b/src/views/error/404.vue new file mode 100644 index 0000000..a66f244 --- /dev/null +++ b/src/views/error/404.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/src/views/login/index.vue b/src/views/login/index.vue new file mode 100644 index 0000000..cb7c290 --- /dev/null +++ b/src/views/login/index.vue @@ -0,0 +1,428 @@ + + + + + + diff --git a/src/views/project/contract/index.vue b/src/views/project/contract/index.vue new file mode 100644 index 0000000..c584437 --- /dev/null +++ b/src/views/project/contract/index.vue @@ -0,0 +1,578 @@ + + + + + diff --git a/src/views/project/list/index.vue b/src/views/project/list/index.vue new file mode 100644 index 0000000..28c9482 --- /dev/null +++ b/src/views/project/list/index.vue @@ -0,0 +1,575 @@ + + + + + + diff --git a/src/views/project/recruitment/index.vue b/src/views/project/recruitment/index.vue new file mode 100644 index 0000000..73c9815 --- /dev/null +++ b/src/views/project/recruitment/index.vue @@ -0,0 +1,570 @@ + + + + + + diff --git a/src/views/project/session/components/InviteModal.vue b/src/views/project/session/components/InviteModal.vue new file mode 100644 index 0000000..3217d65 --- /dev/null +++ b/src/views/project/session/components/InviteModal.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/src/views/project/session/detail.vue b/src/views/project/session/detail.vue new file mode 100644 index 0000000..7cc9493 --- /dev/null +++ b/src/views/project/session/detail.vue @@ -0,0 +1,217 @@ + + + + + diff --git a/src/views/project/session/index.vue b/src/views/project/session/index.vue new file mode 100644 index 0000000..1b496b4 --- /dev/null +++ b/src/views/project/session/index.vue @@ -0,0 +1,175 @@ + + + + + diff --git a/src/views/project/signed/index.vue b/src/views/project/signed/index.vue new file mode 100644 index 0000000..a16fd60 --- /dev/null +++ b/src/views/project/signed/index.vue @@ -0,0 +1,571 @@ + + + + + diff --git a/src/views/support/conversations/index.vue b/src/views/support/conversations/index.vue new file mode 100644 index 0000000..c99f5cc --- /dev/null +++ b/src/views/support/conversations/index.vue @@ -0,0 +1,832 @@ + + + + + diff --git a/src/views/support/session/index.vue b/src/views/support/session/index.vue new file mode 100644 index 0000000..f6eb59a --- /dev/null +++ b/src/views/support/session/index.vue @@ -0,0 +1,513 @@ + + + + + diff --git a/src/views/talent/components/OnlineResume.vue b/src/views/talent/components/OnlineResume.vue new file mode 100644 index 0000000..2deca87 --- /dev/null +++ b/src/views/talent/components/OnlineResume.vue @@ -0,0 +1,250 @@ + + + + + diff --git a/src/views/talent/detail.vue b/src/views/talent/detail.vue new file mode 100644 index 0000000..4b5e080 --- /dev/null +++ b/src/views/talent/detail.vue @@ -0,0 +1,663 @@ + + + + + diff --git a/src/views/talent/index.vue b/src/views/talent/index.vue new file mode 100644 index 0000000..587bfd0 --- /dev/null +++ b/src/views/talent/index.vue @@ -0,0 +1,483 @@ + + + + + diff --git a/src/views/talent/resume-templates.vue b/src/views/talent/resume-templates.vue new file mode 100644 index 0000000..4743c7a --- /dev/null +++ b/src/views/talent/resume-templates.vue @@ -0,0 +1,466 @@ + + + + + diff --git a/src/views/user/certification/index.vue b/src/views/user/certification/index.vue new file mode 100644 index 0000000..634eac6 --- /dev/null +++ b/src/views/user/certification/index.vue @@ -0,0 +1,670 @@ + + + + + diff --git a/src/views/user/levels/index.vue b/src/views/user/levels/index.vue new file mode 100644 index 0000000..8a65593 --- /dev/null +++ b/src/views/user/levels/index.vue @@ -0,0 +1,291 @@ + + + + + diff --git a/src/views/user/list/index.vue b/src/views/user/list/index.vue new file mode 100644 index 0000000..29ec24d --- /dev/null +++ b/src/views/user/list/index.vue @@ -0,0 +1,1235 @@ + + + + + diff --git a/src/views/user/positions/index.vue b/src/views/user/positions/index.vue new file mode 100644 index 0000000..cd8db50 --- /dev/null +++ b/src/views/user/positions/index.vue @@ -0,0 +1,266 @@ + + + + + diff --git a/src/views/user/roles/index.vue b/src/views/user/roles/index.vue new file mode 100644 index 0000000..cfc2d4c --- /dev/null +++ b/src/views/user/roles/index.vue @@ -0,0 +1,265 @@ + + + + + diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..adf7605 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,21 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "types": ["vite/client"], + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + }, + "skipLibCheck": true, + + /* Linting */ + "strict": false, + "noUnusedLocals": false, + "noUnusedParameters": false, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..7bb10b6 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers' +import Icons from 'unplugin-icons/vite' +import IconsResolver from 'unplugin-icons/resolver' +import path from 'path' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + // Vue函数自动导入 + AutoImport({ + imports: [ + 'vue', + 'vue-router', + 'pinia', + { + 'axios': [ + ['default', 'axios'] + ] + } + ], + dts: 'src/auto-imports.d.ts', + eslintrc: { + enabled: false + } + }), + // 组件自动导入 + Components({ + resolvers: [ + // Ant Design Vue组件自动导入 + AntDesignVueResolver({ + importStyle: false + }), + // Ant Design Vue图标自动导入 + IconsResolver({ + prefix: 'icon', + enabledCollections: ['ant-design'] + }) + ], + dts: 'src/components.d.ts' + }), + // 图标插件 + Icons({ + autoInstall: true, + compiler: 'vue3' + }) + ], + resolve: { + alias: { + '@': path.resolve(__dirname, 'src') + } + } +})