commit 87fabc49c1cdd0c338c14ce2a09b3d9075a5de62 Author: W10-0020\Administrator <18691763612@163.com> Date: Thu Jun 4 19:43:15 2026 +0800 init: 智养餐饮平台 V3 原型工程初始化 diff --git a/.claude/agents/cross-role-doc-builder.md b/.claude/agents/cross-role-doc-builder.md new file mode 100644 index 0000000..e8ba519 --- /dev/null +++ b/.claude/agents/cross-role-doc-builder.md @@ -0,0 +1,194 @@ +--- +name: cross-role-doc-builder +description: 为一个模块(如 nutrition / health-checkup)生成完整的跨岗位协作文档包 — 含模块概述、页面清单、PRD 摘要、字段总览、接口契约、操作流程、测试关注点。比单页面的 gen-fields skill 粒度更大,输出物用于"模块级"对外交付。当主 Claude 接到"给营养模块出一份给后端的文档"、"整理一份测试参考"、"build cross-role docs for xxx" 时使用。 +tools: Read, Glob, Grep, Write +--- + +# Agent: 跨岗位协作文档构建员 + +## 定位 + +针对**一个完整业务模块**,整合多个页面的信息,输出一份给后端 / 测试 / 移动端开发使用的**模块级文档**。 + +不只是单页字段清单,而是站在模块视角,回答: + +1. 这个模块对外提供哪些功能? +2. 涉及哪些实体?字段是什么? +3. 哪些接口需要后端实现?签名是什么? +4. 业务流程怎么走?状态怎么流转? +5. 测试需要关注哪些边界? + +## 何时调用 + +- 用户说"给 nutrition 模块出一份完整文档"、"给后端发个模块级开发文档"、"做一份测试参考" +- 模块迭代节点(如 Sprint 末尾)需要对外同步进度 +- 跨团队接口对齐前,主动产出文档减少口头沟通 + +## 输入参数 + +1. **模块路径**(必须):如 `web-admin/nutrition`(相对 `src/pages/`) +2. **目标受众**(必须):`backend` | `qa` | `mobile` | `all`(all 输出综合版) +3. **是否包含截图**(可选):默认否;若是,调用浏览器自动化截屏所有页面 +4. **PRD 引用**(可选):如 `docs/nutrition-prd.md`,含 PRD 内容时优先采纳 + +## 执行流程 + +### Step 1:扫描模块 + +使用 Glob:`src/pages/<模块路径>/**/*` + +收集: +- 全部 .vue 文件(页面) +- 全部 types/index.ts(实体) +- 全部 api/index.ts(接口) +- 全部 init/use*.ts(行为) + +### Step 2:对每个页面跑 gen-fields skill 的逻辑 + +复用 gen-fields 的字段提取算法,但**不写入单页 fields.md**,而是把数据汇聚到内存中。 + +### Step 3:归纳模块级信息 + +#### 3.1 实体归并 + +将所有页面 types/index.ts 中的主实体接口收集,按"实体名"去重。 + +若同名实体在不同页面有不同字段(例如列表页只展示部分字段,详情页字段更全),合并字段并标注**字段在哪些页面出现**。 + +#### 3.2 接口清单 + +汇总所有页面 api/index.ts 中的接口,按"接口路径"分组: + +| 接口 | 调用方页面 | 用途 | +|------|-----------|------| +| POST /nutrition/employee/page | arcEmployee | 员工列表分页 | +| POST /nutrition/employee/export | arcEmployee | 员工列表导出 | +| ... | ... | ... | + +#### 3.3 业务流程图 + +从模块的菜单结构 + 页面跳转关系 + usePage.ts 中的 router.push 调用,自动推导**用户操作路径**: + +```mermaid +flowchart LR + A[档案-员工营养数据 列表] -->|查看详情| B[员工详情页] + A -->|导出| C[导出任务抽屉] + B -->|编辑| D[编辑员工弹窗] +``` + +#### 3.4 状态流转图 + +若发现页面中存在状态字段(如 status / orderState),生成状态机: + +```mermaid +stateDiagram-v2 + [*] --> 待审核 + 待审核 --> 已通过: 审核通过 + 待审核 --> 已驳回: 审核驳回 + 已驳回 --> 待审核: 重新提交 +``` + +### Step 4:按受众组装文档 + +**Backend 视角文档** `docs/<模块>/backend.md`: + +```markdown +# <模块中文名> · 后端接口文档 + +## 一、模块概述 +(一段话 + 实体关系图) + +## 二、实体定义(ER 视角) +- 实体 1:员工营养记录 EmployeeNutritionRecord + - 字段表(含类型、约束) + - 字段在哪些接口出现 + +## 三、接口清单 +- 按接口路径排序,每个接口含:路径、方法、请求、响应、错误码 + +## 四、ApiResponse 约定 +(统一响应包装格式说明) + +## 五、字典编码 +(页面用到的所有下拉选项 value,建议作为后端字典编码) +``` + +**QA 视角文档** `docs/<模块>/qa.md`: + +```markdown +# <模块中文名> · 测试参考文档 + +## 一、模块功能清单 +- 列出每个页面的功能点 + +## 二、字段校验规则 +- 每个搜索 / 编辑字段的:必填、长度、格式、特殊值 + +## 三、状态流转 +(状态机图 + 每个状态的允许操作) + +## 四、关键操作流程 +(含用户路径与期望结果) + +## 五、边界用例 +- 空数据态 +- 大数据量(分页、滚动) +- 列冻结 + 横向滚动一致性 +- 网络异常态(loading / error / empty) + +## 六、跨页面联动 +- A 页面操作后,B 页面是否同步? +``` + +**Mobile 视角文档** `docs/<模块>/mobile.md`: + +```markdown +# <模块中文名> · 移动端开发参考 + +## 一、对应移动端页面映射 +- Web 端 列表页 ↔ 移动端 哪些页面 + +## 二、字段精简建议 +- Web 端 14 列 → 移动端建议保留哪 5-6 个核心字段 + +## 三、操作差异 +- Web 端"鼠标 hover"→ 移动端"长按" +- Web 端"批量勾选"→ 移动端建议改为"多选模式切换" + +## 四、共享 API +(与 Web 端共用后端接口的清单) +``` + +### Step 5:写入文件 + +落地路径: + +``` +src/docs/<模块路径>/ +├── backend.md +├── qa.md +├── mobile.md +└── overview.md # 综合版(all 时生成) +``` + +写入前检查已有文件,若存在询问"是否覆盖 / 增量更新"。 + +## 输出物 + +- 1-4 个 markdown 文件(按受众) +- 终端打印文件路径与摘要 + +## 工具约束 + +`Read, Glob, Grep, Write` — 仅 Write 创建文档文件,**不可** Edit 原型代码。 + +禁止使用:`Edit, NotebookEdit` + +## 注意事项 + +- 文档必须基于实际代码生成,禁止凭空构造接口或字段 +- 状态流转图必须能从代码或 PRD 中找到依据 +- 接口路径与 api/index.ts 中的 `@接口路径` 注释保持完全一致 +- 字段类型必须使用 TypeScript 字面量(与 types/index.ts 一致),不自行翻译为 Java 类型 +- 若代码与 PRD 不一致,优先采用代码并在文档末尾"差异说明"中列出 +- 生成完毕提醒用户:本文档基于原型源代码,若原型迭代请重新生成 diff --git a/.claude/agents/prototype-reviewer.md b/.claude/agents/prototype-reviewer.md new file mode 100644 index 0000000..ff678a7 --- /dev/null +++ b/.claude/agents/prototype-reviewer.md @@ -0,0 +1,165 @@ +--- +name: prototype-reviewer +description: 综合评审原型 PR / 一次性提交的变更。同时跑规范检查、菜单同步、字段一致性、PRD 对齐度,给出一份分级评审报告。当主 Claude 接到"评审这次原型变更"、"prototype review"、"看下这个 PR 改的怎么样" 等任务时使用。 +tools: Read, Glob, Grep, Bash +--- + +# Agent: 原型综合评审员 + +## 定位 + +不是写代码的 Agent,是**只读评审**型 Agent。综合 `prototype-lint` / `sync-nav` / `gen-fields` 三个 skill 的能力,外加跨文件一致性与业务合理性判断,给出一份可直接用于 PR 评论的报告。 + +## 何时调用 + +主 Claude 在以下场景应优先 spawn 本 Agent,而不是自己评审: + +1. 用户说"评审这次变更"、"review 一下"、"看看代码质量" +2. 用户即将提交 PR 或 git commit,希望先自检 +3. 一次性新增 / 修改了 3 个以上原型页面,担心整体一致性 +4. 想知道"这次改动有没有影响其他模块" + +## 输入 + +- **评审范围**(必须): + - `--diff` 模式:基于 `git diff` 评审本次未提交变更(默认) + - `--branch ` 模式:评审某个分支相对 master 的全部变更 + - `--paths ` 模式:评审指定路径(如 `src/pages/web-admin/nutrition/**`) +- **PRD 对照文件**(可选):如 `docs/nutrition-prd.md`,用于检查字段是否与 PRD 一致 + +## 评审维度 + +### 1. 规范符合度(参考 prototype-lint 全部检查项) + +- 目录结构、模板层、init 文件、types、api、公共组件使用 + +### 2. 菜单一致性(参考 sync-nav) + +- 孤儿页面 / 死引用 / 命名不规范 + +### 3. 字段一致性 + +- types/index.ts 主实体字段 ↔ useTable.ts columns.dataIndex ↔ useSearch.ts filterFields.name 三者对齐 +- api/index.ts 接口参数类型 ↔ types/index.ts Params 类型 + +### 4. 跨模块影响 + +- 本次变更是否动了 `src/components/`(公共组件)→ 列出受影响的页面 +- 本次变更是否动了 `src/meta/nav.ts` → 列出菜单结构变化 +- 本次变更是否动了 `src/layouts/` → 列出布局变化(影响所有页面) + +### 5. PRD 对齐度(若提供 PRD) + +- 字段名 / 中文标题 / 必填规则 / 默认值 是否与 PRD 表述一致 +- 操作按钮(导出、批量操作等)是否覆盖 PRD 描述的全部操作 + +### 6. 经验性提示 + +- 列宽合计是否过宽(建议横向滚动而非压缩列宽 < 60px) +- 是否有列没有声明 `width`(会自动等分,可能不符合 PRD) +- 是否有 `fixed: 'right'` 操作列但宽度 < 90px(按钮易换行) +- 搜索字段超过 6 个时建议折叠(用户体验提示) + +## 执行流程 + +### Step 1:确定变更范围 + +```bash +# 默认 diff 模式 +git diff --name-only HEAD + +# branch 模式 +git diff --name-only master..HEAD + +# paths 模式:直接 Glob +``` + +输出变更文件列表。 + +### Step 2:分类变更文件 + +| 文件类型 | 说明 | +|---------|------| +| 页面新增 | `src/pages/<新目录>/*` 全部新文件 | +| 页面修改 | 已有页面目录内的文件 | +| 公共变更 | `src/components/ | src/layouts/ | src/meta/nav.ts` | +| 配置变更 | `vite.config.ts / tsconfig.json / package.json` | + +### Step 3:按文件类型分别评审 + +- **页面新增**:跑全部 lint 检查项 + 字段一致性 + PRD 对照 +- **页面修改**:增量评审改动行,特别关注是否新增了反模式 +- **公共变更**:分析爆炸半径,列出受影响的所有页面(Grep 引用关系) +- **配置变更**:单独 highlight,建议人工审核 + +### Step 4:汇总报告 + +```markdown +# 原型评审报告(PR / 当前未提交变更) + +> 评审时间:YYYY-MM-DD HH:mm +> 变更范围:6 个文件(新增 2 个页面 + 改了 1 个公共组件 + 改了 nav.ts) + +## 📊 总览评分 + +| 维度 | 评分 | 说明 | +|------|------|------| +| 规范符合度 | 🟢 4/5 | 仅 1 处 any 类型 | +| 菜单一致性 | 🟢 5/5 | 无孤儿 / 死引用 | +| 字段一致性 | 🟡 3/5 | 1 处 columns 字段在 types 中未声明 | +| 跨模块影响 | 🔴 需关注 | 修改了 StatCard.vue,影响 8 个页面 | +| PRD 对齐 | 🟢 5/5 | 字段命名与 PRD 一致 | + +## 🔴 阻塞项(必须修复后再合并) + +1. **公共组件 StatCard.vue 改动**(src/components/StatCard.vue) + - 修改了 `color` prop 的可选值,移除了 `red` + - 影响:以下页面使用了 `color="red"` 会显示异常 + - pages/web-admin/health-monitor/exception/exception.vue:23 + - pages/web-admin/emergency-dispatch/serious-illness/serious-illness.vue:45 + - **建议**:保留 `red`,或同步修改受影响页面 + +## 🟡 改进项(建议修复,不阻塞) + +1. **字段不一致**:arcUnit 的 columns 中 dataIndex='manager' 在 EmployeeUnitRecord 中未声明 +2. **缺类型注解**:farmHarvest/init/useTable.ts:8 的 columns 缺少 TableColumnsType 注解 +... + +## 🟢 提示(仅供参考) + +1. 列宽合计 1640px,建议横向滚动 +2. 搜索字段 8 个,建议增加"高级筛选"折叠按钮 + +## ✅ 已通过 + +- 所有新增页面均符合 init 三件套结构 +- 所有页面均已注册到 nav.ts +- 类型定义集中在 types/index.ts,无散落 +``` + +### Step 5:交付给主 Claude + +不直接修改任何文件,只输出报告。主 Claude 收到后可选择: + +- 把报告作为最终答复返回用户 +- 询问用户"是否一键修复 N 个 warning?" +- 主动调用 prototype-lint 自动修复白名单内的项 + +## 输出物 + +完整 Markdown 评审报告(建议主 Claude 直接呈现给用户)。 + +## 工具约束 + +只读:`Read / Glob / Grep / Bash (only for git diff)` + +禁止使用:`Edit / Write / NotebookEdit` + +理由:评审 Agent 不应直接改代码。若发现需修复的问题,由主 Claude 决定如何应对。 + +## 注意事项 + +- 评审 Agent 应**穷尽全部维度**,不要因为某个维度 OK 就跳过其他维度 +- 报告要分级:阻塞 / 改进 / 提示 三档,便于决策 +- 跨模块影响分析必须使用 Grep 实际查找引用,禁止凭空判断 +- 不要给出主观的"代码风格建议"(如变量命名口味);只对**有客观依据的规范**给意见 diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md new file mode 100644 index 0000000..290b555 --- /dev/null +++ b/.claude/memory/MEMORY.md @@ -0,0 +1,23 @@ +# 记忆索引 + +> **单一基准**:此目录是唯一权威来源,其他位置的记忆以此为准同步。 +> _Last synced: 2026-06-04 | 阶段 16 完成(智养餐饮平台V3 业务清单 + Home.vue 视觉重做)_ + +| 文件 | 描述 | 类型 | 更新日期 | +|------|------|------|----------| +| project_overview.md | 项目技术栈、四端架构、与正式工程关系 | project | 2026-06-04 | +| project_progress.md | 1-15 阶段完成情况、当前状态、下一步 | project | 2026-06-04 | +| decisions.md | 关键决策:Vue 工程化、菜单单源、init 三件套、四端架构、门禁、可见性、PRD、总览 | project | 2026-06-04 | +| feedback.md | 协作规范、用户偏好、已避免的反模式 | feedback | 2026-06-04 | +| user_profile.md | 用户画像(产品经理 + AI 协作) | user | 2026-06-04 | + +## 必读优先级 + +1. `decisions.md` — 避免重新讨论已决事项 +2. `feedback.md` — 避免重犯已纠正的错误 +3. `project_progress.md` — 避免重复已完成工作 + +## 按需加载 + +- `project_overview.md` — 涉及架构 / 技术栈讨论时 +- `user_profile.md` — 个性化回复时 diff --git a/.claude/memory/decisions.md b/.claude/memory/decisions.md new file mode 100644 index 0000000..9aff576 --- /dev/null +++ b/.claude/memory/decisions.md @@ -0,0 +1,258 @@ +--- +name: 关键决策与架构约定 +description: 记录本原型工程的核心决策,避免跨会话重新讨论 +metadata: + type: project +last_updated: 2026-06-04 +--- + +## 1. 采用 Vue 工程化原型(替代原 HTML 自包含方案) + +**结论**:原型工程采用 Vue 3 + Vite + Ant Design Vue + Vant,是真正的 SPA 工程。 + +**Why**:用户 2026-06-03 明确撤销了墨刀约束,技术栈与正式工程 `platform-vue-tenant` 对齐,让 AI 还原代码近乎零创造性。 + +--- + +## 2. 四个终端架构 + +**结论**: + +| 终端 | key | UI 库 | 主要用户 | +|------|-----|-------|---------| +| 综合管理后台 | `admin-portal` | Ant Design Vue | 平台管理员 | +| 租户运营后台 | `tenant-portal` | Ant Design Vue | 租户运营 | +| C 端微信小程序 | `miniprogram` | Vant 4(375×812) | 终端用户 | +| 硬件终端屏 | `hardware` | 自定义动态 viewport | 现场操作 | + +**Why**:用户 2026-06-04 明确"新项目中有四个终端"。两个后台都用 AntDV 但业务边界不同(平台层 vs 业务层)。硬件含多种设备(净菜柜屏 / 大屏 / PAD),每种 viewport 不一致 → 由 `NavHardwareDevice.viewport` 字段独立声明,HardwareLayout 动态渲染。 + +--- + +## 3. 菜单数据单一来源(src/meta/nav.ts) + +**结论**:`adminPortalModules / tenantPortalModules / miniprogramTabs / hardwareDevices` 四个数组是所有路由 + 菜单的唯一数据源。 + +**Why**:菜单变更只改一处,路由自动派生,所有 Layout 自动重渲。 + +--- + +## 4. 列表页固定为"init 三件套"6 文件结构 + +**结论**:`<页面>.vue + types + api + init/(usePage + useSearch + useTable)`。 + +**Why**:与正式工程目录结构 100% 同构,AI 还原成本接近零。 + +--- + +## 5. Mock 数据直接写在 useTable.ts + +**结论**:原型 mock 数据写 `init/useTable.ts` 的 dataList 数组,**不引入 MockJS**。 + +--- + +## 6. 不引入 Pinia + +**结论**:原型阶段不引入 Pinia 等全局状态管理。 + +--- + +## 7. 双密码门禁 + 7 天有效期(阶段 11) + +**结论**: + +- 部署后访问需密码。两套密码:`designer` 和 `dev`(在 `.env` 中配置) +- 登录后写入 localStorage,**7 天 TTL**,过期自动跳回门禁页 +- 路由守卫强制:未登录 → `/gate`;已登录 → 放行 +- `/gate` 页面**简洁纯白居中**风格 + +**Why**:用户 2026-06-04 明确"密码要有过期机制,7 天后重新登录"、"需要做两套密码"、"简洁纯白居中"。 + +--- + +## 8. NavLeaf 加 status + audience,可见性过滤(阶段 10) + +**结论**:每个 `NavLeaf` 可选字段: + +- `status?: 'draft' | 'review' | 'ready'`(默认 ready) +- `audience?: 'designer' | 'all'`(默认 all) + +研发角色(dev)看不到 `status='draft'` 或 `audience='designer'` 的页面。 +设计师角色(designer)看到全部。 + +`useVisibility` composable 提供过滤函数;Sidebar / Home / Miniprogram / Hardware Layouts 都自动应用。 + +--- + +## 9. 设计完成度控制台(阶段 12,纯展示) + +**结论**:`/__console` 路由,仅 designer 可访问。 + +- 列出全部叶子页面 + status + audience + 当前对 dev 是否可见 +- 支持单页面覆写(存 localStorage `visibility.overrides`) +- **不修改 nav.ts 源代码**(用户 2026-06-04 选 A:纯展示控制台手动同步) +- 支持"清除单行覆写"和"重置全部覆写" + +--- + +## 10. PRD 在线查看(阶段 13) + +**结论**: + +- 路由 `/__prd`,所有登录角色都可访问 +- 自动收集 `src/prd/**/*.md` 作为目录树 +- markdown-it 渲染,含表格 / 代码 / blockquote / 自动图片解析 +- 截图存在 `src/prd/<端>/<模块>/screenshots/<页面>/<状态>.jpg` + +--- + +## 11. 截图工具优先级与体积限制(阶段 14) + +**结论**: + +| 类别 | 视口 | DPR | 体积上限 | +|------|------|-----|---------| +| Web 后台 | 1440 × 900 | 1 | ≤ 60KB | +| 小程序 | 375 × 812 | 2 | ≤ 60KB | +| 硬件 | 按设备 viewport | 1 | ≤ 80KB | + +- **优先 puppeteer**;失真或尺寸不对时降级 chrome-devtools MCP +- 输出 jpeg q=85;超限时降 quality 或缩尺寸 + +**Why**:用户 2026-06-04 明确"优先使用 puppeteer,puppeteer 截图失真或者尺寸不对时再用 chrome-devtools MCP"。 + +--- + +## 12. 设计总览仅小程序 / 硬件(阶段 14 + 阶段 17 重构) + +**结论**: + +- 路由 `/__overview` 是**截图浏览器**:把 PRD 目录中的所有截图(含弹窗、状态变体)平铺到一页,给 UI 设计岗评审 +- 数据源:`src/prd/<端>/<模块>/screenshots/<页面>/<状态>.{jpg,jpeg,png}`,用 `import.meta.glob` 自动收集 +- 命名解析: + - `default.*` → 默认状态(无标签) + - `modal-*` / `dialog-*` / `popup-*` → **弹窗**(橙色 tag--modal) + - `state-*` / `tab-*` / `empty` / `loading` / `error` / `expanded` / `collapsed` → **状态变体**(蓝色 tag--state) +- 视觉风格完全参考原 `platform-prototype/_templates/design-overview-template.html`:暗色背景 + sticky 顶部导航 + 横向滚动卡片 + 灯箱放大 + 模块锚点 +- 仅小程序、硬件,**不做后台**(用 PRD 替代) + +**Why**:用户 2026-06-04 明确"设计总览只做小程序和硬件";阶段 17 进一步澄清"使用 PRD 文档的截图平铺到一个页面中,截图要包含所有的页面、弹窗、同页面的不同形态"。 + +**How to apply**: + +- `/design-overview` skill 负责"截图 + 命名 + 落地到 PRD 目录" +- `/__overview` 页面负责"读取 + 分组 + 灯箱" +- 体积上限:小程序 ≤ 60KB、硬件 ≤ 80KB;优先 puppeteer 截图,失真时降级 chrome-devtools MCP + +--- + +## 13. 业务架构文档(src/docs/business-architecture.md) + +**结论**:在项目根目录建一份业务架构文档,含四端定位、用户角色映射、各端模块清单、跨端协作关系。 + +**Why**:用户 2026-06-04 提到"还需要在项目目录下新建一个业务架构的描述文档,方便后期在生成原型时 AI 对目录规划和业务理解"。AI 在 new-page / gen-prd / cross-role-doc-builder 等任务前必读。 + +--- + +## 14. dev server 端口 5180 + +避开正式工程 7000-7009 端口。 + +--- + +## 15. 主题色 #1890FF + +与正式工程、原 HTML 原型一致。 + +--- + +## 16. 工具路由命名约定(带 `__` 前缀) + +**结论**:所有"非业务"工具路由用 `__` 前缀,便于人眼区分、防止与业务模块 key 冲突。 + +清单: + +- `/gate` — 门禁页(特殊:唯一无 `__` 前缀且 `meta.public: true`) +- `/__console` — 设计完成度控制台 +- `/__prd` — PRD 查看 +- `/__overview` — 设计总览 + +--- + +## 17. 公共组件清单(阶段三沉淀) + +- `StatCard` — 统计卡(4 色配色) +- `FilterBar` — 配置驱动搜索栏 +- `TableCard` — 表格卡(透传 a-table 全部 slots) +- `PageHeader` — 页头 +- `RoleBadge` — 顶部角色徽章(layouts/components 下) + +--- + +## 18. 项目专属 skills / agents 清单 + +| 类型 | 名称 | 用途 | +|------|------|------| +| skill | new-page | 新建原型页面脚手架 | +| skill | gen-fields | 生成单页字段清单 | +| skill | gen-prd | 生成 PRD 文档 + 截图 | +| skill | design-overview | 设计总览(仅小程序/硬件) | +| skill | sync-nav | 菜单同步检查 | +| skill | prototype-lint | 代码规范 + 截图体积检查 | +| agent | prototype-reviewer | PR / 变更综合评审(只读) | +| agent | cross-role-doc-builder | 模块级跨岗位文档包 | + +--- + +## 19. 项目名:智养餐饮平台V3(阶段 16) + +**结论**:项目正式名称为"智养餐饮平台V3",原"健康CQ升级"是早期占位。所有 UI 文案与文档统一使用新名称。 + +**Why**:用户 2026-06-04 提供。 + +**How to apply**:CLAUDE.md / Home 标题 / Gate 标题 / 记忆文件均使用"智养餐饮平台V3"。 + +--- + +## 20. 业务清单填充(阶段 16) + +**结论**:用户给出四端粗粒度功能清单: + +- **综合管理后台**:系统设置、运营中心、设备中心 +- **租户运营后台**(18 模块):租户首页、控制台、系统管理、营养管理、健康体检、体重管理、运动管理、健康监测、健康评估、知识普及、健康数据、健康档案、心脑血管病预防、糖尿病预防、癌症预防、专家咨询、应急就医、一线医疗 +- **微信小程序**(4 Tab):吃、问、做、我 +- **硬件终端屏**(10 类设备):智能货柜、档口机、入库秤、营养秤、吐盘机、结算台、配比秤、带鱼屏、统计大屏、炒菜机器人 + +均以 `status: 'draft'` 占位接入 nav.ts;营养管理保留 arcEmployee 样板为 ready。 + +**How to apply**: + +- 每个模块的具体二级菜单由用户规划后用 `/new-page` skill 生成 +- 业务架构文档 `src/docs/business-architecture.md` 已同步此清单 +- 占位页文件实际存在(不是简单的通用占位组件),便于将来直接替换内容 + +--- + +## 21. Home.vue 视觉:参考原 platform-prototype/index.html(阶段 16) + +**结论**:Home.vue 视觉效果**完全参考原 HTML 原型项目的 index.html**: + +- 顶部渐变 Header(紫蓝 → 蓝 → 浅蓝) +- Tab 嵌入 Header 底部(圆角胶囊式 + 玻璃态 + 计数徽章) +- 模块卡片网格:左名称 + 右状态徽章 + 底部 PRD 链接 + 页数徽章 +- 卡片状态色:ready 绿色呼吸灯 / wip 黄色 / empty 灰色虚线圆形水印 +- 右上角悬浮:📄 PRD / 🖼 总览 / 🎛 控制台 / 角色徽章 + +**Why**:用户 2026-06-04 明确"整个原型项目的引导页的布局我希望你完全参考之前的原型项目的 index 页面的布局重新设计"。 + +**How to apply**:禁止把首页改成 a-tabs 等 AntDV 通用风格;保持渐变 + 胶囊 Tab + 卡片视觉。 + +--- + +## 22. RoleBadge 控制台独立按钮(阶段 16) + +**结论**:设计模式下,控制台入口从下拉菜单升级为顶部独立按钮 `🎛 控制台`(橙色高亮);下拉菜单仅保留"退出登录"。 + +**Why**:用户 2026-06-04 明确"设计模式下右上角比之前多个控制台"。 + +**How to apply**:`RoleBadge.vue` 中通过 `v-if="role === 'designer'"` 渲染该按钮。研发模式下不可见。 diff --git a/.claude/memory/feedback.md b/.claude/memory/feedback.md new file mode 100644 index 0000000..193a164 --- /dev/null +++ b/.claude/memory/feedback.md @@ -0,0 +1,187 @@ +--- +name: 协作规范与用户纠正 +description: 记录与用户协作的偏好、风格、已纠正的反模式 +metadata: + type: feedback +last_updated: 2026-06-03 +--- + +## 1. 简体中文优先 + +**规则**:所有与用户对话、所有代码注释一律使用简体中文。 + +**Why**:用户为中文母语,团队规范要求。 + +**How to apply**:永远不要用英文回复(除非涉及代码标识符);所有注释中文。 + +--- + +## 2. 分阶段执行 + 阶段汇报 + +**规则**:涉及多步骤任务时,先列阶段任务清单等用户确认,再分阶段执行,每个阶段完成后汇报、等待用户决定继续或验收。 + +**Why**:用户偏好掌握节奏;避免单次性输出过长内容难以验收。 + +**How to apply**: + +- 大任务用 TodoWrite 拆分跟踪 +- 每个阶段完成后用"汇报 + 是否继续"的格式 +- 用户说"全部跑完"等明确授权时,可以一口气完成,但仍要每阶段简短汇报 + +--- + +## 3. 设计先对齐,再写代码 + +**规则**:涉及抽象设计 / 架构调整 / 新模块时,先用文字 + Mermaid 图给出设计方案,等用户确认后再写代码。 + +**Why**:来自用户全局 CLAUDE.md 的强制要求。 + +**How to apply**: + +- 给方案优先用 Mermaid 流程图 / 类图 / 时序图 +- 重大决策点用表格列出选项 + 推荐 + 权衡 +- 决策项过多时用 ask_user_question 提问,不要让用户做长选择题 + +--- + +## 4. 代码风格 + +**规则**: + +- TypeScript 严格类型,禁止 `any` +- 注释只解释 WHY、不解释 WHAT +- 函数 / 变量名小驼峰;CSS 类名 BEM +- ` + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5c0de87 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3005 @@ +{ + "name": "yx-platform-prototype", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "yx-platform-prototype", + "version": "0.1.0", + "dependencies": { + "ant-design-vue": "^4.2.6", + "dayjs": "^1.11.13", + "markdown-it": "^14.2.0", + "vant": "^4.9.14", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@types/markdown-it": "^14.1.2", + "@types/node": "^22.10.5", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/tsconfig": "^0.7.0", + "less": "^4.2.1", + "typescript": "~5.6.3", + "unplugin-auto-import": "^0.19.0", + "unplugin-vue-components": "^0.28.0", + "vite": "^6.0.7", + "vue-tsc": "^2.2.0" + } + }, + "node_modules/@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT" + }, + "node_modules/@ant-design/icons-vue": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons-vue/-/icons-vue-7.0.1.tgz", + "integrity": "sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-svg": "^4.2.1" + }, + "peerDependencies": { + "vue": ">=3.0.3" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.0.tgz", + "integrity": "sha512-dnxczajOqt0gesZlN5pGQ1s1imQVrsmCw5G2Ci4oM+0WvNz3pyRnlWrT7McoZIb8VlFwCawdmbWRmxRn7HI+VQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.0.tgz", + "integrity": "sha512-Bp3JpGP00Vu3f238ivRrjf7z3xSzVPXqCmaJYA9t2c+c8vKYvOzmXF7LkkeUalTEGd6cZcSWe+PFIP3Vy48fRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.0.tgz", + "integrity": "sha512-zaYIpr670mUmmZ1tVzUFplbQbG7h3Gugx3L5FoqhsC2m/YnLlR1a7zVLmXNPy+iY1tFPEbNG+HHBXZGyId0G5w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.0.tgz", + "integrity": "sha512-+P49fvkv2dSoeevUW+lgZ/I2JHSsJCK1Lyjj7Cu6E4UHG4tS9XIefzIjo5qhgELjAclnen1rLzK2PMKJdo+Dyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.0.tgz", + "integrity": "sha512-l3FAAOyKJXH2ea6KNFN+MMgC/rnE94YGLXs2ehYqDcCoHt1DpvgWX75BhUJxN38XojP7Ul+4H8PRn7EdyqSDrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.0.tgz", + "integrity": "sha512-VokPN3TSctKj65cyCNPaUh4vMFA8awxOot/0sp+4J7ZlNRKQEhXhawqPwajoi8H5ZFt61i0ugZJuTKXBjGJ17Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.0.tgz", + "integrity": "sha512-DxH0P3wxm+Yzs/p3zrk9dw1rURu8p0Nv5+MRK/L7OtnLNg5rLZraSBFZ8iUXOd9f2BlhJyEpIZUH/emjq4UJ4g==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.0.tgz", + "integrity": "sha512-T6ZvMNe84kAz6TBWHC7hGAoEtzP1LWYw/AqayGWEF6uISt3Abk/st06LqRD9THd7Xz3NxzurUpzAuEAUbZf+nw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.0.tgz", + "integrity": "sha512-q/4hzvQkDs8b4jIBab1pnLiiM0ayTZsN2amBFPDzuyZxjEd4wDwx0UJFYM3cOZzSf5Kw8fnWSprJzIBMkcR44Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.0.tgz", + "integrity": "sha512-vvYWX3akdEAY6km+9wAqFDnk6pQsbJKVnj7xawcvs/+fdlYBGp+U+Qq/lLfpIxYIZvZLHMAKD9HLdacSx/r3dw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.0.tgz", + "integrity": "sha512-DePa5cqOxDP/Zp0VOXpeWaGew5iIv5DXp9NYbzkX5PFQyWVX9184WCTh3hvr/7lhXo8ZVlbFLkz8+o/q1dU6gA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.0.tgz", + "integrity": "sha512-LV8aWMB8UChglMCEzs7RkN0GsH29RJaLLqwm9fCIjlqwxQTiWAqNcc7wjBkH31hV0PU/yVxGYvrYsgfea2qw6g==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.0.tgz", + "integrity": "sha512-QoNSnwQtaeNu5grdBbsL0tt1uyl5EnS8DA8Mr3nluMXbhdQNyhN+G4tBax7VCdxLKj8YJ0/4OO9Ho84jMnJtKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.0.tgz", + "integrity": "sha512-/zZp5MKapIIApE8trN8qLGNSiRN9TUoaUZ1cmVu4XnVdd5LQLOXTtyi+vtfUbNnT3iyjzpPqYeKXmvJ+gJGYWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.0.tgz", + "integrity": "sha512-RbrzcD3aJ1k3UbtMRRBNwojdVVyXjuVAFTfn/xPa6EEl6GE9Sm/akPgFTb9aAC9pMKGJ6CtWxaGrqWcabH+ySg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.0.tgz", + "integrity": "sha512-ZF+onDsBso8PJf1XaG9lB+O9RnBpKGnY6OrzC4CSHrtC1jb6jWLTKK4bRqdoCXHd22gyr2hiYmEAm8Wns/BOCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.0.tgz", + "integrity": "sha512-Atk0aSIk5Zx2Wuh9dgRQgLP0Koc8hOeYpbWryMXyk8G8/HmPkwPPkMqIIDhrXHHYqfUzSJA/I7IWSBv8xSmRBA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.0.tgz", + "integrity": "sha512-0uMOcf3eZ5K+K4cYHkdxShFMPlPXCOdfDFEFn9dNYAEEd2cVvmOfH7zFgRVoDgmtQ1m9k5q7qfrHzyMAubKYUA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.0.tgz", + "integrity": "sha512-mvFtE4A/t/7hRJ7X8Ozmu8FsIkAUat2nzl12pgU337BRmq87AQUJztwHz2Zv5/tjo9/C95E66CK03SI/ToEDJw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.0.tgz", + "integrity": "sha512-z9b9+aTxvt8n2rNltMPvyaUfB8NJ+CVyOrGK/MdIKHx7B+lXmZpm/XbRsU7Rpf3fRqJ2uS6mBJiJveCtq8LHDg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.0.tgz", + "integrity": "sha512-jXaXFqKMehsOc+g8R6oo33RRC6w07G9jDBxAE5eAKX7mOcCbZloYIPNhfG9Wl+P9O9IWHFO4OJgPi1Ml2qkt7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.0.tgz", + "integrity": "sha512-OXNWVFocS2IA4+QplhTZZ2a+8hPZR7T8KuozsNmJKK8y7cp83StHvGksfHzPG3wczWTczyWHVQuqeiTUbjiyBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.0.tgz", + "integrity": "sha512-AlAbNtBO637LxSldqV43z0FfXoGfl2TW1DgAg/bs7aQswFbDewz2SJm3BUhiGfbOVtW571xbc9p+REdxhyN/Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.0.tgz", + "integrity": "sha512-QRSrQXyJ1M4tjNXdR0/G/IgV6lzfQQJYBjlWIEYkY2Xs86DRl/iEpQ4blMDjJxSl7n19eDKKXMg0AmuBVYy8pQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.0.tgz", + "integrity": "sha512-tkuFxhvKO/HlGd0VsINF6vHSYH8AF8W0TcNxKDK6JZmrehngFj78pToc8iemtnvwilDjs2G/qSzYFhe9U8q+fw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@simonwep/pickr": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@simonwep/pickr/-/pickr-1.8.2.tgz", + "integrity": "sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==", + "license": "MIT", + "dependencies": { + "core-js": "^3.15.1", + "nanopop": "^2.1.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vant/popperjs": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vant/popperjs/-/popperjs-1.3.0.tgz", + "integrity": "sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==", + "license": "MIT" + }, + "node_modules/@vant/use": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vant/use/-/use-1.6.0.tgz", + "integrity": "sha512-PHHxeAASgiOpSmMjceweIrv2AxDZIkWXyaczksMoWvKV2YAYEhoizRuk/xFnKF+emUIi46TsQ+rvlm/t2BBCfA==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz", + "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.15" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz", + "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz", + "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.35.tgz", + "integrity": "sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/shared": "3.5.35", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.35.tgz", + "integrity": "sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.35.tgz", + "integrity": "sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/compiler-core": "3.5.35", + "@vue/compiler-dom": "3.5.35", + "@vue/compiler-ssr": "3.5.35", + "@vue/shared": "3.5.35", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.35.tgz", + "integrity": "sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/language-core": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz", + "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.35.tgz", + "integrity": "sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.35.tgz", + "integrity": "sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.35.tgz", + "integrity": "sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.35", + "@vue/runtime-core": "3.5.35", + "@vue/shared": "3.5.35", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.35.tgz", + "integrity": "sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.35", + "@vue/shared": "3.5.35" + }, + "peerDependencies": { + "vue": "3.5.35" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.35.tgz", + "integrity": "sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.7.0.tgz", + "integrity": "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "5.x", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ant-design-vue": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-4.2.6.tgz", + "integrity": "sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-vue": "^7.0.0", + "@babel/runtime": "^7.10.5", + "@ctrl/tinycolor": "^3.5.0", + "@emotion/hash": "^0.9.0", + "@emotion/unitless": "^0.8.0", + "@simonwep/pickr": "~1.8.0", + "array-tree-filter": "^2.1.0", + "async-validator": "^4.0.0", + "csstype": "^3.1.1", + "dayjs": "^1.10.5", + "dom-align": "^1.12.1", + "dom-scroll-into-view": "^2.0.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.15", + "resize-observer-polyfill": "^1.5.1", + "scroll-into-view-if-needed": "^2.2.25", + "shallow-equal": "^1.0.0", + "stylis": "^4.1.3", + "throttle-debounce": "^5.0.0", + "vue-types": "^3.0.0", + "warning": "^4.0.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design-vue" + }, + "peerDependencies": { + "vue": ">=3.2.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/array-tree-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz", + "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==", + "license": "MIT" + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/core-js": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dom-align": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.4.tgz", + "integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==", + "license": "MIT" + }, + "node_modules/dom-scroll-into-view": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz", + "integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/less": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/less/-/less-4.6.4.tgz", + "integrity": "sha512-OJmO5+HxZLLw0RLzkqaNHzcgEAQG7C0y3aMbwtCzIUFZsLMNNq/1IdAdHEycQ58CwUO3jPTHmoN+tE5I7FQxNg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "copy-anything": "^3.0.5", + "parse-node-version": "^1.0.1" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/linkify-it": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", + "integrity": "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loose-envify/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/markdown-it": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", + "integrity": "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.1", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanopop": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/nanopop/-/nanopop-2.4.2.tgz", + "integrity": "sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==", + "license": "MIT" + }, + "node_modules/needle": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.5.0.tgz", + "integrity": "sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.0.tgz", + "integrity": "sha512-T9mWdbWfQtp0B5lv/HX+wrhYsmXRlcWnXXmJbXqKJhlRaoS6KMhq0gpyzW4UJfclcxrEdLnTgjT2NjruLONu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.0", + "@rollup/rollup-android-arm64": "4.61.0", + "@rollup/rollup-darwin-arm64": "4.61.0", + "@rollup/rollup-darwin-x64": "4.61.0", + "@rollup/rollup-freebsd-arm64": "4.61.0", + "@rollup/rollup-freebsd-x64": "4.61.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.0", + "@rollup/rollup-linux-arm-musleabihf": "4.61.0", + "@rollup/rollup-linux-arm64-gnu": "4.61.0", + "@rollup/rollup-linux-arm64-musl": "4.61.0", + "@rollup/rollup-linux-loong64-gnu": "4.61.0", + "@rollup/rollup-linux-loong64-musl": "4.61.0", + "@rollup/rollup-linux-ppc64-gnu": "4.61.0", + "@rollup/rollup-linux-ppc64-musl": "4.61.0", + "@rollup/rollup-linux-riscv64-gnu": "4.61.0", + "@rollup/rollup-linux-riscv64-musl": "4.61.0", + "@rollup/rollup-linux-s390x-gnu": "4.61.0", + "@rollup/rollup-linux-x64-gnu": "4.61.0", + "@rollup/rollup-linux-x64-musl": "4.61.0", + "@rollup/rollup-openbsd-x64": "4.61.0", + "@rollup/rollup-openharmony-arm64": "4.61.0", + "@rollup/rollup-win32-arm64-msvc": "4.61.0", + "@rollup/rollup-win32-ia32-msvc": "4.61.0", + "@rollup/rollup-win32-x64-gnu": "4.61.0", + "@rollup/rollup-win32-x64-msvc": "4.61.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.31", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", + "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^1.0.20" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unimport": { + "version": "3.14.6", + "resolved": "https://registry.npmjs.org/unimport/-/unimport-3.14.6.tgz", + "integrity": "sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.4", + "acorn": "^8.14.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "fast-glob": "^3.3.3", + "local-pkg": "^1.0.0", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "pathe": "^2.0.1", + "picomatch": "^4.0.2", + "pkg-types": "^1.3.0", + "scule": "^1.3.0", + "strip-literal": "^2.1.1", + "unplugin": "^1.16.1" + } + }, + "node_modules/unimport/node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unimport/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/unimport/node_modules/local-pkg": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unimport/node_modules/local-pkg/node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, + "node_modules/unimport/node_modules/unplugin": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-auto-import": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.19.0.tgz", + "integrity": "sha512-W97gTDEWu/L1EcKCXY5Ni8bsMW1E9kv12wYQv3mYpd7zcFctXYlLKsqeva6sbCQbzS8t9AG/XdU5/WkEJKPlFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.3", + "local-pkg": "^0.5.1", + "magic-string": "^0.30.15", + "picomatch": "^4.0.2", + "unimport": "^3.14.5", + "unplugin": "^2.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^3.2.2", + "@vueuse/core": "*" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@vueuse/core": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-components": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.28.0.tgz", + "integrity": "sha512-jiTGtJ3JsRFBjgvyilfrX7yUoGKScFgbdNw+6p6kEXU+Spf/rhxzgvdfuMcvhCcLmflB/dY3pGQshYBVGOUx7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.4", + "chokidar": "^3.6.0", + "debug": "^4.4.0", + "fast-glob": "^3.3.2", + "local-pkg": "^0.5.1", + "magic-string": "^0.30.15", + "minimatch": "^9.0.5", + "mlly": "^1.7.3", + "unplugin": "^2.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vant": { + "version": "4.9.24", + "resolved": "https://registry.npmjs.org/vant/-/vant-4.9.24.tgz", + "integrity": "sha512-tP1A7Vjzv1/B1ljb95Jhv9Q9w6acaaZDJvy6wcKrwGgY0gQZlg+FXLZH/AIKZBE3xvYGDUsv/M7AuGcr/Pqd6A==", + "license": "MIT", + "dependencies": { + "@vant/popperjs": "^1.3.0", + "@vant/use": "^1.6.0", + "@vue/shared": "^3.5.31" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.35.tgz", + "integrity": "sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.35", + "@vue/compiler-sfc": "3.5.35", + "@vue/runtime-dom": "3.5.35", + "@vue/server-renderer": "3.5.35", + "@vue/shared": "3.5.35" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-tsc": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz", + "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/vue-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/vue-types/-/vue-types-3.0.2.tgz", + "integrity": "sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==", + "license": "MIT", + "dependencies": { + "is-plain-object": "3.0.1" + }, + "engines": { + "node": ">=10.15.0" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..789ed4b --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "yx-platform-prototype", + "private": true, + "version": "0.1.0", + "type": "module", + "description": "智养餐饮平台V3 - 高保真交互原型(Vue 工程版)", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "dependencies": { + "ant-design-vue": "^4.2.6", + "dayjs": "^1.11.13", + "markdown-it": "^14.2.0", + "vant": "^4.9.14", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@types/markdown-it": "^14.1.2", + "@types/node": "^22.10.5", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/tsconfig": "^0.7.0", + "less": "^4.2.1", + "typescript": "~5.6.3", + "unplugin-auto-import": "^0.19.0", + "unplugin-vue-components": "^0.28.0", + "vite": "^6.0.7", + "vue-tsc": "^2.2.0" + }, + "allowScripts": { + "esbuild@0.25.12": true + } +} diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..15565d2 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/components/AppPlaceholder.vue b/src/components/AppPlaceholder.vue new file mode 100644 index 0000000..4b21e1f --- /dev/null +++ b/src/components/AppPlaceholder.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/src/components/FilterBar.vue b/src/components/FilterBar.vue new file mode 100644 index 0000000..c7dd742 --- /dev/null +++ b/src/components/FilterBar.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/src/components/PageHeader.vue b/src/components/PageHeader.vue new file mode 100644 index 0000000..e367f40 --- /dev/null +++ b/src/components/PageHeader.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/src/components/PlaceholderPage.vue b/src/components/PlaceholderPage.vue new file mode 100644 index 0000000..b5ef7b2 --- /dev/null +++ b/src/components/PlaceholderPage.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/src/components/StatCard.vue b/src/components/StatCard.vue new file mode 100644 index 0000000..c4f0d60 --- /dev/null +++ b/src/components/StatCard.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/src/components/TableCard.vue b/src/components/TableCard.vue new file mode 100644 index 0000000..0b54a4f --- /dev/null +++ b/src/components/TableCard.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/src/composables/usePageHistory.ts b/src/composables/usePageHistory.ts new file mode 100644 index 0000000..0a9ff12 --- /dev/null +++ b/src/composables/usePageHistory.ts @@ -0,0 +1,113 @@ +/** + * 页面访问历史(最近打开的菜单 Tabs) + * - 对齐生产 vue 项目 D:\Work\platform-vue-tenant\apps\subNutritionManagement\src\store\pageHistory + * - 实现方式:composable + localStorage(项目不引入 Pinia) + * - 作用域:每个 portal 独立维护一份历史(key: pageHistory.) + */ + +import type { Router } from 'vue-router' + +/** 单个历史 Tab */ +export interface PageHistoryItem { + /** 路由完整路径(包含 query 仅基础路径) */ + path: string + /** 菜单名(用于 Tab 标签) */ + title: string + /** 所属应用 key */ + app?: string + /** 所属一级菜单 key */ + section?: string + /** 叶子 key */ + leafKey?: string +} + +const STORAGE_KEY_PREFIX = 'pageHistory.' + +const readFromStorage = (portalKey: string): PageHistoryItem[] => { + try { + const raw = localStorage.getItem(STORAGE_KEY_PREFIX + portalKey) + return raw ? (JSON.parse(raw) as PageHistoryItem[]) : [] + } catch { + return [] + } +} + +const writeToStorage = (portalKey: string, list: PageHistoryItem[]): void => { + localStorage.setItem(STORAGE_KEY_PREFIX + portalKey, JSON.stringify(list)) +} + +/** 各 portal 的响应式历史列表 */ +const stores = new Map>>() + +const getStore = (portalKey: string) => { + let s = stores.get(portalKey) + if (!s) { + s = ref(readFromStorage(portalKey)) + stores.set(portalKey, s) + } + return s +} + +/** + * 使用页面历史(无数量上限,对齐 vue 项目) + * @param portalKey 'admin-portal' | 'tenant-portal' 等 + */ +export const usePageHistory = (portalKey: string) => { + const list = getStore(portalKey) + + /** 新增页面(已存在则前移到末尾) */ + const addPage = (item: PageHistoryItem): void => { + const arr = (list.value ?? []).filter(i => i.path !== item.path) + arr.push(item) + list.value = arr + writeToStorage(portalKey, arr) + } + + /** 移除指定路径 */ + const removePage = (path: string): string | null => { + const arr = list.value ?? [] + const idx = arr.findIndex(i => i.path === path) + if (idx === -1) return null + arr.splice(idx, 1) + list.value = [...arr] + writeToStorage(portalKey, list.value) + // 返回下一个应跳转的路径(关闭当前页时用) + return list.value[list.value.length - 1]?.path ?? null + } + + /** 关闭除指定路径外的所有 */ + const closeOthers = (path: string): void => { + list.value = (list.value ?? []).filter(i => i.path === path) + writeToStorage(portalKey, list.value) + } + + /** 清空 */ + const clear = (): void => { + list.value = [] + writeToStorage(portalKey, []) + } + + return { list, addPage, removePage, closeOthers, clear } +} + +/** + * 在 router.afterEach 钩子里自动追踪某个 portal 的访问 + * - 仅当路由 meta.portal 匹配时记录 + * - 忽略应用网格首页 / 重定向路由 + */ +export const installPageHistoryTracker = (router: Router): void => { + router.afterEach((to) => { + const portal = to.meta?.portal as string | undefined + if (!portal) return + if (to.meta?.isAppGrid) return + if (!to.name) return + const { addPage } = usePageHistory(portal) + addPage({ + path: to.path, + title: String(to.meta?.label ?? to.name ?? to.path), + app: to.meta?.app as string | undefined, + section: to.meta?.section as string | undefined, + leafKey: String(to.name), + }) + }) +} diff --git a/src/composables/useVisibility.ts b/src/composables/useVisibility.ts new file mode 100644 index 0000000..abd4212 --- /dev/null +++ b/src/composables/useVisibility.ts @@ -0,0 +1,210 @@ +/** + * 可见性控制 + * - 当前角色:'designer'(产品/设计,全部可见) | 'dev'(研发,过滤 draft / designer-only) + * - 角色来自门禁页登录态(localStorage: gateway.role);默认 'designer' + * - 控制台可对单页面覆写 status / audience(localStorage: visibility.overrides) + * - 过滤规则: + * dev 不能看到 status='draft' 或 audience='designer' 的页面 + * designer 始终全部可见 + * + * 层级(与 nav.ts 对齐): + * NavApp -> NavSection -> (NavGroup | NavLeaf) -> NavLeaf + */ + +import { + isNavGroup, + type NavLeaf, + type NavGroup, + type NavSection, + type NavApp, + type NavMiniprogramTab, + type NavHardwareDevice, +} from '@meta/nav' + +/** 用户角色(来自门禁页) */ +export type Role = 'designer' | 'dev' + +/** 单页面可见性覆写记录 */ +export interface VisibilityOverride { + status?: 'draft' | 'review' | 'ready' + audience?: 'designer' | 'all' +} + +/** 控制台覆写集合(key = NavLeaf.key) */ +export type VisibilityOverrides = Record + +const ROLE_KEY = 'gateway.role' +const ROLE_EXPIRE_KEY = 'gateway.expireAt' +const OVERRIDES_KEY = 'visibility.overrides' + +/** 默认 7 天有效期(毫秒) */ +export const ROLE_TTL_MS = 7 * 24 * 60 * 60 * 1000 + +// ============================== 角色读写 ============================== + +/** 读取当前角色(含过期校验) */ +export const readCurrentRole = (): Role | null => { + const role = localStorage.getItem(ROLE_KEY) as Role | null + const expireAt = Number(localStorage.getItem(ROLE_EXPIRE_KEY) ?? 0) + if (!role || !expireAt) return null + if (Date.now() > expireAt) { + clearRole() + return null + } + return role +} + +/** 写入角色 + 7 天有效期 */ +export const writeRole = (role: Role): void => { + localStorage.setItem(ROLE_KEY, role) + localStorage.setItem(ROLE_EXPIRE_KEY, String(Date.now() + ROLE_TTL_MS)) +} + +/** 清除角色(登出 / 过期) */ +export const clearRole = (): void => { + localStorage.removeItem(ROLE_KEY) + localStorage.removeItem(ROLE_EXPIRE_KEY) +} + +/** 读取过期时间戳(用于显示剩余时间) */ +export const readRoleExpireAt = (): number | null => { + const ts = Number(localStorage.getItem(ROLE_EXPIRE_KEY) ?? 0) + return ts > 0 ? ts : null +} + +// ============================== 覆写读写 ============================== + +/** 读取控制台覆写集合 */ +export const readOverrides = (): VisibilityOverrides => { + try { + const raw = localStorage.getItem(OVERRIDES_KEY) + return raw ? JSON.parse(raw) as VisibilityOverrides : {} + } catch { + return {} + } +} + +/** 写入控制台覆写集合 */ +export const writeOverrides = (overrides: VisibilityOverrides): void => { + localStorage.setItem(OVERRIDES_KEY, JSON.stringify(overrides)) +} + +/** 更新单个页面的覆写(partial 合并) */ +export const setOverride = (leafKey: string, patch: VisibilityOverride): void => { + const all = readOverrides() + const next = { ...all[leafKey], ...patch } + if (next.status === undefined && next.audience === undefined) { + delete all[leafKey] + } else { + all[leafKey] = next + } + writeOverrides(all) +} + +/** 清除所有覆写 */ +export const clearOverrides = (): void => { + localStorage.removeItem(OVERRIDES_KEY) +} + +// ============================== 过滤核心 ============================== + +/** 合并 NavLeaf 默认值 + 控制台覆写 → 有效 status/audience */ +const effective = (leaf: NavLeaf, overrides: VisibilityOverrides): Required> => { + const ovr = overrides[leaf.key] ?? {} + return { + status: ovr.status ?? leaf.status ?? 'ready', + audience: ovr.audience ?? leaf.audience ?? 'all', + } +} + +/** 判断单个叶子节点对当前角色是否可见 */ +export const isLeafVisible = (leaf: NavLeaf, role: Role, overrides: VisibilityOverrides): boolean => { + if (role === 'designer') return true + const { status, audience } = effective(leaf, overrides) + if (audience === 'designer') return false + if (status === 'draft') return false + return true +} + +/** 过滤分组 children,移除研发不可见的叶子;若分组空则丢弃 */ +const filterGroup = (group: NavGroup, role: Role, overrides: VisibilityOverrides): NavGroup | null => { + const children = group.children.filter(l => isLeafVisible(l, role, overrides)) + if (children.length === 0) return null + return { ...group, children } +} + +/** 过滤应用一级菜单(NavSection)的 children;若 section 空则丢弃 */ +export const filterSection = (section: NavSection, role: Role, overrides: VisibilityOverrides): NavSection | null => { + const children = section.children + .map((item): NavLeaf | NavGroup | null => { + if (isNavGroup(item)) return filterGroup(item, role, overrides) + return isLeafVisible(item, role, overrides) ? item : null + }) + .filter((x): x is NavLeaf | NavGroup => x !== null) + if (children.length === 0) return null + return { ...section, children } +} + +/** 过滤一个应用:过滤其 sections,sections 都空则该应用整个不可见 */ +export const filterApp = (app: NavApp, role: Role, overrides: VisibilityOverrides): NavApp | null => { + const children = app.children + .map(s => filterSection(s, role, overrides)) + .filter((s): s is NavSection => s !== null) + if (children.length === 0) return null + return { ...app, children } +} + +/** 过滤应用列表 */ +export const filterApps = (apps: NavApp[], role: Role, overrides: VisibilityOverrides): NavApp[] => { + return apps.map(a => filterApp(a, role, overrides)).filter((a): a is NavApp => a !== null) +} + +/** 过滤小程序 tabs:tab 的 page 不可见则整个 tab 丢弃 */ +export const filterMiniprogramTabs = (tabs: NavMiniprogramTab[], role: Role, overrides: VisibilityOverrides): NavMiniprogramTab[] => { + return tabs.filter(t => isLeafVisible(t.page, role, overrides)) +} + +/** 过滤硬件设备 */ +export const filterHardwareDevices = (devices: NavHardwareDevice[], role: Role, overrides: VisibilityOverrides): NavHardwareDevice[] => { + return devices + .map(d => ({ ...d, children: d.children.filter(l => isLeafVisible(l, role, overrides)) })) + .filter(d => d.children.length > 0) +} + +// ============================== Vue 集成 ============================== + +/** 响应式角色 + 覆写(跨组件共享) */ +const roleRef = ref(readCurrentRole()) +const overridesRef = ref(readOverrides()) + +/** 角色发生变化时通知(登录 / 登出) */ +export const setRole = (role: Role | null): void => { + if (role === null) { + clearRole() + } else { + writeRole(role) + } + roleRef.value = role +} + +/** 覆写发生变化时通知(控制台编辑) */ +export const updateOverride = (leafKey: string, patch: VisibilityOverride): void => { + setOverride(leafKey, patch) + overridesRef.value = readOverrides() +} + +export const resetOverrides = (): void => { + clearOverrides() + overridesRef.value = {} +} + +/** Composable 入口 */ +export const useVisibility = () => { + return { + role: roleRef, + overrides: overridesRef, + setRole, + updateOverride, + resetOverrides, + } +} diff --git a/src/docs/business-architecture.md b/src/docs/business-architecture.md new file mode 100644 index 0000000..de9b9f1 --- /dev/null +++ b/src/docs/business-architecture.md @@ -0,0 +1,119 @@ +# 业务架构 + +> **作用**:这份文档供 AI 在生成原型时参考,确保理解业务上下文、不串端、不重复造功能。 +> **维护规则**:用户在规划每个端的具体功能模块时填写本文档,AI 在 new-page / gen-prd / cross-role-doc-builder 等任务中**必须先读这份文档**。 +> _Last updated: 2026-06-04 / 阶段 16(用户提供四端粗粒度功能清单)_ + +--- + +## 一、四个终端 + +### 1.1 综合管理后台 `admin-portal` + +| 子端 | 一级模块 | 说明 | +|------|---------|------| +| 系统设置 | (待规划) | 平台层基础设置:组织、账号、权限、字典、参数 | +| 运营中心 | (待规划) | 平台运营视角:租户管理、应用配置、计费、统计 | +| 设备中心 | (待规划) | 跨租户硬件接入与运维:设备注册、固件版本、在线状态 | + +> 综合后台是**跨租户**视角;与租户运营后台的边界:凡是只能在租户内部生效的业务(如某租户的食堂菜单),归属租户后台。 + +### 1.2 租户运营后台 `tenant-portal` + +| 一级模块 | 说明 | +|---------|------| +| 租户首页 | 单租户运营视角的首页:核心 KPI、待办、快捷入口 | +| 控制台 | 租户运营的控制中心:组织架构、岗位、人员、流程 | +| 系统管理 | 租户级系统管理:账号、角色、字典、日志、消息模板 | +| 营养管理 | 食堂全链路:种植、采购、加工、菜品、食谱、订餐、出餐、质检 | +| 健康体检 | 体检计划、机构、项目、报告、统计 | +| 体重管理 | 体重档案、采集、目标、干预、提醒 | +| 运动管理 | 运动计划、器械、记录、统计、激励 | +| 健康监测 | 穿戴 / 体重秤 / 体脂仪 / 环境监测(室内/室外/水/花粉) | +| 健康评估 | 综合体检评估、风险分级、健康画像 | +| 知识普及 | 健康科普文章、视频、问答 | +| 健康数据 | 各类健康数据接入、清洗、查询 | +| 健康档案 | 个人 / 单位 健康档案聚合视图 | +| 心脑血管病预防 | 高危识别 / 干预方案 / 跟踪 | +| 糖尿病预防 | 同上(糖尿病专题) | +| 癌症预防 | 同上(癌症专题) | +| 专家咨询 | 视频问诊 + 小助手对话 + 评价 + 档案 | +| 应急就医 | 应急工单 / 大病就医 / 派单 / 救助 | +| 一线医疗 | 驻场医务工作站:日常诊疗 / 处置记录 / 转诊 | + +### 1.3 C 端微信小程序 `miniprogram` + +四个底部 Tab: + +| Tab | 含义 | 候选功能 | +|-----|------|---------| +| 吃 | 营养与订餐 | 营养首页、今日食谱、菜品详情、订餐、订单确认、购物车、就餐记录 | +| 问 | AI 助手 / 咨询 | AI 营养助手、AI 对话、历史对话、人工咨询入口 | +| 做 | 创作与社区 | 社区首页、照做食谱、创作食谱、排行、分享、热门、我的创作 | +| 我 | 个人中心 | 个人首页、健康档案、订单、收藏、余额、会员、消息、设置、扫码溯源 | + +### 1.4 硬件终端屏 `hardware` + +10 类设备,每类独立 viewport,互不影响: + +| 设备 key | 名称 | 典型业务 | 备注 | +|---------|------|---------|------| +| smart-cabinet | 智能货柜 | 取菜 / 出柜 / 库存显示 | 通常竖屏 | +| stall-machine | 档口机 | 食堂档口出餐 / 称重 / 结算 | 横屏 | +| inbound-scale | 入库秤 | 毛菜入库称重 + 录入 | 横屏 | +| nutrition-scale | 营养秤 | 餐线上的营养数据采集 | 横屏 | +| disc-machine | 吐盘机 | 餐盘回收 | 竖屏 | +| cashier | 结算台 | 用户结算 / 扫码 | 横屏 | +| ratio-scale | 配比秤 | 配菜组合称重 | 横屏 | +| ribbon-screen | 带鱼屏 | 长条信息展示(食堂排队 / 菜单) | 超宽横屏 | +| stats-screen | 统计大屏 | 数据可视化大屏 | 1920×1080 / 4K | +| cooking-robot | 炒菜机器人 | 烹饪流程触控屏 | 横屏 | + +> **每台设备的 viewport / 朝向 / 缩放**在 `src/meta/nav.ts` 的 `hardwareDevices` 数组中独立声明。新增设备 = 加一条记录,HardwareLayout 自动按此渲染外壳。 + +--- + +## 二、端隔离原则 + +- 4 端不允许互相 `import` 页面或私有组件 +- 跨端共享只能通过:`@meta`(菜单数据)、`@components`(公共业务组件)、`@layouts`(共享布局)、`@composables` +- 同一业务(如"营养管理")会在多端各自有入口(tenant-portal 后台管 + miniprogram 吃 + hardware 智能货柜),但**各端的页面、字段范围、交互方式独立维护** + +--- + +## 三、用户角色映射 + +> 用户后期完善。当前为占位推断: + +``` +平台管理员 → 综合管理后台(admin-portal) +租户运营人员 → 租户运营后台(tenant-portal) +员工 / 终端用户 → 微信小程序(miniprogram) +食堂工作人员 → 硬件终端屏(智能货柜 / 档口机 / 结算台 / 配比秤 / 营养秤 / 入库秤 / 吐盘机 / 炒菜机器人) +公共空间 → 硬件终端屏(带鱼屏 / 统计大屏) +``` + +--- + +## 四、跨端协作关系 + +> 用 Mermaid 图描述跨端业务数据流向。建议在功能模块逐步落地后补充。 + +示例: + +```mermaid +flowchart LR + mp[小程序 · 吃-订餐] -->|订单| t1[tenant-portal · 订餐订单] + t1 -->|食谱排期| t2[tenant-portal · 加工与出餐] + t2 -->|出餐指令| hw1[hardware · 档口机] + hw1 -->|出餐完成| hw2[hardware · 吐盘机] + hw2 -->|餐盘回收数据| t1 +``` + +--- + +## 五、数据字典约定 + +跨多端复用的字典编码(如单位、部门、岗位、设备类型、菜品分类、营养元素等)由 `admin-portal` 定义;其他端通过 API 拉取使用。 + +具体字典清单见各模块 PRD(用 `/gen-prd` 自动汇总)。 diff --git a/src/layouts/HardwareLayout.vue b/src/layouts/HardwareLayout.vue new file mode 100644 index 0000000..11c5244 --- /dev/null +++ b/src/layouts/HardwareLayout.vue @@ -0,0 +1,257 @@ + + + + + diff --git a/src/layouts/MiniprogramLayout.vue b/src/layouts/MiniprogramLayout.vue new file mode 100644 index 0000000..f3df912 --- /dev/null +++ b/src/layouts/MiniprogramLayout.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/src/layouts/PortalLayout.vue b/src/layouts/PortalLayout.vue new file mode 100644 index 0000000..66d4dc4 --- /dev/null +++ b/src/layouts/PortalLayout.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/src/layouts/components/AppTabBar.vue b/src/layouts/components/AppTabBar.vue new file mode 100644 index 0000000..31504f4 --- /dev/null +++ b/src/layouts/components/AppTabBar.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/src/layouts/components/PageHistory.vue b/src/layouts/components/PageHistory.vue new file mode 100644 index 0000000..0671d13 --- /dev/null +++ b/src/layouts/components/PageHistory.vue @@ -0,0 +1,292 @@ + + + + + diff --git a/src/layouts/components/RoleBadge.vue b/src/layouts/components/RoleBadge.vue new file mode 100644 index 0000000..d16f406 --- /dev/null +++ b/src/layouts/components/RoleBadge.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/src/layouts/components/Sidebar.vue b/src/layouts/components/Sidebar.vue new file mode 100644 index 0000000..e7c8b73 --- /dev/null +++ b/src/layouts/components/Sidebar.vue @@ -0,0 +1,106 @@ + + + + + + + diff --git a/src/layouts/components/TopBar.vue b/src/layouts/components/TopBar.vue new file mode 100644 index 0000000..eb1c802 --- /dev/null +++ b/src/layouts/components/TopBar.vue @@ -0,0 +1,329 @@ + + + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..f256786 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,26 @@ +/** + * 应用入口 + * - 挂载 Vue 实例 + * - 接入 vue-router + * - 引入 AntDV reset 样式 + 全局令牌 + * - 组件库通过 unplugin-vue-components 按需自动注册(见 vite.config.ts) + * - antd icons-vue 全量注册到 app.component,让 nav.ts 中字符串名能动态渲染 + */ +import { createApp } from 'vue' +import App from './App.vue' +import router from '@router/index' +import 'ant-design-vue/dist/reset.css' +import './styles/global.less' +import * as Icons from '@ant-design/icons-vue' + +const app = createApp(App) + +// 全量注册 @ant-design/icons-vue(用于菜单/按钮通过字符串名动态引用) +for (const [name, comp] of Object.entries(Icons)) { + if (typeof comp === 'object' || typeof comp === 'function') { + app.component(name, comp as never) + } +} + +app.use(router) +app.mount('#app') diff --git a/src/meta/nav.ts b/src/meta/nav.ts new file mode 100644 index 0000000..c0ef8ca --- /dev/null +++ b/src/meta/nav.ts @@ -0,0 +1,681 @@ +/** + * 菜单数据单一来源 + * - 同时供 router/routes.ts(生成路由)与 layouts/*(渲染菜单)消费 + * - 新增页面只改这一处,路由 + 菜单同步生效 + * + * 四个终端: + * - admin-portal 综合管理后台(AntDV 后台 UI) + * - tenant-portal 租户运营后台(AntDV 后台 UI;下挂 17 个独立应用) + * - miniprogram C 端微信小程序(Vant,375×812;吃 / 问 / 做 / 我) + * - hardware 硬件终端屏(按设备类型动态 viewport;10 类设备) + * + * 后台菜单层级(对齐生产 vue 项目 D:\Work\platform-vue-tenant): + * Portal (终端) + * └─ NavApp (应用,对应 vue 项目的 subXxx 子应用,可独立部署) + * └─ NavSection (应用内一级菜单,对应顶部横向 tabs) + * └─ NavGroup (二级分组,可选) 或 NavLeaf (二级菜单/页面) + * └─ NavLeaf (三级页面) + * + * 图标规范: + * - NavApp.icon:应用图标,可自定义 emoji + * - NavSection / NavGroup / NavLeaf 的 icon:**必须**使用 ant-design-vue 4.2 内置图标组件名 + * (例:'FileTextOutlined' / 'AppstoreOutlined' / 'BarChartOutlined') + * - 渲染:layouts 用 `` 动态渲染 + */ + +/** 终端类型 */ +export type PortalKey = 'admin-portal' | 'tenant-portal' | 'miniprogram' | 'hardware' + +/** 页面叶子节点(最小可路由单元) */ +export interface NavLeaf { + /** 唯一 key,作为 vue-router name */ + key: string + /** 菜单显示文字 */ + label: string + /** 相对路径片段(不含 / 前缀,由父级拼接) */ + path: string + /** 页面组件懒加载 */ + component: () => Promise + /** 可选图标(AntDV / Vant 图标名 / emoji) */ + icon?: string + /** 设计完成度,影响研发受众可见性 */ + status?: 'draft' | 'review' | 'ready' + /** 受众限制,'designer' 仅设计可见;默认 'all' */ + audience?: 'designer' | 'all' +} + +/** 二级分组(侧边栏 SubMenu,里面装三级叶子) */ +export interface NavGroup { + key: string + label: string + /** 可选图标 */ + icon?: string + children: NavLeaf[] +} + +/** 应用内一级菜单(顶部横向 tab,里面装二级菜单/分组) */ +export interface NavSection { + /** 唯一 key */ + key: string + /** tab 标题 */ + label: string + /** 可选图标 */ + icon?: string + /** 侧边栏内容:分组 或 叶子 */ + children: (NavLeaf | NavGroup)[] +} + +/** + * 应用(独立可部署单元,对应生产 vue 项目的 subXxx) + * - 在租户首页以卡片方式展示 + * - 点击卡片进入应用 → 顶部显示 sections 作为 tabs + */ +export interface NavApp { + /** 唯一 key(同时作为路由路径片段,如 nutrition) */ + key: string + /** 应用名(首页卡片标题 / 顶部应用名) */ + label: string + /** 应用图标(emoji 或图标名) */ + icon?: string + /** 应用描述(首页卡片副标题) */ + description?: string + /** 应用所属分组(首页分类展示,如"预防"/"救治"/"数据") */ + category?: 'prevention' | 'rescue' | 'data' | 'system' + /** 应用内一级菜单(顶部横向 tabs) */ + children: NavSection[] +} + +/** 小程序底部 Tab */ +export interface NavMiniprogramTab { + key: string + label: string + /** Vant 图标名 */ + icon: string + /** Tab 默认显示的页面(首页) */ + page: NavLeaf + /** Tab 内的子页面(如详情页),参与路由但不显示在底部 Tab */ + subPages?: NavLeaf[] +} + +/** 硬件设备的视口规格 */ +export interface NavHardwareViewport { + /** 设备宽度(CSS px) */ + width: number + /** 设备高度(CSS px) */ + height: number + /** 朝向 */ + orientation: 'portrait' | 'landscape' + /** 视口缩放(用于在桌面上预览大屏,0.5 = 缩到一半) */ + scale?: number +} + +/** 硬件设备类型(每种设备有独立 viewport 与页面集) */ +export interface NavHardwareDevice { + key: string + label: string + viewport: NavHardwareViewport + /** 该设备包含的页面 */ + children: NavLeaf[] +} + +// =========================== 工具:占位 / 工厂 =========================== + +/** PlaceholderPage 公共占位组件 */ +const PlaceholderImport = () => import('@components/PlaceholderPage.vue') + +/** 生成占位叶子节点(页面组件统一为 PlaceholderPage) */ +const placeholder = ( + key: string, + label: string, + path: string, + status: NavLeaf['status'] = 'draft', +): NavLeaf => ({ + key, + label, + path, + status, + component: PlaceholderImport, +}) + +/** 简易占位应用工厂:生成只有一个"<应用>首页"占位的应用 */ +const makePlaceholderApp = ( + key: string, + label: string, + icon?: string, + description?: string, + category?: NavApp['category'], +): NavApp => ({ + key, + label, + icon, + description, + category, + children: [ + { + key: `${key}-default`, + label, + children: [ + placeholder(`${key}-overview`, `${label}首页`, 'overview'), + ], + }, + ], +}) + +// =========================== 综合管理后台 =========================== + +/** 综合管理后台应用列表(粗粒度占位) */ +export const adminApps: NavApp[] = [ + makePlaceholderApp('system-settings', '系统设置', '⚙️', '平台级用户、角色、权限、字典', 'system'), + makePlaceholderApp('operation-center', '运营中心', '📊', '租户管理、版本管理、计费', 'system'), + makePlaceholderApp('device-center', '设备中心', '🖥️', '硬件设备注册、监控、运维', 'system'), +] + +// =========================== 租户运营后台 =========================== + +/** + * 营养管理应用(nutrition) + * 7 个一级菜单:溯源总览 / 档案 / 绿色种采 / 卫生供应 / 健康生产 / 营养用餐 / 异常事件 + * 菜单结构迁移自 D:\Work\platform-prototype\web-admin\nutrition(老 html 原型) + */ +const nutritionApp: NavApp = { + key: 'nutrition', + label: '营养管理', + icon: '🥗', + description: '从田间到餐桌的全链路营养与配餐管理', + category: 'prevention', + children: [ + { + key: 'nutrition-trace', + label: '溯源总览', + icon: 'NodeIndexOutlined', + children: [ + placeholder('nutrition-trace-query', '溯源查询', 'query'), + placeholder('nutrition-trace-timeline', '链路可视化', 'timeline'), + ], + }, + { + key: 'nutrition-arc', + label: '档案', + icon: 'FolderOpenOutlined', + children: [ + { key: 'nutrition-arc-employee', label: '员工营养数据', path: 'employee', status: 'ready', + component: () => import('@pages/tenant-portal/nutrition/arc/arcEmployee/arcEmployee.vue') }, + { key: 'nutrition-arc-unit', label: '单位报表数据', path: 'unit', status: 'draft', + component: () => import('@pages/tenant-portal/nutrition/arc/ArcUnit.vue') }, + placeholder('nutrition-arc-canteen', '场所报表数据', 'canteen'), + placeholder('nutrition-arc-algo', '算法模型', 'algo'), + ], + }, + { + key: 'nutrition-farm', + label: '绿色种采', + icon: 'EnvironmentOutlined', + children: [ + { key: 'nutrition-farm-land', label: '土地环境', path: 'land', status: 'draft', + component: () => import('@pages/tenant-portal/nutrition/farm/FarmLand.vue') }, + { + key: 'nutrition-farm-monitor', + label: '种养监控', + icon: 'MonitorOutlined', + children: [ + placeholder('nutrition-farm-monitor-crop', '作物种植监控', 'monitor/crop'), + placeholder('nutrition-farm-monitor-livestock', '畜禽养殖监控', 'monitor/livestock'), + ], + }, + placeholder('nutrition-farm-harvest', '收成入库', 'harvest'), + placeholder('nutrition-farm-supplier', '供商管理', 'supplier'), + placeholder('nutrition-farm-purchase', '采供管理', 'purchase'), + ], + }, + { + key: 'nutrition-sup', + label: '卫生供应', + icon: 'SafetyCertificateOutlined', + children: [ + placeholder('nutrition-sup-hygiene', '卫生检测', 'hygiene-check'), + placeholder('nutrition-sup-warehouse', '仓储管理', 'warehouse'), + placeholder('nutrition-sup-in-raw', '毛菜入库', 'in-raw'), + placeholder('nutrition-sup-in-clean', '净菜加工', 'in-clean'), + placeholder('nutrition-sup-in-cleanin', '净菜入库', 'in-cleanin'), + placeholder('nutrition-sup-pkg-label', '净菜包装', 'pkg-label'), + placeholder('nutrition-sup-clean-internal', '净菜内供', 'clean-internal'), + placeholder('nutrition-sup-clean-external', '净菜外售', 'clean-external'), + ], + }, + { + key: 'nutrition-prod', + label: '健康生产', + icon: 'ExperimentOutlined', + children: [ + placeholder('nutrition-prod-qc-daily', '食安监控', 'qc-daily'), + placeholder('nutrition-prod-recipe-plan', '食谱规划', 'recipe-plan'), + placeholder('nutrition-prod-clean-order', '净菜订单', 'clean-order'), + placeholder('nutrition-prod-cabinet', '净菜进柜', 'cabinet'), + placeholder('nutrition-prod-combo', '净菜组配', 'combo'), + placeholder('nutrition-prod-cook', '按单烹制', 'cook'), + placeholder('nutrition-prod-serve', '上餐监控', 'serve'), + placeholder('nutrition-prod-surplus', '余量处置', 'surplus'), + placeholder('nutrition-prod-stats', '生产统计', 'stats'), + ], + }, + { + key: 'nutrition-din', + label: '营养用餐', + icon: 'CoffeeOutlined', + children: [ + placeholder('nutrition-din-canteen', '场所管理', 'canteen'), + placeholder('nutrition-din-dict', '字典管理', 'dict'), + placeholder('nutrition-din-qc-nutrition', '营养检验', 'qc-nutrition'), + placeholder('nutrition-din-ingredient', '食物成分', 'ingredient'), + placeholder('nutrition-din-dish-all', '餐品总库', 'dish-all'), + { + key: 'nutrition-din-algo', + label: '算法标准', + icon: 'CalculatorOutlined', + children: [ + placeholder('nutrition-din-algo-population', '人群基础标准', 'algo/population'), + placeholder('nutrition-din-algo-vip', 'VIP定制标准', 'algo/vip'), + ], + }, + placeholder('nutrition-din-recipe', '食谱管理', 'recipe'), + placeholder('nutrition-din-order-meal', '营养点餐', 'order-meal'), + placeholder('nutrition-din-user', '用户管理', 'user'), + placeholder('nutrition-din-order-line', '餐线管理', 'order-line'), + placeholder('nutrition-din-order-pickup', '取餐管理', 'order-pickup'), + placeholder('nutrition-din-consume', '消费管理', 'consume'), + placeholder('nutrition-din-data-nutri', '营养管理', 'data-nutri'), + placeholder('nutrition-din-data-benefit', '餐材效益', 'data-benefit'), + placeholder('nutrition-din-terminal', '终端管理', 'terminal'), + { + key: 'nutrition-din-model', + label: '模型管理', + icon: 'RobotOutlined', + children: [ + placeholder('nutrition-din-model-llm', '大模型管理', 'model/llm'), + placeholder('nutrition-din-model-small', '小模型管理', 'model/small'), + ], + }, + placeholder('nutrition-din-innovation', '味养创新', 'innovation'), + ], + }, + { + key: 'nutrition-ano', + label: '异常事件', + icon: 'WarningOutlined', + children: [ + placeholder('nutrition-ano-dirty', '系统脏数据', 'dirty'), + placeholder('nutrition-ano-result', '结果异常', 'result'), + ], + }, + ], +} + +/** + * 健康监测应用(monitor) + * 6 个一级菜单:档案 / 健康排查 / 穿戴监测 / 体重监测 / 环境监测 / 异常事件 + * 菜单结构迁移自 D:\Work\platform-prototype\web-admin\health-monitor(menu_parsed.txt) + */ +const monitorApp: NavApp = { + key: 'monitor', + label: '健康监测', + icon: '⌚', + description: '人员排查、穿戴、体重、环境多维健康监测', + category: 'prevention', + children: [ + { + key: 'monitor-archive', + label: '档案', + icon: 'FolderOpenOutlined', + children: [ + placeholder('monitor-archive-personal', '个人监测数据', 'personal'), + placeholder('monitor-archive-unit', '单位报表数据', 'unit'), + placeholder('monitor-archive-algo', '算法模型', 'algo'), + ], + }, + { + key: 'monitor-checkup', + label: '健康排查', + icon: 'FileSearchOutlined', + children: [ + { + key: 'monitor-checkup-survey', + label: '排查问卷', + icon: 'FormOutlined', + children: [ + placeholder('monitor-checkup-survey-category', '问题分类', 'survey/category'), + placeholder('monitor-checkup-survey-score', '积分规则', 'survey/score'), + placeholder('monitor-checkup-survey-template', '模板信息', 'survey/template'), + placeholder('monitor-checkup-survey-question', '问题管理', 'survey/question'), + ], + }, + placeholder('monitor-checkup-maxim', '格言管理', 'maxim'), + { + key: 'monitor-checkup-record', + label: '排查记录', + icon: 'SolutionOutlined', + children: [ + placeholder('monitor-checkup-record-user', '用户排查', 'record/user'), + placeholder('monitor-checkup-record-dept', '部门排查', 'record/dept'), + ], + }, + ], + }, + { + key: 'monitor-wearable', + label: '穿戴监测', + icon: 'HeartOutlined', + children: [ + placeholder('monitor-wearable-tool', '工具管理', 'tool'), + placeholder('monitor-wearable-bind', '绑定记录', 'bind'), + placeholder('monitor-wearable-user-data', '用户数据', 'user-data'), + placeholder('monitor-wearable-exception', '异常事件', 'exception'), + placeholder('monitor-wearable-notify', '通知消息', 'notify'), + placeholder('monitor-wearable-dept-stat', '部门数据统计', 'dept-stat'), + placeholder('monitor-wearable-personal-stat', '个人数据统计', 'personal-stat'), + ], + }, + { + key: 'monitor-weight', + label: '体重监测', + icon: 'DashboardOutlined', + children: [ + { + key: 'monitor-weight-scale', + label: '体重秤', + icon: 'ColumnHeightOutlined', + children: [ + placeholder('monitor-weight-scale-device', '设备管理', 'scale/device'), + placeholder('monitor-weight-scale-monitor', '设备监测', 'scale/monitor'), + placeholder('monitor-weight-scale-stat', '设备统计', 'scale/stat'), + ], + }, + { + key: 'monitor-weight-bluetooth', + label: '蓝牙称', + icon: 'BulbOutlined', + children: [ + placeholder('monitor-weight-bluetooth-device', '设备管理', 'bluetooth/device'), + placeholder('monitor-weight-bluetooth-monitor', '设备监测', 'bluetooth/monitor'), + placeholder('monitor-weight-bluetooth-stat', '设备统计', 'bluetooth/stat'), + ], + }, + { + key: 'monitor-weight-fat', + label: '体脂仪', + icon: 'RadarChartOutlined', + children: [ + placeholder('monitor-weight-fat-device', '设备管理', 'fat/device'), + placeholder('monitor-weight-fat-monitor', '设备监测', 'fat/monitor'), + placeholder('monitor-weight-fat-stat', '设备统计', 'fat/stat'), + ], + }, + { + key: 'monitor-weight-sport', + label: '运动器械', + icon: 'ThunderboltOutlined', + children: [ + placeholder('monitor-weight-sport-device', '设备管理', 'sport/device'), + placeholder('monitor-weight-sport-monitor', '设备监测', 'sport/monitor'), + placeholder('monitor-weight-sport-stat', '设备统计', 'sport/stat'), + ], + }, + placeholder('monitor-weight-employee', '员工数据', 'employee'), + ], + }, + { + key: 'monitor-env', + label: '环境监测', + icon: 'CloudOutlined', + children: [ + { + key: 'monitor-env-indoor', + label: '室内空气', + icon: 'HomeOutlined', + children: [ + placeholder('monitor-env-indoor-device-list', '设备列表', 'indoor/device-list'), + placeholder('monitor-env-indoor-device-category', '设备分类', 'indoor/device-category'), + placeholder('monitor-env-indoor-data-monitor', '数据监测', 'indoor/data-monitor'), + placeholder('monitor-env-indoor-exception-alarm', '异常报警', 'indoor/exception-alarm'), + placeholder('monitor-env-indoor-app-record', '应用记录', 'indoor/app-record'), + placeholder('monitor-env-indoor-history-data', '历史数据', 'indoor/history-data'), + ], + }, + { + key: 'monitor-env-outdoor', + label: '室外空气', + icon: 'GlobalOutlined', + children: [ + placeholder('monitor-env-outdoor-device-list', '设备列表', 'outdoor/device-list'), + placeholder('monitor-env-outdoor-device-category', '设备分类', 'outdoor/device-category'), + placeholder('monitor-env-outdoor-data-monitor', '数据监测', 'outdoor/data-monitor'), + placeholder('monitor-env-outdoor-exception-alarm', '异常报警', 'outdoor/exception-alarm'), + placeholder('monitor-env-outdoor-app-record', '应用记录', 'outdoor/app-record'), + placeholder('monitor-env-outdoor-history-data', '历史数据', 'outdoor/history-data'), + ], + }, + { + key: 'monitor-env-water', + label: '水质监测', + icon: 'CloudDownloadOutlined', + children: [ + placeholder('monitor-env-water-device-list', '设备列表', 'water/device-list'), + placeholder('monitor-env-water-device-category', '设备分类', 'water/device-category'), + placeholder('monitor-env-water-data-monitor', '数据监测', 'water/data-monitor'), + placeholder('monitor-env-water-exception-alarm', '异常报警', 'water/exception-alarm'), + placeholder('monitor-env-water-app-record', '应用记录', 'water/app-record'), + placeholder('monitor-env-water-history-data', '历史数据', 'water/history-data'), + ], + }, + { + key: 'monitor-env-pollen', + label: '花粉监测', + icon: 'BugOutlined', + children: [ + placeholder('monitor-env-pollen-device-list', '设备列表', 'pollen/device-list'), + placeholder('monitor-env-pollen-device-category', '设备分类', 'pollen/device-category'), + placeholder('monitor-env-pollen-data-monitor', '数据监测', 'pollen/data-monitor'), + placeholder('monitor-env-pollen-exception-alarm', '异常报警', 'pollen/exception-alarm'), + placeholder('monitor-env-pollen-app-record', '应用记录', 'pollen/app-record'), + placeholder('monitor-env-pollen-history-data', '历史数据', 'pollen/history-data'), + placeholder('monitor-env-pollen-knowledge', '知识科普', 'pollen/knowledge'), + ], + }, + ], + }, + { + key: 'monitor-ano', + label: '异常事件', + icon: 'WarningOutlined', + children: [ + placeholder('monitor-ano-dirty', '系统脏数据', 'dirty'), + placeholder('monitor-ano-result', '结果异常', 'result'), + ], + }, + ], +} + +/** + * 租户运营后台应用列表(17 个独立应用) + * - 营养管理 / 健康监测:完整菜单结构(来自老原型) + * - 其余 15 个:暂为占位应用(单一首页叶子) + */ +export const tenantApps: NavApp[] = [ + makePlaceholderApp('console', '控制台', '🏠', '租户运营驾驶舱', 'system'), + makePlaceholderApp('system', '系统管理', '⚙️', '组织、用户、角色、权限', 'system'), + nutritionApp, + makePlaceholderApp('checkup', '健康体检', '🩺', '员工体检计划与结果跟踪', 'prevention'), + makePlaceholderApp('weight', '体重管理', '⚖️', '员工体重、体脂、BMI 数据管理', 'prevention'), + makePlaceholderApp('exercise', '运动管理', '🏃', '运动处方与数据追踪', 'prevention'), + monitorApp, + makePlaceholderApp('assessment', '健康评估', '📋', '健康风险评估与报告', 'prevention'), + makePlaceholderApp('knowledge', '知识普及', '📚', '健康知识库与科普推送', 'prevention'), + makePlaceholderApp('data', '健康数据', '📊', '统一健康数据中心与看板', 'data'), + makePlaceholderApp('record', '健康档案', '📁', '员工电子健康档案', 'data'), + makePlaceholderApp('cardiovascular', '心脑血管病预防', '❤️', '心脑血管病专项筛查与干预', 'prevention'), + makePlaceholderApp('diabetes', '糖尿病预防', '🩸', '糖尿病专项筛查与干预', 'prevention'), + makePlaceholderApp('cancer', '癌症预防', '🎗️', '癌症专项筛查与干预', 'prevention'), + makePlaceholderApp('consult', '专家咨询', '👨‍⚕️', '专家在线咨询与预约', 'rescue'), + makePlaceholderApp('emergency', '应急就医', '🚑', '应急就医调度与跟踪', 'rescue'), + makePlaceholderApp('frontline', '一线医疗', '🏥', '一线医疗站点与人员管理', 'rescue'), +] + +// =========================== C 端微信小程序 =========================== + +/** 小程序四个底部 Tab:吃 / 问 / 做 / 我 */ +export const miniprogramTabs: NavMiniprogramTab[] = [ + { + key: 'eat', + label: '吃', + icon: 'shop-o', + page: { + key: 'mp-eat-home', label: '吃', path: 'eat', + component: () => import('@pages/miniprogram/eat/Home.vue'), + status: 'draft', + }, + }, + { + key: 'ask', + label: '问', + icon: 'chat-o', + page: { + key: 'mp-ask-home', label: '问', path: 'ask', + component: () => import('@pages/miniprogram/ask/Home.vue'), + status: 'draft', + }, + }, + { + key: 'make', + label: '做', + icon: 'apps-o', + page: { + key: 'mp-make-home', label: '做', path: 'make', + component: () => import('@pages/miniprogram/make/Home.vue'), + status: 'draft', + }, + }, + { + key: 'me', + label: '我', + icon: 'user-o', + page: { + key: 'mp-me-home', label: '我', path: 'me', + component: () => import('@pages/miniprogram/me/Home.vue'), + status: 'draft', + }, + }, +] + +// =========================== 硬件终端屏 =========================== + +/** + * 硬件设备:10 类 + * - 每类独立 viewport(width × height × orientation × scale) + * - 桌面预览时 scale 用于缩放外壳 + * - 新增设备只需在数组中加一条记录 + */ +export const hardwareDevices: NavHardwareDevice[] = [ + { + key: 'smart-cabinet', + label: '智能货柜', + viewport: { width: 1080, height: 1920, orientation: 'portrait', scale: 0.4 }, + children: [ + { key: 'cabinet-home', label: '首页', path: 'home', + component: () => import('@pages/hardware/smart-cabinet/Home.vue'), status: 'draft' }, + ], + }, + { + key: 'stall-machine', + label: '档口机', + viewport: { width: 1920, height: 1080, orientation: 'landscape', scale: 0.4 }, + children: [ + { key: 'stall-home', label: '首页', path: 'home', + component: () => import('@pages/hardware/stall-machine/Home.vue'), status: 'draft' }, + ], + }, + { + key: 'inbound-scale', + label: '入库秤', + viewport: { width: 1280, height: 800, orientation: 'landscape', scale: 0.55 }, + children: [ + { key: 'inbound-home', label: '首页', path: 'home', + component: () => import('@pages/hardware/inbound-scale/Home.vue'), status: 'draft' }, + ], + }, + { + key: 'nutrition-scale', + label: '营养秤', + viewport: { width: 1280, height: 800, orientation: 'landscape', scale: 0.55 }, + children: [ + { key: 'nutrition-scale-home', label: '首页', path: 'home', + component: () => import('@pages/hardware/nutrition-scale/Home.vue'), status: 'draft' }, + ], + }, + { + key: 'disc-machine', + label: '吐盘机', + viewport: { width: 1080, height: 1920, orientation: 'portrait', scale: 0.4 }, + children: [ + { key: 'disc-home', label: '首页', path: 'home', + component: () => import('@pages/hardware/disc-machine/Home.vue'), status: 'draft' }, + ], + }, + { + key: 'cashier', + label: '结算台', + viewport: { width: 1920, height: 1080, orientation: 'landscape', scale: 0.4 }, + children: [ + { key: 'cashier-home', label: '首页', path: 'home', + component: () => import('@pages/hardware/cashier/Home.vue'), status: 'draft' }, + ], + }, + { + key: 'ratio-scale', + label: '配比秤', + viewport: { width: 1280, height: 800, orientation: 'landscape', scale: 0.55 }, + children: [ + { key: 'ratio-home', label: '首页', path: 'home', + component: () => import('@pages/hardware/ratio-scale/Home.vue'), status: 'draft' }, + ], + }, + { + key: 'ribbon-screen', + label: '带鱼屏', + viewport: { width: 3840, height: 1080, orientation: 'landscape', scale: 0.2 }, + children: [ + { key: 'ribbon-home', label: '首页', path: 'home', + component: () => import('@pages/hardware/ribbon-screen/Home.vue'), status: 'draft' }, + ], + }, + { + key: 'stats-screen', + label: '统计大屏', + viewport: { width: 1920, height: 1080, orientation: 'landscape', scale: 0.45 }, + children: [ + { key: 'stats-home', label: '首页', path: 'home', + component: () => import('@pages/hardware/stats-screen/Home.vue'), status: 'draft' }, + ], + }, + { + key: 'cooking-robot', + label: '炒菜机器人', + viewport: { width: 1280, height: 800, orientation: 'landscape', scale: 0.55 }, + children: [ + { key: 'cooking-home', label: '首页', path: 'home', + component: () => import('@pages/hardware/cooking-robot/Home.vue'), status: 'draft' }, + ], + }, +] + +// =========================== 工具:类型守卫 =========================== + +/** 类型守卫:item 是否为分组(含 children 字段且 children[0] 不是 NavGroup) */ +export const isNavGroup = (item: NavLeaf | NavGroup): item is NavGroup => { + return (item as NavGroup).children !== undefined +} + +// =========================== 工具:按 Portal 取数 =========================== + +/** 按 portalKey 取得对应的应用数组(admin / tenant) */ +export const getPortalApps = (key: 'admin-portal' | 'tenant-portal'): NavApp[] => { + return key === 'admin-portal' ? adminApps : tenantApps +} diff --git a/src/pages/admin-portal/device-center/Overview.vue b/src/pages/admin-portal/device-center/Overview.vue new file mode 100644 index 0000000..2253b84 --- /dev/null +++ b/src/pages/admin-portal/device-center/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/admin-portal/operation-center/Overview.vue b/src/pages/admin-portal/operation-center/Overview.vue new file mode 100644 index 0000000..c3cebdf --- /dev/null +++ b/src/pages/admin-portal/operation-center/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/admin-portal/system-settings/Overview.vue b/src/pages/admin-portal/system-settings/Overview.vue new file mode 100644 index 0000000..1d00e2d --- /dev/null +++ b/src/pages/admin-portal/system-settings/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/console/VisibilityConsole.vue b/src/pages/console/VisibilityConsole.vue new file mode 100644 index 0000000..0c36eb8 --- /dev/null +++ b/src/pages/console/VisibilityConsole.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/src/pages/gate/Gate.vue b/src/pages/gate/Gate.vue new file mode 100644 index 0000000..d95860a --- /dev/null +++ b/src/pages/gate/Gate.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/src/pages/hardware/cashier/Home.vue b/src/pages/hardware/cashier/Home.vue new file mode 100644 index 0000000..e93276e --- /dev/null +++ b/src/pages/hardware/cashier/Home.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/hardware/cooking-robot/Home.vue b/src/pages/hardware/cooking-robot/Home.vue new file mode 100644 index 0000000..35e4fb5 --- /dev/null +++ b/src/pages/hardware/cooking-robot/Home.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/hardware/disc-machine/Home.vue b/src/pages/hardware/disc-machine/Home.vue new file mode 100644 index 0000000..98a5da6 --- /dev/null +++ b/src/pages/hardware/disc-machine/Home.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/hardware/inbound-scale/Home.vue b/src/pages/hardware/inbound-scale/Home.vue new file mode 100644 index 0000000..d995ade --- /dev/null +++ b/src/pages/hardware/inbound-scale/Home.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/hardware/nutrition-scale/Home.vue b/src/pages/hardware/nutrition-scale/Home.vue new file mode 100644 index 0000000..52a765a --- /dev/null +++ b/src/pages/hardware/nutrition-scale/Home.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/hardware/ratio-scale/Home.vue b/src/pages/hardware/ratio-scale/Home.vue new file mode 100644 index 0000000..05c555a --- /dev/null +++ b/src/pages/hardware/ratio-scale/Home.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/hardware/ribbon-screen/Home.vue b/src/pages/hardware/ribbon-screen/Home.vue new file mode 100644 index 0000000..f236064 --- /dev/null +++ b/src/pages/hardware/ribbon-screen/Home.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/hardware/smart-cabinet/Home.vue b/src/pages/hardware/smart-cabinet/Home.vue new file mode 100644 index 0000000..35366aa --- /dev/null +++ b/src/pages/hardware/smart-cabinet/Home.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/hardware/stall-machine/Home.vue b/src/pages/hardware/stall-machine/Home.vue new file mode 100644 index 0000000..8d07c2b --- /dev/null +++ b/src/pages/hardware/stall-machine/Home.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/hardware/stats-screen/Home.vue b/src/pages/hardware/stats-screen/Home.vue new file mode 100644 index 0000000..94f2d88 --- /dev/null +++ b/src/pages/hardware/stats-screen/Home.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/home/Home.vue b/src/pages/home/Home.vue new file mode 100644 index 0000000..5f9e8a7 --- /dev/null +++ b/src/pages/home/Home.vue @@ -0,0 +1,1031 @@ + + + + + diff --git a/src/pages/miniprogram/ask/Home.vue b/src/pages/miniprogram/ask/Home.vue new file mode 100644 index 0000000..3707c86 --- /dev/null +++ b/src/pages/miniprogram/ask/Home.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/miniprogram/eat/Home.vue b/src/pages/miniprogram/eat/Home.vue new file mode 100644 index 0000000..8f73e5c --- /dev/null +++ b/src/pages/miniprogram/eat/Home.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/miniprogram/make/Home.vue b/src/pages/miniprogram/make/Home.vue new file mode 100644 index 0000000..54b0d2c --- /dev/null +++ b/src/pages/miniprogram/make/Home.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/miniprogram/me/Home.vue b/src/pages/miniprogram/me/Home.vue new file mode 100644 index 0000000..10c6967 --- /dev/null +++ b/src/pages/miniprogram/me/Home.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/overview/DesignOverview.vue b/src/pages/overview/DesignOverview.vue new file mode 100644 index 0000000..287ea61 --- /dev/null +++ b/src/pages/overview/DesignOverview.vue @@ -0,0 +1,544 @@ + + + + + diff --git a/src/pages/prd/PrdViewer.vue b/src/pages/prd/PrdViewer.vue new file mode 100644 index 0000000..8b94540 --- /dev/null +++ b/src/pages/prd/PrdViewer.vue @@ -0,0 +1,371 @@ + + + + + diff --git a/src/pages/tenant-portal/assessment/Overview.vue b/src/pages/tenant-portal/assessment/Overview.vue new file mode 100644 index 0000000..6b8de7a --- /dev/null +++ b/src/pages/tenant-portal/assessment/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/tenant-portal/cancer/Overview.vue b/src/pages/tenant-portal/cancer/Overview.vue new file mode 100644 index 0000000..f8d2fbe --- /dev/null +++ b/src/pages/tenant-portal/cancer/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/tenant-portal/cardiovascular/Overview.vue b/src/pages/tenant-portal/cardiovascular/Overview.vue new file mode 100644 index 0000000..5808d87 --- /dev/null +++ b/src/pages/tenant-portal/cardiovascular/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/tenant-portal/checkup/Overview.vue b/src/pages/tenant-portal/checkup/Overview.vue new file mode 100644 index 0000000..b228eb5 --- /dev/null +++ b/src/pages/tenant-portal/checkup/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/tenant-portal/console/Overview.vue b/src/pages/tenant-portal/console/Overview.vue new file mode 100644 index 0000000..31a6f1c --- /dev/null +++ b/src/pages/tenant-portal/console/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/tenant-portal/consult/Overview.vue b/src/pages/tenant-portal/consult/Overview.vue new file mode 100644 index 0000000..2dcece8 --- /dev/null +++ b/src/pages/tenant-portal/consult/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/tenant-portal/data/Overview.vue b/src/pages/tenant-portal/data/Overview.vue new file mode 100644 index 0000000..0358a5f --- /dev/null +++ b/src/pages/tenant-portal/data/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/tenant-portal/diabetes/Overview.vue b/src/pages/tenant-portal/diabetes/Overview.vue new file mode 100644 index 0000000..d27771b --- /dev/null +++ b/src/pages/tenant-portal/diabetes/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/tenant-portal/emergency/Overview.vue b/src/pages/tenant-portal/emergency/Overview.vue new file mode 100644 index 0000000..3de4d65 --- /dev/null +++ b/src/pages/tenant-portal/emergency/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/tenant-portal/exercise/Overview.vue b/src/pages/tenant-portal/exercise/Overview.vue new file mode 100644 index 0000000..f615e1c --- /dev/null +++ b/src/pages/tenant-portal/exercise/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/tenant-portal/frontline/Overview.vue b/src/pages/tenant-portal/frontline/Overview.vue new file mode 100644 index 0000000..569d2ee --- /dev/null +++ b/src/pages/tenant-portal/frontline/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/tenant-portal/home/AppGrid.vue b/src/pages/tenant-portal/home/AppGrid.vue new file mode 100644 index 0000000..fee4dfd --- /dev/null +++ b/src/pages/tenant-portal/home/AppGrid.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/src/pages/tenant-portal/knowledge/Overview.vue b/src/pages/tenant-portal/knowledge/Overview.vue new file mode 100644 index 0000000..809c36f --- /dev/null +++ b/src/pages/tenant-portal/knowledge/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/tenant-portal/monitor/Overview.vue b/src/pages/tenant-portal/monitor/Overview.vue new file mode 100644 index 0000000..9814cf9 --- /dev/null +++ b/src/pages/tenant-portal/monitor/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/tenant-portal/nutrition/arc/ArcUnit.vue b/src/pages/tenant-portal/nutrition/arc/ArcUnit.vue new file mode 100644 index 0000000..b713bc1 --- /dev/null +++ b/src/pages/tenant-portal/nutrition/arc/ArcUnit.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/pages/tenant-portal/nutrition/arc/arcEmployee/api/index.ts b/src/pages/tenant-portal/nutrition/arc/arcEmployee/api/index.ts new file mode 100644 index 0000000..7b2788a --- /dev/null +++ b/src/pages/tenant-portal/nutrition/arc/arcEmployee/api/index.ts @@ -0,0 +1,34 @@ +/** + * 员工营养数据 - 接口契约层(原型阶段) + * - 原型阶段不实际调用后端,mock 数据直接在 init/useTable.ts 中写死 + * - 本文件保留接口签名,作为「未来对接后端时」的契约说明 + * - 后端 / 测试 / 前端工程开发可从本文件读到「需要哪些接口、参数和返回结构」 + * - 工程化迁移时,把签名实现替换为真实 HTTP 调用即可 + */ + +import type { + EmployeeListParams, + EmployeeListResponse, +} from '../types' + +/** + * 员工营养数据 - 分页查询 + * @接口路径 POST /nutrition/employee/page + * @请求参数 EmployeeListParams(搜索条件 + 分页) + * @响应 EmployeeListResponse(records + total) + * + * 原型阶段说明:实际数据见 init/useTable.ts 的 dataList + */ +export const queryEmployeePage = (_params: EmployeeListParams): Promise => { + return Promise.reject(new Error('[原型阶段] 接口未实现,数据来自 init/useTable.ts')) +} + +/** + * 员工营养数据 - 导出列表 + * @接口路径 POST /nutrition/employee/export + * @请求参数 EmployeeListParams(不含分页字段) + * @响应 Blob(xlsx 二进制流) + */ +export const exportEmployeeList = (_params: EmployeeListParams): Promise => { + return Promise.reject(new Error('[原型阶段] 接口未实现')) +} diff --git a/src/pages/tenant-portal/nutrition/arc/arcEmployee/arcEmployee.vue b/src/pages/tenant-portal/nutrition/arc/arcEmployee/arcEmployee.vue new file mode 100644 index 0000000..cadf817 --- /dev/null +++ b/src/pages/tenant-portal/nutrition/arc/arcEmployee/arcEmployee.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/pages/tenant-portal/nutrition/arc/arcEmployee/init/usePage.ts b/src/pages/tenant-portal/nutrition/arc/arcEmployee/init/usePage.ts new file mode 100644 index 0000000..b851b58 --- /dev/null +++ b/src/pages/tenant-portal/nutrition/arc/arcEmployee/init/usePage.ts @@ -0,0 +1,44 @@ +/** + * 员工营养数据 - 页面主逻辑 + * - 页面级 state:统计卡数据 + * - 页面级行为:详情跳转、导出、查看导出任务 + * - 不耦合搜索 / 表格内部细节,与 useSearch / useTable 平级并列 + * + * 新规范:列表页不再显示 PageHeader 标题;菜单/历史栏已表达所在位置。 + */ + +import { message } from 'ant-design-vue' +import type { EmployeeNutritionRecord, NutritionStat } from '../types' + +export const usePage = () => { + + /** 顶部统计卡数据 */ + const stats: NutritionStat[] = [ + { icon: '👤', color: 'blue', value: '1,280', label: '监控员工数' }, + { icon: '📅', color: 'green', value: '132', label: '本年已监控天数' }, + { icon: '📊', color: 'orange', value: '86.5%', label: '营养达标率' }, + { icon: '💾', color: 'green', value: '96.2%', label: '数据覆盖率' }, + ] + + /** 查看员工详情 - 工程化时跳转 detail 路由 */ + const viewDetail = (record: EmployeeNutritionRecord) => { + message.info(`查看 ${record.name} 的详情(原型演示)`) + } + + /** 导出列表 - 工程化时调用 exportEmployeeList */ + const handleExport = () => { + message.success('已触发导出任务(原型演示)') + } + + /** 查看导出任务列表 */ + const viewExportTasks = () => { + message.info('查看导出任务列表(原型演示)') + } + + return { + stats, + viewDetail, + handleExport, + viewExportTasks, + } +} diff --git a/src/pages/tenant-portal/nutrition/arc/arcEmployee/init/useSearch.ts b/src/pages/tenant-portal/nutrition/arc/arcEmployee/init/useSearch.ts new file mode 100644 index 0000000..8fdb779 --- /dev/null +++ b/src/pages/tenant-portal/nutrition/arc/arcEmployee/init/useSearch.ts @@ -0,0 +1,68 @@ +/** + * 员工营养数据 - 搜索逻辑 + * - 暴露搜索表单、下拉选项、字段配置、查询/重置方法 + * - 与 FilterBar 组件配合:fields 数组驱动渲染 + */ + +import { message } from 'ant-design-vue' +import type { FilterField } from '@components/FilterBar.vue' +import type { EmployeeSearchForm } from '../types' + +export const useSearch = () => { + + /** 搜索表单初始值 */ + const initialForm = (): EmployeeSearchForm => ({ + keyword: '', + unit: undefined, + dept: undefined, + year: 2026, + }) + + /** 搜索表单数据(v-model 给 FilterBar) */ + const searchForm = ref(initialForm()) + + /** 所属单位下拉选项 */ + const unitOptions = [ + { value: 'hq', label: 'CQ能源总部' }, + { value: 'oil1', label: '采油一厂' }, + { value: 'oil2', label: '采油二厂' }, + ] + + /** 所属部门下拉选项 */ + const deptOptions = [ + { value: 'rd', label: '研发部' }, + { value: 'mk', label: '市场部' }, + { value: 'op', label: '运营部' }, + { value: 'fin', label: '财务部' }, + { value: 'hr', label: '人事部' }, + ] + + /** 年份下拉选项 */ + const yearOptions = [2026, 2025, 2024].map(y => ({ value: y, label: `${y}年` })) + + /** FilterBar 字段配置(与 FilterField 类型严格对齐) */ + const filterFields: FilterField[] = [ + { name: 'keyword', label: '姓名/工号', type: 'input', placeholder: '请输入姓名或工号' }, + { name: 'unit', label: '所属单位', type: 'select', options: unitOptions }, + { name: 'dept', label: '所属部门', type: 'select', options: deptOptions }, + { name: 'year', label: '年份', type: 'select', options: yearOptions, allowClear: false, width: '120px' }, + ] + + /** 触发查询 - 原型阶段只演示 message 提示 */ + const handleSearch = (form: EmployeeSearchForm) => { + message.success(`已触发查询:${JSON.stringify(form)}`) + } + + /** 重置 - FilterBar 内部已清空字段,这里仅做副作用 */ + const handleReset = () => { + searchForm.value = initialForm() + message.info('已重置筛选条件') + } + + return { + searchForm, + filterFields, + unitOptions, deptOptions, yearOptions, + handleSearch, handleReset, + } +} diff --git a/src/pages/tenant-portal/nutrition/arc/arcEmployee/init/useTable.ts b/src/pages/tenant-portal/nutrition/arc/arcEmployee/init/useTable.ts new file mode 100644 index 0000000..929749d --- /dev/null +++ b/src/pages/tenant-portal/nutrition/arc/arcEmployee/init/useTable.ts @@ -0,0 +1,53 @@ +/** + * 员工营养数据 - 表格逻辑 + * - columns:与正式工程 a-table 列定义完全兼容(dataIndex/key/width/fixed) + * - dataList:原型阶段 mock 数据,工程化时替换为 await queryEmployeePage() + * - pagination:标准 a-table pagination 配置 + */ + +import type { TableColumnsType } from 'ant-design-vue' +import type { EmployeeNutritionRecord } from '../types' + +export const useTable = () => { + + /** 表格列配置(标题、字段、宽度、列冻结) */ + const columns: TableColumnsType = [ + { title: '姓名', dataIndex: 'name', width: 80, fixed: 'left' }, + { title: '性别', dataIndex: 'gender', width: 60 }, + { title: '年龄', dataIndex: 'age', width: 60 }, + { title: '员工工号', dataIndex: 'empNo', width: 100 }, + { title: '所属单位', dataIndex: 'unit', width: 140 }, + { title: '所属部门', dataIndex: 'dept', width: 100 }, + { title: '用餐天数', dataIndex: 'days', width: 90 }, + { title: '用餐次数', dataIndex: 'meals', width: 90 }, + { title: '热量均值(kcal)', dataIndex: 'calorie', width: 130 }, + { title: '蛋白质均值(g)', dataIndex: 'protein', width: 130 }, + { title: '脂肪均值(g)', dataIndex: 'fat', width: 120 }, + { title: '碳水均值(g)', dataIndex: 'carb', width: 120 }, + { title: '营养达标率', key: 'rate', width: 110 }, + { title: '操作', key: 'action', width: 100, fixed: 'right' }, + ] + + /** + * 表格数据源(原型 Mock) + * 工程化时替换为:const { records } = await queryEmployeePage(params) + */ + const dataList = ref([ + { id: 'EMP001', name: '张伟', gender: '男', age: 32, empNo: 'EMP001', unit: 'CQ能源总部', dept: '研发部', days: 132, meals: 386, calorie: '2,156', protein: 78.3, fat: 72.1, carb: 285.4, rate: 92 }, + { id: 'EMP002', name: '李娜', gender: '女', age: 28, empNo: 'EMP002', unit: 'CQ能源总部', dept: '市场部', days: 128, meals: 372, calorie: '1,842', protein: 65.2, fat: 58.7, carb: 241.3, rate: 89 }, + { id: 'EMP003', name: '王磊', gender: '男', age: 35, empNo: 'EMP003', unit: '采油一厂', dept: '运营部', days: 125, meals: 358, calorie: '2,380', protein: 85.6, fat: 89.2, carb: 312.1, rate: 76 }, + { id: 'EMP004', name: '赵敏', gender: '女', age: 30, empNo: 'EMP004', unit: '采油二厂', dept: '财务部', days: 131, meals: 390, calorie: '1,720', protein: 58.4, fat: 52.3, carb: 228.6, rate: 95 }, + { id: 'EMP005', name: '陈刚', gender: '男', age: 41, empNo: 'EMP005', unit: 'CQ能源总部', dept: '人事部', days: 120, meals: 345, calorie: '2,050', protein: 72.1, fat: 68.4, carb: 270.2, rate: 78 }, + ]) + + /** 分页配置 */ + const pagination = reactive({ + current: 1, + pageSize: 30, + total: 1280, + showSizeChanger: true, + showTotal: (total: number) => `共 ${total} 条`, + }) + + return { columns, dataList, pagination } +} diff --git a/src/pages/tenant-portal/nutrition/arc/arcEmployee/types/index.ts b/src/pages/tenant-portal/nutrition/arc/arcEmployee/types/index.ts new file mode 100644 index 0000000..b074e45 --- /dev/null +++ b/src/pages/tenant-portal/nutrition/arc/arcEmployee/types/index.ts @@ -0,0 +1,75 @@ +/** + * 员工营养数据 - 类型集中定义 + * - 所有 Interface / Type 集中在此,禁止散落到各 use*.ts + * - 后端 DTO 字段如有变化,只改这一处 + */ + +import type { StatColor } from '@components/StatCard.vue' + +/** 员工营养监控行数据(对应后端 EmployeeNutritionDTO) */ +export interface EmployeeNutritionRecord { + /** 主键,对应 vue-table row-key */ + id: string + /** 姓名 */ + name: string + /** 性别 */ + gender: '男' | '女' + /** 年龄 */ + age: number + /** 员工工号 */ + empNo: string + /** 所属单位(显示名称) */ + unit: string + /** 所属部门(显示名称) */ + dept: string + /** 用餐天数 */ + days: number + /** 用餐次数 */ + meals: number + /** 热量均值(kcal) */ + calorie: string + /** 蛋白质均值(g) */ + protein: number + /** 脂肪均值(g) */ + fat: number + /** 碳水化合物均值(g) */ + carb: number + /** 营养达标率(0-100) */ + rate: number +} + +/** 搜索表单类型 */ +export interface EmployeeSearchForm { + /** 姓名 / 工号关键词 */ + keyword?: string + /** 所属单位(value) */ + unit?: string + /** 所属部门(value) */ + dept?: string + /** 年份 */ + year?: number +} + +/** 顶部统计卡数据 */ +export interface NutritionStat { + icon: string + color: StatColor + value: string + label: string +} + +/** 列表分页查询请求(与后端约定) */ +export interface EmployeeListParams extends EmployeeSearchForm { + /** 当前页(从 1 开始) */ + current: number + /** 每页条数 */ + pageSize: number +} + +/** 列表分页响应 */ +export interface EmployeeListResponse { + /** 当前页数据 */ + records: EmployeeNutritionRecord[] + /** 总条数 */ + total: number +} diff --git a/src/pages/tenant-portal/nutrition/farm/FarmLand.vue b/src/pages/tenant-portal/nutrition/farm/FarmLand.vue new file mode 100644 index 0000000..25ac1e9 --- /dev/null +++ b/src/pages/tenant-portal/nutrition/farm/FarmLand.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/pages/tenant-portal/record/Overview.vue b/src/pages/tenant-portal/record/Overview.vue new file mode 100644 index 0000000..596ea96 --- /dev/null +++ b/src/pages/tenant-portal/record/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/tenant-portal/system/Overview.vue b/src/pages/tenant-portal/system/Overview.vue new file mode 100644 index 0000000..57543f7 --- /dev/null +++ b/src/pages/tenant-portal/system/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/pages/tenant-portal/weight/Overview.vue b/src/pages/tenant-portal/weight/Overview.vue new file mode 100644 index 0000000..2a5ff15 --- /dev/null +++ b/src/pages/tenant-portal/weight/Overview.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/prd/tenant-portal/nutrition/arc-employee.md b/src/prd/tenant-portal/nutrition/arc-employee.md new file mode 100644 index 0000000..a882046 --- /dev/null +++ b/src/prd/tenant-portal/nutrition/arc-employee.md @@ -0,0 +1,153 @@ +# 员工营养数据 · PRD + +> **路径**:`tenant-portal/nutrition/arc-employee` +> **页面 key**:`arc-employee` +> **完成度**:ready +> **本文档自动由 /gen-prd 生成 / 维护,手工修改时请同步更新 init/ 中的字段** + +--- + +## 一、页面概述 + +员工营养数据列表页面,按年度展示企业全部员工的营养监控数据,支持按姓名/工号、所属单位、所属部门、年份多维筛选,结果导出为 xlsx。 + +页面定位:租户运营后台 > 营养管理 > 档案 > 员工营养数据。 + +--- + +## 二、页面截图 + +> 截图存放在 `src/prd/tenant-portal/nutrition/screenshots/arc-employee/` +> 命名规范:`<状态>.jpg`(如 `default.jpg`、`search-active.jpg`、`empty.jpg`) +> 单图体积 ≤ 60KB,使用 puppeteer 或 chrome-devtools MCP 截取 +> 视口:Web 端 1440×900,1x 缩放 + +![默认状态](screenshots/arc-employee/default.jpg) + +--- + +## 三、统计区 + +页面顶部展示 4 个统计卡: + +| 指标 | 含义 | 示例值 | +|------|------|--------| +| 监控员工数 | 当前年度纳入营养监控的员工总数 | 1,280 | +| 本年已监控天数 | 截至当前的累计监控天数 | 132 | +| 营养达标率 | 监控员工中营养达标的员工占比 | 86.5% | +| 数据覆盖率 | 已采集营养数据的员工占比 | 96.2% | + +--- + +## 四、搜索区 + +| 字段 | 控件 | 是否必填 | 默认值 | 选项来源 | +|------|------|---------|--------|---------| +| 姓名/工号 | 文本框 | 否 | 空 | - | +| 所属单位 | 下拉 | 否 | 全部 | 字典:CQ能源总部 / 采油一厂 / 采油二厂 | +| 所属部门 | 下拉 | 否 | 全部 | 字典:研发部 / 市场部 / 运营部 / 财务部 / 人事部 | +| 年份 | 下拉 | 是 | 当前年 | 2026 / 2025 / 2024 | + +--- + +## 五、表格字段 + +| # | 列名 | 字段名 | 类型 | 列宽 | 备注 | +|---|------|--------|------|------|------| +| 1 | 姓名 | name | string | 80 | 左冻结 | +| 2 | 性别 | gender | '男' \| '女' | 60 | | +| 3 | 年龄 | age | number | 60 | | +| 4 | 员工工号 | empNo | string | 100 | 唯一 | +| 5 | 所属单位 | unit | string | 140 | | +| 6 | 所属部门 | dept | string | 100 | | +| 7 | 用餐天数 | days | number | 90 | | +| 8 | 用餐次数 | meals | number | 90 | | +| 9 | 热量均值(kcal) | calorie | string | 130 | 保留千分位 | +| 10 | 蛋白质均值(g) | protein | number | 130 | 1 位小数 | +| 11 | 脂肪均值(g) | fat | number | 120 | 1 位小数 | +| 12 | 碳水均值(g) | carb | number | 120 | 1 位小数 | +| 13 | 营养达标率 | rate | number(0-100) | 110 | tag 颜色:≥85 绿、≥70 黄、<70 红 | +| 14 | 操作 | - | - | 100 | 右冻结。链接「查看详情」跳转 | + +--- + +## 六、操作 + +| 操作 | 触发位置 | 行为 | +|------|---------|------| +| 查询 | 搜索栏「查询」按钮 | 携搜索条件请求 page 接口 | +| 重置 | 搜索栏「重置」按钮 | 清空筛选 + 查询 | +| 查看详情 | 表格行「查看详情」 | 跳转员工详情页 | +| 导出列表数据 | 页头「导出列表数据」 | 调用 export 接口下载 xlsx | +| 查看导出任务 | 页头「查看导出任务」 | 打开导出任务列表抽屉 | + +--- + +## 七、接口契约 + +### 7.1 分页查询 + +``` +POST /nutrition/employee/page +``` + +请求: + +```json +{ + "current": 1, + "pageSize": 30, + "keyword": "张", + "unit": "hq", + "dept": "rd", + "year": 2026 +} +``` + +响应(标准 ApiResponse 包装): + +```json +{ + "code": "00000", + "msg": "操作成功", + "data": { + "records": [ /* EmployeeNutritionRecord[] */ ], + "total": 1280 + } +} +``` + +### 7.2 导出 + +``` +POST /nutrition/employee/export +``` + +请求:同 7.1 但不含 current / pageSize 字段。 + +响应:`Blob`(xlsx 二进制流) + +--- + +## 八、状态流转 + +无状态字段(纯展示页面)。 + +--- + +## 九、测试关注点 + +- 搜索"重置"后清空所有筛选条件并自动查询 +- 营养达标率颜色阈值:85 / 70 +- 列冻结:左姓名、右操作;横向滚动正常 +- 分页:首页/尾页禁用上下按钮;切页大小回到第一页 +- 导出权限校验 +- 空数据态文案:`暂无数据` + +--- + +## 十、变更记录 + +| 日期 | 类型 | 摘要 | +|------|------|------| +| 2026-06-04 | init | 初始化(来自 /gen-prd skill) | diff --git a/src/prd/共性需求说明.md b/src/prd/共性需求说明.md new file mode 100644 index 0000000..c51b0a8 --- /dev/null +++ b/src/prd/共性需求说明.md @@ -0,0 +1,532 @@ +# 智养餐饮平台V3 - 产品需求文档(PRD) + +> **所属端**:智养餐饮平台V3 共性需求说明 +> **模块路径**:`yx-platform-prototype/` +> **最后更新**:2026-06-04 + +--- +# 一、文档概述 + +本文档为"智养餐饮平台V3"的产品需求规格说明书(以下简称PRD),旨在系统性地定义本次升级的业务目标、功能范围、用户角色及非功能性需求,为项目团队(包括产品、设计、研发、测试等角色)提供统一的需求基线。该文档适用于"智养餐饮平台V3"的全生命周期,涵盖需求分析、系统设计、原型设计、测试验证、交付验收各阶段。 + +### 文档目的 + +- 明确本次平台建设的核心目标与建设范围,确保各方对项目方向达成一致理解; + +- 详细描述各功能模块的业务逻辑、交互流程及数据要求,作为设计与开发的直接依据; + +- 规范验收标准与质量指标,为后续测试与上线交付提供参考。 + +### 读者对象 + +- 项目管理人员:用于把控项目范围、进度与风险; + +- 产品与设计人员:作为功能设计与交互设计的输入依据; + +- 研发人员:用于理解业务逻辑,进行技术方案设计与编码实现; + +- 测试人员:用于编写测试用例,验证系统功能与非功能性指标; + +- 客户方相关人员:用于确认需求内容,参与评审与验收。 + +### 文档内容 + +- 项目背景与目标:阐述"智养餐饮平台V3"的建设背景、升级动因及总体目标,明确本次项目的价值定位; + +- 项目需求分析:拆解面向平台管理员、租户运营人员、终端用户(餐饮服务对象)、食堂工作人员等用户群体的用户画像分析,概述本次项目的具体交付成果,对核心业务板块做简要说明; + +- 共性需求说明:针对交付产物(综合管理后台、租户运营后台、微信小程序、硬件终端屏)通用能力进行统一说明,包括用户权限管理、通用样式布局、操作一致性、数据字典等跨模块、跨端的公共需求; + +- 功能需求详细设计:围绕系统管理、营养管理、健康体检、体重管理、健康监测、健康评估、健康档案、知识普及、专家咨询、应急就医、一线医疗等核心模块,逐一描述业务场景、功能流程、界面要求及业务规则; + +- 非功能性需求:明确系统在性能、安全、可用性、兼容性、可扩展性等方面的质量要求与量化指标; + +- 技术实现方案:阐述系统整体技术架构、技术选型、数据库设计、第三方系统对接(如智能货柜、营养秤、穿戴设备等 IoT 设备)、系统安全与可用性、系统部署与运维等方案。 + +# 二、项目背景与目标 + +## (一)项目背景 + +智养餐饮平台 V1/V2 以面向单一组织的食堂数字化管理为起点,覆盖了菜品、食谱、订餐、出餐等基础场景。随着多租户运营模式的推行、营养健康一体化诉求的增强、以及食堂全链路(绿色种采、加工、出餐、质检、回收)数据上云的实际需要,原有平台在多租户隔离、跨端协同、硬件终端接入、个性化营养干预等方面已无法完全满足业务诉求。 + +为此,启动"智养餐饮平台V3"项目,通过对原有平台的全面重构与升级,搭建一套"综合管理后台 + 租户运营后台 + C 端微信小程序 + 多类硬件终端屏"的四端一体协同体系,将餐饮业务与员工健康管理打通,覆盖从田间到餐桌、从菜品到健康档案的全链路。 + +## (二)项目目标 + +本项目立足于企事业单位食堂与员工健康管理的实际业务场景,围绕"多租户支撑、餐饮健康一体化、硬件协同、智能化升级"四大方向展开,具体目标如下: + +* 多租户支撑:综合管理后台与租户运营后台清晰分层,平台级配置(账号、字典、租户、设备)与租户级运营(菜品、食谱、订餐、出餐、质检)解耦,支持快速复制开通新租户。 + +* 餐饮健康一体化:打通"营养管理 / 健康体检 / 体重管理 / 健康监测 / 健康评估 / 健康档案"等模块的数据流,从单纯的"吃得对"延伸到"吃得健康",把菜品营养元素与员工健康画像绑定,实现个性化食谱推荐与主动健康干预。 + +* 硬件协同:原型期覆盖 10 类硬件终端(智能货柜、档口机、入库秤、营养秤、吐盘机、结算台、配比秤、带鱼屏、统计大屏、炒菜机器人),与后台进行实时双向通信,将食堂前场与后场的人、菜、餐数据全部纳入闭环。 + +* 智能化升级:引入 AI 与数据中台能力,强化营养建议、风险识别、菜品创新、健康评估等核心场景的智能化程度,从"流程数字化"迈向"主动健康守护"。 + +## (三)交付标准 + +为确保项目成果符合预期,本次升级项目将依据以下标准进行验收与交付。所有交付内容需经客户方及我方项目主管部门双重确认。 + +### 性能与稳定性标准 + +* 并发处理能力:系统需支持高峰时段(如午餐高峰、新租户上线季、全员体检季)不低于 3000 用户同时在线,核心业务接口响应时间控制在 3 秒以内。 + +* 数据同步时效:硬件终端(营养秤、智能货柜、健康监测设备等)数据从设备端到平台端的同步延迟不超过 5 分钟,确保食堂出餐与健康监测的实时性。 + +* 系统可用性:系统在试运行期间可用性达到 99.6% 以上,无重大级生产事故。 + +### 功能完整性标准 + +* 模块全覆盖:完成租户运营后台 18 个核心模块(系统管理、营养管理、健康体检、体重管理、运动管理、健康监测、健康评估、知识普及、健康数据、健康档案、心脑血管病预防、糖尿病预防、癌症预防、专家咨询、应急就医、一线医疗、控制台、租户首页)、综合管理后台 3 个子端(系统设置、运营中心、设备中心)、C 端微信小程序四个 Tab(吃 / 问 / 做 / 我)以及 10 类硬件终端的设计与开发,所有功能均需通过预设的测试用例。 + +* 业务流程闭环:关键业务流程(如订餐 → 出餐 → 回收的全过程;菜品创新从灵感采集到上架的全链路;员工健康档案从体检数据归集到主动干预的闭环)需实现端到端的业务闭环,无逻辑断点。 + +* 权限准确率:平台管理员、租户运营人员、终端用户(员工)、食堂工作人员四类角色的数据隔离与操作权限准确率达到 100%。 + +### 智能化改造标准 + +* 主动健康能力:基于营养摄入、健康监测、体检报告数据,通过数据中台与 AI 赋能,实现对营养偏差、慢病风险、异常指标的自动识别与预警推送,预警准确率不低于 80%。 + +* 数据辅助决策:支持生成多维度的营养与健康分析报告(如食堂菜品营养画像、租户健康风险分布、高风险人群清单),为食谱排期、菜品创新、健康干预策略的制定提供数据支撑。 + +### 文档与技术资产交付 + +* 开发文档:提供完整的《系统详细设计说明书》《软件产品需求文档》《接口文档》(包含与智能货柜、营养秤、穿戴设备、环境监测 IoT 设备等第三方硬件的对接规范)。 + +* 部署运维文档:提供《部署手册》《服务器环境清单》《系统监控与运维手册》及《常见问题处理指南》,确保系统具备可持续运维能力。 + +* 测试报告:提供功能测试报告、性能测试报告及安全测试报告,明确各项测试结论。 + +# 三、项目需求分析 + +## (一)用户画像分析 + +| 用户角色 | 角色定义 | 核心使用场景 | 核心诉求 | 当前痛点 | +| --- | --- | --- | --- | --- | +| 平台管理员 | 智养餐饮平台运营方的后台管理人员 | 跨租户基础数据维护、租户开通与配置、字典管理、设备注册、平台级运营统计与监控 | 多租户清晰隔离、配置高效、数据可视化、设备状态可监控 | 原平台多租户能力薄弱;设备接入流程繁琐;缺乏跨租户统计视图 | +| 租户运营人员 | 单租户内部的食堂运营、营养师、健康管理岗位人员 | 菜品 / 食谱 / 订餐 / 出餐 / 质检管理、员工健康档案维护、营养与健康数据查询、专项干预活动发布 | 操作便捷、模块联动、批量处理能力强、数据可导出 | 模块割裂、批量操作能力弱、统计维度单一、数据导出受限 | +| 终端用户(员工) | 享受餐饮与健康服务的最终用户,通过微信小程序使用 | 浏览今日食谱、订餐取餐、查看就餐与营养记录、健康档案查看、AI 营养咨询、社区食谱创作 | 入口简洁、信息及时、营养可视化、互动感强 | 原版小程序功能单一、营养信息不直观、缺少互动与个性化推荐 | +| 食堂工作人员 | 食堂前场(出餐、结算)与后场(入库、加工、配菜、烹饪)的操作人员 | 通过硬件终端进行入库称重、配比、烹饪、出餐、结算、吐盘等环节的操作与记录 | 硬件操作直观、易学易用、与后台数据实时同步、避免重复录入 | 现场设备形态多样、操作不统一、数据上报延迟、与后台脱节 | +| 健康咨询专家 | 提供营养与健康咨询服务的专业医师、营养师、健康管理师 | 接收员工咨询、回复问题、查看咨询历史、辅助健康评估 | 咨询界面清晰、健康档案可调阅、辅助工具完善 | 消息通知不及时;档案调阅不便;缺乏常用回复模板 | +| 应急就医服务人员 | 负责应急就医调度与协调的人员 | 接收应急就医申请、核实信息、协调资源、跟踪处理进度、反馈结果 | 响应高效、信息完整、流程闭环、异常可预警 | 应急流程缺乏自动化提醒;处理进度跟踪困难;与一线医疗信息联动不足 | +| 一线医疗工作人员 | 驻场或一线医疗点的医护人员 | 现场就诊记录录入、慢病随访、体检报告解读、健康宣教 | 信息同步及时、操作简便、与上级机构协同顺畅 | 平台响应慢;就诊记录与平台数据同步困难,存在重复录入 | + +## (二)项目交付成果分析 + +| 交付成果 | 终端定位 | 使用人群 | 核心功能要点 | +| --- | --- | --- | --- | +| 综合管理后台(admin-portal) | 跨租户的平台统一管理后台,支撑全平台基础配置、租户与设备运营 | 平台管理员 | 系统设置(账号、组织、权限、字典、参数)、运营中心(租户开通、应用配置、计费、统计)、设备中心(跨租户硬件注册、固件、在线状态) | +| 租户运营后台(tenant-portal) | 单租户视角的业务运营平台,覆盖营养、健康、咨询、应急等 18 个一级模块 | 租户运营人员、营养师、健康管理岗位 | 租户首页 / 控制台 / 系统管理 / 营养管理 / 健康体检 / 体重 / 运动 / 健康监测 / 健康评估 / 知识普及 / 健康数据 / 健康档案 / 三大慢病预防 / 专家咨询 / 应急就医 / 一线医疗 | +| C 端微信小程序(miniprogram) | 员工日常使用的轻量化服务入口 | 终端用户(员工) | 吃(营养首页、今日食谱、菜品详情、订餐、订单确认、购物车、就餐记录)、问(AI 营养助手、AI 对话、历史对话)、做(创作食谱、社区、排行、分享)、我(个人中心、健康档案、订单、收藏、余额、会员、消息、设置、扫码溯源) | +| 硬件终端屏(hardware) | 食堂前场 / 后场以及公共空间使用的多种专用终端 | 食堂工作人员、公共空间观看用户 | 智能货柜(取菜出柜)、档口机(出餐 / 结算)、入库秤(毛菜入库)、营养秤(餐线营养采集)、吐盘机(餐盘回收)、结算台(用户结算)、配比秤(配菜组合)、带鱼屏(信息展示)、统计大屏(数据可视化)、炒菜机器人(烹饪流程触控) | + +## (三)核心业务分析 + +| 核心业务 | 业务概述 | 现状问题 | 升级方向 | +| --- | --- | --- | --- | +| 系统管理 | 用户管理、角色权限、组织架构、字典与参数、操作日志,保障平台安全稳定运行 | 权限颗粒度粗、日志不完善、字典分散维护 | RBAC 精细化权限、完整操作日志、平台级与租户级字典分层、统一参数中心 | +| 营养管理 | 食堂业务全链路(种植、采购、加工、菜品、食谱、订餐、出餐、质检),打通营养元素至个体摄入数据 | 流程节点多、跨节点数据不一致、营养元素分析弱、与健康数据未联动 | 全链路数据贯通、菜品营养建模、营养摄入可视化、与健康档案双向联动 | +| 健康体检 | 体检计划、机构、项目、报告、统计 | 预约流程繁琐、报告上传依赖人工、跨年趋势不可见 | 优化预约、自动报告归档、历年趋势可视化、数据自动入档 | +| 体重管理 | 体重档案、采集(蓝牙称 / 体脂仪)、目标、干预、提醒 | 仅记录、缺目标激励、智能建议弱 | 目标与进度追踪、趋势图表、AI 减重指导、与营养联动 | +| 运动管理 | 运动计划、器械、记录、目标、激励 | 数据来源单一、缺目标计划、与健康模块联动弱 | 多源数据接入、目标计划、个性化推荐、联动体重与营养 | +| 健康监测 | 穿戴 / 体重秤 / 体脂仪 / 环境监测(室内 / 室外 / 水 / 花粉)的设备接入与数据看板 | 设备同步延迟、仅展示无分析、缺异常告警 | 优化同步、趋势可视化、AI 异常识别、扩展设备兼容性 | +| 健康评估 | 综合体检、监测、营养、生活方式数据生成评估报告与风险等级 | 评估模型单一、报告样式陈旧、缺主动预测 | AI 多模型评估、可视化报告、自动预警 | +| 健康数据 | 各类健康数据接入、清洗、查询,作为数据中台底座 | 数据分散、维度单一、导出能力弱 | 统一数据中心、多维度看板、灵活报表导出 | +| 健康档案 | 个人 / 单位 健康档案聚合视图,串联体检、监测、咨询、营养、干预数据 | 数据未统一、历史留存不完整 | 多源整合、统一视图、灵活统计与导出 | +| 知识普及 | 健康与营养科普文章、视频、问答 | 更新慢、分类弱、形式单一 | 多形式内容、智能推荐、互动反馈 | +| 心脑血管病预防 | 高风险识别、预警推送、干预方案、专项科普 | 评估准确性差、评估与干预脱节 | 高风险自动识别、专项干预方案、科普专区 | +| 糖尿病预防 | 血糖记录、风险预警、饮食运动指导,服务高风险及前期人群 | 评估准确性差、监控难落实、建议针对性弱 | 血糖趋势分析、自动风险识别、个性化建议、专项干预 | +| 癌症预防 | 常见癌症科普、早期筛查提醒、风险评估与预防建议 | 内容覆盖不全、缺评估工具、提醒缺失 | 科普知识库、风险评估问卷、筛查提醒、结果入档 | +| 专家咨询 | 视频问诊、小助手对话、咨询档案、专家管理与评价 | 消息时效差、咨询助手智能化低 | 实时推送、智能助手辅助、档案一键调阅 | +| 应急就医 | 应急工单、大病就医、派单调度、救助跟踪 | 流程不闭环、人工干预多、跨端联动弱 | 完善闭环、就近资源匹配、超时升级提醒、跨端联动 | +| 一线医疗 | 驻场医务工作站:日常诊疗、处置记录、慢病随访、转诊 | 权限混乱、数据滞后、智能化低 | 实时维护、权限分层、AI 辅助诊疗 | +| 待办通知 | 待处理事项、系统消息、预警推送、活动提醒,保障流程推进 | 通知方式单一、缺强提醒、待办分散 | 统一待办中心、多渠道推送(小程序 / 短信 / 微信)、消息分类与状态管理、超时升级提醒 | + +# 四、共性需求说明 + +本章节对平台四端(综合管理后台、租户运营后台、C 端微信小程序、硬件终端屏)的通用交互规范、数据展示规则、权限控制及跨端一致性要求进行统一定义,所有功能模块的设计与开发须遵循本章节规范,不得与之冲突。 + +## (一)后台 Web 端共性需求(综合管理后台 / 租户运营后台) + +### 1.布局与框架 + +- 整体采用顶部导航 + 左侧导航栏 + 右侧内容区的经典后台布局,内容区支持自适应拉伸 + +- 无权限的菜单项、按钮直接隐藏 + +- 导入功能使用统一弹窗布局,支持模板下载、拖拽上传、文件预览 + +- 列表分页默认每页 10 条,支持切换每页条数(10 / 20 / 50 / 100) + +- 列表表格支持列宽拖动、根据每列内容合理规划列宽(如手机号、身份证号、性别、年龄、工号等长度固定的字段)、列显隐配置,配置状态本地持久化 + +- 超长文本单行截断显示"...",鼠标悬浮展示完整内容(Tooltip) + +- 无数据、加载中、接口异常、空白页使用统一样式组件 + +- 详情页与编辑页排版结构与新增页保持一致 + +### 2.表单设计规范 + +- 必填字段在标签前标注红色星号(*),选填字段不做标注 + +- 字段标签统一左对齐,同一表单内标签宽度保持一致 + +- 校验时机:输入框失焦时触发单字段校验,提交时触发全量校验 + +- 只读态字段使用纯文本展示,不使用禁用输入框 + +- 下拉选项超过 10 条时,支持关键词搜索过滤 + +- 日期/时间选择统一使用系统日期组件,不允许纯手动输入 + +- 表单提交后按钮进入 loading 状态,防止重复提交 + +- 表单离开未保存时,弹出"是否放弃修改?"二次确认弹窗 + +- 常规校验:如电话、邮箱、身份证号必须加正则校验,姓名(格式+长度)校验,数值类型(如身高、体重、菜品克数、营养元素含量等)根据常规知识灵活加校验(如体重不能为负数,身高不能大于 300 cm,菜品份量不能为负等) + +- 字段字数限制规范:所有表单输入字段须按下表设置最大字数限制,前端通过 `maxlength` 属性控制,后端同步校验 + +| 字段类型 | 最大字数 | 说明 | +|---------|-------|-------------------------------------------------------------------------------------| +| 姓名/人名 | 20 | 中文姓名+少数民族长名 | +| 手机号 | 11 | 固定位数,正则校验 | +| 座机号 | 12 | 正则校验 | +| 身份证号 | 18 | 固定位数,正则校验 | +| 标题/名称 | 50 | 如菜品名称、食谱标题、模块名称 | +| 短文本输入 | 100 | 如地址、单位名称、一句话备注 | +| 多行备注/描述 | 500 | 如简介、问题说明、菜品描述 | +| 搜索框 | 50 | 关键词搜索 | +| 验证码 | 6 | 短信/邮箱验证码 | +| 密码 | 12~30 | 必须包含至少一个小写字母;必须包含至少一个大写字母;必须包含至少一个数字;必须包含至少一个特殊符号(可根据需要修改特殊符号的定义);密码长度必须大于等于 12 | +| 邮箱 | 50 | 常规邮箱地址,正则校验 | +| URL链接 | 200 | 网址输入 | + + - 多行文本框右下角实时显示字数计数提示,格式为"已输入 X / Y 字" + - 达到字数上限后禁止继续输入 + - 提交校验时,必填字段为空提示"请输入XXX",超限提示"XXX不能超过N个字" + +### 3.搜索与筛选规范 + +- 搜索框支持回车键触发搜索,与点击搜索按钮效果一致 + +- 多条件筛选区域默认展开,条件较多时可折叠收起 + +- 已选筛选条件以标签形式展示在筛选区下方,支持单独点击删除 + +- 筛选条件变更后需手动点击"查询"按钮触发,不自动触发(避免频繁请求) + +- 提供"重置"按钮,一键清空所有筛选条件并恢复默认列表 + +- 列表页搜索/筛选状态不跨页面保留,刷新后恢复默认 + +### 4.交互与操作 + +- 删除、清空、重置、批量操作前必须弹出二次确认弹窗,明确说明操作影响 + +- 批量操作按钮在未选中数据时置灰禁用,选中后激活并显示已选数量 + +- 所有按钮做防连点控制,同一接口短时间内不可重复触发 + +- 接口请求期间按钮进入 loading 状态,内容区显示骨架屏或 loading 遮罩 + +- 导出规范:小数据量(≤1000 条)即时导出;大数据量(>1000 条)异步导出,生成任务后提示"请在导出任务中查看";按钮文案统一为"导出列表数据",导出成功后 Toast 提示 + +- 所有字段校验采用前端校验 + 后端校验双重保障 + +### 5.数据展示 + +- 时间展示统一格式:YYYY-MM-DD HH:mm:ss;日期类型统一格式:YYYY-MM-DD + +- 金额类型保留两位小数;百分比保留一位小数;营养元素含量保留一位小数;克数保留整数 + +- 浮点型数据无特别说明时默认保留两位小数 + +- 手机号、固定电话、身份证号使用统一正则验证规则 + +- 表格中数字列右对齐,文本列左对齐 + +- 空值/无数据字段统一展示为"—",不展示空字符串或 null + +### 6.文件处理规范 + +- 上传入口处明确标注支持的文件类型与大小上限 + +- 图片文件支持在线预览;PDF、Word、Excel 支持在线预览(不强制下载) + +- 大文件上传(>5MB)显示上传进度条,支持取消上传 + +- 下载文件统一通过浏览器下载,文件命名格式:模块名_数据名_时间戳 + +- 批量下载超过 10 个文件时,自动打包为 ZIP 后下载 + +### 7.反馈与提示 + +- 操作成功/失败:使用顶部居中 Toast 提示,3 秒后自动消失;提示词保持格式统一,如"删除成功"、"保存成功"、"保存失败" + +- 关键操作(删除、提交、发布):使用 Confirm 确认框,需用户主动确认 + +- 接口报错:统一提示"系统繁忙,请稍后重试",详细错误信息仅输出至控制台 + +- 网络断开:全局顶部展示"网络连接已断开"横幅提示,恢复后自动消失 + +- 操作日志:新增、修改、删除、导出等关键操作自动记录操作日志,支持审计追溯 + +### 8.权限控制 + +- 应用权限:根据登录用户角色与所属租户动态显示主页应用模块,无权限模块不可见 + +- 菜单权限:根据用户角色动态渲染菜单,无权限菜单项直接隐藏 + +- 按钮权限:页面内操作按钮根据权限控制显示/隐藏或启用/禁用 + +- 数据权限:所有业务数据默认按"租户 → 组织架构层级 → 登录用户管理范围"逐级隔离,禁止数据越权访问(如员工信息、菜品库、订餐记录、监测设备数据等);综合管理后台具备跨租户视图,但仍受平台级角色限制;需要特殊处理或不做数据权限控制的板块会在对应板块的 PRD 文档中备注说明 + +- 接口权限:核心业务接口必须加权限校验,租户运营人员 Token 不可访问平台级综合后台接口,C 端小程序 Token 不可访问后台管理接口 + +## (二)C 端微信小程序共性需求 + +### 1.布局与导航 + +- 整体采用单列布局,内容区上下滑动 + +- 底部 Tab 栏固定为 4 个(吃 / 问 / 做 / 我),当前选中项高亮 + +- 顶部导航栏显示页面标题,左侧返回按钮(一级页面无返回),右侧可放置更多操作入口 + +- 适配主流手机分辨率(iOS:iPhone 12 及以上;Android:主流全面屏),支持安全区域适配 + +- 支持左滑返回上一页;列表支持下拉刷新、上拉加载更多 + +### 2.表单设计规范 + +- 表单采用单列纵向排列,标签置于输入框上方 + +- 必填字段标注红色星号(*) + +- 输入框获取焦点时键盘自动弹起,内容区自动上移,避免键盘遮挡输入框 + +- 点击空白区域收起键盘 + +- 选择类控件使用底部弹出 Picker 或 ActionSheet,不使用 PC 式下拉框 + +- 长表单建议分步骤展示,每步不超过 5 个字段,顶部显示步骤进度条 + +- 表单提交后按钮进入 loading 状态,防止重复提交 + +- 字段字数限制规范:与后台 Web 端保持一致 + +| 字段类型 | 最大字数 | 说明 | +|---------|-------|-------------------------------------------------------------------------------------| +| 姓名/人名 | 20 | 中文姓名+少数民族长名 | +| 手机号 | 11 | 固定位数,正则校验 | +| 座机号 | 12 | 正则校验 | +| 身份证号 | 18 | 固定位数,正则校验 | +| 标题/名称 | 50 | 如菜品名称、食谱标题、模块名称 | +| 短文本输入 | 100 | 如地址、单位名称、一句话备注 | +| 多行备注/描述 | 500 | 如简介、问题说明、菜品描述 | +| 搜索框 | 50 | 关键词搜索 | +| 验证码 | 6 | 短信/邮箱验证码 | +| 密码 | 12~30 | 必须包含至少一个小写字母;必须包含至少一个大写字母;必须包含至少一个数字;必须包含至少一个特殊符号;密码长度必须大于等于 12 | +| 邮箱 | 50 | 常规邮箱地址,正则校验 | +| URL链接 | 200 | 网址输入 | + + - 多行文本框右下角实时显示字数计数提示,格式为"已输入 X / Y 字" + - 达到字数上限后禁止继续输入 + +### 3.交互与操作 + +- 可点击区域不小于 44×44pt,确保手指触控友好 + +- 主按钮使用圆角大按钮,宽度占满或居中,置于页面底部悬浮或跟随内容流 + +- 删除、清空等危险操作使用底部弹出 ActionSheet 或居中模态框,需二次确认 + +- 键盘弹起时底部悬浮按钮自动上移,避免被键盘遮挡 + +- 列表支持下拉刷新,刷新时顶部显示 loading 动画 + +### 4.数据展示 + +- 图表采用移动端适配的简化版本,支持手势缩放查看详情 + +- 营养元素采用环形 / 条形可视化展示,强调直观对比;菜品热量采用色阶卡片 + +- 无数据时显示统一占位图 + 提示文案,提供引导操作(如"先选个菜吧") + +- 空值统一展示为"—" + +### 5.图片与文件处理 + +- 图片上传支持拍照和相册选取两种方式,入口明确区分 + +- 上传前自动压缩,单张图片大小不超过 2MB + +- 图片预览支持双指缩放、左右滑动切换多张图片 + +- 文件下载显示进度,完成后提示"下载完成,点击打开" + +- 上传失败时提示具体原因(如"文件格式不支持"、"文件超出大小限制") + +### 6.反馈与提示 + +- 操作反馈使用 Toast 轻提示,1.5~2 秒自动消失,不阻断操作 + +- 异步请求使用全屏或局部 Loading,避免用户重复点击 + +- 待办事项、未读消息、未取餐订单在入口处显示红点或数字角标 + +- 关键操作(如订餐提交、问卷提交)成功后,给予明确的成功状态页,而非仅 Toast + +### 7.网络异常处理 + +- 请求超时时间统一设置为 10 秒,超时后提示"服务器繁忙,请稍后再试"并提供重试按钮 + +- 网络断开时,页面顶部展示"当前无网络连接"横幅,恢复后自动消失并刷新数据 + +- 弱网环境下优先展示缓存数据,并标注"网络不佳,数据可能存在延迟" + +- 关键操作(如下单、应急就医发起)在网络恢复后自动重试,并通知用户操作结果 + +- 首屏关键数据优先加载,非关键内容懒加载;图片使用缩略图 + 懒加载策略 + +## (三)硬件终端屏共性需求 + +> 硬件终端的设备类型多样(横屏 / 竖屏 / 超宽屏 / 触控 / 数据可视化),不同设备业务差异较大。本节给出跨设备需统一遵守的共性规范,单台设备的特殊交互在对应模块 PRD 中详述。 + +### 1.视口与布局 + +- 每类设备的 viewport(宽 × 高 × 朝向 × 缩放)在 `src/meta/nav.ts > hardwareDevices` 中独立声明,HardwareLayout 自动按此渲染外壳 + +- 设备按用途分两类:操作类(智能货柜 / 档口机 / 入库秤 / 营养秤 / 吐盘机 / 结算台 / 配比秤 / 炒菜机器人)—— 需要触控可点击与表单录入;展示类(带鱼屏 / 统计大屏)—— 以数据可视化为主,不接受人工输入 + +- 操作类设备的主要可点击区域不小于 64×64 px,需适配现场戴手套、油污环境下的触控精度 + +- 展示类设备布局以模块化卡片 + 大数字 + 色块为主,重要 KPI 字号不低于 40 px + +### 2.数据同步与离线 + +- 设备应支持离线缓存关键数据(如菜品库、价格表、用户配额),网络中断 60 分钟内核心功能(出餐 / 称重 / 结算)不受影响 + +- 网络恢复后自动同步本地缓存到平台端,并显示同步进度 + +- 设备状态(在线 / 离线 / 异常)实时上报至综合管理后台「设备中心」,运营方可远程查看 + +### 3.交互与反馈 + +- 关键操作(出餐确认、结算成功、入库完成)必须给出明显的视觉反馈(满屏色块 / 大字提示)+ 声音反馈 + +- 错误操作给出友好提示,避免使用"系统错误"等模糊文案,应明确告知用户下一步该做什么 + +- 设备故障时本机应能展示故障码与重启入口,并自动上报至设备中心 + +### 4.安全与隐私 + +- 现场设备只展示与本次操作相关的最小数据范围(如本人订单、本次称重的菜品),不展示其他用户敏感数据 + +- 操作日志上传至平台后清除本地缓存 + +- 公共展示类设备不展示个人敏感字段(手机号、身份证号),仅展示聚合统计数据 + +## (四)跨端通用规范 + +### 1.品牌与视觉一致性 + +- 综合管理后台、租户运营后台、微信小程序、硬件终端屏采用统一的品牌色、字体、圆角、阴影等视觉语言 + +- 全平台功能命名、按钮文案、提示文案保持一致(如统一使用"订餐"而不出现"点餐"、"预订"等变体) + +- 时间显示格式统一:近 24 小时内显示"X 小时前",超过 24 小时显示具体日期 + +- 错误提示文案统一维护,避免不同模块出现不同表述 + +- 同一用户在不同端操作,数据实时同步,无延迟感知(如小程序下单后档口机立即看到订单) + +### 2.数据安全与隐私保护 + +- 所有数据传输使用 HTTPS 加密,禁止明文传输敏感信息 + +- 敏感字段(手机号、身份证号、健康指标数据)在列表中默认脱敏,详情页按权限展示完整信息 + +- 会话超时时间设置为 30 分钟无操作后自动登出,跳转登录页并提示"登录已过期,请重新登录" + +- 移动端禁止对健康敏感数据页面进行截图分享(系统级限制,如条件不允许则在界面层提示禁止截图) + +- 用户登出后本地缓存的敏感数据立即清除 + +### 3.字典与枚举规范 + +- 平台级字典(单位类型、菜品分类、营养元素、设备类型、岗位等)由综合管理后台维护;租户级字典(本租户的菜品、食谱、用餐时段等)由租户运营后台维护,C 端小程序与硬件终端通过 API 拉取 + +- 前后端枚举值保持严格一致,接口文档中明确枚举定义 + +- 字典数据由后端统一下发,前端缓存使用;字典变更时主动通知前端刷新缓存 + +- 列表筛选、表单选项中的枚举值均从字典接口动态获取 + +### 4.无障碍规范 + +- 后台 Web 端正文字体最小不低于 12 px,C 端小程序正文不低于 14 px,硬件终端正文不低于 18 px + +- 文字与背景色对比度不低于 4.5:1(WCAG AA 标准) + +- 所有图标按钮需配备文字说明或 aria-label 属性 + +- 表单字段需关联 label 标签,支持屏幕阅读器识别 + +- 关键操作结果(成功/失败)需通过文字或图标明确告知,不能仅依赖颜色区分 + +# 五、非功能性需求 + +## (一)性能要求 + +### 业务指标 + +| 指标 | 定义 | 简称 | 本系统标准 | +| --- | --- | --- | --- | +| 请求响应时间 | 指用户从客户端发起一个请求开始,到客户端接收到从服务器端返回的响应结束,整个过程所耗费的时间 | Response Time: RT | 平均响应在 1s 以下;
统计查询接口 3s 以下 | +| 系统处理能力 | 指系统在利用硬件平台和软件平台进行信息处理的能力,通过每秒钟能够处理的交易数量来评价 | HPS(每秒点击次数)
TPS(系统每秒处理交易数)
QPS(系统每秒处理查询次数) | HPS:10000 次/秒
TPS:3000 笔/秒
QPS:5000 次/秒 | +| 并发用户数 | 指在同一时刻内,登录系统并进行业务操作的用户数量 | Virtual User: VU | 正常负载下支持用户并发数 3000+ | +| 错误率 | 指系统在负载情况下,失败交易的概率 | Failure Ratio: FR | 错误率低于千分之六,即成功率高于 99.6% | + +### 服务器资源指标 + +| 指标 | 定义 | 简称 | 本系统标准 | +| --- | --- | --- | --- | +| CPU | 中央处理器,是一台计算机的运算核心与控制核心,主要功能是解释计算机指令并处理软件中的数据 | CPU | CPU sys% 小于 30%;CPU wait% 小于 5%;CPU Load 小于 CPU 核数 | +| 内存 | 计算机中与 CPU 进行沟通的桥梁,所有程序的运行都在内存中进行 | Memory | 一般情况下不会利用到 SWAP 空间,自身内存可支撑项目运行 | +| 磁盘吞吐量 | 指在无磁盘故障的情况下单位时间内通过磁盘的数据量 | Disk Throughput | 分布式架构下,磁盘繁忙率应低于 70% | +| 网络吞吐量 | 指在无网络故障的情况下单位时间内通过的网络数据数量,单位 Byte/s | Network Throughput | 一般情况下不超过设备或链路最大传输能力的 70% | + +### 前端应用指标 + +| 指标 | 二级指标 | 解释 | 本系统指标 | +| --- | --- | --- | --- | +| 页面展示 | 首次显示时间 | 在浏览器地址栏输入 URL 按回车到用户看到网页的第一个视觉标志为止 | 秒级别响应 | +| | OnLoad 事件时间 | 浏览器触发 onLoad 事件的时间 | 秒级别响应 | +| | 完全载入的时间 | 所有 onLoad JavaScript 处理程序执行完毕的时间 | 秒级别响应 | +| 页面数量 | 页面大小 | 整个页面大小 | 所有页面 Body 大小为 KB 级别 | +| | 请求数量 | 从网站下载资源时所有网络请求的总数,尽量少 | — | +| 网络所花时间 | DNS 时间 | DNS 查找时间 | 秒级别响应 | +| | 连接时间 | 浏览器与 Web 服务器建立 TCP/IP 连接的时间 | 秒级别响应 | +| | 服务器时间 | 服务器处理时间 | 秒级别响应 | +| | 传输时间 | 内容传输所用时间 | 秒级别响应 | +| | 等待时间 | 等待某个资源释放的时间 | 秒级处理 | + +### 稳定性、可靠性指标 + +| 指标 | 解释 | 本系统标准 | +| --- | --- | --- | +| 稳定性指标 | 系统按最大容量的 80% 或标准压力(系统的预期日常压力)情况下运行,能够稳定运行的最短时间 | 提供 7×24 运行的系统稳定运行能力 | +| 集群 | 集群中某个节点出现故障时,系统是否有业务中断情况;新增/恢复节点是否需要重启;节点切换需要多长时间 | 故障节点自动从集群中剔除,业务不中断;节点恢复时自动加入集群,无感知切换,纳秒级切换 | +| 备份和恢复 | 备份是否成功及消耗时间;备份是否使用脚本自动化;恢复是否成功及消耗时间;恢复是否使用脚本自动化 | 采用全量 + 增量备份策略,小时为单位周期循环备份,天/周为单位周期循环全量备份,为数据提供基础底层保障 | + +## (二)兼容性要求 + +| 应用名称 | 系统性质 | 操作系统/浏览器内核兼容性要求 | 操作系统版本/浏览器版本要求 | 终端适配要求 | 备注 | +| --- | --- | --- | --- | --- | --- | +| 综合管理后台 / 租户运营后台 | B/S 应用 | 1. 主要适配内核:Blink(Chromium)、Gecko、WebKit。
2. 不再支持 IE 浏览器(Trident 内核已废弃)。
3. 适配 PC 端主流浏览器。 | Chromium 内核:Chrome 100+;Edge 100+;360 极速浏览器(Chromium 内核)13+;QQ 浏览器 14+;搜狗浏览器 12+;Opera 85+。
Gecko 内核:Firefox 100+。
WebKit 内核:Safari 16+(仅适用于 macOS 用户)。
企业微信内置浏览器(Chromium 内核):跟随企业微信最新版本。 | 1. 显示器分辨率:最低 1366×768,推荐 1920×1080 及以上,支持 2K(2560×1440)。
2. 显示器尺寸:13in–34in。 | 1. 推荐使用 Chrome 浏览器以获得最佳体验。
2. 不再支持 IE 浏览器,访问时提示升级。
3. 兼容性测试以浏览器版本为主,需同时注明内核版本。
4. 终端适配以分辨率为准,显示器尺寸为参考值。 | +| C 端微信小程序 | 微信小程序 | 跟随微信官方基础库版本 | 微信客户端 8.0.30+;基础库 2.27+ | 1. 适配 iPhone 12 及以上、Android 主流全面屏(1080×2400 以上);
2. 支持安全区域适配;
3. 设计基准:375×812(iPhone X)。 | 在不同机型上需进行界面兼容性测试,输出兼容性报告。 | +| 硬件终端屏 | 嵌入式应用 / Web 套壳 | Android 10+(操作类设备)或 Windows / Linux + Chromium(展示类设备) | 操作类:Android 10+;展示类:Chromium 100+ | 各设备视口在 nav.ts 中独立声明,详见每台设备 PRD | 现场环境(戴手套触控、油污、强光)需做实地测试 | + diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..b163bff --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,36 @@ +import { createRouter, createWebHashHistory } from 'vue-router' +import routes from './routes' +import { readCurrentRole } from '@composables/useVisibility' +import { installPageHistoryTracker } from '@composables/usePageHistory' + +/** + * 路由实例 + * - 使用 hash 模式,便于部署到任何静态服务(含本地双击 dist/index.html) + * - 全局守卫:未登录或登录已过期则跳转到 /gate + * - 自动追踪 portal 内访问历史(用于顶部"最近打开菜单"Tab 栏) + */ +const router = createRouter({ + history: createWebHashHistory(), + routes, + scrollBehavior: () => ({ top: 0 }), +}) + +router.beforeEach((to) => { + // 公共路由(/gate)直接放行 + if (to.meta.public) return true + const role = readCurrentRole() + // 未登录 → 重定向到 gate,登录后回到原目标 + if (!role) { + return { path: '/gate', query: { redirect: to.fullPath } } + } + // designerOnly 路由:研发角色不可访问 + if (to.meta.designerOnly && role !== 'designer') { + return { path: '/' } + } + return true +}) + +// 追踪 portal 访问历史 +installPageHistoryTracker(router) + +export default router diff --git a/src/router/routes.ts b/src/router/routes.ts new file mode 100644 index 0000000..4be6c93 --- /dev/null +++ b/src/router/routes.ts @@ -0,0 +1,222 @@ +import type { RouteRecordRaw } from 'vue-router' +import { + adminApps, + tenantApps, + miniprogramTabs, + hardwareDevices, + isNavGroup, + type NavApp, + type NavSection, + type NavLeaf, +} from '@meta/nav' + +/** + * 基于 nav.ts 自动生成路由配置 + * - 四个 Portal 各挂一棵子树 + * - 页面组件来自 NavLeaf.component(懒加载) + * + * 后台 URL 结构:///
/ + * / → 应用网格(首页) + * // → 跳到该应用首个一级菜单的首页 + */ + +/** 将后台应用列表展平为路由叶子节点 */ +const flattenPortalPages = ( + apps: NavApp[], + portalKey: string, +): RouteRecordRaw[] => { + const routes: RouteRecordRaw[] = [] + apps.forEach((app) => { + app.children.forEach((section: NavSection) => { + section.children.forEach((item) => { + const leaves: NavLeaf[] = isNavGroup(item) ? item.children : [item] + leaves.forEach((leaf) => { + const r: RouteRecordRaw = { + path: `${app.key}/${section.key}/${leaf.path}`, + name: leaf.key, + component: leaf.component as RouteRecordRaw['component'], + meta: { + portal: portalKey, + app: app.key, + section: section.key, + label: leaf.label, + }, + } as RouteRecordRaw + routes.push(r) + }) + }) + }) + }) + return routes +} + +/** 取后台第一个可访问的页面路径(用于 portal 根路径重定向) */ +const firstPortalEntry = (apps: NavApp[]): string | null => { + for (const app of apps) { + for (const section of app.children) { + const first = section.children[0] + if (!first) continue + const leaf = isNavGroup(first) ? first.children[0] : first + if (leaf) return `${app.key}/${section.key}/${leaf.path}` + } + } + return null +} + +/** 取一个应用的第一个可访问页面路径(用于 // 重定向) */ +const firstAppEntry = (app: NavApp | undefined): string | null => { + if (!app) return null + for (const section of app.children) { + const first = section.children[0] + if (!first) continue + const leaf = isNavGroup(first) ? first.children[0] : first + if (leaf) return `${section.key}/${leaf.path}` + } + return null +} + +/** 小程序路由:展平 tabs + 各 tab 的 subPages */ +const buildMiniprogramRoutes = (): RouteRecordRaw[] => { + const routes: RouteRecordRaw[] = [] + miniprogramTabs.forEach((tab) => { + routes.push({ + path: tab.page.path, + name: tab.page.key, + component: tab.page.component as RouteRecordRaw['component'], + meta: { portal: 'miniprogram', tab: tab.key, label: tab.label }, + } as RouteRecordRaw) + tab.subPages?.forEach((sub) => { + routes.push({ + path: sub.path, + name: sub.key, + component: sub.component as RouteRecordRaw['component'], + meta: { portal: 'miniprogram', tab: tab.key, label: sub.label }, + } as RouteRecordRaw) + }) + }) + return routes +} + +/** 硬件路由:按设备类型嵌套,路径形如 /hardware/cabinet/home */ +const buildHardwareRoutes = (): RouteRecordRaw[] => { + const routes: RouteRecordRaw[] = [] + hardwareDevices.forEach((device) => { + device.children.forEach((leaf) => { + routes.push({ + path: `${device.key}/${leaf.path}`, + name: leaf.key, + component: leaf.component as RouteRecordRaw['component'], + meta: { portal: 'hardware', device: device.key, label: leaf.label }, + } as RouteRecordRaw) + }) + }) + return routes +} + +/** 构建一个 portal 的所有子路由(首页 + 应用入口重定向 + 页面) */ +const buildPortalChildren = (apps: NavApp[], portalKey: string): RouteRecordRaw[] => { + const children: RouteRecordRaw[] = [] + + // 应用网格首页 + children.push({ + path: '', + name: `${portalKey}-home`, + component: () => import('@pages/tenant-portal/home/AppGrid.vue'), + meta: { portal: portalKey, label: '应用首页', isAppGrid: true }, + } as RouteRecordRaw) + + // 每个应用 // → 跳到该应用第一个页面 + apps.forEach((app) => { + const entry = firstAppEntry(app) + if (entry) { + children.push({ + path: app.key, + redirect: `/${portalKey}/${app.key}/${entry}`, + }) + } + }) + + // 所有具体页面 + children.push(...flattenPortalPages(apps, portalKey)) + + return children +} + +/** 全局路由表 */ +const routes: RouteRecordRaw[] = [ + { + path: '/gate', + name: 'gate', + component: () => import('@pages/gate/Gate.vue'), + meta: { label: '访问授权', public: true }, + }, + { + path: '/__console', + name: 'visibility-console', + component: () => import('@pages/console/VisibilityConsole.vue'), + meta: { label: '设计完成度控制台', designerOnly: true }, + }, + { + path: '/__prd', + name: 'prd-viewer', + component: () => import('@pages/prd/PrdViewer.vue'), + meta: { label: 'PRD 在线查看' }, + }, + { + path: '/__overview', + name: 'design-overview', + component: () => import('@pages/overview/DesignOverview.vue'), + meta: { label: '设计总览' }, + }, + { + path: '/', + name: 'home', + component: () => import('@pages/home/Home.vue'), + meta: { label: '原型导航首页' }, + }, + + /** 综合管理后台 */ + { + path: '/admin-portal', + component: () => import('@layouts/PortalLayout.vue'), + props: { variant: 'admin' }, + children: buildPortalChildren(adminApps, 'admin-portal'), + }, + + /** 租户运营后台 */ + { + path: '/tenant-portal', + component: () => import('@layouts/PortalLayout.vue'), + props: { variant: 'tenant' }, + children: buildPortalChildren(tenantApps, 'tenant-portal'), + }, + + /** C 端微信小程序 */ + { + path: '/miniprogram', + component: () => import('@layouts/MiniprogramLayout.vue'), + redirect: `/miniprogram/${miniprogramTabs[0]?.page.path ?? 'home'}`, + children: buildMiniprogramRoutes(), + }, + + /** 硬件终端屏 */ + { + path: '/hardware', + component: () => import('@layouts/HardwareLayout.vue'), + redirect: () => { + const d = hardwareDevices[0] + const p = d?.children[0] + return d && p ? `/hardware/${d.key}/${p.path}` : '/' + }, + children: buildHardwareRoutes(), + }, + + { + path: '/:pathMatch(.*)*', + redirect: '/', + }, +] + +// firstPortalEntry 保留导出(控制台/总览可能用) +export { firstPortalEntry } +export default routes diff --git a/src/styles/global.less b/src/styles/global.less new file mode 100644 index 0000000..e15d456 --- /dev/null +++ b/src/styles/global.less @@ -0,0 +1,46 @@ +/** + * 全局基础样式 + * 设计令牌沿用现有原型规范,AntDV 主题色保持 #1890FF + */ +:root { + --primary: #1890ff; + --primary-light: #e6f7ff; + --success: #52c41a; + --warning: #faad14; + --danger: #ff4d4f; + + --text-primary: #333; + --text-secondary: #666; + --text-placeholder: #999; + --border: #e8e8e8; + --background: #f5f7fa; + + --font-xs: 11px; + --font-sm: 13px; + --font-base: 15px; + --font-lg: 17px; + --font-xl: 20px; + --font-xxl: 24px; + + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + + --shadow-sm: 0 1px 4px rgba(0, 0, 0, 0.08); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.12); + --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.16); +} + +html, +body, +#app { + height: 100%; + margin: 0; +} + +body { + background: var(--background); + color: var(--text-primary); + font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif; + font-size: var(--font-base); +} diff --git a/src/types/shims-vue.d.ts b/src/types/shims-vue.d.ts new file mode 100644 index 0000000..8b53f84 --- /dev/null +++ b/src/types/shims-vue.d.ts @@ -0,0 +1,7 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent, Record, unknown> + export default component +} diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..0dc2fa7 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,24 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "src/types/**/*.d.ts" + ], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@meta/*": ["src/meta/*"], + "@layouts/*": ["src/layouts/*"], + "@components/*": ["src/components/*"], + "@composables/*": ["src/composables/*"], + "@pages/*": ["src/pages/*"], + "@router/*": ["src/router/*"] + }, + "types": ["node"] + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..f15377c --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "extends": "@vue/tsconfig/tsconfig.json", + "include": ["vite.config.ts"], + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "types": ["node"] + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..631d3fb --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,53 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { AntDesignVueResolver, VantResolver } from 'unplugin-vue-components/resolvers' +import { fileURLToPath, URL } from 'node:url' + +/** + * Vite 构建配置 + * - 端口固定 5180,避开正式工程 7000-7009 + * - 自动导入 Vue / Vue Router 核心 API + * - 自动注册 Ant Design Vue(Web 端)与 Vant(移动端)组件 + */ +export default defineConfig({ + server: { + port: 5180, + host: true, + open: false, + }, + + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + '@meta': fileURLToPath(new URL('./src/meta', import.meta.url)), + '@layouts': fileURLToPath(new URL('./src/layouts', import.meta.url)), + '@components': fileURLToPath(new URL('./src/components', import.meta.url)), + '@composables': fileURLToPath(new URL('./src/composables', import.meta.url)), + '@pages': fileURLToPath(new URL('./src/pages', import.meta.url)), + '@router': fileURLToPath(new URL('./src/router', import.meta.url)), + }, + }, + + plugins: [ + vue(), + + /** 自动导入 Vue/Vue Router API,无需手动 import */ + AutoImport({ + imports: ['vue', 'vue-router'], + dts: 'src/types/auto-imports.d.ts', + eslintrc: { enabled: false }, + }), + + /** 自动注册组件库 + 项目公共组件 */ + Components({ + dts: 'src/types/components.d.ts', + dirs: ['src/components', 'src/layouts/components'], + resolvers: [ + AntDesignVueResolver({ importStyle: false }), // 样式由 reset.css 与组件运行时注入 + VantResolver(), + ], + }), + ], +})