HarmonyOS律愈实战02:ArkTS五音数据模型设计
ArkTS 数据建模实战:用五音元数据驱动疗愈 App
本文适合标签:ArkTS、HarmonyOS、数据建模、前端架构、五音疗愈
对应文件:entry/src/main/ets/data/models/WuYinData.ets
1. 为什么先做数据模型
很多应用一开始会直接写页面,写着写着就会出现几个问题:
- 五音名称、颜色、脏腑、频率散落在不同页面。
- 首页、疗愈页、卡片页各维护一份数据,后期很容易不一致。
- 推荐算法拿不到统一的 Session 数据。
“律愈”项目的做法是先把五音领域数据抽出来,页面只消费模型。
2. 核心类型设计
五音 key 被限制为联合类型:
exporttypeToneKey='jiao'|'zhi'|'gong'|'shang'|'yu';这样做比直接写string更安全。比如函数参数声明为ToneKey后,调用方就不能随便传入"abc"。
五音元数据模型如下:
exportinterfaceWuYinMeta{key:ToneKey;name:string;element:string;organ:string;emotion:string;color:string;glow:string;visualEmoji:string;description:string;traits:string[];frequencyHz:number;recommendedTime:string;}这里的字段设计比较完整:
name:角音、徵音、宫音、商音、羽音。element/organ:五行和脏腑映射。color/glow:直接服务 UI 主题色。frequencyHz:服务音频生成。traits:服务辨证结果展示。recommendedTime:服务时辰推荐。
这就是一个典型的“领域数据驱动 UI”的写法。
3. Session 数据设计
疗愈内容不是只靠五音本身,还需要一个可播放的 Session:
exportinterfaceSessionItem{id:string;name:string;tone:ToneKey;durationMin:number;visualEmoji:string;}Session 和 Tone 的关系是多对一:一个音色下面可以有多个疗愈场景。
functionmkSession(id:string,name:string,tone:ToneKey,durationMin:number,visualEmoji:string):SessionItem{consts:SessionItem={id:id,name:name,tone:tone,durationMin:durationMin,visualEmoji:visualEmoji};returns;}为什么要写mkSession()?因为 ArkTS 对对象字面量和类型推断有更严格的规则,用显式构造函数可以减少类型检查问题,也让列表数据更统一。
4. 五音顺序很重要
项目里固定了五音顺序:
exportconstTONE_ORDER:ToneKey[]=['jiao','zhi','gong','shang','yu'];这个顺序不仅用于 UI 展示,还用于辨证算法的分数数组。例如一个问题选项的分数[3,0,0,0,0]表示角音加 3 分。只要顺序固定,数组评分就可以非常轻量。
functiontoneScores(a:number,b:number,c:number,d:number,e:number):number[]{constout:number[]=[];out.push(a);out.push(b);out.push(c);out.push(d);out.push(e);returnout;}5. 查询函数封装
页面并不直接遍历元数据,而是通过函数取值:
exportfunctionwuYinMetaOf(tone:ToneKey):WuYinMeta{switch(tone){case'jiao':returnWU_JIAO;case'zhi':returnWU_ZHI;case'gong':returnWU_GONG;case'shang':returnWU_SHANG;case'yu':returnWU_YU;default:returnWU_GONG;}}这种写法看起来比Record<ToneKey, WuYinMeta>啰嗦,但在 ArkTS 严格检查场景下更稳。尤其是项目启用了严格构建选项后,显式 switch 更容易通过校验。
6. 时辰推荐模型
首页“今日时辰推荐”来自HourRecommendRow:
exportinterfaceHourRecommendRow{start:number;end:number;tone:ToneKey;title:string;sessionId:string;}根据当前小时选择推荐音色:
exportfunctiongetToneByHour(hour:number):ToneKey{for(constrowofHOUR_RECOMMEND){if(hour>=row.start&&hour<row.end){returnrow.tone;}}return'gong';}再根据sessionId找到首页展示的 Session:
exportfunctiongetHeroSessionForHour(hour:number):SessionItem{for(constrowofHOUR_RECOMMEND){if(hour>=row.start&&hour<row.end){constfound=SESSION_LIST.find((s:SessionItem)=>s.id===row.sessionId);if(found){returnfound;}}}returnSESSION_LIST.find((s:SessionItem)=>s.id==='gong_01')asSessionItem;}7. 数据流图
8. 这一层的设计收益
这套模型带来三个明显收益:
- UI 只负责展示,不需要知道每个音色的细节。
- 算法可以直接复用五音顺序和 Session 列表。
- 后续扩展真实音频、更多疗愈场景、更多问卷题目时,不需要大改页面结构。
如果你也在写 HarmonyOS 项目,建议先把“会被多个页面共同使用的数据”抽出来。尤其是音频、内容库、推荐算法这类应用,数据模型的稳定程度会直接影响后续开发效率。
9. ArkTS 严格模式下的数据建模经验
HarmonyOS ArkTS 对类型和对象字面量的要求比普通 JavaScript 更严格。律愈项目里没有大量使用松散的 ny,而是用明确的接口约束领域数据,这一点很适合写给初学者。
s export interface WuYinMetaMap { jiao: WuYinMeta; zhi: WuYinMeta; gong: WuYinMeta; shang: WuYinMeta; yu: WuYinMeta; }
这段代码看起来只是声明了一个 map,但它解决的是“键名必须完整且合法”的问题。如果使用普通对象随意拼接 key,页面在读取 wuYinMetaOf(tone) 时就可能遇到空值。
10. 为什么不用魔法字符串
项目中很多地方都要判断五音,例如播放器、卡片、辨证结果、疗愈库过滤。如果都写成字符串,很容易出现 jiao、Jiao、jue 混用的问题。联合类型能把错误提前到编译期。
s function parseToneKey(raw: string): ToneKey { if (raw === 'jiao' || raw === 'zhi' || raw === 'gong' || raw === 'shang' || raw === 'yu') { return raw; } return 'gong'; }
这种解析函数尤其适合用在 Want 参数、卡片消息、Preferences 存储读取这些“外部输入”场景。原则是:内部使用强类型,外部输入先校验再进入系统。
11. Session 列表如何服务多个页面
SESSION_LIST 同时被首页、疗愈页、疗愈库和辨证结果页使用。它的好处是所有页面看到的是同一批内容:
` s
export function sessionsForTone(tone: ToneKey): SessionItem[] {
return SESSION_LIST.filter((s: SessionItem) => s.tone === tone);
}
export function sessionById(id: string): SessionItem | undefined {
return SESSION_LIST.find((s: SessionItem) => s.id === id);
}
`
这段封装的价值在于:列表过滤函数看似简单,但它把数据查询集中到了模型文件中,页面不用重复写 filter 逻辑。
12. 可扩展字段设计
如果后续要把律愈做成更完整的内容型应用,可以在不破坏现有结构的情况下继续加字段:
s export interface SessionItem { id: string; name: string; tone: ToneKey; durationMin: number; visualEmoji: string; cover?: Resource; audioAsset?: string; tags?: string[]; scene?: 'sleep' | 'focus' | 'relax' | 'breath'; }
这说明一开始的模型设计不需要非常复杂,但要给未来扩展留下位置。文章可以把这点作为“从 Demo 到产品”的思考。
reath’;
}
`
这说明一开始的模型设计不需要非常复杂,但要给未来扩展留下位置。文章可以把这点作为“从 Demo 到产品”的思考。
