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:
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。
版本化源码目录:
/Users/destiny/dev/emoon/emoon-backend/
docs/prototypes/patient-service-desk-v3/
Open Design 发布目录:
/Users/destiny/Library/Application Support/Open Design/namespaces/
release-stable/data/projects/b7a08855-87bd-493c-92a4-31655a384570/
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
应用数据目录。
原型固定使用以下演示人物和业务数据:
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
};
状态模型至少包含:
export const initialState = {
activeTab: "home",
activePatientId: "patient-zhang-san",
events: [],
tasks: [],
resultSnapshots: {},
commands: {},
messages: [],
consents: {},
overlays: {
commandId: null,
consentId: null,
scenarioPanelOpen: false
}
};
任务结构:
{
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
}
命令结构:
{
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"
}
}
Files:
docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.state.mjsCreate:
docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.test.mjs
[ ] Step 1: 写报告事件失败测试
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:
node --test patient-service-desk-v3.test.mjs
Expected: FAIL,提示状态模块或导出函数不存在。
[ ] Step 3: 实现不可变事件转换
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: 增加事件幂等测试
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:
node --test patient-service-desk-v3.test.mjs
Expected: 2 tests PASS。
[ ] Step 6: Commit
git add patient-service-desk-v3.state.mjs patient-service-desk-v3.test.mjs
git commit -m "feat: add patient desk event state model"
Files:
docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.htmldocs/prototypes/patient-service-desk-v3/patient-service-desk-v3.cssdocs/prototypes/patient-service-desk-v3/patient-service-desk-v3.jsCopy:
/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 骨架
<body>
<div class="prototype-stage">
<main class="phone-shell" aria-label="空海医院患者服务台">
<header id="appHeader"></header>
<section id="appView" aria-live="polite"></section>
<nav id="bottomNav" aria-label="主要导航"></nav>
</main>
<aside id="scenarioPanel" aria-label="演示场景控制"></aside>
</div>
<div id="overlayRoot"></div>
<script
src="https://unpkg.com/lucide@0.468.0/dist/umd/lucide.min.js">
</script>
<script type="module" src="./patient-service-desk-v3.js"></script>
</body>
[ ] Step 2: 建立移动端 Token
: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: 实现五导航定义
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:
open patient-service-desk-v3.html
Expected:
浏览器控制台无 JavaScript 错误。
[ ] Step 5: Commit/checkpoint
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"
Files:
patient-service-desk-v3.state.mjspatient-service-desk-v3.jspatient-service-desk-v3.cssTest: patient-service-desk-v3.test.mjs
[ ] Step 1: 写任务排序失败测试
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:
node --test patient-service-desk-v3.test.mjs
Expected: FAIL,selectHomeTasks 未定义。
[ ] Step 3: 实现任务选择器
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: 实现首页组件
首页按顺序渲染:
function renderHome(state) {
return `
${renderHospitalHeader(state)}
${renderGreeting(state)}
${renderTaskSection(selectHomeTasks(state))}
${renderTodayJourney(state)}
${renderServiceGrid()}
${renderCareSection(state)}
`;
}
必须实现:
主动关怀。
[ ] Step 5: 运行测试并检查首页
Run:
node --test patient-service-desk-v3.test.mjs
Expected: 全部 PASS。
Manual:
第一屏只存在一个主 CTA。
[ ] Step 6: Commit/checkpoint
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"
Files:
patient-service-desk-v3.state.mjspatient-service-desk-v3.jspatient-service-desk-v3.cssTest: patient-service-desk-v3.test.mjs
[ ] Step 1: 写未授权不能解读的失败测试
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:
node --test patient-service-desk-v3.test.mjs
Expected: FAIL,授权函数未定义。
授权对象固定包含:
{
id: "consent-report-ai",
purpose: "用于本次报告的 AI 辅助解释",
scope: "当前血常规报告",
expiresAt: "2026-07-09T23:59:59+08:00",
revocable: true,
status: "GRANTED"
}
任务转换:
REPORT_AVAILABLE
→ CONSENT_REQUIRED
→ INTERPRETING
→ INTERPRETED
→ FOLLOWUP_RECOMMENDED
AI 结果必须按以下结构展示:
const interpretation = {
headline: "3 项指标轻度异常",
safety:
"未发现需要立即急诊处理的信号,请结合症状由医生判断",
highlights: [
{ name: "白细胞计数", value: "11.2", flag: "偏高" },
{ name: "中性粒细胞比例", value: "78%", flag: "偏高" },
{ name: "淋巴细胞比例", value: "18%", flag: "偏低" }
],
nextActions: [
"预约神经内科复诊",
"在线咨询医生",
"设置复查提醒"
]
};
Manual:
返回首页后报告任务状态保持一致。
[ ] Step 6: 运行测试并确认通过
Run:
node --test patient-service-desk-v3.test.mjs
Expected: 全部 PASS。
[ ] Step 7: Commit/checkpoint
git add patient-service-desk-v3.*
git commit -m "feat: add consented report interpretation flow"
Files:
patient-service-desk-v3.state.mjspatient-service-desk-v3.jspatient-service-desk-v3.cssTest: patient-service-desk-v3.test.mjs
[ ] Step 1: 写完整条件不重复补问测试
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:
node --test patient-service-desk-v3.test.mjs
Expected: FAIL,interaction 未定义。
原型只识别演示句:
我想挂明天神经内科李明主任的号
生成 TaskState:
{
type: "REGISTRATION",
stage: "CANDIDATE_SELECTION",
constraints: {
departmentName: "神经内科",
doctorName: "李明",
doctorTitle: "主任医师",
visitDate: "2026-07-03"
},
missingFields: [],
activeResultRef: "result-slot-001"
}
生成三个号源候选,必须使用稳定 candidateId,不能只依赖序号。
包含:
无结果替代推荐状态。
[ ] Step 5: 实现点击和文字统一选择
以下操作必须得到相同结果:
点击 candidate-slot-0930
输入“选第一个”
两者都解析为:
{
type: "SELECT_CANDIDATE",
resultRef: "result-slot-001",
resultVersion: 1,
candidateId: "candidate-slot-0930"
}
Expected:
点击和“选第一个”选择同一 candidateId。
[ ] Step 7: Commit/checkpoint
git add patient-service-desk-v3.*
git commit -m "feat: add task-aware registration assistant"
Files:
patient-service-desk-v3.state.mjspatient-service-desk-v3.jspatient-service-desk-v3.cssTest: patient-service-desk-v3.test.mjs
[ ] Step 1: 写重复确认和 UNKNOWN 测试
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 函数未定义。
允许的原型转换:
PREPARED → EXECUTING → COMPLETED
PREPARED → REJECTED
PREPARED → EXPIRED
EXECUTING → FAILED
EXECUTING → UNKNOWN
UNKNOWN → COMPLETED
UNKNOWN → FAILED
任何其他转换抛出明确错误。
确认层显示:
实现以下视觉状态:
已拒绝。
[ ] Step 5: 验证 Command 安全表达
Manual:
成功后首页出现待到院任务。
[ ] Step 6: 运行测试并确认通过
Expected: 全部 PASS。
[ ] Step 7: Commit/checkpoint
git add patient-service-desk-v3.*
git commit -m "feat: add governed command confirmation states"
Files:
patient-service-desk-v3.state.mjspatient-service-desk-v3.jspatient-service-desk-v3.cssTest: patient-service-desk-v3.test.mjs
[ ] Step 1: 写位置事件不自动签到测试
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,签到事件未处理。
支持:
APPOINTMENT_CREATED → 待到院
CHECKIN_AVAILABLE → 待确认签到
CHECKIN_COMPLETED → 候诊中
QUEUE_UPDATED → 更新人数和预计时间
VISIT_STARTED → 就诊中
VISIT_COMPLETED → 诊后服务
候诊页展示:
异常延迟提示。
[ ] Step 5: 验证签到表达
Manual:
队列事件只更新候诊,不改变挂号和签到事实。
[ ] Step 6: 运行测试并确认通过
[ ] Step 7: Commit/checkpoint
git add patient-service-desk-v3.*
git commit -m "feat: add event-driven check-in journey"
Files:
patient-service-desk-v3.jsModify: patient-service-desk-v3.css
[ ] Step 1: 实现就医总览
分组:
待办理
进行中
已完成
需要人工处理
每组至少展示一个真实演示任务。
包含:
报告支持“全部、异常、未解读”三个筛选状态。
包含:
关于 AI。
[ ] Step 4: 实现消息中心
消息按事件类型区分:
点击消息必须定位到对应 TaskState,不直接执行命令。
覆盖:
Command UNKNOWN。
[ ] Step 6: 手工回归
Expected:
技术信息只在演示控制器中可见。
[ ] Step 7: Commit/checkpoint
git add patient-service-desk-v3.js patient-service-desk-v3.css
git commit -m "feat: complete patient desk navigation and states"
Files:
patient-service-desk-v3.htmlpatient-service-desk-v3.jspatient-service-desk-v3.cssTest: patient-service-desk-v3.test.mjs
[ ] Step 1: 定义八个演示场景
export const demoScenarios = [
"EMPTY_HOME",
"REPORT_ARRIVED",
"REPORT_INTERPRETED",
"REGISTRATION_QUERY",
"COMMAND_PREPARED",
"APPOINTMENT_CREATED",
"CHECKIN_AVAILABLE",
"QUEUE_UPDATED"
];
[ ] Step 2: 写场景重置测试
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: 实现场景控制器
控制器位于手机框外,仅用于方案演示:
隐藏控制器进入纯净演示。
[ ] Step 4: 实现键盘快捷键
← 上一个场景
→ 下一个场景
R 重置
P 隐藏/显示控制器
输入框获得焦点时不得响应场景快捷键。
主故事线:
首页
→ 报告到达
→ 授权
→ AI 解读
→ 推荐复诊
→ 自然语言查号
→ Command 确认
→ 挂号成功
→ 到院签到
→ 候诊更新
Expected: 每一步状态可重复、可重置、无残留。
[ ] Step 6: Commit/checkpoint
git add patient-service-desk-v3.*
git commit -m "feat: add patient desk presentation controls"
Files:
docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.html
to Open Design 发布目录docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.css
to Open Design 发布目录docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.js
to Open Design 发布目录docs/prototypes/patient-service-desk-v3/patient-service-desk-v3.state.mjs
to Open Design 发布目录docs/prototypes/patient-service-desk-v3/assets/doctor.png
to Open Design 发布目录/Users/destiny/Library/Application Support/Open Design/namespaces/release-stable/data/projects/b7a08855-87bd-493c-92a4-31655a384570/patient-service-desk-v3.html.artifact.jsonModify:
/Users/destiny/Library/Application Support/Open Design/namespaces/release-stable/data/projects/b7a08855-87bd-493c-92a4-31655a384570/index.html
[ ] Step 1: 创建 Artifact 元数据
先发布版本化源码:
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"
{
"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
}
}
在核心页面最前方增加:
<a href="patient-service-desk-v3.html"
class="screen-card patient-desk-card">
<div class="screen-card-num">V3 · Mobile</div>
<div class="screen-card-title">患者服务台</div>
<div class="screen-card-desc">
事件驱动的手机端小程序/H5方案演示:报告解读、自然语言挂号、
Command确认、到院签到和候诊更新。
</div>
<span class="screen-card-link">打开</span>
</a>
Expected:
原有 v1/v2 原型不受影响。
[ ] Step 4: 记录发布校验
在实施记录中保存:
Open Design 项目 ID:
b7a08855-87bd-493c-92a4-31655a384570
发布入口:
patient-service-desk-v3.html
索引入口:
index.html
Files:
patient-service-desk-v3.htmlpatient-service-desk-v3.cssModify: patient-service-desk-v3.js
[ ] Step 1: 运行状态测试
Run:
node --test patient-service-desk-v3.test.mjs
Expected: 全部 PASS。
检查:
390 × 844
430 × 932
Expected:
文本不截断。
[ ] Step 3: 检查交互
逐项验证:
断网恢复。
[ ] Step 4: 检查视觉
医院品牌信息清晰;
第一屏只有一个最强主操作;
深蓝用于关键操作;
青色用于 AI 和进行中;
红色只用于高风险和失败;
不使用 Emoji;
使用 Lucide 图标;
医生图片不拉伸、不遮挡任务;
圆角、间距和阴影一致。
[ ] Step 5: 检查无障碍
所有按钮有可理解名称;
触控区域至少 44×44px;
焦点状态可见;
对话框打开后焦点进入弹层;
对话框关闭后焦点返回触发按钮;
状态不能只依赖颜色;
动画遵守 prefers-reduced-motion。
[ ] Step 6: 检查患者语言
仓库全文检索原型文件,必须不存在患者可见的:
Mock
traceId
BLE
NFC
L0
L1
cardKey
cardData
nextCard
演示控制器的技术状态摘要可以包含 Event、TaskState 和 Command。
从空状态完整演示:
报告到达
→ 授权
→ 解读
→ 复诊建议
→ 查询号源
→ 确认挂号
→ 到院
→ 确认签到
→ 候诊
Expected:
首页、就医、AI 和消息引用同一任务状态。
[ ] Step 8: Final checkpoint
git add patient-service-desk-v3.*
git commit -m "test: verify patient service desk prototype"
cd /Users/destiny/dev/emoon/emoon-backend/docs/prototypes/patient-service-desk-v3
node --test patient-service-desk-v3.test.mjs
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.*
git -C /Users/destiny/dev/emoon/emoon-backend diff --check
reviewing。