当图鉴从“看一张卡片”升级到“沿线索探索”时,页面不能只放一段说明文字。读者需要知道内容来自哪里、哪些事实已定位、哪些属于本馆导览,以及当前神兽和哪些节点相连。山海万灵把这些信息组织为证据板:详情页与数字馆长都读取同一个神兽、来源与关系上下文。

运行界面

证据板围绕当前目标组装

页面先确定 selectedBeastId,再解析神兽、来源、知识卡和关联关系。来源信息不是页面临时字符串,而是数据模型中的 SourceInfo;关系也不是图片上的装饰线,而是带类型、目标和说明的记录。

interface EvidenceBoardState {
  beast: BeastItem
  source: SourceInfo
  learningCards: BeastLearningCard[]
  relations: CuratorRelationItem[]
  resultState: CuratorResultState
}

type CuratorResultState = 'IDLE' | 'LOADING' | 'AI' | 'FALLBACK' | 'FAILED'

同一个目标在详情页里显示出处与知识卡,在馆长页里显示当前讲解和下一步入口。两处都不自行拼接历史事实,从而避免一页写“东海路线”、另一页把它当作原典地理。

来源、核验与导览分层呈现

应龙详情页把原典条目、数字文本核验、异文说明和人工校注状态放进来源面板;馆长模块在此基础上生成导览文字。前者回答“内容从哪里来”,后者回答“这条线索怎样继续看”。

private sourceLabel(source: SourceInfo): string {
  return source.name + ' · ' + source.verificationLabel
}

private relationText(item: CuratorRelationItem): string {
  return item.label + ':' + item.detail
}

private canRenderGuide(state: CuratorResultState): boolean {
  return state === 'AI' || state === 'FALLBACK'
}
面板 输入 输出给用户的内容
出处 SourceInfo 条目、定位、数字核验与复核状态
知识与故事 学习卡集合 已登记的最小事实和导览说明
图谱关系 关系集合 与当前神兽相关的区域、展厅或叙事线索
馆长结果 CuratorResultState 加载、讲解、降级或失败反馈

选择主题后再请求结果

馆长页让用户选择神兽、故事、地图或展厅主题,再发出请求。请求期间按钮进入同步状态,结果回到同一个状态对象;失败时保留当前神兽和出处板,不把已读内容清空。

async function requestGuide(topic: CuratorGuideTopic): Promise<void> {
  this.resultState = 'LOADING'
  try {
    this.selectedGuide = await this.viewModel.explainBeast(this.selectedBeastId, topic)
    this.resultState = this.selectedGuide.fallback ? 'FALLBACK' : 'AI'
  } catch (_) {
    this.resultState = 'FAILED'
  }
}

用单一状态源约束卡片的更新顺序

页面的关键不是把信息卡片排在同一屏,而是让每次主题切换都遵循同一条状态链:先锁定目标神兽,再切换结果状态,最后提交导览、出处与关系集合。这样,卡片拿到的是同一轮请求的结果;旧请求晚到时不会覆盖新目标。对于“应龙”这样的多关系对象,读者也不会看到已经切到其他神兽、但关系区仍残留应龙路线的错位内容。

private beginGuideRequest(beastId: string, topic: CuratorGuideTopic): number {
  this.curatorRequestSequence += 1
  this.curatorTargetBeastId = beastId
  this.curatorResultState = 'LOADING'
  this.recommendations = []
  this.learningCards = []
  return this.curatorRequestSequence
}

private shouldApplyGuide(sequence: number, beastId: string): boolean {
  return sequence === this.curatorRequestSequence
    && beastId === this.curatorTargetBeastId
}

private restoreGuideActions(): void {
  this.curatorResultState = 'IDLE'
  this.notice = '可重新选择主题后继续导览。'
}

