【观止·诗史汇 HarmonyOS 实战系列 06】时间轴与历史事件:把朝代、事件和兴衰节点串起来

前五篇已经完成了从工程骨架到诗文阅读链路的铺垫:第一篇讲《观止·诗史汇》为什么是一个本地优先的诗文与历史学习 App,第二篇拆 entry / features / commons 三层边界,第三篇落地首页,第四篇把诗文内容包整理成可检索的本地诗库,第五篇把诗文详情页扩展成“读、听、收、记、统计”的学习节点。

第六篇开始,主线从“读一首诗”转向“理解一段历史”。诗文学习如果没有时间感,很多典故、作者处境和情绪来源都会悬空。杜甫的沉郁、陆游的北望、辛弃疾的豪放,都离不开朝代兴替和历史事件。时间轴模块要解决的不是“列出很多年份”,而是把朝代顺序、关键事件、兴衰节点和详情阅读串成一条可理解的路径。

本文就沿着真实源码拆解:TimelinePage 如何组织朝代筛选、兴衰入口和年份节点,HistoryPack 如何把 doc/lishi 的朝代内容注册进运行时数据,HistoryEvents 如何补充重大事件和兴衰节点,TimelineEventDetailPage 如何把事件背景、经过、影响、地点、相关诗文、收藏笔记和统计记录串起来。

观止·诗史汇时间轴截图

上图应来自本机 DevEco 模拟器中打开《观止·诗史汇》的“时间轴”模块。第六篇的正文和封面图应该展示时间轴主体:顶部朝代筛选、兴衰入口卡、年份节点和朝代段,而不是首页,也不是其他模块的详情页。

本篇要解决什么问题

时间轴模块至少要解决四类问题:

问题 当前实现
朝代顺序从哪里来 `HistoryPack.registerHistoryPack()` 注册 `HISTORY_PACK_DYNASTIES`
页面如何展示时间线 `TimelinePage` 中的 `YearNode()` + `DynastySegment()`
如何筛选单个朝代 `selectedDynasty` + 顶部横向 `FilterChip()`
兴衰分析如何接入 `XingShuaiCard()` 跳转 `AppRoutes.DYNASTY_INSIGHT`
事件详情如何展示 `TimelineEventDetailPage` 读取 `eventId`,展示背景、经过、影响
事件如何进入统计 `StatsStore.recordEvent(eventId)`
事件如何关联诗文和地理 `relatedPoemIds`、`placeId`、`GeoService`、`PoemService`

这和前三篇的“首页入口”也能对应起来:首页提供“今天看一件史事”,时间轴提供“按历史顺序理解这些史事”,事件详情页提供“深入读一个节点”。

源码对象总览

文件 职责
`features/src/main/ets/timeline/TimelinePage.ets` 时间轴首页,包含朝代筛选、兴衰入口、年份节点和朝代段
`features/src/main/ets/timeline/TimelineEventDetailPage.ets` 历史事件详情页,展示事件背景、经过、影响、地点和相关诗文
`features/src/main/ets/services/HistoryPack.ets` 历史内容包注册,把朝代数据和扩展事件并入运行时数据
`features/src/main/ets/services/HistoryPackData.ets` 19 个朝代的时间线数据源
`features/src/main/ets/services/HistoryEvents.ets` 扩展重大事件、转折节点和兴衰类型
`features/src/main/ets/services/EventService.ets` 事件查询服务
`features/src/main/ets/services/DynastyService.ets` 朝代查询服务
`features/src/main/ets/state/AppStores.ets` 事件阅读统计、收藏和笔记
`commons/src/main/ets/router/RouteNames.ets` `TIMELINE_EVENT_DETAIL`、`DYNASTY_OVERVIEW`、`DYNASTY_INSIGHT` 等路由

第六篇的核心并不是单个页面 UI,而是“历史内容包 → 时间轴 → 朝代详情/兴衰分析 → 事件详情 → 统计收藏笔记”的完整链路。

HistoryPack:先注册朝代,再展示时间轴

时间轴页面使用的是:

import { MOCK_DYNASTIES_TIMELINE } from '../services/HistoryPack';

