在这里插入图片描述

实例:商品分页列表(Product)|技术:循环生成 60 条商品、分页效果验证

一、种子数据的设计目标

实例 8 的种子数据要验证分页,设计目标非常明确:数据量必须足够分页。pageSize=10,60 条 = 6 页——首页加载 10 条,触底加载 20、30、40、50、60,最后显示「已全部加载」。

具体目标:

  1. 60 条商品:6 页 × 10 条,足够演示多次触底加载;
  2. 6 大分类:数码/服饰/食品/家居/美妆/图书各 10 条——分类筛选条有 6 个分类可选;
  3. 销量梯度:50~500 不等——按销量排序时顺序有区分度,验证 ORDER BY sales DESC;
  4. 价格梯度:9.9 起,每类递增——划线价/现价对比有层次;
  5. 标签覆盖:热卖/新品/清仓/空标签交替——标签徽标的三种状态都能展示。

二、循环生成:60 条数据的手工与自动之争

60 条商品如果逐条手写 seed 数组,代码会膨胀到 200 行。本实例用循环生成——6 类 × 10 件,名称、价格、销量全部按公式生成:

const categories: string[] = ['数码', '服饰', '食品', '家居', '美妆', '图书'];
const images: string[] = ['📱', '👟', '🍫', '🛋️', '💄', '📖', '🎧', '🧥', '🍪', '🕯️'];
const tags: string[] = ['热卖', '新品', '清仓', '', '', ''];
const names: string[] = [ /* 60 个商品名,见下表 */ ];
const now = Date.now();
for (let i = 0; i < 60; i++) {
  const cat = categories[Math.floor(i / 10)];       // 每 10 条一个分类
  const price = 9.9 + (i % 10) * 12.5;               // 每类 10 档价格
  const originalPrice = price * 1.4;                 // 划线价 = 现价 × 1.4
  const sales = 50 + ((i * 37) % 450);               // 销量 50~499
  const values: relationalStore.ValuesBucket = {
    name: names[i],
    price: price,
    original_price: Math.round(originalPrice * 10) / 10,
    sales: sales,
    image: images[i % images.length],
    tag: tags[i % tags.length],
    category: cat,
    created_time: now - i * 3600000,
  };
  await store.insert(ProductDao.TABLE, values);
}

生成公式拆解

字段 公式 效果
分类 categories[i / 10] 每 10 条轮换一个分类
现价 9.9 + (i%10) * 12.5 每类内 10 档价格(9.9~122.4)
划线价 price * 1.4 四舍五入 约 4 折折扣感
销量 50 + (i*37 % 450) 伪随机 50~499,有区分度
图片 images[i % 10] 10 个 emoji 循环
标签 tags[i % 6] 热卖/新品/清仓/空 循环

为什么 (i * 37) % 450 而不是 i * 7 37 是与 450 互质的数,(i*37)%450 在 i=0~59 时产生近似均匀分布的伪随机销量(不会出现明显的线性递增)。i * 7 会线性递增,销量排序变成「按插入顺序」无区分度。伪随机公式让数据看起来自然——这是种子生成的小技巧。

四舍五入Math.round(originalPrice * 10) / 10 保一位小数(如 13.86 → 13.9),避免浮点长尾(13.860000001)。

三、60 个商品名清单

为了「有故事」的商品名,60 条名称按分类设计(节选):

分类 商品名(每类 10 个)
数码 旗舰手机、无线耳机、智能手表、蓝牙音箱、机械键盘、电竞鼠标、显示器、充电宝、平板电脑、相机
服饰 运动鞋、卫衣、牛仔裤、羽绒服、帆布包、棒球帽、袜子三双、围巾、休闲裤、T恤
食品 坚果礼盒、进口巧克力、曲奇饼干、速溶咖啡、茶叶礼盒、蜂蜜、辣条、薯片、酸奶、水果干
家居 懒人沙发、落地灯、香薰蜡烛、收纳盒、地毯、窗帘、靠垫、桌布、花瓶、挂画
美妆 保湿面霜、口红、眼影盘、洗发水、面膜、香水、防晒霜、粉底液、卸妆水、精华液
图书 编程入门、数据结构、设计模式、营销宝典、历史小说、科幻经典、散文集、人物传记、心理学、经济学

