基于鸿蒙OS开发静脉输液智能监控系统(16)-护士端工作台UI设计
基于鸿蒙OS开发静脉输液智能监控系统(16)-护士端工作台UI设计
目录
- 护士工作场景分析
- NurseHomePage工作台
- PatientListPage患者列表
- NurseAnalysisPage聚合分析
- 护士端专属设计考量
- 预警分级与处理流程
- 多患者管理UX
- 护士端数据流
- 与患者端/家属端的数据联动
1. 护士工作场景分析
1.1 临床输液监护的真实挑战
在真实的临床环境中,护士是输液监护的第一责任人与最终执行者。与患者端的被动接收信息不同,护士端面临的是一个高度并发、多线程、高压力的工作场景。理解这一场景,是设计好护士端UI的前提。
1.1.1 同时监控多位患者
在典型的中国三甲医院内科病房中,一个护理单元通常配备4-6名护士,负责40-60张床位。其中需要输液的患者占比通常在60%-80%之间,这意味着每名护士在同一时段内需要管理约8-12位输液患者。这些患者的输液状态各不相同:有的刚开始输液、有的即将完成、有的出现了流速异常、有的需要换瓶。
传统模式下,护士依靠定时巡视(通常每30-60分钟一次)来掌握输液进度。这种模式存在两个核心问题:
- 时间滞后:在两次巡视之间,患者的输液可能已经完成或出现异常,但护士无法及时获知。IVGuard的核心价值就是消除这个时间差,将定时检查转变为实时监控。
- 认知负荷:即使护士在巡视中,也需要逐个查看每个患者的输液瓶,记忆液位状态,判断是否需要干预。当患者数量增多时,这种认知负荷急剧上升,容易出现遗漏。
IVGuard护士端的设计目标,就是将这8-12位患者的信息聚合到一块屏幕上,让护士能够一目了然地掌握所有患者的当前状态,并在出现异常时立即收到通知。
1.1.2 快速响应预警
输液异常的黄金响应时间非常短。以输液完成未及时拔针为例,最严重的后果是空气栓塞——当输液瓶完全排空后,空气可能通过输液管进入静脉,造成致命危险。虽然现代输液器大多配备了防气阀,但这一安全机制并不可靠,特别是在使用廉价耗材时。
因此,IVGuard将预警响应时间设定为30秒。这意味着从系统发出预警到护士注意到预警,不应超过30秒。这一时间要求直接影响了UI设计的多个方面:
- 视觉突出性:预警信息必须在视觉上足够醒目,不能被其他信息淹没。红色的预警卡片、闪烁的指示灯、全屏弹窗,都是为此目的设计的。
- 操作简化性:从看到预警到开始处理,操作步骤应尽可能少。理想情况下,一次点击即可确认并开始处理预警。
- 信息精确性:预警信息必须包含足够的上下文,让护士无需额外查询就能做出判断。例如,预警不仅要说3床液位低,还要说3床张三0.9%氯化钠注射液剩余15%预计5分钟完成。
1.1.3 聚合数据分析需求
除了实时监控,护士还需要对班次内的输液情况进行统计分析。这些数据包括:
- 输液完成率:本班次内计划完成的输液中,实际按时完成的比例。完成率低于90%可能意味着人员配置不足或流程存在问题。
- 异常统计:本班次内出现的各类异常(流速过快/过慢、液位过低、管路阻塞等)的数量和分布。异常频率高的时段或患者可能需要额外关注。
- 预警响应时间:从预警发出到护士确认处理的平均时间。这是衡量系统有效性和护士工作负荷的关键指标。
- 高风险患者识别:基于历史数据,识别出输液异常频率异常高的患者,提示护士增加关注。
这些聚合分析功能在NurseAnalysisPage中实现,帮助护士和护理管理者从宏观层面把握输液安全状况。
1.1.4 信息密度要求
护士站的工作环境决定了UI必须具有极高的信息密度。护士不可能像普通App用户那样慢慢浏览——她们需要在一瞥之间获取关键信息。这要求:
- 一屏展示多患者状态:至少8个患者的核心信息(姓名、床号、液位、预警状态)应该在一屏内完整展示,无需滚动。
- 信息层级清晰:最关键的信息(预警、液位极低)在最显眼的位置,次要信息(常规监控状态)在稍低层级,辅助信息(历史记录)在二级页面。
- 颜色编码直觉化:利用颜色传达状态信息——红色意味着立即行动、橙色意味着关注、绿色意味着正常。这无需阅读文字即可理解。
1.2 护士工作流程与IVGuard交互节点
┌─────────────────────────────────────────────────────────────┐
│ 护士工作流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 08:00 交接班 ──→ 查看昨日输液总结 │
│ │ │ │
│ ▼ ▼ │
│ 08:15 晨间巡视 ──→ 打开NurseHomePage查看当前状态 │
│ │ │ │
│ ▼ ▼ │
│ 08:30 开始输液 ──→ 在患者端启动监测 → 护士端同步显示 │
│ │ │ │
│ ▼ ▼ │
│ 09:00 收到预警 ──→ AlertCard弹出 → 点击处理 → 前往患者处 │
│ │ │ │
│ ▼ ▼ │
│ 09:15 处理完毕 ──→ 标记预警handled → 记录处理结果 │
│ │ │ │
│ ▼ ▼ │
│ 12:00 午间总结 ──→ 打开NurseAnalysisPage查看班次统计 │
│ │ │ │
│ ▼ ▼ │
│ 16:00 交接班 ──→ 生成交班报告(聚合数据) │
│ │
└─────────────────────────────────────────────────────────────┘
在这个工作流程中,IVGuard护士端介入了以下关键节点:
- 交接班时:查看上一班次的输液汇总数据,了解哪些患者需要持续关注。
- 巡视前:快速扫描NurseHomePage,优先前往有预警的患者。
- 预警时:实时接收预警通知,快速定位患者并处理。
- 处理后:记录处理结果,形成闭环。
- 班次总结时:查看聚合分析,生成交班报告。
1.3 护士端与患者端的核心差异
护士端和患者端虽然共享相同的数据层(DataStore),但在UI设计和交互逻辑上存在根本差异:
| 维度 | 患者端 | 护士端 |
|---|---|---|
| 关注范围 | 自身一人 | 8-12位患者 |
| 信息粒度 | 精细(单患者详情) | 聚合(多患者概览) |
| 操作频率 | 低(被动等待) | 高(频繁确认/处理) |
| 预警来源 | 设备传感器 | 患者端上报+系统分析 |
| 时间压力 | 中(关注自身) | 高(多人同时需关注) |
| 数据需求 | 当前状态为主 | 当前状态+历史统计 |
这些差异决定了护士端不能简单地将患者端UI放大或复制,而必须设计一套全新的信息架构和交互模式。
1.4 护士端用户画像
基于对临床护士的调研,我们总结了IVGuard护士端的目标用户画像:
- 年龄:22-45岁,以25-35岁为主
- 技术素养:基本智能手机用户,对新技术持开放态度,但学习时间有限
- 工作节奏:快,平均每3-5分钟切换一次任务
- 使用场景:护士站(坐在电脑前)、走廊(手持设备移动中)、病房(单手操作)
- 视力需求:部分护士年龄较大,字号不能太小
- 手套操作:有时需要戴着手套操作设备,触摸目标不能太小
这些用户特征对UI设计提出了具体要求:按钮足够大(至少44dp触摸区域)、字号适中(正文不小于14fp)、操作简单(一键完成常见操作)、界面清晰(信息层级分明)。
1.5 护士端信息架构总览
┌─────────────────────────────────────────────────────────────┐
│ 护士端信息架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ NurseHomePage(工作台) │
│ ├── 班次信息头 │
│ ├── 四宫格统计面板 │
│ │ ├── 监控数(蓝) │
│ │ ├── 预警数(红) │
│ │ ├── 异常数(橙) │
│ │ └── 已完成(绿) │
│ ├── 预警通知列表 │
│ │ ├── AlertCard(未处理) │
│ │ └── AlertCard(已处理) │
│ └── 导航栏 │
│ ├── → PatientListPage │
│ ├── → NurseAnalysisPage │
│ └── → 角色切换 │
│ │
│ PatientListPage(患者列表) │
│ ├── 搜索栏 │
│ ├── 筛选Tab(全部/监控中/已完成/异常) │
│ └── 患者卡片列表 │
│ ├── PatientCard(床号+姓名+药品+进度条+状态) │
│ └── → PatientDetailPage │
│ │
│ NurseAnalysisPage(聚合分析) │
│ ├── 完成率统计 │
│ ├── 平均响应时间 │
│ ├── 异常分布统计 │
│ ├── 高风险患者 │
│ └── AI生成建议 │
│ │
└─────────────────────────────────────────────────────────────┘
2. NurseHomePage工作台
NurseHomePage是护士端的核心页面,也是护士每次打开App时首先看到的界面。它集成了统计面板、预警列表和导航入口,是护士日常工作的指挥中心。整个页面约184行代码,结构紧凑但功能完整。

