便签墙的数据库骨架:ArkTS 为鸿蒙便签设计置顶与颜色字段
发布时间:2026/8/16 1:51:34 作者:尧图编辑部 阅读量:1,286

实例备忘录便签Memo技术便签字段、置顶/颜色字段、MemoDao一、业务需求分析便签的数据形态便签Memo是轻量级记事工具——比日记轻不用长篇大论、比任务清单自由不用状态管理。它的数据行为有几个独特之处置顶重要便签钉在列表最前「待办」钉在顶部提醒自己——排序字段pinned颜色标签便签用不同颜色区分用途黄色工作、粉色购物、绿色健身——颜色即分类部分更新改标题不动内容、换颜色不动标题——单字段轻量更新是便签的高频操作多彩墙展示彩色便签像便利贴一样铺满墙面视觉即内容。核心需求清单需求数据层实现页面呈现新建便签insert标题内容颜色便签墙新增彩签置顶/取消部分更新 pinned 字段置顶区/普通区换颜色部分更新 color 字段便签变色编辑全量更新弹窗编辑颜色筛选按 color 过滤颜色筛选条删除delete长按删除二、字段设计一张灵活的便签表便签表 memo字段名类型约束说明idINTEGERPRIMARY KEY AUTOINCREMENT自增主键titleTEXTNOT NULL标题contentTEXTDEFAULT ‘’内容colorTEXTDEFAULT ‘#FFE8A3’便签底色 HEXpinnedINTEGERNOT NULL DEFAULT 00 普通 / 1 置顶created_timeINTEGERNOT NULL创建时间戳updated_timeINTEGERNOT NULL最近修改时间戳设计要点拆解1. color 存 HEX 色值字符串。便签颜色是「展示属性」——直接存#FFE8A3这样的色值页面backgroundColor(c.color)零转换渲染。为什么不用枚举数字因为颜色是「开放式取值」用户可以加新颜色枚举反而限制而且色值本身就是可读的。展示型属性存原始值。2. pinned 用 0/1 数字。置顶与否是「状态」可参与排序ORDER BY pinned DESC和过滤WHERE pinned1——数字编码正确选择。排序时置顶在前orderByDesc(pinned)让 1置顶排 0普通前面。3. updated_time 驱动排序。便签墙的默认顺序置顶在前同状态按更新时间倒序最近编辑的在前——ORDER BY pinned DESC, updated_time DESC。两个排序键的组合让置顶区和普通区内部都「最新的在前」。4. content 可空。便签可以只写标题如「超市采购清单」content 默认空串。标题必填NOT NULL内容可选。5. 六色便签色板。DAO 导出色板常量供页面使用exportconstMEMO_COLORS:string[][#FFE8A3,#FFD1DC,#C9F2C7,#BFE3FF,#E2D5FF,#FFE0B3,];米黄/粉/绿/蓝/紫/橙六色——新建便签时循环取色颜色筛选条也用这个数组。色板常量单一来源页面取色、筛选、渲染都引用它。三、建表 SQL 与索引CREATETABLEIFNOTEXISTSmemo(idINTEGERPRIMARYKEYAUTOINCREMENT,titleTEXTNOTNULL,contentTEXTDEFAULT,colorTEXTDEFAULT#FFE8A3,pinnedINTEGERNOTNULLDEFAULT0,created_timeINTEGERNOTNULL,updated_timeINTEGERNOTNULL);CREATEINDEXIFNOTEXISTSidx_memo_pinnedONmemo(pinned);idx_memo_pinned索引服务「置顶在前」的排序与「置顶区」过滤。pinned 只有 0/1 两个值索引收益有限但表达「这是查询热点」的意图。数据量小12 张便签时索引与否无差别——本实例建索引更多是「声明式意图」。四、MemoDao 封装三种更新形态数据层核心MemoDao。本实例的技术亮点是更新方法的三种形态——全量更新、部分更新置顶、部分更新颜色形态一全量更新编辑内容staticasyncupdate(context:common.Context,m:Memo):Promisenumber{conststoreawaitMemoDao.getStore(context);constvalues:relationalStore.ValuesBucket{title:m.title,content:m.content,color:m.color,updated_time:m.updatedTime,};constpredicatesnewrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.equalTo(id,m.id);returnawaitstore.update(values,predicates);}编辑弹窗保存时调用——更新标题、内容、颜色三个业务字段 刷新 updated_time不更新 pinned 和 created_time。形态二部分更新仅置顶状态staticasynctogglePin(context:common.Context,id:number,pinned:number):Promisenumber{conststoreawaitMemoDao.getStore(context);constvalues:relationalStore.ValuesBucket{pinned:pinned,updated_time:Date.now(),};constpredicatesnewrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.equalTo(id,id);returnawaitstore.update(values,predicates);}部分更新的精髓ValuesBucket只放pinned 顺带刷新 updated_time——其他字段标题、内容、颜色完全不动。这就是「只改置顶」的轻量操作避免了「先查全量再更新全量」的冗余读。形态三部分更新仅换颜色staticasyncchangeColor(context:common.Context,id:number,color:string):Promisenumber{conststoreawaitMemoDao.getStore(context);constvalues:relationalStore.ValuesBucket{color:color,updated_time:Date.now(),};constpredicatesnewrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.equalTo(id,id);returnawaitstore.update(values,predicates);}与 togglePin 同构——只改 color 字段。两个部分更新方法展示了 RDB 的天然能力UPDATE语句本来就可以只更新指定列ValuesBucket里有什么就更新什么。这是 SQLite 相对「ORM 全量更新」的优势——按需更新零冗余写。五、查询方法全量、按颜色、计数全部便签置顶在前 更新倒序staticasyncqueryAll(context:common.Context):PromiseMemo[]{conststoreawaitMemoDao.getStore(context);constpredicatesnewrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.orderByDesc(pinned).orderByDesc(updated_time);constresultawaitstore.query(predicates);returnMemoDao.collect(result);}双键排序先按 pinned 降序置顶组在前组内按 updated_time 降序最近编辑在前。排序的层级结构主键定分组、次键定组内顺序。按颜色筛选staticasyncqueryByColor(context:common.Context,color:string):PromiseMemo[]{conststoreawaitMemoDao.getStore(context);constpredicatesnewrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.equalTo(color,color).orderByDesc(pinned).orderByDesc(updated_time);constresultawaitstore.query(predicates);returnMemoDao.collect(result);}计数标题栏统计staticasynccount(context:common.Context):Promisenumber{conststoreawaitMemoDao.getStore(context);constresultawaitstore.querySql(SELECT COUNT(*) AS c FROM${MemoDao.TABLE});lettotal0;if(result.goToNextRow()){totalresult.getLong(result.getColumnIndex(c));}result.close();returntotal;}六、技术要点对照表技术点实现方式生产价值置顶排序orderByDesc(‘pinned’)重要便签在前双键排序pinned updated_time分组 组内序部分更新ValuesBucket 只放目标列单字段轻量写颜色存储HEX 字符串UI 零转换色板常量MEMO_COLORS 导出单一来源更新排序部分更新时刷 updated_time编辑上浮七、文章小结备忘录便签的数据层核心是**「置顶 颜色」两个特色字段 三种更新形态**pinned 驱动置顶排序双键排序的分组结构、color 驱动彩色墙与颜色筛选、部分更新togglePin/changeColor展示 RDB「按需更新」的轻量能力。这是「轻量内容应用」的标准建模——字段少、更新灵、排序活。下一篇11-2展示多彩便签墙 UI——置顶区 颜色筛选 新建浮钮像便利贴墙一样赏心悦目。八、便签表字段设计详解五个字段背后的决策建表容易建好表难。便签表六列虽少每一列都对应一个产品决策。本节把五个核心字段逐个拆解讲清「为什么这样设计、换一种设计会怎样」。8.1 title必填的「门面字段」决策点本实例选择备选方案取舍理由是否必填NOT NULL允许 NULL便签墙卡片靠标题识别空标题 空墙长度限制TEXT 不限长VARCHAR(50)SQLite 的 TEXT 无长度上限UI 层负责限制输入空串处理允许禁止极少数场景可只写内容UI 兜底显示「无标题」标题是便签墙的「门面」——卡片第一行永远是标题。约束层NOT NULL管「必须有值」UI 层管「值不能是空白」双保险。8.2 content纯文本的可空内容content 允许为空DEFAULT ‘’因为便签可以只写标题。内容按纯文本存储详见第十节「富文本与纯文本的选择」。换行用\n字符直接存进 TEXT——查询、展示、编辑全程零转换。8.3 colorHEX 字符串——展示属性存原始值颜色是本实例最有代表性的「展示型属性」不参与业务计算、只参与渲染。存 HEX 字符串#FFE8A3而非枚举数字三个理由零转换渲染backgroundColor(c.color)直接用无需查映射表开放式取值后续加颜色不改表结构、不改 DAO可读性#FFD1DC一看就是粉色数字 2 代表什么要翻文档。HEX 存储的纪律色板常量统一大写、六位、不带 alpha#RRGGBB。若将来要半透明扩为八位#AARRGGBB即可——存储格式兼容只是约定升级。色值纪律比约束更重要数据库管不了大小写MEMO_COLORS色板常量就是「事实上的校验」。8.4 pinned0/1 状态位——排序与过滤的双用字段pinned 用 INTEGER 0/1 而非 TEXT ‘yes’/‘no’因为它是参与排序的状态用法SQL 表达数字方案文本方案排序ORDER BY pinned DESC1 在前、0 在后需 CASE WHEN 转数字过滤WHERE pinned 1直接等值引号 大小写敏感更新SET pinned 1直接赋值需防 ‘YES’/‘No’ 混乱数字 0/1 在排序、过滤、更新三个场景全部「零转换」文本方案处处要转换或兜底。参与计算的字段用数字编码参与展示的字段存原始值——这是字段类型选择的黄金法则。8.5 两个时间戳created_time 与 updated_time 的分工字段写入时机服务场景created_timeinsert 时一次写入新建时间展示updated_timeinsert 每次更新都刷新排序 最近修改展示created_time是「身份证」一次写入不再变updated_time是「心跳」任何变更改内容、换颜色、置顶都要刷新——因为它是排序键之一。两个时间戳不能合并合并后「创建时间」会随编辑漂移「最近编辑」会丢失。九、置顶排序思路置顶优先 时间倒序便签墙的排序是一条规则先置顶、后普通同组内最近编辑在前。实现就是双键 ORDER BYSELECT*FROMmemoORDERBYpinnedDESC,updated_timeDESC;9.1 为什么不是「独立置顶表」有人会问置顶是不是该单独建一张memo_pin关联表memo_id pin_time本实例选择直接在 memo 表加 pinned 字段维度单表 pinned 字段独立置顶表查询一条 SQL 双键排序JOIN 或子查询更新UPDATE memo SET pinned1插入/删除关联记录事务单表原子两表需事务适用量级百张以内海量数据场景独立置顶表适合「置顶是长列表独立操作」的场景如消息列表便签数量少、置顶操作频繁toggle字段方案更简单直接——简单需求用简单建模。9.2 置顶时刷新 updated_time 的连锁反应togglePin 部分更新时顺带刷新 updated_timeconstvalues:relationalStore.ValuesBucket{pinned:pinned,updated_time:Date.now(),};这一步是有意为之置顶本身是一次「用户关注的行为」刷新时间让刚置顶的便签在置顶组内也上浮到最新。排序键与操作绑定——任何改变用户关注度的操作都刷新 updated_time排序永远反映「最近被关注」。十、富文本与纯文本内容字段的技术选型便签内容存什么格式是内容字段最重要的选型。本实例选纯文本维度纯文本富文本HTML/Markdown存储原始字符串带标签字符串体积 3~10 倍编辑TextArea 直接读写需解析器 编辑器渲染Text 组件直接显示需 RichText/Web 解析搜索筛选无影响标签干扰 LIKE 匹配复杂度零依赖需引入渲染库纯文本的优势在于整条数据链路零转换写入TextArea.value→ 存储TEXT→ 展示Text→ 编辑回填全程同一字符串。\n换行符原样存储卡片预览取第一行即可// 卡片摘要取内容第一行超 20 字省略privatepreview(content:string):string{constlinecontent.split(\n)[0]??;returnline.length20?${line.slice(0,20)}…:line;}何时才需要富文本内容需格式排版加粗、列表、链接、内容超长需折叠展示。便签是短内容 彩色卡片视觉纯文本已够用多一分存储就多一分复杂度。十一、FAQ建表与数据层常见问题Q1为什么 id 用 AUTOINCREMENT 而不是 UUID便签是单机数据无跨设备合并需求自增整数索引紧凑、查询快。若未来做多端同步再迁移为字符串主键。Q2置顶便签多了会不会影响查询性能pinned 只有 0/1idx_memo_pinned索引区分度低每个值约一半数据实际走表扫描。但便签表数据量小百张以内扫描开销可忽略。索引收益与数据量成正比——本实例建索引更多是表达查询意图。Q3颜色存 HEX用户自定义颜色怎么校验数据层只保证非空格式校验放 UI 层色板选择 正则^#[0-9A-Fa-f]{6}$兜底。数据层不做无业务意义的强校验校验放在产生数据的边界UI。Q4部分更新不读旧值会不会覆盖其他字段的并发修改单用户单设备无并发写且 UPDATE 只写 ValuesBucket 中的列其他列保持原值——这正是部分更新的安全性来源。Q5同样置顶的便签顺序会不会乱不会。双键排序是确定性的pinned 相同按 updated_time 倒序updated_time 也相同则按 id 倒序兜底——排序键越加越稳生产环境建议显式追加 id 作第三键。Q6内容存富文本标签会不会更好见第十节。便签场景纯文本是「够用且简单」的最优解需求进化到排版时再评估迁移成本。十二、本章小结本文补齐了建表与数据层的完整拼图五列字段各有决策依据必填/可空/编码/展示、颜色 HEX 的存储纪律、置顶排序的字段 vs 关联表之争、纯文本的内容选型。建表不是写 SQL而是把产品决策翻译成列约束——每一列都应该回答「为什么这样存」。