HistoryPack 的职责是把基于 doc/lishi/*.md 的朝代内容写入运行时 mock 数据,并维护一个显式时间轴集合:

export const MOCK_DYNASTIES_TIMELINE: Dynasty[] = [];

export function registerHistoryPack(): void {
  if (_registered) {
    return;
  }
  _registered = true;

  for (let i = 0; i < HISTORY_PACK_DYNASTIES.length; i++) {
    const src: Dynasty = HISTORY_PACK_DYNASTIES[i];
    const idx: number = MOCK_DYNASTIES.findIndex((d: Dynasty) => d.id === src.id);
    if (idx >= 0) {
      const old: Dynasty = MOCK_DYNASTIES[idx];
      const merged: Dynasty = {
        id: src.id,
        name: src.name,
        fullName: src.fullName,
        startYear: src.startYear,
        endYear: src.endYear,
        capital: old.capital && old.capital !== '—' ? old.capital : src.capital,
        founder: old.founder && old.founder !== '—' ? old.founder : src.founder,
        brief: src.brief,
        keyEvents: src.keyEvents,
        inventions: src.inventions,
        famousFigures: src.famousFigures,
        turningPoints: src.turningPoints
      };
      MOCK_DYNASTIES[idx] = merged;
    } else {
      MOCK_DYNASTIES.push(src);
    }
  }
}

这里有一个阶段性工程取舍:项目还保留了 MOCK_DYNASTIES 这类运行时 mock 数据,但通过 HistoryPack.registerHistoryPack() 把文档整理后的历史内容并入进去。这样既不需要一次性推翻旧服务,也能让时间轴开始消费更完整的历史内容包。

注册后再构造时间轴显式集合:

for (let i = 0; i < HISTORY_PACK_DYNASTIES.length; i++) {
  const id: string = HISTORY_PACK_DYNASTIES[i].id;
  const d: Dynasty | undefined = MOCK_DYNASTIES.find((it: Dynasty) => it.id === id);
  if (d) {
    MOCK_DYNASTIES_TIMELINE.push(d);
  }
}
MOCK_DYNASTIES_TIMELINE.sort((a: Dynasty, b: Dynasty) => a.startYear - b.startYear);

这说明时间轴不是随手把所有朝代数组拿来展示,而是显式挑选 HISTORY_PACK_DYNASTIES 中的 19 项,并按起始年升序排序。页面层因此可以假设数据已经按历史顺序就绪。

TimelinePage:页面状态很轻,只保留列表和筛选

时间轴页面状态很小:

interface TimelineState {
  loading: boolean;
  dynasties: Dynasty[];
}

@State state: TimelineState = { loading: true, dynasties: [] };
@State selectedDynasty: string = ALL_ID;

进入页面时直接复制时间轴数据:

async aboutToAppear() {
  const dyn: Dynasty[] = MOCK_DYNASTIES_TIMELINE.slice();
  this.state = { loading: false, dynasties: dyn };
}

这说明 TimelinePage 没有自己解析文档,也没有自己合并事件,更没有在页面里维护复杂缓存。它只消费已经注册好的朝代时间线。页面越轻,越容易把注意力放在布局和交互上。

页面骨架:筛选条、兴衰入口、时间轴主体

TimelinePagebuild() 很直观:

Column() {
  AppTopBar({ title: '时间轴', subtitle: '朝代兴替·古今事变' })

  this.DynastyFilter()
  this.XingShuaiCard()

  if (this.state.loading) {
    LoadingView({ message: '加载朝代时间轴…' }).layoutWeight(1)
  } else if (this.state.dynasties.length === 0) {
    EmptyView({ message: '暂无朝代数据', hint: '请检查内容包' }).layoutWeight(1)
  } else {
    Scroll() {
      this.TimelineColumn()
    }
  }
}

可以拆成三层:

区域 作用
`DynastyFilter()` 横向筛选全部或单个朝代
`XingShuaiCard()` 进入兴替明鉴,查看当前朝代关键节点和归因分析
`TimelineColumn()` 用年份节点和朝代段串起历史顺序

这比一开始就展示事件列表更清晰。用户先看到朝代顺序,再进入某个朝代详情或兴衰分析;事件详情则是下一层。

朝代筛选:全部和单朝代共用同一套 chip

顶部筛选条:

@Builder
DynastyFilter() {
  Scroll() {
    Row({ space: AppDimens.spaceSm }) {
      this.FilterChip(ALL_ID, '全部')
      ForEach(this.state.dynasties, (d: Dynasty) => {
        this.FilterChip(d.id, d.name)
      }, (d: Dynasty) => d.id)
    }
  }
  .scrollable(ScrollDirection.Horizontal)
  .scrollBar(BarState.Off)
  .height(48)
  .width('100%')
}

FilterChip() 只做一件事:根据 selectedDynasty 改变样式,并在点击时更新筛选值。

Text(label)
  .fontSize(AppText.bodySm)
  .fontColor(this.selectedDynasty === id ? '#FFFFFF' : AppColors.textPrimary)
  .backgroundColor(this.selectedDynasty === id ? AppColors.accent : AppColors.cardBg)
  .borderRadius(20)
  .onClick(() => { this.selectedDynasty = id; })

筛选逻辑也很简单:

private filtered(): Dynasty[] {
  if (this.selectedDynasty === ALL_ID) {
    return this.state.dynasties;
  }
  return this.state.dynasties.filter((d: Dynasty) => d.id === this.selectedDynasty);
}

这里没有额外的搜索状态,也没有复制数组缓存。对于 19 个朝代的时间轴,直接过滤足够清楚。

兴衰入口:时间轴负责顺序,兴替明鉴负责归因

时间轴顶部保留了一个“兴衰”入口卡:

@Builder
XingShuaiCard() {
  Row({ space: AppDimens.spaceMd }) {
    Image($r('app.media.ic_milestone_turn'))
      .width(36).height(36)
    Column({ space: 4 }) {
      Text('兴衰').fontSize(AppText.subtitle).fontWeight(FontWeight.Bold)
      Text('查看' + this.currentDynastyLabel() + '的关键节点与归因分析')
    }
    Text('进入 →').fontSize(AppText.bodySm).fontColor(AppColors.accent)
  }
  .onClick(() => this.openDynastyInsight())
}

跳转时,如果当前选中“全部”,就默认用上古:

private openDynastyInsight(): void {
  let id: string = this.selectedDynasty;
  if (id === ALL_ID) {
    id = 'd_ancient';
  }
  const params: NavigateParams = { dynastyId: id };
  Navigator.push(AppRoutes.DYNASTY_INSIGHT, params);
}

这里的分工值得保留:

模块 职责
时间轴 讲顺序、年份、朝代段和入口
兴替明鉴 讲兴盛、危机、转折、衰亡的归因分析

如果把兴衰归因也塞进时间轴,页面会很快变重;如果时间轴只列朝代又没有兴衰入口,用户很难进一步理解“为什么变了”。当前设计让两者互相连接,但不互相吞并。

年份节点:用 startYear/endYear 串起朝代段

时间轴主体由 TimelineColumn() 生成:

Column() {
  ForEach(this.filtered(), (d: Dynasty, idx: number) => {
    this.YearNode(d.startYear, idx === 0)
    this.DynastySegment(d)
    if (idx === this.filtered().length - 1) {
      this.YearNode(d.endYear, false)
    }
  }, (d: Dynasty) => d.id)
}

这里的展示思路是:

起始年份 → 朝代段 → 下一个朝代起始年份 → 朝代段 → 最后结束年份

YearNode() 负责把年份格式化:

private formatYear(y: number): string {
  if (y < 0) return '前' + (-y);
  return y.toString();
}

公元前年份用“前 xxx”展示,公元后直接展示数字。这个小处理让时间轴同时适配上古、夏商周和近现代。

朝代段:卡片展示 fullName 和 brief

朝代段 UI 使用 DynastySegment()

@Builder
DynastySegment(d: Dynasty) {
  Row({ space: AppDimens.spaceSm }) {
    Row() {
      Column().width(2).height('100%').backgroundColor(AppColors.accent)
    }
    .width(56)
    .justifyContent(FlexAlign.Center)

    Column({ space: 4 }) {
      Row({ space: AppDimens.spaceSm }) {
        Text(d.fullName)
          .fontSize(AppText.subtitle)
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)
        Text('详情 →')
          .fontSize(AppText.captionXs)
          .fontColor(AppColors.accent)
      }

      Text(d.brief)
        .fontSize(AppText.bodySm)
        .lineHeight(18)
        .maxLines(1)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
    }
    .onClick(() => this.openDynasty(d.id))
  }
}

它展示的不是“跨度多少年”,而是朝代名称和一句概述。源码注释里也写了这一点:新版时间轴“不使用跨度概念”,而是以“年份 → 朝代 → 年份 → 朝代”串联。

这个取舍适合学习 App。跨度是历史信息,但它不是用户理解历史顺序的第一需求。用户首先要知道“接下来是什么朝代”“这个朝代大致发生了什么”“点进去能看什么”。

点击朝代段:进入朝代概览

朝代段点击后进入朝代概览:

private openDynasty(id: string): void {
  const params: NavigateParams = { dynastyId: id };
  Navigator.push(AppRoutes.DYNASTY_OVERVIEW, params);
}

这让时间轴成为历史模块的导航页。它不负责把朝代详情、人物、制度、事件全部展示出来,而是提供可理解的路径:先看时间顺序,再进入朝代概览或兴替分析。

HistoryEvents:事件不只是日期,还带 nodeType

HistoryEvents.ets 中定义了扩展事件构造函数:

function ev(id: string, title: string, year: number, dynastyId: string,
  category: string, summary: string, detail: string,
  nodeType: DynastyNodeType, placeId: string): HistoryEvent {
  return {
    id, title, year, dynastyId, category, summary, detail,
    relatedPoemIds: [], placeId: placeId || undefined,
    nodeType: nodeType || undefined
  };
}

每个事件至少包含:

字段 说明
`id` 稳定事件 ID
`title` 事件标题
`year` 发生年份
`dynastyId` 所属朝代
`category` 政治、军事、文化、经济等
`summary` 摘要
`detail` 详细经过
`nodeType` 建立、兴盛、转折、危机、衰亡
`placeId` 可选地理位置

例如:

ev('e_qin_unify', '秦始皇统一六国', -221, 'd_qin', '政治',
  '中国第一个中央集权大一统王朝建立。',
  '前 230—前 221 年间秦先后灭韩赵魏楚燕齐六国;嬴政称"皇帝"...',
  'found', 'g_changan')

nodeType 是第六篇很重要的字段。它让事件不仅是“某年发生了某事”,还能参与兴替分析:建立、兴盛、转折、危机、衰亡这些标签,后续可以被朝代洞察页、统计页和练习题复用。

TimelineEventDetailPage:事件详情不是单纯文本

事件详情页状态如下:

interface DetailState {
  loading: boolean;
  event: HistoryEvent | null;
  dynasty: Dynasty | null;
  place: GeoPlace | null;
  poems: RelatedPoemRow[];
  favored: boolean;
  folders: FavoriteFolder[];
  favoriteFolderId: string;
}

和诗文详情页类似,事件详情页也同时承接内容和行为:

状态 作用
`event` 事件主体
`dynasty` 补齐朝代名
`place` 补齐古今地理
`poems` 关联诗文
`favored`、`folders`、`favoriteFolderId` 收藏文件夹

进入页面时先读取路由参数:

const params: NavigateParams = Navigator.getParams();
const eid: string = params.eventId ?? '';

再读取事件、朝代、地点和相关诗文:

const e: HistoryEvent | null = await this.eventSvc.getById(eid);
const d: Dynasty | null = await this.dynastySvc.getById(e.dynastyId);
let p: GeoPlace | null = null;
if (e.placeId) p = await this.geoSvc.getById(e.placeId);

const poems: RelatedPoemRow[] = [];
for (let i = 0; i < e.relatedPoemIds.length; i++) {
  const po: Poem | null = await this.poemSvc.getById(e.relatedPoemIds[i]);
  if (po) poems.push({ id: po.id, title: po.title });
}

最后记录统计:

this.statsStore.recordEvent(eid);

这和第五篇的 recordPoem() 是同一套学习闭环:读诗记录诗文,读史记录事件。统计页最终可以展示用户的诗文阅读和历史阅读进度。

事件详情 HeaderCard:年份、朝代、类别、节点类型

事件详情顶部卡片展示关键信息:

Text(`${this.state.event!.year}`)
  .fontSize(AppText.title)
  .fontWeight(FontWeight.Bold)
  .fontColor(AppColors.accent)

Text(this.state.dynasty ? this.state.dynasty.name : '')
  .fontSize(AppText.bodySm)
  .fontColor(AppColors.textSecondary)

Text(this.state.event!.category)
  .fontSize(AppText.captionXs)
  .fontColor(AppColors.accent)

如果事件有 nodeType,还会显示节点标签:

if (this.state.event!.nodeType && this.state.event!.nodeType!.length > 0) {
  Text(this.nodeLabel(this.state.event!.nodeType!))
    .fontSize(AppText.captionXs)
    .fontColor('#FFFFFF')
    .backgroundColor(AppColors.warning)
}

nodeLabel() 把内部枚举转成中文:

private nodeLabel(t: string): string {
  if (t === 'found') return '建立';
  if (t === 'prosper') return '兴盛';
  if (t === 'turn') return '转折';
  if (t === 'crisis') return '危机';
  if (t === 'fall') return '衰亡';
  return '';
}

这个标签让事件详情直接带上兴替语义。用户看到“秦始皇统一六国”是建立,看到“鸦片战争”是转折,看到“土木堡之变”是危机,理解会比单纯年份更快。

地点关联:事件可以跳到古今地理

如果事件有地点,详情页会展示古今地名:

if (this.state.place) {
  Row({ space: 4 }) {
    Text('发生地点:')
    Text(`${this.state.place.ancientName}(${this.state.place.modernName})`)
      .onClick(() => {
        const p: NavigateParams = { geoId: this.state.place!.id };
        Navigator.push(AppRoutes.GEO_DETAIL, p);
      })
  }
}

这就是第八篇“古今地理”的伏笔。历史事件不是孤立文本,它可以连接到地点;地点再反向连接到诗文、事件和朝代。时间轴因此不只是纵向时间线,也能和空间关系交织。

相关诗文:历史事件反向打开诗文

事件详情页还支持相关诗文:

@Builder
RelatedPoems() {
  Column({ space: AppDimens.spaceSm }) {
    ForEach(this.state.poems, (p: RelatedPoemRow) => {
      Row() {
        Text(p.title).layoutWeight(1)
        Text('阅读 →')
      }
      .onClick(() => {
        const params: NavigateParams = { poemId: p.id };
        Navigator.push(AppRoutes.POEM_DETAIL, params);
      })
    }, (p: RelatedPoemRow) => p.id)
  }
}

这里有一个待优化点:第五篇讲过诗文详情页最好同时拿到 poemId + poemShard。当前事件详情只传 poemId

const params: NavigateParams = { poemId: p.id };
Navigator.push(AppRoutes.POEM_DETAIL, params);

这意味着如果相关诗文来自旧 PoemService,详情页可能需要兜底处理 poemShard 缺失;后续更好的做法是让事件关联诗文时携带内容包的 shard,或者在仓储里提供 getDetailById() 的索引兜底。这个问题放在文章里讲出来,比假装链路完美更有工程价值。

事件收藏和笔记:与诗文详情保持同一种交互

事件详情页也有收藏和笔记:

private onFavoriteTap(): void {
  if (!this.state.event) return;
  if (this.state.favored) {
    this.favStore.removeFavorite('event', this.state.event.id);
    this.choosingFolder = false;
    this.updateFavoriteState(false, '');
    return;
  }
  this.choosingFolder = !this.choosingFolder;
}

写入收藏时类型是 event

this.favStore.addToFolder('event', this.state.event.id, this.state.event.title, folderId);

笔记也是按事件目标关联:

const t: FavoriteType = 'event';
let note = this.noteStore.getByTarget(t, this.state.event.id);
if (!note) {
  note = this.noteStore.newDraft(t, this.state.event.id, this.state.event.title);
  this.noteStore.upsert(note);
}
Navigator.push(AppRoutes.NOTE_DETAIL, { noteId: note.id });

这说明收藏和笔记不是诗文专用能力,而是围绕 FavoriteType 抽象出来的学习状态。诗文、事件、朝代、地点都可以成为收藏和笔记目标。

StatsStore:历史事件阅读也进入学习统计

StatsStore.recordEvent()recordPoem() 是平行设计:

recordEvent(eventId: string): void {
  if (!eventId || eventId.length === 0) return;
  const t: DailyStat = this.ensureToday();
  if (t.eventIds.indexOf(eventId) < 0) {
    t.eventIds.push(eventId);
    this.notifyChanged();
  }
}

这让统计页能够区分:

行为 记录字段
阅读诗文 `DailyStat.poemIds`
阅读事件 `DailyStat.eventIds`
完成练习 `practiceTotal/practiceRight/practiceWrong`
活跃时长 `durationSec`

对学习 App 来说,时间轴和事件详情不是“内容浏览”,而是学习行为。只有把事件阅读记录下来,后续能力图谱、今日目标、勋章和学习趋势才有依据。

时间轴与首页的关系

第三篇讲首页时提到“一日一史”。它展示的是当天的一个史事,并通过:

Navigator.push(AppRoutes.TIMELINE_EVENT_DETAIL, { eventId });

进入事件详情。第六篇的时间轴则提供另一种路径:

时间轴 Tab → 朝代筛选 → 朝代段 → 朝代概览 / 兴替明鉴 → 事件详情

这两条路径面向不同场景:

路径 用户意图
首页一日一史 今天先了解一个事件
时间轴 从历史顺序理解朝代和事件
兴替明鉴 分析一个朝代为什么兴衰
事件详情 深入阅读某个节点

这就是《观止·诗史汇》的产品结构:不是每个模块各做各的,而是围绕学习路径互相导流。

当前实现的边界和后续优化

从源码看,时间轴模块已经完成主骨架,但也能看到几个清晰的后续方向:

当前边界 后续优化
时间轴只展示朝代段 可以在朝代段内展示 1 到 2 个关键事件预览
事件详情的相关诗文只传 `poemId` 需要补齐 `poemShard` 或提供仓储级 ID 查询兜底
`EXTRA_EVENTS` 在 ArkTS 中维护 后续可构建成 rawfile 历史事件包
事件影响文案部分仍较简化 可以从历史内容包中生成更完整的影响字段
时间轴筛选只支持朝代 后续可按政治、军事、文化、科技等事件类别筛选

这些不是缺陷,而是实战项目演进中的正常阶段。先把时间顺序和事件详情跑通,再逐步把事件包、地理关联、诗文关联和练习题生成做深。

本地验收命令

先检查历史内容和页面源码:

Get-Content -LiteralPath .\features\src\main\ets\timeline\TimelinePage.ets -Encoding UTF8
Get-Content -LiteralPath .\features\src\main\ets\timeline\TimelineEventDetailPage.ets -Encoding UTF8
Get-Content -LiteralPath .\features\src\main\ets\services\HistoryPack.ets -Encoding UTF8
Get-Content -LiteralPath .\features\src\main\ets\services\HistoryEvents.ets -Encoding UTF8

再检查模拟器和应用截图:

& "D:\Program Files\HuaWei\DevEco Studio\sdk\default\openharmony\toolchains\hdc.exe" list targets
& "D:\Program Files\HuaWei\DevEco Studio\sdk\default\openharmony\toolchains\hdc.exe" shell aa start -a EntryAbility -b com.example.app_project02
& "D:\Program Files\HuaWei\DevEco Studio\sdk\default\openharmony\toolchains\hdc.exe" shell snapshot_display -i 0 -f /data/local/tmp/timeline.png -w 1080 -h 2400 -t png

人工路径建议:

底部 Tab:时间轴 → 朝代筛选 → 兴衰入口 → 朝代详情 / 兴替明鉴
首页 → 一日一史 → 历史事件详情

页面验收清单

检查项 期望
时间轴入口 底部 Tab 能进入时间轴页
顶部标题 显示“时间轴 / 朝代兴替·古今事变”
朝代筛选 “全部”和各朝代 chip 横向滚动
筛选状态 选中某朝代后只展示该朝代段
兴衰入口 根据当前筛选展示当前朝代文案
兴衰跳转 携带 `dynastyId` 跳转 `DYNASTY_INSIGHT`
年份节点 公元前年份显示“前 xxx”
朝代段 展示 `fullName`、`brief` 和“详情 →”
朝代详情 点击朝代段携带 `dynastyId` 跳转
事件详情 通过 `eventId` 加载事件、朝代、地点和相关诗文
节点标签 `found/prosper/turn/crisis/fall` 显示为建立、兴盛、转折、危机、衰亡
事件统计 打开事件详情后当天 `eventIds` 去重记录
收藏笔记 事件可以收藏到文件夹,也可以打开/创建事件笔记

常见问题复盘

1. 为什么时间轴不直接显示所有事件?

因为时间轴的第一层任务是建立历史顺序。如果一上来塞满事件,用户会被信息量压住。当前实现先展示朝代段,再通过朝代详情、兴替明鉴和事件详情逐层展开,更适合移动端。

2. 为什么有“兴衰”入口,而不是只做朝代详情?

朝代详情回答“这个朝代是什么”,兴替明鉴回答“这个朝代为什么兴盛、为什么转折、为什么衰亡”。两者不是一类信息。时间轴保留兴衰入口,可以让用户从顺序进入分析。

3. 为什么 HistoryPack 还要合并到 MOCK_DYNASTIES

这是项目演进阶段的兼容策略。旧服务仍然依赖 mock 数据,新的历史包通过注册函数合并进去,可以在不大改调用方的情况下让页面消费更完整的数据。

4. 为什么事件需要 nodeType

如果事件只有标题和年份,它只能被阅读;有了 nodeType,事件就可以参与兴衰分析、练习题生成、统计分类和视觉标识。它让历史数据更结构化。

5. 为什么事件详情也要收藏和笔记?

诗文和事件都是学习对象。用户可能收藏一首诗,也可能收藏“靖康之耻”“商鞅变法”这类历史节点。统一用 FavoriteTypetargetId 关联,后续收藏页、笔记页和统计页才能共用能力。

本章小结

第六篇的核心不是“画一条时间线”,而是把历史学习路径组织起来。

HistoryPack 负责把历史内容注册进运行时数据,TimelinePage 用朝代筛选、兴衰入口、年份节点和朝代段建立历史顺序,TimelineEventDetailPage 通过 eventId 展示事件背景、经过、影响、地点和相关诗文,StatsStore 把事件阅读沉淀为学习记录,FavoriteStoreNoteStore 让历史事件也能被长期整理。

这样,时间轴就不是一个孤立页面,而是从首页“一日一史”延展出来的历史主线。下一篇会继续深入“兴替明鉴”,拆解一个朝代如何被拆成四维总览和六类分析,让时间轴里的节点真正变成可理解、可复盘的历史洞察。

[#HarmonyOS](https://so.csdn.net/so/search/s.do?q=HarmonyOS&t=all&o=vip&s=&l=&f=&viparticle=&from_tracking_code=tag_word&from_code=app_blog_art) [#ArkTS](https://so.csdn.net/so/search/s.do?q=ArkTS&t=all&o=vip&s=&l=&f=&viparticle=&from_tracking_code=tag_word&from_code=app_blog_art) [#ArkUI](https://so.csdn.net/so/search/s.do?q=ArkUI&t=all&o=vip&s=&l=&f=&viparticle=&from_tracking_code=tag_word&from_code=app_blog_art) [#DevEco Studio](https://so.csdn.net/so/search/s.do?q=DevEco+Studio&t=all&o=vip&s=&l=&f=&viparticle=&from_code=app_blog_art) [#鸿蒙开发](https://so.csdn.net/so/search/s.do?q=%E9%B8%BF%E8%92%99%E5%BC%80%E5%8F%91&t=all&o=vip&s=&l=&f=&viparticle=&from_tracking_code=tag_word&from_code=app_blog_art)

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