2a0e4d1d
李曜臣
2026-07-09优化
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
|
# 2026-07-09 代码修改
本文档说明 **2026-07-09** 对美国版标签(Label)模块的接口补充:**标签列表**与**批量导入**(Bulk Add Labels)。
---
## 一、背景与目标
| 项目 | 说明 |
|------|------|
| 业务场景 | Web 管理端 **Bulk Add Labels** 页面:同一模板下多行录入标签基本字段 + 模板数据字段后一次性保存 |
| 原行为 | 前端 `LabelsList.tsx` 对每行循环调用 `POST /api/app/label` 单条创建,模板数据再单独 `updateLabelTemplate` |
| 新行为 | 新增 `POST /api/app/label/batch-create`,一次提交多行;可选合并写入 `fl_label_template_product_default` |
| 列表接口 | `GET /api/app/label?SkipCount=1&MaxResultCount=10` 为标签分页列表(按标签维度分页,非 label-product 笛卡尔行) |
---
## 二、标签列表
### 基本信息
| 项目 | 内容 |
|------|------|
| 方法 | `GET` |
| 路径 | `/api/app/label` |
| 鉴权 | Bearer Token |
| 示例 | `/api/app/label?SkipCount=1&MaxResultCount=10` |
### 分页参数
| 参数 | 类型 | 说明 |
|------|------|------|
| `SkipCount` | `int` | 页码,**从 1 起**(非 0) |
| `MaxResultCount` | `int` | 每页条数 |
### 筛选参数(Query)
| 参数 | 说明 |
|------|------|
| `PartnerId` | 按 Company(`fl_partner.Id`)下门店集合筛选 |
| `GroupId` | 按 Region(`fl_group.Id`)筛选:命中 `fl_label_region`、`fl_label_location` 或 `AppliedRegionType=ALL` |
| `LocationId` | 按门店(`location.Id`)筛选;传则优先于 `GroupId` |
| `ProductId` | 按产品筛选(一个产品可对应多个标签) |
| `Keyword` | 关键词 |
| `LabelCategoryId` | 标签分类 |
| `LabelTypeId` | 标签类型(可选) |
| `TemplateCode` | 模板编码 |
| `State` | 启用状态 |
### 出参结构
```json
{
"pageIndex": 1,
"pageSize": 10,
"totalCount": 100,
"totalPages": 10,
"items": [
{
"id": "LBL_xxx",
"labelName": "Turkey Breast",
"locationName": "Store A, Store B",
"locationIds": ["loc-id-1", "loc-id-2"],
"region": "Region A",
"regionIds": ["group-id-1"],
"groupIds": ["group-id-1"],
"appliedRegionType": "SPECIFIED",
"labelCategoryName": "Prep",
"productCategoryName": "Meat",
"products": "Turkey Breast",
"templateName": "3\"x6\" Nutrition Details",
"templateCode": "tpl_229mx5_mr05n3cq",
"labelTypeName": "Use By",
"state": true,
"lastEdited": "2026-07-09T10:00:00",
"hasError": false
}
]
}
```
### 列表 curl 示例
```bash
# 登录(管理端)
curl -X POST "http://192.168.31.88:19001/api/app/account/login" \
-H "Content-Type: application/json" \
-d '{"userName":"sandi@123.com","password":"123456"}'
# 标签列表第一页(10 条)
curl -G "http://192.168.31.88:19001/api/app/label" \
-H "Authorization: Bearer {token}" \
--data-urlencode "SkipCount=1" \
--data-urlencode "MaxResultCount=10"
```
---
## 三、标签批量导入
### 基本信息
| 项目 | 内容 |
|------|------|
| 方法 | `POST` |
| 路径 | `/api/app/label/batch-create` |
| 鉴权 | Bearer Token |
| 对应页面 | Web 端 **Bulk Add Labels**(`LabelsList.tsx` → `LabelBulkAddPage`) |
| 单次上限 | 500 行 |
### Body 顶层字段
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `templateCode` | `string` | 是 | 所有行共用的模板编码 |
| `items` | `array` | 是 | 导入行列表 |
| `saveTemplateProductDefaults` | `bool` | 否 | 是否合并写入模板 ProductDefaults,默认 `true` |
### `items[]` 每行字段
#### 标签基本字段(与单条 `POST /api/app/label` 一致)
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `labelName` | `string` | 是 | 标签名称 |
| `labelCategoryId` | `string` | 是 | 标签分类 Id |
| `labelTypeId` | `string` | 建议 | 标签类型 Id |
| `productIds` | `string[]` | 是 | 至少 1 个产品 |
| `locationIds` | `string[]` | 建议 | 适用门店(主字段) |
| `locationId` | `string` | 否 | 兼容单门店,与 `locationIds` 合并 |
| `appliedRegionType` | `string` | 否 | `ALL` / `SPECIFIED` |
| `regionIds` / `groupIds` | `string[]` | 条件 | `SPECIFIED` 时传 Region |
| `partnerId` / `partnerIds` | `string` / `string[]` | 否 | Company 范围 |
| `labelCode` | `string` | 否 | 不传则自动生成 `LBL_{guid}` |
| `labelInfoJson` | `object` | 否 | 标签级模板 JSON(通常走 ProductDefaults) |
| `state` | `bool` | 否 | 默认 `true` |
#### 模板数据字段(对应 Bulk Add 表格列)
| 字段 | 类型 | 说明 |
|------|------|------|
| `templateDefaultValues` | `object` | 已组装的 elementId → 值;**优先使用**,不再从下方字段推导 |
| `templateDataValues` | `object` | 文本类录入(如 Text、Barcode 手填值) |
| `templateDateOffsets` | `object` | 日期/时间/时长:`{ "el-id": { "unit": "Days", "value": "3" } }` |
| `nutritionByElementId` | `object` | 营养成分:`{ "el-nutrition": { "calories": "120" } }` |
**模板数据落库规则**
- `saveTemplateProductDefaults=true` 时:按 `productId + labelTypeId` 合并写入 `fl_label_template_product_default`(与前端 `saveTemplateDefaultsAfterLabels` 一致)
- 日期偏移存为 JSON 字符串:`{"unit":"Days","value":"3"}`
- Template 面板条码/二维码自动填充产品 `codeValue`;Label Name / Label Type 自动绑定行内 `labelName`、类型名
### 请求示例
```json
{
"templateCode": "tpl_229mx5_mr05n3cq",
"saveTemplateProductDefaults": true,
"items": [
{
"labelName": "Chicken Salad",
"labelCategoryId": "3a2228ca-42fa-5273-1105-3d8252ffdd99",
"labelTypeId": "3a2228e5-33c5-278e-a09f-47fd6e2ddbc7",
"locationIds": ["3a222878-b4c1-d27b-d437-93da14886350"],
"productIds": ["3a2228a8-6a51-6cc4-fee2-b77769766b33"],
"appliedRegionType": "SPECIFIED",
"regionIds": [],
"templateDataValues": {
"el-1782794752668-0gixa9y": "Fresh batch text",
"el-1782794771519-4360c0p": "123456789"
},
"templateDateOffsets": {
"el-1782794481721-sdf7mvy": { "unit": "Days", "value": "3" }
},
"state": true
},
{
"labelName": "Turkey Wrap",
"labelCategoryId": "3a2228ca-42fa-5273-1105-3d8252ffdd99",
"labelTypeId": "3a2228e6-3ed0-b056-a08b-ea8b1eea46a0",
"locationIds": ["3a222878-b4c1-d27b-d437-93da14886350"],
"productIds": ["3a2228a8-6a51-6cc4-fee2-b77769766b33"],
"templateDataValues": {
"el-1782794752668-0gixa9y": "Ready to serve"
},
"templateDateOffsets": {
"el-1782794481721-sdf7mvy": { "unit": "Days", "value": "5" }
}
}
]
}
```
### 响应示例
```json
{
"successCount": 2,
"failCount": 0,
"successItems": [
{ "rowNumber": 1, "labelCode": "LBL_abc...", "labelName": "Chicken Salad" },
{ "rowNumber": 2, "labelCode": "LBL_def...", "labelName": "Turkey Wrap" }
],
"errors": []
}
```
**部分成功示例**(第 2 行校验失败、第 1 行仍创建):
```json
{
"successCount": 1,
"failCount": 1,
"successItems": [
{ "rowNumber": 1, "labelCode": "LBL_abc...", "labelName": "Chicken Salad" }
],
"errors": [
{ "rowNumber": 2, "labelName": "", "message": "标签名称不能为空" }
]
}
```
### 批量导入 curl 示例
```bash
curl -X POST "http://192.168.31.88:19001/api/app/label/batch-create" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"templateCode": "tpl_229mx5_mr05n3cq",
"saveTemplateProductDefaults": true,
"items": [
{
"labelName": "Batch Test",
"labelCategoryId": "3a2228ca-42fa-5273-1105-3d8252ffdd99",
"labelTypeId": "3a2228e5-33c5-278e-a09f-47fd6e2ddbc7",
"locationIds": ["3a222878-b4c1-d27b-d437-93da14886350"],
"productIds": ["3a2228a8-6a51-6cc4-fee2-b77769766b33"],
"templateDataValues": { "el-1782794752668-0gixa9y": "Fresh" },
"templateDateOffsets": { "el-1782794481721-sdf7mvy": { "unit": "Days", "value": "3" } }
}
]
}'
```
---
## 四、与单条创建对比
| 能力 | `POST /api/app/label` | `POST /api/app/label/batch-create` |
|------|----------------------|-----------------------------------|
| 行数 | 1 条 | 最多 500 条 |
| 模板编码 | 每 Body 自带 | 顶层 `templateCode` 共用 |
| 模板数据 | 仅 `labelInfoJson` | 支持结构化模板字段 + ProductDefaults 合并 |
| 失败处理 | 整请求失败 | 行级错误收集,支持部分成功 |
| 返回值 | 标签详情 DTO | `successCount` / `failCount` / `errors` / `successItems` |
---
## 五、代码改动摘要
| 文件 | 改动 |
|------|------|
| `ILabelAppService.cs` | 新增 `BatchCreateAsync` |
| `LabelAppService.cs` | 实现批量导入;逐行复用 `CreateAsync`;可选合并模板 ProductDefaults |
| `LabelBatchCreateInputVo.cs` | 批量入参 DTO |
| `LabelBatchCreateItemInputVo.cs` | 单行(基本字段 + 模板数据字段) |
| `LabelBatchDateOffsetInputVo.cs` | 日期偏移单位+数值 |
| `LabelBatchCreateResultDto.cs` | 批量结果 DTO |
| `LabelBatchTemplateDefaultsHelper.cs` | 组装/合并模板 ProductDefaults |
---
## 六、部署与联调注意
1. `dotnet build` 编译 `FoodLabeling.Application` 与 `Yi.Abp.Web` 后**重启 API 进程**(未重启时 `batch-create` 会返回 405)
2. 管理端登录:`POST /api/app/account/login`(非 `/api/oauth/Login`)
3. 本地 Base URL 以 `appsettings.json` 中 `App:SelfUrl` 为准(当前示例为 `http://192.168.31.88:19001`)
4. 前端 `LabelsList.tsx` 的 `submit()` 仍循环调单条 `createLabel`;对接批量接口时需改为调用 `batch-create`
---
## 七、相关文档
- 标签多门店 `locationIds`:`项目相关文档/6-23代码优化.md`
- 模板三维 scope:`项目相关文档/6-4代码优化.md`
|