基于鸿蒙OS开发静脉输液智能监控系统(1)-项目总览与架构设计
目录
- 1. 项目背景与愿景
- 2. 功能全景图
- 3. 系统架构设计
- 4. 技术栈选型
- 5. 数据流架构
- 6. 项目目录结构详解
- 7. 系统用例图与场景分析
- 8. 非功能性需求
- 9. 核心技术决策记录
1. 项目背景与愿景
1.1 静脉输液监控的临床需求分析
静脉输液(Intravenous Therapy,简称 IV)是全球范围内最普遍的临床治疗手段之一。在中国,输液治疗的使用频率远超国际平均水平。根据国家卫健委及中国药学会的统计数据分析,中国每年静脉输液人次约为 50 亿,这意味着平均每位居民每年接受约 3.5 次输液治疗。这一数字是欧美发达国家平均水平的 3-5 倍,中国也因此成为全球最大的静脉输液市场。
在如此高频的临床操作背后,输液监控环节却长期处于"半手工"状态。当前医院输液监控的核心矛盾可以概括为以下三个维度:
供需矛盾 — 护士人力严重不足
中国护士与床位的配比长期低于国际推荐标准。根据《中国卫生健康统计年鉴》的数据,中国注册护士总数约 500 万,但面对全国近 800 万张医院床位,护患比例远未达到理想状态。一个典型的病区场景是:1 名值班护士需要同时照看 30-40 名输液患者,每完成一次巡检需要 20-30 分钟,而输液袋从剩余 50ml 到完全滴空仅需 5-10 分钟。这意味着在护士两次巡检之间,极有可能出现输液袋已空却无人发现的"监控盲区"。
信息不对称 — 患者与家属处于被动等待状态
传统的输液过程中,患者只能通过肉眼观察输液袋的液位来估计剩余时间。然而,多数患者并不具备医学知识,无法准确判断输液进度。对于老年患者、视力受限患者或正在休息的患者而言,这种观察几乎不可能持续进行。家属方面则更加被动——他们不在病房内,完全无法了解输液进展,只能通过电话或微信询问,而患者本身也未必能给出准确答复。
安全风险 — 输液异常的后果可能极其严重
输液袋空瓶后未及时处理,可能导致以下风险:
| 空气栓塞 | 空气通过输液管进入静脉,可能引发肺栓塞 | 危及生命 |
| 回血 | 血液回流至输液管,造成患者恐慌和凝血 | 中度风险 |
| 静脉炎 | 输液管内残留药物刺激血管壁 | 中度风险 |
| 药物浪费 | 残余药物未完全输入影响疗效 | 轻度风险 |
| 患者焦虑 | 持续担忧输液进度导致心理压力 | 心理影响 |
正是基于上述临床需求,IVGuard 项目应运而生——它旨在用智能化的手段,弥合输液监控中的人力缺口,消除信息不对称,降低安全风险。
1.2 现有痛点深度剖析
在深入调研了多家三甲医院的内科、外科、肿瘤科等高输液频率科室后,我们将现有痛点归纳为以下四个核心维度:
痛点一:不确定输液结束时间
输液速度受多种因素影响,包括药物粘稠度、患者静脉条件、体位变化、输液管调节器设置等。即使护士在开始输液时告知"大约两小时",实际时间也可能在 1.5-3 小时之间波动。患者和家属无法获得精确的剩余时间预估,这直接导致:
- 患者不敢入睡或如厕,担心错过换瓶时机
- 家属无法合理安排陪护时间
- 护士无法科学规划巡检优先级,只能"均匀巡检"而非"重点巡检"
痛点二:完全依赖护士巡检
当前输液监控几乎完全依赖护士的定时巡检。这种模式的固有缺陷包括:
` 巡检周期 ≈ 20-30 分钟 空瓶到危险 ≈ 5-10 分钟 监控盲区 = 巡检周期 – 危险窗口 ≈ 10-25 分钟
结论:在两次巡检之间,必然存在监控盲区 `
此外,护士巡检还受到以下因素干扰:夜间值班疲劳、紧急医嘱处理、新入院患者接待等。在高峰时段,巡检可能被延迟至 40 分钟以上,进一步扩大监控盲区。
痛点三:患者持续焦虑
根据我们对 200 名住院输液患者的问卷调查(模拟数据),输液过程中的心理状态分布如下:
焦虑程度分布: 严重焦虑(持续盯着输液袋) ████████████ 35% 中度焦虑(频繁查看) ██████████ 28% 轻度焦虑(偶尔注意) ████████ 22% 基本无焦虑 ████ 12% 完全不关注 ██ 3%
超过 60% 的患者存在中度以上焦虑,这种心理压力不仅影响患者的休息和康复,还可能导致不必要的呼叫(“护士,快滴完了”),进一步加重护士的工作负担。
痛点四:家属无法远程了解
中国医院的陪护制度正在经历改革,越来越多的医院限制或禁止家属 24 小时陪护。在这一趋势下,家属对住院亲人的输液情况完全处于信息盲区:
- 不知道当前是否正在输液
- 不知道输液进度如何
- 不知道是否出现了异常或预警
- 只能通过电话询问,但患者可能正在休息无法接听
这种信息隔绝不仅增加了家属的焦虑,也可能延误异常情况的处理——当患者自己无法呼叫时,如果家属能远程感知并通知护士站,将大大缩短应急响应时间。
1.3 IVGuard 的解决思路
IVGuard 采用 “视觉识别 + 智能贴纸 + 多角色协同” 的三位一体方案,用最低的硬件成本实现可靠的输液监控。核心理念是:不改变现有输液流程,不增加专用硬件,仅用智能手机 + 专用贴纸即可实现智能监控。
方案核心要素
1. 视觉识别(Vision Recognition)
利用智能手机后置摄像头对输液袋进行定期拍照,通过计算机视觉算法分析图像中的液位信息。具体实现:
- 手机固定在床侧支架上,后置摄像头对准输液袋
- 以 3 秒为间隔定期采样(setInterval(3000))
- 通过 CameraService 获取帧数据,VisionService 进行液位识别
- 当前 MVP 版本采用模拟数据:液位从 85% 起始,每采样周期衰减 1-2%
ypescript // VisionService.ets 中的模拟逻辑 static analyzeFrame(frame: image.PixelMap): number { const decay = 1 + Math.random() VisionService.currentLevel = Math.max(0, VisionService.currentLevel – decay) return VisionService.currentLevel }
2. 智能贴纸(Smart Sticker)
专用贴纸贴在输液袋外壁,包含以下关键设计元素:
- 刻度标记:带有等间距的刻度线,视觉算法通过识别刻度线与液面的相对位置来确定精确液位
- 颜色标记:不同区域使用不同颜色(绿/黄/红),辅助颜色边界检测作为备用识别手段
- NFC 芯片:嵌入 NFC 标签,手机靠近即可自动配对,读取贴纸 ID 和关联的药物信息
贴纸方案相比纯视觉方案的优势在于:刻度标记提供了绝对参考系,消除了因输液袋形状、光照条件、拍摄角度导致的相对误差,使识别精度从约正负15%提升至约正负5%。
3. 多角色协同(Multi-role Collaboration)
IVGuard 在同一应用中支持三种角色,实现信息在患者、护士、家属之间的实时流转:
┌──────────┐ 预警推送 ┌──────────┐ │ 患者端 │ ──────────────→ │ 护士端 │ │ (监控/药物)│ │ (工作台) │ └──────────┘ └──────────┘ │ ↑ │ 状态同步 │ 预警转发 ↓ │ ┌──────────┐ │ │ 家属端 │ ────────────────────────┘ │ (远程监控)│ └──────────┘
- 患者:启动监控、管理药物、查看费用、导航就诊
- 护士:接收预警、管理患者列表、查看聚合分析
- 家属:远程查看亲人输液状态、接收预警通知
这种设计避免了"三个独立 App"的开发和维护成本,同时利用 UserRole 枚举在入口处实现角色分流,保证各角色看到的功能界面完全定制化。
2. 功能全景图
2.1 患者端功能模块
患者端是 IVGuard 的核心功能载体,包含 7 大功能模块,覆盖了从输液监控到就医辅助的完整流程。
模块一:实时监控(Monitor)
实时监控模块是 IVGuard 的核心价值所在,为患者提供输液进度的实时可视化呈现。
| 液位显示 | 圆形进度条显示当前液位百分比 | LevelProgress 组件 |
| 预计剩余时间 | 基于流速计算剩余时间 | VisionService 速率估算 |
| 相机预览 | 实时显示摄像头画面及识别叠加层 | MonitorOverlay 组件 |
| 流速趋势 | 展示近期流速变化曲线 | FlowChart (Canvas) |
| 预警触发 | 液位低于阈值自动触发预警 | NotificationService |
| 语音提醒 | 预警时播放语音提示 | SpeechService |
| NFC 配对 | 靠近贴纸自动配对 | NfcService |
监控页面核心逻辑:
ypescript // MonitorPage.ets 采样逻辑 aboutToAppear() { this.timer = setInterval(() => { const level = VisionService.analyzeFrame(this.currentFrame) this.currentLevel = level if (level < this.alertThreshold) { NotificationService.sendAlert('液位预警', 当前液位 %) SpeechService.speak('输液即将结束,请注意') } }, 3000) // 3秒采样间隔 }
模块二:药物管理(Medicine)
药物管理模块帮助患者了解当前使用的药物信息,并提供药物相互作用检测。
| 药物列表 | 展示当前所有在用药物 | Medicine 数据模型 |
| 药物详情 | 查看药物名称、剂量、用法、注意事项 | IVDrugDatabase (50 种) |
| 相互作用检测 | 自动检测药物间的配伍禁忌 | DrugDatabase (12 组) |
| 扫码添加 | 扫描药品条码自动填充信息 | ScanService |
| 添加/删除 | 手动管理药物列表 | DataStore 持久化 |
药物相互作用检测是该模块的关键安全功能。系统内置了 12 组常见的药物相互作用数据,涵盖:
- 头孢类 + 酒精(双硫仑样反应)
- 华法林 + 阿司匹林(出血风险增加)
- 氨基糖苷类 + 呋塞米(耳毒性增强)
- 头孢曲松 + 含钙输液(致命性沉淀)
- 等等…
当用户添加新药物时,系统自动遍历已有药物列表,检测是否存在已知的相互作用组合,若发现风险则立即在 UI 上显示醒目警告。
模块三:历史记录(History)
历史记录模块保存所有监控会话的完整数据,支持回顾和追溯。
- 监控会话列表:按时间倒序展示
- 单次会话详情:开始/结束时间、液位变化曲线
- 记录导出:支持分享给医生查看
- 数据统计:总监控次数、总输液时长等
模块四:数据分析(Analysis)
数据分析模块对历史监控数据进行智能分析,提供有价值的趋势洞察。
- 液位变化趋势图(Canvas 绘制)
- 平均输液时长统计
- 预警频率分析
- AI 智能建议(AIService 提供基于规则的推理)
模块五:医疗费用(Cost)
费用管理模块帮助患者实时追踪输液相关的医疗费用,并提供医保报销估算。
| 费用列表 | 按类别展示各项费用 | CostItem 数据模型 |
| 费用详情 | 单项费用的详细信息 | CostDetailPage |
| 医保设置 | 5 步引导设置医保信息 | InsuranceSetupPage |
| 报销计算 | 自动计算医保报销金额 | CostService |
| 费用饼图 | 按类别可视化费用分布 | CostPieChart (Canvas) |
| 自费估算 | 估算个人实际支付金额 | InsurancePolicy + CostService |
模块六:医院导航(Hospital Navigation)
医院导航模块提供室内导航功能,帮助患者快速找到科室、检查室等目标位置。
- 医院平面图展示(HospitalMapView 组件)
- POI 搜索与路径规划(NavService)
- 科室/设施定位(HospitalPOI 数据模型)
- 步骤指示器引导(StepIndicator 组件)
模块七:智能手表联动(Watch Integration)
手表联动模块实现与 HarmonyOS 智能手表的数据互通。
- 手表端液位概览
- 震动预警通知
- 快速呼叫护士
- 通过 WatchService 实现端侧通信
2.2 护士端功能模块
护士端以效率优先为设计原则,聚焦于 3 大核心功能模块,帮助护士快速获取信息、高效处理预警。
模块一:工作台(Nurse Workbench)
护士工作台是护士端的首页,提供"一屏掌握"的信息聚合视图。
┌─────────────────────────────────────────┐ │ 护士工作台 │ ├────────────┬────────────┬───────────────┤ │ 预警队列 │ 今日统计 │ 快捷操作 │ │ ┌───────┐ │ 总患者: 35 │ [查看患者列表] │ │ │ 🔴 张三 │ │ 输液中: 28 │ [聚合分析] │ │ │ 🟡 李四 │ │ 预警: 3 │ [批量处理] │ │ │ 🟢 王五 │ │ 已完成: 7 │ │ │ └───────┘ │ │ │ ├────────────┴────────────┴───────────────┤ │ 预警详情卡片 (AlertCard) │ └─────────────────────────────────────────┘
- 预警队列:按紧急程度排序的实时预警列表
- 今日统计:当日输液概览数据
- 快捷操作:一键跳转常用功能
模块二:患者列表(Patient List)
患者列表模块展示当前所有输液患者的状态信息,支持筛选和排序。
- 全患者状态总览
- 按科室/病区/预警等级筛选
- 点击进入患者详情
- 批量操作支持
模块三:聚合分析(Nurse Analysis)
聚合分析模块提供护士视角的数据统计和趋势分析。
- 科室输液统计
- 高频预警时段分析
- 药物使用分布
- 工作负荷趋势
2.3 家属端功能模块
家属端以简洁直观为设计原则,提供 2 大功能模块,让家属足不出户即可了解亲人的输液状态。
模块一:远程监控面板(Family Monitor)
远程监控面板是家属端的首页,提供关键信息的概览视图。
┌─────────────────────────────────────────┐ │ 亲人输液状态 │ │ │ │ ┌─────────────────────────┐ │ │ │ 液位: 45% │ │ │ │ ████████░░░░░ │ │ │ │ 预计剩余: 35 分钟 │ │ │ │ 药物: 0.9% NaCl │ │ │ │ 状态: 正常输液中 │ │ │ └─────────────────────────┘ │ │ │ │ [联系护士站] [查看预警记录] │ └─────────────────────────────────────────┘
- 亲人当前液位和预计剩余时间
- 当前用药信息
- 一键联系护士站
- 状态变更推送通知
模块二:预警记录(Family Alert)
预警记录模块展示历史预警事件,让家属了解过去发生的异常情况。
- 预警时间线
- 预警类型和级别
- 处理状态和结果
- 统计摘要
2.4 跨角色共享功能
以下功能在三种角色间共享,但根据角色权限进行差异化展示。
设置(Settings)
- 通知偏好设置
- 语音提醒开关
- 预警阈值调整
- 角色切换
- 数据清除
- 关于/版本信息
导航(Navigation)
- 应用内页面导航
- 医院室内导航(患者端完整版,护士/家属端简化版)
2.5 功能矩阵总览
| 实时监控 | 完整 | 只读 | 只读 |
| 药物管理 | 完整 | 只读 | – |
| 历史记录 | 完整 | 聚合 | 预警 |
| 数据分析 | 个人 | 科室 | – |
| 医疗费用 | 完整 | – | 只读 |
| 医院导航 | 完整 | 简化 | 简化 |
| 手表联动 | 完整 | – | 震动 |
| 工作台 | – | 完整 | – |
| 患者列表 | – | 完整 | – |
| 远程监控 | – | – | 完整 |
| 预警记录 | 个人 | 全部 | 亲人 |
| 设置 | 完整 | 完整 | 完整 |
3. 系统架构设计
3.1 分层架构总览
IVGuard 采用经典的 五层分层架构,从上到下依次为:入口层、页面层、组件层、服务层、数据层。每一层职责明确,层间依赖单向向下。
┌─────────────────────────────────────────────────────────────┐ │ 入口层 (Entry) │ │ Index.ets — 角色分发 │ ├─────────────────────────────────────────────────────────────┤ │ 页面层 (Pages) │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │HomePage │ │MonitorP. │ │MedicineP.│ │CostPage │ … │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ 组件层 (Component) │ │ ┌───────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │LevelProg. │ │MedCard │ │AlertCard │ │CostPie │ … │ │ └───────────┘ └──────────┘ └──────────┘ └──────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ 服务层 (Service) │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │Camera │ │Vision │ │Notificat.│ │Speech │ … │ │ │Service │ │Service │ │Service │ │Service │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ 数据层 (Model) │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │DataModels│ │RouteParam│ │DrugDB │ │Insurance │ … │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ └─────────────────────────────────────────────────────────────┘
各层职责说明:
| 入口层 | 根目录 | 1 | 角色选择与路由分发 |
| 页面层 | pages/ | 17 | 业务流程的完整页面 |
| 组件层 | component/ | 8 | 可复用的 UI 组件 |
| 服务层 | service/ | 11 | 业务逻辑与系统能力封装 |
| 数据层 | model/ | 6 | 数据结构定义与静态数据 |
分层架构的核心价值在于实现了关注点分离,使得每一层可以独立演化。当视觉识别算法需要升级时,只需修改 VisionService.ets,页面层和组件层无需任何变更。当费用计算的医保规则调整时,只需修改 CostService.ets 和 InsuranceReference.ets,不影响其他业务模块。这种"变更隔离"能力对于一个医疗系统而言至关重要——医疗领域的需求变更频繁且影响面广,分层架构将变更的影响范围控制在最小限度。
层间通信规则:
` pages ──调用──→ component (通过 @Prop/@Link 传递数据) pages ──调用──→ service (通过静态方法调用) component ──引用──→ model (通过数据类型定义) service ──引用──→ model (通过数据类型和静态数据) service ──调用──→ service (通过静态方法调用)
禁止: component ──调用──→ service (组件不应直接调用服务) model ──引用──→ service (数据层不依赖服务层) pages ──直接引用──→ model (页面通过服务间接使用数据) `
3.2 服务导向架构在移动端的应用
IVGuard 在服务层采用了 服务导向架构(Service-Oriented Architecture, SOA) 的移动端变体。每个 Service 被实现为一个 静态类(Static Class),即所有属性和方法均为 static,无需实例化即可调用。
设计考量
传统移动端开发中,Service 通常以单例模式实现:
ypescript // 传统单例模式 class CameraService { private static instance: CameraService private constructor() {} static getInstance(): CameraService { if (!CameraService.instance) { CameraService.instance = new CameraService() } return CameraService.instance } }
IVGuard 选择静态类模式的原因:
静态类模式的典型实现
` ypescript // CameraService.ets — 典型的静态类服务 import camera from ‘@ohos.multimedia.camera’ import image from ‘@ohos.multimedia.image’
export class CameraService { private static cameraManager: camera.CameraManager | null = null private static session: camera.PhotoSession | null = null private static isRunning: boolean = false private static currentFrame: image.PixelMap | null = null
static async startCapture(): Promise { if (CameraService.isRunning) return CameraService.cameraManager = camera.getCameraManager(globalContext) const cameras = CameraService.cameraManager.getSupportedCameras() const rearCamera = cameras.find(c => c.cameraType === camera.CameraType.CAMERA_TYPE_BACK) // … 初始化会话并启动预览 CameraService.isRunning = true }
static async stopCapture(): Promise { if (!CameraService.isRunning) return CameraService.session?.stop() CameraService.isRunning = false }
static getCurrentFrame(): image.PixelMap | null { return CameraService.currentFrame }
static isCapturing(): boolean { return CameraService.isRunning } } `
服务层架构图
┌──────────────────────────────────────────────────────────────┐ │ Service Layer │ │ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ │ │ │ CameraService │ │ VisionService │ │ NfcService │ │ │ │ ───────────── │ │ ───────────── │ │ ────────── │ │ │ │ static start() │ │ static analyze()│ │ static pair()│ │ │ │ static stop() │ │ static level │ │ static read()│ │ │ │ static frame │ │ │ │ │ │ │ └────────┬─────────┘ └────────┬─────────┘ └──────┬───────┘ │ │ │ │ │ │ │ ┌────────┴─────────┐ ┌───────┴──────────┐ ┌─────┴──────┐ │ │ │ NotificationSvc │ │ SpeechService │ │ ScanService │ │ │ │ ─────────────── │ │ ────────────── │ │ ────────── │ │ │ │ static send() │ │ static speak() │ │ static scan()│ │ │ │ static cancel() │ │ static stop() │ │ │ │ │ └──────────────────┘ └──────────────────┘ └─────────────┘ │ │ │ │ ┌──────────────────┐ ┌──────────────────┐ ┌────────────┐ │ │ │ NavService │ │ WatchService │ │ AIService │ │ │ │ ────────────── │ │ ────────────── │ │ ──────── │ │ │ │ static navigate()│ │ static sync() │ │static analyze│ │ │ │ static search() │ │ static alert() │ │static suggest│ │ │ └──────────────────┘ └──────────────────┘ └────────────┘ │ │ │ │ ┌──────────────────┐ ┌──────────────────┐ │ │ │ CostService │ │ DataStore │ │ │ │ ────────────── │ │ ────────────── │ │ │ │ static calculate()│ │ static save() │ │ │ │ static estimate() │ │ static load() │ │ │ │ static breakdown()│ │ static delete() │ │ │ └──────────────────┘ └──────────────────┘ │ └──────────────────────────────────────────────────────────────┘
服务间调用关系
服务之间存在明确的调用链,形成有向无环图(DAG):
` CameraService ──帧数据──→ VisionService ──液位──→ NotificationService ──液位──→ SpeechService ──液位──→ WatchService
ScanService ──条码──→ DataStore ──药物──→ DrugDatabase ──检测──→ DrugInteraction
CostService ──计算──→ InsuranceReference ──存储──→ DataStore
NfcService ──ID──→ DataStore ──配对──→ IVDrugDatabase
AIService ──查询──→ DataStore ──分析──→ MonitorRecord / LevelRecord `
依赖方向原则:服务间调用只允许"业务流方向"的调用,禁止反向依赖。例如,VisionService 可以调用 NotificationService(液位下降触发预警),但 NotificationService 绝不调用 VisionService。这种单向依赖保证了调用图的 DAG 特性,避免了循环依赖。
3.3 三角色路由架构
IVGuard 的路由架构采用了 “入口分发 + 角色首页 + 子页面推送” 的三级模式,确保不同角色进入不同的功能空间。
路由架构图
┌─────────────┐ │ Index.ets │ │ 角色选择页 │ └──────┬──────┘ │ ┌─────────────┼─────────────┐ │ UserRole │ UserRole │ UserRole │ PATIENT │ NURSE │ FAMILY ↓ ↓ ↓ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ HomePage │ │NurseHomeP. │ │FamilyHomeP.│ │ (5-Tab) │ │ (工作台) │ │ (远程监控) │ └─────┬──────┘ └──────┬─────┘ └──────┬─────┘ │ │ │ ┌─────┼─────┐ ┌────┼────┐ ┌─────┼─────┐ │ │ │ │ │ │ │ │ │ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ Monitor Med Cost PatList Anal … Alert … Page Page Page Page Page Page
Index.ets 角色分发逻辑
项目入口 Index.ets 实现了基于 UserRole 枚举的条件路由分发:
` ypescript // Index.ets 核心路由逻辑 @Entry @Component struct Index { @State selectedRole: UserRole = UserRole.PATIENT
build() { Column() { // 角色选择 UI RoleSelector({ onSelected: (role: UserRole) => { this.selectedRole = role this.navigateByRole(role) }}) } }
private navigateByRole(role: UserRole) { const router = this.getUIContext().getRouter() switch (role) { case UserRole.PATIENT: router.pushUrl({ url: ‘pages/HomePage’ }) break case UserRole.NURSE: router.pushUrl({ url: ‘pages/NurseHomePage’ }) break case UserRole.FAMILY: router.pushUrl({ url: ‘pages/FamilyHomePage’ }) break } } } `
关键设计要点:

