【OpenHarmony/HarmonyOs 】收藏、最近浏览与推荐探索:打造本地化学习状态闭环

本文基于我的 OpenHarmony/HarmonyOS 项目「物理视界 PhysicsVision」整理。项目中通过 PersistentStorage@StorageLink、收藏列表、最近浏览、推荐探索等功能,构建了一套轻量本地化学习状态闭环。
这一篇单独讲:一个学习 App 如何在不登录、不联网的情况下,也能记录用户学习路径。❤️

一、为什么学习 App 需要状态闭环?

如果一个学习应用每次打开都像第一次使用,用户体验会很割裂。
好的学习应用应该知道:

  • 用户学过哪些模型;
  • 用户收藏了哪些重点;
  • 哪些内容还没探索;
  • 用户最近停留在哪里;
  • 下次应该推荐什么。

这些能力不一定要依赖账号和服务器。
「物理视界」目前就是通过本地状态完成这些功能。

二、全局状态初始化

项目首页在 aboutToAppear 中初始化持久化字段:

PersistentStorage.persistProp('favorites', '')
PersistentStorage.persistProp('highScore', 0)
PersistentStorage.persistProp('totalPlayed', 0)
PersistentStorage.persistProp('visitedModels', '')

这里保存了四类数据:

  • favorites:收藏模型;
  • highScore:挑战最高正确率;
  • totalPlayed:挑战次数;
  • visitedModels:已浏览模型。

这些字段虽然简单,但足够支撑收藏、最近浏览、成就和设置页统计。

在实验室页中:

@StorageLink('favorites') favStr: string = ''
@StorageLink('visitedModels') @Watch('onVisitedChange') visitedModels: string = ''

在收藏页中:

@StorageLink('favorites') favStr: string = ''
@StorageLink('visitedModels') visitedModels: string = ''

在设置页中:

@StorageLink('favorites') favStr: string = ''
@StorageLink('highScore') highScore: number = 0
@StorageLink('totalPlayed') totalPlayed: number = 0

这样不同组件可以读写同一份状态。
用户在实验室收藏模型后,收藏页和设置页统计会同步变化。

四、收藏功能:用字符串保存索引列表

项目中收藏数据用逗号分隔字符串保存,例如:

1,5,12

判断是否收藏:

isFavorite(idx: number): boolean {
  if (this.favStr.length === 0) return false
  let parts: string[] = this.favStr.split(',')
  for (let i = 0; i < parts.length; i++) {
    if (parseInt(parts[i]) === idx) return true
  }
  return false
}

切换收藏:

toggleFavorite(idx: number): void {
  if (this.isFavorite(idx)) {
    let parts: string[] = this.favStr.split(',')
    let newParts: string[] = []
    for (let i = 0; i < parts.length; i++) {
      if (parseInt(parts[i]) !== idx) newParts.push(parts[i])
    }
    this.favStr = newParts.join(',')
  } else {
    this.favStr = this.favStr.length === 0 ? idx.toString() : this.favStr + ',' + idx.toString()
  }
}

这个方案很轻量,适合小规模模型列表。
如果后续数据量变大,可以改成 JSON 字符串或关系型本地存储。

五、收藏页:分类筛选和空状态

收藏页支持按分类筛选:

private filterNames: string[] = ['全部', '力学', '电磁学', '光学', '热学', '波动']

筛选函数:

getFilteredFavList(): number[] {
  let all: number[] = this.getFavList()
  if (this.selectedFilter === 0) return all
  let filterCat: string = this.filterNames[this.selectedFilter]
  let result: number[] = []
  for (let i = 0; i < all.length; i++) {
    if (this.categoryTags[all[i]] === filterCat) {
      result.push(all[i])
    }
  }
  return result
}

如果没有收藏,页面显示空状态提示。
这比空白页面更友好,也能引导用户去实验室页添加收藏。

六、最近浏览:记录已访问模型

实验室页中记录访问:

recordVisit(idx: number): void {
  if (this.visitedModels.length === 0) {
    this.visitedModels = idx.toString()
    return
  }
  let parts = this.visitedModels.split(',')
  for (let i = 0; i < parts.length; i++) {
    if (parseInt(parts[i]) === idx) return
  }
  this.visitedModels = this.visitedModels + ',' + idx.toString()
}

这里做了去重:已经访问过的模型不会重复加入。

点击模型时:

this.recordVisit(idx)
router.pushUrl({ url: this.routes[idx] }).catch(() => {})

这样路由跳转和学习记录同时发生。

七、最近浏览列表:取最近 6 个

updateRecentList(): void {
  if (this.visitedModels.length === 0) {
    this.recentList = []
    return
  }
  let parts: string[] = this.visitedModels.split(',')
  let result: number[] = []
  let start: number = parts.length > 6 ? parts.length - 6 : 0
  for (let i = parts.length - 1; i >= start; i--) {
    let val: number = parseInt(parts[i])
    if (val >= 0 && val < this.names.length) {
      result.push(val)
    }
  }
  this.recentList = result
}

显示最近浏览的价值很大:

  • 用户可以快速回到上次学习;
  • 不需要重新搜索;
  • 适合碎片化学习;
  • 可以作为跨设备续接的基础。

八、推荐探索:优先未学习的基础模型

推荐逻辑如下:

getRecommended(): number[] {
  let result: number[] = []
  for (let i = 0; i < this.names.length; i++) {
    if (!this.isVisited(i) && this.difficulties[i] === '基础') {
      result.push(i)
      if (result.length >= 4) break
    }
  }
  if (result.length < 4) {
    for (let i = 0; i < this.names.length; i++) {
      if (!this.isVisited(i) && this.difficulties[i] === '进阶') {
        result.push(i)
        if (result.length >= 4) break
      }
    }
  }
  return result
}

这是一种很朴素但有效的推荐:

  • 先补基础;
  • 再看进阶;
  • 不推荐已经访问过的内容;
  • 推荐数量控制在 4 个,避免信息过载。

九、统计数据:学习进度可视化

项目中有这些统计函数:

getVisitedCount(): number {
  if (this.visitedModels.length === 0) return 0
  return this.visitedModels.split(',').length
}

getFavCount(): number {
  if (this.favStr.length === 0) return 0
  return this.favStr.split(',').length
}

getProgressPercent(): number {
  return Math.round(this.getVisitedCount() / this.names.length * 100)
}

这些数据用于首页 Header、设置页数据统计和成就系统。
学习状态被多个页面消费,应用就形成了闭环。

十、可继续优化的方向

后续可以做:

  • 最近浏览支持重复排序,把最近点击的排最前;
  • 收藏数据改成 JSON,保存收藏时间;
  • 推荐逻辑结合挑战错题分类;
  • 增加“继续学习”入口;
  • 支持一键清空最近浏览;
  • 未来做跨设备同步或备份恢复。

总结

收藏、最近浏览和推荐探索看似是小功能,但它们是学习 App 的体验骨架。
有了这些状态,应用才能从“内容列表”变成“学习路径”。

「物理视界」当前使用本地 PersistentStorage@StorageLink 就实现了基础闭环,不需要账号,也不需要网络。
这种本地优先的设计,简单、稳定,也更符合教育应用的隐私需求。❤️

img

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