【鸿蒙原生开发会议随记 Pro】用 NavPathStack 收拢会议页面跳转和返回刷新
会议详情页进入编辑页以后,编辑页可能会修改标题、标签、参会人,也可能会调整时间轴笔记。编辑页保存并返回以后,详情页要重新读取当前会议;详情页再返回会议列表时,列表也要知道会议数据已经变化。这个过程如果只靠页面之间互相约定,时间一长就很容易忘记哪条路径负责刷新。
前言
我在《会议随记 Pro》里整理启动链路以后,很快遇到了另一个更实际的问题:页面越来越多,页面之间的关系也开始变复杂。
早期页面少的时候,会议列表进入会议详情,会议详情再进入会议编辑,直接在页面里写跳转逻辑还能接受。后来项目里陆续增加了主 Tab、新建会议、会议详情、会议编辑、项目详情、联系人详情、设置页。如果继续让每个页面自己决定下一个页面怎么打开,后面会很难维护。
真正让我重新处理导航逻辑的,是返回刷新。
会议详情页进入编辑页以后,编辑页可能会修改标题、标签、参会人,也可能会调整时间轴笔记。编辑页保存并返回以后,详情页要重新读取当前会议;详情页再返回会议列表时,列表也要知道会议数据已经变化。这个过程如果只靠页面之间互相约定,时间一长就很容易忘记哪条路径负责刷新。
我后来把页面跳转集中到 NavPathStack。首页维护一份全局页面栈,页面名称统一注册,页面之间只传业务参数。详情页进入编辑页时,把返回后的刷新动作绑定在这次跳转上。底部 Tab 页面不需要关心业务页面怎么打开,它只负责展示自己的内容。
这里最容易混在一起的其实有三件事。
| 事情 | 负责对象 | 项目里的处理 |
|---|---|---|
| 页面怎么打开 | NavPathStack |
首页统一注册页面名称 |
| 页面打开哪条数据 | meetingId 等业务参数 |
页面之间只传必要 ID |
| 返回以后谁刷新 | 当前页面或全局刷新信号 | 详情页用返回回调,列表用刷新 key |
Navigation 和 NavPathStack 在这个项目里不只是页面跳转工具,它们更像页面关系的中心。启动链路负责把应用带到首页,首页导航栈再负责业务页面之间的流转。只要这个边界立住,后续增加桌面卡片入口、通知入口、项目详情页、联系人详情页时,页面之间不会互相缠在一起。

一、跳转要有一个入口
《会议随记 Pro》的首页 Index.ets 里提供了一份全局导航栈。
@Provide('appStack') appStack: NavPathStack = new NavPathStack();
这个 appStack 不属于某个详情页,也不属于某个列表页。它由首页提供出来,后面的会议详情页、会议编辑页需要返回或者继续打开新页面时,都可以通过 @Consume('appStack') 拿到同一份导航栈。
我更愿意让页面跳转从首页出去。会议列表不需要知道会议详情页的组件文件在哪里,详情页也不需要知道编辑页怎么创建。它们只需要知道目标页面名称和当前业务 ID。
首页同时把页面名称注册到 pageMaps() 里。项目里当前已经有 mainTabs、welcome、meetingNew、meetingDetail、meetingEdit、联系人详情、项目详情、关于页、设置页等映射。页面名称和组件之间的对应关系都放在这里,业务页面只通过名称入栈。
这个处理对项目结构的影响很直接。
| 页面关系 | 页面之间传什么 | 组件由谁注册 |
|---|---|---|
| 会议列表进入详情页 | meetingId |
Index.ets |
| 会议详情进入编辑页 | meetingId |
Index.ets |
| 项目列表进入项目详情 | projectId |
Index.ets |
| 联系人列表进入联系人详情 | contactId |
Index.ets |
这里我会保留一个习惯:页面名称和业务参数分开处理。
meetingDetail 是页面名称,表示要打开会议详情页。meetingId 是业务参数,表示详情页要处理哪一条会议记录。它们放在一起看很容易混淆,拆开以后会好维护很多。后面要复用同一个详情页,或者要给详情页追加来源标记、刷新策略,也不会影响页面注册方式。
底部 Tab 页面也会因此变轻。会议列表页只负责显示会议列表,点击一条会议时把 meetingId 交给导航栈。至于详情页组件怎么创建、它在页面栈里处在哪一层,都交给统一的导航入口。

