front/docs/面单标签模板与PDF生成方案.md
zzh c4979a1fb4 feat: 面单标签设计与仓内操作页优化,接通模板打印链路
完善标签设计器/销售产品模板绑定与打印面单 UX,统一入库出库到仓扫码页布局,并补充库存绑定与相关文档配置。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-18 17:49:18 +08:00

627 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# OMS 面单标签模板设计与 PDF 生成方案
> **版本**:v0.4(开工核对版)
> **日期**:2026-07-18
> **基线文档**:`docs/oms系统设计-050801.md`(§5.1 标签设计 / §5.2.20 `t_label_template` / §6 操作打印 / §9~10 订单 `labelUrl`)
### 已确认决策
| 项 | 结论 |
| --- | --- |
| 出 PDF 时机 | **异步**(Pending → Ready / Failed) |
| 产品「客户面单」 | **下拉选具体模板**(`LabelTemplateId`),去掉写死「系统面单/客户面单」枚举作为出单依据 |
| 渲染引擎 | **纯 .NET**:`HiprintJson → QuestPDF`;vg-print **只做设计** |
| 默认模板 | 产品未选时回落 **全局默认模板 Id**(配置项) |
| 模板归属(一期) | **仅运营/管理员**设计与维护(客户自助 → 二期) |
| 默认纸张(一期) | **100×150 mm**(模板可改;未填时用此默认) |
### 落地进度(相对本文)
| 模块 | 状态 | 说明 |
| --- | --- | --- |
| 标签设计列表 + 新建弹窗 + 懒加载设计器 | **已做** | `labelDesign`:list/get/save/delete;新建弹窗填名称/类型再进设计 |
| 模板 CRUD API | **已做** | `GET list` / `GET {id}` / `POST save` / `POST delete` |
| Hiprint→QuestPDF 渲染器 | **已做** | Parser/IR/QuestPDF + 样本单测 |
| 异步出 PDF + 订单状态字段 | **已做** | Pending/Ready/Failed;创单投递 `generate-label-pdf` |
| 替换 HTML stub / 批量&仓内打 PDF | **已做** | print / batch / 仓内返回 PDF `printUrl`;无模板时回落 HTML |
| `IsEnabled` / 纸张字段 / enable API | **一期可砍** | 用「有无模板」即可;纸张从 JSON panel 读 |
| 独立 `labelTemplate` 列表页 | **取消** | 已并入 `labelDesign`,勿再建 |
| 设计器字段字典注入 | **已做** | `field-dictionary` + 设计器左侧字段 |
| 销售产品绑 `LabelTemplateId` | **已做** | 下拉接模板列表 |
---
## 0. 一期开工范围(核对无遗漏后按此做)
### 0.1 必须做(闭环)
1. **导出真实 TemplateJson 样本** + 单位校准单测(pt/mm)。
2. **`ILabelPdfRenderer`**:Parser → IR → QuestPDF;一期元素:text / barcode / qrcode / longText / hline / vline / rect / image。
3. **`LabelPrintDataBuilder`**:订单 → PrintData(附录 B 字段)。
4. **订单字段**:`LabelUrl`(复用/确认)、`LabelTemplateId`、`LabelGenerateStatus`、`LabelGenerateError`;SQL 迁移。
5. **异步任务** `generateLabelPdf`(复用现有批量队列机制)。
6. **创单投递任务**;`POST /order/print`、`batch/print` 返回 PDF `printUrl`;Pending/Failed 前端提示。
7. **销售产品**:`LabelTemplateId` + 下拉接 `label-template/list`;废弃枚举出单逻辑。
8. **全局默认模板配置**(配置表或 appsettings 键均可,先简单)。
9. **设计器字段字典**:`GET field-dictionary` + vg-print 左侧可拖字段(至少单号/收件/重量常用组)。
10. **仓内 print-label**:返回 `printUrl`;模板下拉改真实列表(可选覆盖)。
### 0.2 一期明确不做(避免范围膨胀)
- Playwright / 浏览器渲染
- table / html 元素、页眉页脚高级规则
- 模板启停 API、预览图、客户自助设计
- PDF 合并下载、地址变更自动 stale(二期)
- 平台店铺 ShippingLabelUrl
### 0.3 建议实施顺序
```text
① JSON 样本 + Renderer 单测
② PrintDataBuilder + preview-pdf(管理端验收版式)
③ 订单字段迁移 + 异步 generateLabelPdf + 创单挂钩
④ 销售产品 LabelTemplateId 下拉
⑤ 设计器字段字典
⑥ order/print + batch/print + 仓内 printUrl
```
---
## 1. 背景与目标
### 1.1 要解决的问题
1. **标签样式不可配置**:订单创建后的「面单」目前是固定 HTML stub,无法按客户/产品定制版式。
2. **设计器与打印链路断开**:`vg-print` 已能设计并保存 `TemplateJson`,但创建订单、单票打印、批量打印均未消费该模板。
3. **批量打印需要稳定文件**:仓内/客服批量出单希望拿到 **PDF**(可下载、可静默打印、可归档),而不是临时拼 HTML。
4. **产品「客户面单」未绑模板**:字段名虽叫客户面单,下拉却是写死的「系统面单/客户面单」,与 `t_label_template` 无关。
### 1.2 目标(一期)
| 目标 | 说明 |
| --- | --- |
| 可设计 | vg-print 设计,拖拽绑定订单字段,保存 `TemplateJson` |
| 可保存 | 模板 CRUD(名称、类型;列表浏览/删除)— **已有** |
| 可绑定 | 销售产品「客户面单」改为 **模板下拉**(`LabelTemplateId`) |
| 可出 PDF | 纯 .NET 解析 `TemplateJson` + PrintData → 异步生成 PDF |
| 可批量 | 按 `labelUrl` 批量静默打 / 下载 |
### 1.3 非目标(一期不做)
- 平台店铺面单(Shopee 等)仍走平台 URL。
- 不在服务端跑 Playwright/Chromium。
- 不保证 hiprint 全部高级特性 100% 还原(见 §3.3 支持矩阵);一期覆盖面单常用元素。
- 不做模板审批流 / 模板市场 / 客户自助设计。
---
## 2. 现状结论(Gap)
```mermaid
flowchart LR
A[vg-print 设计器] -->|保存 TemplateJson| B[(t_label_template)]
C[销售产品 CustomerWaybill] -.->|仅枚举字符串| X[未关联模板]
D[创建订单 / 打单] -->|CreateOrderPrintFileAsync| E[极简 HTML]
F[仓内 print-label] -->|只写记录| G[(t_label_print_record)]
H[批量 print] --> E
B -.->|未消费| D
```
| 能力 | 现状 | 缺口 |
| --- | --- | --- |
| 设计器 | 列表+懒加载设计器 **已有**;新建弹窗 **已有** | 字段字典未注入 |
| 模板存储 | `t_label_template` CRUD **已有** | 纸张可读 panel;启停可砍 |
| 产品绑定 | `CustomerWaybill` 写死两枚举 | → `LabelTemplateId` |
| 渲染 | HTML stub | HiprintJson→PDF |
| 仓内打印 | 记日志 | 返回/打开 PDF |
| 批量 | 有框架 | 依赖真实 `labelUrl`(PDF) |
---
## 3. 总体方案
### 3.1 核心思路
把链路拆成四段,职责清晰:
| 阶段 | 做什么 | 产物 |
| --- | --- | --- |
| **① 设计** | vg-print 配置版式,左侧拖订单字段占位符 | `TemplateJson` |
| **② 绑定** | 销售产品「客户面单」下拉选模板 | `SalesProduct.LabelTemplateId` |
| **③ 灌数出 PDF** | 异步:.NET 解析 TemplateJson + PrintData → PDF | `Order.LabelUrl` |
| **④ 打印** | 单票 / 批量 / 仓内,统一打 `labelUrl` | 打印机 / 下载 |
### 3.2 架构(纯 .NET 渲染 + 客户端静默打)
```mermaid
sequenceDiagram
participant Designer as vg-print 设计器
participant API as OMS API (.NET)
participant DB as DB
participant Worker as 异步 Worker
participant FE as 订单/仓内前端
participant Hiprint as 本地 hiprint 客户端
Designer->>API: 保存 TemplateJson
API->>DB: t_label_template
Note over API,DB: 产品「客户面单」下拉存 LabelTemplateId
FE->>API: 创建订单
API->>DB: 落单 + Pending + 投递 generateLabelPdf
Worker->>DB: 读模板 JSON + 组 PrintData
Worker->>Worker: HiprintJsonRenderer → QuestPDF
Worker->>DB: 写 PDF + Ready/labelUrl
FE->>API: 打印(Ready)
API-->>FE: printUrl (PDF)
FE->>Hiprint: 静默打印 PDF
```
- **设计**:继续 vg-print(前端)。
- **出文件**:纯 .NET,**不**起浏览器。
- **送打印机**:前端 hiprint 客户端打已生成的 PDF(与现网批量打印一致)。
### 3.3 纯 .NET 如何「转化使用」vg-print 保存的 JSON(已确认)
#### 3.3.1 思路
vg-print / hiprint 的 `TemplateJson` 本质是 **声明式版式**:纸张尺寸 + 绝对定位元素列表(文本/条码/线/图/表…)。
我们不在服务端执行 JS,而是:
1. **反序列化** JSON → C# 模型(`HiprintTemplate` / `HiprintPanel` / `HiprintElement`)
2. **灌数**:按 `options.field` 从 `PrintData` 取值(无 field 则用 `title` 静态文案)
3. **绘制**:用 **QuestPDF**(或 PdfSharp)按 left/top/width/height 绝对定位画到页面
4. **条码/二维码**:`options.textType = barcode | qrcode` 时用 **ZXing.Net** 生成图片再嵌入
```text
TemplateJson (hiprint)
│
▼
HiprintTemplateParser ← 只认我们支持的元素子集
│
▼
Intermediate IR ← 统一:PageSize + List<DrawOp>
│
▼
QuestPdfEmitter ← 输出 PDF bytes
```
中间 IR 的好处:以后若换 PdfSharp,只换 Emitter;Parser 不动。
#### 3.3.2 JSON 结构(需解析的关键部分)
典型形态(示意):
```json
{
"panels": [
{
"index": 0,
"width": 100,
"height": 150,
"printElements": [
{
"printElementType": { "type": "text" },
"options": {
"left": 20, "top": 30, "width": 120, "height": 20,
"field": "serviceNo",
"title": "运单号",
"fontSize": 12,
"fontWeight": "bold",
"textAlign": "left",
"textType": "text"
}
},
{
"printElementType": { "type": "text" },
"options": {
"left": 20, "top": 60, "width": 160, "height": 40,
"field": "serviceNo",
"textType": "barcode"
}
}
]
}
]
}
```
坐标约定(实现时用真实样本校准一次):
| 项 | hiprint 常见单位 | .NET 处理 |
| --- | --- | --- |
| 纸张 width/height | **mm** | QuestPDF `PageSizes` / 自定义 mm |
| 元素 left/top/width/height | **pt**(1 inch = 72 pt) | 换算:`pt → mm = pt * 25.4 / 72` |
| fontSize | pt | 直接用 |
> 落地第一步:从设计器导出 1~2 份真实 JSON 做单元测试,校准单位(不同 vg-print 版本偶有差异)。
#### 3.3.3 元素支持矩阵(一期 / 二期)
| 元素 type / textType | 一期 | 实现要点 |
| --- | --- | --- |
| text(普通文本) | ✅ | 灌 field;字体/对齐/颜色/粗体 |
| text + barcode | ✅ | ZXing Code128(可配置) |
| text + qrcode | ✅ | ZXing QR |
| longText | ✅ | 多行换行、超出裁剪或缩小 |
| hline / vline | ✅ | 线宽、颜色 |
| rect | ✅ | 边框/填充(简化) |
| image | ✅ | 静态图 URL/相对路径;或 field 指向图片地址 |
| table | 二期 | 按 columns + items[] 画表;一期可用「多行 text」凑合 |
| html | ❌ 一期跳过 | 设计规范禁止用自定义 HTML 做面单关键信息 |
| 页眉页脚/页码高级规则 | 二期 | 面单多为单页,一期可忽略 |
**设计规范(避免踩坑)**:面单模板只用支持矩阵内的控件;保存模板时可做服务端校验,遇到不支持类型给出警告。
#### 3.3.4 关键代码落点(建议)
| 组件 | 位置(建议) |
| --- | --- |
| `HiprintTemplateDocument` 等模型 | `Oms.Application/Labeling/Hiprint/` |
| `HiprintTemplateParser` | 同上 |
| `ILabelPdfRenderer` / `QuestPdfLabelRenderer` | 同上 |
| `LabelPrintDataBuilder` | 订单 → PrintData 字典 |
| `GenerateLabelPdfJobHandler` | 异步队列 Worker |
| NuGet | `QuestPDF`、`ZXing.Net`(及 ImageSharp 如需) |
接口形态:
```csharp
public interface ILabelPdfRenderer
{
Task<byte[]> RenderAsync(string templateJson, IReadOnlyDictionary<string, object?> printData, CancellationToken ct = default);
}
```
#### 3.3.5 与「设计预览」的对齐策略
| 手段 | 说明 |
| --- | --- |
| 设计器预览 | 仍用 vg-print 前端预览(设计时) |
| 服务端预览 API | `POST .../preview-pdf` 走同一套 `ILabelPdfRenderer`,给产品配置/验收用 |
| 回归用例 | 固定 TemplateJson + PrintData → 对比 PDF 哈希或关键区域截图 |
预期:**常用面单元素**视觉接近;像素级与浏览器 hiprint 不完全一致(字体 hinting 等),以「可扫码、信息齐全、布局不错位」为验收标准。
#### 3.3.6 为何仍划算(相对 Playwright)
- 无 Chromium 运维,部署与现有 API 一致。
- 异步高峰更可控(线程池而非浏览器池)。
- 设计器继续复用,**不是**推倒重做;成本在「解析器 + 支持矩阵」,可控且可测。
### 3.4 异步出 PDF(已确认)
创单:落单 → 解析产品 `LabelTemplateId`(空则全局默认)→ `LabelGenerateStatus=Pending` → 投递 `generateLabelPdf` → 立即返回。
Worker:`PrintData` → `ILabelPdfRenderer` → 落盘 → `Ready` / `Failed`。
打印:Ready 用 `labelUrl`;Pending 提示生成中或短等补生成;Failed 可「重新生成」。
### 3.5 产品「客户面单」字段改造(已确认)
**现状**:`customerWaybillOptions` 写死:
```ts
{ label: '系统面单', value: '系统面单' },
{ label: '客户面单', value: '客户面单' },
```
与模板库无关,不能表达「用哪张版式」。
**改造**:
| 项 | 做法 |
| --- | --- |
| UI | 「客户面单」下拉改为拉 `GET /channel/label-template/list`(启用中的模板),展示 `Name` |
| 存储 | 新增/使用 `LabelTemplateId`(long,可空) |
| 旧字段 `CustomerWaybill` | 迁移期可废弃写入,或仅作展示兼容;**出 PDF 只认 LabelTemplateId** |
| 未选模板 | 使用系统配置的 **全局默认模板 Id**;若默认也没有 → 异步任务 Failed,并提示去配置 |
仓内打印页的模板下拉同理:不要写死 `lastMile/customer/...` 类型枚举当「模板」,应拉真实模板列表(类型可作筛选)。
平台店铺面单 URL 仍独立,不进该下拉。
## 4. 领域模型
### 4.1 模板 `t_label_template`
| 字段 | 一期 | 说明 |
| --- | --- | --- |
| Id / Name / TemplateType / TemplateJson | ✅ 已有 | CRUD 已通 |
| PaperWidthMm / PaperHeightMm | 可选 | **优先从 TemplateJson.panels[0] 读**;缺省 100×150 |
| IsEnabled / PreviewImageUrl / Remark | 二期 | 一期不做启停 |
### 4.2 销售产品绑定
| 字段 | 说明 |
| --- | --- |
| LabelTemplateId | **主字段**:客户面单下拉选中的模板 Id |
| CustomerWaybill | 旧枚举;迁移后停止作为出单依据(可留列不清) |
解析:`产品.LabelTemplateId` → 否则 → `全局默认模板Id` → 否则 Failed。
### 4.3 订单侧产物
| 字段 | 说明 |
| --- | --- |
| `LabelUrl` | PDF 路径(替换 HTML stub) |
| `LabelTemplateId` | 实际模板 Id |
| `LabelGenerateStatus` | `Pending` / `Ready` / `Failed` |
| `LabelGenerateError` | 失败原因 |
| PrintData 快照 | 建议一期就落 json 列或扩展表,便于重打 |
### 4.4 仓内打印记录
- `print-label/print` 返回 `printUrl`;可选写 `PrintFileUrl` 到记录表。
### 设计页交互(已落地)
- 默认列表;新建弹窗(名称+类型)→ 懒加载设计器;返回卸载。
- API:list / get / save / delete。
- **待补**:字段字典注入。
---
## 5. 字段字典(设计器左侧可拖字段)
设计器不应让业务人员手写字段名。一期提供 **标准 PrintData Schema**,前端注入 vg-print 的元素/字段面板。
### 5.1 命名约定
- 全部 **camelCase**,与前端/hiprint `print(data)` 一致。
- 地址类同时提供分行字段与 `recipientAddressFull`。
### 5.2 字段分组(初稿)
#### A. 单号
| 字段 | 说明 |
| --- | --- |
| serviceNo | 运单号 / 服务商单号 |
| clientNo | 客户单号 |
| trackNo | 跟踪号 |
| otherNo | 其他单号(若有) |
| requestId | 请求号 |
#### B. 产品 / 渠道 / 仓库
| 字段 | 说明 |
| --- | --- |
| productCode | 销售产品编码 |
| salesProductName | 销售产品名称 |
| channelCode | 渠道编码 |
| channelName | 渠道名称 |
| warehouseName | 仓库名称 |
| customerCode / customerName | 客户(管理端场景) |
#### C. 收件人
| 字段 | 说明 |
| --- | --- |
| recipientName | 姓名 |
| recipientCompany | 公司 |
| recipientPhone | 电话 |
| recipientEmail | 邮箱 |
| recipientZip | 邮编 |
| recipientCountry / recipientCountryName | 国家 |
| recipientProvince / City / Street1/2/3 | 省市区街道 |
| recipientAddressFull | 拼接地址 |
| recipientTaxNo / vatNo / eori | 税号类 |
#### D. 发件人
对称字段:`senderName`、`senderCompany`、`senderPhone`、`senderAddressFull` …
#### E. 重量尺寸
| 字段 | 说明 |
| --- | --- |
| actualWeight | 实重 |
| volumetricWeight | 体积重 |
| chargeableWeight | 计费重 |
| length / width / height | 长宽高 |
| weightUnit | 默认 KG |
| dimUnit | 默认 CM |
#### F. 货品明细(表格 — 二期)
```json
"items": [
{ "skuCode": "", "skuName": "", "qty": 1, "weight": 0.1, "declareNameEn": "", "declareValue": 0 }
]
```
一期可用多个 text 字段代替。
#### G. 系统
| 字段 | 说明 |
| --- | --- |
| printTime | 出 PDF 时间 |
| operatorName | 操作人(仓内打印时) |
### 5.3 设计器待补
1. `GET /channel/label-template/field-dictionary`
2. 注入 vg-print provider / 可拖字段分组
3. 预览可用 Mock PrintData
(列表/新建弹窗/保存/删除已完成,勿重复建设独立列表页。)
---
## 6. 关键业务流程
### 6.1 设计与发布
1. 运营「标签设计」→ 新建弹窗 / 设计 → 拖字段 → 预览 → 保存。
2. 销售产品「客户面单」下拉选模板(`LabelTemplateId`)。
3. 配置全局默认模板(产品未选时回落)。
### 6.2 创建订单 → 生成 PDF
| 触发 | 建议 |
| --- | --- |
| 订单创建成功 | **主路径**:异步 `generateLabelPdf` |
| 预报/生成包裹 | 地址重量补全后可 **重生** |
| 地址变更 | 二期:`labelStale` |
```
resolveTemplate(product) → buildPrintData(order) → renderPdf
→ /uploads/prints/orders/xxx.pdf → labelUrl + Ready
```
### 6.3 单票 / 批量打印
- 单票 / 批量:返回 PDF `printUrl`;Pending 提示生成中;Failed 可重生。
- 合并下载 → 二期。
### 6.4 仓内「打印标签」
1. lookup(已有)→ 确认打印。
2. 优先订单 `labelUrl`;无则现场生成。
3. 返回 `printUrl`;模板下拉接真实列表。
---
## 7. API 设计(增量)
### 7.1 模板
| 方法 | 路径 | 一期 |
| --- | --- | --- |
| POST save | `/channel/label-template/save` | ✅ |
| GET list | `/channel/label-template/list` | ✅ |
| GET detail | `/channel/label-template/{id}` | ✅ |
| POST delete | `/channel/label-template/delete` | ✅ |
| GET field-dictionary | `/channel/label-template/field-dictionary` | 待做 |
| POST preview-pdf | `/channel/label-template/preview-pdf` | 待做 |
| POST enable | `/channel/label-template/enable` | 砍掉(二期) |
### 7.2 销售产品
- Save/Detail 增加 `labelTemplateId`;下拉接 list。
### 7.3 订单 / 打印
| 方法 | 说明 |
| --- | --- |
| 创单内部 | 投递 `generateLabelPdf`(非同步堵接口) |
| `POST /order/print` | PDF `printUrl` |
| `POST /order/batch/print` | 每单 PDF url |
| `POST /order/label/regenerate` | 强制重生 |
### 7.4 仓内
| 方法 | 说明 |
| --- | --- |
| `POST /operation/print-label/print` | 返回 `printUrl` |
### 7.5 渲染(进程内)
```csharp
Task<byte[]> RenderAsync(string templateJson, IReadOnlyDictionary<string, object?> printData, CancellationToken ct = default);
```
---
## 8. 前端改造清单
| 页面/模块 | 状态 |
| --- | --- |
| `channel/labelDesign` | ✅ 列表+弹窗+懒加载;⏳ 字段字典 |
| `channel/labelTemplate` 独立页 | ❌ 取消 |
| `channel/salesProducts` | ⏳ 模板下拉 |
| `OrderPrintModal` / 我的订单批量 | ⏳ PDF + Pending 提示 |
| `operation/printLabel` | ⏳ printUrl + 真实模板下拉 |
| 权限文案 | 随菜单既有资源即可 |
---
## 9. 分阶段落地
### 一期(按 §0.3 顺序)
见文首 **§0**。
### 二期
1. table 元素、启停、预览图。
2. 地址变更 stale / 重生;PDF 合并。
3. 客户自助设计;保存时不支持元素强校验。
### 三期(可选)
- 仅当 QuestPDF 还原成本过高时再评估 Playwright 兜底。
---
## 10. 风险与对策
| 风险 | 对策 |
| --- | --- |
| JSON 与 PDF 视觉偏差 | 支持矩阵;preview-pdf;pt/mm 单测 |
| 中文字体 | 服务端嵌入 Noto Sans SC / 雅黑 |
| 创单后立刻打印 | Pending 短等或提示稍后 |
| 旧枚举数据 | 出单只认 LabelTemplateId |
| 批量高峰 | 异步队列限流 |
---
## 11. 验收标准(一期)
1. 设计器模板(文本+条码+线框)经 .NET 出可扫码 PDF。
2. 销售产品下拉为真实模板,存 `LabelTemplateId`。
3. 创单异步后 Ready + `labelUrl` 可打开。
4. 单票/批量/仓内能打该 PDF;Pending 有提示。
5. 无产品模板且无全局默认 → Failed + 明确提示。
---
## 12. 决策状态
| # | 项 | 状态 |
| --- | --- | --- |
| 1 | 渲染:纯 .NET + Hiprint JSON | **已确认** |
| 2 | 出 PDF:异步 | **已确认** |
| 3 | 产品:模板下拉 | **已确认** |
| 4 | 模板归属 | **一期:仅运营**(已拍板默认) |
| 5 | 默认纸张 | **一期:100×150 mm**(已拍板默认) |
**结论:方案可开工。** 无阻塞性遗漏;下一刀从 §0.3 ①「真实 JSON 样本 + Renderer」开始。
---
## 附录 A. 与现有文档章节映射
| 本方案 | 设计总册 |
| --- | --- |
| 设计器 / TemplateJson | §5.1、§5.2.20、§5.3.12 |
| 仓内打印记录 | §6.1、§6.2.3、§6.3.3 |
| 订单 labelUrl / batch print | §4.4.1.8、§9.6.3、§10 |
| 批量异步 | `订单批量处理与异步队列方案0707.md` |
## 附录 B. PrintData 示例(节选)
```json
{
"serviceNo": "S202607180001",
"clientNo": "C-10086",
"trackNo": "YT1234567890",
"salesProductName": "专线-普货",
"warehouseName": "SZ-01",
"recipientName": "John Lee",
"recipientPhone": "+1-xxx",
"recipientCountryName": "United States",
"recipientAddressFull": "xxx Street, City, CA 90001",
"actualWeight": 1.25,
"length": 30,
"width": 20,
"height": 10,
"items": [
{ "skuCode": "SKU001", "skuName": "T-Shirt", "qty": 2 }
],
"printTime": "2026-07-18 14:00:00"
}
```