From 155f06846d18c3e1d0a69fcde754812c5748fd4b Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 30 Jul 2026 17:59:42 +0800 Subject: [PATCH] chore: initial commit --- .env.example | 2 + .gitignore | 19 + AGENTS.md | 58 + README.md | 40 + index.html | 14 + package.json | 31 + pnpm-lock.yaml | 1719 ++++++++++++++++++ src/App.vue | 4 + src/api/auth.ts | 38 + src/api/firstLeg.ts | 248 +++ src/api/http.ts | 107 ++ src/api/merchantIam.test.mjs | 40 + src/api/merchantIam.ts | 188 ++ src/api/partners.ts | 32 + src/api/session.ts | 38 + src/api/types.ts | 35 + src/auto-imports.d.ts | 10 + src/components.d.ts | 46 + src/components/ClientSidebarMenu.vue | 59 + src/components/ClientSidebarMenuNode.vue | 68 + src/data/firstLegParties.ts | 47 + src/data/firstLegPartySource.test.mjs | 23 + src/data/firstLegPartySource.ts | 51 + src/env.d.ts | 15 + src/layouts/AppLayout.test.mjs | 106 ++ src/layouts/AppLayout.vue | 176 ++ src/main.ts | 7 + src/navigation/clientNavigation.test.mjs | 326 ++++ src/navigation/clientNavigation.ts | 117 ++ src/router/index.ts | 52 + src/stores/auth.ts | 201 ++ src/stores/firstLegOrders.test.mjs | 50 + src/stores/firstLegOrders.ts | 222 +++ src/stores/tenantAdministration.ts | 422 +++++ src/styles/index.css | 180 ++ src/styles/navigationStyles.test.mjs | 49 + src/types/firstLegOrder.ts | 112 ++ src/utils/accountProfile.test.mjs | 37 + src/utils/accountProfile.ts | 57 + src/utils/firstLegOrder.test.mjs | 108 ++ src/utils/firstLegOrder.ts | 101 + src/utils/jwt.test.mjs | 31 + src/utils/jwt.ts | 37 + src/utils/qualificationFile.test.mjs | 21 + src/utils/qualificationFile.ts | 25 + src/views/AccountProfileView.test.mjs | 46 + src/views/AccountProfileView.vue | 350 ++++ src/views/AuthViews.test.mjs | 54 + src/views/CompanyProfileView.vue | 372 ++++ src/views/DashboardView.vue | 35 + src/views/FirstLegOrdersView.test.mjs | 83 + src/views/FirstLegOrdersView.vue | 605 ++++++ src/views/ForbiddenView.vue | 29 + src/views/LoginView.vue | 100 + src/views/MembersView.vue | 206 +++ src/views/ModuleView.vue | 16 + src/views/PermissionsView.vue | 153 ++ src/views/RegisterView.vue | 136 ++ src/views/RolesView.vue | 154 ++ src/views/TenantAdministrationViews.test.mjs | 82 + tsconfig.json | 24 + vite.config.ts | 38 + 62 files changed, 7852 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 README.md create mode 100644 index.html create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 src/App.vue create mode 100644 src/api/auth.ts create mode 100644 src/api/firstLeg.ts create mode 100644 src/api/http.ts create mode 100644 src/api/merchantIam.test.mjs create mode 100644 src/api/merchantIam.ts create mode 100644 src/api/partners.ts create mode 100644 src/api/session.ts create mode 100644 src/api/types.ts create mode 100644 src/auto-imports.d.ts create mode 100644 src/components.d.ts create mode 100644 src/components/ClientSidebarMenu.vue create mode 100644 src/components/ClientSidebarMenuNode.vue create mode 100644 src/data/firstLegParties.ts create mode 100644 src/data/firstLegPartySource.test.mjs create mode 100644 src/data/firstLegPartySource.ts create mode 100644 src/env.d.ts create mode 100644 src/layouts/AppLayout.test.mjs create mode 100644 src/layouts/AppLayout.vue create mode 100644 src/main.ts create mode 100644 src/navigation/clientNavigation.test.mjs create mode 100644 src/navigation/clientNavigation.ts create mode 100644 src/router/index.ts create mode 100644 src/stores/auth.ts create mode 100644 src/stores/firstLegOrders.test.mjs create mode 100644 src/stores/firstLegOrders.ts create mode 100644 src/stores/tenantAdministration.ts create mode 100644 src/styles/index.css create mode 100644 src/styles/navigationStyles.test.mjs create mode 100644 src/types/firstLegOrder.ts create mode 100644 src/utils/accountProfile.test.mjs create mode 100644 src/utils/accountProfile.ts create mode 100644 src/utils/firstLegOrder.test.mjs create mode 100644 src/utils/firstLegOrder.ts create mode 100644 src/utils/jwt.test.mjs create mode 100644 src/utils/jwt.ts create mode 100644 src/utils/qualificationFile.test.mjs create mode 100644 src/utils/qualificationFile.ts create mode 100644 src/views/AccountProfileView.test.mjs create mode 100644 src/views/AccountProfileView.vue create mode 100644 src/views/AuthViews.test.mjs create mode 100644 src/views/CompanyProfileView.vue create mode 100644 src/views/DashboardView.vue create mode 100644 src/views/FirstLegOrdersView.test.mjs create mode 100644 src/views/FirstLegOrdersView.vue create mode 100644 src/views/ForbiddenView.vue create mode 100644 src/views/LoginView.vue create mode 100644 src/views/MembersView.vue create mode 100644 src/views/ModuleView.vue create mode 100644 src/views/PermissionsView.vue create mode 100644 src/views/RegisterView.vue create mode 100644 src/views/RolesView.vue create mode 100644 src/views/TenantAdministrationViews.test.mjs create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..17b31d0 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +VITE_API_BASE_URL=/api +API_PROXY_TARGET=http://127.0.0.1:9000 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2bc4c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +node_modules/ +node_modules.broken-by-path/ +dist/ + +.env +.env.* +!.env.example + +.playwright-cli/ +.impeccable/ +.codex-temp/ +.codex-tmp/ +.logs/ +outputs/ + +*.log +.DS_Store +Thumbs.db + diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..a7f2244 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,58 @@ +# Client Web Instructions + +## 项目定位 + +- 本项目是入驻企业及其成员使用的客户工作台,所有业务数据都处于企业租户上下文中。 +- 当前范围包括注册、业务总览、企业资料、企业成员、企业角色与权限、头程订单。 +- 不在本项目实现平台内部运营管理、品牌官网、仓库 PDA 作业或仅属于原生移动端的能力。 + +## 技术基线 + +- 使用 Vue 3、TypeScript、Vite、Vue Router、Pinia、Element Plus、Axios 和 Lucide Vue。 +- TypeScript 保持严格模式;不要使用 `any` 掩盖接口或状态模型问题,优先从 `@chongchong/shared` 复用公共类型。 +- 使用现有 `@` 别名引用 `src/`,保持 ES Module 和现有无分号风格。 +- Element Plus 组件使用现有自动导入配置;`ref`、`computed` 等 Vue API 必须显式导入。不要手工向 `src/auto-imports.d.ts` 或 `src/components.d.ts` 写入业务声明。 +- 不擅自替换现有框架、路由、状态管理、组件库或构建工具。 + +## 目录与职责 + +- `src/views/`:路由级页面和业务流程入口;复用交互拆入组件。 +- `src/layouts/`:租户工作台壳层、导航和全局页面结构。 +- `src/router/`:公开页、受保护路由、页面标题和权限元数据;导航与路由必须同步。 +- `src/stores/`:认证、当前租户和跨页面业务状态;局部表单状态留在页面或组件内。 +- `src/api/`:统一处理基础地址、超时、认证头、响应解包和错误映射;页面不得直接创建独立 Axios 客户端。 +- `VITE_API_BASE_URL` 控制浏览器请求前缀,`API_PROXY_TARGET` 控制 Vite 开发代理上游;后者只由开发服务器读取,不进入客户端构建。新增或变更环境变量时同步更新 `.env.example`,不得提交真实密钥或环境专属凭据。 +- `src/styles/`:全局设计令牌和布局;优先复用现有 `--primary`、`--navy`、`--border`、`--muted`。 +- 跨应用稳定模型放到 `packages/shared`,客户工作台专属类型保留在本项目。 + +## 租户、认证与数据安全 + +- 任何查询和变更都必须以服务端确认的当前租户为边界;绝不从可篡改的前端参数推断授权租户。 +- 企业成员、角色和权限只能管理当前企业范围。前端隐藏不是安全控制,服务端必须重复校验租户与权限。 +- 认证统一经过 `src/stores/auth.ts` 和 `src/api/http.ts`;不要在页面中复制令牌逻辑或持久化密码、敏感资质内容。 +- 当前认证仍包含模拟数据。接入真实 API 后移除对应模拟分支,生产环境不得回退为模拟成功。 +- 注册、上传资质、成员邀请和订单操作必须提供明确校验、幂等保护、提交中状态和可恢复的失败反馈。 + +## UI 与交互 + +- 延续现有客户工作台的可信、简洁和服务型视觉语言,与内部管理后台保持品牌关联但不照搬高密度管理界面。 +- 优先使用 Element Plus 和 Lucide 图标;不使用表情符号代替功能图标,不引入第二套组件库。 +- 业务页按实际场景覆盖适用的加载、空数据、失败、无权限和数据过期状态;危险操作需要确认和明确结果。 +- 保持键盘可达、可见焦点、语义化标签、足够对比度和清晰错误提示。 +- 响应式变更至少检查 320px、720px 和桌面布局;避免只能横向滚动才能完成核心任务。 + +## 本地命令与质量门禁 + +在 `apps/client-web` 目录执行: + +```bash +pnpm install +pnpm dev +pnpm test +pnpm typecheck +pnpm build +``` + +- 日常变更至少运行 `pnpm typecheck`;交付前运行 `pnpm build`。 +- 当前测试使用 Node.js 内置测试运行器,执行 `pnpm test`;项目仍未声明 lint 脚本,不得声称 lint 已通过。 +- 不手工修改 `dist/` 或 `node_modules/`;依赖变更必须同步当前项目依赖清单和锁文件。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e840607 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# 虫虫出海客户工作台 + +入驻企业及其成员使用的业务应用,使用 Vue 3、TypeScript、Vite、Vue Router、Pinia 和 Element Plus。 + +## 本地运行 + +```bash +cd apps/client-web +pnpm install +pnpm dev +``` + +生产构建: + +```bash +pnpm typecheck +pnpm build +``` + +自动化测试: + +```bash +pnpm test +``` + +依赖和 `pnpm-lock.yaml` 均保存在当前前端项目中,不需要在仓库根目录安装 Node 依赖。 + +一期路由:登录、注册、工作台、我的信息、企业资料、企业成员、角色权限和头程订单。登录、注册、退出、令牌刷新、个人资料、修改密码、企业资料、企业资质、权限资源、角色列表/新增/授权和成员完整维护已经接入一期后端接口;开发代理默认转发到 `http://127.0.0.1:9000`,可通过 `API_PROXY_TARGET` 覆盖。登录使用后端约定的用户名和 8–72 位密码,注册成功后可立即登录,企业资料审核只影响订单提交资格。 + +认证会话保存 Access Token 和轮换后的 Refresh Token,并对并发 401 请求执行单次刷新。客户端只接受 `app_code` 为 `MERCHANT` 的 JWT;页面权限取 JWT 声明与后端启用权限资源的交集。前端权限仅用于导航和交互控制,服务端仍须按用户、租户和权限编码完成最终授权。 + +本地联调账号提示和默认用户名只在 Vite 开发模式下启用,URL 显式传入的登录用户名仍优先。生产登录表单默认留空,生产构建产物不得包含本地联调账号或开发提示文案。 + +“我的信息”使用真实个人资料和修改密码接口,姓名、电话、邮箱按服务端约束维护;修改密码成功后清理旧会话并返回登录页。成员列表返回姓名、联系方式和 `roleIds`,新增、编辑、角色全量保存、启停和管理员重置密码均使用真实接口,不回退静态成功。角色列表返回 `permissionIds`,页面可回显并全量保存角色权限;角色编辑、启停、删除虽在回归报告中通过,但路径尚未进入当前 Gateway OpenAPI,前端继续保持禁用。 + +企业资料和资质列表使用真实租户接口,覆盖 `PROFILE_REQUIRED`、`PENDING`、`ACTIVE`、`REJECTED`、`DISABLED` 五种企业状态。企业名称、法定名称和联系人信息支持真实保存;营业执照上传使用文件服务,仅接受 JPG、PNG 且不超过 5MB,文件预览通过当前商户 Token 临时下载并在关闭弹窗后释放。 + +头程订单列表、详情、仓库查询、创建/编辑草稿和提交已经接入真实 API,并显式映射后端 `transportMode`、`containerMode` 和订单状态。创建与提交请求携带幂等键,`merchant:order:create`、`merchant:order:edit`、`merchant:order:submit` 分别映射到独立前端操作权限。页面补齐渠道、提货方式、报关模式、目的仓类型、包装类型等后端必填字段;API 模式先清空本地记录,请求失败不会回退模拟成功。收发货单位通过 `/merchant/v1/partners/candidates` 按类型读取启用候选并保存订单快照,不再使用演示单位兜底;快递信息仍由平台维护、客户只读。订单取消不属于本轮本地一期验收,客户端不暴露取消写操作。订单产品状态仍按已确认的七状态执行,不设置“已驳回”;若兼容旧接口数据收到 `REJECTED`,只按待审核展示且不开放编辑。 + +联调远端 Gateway 时可在启动命令前设置 `API_PROXY_TARGET=http://192.168.110.149:9000`;进程环境变量优先于 `.env` 文件。浏览器仍统一请求同源 `/api`,源码中不写死业务服务地址。 diff --git a/index.html b/index.html new file mode 100644 index 0000000..be4f977 --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + + 虫虫出海客户工作台 + + +
+ + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..f5e7a83 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "@chongchong/client-web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0 --port 5174", + "build": "vue-tsc --noEmit && vite build", + "typecheck": "vue-tsc --noEmit", + "test": "node --test src/**/*.test.mjs", + "preview": "vite preview --port 4174" + }, + "dependencies": { + "@chongchong/shared": "link:../../packages/shared", + "axios": "^1.18.1", + "element-plus": "^2.14.3", + "@lucide/vue": "^1.27.0", + "pinia": "^4.0.2", + "vue": "^3.5.40", + "vue-router": "^5.2.0" + }, + "devDependencies": { + "@types/node": "^24.10.0", + "@vitejs/plugin-vue": "^6.0.8", + "typescript": "^5.9.3", + "unplugin-auto-import": "^21.0.0", + "unplugin-vue-components": "^32.1.0", + "vite": "^8.1.5", + "vue-tsc": "^3.3.8" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..115db38 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1719 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@chongchong/shared': + specifier: link:../../packages/shared + version: link:../../packages/shared + '@lucide/vue': + specifier: ^1.27.0 + version: 1.27.0(vue@3.5.40(typescript@5.9.3)) + axios: + specifier: ^1.18.1 + version: 1.18.1(debug@4.4.3) + element-plus: + specifier: ^2.14.3 + version: 2.14.3(vue@3.5.40(typescript@5.9.3)) + pinia: + specifier: ^4.0.2 + version: 4.0.2(@vue/devtools-api@8.2.1)(typescript@5.9.3)(vue@3.5.40(typescript@5.9.3)) + vue: + specifier: ^3.5.40 + version: 3.5.40(typescript@5.9.3) + vue-router: + specifier: ^5.2.0 + version: 5.2.0(@vue/compiler-sfc@3.5.40)(pinia@4.0.2(@vue/devtools-api@8.2.1)(typescript@5.9.3)(vue@3.5.40(typescript@5.9.3)))(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(yaml@2.9.0))(vue@3.5.40(typescript@5.9.3)) + devDependencies: + '@types/node': + specifier: ^24.10.0 + version: 24.13.3 + '@vitejs/plugin-vue': + specifier: ^6.0.8 + version: 6.0.8(vite@8.1.5(@types/node@24.13.3)(yaml@2.9.0))(vue@3.5.40(typescript@5.9.3)) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + unplugin-auto-import: + specifier: ^21.0.0 + version: 21.0.0(@vueuse/core@14.3.0(vue@3.5.40(typescript@5.9.3))) + unplugin-vue-components: + specifier: ^32.1.0 + version: 32.1.0(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(yaml@2.9.0))(vue@3.5.40(typescript@5.9.3)) + vite: + specifier: ^8.1.5 + version: 8.1.5(@types/node@24.13.3)(yaml@2.9.0) + vue-tsc: + specifier: ^3.3.8 + version: 3.3.8(typescript@5.9.3) + +packages: + + '@babel/generator@8.0.0': + resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@8.0.0': + resolution: {integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@8.0.4': + resolution: {integrity: sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/parser@8.0.4': + resolution: {integrity: sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==} + engines: {node: ^22.18.0 || >=24.11.0} + hasBin: true + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@babel/types@8.0.4': + resolution: {integrity: sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@ctrl/tinycolor@4.2.0': + resolution: {integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==} + engines: {node: '>=14'} + + '@element-plus/icons-vue@2.3.2': + resolution: {integrity: sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==} + peerDependencies: + vue: ^3.2.0 + + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} + + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} + + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@lucide/vue@1.27.0': + resolution: {integrity: sha512-LKDEZJ0Uo9UaT4vxBMYVwXeK/0dGcWCTdav1AK/aGD/jYsDPtiVvOgfM7jPlx9BVeyGZ1Uf0aQhHA++rmbe7rw==} + peerDependencies: + vue: '>=3.0.1' + + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@oxc-project/types@0.139.0': + resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} + + '@rolldown/binding-android-arm64@1.1.5': + resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.1.5': + resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.1.5': + resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.1.5': + resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.1.5': + resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.1.5': + resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.1.5': + resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.1.5': + resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.1.5': + resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.1.5': + resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.1.5': + resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.1.5': + resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.1.5': + resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@sxzz/popperjs-es@2.11.8': + resolution: {integrity: sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==} + + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/jsesc@2.5.1': + resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.24': + resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + + '@types/node@24.13.3': + resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} + + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + + '@vitejs/plugin-vue@6.0.8': + resolution: {integrity: sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + + '@vue-macros/common@3.1.4': + resolution: {integrity: sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw==} + engines: {node: '>=20.19.0'} + peerDependencies: + vue: ^2.7.0 || ^3.2.25 + peerDependenciesMeta: + vue: + optional: true + + '@vue/compiler-core@3.5.40': + resolution: {integrity: sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==} + + '@vue/compiler-dom@3.5.40': + resolution: {integrity: sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==} + + '@vue/compiler-sfc@3.5.40': + resolution: {integrity: sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==} + + '@vue/compiler-ssr@3.5.40': + resolution: {integrity: sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==} + + '@vue/devtools-api@8.2.1': + resolution: {integrity: sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A==} + + '@vue/devtools-kit@8.2.1': + resolution: {integrity: sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==} + + '@vue/devtools-shared@8.2.1': + resolution: {integrity: sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==} + + '@vue/language-core@3.3.8': + resolution: {integrity: sha512-ieGT8jJdhhy0mGzStZhsg/qPw5bQZJg5yF+3+XU6saf4sM7yo9ZXy3h+nCwrm2+b4qS/SypkNdR2jAF3uei9tA==} + + '@vue/reactivity@3.5.40': + resolution: {integrity: sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==} + + '@vue/runtime-core@3.5.40': + resolution: {integrity: sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==} + + '@vue/runtime-dom@3.5.40': + resolution: {integrity: sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==} + + '@vue/server-renderer@3.5.40': + resolution: {integrity: sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==} + + '@vue/shared@3.5.40': + resolution: {integrity: sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==} + + '@vueuse/core@14.3.0': + resolution: {integrity: sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==} + peerDependencies: + vue: ^3.5.0 + + '@vueuse/metadata@14.3.0': + resolution: {integrity: sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==} + + '@vueuse/shared@14.3.0': + resolution: {integrity: sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==} + peerDependencies: + vue: ^3.5.0 + + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + alien-signals@3.2.1: + resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==} + + ast-kit@2.2.0: + resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} + engines: {node: '>=20.19.0'} + + ast-walker-scope@0.9.0: + resolution: {integrity: sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==} + engines: {node: '>=20.19.0'} + + async-validator@4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.18.1: + resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==} + + birpc@2.9.0: + resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + element-plus@2.14.3: + resolution: {integrity: sha512-pJcvxcpZjYruNzuJhAeVwnbYjfNgzBKnWHwSVEhwzM2/kcLI3brzmtIBxtPqd4hQWJfD1PRnjoc1WipLw2eBGg==} + peerDependencies: + vue: ^3.3.7 + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + exsolve@1.1.0: + resolution: {integrity: sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + local-pkg@1.2.1: + resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==} + engines: {node: '>=14'} + + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + lodash-unified@1.0.3: + resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==} + peerDependencies: + '@types/lodash-es': '*' + lodash: '*' + lodash-es: '*' + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + magic-string-ast@1.0.3: + resolution: {integrity: sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==} + engines: {node: '>=20.19.0'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.16: + resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + normalize-wheel-es@1.2.0: + resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==} + + nostics@1.2.0: + resolution: {integrity: sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg==} + + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@2.1.0: + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + pinia@4.0.2: + resolution: {integrity: sha512-yKVVA7bSj5oRZFp/Ab9wLlmyb5gPUYEiIm4ryiWTe/xe7PtkRdMVOp1X1ggvq0c6Uj7Q0Du1HnV2mtAwM0Ks1g==} + peerDependencies: + '@vue/devtools-api': ^8.1.5 + typescript: '>=5.6.0' + vue: ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.1: + resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} + + postcss@8.5.23: + resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==} + engines: {node: ^10 || ^12 || >=14} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + + rolldown@1.1.5: + resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + unimport@5.7.0: + resolution: {integrity: sha512-njnL6sp8lEA8QQbZrt+52p/g4X0rw3bnGGmUcJnt1jeG8+iiqO779aGz0PirCtydAIVcuTBRlJ52F0u46z309Q==} + engines: {node: '>=18.12.0'} + + unplugin-auto-import@21.0.0: + resolution: {integrity: sha512-vWuC8SwqJmxZFYwPojhOhOXDb5xFhNNcEVb9K/RFkyk/3VnfaOjzitWN7v+8DEKpMjSsY2AEGXNgt6I0yQrhRQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@nuxt/kit': ^4.0.0 + '@vueuse/core': '*' + peerDependenciesMeta: + '@nuxt/kit': + optional: true + '@vueuse/core': + optional: true + + unplugin-utils@0.3.2: + resolution: {integrity: sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==} + engines: {node: '>=20.19.0'} + + unplugin-vue-components@32.1.0: + resolution: {integrity: sha512-YiUkSxuRjab18XFOrX5VsIxXzccrfmHVGsGeJgSgklb829DQmCy9E4vvDUE4tuvZZdxyFJZX0Oc4TPnnxiiMyg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@nuxt/kit': ^3.2.2 || ^4.0.0 + vue: ^3.0.0 + peerDependenciesMeta: + '@nuxt/kit': + optional: true + + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + + unplugin@3.3.0: + resolution: {integrity: sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@farmfe/core': '*' + '@rspack/core': '*' + bun-types-no-globals: '*' + esbuild: '*' + rolldown: '*' + rollup: '*' + unloader: '*' + vite: '*' + webpack: '*' + peerDependenciesMeta: + '@farmfe/core': + optional: true + '@rspack/core': + optional: true + bun-types-no-globals: + optional: true + esbuild: + optional: true + rolldown: + optional: true + rollup: + optional: true + unloader: + optional: true + vite: + optional: true + webpack: + optional: true + + vite@8.1.5: + resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.3.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-component-type-helpers@3.3.8: + resolution: {integrity: sha512-troqCMmQodQDqUqn63NQaFi+CDSclSe7sc8VEBFqf5GFLqmGR2Ph3P2WEC7qwpRVyEWsTi/aAr4vyOe/B1hU3g==} + + vue-router@5.2.0: + resolution: {integrity: sha512-QAC5i0LEb1GLG0LXDQmHu8L7FX12j0KwU/JTKmLQUJMrn04gQdKP6Du+p0QwpHb3iy71vBlqnHQ8WAfOSAWhqw==} + peerDependencies: + '@pinia/colada': '>=0.21.2' + '@vue/compiler-sfc': ^3.5.34 || ^4.0.0 + pinia: ^3.0.4 || ^4.0.2 + vite: ^7.3.0 || ^8.0.0 + vue: ^3.5.34 || ^4.0.0 + peerDependenciesMeta: + '@pinia/colada': + optional: true + '@vue/compiler-sfc': + optional: true + pinia: + optional: true + vite: + optional: true + + vue-tsc@3.3.8: + resolution: {integrity: sha512-xXmYlVQpcwJDWyGlqbHrGVOl1h3UOsASymRibrHc+iy9j/UNnOrOn4u+fntHz4D6Cs74RtapeqVV6CzJeg+UlA==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.40: + resolution: {integrity: sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + +snapshots: + + '@babel/generator@8.0.0': + dependencies: + '@babel/parser': 8.0.4 + '@babel/types': 8.0.4 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 + jsesc: 3.1.0 + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-string-parser@8.0.0': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-identifier@8.0.4': {} + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/parser@8.0.4': + dependencies: + '@babel/types': 8.0.4 + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@babel/types@8.0.4': + dependencies: + '@babel/helper-string-parser': 8.0.0 + '@babel/helper-validator-identifier': 8.0.4 + + '@ctrl/tinycolor@4.2.0': {} + + '@element-plus/icons-vue@2.3.2(vue@3.5.40(typescript@5.9.3))': + dependencies: + vue: 3.5.40(typescript@5.9.3) + + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@floating-ui/core@1.8.0': + dependencies: + '@floating-ui/utils': 0.2.12 + + '@floating-ui/dom@1.8.0': + dependencies: + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 + + '@floating-ui/utils@0.2.12': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@lucide/vue@1.27.0(vue@3.5.40(typescript@5.9.3))': + dependencies: + vue: 3.5.40(typescript@5.9.3) + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@oxc-project/types@0.139.0': {} + + '@rolldown/binding-android-arm64@1.1.5': + optional: true + + '@rolldown/binding-darwin-arm64@1.1.5': + optional: true + + '@rolldown/binding-darwin-x64@1.1.5': + optional: true + + '@rolldown/binding-freebsd-x64@1.1.5': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.1.5': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-x64-musl@1.1.5': + optional: true + + '@rolldown/binding-openharmony-arm64@1.1.5': + optional: true + + '@rolldown/binding-wasm32-wasi@1.1.5': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.1.5': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.1.5': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@sxzz/popperjs-es@2.11.8': {} + + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/estree@1.0.9': {} + + '@types/jsesc@2.5.1': {} + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.24 + + '@types/lodash@4.17.24': {} + + '@types/node@24.13.3': + dependencies: + undici-types: 7.18.2 + + '@types/web-bluetooth@0.0.21': {} + + '@vitejs/plugin-vue@6.0.8(vite@8.1.5(@types/node@24.13.3)(yaml@2.9.0))(vue@3.5.40(typescript@5.9.3))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.1.5(@types/node@24.13.3)(yaml@2.9.0) + vue: 3.5.40(typescript@5.9.3) + + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue-macros/common@3.1.4(vue@3.5.40(typescript@5.9.3))': + dependencies: + '@vue/compiler-sfc': 3.5.40 + ast-kit: 2.2.0 + local-pkg: 1.2.1 + magic-string-ast: 1.0.3 + unplugin-utils: 0.3.2 + optionalDependencies: + vue: 3.5.40(typescript@5.9.3) + + '@vue/compiler-core@3.5.40': + dependencies: + '@babel/parser': 7.29.7 + '@vue/shared': 3.5.40 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.40': + dependencies: + '@vue/compiler-core': 3.5.40 + '@vue/shared': 3.5.40 + + '@vue/compiler-sfc@3.5.40': + dependencies: + '@babel/parser': 7.29.7 + '@vue/compiler-core': 3.5.40 + '@vue/compiler-dom': 3.5.40 + '@vue/compiler-ssr': 3.5.40 + '@vue/shared': 3.5.40 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.23 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.40': + dependencies: + '@vue/compiler-dom': 3.5.40 + '@vue/shared': 3.5.40 + + '@vue/devtools-api@8.2.1': + dependencies: + '@vue/devtools-kit': 8.2.1 + + '@vue/devtools-kit@8.2.1': + dependencies: + '@vue/devtools-shared': 8.2.1 + birpc: 2.9.0 + hookable: 5.5.3 + perfect-debounce: 2.1.0 + + '@vue/devtools-shared@8.2.1': {} + + '@vue/language-core@3.3.8': + dependencies: + '@volar/language-core': 2.4.28 + '@vue/compiler-dom': 3.5.40 + '@vue/shared': 3.5.40 + alien-signals: 3.2.1 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.5 + + '@vue/reactivity@3.5.40': + dependencies: + '@vue/shared': 3.5.40 + + '@vue/runtime-core@3.5.40': + dependencies: + '@vue/reactivity': 3.5.40 + '@vue/shared': 3.5.40 + + '@vue/runtime-dom@3.5.40': + dependencies: + '@vue/reactivity': 3.5.40 + '@vue/runtime-core': 3.5.40 + '@vue/shared': 3.5.40 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.40': + dependencies: + '@vue/compiler-ssr': 3.5.40 + '@vue/runtime-dom': 3.5.40 + '@vue/shared': 3.5.40 + + '@vue/shared@3.5.40': {} + + '@vueuse/core@14.3.0(vue@3.5.40(typescript@5.9.3))': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 14.3.0 + '@vueuse/shared': 14.3.0(vue@3.5.40(typescript@5.9.3)) + vue: 3.5.40(typescript@5.9.3) + + '@vueuse/metadata@14.3.0': {} + + '@vueuse/shared@14.3.0(vue@3.5.40(typescript@5.9.3))': + dependencies: + vue: 3.5.40(typescript@5.9.3) + + acorn@8.17.0: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + alien-signals@3.2.1: {} + + ast-kit@2.2.0: + dependencies: + '@babel/parser': 7.29.7 + pathe: 2.0.3 + + ast-walker-scope@0.9.0: + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + ast-kit: 2.2.0 + + async-validator@4.2.5: {} + + asynckit@0.4.0: {} + + axios@1.18.1(debug@4.4.3): + dependencies: + follow-redirects: 1.16.0(debug@4.4.3) + form-data: 4.0.6 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + + birpc@2.9.0: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + confbox@0.1.8: {} + + confbox@0.2.4: {} + + csstype@3.2.3: {} + + dayjs@1.11.21: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + delayed-stream@1.0.0: {} + + detect-libc@2.1.2: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + element-plus@2.14.3(vue@3.5.40(typescript@5.9.3)): + dependencies: + '@ctrl/tinycolor': 4.2.0 + '@element-plus/icons-vue': 2.3.2(vue@3.5.40(typescript@5.9.3)) + '@floating-ui/dom': 1.8.0 + '@popperjs/core': '@sxzz/popperjs-es@2.11.8' + '@types/lodash': 4.17.24 + '@types/lodash-es': 4.17.12 + '@vueuse/core': 14.3.0(vue@3.5.40(typescript@5.9.3)) + async-validator: 4.2.5 + dayjs: 1.11.21 + lodash: 4.18.1 + lodash-es: 4.18.1 + lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.18.1)(lodash@4.18.1) + memoize-one: 6.0.0 + normalize-wheel-es: 1.2.0 + vue: 3.5.40(typescript@5.9.3) + vue-component-type-helpers: 3.3.8 + + entities@7.0.1: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + escape-string-regexp@5.0.0: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + exsolve@1.1.0: {} + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + + follow-redirects@1.16.0(debug@4.4.3): + optionalDependencies: + debug: 4.4.3 + + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hookable@5.5.3: {} + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + js-tokens@9.0.1: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + local-pkg@1.2.1: + dependencies: + mlly: 1.8.2 + pkg-types: 2.3.1 + quansync: 0.2.11 + + lodash-es@4.18.1: {} + + lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.18.1)(lodash@4.18.1): + dependencies: + '@types/lodash-es': 4.17.12 + lodash: 4.18.1 + lodash-es: 4.18.1 + + lodash@4.18.1: {} + + magic-string-ast@1.0.3: + dependencies: + magic-string: 0.30.21 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + memoize-one@6.0.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mlly@1.8.2: + dependencies: + acorn: 8.17.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.4 + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.16: {} + + normalize-wheel-es@1.2.0: {} + + nostics@1.2.0: {} + + obug@2.1.4: {} + + path-browserify@1.0.1: {} + + pathe@2.0.3: {} + + perfect-debounce@2.1.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.5: {} + + pinia@4.0.2(@vue/devtools-api@8.2.1)(typescript@5.9.3)(vue@3.5.40(typescript@5.9.3)): + dependencies: + '@vue/devtools-api': 8.2.1 + nostics: 1.2.0 + vue: 3.5.40(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + + pkg-types@2.3.1: + dependencies: + confbox: 0.2.4 + exsolve: 1.1.0 + pathe: 2.0.3 + + postcss@8.5.23: + dependencies: + nanoid: 3.3.16 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + proxy-from-env@2.1.0: {} + + quansync@0.2.11: {} + + readdirp@5.0.0: {} + + rolldown@1.1.5: + dependencies: + '@oxc-project/types': 0.139.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.1.5 + '@rolldown/binding-darwin-arm64': 1.1.5 + '@rolldown/binding-darwin-x64': 1.1.5 + '@rolldown/binding-freebsd-x64': 1.1.5 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 + '@rolldown/binding-linux-arm64-gnu': 1.1.5 + '@rolldown/binding-linux-arm64-musl': 1.1.5 + '@rolldown/binding-linux-ppc64-gnu': 1.1.5 + '@rolldown/binding-linux-s390x-gnu': 1.1.5 + '@rolldown/binding-linux-x64-gnu': 1.1.5 + '@rolldown/binding-linux-x64-musl': 1.1.5 + '@rolldown/binding-openharmony-arm64': 1.1.5 + '@rolldown/binding-wasm32-wasi': 1.1.5 + '@rolldown/binding-win32-arm64-msvc': 1.1.5 + '@rolldown/binding-win32-x64-msvc': 1.1.5 + + scule@1.3.0: {} + + source-map-js@1.2.1: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + tslib@2.8.1: + optional: true + + typescript@5.9.3: {} + + ufo@1.6.4: {} + + undici-types@7.18.2: {} + + unimport@5.7.0: + dependencies: + acorn: 8.17.0 + escape-string-regexp: 5.0.0 + estree-walker: 3.0.3 + local-pkg: 1.2.1 + magic-string: 0.30.21 + mlly: 1.8.2 + pathe: 2.0.3 + picomatch: 4.0.5 + pkg-types: 2.3.1 + scule: 1.3.0 + strip-literal: 3.1.0 + tinyglobby: 0.2.17 + unplugin: 2.3.11 + unplugin-utils: 0.3.2 + + unplugin-auto-import@21.0.0(@vueuse/core@14.3.0(vue@3.5.40(typescript@5.9.3))): + dependencies: + local-pkg: 1.2.1 + magic-string: 0.30.21 + picomatch: 4.0.5 + unimport: 5.7.0 + unplugin: 2.3.11 + unplugin-utils: 0.3.2 + optionalDependencies: + '@vueuse/core': 14.3.0(vue@3.5.40(typescript@5.9.3)) + + unplugin-utils@0.3.2: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.5 + + unplugin-vue-components@32.1.0(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(yaml@2.9.0))(vue@3.5.40(typescript@5.9.3)): + dependencies: + chokidar: 5.0.0 + local-pkg: 1.2.1 + magic-string: 0.30.21 + mlly: 1.8.2 + obug: 2.1.4 + picomatch: 4.0.5 + tinyglobby: 0.2.17 + unplugin: 3.3.0(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(yaml@2.9.0)) + unplugin-utils: 0.3.2 + vue: 3.5.40(typescript@5.9.3) + transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' + - bun-types-no-globals + - esbuild + - rolldown + - rollup + - unloader + - vite + - webpack + + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.17.0 + picomatch: 4.0.5 + webpack-virtual-modules: 0.6.2 + + unplugin@3.3.0(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(yaml@2.9.0)): + dependencies: + '@jridgewell/remapping': 2.3.5 + picomatch: 4.0.5 + webpack-virtual-modules: 0.6.2 + optionalDependencies: + rolldown: 1.1.5 + vite: 8.1.5(@types/node@24.13.3)(yaml@2.9.0) + + vite@8.1.5(@types/node@24.13.3)(yaml@2.9.0): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.5 + postcss: 8.5.23 + rolldown: 1.1.5 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.13.3 + fsevents: 2.3.3 + yaml: 2.9.0 + + vscode-uri@3.1.0: {} + + vue-component-type-helpers@3.3.8: {} + + vue-router@5.2.0(@vue/compiler-sfc@3.5.40)(pinia@4.0.2(@vue/devtools-api@8.2.1)(typescript@5.9.3)(vue@3.5.40(typescript@5.9.3)))(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(yaml@2.9.0))(vue@3.5.40(typescript@5.9.3)): + dependencies: + '@babel/generator': 8.0.0 + '@vue-macros/common': 3.1.4(vue@3.5.40(typescript@5.9.3)) + '@vue/devtools-api': 8.2.1 + ast-walker-scope: 0.9.0 + chokidar: 5.0.0 + json5: 2.2.3 + local-pkg: 1.2.1 + magic-string: 0.30.21 + mlly: 1.8.2 + muggle-string: 0.4.1 + nostics: 1.2.0 + pathe: 2.0.3 + picomatch: 4.0.5 + scule: 1.3.0 + tinyglobby: 0.2.17 + unplugin: 3.3.0(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(yaml@2.9.0)) + unplugin-utils: 0.3.2 + vue: 3.5.40(typescript@5.9.3) + yaml: 2.9.0 + optionalDependencies: + '@vue/compiler-sfc': 3.5.40 + pinia: 4.0.2(@vue/devtools-api@8.2.1)(typescript@5.9.3)(vue@3.5.40(typescript@5.9.3)) + vite: 8.1.5(@types/node@24.13.3)(yaml@2.9.0) + transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' + - bun-types-no-globals + - esbuild + - rolldown + - rollup + - unloader + - webpack + + vue-tsc@3.3.8(typescript@5.9.3): + dependencies: + '@volar/typescript': 2.4.28 + '@vue/language-core': 3.3.8 + typescript: 5.9.3 + + vue@3.5.40(typescript@5.9.3): + dependencies: + '@vue/compiler-dom': 3.5.40 + '@vue/compiler-sfc': 3.5.40 + '@vue/runtime-dom': 3.5.40 + '@vue/server-renderer': 3.5.40 + '@vue/shared': 3.5.40 + optionalDependencies: + typescript: 5.9.3 + + webpack-virtual-modules@0.6.2: {} + + yaml@2.9.0: {} diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..4be8965 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,4 @@ + + diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 0000000..b310ef0 --- /dev/null +++ b/src/api/auth.ts @@ -0,0 +1,38 @@ +import { apiRequest } from '@/api/http' +import type { TokenPair } from '@/api/session' + +export interface RegisterMerchantRequest { + username: string + password: string + displayName: string + companyName: string + legalPerson: string + legalPhone: string + contactEmail?: string + countryCode?: string + address?: string +} + +export interface RegisterMerchantResult { + accountId: number | string + tenantId: number | string + membershipId: number | string + tenantStatus: 'PROFILE_REQUIRED' + nextStep: string +} + +export function loginMerchant(username: string, password: string) { + return apiRequest({ method: 'POST', url: '/auth/login', data: { username, password } }) +} + +export function registerMerchant(payload: RegisterMerchantRequest) { + return apiRequest({ method: 'POST', url: '/auth/register', data: payload }) +} + +export function logoutMerchant(refreshToken: string) { + return apiRequest({ method: 'POST', url: '/auth/logout', data: { refreshToken } }) +} + +export function changeCurrentPassword(oldPassword: string, newPassword: string) { + return apiRequest({ method: 'PUT', url: '/auth/password', data: { oldPassword, newPassword } }) +} diff --git a/src/api/firstLeg.ts b/src/api/firstLeg.ts new file mode 100644 index 0000000..4f08cc0 --- /dev/null +++ b/src/api/firstLeg.ts @@ -0,0 +1,248 @@ +import { apiRequest } from '@/api/http' + +export type ApiFirstLegStatus = 'DRAFT' | 'SUBMITTED' | 'APPROVED' | 'ACCEPTED' | 'REJECTED' | 'CANCELLED' | 'PROCESSING' | 'COMPLETED' +export type ApiTransportMode = 'SEA' | 'AIR' | 'RAIL' | 'TRUCK' +export type ApiPickupMethod = 'SELF_EXPRESS' | 'PICKUP' | 'SELF_TRUCK' +export type ApiTaxMode = 'BUY_DECLARATION' | 'TAX_REFUND' +export type ApiContainerMode = 'FULL_CONTAINER' | 'LCL' +export type ApiDestinationType = 'FBA' | 'OVERSEAS' | 'PRIVATE' + +export interface PageResult { + records: T[] + page: number + size: number + total: number + pages: number +} + +export interface MerchantWarehouse { + id: number | string + warehouseCode: string + warehouseName: string + warehouseType: string + countryCode: string + timezone: string | null + contactName: string | null + contactPhone: string | null + contactEmail: string | null + status: 'ACTIVE' | 'INACTIVE' + remark: string | null + updatedAt: string | null +} + +export interface MerchantWarehouseAddress { + countryCode: string + stateProvince: string | null + city: string | null + district: string | null + postalCode: string | null + addressLine1: string + addressLine2: string | null +} + +export interface MerchantWarehouseCapability { + capabilityCode: string + capabilityName: string + enabled: boolean + cutoffTime: string | null + description: string | null +} + +export interface MerchantWarehouseDetail { + warehouse: MerchantWarehouse + addresses: MerchantWarehouseAddress[] + capabilities: MerchantWarehouseCapability[] +} + +export interface MerchantPartner { + id: number | string + partnerCode: string + partnerName: string + partnerType: string + contactName: string | null + contactPhone: string | null + contactEmail: string | null + countryCode: string + province: string | null + city: string | null + district: string | null + addressLine: string + postalCode: string | null + status: 'ACTIVE' | 'INACTIVE' +} + +export interface FirstLegOrderPartyPayload { + companyName: string + contactName: string + phone: string + email?: string + countryCode: string + province?: string + city: string + district?: string + addressLine: string + postalCode?: string +} + +export interface FirstLegOrderCargoPayload { + cargoName: string + hsCode?: string + packageType: string + packageCount: number + grossWeightKg: number + volumeCbm: number + declaredValue?: number + currency?: string + dangerousGoods?: boolean + cargoRemark?: string + skuNo?: string + cargoNameEn?: string + totalQuantity?: number + netWeightKg?: number +} + +export interface FirstLegOrderSaveRequest { + customerId?: number | string + customerName: string + transportMode: ApiTransportMode + tradeTerm?: string + destinationCountryCode: string + destinationWarehouseId?: number | string + destinationWarehouseName: string + expectedShipDate?: string + customerRemark?: string + sender: FirstLegOrderPartyPayload + receiver: FirstLegOrderPartyPayload + cargoes: FirstLegOrderCargoPayload[] + pickupMethod: ApiPickupMethod + receivingWarehouseId: number | string + receivingWarehouseName: string + receivingWarehouseAddress?: string | null + receivingTime?: string | null + channelCode: string + channelName: string + taxMode: ApiTaxMode + containerMode?: ApiContainerMode + customsInspection: boolean + destinationType: ApiDestinationType + amazonWarehouseCode?: string + shipmentId?: string + referenceId?: string +} + +export interface MerchantFirstLegOrder { + id: number | string + orderNo: string | null + customerId: number | string | null + customerName: string + status: ApiFirstLegStatus + transportMode: ApiTransportMode + tradeTerm: string | null + destinationCountryCode: string + destinationWarehouseId: number | string | null + destinationWarehouseName: string + expectedShipDate: string | null + customerRemark: string | null + pickupMethod: ApiPickupMethod + receivingWarehouseId: number | string + receivingWarehouseName: string + channelCode: string + channelName: string + taxMode: ApiTaxMode + containerMode: ApiContainerMode | null + customsInspection: boolean + destinationType: ApiDestinationType + amazonWarehouseCode: string | null + shipmentId: string | null + referenceId: string | null + expressCompany?: string | null + expressTrackingNo?: string | null + version: number + createdAt: string + updatedAt: string +} + +export interface MerchantFirstLegOrderCargo extends FirstLegOrderCargoPayload { + id: number | string + itemNo: number +} + +export interface MerchantFirstLegOrderParty extends FirstLegOrderPartyPayload { + id: number | string + partyType: 'SENDER' | 'RECEIVER' | string +} + +export interface MerchantOrderStatusHistory { + id: number | string + fromStatus: ApiFirstLegStatus | null + toStatus: ApiFirstLegStatus + action: string + reason: string | null + operatorName: string | null + occurredAt: string +} + +export interface MerchantFirstLegOrderDetail { + order: MerchantFirstLegOrder + cargoes: MerchantFirstLegOrderCargo[] + parties: MerchantFirstLegOrderParty[] + internalNotes: [] + statusHistory: MerchantOrderStatusHistory[] +} + +export interface FirstLegOrderQuery { + keyword?: string + status?: ApiFirstLegStatus + customerId?: number | string + destinationWarehouseId?: number | string + createdFrom?: string + createdTo?: string + page?: number + size?: number +} + +export function getMerchantWarehouses() { + return apiRequest>({ method: 'GET', url: '/merchant/v1/warehouses', params: { page: 1, size: 200, status: 'ACTIVE' } }) +} + +export function getMerchantWarehouseDetail(id: number | string) { + return apiRequest({ method: 'GET', url: `/merchant/v1/warehouses/${id}` }) +} + +export function getMerchantPartnerCandidates(type: 'SHIPPER' | 'CONSIGNEE') { + return apiRequest({ method: 'GET', url: '/merchant/v1/partners/candidates', params: { type } }) +} + +export function getMerchantFirstLegOrders(params: FirstLegOrderQuery = {}) { + return apiRequest>({ method: 'GET', url: '/merchant/v1/orders/first-leg', params: { page: 1, size: 200, ...params } }) +} + +export function getMerchantFirstLegOrder(id: number | string) { + return apiRequest({ method: 'GET', url: `/merchant/v1/orders/first-leg/${id}` }) +} + +export function createMerchantFirstLegOrder(payload: FirstLegOrderSaveRequest) { + return apiRequest({ + method: 'POST', + url: '/merchant/v1/orders/first-leg', + data: payload, + headers: { 'Idempotency-Key': createIdempotencyKey('first-leg-create') }, + }) +} + +export function updateMerchantFirstLegOrder(id: number | string, payload: FirstLegOrderSaveRequest) { + return apiRequest({ method: 'PUT', url: `/merchant/v1/orders/first-leg/${id}`, data: payload }) +} + +export function submitMerchantFirstLegOrder(id: number | string) { + return apiRequest({ + method: 'POST', + url: `/merchant/v1/orders/first-leg/${id}/submit`, + headers: { 'Idempotency-Key': createIdempotencyKey(`first-leg-submit-${id}`) }, + }) +} + +function createIdempotencyKey(scope: string) { + const randomPart = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}` + return `${scope}-${randomPart}` +} diff --git a/src/api/http.ts b/src/api/http.ts new file mode 100644 index 0000000..1bbd964 --- /dev/null +++ b/src/api/http.ts @@ -0,0 +1,107 @@ +import axios, { AxiosError, type AxiosRequestConfig, type InternalAxiosRequestConfig } from 'axios' +import { clearSessionTokens, getAccessToken, getRefreshToken, persistTokenPair, type TokenPair } from '@/api/session' +import { ApiError, asFieldErrors, isApiEnvelope, type ApiEnvelope } from '@/api/types' + +const transport = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL || '/api', + timeout: 15_000, +}) + +interface RetryableRequest extends InternalAxiosRequestConfig { + _retriedAfterRefresh?: boolean +} + +function unwrap(envelope: ApiEnvelope) { + if (envelope.code !== '0') { + throw new ApiError(envelope.message || '请求失败', envelope.code, undefined, asFieldErrors(envelope.data), envelope.traceId) + } + return envelope.data +} + +function mapError(error: unknown) { + if (error instanceof ApiError) return error + if (!axios.isAxiosError(error)) return new ApiError(error instanceof Error ? error.message : '请求失败') + + const body = error.response?.data + if (isApiEnvelope(body)) { + return new ApiError( + body.message || '请求失败', + body.code, + error.response?.status, + asFieldErrors(body.data), + typeof body.traceId === 'string' ? body.traceId : null, + ) + } + if (error.code === AxiosError.ERR_NETWORK) return new ApiError('无法连接服务器,请确认后端服务已启动', 'NETWORK_ERROR') + if (error.code === AxiosError.ECONNABORTED) return new ApiError('请求超时,请稍后重试', 'TIMEOUT') + return new ApiError(error.message || '请求失败', 'HTTP_ERROR', error.response?.status) +} + +let refreshPromise: Promise | null = null + +async function refreshSession() { + const refreshToken = getRefreshToken() + if (!refreshToken) throw new ApiError('登录已过期,请重新登录', 'A0401', 401) + if (!refreshPromise) { + refreshPromise = transport + .post>('/auth/refresh', { refreshToken }) + .then((response) => unwrap(response.data)) + .then((tokens) => { + persistTokenPair(tokens) + return tokens + }) + .finally(() => { + refreshPromise = null + }) + } + return refreshPromise +} + +transport.interceptors.request.use((config) => { + const token = getAccessToken() + if (token) config.headers.Authorization = `Bearer ${token}` + return config +}) + +transport.interceptors.response.use( + (response) => response, + async (error: AxiosError) => { + const request = error.config as RetryableRequest | undefined + const publicAuthPaths = ['/auth/login', '/auth/register', '/auth/refresh'] + const isPublicAuthRequest = publicAuthPaths.some((path) => request?.url?.endsWith(path)) + if (error.response?.status !== 401 || !request || request._retriedAfterRefresh || isPublicAuthRequest) { + throw mapError(error) + } + + request._retriedAfterRefresh = true + try { + const tokens = await refreshSession() + request.headers.Authorization = `Bearer ${tokens.accessToken}` + return await transport.request(request) + } catch (refreshError) { + clearSessionTokens() + window.dispatchEvent(new CustomEvent('cc-client-session-expired')) + throw mapError(refreshError) + } + }, +) + +export async function apiRequest(config: AxiosRequestConfig) { + try { + const response = await transport.request>(config) + return unwrap(response.data) + } catch (error) { + throw mapError(error) + } +} + +export async function apiBlobRequest(config: AxiosRequestConfig) { + try { + const response = await transport.request({ ...config, responseType: 'blob' }) + return response.data + } catch (error) { + throw mapError(error) + } +} + +export { ApiError } diff --git a/src/api/merchantIam.test.mjs b/src/api/merchantIam.test.mjs new file mode 100644 index 0000000..d2c01c2 --- /dev/null +++ b/src/api/merchantIam.test.mjs @@ -0,0 +1,40 @@ +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import test from 'node:test' + +const source = readFileSync(new URL('./merchantIam.ts', import.meta.url), 'utf8') +const storeSource = readFileSync(new URL('../stores/tenantAdministration.ts', import.meta.url), 'utf8') + +test('declares the documented merchant role and member endpoints', () => { + assert.match(source, /url: '\/merchant\/v1\/iam\/roles'/) + assert.match(source, /url: '\/merchant\/v1\/iam\/roles\/permissions'/) + assert.match(source, /url: '\/merchant\/v1\/iam\/members'/) + assert.match(source, /url: '\/merchant\/v1\/iam\/members\/roles'/) + assert.match(source, /url: `\/merchant\/v1\/iam\/members\/\$\{membershipId\}`/) + assert.match(source, /url: `\/merchant\/v1\/iam\/members\/\$\{membershipId\}\/status`/) + assert.match(source, /url: `\/merchant\/v1\/iam\/members\/\$\{membershipId\}\/password`/) + assert.match(source, /url: '\/merchant\/v1\/iam\/profile'/) +}) + +test('supports tenant profile updates and authenticated qualification downloads', () => { + assert.match(source, /method: 'PUT', url: '\/merchant\/v1\/iam\/tenant\/profile'/) + assert.match(source, /apiBlobRequest\(\{ method: 'GET', url \}\)/) + assert.match(source, /url\.startsWith\('\/merchant\/v1\/files\/qualifications\/'\)/) + assert.match(storeSource, /await updateTenantProfile/) +}) + +test('uses membershipId and full replacement arrays in authorization writes', () => { + assert.match(source, /data: \{ roleId, permissionIds \}/) + assert.match(source, /data: \{ membershipId, roleIds \}/) + assert.doesNotMatch(source, /data: \{ userId, roleIds \}/) +}) + +test('clears mock records before loading server role and member lists', () => { + assert.match(storeSource, /rolesLoadedFromApi\.value = true\s+roles\.value = \[\]/) + assert.match(storeSource, /membersLoadedFromApi\.value = true\s+members\.value = \[\]/) + assert.match(storeSource, /await createMerchantRole/) + assert.match(storeSource, /await createMerchantMember/) + assert.match(storeSource, /await updateMerchantMember/) + assert.match(storeSource, /await changeMerchantMemberStatus/) + assert.match(storeSource, /await resetMerchantMemberPassword/) +}) diff --git a/src/api/merchantIam.ts b/src/api/merchantIam.ts new file mode 100644 index 0000000..3748630 --- /dev/null +++ b/src/api/merchantIam.ts @@ -0,0 +1,188 @@ +import { apiBlobRequest, apiRequest } from '@/api/http' + +export type MerchantTenantStatus = 'PROFILE_REQUIRED' | 'PENDING' | 'ACTIVE' | 'REJECTED' | 'DISABLED' +export type QualificationStatus = 'PENDING' | 'APPROVED' | 'REJECTED' + +export interface MerchantTenantProfile { + id: number | string + tenantCode: string + tenantName: string + status: MerchantTenantStatus + legalName: string + unifiedSocialCreditCode: string | null + contactName: string + contactPhone: string + contactEmail: string | null +} + +export interface UpdateMerchantTenantProfileRequest { + tenantName: string + legalName: string + contactName: string + contactPhone: string + contactEmail: string +} + +export interface MerchantQualification { + id: number | string + tenantId: number | string + qualificationType: 'BUSINESS_LICENSE' + certificateNo: string | null + documentUrl: string + status: QualificationStatus + reviewComment: string | null +} + +export interface MerchantPermissionResource { + id: number | string + tenantId: number | string + appCode: 'MERCHANT' + permissionType: 'MENU' | 'BUTTON' + parentId: number | string + permissionCode: string + permissionName: string + routePath: string | null + httpMethod: string | null + sortNo: number + status: 'ACTIVE' | string +} + +export interface QualificationUploadResult { + qualificationId: number | string + reviewId: number | string + tenantStatus: 'PENDING' + documentUrl: string +} + +export interface MerchantRole { + id: number | string + tenantId: number | string + appCode: 'MERCHANT' + roleCode: string + roleName: string + roleType: 'SYSTEM' | 'CUSTOM' | string + status: 'ACTIVE' | 'INACTIVE' | string + description: string | null + permissionIds: Array +} + +export interface CreateMerchantRoleRequest { + roleCode: string + roleName: string + description: string +} + +export interface MerchantMember { + membershipId: number | string + userId: number | string + authUserId: number | string + displayName: string + phone: string | null + email: string | null + memberType: 'OWNER' | string + memberNo: string + status: 'ACTIVE' | 'INACTIVE' | string + roleIds: Array +} + +export interface MerchantPersonalProfile { + userId: number | string + authUserId: number | string + username: string + displayName: string + phone: string | null + email: string | null +} + +export interface CreateMerchantMemberRequest { + username: string + password: string + displayName: string + phone: string + email: string + memberType: 'EMPLOYEE' + memberNo: string + roleIds: Array +} + +export interface UpdateMerchantMemberRequest { + displayName: string + phone: string + email: string + memberType: string + memberNo: string + roleIds: Array +} + +export function getTenantProfile() { + return apiRequest({ method: 'GET', url: '/merchant/v1/iam/tenant/profile' }) +} + +export function updateTenantProfile(payload: UpdateMerchantTenantProfileRequest) { + return apiRequest({ method: 'PUT', url: '/merchant/v1/iam/tenant/profile', data: payload }) +} + +export function getTenantQualifications() { + return apiRequest({ method: 'GET', url: '/merchant/v1/iam/tenant/qualifications' }) +} + +export function getMerchantPermissionResources() { + return apiRequest({ method: 'GET', url: '/merchant/v1/iam/permissions' }) +} + +export function getMerchantPersonalProfile() { + return apiRequest({ method: 'GET', url: '/merchant/v1/iam/profile' }) +} + +export function updateMerchantPersonalProfile(payload: Pick) { + return apiRequest({ method: 'PUT', url: '/merchant/v1/iam/profile', data: payload }) +} + +export function uploadBusinessLicense(file: File, certificateNo?: string) { + const data = new FormData() + data.append('file', file) + if (certificateNo) data.append('certificateNo', certificateNo) + return apiRequest({ method: 'POST', url: '/merchant/v1/files/qualifications', data }) +} + +export function downloadQualificationImage(documentUrl: string) { + const url = documentUrl.startsWith('/api/') ? documentUrl.slice(4) : documentUrl + if (!url.startsWith('/merchant/v1/files/qualifications/')) throw new Error('资质文件地址无效') + return apiBlobRequest({ method: 'GET', url }) +} + +export function getMerchantRoles() { + return apiRequest({ method: 'GET', url: '/merchant/v1/iam/roles' }) +} + +export function createMerchantRole(payload: CreateMerchantRoleRequest) { + return apiRequest({ method: 'POST', url: '/merchant/v1/iam/roles', data: payload }) +} + +export function replaceMerchantRolePermissions(roleId: number | string, permissionIds: Array) { + return apiRequest({ method: 'PUT', url: '/merchant/v1/iam/roles/permissions', data: { roleId, permissionIds } }) +} + +export function getMerchantMembers() { + return apiRequest({ method: 'GET', url: '/merchant/v1/iam/members' }) +} + +export function createMerchantMember(payload: CreateMerchantMemberRequest) { + return apiRequest({ method: 'POST', url: '/merchant/v1/iam/members', data: payload }) +} + +export function updateMerchantMember(membershipId: number | string, payload: UpdateMerchantMemberRequest) { + return apiRequest({ method: 'PUT', url: `/merchant/v1/iam/members/${membershipId}`, data: payload }) +} + +export function changeMerchantMemberStatus(membershipId: number | string, status: 'ACTIVE' | 'DISABLED') { + return apiRequest({ method: 'PUT', url: `/merchant/v1/iam/members/${membershipId}/status`, data: { status } }) +} + +export function resetMerchantMemberPassword(membershipId: number | string, newPassword: string) { + return apiRequest({ method: 'PUT', url: `/merchant/v1/iam/members/${membershipId}/password`, data: { newPassword } }) +} + +export function replaceMerchantMemberRoles(membershipId: number | string, roleIds: Array) { + return apiRequest({ method: 'PUT', url: '/merchant/v1/iam/members/roles', data: { membershipId, roleIds } }) +} diff --git a/src/api/partners.ts b/src/api/partners.ts new file mode 100644 index 0000000..877a85d --- /dev/null +++ b/src/api/partners.ts @@ -0,0 +1,32 @@ +import { apiRequest } from '@/api/http' + +export type MerchantPartnerType = 'SENDER' | 'RECEIVER' + +export interface MerchantPartner { + id: number | string + partnerCode: string + partnerName: string + partnerType: MerchantPartnerType | string + contactName: string | null + contactPhone: string | null + contactEmail: string | null + countryCode: string | null + province: string | null + city: string | null + district: string | null + addressLine: string | null + postalCode: string | null + status: 'ACTIVE' | 'INACTIVE' | 'DISABLED' | string +} + +export function getMerchantPartnerCandidates(type: MerchantPartnerType, keyword?: string) { + return apiRequest({ + method: 'GET', + url: '/merchant/v1/partners/candidates', + params: { type, keyword: keyword?.trim() || undefined }, + }) +} + +export function getMerchantPartner(id: number | string) { + return apiRequest({ method: 'GET', url: `/merchant/v1/partners/${id}` }) +} diff --git a/src/api/session.ts b/src/api/session.ts new file mode 100644 index 0000000..c3d38f6 --- /dev/null +++ b/src/api/session.ts @@ -0,0 +1,38 @@ +export const ACCESS_TOKEN_KEY = 'cc-client-access-token' +export const REFRESH_TOKEN_KEY = 'cc-client-refresh-token' + +export interface TokenPair { + tokenType: string + accessToken: string + expiresIn: number + refreshToken: string + refreshExpiresIn: number +} + +function activeStorage() { + if (localStorage.getItem(ACCESS_TOKEN_KEY)) return localStorage + return sessionStorage +} + +export function getAccessToken() { + return localStorage.getItem(ACCESS_TOKEN_KEY) || sessionStorage.getItem(ACCESS_TOKEN_KEY) || '' +} + +export function getRefreshToken() { + return localStorage.getItem(REFRESH_TOKEN_KEY) || sessionStorage.getItem(REFRESH_TOKEN_KEY) || '' +} + +export function persistTokenPair(tokens: TokenPair, remember?: boolean) { + const storage = remember === undefined ? activeStorage() : remember ? localStorage : sessionStorage + clearSessionTokens() + storage.setItem(ACCESS_TOKEN_KEY, tokens.accessToken) + storage.setItem(REFRESH_TOKEN_KEY, tokens.refreshToken) +} + +export function clearSessionTokens() { + for (const storage of [localStorage, sessionStorage]) { + storage.removeItem(ACCESS_TOKEN_KEY) + storage.removeItem(REFRESH_TOKEN_KEY) + storage.removeItem('cc-client-token') + } +} diff --git a/src/api/types.ts b/src/api/types.ts new file mode 100644 index 0000000..5097dfa --- /dev/null +++ b/src/api/types.ts @@ -0,0 +1,35 @@ +export interface ApiEnvelope { + code: string + message: string + data: T + requestId: string | null + traceId: string | null + timestamp: string +} + +export type ApiFieldErrors = Record + +export class ApiError extends Error { + constructor( + message: string, + readonly code = 'NETWORK_ERROR', + readonly status?: number, + readonly fieldErrors?: ApiFieldErrors, + readonly traceId?: string | null, + ) { + super(message) + this.name = 'ApiError' + } +} + +export function isApiEnvelope(value: unknown): value is ApiEnvelope { + if (!value || typeof value !== 'object') return false + const record = value as Record + return typeof record.code === 'string' && typeof record.message === 'string' && 'data' in record +} + +export function asFieldErrors(value: unknown): ApiFieldErrors | undefined { + if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined + const entries = Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === 'string') + return entries.length > 0 ? Object.fromEntries(entries) : undefined +} diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts new file mode 100644 index 0000000..9d24007 --- /dev/null +++ b/src/auto-imports.d.ts @@ -0,0 +1,10 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +// biome-ignore lint: disable +export {} +declare global { + +} diff --git a/src/components.d.ts b/src/components.d.ts new file mode 100644 index 0000000..1a30d16 --- /dev/null +++ b/src/components.d.ts @@ -0,0 +1,46 @@ +/* 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 { + ClientSidebarMenu: typeof import('./components/ClientSidebarMenu.vue')['default'] + ClientSidebarMenuNode: typeof import('./components/ClientSidebarMenuNode.vue')['default'] + ElButton: typeof import('element-plus/es')['ElButton'] + ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] + ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] + ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] + ElDialog: typeof import('element-plus/es')['ElDialog'] + ElDrawer: typeof import('element-plus/es')['ElDrawer'] + ElEmpty: typeof import('element-plus/es')['ElEmpty'] + ElForm: typeof import('element-plus/es')['ElForm'] + ElFormItem: typeof import('element-plus/es')['ElFormItem'] + ElInput: typeof import('element-plus/es')['ElInput'] + ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] + ElOption: typeof import('element-plus/es')['ElOption'] + ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] + ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] + ElSelect: typeof import('element-plus/es')['ElSelect'] + ElSwitch: typeof import('element-plus/es')['ElSwitch'] + ElTable: typeof import('element-plus/es')['ElTable'] + ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] + ElTabPane: typeof import('element-plus/es')['ElTabPane'] + ElTabs: typeof import('element-plus/es')['ElTabs'] + ElTag: typeof import('element-plus/es')['ElTag'] + ElTimeline: typeof import('element-plus/es')['ElTimeline'] + ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] + ElUpload: typeof import('element-plus/es')['ElUpload'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + } + export interface GlobalDirectives { + vLoading: typeof import('element-plus/es')['ElLoadingDirective'] + } +} diff --git a/src/components/ClientSidebarMenu.vue b/src/components/ClientSidebarMenu.vue new file mode 100644 index 0000000..ff89446 --- /dev/null +++ b/src/components/ClientSidebarMenu.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/components/ClientSidebarMenuNode.vue b/src/components/ClientSidebarMenuNode.vue new file mode 100644 index 0000000..c75272c --- /dev/null +++ b/src/components/ClientSidebarMenuNode.vue @@ -0,0 +1,68 @@ + + + diff --git a/src/data/firstLegParties.ts b/src/data/firstLegParties.ts new file mode 100644 index 0000000..3d6755f --- /dev/null +++ b/src/data/firstLegParties.ts @@ -0,0 +1,47 @@ +export type FirstLegPartyRole = 'SHIPPER' | 'CONSIGNEE' + +export interface FirstLegParty { + id: string + code: string + name: string + roles: FirstLegPartyRole[] + contactName: string + phone: string + email: string + countryRegion: string + province: string + city: string + postalCode: string + address: string + status: 'ACTIVE' | 'INACTIVE' +} + +export interface FirstLegPartySnapshot extends Omit { + role: FirstLegPartyRole +} + +export function createFirstLegPartySnapshot(party: FirstLegParty, role: FirstLegPartyRole): FirstLegPartySnapshot { + return { + id: party.id, + code: party.code, + name: party.name, + role, + contactName: party.contactName, + phone: party.phone, + email: party.email, + countryRegion: party.countryRegion, + province: party.province, + city: party.city, + postalCode: party.postalCode, + address: party.address, + } +} + +// API 接入前供客户端订单静态流程使用,不代表已确认的往来单位类型或数据范围契约。 +export const DEMO_FIRST_LEG_PARTIES: readonly FirstLegParty[] = [ + { id: 'CP001', code: 'CN-SHIP-001', name: '示例贸易有限公司', roles: ['SHIPPER'], contactName: '王经理', phone: '138****1201', email: 'shipping@example.com', countryRegion: '中国', province: '浙江省', city: '杭州市', postalCode: '310000', address: '滨江区演示产业园 A 座', status: 'ACTIVE' }, + { id: 'CP002', code: 'CN-SHIP-002', name: '华南演示供应链有限公司', roles: ['SHIPPER'], contactName: '陈主管', phone: '139****8820', email: 'ops@example.cn', countryRegion: '中国', province: '广东省', city: '深圳市', postalCode: '518000', address: '宝安区演示供应链园 2 号楼', status: 'ACTIVE' }, + { id: 'CP003', code: 'US-CONS-001', name: 'DEMO GLOBAL LLC', roles: ['CONSIGNEE'], contactName: 'Alex Morgan', phone: '+1 *** *** 2108', email: 'receiving@example.us', countryRegion: '美国', province: 'California', city: 'Los Angeles', postalCode: '90001', address: '100 Demo Logistics Ave', status: 'ACTIVE' }, + { id: 'CP004', code: 'DE-CONS-001', name: 'DEMO EUROPE GMBH', roles: ['CONSIGNEE'], contactName: 'Lena Fischer', phone: '+49 *** *** 4810', email: 'warehouse@example.de', countryRegion: '德国', province: 'Hamburg', city: 'Hamburg', postalCode: '20095', address: '8 Demo Hafenstrasse', status: 'ACTIVE' }, + { id: 'CP005', code: 'US-CONS-OLD', name: '停用演示收货单位', roles: ['CONSIGNEE'], contactName: 'Demo Contact', phone: '', email: '', countryRegion: '美国', province: '', city: '', postalCode: '', address: '', status: 'INACTIVE' }, +] diff --git a/src/data/firstLegPartySource.test.mjs b/src/data/firstLegPartySource.test.mjs new file mode 100644 index 0000000..e22aca0 --- /dev/null +++ b/src/data/firstLegPartySource.test.mjs @@ -0,0 +1,23 @@ +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import test from 'node:test' + +const source = readFileSync(new URL('./firstLegPartySource.ts', import.meta.url), 'utf8') +const viewSource = readFileSync(new URL('../views/FirstLegOrdersView.vue', import.meta.url), 'utf8') +const apiSource = readFileSync(new URL('../api/partners.ts', import.meta.url), 'utf8') + +test('keeps counterparty availability behind a replaceable read-only source', () => { + assert.match(source, /export interface FirstLegPartySource/) + assert.match(source, /listAvailable\(role/) + assert.match(source, /findAvailable\(id, role/) + assert.match(source, /async load\(\)/) + assert.match(source, /party\.status === 'ACTIVE' && party\.roles\.includes\(role\)/) + assert.match(source, /getMerchantPartnerCandidates/) + assert.match(viewSource, /firstLegPartySource\.listAvailable\('SHIPPER'\)/) + assert.match(viewSource, /firstLegPartySource\.listAvailable\('CONSIGNEE'\)/) + assert.match(viewSource, /firstLegPartySource\.findAvailable\(id, role\)/) + assert.match(viewSource, /firstLegPartySource\.load\(\)/) + assert.doesNotMatch(viewSource, /DEMO_FIRST_LEG_PARTIES/) + assert.match(apiSource, /url: '\/merchant\/v1\/partners\/candidates'/) + assert.match(apiSource, /url: `\/merchant\/v1\/partners\/\$\{id\}`/) +}) diff --git a/src/data/firstLegPartySource.ts b/src/data/firstLegPartySource.ts new file mode 100644 index 0000000..d3787a3 --- /dev/null +++ b/src/data/firstLegPartySource.ts @@ -0,0 +1,51 @@ +import { getMerchantPartnerCandidates, type MerchantPartner, type MerchantPartnerType } from '@/api/partners' +import type { FirstLegParty, FirstLegPartyRole } from '@/data/firstLegParties' + +export interface FirstLegPartySource { + listAvailable(role: FirstLegPartyRole): readonly FirstLegParty[] + findAvailable(id: string, role: FirstLegPartyRole): FirstLegParty | undefined + load(): Promise +} + +function isAvailableForRole(party: FirstLegParty, role: FirstLegPartyRole) { + return party.status === 'ACTIVE' && party.roles.includes(role) +} + +const parties: FirstLegParty[] = [] + +function mapPartner(partner: MerchantPartner, role: FirstLegPartyRole): FirstLegParty { + return { + id: String(partner.id), + code: partner.partnerCode, + name: partner.partnerName, + roles: [role], + contactName: partner.contactName ?? '', + phone: partner.contactPhone ?? '', + email: partner.contactEmail ?? '', + countryRegion: partner.countryCode ?? '', + province: partner.province ?? '', + city: partner.city ?? '', + postalCode: partner.postalCode ?? '', + address: [partner.district, partner.addressLine].filter(Boolean).join(''), + status: partner.status === 'ACTIVE' ? 'ACTIVE' : 'INACTIVE', + } +} + +async function loadCandidates(type: MerchantPartnerType, role: FirstLegPartyRole) { + return (await getMerchantPartnerCandidates(type)).map((partner) => mapPartner(partner, role)) +} + +export const firstLegPartySource: FirstLegPartySource = { + listAvailable(role) { + return parties.filter((party) => isAvailableForRole(party, role)) + }, + findAvailable(id, role) { + return parties.find((party) => party.id === id && isAvailableForRole(party, role)) + }, + async load() { + parties.splice(0, parties.length, ...await Promise.all([ + loadCandidates('SENDER', 'SHIPPER'), + loadCandidates('RECEIVER', 'CONSIGNEE'), + ]).then((groups) => groups.flat())) + }, +} diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..f5c051e --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,15 @@ +/// + +import 'vue-router' + +export {} + +declare module 'vue-router' { + interface RouteMeta { + title?: string + description?: string + requiresAuth?: boolean + permission?: string + } +} + diff --git a/src/layouts/AppLayout.test.mjs b/src/layouts/AppLayout.test.mjs new file mode 100644 index 0000000..f1964ba --- /dev/null +++ b/src/layouts/AppLayout.test.mjs @@ -0,0 +1,106 @@ +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import test from 'node:test' + +const layoutSource = readFileSync(new URL('./AppLayout.vue', import.meta.url), 'utf8') +const routerSource = readFileSync(new URL('../router/index.ts', import.meta.url), 'utf8') +const sidebarMenuSource = readFileSync(new URL('../components/ClientSidebarMenu.vue', import.meta.url), 'utf8') +const sidebarMenuNodeSource = readFileSync(new URL('../components/ClientSidebarMenuNode.vue', import.meta.url), 'utf8') + +test('source contract: renders the grouped sidebar menu and mobile drawer controls', () => { + assert.match(layoutSource, /import \{ computed, nextTick, onBeforeUnmount, onMounted, ref, watch \} from 'vue'/) + assert.match(layoutSource, /import \{[\s\S]*?\bX,[\s\S]*?\} from '@lucide\/vue'/) + assert.match(layoutSource, /import ClientSidebarMenu from '@\/components\/ClientSidebarMenu\.vue'/) + assert.match(layoutSource, /:class="\{ 'mobile-menu-open': mobileMenuOpen \}"/) + assert.match(layoutSource, /]*class="client-sidebar-scrim"[^>]*aria-label="关闭导航"[^>]*@click="closeMobileMenu"/s) + assert.match(layoutSource, //) + assert.doesNotMatch(layoutSource, /]*aria-label=/) + assert.match(layoutSource, /