二、参数不要传得太满
会议详情页不需要拿到一整条会议对象。它真正需要的是 meetingId。
这个判断来自真实项目里的数据结构。会议详情页打开以后,不只是展示标题和摘要,还要读取会议主记录、时间轴笔记、待办、评论和参会人。如果列表页把完整对象传过去,详情页拿到的只是一份快照。编辑页修改标题以后,详情页手里的旧对象马上过期。
所以我在这个项目里更倾向于传业务 ID。详情页拿到 meetingId,再通过 Repository 加载当前会议。编辑页也是一样,拿到同一个 meetingId,自己读取要编辑的数据。
| 参数方式 | 适合场景 | 在这个项目里的处理 |
|---|---|---|
| 传完整对象 | 临时确认页、一次性展示页 | 不用于会议详情和会议编辑 |
| 传业务 ID | 详情页、编辑页、需要重新读取数据的页面 | 会议详情和会议编辑采用这个方式 |
| 传筛选条件 | 列表页、搜索页、聚合页 | 会议列表和项目列表继续沿用 |
| 传入口动作 | 启动页、通知跳转、桌面卡片入口 | 进入首页后再转换成页面动作 |
参数越少,返回刷新越容易处理。详情页不需要判断上一个页面传来的对象是不是过期,也不需要合并局部字段。编辑页返回以后,详情页直接根据 meetingId 重新读取当前会议,页面状态就能回到最新数据上。
这里还有一个容易忽略的边界。导航栈只负责页面关系,不负责数据仓库。页面要显示哪条记录,可以通过参数决定;这条记录怎么读取、怎么更新、怎么处理关联数据,仍然要回到 Repository。把这两层混在一起,后面会出现页面能打开,但数据刷新很难查的问题。
我之前在页面跳转里踩过一个坑。列表页跳详情页时传了完整对象,详情页显示出来没问题;详情页再进入编辑页,编辑完返回以后,详情页标题没有更新。继续排查才发现,详情页展示的是旧对象,不是重新查询后的会议记录。改成只传 meetingId 后,这类问题会少很多。

三、返回刷新要绑定在这次跳转上
详情页进入编辑页时,我不会只写一个普通的 pushPath。这个跳转本身就带着后续动作:编辑页返回以后,详情页要重新加载。
真实项目里可以这样处理。
private handleEdit(): void {
if (!this.meeting) {
return;
}
if (this.isPlaying && this.player) {
this.player.pause();
}
const param: MeetingDetailParam = {
meetingId: this.meeting.id
};
this.appStack.pushPath({
name: 'meetingEdit',
param: param,
onPop: () => {
this.loadData();
}
});
}
这段逻辑里有两个动作我会保留。详情页进入编辑页前,先暂停正在播放的录音,避免用户编辑会议时音频还在继续播放。然后把 loadData() 绑定到这次入栈的 onPop 上。编辑页返回时,详情页重新读取当前会议。
这个写法的好处不在 API 调用本身,而在刷新关系清楚。详情页打开编辑页,编辑页返回以后详情页刷新。这是一条当前页面栈里的关系,不需要变成全局监听,也不需要让编辑页知道详情页内部方法。
编辑页保存时,做的事情也应该收住。它更新会议数据,通知全局会议数据已经变化,然后调用 appStack.pop() 返回上一页。详情页通过 onPop 重新加载,列表和工作台通过全局刷新信号感知变化。
| 刷新方式 | 适合的页面关系 | 在项目里的位置 |
|---|---|---|
onPop 回调 |
详情页打开编辑页,编辑完成后详情页重新加载 | 详情页进入编辑页时绑定 |
MeetingReloadKey |
列表页、工作台、其他不在当前栈顶的页面感知数据变化 | 保存、删除、编辑完成后通知 |
| 页面初始化加载 | 页面首次打开,根据当前业务 ID 加载数据 | 详情页、编辑页、项目详情页 |
| Tab 显示检查 | Tab 重新出现时检查是否需要刷新 | 列表页和工作台 |
把这些刷新方式放在不同位置,后面排查问题会轻松一些。详情页不用等待全局信号来判断自己要不要刷新,列表页也不用知道详情页和编辑页之间发生了什么。每个页面只处理自己所在位置能确定的事情。

