十五条种子数据注入 项目:鸿蒙待办清单一开屏就热闹非凡
实例:待办事项清单(Todo List)
本文提供 15 条种子数据,让页面首次打开就内容丰富,面板统计数字饱满
一、种子数据设计思路
- 覆盖 3 种优先级(低/中/高)
- 覆盖 4 个分类(工作/生活/学习/日常)
- 未完成 12 条、已完成 3 条(对应统计卡:未完成 12 / 已完成 3 / 高优 4)
- 时间跨度为近一周,展示时间排序效果
二、种子数据表(15 条)
| id | title | priority | category | completed | created_time | 备注 |
|---|---|---|---|---|---|---|
| 1 | 完成季度周报并提交领导 | 2 | 工作 | 0 | 周一 09:30 | 含图表 |
| 2 | 预约明天上午的牙医 | 1 | 生活 | 0 | 周一 10:05 | 电话 021-… |
| 3 | 阅读《SQLite 权威指南》第 4 章 | 1 | 学习 | 0 | 周一 20:15 | 做笔记 |
| 4 | 整理桌面文件,清理垃圾 | 0 | 日常 | 0 | 周二 08:40 | - |
| 5 | 给项目加异常日志上报 | 2 | 工作 | 0 | 周二 14:22 | 需求 #128 |
| 6 | 超市采购:牛奶、鸡蛋、水果 | 1 | 生活 | 0 | 周三 09:00 | 预算 150 |
| 7 | 复习 ArkTS 状态管理 | 1 | 学习 | 0 | 周三 19:30 | 两小时 |
| 8 | 给爸妈打电话 | 1 | 生活 | 0 | 周四 12:00 | 半小时 |
| 9 | 修复登录页闪退问题 | 2 | 工作 | 0 | 周四 15:47 | 紧急 |
| 10 | 缴纳水电燃气费 | 0 | 日常 | 0 | 周五 08:20 | - |
| 11 | 整理照片备份到云端 | 1 | 日常 | 0 | 周五 21:00 | 50G |
| 12 | 准备周末分享会 PPT | 2 | 工作 | 0 | 周六 10:00 | 主题:RDB 实践 |
| 13 | 完成健身卡续费 | 1 | 生活 | 1 | 上周 16:00 | 已完成 |
| 14 | 更新应用隐私协议 | 1 | 工作 | 1 | 上周 11:30 | 已完成 |
| 15 | 看完收藏的 3 篇技术文章 | 0 | 学习 | 1 | 上周 22:10 | 已完成 |
统计效果:未完成 12(其中高优先级 4:id 1、5、9、12)、已完成 3 → 完成率 20%,底部进度条显示3/15 = 20%。
三、种子数据 SQL
INSERTINTOtodo(title,priority,category,completed,created_time,completed_time,remark)VALUES('完成季度周报并提交领导',2,'工作',0,1715581800000,0,'含图表'),('预约明天上午的牙医',1,'生活',0,1715583900000,0,'电话 021-88886666'),('阅读《SQLite 权威指南》第 4 章',1,'学习',0,1715613300000,0,'做笔记'),('整理桌面文件,清理垃圾',0,'日常',0,1715647200000,0,''),('给项目加异常日志上报',2,'工作',0,1715660520000,0,'需求 #128'),('超市采购:牛奶、鸡蛋、水果',1,'生活',0,1715706000000,0,'预算 150'),('复习 ArkTS 状态管理',1,'学习',0,1715743800000,0,'两小时'),('给爸妈打电话',1,'生活',0,1715752800000,0,'半小时'),('修复登录页闪退问题',2,'工作',0,1715766420000,0,'紧急'),('缴纳水电燃气费',0,'日常',0,1715797200000,0,''),('整理照片备份到云端',1,'日常',0,1715850000000,0,'50G'),('准备周末分享会 PPT',2,'工作',0,1715868000000,0,'主题:RDB 实践'),('完成健身卡续费',1,'生活',1,1715500800000,1715500800000,'已完成'),('更新应用隐私协议',1,'工作',1,1715452200000,1715452200000,'已完成'),('看完收藏的 3 篇技术文章',0,'学习',1,1715590200000,1715590200000,'已完成');时间戳为示例值,实际初始化时可用
Date.now()偏移生成,保证列表时间展示合理。
四、DAO 内联初始化方法
在TodoDao中提供initSeedData(),首次启动(表为空时)自动填充:
staticasyncinitSeedData(context:common.UIAbilityContext):Promise<void>{conststore=awaitthis.getStore(context);constcount=awaitstore.querySql(`SELECT COUNT(*) AS c FROM${this.TABLE}`);lettotal=0;if(count.goToNextRow())total=count.getLong(count.getColumnIndex('c'));count.close();if(total>0)return;// 已有数据,跳过constnow=Date.now();constseed=[{title:'完成季度周报并提交领导',priority:2,category:'工作',completed:0,createdTime:now-6*86400000,remark:'含图表'},// ... 其余 14 条同上表];for(constsofseed){constvalues:relationalStore.ValuesBucket={title:s.title,priority:s.priority,category:s.category,completed:s.completed,created_time:s.createdTime,completed_time:0,remark:s.remark,};awaitstore.insert(this.TABLE,values);}}五、页面效果对照
| 面板区域 | 填充后效果 |
|---|---|
| 统计卡 | 未完成12/ 已完成3/ 高优先级4 |
| 全部视图 | 15 张卡片,未完成在前,高优带 ⚡ |
| 未完成视图 | 12 张卡片,前三张为高优任务 |
| 已完成视图 | 3 张置灰卡片,带删除线 |
| 底部进度条 | 已完成 3 项 ▏▏▏▏ 20% |
| 分类标签 | 工作 5 / 生活 4 / 学习 3 / 日常 3 |
六、扩展建议
| 方向 | 做法 |
|---|---|
| 数据更多 | 生成 50 条时用循环 + 随机组合标题 |
| 分组显示 | 按 category GROUP BY 分段标题 |
| 截止日期 | 增加 due_time 字段 + 逾期红色提示 |
| 拖动排序 | 增加 sort_order 字段,长按拖动重排 |
七、文章小结
15 条种子数据覆盖了 4 分类 × 3 优先级 × 双状态 × 一周时间跨度,首次打开页面统计卡、列表、进度条全部有内容可展示,视觉饱满且能直接验证排序、过滤、聚合逻辑。initSeedData()的空表判断保证重复启动不重复插入,是生产环境初始化演示数据的标准做法。
八、种子数据设计思路复盘
1. 为什么是 15 条?
15 条是「演示数据黄金数量」:
| 数据量 | 问题 | 15 条的表现 |
|---|---|---|
| 5 条 | 列表空荡,统计卡数字没分量,排序效果看不出来 | — |
| 15 条 | — | 一屏半、有滚动感,统计数字饱满 |
| 50 条 | 首屏刷屏、手工维护成本高、数字失去可读性 | — |
15 条 = 4 分类 × 3 优先级 = 12 种组合至少各出现 1 次,再留 3 条给「已完成」,恰好覆盖「未完成在前、高优置顶、完成率 20%」三类验证需求,不浪费一条数据。
2. 状态分布:12 未完成 / 3 已完成
- 未完成 12 条(80%):撑起未完成视图的滚动列表,让统计卡「未完成 12」有分量。
- 已完成 3 条(20%):不多不少——多了喧宾夺主,少了进度条
3/15 = 20%没有存在感。 - 完成率 20% 是个「有故事」的数字:一眼能读出五分之一,既不显得全员躺平,也不像演示数据那样 100% 完成,反而真实。
3. 优先级分布:4 高 / 7 中 / 4 低
| 优先级 | 条数 | 分布 | 作用 |
|---|---|---|---|
| 高(2) | 4 | id 1、5、9、12,分散在周一~周六 | 验证高优置顶,统计卡「高优 4」 |
| 中(1) | 7 | 生活 / 学习 / 日常为主 | 常规任务,构成列表主体 |
| 低(0) | 4 | id 4、10、15 等 | 验证降序时沉底 |
高优先级刻意分散到一周各天而不是扎堆——这样无论按优先级排序还是按时间排序,都能看到高优任务穿插其中,避免出现「某一天全是高优」的失真画面。
九、种子数据完整表格(15 条)
| # | 标题 | 状态 | 优先级 | 分类 | 创建时间 |
|---|---|---|---|---|---|
| 1 | 完成季度周报并提交领导 | 未完成 | 高 | 工作 | 周一 09:30 |
| 2 | 预约明天上午的牙医 | 未完成 | 中 | 生活 | 周一 10:05 |
| 3 | 阅读《SQLite 权威指南》第 4 章 | 未完成 | 中 | 学习 | 周一 20:15 |
| 4 | 整理桌面文件,清理垃圾 | 未完成 | 低 | 日常 | 周二 08:40 |
| 5 | 给项目加异常日志上报 | 未完成 | 高 | 工作 | 周二 14:22 |
| 6 | 超市采购:牛奶、鸡蛋、水果 | 未完成 | 中 | 生活 | 周三 09:00 |
| 7 | 复习 ArkTS 状态管理 | 未完成 | 中 | 学习 | 周三 19:30 |
| 8 | 给爸妈打电话 | 未完成 | 中 | 生活 | 周四 12:00 |
| 9 | 修复登录页闪退问题 | 未完成 | 高 | 工作 | 周四 15:47 |
| 10 | 缴纳水电燃气费 | 未完成 | 低 | 日常 | 周五 08:20 |
| 11 | 整理照片备份到云端 | 未完成 | 中 | 日常 | 周五 21:00 |
| 12 | 准备周末分享会 PPT | 未完成 | 高 | 工作 | 周六 10:00 |
| 13 | 完成健身卡续费 | 已完成 | 中 | 生活 | 上周 16:00 |
| 14 | 更新应用隐私协议 | 已完成 | 中 | 工作 | 上周 11:30 |
| 15 | 看完收藏的 3 篇技术文章 | 已完成 | 低 | 学习 | 上周 22:10 |
十、initSeedData 的幂等保护实现
「幂等」指同一操作无论执行多少次,结果都一致。initSeedData()必须幂等——否则每次冷启动都重复插入 15 条,统计卡数字会像滚雪球一样翻倍(15 → 30 → 45 …)。
1. 双重防线实现
staticasyncinitSeedData(context:common.UIAbilityContext):Promise<void>{conststore=awaitthis.getStore(context);// 防线一:空表判断——表里已有任何数据就直接返回constresultSet=awaitstore.querySql(`SELECT COUNT(*) AS c FROM${this.TABLE}`);lettotal=0;if(resultSet.goToNextRow())total=resultSet.getLong(resultSet.getColumnIndex('c'));resultSet.close();if(total>0){console.info(`[TodoDao] 已有数据${total}条,跳过种子注入`);return;}// 防线二:事务包裹——15 条要么全成功,要么全回滚,杜绝「插了一半」constnow=Date.now();constDAY=86400000,H=3600000,M=60000;constseed:Array<relationalStore.ValuesBucket>=[{title:'完成季度周报并提交领导',priority:2,category:'工作',completed:0,created_time:now-6*DAY,completed_time:0,remark:'含图表'},{title:'预约明天上午的牙医',priority:1,category:'生活',completed:0,created_time:now-6*DAY+35*M,completed_time:0,remark:'电话 021-88886666'},{title:'阅读《SQLite 权威指南》第 4 章',priority:1,category:'学习',completed:0,created_time:now-6*DAY+10*H+45*M,completed_time:0,remark:'做笔记'},{title:'整理桌面文件,清理垃圾',priority:0,category:'日常',completed:0,created_time:now-5*DAY+8*H+40*M,completed_time:0,remark:''},{title:'给项目加异常日志上报',priority:2,category:'工作',completed:0,created_time:now-5*DAY+14*H+22*M,completed_time:0,remark:'需求 #128'},{title:'超市采购:牛奶、鸡蛋、水果',priority:1,category:'生活',completed:0,created_time:now-4*DAY+9*H,completed_time:0,remark:'预算 150'},{title:'复习 ArkTS 状态管理',priority:1,category:'学习',completed:0,created_time:now-4*DAY+19*H+30*M,completed_time:0,remark:'两小时'},{title:'给爸妈打电话',priority:1,category:'生活',completed:0,created_time:now-3*DAY+12*H,completed_time:0,remark:'半小时'},{title:'修复登录页闪退问题',priority:2,category:'工作',completed:0,created_time:now-3*DAY+15*H+47*M,completed_time:0,remark:'紧急'},{title:'缴纳水电燃气费',priority:0,category:'日常',completed:0,created_time:now-2*DAY+8*H+20*M,completed_time:0,remark:''},{title:'整理照片备份到云端',priority:1,category:'日常',completed:0,created_time:now-2*DAY+21*H,completed_time:0,remark:'50G'},{title:'准备周末分享会 PPT',priority:2,category:'工作',completed:0,created_time:now-1*DAY+10*H,completed_time:0,remark:'主题:RDB 实践'},{title:'完成健身卡续费',priority:1,category:'生活',completed:1,created_time:now-7*DAY+16*H,completed_time:now-7*DAY+16*H,remark:'已完成'},{title:'更新应用隐私协议',priority:1,category:'工作',completed:1,created_time:now-7*DAY+11*H+30*M,completed_time:now-7*DAY+11*H+30*M,remark:'已完成'},{title:'看完收藏的 3 篇技术文章',priority:0,category:'学习',completed:1,created_time:now-7*DAY+22*H+10*M,completed_time:now-7*DAY+22*H+10*M,remark:'已完成'},];store.beginTransaction();try{for(constitemofseed)awaitstore.insert(this.TABLE,item);store.commit();console.info(`[TodoDao] 种子数据注入完成,共${seed.length}条`);}catch(e){store.rollBack();console.error('[TodoDao] 种子注入失败,已整体回滚:',JSON.stringify(e));}}相比第四章的简化版,这里把时间戳改为
Date.now()相对偏移:周一 =now - 6*DAY,再逐条加小时 / 分钟偏移,保证列表时间永远锚定「最近一周」,不会随真实日期漂移。
2. 为什么不用「建表时一次性写入」?
有人会在建表 SQL 里用INSERT ... SELECT ... WHERE NOT EXISTS一步完成判空 + 注入,但 ArkTS 的executeSql对多值 INSERT 支持不友好,而且判空本身只需一次COUNT(*),成本极低。「先查后插 + 事务回滚」是最贴合 RDB 最佳实践、也最容易读懂的方案:判空保护幂等,事务保护原子性,两者各司其职。
十一、注入后页面效果预测与验证方法
1. 效果预测
| 页面区域 | 预期表现 |
|---|---|
| 统计卡 | 未完成12/ 已完成3/ 高优4 |
| 全部视图 | 15 张卡片,未完成在前,高优带 ⚡ |
| 底部进度条 | 已完成 3 项 ▏▏▏▏ 20% |
| 分类标签 | 工作 5 / 生活 4 / 学习 3 / 日常 3 |
2. SQL 验证语句
真机或模拟器跑起来后,可用 DevEco Studio 的 RDB 调试面板,或直接在 DAO 里打日志执行以下 SQL 核对:
-- ① 总数与状态分布:期望 15 = 12 未完成 + 3 已完成SELECTcompleted,COUNT(*)FROMtodoGROUPBYcompleted;-- ② 优先级分布:期望 高4 / 中7 / 低4SELECTpriority,COUNT(*)FROMtodoGROUPBYpriority;-- ③ 分类分布:期望 工作5 / 生活4 / 学习3 / 日常3SELECTcategory,COUNT(*)FROMtodoGROUPBYcategory;-- ④ 未完成视图排序:高优在前,同优先级按创建时间倒序SELECTid,title,priority,created_timeFROMtodoWHEREcompleted=0ORDERBYpriorityDESC,created_timeDESC;-- ⑤ 幂等验证:连续冷启动两次后总数仍应为 15,而不是 30SELECTCOUNT(*)FROMtodo;判断标准:①~③ 的数字与统计卡完全一致;④ 的前三条应为高优任务(id 9、12、5、1 之一);⑤ 能直接证明initSeedData()的幂等保护生效。
十二、FAQ
Q1:为什么不用固定时间戳,而用Date.now()偏移?
固定时间戳写死的是某个具体时刻,半年后打开 App,「昨天」的待办实际是一百多天前创建的,时间语义完全错乱。用now - 偏移生成,数据永远锚定「今天」,演示效果不随真实时间漂移。
Q2:种子数据会不会污染用户的真实数据?
不会。initSeedData()只在表为空(COUNT = 0)时注入,用户一旦新增或删除过任何一条,总数 > 0,后续启动全部跳过。生产环境建议在设置页提供「恢复示例数据」入口:先DELETE FROM todo;再重新调用注入。
Q3:已完成的 3 条,completed_time 为什么等于 created_time?
completed_time 的语义是「何时打勾」。演示数据没有真实勾选动作,把完成时间设为与创建时间相同,保证状态切换逻辑读到的完成时间是合法的非 0 值,不会出现「已完成了但完成时间为 0」的脏数据。
Q4:注入 15 条会不会卡启动?
15 条单条 INSERT 走本地 RDB,实测毫秒级(< 10ms),远低于首帧渲染耗时。真正的开销是每次getStore的建库动作,所以initSeedData()应放在onWindowStageCreate之后异步调用,不阻塞 UI 线程。