HarmonyOs应用《重要日》开发第16篇 - 重要日列表页卡片设计
本文深入解析 ImportantDays 项目的主页面ImportantDayPage包括页面整体布局、统计信息栏、空视图设计、重要日卡片的视觉设计色条天数内容操作按钮、排序切换、以及添加/编辑弹窗的调用流程。一、页面定位ImportantDayPage是应用的第一个 Tab 页面也是用户最常使用的页面。它负责展示所有重要日列表提供统计信息全部/即将/今天/已过支持排序切换提供添加入口支持编辑、删除、置顶操作二、组件定义ComponentV2exportstruct ImportantDayPage{manager:ImportantDayManagerImportantDayManager.getInstance();LocalshowAddSheet:booleanfalse;LocaleditingDay:ImportantDay|nullnull;LocalsortBy:SortTypeEnumSortTypeEnum.DATE_ASC;}状态变量变量装饰器说明manager无引用数据管理器单例showAddSheetLocal控制添加弹窗显示editingDayLocal当前编辑的重要日null添加模式sortByLocal排序方式三、build 方法整体结构build(){Stack({alignContent:Alignment.BottomEnd}){Column(){// 1. 标题栏标题 排序按钮Row(){...}// 2. 统计信息栏this.statsBar()// 3. 列表区域if(this.manager.allDays.length0){this.emptyView()}else{Scroll(){Column(){ForEach(this.manager.getSortedList(this.sortBy),(day:ImportantDay){this.importantDayCard(day)})}}}}// 4. 添加按钮浮动Button(){...}}.bindSheet($$this.showAddSheet,this.addSheetBuilder(),{...})}布局层次Stack (BottomEnd 对齐) ├── Column (主体内容) │ ├── Row (标题栏) │ ├── statsBar (统计信息) │ └── Scroll (列表) / emptyView (空视图) └── Button (浮动添加按钮)四、标题栏Row(){Text(重要日).fontSize(24).fontWeight(FontWeight.Bold).fontColor(#333333)Blank()// 弹性空间Text(this.getSortName()).fontSize(13).fontColor(#666666).padding({left:8,right:8,top:4,bottom:4}).backgroundColor(#F5F5F5).borderRadius(12).onClick((){this.cycleSortType();})}.padding({left:16,right:16,top:12,bottom:8})排序按钮privategetSortName():string{switch(this.sortBy){caseSortTypeEnum.DATE_ASC:return日期↑;caseSortTypeEnum.DATE_DESC:return日期↓;caseSortTypeEnum.CREATE_TIME:return创建;default:return日期↑;}}privatecycleSortType():void{consttypes[SortTypeEnum.DATE_ASC,SortTypeEnum.DATE_DESC,SortTypeEnum.CREATE_TIME];constcurrentIndextypes.indexOf(this.sortBy);this.sortBytypes[(currentIndex1)%types.length];}排序切换点击排序按钮循环切换三种排序方式日期↑ → 日期↓ → 创建 → 日期↑ → ...五、统计信息栏BuilderstatsBar(){Row(){this.statItem(全部,this.manager.getStats().total,#666666)this.statItem(即将,this.manager.getStats().upcoming,#FF6B6B)this.statItem(今天,this.manager.getStats().today,#4ECDC4)this.statItem(已过,this.manager.getStats().past,#999999)}.justifyContent(FlexAlign.SpaceAround)}BuilderstatItem(label:string,count:number,color:string){Column(){Text(${count}).fontSize(20).fontWeight(FontWeight.Bold).fontColor(color)Text(label).fontSize(11).fontColor(#999999).margin({top:2})}.alignItems(HorizontalAlign.Center)}统计项颜色设计统计项颜色含义全部#666666灰中性总数即将#FF6B6B红需要关注今天#4ECDC4青特别提示已过#999999浅灰弱化视觉效果12 5 1 6 全部 即将 今天 已过四个数字均匀分布颜色区分不同状态。六、空视图BuilderemptyView(){Column(){Text().fontSize(60).margin({bottom:16})Text(还没有重要日).fontSize(16).fontColor(#999999)Text(点击右下角添加你的第一个重要日).fontSize(13).fontColor(#CCCCCC).margin({top:4})}.layoutWeight(1).justifyContent(FlexAlign.Center)}空视图设计日历 emoji 作为视觉元素提示文字引导用户操作居中显示占据剩余空间七、重要日卡片这是列表页最核心的 UI 元素。BuilderimportantDayCard(day:ImportantDay){Row(){// 左侧颜色条 天数Stack({alignContent:Alignment.Center}){Column().width(56).height(70).backgroundColor(day.color).borderRadius({topLeft:8,bottomLeft:8})Column(){Text(this.getDaysNumber(day)).fontSize(22).fontWeight(FontWeight.Bold).fontColor(#FFFFFF)Text(day.isCountdown?倒数:正数).fontSize(10).fontColor(#FFFFFFCC).margin({top:2})}}.width(56).height(70)// 右侧内容Column(){Row(){Text(day.title).fontSize(16).fontWeight(FontWeight.Medium).fontColor(#333333).maxLines(1).layoutWeight(1)if(day.isPinned){Text().fontSize(14).margin({left:4})}}Text(DateUtil.getFullDateStr(day.targetDate)).fontSize(12).fontColor(#999999).margin({top:4})Text(day.getDisplayText()).fontSize(13).fontColor(day.isToday()?day.color:#666666).margin({top:2})if(day.note){Text(day.note).fontSize(11).fontColor(#BBBBBB).maxLines(1).margin({top:2})}}.layoutWeight(1).padding({left:12,right:12}).height(70)// 操作按钮Column(){Text(day.isPinned?取消置顶:置顶).fontSize(11).fontColor(#666666).padding(4).onClick((){this.manager.togglePin(day.dayID);})Text(编辑).fontSize(11).fontColor(#4ECDC4).padding(4).margin({top:4}).onClick((){this.editingDayday;this.showAddSheettrue;})Text(删除).fontSize(11).fontColor(#FF6B6B).padding(4).margin({top:4}).onClick((){this.confirmDelete(day);})}.height(70).padding({right:8})}.borderRadius(8).backgroundColor(#FAFAFA).margin({bottom:8}).shadow({radius:2,color:#10000000,offsetX:0,offsetY:1})}卡片布局┌──────┬──────────────────────────┬──────────┐ │ │ 妈妈生日 │ 取消置顶 │ │ 29 │ 2026年08月15日 │ 编辑 │ │ 倒数 │ 还有 29 天 │ 删除 │ │ │ 记得买蛋糕 │ │ └──────┴──────────────────────────┴──────────┘ 56vp layoutWeight(1) auto三栏布局区域宽度内容左侧色条56vp天数 倒数/正数标签中间内容layoutWeight(1)标题 日期 显示文本 备注右侧操作自适应置顶 编辑 删除天数显示privategetDaysNumber(day:ImportantDay):string{constdiffday.getDaysDiff();if(diff0){return今天;}return${Math.abs(diff)};}今天显示今天文字其他显示绝对值天数显示文本颜色.fontColor(day.isToday()?day.color:#666666)今天的重要日使用其自定义颜色突出显示。置顶标记if(day.isPinned){Text().fontSize(14).margin({left:4})}置顶的重要日在标题右侧显示图钉 emoji。卡片阴影.shadow({radius:2,color:#10000000,offsetX:0,offsetY:1})轻微的阴影效果增强卡片层次感。八、操作按钮设计置顶/取消置顶Text(day.isPinned?取消置顶:置顶).onClick((){this.manager.togglePin(day.dayID);})编辑Text(编辑).onClick((){this.editingDayday;this.showAddSheettrue;})点击编辑时设置editingDay并打开弹窗。删除Text(删除).onClick((){this.confirmDelete(day);})privateconfirmDelete(day:ImportantDay):void{this.manager.deleteDay(day.dayID);}按钮颜色按钮颜色含义置顶/取消置顶#666666灰中性操作编辑#4ECDC4青安全操作删除#FF6B6B红危险操作九、添加按钮Button(){Row(){Text().fontSize(28).fontColor(#FFFFFF)Text(添加).fontSize(14).fontColor(#FFFFFF).margin({left:4})}}.width(100).height(44).borderRadius(22).backgroundColor(#FF6B6B).margin({right:16,bottom:16}).onClick((){this.editingDaynull;this.showAddSheettrue;})设计细节圆角按钮borderRadius: 22 高度的一半主题色背景右下角浮动Stack的BottomEnd对齐点击时清空editingDay进入添加模式十、弹窗绑定.bindSheet($$this.showAddSheet,this.addSheetBuilder(),{height:600,dragBar:true,showClose:false,})BuilderaddSheetBuilder(){AddImportantDayBuilder({editingDay:this.editingDay,onSave:(day:ImportantDay){if(this.editingDay){this.manager.updateDay(day);}else{this.manager.addDay(day);}this.showAddSheetfalse;},onCancel:(){this.showAddSheetfalse;}})}弹窗配置配置值说明height600弹窗高度dragBartrue显示拖拽条showClosefalse不显示关闭按钮保存回调onSave:(day:ImportantDay){if(this.editingDay){this.manager.updateDay(day);// 编辑模式 → 更新}else{this.manager.addDay(day);// 添加模式 → 新增}this.showAddSheetfalse;}十一、列表渲染Scroll(){Column(){ForEach(this.manager.getSortedList(this.sortBy),(day:ImportantDay){this.importantDayCard(day)})}.padding({left:16,right:16,bottom:80})}.layoutWeight(1).align(Alignment.Top).scrollBar(BarState.Off)渲染特点ForEach使用ForEach而非LazyForEach因为重要日数量通常不多实时排序getSortedList(this.sortBy)每次渲染时实时排序隐藏滚动条scrollBar(BarState.Off)底部留白bottom: 80避免浮动按钮遮挡内容十二、总结ImportantDayPage的设计体现了以下原则信息层次清晰标题栏 → 统计 → 列表自上而下卡片设计精巧三栏布局色条内容操作一目了然操作便捷置顶/编辑/删除直接在卡片上操作空状态友好空视图引导新用户浮动入口右下角添加按钮始终可见排序灵活一键循环切换排序方式列表页是用户使用频率最高的页面其设计质量直接决定了应用的用户体验。