
实例:通讯录管理(Contacts)
20 条种子数据,覆盖 6 个字母分组,索引条和统计条都有内容
一、种子数据设计思路
- 覆盖 6 个首字母组:A×5、B×2、C×3、L×4、W×3、Z×3
- 姓名含常见姓氏(张/李/王/赵/周/刘/陈/杨/黄/林/孙/吴/徐/马/朱/胡/郭/何/高/罗)
- 电话 11 位真实格式、公司职位多样化、头像颜色 8 色循环
二、种子数据表(20 条)
| 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
INSERT INTO contact (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 同构,供循环插入)
const SEED_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 直接返回
static async initSeedData(context: common.Context): Promise<void> {
const store = await ContactDao.getStore(context);
const countPredicates = new relationalStore.RdbPredicates(ContactDao.TABLE);
const resultSet = await store.query(countPredicates, ['COUNT(*) AS cnt']);
let count = 0;
if (resultSet.goToFirstRow()) {
count = resultSet.getLong(resultSet.getColumnIndex('cnt'));
}
resultSet.close();
if (count > 0) {
console.info('SeedData: 已有数据,跳过注入');
return;
}
// 3. 事务包裹批量插入,任一条失败整体回滚
await store.beginTransaction();
try {
for (const c of SEED_CONTACTS) {
const values: 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, // 递增时间戳,避免排序不稳定
};
await store.insert(ContactDao.TABLE, values);
}
await store.commit();
} catch (e) {
await store.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. 总数应为 20
SELECT COUNT(*) FROM contact;
— 2. 分组统计应为 5/2/3/4/3/3,共 6 行
SELECT pinyin, COUNT(*) AS cnt FROM contact GROUP BY pinyin ORDER BY pinyin;
— 3. L 组内姓名排序:李娜、林晨、罗敏、刘洋
SELECT name FROM contact WHERE pinyin = 'L' ORDER BY name;
— 4. 电话前缀搜索命中 10 条
SELECT COUNT(*) FROM contact WHERE phone LIKE '1380000001%';
— 5. 幂等验证:连续执行两次注入后总数仍为 20
SELECT COUNT(*) FROM contact;
在 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%' 删除即可。







