患者服务台移动端原型实施计划.md 32 KB


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 源码与发布目录

版本化源码目录:

/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/

1.2 文件结构

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. 统一演示数据

原型固定使用以下演示人物和业务数据:

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. 状态模型

状态模型至少包含:

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"
  }
}

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: 写报告事件失败测试

    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"
    

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 骨架

    <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:

  • 430px 手机容器完整显示;
  • 五个底部导航均可见;
  • 页面没有水平滚动;
  • Lucide 图标加载成功;
  • 浏览器控制台无 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"
    

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: 写任务排序失败测试

    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)}
  `;
}

必须实现:

  • 医院和就诊人;
  • 未读消息角标;
  • AI 轻量输入入口;
  • “现在要做”任务卡;
  • 今日旅程;
  • 八个常用服务;
  • 主动关怀。

  • [ ] 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"
    

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: 写未授权不能解读的失败测试

    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,授权函数未定义。

  • Step 3: 实现授权和解读状态转换

授权对象固定包含:

{
  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
  • Step 4: 实现四个报告视图
  1. 报告授权;
  2. 报告详情;
  3. AI 解读加载;
  4. AI 解读结果。

AI 结果必须按以下结构展示:

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:

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"
    

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: 写完整条件不重复补问测试

    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 未定义。

  • Step 3: 实现挂号演示解析器

原型只识别演示句:

我想挂明天神经内科李明主任的号

生成 TaskState:

{
  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: 实现点击和文字统一选择

以下操作必须得到相同结果:

点击 candidate-slot-0930
输入“选第一个”

两者都解析为:

{
  type: "SELECT_CANDIDATE",
  resultRef: "result-slot-001",
  resultVersion: 1,
  candidateId: "candidate-slot-0930"
}
  • Step 6: 运行测试并手工检查

Expected:

  • 完整条件直接返回候选;
  • 不出现科室卡、医生卡、时间卡链;
  • Presentation 隐藏后文字仍完整;
  • 点击和“选第一个”选择同一 candidateId。

  • [ ] Step 7: Commit/checkpoint

    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 测试

    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 状态机

允许的原型转换:

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

    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: 写位置事件不自动签到测试

    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: 实现事件转换

支持:

APPOINTMENT_CREATED → 待到院
CHECKIN_AVAILABLE → 待确认签到
CHECKIN_COMPLETED → 候诊中
QUEUE_UPDATED → 更新人数和预计时间
VISIT_STARTED → 就诊中
VISIT_COMPLETED → 诊后服务
  • Step 4: 实现今日旅程和候诊页

候诊页展示:

  • 当前排队号;
  • 前方人数;
  • 预计时间;
  • 当前诊室;
  • 导航;
  • 队列更新时间;
  • 异常延迟提示。

  • [ ] 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"
    

Task 8: 补齐五导航和系统状态

Files:

  • Modify: patient-service-desk-v3.js
  • Modify: patient-service-desk-v3.css

  • [ ] Step 1: 实现就医总览

分组:

待办理
进行中
已完成
需要人工处理

每组至少展示一个真实演示任务。

  • 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

    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: 定义八个演示场景

    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: 实现场景控制器

控制器位于手机框外,仅用于方案演示:

  • 上一步;
  • 下一步;
  • 场景列表;
  • 重置;
  • 切换 390×844 / 430×932 视口;
  • 显示当前 Event、TaskState 和 Command 摘要;
  • 隐藏控制器进入纯净演示。

  • [ ] Step 4: 实现键盘快捷键

    ← 上一个场景
    → 下一个场景
    R 重置
    P 隐藏/显示控制器
    

输入框获得焦点时不得响应场景快捷键。

  • Step 5: 运行测试并执行主故事线

主故事线:

首页
→ 报告到达
→ 授权
→ AI 解读
→ 推荐复诊
→ 自然语言查号
→ Command 确认
→ 挂号成功
→ 到院签到
→ 候诊更新

Expected: 每一步状态可重复、可重置、无残留。

  • [ ] Step 6: Commit/checkpoint

    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 元数据

先发布版本化源码:

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
  }
}
  • Step 2: 更新索引

在核心页面最前方增加:

<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>
  • Step 3: 验证索引入口

Expected:

  • Open Design 能识别新 Artifact;
  • 索引点击可进入 v3;
  • 返回索引可再次打开;
  • 原有 v1/v2 原型不受影响。

  • [ ] Step 4: 记录发布校验

在实施记录中保存:

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:

node --test patient-service-desk-v3.test.mjs

Expected: 全部 PASS。

  • Step 2: 检查两个手机视口

检查:

390 × 844
430 × 932

Expected:

  • 无横向滚动;
  • 底部导航不遮挡内容;
  • 弹层不超出视口;
  • 主按钮在安全区内;
  • 文本不截断。

  • [ ] Step 3: 检查交互

逐项验证:

  • 就诊人切换;
  • 五导航;
  • 首页任务主次操作;
  • 报告授权同意和拒绝;
  • 报告详情展开;
  • AI 输入;
  • 候选选择;
  • Command 确认和拒绝;
  • UNKNOWN 禁止重复;
  • 消息定位任务;
  • 场景控制器;
  • 断网恢复。

  • [ ] 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。

  • Step 7: 最终回归

从空状态完整演示:

报告到达
→ 授权
→ 解读
→ 复诊建议
→ 查询号源
→ 确认挂号
→ 到院
→ 确认签到
→ 候诊

Expected:

  • 无断链;
  • 无重复业务动作;
  • 所有状态可重置;
  • Card 隐藏后文本仍能说明结果;
  • 首页、就医、AI 和消息引用同一任务状态。

  • [ ] Step 8: Final checkpoint

    git add patient-service-desk-v3.*
    git commit -m "test: verify patient service desk prototype"
    

5. 验证命令汇总

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

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