【喵汪星球HarmonyOS 6.0】技术实战 09:健康症状库、关键词匹配与应急就医卡
前言
健康模块是养宠 App 里最敏感的一部分。用户发现宠物异常时,往往已经很焦虑。App 能做的是帮用户整理信息、识别明显风险、准备就医材料,而不是给出“诊断结论”。
喵汪星球的健康模块选择了一个克制的方案:本地症状库 + 关键词匹配 + 风险提示 + 就医信息卡。它不联网、不做诊断、不替代兽医,只帮助用户把信息组织得更清楚。
这篇会讲:
- 如何设计症状内容模型
- 如何做本地关键词匹配
- 如何给未匹配输入做高风险兜底
- 如何把宠物档案和当天记录聚合成就医卡
- 健康类功能在文案和产品边界上的取舍
先说边界:健康模块不是问诊
项目里多处出现类似提示:
该功能仅供娱乐参考,症状比对不能替代兽医诊断或治疗建议这个提示不是走形式,而是功能设计原则。
健康模块只做三件事:
第一,帮用户把“我观察到了什么”映射到可能相关的症状条目。
第二,提醒用户哪些情况需要尽快就医。
第三,把宠物档案、近期记录和准备清单整理成就医信息卡。
它不做处方、不推荐药、不说“就是某个病”。这类边界写清楚,产品才安全。
症状模型:内容必须结构化
项目中的症状文章模型:
interface SymptomArticle { title: string risk: string summary: string doctor: string observe: string checklist: string keywords: string[] }字段分别对应页面信息块:
title:症状或疾病条目名称。risk:风险等级,例如高风险、中风险、低中风险。summary:常见表现。doctor:什么情况下尽快就医。observe:在家先观察哪些指标。checklist:去医院前准备哪些材料。keywords:用于本地关键词匹配。
示例条目:
{ title: '猫传腹', risk: '高风险', summary: '可能表现为持续发热、精神差、消瘦、腹围变大、胸腹水、呼吸费力或眼部神经症状。', doctor: '反复发热、腹部明显变大、呼吸费力、黄疸、眼睛异常或神经症状,建议尽快检查', observe: '体温曲线、体重变化、腹围、食欲、呼吸、眼睛和行动协调性', checklist: '体温记录、腹部照片、体重变化、血检和影像资料、发病时间线', keywords: ['猫传腹', '传腹', 'fip', '腹水', '胸水', '腹围大', '肚子大'] }为什么不写成长文本?
因为用户在异常场景下没有耐心读大段内容。结构化字段能让页面直接分区展示,也方便后续导出就医卡。
健康页交互流程
页面的主流程如下:
```mermaid flowchart TD A["用户输入症状描述"] --> B["normalizeSymptomText 归一化"] B --> C["遍历症状库 keywords"] C --> D{"是否命中关键词"} D -->|是| E["选择分数最高的 SymptomArticle"] D -->|否| F["展示未匹配兜底建议"] E --> G["展示风险等级、表现、何时就医、观察指标、准备清单"] G --> H{"是否已建档"} H -->|是| I["生成就医信息卡"] H -->|否| J["引导添加宠物档案"] ```这条链路的核心不是算法多复杂,而是每一步都要给用户明确反馈。
输入分析:空输入、未匹配、匹配成功都要处理
用户点击“分析”后:
private analyzeSymptomInput(): void { const value = this.symptomInput.trim() if (value.length === 0) { this.selectedSymptomIndex = -1 this.customHealthAdvice = '请输入观察到的症状,例如呕吐、腹泻、精神萎靡。' this.emergencyCardVisible = false this.saveState() return } const matchedIndex = this.matchSymptomIndex(value) if (matchedIndex < 0) { this.selectedSymptomIndex = -1 this.customHealthAdvice = '暂时识别不出来。请补充体温、食欲、饮水、排便排尿、精神状态和症状持续时间;如果已经精神差、不吃不喝、呼吸异常、抽搐、便血、尿不出或明显疼痛,请尽快就医。' } else { this.selectedSymptomIndex = matchedIndex const symptom = this.symptoms[matchedIndex] this.customHealthAdvice = '可能相关症状:' + symptom.title + '。匹配到:' + this.matchedKeywordText(value, symptom) + '。这只是本地症状比对,不能替代兽医诊断;请继续观察并按“何时就医”处理。' } this.emergencyCardVisible = false this.saveState() }这个方法的质量在于兜底。
空输入时,不是报错,而是教用户怎么描述。
未匹配时,不说“没事”,而是提示补充体温、食欲、饮水、排便排尿、精神状态和持续时间,并列出高风险症状。
匹配成功时,用“可能相关症状”表达,并明确不能替代兽医诊断。
健康类功能最怕两个极端:一个是吓用户,一个是安慰过头。这里的处理相对稳。
关键词匹配:MVP 阶段要可解释
匹配算法很简单:
private matchSymptomIndex(value: string): number { const normalized = this.normalizeSymptomText(value) let bestIndex = -1 let bestScore = 0 for (let index = 0; index < this.symptoms.length; index++) { let score = 0 const keywords = this.symptoms[index].keywords for (let keyIndex = 0; keyIndex < keywords.length; keyIndex++) { if (normalized.indexOf(this.normalizeSymptomText(keywords[keyIndex])) >= 0) { score++ } } if (score > bestScore) { bestScore = score bestIndex = index } } return bestScore > 0 ? bestIndex : -1 }归一化:
private normalizeSymptomText(value: string): string { return value.toLowerCase().replace(/\s/g, '') }这不是智能诊断,但它有三个优点:
第一,完全本地运行,符合离线优先。
第二,可解释。用户能看到匹配到了哪些关键词。
第三,内容库可控。新增一个症状条目,只需要补内容和关键词,不会引入黑盒判断。
MVP 阶段,我更推荐这种可解释的规则系统,而不是一上来就接一个无法解释的模型。
展示命中的关键词
为了避免用户觉得结果是“凭空猜的”,项目会拼出命中的关键词:
private matchedKeywordText(value: string, symptom: SymptomArticle): string { const normalized = this.normalizeSymptomText(value) let text = '' let count = 0 for (let index = 0; index < symptom.keywords.length; index++) { const keyword = symptom.keywords[index] if (normalized.indexOf(this.normalizeSymptomText(keyword)) >= 0) { if (count > 0) { text += '、' } text += keyword count++ } } return text }比如用户输入“肚子大腹水”,页面提示“匹配到:腹水、肚子大”。这会让本地匹配结果更可信,也方便用户检查自己输入是否准确。
页面结构:风险等级必须醒目
健康页核心卡片包含:
Text(this.currentSymptom().title) Text('根据输入关键词本地比对') Text(this.currentSymptom().risk) Text(this.currentSymptom().summary) this.InfoRow('何时就医', this.currentSymptom().doctor) this.InfoRow('观察指标', this.currentSymptom().observe) this.InfoRow('准备清单', this.currentSymptom().checklist)风险等级使用不同颜色:
Text(this.currentSymptom().risk) .fontColor( this.currentSymptom().risk === '高风险' || this.currentSymptom().risk === '需就医' ? Theme.emergency : Theme.warning ) .backgroundColor( this.currentSymptom().risk === '高风险' || this.currentSymptom().risk === '需就医' ? Theme.emergencySoft : Theme.warningSoft )健康功能里,“高风险”不能藏在段落里。它应该是用户扫一眼就能看到的状态。
就医信息卡:把档案和记录变成现场沟通材料
症状详情页可以生成就医卡:
if (this.emergencyCardVisible && this.hasPetProfile) { Column({ space: Theme.spaceS }) { Text('就医信息卡') Text(this.emergencyPetSummary()) Text('症状:' + this.currentSymptom().title + ' · ' + this.currentSymptom().risk) Text(this.emergencyRecentRecordText()) } }宠物摘要:
private emergencyPetSummary(): string { if (!this.hasPetProfile) { return '尚未添加宠物档案' } return this.selectedPetName + ' · ' + this.petMeta() }最近记录:
private emergencyRecentRecordText(): string { const records = this.recordsForSelectedDay() if (records.length === 0) { return '今天还没有补充记录,建议记录食欲、饮水、排便排尿和精神状态。' } return records[0].title + ':' + records[0].detail }应急携带:
private emergencyKitText(): string { return '带上近期照片/视频、叫声录音、疫苗驱虫记录、发病时间线和正在使用的药品。' }这个功能的价值在于“帮用户和医生沟通”。很多人一着急就忘记描述时间线、饮食饮水、排便排尿、疫苗驱虫记录。就医卡能把 App 里已有的数据组织起来。
为什么健康模块要绑定宠物档案
如果没有宠物档案,症状库也能查,但就医卡价值会低很多。
有档案后,就医卡能带上:
- 名字
- 品种或类型
- 年龄阶段
- 体重
- 性别和绝育状态
- 疫苗信息
- 驱虫信息
- 过敏史
- 慢性病史
这些信息看似基础,但在实际就医沟通中非常重要。宠物档案不是孤立页面,它在健康场景里会变成应急信息的一部分。
未匹配输入的高风险兜底
我认为健康模块最值得保留的是这段未匹配文案:
如果已经精神差、不吃不喝、呼吸异常、抽搐、便血、尿不出或明显疼痛,请尽快就医。为什么?
因为关键词库再怎么扩,也不可能覆盖用户所有描述。用户可能输入“它不对劲”“一直趴着”“叫得很惨”。匹配不到时,App 不能给用户安全错觉。
所以未匹配并不等于低风险。未匹配只是说明本地知识库暂时识别不了,仍然要提示用户观察高风险信号。
后续可以怎么升级
当前症状库写在 ArkTS 数组里。后续可以迁移:
resources/rawfile/symptoms.json -> KnowledgeRepository -> SymptomService -> HealthPage还可以增加:
- 同义词词典,例如“拉稀”和“腹泻”。
- 高风险关键词强规则,例如“尿不出”“抽搐”“呼吸困难”直接置顶。
- 多症状组合匹配,例如“呕吐 + 腹泻 + 幼猫 + 未免疫”提高风险提示。
- 最近 7 天记录自动摘要。
- 就医卡导出图片或 PDF。
- 症状内容版本号,方便后续更新。
但无论怎么升级,都要保留“不替代兽医诊断”的边界。
这部分最容易踩的坑
第一,健康内容不能写成诊断结论。尽量使用“可能相关”“建议尽快检查”“不能替代诊断”。
第二,未匹配时不能说“没问题”。要提示补充信息和高风险情况。
第三,风险等级必须醒目。用户异常场景下不会细读长文。
第四,症状库要结构化。否则后续就医卡、搜索、导出都很难做。
第五,不要让就医卡只展示静态清单。它要聚合宠物档案和近期记录,才有真实价值。
本篇小结
健康模块的核心不是算法炫技,而是稳:
- 用结构化症状库承载内容。
- 用本地关键词匹配保证离线可用。
- 用命中关键词增强可解释性。
- 用高风险兜底处理未匹配输入。
- 用就医信息卡聚合档案和记录。
- 用克制文案守住产品边界。
下一篇是系列收尾:主题系统、暗色模式、多设备适配和发布前体验打磨。一个 App 能不能像正式产品,很多时候就看最后这些细节。
