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

证据板围绕当前目标组装
页面先确定 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 官方说明。
状态切换的失败出口
讲解请求进入加载状态后,证据板仍保留当前神兽与出处;结果可用时更新导览卡,失败时显示可恢复反馈。页面不以空白替代失败,也不把上一次神兽的关系结果混入新的选择。
更多推荐

所有评论(0)