最近刚好在做鸿蒙版的心理健康评估模块,接手了一个看似简单、细想却有不少讲究的需求:用 React Native 实现 PHQ-9 / GAD-7 标准化量表的评分功能,核心机制是“选项索引映射评分”——用户从“完全没有”“几天”“一半以上天数”“几乎每天”这类选项里选一个,系统把选项对应的索引值转成分数,累加出总分。接到这个需求的第一反应是“这不就是个数组下标取值吗”,真正落地时才发现,跨端一致性、量表规则的严谨性、鸿蒙适配的兼容性,每一样都是坑。这篇文章就把完整实现思路、鸿蒙实测踩坑记录和评分校验方法一次说清楚,给正在做跨平台健康类 App 的开发者一个可复用的参考。
1. 需求拆解与整体设计思路
1.1 量表的评分规则,和普通问卷到底差在哪
PHQ-9(患者健康问卷抑郁量表)和 GAD-7(广泛性焦虑障碍量表)都是临床上广泛使用的标准化自评工具。它们的计分方式很明确:每个条目按症状出现频率分为四级,依次对应 0 到 3 分,所有条目得分相加得到总分,再用总分区间划分严重程度。
以 PHQ-9 为例,9 个条目,每个条目 0 到 3 分,总分范围是 0 到 27 分。GAD-7 是 7 个条目,总分范围 0 到 21 分。评分的核心难点不在加法本身,而在“选项文本”和“分值”的映射关系不能出错。用户看到的是中文选项“几天”,程序内部必须稳定地把它识别为 1 分,而不是靠字符串匹配时多一个空格、换一个语言就崩掉。
这里的关键认知是:标准化量表的评分依据是“选项所在的位置”,而不是选项文本本身。换句话说,无论选项文案怎么翻译、怎么改写,只要它在选项列表里的顺序不变,对应的分值就不变。这就是“索引映射”的底层逻辑。
1.2 为什么不能把分数直接硬编码到选项对象里
很多同学第一版会这么写:
typescript复制const options = [
{ label: '完全没有', value: 0 },
{ label: '几天', value: 1 },
{ label: '一半以上天数', value: 2 },
{ label: '几乎每天', value: 3 },
];
看起来没问题,但一旦遇到这几种情况就会翻车:
- 产品经理说“我要在‘几天’和‘一半以上天数’之间加一个选项”,直接在所有选项对象里插入一项,后面所有 value 都要跟着改。
- 多语言版本中,日文、英文的选项中“几天”的排序可能不同,字符串匹配就会错位。
- 如果选项列表被服务端动态下发,服务端给数组重新排序后,value 字段就会和排序产生冲突。
用索引值作为分数依据,本质上是一种“约定优于配置”的思路:选项数组的顺序就是分值的顺序,数组第几项就代表几分。这样一来,选项文案、翻译、增删都不会影响计分逻辑,只需要保证数组顺序和量表标准一致。
1.3 方案选型:为什么在鸿蒙场景下选 React Native
这个项目原本的跨平台诉求是 iOS、Android、鸿蒙三端共用一套逻辑。React Native 的跨平台能力成熟,社区生态丰富,而鸿蒙这边也有 React Native for OpenHarmony(社区简称 RNOH)的适配版本,基础组件和核心 API 已经能做到大部分兼容。
选 React Native 而不是纯 ArkUI 开发,原因很实际:团队已经有一套完整的 RN 组件库和业务代码,直接迁移到鸿蒙可以省掉大量重复开发。但要注意的是,RN 在鸿蒙上的兼容性没有 iOS/Android 那么“无脑”,UI 组件层级复杂时尤其需要做真机验证。
在我实际测试中,React Native 的老架构(Bridge 模式)在鸿蒙上兼容性更好,新架构(Fabric + TurboModule)虽然官方适配持续推进,但部分第三方原生模块还没有完全迁移。所以核心原则是:业务逻辑全用纯 JS 实现,不依赖原生模块,评分计算这种逻辑放在逻辑层,完全避开鸿蒙适配差异的风险点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心实现:选项索引映射评分的完整设计
2.1 选项模型与分数映射表的定义
第一步,定义一套和量表规则严格对应的选项模型。这里不直接用 value 字段存分数,而是用一个独立的常量数组来定义选项顺序:
typescript复制// 频率选项,顺序对应 PHQ-9 / GAD-7 的标准计分规则
export const FREQUENCY_OPTIONS = [
{ key: 'not_at_all', label: '完全没有' },
{ key: 'several_days', label: '几天' },
{ key: 'more_than_half', label: '一半以上天数' },
{ key: 'nearly_every_day', label: '几乎每天' },
] as const;
再看分数映射表。这个表是整个评分机制的核心,它不依赖选项对象里的某个字段,而是直接建立“选项 key 到分值”的稳定对应关系:
typescript复制export const SCORE_MAP: Record<string, number> = {
not_at_all: 0,
several_days: 1,
more_than_half: 2,
nearly_every_day: 3,
};
为什么要用 key
