前端工程启动指南.md 8.3 KB


doc_id: DDS-202606-003 feature_id: FEAT-202606-001-unified-entry-client type: detailed-design title: 统一入口客户端前端工程启动指南 status: approved owner: 医梦研发团队 created_at: 2026-06-17 updated_at: 2026-06-17 reviewers: [] related_docs:

  • PRD-202606-001
  • DDS-202606-001
  • API-202606-001 related_modules:
  • emoon-terminal-client tags:
  • 统一入口客户端
  • 前端工程
  • Vue ---

统一入口客户端前端工程启动指南

目标仓库: emoon-terminal-client(独立仓库,不在 AI 中台后端工程内)
技术栈建议: Vue 3 + TypeScript + Vite,复用现有 Demo 前端交互资产
后端契约基准: docs/initiatives/FEAT-202606-001-unified-entry-client/MVP接口契约.md + docs/api/openapi/医梦AI中台开放平台接口-v1.2.openapi.yaml


1. 推荐工程结构

emoon-terminal-client
├── apps
│   ├── robot-client        # 机器人端(Android WebView 壳)
│   ├── kiosk-client        # 自助机端
│   └── guide-screen-client # 导诊大屏端
│
├── packages
│   ├── api-client          # 统一 OpenPlatform API SDK(自动从 openapi.yaml 生成)
│   ├── medical-cards       # 医疗卡片组件库(department-selection, doctor-selection 等)
│   ├── terminal-bridge     # JSBridge 抽象层(导航、扫码、读卡、打印)
│   ├── terminal-theme      # 主题系统(医梦主题 + 医院定制)
│   └── terminal-core       # 共享 Hooks / Stores / Utils
│
├── package.json
├── turbo.json              # Turborepo 编排
└── README.md

2. 三端差异速查

层次 共用 差异
API SDK (api-client) ✅ 统一调用 OpenPlatform
卡片组件 (medical-cards) ✅ 科室卡、医生卡、号源卡、路线卡、舌诊卡 不同终端的 allowedCards 限制不同
Bridge (terminal-bridge) ✅ 统一 JSBridge 抽象接口 机器人有导航;自助机有读卡/打印;导诊大屏能力最少
动线区 (journey-panel) ✅ 渲染后端 journeyState 和下一步服务 导诊屏偏公共导流,自助机偏业务办理,机器人偏导航
感知状态 (perception-status) ✅ 展示身份绑定、授权、扫码/NFC/刷卡状态 不同终端感知能力来自 perceptionCapabilities
主题 (terminal-theme) ✅ 医梦主题 + 医院主题 屏幕尺寸、字体、按钮密度不同
首页 ✅ 聊天主控件可复用 三类终端首页布局不同

3. 首页启动流程(必实现)

loadConfig(hospitalId, accessKey)
  → POST /devices/register        # 拿到 deviceId + activateStatus
  → setInterval 30s: POST /devices/{deviceId}/heartbeat
  → GET /devices/{deviceId}/scene  # 拿到 homeTemplate + allowedAgents + allowedCards
  → initPerception(scene.perceptionCapabilities)
  → initJourneyPanel(scene.journeyPolicy)
  → renderHome(homeTemplate)       # 根据 sceneCode 渲染首页

阻塞条件:

  • activateStatus = "pending" → 展示"等待激活"状态,不进入核心业务
  • activateStatus = "rejected" → 展示拒绝原因 + 联系运维入口
  • activateStatus = "activated" → 正常进入

首页推荐布局:

顶部:设备/院区/身份绑定/授权状态
左侧:公共快捷入口、人工服务、无障碍入口
中央:AI 对话、虚拟形象、业务卡片
右侧:当前动线阶段、下一步任务、排队/缴费/检查提醒
底部:通知、异常、扫码/NFC/人工降级入口

journeyState 只能由后端返回后展示。前端可以上报扫码、NFC、刷卡、近场等 perceptionContext 或设备事件,但不能自行判断“已签到”“可缴费”“可挂号成功”。

4. 卡片组件渲染 MVP(P0 必须支持)

cardKey 渲染要求
department-selection 科室列表(名称 + 推荐原因)+ 选择按钮
doctor-selection 医生列表(姓名 + 职称)+ 选择按钮
time-slot-selection 号源列表(时间 + 剩余号数)+ 选择按钮
confirm-appointment 挂号信息摘要 + 二次确认弹窗
appointment-success 成功页(预约号 + Mock 标识)
route-card 路线文字 + "开始导航"按钮(机器人端调 JSBridge)
tongue-capture 拍照/上传引导 + 提交按钮

