本文解析 ImportantDays 项目中的日历页面
CalendarPage,包括年/月视图切换、日期导航(前后月、今天、日期选择器)、选中日期的重要日列表展示、以及日历与重要日数据的联动设计。
一、页面定位
CalendarPage是应用的第二个 Tab 页面,提供日历浏览功能。用户可以:
- 在年视图和月视图之间切换
- 左右滑动浏览不同月份/年份
- 点击日期查看当天的重要日
- 通过日期选择器快速跳转
二、组件定义
@ComponentV2exportstruct CalendarPage{vm:CalendarVM=CalendarVM.getInstance();manager:ImportantDayManager=ImportantDayManager.getInstance();@LocalcurrentTitle:string='';@LocalselectedDays:ImportantDay[]=[];@LocalshowDatePicker:boolean=false;}状态变量
| 变量 | 说明 |
|---|---|
vm | 日历视图模型单例 |
manager | 重要日管理器单例 |
currentTitle | 当前显示的标题(年/月) |
selectedDays | 选中日期的重要日列表 |
showDatePicker | 日期选择器弹窗状态 |
三、生命周期
aboutToAppear():void{this.updateTitle();this.updateSelectedDays();}页面即将出现时初始化标题和选中日期的重要日列表。
四、build 方法整体结构
build(){Column(){// 1. 标题栏(导航 + 今天按钮)Row(){...}// 2. 视图切换按钮Row(){...}// 3. 日历主体if(this.vm.viewType===ViewTypeEnum.YEAR){YearCalendar({...})}else{MonthWeekCalendar({...})}// 4. 选中日期的重要日列表if(this.selectedDays.length>0){Divider()Text('当日重要日')Scroll(){Column(){ForEach(this.selectedDays,(day)=>{this.importantDayCard(day)})}}}}.bindSheet($$this.showDatePicker,this.datePickerBuilder(),{...})}布局层次
Column ├── Row (标题栏) ├── Row (视图切换) ├── YearCalendar / MonthWeekCalendar (日历主体) └── [条件] 当日重要日列表 ├── Divider ├── Text (标题) └── Scroll → ForEach → importantDayCard五、标题栏与导航
Row(){// 上个月按钮Text('<').fontSize(20).fontColor('#666666').padding(8).onClick(()=>{this.vm.selectDate=this.vm.selectDate.subtract(1,'month');this.updateTitle();})// 当前月份标题Text(this.currentTitle).fontSize(20).fontWeight(FontWeight.Bold).fontColor('#333333').layoutWeight(1).textAlign(TextAlign.Center).onClick(()=>{this.showDatePicker=true;})// 下个月按钮Text('>').fontSize(20).fontColor('#666666').padding(8).onClick(()=>{this.vm.selectDate=this.vm.selectDate.add(1,'month');this.updateTitle();})// 今天按钮Text('今天').fontSize(14).fontColor('#FF6B6B').padding({left:8,right:8}).onClick(()=>{this.vm.goToToday();this.updateTitle();this.updateSelectedDays();})}导航功能
| 按钮 | 功能 |
|---|---|
< | 上一月 |
| 标题 | 打开日期选择器 |
> | 下一月 |
| 今天 | 跳转到今天 |
标题点击
点击标题文字打开日期选择器,用户可以快速选择目标日期。
六、视图切换
Row(){ForEach(this.getViewTypes(),(item:ViewTypeItem)=>{Text(item.name).fontSize(13).fontColor(this.vm.viewType===item.type?'#FFFFFF':'#666666').backgroundColor(this.vm.viewType===item.type?'#FF6B6B':'#F5F5F5').borderRadius(12).padding({left:12,right:12,top:4,bottom:4}).margin({right:8}).onClick(()=>{this.vm.changeViewType(item.type);this.updateTitle();})})}privategetViewTypes():ViewTypeItem[]{return[{type:ViewTypeEnum.YEAR,name:'年'},{type:ViewTypeEnum.MONTH,name:'月'}];}视图切换按钮
[年] [月]- 选中状态:白字红底
- 未选中:灰字灰底
切换逻辑
点击按钮 →vm.changeViewType(type)→ VM 切换视图并重置数据 →updateTitle()更新标题。
七、日历主体渲染
if(this.vm.viewType===ViewTypeEnum.YEAR){YearCalendar({vm:this.vm,onSelectDate:(date:Date)=>{this.vm.changeDate(dayjs(date));this.vm.changeViewType(ViewTypeEnum.MONTH);this.updateTitle();this.updateSelectedDays();}})}else{MonthWeekCalendar({vm:this.vm,onChangeDay:(date:Date)=>{this.updateSelectedDays();},selectedColor:'#FF6B6B',startWeekday:this.vm.startWeekday,isShowHeader:true})}年视图回调
年视图中选择日期后:
- 更新 VM 选中日期
- 自动切换到月视图
- 更新标题和重要日列表
月视图回调
月视图中选择日期后:
- 更新选中日期的重要日列表
- 下方区域展示当天重要日
八、当日重要日列表
if(this.selectedDays.length>0){Divider().color('#EEEEEE')Text('当日重要日').fontSize(14).fontWeight(FontWeight.Medium).fontColor('#333333').padding({left:12,top:8,bottom:4})Scroll(){Column(){ForEach(this.selectedDays,(day:ImportantDay)=>{this.importantDayCard(day)})}.padding({left:12,right:12,bottom:12})}.layoutWeight(1)}条件渲染
只有选中日期有重要日时才显示此区域,避免空列表占用空间。
重要日卡片
@BuilderimportantDayCard(day:ImportantDay){Row(){// 颜色条Column().width(4).height(40).backgroundColor(day.color).borderRadius(2).margin({right:8})Column(){Text(day.title).fontSize(15).fontWeight(FontWeight.Medium).fontColor('#333333').maxLines(1)Text(`${DateUtil.getFullDateStr(day.targetDate)}·${day.getDisplayText()}`).fontSize(12).fontColor('#999999').margin({top:2})}.layoutWeight(1)// 倒数/正数标签Text(day.isCountdown?'倒数':'正数').fontSize(10).fontColor(day.isCountdown?'#FF6B6B':'#4ECDC4').backgroundColor(day.isCountdown?'#FFF0F0':'#E8FAF8').borderRadius(4).padding({left:6,right:6,top:2,bottom:2})}.padding({top:8,bottom:8}).borderRadius(8).backgroundColor('#FAFAFA').margin({bottom:4})}卡片设计
┌─┬───────────────────────────────┬──────┐ │ │ 妈妈生日 │ 倒数 │ │ │ 2026年08月15日 · 还有 29 天 │ │ └─┴───────────────────────────────┴──────┘ 4vp layoutWeight(1) auto比列表页的卡片更简洁:
- 细色条(4vp 宽)代替宽色条
- 无操作按钮
- 添加倒数/正数标签
九、日期选择器
@BuilderdatePickerBuilder(){Column(){Text('选择日期').fontSize(18).fontWeight(FontWeight.Medium).padding(16)DatePicker({start:newDate('2000-01-01'),end:newDate('2099-12-31'),selected:this.vm.selectDate.toDate()}).onChange((value:DatePickerResult)=>{if(value.year&&value.month!==undefined&&value.day){constdate=dayjs().year(value.year).month(value.month).date(value.day);this.vm.changeDate(date);this.updateTitle();this.updateSelectedDays();}}).width('100%').height(200)Button('确定').width('80%').height(40).backgroundColor('#FF6B6B').margin({top:16}).onClick(()=>{this.showDatePicker=false;})}}DatePicker 配置
| 配置 | 值 | 说明 |
|---|---|---|
start | 2000-01-01 | 可选最早日期 |
end | 2099-12-31 | 可选最晚日期 |
selected | 当前选中日期 | 初始显示日期 |
选择流程
- 用户滚动选择年/月/日
onChange实时更新 VM 选中日期- 点击"确定"关闭弹窗
- 标题和重要日列表自动更新
十、更新方法
privateupdateTitle():void{if(this.vm.viewType===ViewTypeEnum.YEAR){this.currentTitle=this.vm.getCurrentYearTitle();}else{this.currentTitle=this.vm.getCurrentMonthTitle();}}privateupdateSelectedDays():void{this.selectedDays=this.vm.getSelectedDateDays();}updateTitle
根据当前视图类型生成不同标题:
- 年视图:
"2026年" - 月视图:
"2026年07月"
updateSelectedDays
从 VM 获取选中日期的重要日列表,更新到selectedDays。
十一、数据联动
日期选择 → 列表更新
用户点击日期 ↓ MonthWeekCalendar.onDateClick ↓ vm.changeDate(date) ↓ onChangeDay 回调 ↓ CalendarPage.updateSelectedDays() ↓ vm.getSelectedDateDays() ↓ ImportantDayManager.getDaysByDate(dateStr) ↓ selectedDays 更新 ↓ UI 自动刷新(@Local 变化)月份切换 → 标题更新
用户滑动 Swiper ↓ handleSwiperChange ↓ vm.selectDate 更新到新月份 ↓ CalendarPage 检测到月份变化 ↓ updateTitle() ↓ 标题更新十二、总结
CalendarPage的设计体现了以下原则:
- 多视图共存:年视图和月视图通过条件渲染切换
- 多维度导航:前后按钮 + 今天按钮 + 日期选择器
- 数据联动:日期选择自动更新重要日列表
- 条件渲染:无重要日时不显示列表区域
- 卡片差异化:日历页卡片比列表页更简洁
- 标题智能切换:根据视图类型显示年或月标题
日历页为用户提供了从宏观(年视图)到微观(月视图+当日重要日)的完整日历浏览体验。