这一篇我想聊的是看起来很轻、做起来却很容易失衡的一类功能:桌面卡片。很多人第一次做运营看板卡片,最先关注的是“怎么把一块卡片显示出来”。但真正把它做成一个能用、可信、可跳转、可同步的业务入口以后,你会发现难点根本不在“显示”,而在“卡片和应用主页面之间,到底能不能长期保持一致”。

一、卡片最容易出现的问题,不是不好看,而是“不可信”

我这次做的是一个运营看板卡片,目标很明确:

  • 在桌面或负一屏,快速看到核心业务数据;
  • 支持手动刷新;
  • 点击卡片能直接进入应用详情页;
  • 卡片上的数据和应用内的数据要尽量一致。

最早做 Demo 的时候,我觉得事情很简单:

  • 从仓储里拿一份数据;
  • 绑定到卡片;
  • 点一下刷新就重新取一份;
  • 点卡片就跳详情页。

但一旦真正开始联调,很快就发现问题一点都不少:

  • 卡片上明明是旧数据,应用里却已经更新了;
  • 用户手动刷新以后,只改了数值,没有改刷新时间;
  • 卡片点进去能打开页面,但页面不知道这次来自卡片;
  • 卡片多实例存在时,哪个实例更新了,根本说不清;
  • 用户以为看到的是“实时数据”,其实是上一次缓存结果。

说白了,卡片功能最怕的,不是样式一般,而是用户不再相信它展示的内容。

二、我后来先把问题拆成三条链:刷新、同步、回流

卡片一旦上业务,就不能只当成一个 UI 组件来看。

我最后是按三条链来梳理它的:

  1. 刷新链:卡片什么时候拉数据,谁来触发刷新;
  2. 同步链:数据、刷新时间、状态文案,怎么一起更新;
  3. 回流链:点击卡片以后,应用如何识别来源并落到正确页面。

这三个词看起来抽象,但特别适合治理这类功能。因为卡片的难点,刚好就集中在这三件事上。

如果这三条链没理顺,表面上你已经“做出一张卡片”了,实际上只是做出了一张可能会过期、可能会跳错、也可能会误导用户的卡片。

三、刷新不是越频繁越好,关键是触发点得对

刚开始做卡片时,很多人会本能地往“高频刷新”上走。

可做一阵子就会发现,高频刷新并不等于高质量刷新。它可能带来两个问题:

  • 数据源压力变大;
  • 刷新动作频繁,但展示结果未必稳定。

所以我后来把刷新触发点分成了三类:

  • 首次添加卡片:初始化就拉一次;
  • 定时刷新:例如每 15 分钟刷新一次;
  • 用户主动刷新:用户点击刷新按钮时立即同步。

也就是说,刷新策略不是“拼命更新”,而是要能解释清楚:这次更新是为什么发生的。

在 FormAbility 这一层,我最后把这几个触发点都收到了同一条数据更新方法里:

private async updateFormData(formId: string) {
  const data: DashboardData = await this.repository.getDashboardData()
  const formData = formBindingData.createFormBindingData({
    orderCount: data.orderCount,
    activeUser: data.activeUser,
    amount: data.amount,
    refreshTime: data.refreshTime
  })
  await formInfo.updateForm(formId, formData)
  console.info(`updateFormData success, formId: ${formId}`)
}

我很喜欢这种收法,因为它带来一个非常直接的好处:

  • 首次加载走它;
  • 定时刷新走它;
  • 手动刷新也走它。

也就是说,卡片的数据出口只有一个。

这比每个触发点都自己拼一份卡片数据要稳定得多。

四、真正容易乱掉的,是“数据同步”而不是“数据刷新”

一开始我也以为,只要刷新成功,卡片就对了。

后来发现不是这样。

比如卡片上有三个数字:

  • 今日新增;
  • 活跃用户;
  • 成交金额。

如果这三个数字更新了,但“刷新时间”还是旧的,用户就会怀疑。反过来,如果刷新时间变了,但数值没变,也会让人觉得卡片不靠谱。

所以“同步”这件事,真正要同步的不是某一个字段,而是一组互相能解释的状态:

  • 核心数据;
  • 刷新时间;
  • 同步状态;
  • 数据来源;
  • 最近一次同步结果。

页面上我后面也专门给了一个可观察区域,把这些同步信息单独展示出来,而不是只给一个“同步成功”的结论。

这张图里红色标注圈出来的内容,我觉得特别适合拿来解释“同步链”:

  • 实例ID 解决的是“到底是哪一张卡片”;
  • 刷新时间 解决的是“这次数据什么时候来的”;
  • 同步结果 解决的是“卡片有没有真正更新成功”;
  • 回流目标页 则把同步和点击行为接到了同一条业务链上。

很多文章只讲卡片绑定数据,但如果不把这些同步证据展示出来,读者其实还是很难知道卡片为什么会“可信”。

五、点击回流最怕的是“能跳,但跳不明白”