四、用一个小页面验证状态链路
为了把这套关系看清楚,我把真实项目里的页面栈压缩成一个小页面。这个页面里保留三种状态:会议列表、会议详情、会议编辑。它不连接真实数据库,会议数据保存在页面状态里。
这个示例不是为了替代真实项目里的 NavPathStack。它的作用是把页面栈、业务 ID、保存动作、列表刷新、详情重载、编辑返回这几件事放在同一个界面里观察。真实项目里再把这个状态链路迁回 NavPathStack + onPop + Repository + RefreshUtil。
我在这个小页面里把状态更新放在同一个组件里,原因很实际。文章示例需要稳定展示运行结果,不能让页面栈、标题更新和计数器状态分散在多个 NavDestination 子页面之间。完整项目可以用更细的组件拆分,文章里的演示页先保证状态链路足够清楚。
这个小页面会跑出一条固定路径。
会议列表 → 会议详情 → 会议编辑 → 保存并返回 → 会议详情 → 返回列表
每一步对应的状态变化如下。
| 操作 | 页面栈 | 列表刷新次数 | 详情重载次数 | 编辑返回次数 |
|---|---|---|---|---|
| 初始进入列表 | meetingList |
0 | 0 | 0 |
| 点击会议进入详情 | meetingList → meetingDetail |
0 | 1 | 0 |
| 点击编辑标题 | meetingList → meetingDetail → meetingEdit |
0 | 1 | 0 |
| 保存并返回详情 | meetingList → meetingDetail |
1 | 2 | 1 |
| 返回列表 | meetingList |
1 | 2 | 1 |
这样跑出来以后,截图里就能观察到四个结果。
第一,当前页面栈会随着列表、详情、编辑切换而变化。第二,编辑保存以后,详情页标题会显示新标题。第三,列表刷新次数、详情重载次数、编辑返回次数都会增加。第四,返回列表后,对应会议卡片也会显示保存后的标题。

五、迁回真实项目时怎么处理
这个小页面为了便于观察,把列表、详情、编辑三种状态压缩到一个 Index.ets 里。真实项目不会这样写。项目里仍然是列表页、详情页、编辑页分开,页面栈交给 NavPathStack,数据读写交给 Repository,跨页面通知交给 RefreshUtil。
迁回真实项目时,我会保留下面这几条关系。
| 小页面里的逻辑 | 真实项目里的处理 |
|---|---|
currentPage 模拟页面栈 |
NavPathStack 管理页面栈 |
currentMeetingId |
页面跳转时传入的业务 ID |
syncDetailSnapshot() |
详情页里的 loadData() |
saveAndBackToDetail() |
编辑页保存会议,再调用 appStack.pop() |
listRefreshCount |
RefreshUtil.notifyMeetingUpdate() 后列表感知刷新 |
detailReloadCount |
详情页通过 onPop 重新加载 |
editReturnCount |
编辑页保存并返回这一条路径的观察值 |
这里最值得保留的是边界,而不是演示代码的组件组织方式。真实项目里我不会把所有页面都塞进一个文件,也不会让编辑页直接知道列表怎么刷新。编辑页只负责保存和返回。详情页负责返回后的重新加载。列表页和工作台通过全局刷新信号感知会议数据变化。
这个边界很适合《会议随记 Pro》现在的结构。会议详情页本身已经有播放器、时间轴、待办、评论、参会人等模块,编辑页保存以后,详情页必须重新加载;会议列表和工作台不在当前页面栈顶部,只要通过全局刷新信号知道数据变化就够了。