这组字段把“谁是当前对象”“本轮是否仍有效”“结果处于哪个阶段”拆开保存。curatorRequestSequence 只负责淘汰过期请求,curatorTargetBeastId 决定页面展示对象,curatorResultState 决定结果卡的视觉分支。三者不相互代替,状态切换才不会把网络时序错误伪装成内容错误。

事件 必须更新的状态 页面可见结果 不能发生的副作用
选择新的神兽 目标 ID、请求序号 基础档案与出处先对齐新对象 继续显示旧对象的关系列表
提交主题 结果状态为 LOADING 结果卡显示进行中,证据板仍可阅读 清空已确认的出处字段
返回导览结果 导览、学习卡、关系集合 同一对象的讲解和关系同时更新 让过期请求覆盖新选择
返回降级结果 结果状态为 FALLBACK 标明本地可用的导览内容 把降级内容标为 AI 返回
请求失败 结果状态为 FAILED 给出可恢复提示与再次选择入口 以空白卡片隐藏失败原因

结果卡与关系区的组合边界

结果卡负责说明“本轮导览给了什么”,关系区负责说明“这些内容怎样连接到区域、展厅和叙事线索”。两个组件通过同一份馆长页面数据组合,而不是在各自内部再次查询。这样在窄屏时可以纵向排布,在宽屏时可以并列显示,数据边界不会随布局变化而漂移。

ShanhaiCuratorResultCard({
  resultState: this.resultState,
  guide: this.selectedGuide,
  selectedBeast: this.selectedBeast,
  onRetry: () => this.requestGuide(this.selectedTopic)
})

ShanhaiCuratorSourceRelations({
  source: this.selectedBeast.sourceInfo,
  learningCards: this.learningCards,
  relations: this.recommendations,
  onOpenRelation: (target) => this.openRouteTarget(target)
})

组合时应保持三个边界。第一,结果卡不拼接出处文本,它只消费已准备好的导览结果和状态。第二,关系区不决定请求成功与否,它只展示当前对象可用的来源、学习卡和连接线索。第三,页面容器集中处理重试、切换对象和进入下一站,子组件通过回调表达用户动作。这样的拆分让状态分支集中在页面层,组件可以围绕各自的阅读任务演进。

从回读结果验收证据板

验收不以“看见一个按钮”作为结论,而是检查一次完整动作之后的内容是否互相一致。选择应龙主题并进入馆长模块后,导览段落、出处条目、核验状态和“与应龙相关”的关系区应同时归属于应龙;切换到另一个主题后,旧关系和旧讲解不能继续停留在新对象的结果区。加载、降级和失败分支则分别保留可读的基础档案,避免读者在等待或恢复时失去已确认的来源线索。

function assertEvidenceBoard(result: EvidenceBoardState): boolean {
  const sameBeast = result.beast.id === result.source.beastId
  const hasGuide = result.resultState === 'AI'
    || result.resultState === 'FALLBACK'
    || result.resultState === 'LOADING'
  const relationTargetsValid = result.relations
    .every((item) => item.fromBeastId === result.beast.id)
  const fallbackReadable = result.resultState !== 'FALLBACK'
    || result.guide.content.length > 0
  return sameBeast && hasGuide && relationTargetsValid && fallbackReadable
}

function canOpenNextStop(target: CuratorRouteTarget): boolean {
  return target.kind === 'REGION'
    || target.kind === 'HALL'
    || target.kind === 'BEAST'
}

运行时选择“神兽”并打开模块后,界面会展示应龙的导览段落、出处条目和“与应龙相关”的关系区。该结构把选择、来源与结果放入明确状态,而不是让多个卡片各自保存一份文本。更多 ArkUI 状态模型可参考 HarmonyOS 官方说明

状态切换的失败出口

讲解请求进入加载状态后,证据板仍保留当前神兽与出处;结果可用时更新导览卡,失败时显示可恢复反馈。页面不以空白替代失败,也不把上一次神兽的关系结果混入新的选择。

Logo

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

更多推荐