2.1 页面整体结构
@Entry
@Component
struct NurseHomePage {
@State todayMonitoring: number = 0
@State todayWarnings: number = 0
@State todayAnomalies: number = 0
@State todayCompleted: number = 0
@State alerts: AlertRecord[] = []
@State currentShift: string = '白班'
private dataStore: DataStore = DataStore.getInstance()
aboutToAppear() {
this.loadStatistics()
this.loadAlerts()
}
build() {
Column({ space: 12 }) {
this.ShiftHeader()
this.StatisticsPanel()
this.AlertListSection()
this.NavigationBar()
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
}
}
页面采用垂直布局,从上到下依次为:班次信息头、统计面板、预警列表、导航栏。这种布局遵循了从宏观到微观的信息架构原则——先看到整体数字,再看到具体预警,最后通过导航进入详细信息。
布局比例分析:
在640dp的可用高度中,各区域的分配如下:
| 区域 | 高度 | 占比 | 说明 |
|---|---|---|---|
| ShiftHeader | 48dp | 7.5% | 紧凑的标题区 |
| StatisticsPanel | 80dp | 12.5% | 四宫格面板 |
| AlertListSection | 460dp | 71.9% | 预警列表(弹性扩展) |
| NavigationBar | 40dp | 6.3% | 底部导航 |
| 间距(space:12x3) | 12dp | 1.8% | 区域间隔 |
预警列表占据了页面71.9%的空间,这是合理的——预警是护士端最核心的信息,需要最大的展示面积。统计面板虽然只占12.5%,但四个数字提供了宏观概览,信息密度极高。
2.2 四宫格统计面板
统计面板是NurseHomePage的视觉焦点,采用四宫格布局,展示当日四个核心指标。
2.2.1 代码实现
@Builder
StatisticsPanel() {
Row({ space: 8 }) {
Column({ space: 4 }) {
Text(this.todayMonitoring.toString())
.fontSize(22)
.fontColor('#2196F3')
.fontWeight(FontWeight.Bold)
Text('监控数')
.fontSize(11)
.fontColor('#888888')
}
.layoutWeight(1)
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.alignItems(HorizontalAlign.Center)
Column({ space: 4 }) {
Text(this.todayWarnings.toString())
.fontSize(22)
.fontColor('#F44336')
.fontWeight(FontWeight.Bold)
Text('预警数')
.fontSize(11)
.fontColor('#888888')
}
.layoutWeight(1)
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.alignItems(HorizontalAlign.Center)
Column({ space: 4 }) {
Text(this.todayAnomalies.toString())
.fontSize(22)
.fontColor('#FF9800')
.fontWeight(FontWeight.Bold)
Text('异常数')
.fontSize(11)
.fontColor('#888888')
}
.layoutWeight(1)
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.alignItems(HorizontalAlign.Center)
Column({ space: 4 }) {
Text(this.todayCompleted.toString())
.fontSize(22)
.fontColor('#4CAF50')
.fontWeight(FontWeight.Bold)
Text('已完成')
.fontSize(11)
.fontColor('#888888')
}
.layoutWeight(1)
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
}
2.2.2 四个指标详解
| 指标 | 颜色代码 | 含义 | 数据来源 |
|---|---|---|---|
| 监控数 | #2196F3(蓝色) | 当前正在监测的输液数量 | DataStore.loadRecords()中status === monitoring的记录数 |
| 预警数 | #F44336(红色) | 当日产生的预警总数(含已处理和未处理) | DataStore.loadAlerts()中当日记录数 |
| 异常数 | #FF9800(橙色) | 当日检测到的异常事件数 | DataStore.loadRecords()中anomalyCount汇总 |
| 已完成 | #4CAF50(绿色) | 当日已完成的输液数量 | DataStore.loadRecords()中status === completed的记录数 |
颜色选择的逻辑:
- 蓝色(监控数):蓝色代表正在进行中,是中性信息,不需要采取行动。
- 红色(预警数):红色代表需要立即注意,是最紧急的信号。
- 橙色(异常数):橙色代表需要关注,紧急程度低于红色但高于蓝色。
- 绿色(已完成):绿色代表已完成、正常,是最安心的信号。
这种颜色编码与医疗领域的通用颜色语言一致(红色=紧急、橙色=预警、绿色=正常),护士无需额外学习即可理解。
2.2.3 数据加载与计算
private loadStatistics() {
const records: InfusionRecord[] = this.dataStore.loadRecords()
const today: string = this.getCurrentDate()
this.todayMonitoring = records.filter(
(r: InfusionRecord) => r.date === today && r.status === 'monitoring'
).length
this.todayWarnings = this.dataStore.loadAlerts().filter(
(a: AlertRecord) => a.date === today
).length
this.todayAnomalies = records.filter(
(r: InfusionRecord) => r.date === today && r.anomalyCount > 0
).reduce((sum: number, r: InfusionRecord) => sum + r.anomalyCount, 0)
this.todayCompleted = records.filter(
(r: InfusionRecord) => r.date === today && r.status === 'completed'
).length
}
loadStatistics()方法在aboutToAppear()生命周期中被调用,每次页面显示时刷新数据。由于当前MVP阶段数据量有限(单设备、单班次),直接使用filter和reduce进行内存计算,性能开销可忽略。
未来在数据量增大后,可以考虑以下优化:
- 将统计计算移到DataStore内部,使用预聚合的缓存
- 增量更新:只在数据变化时重新计算受影响的指标
- 后台线程计算:避免阻塞UI线程
2.2.4 统计面板的视觉设计考量
统计面板的设计遵循了一眼扫到关键数字的原则:
- 数字大(22fp)、标签小(11fp):数字是核心信息,标签是辅助信息。这种2:1的字号比例确保了数字在视觉上的主导地位。
- 白色卡片背景:与页面底色(#F5F5F5灰色)形成对比,使每个统计卡片在视觉上独立。
- 等宽布局(layoutWeight(1)):四个卡片等宽排列,视觉平衡。
- 8dp间距:卡片之间留有适当间距,避免视觉粘连。
- 居中对齐:数字和标签都居中,视觉整洁。
2.2.5 统计面板的交互扩展
当前MVP版本的统计面板是纯展示性的,未来可以增加点击交互:
- 点击监控数:跳转到PatientListPage,自动选中监控中Tab。
- 点击预警数:滚动到预警列表顶部,聚焦未处理的预警。
- 点击异常数:跳转到NurseAnalysisPage,聚焦异常分布统计。
- 点击已完成:跳转到PatientListPage,自动选中已完成Tab。
这种点击数字即跳转详情的交互模式,将统计面板从被动的信息展示转变为主动的导航入口,进一步减少护士的操作步骤。
2.3 预警通知列表
预警通知列表是NurseHomePage中信息量最大的区域,占据了页面的主要空间。
2.3.1 AlertCard组件设计
@Component
export struct AlertCard {
@Prop alert: AlertRecord
private onHandle: (alertId: string) => void = () => {}
build() {
Row({ space: 12 }) {
Column() {
Circle({ width: 8, height: 8 })
.fill(this.getAlertColor())
.margin({ top: 4 })
}
.width(20)
.alignItems(HorizontalAlign.Center)
Column({ space: 4 }) {
Text(`${this.alert.bedNo}床 ${this.alert.patientName}`)
.fontSize(14)
.fontColor('#333333')
.fontWeight(FontWeight.Medium)
Text(this.alert.message)
.fontSize(12)
.fontColor('#666666')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(this.formatTime(this.alert.timestamp))
.fontSize(10)
.fontColor('#999999')
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
if (!this.alert.handled) {
Button('处理')
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor(this.getAlertColor())
.height(28)
.borderRadius(4)
.padding({ left: 12, right: 12 })
.onClick(() => this.onHandle(this.alert.id))
} else {
Text('已处理')
.fontSize(11)
.fontColor('#999999')
}
}
.width('100%')
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.border({
width: 1,
color: this.alert.handled ? '#E0E0E0' : this.getAlertColor(),
style: BorderStyle.Solid
})
}
private getAlertColor(): string {
switch (this.alert.level) {
case 'danger':
return '#F44336'
case 'warning':
return '#FF9800'
case 'info':
return '#2196F3'
default:
return '#999999'
}
}
private formatTime(timestamp: number): string {
const date: Date = new Date(timestamp)
const hours: string = date.getHours().toString().padStart(2, '0')
const minutes: string = date.getMinutes().toString().padStart(2, '0')
return `${hours}:${minutes}`
}
}
AlertCard是一个独立的@Component,每个预警记录渲染为一张卡片。卡片包含以下信息区域:
- 左侧色标圆点:根据预警级别显示不同颜色,提供快速的视觉分类。
- 中间信息区:包含床号+姓名(主标题)、预警消息(副标题)、时间戳。
- 右侧操作区:未处理的预警显示处理按钮,已处理的显示已处理标签。
卡片的关键设计决策:
- 未处理的预警卡片边框着色:整个卡片的边框颜色与预警级别一致(红/橙/蓝),在列表中形成强烈的视觉信号,护士可以快速扫过列表,识别出红色边框的danger级别预警。
- 已处理的预警边框变灰:处理后的预警视觉权重降低,避免干扰护士对未处理预警的关注。
- 预警消息截断(maxLines(2)):过长的消息只显示两行,避免单张卡片占用过多空间。护士可以在点击进入详情后查看完整消息。
2.3.2 AlertCard的信息层级分析
AlertCard内部的信息层级按重要性递减排列:
┌─ 预警级别(边框颜色 + 色标圆点) ← 最高优先级,一眼识别
│ ┌─ 床号 + 姓名 ← 定位患者
│ │ ┌─ 预警消息 ← 了解具体情况
│ │ │ ┌─ 时间戳 ← 判断时效性
│ │ │ │ └─ 操作按钮 ← 采取行动
这种层级在视觉上的体现:
- 边框颜色:最外层,扫描时首先注意到。
- 床号+姓名:14fp中号字,加粗,左侧位置(F型扫描起始点)。
- 预警消息:12fp小号字,常规字重,最多两行。
- 时间戳:10fp最小号字,灰色,辅助信息。
- 操作按钮:右侧固定位置,颜色醒目,操作目标明确。
2.3.3 预警列表渲染
@Builder
AlertListSection() {
Column({ space: 8 }) {
Row() {
Text('预警通知')
.fontSize(16)
.fontColor('#333333')
.fontWeight(FontWeight.Medium)
Blank()
Text(`${this.alerts.filter((a: AlertRecord) => !a.handled).length}条未处理`)
.fontSize(12)
.fontColor('#F44336')
}
.width('100%')
if (this.alerts.length === 0) {
this.EmptyAlertState()
} else {
List({ space: 8 }) {
ForEach(
this.alerts,
(alert: AlertRecord) => {
ListItem() {
AlertCard({
alert: alert,
onHandle: (alertId: string) => this.handleAlert(alertId)
})
}
},
(alert: AlertRecord) => alert.id
)
}
.width('100%')
.layoutWeight(1)
}
}
.width('100%')
.layoutWeight(1)
}
列表渲染使用ForEach组件,以alert.id作为key,确保列表更新时的正确diff和复用。当预警列表为空时,显示空状态占位。
列表标题区域的X条未处理标签是一个重要的上下文信息——即使护士没有仔细看列表内容,也能通过这个数字快速了解当前有多少预警等待处理。当未处理数量为0时,这个标签自动消失,视觉更简洁。
2.3.4 空状态设计
@Builder
EmptyAlertState() {
Column({ space: 8 }) {
Text('✓')
.fontSize(40)
.fontColor('#4CAF50')
Text('暂无预警')
.fontSize(14)
.fontColor('#999999')
Text('所有患者状态正常')
.fontSize(12)
.fontColor('#CCCCCC')
}
.width('100%')
.height(200)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
空状态并非简单的无数据提示,而是一个积极的反馈——所有患者状态正常。这对护士来说是最安心的消息,绿色的勾号强化了这一正向情绪。
空状态设计在医疗场景中尤为重要。一个空白的列表可能让护士疑惑是系统出了问题还是真的没有预警,而明确的暂无预警+所有患者状态正常消除了这种歧义。
2.3.5 预警列表的排序策略
预警列表的默认排序遵循以下优先级:
- 预警级别:danger > warning > info,确保最紧急的预警始终在列表顶部。
- 处理状态:未处理 > 已处理,未处理的预警排在前面。
- 时间:最新的排在前面,确保护士首先看到最新情况。
private sortAlerts(alerts: AlertRecord[]): AlertRecord[] {
const levelOrder: Record<string, number> = { 'danger': 0, 'warning': 1, 'info': 2 }
return alerts.slice().sort((a: AlertRecord, b: AlertRecord) => {
if (a.handled !== b.handled) {
return a.handled ? 1 : -1
}
if (levelOrder[a.level] !== levelOrder[b.level]) {
return levelOrder[a.level] - levelOrder[b.level]
}
return b.timestamp - a.timestamp
})
}
这种排序策略确保了护士看到的第一个预警永远是最紧急且未处理的。
排序策略的选择理由:
为什么将处理状态排在预警级别之前?因为已处理的danger预警不如未处理的warning预警紧急。已处理的预警虽然级别高,但已经有护士响应了,不需要再次关注。而未处理的warning虽然级别低于danger,但如果没有被看到,可能演变为更严重的问题。
实际排序效果:
未处理 danger(最紧急)
未处理 warning
未处理 info
已处理 danger
已处理 warning
已处理 info
2.4 预警处理交互
2.4.1 handleAlert方法
private handleAlert(alertId: string) {
const alertIndex: number = this.alerts.findIndex(
(a: AlertRecord) => a.id === alertId
)
if (alertIndex === -1) {
return
}
this.alerts[alertIndex].handled = true
this.alerts[alertIndex].handledAt = Date.now()
this.alerts[alertIndex].handledBy = 'current_nurse'
this.dataStore.saveAlerts(this.alerts)
this.todayWarnings = this.dataStore.loadAlerts().filter(
(a: AlertRecord) => a.date === this.getCurrentDate()
).length
promptAction.showToast({
message: '预警已标记为已处理',
duration: 1500
})
}
handleAlert方法是护士端最核心的交互之一。当护士点击AlertCard上的处理按钮时触发,执行以下步骤:
- 定位预警记录:通过alertId在列表中找到对应记录。
- 更新处理状态:将handled标记为true,记录处理时间(handledAt)和处理人(handledBy)。
- 持久化保存:调用DataStore.saveAlerts()将更新后的预警列表写回持久化存储。
- 刷新统计:重新计算预警数,更新统计面板。
- 确认反馈:显示Toast提示预警已标记为已处理,给护士明确的操作确认。
整个处理流程只需一次点击,符合一键操作优先的设计原则。
2.4.2 处理后的数据记录
预警处理后保留的关键数据:
| 字段 | 类型 | 说明 |
|---|---|---|
| handled | boolean | 是否已处理 |
| handledAt | number | 处理时间戳(毫秒) |
| handledBy | string | 处理人标识 |
这些数据在聚合分析中用于计算响应时间(handledAt - timestamp)和处理人工作量统计。
数据完整性考量:
handledAt字段不仅记录了何时处理,还用于计算响应时间——这是衡量预警系统有效性的核心指标。如果handledAt为0或为空,说明预警尚未处理,在计算平均响应时间时需要排除。
handledBy字段在当前MVP中是固定值current_nurse,因为单设备模式不需要区分处理人。未来多护士多设备场景下,这个字段将记录实际处理预警的护士ID,用于工作量统计和责任追溯。
2.4.3 滑动操作预留
当前MVP版本使用按钮点击方式处理预警。未来版本计划增加滑动操作,进一步简化交互:
// 未来实现方向(MVP阶段不实现)
ListItem() {
AlertCard({ alert: alert, onHandle: (id: string) => this.handleAlert(id) })
}
.swipeAction({ end: this.SwipeDeleteBuilder() })
滑动操作的设计方向:
- 向左滑动:显示处理按钮,单手操作友好。
- 向右滑动:显示查看详情按钮,跳转到患者详情页。
- 长按:显示更多操作选项(标记误报、转交给其他护士等)。
滑动操作相比按钮点击的优势:
- 操作速度更快:手指只需一个方向的滑动,不需要精确点击按钮。
- 单手操作友好:大屏手机上,按钮可能位于屏幕右侧,单手不易触及;而滑动可以在任意位置发起。
- 符合移动端习惯:iOS和Android用户已经习惯了滑动操作(如邮件App中的删除/归档)。
2.4.4 预警处理的完整生命周期
预警触发 → 通知推送 → 护士看到AlertCard → 点击处理 →
handleAlert()执行 → DataStore.saveAlerts() →
Toast确认 → AlertCard更新为已处理状态 →
边框变灰 → 统计面板数字更新
整个生命周期中,每个步骤都有明确的视觉反馈:
- 预警触发:系统检测到异常条件。
- 通知推送:设备发出声音+振动通知。
- 看到AlertCard:红色/橙色边框的卡片出现在列表顶部。
- 点击处理:按钮响应点击。
- handleAlert()执行:数据更新。
- DataStore.saveAlerts():持久化保存。
- Toast确认:1500ms的提示信息。
- AlertCard更新:按钮变为已处理标签,边框变灰。
- 统计面板更新:预警数保持不变(因为已处理的预警仍计入总数),但未处理计数减少。
2.5 班次信息头
@Builder
ShiftHeader() {
Row({ space: 8 }) {
Column({ space: 2 }) {
Text('IVGuard 护士工作台')
.fontSize(18)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
Text(`${this.getCurrentDate()} ${this.currentShift}`)
.fontSize(12)
.fontColor('#888888')
}
.layoutWeight(1)
Row({ space: 4 }) {
Circle({ width: 8, height: 8 })
.fill('#4CAF50')
Text('系统正常')
.fontSize(11)
.fontColor('#4CAF50')
}
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.backgroundColor('#E8F5E9')
.borderRadius(12)
}
.width('100%')
.padding({ bottom: 4 })
}
班次信息头显示两个关键信息:
- 页面标题:让护士确认自己在正确的页面。
- 系统状态:IVGuard系统当前是否正常运行。如果传感器连接断开或数据同步异常,此处会显示系统异常并以红色标识。
系统状态指示器的设计:
// 系统异常时的显示
Row({ space: 4 }) {
Circle({ width: 8, height: 8 })
.fill('#F44336')
Text('系统异常')
.fontSize(11)
.fontColor('#F44336')
}
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.backgroundColor('#FFEBEE')
.borderRadius(12)
系统状态是护士信任系统的基础。如果系统状态显示异常,护士就知道当前数据可能不准确,需要增加人工巡视频率。这种自我监控机制是医疗系统可靠性的重要保障。
2.6 导航栏
@Builder
NavigationBar() {
Row({ space: 8 }) {
Button('患者列表')
.fontSize(13)
.fontColor('#2196F3')
.backgroundColor('#E3F2FD')
.layoutWeight(1)
.height(40)
.borderRadius(8)
.onClick(() => {
// 跳转到PatientListPage
})
Button('聚合分析')
.fontSize(13)
.fontColor('#FF9800')
.backgroundColor('#FFF3E0')
.layoutWeight(1)
.height(40)
.borderRadius(8)
.onClick(() => {
// 跳转到NurseAnalysisPage
})
Button('切换角色')
.fontSize(13)
.fontColor('#666666')
.backgroundColor('#F0F0F0')
.layoutWeight(1)
.height(40)
.borderRadius(8)
.onClick(() => {
// 切换到患者端或家属端
})
}
.width('100%')
}
底部导航栏提供三个入口:
- 患者列表:查看所有输液患者的详细状态,蓝色主题与监控语义一致。
- 聚合分析:查看班次统计数据和分析报告,橙色主题与分析语义一致。
- 切换角色:在护士端、患者端、家属端之间切换,灰色主题表示功能性操作。
导航按钮的颜色设计遵循浅底深字原则——背景色是对应语义色的极浅版本(如蓝色的#E3F2FD是#2196F3的10%透明度版本),文字色是对应的语义色。这种设计既保持了颜色的一致性,又不会因为大面积使用饱和色而造成视觉疲劳。
2.7 NurseHomePage完整代码结构
NurseHomePage (184行)
├── @State 变量声明 (5个)
│ ├── todayMonitoring: number
│ ├── todayWarnings: number
│ ├── todayAnomalies: number
│ ├── todayCompleted: number
│ └── alerts: AlertRecord[]
├── aboutToAppear()
│ ├── loadStatistics()
│ └── loadAlerts()
├── build()
│ └── Column
│ ├── ShiftHeader() — 班次信息头
│ ├── StatisticsPanel() — 四宫格统计面板
│ ├── AlertListSection() — 预警通知列表
│ │ ├── ForEach → AlertCard
│ │ └── EmptyAlertState()
│ └── NavigationBar() — 底部导航
├── handleAlert(alertId) — 预警处理
├── loadStatistics() — 统计数据加载
├── loadAlerts() — 预警列表加载
├── sortAlerts() — 预警排序
└── 辅助方法
├── getCurrentDate()
└── formatTime()
2.8 NurseHomePage的渲染性能分析
在8-12位患者、每人可能产生3-5条预警的场景下,NurseHomePage需要渲染约20-60条AlertCard。每条AlertCard包含3-4个Text组件、1个Circle组件、1个Button或Text组件,约8-9个UI组件。这意味着整个列表约160-540个UI组件。
ArkUI的ForEach在数据变化时会进行diff操作,以alert.id为key确保只更新发生变化的卡片。当护士点击处理按钮时,只有对应的AlertCard需要更新(按钮变为已处理标签,边框变灰),其他卡片不受影响。
@State变量的更新触发机制:
- alerts数组变化 → AlertListSection重新渲染
- todayMonitoring等数字变化 → StatisticsPanel重新渲染
- handleAlert中同时修改了alerts(触发列表更新)和统计数字(触发面板更新)
为了优化性能,可以考虑将统计数字的计算结果缓存为独立的@State变量,避免每次渲染时重新计算。当前MVP阶段数据量小,这种优化不是必需的。
3. PatientListPage患者列表
PatientListPage是护士查看所有输液患者状态的页面,提供了筛选、搜索和快速定位功能。与NurseHomePage的预警驱动视角不同,PatientListPage采用患者驱动视角——以患者为基本单位组织信息。
3.1 页面整体结构
@Entry
@Component
struct PatientListPage {
@State patients: PatientInfo[] = []
@State currentTab: string = 'all'
@State searchText: string = ''
private dataStore: DataStore = DataStore.getInstance()
aboutToAppear() {
this.loadPatients()
}
build() {
Column({ space: 0 }) {
this.SearchBar()
this.FilterTabs()
this.PatientListView()
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
}
3.2 筛选Tab设计
@Builder
FilterTabs() {
Row({ space: 0 }) {
this.TabItem('全部', 'all', this.patients.length)
this.TabItem('监控中', 'monitoring',
this.patients.filter((p: PatientInfo) => p.status === 'monitoring').length)
this.TabItem('已完成', 'completed',
this.patients.filter((p: PatientInfo) => p.status === 'completed').length)
this.TabItem('异常', 'anomaly',
this.patients.filter((p: PatientInfo) => p.hasAnomaly).length)
}
.width('100%')
.height(40)
.backgroundColor('#FFFFFF')
}
@Builder
TabItem(label: string, key: string, count: number) {
Column() {
Text(`${label}(${count})`)
.fontSize(13)
.fontColor(this.currentTab === key ? '#2196F3' : '#666666')
.fontWeight(this.currentTab === key ? FontWeight.Medium : FontWeight.Normal)
}
.layoutWeight(1)
.height('100%')
.justifyContent(FlexAlign.Center)
.borderBottom({
width: this.currentTab === key ? 2 : 0,
color: '#2196F3'
})
.onClick(() => {
this.currentTab = key
})
}
四个Tab的设计逻辑:
| Tab | 筛选条件 | 使用场景 |
|---|---|---|
| 全部 | 无筛选 | 了解整体情况 |
| 监控中 | status === monitoring | 关注当前正在进行输液的患者 |
| 已完成 | status === completed | 确认已完成输液的患者 |
| 异常 | hasAnomaly === true | 快速定位有异常的患者 |
每个Tab后面跟着括号内的数量,让护士无需切换Tab就能了解每个分类的患者数量。这种设计在信息密度和操作便捷性之间取得了平衡。
Tab切换时的视觉效果:
- 选中Tab:蓝色文字 + 底部蓝色下划线(2px),清晰标识当前位置。
- 未选中Tab:灰色文字,无下划线,降低视觉权重。
- 过渡动画:下划线滑动动画(未来实现),提供流畅的视觉反馈。
Tab设计的心理学依据:
数字标签的设计基于信息气味理论——用户在浏览选项时,会根据每个选项提供的信息线索来判断是否值得点击。括号内的数字就是这种信息气味:如果异常Tab显示(3),护士就知道有3位异常患者,可能需要切换查看;如果显示(0),就可以安全跳过。
3.3 搜索栏
@Builder
SearchBar() {
Row({ space: 8 }) {
Image($r('app.media.ic_search'))
.width(16)
.height(16)
.fillColor('#999999')
TextInput({ placeholder: '搜索床号或患者姓名' })
.fontSize(14)
.fontColor('#333333')
.placeholderColor('#CCCCCC')
.layoutWeight(1)
.height(36)
.backgroundColor(Color.Transparent)
.onChange((value: string) => {
this.searchText = value
})
}
.width('100%')
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.backgroundColor('#FFFFFF')
.borderRadius(8)
.margin({ left: 16, right: 16, top: 12, bottom: 8 })
}
搜索功能支持按床号和患者姓名进行模糊匹配:
private getFilteredPatients(): PatientInfo[] {
let result: PatientInfo[] = this.patients
if (this.currentTab === 'monitoring') {
result = result.filter((p: PatientInfo) => p.status === 'monitoring')
} else if (this.currentTab === 'completed') {
result = result.filter((p: PatientInfo) => p.status === 'completed')
} else if (this.currentTab === 'anomaly') {
result = result.filter((p: PatientInfo) => p.hasAnomaly)
}
if (this.searchText.length > 0) {
const query: string = this.searchText.toLowerCase()
result = result.filter((p: PatientInfo) =>
p.bedNo.toLowerCase().includes(query) ||
p.patientName.toLowerCase().includes(query)
)
}
return result
}
搜索与Tab筛选是组合关系——先按Tab筛选,再按搜索词过滤。这意味着护士可以选中监控中Tab后,再搜索特定床号,精确定位目标患者。
搜索功能的实际使用场景:
- 按床号搜索:护士听到同事说5床液位低,直接搜索5快速定位。
- 按姓名搜索:医生询问某位患者的输液情况,护士搜索患者姓名。
- 快速过滤:在患者数量多时,输入部分床号(如1)过滤出所有1X床的患者。
3.4 患者卡片设计
患者卡片是PatientListPage的核心组件,每张卡片展示一位患者的关键输液信息。
3.4.1 PatientCard组件
@Component
export struct PatientCard {
@Prop patient: PatientInfo
private onTap: (patientId: string) => void = () => {}
build() {
Row({ space: 12 }) {
Column({ space: 4 }) {
Text(`${this.patient.bedNo}床`)
.fontSize(16)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
Text(this.patient.patientName)
.fontSize(13)
.fontColor('#666666')
}
.width(60)
.alignItems(HorizontalAlign.Center)
Column({ space: 6 }) {
Text(this.patient.medicineName)
.fontSize(14)
.fontColor('#333333')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
LevelProgress({
currentLevel: this.patient.currentLevel,
totalLevel: this.patient.totalLevel
})
Text(`${this.getRemainingText()}`)
.fontSize(11)
.fontColor('#999999')
}
.layoutWeight(1)
Column({ space: 4 }) {
Text(this.getStatusLabel())
.fontSize(12)
.fontColor(this.getStatusColor())
Image($r('app.media.ic_arrow_right'))
.width(16)
.height(16)
.fillColor('#CCCCCC')
}
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.onClick(() => this.onTap(this.patient.id))
}
private getStatusLabel(): string {
if (this.patient.hasAnomaly) {
return '异常'
}
switch (this.patient.status) {
case 'monitoring':
return '监控中'
case 'completed':
return '已完成'
default:
return '未知'
}
}
private getStatusColor(): string {
if (this.patient.hasAnomaly) {
return '#F44336'
}
switch (this.patient.status) {
case 'monitoring':
return '#2196F3'
case 'completed':
return '#4CAF50'
default:
return '#999999'
}
}
private getRemainingText(): string {
if (this.patient.status === 'completed') {
return '输液完成'
}
const percentage: number = Math.round(
(this.patient.currentLevel / this.patient.totalLevel) * 100
)
return `剩余 ${percentage}%`
}
}
卡片的信息架构遵循左-中-右三栏布局:
- 左栏:床号(大字、粗体)+ 姓名。床号是护士最常用的患者标识,放在最显眼的位置。
- 中栏:药品名称 + 液位进度条 + 剩余百分比。这是临床决策的核心信息。
- 右栏:状态标签 + 箭头图标。状态标签提供快速分类,箭头提示可点击查看详情。
左栏的固定宽度设计:
左栏宽度固定为60dp,这是因为床号最多3个字符(如12床),加上床字,60dp足够显示。固定宽度确保了所有卡片的床号列对齐,视觉整齐。姓名在60dp内可能显示不全,但床号是更重要的信息,且姓名可以在详情页查看。
3.4.2 LevelProgress液位进度条组件
LevelProgress是患者卡片中最具信息密度的组件,它不仅展示了液位的百分比,还通过颜色编码传达了紧急程度。
@Component
export struct LevelProgress {
@Prop currentLevel: number = 0
@Prop totalLevel: number = 100
private getLevelColor(): string {
const percentage: number = this.currentLevel / this.totalLevel
if (percentage > 0.5) {
return '#4CAF50'
} else if (percentage > 0.2) {
return '#FF9800'
} else {
return '#F44336'
}
}
build() {
Stack() {
Row()
.width('100%')
.height(6)
.backgroundColor('#E0E0E0')
.borderRadius(3)
Row()
.width(`${Math.max(2, (this.currentLevel / this.totalLevel) * 100)}%`)
.height(6)
.backgroundColor(this.getLevelColor())
.borderRadius(3)
.animation({ duration: 300 })
}
.width('100%')
.alignContent(Alignment.Start)
}
}
液位颜色编码规则:
| 液位范围 | 颜色 | 含义 | 护士行动 |
|---|---|---|---|
| >50% | 绿色 #4CAF50 | 正常 | 无需立即行动 |
| 20%-50% | 橙色 #FF9800 | 需关注 | 计划前往查看 |
| <20% | 红色 #F44336 | 紧急 | 立即前往处理 |
这种颜色编码与预警分级系统完全一致,形成了统一的视觉语言。护士在扫描患者列表时,只需要关注红色和橙色的进度条,绿色进度条可以安全忽略。
进度条的设计细节:
- 最小宽度2%:即使液位接近0,进度条也不会完全消失,避免视觉上的信息缺失。
- 300ms动画:液位变化时有平滑过渡,而不是突然跳变,避免视觉干扰。
- 6dp高度:足够可见但不占过多空间,与文字信息形成适当的比例。
- 圆角3dp:视觉上更柔和,符合现代UI设计趋势。
为什么最小宽度是2%而不是0%:
当液位为0%时,如果进度条完全消失,护士可能误以为这是一个加载中的空白区域,而不是已经空了。保留2%的最小宽度,加上红色的颜色,清晰地传达了液位极低的信息。
3.4.3 患者列表渲染
@Builder
PatientListView() {
List({ space: 8 }) {
ForEach(
this.getFilteredPatients(),
(patient: PatientInfo) => {
ListItem() {
PatientCard({
patient: patient,
onTap: (patientId: string) => {
// 跳转到患者详情页
}
})
}
},
(patient: PatientInfo) => patient.id
)
}
.width('100%')
.layoutWeight(1)
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
}
列表同样使用ForEach组件,以patient.id为key。列表的默认排序为床号升序(符合护士的巡视习惯),但支持自定义排序(详见第7节)。
3.4.4 患者卡片的视觉优先级
在8-12张患者卡片中,护士需要快速识别出需要关注的患者。视觉优先级通过以下机制实现:
- 进度条颜色:红色/橙色进度条在绿色进度条中非常显眼。
- 状态标签颜色:红色异常标签 > 蓝色监控中 > 绿色已完成。
- 异常卡片背景:未来版本可以为异常患者的卡片添加浅红色背景(#FFF8E1),进一步提升视觉优先级。
视觉优先级排序(从高到低):
├── 红色进度条 + 红色异常标签 ← 最高优先级
├── 红色进度条 + 蓝色监控中标签
├── 橙色进度条 + 蓝色监控中标签
├── 绿色进度条 + 蓝色监控中标签
└── 绿色进度条 + 绿色已完成标签 ← 最低优先级
3.5 点击进入患者详情
点击患者卡片后,跳转到该患者的详情页(PatientDetailPage),展示更完整的输液信息:
- 输液参数:流速、总量、已输入量、预计剩余时间
- 药品信息:药品名称、剂量、配药时间
- 预警历史:该患者的所有预警记录
- 传感器状态:设备连接状态、电池电量
- 操作记录:护士处理记录
onClick(() => {
this.pathStack.pushPath({
name: 'PatientDetail',
param: { patientId: patient.id }
})
})
PatientDetailPage的详细设计不在本文档范围内,但其核心设计原则与护士端一致:信息层级清晰、颜色编码一致、操作步骤最少。
3.6 患者列表的空状态
@Builder
EmptyPatientState() {
Column({ space: 8 }) {
Text('📋')
.fontSize(36)
Text('暂无患者数据')
.fontSize(14)
.fontColor('#999999')
Text('请在患者端启动输液监测')
.fontSize(12)
.fontColor('#CCCCCC')
}
.width('100%')
.height(300)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
与预警列表的空状态不同,患者列表为空时需要给出操作引导——请在患者端启动输液监测,帮助新用户理解数据来源。
3.7 PatientListPage的数据加载
private loadPatients() {
const records: InfusionRecord[] = this.dataStore.loadRecords()
const today: string = this.getCurrentDate()
this.patients = records
.filter((r: InfusionRecord) => r.date === today)
.map((r: InfusionRecord) => ({
id: r.id,
bedNo: r.bedNo,
patientName: r.patientName,
medicineName: r.medicineName,
currentLevel: r.currentLevel,
totalLevel: r.totalLevel,
status: r.status,
hasAnomaly: r.anomalyCount > 0
}))
.sort((a: PatientInfo, b: PatientInfo) => {
return parseInt(a.bedNo) - parseInt(b.bedNo)
})
}
数据加载时将InfusionRecord转换为PatientInfo,这种转换有两个好处:
- 数据裁剪:PatientInfo只包含列表展示所需的字段,减少了数据传输和渲染开销。
- 计算属性:hasAnomaly是根据anomalyCount > 0计算得出的,避免了在UI中进行条件判断。
默认按床号数字排序(而非字符串排序),确保2床排在10床之前。
4. NurseAnalysisPage聚合分析
NurseAnalysisPage是护士端的数据大脑,提供班次内的输液统计、趋势分析和AI生成的改进建议。它帮助护士和护理管理者从宏观层面了解输液安全状况,发现潜在问题并优化工作流程。

4.1 页面整体结构
@Entry
@Component
struct NurseAnalysisPage {
@State completionRate: number = 0
@State avgResponseTime: number = 0
@State anomalyDistribution: AnomalyItem[] = []
@State highRiskPatients: PatientRiskInfo[] = []
@State aiSuggestions: string[] = []
private dataStore: DataStore = DataStore.getInstance()
aboutToAppear() {
this.calculateCompletionRate()
this.calculateAvgResponseTime()
this.loadAnomalyDistribution()
this.identifyHighRiskPatients()
this.generateAISuggestions()
}
build() {
Scroll() {
Column({ space: 16 }) {
this.PageHeader()
this.CompletionRateCard()
this.ResponseTimeCard()
this.AnomalyDistributionCard()
this.HighRiskPatientCard()
this.AISuggestionCard()
}
.width('100%')
.padding(16)
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
}
由于分析页面的内容较多,使用Scroll容器确保所有内容可滚动查看。每个分析模块封装为独立的Card,保持视觉一致性和模块化。
4.2 完成率统计
@Builder
CompletionRateCard() {
Column({ space: 12 }) {
Text('输液完成率')
.fontSize(16)
.fontColor('#333333')
.fontWeight(FontWeight.Medium)
Row({ space: 16 }) {
Stack() {
Progress({
value: this.completionRate,
total: 100,
type: ProgressType.Ring
})
.width(80)
.height(80)
.color(this.completionRate >= 90 ? '#4CAF50' :
this.completionRate >= 70 ? '#FF9800' : '#F44336')
.style({ strokeWidth: 8 })
Text(`${this.completionRate.toFixed(1)}%`)
.fontSize(18)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
}
Column({ space: 4 }) {
Text(`按时完成: ${this.completedOnTime}例`)
.fontSize(13)
.fontColor('#4CAF50')
Text(`延迟完成: ${this.completedLate}例`)
.fontSize(13)
.fontColor('#FF9800')
Text(`未完成: ${this.notCompleted}例`)
.fontSize(13)
.fontColor('#F44336')
}
.layoutWeight(1)
}
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
完成率的计算逻辑:
private calculateCompletionRate() {
const records: InfusionRecord[] = this.dataStore.loadRecords()
const todayRecords: InfusionRecord[] = records.filter(
(r: InfusionRecord) => r.date === this.getCurrentDate()
)
const total: number = todayRecords.length
if (total === 0) {
this.completionRate = 0
return
}
const completed: number = todayRecords.filter(
(r: InfusionRecord) => r.status === 'completed'
).length
this.completionRate = (completed / total) * 100
}
完成率的颜色编码:
- >=90%(绿色):优秀,输液管理规范。
- 70%-90%(橙色):一般,可能存在流程问题。
- <70%(红色):较差,需要排查原因(人员不足、设备故障等)。
环形进度条+数字的组合设计:
环形进度条(ProgressType.Ring)提供了直觉化的视觉反馈——圆环的填充程度直观地展示了完成率的高低。同时,中心的数字提供了精确的百分比,满足需要确切数据的需求。
这种图形+数字的组合是信息展示的最佳实践:图形提供快速识别,数字提供精确度量。研究表明,人类对图形的感知速度比数字快约10倍,但数字的精确度远高于图形。
右侧的三行文字(按时完成/延迟完成/未完成)提供了完成率的分解信息。护士不仅要知道完成率是多少,还要了解为什么不是100%——是延迟完成占多数还是未完成占多数,对应的改进策略完全不同。
4.3 平均响应时间
@Builder
ResponseTimeCard() {
Column({ space: 12 }) {
Text('预警响应时间')
.fontSize(16)
.fontColor('#333333')
.fontWeight(FontWeight.Medium)
Row({ space: 8 }) {
Column({ space: 2 }) {
Text(this.formatResponseTime(this.avgResponseTime))
.fontSize(22)
.fontColor(this.avgResponseTime <= 30 ? '#4CAF50' :
this.avgResponseTime <= 120 ? '#FF9800' : '#F44336')
.fontWeight(FontWeight.Bold)
Text('平均响应')
.fontSize(11)
.fontColor('#888888')
}
Column({ space: 2 }) {
Text(this.formatResponseTime(this.minResponseTime))
.fontSize(16)
.fontColor('#4CAF50')
Text('最快')
.fontSize(11)
.fontColor('#888888')
}
Column({ space: 2 }) {
Text(this.formatResponseTime(this.maxResponseTime))
.fontSize(16)
.fontColor('#F44336')
Text('最慢')
.fontSize(11)
.fontColor('#888888')
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
响应时间的计算基于已处理预警的handledAt - timestamp:
private calculateAvgResponseTime() {
const alerts: AlertRecord[] = this.dataStore.loadAlerts()
const handledAlerts: AlertRecord[] = alerts.filter(
(a: AlertRecord) => a.handled && a.handledAt > 0
)
if (handledAlerts.length === 0) {
this.avgResponseTime = 0
return
}
const responseTimes: number[] = handledAlerts.map(
(a: AlertRecord) => (a.handledAt - a.timestamp) / 1000
)
this.avgResponseTime = responseTimes.reduce(
(sum: number, t: number) => sum + t, 0
) / responseTimes.length
this.minResponseTime = Math.min(...responseTimes)
this.maxResponseTime = Math.max(...responseTimes)
}
响应时间的评判标准:
- <=30秒(绿色):优秀,符合设计目标。
- 30秒-2分钟(橙色):一般,需要优化流程。
- >2分钟(红色):较差,可能存在严重问题。
三指标(平均/最快/最慢)的展示策略:
- 平均响应时间:整体水平的概览,字体最大(22fp)。
- 最快响应时间:最佳表现,绿色,表明系统能力上限。
- 最慢响应时间:最差表现,红色,揭示可能的流程瓶颈。
平均+最快+最慢的组合比单纯的平均值提供了更丰富的信息。例如,如果平均响应时间是45秒(橙色),但最慢是5分钟(红色),说明存在个别严重超时的情况,需要针对性排查。
4.4 异常统计分布
@Builder
AnomalyDistributionCard() {
Column({ space: 12 }) {
Text('异常分布统计')
.fontSize(16)
.fontColor('#333333')
.fontWeight(FontWeight.Medium)
ForEach(
this.anomalyDistribution,
(item: AnomalyItem) => {
Row({ space: 8 }) {
Text(item.type)
.fontSize(13)
.fontColor('#333333')
.width(80)
Row()
.width(`${item.percentage}%`)
.height(16)
.backgroundColor(item.color)
.borderRadius(4)
Text(`${item.count}次(${item.percentage.toFixed(1)}%)`)
.fontSize(12)
.fontColor('#888888')
.width(80)
}
.width('100%')
},
(item: AnomalyItem) => item.type
)
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
异常类型的分类与颜色:
| 异常类型 | 颜色 | 说明 |
|---|---|---|
| 流速过快 | #F44336 | 输液速度超过设定值20%以上 |
| 流速过慢 | #FF9800 | 输液速度低于设定值50%以下 |
| 液位过低 | #FF9800 | 液位低于20% |
| 管路阻塞 | #F44336 | 输液管路可能发生阻塞 |
| 传感器断连 | #9C27B0 | 传感器与设备失去连接 |
| 其他 | #999999 | 未分类的异常 |
private loadAnomalyDistribution() {
const records: InfusionRecord[] = this.dataStore.loadRecords()
const anomalyMap: Record<string, number> = {}
records.forEach((r: InfusionRecord) => {
r.anomalies.forEach((a: AnomalyEvent) => {
if (anomalyMap[a.type]) {
anomalyMap[a.type]++
} else {
anomalyMap[a.type] = 1
}
})
})
const total: number = Object.values(anomalyMap).reduce(
(sum: number, count: number) => sum + count, 0
)
const colorMap: Record<string, string> = {
'流速过快': '#F44336',
'流速过慢': '#FF9800',
'液位过低': '#FF9800',
'管路阻塞': '#F44336',
'传感器断连': '#9C27B0'
}
this.anomalyDistribution = Object.entries(anomalyMap).map(
([type, count]: [string, number]) => ({
type: type,
count: count,
percentage: total > 0 ? (count / total) * 100 : 0,
color: colorMap[type] || '#999999'
})
).sort((a: AnomalyItem, b: AnomalyItem) => b.count - a.count)
}
异常分布统计帮助护士识别最常见的问题类型,以便有针对性地改进。例如,如果流速过慢占比最高,可能需要检查输液泵的设置是否合理,或者管路是否存在打折。
水平条形图的选择理由:
异常分布使用水平条形图而非饼图,原因如下:
- 标签可读性:条形图的标签在左侧,文字水平排列,易于阅读;饼图的标签需要倾斜或使用图例,阅读效率低。
- 比较效率:条形图的长度对比更直觉,人类对长度的判断精度高于角度。
- 空间利用:条形图在窄屏(手机)上更节省空间,因为标签和条形是水平排列的。
- 扩展性:新增异常类型时,条形图只需增加一行;饼图需要重新计算所有扇区角度。
4.5 高风险患者识别与提示
@Builder
HighRiskPatientCard() {
Column({ space: 12 }) {
Row() {
Text('高风险患者')
.fontSize(16)
.fontColor('#333333')
.fontWeight(FontWeight.Medium)
Blank()
Text(`${this.highRiskPatients.length}位`)
.fontSize(13)
.fontColor('#F44336')
}
.width('100%')
if (this.highRiskPatients.length === 0) {
Text('本班次未识别到高风险患者')
.fontSize(13)
.fontColor('#4CAF50')
.padding(12)
} else {
ForEach(
this.highRiskPatients,
(patient: PatientRiskInfo) => {
Row({ space: 12 }) {
Column({ space: 2 }) {
Text(`${patient.bedNo}床 ${patient.patientName}`)
.fontSize(14)
.fontColor('#333333')
Text(patient.riskReason)
.fontSize(12)
.fontColor('#666666')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.layoutWeight(1)
Column({ space: 2 }) {
Text(`异常${patient.anomalyCount}次`)
.fontSize(13)
.fontColor('#F44336')
Text(`风险评分 ${patient.riskScore}`)
.fontSize(11)
.fontColor('#888888')
}
}
.width('100%')
.padding(10)
.backgroundColor('#FFF8E1')
.borderRadius(8)
},
(patient: PatientRiskInfo) => patient.patientId
)
}
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
高风险患者的识别算法:
private identifyHighRiskPatients() {
const records: InfusionRecord[] = this.dataStore.loadRecords()
const todayRecords: InfusionRecord[] = records.filter(
(r: InfusionRecord) => r.date === this.getCurrentDate()
)
this.highRiskPatients = todayRecords
.filter((r: InfusionRecord) => r.anomalyCount >= 2)
.map((r: InfusionRecord) => {
const riskScore: number = this.calculateRiskScore(r)
return {
patientId: r.patientId,
bedNo: r.bedNo,
patientName: r.patientName,
anomalyCount: r.anomalyCount,
riskScore: riskScore,
riskReason: this.getRiskReason(r)
}
})
.filter((p: PatientRiskInfo) => p.riskScore >= 60)
.sort((a: PatientRiskInfo, b: PatientRiskInfo) => b.riskScore - a.riskScore)
}
private calculateRiskScore(record: InfusionRecord): number {
let score: number = 0
score += record.anomalyCount * 20
score += record.alertCount * 10
if (record.maxResponseTime > 120) {
score += 15
}
if (record.hasCriticalAlert) {
score += 25
}
return Math.min(100, score)
}
private getRiskReason(record: InfusionRecord): string {
const reasons: string[] = []
if (record.anomalyCount >= 3) {
reasons.push('异常频次过高')
}
if (record.hasCriticalAlert) {
reasons.push('出现过紧急预警')
}
if (record.maxResponseTime > 120) {
reasons.push('响应时间过长')
}
return reasons.join(';') || '异常次数较多'
}
高风险患者识别的核心逻辑:
- 筛选:当日异常次数>=2的患者进入候选。
- 评分:基于异常次数(权重20)、预警次数(权重10)、响应时间(权重15)、是否有紧急预警(权重25)计算风险评分。
- 过滤:风险评分>=60的患者标记为高风险。
- 排序:按风险评分降序排列,最危险的患者排在最前。
这个算法在当前MVP阶段比较简单,未来可以引入更多因素(患者年龄、药品类型、过敏史等)进行更精确的风险评估。
风险评分的权重设计理由:
- 异常次数x20:异常是风险的最直接指标,每次异常贡献20分。3次异常即达60分阈值。
- 预警次数x10:预警次数反映了问题的严重程度,但权重低于异常次数,因为预警可能包含误报。
- 响应时间>120秒+15:延迟响应可能导致问题恶化,但这是流程问题而非患者本身的风险因素。
- 紧急预警+25:danger级别预警是最严重的信号,一次性贡献25分。
高风险患者卡片的浅黄色背景:
#FFF8E1是一个非常浅的黄色,类似于警示黄的淡化版本。这种背景色在不干扰整体视觉的前提下,为高风险患者卡片添加了微妙的注意标记。与纯白色背景的普通卡片相比,浅黄色背景在扫描时会稍微引人注目,但不至于像红色背景那样造成视觉压力。
4.6 AI生成建议
@Builder
AISuggestionCard() {
Column({ space: 12 }) {
Row() {
Text('智能建议')
.fontSize(16)
.fontColor('#333333')
.fontWeight(FontWeight.Medium)
Text('AI')
.fontSize(10)
.fontColor('#FFFFFF')
.backgroundColor('#7C4DFF')
.borderRadius(4)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
.margin({ left: 6 })
}
ForEach(
this.aiSuggestions,
(suggestion: string, index: number) => {
Row({ space: 8 }) {
Text(`${index + 1}`)
.fontSize(12)
.fontColor('#7C4DFF')
.width(20)
.height(20)
.textAlign(TextAlign.Center)
.borderRadius(10)
.backgroundColor('#EDE7F6')
Text(suggestion)
.fontSize(13)
.fontColor('#333333')
.lineHeight(20)
}
.width('100%')
.alignItems(VerticalAlign.Top)
},
(suggestion: string, index: number) => `${index}`
)
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
AI建议的生成逻辑(当前为规则引擎,未来接入LLM):
private generateAISuggestions() {
const suggestions: string[] = []
if (this.completionRate < 80) {
suggestions.push(
`本班次输液完成率为${this.completionRate.toFixed(1)}%,低于80%标准线。` +
`建议检查人员配置是否充足,以及输液流程是否存在瓶颈。`
)
}
if (this.avgResponseTime > 60) {
suggestions.push(
`平均预警响应时间为${this.formatResponseTime(this.avgResponseTime)},` +
`超过1分钟。建议优化预警通知机制,确保护士在30秒内注意到预警。`
)
}
const slowFlowAnomalies: number = this.anomalyDistribution.find(
(a: AnomalyItem) => a.type === '流速过慢'
)?.count || 0
if (slowFlowAnomalies >= 3) {
suggestions.push(
`流速过慢异常出现${slowFlowAnomalies}次,频率较高。` +
`建议检查输液泵设置及管路是否存在打折或阻塞。`
)
}
if (this.highRiskPatients.length > 0) {
suggestions.push(
`识别到${this.highRiskPatients.length}位高风险患者,` +
`建议增加巡视频次,优先关注${this.highRiskPatients[0].bedNo}床患者。`
)
}
if (suggestions.length === 0) {
suggestions.push('本班次输液状况良好,继续保持当前工作节奏。')
}
this.aiSuggestions = suggestions
}
当前MVP阶段的AI建议实际上是基于规则的专家系统,通过预设的规则模板和阈值判断生成建议文本。未来版本计划:
- 接入大语言模型(LLM),生成更自然、更具针对性的建议。
- 引入历史数据对比,提供趋势分析和异常预测。
- 个性化建议:根据不同护士的工作习惯和经验水平调整建议内容。
AI标签的设计:
紫色(#7C4DFF)是AI建议模块的专属颜色,与系统中的红/橙/蓝/绿语义色不同,紫色代表智能和分析能力。AI标签使用极小的字号(10fp)和紧凑的内边距,避免喧宾夺主。紫色编号圆点(20x20dp)为每条建议提供了清晰的视觉起点,帮助护士逐条阅读。
5. 护士端专属设计考量
5.1 信息密度与可扫描性
护士端UI设计的首要原则是信息密度与可扫描性的平衡。护士需要在极短时间内获取8-12位患者的核心状态信息,这要求界面既信息丰富又易于扫描。
5.1.1 一屏看8-12个患者的设计策略
以典型的6英寸手机屏幕(1080x2400像素)为例,可用显示区域约为360x640dp。在一屏内展示8-12个患者卡片,每个卡片的可用高度约为:
(640dp - 统计面板80dp - 导航栏48dp - 间距) / 10 = 48dp/卡片
48dp的高度非常有限,必须精心安排每一条信息的优先级。我们的策略是:
- 核心信息(必显示):床号(16dp)、液位进度条(6dp)、状态标签(12dp)。
- 次要信息(空间允许时显示):姓名(13dp)、药品名称(14dp)。
- 辅助信息(点击查看):详细参数、历史记录。
在紧凑模式下(患者数量>8),可以隐藏药品名称,只显示床号+姓名+进度条+状态。这种自适应布局确保了在不同患者数量下都能保持良好的可扫描性。
5.1.2 F型扫描模式
研究表明,用户在浏览列表信息时,视线通常呈F型模式——先水平扫过顶部,然后沿左侧垂直扫下,偶尔向右扫一些内容。护士端的设计顺应这一模式:
- 左侧:床号和姓名,是护士最关注的信息,放在左侧确保垂直扫描时优先获取。
- 中间:液位进度条,提供最关键的状态信息,中等宽度即可理解。
- 右侧:状态标签和操作按钮,是扫描的末端,但仍然是重要信息。
5.1.3 颜色预过滤
颜色编码是提高可扫描性的最重要手段。护士扫描患者列表时,眼睛会自动被红色和橙色吸引,这意味着:
- 有问题的患者(红色/橙色进度条)会自动浮现在视野中。
- 正常的患者(绿色进度条)会自动退到背景中。
这种颜色预过滤大大减少了护士的认知负荷——她不需要逐个阅读每个患者的数据,只需要关注颜色异常的那些。
5.1.4 列表与网格布局的选择
护士端的患者列表采用垂直列表而非网格布局,原因如下:
- 信息完整性:列表布局允许每行显示更多文字信息(床号、姓名、药品、进度条、状态),网格布局只能显示床号和简化的状态。
- 排序直观:列表布局的优先级排序更直观——越靠上的患者越需要关注。
- 操作便捷:列表布局的点击区域更大,误触概率更低。
- 扩展性:列表布局可以通过添加列来展示更多信息,网格布局的空间受限于卡片大小。
未来在平板设备上,可以考虑使用网格布局展示更多患者,但手机端仍以列表为主。
5.2 一键操作优先
护士工作节奏快,每次操作的步骤都应该尽可能少。我们为常见操作设定了一键标准:
| 操作 | 点击次数 | 说明 |
|---|---|---|
| 处理预警 | 1次 | 点击AlertCard上的处理按钮 |
| 查看患者详情 | 1次 | 点击PatientCard |
| 切换Tab筛选 | 1次 | 点击Tab标签 |
| 搜索患者 | 2次 | 点击搜索框 + 输入关键词 |
| 查看聚合分析 | 2次 | 点击底部导航 + 页面加载 |
处理预警作为最高频操作,被设计为1次点击完成。这比传统的选中-确认-提交三步操作节省了67%的时间。
一键操作的设计哲学:
一键操作不仅是减少步骤,更是一种设计理念——每一种护士需要执行的操作,都应该被设计为最简形式。这要求设计者深入理解护士的工作流程,识别出哪些步骤是必要的,哪些可以省略。
例如,处理预警时,我们省略了选择处理方式(前往查看/电话通知/标记误报)的步骤。在当前MVP阶段,处理就是标记已处理,不需要区分处理方式。未来如果需要区分,可以通过滑动操作或长按菜单提供更多选项,但默认操作仍应是一键处理。
5.3 声音提示设计
声音提示是预警系统中不可替代的通道——护士不一定时刻盯着屏幕,但声音可以主动引起注意。IVGuard护士端的声音提示设计遵循以下原则:
5.3.1 区分患者的音调方案
不同床号的预警使用不同的提示音,帮助护士在不看屏幕的情况下就识别出是哪个患者出了问题:
床号1-3:低音调(C4, 262Hz)
床号4-6:中音调(E4, 330Hz)
床号7-9:高音调(G4, 392Hz)
床号10+:双音调(C5+E5交替)
这种设计让护士听到提示音后,能立即判断患者的大致位置,减少查找时间。
5.3.2 区分级别的音型方案
除了音调区分床号,音型(声音的模式)区分预警级别:
| 预警级别 | 音型 | 说明 |
|---|---|---|
| danger | 连续急促(500ms间隔,3次/组) | 最紧急,类似心电监护仪的报警 |
| warning | 间隔短促(1000ms间隔,2次/组) | 较紧急,但不如danger紧迫 |
| info | 单次提示(1次) | 仅提醒,不造成打扰 |
5.3.3 声音提示的实现
class AlertSoundPlayer {
private soundMap: Record<string, number> = {
'danger_bed1_3': 0,
'danger_bed4_6': 1,
'danger_bed7_9': 2,
'warning_bed1_3': 3,
'warning_bed4_6': 4,
'warning_bed7_9': 5,
'info': 6
}
play(alertLevel: string, bedNo: string): void {
const bedNum: number = parseInt(bedNo) || 0
let soundKey: string = ''
if (alertLevel === 'info') {
soundKey = 'info'
} else {
if (bedNum <= 3) {
soundKey = `${alertLevel}_bed1_3`
} else if (bedNum <= 6) {
soundKey = `${alertLevel}_bed4_6`
} else if (bedNum <= 9) {
soundKey = `${alertLevel}_bed7_9`
} else {
soundKey = `${alertLevel}_bed1_3`
}
}
const soundId: number = this.soundMap[soundKey] || 0
this.playSoundById(soundId)
}
private playSoundById(soundId: number): void {
// 使用media组件播放预设提示音
}
}
5.3.4 振动模式
除声音外,振动也是重要的预警通道,特别适用于嘈杂的病房环境:
| 预警级别 | 振动模式 | 持续时间 |
|---|---|---|
| danger | 长振动(500ms)+ 短振动(100ms)x3 | 800ms |
| warning | 中振动(300ms)x2 | 600ms |
| info | 短振动(100ms)x1 | 100ms |
5.3.5 多通道通知的协同
IVGuard护士端的多通道通知遵循递进原则:
- 第一级(即时):屏幕通知 + 声音 + 振动——danger级别预警触发。
- 第二级(5秒后未处理):重复声音 + 振动——预警仍未被查看。
- 第三级(30秒后未处理):升级通知——发送给值班护士长。
- 第四级(2分钟后未处理):全科室广播——danger级别预警仍未处理。
这种递进式通知确保了预警不会被遗漏,同时避免了过度打扰——如果护士在第一级就处理了预警,后续级别不会触发。
5.4 颜色编码直觉
IVGuard护士端建立了统一的颜色编码体系,贯穿所有页面和组件:
┌──────────────────────────────────────────────┐
│ IVGuard 颜色编码体系 │
├──────────────────────────────────────────────┤
│ │
│ 红色 #F44336 │
│ ├── 含义:立即行动 │
│ ├── 使用场景: │
│ │ ├── danger级别预警 │
│ │ ├── 液位<20%进度条 │
│ │ ├── 完成率<70% │
│ │ └── 异常患者标签 │
│ │ │
│ 橙色 #FF9800 │
│ ├── 含义:关注 │
│ ├── 使用场景: │
│ │ ├── warning级别预警 │
│ │ ├── 液位20-50%进度条 │
│ │ ├── 完成率70-90% │
│ │ └── 异常数统计 │
│ │ │
│ 蓝色 #2196F3 │
│ ├── 含义:正常/进行中 │
│ ├── 使用场景: │
│ │ ├── 监控数统计 │
│ │ ├── 监控中状态标签 │
│ │ └── info级别预警 │
│ │ │
│ 绿色 #4CAF50 │
│ ├── 含义:正常/已完成 │
│ ├── 使用场景: │
│ │ ├── 已完成统计/标签 │
│ │ ├── 液位>50%进度条 │
│ │ ├── 完成率>=90% │
│ │ └── 系统正常指示灯 │
│ │ │
│ 紫色 #7C4DFF │
│ ├── 含义:智能/分析 │
│ ├── 使用场景: │
│ │ ├── AI建议标签 │
│ │ ├── AI建议编号圆点 │
│ │ └── 智能分析模块 │
│ │ │
│ 灰色 #999999 │
│ ├── 含义:次要/已处理 │
│ ├── 使用场景: │
│ │ ├── 已处理预警标签 │
│ │ ├── 时间戳 │
│ │ └── 辅助说明文字 │
│ │
└──────────────────────────────────────────────┘
这套颜色体系的核心原则是一致性——同一种颜色在不同场景下代表相同的含义。护士学会一次,就能在所有页面中理解颜色所传达的信息。
颜色选择的可访问性考量:
所有颜色组合都经过了对比度检查,确保在日光和暗光环境下都清晰可辨。特别是:
- 红色#F44336在白色背景上的对比度为4.6:1,满足WCAG AA标准。
- 橙色#FF9800在白色背景上的对比度为3.1:1,略低于AA标准,但在进度条等非文字元素上可以接受。
- 所有文字颜色(#333333、#666666、#999999)与白色背景的对比度均超过4.5:1。
5.5 无障碍设计
考虑到护士群体中存在视力差异,IVGuard护士端遵循以下无障碍设计原则:
- 最小触摸目标44dp:所有可点击元素的触摸区域至少44x44dp,确保戴手套操作和手抖情况下的准确性。
- 对比度>=4.5:1:所有文字与背景的对比度符合WCAG AA标准。
- 字号缩放:支持系统字号设置,关键数字(统计面板数字、床号)使用固定大小,辅助文字跟随系统缩放。
- 语义化标签:所有交互元素添加accessibility属性,支持屏幕阅读器。
- 颜色非唯一标识:颜色不是传达信息的唯一手段,总是配合文字标签使用(如红色进度条+异常文字标签)。
5.6 紧凑与舒适模式的切换
考虑到护士在不同场景下对信息密度的需求不同,未来版本计划增加紧凑/舒适模式切换:
| 模式 | 卡片高度 | 显示内容 | 适用场景 |
|---|---|---|---|
| 紧凑 | 48dp | 床号+进度条+状态 | 患者多、快速扫描 |
| 标准 | 64dp | 床号+姓名+进度条+状态 | 常规使用 |
| 舒适 | 80dp | 床号+姓名+药品+进度条+状态 | 患者少、详细查看 |
切换可以在设置页面或通过手势(双指缩放)触发。当前MVP版本默认使用标准模式。
6. 预警分级与处理流程
IVGuard的预警系统采用三级分级机制,每级对应不同的紧急程度、通知方式和处理时限。这一分级体系是护士端UI设计的核心框架——所有预警相关的视觉、听觉和交互设计都围绕这个分级展开。
6.1 danger(红色紧急)
6.1.1 触发条件
danger级别预警在以下条件下触发:
- 液位<10%:输液瓶液位降至10%以下,即将完成或已经完成。
- 流速为0:输液流速突然降为0,可能是管路阻塞或输液完成。
- 传感器断连超过5分钟:传感器与设备失去连接超过5分钟,无法获取实时数据。
6.1.2 响应要求
danger级别预警是最高紧急程度的预警,必须立即响应:
- 响应时限:30秒内必须注意到预警,2分钟内必须开始处理。
- 超时升级:2分钟未处理,系统自动升级通知——发送给值班护士长。
- 记录要求:必须记录处理时间和处理人,用于事后追溯。
6.1.3 通知方式
danger级别预警使用全通道通知,确保护士不会错过:
- 屏幕通知:全屏弹窗,显示患者信息、预警原因和操作按钮。
- 声音:连续急促提示音(500ms间隔,3次/组),循环播放直到确认。
- 振动:长振动(500ms)+ 短振动(100ms)x3,循环。
- 智能手表:向连接的智能手表推送预警通知,包含床号和预警类型。
- 常亮屏幕:设备屏幕强制点亮,即使处于锁屏状态。
6.1.4 danger预警的UI表现
// danger预警的全屏弹窗
@Builder
DangerAlertDialog(alert: AlertRecord) {
Column({ space: 16 }) {
Text('紧急预警')
.fontSize(20)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
Text(`${alert.bedNo}床 ${alert.patientName}`)
.fontSize(18)
.fontColor('#FFFFFF')
Text(alert.message)
.fontSize(14)
.fontColor('#FFCDD2')
.maxLines(3)
Button('立即处理')
.fontSize(16)
.fontColor('#F44336')
.backgroundColor('#FFFFFF')
.width('80%')
.height(48)
.borderRadius(24)
.onClick(() => this.handleAlert(alert.id))
}
.width('100%')
.height('100%')
.backgroundColor('#F44336')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.padding(24)
}
danger预警的全屏弹窗采用红色背景+白色文字的高对比度设计,确保在任何光线条件下都能清晰阅读。立即处理按钮使用白色背景+红色文字,与整体红色背景形成对比,操作目标一目了然。
6.1.5 danger预警的临床场景
典型场景:3床患者张三正在输入0.9%氯化钠注射液250ml,系统检测到液位降至8%,流速正常但即将完成。系统触发danger级别预警——“3床 张三 0.9%氯化钠注射液 液位8% 预计3分钟完成 请及时更换或拔针”。
护士收到预警后,应在2分钟内到达患者床旁,确认输液状态并采取适当措施(更换输液瓶或结束输液)。
6.2 warning(橙色预警)
6.2.1 触发条件
warning级别预警在以下条件下触发:
- 液位10%-20%:输液瓶液位降至10%-20%区间,尚有时间但需要关注。
- 流速异常:输液流速偏离设定值50%以上(过快或过慢)。
- 长时间未更新:传感器数据超过2分钟未更新,可能存在连接问题。
6.2.2 响应要求
warning级别预警需要较快响应,但紧急程度低于danger:
- 响应时限:5分钟内应注意到预警,10分钟内应开始处理。
- 超时提醒:5分钟未处理,系统发送提醒通知(不升级到护士长)。
6.2.3 通知方式
warning级别预警使用部分通道通知:
- 屏幕通知:在NurseHomePage的预警列表中显示橙色边框的AlertCard。
- 声音:间隔短促提示音(1000ms间隔,2次/组),播放一次。
- 振动:中振动(300ms)x2,播放一次。
- 不触发:全屏弹窗、智能手表推送、常亮屏幕(这些是danger专属通道)。
6.2.4 warning预警的UI表现
warning预警以AlertCard形式出现在列表中,橙色边框和色标圆点使其在视觉上与danger(红色)和info(蓝色)区分。卡片上的处理按钮也是橙色背景,提示护士需要关注但不必立即行动。
6.2.5 warning预警的临床场景
典型场景:7床患者李四正在输入5%葡萄糖注射液500ml,系统检测到流速从设定的80ml/h降至35ml/h(低于设定值56%)。系统触发warning级别预警——“7床 李四 5%葡萄糖注射液 流速异常 当前35ml/h 设定80ml/h 偏低56%”。
护士收到预警后,应在10分钟内检查输液管路是否打折、输液泵设置是否正确,必要时调整流速。
6.3 info(蓝色提示)
6.3.1 触发条件
info级别预警在以下条件下触发:
- 输液完成:患者输液已正常完成,需要护士确认并拔针。
- 药物相互作用提示:系统检测到患者正在使用的药物之间可能存在相互作用。
- 常规提醒:定时巡视提醒、交接班提醒等。
6.3.2 响应要求
info级别预警是常规提示,不涉及紧急情况:
- 响应时限:无硬性要求,建议在30分钟内处理。
- 无超时机制:info预警不设置超时升级。
6.3.3 通知方式
info级别预警仅使用最轻量的通知方式:
- 屏幕通知:在NurseHomePage的预警列表中显示蓝色边框的AlertCard。
- 声音:单次提示音(1次)。
- 不触发:振动、全屏弹窗、智能手表推送、常亮屏幕。
6.3.4 info预警的UI表现
info预警的AlertCard使用蓝色边框,视觉上表示这是一个提示性信息而非紧急情况。处理按钮也是蓝色背景,传达冷静处理的语义。
6.3.5 info预警的临床场景
典型场景:5床患者王五的输液已正常完成,传感器检测到液位为0且流速为0。系统触发info级别预警——“5床 王五 头孢呋辛钠注射液 输液完成 请确认并拔针”。
护士在方便时前往患者床旁,确认输液完成并拔针。
6.4 预警级别的转换与升级
预警级别并非一成不变,系统会根据实时数据动态调整:
- warning → danger:如果液位从15%降至8%,预警级别自动升级为danger,通知方式也相应升级。
- info → warning:如果输液完成后30分钟仍未拔针,info升级为warning(针头留置时间过长有感染风险)。
- 误报标记:护士可以将预警标记为误报(如传感器误触发),标记后的预警不再计入统计。
private checkAlertUpgrade(alert: AlertRecord): AlertRecord {
if (alert.level === 'warning' && alert.age > 300000) {
// warning预警5分钟未处理,升级为danger
alert.level = 'danger'
alert.message = `[升级] ${alert.message}`
}
return alert
}
预警级别的转换逻辑确保了问题的严重性不会被低估——一个最初只是warning的问题,如果未被及时处理,会随着时间推移升级为更高级别的预警。
6.5 预警处理流程的完整时序图
患者端传感器检测异常
│
▼
AlertEngine评估预警级别
│
├── danger → 全通道通知(屏幕+声音+振动+手表+常亮)
│ │
│ ├── 30秒内护士点击处理 → 记录handled → 结束
│ │
│ ├── 30秒未处理 → 重复通知
│ │
│ └── 2分钟未处理 → 升级通知护士长
│
├── warning → 部分通道通知(屏幕+声音+振动)
│ │
│ ├── 5分钟内护士点击处理 → 记录handled → 结束
│ │
│ └── 5分钟未处理 → 重复提醒
│
└── info → 轻量通知(屏幕+单次声音)
│
└── 护士方便时处理 → 记录handled → 结束
7. 多患者管理UX
管理8-12位患者的信息是一项复杂的UX挑战。IVGuard护士端通过排序、批量操作、搜索过滤和刷新机制,帮助护士高效管理多患者信息。
7.1 排序策略
患者列表支持多种排序方式,适应不同的工作场景:
7.1.1 按床号排序(默认)
patients.sort((a, b) => parseInt(a.bedNo) - parseInt(b.bedNo))
按床号排序是最直觉的方式,符合护士的巡视路线(通常从1床到12床)。这种方式让护士可以按照物理位置依次查看患者,与实际工作流程一致。
7.1.2 按预警级别排序
patients.sort((a, b) => {
const priorityMap: Record<string, number> = {
'danger': 0, 'warning': 1, 'normal': 2, 'completed': 3
}
return priorityMap[a.alertLevel] - priorityMap[b.alertLevel]
})
按预警级别排序时,最紧急的患者排在最前面,确保护士优先关注最需要干预的患者。这种排序在预警密集时特别有用——护士不需要翻阅列表就能知道哪位患者最需要关注。
7.1.3 按剩余时间排序
patients.sort((a, b) => a.estimatedRemaining - b.estimatedRemaining)
按剩余时间排序时,即将完成输液的患者排在最前面。这种排序帮助护士提前规划——先去即将完成的患者处,再去还有大量时间的患者处。
7.1.4 排序切换的交互设计
@Builder
SortSelector() {
Row({ space: 4 }) {
Text('排序:')
.fontSize(12)
.fontColor('#888888')
this.SortOption('床号', 'bedNo')
this.SortOption('预警', 'alertLevel')
this.SortOption('剩余', 'remaining')
}
.padding({ left: 16, right: 16, top: 4, bottom: 4 })
}
@Builder
SortOption(label: string, key: string) {
Text(label)
.fontSize(12)
.fontColor(this.currentSort === key ? '#2196F3' : '#666666')
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.backgroundColor(this.currentSort === key ? '#E3F2FD' : Color.Transparent)
.borderRadius(4)
.onClick(() => {
this.currentSort = key
})
}
排序选项以小标签的形式展示在搜索栏下方,选中项蓝色高亮。这种设计不会占用太多空间,同时提供了灵活的排序切换能力。
7.2 批量处理
当班次结束时有多个已完成的预警需要标记,批量处理功能可以大大提高效率:
@State selectedAlerts: Set<string> = new Set()
@Builder
BatchActionBar() {
Row({ space: 8 }) {
Checkbox()
.select(this.selectedAlerts.size === this.getUnHandledCount())
.onChange((checked: boolean) => {
if (checked) {
this.selectAllUnhandled()
} else {
this.selectedAlerts.clear()
}
})
Text(`已选${this.selectedAlerts.size}项`)
.fontSize(13)
.fontColor('#666666')
Button('批量处理')
.fontSize(13)
.fontColor('#FFFFFF')
.backgroundColor('#2196F3')
.height(32)
.borderRadius(4)
.enabled(this.selectedAlerts.size > 0)
.onClick(() => this.batchHandleAlerts())
}
.width('100%')
.padding(8)
.backgroundColor('#FFFFFF')
}
private batchHandleAlerts() {
this.selectedAlerts.forEach((alertId: string) => {
const alertIndex: number = this.alerts.findIndex(
(a: AlertRecord) => a.id === alertId
)
if (alertIndex !== -1) {
this.alerts[alertIndex].handled = true
this.alerts[alertIndex].handledAt = Date.now()
this.alerts[alertIndex].handledBy = 'current_nurse'
}
})
this.dataStore.saveAlerts(this.alerts)
this.selectedAlerts.clear()
this.loadAlerts()
this.loadStatistics()
}
批量处理的典型使用场景:
- 班次结束时:护士一次性标记所有已处理的预警,生成交班报告。
- 巡视后:护士巡视回来后,批量标记在巡视中已处理的预警。
- 误报清理:批量标记传感器误触发的预警为误报。
7.3 搜索过滤
搜索功能已在3.3节详细描述,这里补充搜索的UX设计细节:
- 即时过滤:输入时实时过滤,不需要按搜索按钮,减少操作步骤。
- 高亮匹配:搜索结果中匹配的关键词高亮显示(未来实现),帮助护士快速定位。
- 搜索历史:保存最近的搜索词(未来实现),一键重复常用搜索。
- 清空按钮:搜索框右侧的清空按钮,一键清除搜索条件。
7.4 刷新机制
数据的及时性对护士端至关重要。当前MVP版本使用手动刷新,未来将增加自动刷新:
7.4.1 下拉刷新
List()
.refreshToggler(true)
.onRefresh(() => {
this.loadPatients()
this.loadStatistics()
})
下拉刷新是移动端用户最熟悉的刷新方式。护士在需要最新数据时,下拉列表即可刷新。刷新过程中显示加载指示器,刷新完成后自动消失。
7.4.2 自动轮询(未来实现)
// 未来实现方向
private pollTimer: number = -1
aboutToAppear() {
this.startPolling()
}
aboutToDisappear() {
this.stopPolling()
}
private startPolling() {
this.pollTimer = setInterval(() => {
this.loadPatients()
this.loadStatistics()
}, 30000) // 30秒轮询一次
}
private stopPolling() {
if (this.pollTimer !== -1) {
clearInterval(this.pollTimer)
this.pollTimer = -1
}
}
自动轮询间隔设定为30秒,与预警响应时间目标一致。轮询在页面不可见时自动暂停(aboutToDisappear),避免不必要的资源消耗。
7.4.3 WebSocket实时推送(未来实现)
// 未来实现方向
private websocket: WebSocket | null = null
private connectWebSocket() {
this.websocket = new WebSocket('wss://ivguard-server/alerts')
this.websocket.onmessage = (event: MessageEvent) => {
const alert: AlertRecord = JSON.parse(event.data as string)
this.alerts.unshift(alert)
this.playAlertSound(alert.level, alert.bedNo)
}
}
WebSocket是最终目标——当服务端有新预警时,实时推送到护士端,无需轮询。这将数据延迟从30秒(轮询间隔)降低到毫秒级(网络延迟),实现真正的实时监控。
7.5 多患者场景下的认知负荷管理
管理8-12位患者的信息对护士的认知负荷是一个挑战。IVGuard通过以下策略降低认知负荷:
- 分而治之:通过Tab筛选将患者分组(监控中/已完成/异常),护士不需要同时处理所有信息。
- 优先级排序:预警列表和患者列表都按优先级排序,护士只需要从上到下处理,不需要自行判断优先级。
- 颜色编码:通过颜色传达状态信息,护士不需要阅读文字就能了解大致情况。
- 聚合统计:四宫格面板提供了宏观概览,护士不需要看列表就能了解整体状况。
- 渐进式细节:列表展示核心信息,详情页展示完整信息,避免信息过载。
8. 护士端数据流
护士端的数据流定义了数据在各组件之间的流转路径。理解数据流,是理解护士端架构的关键。
8.1 数据流总览
┌─────────────────────────────────────────────────────────────┐
│ 护士端数据流 │
├─────────────────────────────────────────────────────────────┤
│ │
│ DataStore(数据层) │
│ ├── loadRecords() → InfusionRecord[] │
│ ├── loadAlerts() → AlertRecord[] │
│ ├── saveAlerts(alerts) → void │
│ └── saveRecords(records) → void │
│ │
│ NurseHomePage │
│ ├── loadStatistics() │
│ │ ├── DataStore.loadRecords() → 计算todayMonitoring等 │
│ │ └── DataStore.loadAlerts() → 计算todayWarnings │
│ ├── loadAlerts() │
│ │ └── DataStore.loadAlerts() → sortAlerts() → alerts │
│ └── handleAlert(alertId) │
│ ├── alerts[index].handled = true │
│ ├── DataStore.saveAlerts(alerts) │
│ └── loadStatistics() → 更新面板数字 │
│ │
│ PatientListPage │
│ └── loadPatients() │
│ ├── DataStore.loadRecords() → 转换为PatientInfo[] │
│ └── sort() → 按床号排序 │
│ │
│ NurseAnalysisPage │
│ ├── calculateCompletionRate() │
│ │ └── DataStore.loadRecords() → 计算完成率 │
│ ├── calculateAvgResponseTime() │
│ │ └── DataStore.loadAlerts() → 计算响应时间 │
│ ├── loadAnomalyDistribution() │
│ │ └── DataStore.loadRecords() → 统计异常分布 │
│ ├── identifyHighRiskPatients() │
│ │ └── DataStore.loadRecords() → 识别高风险患者 │
│ └── generateAISuggestions() │
│ └── 基于上述计算结果 → 生成建议 │
│ │
└─────────────────────────────────────────────────────────────┘
8.2 DataStore.loadAlerts → 列表渲染
预警列表的数据流:
aboutToAppear()→ 调用loadAlerts()loadAlerts()→ 调用DataStore.loadAlerts()获取所有预警记录sortAlerts()→ 按处理状态、级别、时间排序- 排序结果赋值给
@State alerts @State alerts变化 →ForEach重新渲染 → AlertCard列表更新
这个数据流是单向的——数据从DataStore流向UI,UI不直接修改数据。唯一的例外是handleAlert(),它修改了alerts数组并写回DataStore。
8.3 handleAlert → 标记handled → saveAlerts
预警处理的数据流:
- 护士点击处理按钮 →
onHandle(alertId)回调 handleAlert(alertId)→ 在alerts数组中找到对应记录- 修改
handled = true、handledAt = Date.now()、handledBy = 'current_nurse' DataStore.saveAlerts(alerts)→ 持久化保存@State alerts变化 → AlertCard自动更新(按钮→已处理标签,边框变色)loadStatistics()→ 重新计算统计数字@State todayWarnings等变化 → 统计面板自动更新
这个数据流的关键特性是即时性——从点击处理到看到视觉反馈,中间没有任何需要等待的步骤。DataStore.saveAlerts()是同步操作(当前MVP使用本地存储),不会造成延迟。
8.4 DataStore.loadRecords → 统计计算
统计计算的数据流:
loadStatistics()→ 调用DataStore.loadRecords()获取所有输液记录- filter + reduce → 计算四个统计指标
- 赋值给
@State todayMonitoring等 @State变化 → 统计面板自动更新
统计计算的特点是聚合性——从多条记录中提取出4个数字,信息密度极高。这种聚合计算在数据量小(<100条记录)时性能开销可忽略。
8.5 未来:WebSocket实时推送新预警
当前MVP的数据流是拉取式——页面需要主动调用DataStore方法获取数据。未来的数据流将转变为推送式——当有新预警时,服务端主动推送到护士端:
当前MVP:
页面 → DataStore.loadAlerts() → 渲染
未来:
服务端 → WebSocket → 页面回调 → 更新@State → 渲染
WebSocket推送的数据流:
- 服务端检测到异常 → 生成AlertRecord
- 服务端通过WebSocket推送AlertRecord到护士端
- 护士端回调函数接收AlertRecord
- 将新AlertRecord插入alerts数组顶部
@State alerts变化 → AlertCard列表自动更新- 播放提示音和振动
这种推送式数据流的延迟极低(毫秒级),可以实现真正的实时预警。
8.6 数据一致性保障
在当前MVP阶段,护士端和患者端共享同一设备的DataStore,数据一致性由DataStore的单例模式保证:
- DataStore使用
getInstance()确保全局只有一个实例。 - 读写操作都是同步的,不存在并发问题。
- 每次页面
aboutToAppear()都重新加载数据,确保显示最新状态。
未来多设备场景下,数据一致性需要通过以下机制保障:
- 乐观锁:写入时检查数据版本号,避免覆盖其他设备的更新。
- 冲突解决:当多个护士同时处理同一条预警时,采用后写者胜策略(Last Write Wins)。
- 最终一致性:通过WebSocket推送+定期全量同步,确保所有设备最终达到一致状态。
9. 与患者端/家属端的数据联动
IVGuard是一个多角色系统,护士端、患者端和家属端共享同一套数据,但各自关注的维度不同。数据联动机制确保了三个端的信息同步和语义一致。
9.1 预警事件的跨端流转
预警事件的完整流转路径:
患者端传感器检测异常
│
▼
患者端AlertEngine评估预警级别
│
├── 患者端显示预警(安慰性提示 + 操作建议)
│
▼
DataStore.saveAlerts() 持久化
│
▼
护士端DataStore.loadAlerts() 读取
│
▼
护士端AlertCard渲染 → 护士处理 → 标记handled
│
▼
DataStore.saveAlerts() 持久化
│
▼
家属端DataStore.loadAlerts() 读取
│
▼
家属端显示预警状态 + 处理进度
9.2 当前MVP:同一设备共享DataStore
当前MVP阶段,三个端(护士端、患者端、家属端)运行在同一设备上,共享同一个DataStore实例。数据联动的实现非常简单:
- 写入端:患者端调用
DataStore.saveAlerts()写入新预警。 - 读取端:护士端调用
DataStore.loadAlerts()读取预警列表。 - 更新端:护士端调用
DataStore.saveAlerts()更新预警状态。 - 共享机制:DataStore是单例,所有端访问同一份数据。
这种设计的优点是简单可靠,缺点是无法跨设备同步。当护士端和患者端运行在不同设备上时,需要服务端中转。
9.3 未来:服务端中转与多设备同步
未来的多设备架构:
患者设备A → 传感器数据 → 云端 → 护士设备B
↓
家属设备C
数据同步的技术方案:
- WebSocket长连接:每个设备与服务端保持WebSocket长连接,用于实时推送。
- RESTful API:用于历史数据查询和批量操作。
- 数据版本号:每条记录携带版本号,用于冲突检测和增量同步。
- 离线缓存:设备离线时,操作缓存在本地,上线后自动同步。
9.4 跨端数据语义一致性
三个端虽然共享数据,但对同一条数据的理解和展示方式不同:
| 数据 | 患者端展示 | 护士端展示 | 家属端展示 |
|---|---|---|---|
| 液位 | 安慰性描述(还剩不少/快完了) | 精确百分比+颜色编码 | 简单文字+颜色 |
| 预警 | 不显示danger级别(避免恐慌) | 显示所有级别 | 仅显示warning以上 |
| 流速 | 简化描述(正常/偏慢/偏快) | 精确数值+设定值对比 | 不显示 |
| 统计 | 不显示 | 完整统计面板 | 简化统计 |
这种语义差异不是数据不一致,而是信息适配——根据不同角色的需求和认知水平,对同一数据进行不同的呈现。
9.5 预警状态的跨端同步
预警状态在不同端的同步逻辑:
- 患者端触发预警:患者端传感器检测到异常 → AlertEngine评估级别 → DataStore.saveAlerts()
- 护士端显示预警:DataStore.loadAlerts() → AlertCard渲染 → 护士看到预警
- 护士处理预警:点击处理 → handleAlert() → DataStore.saveAlerts()
- 患者端状态更新:下次加载时读取handled状态 → 显示已处理提示 → 患者安心
- 家属端状态更新:下次加载时读取handled状态 → 显示护士已响应 → 家属放心
整个流程形成了一个完整的闭环:问题发现 → 问题传达 → 问题处理 → 状态反馈。这个闭环确保了输液安全问题不会被遗漏,并且所有相关方都能了解最新状态。
9.6 数据联动的隐私考量
跨端数据共享需要考虑隐私保护:
- 最小化数据共享:家属端只显示必要的信息(液位、预警状态、处理进度),不显示详细的医疗参数(流速设定值、药品剂量等)。
- 患者授权:家属端查看患者数据需要患者授权。当前MVP暂不实现授权机制,未来通过二维码或密码验证。
- 数据脱敏:护士端与家属端之间的数据传输,对敏感信息(如药品名称中的剂量信息)进行脱敏处理。
- 日志记录:所有数据访问操作记录日志,用于隐私审计。
附录
附录A:护士端颜色常量定义
export class NurseColors {
static readonly DANGER: string = '#F44336'
static readonly WARNING: string = '#FF9800'
static readonly INFO: string = '#2196F3'
static readonly SUCCESS: string = '#4CAF50'
static readonly AI: string = '#7C4DFF'
static readonly SECONDARY: string = '#999999'
static readonly TEXT_PRIMARY: string = '#333333'
static readonly TEXT_SECONDARY: string = '#666666'
static readonly TEXT_HINT: string = '#CCCCCC'
static readonly BACKGROUND: string = '#F5F5F5'
static readonly CARD_BACKGROUND: string = '#FFFFFF'
static readonly DANGER_LIGHT: string = '#FFEBEE'
static readonly WARNING_LIGHT: string = '#FFF3E0'
static readonly INFO_LIGHT: string = '#E3F2FD'
static readonly SUCCESS_LIGHT: string = '#E8F5E9'
static readonly AI_LIGHT: string = '#EDE7F6'
static readonly HIGH_RISK_BG: string = '#FFF8E1'
}
附录B:护士端数据类型定义
interface AlertRecord {
id: string
patientId: string
bedNo: string
patientName: string
level: 'danger' | 'warning' | 'info'
message: string
timestamp: number
handled: boolean
handledAt: number
handledBy: string
date: string
}
interface PatientInfo {
id: string
bedNo: string
patientName: string
medicineName: string
currentLevel: number
totalLevel: number
status: 'monitoring' | 'completed' | 'paused'
hasAnomaly: boolean
}
interface AnomalyItem {
type: string
count: number
percentage: number
color: string
}
interface PatientRiskInfo {
patientId: string
bedNo: string
patientName: string
anomalyCount: number
riskScore: number
riskReason: string
}
interface InfusionRecord {
id: string
patientId: string
bedNo: string
patientName: string
medicineName: string
date: string
status: 'monitoring' | 'completed' | 'paused'
currentLevel: number
totalLevel: number
anomalyCount: number
alertCount: number
hasCriticalAlert: boolean
maxResponseTime: number
anomalies: AnomalyEvent[]
}
附录C:护士端页面导航关系
NurseHomePage
├── → PatientListPage
│ └── → PatientDetailPage (患者详情)
├── → NurseAnalysisPage
│ └── → PatientDetailPage (高风险患者详情)
└── → 角色切换 (患者端/家属端)
附录D:护士端组件依赖关系
NurseHomePage
├── AlertCard
│ └── getAlertColor() → NurseColors
├── StatisticsPanel
│ └── 四宫格数字 → DataStore
├── AlertListSection
│ └── ForEach → AlertCard
└── NavigationBar
PatientListPage
├── SearchBar
├── FilterTabs
│ └── TabItem
├── PatientListView
│ └── ForEach → PatientCard
│ └── LevelProgress → NurseColors
└── EmptyPatientState
NurseAnalysisPage
├── CompletionRateCard
│ └── Progress (环形)
├── ResponseTimeCard
├── AnomalyDistributionCard
│ └── ForEach → Row (条形图)
├── HighRiskPatientCard
│ └── ForEach → Row (患者项)
└── AISuggestionCard
└── ForEach → Row (建议项)
附录E:护士端与患者端UI对比
| 设计维度 | 患者端 | 护士端 |
|---|---|---|
| 页面数量 | 3个(首页/监测/设置) | 3个(首页/列表/分析) |
| 核心页面 | MonitoringPage | NurseHomePage |
| 信息粒度 | 单患者精细展示 | 多患者聚合展示 |
| 预警展示 | 安慰性提示+操作建议 | 级别色标+处理按钮 |
| 液位展示 | 动画液体效果 | 颜色编码进度条 |
| 数据分析 | 无 | 完整统计+AI建议 |
| 操作频率 | 低 | 高 |
| 通知方式 | 声音+振动 | 声音+振动+手表+常亮 |
| 空状态 | 等待启动监测 | 所有患者正常 |
更多推荐




所有评论(0)