总结
NavPathStack 在这个项目里解决的是页面关系维护问题。页面名称统一注册以后,列表页和详情页不需要互相导入组件。页面之间只传 meetingId 这类业务参数,具体数据继续交给 Repository 读取。详情页打开编辑页时,把返回后的重新加载绑定在这次跳转上,编辑页只负责保存并返回。
这套处理我会继续用在《会议随记 Pro》的业务页面里。会议详情、会议编辑、项目详情、联系人详情、设置页都可以放在同一份导航栈里。编辑页保存以后,当前详情页通过 onPop 重新加载;列表和工作台通过全局刷新信号感知数据变化。页面栈管页面关系,数据层管数据读取,这个边界保留下来,后面继续增加页面时会少很多绕路。
这几个点在项目里要分开处理:
- 当前页面栈由
NavPathStack维护 - 当前业务数据由
meetingId决定 - 详情页返回刷新由
onPop触发 - 列表和工作台刷新由
MeetingReloadKey通知 - 编辑页只负责保存数据和返回上一页
我在《会议随记 Pro》里已经使用了这套页面跳转和返回刷新处理,应用目前已经上架华为应用市场。里面包含会议录音、时间轴笔记、联系人、项目、标签管理和多设备适配这些功能。对鸿蒙原生应用的完整实现感兴趣的话,可以下载体验一下:会议随记 Pro。
完整代码
interface MeetingItem {
id: string;
title: string;
summary: string;
updatedAt: number;
}
interface RouteLog {
id: number;
action: string;
detail: string;
}
enum DemoPage {
List = 0,
Detail = 1,
Edit = 2
}
@Entry
@Component
struct Index {
@State currentPage: DemoPage = DemoPage.List;
@State meetings: MeetingItem[] = [
{
id: 'meeting-001',
title: '产品评审会',
summary: '确认 1.3 版本多设备适配范围',
updatedAt: 1717819200000
},
{
id: 'meeting-002',
title: '录音链路复盘',
summary: '整理录音状态机、保存流程和权限降级',
updatedAt: 1717905600000
},
{
id: 'meeting-003',
title: '桌面卡片讨论',
summary: '确认 FormID 管理和卡片刷新时机',
updatedAt: 1717992000000
}
];
@State currentMeetingId: string = 'meeting-001';
@State detailTitle: string = '产品评审会';
@State detailSummary: string = '确认 1.3 版本多设备适配范围';
@State detailUpdatedAt: number = 1717819200000;
@State editTitleDraft: string = '';
@State listRefreshCount: number = 0;
@State detailReloadCount: number = 0;
@State editReturnCount: number = 0;
@State stackText: string = 'meetingList';
@State logSeed: number = 0;
@State logs: RouteLog[] = [];
private addLog(action: string, detail: string): void {
const next: RouteLog = {
id: this.logSeed + 1,
action: action,
detail: detail
};
this.logSeed = next.id;
this.logs = [next, ...this.logs].slice(0, 12);
}
private setStack(names: string[]): void {
this.stackText = names.join(' → ');
}
private getMeetingById(meetingId: string): MeetingItem | undefined {
return this.meetings.find((item: MeetingItem) => item.id === meetingId);
}
private syncDetailSnapshot(meetingId: string, reason: string): void {
const current = this.getMeetingById(meetingId);
if (!current) {
this.detailTitle = '会议不存在';
this.detailSummary = '当前 meetingId 没有对应的会议记录';
this.detailUpdatedAt = 0;
this.addLog('detail empty', `没有找到会议,meetingId=${meetingId}`);
return;
}
this.currentMeetingId = meetingId;
this.detailTitle = current.title;
this.detailSummary = current.summary;
this.detailUpdatedAt = current.updatedAt;
this.detailReloadCount += 1;
this.addLog('detail reload', `${reason},meetingId=${meetingId}`);
}
private prepareEditDraft(): void {
const current = this.getMeetingById(this.currentMeetingId);
if (current) {
this.editTitleDraft = current.title;
return;
}
this.editTitleDraft = '未命名会议';
}
private openDetail(meetingId: string): void {
this.syncDetailSnapshot(meetingId, '打开详情页时同步会议快照');
this.setStack(['meetingList', 'meetingDetail']);
this.currentPage = DemoPage.Detail;
this.addLog('push detail', `会议列表进入会议详情,meetingId=${meetingId}`);
}
private openEdit(): void {
this.prepareEditDraft();
this.setStack(['meetingList', 'meetingDetail', 'meetingEdit']);
this.currentPage = DemoPage.Edit;
this.addLog('push edit', `会议详情进入会议编辑,meetingId=${this.currentMeetingId}`);
}
private saveAndBackToDetail(): void {
const finalTitle = this.editTitleDraft.trim().length > 0 ? this.editTitleDraft.trim() : '未命名会议';
const savedAt = Date.now();
let savedSummary = this.detailSummary;
const nextItems = this.meetings.map((item: MeetingItem): MeetingItem => {
if (item.id === this.currentMeetingId) {
savedSummary = item.summary;
return {
id: item.id,
title: finalTitle,
summary: item.summary,
updatedAt: savedAt
};
}
return item;
});
this.meetings = nextItems;
this.detailTitle = finalTitle;
this.detailSummary = savedSummary;
this.detailUpdatedAt = savedAt;
this.listRefreshCount += 1;
this.detailReloadCount += 1;
this.editReturnCount += 1;
this.setStack(['meetingList', 'meetingDetail']);
this.currentPage = DemoPage.Detail;
this.addLog('save', `会议 ${this.currentMeetingId} 的标题保存为:${finalTitle}`);
this.addLog('list refresh', `列表刷新次数增加到 ${this.listRefreshCount}`);
this.addLog('detail reload', `详情重载次数增加到 ${this.detailReloadCount}`);
this.addLog('edit return', `编辑返回次数增加到 ${this.editReturnCount}`);
}
private backToList(): void {
this.setStack(['meetingList']);
this.currentPage = DemoPage.List;
this.addLog('pop detail', '会议详情返回会议列表');
}
private manualRefreshList(): void {
this.listRefreshCount += 1;
this.addLog('list refresh', `手动刷新会议列表,刷新次数 ${this.listRefreshCount}`);
}
private manualReloadDetail(): void {
this.syncDetailSnapshot(this.currentMeetingId, '详情页手动重新同步会议数据');
}
@Builder
private StatCard(label: string, value: string) {
Column({ space: 6 }) {
Text(label)
.fontSize(12)
.fontColor('#64748B')
Text(value)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#0F172A')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.padding(14)
.backgroundColor(Color.White)
.borderRadius(16)
}
@Builder
private StackCard() {
Column({ space: 12 }) {
Text('当前页面栈')
.fontSize(13)
.fontColor('#64748B')
Text(this.stackText)
.fontSize(17)
.fontWeight(FontWeight.Medium)
.fontColor('#0F172A')
.width('100%')
}
.width('100%')
.padding(14)
.backgroundColor(Color.White)
.borderRadius(16)
}
@Builder
private MeetingCard(item: MeetingItem) {
Column({ space: 8 }) {
Row() {
Column({ space: 4 }) {
Text(item.title)
.fontSize(17)
.fontWeight(FontWeight.Medium)
.fontColor('#0F172A')
Text(item.summary)
.fontSize(13)
.fontColor('#64748B')
.lineHeight(20)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
Text(this.currentMeetingId === item.id ? '当前' : '打开')
.fontSize(12)
.fontColor(this.currentMeetingId === item.id ? '#2563EB' : '#64748B')
.padding({
left: 10,
right: 10,
top: 4,
bottom: 4
})
.backgroundColor(this.currentMeetingId === item.id ? '#DBEAFE' : '#F1F5F9')
.borderRadius(12)
}
.width('100%')
Text(`updatedAt=${item.updatedAt}`)
.fontSize(11)
.fontColor('#94A3B8')
.width('100%')
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(18)
.shadow({
radius: 10,
color: '#10000000',
offsetX: 0,
offsetY: 3
})
.onClick(() => {
this.openDetail(item.id);
})
}
@Builder
private CounterPanel() {
Column({ space: 12 }) {
Row({ space: 12 }) {
this.StatCard('当前 meetingId', this.currentMeetingId)
this.StatCard('列表刷新次数', this.listRefreshCount.toString())
}
.width('100%')
Row({ space: 12 }) {
this.StatCard('详情重载次数', this.detailReloadCount.toString())
this.StatCard('编辑返回次数', this.editReturnCount.toString())
}
.width('100%')
}
.width('100%')
}
@Builder
private BuildListPage() {
Column({ space: 18 }) {
Column({ space: 8 }) {
Text('会议列表')
.fontSize(26)
.fontWeight(FontWeight.Bold)
.fontColor('#0F172A')
Text('列表页只展示会议摘要。点击一条会议后,页面栈会进入会议详情,当前 meetingId 会同步到页面层。')
.fontSize(14)
.fontColor('#475569')
.lineHeight(22)
}
.width('100%')
.alignItems(HorizontalAlign.Start)
this.StackCard()
this.CounterPanel()
Button('手动刷新列表')
.width('100%')
.height(44)
.backgroundColor('#2563EB')
.fontColor(Color.White)
.borderRadius(22)
.onClick(() => {
this.manualRefreshList();
})
Column({ space: 12 }) {
ForEach(this.meetings, (item: MeetingItem) => {
this.MeetingCard(item)
}, (item: MeetingItem) => `${item.id}-${item.updatedAt}-${this.listRefreshCount}`)
}
.width('100%')
this.LogPanel()
}
.width('100%')
}
@Builder
private BuildDetailPage() {
Column({ space: 18 }) {
Column({ space: 8 }) {
Text('会议详情')
.fontSize(26)
.fontWeight(FontWeight.Bold)
.fontColor('#0F172A')
Text('详情页展示当前会议快照。编辑页保存返回后,标题、更新时间和刷新计数会一起变化。')
.fontSize(14)
.fontColor('#475569')
.lineHeight(22)
}
.width('100%')
.alignItems(HorizontalAlign.Start)
this.StackCard()
Column({ space: 12 }) {
Text(this.detailTitle)
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor('#0F172A')
Text(this.detailSummary)
.fontSize(15)
.fontColor('#475569')
.lineHeight(24)
Text(`meetingId=${this.currentMeetingId}`)
.fontSize(12)
.fontColor('#64748B')
Text(`updatedAt=${this.detailUpdatedAt}`)
.fontSize(12)
.fontColor('#64748B')
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.padding(18)
.backgroundColor(Color.White)
.borderRadius(20)
Row({ space: 12 }) {
Button('编辑标题')
.layoutWeight(1)
.height(44)
.backgroundColor('#2563EB')
.fontColor(Color.White)
.borderRadius(22)
.onClick(() => {
this.openEdit();
})
Button('重新加载')
.layoutWeight(1)
.height(44)
.backgroundColor('#E2E8F0')
.fontColor('#0F172A')
.borderRadius(22)
.onClick(() => {
this.manualReloadDetail();
})
}
.width('100%')
Button('返回列表')
.width('100%')
.height(44)
.backgroundColor('#0F766E')
.fontColor(Color.White)
.borderRadius(22)
.onClick(() => {
this.backToList();
})
this.CounterPanel()
this.LogPanel()
}
.width('100%')
}
@Builder
private BuildEditPage() {
Column({ space: 18 }) {
Column({ space: 8 }) {
Text('会议编辑')
.fontSize(26)
.fontWeight(FontWeight.Bold)
.fontColor('#0F172A')
Text('编辑页只负责修改标题。保存动作会更新会议数组、详情快照和三个统计值。')
.fontSize(14)
.fontColor('#475569')
.lineHeight(22)
}
.width('100%')
.alignItems(HorizontalAlign.Start)
this.StackCard()
Column({ space: 12 }) {
Text('会议标题')
.fontSize(14)
.fontColor('#64748B')
TextInput({
text: this.editTitleDraft,
placeholder: '请输入会议标题'
})
.height(48)
.fontSize(16)
.backgroundColor('#F8FAFC')
.borderRadius(14)
.padding({
left: 12,
right: 12
})
.onChange((value: string) => {
this.editTitleDraft = value;
})
Text(`meetingId=${this.currentMeetingId}`)
.fontSize(12)
.fontColor('#94A3B8')
}
.width('100%')
.padding(18)
.backgroundColor(Color.White)
.borderRadius(20)
Button('保存并返回')
.width('100%')
.height(46)
.backgroundColor('#2563EB')
.fontColor(Color.White)
.borderRadius(23)
.onClick(() => {
this.saveAndBackToDetail();
})
this.CounterPanel()
this.LogPanel()
}
.width('100%')
}
@Builder
private LogPanel() {
Column({ space: 12 }) {
Text('导航日志')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#0F172A')
.width('100%')
if (this.logs.length === 0) {
Text('还没有导航记录')
.fontSize(13)
.fontColor('#94A3B8')
.width('100%')
.padding(14)
.backgroundColor('#F8FAFC')
.borderRadius(14)
} else {
ForEach(this.logs, (item: RouteLog) => {
Row({ space: 10 }) {
Text(item.action)
.fontSize(11)
.fontColor('#1D4ED8')
.padding({
left: 8,
right: 8,
top: 3,
bottom: 3
})
.backgroundColor('#DBEAFE')
.borderRadius(10)
Text(item.detail)
.fontSize(13)
.fontColor('#334155')
.lineHeight(20)
.layoutWeight(1)
}
.width('100%')
.alignItems(VerticalAlign.Top)
.padding(12)
.backgroundColor('#F8FAFC')
.borderRadius(14)
}, (item: RouteLog) => item.id.toString())
}
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(20)
}
build() {
Scroll() {
Column({ space: 18 }) {
if (this.currentPage === DemoPage.List) {
this.BuildListPage()
} else if (this.currentPage === DemoPage.Detail) {
this.BuildDetailPage()
} else {
this.BuildEditPage()
}
}
.width('100%')
.padding(20)
}
.width('100%')
.height('100%')
.backgroundColor('#EEF2F7')
}
}
更多推荐




所有评论(0)