第47篇:搜索组件——考题搜索功能实现
一、引言
搜索功能是驾考应用中帮助用户快速定位特定考题的重要工具。当用户遇到不会的题目想查找相关知识点,或者想针对某个特定主题进行练习时,搜索功能就派上了用场。DriverLicenseExam 项目的search组件封装了搜索入口、搜索控制器和搜索结果跳转的完整流程。本文将深入解析其实现。
二、搜索组件结构
2.1 组件目录
search/ ├──src/main/ets/ │ ├── components/ │ │ └── SearchComponents.ets← 搜索 UI 组件 │ └── controller/ │ └── SearchController.ets← 搜索控制器 └── Index.ets← 模块导出2.2 搜索流程
用户点击搜索框 │ ▼ 进入 SearchPage │ ▼ 输入关键词 │ ▼ SearchController.search(keyword) │ ▼ 在 ExamDetail 中匹配题目文本 │ ├── 匹配成功 → 跳转到练习页,展示搜索结果 │ └── 匹配失败 → 显示"未找到相关题目"提示三、搜索入口实现
3.1 主页面搜索框
搜索入口位于 HomeView 顶部,与城市选择并列:
// HomeView.ets - 顶部搜索框Row(){// 城市选择Row(){Text(this.guideService.getGuideData().city) .fontSize(14).fontColor($r('sys.color.font_primary')) .fontWeight(FontWeight.Medium).margin({ right:4}) .maxFontScale(1);Image($r('app.media.city_triangle')) .width(12).height(12); } .onClick(()=> { this.vm.navStack.pushPathByName('selectCityView',true); }) .margin({ left:12, right:8});// 搜索框Row({space: 8 }){Image($r('app.media.ic_glass')) .width(16) .fillColor($r('sys.color.icon_primary'));Text('请输入关键字搜索').fontSize(14).fontColor($r('sys.color.font_secondary')) .maxFontScale(1); } .backgroundColor($r('app.color.search_background')) .height(40) .layoutWeight(1).margin({ right:12}) .padding(12) .borderRadius(8).onClick(()=> { this.vm.navStack.pushPathByName('SearchPage',undefined); }); }这个搜索框的 UI 设计有几个值得注意的点:
- 搜索图标:使用
$r('app.media.ic_glass')引用本地资源 - 占位文本:使用
Text组件而非TextInput的 placeholder,因为搜索框实际上是点击跳转的入口 - **layoutWeight(1)**:搜索框占据城市选择后的剩余空间
- **maxFontScale(1)**:限制搜索框文字不跟随系统字体缩放
3.2 搜索页面
// SearchPage.ets@ComponentV2export struct SearchPage {@Localkeyword: string ='';@LocalsearchResults: ExamDetail[] = [];privatesearchController: SearchController = new SearchController(); build() { NavDestination() { Column() {// 搜索输入框TextInput({ placeholder:'输入考题关键词...', text:this.keyword }) .height(48) .padding({ left:16}) .borderRadius(24) .backgroundColor($r('sys.color.comp_background_primary')) .onChange((value: string) => {this.keyword = value;this.performSearch(); });// 搜索结果列表if(this.searchResults.length >0) { List() { ForEach(this.searchResults, (item: ExamDetail) => { ListItem() {this.searchResultItem(item); } }); } .layoutWeight(1); }else{// 空状态Column() { Image($r('app.media.ic_search_empty')).width(80).height(80); Text('未找到相关题目').fontSize(14).fontColor('#999'); } .layoutWeight(1) .justifyContent(FlexAlign.Center); } } .padding(16); } .title('搜索考题'); } performSearch() {if(this.keyword.trim().length ===0) {this.searchResults = [];return; }// 通过 SearchController 执行搜索this.searchResults =this.searchController.search(this.keyword); } }四、搜索控制器实现
4.1 SearchController
// SearchController.etsexportclassSearchController{privateexamDetails:ExamDetail[] = [];constructor() {// 初始化时获取所有考题数据this.examDetails=ExamService.instance(null).getAllExamDetails(); }search(keyword:string):ExamDetail[] {if(!keyword || keyword.trim().length===0) {return[]; }constlowerKeyword = keyword.toLowerCase();// 在题目文本和章节名称中匹配returnthis.examDetails.filter(item=>item.question.toLowerCase().includes(lowerKeyword) || item.chapterName.toLowerCase().includes(lowerKeyword) ); }// 按题型筛选搜索结果searchByType(keyword:string,type:QuestionTypeEnum):ExamDetail[] {constresults =this.search(keyword);returnresults.filter(item=>item.questionType===type); } }4.2 搜索结果的跳转
搜索结果的点击跳转到练习页面,使用EXAM_MANAGER_TYPE.search类型:
// 搜索项点击事件@Builder searchResultItem(item: ExamDetail){Row(){Column({space: 4 }){Text(item.question).fontSize(14).maxLines(2).textOverflow({overflow: TextOverflow.Ellipsis });Text(item.chapterName).fontSize(12).fontColor('#999'); } .layoutWeight(1); } .padding(12) .width('100%') .onClick(()=> { const param: ROUTE_PARAM = { title: '搜索结果',type:EXAM_MANAGER_TYPE.search, keyword: this.keyword, }; this.vm.navStack.pushPathByName('practiceView',param); }); }五、搜索数据流
5.1 搜索与 ExamService 的集成
搜索功能与 ExamService 的getExamQuestionList方法集成:
// ExamService.ets - 搜索数据过滤case EXAM_MANAGER_TYPE.search: examData = originData.filter(item=>item.question.includes(searchCondition));break;5.2 搜索参数传递
// 搜索跳转时传递 keyword 参数constparam: ROUTE_PARAM = { title:'搜索结果', type: EXAM_MANAGER_TYPE.search, keyword:'交通标志',// 搜索关键词};// PracticeView 接收参数.onReady((ctx: NavDestinationContext) => {constparam =this.vm.navStack.getParamByIndex(index -1)asROUTE_PARAM;if(param.keyword) {this.keyword = param.keyword; }this.getExamManger(); });六、搜索功能优化
6.1 防抖处理
为了避免用户每次输入都触发搜索,可以添加防抖机制:
privatesearchTimer: number = -1; performSearch() {if(this.searchTimer !== -1) { clearTimeout(this.searchTimer); }this.searchTimer = setTimeout(() => {if(this.keyword.trim().length ===0) {this.searchResults = [];return; }this.searchResults =this.searchController.search(this.keyword); },300);// 300ms 防抖}6.2 搜索历史
可以扩展搜索功能,记录用户的历史搜索关键词(通过 Preferences 持久化):
saveSearchHistory(keyword:string){ const preferencesUtil =PreferencesUtil.getInstance(); const preference = preferencesUtil.getPreferences(context);lethistory = preferencesUtil.getPreferencesValue(preference, 'searchHistory')asstring[];if(!history) history =[];if(!history.includes(keyword)) { history.unshift(keyword);if(history.length >10) history.pop();// 只保留最近10条preferencesUtil.preferencesPut(preference, 'searchHistory',history); } }七、总结
搜索组件通过 SearchController 和 SearchComponents 的组合,实现了完整的搜索功能:
- 搜索入口:主页面的搜索框,点击跳转到搜索页面
- 搜索逻辑:关键词匹配题目文本和章节名称
- 结果展示:列表展示搜索结果,支持点击跳转
- 数据集成:与 ExamService 的 EXAM_MANAGER_TYPE.search 集成
- 扩展性:支持防抖、搜索历史等优化
关键源码文件:
components/search/src/main/ets/components/SearchComponents.ets— 搜索组件components/search/src/main/ets/controller/SearchController.ets— 搜索控制器products/entry/src/main/ets/pages/home/HomeView.ets— 搜索入口products/entry/src/main/ets/pages/home/SearchPage.ets— 搜索页面commons/datasource/src/main/ets/ExamService.ets— 搜索数据源