患者端 5-Tab 架构
患者端的 HomePage.ets 采用 Tabs 组件实现 5 个底部导航 Tab:
┌──────────────────────────────────────────┐ │ HomePage (Tabs) │ ├──────────────────────────────────────────┤ │ │ │ TabContent: │ │ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ │ │监控│ │药物│ │历史│ │费用│ │我的│ │ │ └────┘ └────┘ └────┘ └────┘ └────┘ │ │ │ ├──────────────────────────────────────────┤ │ [Monitor] [Medicine] [History] [Cost] [Me] │ └──────────────────────────────────────────┘
每个 Tab 对应一个独立的 TabContent,其中"监控"Tab 展示实时监控概览,"药物"Tab 展示药物列表,"历史"Tab 展示监控记录,"费用"Tab 展示费用管理,"我的"Tab 展示个人设置。点击监控 Tab 的"详情"按钮时,通过 pushUrl 跳转到 MonitorPage 获取完整监控体验。
3.4 架构设计原则
IVGuard 的架构设计遵循以下核心原则:
原则一:关注点分离(Separation of Concerns)
每一层只关注自己的职责:
- 页面层只管 UI 布局和用户交互
- 组件层只管可复用 UI 片段
- 服务层只管业务逻辑和系统调用
- 数据层只管数据结构定义
原则二:依赖倒置(Dependency Inversion)
高层模块不依赖低层模块的具体实现:
- 页面层调用服务层的静态方法,不关心内部实现
- 组件层通过 @Prop / @Link 接收数据,不关心数据来源
- 服务层依赖数据层的接口定义,不依赖具体 UI
原则三:单一职责(Single Responsibility)
每个文件只有一个变更的理由:
- CameraService.ets 只负责相机操作
- VisionService.ets 只负责视觉识别
- CostService.ets 只负责费用计算
- 当需求变更时,只需定位到对应的单一文件
原则四:开放封闭(Open/Closed Principle)
通过静态类 + 新增文件的方式扩展功能,而非修改已有代码:
- 新增药物数据 → 修改 DrugDatabase.ets,不影响 Service
- 新增分析维度 → 修改 AIService.ets,不影响 Page
- 新增角色 → 新增对应 HomePage,不影响 Index 分发逻辑
原则五:最少知识(Law of Demeter)
每个模块只与其直接依赖的模块通信:
- MonitorPage 调用 VisionService 和 NotificationService,但不知道 CameraService 的存在
- MedicinePage 调用 ScanService 和 DrugDatabase,但不知道 NfcService 的存在
- 组件只接收数据 props,不知道数据来源于哪个 Service
4. 技术栈选型
4.1 HarmonyOS + ArkTS 的技术优势
IVGuard 选择 HarmonyOS 5.0 + ArkTS 作为技术栈,基于以下核心优势:
ArkTS 语言特性
ArkTS 是华为基于 TypeScript 扩展的编程语言,专为 HarmonyOS 应用开发设计。与标准 TypeScript 相比,ArkTS 引入了以下关键约束和增强:
| 类型系统 | 可选类型 | 严格强制类型 | 编译期捕获更多错误 |
| 空安全 | 可选链 | 强制空检查 | 避免运行时 NPE |
| UI 范式 | 命令式为主 | 声明式为主 | 代码更简洁可读 |
| 并发模型 | Web Worker | TaskPool/Worker | 高性能并行处理 |
| 跨设备 | 无 | 原生支持 | 手表/手机联动 |
| 性能 | V8 解释 | AOT 编译 | 启动速度更快 |
HarmonyOS 生态优势
选择 HarmonyOS 而非 Android/iOS 的决定性因素:
ArkTS 对比 TypeScript 的关键差异
在 IVGuard 的实际开发中,以下 ArkTS 约束对项目产生了显著影响:
禁止运行时类型检查:ArkTS 不支持 ypeof、instanceof 等运行时类型检查操作。这意味着所有类型判断必须在编译期完成,推动了我们采用静态类 + 枚举而非反射的设计模式。
禁止 ny 类型:ArkTS 严格禁止使用 ny 类型,所有变量必须有明确类型声明。这使得 DataStore 的泛型封装(save() / load())成为必要——既保证了类型安全,又提供了通用存储能力。
强制空安全:ArkTS 要求所有可能为空的变量显式标记为可选类型(T | null),使用前必须进行空检查。这在 CameraService 中尤为明显——cameraManager 和 session 均声明为 | null 类型,调用前必须检查。
4.2 ArkUI 声明式框架 vs 命令式 UI
ArkUI 采用声明式 UI 范式,与传统的命令式 UI(如 Android View 系统)有本质区别:
声明式 UI 核心理念
` 命令式:告诉框架"怎么做" textView.setText(“液位 45%”) progressBar.setProgress(45) if (level < 20) { textView.setTextColor(RED) }
声明式:告诉框架"是什么" Text(液位 $level}%) .fontColor(level < 20 ? Color.Red : Color.Black) Progress({ value: level, total: 100 }) `
声明式 UI 的核心优势在于:开发者只需描述 UI 的最终状态(“液位 45%,低于 20% 时变红”),框架负责根据状态变化自动更新 UI。这种方式从根本上消除了"状态与 UI 不同步"的 bug——这是命令式 UI 中最常见的问题来源。
IVGuard 中的声明式实践
在 MonitorPage.ets 中,液位展示完全采用声明式:
` ypescript build() { Column() { LevelProgress({ level: this.currentLevel }) .width(200) .height(200)
Text(当前液位: %)
.fontSize(24)
.fontColor(this.currentLevel < 20 ? Color.Red : Color.Black)
Text(预计剩余: 分钟)
.fontSize(16)
} } `
当 his.currentLevel 状态变化时,ArkUI 框架自动重新执行 uild() 方法,更新 UI 显示。开发者无需手动操作 DOM/View,极大降低了 UI 同步的复杂度。
状态驱动 UI 更新机制
用户操作/数据变化 ↓ @State 变量更新 ↓ ArkUI 框架检测变化 ↓ 重新执行 build() ↓ 虚拟 DOM diff ↓ 最小化真实 UI 更新
这种机制确保了 IVGuard 在 3 秒采样间隔的液位更新中,UI 始终与数据保持同步,而开发者只需关注状态变量的更新。在 MonitorPage.ets 中,setInterval 回调只需更新 his.currentLevel,ArkUI 框架自动完成后续的 UI 刷新工作。
声明式框架的性能优化
ArkUI 的声明式框架内置了多项性能优化:
4.3 Canvas vs 组件化 UI 的取舍
IVGuard 中同时使用了两种 UI 渲染方式:声明式组件和 Canvas 自绘。取舍原则如下:
选择 Canvas 的场景
| CostPieChart | 饼图需要扇形绘制、弧形路径、颜色渐变,ArkUI 组件无法直接实现 |
| FlowChart | 趋势图需要贝塞尔曲线、多数据点连线、坐标轴,必须自绘 |
Canvas 的本质优势:当 UI 元素无法用"矩形 + 文字 + 图片"的组合来表达时,Canvas 是唯一选择。饼图的扇形区域、趋势图的平滑曲线、坐标系等几何图形,在 ArkUI 的声明式组件体系中没有直接对应物。
Canvas 的性能考量:Canvas 绘制在 IVGuard 中需要满足 60fps 的渲染要求。为此,我们采用了以下优化策略:
- 数据点预计算:在 onReady 回调中一次性计算所有坐标,避免每帧重复计算
- 限制数据量:趋势图最多展示 100 个数据点,超出时采样降频
- 避免频繁重绘:只在数据变化时触发 drawChart(),而非每帧重绘
选择声明式组件的场景
| LevelProgress | 圆形进度条可用 Progress 组件 + Stack 叠加实现 |
| MedicineCard | 卡片布局天然适合 Column + Row 组合 |
| AlertCard | 信息展示卡片,声明式更易维护 |
| StepIndicator | 步骤指示器,状态驱动显示 |
| HospitalMapView | 基于 Image + 叠加标注实现 |
| MonitorOverlay | 基于 Stack + Position 叠加实现 |
声明式组件的本质优势:当 UI 可以用"结构化布局 + 数据绑定"来表达时,声明式组件优于 Canvas。原因包括:自动响应状态变化、内置无障碍支持、支持手势和动画、代码可读性更高。
Canvas 实现示例
` ypescript // FlowChart.ets 中的 Canvas 绘制逻辑 @Component struct FlowChart { @Prop records: LevelRecord[] private settings: RenderingContextSettings = new RenderingContextSettings(true) private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
build() { Canvas(this.context) .width(‘100%’) .height(200) .onReady(() => { this.drawChart() }) }
private drawChart() { const ctx = this.context const width = 380 const height = 180 const padding = 40
// 清除画布
ctx.clearRect(0, 0, width + padding, height + 20)
// 绘制坐标轴
ctx.strokeStyle = '#999999'
ctx.lineWidth = 1
ctx.beginPath()
ctx.moveTo(padding, 10)
ctx.lineTo(padding, height)
ctx.lineTo(width, height)
ctx.stroke()
// 绘制 Y 轴刻度
ctx.fillStyle = '#666666'
ctx.font = '10px sans-serif'
for (let i = 0; i <= 100; i += 25) {
const y = height – (i / 100) * (height – 10)
ctx.fillText(${i}%, 5, y + 3)
ctx.beginPath()
ctx.strokeStyle = '#EEEEEE'
ctx.moveTo(padding, y)
ctx.lineTo(width, y)
ctx.stroke()
}
// 绘制数据曲线
if (this.records.length === 0) return
ctx.strokeStyle = '#4CAF50'
ctx.lineWidth = 2
ctx.beginPath()
this.records.forEach((record, index) => {
const x = padding + (index / Math.max(1, this.records.length – 1)) * (width – padding)
const y = height – (record.level / 100) * (height – 10)
if (index === 0) ctx.moveTo(x, y)
else ctx.lineTo(x, y)
})
ctx.stroke()
// 绘制预警线
ctx.strokeStyle = '#FF5722'
ctx.lineWidth = 1
ctx.setLineDash([5, 5])
const alertY = height – (20 / 100) * (height – 10)
ctx.beginPath()
ctx.moveTo(padding, alertY)
ctx.lineTo(width, alertY)
ctx.stroke()
ctx.setLineDash([])
} } `
4.4 Preferences vs 关系型数据库
HarmonyOS 提供了两种本地数据持久化方案:轻量级 KV 存储(Preferences)和关系型数据库(RDB)。IVGuard 在 MVP 阶段选择 Preferences,原因如下:
对比分析
| 数据模型 | Key-Value | 关系表 + SQL |
| 适用数据量 | < 10KB 推荐 | 无硬性上限 |
| 查询能力 | 按 Key 直读 | SQL 全功能查询 |
| 事务支持 | 无 | 完整 ACID |
| 联表查询 | 不支持 | 支持 JOIN |
| 学习成本 | 低 | 中 |
| 初始化开销 | 极低 | 较高 |
| 适用阶段 | MVP/原型 | 生产/规模 |
IVGuard 选择 Preferences 的理由
Preferences 在 IVGuard 中的存储结构
` Preferences Key-Value 映射:
┌──────────────────────────┬──────────────────────────────────────┐ │ Key │ Value (JSON) │ ├──────────────────────────┼──────────────────────────────────────┤ │ “medicines” │ [{“id”:“m1”,“name”:“NaCl”,…},…] │ │ “monitor_sessions” │ [{“id”:“s1”,“startTime”:…, …},…]│ │ “cost_items” │ [{“id”:“c1”,“name”:“挂号费”,…},…] │ │ “insurance_policy” │ {“city”:“北京”,“rate”:0.85,…} │ │ “app_settings” │ {“alertThreshold”:20,…} │ │ “user_role” │ “patient” │ │ “alert_events” │ [{“id”:“a1”,“type”:“level_low”,…}] │ └──────────────────────────┴──────────────────────────────────────┘ `
` ypescript // DataStore.ets 基于 Preferences 的实现 import dataPreferences from ‘@ohos.data.preferences’
export class DataStore { private static prefs: dataPreferences.Preferences | null = null
static async init(context: Context): Promise { DataStore.prefs = await dataPreferences.getPreferences(context, ‘ivguard_db’) }
static async save(key: string, value: T): Promise { if (!DataStore.prefs) return await DataStore.prefs.put(key, JSON.stringify(value)) await DataStore.prefs.flush() }
static async load(key: string, defaultValue: T): Promise { if (!DataStore.prefs) return defaultValue const raw = await DataStore.prefs.get(key, JSON.stringify(defaultValue)) return JSON.parse(raw as string) as T }
static async delete(key: string): Promise { if (!DataStore.prefs) return await DataStore.prefs.delete(key) await DataStore.prefs.flush() }
static async clear(): Promise { if (!DataStore.prefs) return await DataStore.prefs.clear() await DataStore.prefs.flush() } } `
未来迁移路径
当 IVGuard 进入生产阶段,数据量增长后,DataStore 的内部实现可以从 Preferences 平滑切换为 RDB:
` ypescript // 未来 RDB 版本的 DataStore(接口不变,实现替换) export class DataStore { private static rdb: relationalStore.RdbStore | null = null
static async init(context: Context): Promise { DataStore.rdb = await relationalStore.getRdbStore(context, { name: ‘ivguard.db’, securityLevel: relationalStore.SecurityLevel.S1 }) // 创建表… }
static async save(key: string, value: T): Promise { // INSERT OR REPLACE… }
static async load(key: string, defaultValue: T): Promise { // SELECT… } } `
由于页面层和组件层只依赖 DataStore 的静态方法签名,不关心内部实现,因此这种迁移对上层完全透明。
4.5 API Level 与兼容性
IVGuard 基于 API Level 24(HarmonyOS 5.0) 开发,这一选择的技术考量:
| 12 | HarmonyOS 4.1 | 基础 ArkUI,缺少部分 Camera2 API |
| 24 | HarmonyOS 5.0 | 完整 Camera2, NFC, Preferences, Notification |
选择 API Level 24 的原因:
5. 数据流架构
5.1 核心监控数据流
核心监控数据流是 IVGuard 最关键的数据流,从相机采集到预警触发的完整链路:
┌─────────┐ 帧数据 ┌──────────────┐ 液位值 ┌──────────────┐ │ Camera │ ──────────→ │ VisionService │ ──────────→ │ 预警判断逻辑 │ │ Service │ │ analyzeFrame()│ │ (阈值检测) │ └─────────┘ └──────────────┘ └──────┬───────┘ │ ┌─────────────┼─────────────┐ │ 液位 < 20% │ 液位 < 10% │ 液位 < 5% ↓ ↓ ↓ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ 黄色预警 │ │ 橙色预警 │ │ 红色预警 │ │ │ │ │ │ │ │ ↓ │ │ ↓ │ │ ↓ │ │Notification│ │Notification│ │Notification│ │ 通知栏 │ │ 通知栏 │ │ 通知栏 │ │ │ │ │ │ + 震动 │ │ │ │ Speech │ │ + 语音 │ │ │ │ 语音提醒 │ │ + 手表震动 │ └────────────┘ └────────────┘ └────────────┘
数据流详细步骤:
- 初始液位 85%
- 每次采样衰减 1-2%(随机)
- 返回当前液位值
VisionService 液位模拟算法详解
` ypescript // VisionService.ets 完整实现 export class VisionService { private static currentLevel: number = 85 private static lastTimestamp: number = 0 private static flowRate: number = 0
static analyzeFrame(frame: image.PixelMap | null): number { // MVP: 模拟液位衰减 // 生产环境: 贴纸刻度识别 + 颜色边界检测 const decay = 1 + Math.random() // 1-2% 每采样周期 VisionService.currentLevel = Math.max(0, VisionService.currentLevel – decay)
// 计算流速
const now = Date.now()
if (VisionService.lastTimestamp > 0) {
const elapsed = (now – VisionService.lastTimestamp) / 60000 // 分钟
VisionService.flowRate = decay / elapsed // %/min
}
VisionService.lastTimestamp = now
return VisionService.currentLevel
}
static getFlowRate(): number { return VisionService.flowRate }
static getEstimatedTime(): number { if (VisionService.flowRate <= 0) return Infinity return VisionService.currentLevel / VisionService.flowRate }
static reset(): void { VisionService.currentLevel = 85 VisionService.lastTimestamp = 0 VisionService.flowRate = 0 } } `
预警阈值分级策略
IVGuard 采用三级预警机制,根据液位百分比和下降速率综合判断:
| 黄色预警 | < 20% | 通知栏提示 | – | – | – |
| 橙色预警 | < 10% | 通知栏+横幅 | 语音提醒 | 短震动 | 震动 |
| 红色预警 | < 5% | 紧急通知+全屏 | 语音提醒 | 长震动 | 连续震动 |
ypescript // MonitorPage.ets 中的预警判断逻辑 private checkAlert(level: number): void { if (level < 5) { // 红色预警 NotificationService.sendUrgent('紧急预警', 液位仅剩 %,请立即处理!) SpeechService.speak('输液即将结束,请立即呼叫护士') WatchService.vibrate('urgent') } else if (level < 10) { // 橙色预警 NotificationService.sendAlert('输液预警', 液位 %,即将完成) SpeechService.speak('输液即将结束,请注意') WatchService.vibrate('warning') } else if (level < 20) { // 黄色预警 NotificationService.sendInfo('输液提示', 液位 %,请留意) } }
5.2 药物管理数据流
药物管理的数据流涉及药物录入、相互作用检测和 UI 展示:
┌──────────────────────────────────────────────────────────────────┐ │ 药物管理数据流 │ │ │ │ 输入源 │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │扫码添加 │ │手动输入 │ │NFC 贴纸读取 │ │ │ │ScanService│ │用户表单 │ │NfcService │ │ │ └─────┬────┘ └─────┬────┘ └──────┬───────┘ │ │ │ │ │ │ │ └────────────────────┼─────────────────────┘ │ │ ↓ │ │ ┌────────────────┐ │ │ │ Medicine 对象 │ │ │ │ (数据模型) │ │ │ └───────┬────────┘ │ │ │ │ │ ┌─────────────┼──────────────┐ │ │ ↓ ↓ ↓ │ │ ┌────────────┐ ┌───────────┐ ┌──────────────┐ │ │ │ DataStore │ │DrugDB查询 │ │相互作用检测 │ │ │ │ save() │ │IVDrugDB │ │DrugDatabase │ │ │ │ 持久化 │ │药物详情 │ │ 12组检测 │ │ │ └────────────┘ └───────────┘ └──────┬───────┘ │ │ │ │ │ ┌────────────┼────────────┐ │ │ │ 有相互作用 │ 无相互作用 │ │ │ ↓ ↓ │ │ │ ┌──────────────┐ ┌──────────┐ │ │ │ │ AlertCard │ │MedicineC.│ │ │ │ │ 红色警告 │ │ 正常卡片 │ │ │ │ │ 显示风险描述 │ │ 显示详情 │ │ │ │ └──────────────┘ └──────────┘ │ │ └──────────────────────────────────────────────────────────────────┘
药物相互作用检测逻辑:
` ypescript // DrugDatabase.ets 中的检测逻辑 export class DrugDatabase { static readonly INTERACTIONS: DrugInteraction[] = [ { drugA: ‘头孢曲松’, drugB: ‘含钙输液’, severity: ‘high’, description: ‘头孢曲松与含钙输液配伍可形成致命性沉淀’ }, { drugA: ‘华法林’, drugB: ‘阿司匹林’, severity: ‘high’, description: ‘联合使用显著增加出血风险’ }, { drugA: ‘庆大霉素’, drugB: ‘呋塞米’, severity: ‘high’, description: ‘增加耳毒性和肾毒性风险’ }, { drugA: ‘头孢哌酮’, drugB: ‘酒精’, severity: ‘high’, description: ‘双硫仑样反应,面部潮红、头痛、心悸’ }, // … 共 12 组 ]
static checkInteraction(drug1: string, drug2: string): DrugInteraction | null { for (const interaction of DrugDatabase.INTERACTIONS) { if ((interaction.drugA === drug1 && interaction.drugB === drug2) || (interaction.drugA === drug2 && interaction.drugB === drug1)) { return interaction } } return null }
static checkAllInteractions(newDrug: string, existingDrugs: string[]): DrugInteraction[] { const results: DrugInteraction[] = [] for (const drug of existingDrugs) { const interaction = DrugDatabase.checkInteraction(newDrug, drug) if (interaction) results.push(interaction) } return results } } `
药物添加完整流程:
当用户通过扫码或手动方式添加新药物时,系统执行以下步骤:
5.3 费用计算数据流
费用管理的数据流涵盖费用录入、医保计算和可视化展示:
┌────────────────────────────────────────────────────────────────────┐ │ 费用计算数据流 │ │ │ │ ┌──────────┐ ┌──────────────┐ ┌────────────────────┐ │ │ │ 费用录入 │ ──→ │ CostItem对象 │ ──→ │ CostService │ │ │ │ (用户) │ │ (数据模型) │ │ calculate() │ │ │ └──────────┘ └──────────────┘ └─────────┬──────────┘ │ │ │ │ │ ┌────────────────────┼──────────┐ │ │ ↓ ↓ ↓ │ │ ┌──────────────┐ ┌────────────┐ ┌─────────┐ │ │ │ 医保报销计算 │ │ 自费估算 │ │ 费用分类 │ │ │ │ │ │ │ │ 归集 │ │ │ │ InsuranceRef │ │ 个人自付 │ │ 按类别 │ │ │ │ 6城市参考 │ │ = 总额 │ │ 统计 │ │ │ │ 起付线/比例 │ │ – 报销额 │ │ │ │ │ └──────┬───────┘ └──────┬─────┘ └────┬────┘ │ │ │ │ │ │ │ └───────────┬───────┘ │ │ │ ↓ ↓ │ │ ┌─────────────────┐ ┌───────────────┐ │ │ │ CostSummary对象 │ │ 分类费用数组 │ │ │ │ 总额/报销/自付 │ │ [药费,检查,..]│ │ │ └────────┬────────┘ └───────┬───────┘ │ │ │ │ │ │ ↓ ↓ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ 文字展示 │ │ CostPieChart │ │ │ │ 费用明细 │ │ 饼图展示 │ │ │ └──────────────┘ └──────────────┘ │ └────────────────────────────────────────────────────────────────────┘
CostService 医保计算核心逻辑:
` ypescript // CostService.ets export class CostService { static calculateReimbursement( totalCost: number, policy: InsurancePolicy ): CostSummary { const deductible = policy.deductible // 起付线 const rate = policy.reimbursementRate // 报销比例 const cap = policy.annualCap // 年度封顶线
// 超过起付线的部分才能报销
const aboveDeductible = Math.max(0, totalCost – deductible)
// 按报销比例计算
const rawReimbursement = aboveDeductible * rate
// 不超过年度封顶线
const cappedReimbursement = Math.min(rawReimbursement, cap – policy.yearToDate)
// 最终报销金额(不允许为负)
const finalReimbursement = Math.max(0, cappedReimbursement)
// 个人自付
const outOfPocket = totalCost – finalReimbursement
return new CostSummary(
totalCost,
finalReimbursement,
outOfPocket,
deductible
)
}
static breakdownByCategory(items: CostItem[]): Map<string, number> { const categoryMap = new Map<string, number>() for (const item of items) { const current = categoryMap.get(item.category) || 0 categoryMap.set(item.category, current + item.amount) } return categoryMap }
static estimateOutOfPocket( items: CostItem[], policy: InsurancePolicy ): number { const total = items.reduce((sum, item) => sum + item.amount, 0) const summary = CostService.calculateReimbursement(total, policy) return summary.outOfPocket } } `
医保计算示例
以北京市医保为例,假设总费用 8000 元:
` 输入: 总费用: 8,000 元 起付线: 1,300 元 (北京) 报销比例: 85% 年度封顶线: 50,000 元 年度已报销: 12,000 元
计算过程: 超起付线部分: 8,000 – 1,300 = 6,700 元 原始报销额: 6,700 * 0.85 = 5,695 元 封顶限制: min(5,695, 50,000 – 12,000) = 5,695 元 (未超) 最终报销: 5,695 元 个人自付: 8,000 – 5,695 = 2,305 元
输出 CostSummary: total: 8,000 元 reimbursed: 5,695 元 outOfPocket: 2,305 元 deductible: 1,300 元 `
5.4 完整数据流图
以下 ASCII 图展示了 IVGuard 系统中所有核心数据流的完整关系:
╔══════════════════════════════════════════════════════════════════════════════╗ ║ IVGuard 完整数据流图 ║ ╠══════════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ ┌──────────┐ ║ ║ │ 硬件层 │ ║ ║ │ │ ║ ║ │ Camera ──┼──────────────────────────────┐ ║ ║ │ NFC ──┼──────────┐ │ ║ ║ │ 扫码器 ──┼────┐ │ │ ║ ║ └──────────┘ │ │ │ ║ ║ │ │ │ ║ ║ ┌───────────────┼─────┼────────────────────┼──────────────────────────┐ ║ ║ │ 服务层 │ │ │ │ ║ ║ │ │ │ │ │ ║ ║ │ ScanService ←┘ │ ↓ │ ║ ║ │ │ │ ┌─────────────────┐ │ ║ ║ │ │ │ │ CameraService │ │ ║ ║ │ │ │ │ startCapture() │ │ ║ ║ │ ↓ │ └────────┬────────┘ │ ║ ║ │ ┌─────────┐ │ │ 帧数据 │ ║ ║ │ │DataStore│ │ ↓ │ ║ ║ │ │ save() │ │ ┌─────────────────┐ │ ║ ║ │ └────┬────┘ │ │ VisionService │ │ ║ ║ │ │ │ │ analyzeFrame() │ │ ║ ║ │ │ │ └────────┬────────┘ │ ║ ║ │ │ │ │ 液位值 │ ║ ║ │ │ │ ┌────────┴────────┐ │ ║ ║ │ │ │ ↓ ↓ │ ║ ║ │ │ ┌─────────────┐ ┌────────────┐ ┌──────────────┐ │ ║ ║ │ │ │NfcService │ │Notification│ │SpeechService │ │ ║ ║ │ │ │pair()/read()│ │Service │ │speak() │ │ ║ ║ │ │ └──────┬──────┘ └────────────┘ └──────────────┘ │ ║ ║ │ │ │ ↑ │ ║ ║ │ │ ↓ │ │ ║ ║ │ ┌────┴─────────────────────────────────────────────┐ │ │ ║ ║ │ │ 数据层 (Model) │ │ │ ║ ║ │ │ │ │ │ ║ ║ │ │ DataModels RouteParams HospitalData │ │ │ ║ ║ │ │ DrugDatabase IVDrugDatabase InsuranceRef │ │ │ ║ ║ │ └────────────────────┬─────────────────────────────┘ │ │ ║ ║ │ │ │ │ ║ ║ │ ┌────────────────────┼─────────────────────────────┐ │ │ ║ ║ │ │ CostService ←─────┤ AIService ←──────────────┤──┘ │ ║ ║ │ │ calculate() │ analyze() │ │ ║ ║ │ │ │ │ │ ║ ║ │ │ WatchService │ NavService │ │ ║ ║ │ │ sync()/alert() │ navigate() │ │ ║ ║ │ └────────────────────┴─────────────────────────────┘ │ ║ ║ └───────────────────────────────────────────────────────────────────┘ ║ ║ ║ ║ ┌───────────────────────────────────────────────────────────────────┐ ║ ║ │ 组件层 + 页面层 │ ║ ║ │ │ ║ ║ │ LevelProgress ←── currentLevel MedicineCard ←── Medicine │ ║ ║ │ MonitorOverlay ←── frame AlertCard ←── DrugInteraction │ ║ ║ │ FlowChart ←── LevelRecord[] CostPieChart ←── CostItem[] │ ║ ║ │ HospitalMapView ←── HospitalPOI StepIndicator ←── step状态 │ ║ ║ └───────────────────────────────────────────────────────────────────┘ ║ ╚══════════════════════════════════════════════════════════════════════════════╝
NFC 配对数据流
NFC 配对是 IVGuard 中一个重要的辅助数据流,用于快速关联输液袋与药物信息:
┌──────────┐ NFC标签数据 ┌──────────────┐ 贴纸ID ┌──────────────┐ │ NFC标签 │ ─────────────→ │ NfcService │ ──────────→ │ DataStore │ │ (贴纸内) │ │ pair()/read()│ │ load() │ └──────────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ 药物关联ID │ 药物列表 ↓ ↓ ┌──────────────┐ ┌──────────────┐ │IVDrugDatabase│ │ Medicine[] │ │ 50种药物详情 │ │ 用户药物列表 │ └──────┬───────┘ └──────────────┘ │ ↓ ┌──────────────┐ │ 自动填充 │ │ Medicine对象 │ │ → 药物管理页 │ └──────────────┘
当用户将手机靠近输液袋上的 NFC 贴纸时,NfcService 读取标签中的贴纸 ID,然后根据 ID 在 IVDrugDatabase 中查找关联的药物信息,自动填充到 Medicine 对象中,省去了手动输入的步骤。
AI 分析数据流
AIService 通过查询 DataStore 中的历史监控数据,提供智能建议:
┌──────────────┐ 历史数据 ┌──────────────┐ 分析结果 ┌──────────────┐ │ DataStore │ ──────────→ │ AIService │ ──────────→ │ AnalysisPage │ │ load() │ │ analyze() │ │ UI展示 │ │ sessions │ │ suggest() │ │ │ │ records │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ 基于规则的推理 ↓ ┌──────────────────────────────┐ │ 规则引擎: │ │ – 平均输液时长 > 3h → 建议咨询 │ │ – 预警频率 > 3次/天 → 关注流速 │ │ – 药物数 > 5 → 检查相互作用 │ │ – 夜间输液 → 提醒陪护 │ └──────────────────────────────┘
6. 项目目录结构详解
6.1 目录树总览
entry/src/main/ets/ ├── model/ # 数据模型层 (6个文件) │ ├── DataModels.ets # 15个核心数据类 │ ├── RouteParams.ets # 路由参数接口 │ ├── HospitalData.ets # 医院POI静态数据 │ ├── DrugDatabase.ets # 药物相互作用 (12组) │ ├── IVDrugDatabase.ets # 静脉药物数据库 (50种) │ └── InsuranceReference.ets # 6城市医保参考 ├── service/ # 服务层 (11个文件) │ ├── DataStore.ets # 数据持久化 │ ├── CameraService.ets # 相机服务 │ ├── VisionService.ets # 视觉识别 │ ├── NfcService.ets # NFC配对 │ ├── NotificationService.ets # 通知推送 │ ├── SpeechService.ets # 语音提醒 │ ├── ScanService.ets # 扫码服务 │ ├── NavService.ets # 导航服务 │ ├── WatchService.ets # 手表联动 │ ├── AIService.ets # AI分析 │ └── CostService.ets # 费用计算 ├── component/ # 组件层 (8个文件) │ ├── LevelProgress.ets # 液位进度 │ ├── MedicineCard.ets # 药物卡片 │ ├── AlertCard.ets # 预警卡片 │ ├── MonitorOverlay.ets # 监控叠加层 │ ├── HospitalMapView.ets # 医院平面图 │ ├── FlowChart.ets # 流速趋势图 │ ├── CostPieChart.ets # 费用饼图 │ └── StepIndicator.ets # 步骤指示器 └── pages/ # 页面层 (17个文件) ├── Index.ets # 入口 (角色选择) ├── HomePage.ets # 患者主页 (5-Tab) ├── MonitorPage.ets # 实时监控 ├── MedicinePage.ets # 药物管理 ├── MedicineDetailPage.ets # 药品详情 ├── HistoryPage.ets # 历史记录 ├── AnalysisPage.ets # 数据分析 ├── CostPage.ets # 费用管理 ├── CostDetailPage.ets # 费用详情 ├── InsuranceSetupPage.ets # 医保设置 (5步) ├── SettingsPage.ets # 系统设置 ├── HospitalNavPage.ets # 医院导航 ├── NurseHomePage.ets # 护士工作台 ├── PatientListPage.ets # 患者列表 ├── NurseAnalysisPage.ets # 护士分析 ├── FamilyHomePage.ets # 家属远程监控 └── FamilyAlertPage.ets # 家属预警记录
文件统计:
| model/ | 6 | ~600 | 数据结构与静态数据 |
| service/ | 11 | ~1500 | 业务逻辑与系统封装 |
| component/ | 8 | ~1200 | 可复用 UI 组件 |
| pages/ | 17 | ~3000 | 完整业务页面 |
| 合计 | 42 | ~6300 | – |
6.2 数据模型层 (model/)
数据模型层是整个系统的数据基础,定义了所有业务实体的结构和关系。共 6 个文件,承担不同的数据职责。
DataModels.ets — 核心数据类定义
这是系统最核心的数据文件,定义了 15 个数据类,涵盖了 IVGuard 的所有业务实体:
| 1 | Medicine | 药物信息 | id, name, dosage, frequency, startDate |
| 2 | MonitorSession | 监控会话 | id, startTime, endTime, startLevel, endLevel |
| 3 | LevelRecord | 液位记录 | timestamp, level, flowRate |
| 4 | MonitorRecord | 监控记录 | sessionId, records: LevelRecord[] |
| 5 | DrugInteraction | 药物相互作用 | drugA, drugB, severity, description |
| 6 | AlertEvent | 预警事件 | id, timestamp, type, level, message, handled |
| 7 | HospitalPOI | 医院兴趣点 | id, name, type, floor, x, y |
| 8 | IVDrugInfo | 静脉药物详情 | name, category, concentration, notes |
| 9 | CostItem | 费用条目 | id, name, category, amount, date |
| 10 | InsurancePolicy | 医保政策 | city, deductible, rate, cap |
| 11 | CostSummary | 费用汇总 | total, reimbursed, outOfPocket |
| 12 | AppSettings | 应用设置 | alertThreshold, speechEnabled, darkMode |
| 13 | UserRole | 用户角色枚举 | PATIENT, NURSE, FAMILY |
| 14 | NavPathData | 导航路径 | steps: string[], target: HospitalPOI |
| 15 | (辅助类型) | 路由参数等 | 视具体业务定义 |
UserRole 枚举的特别说明——它不仅用于 UI 分发,更是全系统权限控制的基础:
ypescript export enum UserRole { PATIENT = 'patient', NURSE = 'nurse', FAMILY = 'family' }
数据类之间的关系:
MonitorSession ──1:N──→ LevelRecord (一次会话包含多条液位记录) MonitorSession ──1:1──→ MonitorRecord (一次会话对应一条汇总记录) Medicine ──N:N──→ DrugInteraction (药物间多对多相互作用) AlertEvent ──N:1──→ MonitorSession (多个预警属于同一次会话) CostItem ──N:1──→ InsurancePolicy (多项费用使用同一医保) CostItem ──N:1──→ CostSummary (多项费用汇总为一个结果) HospitalPOI ──1:1──→ NavPathData (一个目标对应一条路径)
RouteParams.ets — 路由参数接口
定义页面间导航时传递的参数接口,确保类型安全:
` ypescript export interface MedicineDetailParams { medicineId: string }
export interface MonitorParams { sessionId?: string }
export interface CostDetailParams { costItemId: string }
export interface NursePatientParams { patientId: string } `
路由参数的作用:在 pushUrl 时,通过 params 字段传递参数对象,目标页面通过路由状态获取参数,实现页面间的数据传递。相比全局状态管理,路由参数使页面间的依赖关系更加显式和可追溯。
HospitalData.ets — 医院 POI 静态数据
存储医院内部的兴趣点(Point of Interest)数据,用于导航功能:
` ypescript export class HospitalData { static readonly POIS: HospitalPOI[] = [ { id: ‘poi_001’, name: ‘内科诊室’, type: ‘department’, floor: 1, x: 120, y: 80 }, { id: ‘poi_002’, name: ‘输液室’, type: ‘treatment’, floor: 1, x: 200, y: 150 }, { id: ‘poi_003’, name: ‘检验科’, type: ‘examination’, floor: 2, x: 80, y: 200 }, { id: ‘poi_004’, name: ‘药房’, type: ‘pharmacy’, floor: 1, x: 300, y: 100 }, { id: ‘poi_005’, name: ‘收费处’, type: ‘billing’, floor: 1, x: 350, y: 80 }, // … 更多 POI ]
static readonly FLOORS: number[] = [1, 2, 3]
static getByType(type: string): HospitalPOI[] { return HospitalData.POIS.filter(poi => poi.type === type) }
static getById(id: string): HospitalPOI | undefined { return HospitalData.POIS.find(poi => poi.id === id) } } `
POI 的 x 和 y 坐标对应医院平面图上的像素位置,NavService 基于这些坐标进行路径规划。
DrugDatabase.ets — 药物相互作用数据库
内置 12 组常见的静脉药物相互作用数据,是药物安全检测的核心数据源:
ypescript export class DrugDatabase { static readonly INTERACTIONS: DrugInteraction[] = [ { drugA: '头孢曲松', drugB: '含钙输液', severity: 'high', description: '头孢曲松与含钙输液配伍可形成致命性沉淀' }, { drugA: '华法林', drugB: '阿司匹林', severity: 'high', description: '联合使用显著增加出血风险' }, { drugA: '庆大霉素', drugB: '呋塞米', severity: 'high', description: '增加耳毒性和肾毒性风险' }, { drugA: '头孢哌酮', drugB: '酒精', severity: 'high', description: '双硫仑样反应,面部潮红、头痛、心悸' }, { drugA: '氯化钾', drugB: '葡萄糖', severity: 'medium', description: '需控制滴速,避免高钾血症' }, { drugA: '氨苄西林', drugB: '氨基糖苷类', severity: 'medium', description: '不可同瓶滴注,需分开给药' }, { drugA: '维生素K', drugB: '华法林', severity: 'high', description: '维生素K拮抗华法林抗凝作用' }, { drugA: '肝素', drugB: '阿司匹林', severity: 'high', description: '联合使用出血风险显著增加' }, { drugA: '胰岛素', drugB: '葡萄糖', severity: 'medium', description: '需密切监测血糖,调整胰岛素剂量' }, { drugA: '多巴胺', drugB: '碳酸氢钠', severity: 'medium', description: '碱性环境中多巴胺失活' }, { drugA: '两性霉素B', drugB: '氯化钠', severity: 'high', description: '两性霉素B不可用氯化钠溶解' }, { drugA: '万古霉素', drugB: '肝素', severity: 'medium', description: '同管输注可能产生沉淀' }, ] }
IVDrugDatabase.ets — 静脉药物数据库
收录 50 种常见的静脉输液药物信息,包括药物名称、分类、浓度、注意事项等:
` ypescript export class IVDrugDatabase { static readonly DRUGS: IVDrugInfo[] = [ { name: ‘0.9%氯化钠注射液’, category: ‘溶媒’, concentration: ‘0.9%’, notes: ‘等渗溶液,常用溶媒’ }, { name: ‘5%葡萄糖注射液’, category: ‘溶媒’, concentration: ‘5%’, notes: ‘等渗溶液,糖尿病患者慎用’ }, { name: ‘头孢呋辛钠’, category: ‘抗生素’, concentration: ‘0.75g/支’, notes: ‘需皮试,滴注时间不少于30分钟’ }, { name: ‘左氧氟沙星’, category: ‘抗生素’, concentration: ‘0.5g/100ml’, notes: ‘避光滴注,滴速不宜过快’ }, // … 共 50 种 ]
static searchByName(keyword: string): IVDrugInfo[] { return IVDrugDatabase.DRUGS.filter( drug => drug.name.includes(keyword) ) }
static getByCategory(category: string): IVDrugInfo[] { return IVDrugDatabase.DRUGS.filter( drug => drug.category === category ) } } `
InsuranceReference.ets — 医保参考数据
收录 6 个城市的医保报销参考数据,为费用估算提供基础参数:
` ypescript export class InsuranceReference { static readonly POLICIES: InsurancePolicy[] = [ { city: ‘北京’, deductible: 1300, reimbursementRate: 0.85, annualCap: 50000 }, { city: ‘上海’, deductible: 1500, reimbursementRate: 0.85, annualCap: 55000 }, { city: ‘广州’, deductible: 1600, reimbursementRate: 0.80, annualCap: 45000 }, { city: ‘深圳’, deductible: 1000, reimbursementRate: 0.90, annualCap: 60000 }, { city: ‘成都’, deductible: 800, reimbursementRate: 0.82, annualCap: 40000 }, { city: ‘武汉’, deductible: 1200, reimbursementRate: 0.83, annualCap: 48000 }, ]
static getByCity(city: string): InsurancePolicy | undefined { return InsuranceReference.POLICIES.find(p => p.city === city) }
static getCities(): string[] { return InsuranceReference.POLICIES.map(p => p.city) } } `
6.3 服务层 (service/)
服务层是 IVGuard 的业务逻辑核心,共 11 个文件,全部采用静态类模式。
完整服务清单
| DataStore | DataStore.ets | init, save, load, delete, clear | Preferences |
| CameraService | CameraService.ets | startCapture, stopCapture, getCurrentFrame | Camera2 API |
| VisionService | VisionService.ets | analyzeFrame, getFlowRate, getEstimatedTime, reset | CameraService(frame) |
| NfcService | NfcService.ets | pair, read, stop | NFC API |
| NotificationService | NotificationService.ets | sendAlert, sendUrgent, sendInfo, cancel | Notification API |
| SpeechService | SpeechService.ets | speak, stop | TextToSpeech API |
| ScanService | ScanService.ets | scan, stop | Barcode Scan API |
| NavService | NavService.ets | navigate, search, getPath | HospitalData |
| WatchService | WatchService.ets | sync, alert, vibrate | DeviceManager API |
| AIService | AIService.ets | analyze, suggest | DataStore |
| CostService | CostService.ets | calculateReimbursement, breakdownByCategory, estimateOutOfPocket | InsuranceReference, DataStore |
服务依赖矩阵
` DataStore Camera Vision NFC Notif Speech Scan Nav Watch AI Cost DataStore – – – – – – – – – – – CameraService – – – – – – – – – – – VisionService – →① – – – – – – – – – NfcService →② – – – – – – – – – – NotificationSvc – – – – – – – – – – – SpeechService – – – – – – – – – – – ScanService →③ – – – – – – – – – – NavService – – – – – – – – – – – WatchService – – – – – – – – – – – AIService →④ – – – – – – – – – – CostService →⑤ – – – – – – – – – –
① VisionService 接收 CameraService 的帧数据(但通过参数传递,非直接调用) ② NfcService 将读取的贴纸 ID 存入 DataStore ③ ScanService 将扫码结果存入 DataStore ④ AIService 从 DataStore 读取历史数据进行分析 ⑤ CostService 从 DataStore 读取费用数据和医保设置 `
6.4 组件层 (component/)
组件层包含 8 个可复用 UI 组件,每个组件专注于单一视觉职责。
组件清单与属性接口
| LevelProgress | LevelProgress.ets | @Prop level: number | 声明式(Progress) |
| MedicineCard | MedicineCard.ets | @Prop medicine: Medicine | 声明式(Card) |
| AlertCard | AlertCard.ets | @Prop interaction: DrugInteraction | 声明式(Card) |
| MonitorOverlay | MonitorOverlay.ets | @Prop level: number, @Prop isMonitoring: boolean | 声明式(Stack) |
| HospitalMapView | HospitalMapView.ets | @Prop pois: HospitalPOI[], @Prop currentFloor: number | 声明式(Image+Stack) |
| FlowChart | FlowChart.ets | @Prop records: LevelRecord[] | Canvas |
| CostPieChart | CostPieChart.ets | @Prop items: CostItem[] | Canvas |
| StepIndicator | StepIndicator.ets | @Prop currentStep: number, @Prop totalSteps: number | 声明式(Row) |
组件复用分析
` 组件被哪些页面使用:
LevelProgress: MonitorPage, FamilyHomePage MedicineCard: MedicinePage, MedicineDetailPage AlertCard: MedicinePage, NurseHomePage, FamilyAlertPage MonitorOverlay: MonitorPage HospitalMapView: HospitalNavPage FlowChart: AnalysisPage, MonitorPage CostPieChart: CostPage, CostDetailPage StepIndicator: InsuranceSetupPage, HospitalNavPage `
6.5 页面层 (pages/)
页面层包含 17 个完整业务页面,按角色分组如下:
患者端页面 (12 个)
| 首页 | HomePage.ets | Index 角色分发 | 5-Tab 导航框架 |
| 实时监控 | MonitorPage.ets | 首页监控 Tab → 详情 | 液位监控、相机预览 |
| 药物管理 | MedicinePage.ets | 首页药物 Tab | 药物列表、添加/删除 |
| 药品详情 | MedicineDetailPage.ets | MedicinePage → 点击卡片 | 药物详情、相互作用 |
| 历史记录 | HistoryPage.ets | 首页历史 Tab | 会话列表、详情查看 |
| 数据分析 | AnalysisPage.ets | 首页 → 更多 | 趋势图、AI 建议 |
| 费用管理 | CostPage.ets | 首页费用 Tab | 费用列表、饼图 |
| 费用详情 | CostDetailPage.ets | CostPage → 点击条目 | 费用详情、报销计算 |
| 医保设置 | InsuranceSetupPage.ets | CostPage → 设置医保 | 5 步引导设置 |
| 系统设置 | SettingsPage.ets | 首页 → 我的 → 设置 | 通知、语音、阈值 |
| 医院导航 | HospitalNavPage.ets | 首页 → 导航 | 平面图、路径规划 |
护士端页面 (3 个)
| 工作台 | NurseHomePage.ets | Index 角色分发 | 预警队列、今日统计 |
| 患者列表 | PatientListPage.ets | 工作台 → 患者列表 | 全患者状态总览 |
| 聚合分析 | NurseAnalysisPage.ets | 工作台 → 分析 | 科室统计、趋势 |
家属端页面 (2 个)
| 远程监控 | FamilyHomePage.ets | Index 角色分发 | 亲人状态、一键联系 |
| 预警记录 | FamilyAlertPage.ets | 远程监控 → 预警记录 | 历史预警、处理状态 |
入口页面 (1 个)
| 角色选择 | Index.ets | 应用启动 | 角色选择、路由分发 |
6.6 文件命名规范
IVGuard 严格遵循以下命名规范:
| 页面文件 | PascalCase + Page 后缀 | MonitorPage.ets、CostDetailPage.ets |
| 组件文件 | PascalCase | LevelProgress.ets、AlertCard.ets |
| 服务文件 | PascalCase + Service 后缀 | CameraService.ets、VisionService.ets |
| 数据模型 | PascalCase | DataModels.ets、RouteParams.ets |
| 静态数据 | PascalCase + Database/Reference/Data 后缀 | DrugDatabase.ets、InsuranceReference.ets |
| 数据类 | PascalCase | Medicine、MonitorSession、CostItem |
| 枚举 | PascalCase | UserRole |
| 接口 | PascalCase + Params/Config 后缀 | MedicineDetailParams |
| 静态方法 | camelCase | startCapture()、nalyzeFrame() |
| 静态属性 | camelCase | currentLevel、isRunning |
6.7 层间依赖关系
IVGuard 的层间依赖遵循严格的单向规则,禁止反向依赖和跨层调用:
┌─────────────────────────────────────────────────────────────┐ │ 依赖方向图 │ │ │ │ Index.ets │ │ ↓ pushUrl │ │ Pages ──────────→ Component (通过 @Prop/@Link 传递数据) │ │ │ │ │ ↓ 静态方法调用 │ │ Services ────────→ Model (通过数据类型和静态数据) │ │ │ │ │ ↓ JSON序列化/反序列化 │ │ DataStore ───────→ Preferences (底层存储) │ │ │ │ 禁止依赖: │ │ ✗ Component → Service (组件不直接调用服务) │ │ ✗ Model → Service (数据层不依赖服务层) │ │ ✗ Service → Page (服务不依赖页面) │ │ ✗ Page → Model (直接) (页面通过服务间接使用数据) │ └─────────────────────────────────────────────────────────────┘
依赖注入替代方案:由于 ArkTS 不支持依赖注入框架,IVGuard 通过以下方式实现解耦:
7. 系统用例图与场景分析
7.1 系统用例图
┌─────────────────────────────────────────────────────────────────────────┐ │ IVGuard 系统用例图 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────┐ │ │ │ 患者 │ │ │ └────┬────┘ │ │ │ │ │ ├── UC01: 启动输液监控 │ │ ├── UC02: 查看液位状态 │ │ ├── UC03: 管理药物列表 │ │ ├── UC04: 查看药物详情 │ │ ├── UC05: 检测药物相互作用 ──── «include» ── UC03 │ │ ├── UC06: 扫码添加药物 │ │ ├── UC07: NFC配对输液袋 │ │ ├── UC08: 查看历史记录 │ │ ├── UC09: 查看数据分析 │ │ ├── UC10: 录入医疗费用 │ │ ├── UC11: 估算医保报销 ──── «include» ── UC10 │ │ ├── UC12: 设置医保信息 │ │ ├── UC13: 查看费用饼图 ──── «extend» ── UC10 │ │ ├── UC14: 医院室内导航 │ │ ├── UC15: 修改应用设置 │ │ ├── UC16: 接收语音预警 ──── «extend» ── UC02 │ │ └── UC17: 手表联动查看 │ │ │ │ ┌─────────┐ │ │ │ 护士 │ │ │ └────┬────┘ │ │ │ │ │ ├── UC18: 查看预警队列 │ │ ├── UC19: 处理预警事件 │ │ ├── UC20: 查看患者列表 │ │ ├── UC21: 筛选患者状态 │ │ ├── UC22: 查看聚合分析 │ │ └── UC23: 批量处理预警 ──── «extend» ── UC19 │ │ │ │ ┌─────────┐ │ │ │ 家属 │ │ │ └────┬────┘ │ │ │ │ │ ├── UC24: 远程查看输液状态 │ │ ├── UC25: 接收预警通知 ──── «extend» ── UC24 │ │ ├── UC26: 查看预警记录 │ │ └── UC27: 一键联系护士站 │ │ │ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ │ │ │ 跨角色共享: │ │ ├── UC28: 修改应用设置 │ │ └── UC29: 切换用户角色 │ │ │ └─────────────────────────────────────────────────────────────────────────┘
用例关系说明
- «include»:被包含用例是基础用例的必经步骤。例如 UC05(检测药物相互作用)是 UC03(管理药物列表)的必要环节——每次添加药物都必须执行相互作用检测
- «extend»:扩展用例是基础用例的可选增强。例如 UC16(接收语音预警)是 UC02(查看液位状态)的可选扩展——只有液位低于阈值时才触发语音
7.2 患者典型场景
场景一:完整输液监控流程
这是患者使用 IVGuard 的最核心场景,覆盖从挂号到费用结算的完整流程。
时间线 操作步骤 系统响应 涉及页面/服务 ─────── ──────────────────────────────── ──────────────────────────── ────────────── 09:00 患者挂号就诊 – – 09:30 医生开具输液处方 – – 09:45 护士配药并开始输液 – – 患者打开 IVGuard,选择"患者"角色 Index 分发至 HomePage Index → HomePage 09:46 患者点击"开始监控" 启动 CameraService MonitorPage VisionService 开始识别 3秒间隔采样液位 液位显示 85% LevelProgress 渲染 LevelProgress 09:50 患者通过 NFC 贴纸配对 NfcService 读取贴纸 NfcService 系统自动填充药物信息 查询 IVDrugDatabase MedicinePage 自动检测 DrugDatabase AlertCard 10:30 液位降至 20% 触发黄色预警 NotificationService 发送通知栏提示 NotificationService 10:45 液位降至 10% 触发橙色预警 NotificationService 通知栏 + 语音提醒 SpeechService 手表震动 WatchService 10:50 液位降至 5% 触发红色预警 NotificationService 紧急通知 + 语音 SpeechService 手表连续震动 WatchService 通知护士端 NotificationService 10:55 护士到达,更换输液袋 患者确认换瓶 MonitorPage 系统重置液位至 85% VisionService.reset() VisionService 11:30 输液完成 患者结束监控 MonitorPage 保存 MonitorSession DataStore.save() DataStore 11:35 患者录入费用项目 添加 CostItem CostPage 设置医保信息 InsuranceSetupPage 5步引导 InsuranceSetupPage 查看报销估算 CostService.calculate() CostService 查看费用饼图 CostPieChart 渲染 CostPieChart
场景二:药物相互作用检测场景
步骤 操作 系统处理 UI 展示 ──── ──────────────────────────── ──────────────────────────────── ────────────── 1 患者已有药物: 华法林 DataStore.load('medicines') MedicineCard 2 患者扫码添加: 阿司匹林 ScanService.scan() 扫码界面 3 系统检测相互作用 DrugDatabase.checkAllInteractions – 4 发现高危组合: 华法林+阿司匹林 返回 DrugInteraction(severity=high) – 5 显示红色预警卡片 AlertCard 渲染 AlertCard(红色) "联合使用显著增加出血风险" 描述文字显示 警告描述 6 患者确认已知晓 预警状态更新 预警标记已读 7 药物添加完成 DataStore.save('medicines') 更新药物列表
场景三:医院导航场景
步骤 操作 系统处理 UI 展示 ──── ──────────────────────────── ──────────────────────────────── ────────────── 1 患者点击"医院导航" HospitalNavPage 加载 HospitalMapView 2 平面图展示 HospitalData.POIS 加载 楼层平面图 3 患者选择"检验科" NavService.search('检验科') 搜索结果 4 系统规划路径 NavService.navigate() StepIndicator 5 步骤引导: "沿走廊前行50米" StepIndicator 渲染步骤1 路径指引 6 步骤引导: "上2楼" StepIndicator 渲染步骤2 楼层切换 7 到达目标 标记完成 到达确认
7.3 护士典型场景
场景一:预警处理流程
时间线 操作步骤 系统响应 涉及页面/服务 ─────── ──────────────────────────────── ──────────────────────────── ────────────── 10:45 护士收到橙色预警推送 NotificationService 通知栏 "3床张三 液位10%" AlertCard 显示 AlertCard 10:46 护士打开工作台 NurseHomePage 加载 NurseHomePage 查看预警队列 按紧急程度排序 预警列表 10:47 护士点击张三预警卡片 展开预警详情 AlertCard(详情) 查看患者信息: 3床, 0.9%NaCl 显示药物和液位信息 详情面板 10:48 护士标记"已处理" AlertEvent.handled = true 状态更新 前往3床更换输液袋 – – 10:55 更换完成,回到工作台 预警队列更新 NurseHomePage 预警状态变为"已处理" 移出预警队列 已处理列表
场景二:聚合分析场景
步骤 操作 系统处理 UI 展示 ──── ──────────────────────────── ──────────────────────────────── ────────────── 1 护士点击"聚合分析" NurseAnalysisPage 加载 NurseAnalysisPage 2 查看今日统计 AIService.analyze('daily') 统计卡片 3 查看科室输液分布 按科室分组统计 柱状图/列表 4 查看高频预警时段 按小时统计预警频率 热力图/列表 5 查看药物使用排行 按药物统计使用次数 排行榜 6 查看工作负荷趋势 按日统计巡检和预警数 趋势图
7.4 家属典型场景
场景一:远程监控流程
时间线 操作步骤 系统响应 涉及页面/服务 ─────── ──────────────────────────────── ──────────────────────────── ────────────── 09:00 家属打开 IVGuard,选择"家属"角色 Index 分发至 FamilyHomePage Index → FamilyHomePage 09:01 查看亲人输液状态 加载实时数据 状态面板 液位 85%,预计剩余 2小时 LevelProgress 渲染 LevelProgress 药物: 0.9%NaCl + 头孢呋辛 药物列表显示 MedicineCard 10:30 家属手机推送: "液位 20%" NotificationService 通知推送 家属打开 App 查看详情 FamilyHomePage 刷新 状态更新 10:45 家属收到预警: "液位 10%" 推送 + App内提醒 通知+弹窗 家属点击"联系护士站" 拨打护士站电话 系统电话 10:55 护士处理完成 状态更新为"已处理" 状态刷新 家属看到状态恢复为 85% FamilyHomePage 更新 状态更新
场景二:预警记录查看
步骤 操作 系统处理 UI 展示 ──── ──────────────────────────── ──────────────────────────────── ────────────── 1 家属点击"预警记录" FamilyAlertPage 加载 FamilyAlertPage 2 查看历史预警时间线 按时间倒序排列 时间线 3 查看单条预警详情 展开预警类型、级别、处理状态 详情面板 4 统计摘要: 本周 3 次预警 统计已处理/未处理/平均响应时间 摘要卡片
8. 非功能性需求
8.1 性能需求
IVGuard 作为实时输液监控系统,性能是用户体验的基石。以下是具体的性能指标及实现策略:
核心性能指标
| 采样间隔 | 3 秒 (3000ms) | setInterval 回调间隔 | MonitorPage.ets 中设置 setInterval(3000) |
| 液位更新延迟 | < 100ms | 从 VisionService 返回到 UI 更新 | @State 驱动自动刷新 |
| Canvas 渲染帧率 | 60fps | onReady + 绘制耗时 | 预计算坐标,限制数据量 |
| 页面切换时间 | < 500ms | 从 pushUrl 到页面渲染完成 | 懒加载非首屏内容 |
| 应用冷启动 | < 3 秒 | 从点击图标到首页可交互 | DataStore.init() 异步化 |
| 通知推送延迟 | < 2 秒 | 从预警触发到通知到达 | 系统级通知通道 |
| 语音合成延迟 | < 500ms | 从调用 speak() 到声音输出 | 预加载语音引擎 |
3 秒采样间隔的设计考量
3 秒的采样间隔是经过权衡后的选择:
` 间隔选择分析:
1秒间隔: 优势: 液位更新更频繁,感知延迟更低 劣势: CPU/相机/电池消耗大,视觉识别负担重 结论: 过度消耗资源,不必要
3秒间隔 (最终选择): 优势: 平衡了实时性和资源消耗 劣势: 最大 3 秒的液位更新延迟 结论: 液位变化是缓慢过程,3秒延迟可接受
5秒间隔: 优势: 资源消耗更低 劣势: 预警响应延迟增大,用户体验下降 结论: 预警不够及时
10秒间隔: 优势: 资源消耗极低 劣势: 预警延迟过高,可能错过最佳处理窗口 结论: 不可接受 `
在 MonitorPage.ets 中的实现:
` ypescript aboutToAppear() { this.timer = setInterval(() => { const frame = CameraService.getCurrentFrame() if (frame) { this.currentLevel = VisionService.analyzeFrame(frame) this.flowRate = VisionService.getFlowRate() this.estimatedTime = VisionService.getEstimatedTime() this.checkAlert(this.currentLevel) } }, 3000) }
aboutToDisappear() { if (this.timer) { clearInterval(this.timer) CameraService.stopCapture() } } `
特别注意 boutToDisappear 中的清理逻辑——页面销毁时必须清除定时器并停止相机,否则会导致后台持续占用硬件资源。
Canvas 60fps 优化策略
IVGuard 的 FlowChart 和 CostPieChart 组件采用 Canvas 绘制,需要保持 60fps 的渲染性能:
` ypescript // 优化策略一: 数据预计算 private drawChart() { // 在绘制前一次性计算所有坐标,避免逐帧计算 const points = this.records.map((record, index) => { return { x: padding + (index / Math.max(1, this.records.length – 1)) * chartWidth, y: chartHeight – (record.level / 100) * chartHeight } }) // 然后用预计算的 points 绘制 }
// 优化策略二: 限制数据量 private get displayRecords(): LevelRecord[] { if (this.records.length <= 100) return this.records // 采样降频: 取最近 100 个点 const step = Math.ceil(this.records.length / 100) return this.records.filter((_, index) => index % step === 0) }
// 优化策略三: 按需重绘 // 只在 records 数据变化时触发重绘,而非每帧重绘 build() { Canvas(this.context) .width(‘100%’) .height(200) .onReady(() => { this.drawChart() // 只在 Canvas 准备好时绘制一次 }) } `
8.2 可靠性需求
医疗系统的可靠性要求远高于普通消费级应用。IVGuard 从以下维度保障系统可靠性:
数据持久化保障
| 应用意外退出 | 监控数据丢失 | 每次 setInterval 回调后 DataStore.save() |
| 手机重启 | 内存数据丢失 | 所有关键数据通过 Preferences 持久化 |
| 存储空间不足 | 写入失败 | lush() 异常捕获,降级为内存缓存 |
| 并发写入 | 数据覆盖 | 单线程模型下不存在真正的并发,但 lush() 保证原子性 |
优雅降级策略
IVGuard 实现了多层优雅降级,确保核心功能在各种异常情况下依然可用:
` 降级层级:
Level 0 – 完整功能: 相机可用 + NFC可用 + 通知可用 + 语音可用 + 手表可用 → 完整监控体验
Level 1 – 核心功能 (相机不可用): VisionService 使用模拟数据 (currentLevel 从85%衰减) → 手动模式: 用户观察输液袋,App 估算剩余时间
Level 2 – 基础功能 (通知/语音不可用): 仅页面内预警提示,无系统通知 → 用户需保持 App 在前台
Level 3 – 最小功能 (DataStore 不可用): 所有数据仅保存在内存中 → 当前会话数据可用,重启后丢失
Level 4 – Mock 服务 (开发/测试环境): 所有 Service 使用 Mock 实现 → 完整功能模拟,无需真实硬件 `
Mock 服务实现
` ypescript // VisionService.ets 的 Mock 实现 (当前 MVP) export class VisionService { private static currentLevel: number = 85
static analyzeFrame(frame: image.PixelMap | null): number { // Mock: 不依赖真实帧数据,直接模拟衰减 const decay = 1 + Math.random() VisionService.currentLevel = Math.max(0, VisionService.currentLevel – decay) return VisionService.currentLevel } }
// 未来生产版本的 VisionService (OpenCV NAPI) // static analyzeFrame(frame: image.PixelMap): number { // const mat = OpenCVUtils.pixelMapToMat(frame) // const stickerRegion = StickerDetector.detect(mat) // const level = LevelReader.read(stickerRegion) // return level // } `
8.3 安全需求
医疗数据属于敏感个人信息,IVGuard 在安全方面遵循"最小权限、数据脱敏、本地优先"三大原则。
权限最小化
IVGuard 请求的 8 项系统权限,每一项都有明确的使用场景和最小化范围:
| 1 | ohos.permission.CAMERA | 拍摄输液袋进行液位识别 | 首次启动监控时 | 患者 |
| 2 | ohos.permission.NFC_TAG | 读取 NFC 贴纸信息 | 首次配对时 | 患者 |
| 3 | ohos.permission.NOTIFICATION | 发送预警通知 | 应用启动时 | 全角色 |
| 4 | ohos.permission.VIBRATE | 紧急预警时震动提醒 | 应用启动时 | 全角色 |
| 5 | ohos.permission.INTERNET | AI 分析和网络同步(预留) | 应用启动时 | 全角色 |
| 6 | ohos.permission.LOCATION | 医院导航定位 | 首次导航时 | 患者/护士 |
| 7 | ohos.permission.SCAN_CODE | 扫描药品条码 | 首次扫码时 | 患者 |
| 8 | ohos.permission.DISTRIBUTED_DATASYNC | 手表数据同步 | 首次连接手表时 | 患者/家属 |
权限请求策略:
医疗数据脱敏
` 数据分类与脱敏策略:
┌──────────────────────────────────────────────────────┐ │ 敏感等级 │ 数据类型 │ 脱敏策略 │ ├──────────────┼────────────────────┼──────────────────┤ │ 高敏感 │ 患者姓名、身份证号 │ 不采集,使用代号 │ │ 高敏感 │ 具体药物剂量 │ 仅显示通用名称 │ │ 中敏感 │ 监控记录、液位数据 │ 本地存储,不上传 │ │ 中敏感 │ 预警事件 │ 本地存储,不上传 │ │ 低敏感 │ 费用数据 │ 本地存储,不上传 │ │ 非敏感 │ 应用设置 │ 本地存储 │ │ 非敏感 │ 医院POI数据 │ 静态内置 │ └──────────────────────────────────────────────────────┘ `
本地存储优先
IVGuard 的 MVP 版本采用 纯本地存储 架构,所有数据保存在设备本地,不涉及云端传输:
` 数据存储位置:
Preferences (‘ivguard_db’): ├── 药物列表 medicines ├── 监控会话 monitor_sessions ├── 费用条目 cost_items ├── 医保设置 insurance_policy ├── 应用设置 app_settings ├── 用户角色 user_role └── 预警事件 alert_events
无云端存储: ✗ 无用户账号系统 ✗ 无数据上传 ✗ 无第三方数据共享 ✓ 所有数据仅存在于设备本地 `
这种设计在 MVP 阶段的优势是显著的:无需后端服务器、无需网络安全协议、无需数据合规审批、无需用户注册登录。同时,DataStore 的抽象层设计为未来引入云端同步预留了扩展点——只需在 DataStore 中增加云端读写逻辑,上层代码无需变更。
8.4 可维护性需求
分层架构的可维护性
IVGuard 的五层架构直接支撑了以下可维护性指标:
| 修改预警阈值 | 页面层 | 1 (MonitorPage) | 低 |
| 替换视觉识别算法 | 服务层 | 1 (VisionService) | 低 |
| 新增药物数据 | 数据层 | 1 (DrugDatabase) | 极低 |
| 新增费用类别 | 数据层+服务层 | 2 (DataModels+CostService) | 低 |
| 修改液位进度条样式 | 组件层 | 1 (LevelProgress) | 极低 |
| 新增页面 | 页面层 | 1 (新Page文件) | 低 |
服务单例的可维护性
静态类模式的 Service 使得代码定位和修改变得极其直观:
` 需求: “修改语音提醒的文案”
定位路径:
影响范围: 仅 SpeechService.ets 一个文件 回归风险: 仅影响语音输出,不影响其他功能 `
组件复用的可维护性
8 个通用组件在多个页面间复用,降低了重复代码量:
` 组件复用率:
LevelProgress: 2 个页面使用 → 1 个组件替代 2 份重复代码 MedicineCard: 2 个页面使用 → 节省约 120 行代码 AlertCard: 3 个页面使用 → 节省约 180 行代码 FlowChart: 2 个页面使用 → 节省约 200 行代码 CostPieChart: 2 个页面使用 → 节省约 200 行代码
总节省: 约 700 行代码 (占页面层代码的 ~23%) `
8.5 可扩展性需求
IVGuard 在架构层面预留了多个扩展点,确保未来功能迭代无需大规模重构:
扩展点一:OpenCV NAPI 预留
当前 VisionService 采用模拟算法,未来接入真实视觉识别时,需要通过 NAPI(Native API)调用 OpenCV:
` 扩展路径:
当前 (MVP): VisionService.analyzeFrame(frame) → 模拟: currentLevel – random(1-2)
未来 (生产): VisionService.analyzeFrame(frame) → NAPI: OpenCVUtils.pixelMapToMat(frame) → NAPI: StickerDetector.detect(mat) → NAPI: LevelReader.read(stickerRegion) → 返回真实液位值
变更范围: 仅 VisionService.ets 内部实现 上层影响: 无 (MonitorPage 调用接口不变) `
扩展点二:手表端预留
WatchService 的接口设计已考虑手表端扩展:
` ypescript export class WatchService { // 当前实现: 模拟手表通信 static async sync(data: MonitorSession): Promise { // TODO: 通过 DeviceManager API 实现真实通信 }
static async vibrate(pattern: string): Promise { // TODO: 调用手表端震动 API }
// 未来扩展: 手表端 UI // static async updateWidget(level: number): Promise // static async showAlert(message: string): Promise } `
扩展点三:云端同步预留
DataStore 的抽象层设计支持未来无缝切换至云端同步:
` 当前架构: Page → DataStore.save() → Preferences.put()
未来架构: Page → DataStore.save() → Preferences.put() (本地缓存) + CloudSync.upload() (云端同步)
Page → DataStore.load() → Preferences.get() (本地优先) + CloudSync.download() (冲突合并) `
扩展点四:多语言预留
所有用户可见的字符串均未硬编码在页面层,而是通过常量或数据模型提供:
` ypescript // 当前: 中文硬编码 (MVP 简化) Text(‘当前液位’)
// 未来: 多语言支持 Text(StringResource.get(‘monitor_current_level’)) // zh: ‘当前液位’ // en: ‘Current Level’ `
扩展点五:主题切换预留
AppSettings 数据模型中预留了 darkMode 字段:
ypescript export class AppSettings { alertThreshold: number = 20 speechEnabled: boolean = true darkMode: boolean = false // 预留:深色模式 fontSize: number = 16 // 预留:字号调整 language: string = 'zh-CN' // 预留:语言设置 }
9. 核心技术决策记录
技术决策记录(Architecture Decision Record, ADR)是 IVGuard 项目最重要的架构资产之一。每个决策都经过充分的方案对比和利弊权衡,确保选择的方案在当前阶段是最优解,同时为未来的演进预留了空间。
9.1 视觉识别方案选择
决策编号: ADR-001 决策状态: 已采纳 决策日期: 项目启动期
决策背景
IVGuard 的核心功能是输液液位识别。需要选择一种可靠的液位识别方案,使得普通智能手机能够准确读取输液袋中的液位信息。
候选方案
方案 A:纯颜色边界检测
` 原理: 输液袋中液体区域与空气区域的颜色/透明度不同 通过检测颜色边界确定液面位置 液位 = 边界位置 / 输液袋总高度
优势: ✓ 无需额外硬件 ✓ 实现简单
劣势: ✗ 受光照影响大 (日光/灯光/阴影) ✗ 受输液袋形状影响 (不同厂家袋型不同) ✗ 受液体颜色影响 (有色液体 vs 无色液体) ✗ 识别精度约 ±15% ✗ 需要大量训练数据 `
方案 B:贴纸刻度读取(最终选择)
` 原理: 在输液袋外壁贴一张专用贴纸 贴纸包含等间距刻度线和颜色标记 视觉算法通过识别刻度线与液面的相对位置确定液位 液位 = 液面处刻度值
优势: ✓ 刻度提供绝对参考系,消除形状/角度误差 ✓ 颜色标记作为辅助识别,双重保障 ✓ 识别精度约 ±5% ✓ 对光照条件不敏感 ✓ 不同输液袋通用
劣势: ✗ 需要物理贴纸 (成本约 0.5 元/张) ✗ 贴纸粘贴需要规范操作 ✗ NFC 芯片增加贴纸成本 `
方案 C:液位传感器硬件
` 原理: 在输液袋上安装专用液位传感器 通过电阻/电容/超声波原理直接测量液位
优势: ✓ 精度极高 (±1%) ✓ 不受视觉条件影响
劣势: ✗ 需要专用硬件,成本高 (50-200 元/个) ✗ 需要与输液袋集成,涉及医疗器械审批 ✗ 需要蓝牙/WiFi 桥接到手机 ✗ 不适合 MVP 验证 `
决策结论
选择 方案 B(贴纸刻度读取),理由:
影响范围
- VisionService.ets:识别算法需针对贴纸刻度设计
- NfcService.ets:需实现 NFC 贴纸读取
- MonitorOverlay.ets:需叠加显示识别结果
- 硬件供应链:需设计并生产专用贴纸
9.2 多瓶监控方案
决策编号: ADR-002 决策状态: 已采纳 决策日期: 项目启动期
决策背景
临床场景中,患者可能同时接受多瓶输液(序贯输注或双通道输注)。需要选择多瓶监控的实现方式。
候选方案
方案 A:快速切换监控(最终选择)
` 原理: 用户在多个输液瓶之间快速切换 每次只监控一瓶 通过 Tab 或下拉选择切换目标
优势: ✓ 实现简单 ✓ 单摄像头即可 ✓ UI 设计简洁 ✓ 符合序贯输注的常见场景
劣势: ✗ 无法同时监控多瓶 ✗ 切换间隔存在监控盲区 `
方案 B:同时多标记监控
` 原理: 在同一画面中识别多个贴纸标记 分别计算每个标记的液位 同时展示多瓶状态
优势: ✓ 真正的多瓶并行监控 ✓ 无切换盲区 ✓ 适合双通道输注
劣势: ✗ 视觉识别算法复杂度倍增 ✗ 需要更广的摄像头视野 ✗ UI 展示复杂 ✗ 性能开销增大 ✗ 多标记干扰问题 `
决策结论
选择 方案 A(快速切换监控),理由:
9.3 家属端方案
决策编号: ADR-003 决策状态: 已采纳 决策日期: 项目启动期
决策背景
家属需要远程查看亲人的输液状态。需要选择家属功能的实现方式。
候选方案
方案 A:同 App 角色切换(最终选择)
` 原理: 在同一个 App 中支持三种角色 Index.ets 入口处选择角色 不同角色进入不同的 HomePage 共享底层 Service 和 Model
优势: ✓ 单一 App,开发和维护成本低 ✓ 角色切换方便 (一个手机可切换多个角色) ✓ 共享代码量大 (Service/Component/Model 全部复用) ✓ 应用商店只需发布一个包
劣势: ✗ 角色切换需重新登录 ✗ App 体积稍大 (包含所有角色页面) ✗ 不支持同时在手机端看患者和家属界面 `
方案 B:独立 App
` 原理: 为三种角色分别开发独立 App IVGuard-Patient / IVGuard-Nurse / IVGuard-Family 各自独立安装和运行
优势: ✓ 各 App 功能聚焦,UI 精简 ✓ 可以同时安装运行 (手机端+手表端分别运行) ✓ 安全隔离更彻底
劣势: ✗ 开发和维护成本 ×3 ✗ 代码重复量大 ✗ 三个 App 的发布和更新管理 ✗ 家属 App 的推广和安装门槛 `
决策结论
选择 方案 A(同 App 角色切换),理由:
9.4 数据存储方案
决策编号: ADR-004 决策状态: 已采纳 决策日期: 项目启动期
决策背景
IVGuard 需要本地持久化存储用户数据(药物列表、监控记录、费用数据等)。HarmonyOS 提供了 Preferences(KV 存储)和关系型数据库(RDB)两种方案。
候选方案对比
` 方案 A: Preferences (轻量级 KV 存储) — 最终选择
适用场景: 数据量小、结构简单、查询需求低 数据模型: Key-Value (JSON 序列化) 性能特征: 读取 O(1),写入需 flush() 存储上限: 建议 < 10KB
优势: ✓ 初始化零开销 ✓ API 极简 (get/put/flush) ✓ 学习成本低 ✓ 适合 MVP 快速验证 ✓ 性能稳定可预测
劣势: ✗ 无法按条件查询 (只能按 Key 读取) ✗ 无事务支持 ✗ 数据量增大后性能下降 ✗ 数据关系需在应用层维护
方案 B: 关系型数据库 (RDB)
适用场景: 数据量大、关系复杂、需复杂查询 数据模型: 关系表 + SQL 性能特征: SQL 解析 + 索引查询 存储上限: 无硬性上限
优势: ✓ 完整 SQL 查询能力 ✓ ACID 事务支持 ✓ 联表查询 ✓ 适合数据量增长 ✓ 数据关系在数据库层维护
劣势: ✗ 初始化开销较大 (建表、索引) ✗ SQL 代码量多 ✗ 学习成本中 ✗ MVP 阶段过度设计 `
决策结论
选择 方案 A(Preferences),理由:
9.5 药物数据库方案
决策编号: ADR-005 决策状态: 已采纳 决策日期: 项目启动期
决策背景
IVGuard 需要内置静脉药物数据库(50 种药物)和药物相互作用数据库(12 组)。需要选择数据的存储和加载方式。
候选方案
方案 A:ArkTS 静态数组(最终选择)
ypescript export class DrugDatabase { static readonly INTERACTIONS: DrugInteraction[] = [ { drugA: '头孢曲松', drugB: '含钙输液', severity: 'high', description: '头孢曲松与含钙输液配伍可形成致命性沉淀' }, // … ] }
方案 B:rawfile JSON 文件
resources/rawfile/drug_interactions.json: [ { "drugA": "头孢曲松", "drugB": "含钙输液", … }, … ]
对比分析
| 类型安全 | 编译期类型检查 | 运行时 JSON 解析,需手动类型转换 |
| 加载方式 | 类加载时自动可用 | 需异步读取 rawfile |
| 代码提示 | IDE 完整提示 | 无(JSON 是纯文本) |
| 修改方式 | 修改 .ets 文件 | 修改 .json 文件 |
| 性能 | 零加载开销 | 首次读取有 IO 开销 |
| ArkTS 友好度 | 原生支持 | 需 JSON.parse + 类型断言 |
决策结论
选择 方案 A(ArkTS 静态数组),理由:
9.6 状态管理方案
决策编号: ADR-006 决策状态: 已采纳 决策日期: 项目启动期
决策背景
HarmonyOS ArkUI 提供了两套状态管理方案:V1(@Component / @State)和 V2(@ComponentV2 / @Local)。需要选择一套作为项目标准。
候选方案
方案 A:V1 状态管理(最终选择)
` ypescript @Component struct MonitorPage { @State currentLevel: number = 85 @State isMonitoring: boolean = false @Prop alertThreshold: number
build() { Column() { LevelProgress({ level: this.currentLevel }) } } } `
方案 B:V2 状态管理
` ypescript @ComponentV2 struct MonitorPage { @Local currentLevel: number = 85 @Local isMonitoring: boolean = false @Param alertThreshold: number
build() { Column() { LevelProgress({ level: this.currentLevel }) } } } `
对比分析
| 稳定性 | 成熟稳定,API 5.0 官方推荐 | 较新,部分 API 仍在迭代 |
| 社区资源 | 丰富,大量示例和文档 | 较少,仍在建设中 |
| 装饰器 | @State, @Prop, @Link, @Provide, @Consume | @Local, @Param, @Provider, @Consumer |
| 性能 | 优 | 更优(精细化更新) |
| 兼容性 | API Level 12+ 完整支持 | API Level 24+ 完整支持 |
| 迁移风险 | 低 | 中(API 可能变更) |
决策结论
选择 方案 A(V1 状态管理),理由:
9.7 决策记录汇总表
| ADR-001 | 视觉识别方案 | 贴纸刻度读取 | 精度与成本平衡,MVP 友好 | OpenCV NAPI 真实识别 |
| ADR-002 | 多瓶监控方案 | 快速切换 | MVP 简化,覆盖主要场景 | VisionService 多目标检测 |
| ADR-003 | 家属端方案 | 同 App 角色切换 | 开发成本低,代码复用 | 可拆分为独立 App |
| ADR-004 | 数据存储方案 | Preferences | MVP 轻量,开发效率高 | 迁移至 RDB |
| ADR-005 | 药物数据库方案 | ArkTS 静态数组 | 类型安全,零加载开销 | rawfile JSON + 数据更新 |
| ADR-006 | 状态管理方案 | V1 (@Component/@State) | 成熟稳定,文档丰富 | 渐进迁移至 V2 |
决策原则总结
回顾以上 6 个核心技术决策,可以发现一个统一的决策原则:MVP 优先、渐进演进。
每个决策都在"当前最优"和"未来可扩展"之间取得了平衡:
这种"小步快跑"的技术策略,确保 IVGuard 能够快速验证产品假设,同时不因过早优化而浪费资源,也不因架构缺陷而阻碍未来演进。
附录
附录 A:术语表
| 静脉输液 | Intravenous (IV) Therapy | 通过静脉途径将液体、药物输入体内的治疗方法 |
| 液位 | Level | 输液袋中液体的剩余量,以百分比表示 |
| 贴纸 | Smart Sticker | IVGuard 专用的输液袋标识贴纸,含刻度和 NFC 芯片 |
| 预警 | Alert | 液位低于阈值时触发的分级警告 |
| POI | Point of Interest | 兴趣点,医院内的科室、设施等位置 |
| 相互作用 | Drug Interaction | 两种或多种药物同时使用时产生的不良影响 |
| 医保 | Medical Insurance | 基本医疗保险,用于报销部分医疗费用 |
| 起付线 | Deductible | 医保开始报销前需个人先行支付的费用额度 |
| 封顶线 | Annual Cap | 医保年度报销的最高限额 |
| NFC | Near Field Communication | 近场通信技术,用于贴纸快速配对 |
| SOA | Service-Oriented Architecture | 服务导向架构 |
| MVP | Minimum Viable Product | 最小可行产品 |
| ADR | Architecture Decision Record | 架构决策记录 |
| KV | Key-Value | 键值对数据模型 |
| RDB | Relational Database | 关系型数据库 |
| AOT | Ahead-Of-Time | 提前编译 |
| NAPI | Native API | 原生接口,用于调用 C/C++ 库 |
网硕互联帮助中心





评论前必须登录!
注册