# 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`