news 2026/6/13 15:17:50

鸿蒙原生应用实战(三):表单交互与搜索筛选——添加包裹、搜索过滤与公司管理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
鸿蒙原生应用实战(三):表单交互与搜索筛选——添加包裹、搜索过滤与公司管理

鸿蒙原生应用实战(三):表单交互与搜索筛选——添加包裹、搜索过滤与公司管理

本文是系列第三篇,深入讲解快递追踪 App 中的三个交互型页面:添加包裹表单、搜索与筛选、快递公司管理。涵盖表单验证、选择器实现、多条件搜索、Toggle 开关等实战内容。


一、概述

本项目的交互型页面有三个,各有特点:

页面核心交互技术要点
AddPackagePage文本输入 + 下拉选择 + 表单验证TextInput、自定义Picker、实时校验
SearchPage文本搜索 + 标签筛选 + 结果列表多条件组合查询、Tab 筛选
CompanyManagePage搜索过滤 + Toggle 开关 + 收藏管理计算属性、ToggleType.Switch

二、添加包裹页面(AddPackagePage)

2.1 页面布局

┌─────────────────────────────┐ │ ← 添加包裹 │ ├─────────────────────────────┤ │ 输入快递单号 │ │ ┌───────────────────────┐ │ │ │ 请输入快递单号 │ │ │ └───────────────────────┘ │ │ │ │ 快递公司 │ │ ┌───────────────────────┐ │ │ │ 顺丰速运 ▼ │ │ │ ├───────────────────────┤ │ ← 点击展开 │ │ 圆通速递 │ │ │ │ 中通快递 │ │ │ │ ... │ │ │ └───────────────────────┘ │ │ │ │ 备注 │ │ ┌───────────────────────┐ │ │ │ 选填 │ │ │ └───────────────────────┘ │ │ │ │ ┌───────────────────────┐ │ │ │ 保存 │ │ │ └───────────────────────┘ │ │ 单号格式正确 ✓ │ ← 实时校验提示 └─────────────────────────────┘

2.2 状态管理

