【三国志 App 实战系列 04】人物详情页与本地内容模型设计:让传记、年表和听书入口统一起来
系列第 4 篇。本文介绍三国人物详情页如何设计数据模型与页面结构,让阅读、收藏、笔记、听书形成统一体验。

一、人物详情页的信息结构
一个完整的人物详情页至少需要:
- 头像与基础信息
- 势力、年份、标签
- 人物简介
- 生平经历
- 关键事迹
- 评价与影响
- 年表
- 相关人物
- 收藏、笔记、听书入口
如果把这些内容都塞进一个长页面,用户会很累。更好的方式是使用 Tab 分组。
二、本地内容模型
人物模型通常包含基础字段和富文本字段:
export class Person {
id: string = '';
name: string = '';
courtesyName: string = '';
factionId: string = '';
years: string = '';
summary: string = '';
avatar: ResourceStr = '';
tags: string[] = [];
biography: string = '';
achievements: string[] = [];
comments: string[] = [];
timeline: string[] = [];
relatedIds: string[] = [];
}
实际项目中可以根据内容复杂度拆分更多字段,但核心原则是:详情页显示的文本,就是听书朗读的内容来源。
三、听书入口为什么不能只依赖播放列表
一开始很多人会把“播放列表”当作唯一音频来源。但用户如果清空播放列表,再从人物详情点“听书”,就会找不到音频。
正确做法是分离:
- 音频模板目录:能力来源,不被用户删除
- 用户播放队列:用户当前列表,可编辑可清空
示例:
private ensureAudioInList(targetId: string): AudioRecord {
const existed = this.audios().find((item: AudioRecord) => item.targetId === targetId);
if (existed) {
return existed;
}
const template = this.audioCatalog().find((item: AudioRecord) => item.targetId === targetId);
const next = new AudioRecord(
template.id,
template.targetId,
template.title,
template.durationText,
0,
template.durationSeconds
);
this.audioRecords = this.audioRecords.concat([next]);
return next;
}
四、收藏与笔记的 targetId 设计
收藏和笔记不要直接绑定页面路径,而应绑定业务目标:
interface FavoriteJson {
id: string;
targetId: string;
targetType: string;
title: string;
summary: string;
createdAt: string;
updatedAt: string;
}
这样人物、事件、文章、地图标记都可以复用收藏逻辑。
五、页面布局建议
人物详情页适合卡片式分层:
Column() {
this.personHeader(person);
this.personActionBar(person);
this.personTabs(person);
this.personTabContent(person);
}
.padding(AppSpacing.S20)
.backgroundColor(this.palette().pageBg)
视觉上,头像和姓名是焦点;生平正文是阅读核心;听书按钮是高频操作入口。
六、避免内容和功能脱节
在这个项目中,一个人物详情页如果有听书按钮,就必须满足:
Person.id能匹配AudioRecord.targetId- 传记文本不为空
- 收藏 target 存在
- 搜索能命中姓名、简介、关键词
这类覆盖校验越早做越好。
七、详情页状态流设计
从工程角度看,人物详情页不是“展示一份静态人物资料”,而是把三类状态拼到一起:
- 人物模板:来自本地人物仓库,只读
- 用户状态:收藏、笔记、听书进度,可变
- 页面状态:当前选中的 Tab、滚动位置、是否展开摘要,短生命周期
如果这三类状态混写在一个对象里,后续最容易出现的问题是:
- 页面刷新后收藏状态丢失
- 编辑笔记时误改人物模板
- 切换人物后保留上一位人物的 Tab 或音频状态
比较稳的做法是把详情页渲染状态显式收敛成组合对象:
interface PersonDetailViewState {
person: PersonModel;
favorite: FavoriteRecord | null;
note: NoteRecord | null;
audio: AudioRecord | null;
selectedTab: PersonTab;
}
页面渲染永远依赖这个组合状态,而不是从多个 service 零散读值。这样后续要做平板双栏布局、返回态恢复、断点续读时,状态边界会清楚很多。
八、调试命令与联调方法
人物详情页最容易出错的不是排版,而是状态联动。开发阶段我会优先用以下命令验证安装、拉起和日志:
hdc list targets
hdc install -r .\entry\build\default\outputs\default\entry-default-signed.hap
hdc shell aa force-stop com.example.recordofthreekingdoms
hdc shell aa start -a EntryAbility -b com.example.recordofthreekingdoms
hdc shell hilog | Select-String -Pattern "Person|Favorite|Audio|Note"
如果发现“详情页能打开,但听书按钮点了没反应”,优先检查这三件事:
Person.id是否和AudioRecord.targetId一致- 详情页动作栏点击后是否真的调用了 service
- 页面状态更新后是否触发了 ArkUI 重建
例如在详情页动作栏里,可以先加最小日志确认链路:
private async handleListen(person: PersonModel) {
hilog.info(0x0000, 'PersonPage', 'listen target=%{public}s', person.id);
const audio = await this.audioService.ensureForTarget(person.id);
this.currentAudio = audio;
}
这类日志看起来简单,但能很快区分“按钮事件没触发”和“service 返回了错误数据”。
九、常见坑位与修复策略
1. 详情页收藏状态延迟刷新
如果只在详情页本地翻转一个 boolean,收藏页或首页统计可能不会同步。更稳的方式是:
- 先执行 service 层写入
- 再重新读取当前
targetId的收藏状态 - 最后整体回写详情页 view state
2. 笔记 target 绑定错对象
很多内容型 App 初版会把笔记只挂到“当前页面路由”,这会导致从收藏页、搜索结果页进入人物详情时,笔记找不到。人物详情页必须始终以 personId 为主键,而不是页面入口。
3. 听书入口只依赖当前播放列表
如果 ensureAudioInList() 没有兜底模板目录,用户清空列表后详情页会出现“有按钮但不能播”的体验断层。这也是人物详情页和听书页最容易脱节的地方。
4. 平板布局把人物正文拉得过宽
平板上不要简单把手机布局放大。对人物正文这种长文本区域,建议限制正文容器最大宽度,否则阅读行长过长,信息密度会明显下降。
Column() {
this.personBiography(person);
}
.constraintSize({ maxWidth: 960 })
.alignSelf(ItemAlign.Center)
十、工程实现与验收清单
人物详情页是内容型 App 的核心页面,它会同时连接静态内容、用户行为和音频能力。详情页可以使用组合模型,把人物静态信息和用户侧状态合并到一个渲染对象里。
interface PersonDetailState {
person: PersonModel;
isFavorite: boolean;
note?: NoteRecord;
audio?: AudioRecord;
}
页面初始化时按 personId 拉取人物模板,再并行读取收藏、笔记和听书进度:
private async loadPersonDetail(personId: string) {
const person = PersonRepository.findById(personId);
const isFavorite = await this.favoriteService.has(personId);
const note = await this.noteService.findByTargetId(personId);
const audio = await this.audioService.findByTargetId(personId);
this.detailState = { person, isFavorite, note, audio };
}
验收用例建议覆盖:
| 用例 | 期望结果 |
|---|---|
| 进入人物详情后点击收藏 | 收藏按钮立即变为选中态 |
| 返回列表再进入同一人物 | 收藏状态保持 |
| 在详情页编辑笔记 | 笔记内容保存到对应 targetId |
| 从收藏页进入人物详情 | 能恢复人物内容和笔记入口 |
| 删除笔记后重启 App | 不显示旧笔记 |
| 清空听书列表后从详情页点听书 | 能重新创建音频记录 |
| 平板横屏阅读人物正文 | 行长不过宽,Tab 与正文关系清晰 |
人物详情页不是简单展示字段,而是内容、交互和能力的汇聚点。下一篇会讲收藏与笔记:如何用 Preferences 保存 JSON,并保证页面即时刷新。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)