当前位置: 首页 > news >正文

乒乓球口袋教练 HarmonyOS 学习应用(05):搜索与分类组合过滤

一、把关键词和专项筛选放进同一个查询入口

课程多起来以后,用户常常先输入“反手”,再把范围切到相持模块。若关键词和模块按钮分别在页面各自过滤,列表条数、课程标签和点击后的详情很容易不一致。本项目把两个条件交给 SearchService.search:keyword 先 trim 并转小写,moduleId 决定课程范围,再对标题、描述、关键词和子分类标签做同一轮匹配。

// 简单全字段匹配:标题 + 描述 + 关键词 + 子分类标签 export class SearchService { static search(keyword: string, moduleId?: string): SearchHit[] { const kw: string = keyword.trim().toLowerCase(); return COURSES .filter(c => moduleId === undefined || moduleId === 'all' || c.moduleId === moduleId) .filter(c => SearchService.matches(c, kw)) .map<SearchHit>((c: Course): SearchHit => { const m: LearnModule | undefined = findModule(c.moduleId); return { course: c, moduleTitle: m?.title ?? '' } as SearchHit; }); } private static matches(c: Course, kw: string): boolean { if (kw.length === 0) return true; if (c.title.toLowerCase().indexOf(kw) >= 0) return true; if (c.desc.toLowerCase().indexOf(kw) >= 0) return true; if (c.subCategoryLabel.indexOf(kw) >= 0) return true; for (const k of c.keywords) { if (k.toLowerCase().indexOf(kw) >= 0) return true; } return false; }

二、空关键词并不等于跳过模块边界

模块条件的 all 不是删除筛选逻辑,而是显式表示“允许所有 moduleId”。因此空关键词时仍会返回当前模块的完整课程集合;输入词后也不会意外跨到发球或战术模块。这个约束让顶部筛选按钮和搜索框是两个输入,却只有一个可解释的结果集合。

参与者输入输出或约束
模型或配置稳定标识、模式或模块字段给出可追溯的工程事实
服务或系统能力经过归一化的请求返回明确结果或失败原因
页面回读后的结果只渲染,不保存第二份事实
} private hits(): SearchHit[] { return SearchService.search(this.keyword, this.moduleFilter); } @Builder resultSection() { Column() { if (this.hits().length === 0) { EmptyState({ emoji: '🔍', title: '未找到相关内容', subtitle: '换个关键词试试,或在分类里浏览' }); } else { this.resultTitle(); Column({ space: AppSizes.s2 }) { ForEach(this.hits(), (h: SearchHit) => { this.hitRow(h); }, (h: SearchHit) => h.course.id); } .padding({ left: AppSizes.s4, right: AppSizes.s4 });

三、命中结果为何要带回匹配字段

SearchHit 不只保存课程对象,还保留 matchedFields。页面可以提示用户命中的是标题、描述还是关键词,而不是把所有结果都说成标题匹配。对于“旋转”这类可能出现在描述中的词,这比只调用 title.includes 更接近用户的真实预期,也让后续高亮有稳定来源。

Column({ space: AppSizes.s2 }) { ForEach(this.hits(), (h: SearchHit) => { this.hitRow(h); }, (h: SearchHit) => h.course.id); } .padding({ left: AppSizes.s4, right: AppSizes.s4 }); } } .width('100%'); } @Builder resultTitle() { Row() { Text('搜索结果(' + this.hits().length + ')') .fontSize(AppText.fsTitle) .fontWeight(AppText.fwSemi) .fontColor(this.palette.textPrimary) .layoutWeight(1); } .width('100%') .padding({ left: AppSizes.s4, right: AppSizes.s4, top: AppSizes.s2, bottom: AppSizes.s2 }); }

四、搜索页和课程详情怎样交接

点击命中项后,SearchPage 传递的是 course.id;课程详情再按 id 查回模型。这样搜索结果不会把一份旧课程对象塞进路由,也不会因为返回搜索页而保留已经失效的对象。找不到 id 时应留在可解释的结果或空态,不能拼一个默认课程继续播放。