卡片点进去打开页面,这件事看起来很基础,但真正做起来最容易留下“半截工程”。

什么叫半截工程?

就是用户点了卡片,页面确实打开了,可应用并不知道:

  • 这次打开来自卡片;
  • 来自哪一张卡片;
  • 用户点的是哪个区域;
  • 该进入首页、详情页还是某个子 Tab。

如果不把这些信息带进去,卡片就只是一个“能打开 App 的快捷方式”,而不是一个真正的业务入口。

所以我在点击回流这块做了两件事:

  1. 卡片事件统一走 onFormEvent;
  2. 回流时显式携带 from=card、page=Index 这类参数。

下面这段代码就是整个回流链的核心:

onFormEvent(formId: string, message: string) {
  if (message === 'card_click') {
    let want = new Want()
    want.bundleName = 'com.example.dashboard'
    want.abilityName = 'MainAbility'
    want.parameters = {
      page: 'Index',
      from: 'card',
      formId: formId
    }
    this.context.startAbility(want)
  }
}

这个写法最值钱的地方,不在“能打开页面”,而在“打开之后,页面有上下文”。

有了 from=card 以后,首页完全可以决定:

  • 是否显示卡片来源提示;
  • 是否直接滚到某个数据模块;
  • 是否记录一次卡片回流事件;
  • 是否在详情页回写停留统计。

这时卡片才真正从“展示模块”变成了“业务入口”。

六、应用页为什么要保留卡片预览和回流痕迹

做这类功能时,我现在特别不建议把卡片和应用页完全割裂开。

因为如果页面里一点卡片痕迹都没有,开发和测试很难验证两件事:

  • 卡片当前显示的内容,到底和应用内是不是一致;
  • 用户从卡片过来以后,这次回流是否真的生效。

所以我后面在应用页里有意保留了三块信息:

  • 当前卡片预览;
  • 最新刷新时间;
  • 卡片点击回流后的目标页说明。

这张手机图其实特别像真实项目调试时的“中间态页面”:

  • 上半部分让你能看到卡片当前展示什么;
  • “刷新入口”告诉你这次刷新由谁触发;
  • “状态同步”让你知道当前界面看到的是不是最新状态;
  • “卡片点击”则把桌面卡片和应用详情页的关系讲清楚了。

而且红色箭头、红圈这种讲解方式非常适合正文配图。它不会把画面做得很花,但能很快帮读者抓住重点。

七、为什么这次工程最后能稳定,我觉得关键是“实例可追踪”

很多桌面卡片示例在 Demo 层其实已经足够了,但真上业务以后,经常会少一个东西:实例追踪能力。

一旦用户在桌面放了多张卡片,或者卡片重新创建、重新绑定,问题就会变得非常现实:

  • 这次刷新的是哪一张卡片;
  • 某张卡片没更新,是数据问题还是实例问题;
  • 某次点击回流,到底来自桌面还是应用内按钮。

所以我后面几乎把所有关键节点都带上了 formId,包括:

  • 初始化添加;
  • 定时刷新;
  • 手动刷新;
  • 点击回流;
  • 同步日志。

这样一来,只要看到日志,就能很快知道哪条链路出了问题。

八、从开发视角看,这个项目真正成型是在“结构收拢”之后

如果从 DevEco 的工程视角回头看这次实战,我觉得最值得记下来的,不是卡片做得多漂亮,而是结构终于收拢了。

这张图里其实已经把整个结构讲得很清楚了:

  • 左边工程目录把 form、model、pages 分开了;
  • 中间代码把“卡片数据源”“定时刷新”“点击回流”都收在同一个 FormAbility 里;
  • 右侧模拟器同时展示了桌面卡片和应用详情页;
  • 底部日志则把“定时刷新事件”“数据同步日志”“点击回流日志”都串起来了。

也就是说,这个功能最后之所以稳,不是因为某一个 API 很强,而是因为:

  • 触发点被统一了;
  • 数据出口被统一了;
  • 回流入口也被统一了。

这三件事一旦收拢,卡片功能就不再是零碎补丁,而是一个有工程闭环的小系统。

九、本文小记

如果让我用一句话总结这次实战,我会写成:桌面卡片真正难的,不是“显示”,而是“长期一致”。

一致的是什么?

  • 数据一致;
  • 时间一致;
  • 状态一致;
  • 点击后的目标路径也一致。

对我自己来说,这次做完以后,有几个判断基本已经定下来了:

  • 卡片刷新一定要有清晰触发点;
  • 数据同步不能只改数字,还要同步时间和状态;
  • 点击回流一定要带来源和目标页参数;
  • 多实例卡片一定要可追踪;
  • 页面里最好保留一部分卡片联动痕迹,方便验证和排查。

如果你现在也在做 HarmonyOS 的卡片类能力,我的建议是:先别急着优化样式,先把刷新链、同步链、回流链这三条线走通。

这三条线一旦顺了,卡片才真正配得上“业务入口”这四个字。

Logo

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

更多推荐