@Entry@Componentstruct AddPackagePage{@StatetrackingNo:string='';// 快递单号@StateselectedCompany:string='顺丰速运';// 选中的快递公司@Statenote:string='';// 备注@StateshowPicker:boolean=false;// 选择器展开/收起}

2.3 自定义下拉选择器

鸿蒙没有原生的下拉选择器(Picker/Select),我们用 Text + 条件渲染实现一个:

// 快递公司列表letcourierCompanies:string[]=['顺丰速运','圆通速递','中通快递','韵达快递','京东快递','邮政EMS','极兔速递'];// 选择器触发器Row(){Text(this.selectedCompany)Blank()Text('▼')// 下拉箭头}.onClick(()=>{this.showPicker=!this.showPicker;// 切换展开/收起})// 下拉列表(条件渲染)if(this.showPicker){Column(){ForEach(courierCompanies,(company:string)=>{Text(company).onClick(()=>{this.selectedCompany=company;// 选中this.showPicker=false;// 收起})},(company:string)=>company)}}

设计要点

  • 选中项高亮显示:fontColor(this.selectedCompany === company ? primary : text_primary)
  • 点击列表项后自动收起选择器
  • 列表外部没有点击遮罩层——可以通过再次点击触发器收起

2.4 表单实时验证

// 保存按钮Button($r('app.string.btn_save')).onClick(()=>{if(this.trackingNo.length===0){return;// 空单号不处理}if(this.trackingNo.length<6){return;// 单号太短}router.back();// 验证通过,返回首页})// 实时提示文字Row(){if(this.trackingNo.length>0&&this.trackingNo.length<6){Text('单号长度不足,请检查').fontColor($r('app.color.status_exception'))// 红色警告}elseif(this.trackingNo.length===0){Text('请输入快递单号').fontColor($r('app.color.text_hint'))// 灰色提示}else{Text('单号格式正确 ✓').fontColor($r('app.color.status_delivered'))// 绿色成功}}

三种状态提示

状态条件颜色文案
未输入length === 0灰色请输入快递单号
输入中但不足length > 0 && length < 6红色单号长度不足
输入完成length >= 6绿色单号格式正确 ✓

这是典型的正向反馈设计——用户每输入一个字符都能看到状态变化。

2.5 为什么不使用 TextArea 做备注?

当前备注使用了单行TextInput,对于简短备注(如"手机"“书籍”)足够。如果需要长文本,应该替换为TextArea

// 如果需要多行备注TextArea({placeholder:'选填,支持多行',text:this.note}).height(100)

三、搜索与筛选页面(SearchPage)

3.1 功能设计

搜索页支持两种筛选维度:

  1. 文本搜索:按快递单号、公司名、备注搜索
  2. 状态筛选:全部 / 运输中 / 已签收 / 异常

两种维度组合生效,即搜索结果同时匹配文本和状态。

3.2 数据结构

interfacePackageResult{id:number;trackingNo:string;company:string;status:string;// transit | delivered | exceptionstatusText:string;note:string;updateTime:string;}

3.3 多条件组合搜索

doSearch():void{this.hasSearched=true;letquery=this.searchQuery.toLowerCase().trim();this.searchResults=[];for(letpkgofthis.allPackages){// 条件1:文本匹配(单号/公司/备注)letmatchQuery=query.length===0||pkg.trackingNo.toLowerCase().indexOf(query)>=0||pkg.company.indexOf(query)>=0||pkg.note.indexOf(query)>=0;// 条件2:状态筛选letmatchFilter=this.selectedFilter===0||// 全部(this.selectedFilter===1&&pkg.status==='transit')||(this.selectedFilter===2&&pkg.status==='delivered')||(this.selectedFilter===3&&pkg.status==='exception');// AND 组合if(matchQuery&&matchFilter){this.searchResults.push(pkg);}}}

设计模式

  • 将"文本匹配"和"状态匹配"分开为两个布尔变量
  • &&组合,语义清晰
  • query.length === 0时不限制文本,即显示该状态筛选下的全部

3.4 标签筛选栏

privatefilterTabs:string[]=['全部','运输中','已签收','异常'];Row(){ForEach(this.filterTabs,(tab:string,index:number)=>{Text(tab).fontColor(this.selectedFilter===index?Color.White:$r('app.color.text_primary')).backgroundColor(this.selectedFilter===index?$r('app.color.primary'):$r('app.color.background')).borderRadius(16).onClick(()=>{this.onFilterClick(index);})},(tab:string)=>tab)}

选中态与未选中态的视觉区分

  • 选中:白色文字 + 主题色背景
  • 未选中:主题色文字 + 浅灰背景

3.5 搜索框与键盘交互

TextInput({placeholder:'输入单号/快递公司/备注',text:this.searchQuery}).onChange((value:string)=>{this.searchQuery=value;}).onSubmit(()=>{this.doSearch();})// 键盘回车触发搜索

onSubmit捕获键盘回车事件,提升移动端输入体验——用户输入完毕直接点回车即可搜索。

3.6 搜索状态的三种 UI

// 状态1:初始态(未搜索过)if(!this.hasSearched){// 显示引导文案}// 状态2:搜索有结果if(this.hasSearched&&this.searchResults.length>0){// 显示结果列表}// 状态3:搜索无结果if(this.hasSearched&&this.searchResults.length===0){// 显示"没有找到"空状态}

三种状态的切换必须处理好"闪烁"问题——hasSearched初始为false,用户首次点击搜索才置为true


四、快递公司管理页面(CompanyManagePage)

4.1 功能需求

  • 展示所有支持的快递公司列表
  • 可搜索过滤公司
  • 通过 Toggle 开关标记常用公司
  • 顶部显示已选数量统计

4.2 数据结构

interfaceCompanyItem{name:string;phone:string;// 客服电话website:string;// 官网isFavorite:boolean;// 是否收藏}

4.3 计算属性(getter)

// 筛选后的公司列表getfilteredCompanies():CompanyItem[]{if(!this.companies)return[];if(this.searchText.length===0)returnthis.companies;letq=this.searchText.toLowerCase();letresult:CompanyItem[]=[];for(letcofthis.companies){if(c.name.toLowerCase().indexOf(q)>=0){result.push(c);}}returnresult;}// 收藏数量getfavoriteCount():number{if(!this.companies)return0;letcount=0;for(letcofthis.companies){if(c.isFavorite)count++;}returncount;}

ArkTS 的计算属性(getter)会在每次渲染时重新求值,因此不需要额外声明@State来存储筛选结果,减少了状态同步的复杂度。

4.4 Toggle 开关组件

Toggle({type:ToggleType.Switch,isOn:company.isFavorite}).onChange(()=>{this.toggleFavorite(company.name);})

Toggle是鸿蒙原生开关组件,支持三种类型:

  • ToggleType.Switch:滑动开关(最常用)
  • ToggleType.Checkbox:复选框
  • ToggleType.Button:按钮式

⚠️ 注意:isOn是初始值,但Toggle内部会维护自己的选中状态。当用户操作时,我们需要通过toggleFavorite同步修改companies数据。

toggleFavorite(name:string):void{for(leti=0;i<this.companies.length;i++){if(this.companies[i].name===name){this.companies[i].isFavorite=!this.companies[i].isFavorite;break;}}}

由于companies是用@State装饰的数组,通过索引修改元素属性不会触发 UI 更新——但这里我们只是修改isFavorite布尔值,Toggle 组件自身维护了视觉状态,所以 UI 不会出现不同步。

如果需要确保 UI 同步,应该创建新数组:

toggleFavorite(name:string):void{this.companies=this.companies.map(c=>c.name===name?{...c,isFavorite:!c.isFavorite}:c);}

4.5 头像圆圈设计

Stack(){Circle().width(44).height(44).fill($r('app.color.primary'))Text(company.name.substring(0,1))// 取公司名的第一个字.fontSize(20).fontColor(Color.White).fontWeight(FontWeight.Bold)}

使用Stack叠放圆形背景和首字,形成类似微信头像的风格。取公司名第一个字(顺→S、圆→Y、中→Z),简单有效。


五、表单验证的最佳实践

5.1 验证时机

验证时机实现方式适用场景
实时验证onChange + 状态提示单号长度、格式
提交时验证onClick 统一校验必填项检查
失焦验证onBlur输入完成后检查

5.2 用户反馈层次

好的表单反馈应该有层次感:

🔴 单号长度不足,请检查 → 错误(阻止提交) ⚪ 请输入快递单号 → 提示(允许提交,但提交会失败) 🟢 单号格式正确 ✓ → 成功(可提交)

六、交互设计要点总结

6.1 添加包裹页

  • showPicker控制下拉展开/收起,点外侧不会自动关闭(简化实现)
  • 实时校验三个状态:未输入/错误/正确
  • 保存按钮校验后router.back()返回首页(实际项目应传递数据给首页)

6.2 搜索页

  • 文本 + 状态双维度筛选,组合使用
  • 三种 UI 状态:未搜索 / 有结果 / 无结果
  • 搜索框支持回车提交

6.3 公司管理页

  • 搜索即时过滤(onChange 触发)
  • Toggle 开关管理收藏状态
  • 顶部显示统计 “已选 N/M”
  • 公司名首字 + 圆形背景构成头像

七、小结

本篇完成了三个交互密集型页面的开发:

  1. AddPackagePage:表单输入 + 自定义选择器 + 实时验证
  2. SearchPage:多条件搜索 + 标签筛选 + 三种状态 UI
  3. CompanyManagePage:搜索过滤 + Toggle 开关 + 计算属性

核心知识点:

  • 条件渲染实现下拉选择器
  • 搜索的逻辑组合与性能
  • @State 数组修改的限制与正确方式
  • 表单验证的层次反馈设计

下一篇将进入物流时间线与历史记录,详解时间线 UI 组件的绘制、router 参数传递与接收、列表统计等高级内容。


系列索引

  • 第一篇:项目初始化与工程架构
  • 第二篇:首页与列表开发实战
  • 第三篇:表单交互与搜索筛选(本文)
  • 第四篇:物流时间线与历史记录
  • 第五篇:数据统计与个人中心
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/13 15:14:54

TVA 视觉智能体二次开发实战(七):多相机高并发优化|TVA 视觉智能体 API 连接池复用 + 请求合并 + 接口节流 性能调优实战

导读在多工位自动化车间&#xff0c;十几路甚至几十路工业相机同时接入 TVA 视觉智能体&#xff0c;高并发接口请求会直接造成接口响应卡顿、AI 推理排队、画面延迟&#xff0c;严重影响整体生产效率。本文针对多相机集群高并发场景&#xff0c;从 HTTP 连接池、请求合并、接口…

作者头像 李华
网站建设 2026/6/13 15:10:09

【JUC】ThreadLocal底层原理|内存泄漏|弱引用|跨线程传递方案

大家好&#xff0c;我是程序员二叉。简介 ThreadLocal是线程私有存储工具&#xff0c;常用于上下文传递、多数据源隔离、用户信息透传&#xff0c;面试高频深挖内存泄漏与引用机制&#xff1b;文末补充跨线程传值解决方案&#xff0c;拔高面试回答深度。欢迎点赞关注收藏。一、…

作者头像 李华
网站建设 2026/6/13 15:09:52

Windows系统上如何实现安卓应用的无缝安装:APK-Installer完整指南

Windows系统上如何实现安卓应用的无缝安装&#xff1a;APK-Installer完整指南 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 在Windows系统上直接运行安卓应用曾经是技…

作者头像 李华
网站建设 2026/6/13 15:06:10

从C到RISC-V汇编:手把手教你用GCC编译并反汇编理解函数调用栈

从C到RISC-V汇编&#xff1a;手把手教你用GCC编译并反汇编理解函数调用栈当C语言代码被编译成机器指令时&#xff0c;函数调用、参数传递和栈帧管理等底层细节往往被高级语法糖所掩盖。本文将带您亲自动手&#xff0c;通过GCC工具链将C程序编译为RISC-V汇编&#xff0c;再借助反…

作者头像 李华
网站建设 2026/6/13 15:02:52

Unlock Music:浏览器端音乐文件解密全栈解决方案

Unlock Music&#xff1a;浏览器端音乐文件解密全栈解决方案 【免费下载链接】unlock-music 在浏览器中解锁加密的音乐文件。原仓库&#xff1a; 1. https://github.com/unlock-music/unlock-music &#xff1b;2. https://git.unlock-music.dev/um/web 项目地址: https://gi…

作者头像 李华
网站建设 2026/6/13 15:01:51

深入解析M68040总线机制:从同步握手到中断响应的硬件设计精髓

1. 项目概述&#xff1a;深入M68040的总线世界如果你曾经拆解过一台老式的工控机、工作站&#xff0c;或者研究过一些经典的嵌入式系统&#xff0c;大概率会与摩托罗拉的68K家族处理器打过交道。而M68040&#xff0c;作为这个家族中集成度与性能都达到一个高峰的成员&#xff0…

作者头像 李华