每个分类的商品名是「该品类真实存在的商品」,读者滚动瀑布流时能产生「这店真在卖货」的代入感。商品名的真实性直接影响演示效果

四、分页效果预测

60 条商品注入后,分页表现:

操作 已加载 页码 底部状态
进入页面 10 0 上滑加载更多
触底一次 20 1 上滑加载更多
触底两次 30 2 上滑加载更多
触底五次 60 5 — 已全部加载 —

**「已加载 60 / 共 60 件」**时 finished=true,不再触发加载。整个过程 6 次网络交互(数据库查询),每次 10 条——这就是懒加载的完整生命周期。

分类筛选预测:切到「数码」→ 加载数码的 10 条(正好一页,第二页不足一页 → 直接 finished)→ 标题「共 60 件」不变(total 是全表),列表 10 条。单分类不足 10 条时,一次加载即完成——验证「不足一页即完成」的终止判据。

五、验证方法

  1. hilog 日志:搜 ProductDao,看到「已填充 60 条商品种子数据」;
  2. 页面显示:首屏 10 条双列商品,底部「上滑加载更多」;滚动触底自动追加;
  3. 数据库直查
    SELECT COUNT(*) FROM goods;  -- 60
    SELECT category, COUNT(*) FROM goods GROUP BY category;  -- 每类 10
    SELECT name, sales FROM goods ORDER BY sales DESC LIMIT 3;  -- 销量前三
    
  4. 分页验证:观察「已加载」数字从 10 → 20 → 30 递增,最终 60 后显示「已全部加载」。

六、FAQ

Q1:为什么 60 条而不是 600 条?
A:60 条 = 6 页,足够演示「多次触底加载」且每次加载肉眼可见。600 条要触底 60 次,演示冗长;且 600 条循环生成对页面初次渲染有压力。种子量 = 演示场景所需的最小充分量

Q2:销量「伪随机」会不会有重复值?
A:会,50~499 区间 60 个数必然有重复。重复销量不影响排序正确性(相同销量按插入顺序稳定),也不影响分页。真正的随机分布反而让数据更真实。

Q3:价格为什么从 9.9 开始递增?
A:9.9 是电商「心理定价」的经典锚点(9.9、19.9、29.9…),递增公式 9.9 + (i%10)*12.5 生成 9.9~122.4 的十档价格,覆盖「低价引流款」到「中端主力款」。价格梯度让瀑布流的价格显示有层次。

Q4:可以手动添加商品吗?
A:数据层有 insert 方法,但页面未提供「添加商品」UI(只读列表)。读者可自行扩展:悬浮按钮 + 表单 + insert + refresh。新增商品会出现在按销量排序的对应位置。

Q5:60 条数据注入需要多久?
A:毫秒级(60 次 insert)。如果未来扩展到几千条,应包事务批量提交(6-3 文章讲过),性能提升一个数量级。

Q6:为什么 created_time 用 now - i * 3600000
A:每条间隔 1 小时的上架时间——虽然本实例未按时间排序,但保留时间戳字段让「按新品排序」成为可扩展能力。种子数据的时间字段为后续功能留余地(与 8-1 文章的设计呼应)。

七、文章小结

本篇文章展示了实例 8 的种子数据:循环生成的 60 条商品(6 类 × 10 件)+ 伪随机销量 + 价格梯度 + 标签覆盖。核心是「循环生成代替手工数组」——用分类轮换、销量伪随机、价格公式把 60 条数据压缩到十几行代码,同时保证数据自然不机械。60 条正好 6 页,分页与懒加载在真实数据下得到完整验证。

下一篇(8-5)是实例 8 的收官文章,展示 ProductPage 全量代码与运行效果。

八、60 条种子数据全览与批量注入实录

8.1 种子数据示例(前 18 条)

公式生成了 60 条数据,但「公式正确 ≠ 数据合理」——把前 18 条摊开看(数码 10 条 + 服饰 8 条,其余 42 条由同一公式依分类轮换生成):

