欢迎光临
我们一直在努力

鸿蒙 RecommendData 这类卡片数据层应该怎么组织

适合谁看

  • 想写可维护鸿蒙卡片数据层的人

  • 正在做每日推荐、排行榜、轮播类鸿蒙卡片的人

  • 不想把数据硬写进鸿蒙 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: Set<string> = 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 个字段。这让数据项结构先稳定下来。

字段设计的考量:

字段

类型

用途

为什么需要

id

string

点击跳转到菜品详情页

卡片点击需要传参数

name

string

卡片上显示菜名

核心展示信息

region

string

卡片上显示地区

帮助用户判断兴趣

imageResName

string

鸿蒙图片资源名

卡片需要显示图片

highlight

string

口味亮点标签

吸引用户点击

summary

string

一句话介绍

补充信息,帮助决策

这个结构既不过于复杂(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: Set<string> = 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];
    }

    日期轮询算法:用 年月日 生成数字,对列表长度取模。

    日期

    dateNum

    index (mod 10)

    菜品

    2025-01-15

    20250115

    5

    牛肉塔可

    2025-01-16

    20250116

    6

    石锅拌饭

    2025-01-17

    20250117

    7

    班尼迪克蛋

    这个算法的特点:

    • 同一天内结果一致 — 所有卡片展示同一道菜

    • 不同天自动切换 — 不需要手动更新

    • 列表为空时兜底 — 返回 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.ets(Ability 层)

    ├─ 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: Set<string> = 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_LIST + FALLBACK_ITEM

  • 资源校验完善 — VALID_IMAGE_RES_NAMES 白名单 + resolveImageResName 兜底

  • 选择算法轻量 — 日期轮询,不依赖网络

  • 职责分离 — 数据层管内容,Ability 管生命周期

  • 先把数据结构、兜底和轮换规则单独收好,后面维护会轻很多。这份代码很适合当鸿蒙卡片数据层组织的第一块样板。

    赞(0)
    未经允许不得转载:171主机测评 » 鸿蒙 RecommendData 这类卡片数据层应该怎么组织
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址