情况容易出现的错误本文采用的处理
数据或配置缺项伪造默认成功状态停在可解释的失败或空态
页面重进使用上一页残留对象从模型、服务或系统重新回读
重复动作再写一遍相同业务事实由稳定入口或回调收敛
import { packagedVideoFileName } from './CourseVideoAssets'; export interface Course { id: string; moduleId: string; subCategory: string; subCategoryLabel: string; title: string; desc: string; thumbKey: string; videoSrc?: string; durationSec: number; order: number; intro: Block[]; keyPoints: Block[]; mistakes: Block[]; training: Block[]; keywords: string[]; externalVideoRefs?: ExternalVideoRef[]; } export interface SubCategory { id: string;

五、筛选回归要观察哪些具体结果

回归时先在全部范围搜索一个只出现在关键词中的动作术语,再切到不含该术语的专项确认结果归零;随后清空关键词,确认当前专项课程恢复;最后点击一条命中课程、返回并重进搜索页,检查标题、专项标签和命中字段仍来自同一条查询链路。

验收阶段实际动作回读重点
前置确认启动正确 bundle 或打开目标页标题、入口与模块身份
主题操作执行搜索、切换、完成或跳转服务/系统返回的结果
重进检查返回、重启或切换范围后再进入事实没有依赖旧页面残留

六、实现边界与维护顺序

搜索词、专项模块和课程字段在同一条查询链路中收敛,避免页面分别筛选后出现标题、分类与结果数量互相矛盾。 新增需求时应先补齐模型、配置或服务合同,再调整页面入口;把同一个判断复制到多个组件,短期看似方便,后续会使结果无法回读。ArkTS 状态管理的基础机制可参考 HarmonyOS 官方文档。

七、继续扩展时的约束

搜索条件的归一化必须早于排序。先把关键词转换为可比较的值,再确定模块范围,最后计算匹配字段,结果才能稳定复现。若把排序写在页面中,用户从课程详情返回后可能看到同一关键词却是另一种顺序,测试也无法知道变化来自课程数据还是渲染时机。

对于不存在的 moduleId,服务不应该宽容地退回全部课程;那会让路由或按钮传错参数时看起来仍有结果。明确返回空集合配合页面空态,更容易暴露调用链错误。真正需要全量搜索时,调用方显式传 all,使阅读代码的人一眼能看懂扩大范围是有意的。

后续若加入历史搜索或远程建议词,历史项只保存用户输入,不缓存旧 SearchHit。重新打开历史词时仍需经过当前 SearchService,这样课程下架、关键词修改和模块调整才能立刻反映到结果中。

结果数量为何必须从命中集合计算

搜索页标题中的数量直接来自 hits().length,而不是由输入框事件维护一个计数器。前者会随关键词、模块和课程模型重算,后者容易在清空输入、返回页面或课程数据变化后留下旧数字。对于用户而言,数量、每一行课程和命中说明应始终是同一集合的三个投影。

匹配规则改变时怎样验收

增加一个关键词字段后,要准备标题不含该词但 keywords 含该词的课程,确认它被召回;再准备同词但不同模块的课程,确认模块筛选排除它。两组数据分别覆盖匹配能力和范围能力,不能只用一条“搜索成功”的截图混在一起。

http://www.jsqmd.com/news/1293640/

相关文章:

  • Forza-Mods-AIO:内存Hook与游戏修改的3大核心技术深度解析
  • OpenSSL实战:手把手创建与管理SAN证书,解决多域名HTTPS难题
  • 2026年东莞办公室出租房东推荐:优质服务商选型参考 - 汇聚至此
  • 小龙虾搭建OpenClaw环境,2026稳定版部署全流程
  • Matplotlib三角剖分实战:从散乱数据到三维曲面可视化
  • 2026年KDX康得新爱创酷七星贴膜店综合实力解析 - 百航
  • Oracle:SELECT * FROM TABLE(...)
  • Keil MDK中STM32现代C++开发环境配置与优化指南
  • 成人纸尿裤生产线设备源头工厂
  • 智能体开发之MCP协议从理论到实践
  • 如何轻松解决跨平台格式兼容问题:开源HEIF实用工具的实战指南
  • 2026年求推荐靠谱的房产中介ERP系统
  • PTC电辅热技术原理与应用:解决空调制热瓶颈的关键
  • C++日期时间处理全解析:从chrono库到C++20新特性
  • 2026年8月恩施非急救救护车转运指南:山区转运如何安排 - 小校长
  • 2026年5月-7月北京代账公司参考推荐 - yunying2025
  • 从业者视角:智微工业工控机为何成为激光行业的主流选型参考
  • 5分钟搞定B站视频转文字:开源神器bili2text完整使用指南
  • Multisim仿真调幅电路:从原理到实践,掌握AM调制与解调
  • 乒乓球口袋教练 HarmonyOS 学习应用(06):主题 Token 与球台视觉风格
  • Navicat重置工具Mac版终极指南:轻松实现无限试用的完整解决方案
  • 从ASK/FSK信号调制到雷达通信系统:基础原理与工程实践全解析
  • 大模型Agents工作流优化与本地部署实践
  • RFID电力工器具全流程数字化管理方案设计与实践
  • A-59工业级语音模块:十种工作模式的架构逻辑与接口兼容性分析
  • Prompt Drift 检测实战:Taotoken 监控 4 个模型 30 天后,我们这样止损
  • Codex 工程化落地指南 05:.codex/config.toml 与权限配置——沙箱: 审批、网络访问与可信项目
  • 2026年07月浙江三联环保科技股份有限公司LWnj系列泥浆分离脱水机市场格局与价值解析 - 优企名品
  • 徐汇业主参考|2026.7月上海专属防水修缮深度指南:适配长三角滨海软土地质、全域辖区堵漏、根治申城梅雨季渗漏顽疾 - 吉林同城获客
  • 一加15顶配版游戏性能实测:骁龙8 Gen4与散热系统深度解析