# 名称 分类 现价(元) 划线价(元) 销量 库存
1 旗舰手机 数码 9.9 13.9 50 100
2 无线耳机 数码 22.4 31.4 87 117
3 智能手表 数码 34.9 48.9 124 134
4 蓝牙音箱 数码 47.4 66.4 161 151
5 机械键盘 数码 59.9 83.9 198 168
6 电竞鼠标 数码 72.4 101.4 235 185
7 显示器 数码 84.9 118.9 272 202
8 充电宝 数码 97.4 136.4 309 219
9 平板电脑 数码 109.9 153.9 346 236
10 相机 数码 122.4 171.4 383 253
11 运动鞋 服饰 9.9 13.9 420 270
12 卫衣 服饰 22.4 31.4 457 287
13 牛仔裤 服饰 34.9 48.9 494 304
14 羽绒服 服饰 47.4 66.4 81 321
15 帆布包 服饰 59.9 83.9 118 338
16 棒球帽 服饰 72.4 101.4 155 355
17 袜子三双 服饰 84.9 118.9 192 372
18 围巾 服饰 97.4 136.4 229 389

表内观察

  • 价格循环:1~10 号是数码的 10 档价格,11 号运动鞋回到 9.9 起——价格按 i%10 循环、与分类解耦,每个分类都有「引流款 + 主力款」的价格层次;
  • 销量跳变:13 号牛仔裤 494 全表最高、14 号羽绒服 81 骤降——(i*37)%450 伪随机让相邻行差异大,按销量排序的瀑布流才有「意外感」;
  • 划线价收尾:所有划线价都以 .4/.9 结尾(13.9、31.4、48.9…)——price*1.4 四舍五入后的零售习惯价,比整数价更「电商」;
  • 其余 42 条:服饰后 2 条 + 食品/家居/美妆/图书各 10 条,名称见第三章分类清单——公式保证结构,名称保证真实

8.2 种子设计复盘:四组梯度是否达标

60 条数据设计完,回看四组梯度:

设计维度 期望 实际 验证方式
销量梯度 50~500 有区分度 50~494 全覆盖 ORDER BY sales DESC 前三名各不相同
价格区间 9.9 起逐档递增 9.9~122.4 十档 瀑布流卡片价格高低错落
库存梯度 模拟真实库存 100~899 伪随机 详情页「仅剩 X 件」有数据可显示
分页页数 3 页到底 60 ÷ 20 = 3 页 触底 2 次后显示「已全部加载」

销量梯度是分页排序的命根子ORDER BY sales DESC 依赖销量有区分度——如果 60 条销量全相同,分页边界就退化为「不可预测的插入序」,触底加载的页码验证失去意义。伪随机公式保证了这一点;同时 60 条恰好能被 20 整除,pageSize=20 时 3 页走完,页码验证特别清爽

8.3 initSeedData 批量插入:循环 + 事务

第二节的种子代码为了突出生成公式用了「裸循环 insert」;工程上应包事务——60 次写操作合并为 1 次提交:

static async initSeedData(context: common.Context): Promise<void> {
  const store = await ProductDao.getStore(context);
  const categories: string[] = ['数码', '服饰', '食品', '家居', '美妆', '图书'];
  const images: string[] = ['📱', '👟', '🍫', '🛋️', '💄', '📖', '🎧', '🧥', '🍪', '🕯️'];
  const tags: string[] = ['热卖', '新品', '清仓', '', '', ''];
  const names: string[] = [ /* 60 个商品名,见第三章清单 */ ];
  const now = Date.now();
  try {
    await store.beginTransaction();                    // 1. 开启事务
    for (let i = 0; i < 60; i++) {
      const price = 9.9 + (i % 10) * 12.5;
      const values: relationalStore.ValuesBucket = {
        name: names[i],
        price: price,
        original_price: Math.round(price * 1.4 * 10) / 10,
        sales: 50 + ((i * 37) % 450),
        stock: 100 + ((i * 17) % 800),                 // 库存伪随机 100~899
        image: images[i % images.length],
        tag: tags[i % tags.length],
        category: categories[Math.floor(i / 10)],
        created_time: now - i * 3600000,
      };
      await store.insert(ProductDao.TABLE, values);    // 2. 循环插入 60 条
    }
    await store.commit();                              // 3. 统一提交
    hilog.info(0x0000, 'ProductDao', '已填充 60 条商品种子数据');
  } catch (e) {
    await store.rollBack();                            // 4. 异常回滚
    hilog.error(0x0000, 'ProductDao', '种子注入失败: %{public}s', JSON.stringify(e));
  }
}

