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

HarmonyOS7 搜索页最容易做成半成品:历史、热词、结果页这次一次补齐

文章目录

    • 前言
    • 搜索栏组件
    • 输入联想与防抖搜索
    • 搜索历史:PersistentStorage 持久化
    • 搜索落地页:历史 + 热词
    • 搜索结果页:商品列表 + 筛选排序
    • 空状态处理
    • 搜索执行的入口
    • 几点实用建议

前言

搜索是电商 App 的核心入口之一。用户明确知道自己要什么的时候,搜索比一层层翻分类快得多。这篇我们把搜索模块做完整:搜索栏交互、历史记录、热词推荐、搜索结果页,外加筛选排序和空状态处理。

搜索栏组件

搜索栏要支持两个场景:首页顶部的简洁版(点击跳转到搜索页),和搜索页顶部的完整版(输入框 + 取消按钮)。我把它们做成了同一个组件,通过参数区分模式。

// entry/src/main/ets/components/SearchBar.ets@Componentexportstruct SearchBar{@Propplaceholder:string='搜索商品'@PropisFocusMode:boolean=false@StateinputValue:string=''onSearch?:(keyword:string)=>voidonCancel?:()=>voidonFocus?:()=>voidonInputChange?:(value:string)=>voidbuild(){Row({space:10}){// 搜索输入框Row(){Image($r('app.media.ic_search')).width(18).height(18).margin({left:12})TextInput({text:this.inputValue,placeholder:this.placeholder}).layoutWeight(1).height(36).backgroundColor(Color.Transparent).fontSize(14).placeholderColor('#BBBBBB').onChange((value:string)=>{this.inputValue=valuethis.onInputChange?.(value)}).onSubmit(()=>{if(this.inputValue.trim().length>0){this.onSearch?.(this.inputValue.trim())}})// 有输入内容时显示清除按钮if(this.inputValue.length>0){Image($r('app.media.ic_clear')).width(18).height(18).margin({right:8}).onClick(()=>{this.inputValue=''this.onInputChange?.('')})}}.layoutWeight(1).height(36).backgroundColor('#F5F5F5').borderRadius(18)// 聚焦模式下显示取消按钮if(this.isFocusMode){Text('取消').fontSize(15).fontColor('#666666').onClick(()=>{this.onCancel?.()})}}.width('100%').height(56).padding({left:12,right:12}).backgroundColor(Color.White)}}

首页的搜索栏是非聚焦模式,点击整个区域跳转到搜索页。搜索页用聚焦模式,输入框自动弹起键盘。

输入联想与防抖搜索

用户边输入边出联想词,体验很好,但直接每次输入都请求太浪费。加个防抖,停顿 300ms 再发请求:

// 在搜索页中使用防抖@Componentstruct SearchPage{@Statekeyword:string=''@Statesuggestions:string[]=[]privatedebounceTimer:number=-1build(){Column(){SearchBar({isFocusMode:true,onInputChange:(value:string)=>{this.keyword=valuethis.debounceSearch(value)},onSearch:(kw:string)=>{this.doSearch(kw)},onCancel:()=>{// 返回首页}})if(this.suggestions.length>0&&this.keyword.length>0){this.SuggestionList()}else{this.SearchLanding()// 搜索落地页:历史 + 热词}}}privatedebounceSearch(value:string){// 清除上一次定时器if(this.debounceTimer!==-1){clearTimeout(this.debounceTimer)}if(value.trim().length===0){this.suggestions=[]return}this.debounceTimer=setTimeout(()=>{this.fetchSuggestions(value)},300)}privateasyncfetchSuggestions(keyword:string){// 调用联想词接口try{// this.suggestions = await ProductRepository.getSuggestions(keyword)// mock 数据this.suggestions=[keyword+' 新鲜',keyword+' 礼盒装',keyword+' 包邮',keyword+' 当季',]}catch(e){this.suggestions=[]}}}

clearTimeout+setTimeout是经典的防抖实现。ArkTS 里这两个方法是全局可用的,不需要额外引入。300ms 的延迟体感上刚好——用户连续打字不会频繁请求,停下来又很快出结果。

搜索历史:PersistentStorage 持久化

搜索历史需要持久化存储,关了 App 再打开还在。HarmonyOS 提供了PersistentStorage,专门做这个事。

