【OpenHarmony/HarmonyOs 】实验室首页细节拆解:分类侧栏、搜索筛选与推荐探索交互
【OpenHarmony/HarmonyOs 】实验室首页细节拆解:分类侧栏、搜索筛选与推荐探索交互
本文基于我的 OpenHarmony/HarmonyOS 项目「物理视界 PhysicsVision」整理。实验室首页是整个应用的核心入口,它承载了 28 个物理模型的分类展示、年级筛选、关键词搜索、最近浏览、推荐探索和随机探索。
这一篇单独拆实验室首页的交互细节,重点对应“全新视觉与交互体验”和“端侧智能推荐”的主题。🔬
一、实验室首页承担什么角色?
一个物理学习 App 不能只是把所有模型堆成一个列表。
如果模型数量多,用户很快会遇到几个问题:
- 不知道从哪里开始学;
- 找不到某个具体模型;
- 不清楚哪些模型适合自己年级;
- 学过的内容和没学过的内容混在一起;
- 每次打开都要重新寻找上次内容。
「物理视界」的实验室首页就是为了解决这些问题。它不是简单列表,而是一个学习入口:
- 左侧分类侧栏:按学科方向筛选;
- 年级筛选:按初中、高一、高二、高三定位;
- 搜索栏:按模型名、描述、分类、难度搜索;
- 推荐探索:优先推荐未学习的基础模型;
- 最近浏览:帮助用户回到上次学习;
- 随机探索:降低选择成本。
这些细节组合起来,就形成了一个很完整的学习流。
二、模型数据的结构化组织
实验室页中,模型不是孤立存在的,而是用多个数组维护元数据:
privatenames:string[] = ['声音的传播','光的反射','串并联电路','匀变速直线运动','自由落体','力的合成与分解']privatedescs:string[] = ['探索声波在不同介质中的传播','观察光线的反射规律','理解串联与并联电路特性','研究匀变速运动的规律']privategradeTags:string[] = ['初中','初中','初中','高一','高一']privatecategoryTags:string[] = ['波动','光学','电磁学','力学','力学']每个模型都有名称、描述、年级、分类、难度和路由。
这让首页可以做筛选、搜索、推荐、统计和跳转。
如果后续接入元服务或近场快传,这些结构化数据也能直接复用。
三、左侧分类侧栏:让知识结构更清楚
项目中分类包括:
privatecategories:string[] = ['全部','力学','电磁学','光学','热学','波动']侧栏每个分类都有图标和选中态:
ForEach(this.categories, (c:string,index:number)=> {Column({space: 4 }){Image(this.catIconRes[index]).width(20) .height(20) .fillColor(this.selectedCategory===index?this.getCatAccent(index):$r('app.color.icon_gray'))Text(c).fontSize(10).fontWeight(this.selectedCategory===index? FontWeight.Bold : FontWeight.Normal).fontColor(this.selectedCategory===index?this.getCatAccent(index):$r('app.color.text_sub')) } .backgroundColor(this.selectedCategory===index? $r('app.color.bg_card'): Color.Transparent) .borderRadius(12).onClick(()=> { animateTo({duration: 200,curve: Curve.EaseOut }, ()=> { this.selectedCategory = index }) this.updateDisplayList()}) })这段代码体现了三个细节:
- 图标和文字都跟随选中态变化;
- 点击分类后有轻动画;
- 状态变化后立即刷新模型列表。
这种侧栏很适合知识型应用,因为它能把内容结构放在用户眼前。
四、年级筛选:贴合学习阶段
物理内容和年级强相关,所以项目提供了年级筛选:
privategrades:string[] = ['全部','初中','高一','高二','高三']筛选按钮使用轻量文本块:
ForEach(this.grades, (g:string,index:number)=> {Text(g).fontSize(11).fontWeight(this.selectedGrade===index? FontWeight.Bold : FontWeight.Normal).fontColor(this.selectedGrade===index? '#1A73E8' : $r('app.color.text_weak')) .backgroundColor(this.selectedGrade===index? $r('app.color.result_bg'): Color.Transparent) .borderRadius(10).onClick(()=> { animateTo({duration: 200,curve: Curve.EaseOut }, ()=> { this.selectedGrade = index }) this.updateDisplayList()}) })年级筛选可以避免学生一上来就看到太难的内容。
这也符合教育应用的基本原则:先让用户找到适合自己的学习层级。
五、搜索栏:多字段匹配
搜索不是只匹配名称,而是匹配多个字段:
let searchMatch:boolean= keyword.length ===0||this.names[i].includes(keyword) ||this.descs[i].includes(keyword) ||this.categoryTags[i].includes(keyword) ||this.gradeTags[i].includes(keyword) ||this.difficulties[i].includes(keyword)这意味着用户输入:
- “光学”可以找到光学模型;
- “高三”可以找到高三模型;
- “挑战”可以找到高难度内容;
- “电场”可以找到电场线、电场偏转。
对学习应用来说,搜索应该理解用户的意图,而不是只做标题匹配。
六、筛选主逻辑:分类、年级、搜索同时生效
完整筛选逻辑如下:
updateDisplayList(): void {this.listAnim =falselet result: number[] = [] let keyword: string =this.searchText.trim()for(let i =0; i <this.names.length; i++) { let categoryMatch: boolean =this.selectedCategory ===0||this.categoryTags[i] ===this.categories[this.selectedCategory] let gradeMatch: boolean =this.selectedGrade ===0||this.gradeTags[i] ===this.grades[this.selectedGrade] let searchMatch: boolean = keyword.length ===0||this.names[i].includes(keyword) ||this.descs[i].includes(keyword)if(categoryMatch && gradeMatch && searchMatch) { result.push(i) } }this.displayList = 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} } }returnresult }虽然这不是大模型 AI,但它是很实用的端侧智能:
- 不联网;
- 不上传用户数据;
- 根据本地学习状态推荐;
- 规则可解释。
对于教育应用来说,这种“本地可解释推荐”比黑盒推荐更可靠。
八、最近浏览:学习不断线
最近浏览列表取最近 6 个模型:
updateRecentList():void{if(this.visitedModels.length===0) {this.recentList= []return}letparts:string[] =this.visitedModels.split(',')letresult:number[] = []letstart:number= parts.length>6? parts.length-6:0for(leti = parts.length-1; i >= start; i--) {letval:number=parseInt(parts[i])if(val >=0&& val <this.names.length) { result.push(val) } }this.recentList= result }这个功能非常适合全场景智慧学习。
用户在手机上学过一个模型,后续如果做跨设备同步或备份恢复,就能在平板继续学习。
九、随机探索:降低选择压力
项目还提供随机探索:
navigateToRandom():void{letunvisited:number[] = []for(leti =0; i <this.names.length; i++) {if(!this.isVisited(i)) unvisited.push(i) }lettarget:number=0if(unvisited.length>0) { target = unvisited[Math.floor(Math.random() * unvisited.length)] }else{ target =Math.floor(Math.random() *this.names.length) }this.recordVisit(target) router.pushUrl({url:this.routes[target] }).catch(() =>{}) }它优先随机未探索模型,而不是完全随机。
这也是一个小小的学习引导:用户想不出学什么时,系统帮他迈出下一步。
十、总结
实验室首页的价值不只是展示模型,而是组织学习路径。
分类侧栏、年级筛选、关键词搜索、推荐探索、最近浏览、随机探索,这些功能共同构成了一个轻量但完整的学习入口。
这篇文章对应的主题是:全新视觉与交互体验 + 端侧智能推荐 + 全场景学习续接。
它没有脱离项目,因为所有细节都来自当前代码;它也没有空谈 AI,而是从本地学习状态出发,说明如何把一个模型列表做成真正可用的学习首页。🚀