事务的三个收益

收益 说明
原子性 60 条要么全进、要么全不进——第 35 条失败不会留下「半批数据」
性能 60 次独立提交 ≈ 60 次磁盘刷写;合并为 1 次提交,量级越大差距越大
可恢复 catch 里 rollBack 兜底 + 错误日志,失败原因可查

注意beginTransaction 必须与 commit/rollBack 成对出现,任何路径都要收尾——事务编码的纪律。种子数据是一次性写入,用 try-catch 包住最稳妥。

8.4 注入后的分页效果预测:每页 20 条,3 页到底

60 条数据注入后,把 pageSize 从 10 调到 20,分页变为「3 页到底」——比 6 页更紧凑:

操作 已加载 页码 底部状态
进入页面 20 0(OFFSET 0) 上滑加载更多
触底一次 40 1(OFFSET 20) 上滑加载更多
触底两次 60 2(OFFSET 40) — 已全部加载 —

预测推演COUNT(*) = 60,第 3 次查询 LIMIT 20 OFFSET 40 返回 20 条,加载后「已加载 60 == 总数 60」→ finished = true——恰好 3 次数据库查询走完全程,比 pageSize=10 的 6 次交互少一半。若某页不足 20 条(如分类筛选),直接置 finished——「不足一页即完成」的判据在两种 pageSize 下都成立

pageSize 权衡:10 条/页 → 加载动画频繁、演示效果直观;20 条/页 → 交互少、加载快,真实 App 更常用。pageSize 是体验参数而非固定值——种子 60 条在 6 页和 3 页两种配置下都能完整验证分页。

8.5 验证 SQL:直查种子结果

页面跑起来前,先用 SQL 验证种子「按设计」落库:

SELECT COUNT(*) FROM goods;                                  -- 60:总量正确
SELECT category, COUNT(*) FROM goods GROUP BY category;      -- 6 类 × 10:分类均衡
SELECT MIN(price), MAX(price) FROM goods;                    -- 9.9 / 122.4:价格区间
SELECT MIN(sales), MAX(sales) FROM goods;                    -- 50 / 494:销量梯度
SELECT MIN(stock), MAX(stock) FROM goods;                    -- 100 / 899:库存梯度
SELECT name, sales FROM goods ORDER BY sales DESC LIMIT 3;   -- 前三名各不相同
SELECT COUNT(*) FROM goods WHERE tag = '热卖';               -- 10:标签覆盖

验证要点:总量 60、分类 6×10、价格/销量/库存区间在预期内、排序前三不重复——全部通过,种子才算合格。

8.6 FAQ

Q1:14 号羽绒服销量 81,比 13 号牛仔裤 494 差这么多,是 bug 吗?
A:不是 bug,是伪随机想要的效果——(i*37)%450 在 i=13 时余数骤降到 31。真实电商的销量就是忽高忽低,相邻商品销量差异大,按销量排序的瀑布流才不呆板

Q2:库存字段之前的文章没提,种子为什么要带?
A:8-1 的表结构里 stock 字段已建(默认 0),种子里一并填伪随机值,是为了让「仅剩 X 件」「库存紧张」这类 UI 有数据可显示——字段建了不用等于没建,种子把每个字段都激活

Q3:事务版和裸循环版性能差多少?
A:60 条差距不明显(都是毫秒级),但放大到 6000 条:裸循环 = 6000 次独立提交(每次都有磁盘刷写开销),事务版 = 1 次提交,差一个数量级。种子代码养成「循环即事务」的习惯,数据量增长不用返工。

Q4:pageSize 用 10 还是 20?
A:演示用 10(多几次触底动画,效果直观),真实产品用 20~30(交互少、加载快)。两种配置种子数据都不用改——60 ÷ 10 = 6 页、60 ÷ 20 = 3 页,分页逻辑天然适配

Q5:GROUP BY 每类正好 10 条,是巧合吗?
A:不是巧合——生成公式 categories[i/10] 保证每 10 条一个分类,这是设计保证而非数据巧合。种子数据的一大价值就是「可预期的验证结果」:SQL 查出的数字应与设计值完全一致。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