卡片通用交互模式:

// 1. 渲染卡片(cardData 来自 GET /cards/{cardInstanceId})
renderCard(cardInstanceId, cardKey, cardData)

// 2. 用户操作 → 生成幂等键 → 提交动作
const idempotencyKey = crypto.randomUUID()  // 客户端生成,同一逻辑操作复用
const result = await apiClient.cardAction(cardInstanceId, actionName, {
  idempotencyKey,
  confirm: action.requiredConfirm ? userConfirmed : true,
  payload: { /* 用户选择的 departmentId / doctorId / slotId */ }
})

// 3. 如有 nextCard → 继续渲染下一卡片
if (result.nextCard) {
  renderCard(result.nextCard.cardInstanceId, result.nextCard.cardKey, ...)
}

5. SSE 流式对话处理

const eventSource = await apiClient.chatStream({
  deviceContext: { deviceId, deviceType },
  message: { type: "text", text: userInput, clientMessageId: crypto.randomUUID() }
  // 注意:不传 agentId,由后端 AgentRouter 自动路由
})

eventSource.on("message_delta", (data) => {
  appendText(data.text)           // 逐字追加到对话气泡
})

eventSource.on("card_created", (data) => {
  fetchAndRenderCard(data.cardInstanceId)  // 拉取卡片详情并渲染
})

eventSource.on("journey_updated", (data) => {
  renderJourneyPanel(data)        // P0 必须处理;前端不得从 task/card 反推动线
})

eventSource.on("completed", (data) => {
  saveConversationId(data.conversationId)  // 记录,用于断线恢复
})

eventSource.on("error", (data) => {
  showError(data.message)
})

6. API SDK 生成建议

使用 openapi-generatoropenapi-typescriptdocs/api/openapi/医梦AI中台开放平台接口-v1.2.openapi.yaml 自动生成 TypeScript SDK:

# 示例:openapi-typescript
npx openapi-typescript \
  https://raw.githubusercontent.com/<repo>/docs/api/openapi/医梦AI中台开放平台接口-v1.2.openapi.yaml \
  -o packages/api-client/src/generated/schema.ts

7. 前端禁止事项

禁止 原因
请求中传 agentId 后端 AgentRouter 决定路由
直连 Dify API 前端不决定流程编排
直连 HIS / 舌诊底层接口 必须通过卡片动作闭环
自行构造医疗建议文本 只展示后端返回的文本和卡片
自行推导动线阶段并放开能力 动线阶段由后端结合 HIS/叫号/任务状态判断
保存患者连续位置轨迹 P0 只允许服务区域级事件,不保存移动路线
隐藏 Mock 标识 后端返回 "mock":true 时必须标注"联调演示"
不传 idempotencyKey 卡片动作必须带幂等键

8. 环境配置

// packages/api-client/src/config.ts
export const apiConfig = {
  baseUrl: import.meta.env.VITE_API_BASE_URL || "https://api.demo.emoon.local",
  accessKey: import.meta.env.VITE_ACCESS_KEY || "",
}

关键:VITE_* 前缀变量会被编译进前端 bundle,浏览器端可直接读取。签名密钥不得放入此类变量。
签名应由受控终端壳(Android WebView bridge / Electron main process / 自助机原生层)持有并在每次请求前完成 HMAC 签名计算。前端 API SDK 仅接收已签名的请求头,或调用桥接层提供的 signAndSend() 方法。

三个 app 通过各自的 .env 文件区分:

# apps/kiosk-client/.env
VITE_API_BASE_URL=https://api.demo.emoon.local
VITE_ACCESS_KEY=kiosk_demo_key
# 注意:不要在此文件放置签名密钥

9. 参考资产

资产 位置
OpenAPI 完整契约 docs/api/openapi/医梦AI中台开放平台接口-v1.2.openapi.yaml
终端接口契约(含 curl 示例) docs/initiatives/FEAT-202606-001-unified-entry-client/MVP接口契约.md
对外接口设计文档 docs/initiatives/FEAT-202606-001-unified-entry-client/对外接口联调基准.md
技术设计文档 docs/initiatives/FEAT-202606-001-unified-entry-client/详细设计.md
现有 Demo 前端 emoon-ui/(交互资产可复用,业务逻辑需重构)