--- doc_id: DEV-202607-001 feature_id: FEAT-202606-001-unified-entry-client type: dev-progress title: 患者服务台移动端原型实施计划 status: draft owner: 医梦研发团队 created_at: 2026-07-02 updated_at: 2026-07-02 reviewers: - 产品负责人 - 终端负责人 related_docs: - DES-202607-001 - ODS-202606-002 - ADR-202606-001 related_modules: - emoon-terminal-client tags: - 患者服务台 - 移动端原型 - Open Design - 事件驱动 --- # 患者服务台移动端原型 Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use > superpowers:subagent-driven-development (recommended) or > superpowers:executing-plans to implement this plan task-by-task. > Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 在现有 Open Design 项目中建设一个可交互的患者手机端小程序/H5 方案演示原型,完整演示报告事件、患者授权、AI 解读、自然语言挂号、 Command 确认、到院签到和候诊更新。 **Architecture:** 使用单页移动端应用承载全部演示状态,纯状态模型保存 MedicalEvent、TaskState、ResultSnapshot 和 Command;视图只根据状态渲染, 不使用 Card 类型推进流程。场景控制器向状态模型注入演示事件,使首页、 AI 助手、就医旅程和消息中心同步变化。 **Tech Stack:** HTML5、CSS3、原生 JavaScript ES Modules、Node.js 内置 `node:test`、Lucide 图标、Open Design HTML Artifact。 --- ## 1. 实施范围 ### 1.1 源码与发布目录 版本化源码目录: ```text /Users/destiny/dev/emoon/emoon-backend/ docs/prototypes/patient-service-desk-v3/ ``` Open Design 发布目录: ```text /Users/destiny/Library/Application Support/Open Design/namespaces/ release-stable/data/projects/b7a08855-87bd-493c-92a4-31655a384570/ ``` ### 1.2 文件结构 ```text docs/prototypes/patient-service-desk-v3/ ├── assets/ │ └── doctor.png ├── patient-service-desk-v3.css ├── patient-service-desk-v3.html ├── patient-service-desk-v3.js ├── patient-service-desk-v3.state.mjs └── patient-service-desk-v3.test.mjs Open Design 发布目录/ ├── patient-service-desk-v3.css ├── patient-service-desk-v3.html ├── patient-service-desk-v3.html.artifact.json ├── patient-service-desk-v3.js ├── patient-service-desk-v3.state.mjs └── index.html ``` 文件职责: | 文件 | 职责 | |---|---| | `patient-service-desk-v3.html` | 移动端 App Shell、页面容器、对话框和演示控制器容器 | | `patient-service-desk-v3.css` | 品牌 Token、移动端布局、组件状态和动效 | | `patient-service-desk-v3.state.mjs` | 事件、TaskState、ResultSnapshot、Command 和场景转换 | | `patient-service-desk-v3.js` | DOM 渲染、用户交互、导航和无障碍行为 | | `patient-service-desk-v3.test.mjs` | 纯状态模型测试 | | `.artifact.json` | Open Design Artifact 元数据 | | `index.html` | 将 v3 原型加入现有原型索引 | 不修改现有 v1/v2 HTML,以便对比演示和回退。 除 Task 10 发布步骤外,所有命令从版本化源码目录执行。每个任务的 Git checkpoint 提交 `emoon-backend` 中的原型源码,不提交 Open Design 应用数据目录。 ## 2. 统一演示数据 原型固定使用以下演示人物和业务数据: ```js export const demoPatient = { id: "patient-zhang-san", name: "张三", relation: "本人", hospital: "空海医院", campus: "本部院区" }; export const demoReport = { id: "report-blood-20260702", type: "血常规", issuedAt: "2026-07-02 10:18", reviewStatus: "FINAL", abnormalCount: 3, totalCount: 18, summary: "3 项指标轻度异常,未发现需要立即急诊处理的信号" }; export const demoAppointment = { department: "神经内科", doctor: "李明", title: "主任医师", visitAt: "2026-07-03 09:30", location: "门诊三楼 302", fee: 25 }; ``` ## 3. 状态模型 状态模型至少包含: ```js export const initialState = { activeTab: "home", activePatientId: "patient-zhang-san", events: [], tasks: [], resultSnapshots: {}, commands: {}, messages: [], consents: {}, overlays: { commandId: null, consentId: null, scenarioPanelOpen: false } }; ``` 任务结构: ```js { id: "task-report-001", type: "REPORT_REVIEW", status: "ACTION_REQUIRED", stage: "REPORT_AVAILABLE", priority: 90, title: "血常规报告已出", description: "18 项指标中有 3 项超出参考范围", primaryAction: "INTERPRET_REPORT", secondaryAction: "VIEW_REPORT", expiresAt: null } ``` 命令结构: ```js { id: "command-register-001", type: "REGISTER_APPOINTMENT", riskLevel: "L2", status: "PREPARED", version: 1, expiresAt: "2026-07-02T15:02:00+08:00", summary: { department: "神经内科", doctor: "李明 主任医师", visitAt: "7月3日 09:30", fee: "¥25.00" } } ``` ## 4. 实施任务 ### Task 1: 创建状态模型和基础测试 **Files:** - Create: `docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.state.mjs` - Create: `docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs` - [ ] **Step 1: 写报告事件失败测试** ```js import test from "node:test"; import assert from "node:assert/strict"; import { createInitialState, dispatchMedicalEvent } from "./patient-service-desk-v3.state.mjs"; test("REPORT_ISSUED creates one actionable report task", () => { const state = createInitialState(); const next = dispatchMedicalEvent(state, { id: "event-report-001", type: "REPORT_ISSUED", occurredAt: "2026-07-02T10:18:00+08:00", payload: { reportId: "report-blood-20260702" } }); assert.equal(next.tasks.length, 1); assert.equal(next.tasks[0].type, "REPORT_REVIEW"); assert.equal(next.tasks[0].status, "ACTION_REQUIRED"); assert.equal(next.tasks[0].primaryAction, "INTERPRET_REPORT"); }); ``` - [ ] **Step 2: 运行测试并确认失败** Run: ```bash node --test patient-service-desk-v3.test.mjs ``` Expected: FAIL,提示状态模块或导出函数不存在。 - [ ] **Step 3: 实现不可变事件转换** ```js export function createInitialState() { return { activeTab: "home", activePatientId: "patient-zhang-san", events: [], tasks: [], resultSnapshots: {}, commands: {}, messages: [], consents: {}, overlays: { commandId: null, consentId: null, scenarioPanelOpen: false } }; } export function dispatchMedicalEvent(state, event) { if (state.events.some(item => item.id === event.id)) { return state; } const next = { ...state, events: [...state.events, event], tasks: [...state.tasks], messages: [...state.messages] }; if (event.type === "REPORT_ISSUED") { next.tasks.push({ id: "task-report-001", type: "REPORT_REVIEW", status: "ACTION_REQUIRED", stage: "REPORT_AVAILABLE", priority: 90, title: "血常规报告已出", description: "18 项指标中有 3 项超出参考范围", primaryAction: "INTERPRET_REPORT", secondaryAction: "VIEW_REPORT", expiresAt: null }); next.messages.push({ id: "message-report-001", type: "REPORT", unread: true, title: "血常规报告已出", createdAt: event.occurredAt }); } return next; } ``` - [ ] **Step 4: 增加事件幂等测试** ```js test("duplicate event does not duplicate task or message", () => { const event = { id: "event-report-001", type: "REPORT_ISSUED", occurredAt: "2026-07-02T10:18:00+08:00", payload: { reportId: "report-blood-20260702" } }; const once = dispatchMedicalEvent(createInitialState(), event); const twice = dispatchMedicalEvent(once, event); assert.equal(twice.tasks.length, 1); assert.equal(twice.messages.length, 1); }); ``` - [ ] **Step 5: 运行测试并确认通过** Run: ```bash node --test patient-service-desk-v3.test.mjs ``` Expected: 2 tests PASS。 - [ ] **Step 6: Commit** ```bash git add patient-service-desk-v3.state.mjs patient-service-desk-v3.test.mjs git commit -m "feat: add patient desk event state model" ``` ### Task 2: 建立移动端 App Shell 和设计 Token **Files:** - Create: `docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.html` - Create: `docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.css` - Create: `docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.js` - Copy: `/Users/destiny/Library/Application Support/Open Design/namespaces/release-stable/data/projects/b7a08855-87bd-493c-92a4-31655a384570/doctor.png` to `docs/prototypes/patient-service-desk-v3/assets/doctor.png` - [ ] **Step 1: 创建语义化 HTML 骨架** ```html
``` - [ ] **Step 2: 建立移动端 Token** ```css :root { --bg: #f6f8fc; --surface: #ffffff; --surface-soft: #f0f5ff; --fg: #1a1835; --muted: #706d89; --border: #e5e8f2; --brand: #2b1f99; --ai: #3ad4d8; --success: #1aa981; --warning: #e58a16; --danger: #d74747; --radius-sm: 10px; --radius-md: 16px; --radius-lg: 24px; --shadow-card: 0 8px 24px rgb(43 31 153 / 8%); --tap-size: 44px; } .phone-shell { width: min(100%, 430px); min-height: 844px; background: var(--bg); border-radius: 32px; overflow: hidden; position: relative; } button, a { min-height: var(--tap-size); } ``` - [ ] **Step 3: 实现五导航定义** ```js export const navItems = [ { id: "home", label: "首页", icon: "house" }, { id: "care", label: "就医", icon: "stethoscope" }, { id: "assistant", label: "AI助手", icon: "sparkles" }, { id: "records", label: "健康档案", icon: "folder-heart" }, { id: "profile", label: "我的", icon: "user-round" } ]; ``` - [ ] **Step 4: 加载页面并检查基础结构** Run: ```bash open patient-service-desk-v3.html ``` Expected: - 430px 手机容器完整显示; - 五个底部导航均可见; - 页面没有水平滚动; - Lucide 图标加载成功; - 浏览器控制台无 JavaScript 错误。 - [ ] **Step 5: Commit/checkpoint** ```bash git add patient-service-desk-v3.html patient-service-desk-v3.css patient-service-desk-v3.js git commit -m "feat: scaffold mobile patient service desk" ``` ### Task 3: 实现首页和任务排序 **Files:** - Modify: `patient-service-desk-v3.state.mjs` - Modify: `patient-service-desk-v3.js` - Modify: `patient-service-desk-v3.css` - Test: `patient-service-desk-v3.test.mjs` - [ ] **Step 1: 写任务排序失败测试** ```js import { selectHomeTasks } from "./patient-service-desk-v3.state.mjs"; test("home tasks are sorted by priority and completion", () => { const state = createInitialState(); state.tasks = [ { id: "done", priority: 100, status: "COMPLETED" }, { id: "payment", priority: 80, status: "ACTION_REQUIRED" }, { id: "report", priority: 90, status: "ACTION_REQUIRED" } ]; assert.deepEqual( selectHomeTasks(state).map(item => item.id), ["report", "payment", "done"] ); }); ``` - [ ] **Step 2: 运行测试并确认失败** Run: ```bash node --test patient-service-desk-v3.test.mjs ``` Expected: FAIL,`selectHomeTasks` 未定义。 - [ ] **Step 3: 实现任务选择器** ```js const statusRank = { ACTION_REQUIRED: 0, IN_PROGRESS: 1, UPCOMING: 2, COMPLETED: 3 }; export function selectHomeTasks(state) { return [...state.tasks].sort((left, right) => { const statusDelta = statusRank[left.status] - statusRank[right.status]; return statusDelta || right.priority - left.priority; }); } ``` - [ ] **Step 4: 实现首页组件** 首页按顺序渲染: ```js function renderHome(state) { return ` ${renderHospitalHeader(state)} ${renderGreeting(state)} ${renderTaskSection(selectHomeTasks(state))} ${renderTodayJourney(state)} ${renderServiceGrid()} ${renderCareSection(state)} `; } ``` 必须实现: - 医院和就诊人; - 未读消息角标; - AI 轻量输入入口; - “现在要做”任务卡; - 今日旅程; - 八个常用服务; - 主动关怀。 - [ ] **Step 5: 运行测试并检查首页** Run: ```bash node --test patient-service-desk-v3.test.mjs ``` Expected: 全部 PASS。 Manual: - 空闲场景显示“今天没有待处理事项”; - 注入报告事件后报告任务排第一; - 已完成任务不抢占第一屏; - 第一屏只存在一个主 CTA。 - [ ] **Step 6: Commit/checkpoint** ```bash git add patient-service-desk-v3.state.mjs patient-service-desk-v3.js \ patient-service-desk-v3.css patient-service-desk-v3.test.mjs git commit -m "feat: add task-driven patient desk home" ``` ### Task 4: 实现报告授权、详情和 AI 解读 **Files:** - Modify: `patient-service-desk-v3.state.mjs` - Modify: `patient-service-desk-v3.js` - Modify: `patient-service-desk-v3.css` - Test: `patient-service-desk-v3.test.mjs` - [ ] **Step 1: 写未授权不能解读的失败测试** ```js import { requestReportInterpretation, grantConsent } from "./patient-service-desk-v3.state.mjs"; test("report interpretation requires active consent", () => { const state = dispatchMedicalEvent(createInitialState(), { id: "event-report-001", type: "REPORT_ISSUED", occurredAt: "2026-07-02T10:18:00+08:00", payload: { reportId: "report-blood-20260702" } }); const blocked = requestReportInterpretation( state, "report-blood-20260702" ); assert.equal(blocked.overlays.consentId, "consent-report-ai"); assert.equal(blocked.tasks[0].stage, "CONSENT_REQUIRED"); const allowed = requestReportInterpretation( grantConsent(blocked, "consent-report-ai"), "report-blood-20260702" ); assert.equal(allowed.tasks[0].stage, "INTERPRETING"); }); ``` - [ ] **Step 2: 运行测试并确认失败** Run: ```bash node --test patient-service-desk-v3.test.mjs ``` Expected: FAIL,授权函数未定义。 - [ ] **Step 3: 实现授权和解读状态转换** 授权对象固定包含: ```js { id: "consent-report-ai", purpose: "用于本次报告的 AI 辅助解释", scope: "当前血常规报告", expiresAt: "2026-07-09T23:59:59+08:00", revocable: true, status: "GRANTED" } ``` 任务转换: ```text REPORT_AVAILABLE → CONSENT_REQUIRED → INTERPRETING → INTERPRETED → FOLLOWUP_RECOMMENDED ``` - [ ] **Step 4: 实现四个报告视图** 1. 报告授权; 2. 报告详情; 3. AI 解读加载; 4. AI 解读结果。 AI 结果必须按以下结构展示: ```js const interpretation = { headline: "3 项指标轻度异常", safety: "未发现需要立即急诊处理的信号,请结合症状由医生判断", highlights: [ { name: "白细胞计数", value: "11.2", flag: "偏高" }, { name: "中性粒细胞比例", value: "78%", flag: "偏高" }, { name: "淋巴细胞比例", value: "18%", flag: "偏低" } ], nextActions: [ "预约神经内科复诊", "在线咨询医生", "设置复查提醒" ] }; ``` - [ ] **Step 5: 验证授权和患者安全** Manual: - 未授权点击“AI 解读”只打开授权层; - 拒绝授权后仍能查看原始报告; - 解读页不出现“确诊”“一定是”等确定性表述; - 撤回授权后不能重新打开历史 AI 解读; - 返回首页后报告任务状态保持一致。 - [ ] **Step 6: 运行测试并确认通过** Run: ```bash node --test patient-service-desk-v3.test.mjs ``` Expected: 全部 PASS。 - [ ] **Step 7: Commit/checkpoint** ```bash git add patient-service-desk-v3.* git commit -m "feat: add consented report interpretation flow" ``` ### Task 5: 实现 AI 助手和自然语言挂号 **Files:** - Modify: `patient-service-desk-v3.state.mjs` - Modify: `patient-service-desk-v3.js` - Modify: `patient-service-desk-v3.css` - Test: `patient-service-desk-v3.test.mjs` - [ ] **Step 1: 写完整条件不重复补问测试** ```js import { submitInteraction } from "./patient-service-desk-v3.state.mjs"; test("complete registration request produces slot candidates directly", () => { const next = submitInteraction( createInitialState(), "我想挂明天神经内科李明主任的号" ); const task = next.tasks.find(item => item.type === "REGISTRATION"); assert.equal(task.stage, "CANDIDATE_SELECTION"); assert.equal(task.missingFields.length, 0); assert.ok(task.activeResultRef); assert.equal( next.resultSnapshots[task.activeResultRef].items.length, 3 ); }); ``` - [ ] **Step 2: 运行测试并确认失败** Run: ```bash node --test patient-service-desk-v3.test.mjs ``` Expected: FAIL,interaction 未定义。 - [ ] **Step 3: 实现挂号演示解析器** 原型只识别演示句: ```text 我想挂明天神经内科李明主任的号 ``` 生成 TaskState: ```js { type: "REGISTRATION", stage: "CANDIDATE_SELECTION", constraints: { departmentName: "神经内科", doctorName: "李明", doctorTitle: "主任医师", visitDate: "2026-07-03" }, missingFields: [], activeResultRef: "result-slot-001" } ``` 生成三个号源候选,必须使用稳定 `candidateId`,不能只依赖序号。 - [ ] **Step 4: 实现 AI 对话页面** 包含: - 场景建议; - 消息列表; - 完整文本回答; - 号源候选 Presentation; - 固定底部输入框; - 返回当前任务; - 无结果替代推荐状态。 - [ ] **Step 5: 实现点击和文字统一选择** 以下操作必须得到相同结果: ```text 点击 candidate-slot-0930 输入“选第一个” ``` 两者都解析为: ```js { type: "SELECT_CANDIDATE", resultRef: "result-slot-001", resultVersion: 1, candidateId: "candidate-slot-0930" } ``` - [ ] **Step 6: 运行测试并手工检查** Expected: - 完整条件直接返回候选; - 不出现科室卡、医生卡、时间卡链; - Presentation 隐藏后文字仍完整; - 点击和“选第一个”选择同一 candidateId。 - [ ] **Step 7: Commit/checkpoint** ```bash git add patient-service-desk-v3.* git commit -m "feat: add task-aware registration assistant" ``` ### Task 6: 实现 Command 确认和异常状态 **Files:** - Modify: `patient-service-desk-v3.state.mjs` - Modify: `patient-service-desk-v3.js` - Modify: `patient-service-desk-v3.css` - Test: `patient-service-desk-v3.test.mjs` - [ ] **Step 1: 写重复确认和 UNKNOWN 测试** ```js import { prepareRegistrationCommand, confirmCommand, markCommandUnknown } from "./patient-service-desk-v3.state.mjs"; test("same command cannot execute twice", () => { const prepared = prepareRegistrationCommand(createInitialState(), { candidateId: "candidate-slot-0930" }); const commandId = prepared.overlays.commandId; const completed = confirmCommand(prepared, commandId); const repeated = confirmCommand(completed, commandId); assert.equal(repeated.commands[commandId].status, "COMPLETED"); assert.equal(repeated.events.filter( item => item.type === "APPOINTMENT_CREATED" ).length, 1); }); test("unknown command blocks retry", () => { const prepared = prepareRegistrationCommand(createInitialState(), { candidateId: "candidate-slot-0930" }); const commandId = prepared.overlays.commandId; const unknown = markCommandUnknown(prepared, commandId); assert.throws( () => confirmCommand(unknown, commandId), /结果确认中/ ); }); ``` - [ ] **Step 2: 运行测试并确认失败** Expected: FAIL,Command 函数未定义。 - [ ] **Step 3: 实现 Command 状态机** 允许的原型转换: ```text PREPARED → EXECUTING → COMPLETED PREPARED → REJECTED PREPARED → EXPIRED EXECUTING → FAILED EXECUTING → UNKNOWN UNKNOWN → COMPLETED UNKNOWN → FAILED ``` 任何其他转换抛出明确错误。 - [ ] **Step 4: 实现统一确认层** 确认层显示: - 操作名称; - 就诊人; - 科室、医生和时间; - 金额; - 有效倒计时; - 确认和暂不操作。 实现以下视觉状态: - 待确认; - 执行中; - 成功; - 失败; - 已过期; - 结果确认中; - 已拒绝。 - [ ] **Step 5: 验证 Command 安全表达** Manual: - 页面不能编辑金额和号源参数; - 重复点击确认只产生一次成功事件; - UNKNOWN 显示“请勿重复操作”; - 过期后只允许重新查询号源; - 成功后首页出现待到院任务。 - [ ] **Step 6: 运行测试并确认通过** Expected: 全部 PASS。 - [ ] **Step 7: Commit/checkpoint** ```bash git add patient-service-desk-v3.* git commit -m "feat: add governed command confirmation states" ``` ### Task 7: 实现到院、签到和候诊事件 **Files:** - Modify: `patient-service-desk-v3.state.mjs` - Modify: `patient-service-desk-v3.js` - Modify: `patient-service-desk-v3.css` - Test: `patient-service-desk-v3.test.mjs` - [ ] **Step 1: 写位置事件不自动签到测试** ```js test("arrival event offers check-in but does not execute it", () => { const next = dispatchMedicalEvent(createInitialState(), { id: "event-arrival-001", type: "CHECKIN_AVAILABLE", occurredAt: "2026-07-03T09:10:00+08:00", payload: { location: "门诊三楼神经内科诊区" } }); const task = next.tasks.find(item => item.type === "CHECK_IN"); assert.equal(task.status, "ACTION_REQUIRED"); assert.equal(task.stage, "CONFIRMATION_REQUIRED"); assert.equal( next.events.some(item => item.type === "CHECKIN_COMPLETED"), false ); }); ``` - [ ] **Step 2: 运行测试并确认失败** Expected: FAIL,签到事件未处理。 - [ ] **Step 3: 实现事件转换** 支持: ```text APPOINTMENT_CREATED → 待到院 CHECKIN_AVAILABLE → 待确认签到 CHECKIN_COMPLETED → 候诊中 QUEUE_UPDATED → 更新人数和预计时间 VISIT_STARTED → 就诊中 VISIT_COMPLETED → 诊后服务 ``` - [ ] **Step 4: 实现今日旅程和候诊页** 候诊页展示: - 当前排队号; - 前方人数; - 预计时间; - 当前诊室; - 导航; - 队列更新时间; - 异常延迟提示。 - [ ] **Step 5: 验证签到表达** Manual: - 到达事件只出现“确认签到”; - 不使用“系统自动帮您完成签到”文案; - 签到成功后旅程节点和首页任务同步更新; - 队列事件只更新候诊,不改变挂号和签到事实。 - [ ] **Step 6: 运行测试并确认通过** - [ ] **Step 7: Commit/checkpoint** ```bash git add patient-service-desk-v3.* git commit -m "feat: add event-driven check-in journey" ``` ### Task 8: 补齐五导航和系统状态 **Files:** - Modify: `patient-service-desk-v3.js` - Modify: `patient-service-desk-v3.css` - [ ] **Step 1: 实现就医总览** 分组: ```text 待办理 进行中 已完成 需要人工处理 ``` 每组至少展示一个真实演示任务。 - [ ] **Step 2: 实现健康档案** 包含: - 检验报告; - 检查报告; - 门诊记录; - 处方与用药; - 过敏史; - 授权记录。 报告支持“全部、异常、未解读”三个筛选状态。 - [ ] **Step 3: 实现我的** 包含: - 就诊人管理; - 授权中心; - 消息设置; - 常用院区; - 医保和支付; - 电子票据; - 人工客服; - 关于 AI。 - [ ] **Step 4: 实现消息中心** 消息按事件类型区分: - 报告; - 就医; - 缴费; - 随访; - 系统。 点击消息必须定位到对应 TaskState,不直接执行命令。 - [ ] **Step 5: 实现系统状态** 覆盖: - 初始加载; - AI 解读加载; - 空状态; - 断网; - 服务降级; - 查询失败; - Command 失败; - Command UNKNOWN。 - [ ] **Step 6: 手工回归** Expected: - 五个导航均可进入和返回; - 所有空状态有下一步; - 断网不清空本地任务展示; - 无患者可见 Mock、BLE、NFC、L0/L1 或 traceId; - 技术信息只在演示控制器中可见。 - [ ] **Step 7: Commit/checkpoint** ```bash git add patient-service-desk-v3.js patient-service-desk-v3.css git commit -m "feat: complete patient desk navigation and states" ``` ### Task 9: 建设方案演示控制器 **Files:** - Modify: `patient-service-desk-v3.html` - Modify: `patient-service-desk-v3.js` - Modify: `patient-service-desk-v3.css` - Test: `patient-service-desk-v3.test.mjs` - [ ] **Step 1: 定义八个演示场景** ```js export const demoScenarios = [ "EMPTY_HOME", "REPORT_ARRIVED", "REPORT_INTERPRETED", "REGISTRATION_QUERY", "COMMAND_PREPARED", "APPOINTMENT_CREATED", "CHECKIN_AVAILABLE", "QUEUE_UPDATED" ]; ``` - [ ] **Step 2: 写场景重置测试** ```js import { loadScenario } from "./patient-service-desk-v3.state.mjs"; test("loading scenario resets previous state deterministically", () => { const first = loadScenario(createInitialState(), "REPORT_ARRIVED"); const second = loadScenario(first, "EMPTY_HOME"); assert.equal(second.tasks.length, 0); assert.equal(second.events.length, 0); assert.equal(second.activeTab, "home"); }); ``` - [ ] **Step 3: 实现场景控制器** 控制器位于手机框外,仅用于方案演示: - 上一步; - 下一步; - 场景列表; - 重置; - 切换 390×844 / 430×932 视口; - 显示当前 Event、TaskState 和 Command 摘要; - 隐藏控制器进入纯净演示。 - [ ] **Step 4: 实现键盘快捷键** ```text ← 上一个场景 → 下一个场景 R 重置 P 隐藏/显示控制器 ``` 输入框获得焦点时不得响应场景快捷键。 - [ ] **Step 5: 运行测试并执行主故事线** 主故事线: ```text 首页 → 报告到达 → 授权 → AI 解读 → 推荐复诊 → 自然语言查号 → Command 确认 → 挂号成功 → 到院签到 → 候诊更新 ``` Expected: 每一步状态可重复、可重置、无残留。 - [ ] **Step 6: Commit/checkpoint** ```bash git add patient-service-desk-v3.* git commit -m "feat: add patient desk presentation controls" ``` ### Task 10: 接入 Open Design 索引和 Artifact **Files:** - Copy: `docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.html` to Open Design 发布目录 - Copy: `docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.css` to Open Design 发布目录 - Copy: `docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.js` to Open Design 发布目录 - Copy: `docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.state.mjs` to Open Design 发布目录 - Copy: `docs/prototypes/patient-service-desk-v3/assets/doctor.png` to Open Design 发布目录 - Create: `/Users/destiny/Library/Application Support/Open Design/namespaces/release-stable/data/projects/b7a08855-87bd-493c-92a4-31655a384570/patient-service-desk-v3.html.artifact.json` - Modify: `/Users/destiny/Library/Application Support/Open Design/namespaces/release-stable/data/projects/b7a08855-87bd-493c-92a4-31655a384570/index.html` - [ ] **Step 1: 创建 Artifact 元数据** 先发布版本化源码: ```bash OPEN_DESIGN="/Users/destiny/Library/Application Support/Open Design/namespaces/release-stable/data/projects/b7a08855-87bd-493c-92a4-31655a384570" cp patient-service-desk-v3.html "$OPEN_DESIGN/" cp patient-service-desk-v3.css "$OPEN_DESIGN/" cp patient-service-desk-v3.js "$OPEN_DESIGN/" cp patient-service-desk-v3.state.mjs "$OPEN_DESIGN/" cp assets/doctor.png "$OPEN_DESIGN/doctor.png" ``` ```json { "version": 1, "kind": "html", "title": "patient-service-desk-v3.html", "entry": "patient-service-desk-v3.html", "renderer": "html", "status": "complete", "exports": ["html", "pdf", "zip"], "createdAt": "2026-07-02T00:00:00.000Z", "updatedAt": "2026-07-02T00:00:00.000Z", "metadata": { "inferred": false, "reconciled": true } } ``` - [ ] **Step 2: 更新索引** 在核心页面最前方增加: ```html
V3 · Mobile
患者服务台
事件驱动的手机端小程序/H5方案演示:报告解读、自然语言挂号、 Command确认、到院签到和候诊更新。
打开
``` - [ ] **Step 3: 验证索引入口** Expected: - Open Design 能识别新 Artifact; - 索引点击可进入 v3; - 返回索引可再次打开; - 原有 v1/v2 原型不受影响。 - [ ] **Step 4: 记录发布校验** 在实施记录中保存: ```text Open Design 项目 ID: b7a08855-87bd-493c-92a4-31655a384570 发布入口: patient-service-desk-v3.html 索引入口: index.html ``` ### Task 11: 视觉、交互和无障碍 QA **Files:** - Modify: `patient-service-desk-v3.html` - Modify: `patient-service-desk-v3.css` - Modify: `patient-service-desk-v3.js` - [ ] **Step 1: 运行状态测试** Run: ```bash node --test patient-service-desk-v3.test.mjs ``` Expected: 全部 PASS。 - [ ] **Step 2: 检查两个手机视口** 检查: ```text 390 × 844 430 × 932 ``` Expected: - 无横向滚动; - 底部导航不遮挡内容; - 弹层不超出视口; - 主按钮在安全区内; - 文本不截断。 - [ ] **Step 3: 检查交互** 逐项验证: - 就诊人切换; - 五导航; - 首页任务主次操作; - 报告授权同意和拒绝; - 报告详情展开; - AI 输入; - 候选选择; - Command 确认和拒绝; - UNKNOWN 禁止重复; - 消息定位任务; - 场景控制器; - 断网恢复。 - [ ] **Step 4: 检查视觉** - 医院品牌信息清晰; - 第一屏只有一个最强主操作; - 深蓝用于关键操作; - 青色用于 AI 和进行中; - 红色只用于高风险和失败; - 不使用 Emoji; - 使用 Lucide 图标; - 医生图片不拉伸、不遮挡任务; - 圆角、间距和阴影一致。 - [ ] **Step 5: 检查无障碍** - 所有按钮有可理解名称; - 触控区域至少 44×44px; - 焦点状态可见; - 对话框打开后焦点进入弹层; - 对话框关闭后焦点返回触发按钮; - 状态不能只依赖颜色; - 动画遵守 `prefers-reduced-motion`。 - [ ] **Step 6: 检查患者语言** 仓库全文检索原型文件,必须不存在患者可见的: ```text Mock traceId BLE NFC L0 L1 cardKey cardData nextCard ``` 演示控制器的技术状态摘要可以包含 Event、TaskState 和 Command。 - [ ] **Step 7: 最终回归** 从空状态完整演示: ```text 报告到达 → 授权 → 解读 → 复诊建议 → 查询号源 → 确认挂号 → 到院 → 确认签到 → 候诊 ``` Expected: - 无断链; - 无重复业务动作; - 所有状态可重置; - Card 隐藏后文本仍能说明结果; - 首页、就医、AI 和消息引用同一任务状态。 - [ ] **Step 8: Final checkpoint** ```bash git add patient-service-desk-v3.* git commit -m "test: verify patient service desk prototype" ``` ## 5. 验证命令汇总 ```bash cd /Users/destiny/dev/emoon/emoon-backend/docs/prototypes/patient-service-desk-v3 node --test patient-service-desk-v3.test.mjs ``` ```bash rg -n \ "Mock|traceId|BLE|NFC|L0|L1|cardKey|cardData|nextCard" \ /Users/destiny/dev/emoon/emoon-backend/docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.* ``` ```bash git -C /Users/destiny/dev/emoon/emoon-backend diff --check ``` ## 6. 完成标准 1. Open Design 索引能打开 v3 患者服务台。 2. 390×844 和 430×932 两个视口均无布局破损。 3. 八个演示场景可独立加载和重置。 4. 报告主线、挂号主线和到院主线能够连续演示。 5. TaskState 不依赖 Card 类型推进。 6. 报告解读前必须授权。 7. Command 状态覆盖确认、执行、成功、失败、过期和 UNKNOWN。 8. 位置事件不能自动完成签到。 9. Card 隐藏或渲染失败不阻塞文本和任务。 10. Node 状态测试全部通过。 11. 原有 v1/v2 原型保持不变。 12. 设计文档根据最终原型更新为 `reviewing`。