在文化知识应用中,读者从一只神兽继续阅读时,下一张卡片不能只按列表顺序出现。山海万灵把神兽、地域和展厅放进同一组稳定标识:推荐项既带有目标节点,也带有“同展厅关联”“同区域关联”等可读原因。页面据此展示推荐卡,接口不可用时仍能基于本地目录给出一致的导览结果。

白泽详情页的图谱推荐卡展示獬豸的同展厅关联,以及应龙、九尾狐等补充图谱覆盖

从展品目录提取可比较的节点

每条神兽记录包含 idregionIdhallId。这三个字段是推荐计算的输入,而名称、摘要和卡片配色属于展示信息。把关联键放在领域对象中,图鉴、馆长导览和展厅页面就不必各自维护一份关系表。

export interface GraphRecommendationItem {
  nodeType: string
  nodeId: string
  name: string
  summary: string
  graphReason: string
}

export interface BeastItem {
  id: string
  name: string
  regionId: string
  hallId: string
  summary: string
}

nodeId 保持业务主键,卡片组件再用它查找完整神兽资料。这样推荐结果不会携带两套容易漂移的名称和图片数据;当目录补充别名或来源信息时,展示层仍从同一份神兽集合取值。

字段 负责的问题 页面使用方式
regionId 表达神兽所属导览地域 优先推荐同区域的相关条目
hallId 表达当前主题展厅 为同馆展品提供更高关联度
nodeId 锁定可打开的目标节点 点击推荐卡后进入对应图鉴详情
graphReason 解释推荐来源 在卡片上下文中显示关联标签

推荐排序先保留关系,再处理展示

本地推荐并不把“相关”写死在页面。ViewModel 先排除当前神兽,再按地域和展厅计算分数;同地域得到两分,同展厅额外得到一分。排序后的前四项被映射为轻量推荐对象,页面只消费稳定的输出结构。

private buildLocalRecommendations(source: BeastItem, beasts: BeastItem[]): GraphRecommendationItem[] {
  return beasts
    .filter((item: BeastItem) => item.id !== source.id)
    .slice()
    .sort((left: BeastItem, right: BeastItem) => {
      const leftScore: number = (left.regionId === source.regionId ? 2 : 0)
        + (left.hallId === source.hallId ? 1 : 0)
      const rightScore: number = (right.regionId === source.regionId ? 2 : 0)
        + (right.hallId === source.hallId ? 1 : 0)
      return rightScore - leftScore
    })
    .slice(0, 4)
    .map((item: BeastItem): GraphRecommendationItem => ({
      nodeType: 'BEAST',
      nodeId: item.id,
      name: item.name,
      summary: item.summary,
      graphReason: item.hallId === source.hallId ? '同展厅关联'
        : (item.regionId === source.regionId ? '同区域关联' : '补充图谱覆盖')
    }))
}

这个顺序让“关系理由”来自同一套计算,而不是在 UI 中拼接说明文字。若两个候选只有地域相同,读者会看到同区域关联;当候选同时位于同一展厅,标签会升级为同展厅关联。

远端图谱与本地导览共用输出契约

网络数据源请求图谱推荐时,将服务端返回投影为 GraphRecommendationItem。本地算法和 HTTP 映射最终都交给同一种类型,因此组件不需要分辨数据来自服务端还是内置目录。

async listGraphRecommendations(nodeType: string, nodeId: string): Promise<GraphRecommendationItem[]> {
  const response = await this.apiClient.getJson<ApiEnvelope<ApiGraphRecommendationPayload>>(
    '/graph/recommendations?nodeType=' + nodeType + '&nodeId=' + nodeId
  )
  return response.data.items.map((item: ApiGraphRecommendation): GraphRecommendationItem => ({
    nodeType: item.nodeType,
    nodeId: item.nodeId,
    name: item.name,
    summary: item.summary,
    graphReason: item.graphReason
  }))
}
场景 结果来源 保持不变的边界
图谱接口正常返回 HTTP 映射后的推荐项 nodeId、名称、摘要和关联理由
接口请求失败 本地目录计算的前四项 点击卡片仍可打开目标神兽
推荐为空 不渲染推荐面板 主导览内容不因空列表中断
新增关系类型 扩展 graphReason 的来源 卡片组件继续按字符串渲染上下文

推荐面板只负责渲染与跳转

推荐面板根据数组是否为空决定是否出现。每个条目用 nodeId 作为列表稳定键,再将关联理由传入神兽卡片。点击回调只上报目标神兽标识,路由和详情加载仍由外层页面统一处理。

if (this.recommendations.length > 0) {
  Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
    ForEach(this.recommendations, (item: GraphRecommendationItem) => {
      ShanhaiBeastCard({
        beast: this.recommendationBeast(item),
        contextText: '关联:' + item.graphReason,
        onOpen: (beastId: string) => this.onOpen(beastId)
      })
    }, (item: GraphRecommendationItem) => item.nodeId)
  }
}

这种拆分避免在卡片内部重新计算关系,也避免把网络状态塞进组件。窄屏时推荐卡纵向排列,较宽的内容区使用两列;两种布局共用同一组推荐对象和点击回调。

关系面板保留出处与导览边界

馆长结果页同时显示来源、地域和展厅关系。展示文案把“本馆导览地域”“本馆主题展厅”与古籍事实区分开:前者是应用中的导航关系,后者由来源字段和核验信息承载。关系数组为空时,组件按照当前神兽的地域与展厅生成保底导览行,不让信息面板变成空白。

private displayedRelations(): CuratorRelationItem[] {
  if (this.relations.length > 0) return this.relations.slice(0, 2)
  const region = SHANHAI_REGIONS.find((item: RegionItem) => item.id === this.beast.regionId)
  const hall = SHANHAI_HALLS.find((item: HallItem) => item.id === this.beast.hallId)
  return [
    { type: '本馆导览地域', targetType: 'REGION', targetId: this.beast.regionId,
      label: region?.name ?? '已登记地域', detail: '从此地域展开本馆导览,不作为古籍地理断言' },
    { type: '本馆主题展厅', targetType: 'HALL', targetId: this.beast.hallId,
      label: hall?.name ?? '已登记展厅', detail: '可继续查看本馆主题资料,不作为古籍分类' }
  ]
}

验收关注关联原因和跳转目标

验证时选择一只神兽进入馆长导览,查看关系区和推荐卡中的关联说明;再打开一张推荐卡,核对详情对象与卡片的 nodeId 一致。随后切换到没有远端图谱结果的状态,检查本地目录仍输出可打开的推荐项。ArkUI 的状态管理与组件更新机制可参考 HarmonyOS 状态管理文档

推荐关系由领域标识、排序策略和展示组件依次衔接。服务端可以逐步增加更丰富的关系权重,应用仍通过同一输出契约保持导览卡片、关系标签和跳转行为一致。

Logo

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

更多推荐