鸿蒙实战:ArchivePage 档案管理页——875 行最复杂页面完整拆解
鸿蒙实战:ArchivePage 档案管理页——875 行最复杂页面完整拆解
前言
图:鸿蒙实战第92篇:ArchivePage 档案管理页——875 行最复杂页面完整拆解 运行效果截图(HarmonyOS NEXT)
在整个项目的 19 个页面中,ArchivePage以875 行位居代码量第一。它是"鹿鹿"的核心数据管理界面,集合了:子页签过滤、防抖搜索、创建对话框、右滑删除 + AlertDialog 确认、交错入场动画、底部毛玻璃 TabBar、名字哈希头像颜色等众多实用技术。
本篇将从页面架构、核心交互、数据管理三个维度全面拆解这 875 行代码的设计智慧。
系列导航:91 AnalyzingPage · 93 TrendPage 情绪趋势
相关文档:List swipeAction · AlertDialog · ArchiveDao
一、页面架构全景
1.1 组件树结构
1.2 状态变量清单
@Entry@Componentstruct ArchivePage{@Statearchives:ArchiveEntity[]=[]// 档案列表(完整)@StatefilteredArchives:ArchiveEntity[]=[]// 过滤后(搜索+子页签)@StatecurrentTab:number=0// 0=TA的 1=我的 2=全部@StatesearchText:string=''// 搜索关键词@StateshowCreateDialog:boolean=false// 新建对话框显示@StatenewArchiveName:string=''// 新建对话框输入@StatenewArchiveRelation:string=''// 关系(可选)@StatedialogOffset:number=300// 对话框入场偏移@StateisLoading:boolean=falseprivatesearchTimer:number=-1// 防抖定时器privateuserId:number=-1}二、子页签三态过滤
2.1 过滤逻辑
子页签"TA 的档案 / 我的 / 全部"通过relation字段区分:
privateapplyFilter():void{letfiltered=[...this.archives]// ① 子页签过滤if(this.currentTab===0){// TA 的档案:relation 非空且非"未设置"filtered=filtered.filter(a=>a.relation&&a.relation!=='未设置'&&a.relation.trim()!=='')}elseif(this.currentTab===1){// 我的:没有 relation 或 relation 为"未设置"filtered=filtered.filter(a=>!a.relation||a.relation==='未设置'||a.relation.trim()==='')}// tab=2:全部,不过滤// ② 搜索关键词过滤if(this.searchText.trim()){constkw=this.searchText.trim().toLowerCase()filtered=filtered.filter(a=>a.name.toLowerCase().includes(kw))}this.filteredArchives=filtered}2.2 数据分布说明
| 子页签 | 对应数据 | relation值 |
|---|---|---|
| TA 的档案 | 为他人分析的记录 | '伴侣'/'朋友'/'家人'等 |
| 我的 | 自我分析记录 | null/''/'未设置' |
| 全部 | 不过滤 | 任意 |
三、防抖搜索
3.1 搜索框实现
TextInput({placeholder:'搜索档案...',text:$$this.searchText}).onChange((val:string)=>{this.searchText=val// 防抖:延迟 300ms 后才执行过滤if(this.searchTimer!==-1){clearTimeout(this.searchTimer)}this.searchTimer=setTimeout(()=>{this.applyFilter()},300)})为什么需要防抖?用户快速输入"张三"时,会触发"张"、"张三"两次 onChange,不防抖会执行两次过滤操作,效率低下。300ms 防抖后,只执行最后一次的过滤。
四、右滑删除 + AlertDialog 确认
4.1 swipeAction 配置
ListItem().swipeAction({end:()=>{// 右侧滑出的删除按钮this.SwipeDeleteButton(archive)}})4.2 删除按钮 Builder
@BuilderSwipeDeleteButton(archive:ArchiveEntity){Column(){Text('🗑️').fontSize(22)Text('删除').fontSize(11).fontColor(Color.White)}.width(72).height('100%').justifyContent(FlexAlign.Center).backgroundColor('#FF4444').onClick(()=>{AlertDialog.show({title:'删除档案',message:`确认删除"${archive.name}"的所有分析记录?此操作不可恢复。`,primaryButton:{value:'取消',action:()=>{}// 取消:不操作},secondaryButton:{value:'删除',fontColor:Color.Red,action:async()=>{awaitArchiveDao.delete(archive.id)awaitthis.loadArchives()// 重新加载列表}}})})}| 特性 | 实现方式 | 说明 |
|---|---|---|
| 滑出操作区 | swipeAction.end | 右侧显示红色删除按钮 |
| 二次确认 | AlertDialog.show | 防止误操作 |
| 删除后刷新 | loadArchives() | 重新查 DB,确保列表同步 |
五、名字哈希头像颜色
5.1 算法实现
每个档案卡片的头像用名字首字 + 哈希颜色实现,无需图片资源:
privategetAvatarColor(name:string):string{// 简单哈希:对字符串每个字符 ASCII 码累积运算lethash=0for(leti=0;i<name.length;i++){hash=(hash<<5)-hash+name.charCodeAt(i)hash=hash&hash// 转为 32 位整数}constcolors=['#D9A689',// 暖棕'#9BAEA8',// 灰绿'#C9A0A0',// 玫瑰'#A8C4D9',// 天蓝'#C4D9A8'// 草绿]returncolors[Math.abs(hash)%colors.length]}效果:相同名字始终显示相同颜色(确定性),不同名字颜色分布均匀(伪随机)。
六、新建档案对话框(底部上滑)
6.1 入场动画
// 对话框容器Column(){// ... 对话框内容}.translate({y:this.dialogOffset})// 初始偏移 300(屏幕下方).animation({duration:350,curve:Curve.FastOutSlowIn})// 触发入场:privateopenCreateDialog():void{this.showCreateDialog=true// 延迟一帧,让组件先渲染到 y:300,再动画到 y:0setTimeout(()=>{this.dialogOffset=0},16)}// 触发退场:privatecloseCreateDialog():void{this.dialogOffset=300setTimeout(()=>{this.showCreateDialog=false},350)}为什么需要
setTimeout(16ms)?ArkUI 的渲染是异步的。如果直接在showCreateDialog = true的同一帧设置dialogOffset = 0,组件还没渲染,动画无法触发。16ms 延迟(约 1 帧)确保组件已挂载在y:300位置后再触发动画。
6.2 关系标签选择器
// 预设关系标签(可扩展)privatereadonlyRELATION_TAGS=['伴侣','好友','家人','同事','']Wrap({space:8}){ForEach(this.RELATION_TAGS,(tag:string)=>{Text(tag||'无关系').padding({left:12,right:12,top:6,bottom:6}).backgroundColor(this.newArchiveRelation===tag?AppColors.PRIMARY:AppColors.CARD).fontColor(this.newArchiveRelation===tag?Color.White:AppColors.TEXT).borderRadius(AppRadius.ROUND).animation({duration:200}).onClick(()=>{this.newArchiveRelation=tag})})}七、交错入场动画
7.1 卡片交错入场
privatestartListAnimation():void{// 每张卡片延迟 80ms,最多延迟到前 8 张this.filteredArchives.forEach((_,index)=>{setTimeout(()=>{animateTo({duration:400,curve:Curve.FastOutSlowIn},()=>{this.cardOpacities[index]=1this.cardOffsets[index]=0})},Math.min(index,8)*80)// 超过第8张不再增加延迟})}Math.min(index, 8) * 80限制最大延迟为 640ms——避免列表很长时最后几张卡片等太久才出现。
八、底部 TabBar 毛玻璃
Row(){this.TabItem('🏠','首页',0)this.TabItem('📁','档案',1)this.TabItem('📈','趋势',2)this.TabItem('👤','我的',3)}.width('100%').height(56).backdropBlur(20)// ← 毛玻璃效果.backgroundColor('rgba(255,252,247,0.85)')// ← 半透明底色.position({y:'100%',x:0})// 固定底部.translate({y:-56})// 上移自身高度
backdropBlur(20)对下层内容进行高斯模糊,配合半透明背景色,营造 iOS 风格的毛玻璃效果。值越大模糊越强,20 是较常用的适中值。
十、最佳实践与性能优化
10.1 代码组织规范
以下规范可显著提升代码可读性与可维护性:
- 分层解耦:UI 逻辑(pages/)与数据操作(DAO/Service)严格分离,不在 build() 中调用数据库
- 组件拆分:单个
@Component保持 100~200 行以内,大型组件拆分为@Builder子函数 - 令牌约束:颜色、字号、间距统一引用
AppColors/AppFonts/AppAnimations,禁止魔数 - 错误处理:异步方法统一返回
Promise<T | null>,异常时hilog.error记录并返回null
10.2 性能优化要点
以下是常见性能优化措施的对比分析:
| 优化维度 | 优化前 | 优化后 | 效果 |
|---|---|---|---|
| 列表渲染 | ForEach全量渲染 | LazyForEach按需渲染 | 内存降低 40-60% |
| 图片加载 | 不指定尺寸 | 指定width/height | 减少布局重计算 |
| 数据库查询 | 每次重新查询 | 合理缓存 + 精确 Predicates | 查询速度提升 5-10x |
| 动画实现 | 逐帧手动绘制 | animateTo属性动画 | 稳定 60fps |
| 状态更新 | 全局刷新 | @State精细化作用域 | 减少无效渲染 |
总结
ArchivePage 的 875 行代码覆盖了鸿蒙应用开发中最常用的交互组合:
- 子页签 + 搜索防抖:
relation字段三态过滤 + 300ms 防抖,两种过滤机制组合 swipeAction右滑删除:配合AlertDialog二次确认,防止误操作- 名字哈希颜色:
hash<<5-hash简单哈希 + 5 色数组,确定性美观头像 - 底部对话框入场:
translate(y:300→0)+ 16ms 延迟触发,底部上滑效果 backdropBlur毛玻璃 TabBar:ArkUI 一行代码实现 iOS 质感
下一篇预告:第93篇 TrendPage——情绪趋势折线图与关键词云生成
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- List swipeAction API
- AlertDialog 文档
- TextInput 防抖方案
- backdropBlur 毛玻璃
- animateTo 显式动画
- ArchiveDao 档案数据操作 → 第84篇
- 入场动画设计 → 第59篇
- AppStorage 全局状态 → 第72篇
七、ArchivePage 核心技术汇总
7.1 防抖搜索实现
@StatesearchText:string='';privatesearchTimer:number=-1;onSearchChange(value:string){this.searchText=value;clearTimeout(this.searchTimer);this.searchTimer=setTimeout(()=>{this.filterArchives(value);// 300ms 防抖},300);}7.2 名字哈希头像颜色
privategetAvatarColor(name:string):string{// 根据名字第一个字的 Unicode 码点取模constcode=name.charCodeAt(0)%6;constcolors=['#C4956A','#7BAE7F','#8BB4D4','#D4A478','#A488C4','#D47878'];returncolors[code];}7.3 右滑删除 + 确认弹窗
ListItem(){/* 档案卡片 */}.swipeAction({end:{builder:()=>this.deleteButton(archive.id),onAction:()=>this.confirmDelete(archive)}})privateconfirmDelete(archive:ArchiveInfo){AlertDialog.show({title:'确认删除',message:`删除「${archive.name}」档案将同时删除所有相关笔迹和报告,此操作不可撤销`,secondaryButton:{value:'删除',fontColor:AppColors.errorRed,action:async()=>{awaitArchiveDao.delete(archive.id);this.loadArchives();}}});}ArchivePage 工程价值:875 行代码综合运用了 ArkUI 几乎所有常用能力,是学习鸿蒙工程化 UI 开发的最佳实战样本之一。防抖搜索、哈希头像色、滑动删除三个技术点可直接复用到其他项目。
延伸阅读:第84篇 ArchiveDao · 第85篇 HandwritingDao
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!