HarmonyOS实战《疆域纪行》第02篇|离线内容库:景点、美食、路线如何用 ArkTS 建模
这一篇专门拆“内容底座”。离线旅行 App 看起来是页面项目,本质上先是内容项目:景点、美食、路线、城市和旅行贴士必须先被结构化,后面的首页推荐、探索筛选、详情页和收藏才能顺起来。
项目落点:
- 类型定义:
entry/src/main/ets/models/AtlasModels.ets - 离线内容:
entry/src/main/ets/data/AtlasData.ets - 图片引用:
entry/src/main/resources/base/media
为什么先做数据模型
做内容型 App 时,最容易犯的错误是先画页面,再在页面里临时拼字段。这样前期看起来快,后期一定会痛苦:详情页要字段,搜索页要字段,收藏页还要字段,最后每个页面都有一套自己的数据假设。
《疆域纪行》的做法是先建模,再写页面。项目里核心类型放在:
entry/src/main/ets/models/AtlasModels.ets
这个文件不长,但决定了整个 App 的内容表达能力。
景点和美食共用 AtlasItem
项目把景点和美食抽象成同一个 AtlasItem:
export interface AtlasItem {
id: string;
kind: string;
title: string;
english: string;
region: string;
summary: string;
tags: string[];
meta: string;
color: string;
image: Resource;
bestSeason: string;
duration: string;
cost: string;
transport: string;
highlights: string[];
shops?: string[];
tips: string[];
}
这个设计很实用。景点和美食虽然业务含义不同,但在列表、收藏、搜索、详情头部里需要的字段非常相似:
- 标题:
title - 英文名:
english - 地区:
region - 简介:
summary - 标签:
tags - 图片:
image - 主题色:
color - 注意事项:
tips
共用模型后,很多 UI 组件可以复用,比如 ListCard、FavoriteAtlasCard、AtlasDetail。真正有差异的部分再用 kind 和可选字段控制,例如美食才会用到 shops。
路线单独建模
路线和景点不一样。路线有天数、每日安排、住宿、餐食、行程亮点,所以项目定义了 RouteItem:
export interface RouteItem {
id: string;
title: string;
days: string;
region: string;
summary: string;
tags: string[];
color: string;
image: Resource;
bestSeason: string;
transport: string;
dailyPlans: string[];
dayDetails: RouteDayDetail[];
tips: string[];
}
export interface RouteDayDetail {
title: string;
meal: string;
stay: string;
highlight: string;
}
这里有一个不错的分层:dailyPlans 用于快速概览,dayDetails 用于路线详情页的时间轴。
也就是说,同一条路线在不同页面可以用不同的信息密度:
- 首页卡片:标题、天数、简介、标签
- 路线列表:封面、区域、适合季节、交通方式
- 详情页:每日安排、住宿、餐食、亮点、提示
这就是内容建模的关键:不要让所有页面都被迫消费完整字段,也不要让详情页缺字段。
城市和贴士保持轻量
城市卡片模型:
export interface CityItem {
id: string;
name: string;
region: string;
summary: string;
image: Resource;
color: string;
linkedRegion: string;
}
旅行贴士模型:
export interface TipItem {
title: string;
summary: string;
}
这两个类型都很克制。它们不追求“一步到位”,只满足当前页面需要。工程里建模不是字段越多越好,而是要匹配产品阶段。MVP 阶段只要城市能参与探索筛选、贴士能展示在“我的”页,就足够了。
本地状态也要建模
除了内容模型,项目还定义了本地状态:
export interface LocalAppState {
favoriteIds: string[];
searchHistory: string[];
itineraryIds: string[];
}
这个模型看起来简单,却很重要。它把“用户数据”和“内容数据”分开了:
- 内容数据:景点、路线、美食,随应用发布。
- 用户数据:收藏、历史、行程,只存在本机。
这样做后,本地存储服务只需要负责 LocalAppState 的读写,页面只需要处理数组状态,不用到处关心 Preferences 的 key 和序列化格式。
离线内容库如何写
内容库放在:
entry/src/main/ets/data/AtlasData.ets
一个景点数据大概长这样:
{
id: 'spot_sayram',
kind: '景点',
title: '赛里木湖',
english: 'Sayram Lake',
region: '北疆-伊犁地区',
summary: '雪山环抱的高山湖泊,是进入伊犁河谷前最经典的湖泊目的地。',
tags: ['湖泊', '雪山', '摄影', '自驾'],
meta: '博尔塔拉 / 伊犁门户 · 5月-9月',
color: '#2F6F9F',
image: $r('app.media.img_sayram'),
bestSeason: '5月-9月',
duration: '3-5小时',
cost: '门票以景区公告为准',
transport: '自驾 / 包车',
highlights: ['环湖公路', '雪山倒影', '日出日落', '松树头观景'],
tips: ['湖区早晚温差大', '夏季也建议带外套', '环湖建议预留半天以上']
}
这里有几个值得学习的点。
第一,id 是稳定主键。收藏、行程、详情页查找都靠它,而不是靠标题。标题可能会改,id 不应该轻易改。
第二,image 使用 Resource:
image: $r('app.media.img_sayram')
这样图片会走 HarmonyOS 的资源系统,打包、引用、预览都更稳定。
第三,color 跟着内容走。每个景点和美食都有自己的主题色,卡片加载图片前可以先显示色块,也能让同一套卡片组件有差异化气质。
第四,tips 不写成长字符串,而是数组。详情页可以逐条渲染,后续也方便做图标化或重要程度排序。
内容库规模
当前项目内置了:
- 20 多个景点
- 6 个美食条目
- 7 条路线
- 8 个城市区域
- 5 条旅行贴士
它不是一个“空壳 Demo”,而是已经具备可浏览内容的离线 App。对旅行攻略类应用来说,内容量直接决定页面是否真实。只有数据足够多,搜索、筛选、空状态、卡片复用才有测试价值。
静态 TS 数据 vs JSON 文件
这个项目选择把内容写在 AtlasData.ets 中,而不是放到 rawfile JSON。两种方式各有取舍。
写在 TS 里的优点:
- 字段有类型检查。
$r()资源引用更直接。- 编译期就能发现字段缺失。
- 小规模内容维护快。
JSON 的优点:
- 内容和代码分离更彻底。
- 后续可以做内容导入、更新包、远程配置。
- 非开发人员也更容易参与维护。
我的建议是:MVP 和小规模内容库,用 TS 静态数据更高效;当内容增长到数百条,再考虑迁移到 JSON、SQLite 或可导入离线包。
数据驱动页面的第一步
在 Index.ets 里,页面直接引用这些内容:
private readonly spots: AtlasItem[] = SPOTS;
private readonly foods: AtlasItem[] = FOODS;
private readonly routes: RouteItem[] = ROUTES;
private readonly cities: CityItem[] = CITIES;
private readonly tips = TIPS;
private getAllItems(): AtlasItem[] {
return [...this.spots, ...this.foods];
}
getAllItems() 是一个小函数,但很有用。搜索、收藏等功能不需要分别处理景点和美食,而是先拿到统一的 AtlasItem[]。
这就是数据建模带来的收益:页面逻辑会变短,组件复用会变自然。
实战经验:内容模型要服务页面,不要炫技
做内容模型时,有两个常见误区:
第一个误区是过度抽象。比如一上来就设计 BaseEntity<T>、ContentBlock、MetaSchema。对 MVP 来说,这会增加理解成本。
第二个误区是完全不抽象。所有字段都散在页面里,列表一份、详情一份、收藏一份,后期改字段会非常痛。
《疆域纪行》的模型刚好处在中间:
- 景点和美食共用
AtlasItem - 路线独立成
RouteItem - 城市和贴士轻量建模
- 本地状态独立成
LocalAppState
这是一个很适合初中级 HarmonyOS 项目的数据模型范式。
本篇小结
这一篇我们完成了离线内容库的设计拆解:
- 用 ArkTS interface 定义业务模型。
- 用稳定 id 连接收藏、详情和行程。
- 用
Resource引用本地图片。 - 用数组字段支持标签、亮点、贴士等多值内容。
- 把内容数据和用户本地状态分开。
下一篇进入 ArkUI 页面实战:如何用大图 Hero、横向卡片、底部导航,搭建一个有旅行杂志感的首页。
更多推荐



所有评论(0)