
本篇深入剖析 ImportantDays 项目的数据模型设计包括核心的 ImportantDay 模型、枚举类型、接口定义以及 JSON 序列化/反序列化的实现。一、模型设计原则在 ImportantDays 项目中数据模型设计遵循以下原则单一职责每个模型只负责一个领域可序列化支持 JSON 序列化以持久化存储响应式友好使用ObservedV2Trace支持数据驱动 UI类型安全使用枚举和接口确保类型正确二、核心模型ImportantDay2.1 模型定义ImportantDay是整个应用的核心数据模型表示一个用户记录的重要日期// model/ImportantDay.etsexportenumCountMode{COUNTDOWN0,// 倒数模式距离目标日期还有多少天COUNTUP1,// 正数模式目标日期已经过去多少天}exportenumRepeatType{NONE0,// 不重复YEARLY1,// 每年重复MONTHLY2,// 每月重复}exportenumSortType{BY_DATE0,// 按日期排序BY_CREATED1,// 按创建时间排序BY_COUNTDOWN2,// 按倒计时排序}ObservedV2exportclassImportantDay{Traceid:string;Tracetitle:string;Tracedate:string;Tracedescription:string;TracecolorIndex:number0;TracecountMode:numberCountMode.COUNTDOWN;TracerepeatType:numberRepeatType.NONE;TraceadvanceDays:number0;TraceisPinned:booleanfalse;TracecreatedAt:number0;TraceupdatedAt:number0;constructor(id:string,title:string,date:string){this.idid;this.titletitle;this.datedate;this.createdAtDate.now();this.updatedAtDate.now();}}2.2 字段详解字段类型说明idstring唯一标识符格式为时间戳_随机数titlestring重要日标题如妈妈的生日datestring目标日期格式YYYY-MM-DDdescriptionstring描述信息可选colorIndexnumber颜色索引0-15对应16色调色板countModenumber计数模式0倒数1正数repeatTypenumber重复类型0不重复1每年2每月advanceDaysnumber提前提醒天数0-30isPinnedboolean是否置顶createdAtnumber创建时间戳updatedAtnumber最后更新时间戳2.3 为什么使用 number 而非 enum在 ArkTS 中Trace装饰器对枚举类型的支持有限。为了确保序列化和反序列化的稳定性项目中使用number类型存储枚举值但在代码中通过引入枚举常量来使用保证可读性。// 在 Constants.ets 中重新导出枚举export{CountMode,RepeatType,SortType};// 使用时通过枚举名称引用if(day.countModeCountMode.COUNTDOWN){// 倒数逻辑}三、JSON 序列化与反序列化3.1 序列化toJSONtoJSON方法将ImportantDay对象转换为 JSON 字符串toJSON():string{returnJSON.stringify({id:this.id,title:this.title,date:this.date,description:this.description,colorIndex:this.colorIndex,countMode:this.countMode,repeatType:this.repeatType,advanceDays:this.advanceDays,isPinned:this.isPinned,createdAt:this.createdAt,updatedAt:this.updatedAt,});}3.2 反序列化fromJSONfromJSON是静态方法从 JSON 字符串重建ImportantDay对象staticfromJSON(json:string):ImportantDay|null{try{constobj:Recordstring,ObjectJSON.parse(json);constdaynewImportantDay(obj[id]asstring,obj[title]asstring,obj[date]asstring);day.description(obj[description]asstring)||;day.colorIndex(obj[colorIndex]asnumber)||0;day.countMode(obj[countMode]asnumber)??CountMode.COUNTDOWN;day.repeatType(obj[repeatType]asnumber)??RepeatType.NONE;day.advanceDays(obj[advanceDays]asnumber)||0;day.isPinned(obj[isPinned]asboolean)||false;day.createdAt(obj[createdAt]asnumber)||Date.now();day.updatedAt(obj[updatedAt]asnumber)||Date.now();returnday;}catch(e){returnnull;}}3.3 序列化的设计要点容错处理使用||或??提供默认值防止旧数据缺少字段时崩溃null 安全解析失败时返回null调用方可以处理向后兼容新增字段时旧数据的缺失字段会被赋予默认值// 使用 ?? 处理可能为 undefined 的字段day.countMode(obj[countMode]asnumber)??CountMode.COUNTDOWN;这里??和||的选择有讲究??空值合并只有null和undefined时才使用默认值||逻辑或0、false、也会触发默认值对于countMode值0是合法的COUNTDOWN所以用??对于isPinned值false是合法的但用|| false也是安全的。四、辅助模型AvoidAreaAvoidArea用于管理安全区域状态栏、导航栏的高度// model/AvoidArea.etsObservedV2exportclassAvoidArea{TracetopRectHeight:number0;TracebottomRectHeight:number0;constructor(topRectHeight:number,bottomRectHeight:number){this.topRectHeighttopRectHeight;this.bottomRectHeightbottomRectHeight;}}这个模型通过AppStorageV2全局共享所有页面都能获取安全区域信息实现沉浸式全屏布局。五、类型定义Types.ets项目中使用接口interface定义类型契约// types/Types.etsexportinterfaceTabListItem{label:Resource;icon:Resource;iconChecked:Resource;}exportinterfaceYearMonth{year:number;month:number;}exportinterfaceCountText{value:number;suffix:string;isToday:boolean;}exportinterfaceStats{total:number;upcoming:number;passed:number;thisMonth:number;}5.1 CountText 接口CountText用于封装倒数/正数的显示文本exportinterfaceCountText{value:number;// 数值如 30suffix:string;// 后缀如 天后 或 天前isToday:boolean;// 是否是今天}在MainViewModel中使用publicgetCountText(day:ImportantDay):CountText{consteffectiveDatethis.getEffectiveDate(day);constdaysDateUtil.daysFromToday(effectiveDate);if(days0){return{value:0,suffix:今天,isToday:true};}if(day.countModeCountMode.COUNTDOWN){return{value:Math.abs(days),suffix:days0?天后:天前,isToday:false};}else{constpassedDays-days;return{value:Math.abs(passedDays),suffix:天,isToday:false};}}5.2 Stats 接口Stats用于统计数据exportinterfaceStats{total:number;// 总计upcoming:number;// 即将到来passed:number;// 已过thisMonth:number;// 本月}5.3 CalendarMonthData 和 CalendarCell日历数据结构定义在CalendarDataSource.ets中exportinterfaceCalendarMonthData{year:number;month:number;cells:CalendarCell[];}exportinterfaceCalendarCell{day:number;// 日期几号isCurrentMonth:boolean;// 是否是当前月date:string;// YYYY-MM-DD 格式}六、ID 生成策略DateUtil.generateId()方法用于生成唯一 IDstaticgenerateId():string{return${Date.now()}_${Math.floor(Math.random()*10000)};}格式1690000000000_3456这种方案简单高效时间戳保证唯一性和时间顺序随机数避免同一毫秒创建多条记录时的冲突不依赖外部库不需要网络连接七、数据流转全流程用户操作 UI 组件 ViewModel Preferences │ │ │ │ │── 添加重要日 ──→ │ │ │ │ │── addDay(day) ────→│ │ │ │ │── dayList.push ──→│ │ │ │── saveDays() ────→│ │ │ │ │── putSync(key, json) │ │ │ │── flushSync() │ │←── Monitor 触发 ──│ │ │ │── 刷新 UI │ │ │←── 显示新列表 ────│ │ │八、小结本篇详细介绍了 ImportantDays 项目的数据模型设计。ImportantDay模型通过ObservedV2Trace实现响应式通过toJSON/fromJSON实现持久化。枚举和接口的设计保证了类型安全和代码可读性。下一篇将深入 MainViewModel 单例架构的实现。