适合谁看想写可维护鸿蒙卡片数据层的人正在做每日推荐、排行榜、轮播类鸿蒙卡片的人不想把数据硬写进鸿蒙 Ability 文件的人问题背景鸿蒙卡片代码很容易越写越乱的一个原因是Ability 管生命周期Ability 还管数据Ability 还管兜底Ability 还管资源校验最后所有逻辑都挤在同一个文件里改一个数据要动 Ability改一个兜底要动 Ability改一个资源名也要动 Ability。项目中的真实场景食界探味当前把鸿蒙卡片数据层单独放在app/ohos/entry/src/main/ets/formability/RecommendData.ets对应的 Ability 只负责消费它// DailyRecommendFormAbility.ets import { getRecommendOfToday, resolveImageResName } from ./RecommendData; onAddForm(want: Want): formBindingData.FormBindingData { const item getRecommendOfToday(); return formBindingData.createFormBindingData({ dishName: item.name, dishImage: resolveImageResName(item.imageResName), // ... }); }Ability 不关心数据从哪来、怎么选、怎么校验只关心拿数据 → 绑定到卡片。核心实现一、RecommendData.ets 的完整结构// 1. 数据结构定义 export interface RecommendItem { id: string; // 菜品 ID用于点击跳转 name: string; // 菜名 region: string; // 地区 imageResName: string; // 鸿蒙图片资源名 highlight: string; // 口味亮点 summary: string; // 一句话介绍 } // 2. 推荐列表10 道菜品 const RECOMMEND_LIST: RecommendItem[] [ { id: beef-curry, name: 牛肉咖喱, region: 印度 · 亚洲, imageResName: dish_beef_curry, highlight: 浓郁香料, summary: 椰香与香料层层叠起入口热烈又厚实。 }, { id: sukiyaki, name: 寿喜锅, region: 日本 · 亚洲, imageResName: dish_sukiyaki, highlight: 鲜甜酱香, summary: 牛肉、蔬菜与寿喜烧汁一起慢慢煮到刚好。 }, // ... 共 10 道菜品 ]; // 3. 兜底数据 const FALLBACK_ITEM: RecommendItem { id: fallback, name: 环球美食, region: 世界, imageResName: dish_fallback, highlight: 今天吃什么, summary: 打开食界探味挑一道想去认识的新菜。, }; // 4. 图片资源白名单 const VALID_IMAGE_RES_NAMES: Setstring new Set( RECOMMEND_LIST.map((item) item.imageResName).concat(FALLBACK_ITEM.imageResName) ); // 5. 今日选择算法 export function getRecommendOfToday(): RecommendItem { ... } // 6. 兜底获取 export function getFallbackItem(): RecommendItem { ... } // 7. 图片资源校验 export function resolveImageResName(imageResName: string): string { ... }这个文件承担了 7 类职责每一类都值得单独分析。二、职责 1定义卡片数据结构export interface RecommendItem { id: string; name: string; region: string; imageResName: string; highlight: string; summary: string; }通过RecommendItem明确了卡片需要的 6 个字段。这让数据项结构先稳定下来。字段设计的考量字段类型用途为什么需要idstring点击跳转到菜品详情页卡片点击需要传参数namestring卡片上显示菜名核心展示信息regionstring卡片上显示地区帮助用户判断兴趣imageResNamestring鸿蒙图片资源名卡片需要显示图片highlightstring口味亮点标签吸引用户点击summarystring一句话介绍补充信息帮助决策这个结构既不过于复杂6 个字段也不过于简单包含了点击跳转需要的id。三、职责 2维护推荐列表和兜底项const RECOMMEND_LIST: RecommendItem[] [ // 10 道菜品... ]; const FALLBACK_ITEM: RecommendItem { id: fallback, name: 环球美食, region: 世界, imageResName: dish_fallback, highlight: 今天吃什么, summary: 打开食界探味挑一道想去认识的新菜。, };RECOMMEND_LIST是正常推荐数据FALLBACK_ITEM是兜底数据。兜底项的价值场景没有兜底有兜底列表为空卡片显示空白显示环球美食图片资源不存在卡片渲染崩溃显示兜底图片数据异常用户看到异常用户看到正常兜底鸿蒙卡片比应用页面更怕显示异常——因为卡片在桌面上异常会一直显示用户无法刷新。四、职责 3图片资源校验const VALID_IMAGE_RES_NAMES: Setstring new Set( RECOMMEND_LIST.map((item) item.imageResName).concat(FALLBACK_ITEM.imageResName) ); export function resolveImageResName(imageResName: string): string { if (!imageResName || !VALID_IMAGE_RES_NAMES.has(imageResName)) { return FALLBACK_ITEM.imageResName; } return imageResName; }VALID_IMAGE_RES_NAMES是鸿蒙图片资源的白名单。resolveImageResName会检查传入的资源名是否在白名单中如果不在就返回兜底图片。这个设计的好处防止资源不存在导致崩溃— 鸿蒙卡片如果引用了不存在的$r()资源会直接崩溃新增菜品时只需加白名单— 不需要改校验逻辑兜底行为明确— 用户永远看不到空白图片五、职责 4今日选择算法export function getRecommendOfToday(): RecommendItem { if (RECOMMEND_LIST.length 0) return FALLBACK_ITEM; const now new Date(); const dateNum now.getFullYear() * 10000 (now.getMonth() 1) * 100 now.getDate(); const index dateNum % RECOMMEND_LIST.length; return RECOMMEND_LIST[index]; }日期轮询算法用年月日生成数字对列表长度取模。日期dateNumindex (mod 10)菜品2025-01-15202501155牛肉塔可2025-01-16202501166石锅拌饭2025-01-17202501177班尼迪克蛋这个算法的特点同一天内结果一致— 所有卡片展示同一道菜不同天自动切换— 不需要手动更新列表为空时兜底— 返回FALLBACK_ITEM不依赖网络— 纯本地计算六、职责 5兜底获取export function getFallbackItem(): RecommendItem { return FALLBACK_ITEM; }单独导出兜底项方便其他地方使用比如卡片 UI 的默认值。七、数据层和 Ability 的分工RecommendData.ets数据层 │ ├─ RecommendItem ← 数据结构 ├─ RECOMMEND_LIST[] ← 推荐列表 ├─ FALLBACK_ITEM ← 兜底数据 ├─ VALID_IMAGE_RES_NAMES ← 资源白名单 ├─ getRecommendOfToday() ← 选择算法 ├─ getFallbackItem() ← 兜底获取 └─ resolveImageResName() ← 资源校验 DailyRecommendFormAbility.etsAbility 层 │ ├─ onAddForm() ← 消费数据层 ├─ onUpdateForm() ← 消费数据层 └─ onRemoveForm() ← 清理资源Ability 只关心拿数据 → 绑定到卡片不关心数据从哪来、怎么选、怎么校验。八、为什么这种组织方式值得复用好处说明职责清晰数据层管内容Ability 管生命周期易于测试数据层可以独立测试不需要启动 Ability易于扩展新增菜品只需加 RECOMMEND_LIST易于维护改数据不影响 Ability改 Ability 不影响数据兜底完善资源异常时不会崩溃关键代码位置文件作用app/ohos/entry/src/main/ets/formability/RecommendData.ets鸿蒙卡片数据层本文核心app/ohos/entry/src/main/ets/formability/DailyRecommendFormAbility.ets消费数据层数据层职责全景图RecommendData.ets │ ├─ 接口定义 │ └─ RecommendItem { id, name, region, imageResName, highlight, summary } │ ├─ 数据存储 │ ├─ RECOMMEND_LIST[] ← 10 道菜品 │ ├─ FALLBACK_ITEM ← 兜底数据 │ └─ VALID_IMAGE_RES_NAMES ← 图片资源白名单 │ ├─ 选择算法 │ └─ getRecommendOfToday() ← 日期轮询 │ ├─ 兜底获取 │ └─ getFallbackItem() ← 返回 FALLBACK_ITEM │ └─ 资源校验 └─ resolveImageResName() ← 白名单校验 兜底常见坑把推荐列表直接写在 Ability 里— 改数据要动 Ability职责混乱没有兜底数据项— 鸿蒙卡片异常时显示空白图片资源名不做校验— 鸿蒙$r()引用不存在的资源会崩溃每次更新时间策略都散落在多个地方— 应该集中管理数据结构不稳定— 新增字段时要改多个文件没有导出数据结构— 其他文件无法复用RecommendItem可复用模板鸿蒙卡片数据层模板// 1. 数据结构 export interface CardItem { id: string; title: string; subtitle: string; imageRes: string; highlight: string; summary: string; } // 2. 数据列表 const ITEM_LIST: CardItem[] [ { id: 1, title: 标题1, subtitle: 副标题1, imageRes: img_1, highlight: 亮点1, summary: 简介1 }, // ... ]; // 3. 兜底数据 const FALLBACK: CardItem { id: fallback, title: 默认标题, subtitle: 默认副标题, imageRes: img_fallback, highlight: 默认亮点, summary: 默认简介, }; // 4. 资源白名单 const VALID_RES: Setstring new Set(ITEM_LIST.map(i i.imageRes).concat(FALLBACK.imageRes)); // 5. 选择算法 export function getTodayItem(): CardItem { if (ITEM_LIST.length 0) return FALLBACK; const now new Date(); const dateNum now.getFullYear() * 10000 (now.getMonth() 1) * 100 now.getDate(); return ITEM_LIST[dateNum % ITEM_LIST.length]; } // 6. 资源校验 export function safeImage(name: string): string { if (!name || !VALID_RES.has(name)) return FALLBACK.imageRes; return name; }数据层职责清单鸿蒙卡片数据层应该包含 □ 数据结构定义export interface □ 数据列表const LIST □ 兜底数据const FALLBACK □ 资源白名单const VALID_RES □ 选择算法getTodayItem □ 资源校验safeImage □ 兜底获取getFallback本篇总结鸿蒙卡片数据层应该独立于 Ability 文件。RecommendData.ets这种组织方式很适合每日推荐类卡片数据结构稳定—RecommendItem接口定义了 6 个字段数据和兜底并存—RECOMMEND_LISTFALLBACK_ITEM资源校验完善—VALID_IMAGE_RES_NAMES白名单 resolveImageResName兜底选择算法轻量— 日期轮询不依赖网络职责分离— 数据层管内容Ability 管生命周期先把数据结构、兜底和轮换规则单独收好后面维护会轻很多。这份代码很适合当鸿蒙卡片数据层组织的第一块样板。