完善标签设计器/销售产品模板绑定与打印面单 UX,统一入库出库到仓扫码页布局,并补充库存绑定与相关文档配置。 Co-authored-by: Cursor <cursoragent@cursor.com>
627 lines
22 KiB
Markdown
627 lines
22 KiB
Markdown
# 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"
|
||
}
|
||
```
|