commit 155f06846d18c3e1d0a69fcde754812c5748fd4b
Author: ouyangxq <775153912@qq.com>
Date: Thu Jul 30 17:59:42 2026 +0800
chore: initial commit
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, /