公司动态
十二篇日记三层时间轴:鸿蒙日记本种子数据与饱满节点
实例电子日记本Diary技术种子数据注入、跨月时间分布一、种子数据的设计目标实例 4 的种子数据要服务时间轴页面设计目标与记账本不同——记账本要「统计有层次」日记本要「时间有跨度、内容有故事」时间跨度覆盖最近约两个月时间轴不是集中在几天而是自然铺开内容故事感每篇日记是一个生活片段搬新家、学做饭、加班、老友重逢时间轴滚动起来像在翻阅一个人的生活心情多样性 开心、 平淡、 低落都有覆盖心情 emoji 锚点视觉不单调天气/地点标签晴雨多云、新家/公司/书房/公园卡片底部的信息位有内容可展示。二、12 篇日记种子数据全表#标题心情天气地点时间偏移1新生活开始晴新家61 天前2第一次做饭晴厨房58 天前3加班到深夜多云公司55 天前4老友重逢晴老巷饭店47 天前5雨夜读书雨书房41 天前6跑步五公里晴滨江公园35 天前7被领导批评了阴公司30 天前8周末爬山晴青山24 天前9学做咖啡拉花晴家里17 天前10项目顺利上线晴火锅店10 天前11失眠的一晚雨卧室5 天前12给妈妈打电话晴家里2 天前数据叙事这是一份「独立生活初期的青年日记」——搬进新家开始新生活 → 学做饭 → 工作受挫加班/批评→ 自我调节跑步/爬山/拉花→ 项目成功 → 想家。情绪曲线有起伏→→→时间轴滚动时能感受到故事的发展而不是冷冰冰的数据堆。三、代码实现相对时间 完整正文每篇日记都配了完整的正文内容几十字到上百字这是长文本字段 content 的展示素材。看两篇样例{title:新生活开始,content:搬进新家的第一天窗外有棵很大的梧桐树。晚上整理完行李坐在阳台上喝了一杯茶感觉生活终于要重新开始了。,mood:,weather:晴,location:新家,createdTime:now-61*day},{title:雨夜读书,content:下了一整天雨窝在家里把《活着》读完了。福贵的一生让人唏嘘活着本身就有意义。雨声里读书格外安静。,mood:,weather:雨,location:书房,createdTime:now-41*day},注入逻辑与记账本完全同构constnowDate.now();constday86400000;constseed:DiarySeed[][/* 12 篇 */];for(constsofseed){constvalues:relationalStore.ValuesBucket{title:s.title,content:s.content,mood:s.mood,weather:s.weather,location:s.location,created_time:s.createdTime,updated_time:s.createdTime,};awaitstore.insert(DiaryDao.TABLE,values);}hilog.info(DOMAIN,TAG,已填充 12 篇日记种子数据);DiarySeed 接口种子专用精简字段interfaceDiarySeed{title:string;content:string;mood:string;weather:string;location:string;createdTime:number;}没有 id自增、没有 updatedTime复用 createdTime——与 4-1 文章的设计原则一致种子数据结构只含业务必要字段其余代码补齐。四、幂等保护与记账本完全一致的模式constcountResultawaitstore.querySql(SELECT COUNT(*) AS c FROM${DiaryDao.TABLE});lettotal0;if(countResult.goToNextRow()){totalcountResult.getLong(countResult.getColumnIndex(c));}countResult.close();if(total0){return;// 已有数据跳过}与实例 3 的 initSeedData 是同一套模板查 COUNT → 非空即返回 → 空则注入。幂等是种子数据的黄金法则从实例 1 到实例 20 所有 initSeedData 都遵循这个模式读者可以把它当作固定模板复用。五、种子数据与时间轴页面的联动效果12 篇跨月日记注入后时间轴页面呈现以下效果时间轴节点分布左侧蓝色日期节点依次显示05-16、05-19、05-22…… 一直到07-16相对日期覆盖最近两个月节点错落有致时间轴「饱满」但不拥挤。心情锚点每张卡片左上角是心情 emoji 出现 8 次、 2 次、 2 次——大部分是积极情绪符合「记录美好生活」的产品调性。底部标签天气标签晴/多云/雨/阴与月份标签2025年5月/6月/7月交替出现卡片信息位丰富。搜索命中演示在搜索框输入「爬山」命中第 8 篇「周末爬山」标题匹配输入「活着」命中第 5 篇「雨夜读书」正文匹配「《活着》」——双字段 OR LIKE 的搜索能力在真实数据下一目了然。5.1 时间轴「饱满度」的量化评估「饱满」是个感性词但我们可以量化它。12 篇日记分布在 61 天的区间里平均 5 天一篇——这意味着滚动时间轴时大约每 5 天就会出现一个日期节点一屏约 6~8 条正好覆盖一个多月的日记密度。这个密度是刻意设计的过密每天一篇时间轴变成日更流水账节点密集到失去节奏感滚动时视觉疲劳过疏每月一篇时间轴稀疏页面大段空白观感单薄12 篇 / 2 个月刚好让滚动有「翻月历」的仪式感——偶尔连续几天05-16、05-19、05-22 间隔 3 天偶尔跳跃一周05-22 到 05-30 隔了 8 天节奏有疏有密真实自然。这个「5 天一篇」的密度参考值可以推广任何「内容流」类应用的种子数据以「一屏能展示 2~3 个时间节点」为基准设计密度观感最佳。5.2 正文长度的分级设计12 篇日记的正文长度也不是均一的刻意分了三档长度档字数范围代表篇目用途短40~60 字跑步五公里、失眠的一晚卡片摘要直接显示全文中60~90 字新生活开始、老友重逢摘要截断60 字演示长90~120 字雨夜读书、项目顺利上线完整叙事体现长文本为什么分档因为页面有「超过 60 字显示摘要 省略号」的截断逻辑4-2 文章讲过——如果所有日记都 50 字截断逻辑永远不触发无法演示如果都 200 字卡片全是「…」观感不佳。分级设计让每一档 UI 分支都有真实数据覆盖这也是测试驱动种子数据设计的思想。5.3 心情分布的情绪曲线如果把这 12 篇日记按时间顺序排列心情的变化是一条有起伏的曲线 → → → → → → → → → → → 新生活 做饭 加班 重逢 雨夜 跑步 批评 爬山 拉花 上线 失眠 电话这条曲线的意义在于它不是单调的而是真实情绪的起伏——开心新生活、重逢、低落加班、被批评、平淡失眠、治愈跑步、爬山。用户在演示 App 时滚动时间轴能感受到「这是一个有喜怒哀乐的人」而不是情绪稳定的机器人。种子数据的故事性就藏在这些细节里。六、验证方法hilog 日志搜DiaryDao看到「已填充 12 篇日记种子数据」页面显示进入日记本页面时间轴出现 12 个节点、12 张卡片数据库直查SELECTCOUNT(*)FROMdiary;-- 应返回 12SELECTtitle,mood,weatherFROMdiaryORDERBYcreated_time;逐行核对与第二节表格一致搜索验证输入「爬山」应只剩 1 条结果清空恢复 12 条。七、FAQ种子数据常见问题Q1为什么时间轴节点不是每天一个A日记不是天天写时间轴设计允许「日期间隔」——61 天前、58 天前、55 天前这样不均匀分布反而真实。每天都有日记的「全勤时间轴」看起来整齐但不真实。Q2正文可以多长ATEXT 字段理论支持数百 MB。本实例的种子正文控制在 100 字内卡片摘要展示生产环境写几千字毫无压力——TextArea 滚动输入 TEXT 存储天生匹配。Q3搜索中文可以吗A可以。SQLite 的 LIKE 对 UTF-8 中文按字节序列匹配「爬山」「活着」这类中文关键词都能命中无需特殊配置。Q4新增日记后种子数据会怎样A互不干扰。种子只在首次表空注入用户新增的日记追加在后时间轴按时间自然融合。Q512 篇日记的时间偏移为什么不是均匀的A看数据表会发现偏移量是 61、58、55、47、41、35、30、24、17、10、5、2 天——这是刻意的不均匀。均匀间隔每天一篇看起来机械不均匀分布更接近真实写作频率有时连续几天有灵感有时一周不写。种子数据的时间分布本身就是一种「设计」服务于真实感。Q6可以加入图片字段吗A可以。图片一般存路径字符串TEXT图片文件存沙箱目录数据库只存引用。生产环境可加image_path TEXT DEFAULT 字段查询不变页面用 Image 组件加载。八、文章小结本篇文章展示了实例 4 的种子数据12 篇跨月日记 完整正文 心情天气地点标签。它们让时间轴页面一开屏就有「饱满」的节点分布和「有故事」的内容质感也让关键词搜索有了可验证的命中素材。更进一步我们量化了「饱满度」的密度设计、正文长度的分级设计、情绪曲线的叙事设计——种子数据不只是「往数据库塞数据」更是一场精心编排的「数据策展」。种子数据到此已第三次出场实例 1/3/4模板已非常成熟。九、种子数据的代码实现细节回顾为了帮助读者在本地复现这里把initSeedData的完整骨架再梳理一遍标注每一步的关键点staticasyncinitSeedData(context:common.Context):Promisevoid{conststoreawaitDiaryDao.getStore(context);// 1. 幂等判断查总数非空即返回constcountResultawaitstore.querySql(SELECT COUNT(*) AS c FROM${DiaryDao.TABLE});lettotal0;if(countResult.goToNextRow()){totalcountResult.getLong(countResult.getColumnIndex(c));}countResult.close();if(total0){return;}// 2. 构造种子数组相对时间偏移constnowDate.now();constday86400000;constseed:DiarySeed[][/* 12 篇日记见第二节表格 */];// 3. 逐条插入for(constsofseed){constvalues:relationalStore.ValuesBucket{title:s.title,content:s.content,mood:s.mood,weather:s.weather,location:s.location,created_time:s.createdTime,updated_time:s.createdTime,};awaitstore.insert(DiaryDao.TABLE,values);}hilog.info(DOMAIN,TAG,已填充 12 篇日记种子数据);}三个关键设计回顾相对时间所有 createdTime 用now - N * day生成任何时刻运行都呈现「最近两个月」的时间轴updatedTime 复用 createdTime新注入的日记没有编辑历史两个时间戳一致是合理的Content 完整正文每篇 40~120 字不等的正文是长文本字段的展示素材也是搜索验证的命中目标。十、扩展到「编辑日记」的种子验证本实例页面暂未提供编辑 UI但数据层update方法已就绪。如果读者想验证编辑逻辑可以这样测用 DevEco Studio 的数据库工具直接执行一条 UPDATE 语句模拟「用户编辑了日记」UPDATEdiarySETcontent修改后的正文,updated_time1750000000000WHEREid1;然后重新进入页面——queryAll按 created_time 排序编辑不影响顺序但如果你切换排序字段为 updated_time4-3 文章讲过这条被编辑的日记会跳到最前。双时间戳的协同效应在数据层面一目了然。十一、常见问题 FAQ补充Q7种子数据里可以直接写中文吗文件编码有要求吗A可以。.ets 文件默认 UTF-8 编码中文标题/正文/备注直接写入字符串字面量即可。SQLite 存储 UTF-8 文本无压力。唯一要注意的是如果 IDE 文件编码被改成 GBK 等会出现乱码——保持 UTF-8 是团队协作的基本约定。Q812 篇日记的正文里可以带换行符吗A可以。\n换行符会原样存入 TEXT 字段读取后 Text 组件默认不渲染换行需要设置lineHeight或使用富文本但数据层面无损失。生产环境的长日记多段落通常用\n\n分段展示层按段落渲染。Q9如果用户把所有日记都删了重新进入会重新注入吗A会因为幂等判断依据是「表内数据量 0」全部删除后表空了再次进入aboutToAppear触发initSeedData会重新注入 12 篇。这是有意设计——Demo 永远有内容。如果产品不希望这样用户删光就是真的想清空需要额外标记「是否已初始化」的开关如 SharedPreferences 存一个 flag。Q10种子数据的时间为什么从 61 天前开始而不是 30 天A覆盖两个月61 天比一个月30 天的时间轴视觉更丰富——滚动时能看到「上个月」和「这个月」两组节点月份标签2025年5月/6月/7月才有机会交替出现。这是为时间轴「跨月感」服务的设计。十二、总结与预告至此实例 4 的四篇正文全部完成建表、UI、查询、种子。下一篇4-5是收官文章展示 DiaryPage 全量代码与运行效果描述让读者把前面四篇的知识串成一条完整的「能跑的 App」。十三、补充种子数据验证13.1 验证 SQL按月份分组统计时间轴页面的「跨月感」不能只靠肉眼用一条分组 SQL 即可量化验证SELECTstrftime(%Y-%m,created_time/1000,unixepoch,localtime)ASmonth,COUNT(*)AScntFROMdiaryGROUPBYmonthORDERBYmonth;按第三节的时间偏移换算期望输出三行2025-05 | 5、2025-06 | 5、2025-07 | 2。上月 5 篇、当月 5 篇、本月 2 篇——两个月各占一半正是时间轴「跨月铺开」的数据证明也让月份标签2025年5月/6月/7月有交替出现的机会。13.2 验证 SQL字数排行正文分级设计5.2 节同样可以用 SQL 验证SELECTtitle,LENGTH(content)ASchar_countFROMdiaryORDERBYchar_countDESC;期望结果雨夜读书、项目顺利上线排最前90~120 字档跑步五公里、失眠的一晚垫底40~60 字档——三档长度在数据层面一目了然长文本展示与 60 字摘要截断逻辑都有对应素材。LENGTH对中文按 1 个字符计数正好匹配「字数」口径。13.3 注入后的时间轴效果预测把 12 篇日记按时间轴「播放」一遍预期观感滚动位置看到的节点预期感受顶部给妈妈打电话、失眠的一晚当月 2 篇稀疏开场中部上线、拉花、爬山、批评每周 1~2 篇节奏适中底部新生活开始、第一次做饭上月 5 篇跨月标签切换验收三连月份标签至少切换 3 次5月→6月→7月心情锚点 占多数8/12搜索「爬山」「活着」各命中 1 条。三个维度全部通过种子数据即验收合格无需再逐条肉眼核对。13.4 FAQQ11为什么验证 SQL 里 created_time 要除以 1000ARDB 存储的是毫秒时间戳而 SQLite 的strftime按秒计算需要先/1000转成秒再用unixepoch格式化localtime参数保证按本地时区取月份避免 UTC 偏差导致 5/6 月边界错位——这也是「数据库直查」验证时最容易踩的坑。Q12验证时发现月份统计不对怎么排查A按三步走①SELECT COUNT(*) FROM diary确认是否被幂等逻辑跳过表非空时不注入种子时刻是旧数据② 抽查单条SELECT created_time FROM diary换算成日期核对相对偏移是否与第二节表格一致③ 再跑分组统计。绝大多数「验证失败」都出在前两步。Q13这些验证 SQL 必须手动执行吗A不一定。它们适合 DevEco Studio 的数据库工具手动执行也可以把统计逻辑封装成DiaryDao.getMonthStats()供页面复用——实际上 4-3 的月份分组就是同一思路。手动验证用于验收代码封装用于产品功能两者不冲突。