实例:通讯录管理(Contacts)
20 条种子数据,覆盖 6 个字母分组,索引条和统计条都有内容
一、种子数据设计思路
- 覆盖 6 个首字母组:A×5、B×2、C×3、L×4、W×3、Z×3
- 姓名含常见姓氏(张/李/王/赵/周/刘/陈/杨/黄/林/孙/吴/徐/马/朱/胡/郭/何/高/罗)
- 电话 11 位真实格式、公司职位多样化、头像颜色 8 色循环
二、种子数据表(20 条)
| id | name | phone | pinyin | company | position | avatarColor |
|---|---|---|---|---|---|---|
| 1 | 张伟 | 13800000001 | Z | 华为 | 高级工程师 | #2563EB |
| 2 | 李娜 | 13800000002 | L | 腾讯 | 产品经理 | #DC2626 |
| 3 | 王芳 | 13800000003 | W | 阿里巴巴 | 运营主管 | #059669 |
| 4 | 刘洋 | 13800000004 | L | 字节跳动 | 算法工程师 | #D97706 |
| 5 | 陈静 | 13800000005 | C | 百度 | 前端工程师 | #7C3AED |
| 6 | 杨敏 | 13800000006 | A | 美团 | 数据分析师 | #0891B2 |
| 7 | 黄涛 | 13800000007 | A | 京东 | 后端工程师 | #DB2777 |
| 8 | 林晨 | 13800000008 | L | 网易 | 游戏策划 | #4D7C0F |
| 9 | 孙悦 | 13800000009 | A | 小米 | 测试工程师 | #2563EB |
| 10 | 吴磊 | 13800000010 | W | 滴滴 | 司机运营 | #DC2626 |
| 11 | 徐娇 | 13800000011 | A | 携程 | 客户成功 | #059669 |
| 12 | 马超 | 13800000012 | B | 顺丰 | 物流经理 | #D97706 |
| 13 | 朱琳 | 13800000013 | Z | 联想 | 财务主管 | #7C3AED |
| 14 | 胡军 | 13800000014 | A | 海尔 | 渠道销售 | #0891B2 |
| 15 | 郭鹏 | 13800000015 | B | TCL | 项目经理 | #DB2777 |
| 16 | 何雨 | 13800000016 | C | 格力 | 采购专员 | #4D7C0F |
| 17 | 高翔 | 13800000017 | C | 比亚迪 | 结构工程师 | #2563EB |
| 18 | 罗敏 | 13800000018 | L | 蔚来 | 品牌公关 | #DC2626 |
| 19 | 周涛 | 13800000019 | W | 理想 | 售后经理 | #059669 |
| 20 | 赵雪 | 13800000020 | Z | 小鹏 | 市场专员 | #D97706 |
注:杨/黄/林/孙/徐/胡/何/高/罗 均以 A 组示例简化首字母映射(生产环境按真实拼音)。
三、分组统计效果
| 字母 | 人数 | 联系人 |
|---|---|---|
| A | 5 | 杨敏、黄涛、孙悦、徐娇、胡军 |
| B | 2 | 马超、郭鹏 |
| C | 3 | 陈静、何雨、高翔 |
| L | 4 | 李娜、刘洋、林晨、罗敏 |
| W | 3 | 王芳、吴磊、周涛 |
| Z | 3 | 张伟、朱琳、赵雪 |
页面效果:
- 顶部统计条:
A·5 B·2 C·3 L·4 W·3 Z·3 - 右侧索引条 26 字母中 6 个高亮可点
- 列表 6 个分组标题 + 20 行联系人
四、种子数据 SQL
INSERTINTOcontact(name,phone,email,company,position,avatar_color,pinyin,remark,created_time)VALUES('张伟','13800000001','zhangwei@example.com','华为','高级工程师','#2563EB','Z','项目对接人',1715500800000),('李娜','13800000002','lina@example.com','腾讯','产品经理','#DC2626','L','',1715500860000),('王芳','13800000003','wangfang@example.com','阿里巴巴','运营主管','#059669','W','',1715500920000),('刘洋','13800000004','liuyang@example.com','字节跳动','算法工程师','#D97706','L','老同学',1715500980000),('陈静','13800000005','chenjing@example.com','百度','前端工程师','#7C3AED','C','',1715501040000),('杨敏','13800000006','yangmin@example.com','美团','数据分析师','#0891B2','A','',1715501100000),('黄涛','13800000007','huangtao@example.com','京东','后端工程师','#DB2777','A','',1715501160000),('林晨','13800000008','linchen@example.com','网易','游戏策划','#4D7C0F','L','',1715501220000),('孙悦','13800000009','sunyue@example.com','小米','测试工程师','#2563EB','A','',1715501280000),('吴磊','13800000010','wulei@example.com','滴滴','司机运营','#DC2626','W','',1715501340000),('徐娇','13800000011','xujiao@example.com','携程','客户成功','#059669','A','合作方',1715501400000),('马超','13800000012','machao@example.com','顺丰','物流经理','#D97706','B','',1715501460000),('朱琳','13800000013','zhulin@example.com','联想','财务主管','#7C3AED','Z','',1715501520000),('胡军','13800000014','hujun@example.com','海尔','渠道销售','#0891B2','A','',1715501580000),('郭鹏','13800000015','guopeng@example.com','TCL','项目经理','#DB2777','B','',1715501640000),('何雨','13800000016','heyu@example.com','格力','采购专员','#4D7C0F','C','',1715501700000),('高翔','13800000017','gaoxiang@example.com','比亚迪','结构工程师','#2563EB','C','',1715501760000),('罗敏','13800000018','luomin@example.com','蔚来','品牌公关','#DC2626','L','',1715501820000),('周涛','13800000019','zhoutao@example.com','理想','售后经理','#059669','W','',1715501880000),('赵雪','13800000020','zhaoxue@example.com','小鹏','市场专员','#D97706','Z','',1715501940000);五、页面效果对照
| 面板区域 | 填充后效果 |
|---|---|
| 标题栏 | 「共 20 位联系人」 |
| 统计条 | A·5 B·2 C·3 L·4 W·3 Z·3(6 个标签) |
| 列表 | 6 组标题 + 20 行,色块头像 8 色循环 |
| 索引条 | 6 个字母高亮,点击跳转对应分组 |
| 搜索「1380000001」 | 命中 1-10 号联系人 |
| 搜索「张」 | 命中张伟 |
六、扩展建议
| 方向 | 做法 |
|---|---|
| 更大量数据 | 循环生成 200 条:for i in 1..200拼接姓名与电话 |
| 头像真实化 | 用@ohos.multimedia.image生成首字圆形位图 |
| 通讯组/标签 | 增加 group 字段 + 多选筛选 |
| 号码拨打 | 集成@ohos.telephony.call点击拨号 |
七、文章小结
20 条种子数据覆盖 6 个字母分组、20 个常用姓氏、8 种头像色,让索引条、统计条、分组列表、搜索四个面板区域首次打开就有饱满展示。分组的均匀分布(5/2/3/4/3/3)也方便验证索引跳转与统计逻辑的正确性。
八、20 位种子数据完整清单
| 序号 | 姓名 | 电话 | 分组 | 备注 |
|---|---|---|---|---|
| 1 | 张伟 | 13800000001 | Z | 项目对接人 |
| 2 | 李娜 | 13800000002 | L | — |
| 3 | 王芳 | 13800000003 | W | — |
| 4 | 刘洋 | 13800000004 | L | 老同学 |
| 5 | 陈静 | 13800000005 | C | — |
| 6 | 杨敏 | 13800000006 | A | — |
| 7 | 黄涛 | 13800000007 | A | — |
| 8 | 林晨 | 13800000008 | L | — |
| 9 | 孙悦 | 13800000009 | A | — |
| 10 | 吴磊 | 13800000010 | W | — |
| 11 | 徐娇 | 13800000011 | A | 合作方 |
| 12 | 马超 | 13800000012 | B | — |
| 13 | 朱琳 | 13800000013 | Z | — |
| 14 | 胡军 | 13800000014 | A | — |
| 15 | 郭鹏 | 13800000015 | B | — |
| 16 | 何雨 | 13800000016 | C | — |
| 17 | 高翔 | 13800000017 | C | — |
| 18 | 罗敏 | 13800000018 | L | — |
| 19 | 周涛 | 13800000019 | W | — |
| 20 | 赵雪 | 13800000020 | Z | — |
本表与第四章 SQL 数据一一对应,区别在于按「验收维度」重新组织:电话列用于验证搜索、分组列用于验证索引跳转、备注列用于验证详情页展示,三列恰好对应三个面板区域的验收点。
九、种子数据设计复盘
1. 多姓氏覆盖:20 个姓氏零重复
20 条姓名全部取自高频姓氏且互不重复(张李王赵周刘陈杨黄林孙吴徐马朱胡郭何高罗),原因有三:
- 姓氏是 pinyin 字段的唯一来源,同姓会让某个分组多 1 条而挤占其他分组,统计条比例失真;
- 零重复保证「一行对应一个分组样本」,排查数据问题时可按姓名直接定位;
- 贴近真实通讯录观感,又不引入同姓二次排序的额外复杂度。
补充:杨/黄/林/孙/徐/胡/何/高/罗 的真实拼音首字母分别为 Y/H/L/S/X/H/H/G/L,其中多数会落入 Y、H、S、X、G 等新分组。本文统一简化为 A 组,是为了用最少分组数覆盖「多分组 UI 展示」,生产环境务必按真实拼音入库。
2. 分组分布:为什么是 5/2/3/4/3/3
| 分组 | 人数 | 设计意图 |
|---|---|---|
| A | 5 | 验证统计条首位 + 索引条首字母点击 |
| L | 4 | 验证中间分组与同组内姓名二次排序 |
| C / W | 各 3 | 验证普通分组的常规渲染 |
| B | 2 | 验证最少人数分组的标题与索引显示 |
| Z | 3 | 验证末位索引跳转到底部 |
刻意避开「每组 3~4 人」的平均分布,因为平均分布暴露不出排序抖动、分组标题边界、索引条高低位差异三类问题;同时每组至少 2 人,避免出现空分组标题闪烁。
十、initSeedData 幂等实现
种子数据只应在首次安装后注入一次,重复启动不能重复插入。实现分两步:先查总数做幂等判断,再在事务中批量插入。
// 1. 种子数据源(与第四章 SQL 同构,供循环插入)constSEED_CONTACTS:Array<ContactSeed>=[{index:1,name:'张伟',phone:'13800000001',company:'华为',position:'高级工程师',avatarColor:'#2563EB',pinyin:'Z',remark:'项目对接人'},{index:2,name:'李娜',phone:'13800000002',company:'腾讯',position:'产品经理',avatarColor:'#DC2626',pinyin:'L',remark:''},// ... 其余 18 条与第四章 SQL 完全一致,此处省略];// 2. 幂等注入:count > 0 直接返回staticasyncinitSeedData(context:common.Context):Promise<void>{conststore=awaitContactDao.getStore(context);constcountPredicates=newrelationalStore.RdbPredicates(ContactDao.TABLE);constresultSet=awaitstore.query(countPredicates,['COUNT(*) AS cnt']);letcount=0;if(resultSet.goToFirstRow()){count=resultSet.getLong(resultSet.getColumnIndex('cnt'));}resultSet.close();if(count>0){console.info('SeedData: 已有数据,跳过注入');return;}// 3. 事务包裹批量插入,任一条失败整体回滚awaitstore.beginTransaction();try{for(constcofSEED_CONTACTS){constvalues:relationalStore.ValuesBucket={name:c.name,phone:c.phone,email:`${c.name}@example.com`,company:c.company,position:c.position,avatar_color:c.avatarColor,pinyin:c.pinyin,remark:c.remark,created_time:Date.now()+c.index*1000,// 递增时间戳,避免排序不稳定};awaitstore.insert(ContactDao.TABLE,values);}awaitstore.commit();}catch(e){awaitstore.rollBack();console.error(`SeedData: 注入失败${JSON.stringify(e)}`);}}幂等设计要点:
| 场景 | 表现 |
|---|---|
| 首次启动 | count=0 → 注入 20 条 |
| 二次启动 | count=20 → 直接 return |
| 用户删除部分后启动 | count>0 → 不补插(尊重用户操作) |
| 注入中途崩溃 | 事务回滚,下次启动重试 |
十一、注入后的索引条与列表效果预测
假设queryAll()返回全部数据并按pinyin, name排序,页面渲染效果可预测如下:
- 索引条:26 个字母中高亮 6 个(A/B/C/L/W/Z),其余 20 个置灰;点 A 滚到顶部,点 Z 滚到底部。
- 分组列表:滚动顺序为 A(杨敏、黄涛、孙悦、徐娇、胡军)→ B(马超、郭鹏)→ C(陈静、何雨、高翔)→ L(李娜、刘洋、林晨、罗敏)→ W(王芳、吴磊、周涛)→ Z(张伟、朱琳、赵雪)。
| 验证点 | 预期结果 |
|---|---|
| 统计条标签 | A·5 B·2 C·3 L·4 W·3 Z·3 共 6 个 |
| 列表项总数 | 20 行联系人 + 6 个分组标题 |
| 头像颜色 | 8 色循环,第 1 与第 9 条同色(#2563EB) |
| L 组内排序 | 李娜 → 林晨 → 罗敏 → 刘洋(按拼音 name) |
| 搜索「1380000001」 | 命中 1~10 号联系人 |
十二、验证方法(SQL)
注入完成后用以下 SQL 核对数据层,全部通过即代表种子数据正确:
-- 1. 总数应为 20SELECTCOUNT(*)FROMcontact;-- 2. 分组统计应为 5/2/3/4/3/3,共 6 行SELECTpinyin,COUNT(*)AScntFROMcontactGROUPBYpinyinORDERBYpinyin;-- 3. L 组内姓名排序:李娜、林晨、罗敏、刘洋SELECTnameFROMcontactWHEREpinyin='L'ORDERBYname;-- 4. 电话前缀搜索命中 10 条SELECTCOUNT(*)FROMcontactWHEREphoneLIKE'1380000001%';-- 5. 幂等验证:连续执行两次注入后总数仍为 20SELECTCOUNT(*)FROMcontact;在 DevEco Studio 的 RDB 调试面板或hdc shell进入应用数据库目录执行;第 2 条返回 6 行且计数与预期一致,说明分组与索引逻辑全部正确。
十三、FAQ
Q1:注入为什么放在 onPageShow 而不是 aboutToAppear?
A:aboutToAppear时组件未完全挂载,注入失败没有 UI 反馈;在首帧渲染后的onPageShow调用initSeedData,可配合 loading 提示「正在准备通讯录…」。
Q2:用户手动删光全部联系人,重启会重新注入吗?
A:不会。initSeedData只要count > 0就跳过,删光是用户主动行为,不应被种子数据覆盖;如需重置,可在设置页提供「恢复示例数据」入口。
Q3:200 条数据也能复用这套幂等逻辑吗?
A:可以,但建议把SEED_CONTACTS改为循环生成,并给created_time加随机偏移,否则低端设备批量插入可能出现数百毫秒卡顿。
Q4:pinyin 为什么不直接存全拼?
A:索引条只显示首字母,存全拼浪费存储;若搜索需匹配全拼,可在查询层动态拼接或冗余pinyin_full字段。本实例按首字母索引设计,存单字母即可。
Q5:种子数据与真实业务数据混在一起,如何区分?
A:可在 contact 表增加is_seed标记字段,或为种子数据统一使用 1380000 号段电话;本实例电话号段本身即可作为区分依据,清理时按phone LIKE '1380000%'删除即可。