// lib_core/src/main/ets/utils/SearchHistoryManager.etsconstHISTORY_KEY='search_history'constMAX_HISTORY=15exportclassSearchHistoryManager{// 初始化时从持久化存储读取staticinit(){PersistentStorage.persistProp<string[]>(HISTORY_KEY,[])}// 获取历史列表staticgetHistory():string[]{returnAppStorage.get<string[]>(HISTORY_KEY)??[]}// 添加搜索记录(去重 + 放最前面)staticaddHistory(keyword:string){lethistory=this.getHistory()// 去重:如果已有,先删掉旧的constindex=history.indexOf(keyword)if(index>=0){history.splice(index,1)}// 插入到头部history.unshift(keyword)// 限制最大数量if(history.length>MAX_HISTORY){history=history.slice(0,MAX_HISTORY)}AppStorage.set<string[]>(HISTORY_KEY,history)}// 清空历史staticclearHistory(){AppStorage.set<string[]>(HISTORY_KEY,[])}}

思路很简单:用PersistentStorage.persistProp把搜索历史和磁盘绑定,之后通过AppStorage读写就行。每次搜索的时候调一下addHistory,自动去重、自动限制条数。

在 EntryAbility 的onCreate里记得调SearchHistoryManager.init(),不然第一次打开 App 读不到历史。

搜索落地页:历史 + 热词

用户点进搜索页但还没输入的时候,展示搜索历史 + 热门搜索。这个页面我管它叫「搜索落地页」:

@BuilderSearchLanding(){Scroll(){Column({space:20}){// 搜索历史if(this.historyList.length>0){Column(){Row(){Text('搜索历史').fontSize(16).fontWeight(FontWeight.Medium)Blank()Image($r('app.media.ic_delete')).width(20).height(20).onClick(()=>{// 弹出确认弹窗后清空AlertDialog.show({title:'提示',message:'确认清空搜索历史?',primaryButton:{value:'取消',action:()=>{}},secondaryButton:{value:'清空',action:()=>{SearchHistoryManager.clearHistory()this.historyList=[]}}})})}.width('100%').padding({bottom:12})// 历史标签用 Flex 换行排列Flex({wrap:FlexWrap.Wrap}){ForEach(this.historyList,(item:string)=>{Text(item).fontSize(13).fontColor('#666666').padding({left:12,right:12,top:6,bottom:6}).backgroundColor('#F5F5F5').borderRadius(16).margin({right:8,bottom:8}).onClick(()=>{this.doSearch(item)})},(item:string)=>item)}}.width('100%').padding({left:16,right:16})}// 热门搜索Column(){Text('热门搜索').fontSize(16).fontWeight(FontWeight.Medium).width('100%').padding({bottom:12})ForEach(this.hotKeywords,(item:HotKeyword,index:number)=>{Row(){Text(`${index+1}`).fontSize(15).fontWeight(FontWeight.Bold).fontColor(index<3?'#FF6B35':'#999999').width(24)Text(item.keyword).fontSize(14).fontColor('#333333').layoutWeight(1)if(item.isHot){Text('热').fontSize(10).fontColor(Color.White).backgroundColor('#FF4D4F').borderRadius(4).padding({left:4,right:4,top:2,bottom:2})}}.width('100%').height(44).onClick(()=>{this.doSearch(item.keyword)})},(item:HotKeyword)=>item.keyword)}.width('100%').padding({left:16,right:16})}.width('100%').padding({top:12})}}

历史标签用FlexWrap模式做流式布局,标签多了自动换行,每个标签是一个胶囊形状(圆角 16vp)。

热门搜索用列表形式,前三名数字用主题色高亮。有的热词后面带个红色的「热」标签,这个效果用一个小 Text 加红色背景就搞定了。

搜索结果页:商品列表 + 筛选排序

用户提交搜索后进入结果页。上面是筛选排序栏,下面是商品列表:

@BuilderSearchResultView(){Column(){// 排序栏Row(){ForEach(this.sortOptions,(option:SortOption)=>{Column({space:2}){Text(option.label).fontSize(14).fontColor(this.currentSort===option.value?'#FF6B35':'#666666').fontWeight(this.currentSort===option.value?FontWeight.Medium:FontWeight.Normal)if(this.currentSort===option.value){Rect().width(20).height(2).fill('#FF6B35').borderRadius(1)}}.layoutWeight(1).height(44).justifyContent(FlexAlign.Center).onClick(()=>{this.currentSort=option.valuethis.doSearch(this.keyword)})},(option:SortOption)=>option.value)}.width('100%').backgroundColor(Color.White)// 搜索结果列表if(this.resultList.length===0&&!this.isSearching){this.EmptyState()}else{List(){ForEach(this.resultList,(item:ProductItem)=>{ListItem(){this.ProductListItem(item)}},(item:ProductItem)=>item.id)}.width('100%').layoutWeight(1)}}}

搜索结果用普通的 List 就行,不需要瀑布流——搜索结果强调信息对比,等高卡片更容易横向比较价格。

空状态处理

搜索没有结果的时候不能给用户看空白页。做一个友好的空状态:

@BuilderEmptyState(){Column({space:12}){Image($r('app.media.ic_empty_search')).width(120).height(120).margin({top:80})Text('没有找到相关商品').fontSize(16).fontColor('#999999')Text('换个关键词试试,或者看看热门推荐').fontSize(13).fontColor('#CCCCCC')Button('看看热门推荐').fontSize(14).fontColor('#FF6B35').backgroundColor(Color.Transparent).borderRadius(20).border({width:1,color:'#FF6B35'}).margin({top:20}).onClick(()=>{// 跳转到推荐页})}.width('100%').alignItems(HorizontalAlign.Center)}

空状态三要素:一个插图、一句主文案、一个行动按钮。别小看这个页面,很多用户搜不到东西就流失了,一个好的空状态能把人拉回来。

搜索执行的入口

搜索动作触发时记得保存历史:

privatedoSearch(keyword:string){this.keyword=keywordthis.inputValue=keyword SearchHistoryManager.addHistory(keyword)this.historyList=SearchHistoryManager.getHistory()this.suggestions=[]// 清除联想this.fetchResults()// 拉取搜索结果}

几点实用建议

防抖时间别太短。200ms 以下用户感知不到延迟,请求照样频繁。300-500ms 是比较舒适的区间。

搜索历史条数要限制。我设的 15 条,太多了用户翻着也累,占存储也没必要。

热词要后端可控。热门搜索是运营位,一定从后端拉取,别写死在客户端。后端可以按时间段、按活动灵活配置。

空状态的推荐内容要真实。别用固定数据糊弄,调一下推荐接口,让用户真的能从这里找到东西。

搜索模块做完,首页的核心交互就差不多了。下一篇我们做分类页面——左边一级分类、右边二级分类、联动滚动,是电商 App 里实现起来最有意思的一个页面。

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

相关文章:

  • 吴恩达《深度学习》之看懂超参数搜索的“对数标尺”
  • B站评论采集实践:如何快速获取评论数据并接入AI分析平台
  • 移动网络用户访问异常专项:为什么移动投诉往往最多
  • 【量化实战】基于LLMCompressor一键落地vLLM部署
  • 鸿蒙操作系统是否超越安卓?
  • 网站站长每天必做的工作有哪些?
  • DeepSeek正式官宣摇人,夯!
  • 西门子罗宾康 A1A10000423.00M 高压变频器 I/O 板
  • 赛克艾威早报20260630:Oracle EBS与Apache HTTP Server曝高危漏洞,多款产品遭在野利用
  • rat与生态系统集成:如何将高性能文件查看器融入你的开发工作流
  • 当灯光“躲”进陪伴机器人:智能照明的隐藏式进化与异业合作新浪潮
  • Windows 11系统优化神器:Win11Debloat让你的电脑性能提升51%的秘密
  • 从零到一:在STM32上跑通TinyML的完整实践指南
  • 2026年AI建站平台哪个好?企业官网、SEO和GEO能力对比
  • ABAP :新语法 - REF
  • 编写自动化脚本时使用多线程技术
  • LangChain4j Guardrails:给你的 AI Service 装上输入输出双层卡口
  • Windows10上安装MySQL操作步骤
  • 纯小白零基础漏洞挖掘完整教程,从理论到实操一步到位,看完即可上手提交漏洞拿赏金
  • 论文格式改 3 遍还不合格?笔墨 AI 一键匹配院校模板,不用手动调半天
  • 多场景学术写作一站式解决方案,paperxie 智能论文写作功能拆解实测
  • 使用JMeter进行gRPC微服务性能测试的完整指南
  • 优化数据库查询性能的五个实用技巧
  • 哔哩下载姬完整指南:告别网络焦虑,轻松掌控B站视频资源
  • 简单聊一下JAX
  • 3个关键突破:如何用dnSpyEx解决.NET逆向工程的核心痛点?
  • 工业4-20mA电流环设计:DAC161S997与PIC32实战解析
  • 工业清洁机器人智能化应用与厂区使用优势
  • Mac远程控制Windows电脑的两种方法
  • 论文选题总是太宽泛?笔墨 AI 前置引导锚定研究边界,从源头避免跑偏