鸿蒙原生应用实战(四):分类浏览与个人中心的多维数据展示

鸿蒙原生应用实战(四):分类浏览与个人中心的多维数据展示 鸿蒙原生应用实战四分类浏览与个人中心的多维数据展示系列目录第一篇项目搭建与页面架构设计第二篇首页开发与全局数据流设计第三篇笔记详情与编辑页面的路由与CRUD第四篇分类浏览与个人中心的多维数据展示 ← 当前第五篇构建调试、异常处理与HAP发布一、前言前三篇我们完成了首页、详情页、编辑页实现了笔记的完整 CRUD 和数据流。本篇将开发最后两个页面——分类浏览页CategoryPage和个人中心页ProfilePage。这两个页面的特点是数据聚合对原始笔记数据进行分类统计和汇总多维展示卡片网格、色条列表、统计图表等多种视觉形式ArkTS 严格模式涉及较多 UI 组合更容易踩严格模式的坑二、分类浏览页 (CategoryPage)2.1 页面布局Column ├── 顶部导航栏 │ ├── 返回按钮 │ ├── 分类浏览 标题 │ └── 占位图标 ├── 分类统计卡片 (Row × 2) │ ├── [全部] [工作] [学习] ← 第一行 │ └── [生活] [灵感] ← 第二行 ├── 当前分类标题 笔记数 └── 笔记列表 (ListItem 带色条) ├── ┃ 工作笔记标题 2024-12-15 ├── ┃ 学习笔记标题 2024-12-01 └── ... └── 底部导航2.2 分类统计数据分类卡片需要展示每个分类的笔记数量我们在CategoryStat接口中定义interfaceCategoryStat{name:string;// 分类名称count:number;// 笔记数量动态计算color:string;// 颜色icon:number;// 图标索引}数据加载时动态计算各分类计数loadData():void{letstored:string|undefinedAppStorage.getstring(notes);this.notesstored?JSON.parse(stored)asNote[]:[];this.updateCategoryCounts();this.filterByCategory();}updateCategoryCounts():void{for(letcatofthis.categories){if(cat.name全部){cat.countthis.notes.length;}else{cat.countthis.notes.filter((n:Note)n.categorycat.name).length;}}}2.3 分类卡片网格第一行展示 3 个卡片全部、工作、学习第二行展示 2 个生活、灵感// 第一行全部 工作 学习Row(){ForEach([this.categories[0],this.categories[1],this.categories[2]],(cat:CategoryStat){Column(){Text(cat.name).fontSize($r(app.float.body_font_size)).fontColor(Color.White)Text(String(cat.count)).fontSize($r(app.float.title_font_size)).fontColor(Color.White).fontWeight(FontWeight.Bold)Text(篇笔记).fontSize($r(app.float.tiny_font_size)).fontColor(rgba(255,255,255,0.7))}.layoutWeight(1).height(100).backgroundColor(cat.color).borderRadius($r(app.float.card_radius)).opacity(this.selectedCategorycat.name?1.0:0.75)// 选中高亮.onClick(()this.selectCategory(cat.name))})}// 第二行生活 灵感省略相似代码设计要点每个卡片使用layoutWeight(1)均分宽度选中时opacity: 1.0未选中时0.75制造层次感背景色使用分类专属颜色白色文字 半透明辅助文字适配彩色背景2.4 色条笔记列表分类页的笔记列表与首页不同左侧加了一条分类色条ListItem(){Row(){// 分类色条Column().width(4).height(100%).backgroundColor(this.getCategoryColor(note.category)).borderRadius(2)Column(){Text(note.title).fontSize($r(app.float.body_font_size)).fontWeight(FontWeight.Medium).maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis})Text(note.date).fontSize($r(app.float.tiny_font_size)).fontColor($r(app.color.text_tertiary))}.layoutWeight(1).padding({left:12,right:12})}.height(64).backgroundColor($r(app.color.card_bg)).borderRadius($r(app.float.card_radius))}色条的设计让分类更加直观——用户一眼就能从色条颜色判断笔记属于哪个分类。2.5 分类筛选联动点击卡片时切换selectedCategory更新笔记列表selectCategory(name:string):void{this.selectedCategoryname;this.filterByCategory();}filterByCategory():void{if(this.selectedCategory全部){this.categoryNotesthis.notes;}else{this.categoryNotesthis.notes.filter((n:Note)n.categorythis.selectedCategory);}}分类页也注册了onPageShow生命周期确保从编辑页返回后计数刷新onPageShow():void{this.loadData();}三、个人中心页 (ProfilePage)3.1 页面布局Column ├── 顶部导航栏 ├── Scroll │ └── Column │ ├── 用户头像区 │ │ ├── 头像占位 │ │ ├── 我的知识笔记 │ │ └── 记录每一个灵感瞬间 │ ├── 统计概览 │ │ ├── 左侧总笔记数大号蓝色数字 │ │ └── 右侧分类分布 2×2 │ │ ├── [工作 3] [学习 2] │ │ └── [生活 1] [灵感 1] │ ├── 最近笔记 │ │ └── 最新创建的笔记标题 │ └── 设置菜单 │ ├── 关于 → 知识笔记 v1.0.0 │ └── 版本 → v1.0.0 └── 底部导航3.2 用户头像区域Column(){Image($r(app.media.foreground)).width(64).height(64).opacity(0)// 占位实际项目中替换为真实头像Text(我的知识笔记).fontSize($r(app.float.subtitle_font_size)).fontWeight(FontWeight.Bold)Text(记录每一个灵感瞬间).fontSize($r(app.float.small_font_size)).fontColor($r(app.color.text_tertiary))}.width(100%).padding({top:20,bottom:20}).backgroundColor($r(app.color.card_bg))3.3 统计概览核心难点统计概览将总笔记数和分类分布组合成一个 2-2 网格布局是个人中心页最复杂的 UIRow(){// 左侧总笔记数Column(){Text(String(this.totalNotes)).fontSize(40).fontWeight(FontWeight.Bold).fontColor($r(app.color.primary))Text(总笔记数).fontSize($r(app.float.tiny_font_size)).fontColor($r(app.color.text_tertiary))}.layoutWeight(1).height(80).justifyContent(FlexAlign.Center).backgroundColor(Color.White).borderRadius(10)Blank().width(8)// 右侧分类分布 2×2Column(){// 第一行工作 学习Row(){this.StatBadge(工作,String(this.workCount),#007AFF)Blank().width(8)this.StatBadge(学习,String(this.studyCount),#34C759)}.layoutWeight(1)Blank().height(8)// 第二行生活 灵感Row(){this.StatBadge(生活,String(this.lifeCount),#FF9500)Blank().width(8)this.StatBadge(灵感,String(this.inspirationCount),#AF52DE)}.layoutWeight(1)}.layoutWeight(2)}3.4 StatBadge 自定义组件使用Builder实现可复用的统计徽章BuilderStatBadge(label:string,value:string,color:string){Column(){Text(value).fontSize($r(app.float.subtitle_font_size)).fontWeight(FontWeight.Bold).fontColor(color)// 数字使用分类颜色Text(label).fontSize($r(app.float.tiny_font_size)).fontColor($r(app.color.text_tertiary)).margin({top:2})}.layoutWeight(1).height(100%).justifyContent(FlexAlign.Center).backgroundColor(Color.White).borderRadius(8)}注意这里StatBadge使用 3 个独立参数(label, value, color)而不是对象参数。这是为了避免 ArkTS 严格模式下的arkts-no-untyped-obj-literals错误。3.5 数据加载与统计loadStats():void{letstored:string|undefinedAppStorage.getstring(notes);letnotes:Note[]stored?JSON.parse(stored)asNote[]:[];this.totalNotesnotes.length;this.workCountnotes.filter((n:Note)n.category工作).length;this.studyCountnotes.filter((n:Note)n.category学习).length;this.lifeCountnotes.filter((n:Note)n.category生活).length;this.inspirationCountnotes.filter((n:Note)n.category灵感).length;// 最近笔记 数组第一个最新创建的在最前面if(notes.length0){this.recentNotenotes[0].title;}else{this.recentNote暂无笔记;}}3.6 设置菜单Column(){Text(设置).fontSize($r(app.float.body_font_size)).fontWeight(FontWeight.Medium)// 关于Row(){Text(关于)Blank()Text(知识笔记 v1.0.0).fontColor($r(app.color.text_tertiary))}.height(48).backgroundColor(Color.White).borderRadius({topLeft:8,topRight:8,bottomLeft:0,bottomRight:0})Divider().color($r(app.color.divider)).width(92%)// 版本Row(){Text(版本)Blank()Text(v1.0.0).fontColor($r(app.color.text_tertiary))}.height(48).backgroundColor(Color.White).borderRadius({topLeft:0,topRight:0,bottomLeft:8,bottomRight:8})}关于 borderRadius 的语法在 ArkUI 中borderRadius接受一个BorderRadiuses对象来分别控制四个角// ✅ 正确对象形式.borderRadius({topLeft:8,topRight:8,bottomLeft:0,bottomRight:0})// ❌ 错误不能传两个参数.borderRadius(8,{topLeft:8,...})// 编译错误四、页面间数据同步4 个消费数据的页面都需要在显示时刷新页面刷新时机实现方式Index返回时onPageShow()CategoryPage返回时onPageShow()ProfilePage返回时onPageShow()NotePage进入时aboutToAppear()读参数// 所有消费数据的页面onPageShow():void{this.loadData();// 从 AppStorage 重新读取}编辑页保存后调用router.back()上一页的onPageShow()自动触发数据刷新。五、本篇总结本篇我们完成了✅ 分类浏览页统计卡片网格 色条笔记列表 筛选联动✅ 个人中心页用户头像区 统计概览 2×2 网格 设置菜单✅ Builder 组件复用与边界 borderRadius 的正确用法✅ 多页面数据同步onPageShow 生命周期刷新机制至此知识笔记 App 全部 5 个页面已完成开发下一篇最终篇将进入构建和发布环节编译构建、模拟器调试、运行时错误诊断与修复、HAP 包生成。如果本系列对你有帮助欢迎收藏关注我们最终篇见