---
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`。