六单十五明细五种状态:鸿蒙订单模块种子数据与主从效果

实例:订单与明细(Order)|技术:双表种子注入、状态覆盖、总价回填
一、种子数据的设计目标
实例 9 的种子数据要验证「主从嵌套 + 状态流转」,设计目标:
- 6 个订单:数量适中,主从列表有内容但不拥挤;
- 15 条明细:每单 1~3 件商品,明细区有层次;
- 5 种状态全覆盖:待支付/已支付/已发货/已完成/已取消各至少 1 单——状态筛选条每个按钮都有数据;
- 总价回填:明细小计之和 = 订单总价(模拟下单时的计算逻辑)。
二、6 个订单种子数据
| # | 订单号 | 状态 | 收货人 | 电话 | 地址 | 时间偏移 |
|---|---|---|---|---|---|---|
| 1 | SO20250601001 | 0 待支付 | 张小帅 | 13911112222 | 北京市朝阳区建国路 88 号 | 6 天前 |
| 2 | SO20250602002 | 1 已支付 | 李美美 | 13922223333 | 上海市浦东新区张江高科 | 5 天前 |
| 3 | SO20250603003 | 2 已发货 | 王大壮 | 13933334444 | 广州市天河区体育西路 | 4 天前 |
| 4 | SO20250604004 | 3 已完成 | 赵甜甜 | 13944445555 | 深圳市南山区科技园 | 3 天前 |
| 5 | SO20250605005 | 3 已完成 | 刘一鸣 | 13955556666 | 杭州市西湖区文三路 | 2 天前 |
| 6 | SO20250606006 | 4 已取消 | 陈小雨 | 13966667777 | 成都市武侯区天府大道 | 1 天前 |
状态覆盖设计:待支付(1 单)、已支付(1 单)、已发货(1 单)、已完成(2 单)、已取消(1 单)——筛选条的 5 个状态按钮都有真实数据。每种状态至少一单是状态类种子数据的基本要求——否则「状态流转演示」找不到起始数据。
收货人叙事:张小帅、李美美、王大壮…每个名字配一个城市地址(北京/上海/广州/深圳/杭州/成都),像一份真实的电商订单簿。订单数据的地理多样性让演示有「全国发货」的代入感。
三、15 条明细种子数据
| # | 所属订单 | 商品 | 单价 | 数量 | 小计 |
|---|---|---|---|---|---|
| 1 | 1 | 无线蓝牙耳机 | 299 | 1 | 299 |
| 2 | 1 | 快充数据线 | 39 | 2 | 78 |
| 3 | 1 | 手机壳 | 25 | 1 | 25 |
| 4 | 2 | 机械键盘 | 459 | 1 | 459 |
| 5 | 2 | 电竞鼠标 | 199 | 1 | 199 |
| 6 | 2 | 鼠标垫 | 49 | 1 | 49 |
| 7 | 3 | 运动跑鞋 | 699 | 1 | 699 |
| 8 | 3 | 运动袜 | 29 | 3 | 87 |
| 9 | 4 | 坚果礼盒 | 159 | 2 | 318 |
| 10 | 4 | 进口巧克力 | 89 | 1 | 89 |
| 11 | 5 | 保温杯 | 129 | 1 | 129 |
| 12 | 5 | 充电宝 | 149 | 1 | 149 |
| 13 | 5 | 数据收纳包 | 45 | 1 | 45 |
| 14 | 6 | 折叠雨伞 | 59 | 1 | 59 |
| 15 | 6 | 帆布包 | 79 | 1 | 79 |
明细设计:每单 1~3 件,覆盖「单件订单」(订单 4 两件、订单 6 两件)与「多件订单」(订单 1 三件、订单 5 三件)两种形态。商品名都是真实商品(蓝牙耳机、机械键盘、跑鞋),明细区看起来像真实购物车。
总价验证(手推):
- 订单 1:299 + 78 + 25 = 402
- 订单 2:459 + 199 + 49 = 707
- 订单 3:699 + 87 = 786
- 订单 4:318 + 89 = 407
- 订单 5:129 + 149 + 45 = 323
- 订单 6:59 + 79 = 138
四、代码实现:三段式注入
种子注入分三段:插订单(先拿 id)→ 插明细(引用 id)→ 回填总价(SUM 计算):
// 1. 插入 6 个订单(total_amount 先填 0,稍后回填)
const orders: OrderSeed[] = [ /* 见第二节表格 */ ];
for (const o of orders) {
const orderValues: relationalStore.ValuesBucket = {
order_no: o.no, status: o.status, total_amount: 0,
customer: o.customer, phone: o.phone, address: o.address,
created_time: now - o.daysAgo * day,
};
await store.insert(OrderDao.TABLE, orderValues);
}
// 2. 插入 15 条明细(orderId 是表格里的 1~6,对应插入顺序)
const items: ItemSeed[] = [ /* 见第三节表格 */ ];
for (const it of items) {
const itemValues: relationalStore.ValuesBucket = {
order_id: it.orderId, product_name: it.productName,
price: it.price, quantity: it.quantity,
subtotal: it.price * it.quantity,
};
await store.insert(OrderDao.ITEM_TABLE, itemValues);
}
// 3. 回填订单总价:按订单分组 SUM 明细小计
const ordersResult = await store.querySql(`SELECT id FROM ${OrderDao.TABLE}`);
const ids: number[] = [];
while (ordersResult.goToNextRow()) {
ids.push(ordersResult.getLong(ordersResult.getColumnIndex('id')));
}
ordersResult.close();
for (const id of ids) {
const sumResult = await store.querySql(
`SELECT SUM(subtotal) AS total FROM ${OrderDao.ITEM_TABLE} WHERE order_id = ${id}`
);
let totalAmount = 0;
if (sumResult.goToNextRow()) {
totalAmount = sumResult.getDouble(sumResult.getColumnIndex('total'));
}
sumResult.close();
const values: relationalStore.ValuesBucket = { total_amount: totalAmount };
const pred = new relationalStore.RdbPredicates(OrderDao.TABLE);
pred.equalTo('id', id);
await store.update(values, pred);
}
第三段回填的巧妙之处:不手动算每个订单的总价,而是用 SQL 按订单分组 SUM 明细小计,再逐单 UPDATE——模拟了真实下单的「总价 = Σ明细」逻辑,同时保证种子数据的总价与明细严格自洽。种子数据用 SQL 反推关联值,比手填更可靠(手填容易算错)。
ItemSeed 的 orderId 语义:items 数组里的 orderId 是 1~6——它与订单插入顺序对应(第一个插入的订单 id=1)。依赖 AUTOINCREMENT 从 1 递增是可行的(新库首次插入),但不绝对保险;更稳的做法是用 ids[] 数组收集插入返回值再引用(实例 6 用过)。本实例用固定 1~6 简化,读者注意这个假设。
五、注入后的页面效果预测
| 区块 | 预期效果 |
|---|---|
| 状态统计条 | 全部 6 · 待支付 1 · 已支付 1 · 已发货 1 · 已完成 2 · 已取消 1 |
| 订单列表 | 6 张卡片,每张内嵌 1~3 行明细,总价分别为 402/707/786/407/323/138 |
| 状态标签 | 待支付橙 / 已支付蓝 / 已发货紫 / 已完成绿 / 已取消灰 |
| 操作按钮 | 订单 1:取消 + 支付;订单 2/3:推进按钮;订单 4/5/6:仅删除 |
演示脚本:指着订单 1「SO20250601001 待支付」→ 点「→ 已支付」→ 状态变蓝、统计条更新(待支付 0、已支付 2)→ 再点两次推进到「已发货」「已完成」→ 展示状态机流转。点「取消订单」把订单 6 已取消的流程再走一遍——五种状态的完整流转演示。
六、验证方法
- hilog 日志:搜
OrderDao,看到「已填充 6 单 15 条明细种子数据」; - 页面显示:6 张订单卡 + 15 行明细 + 状态统计条数字(1/1/1/2/1);
- 数据库直查:
SELECT COUNT(*) FROM orders; -- 6 SELECT COUNT(*) FROM order_item; -- 15 SELECT o.order_no, o.total_amount, SUM(i.subtotal) AS calc FROM orders o LEFT JOIN order_item i ON i.order_id = o.id GROUP BY o.id; -- total_amount 与 calc 每行相等(总价自洽验证) SELECT status, COUNT(*) FROM orders GROUP BY status; -- 0:1 1:1 2:1 3:2 4:1
七、FAQ
Q1:为什么订单 4 和 5 都是「已完成」?
A:两个已完成订单让「已完成」状态有 2 条数据——展示「同一状态多订单」的列表形态,也让筛选「已完成」时列表不至于只有一行。状态分布 1/1/1/2/1 比全 1 更有层次。
Q2:订单号 SO20250601001 是写死的吗?
A:是写死的(种子数据要可读可验证),真实下单用 SO${Date.now()} 动态生成(9-3 文章讲过)。种子用「可读的固定单号」便于演示和 SQL 查询对照。
Q3:总价回填为什么要查一遍再更新?
A:因为插入明细时订单的 total_amount 是 0(先填 0 占位)。回填用「SUM 明细 → UPDATE」保证总价与明细自洽——如果手填总价,可能填错或与明细不一致。让数据库算,不让手算。
Q4:可以演示「下单」吗(页面有下单功能吗)?
A:数据层 createOrder 已就绪,但页面未提供下单 UI(聚焦订单管理与状态流转)。读者可自行扩展:悬浮「下单」按钮 + 表单(收货人 + 商品行)+ createOrder + refresh。种子数据是下单结果的展示,扩展下单后能形成完整闭环。
Q5:明细的 subtotal 和订单 total_amount 是冗余的吗?
A:是,但目的不同——subtotal 是「单件商品小计」(明细行展示),total_amount 是「订单合计」(卡片展示)。两者都冗余(可从价格×数量推导),但高频展示场景冗余换取零计算。展示层的冗余与业务层的规范:总价由 SUM 回填保证一致性。
八、文章小结
本篇文章展示了实例 9 的种子数据:6 订单(5 状态全覆盖)+ 15 明细(1~3 件/单)+ SQL 回填总价。核心是「状态全覆盖」与「总价自洽」两个设计——状态覆盖让筛选条和流转演示有数据支撑,SQL 回填让主从数据严格一致。三段式注入(先主后从再回填)是「一对多种子」的标准流程。
下一篇(9-5)是实例 9 的收官文章,展示 OrderPage 全量代码与运行效果。
九、种子数据速查:六单十五明细全景图
正文(一~八节)已完整演示设计目标与三段式注入,本节把 6 单 15 明细整理成速查手册:订单维度、明细维度两张表 + 插入顺序 + 状态覆盖 + 效果预测 + 验证 SQL + FAQ,实现时对照本节即可。
9.1 订单维度速查(订单号/客户/状态/金额)
| 订单号 | 客户 | 状态 | 金额(回填后) | 明细数 |
|---|---|---|---|---|
| SO20250601001 | 张小帅 | 0 待支付 | 402 | 3 |
| SO20250602002 | 李美美 | 1 已支付 | 707 | 3 |
| SO20250603003 | 王大壮 | 2 已发货 | 786 | 2 |
| SO20250604004 | 赵甜甜 | 3 已完成 | 407 | 2 |
| SO20250605005 | 刘一鸣 | 3 已完成 | 323 | 3 |
| SO20250606006 | 陈小雨 | 4 已取消 | 138 | 2 |
金额列即「SUM(subtotal) 回填」后的 total_amount;明细数合计 3+3+2+2+3+2 = 15,与「6 单 15 明细」严格对上。
9.2 明细维度速查(15 行)
| 所属订单 | 商品 | 单价 | 数量 | 小计 |
|---|---|---|---|---|
| 1 | 无线蓝牙耳机 | 299 | 1 | 299 |
| 1 | 快充数据线 | 39 | 2 | 78 |
| 1 | 手机壳 | 25 | 1 | 25 |
| 2 | 机械键盘 | 459 | 1 | 459 |
| 2 | 电竞鼠标 | 199 | 1 | 199 |
| 2 | 鼠标垫 | 49 | 1 | 49 |
| 3 | 运动跑鞋 | 699 | 1 | 699 |
| 3 | 运动袜 | 29 | 3 | 87 |
| 4 | 坚果礼盒 | 159 | 2 | 318 |
| 4 | 进口巧克力 | 89 | 1 | 89 |
| 5 | 保温杯 | 129 | 1 | 129 |
| 5 | 充电宝 | 149 | 1 | 149 |
| 5 | 数据收纳包 | 45 | 1 | 45 |
| 6 | 折叠雨伞 | 59 | 1 | 59 |
| 6 | 帆布包 | 79 | 1 | 79 |
9.3 主从种子的插入顺序:先主后从、收集 id
主从(一对多)种子有一条铁律:先插主表、收集自增 id,再插从表、用收集到的 id 作外键。三段式完整流程:
// 段一:先插订单(主),insert 返回值即自增 id,收集进 orderIds
const orderIds: number[] = [];
for (const o of orderSeeds) {
const orderId = await store.insert(OrderDao.TABLE, toOrderBucket(o));
orderIds.push(orderId); // 收集 id,供明细引用
}
// 段二:再插明细(从),order_id 引用收集到的 id,而非写死 1~6
for (let i = 0; i < itemSeeds.length; i++) {
const it = itemSeeds[i];
const bucket: relationalStore.ValuesBucket = {
order_id: orderIds[it.orderIndex - 1], // 关键:用收集的 id
product_name: it.productName, price: it.price,
quantity: it.quantity, subtotal: it.price * it.quantity,
};
await store.insert(OrderDao.ITEM_TABLE, bucket);
}
// 段三:回填 total_amount(SUM 明细),见第四节
为什么收集 id 而不是写死 1~6:写死只在「全新空库首次插入」时成立;一旦库里有历史数据,自增 id 已前进,写死的 order_id 会指错订单甚至触发外键约束失败。收集 id 让种子在任何库状态下都自洽——这是主从种子的通用姿势。
9.4 五种状态覆盖一览
| 状态 | 值 | 覆盖订单 | 界面行为 |
|---|---|---|---|
| 待支付 | 0 | SO…001 | 取消 + 支付按钮(流转起点) |
| 已支付 | 1 | SO…002 | 推进「发货」按钮 |
| 已发货 | 2 | SO…003 | 推进「完成」按钮 |
| 已完成 | 3 | SO…004、SO…005 | 终态:仅删除 |
| 已取消 | 4 | SO…006 | 终态:仅删除 |
分布 1/1/1/2/1:筛选条 5 个按钮全有数据;流转演示从订单 1 一路推到已完成,订单 6 演示取消分支——五种状态的全路径都能走通。
9.5 注入后的主从列表效果预测
| 界面区块 | 注入后预期 |
|---|---|
| 状态统计条 | 全部 6 · 待支付 1 · 已支付 1 · 已发货 1 · 已完成 2 · 已取消 1 |
| 订单列表 | 6 张订单卡;订单 1 内嵌耳机/数据线/手机壳 3 行,订单 5 内嵌 3 行,其余 2 行——卡片高度随明细数自然错落 |
| 状态标签 | 待支付橙 / 已支付蓝 / 已发货紫 / 已完成绿 / 已取消灰 |
| 操作按钮 | 订单 1 取消+支付;订单 2/3 各一个推进按钮;订单 4/5/6 仅删除 |
9.6 验证 SQL:按订单统计明细数
-- 主从数量自洽:每单明细数应为 3/3/2/2/3/2,合计 15
SELECT o.order_no, o.total_amount,
COUNT(i.id) AS item_count, SUM(i.subtotal) AS calc
FROM orders o
LEFT JOIN order_item i ON i.order_id = o.id
GROUP BY o.id
ORDER BY o.id;
两条断言:① item_count 依次为 3/3/2/2/3/2;② total_amount 与 calc 每行相等(总价回填自洽)。用 LEFT JOIN 而非 INNER JOIN——无明细的订单会以 item_count=0 现身,孤儿订单一目了然。
9.7 种子 FAQ
Q1:种子数据能重复执行吗?
A:不能直接重复——order_no 有 UNIQUE 约束,重复插入抛约束异常。initSeedData 应先 DELETE FROM order_item; DELETE FROM orders; 清库再注入,或先 SELECT COUNT(*) FROM orders 判断已初始化则跳过(幂等种子)。
Q2:orderIds 收集的 id 顺序一定和订单数组一致吗?
A:是的——单线程按数组顺序逐条 insert,自增 id 严格递增,orderIds[i] 对应 orderSeeds[i]。只要不并发插入,顺序即下标。
Q3:LEFT JOIN 验证时 SUM 为什么不会重复计算?
A:一对多连接中,每个明细只属于一个订单,按订单分组后 SUM(subtotal) 是各明细小计之和,不会像「多对多」那样出现笛卡尔积翻倍。这也是主从模型比多对多简单的地方。
更多推荐


所有评论(0)