## 概述 美国版后端采用 ABP 动态接口(ConventionalControllers),宿主统一前缀为 `api/app`。 Swagger 地址: - `http://localhost:19001/swagger` 说明: - 接口最终 URL 以 Swagger 展示为准(可在 Swagger 里搜索 `LabelCategory / LabelType / LabelMultipleOption / LabelTemplate / Label / UsAppLabeling`)。 - 本模块后端接口以各 AppService 的方法签名自动暴露。 - 返回分页统一包含 `PageIndex / PageSize / TotalCount / TotalPages / Items`。 --- ## Swagger 中如何找到 1. 启动后端宿主(`Yi.Abp.Web`),端口 `19001`。 2. 打开 `http://localhost:19001/swagger`。 3. 在接口分组里搜索以下关键词之一: - `label-category` - `label-type` - `label-multiple-option` - `label-template` - `label` - `us-app-labeling`(App 端 Labeling 四级树) --- ## 接口 1:Label Categories(标签分类) ### 1.1 分页列表 方法:`GET /api/app/label-category` 入参(`LabelCategoryGetListInputVo`,查询参数): - `skipCount`(int) - `maxResultCount`(int) - `sorting`(string,可选) - `keyword`(string,可选) - `state`(boolean,可选) 示例(查询参数): ```json { "skipCount": 0, "maxResultCount": 10, "keyword": "Prep", "state": true } ``` ### 1.2 详情 方法:`GET /api/app/label-category/{id}` 入参: - `id`:分类 Id(字符串) ### 1.3 新增 方法:`POST /api/app/label-category` 入参(Body:`LabelCategoryCreateInputVo`): ```json { "categoryCode": "CAT_PREP", "categoryName": "Prep", "categoryPhotoUrl": "https://cdn.example.com/cat-prep.png", "state": true, "orderNum": 1 } ``` ### 1.4 编辑 方法:`PUT /api/app/label-category/{id}` 入参(Body:`LabelCategoryUpdateInputVo`,字段同创建): ```json { "categoryCode": "CAT_PREP", "categoryName": "Prep", "categoryPhotoUrl": null, "state": true, "orderNum": 2 } ``` ### 1.5 删除(逻辑删除) 方法:`DELETE /api/app/label-category/{id}` 入参: - `id`:分类 Id(字符串) 删除校验: - 若该分类已被 `fl_label` 引用,则抛出友好错误,禁止删除。 --- ## 接口 2:Label Types(标签类型) ### 2.1 分页列表 方法:`GET /api/app/label-type` 入参(`LabelTypeGetListInputVo`,查询参数): ```json { "skipCount": 0, "maxResultCount": 10, "keyword": "Defrost", "state": true } ``` ### 2.2 详情 方法:`GET /api/app/label-type/{id}` 入参: - `id`:类型 Id(字符串) ### 2.3 新增 方法:`POST /api/app/label-type` 入参(Body:`LabelTypeCreateInputVo`): ```json { "typeCode": "TYPE_DEFROST", "typeName": "Defrost", "state": true, "orderNum": 1 } ``` ### 2.4 编辑 方法:`PUT /api/app/label-type/{id}` 入参(Body:`LabelTypeUpdateInputVo`,字段同创建): ```json { "typeCode": "TYPE_DEFROST", "typeName": "Defrost", "state": true, "orderNum": 2 } ``` ### 2.5 删除(逻辑删除) 方法:`DELETE /api/app/label-type/{id}` 删除校验: - 若该类型已被 `fl_label` 引用,则禁止删除。 --- ## 接口 3:Multiple Options(多选项字典) ### 3.1 分页列表 方法:`GET /api/app/label-multiple-option` 入参(`LabelMultipleOptionGetListInputVo`,查询参数): ```json { "skipCount": 0, "maxResultCount": 10, "keyword": "Allergens", "state": true } ``` ### 3.2 详情 方法:`GET /api/app/label-multiple-option/{id}` 入参: - `id`:多选项 Id(字符串) ### 3.3 新增 方法:`POST /api/app/label-multiple-option` 入参(Body:`LabelMultipleOptionCreateInputVo`): ```json { "optionCode": "OPT_ALLERGENS", "optionName": "Allergens", "optionValuesJson": ["Peanuts", "Dairy", "Gluten", "Soy"], "state": true, "orderNum": 1 } ``` ### 3.4 编辑 方法:`PUT /api/app/label-multiple-option/{id}` 入参(Body:`LabelMultipleOptionUpdateInputVo`,字段同创建): ```json { "optionCode": "OPT_ALLERGENS", "optionName": "Allergens", "optionValuesJson": ["Peanuts", "Dairy"], "state": true, "orderNum": 2 } ``` ### 3.5 删除(逻辑删除) 方法:`DELETE /api/app/label-multiple-option/{id}` --- ## 接口 4:Label Templates(标签模板) 说明: - 模板标识入参 `id` 使用 `fl_label_template.TemplateCode`。 - 创建/编辑的 Body 字段名对齐你前端 editor JSON(`id/name/appliedLocation/elements/config`)。 - 模板组件 `elements[]` 的 `elementName` 为必填(前端传值,后端校验为空会报错)。 - `templateProductDefaults[]` 用于模板内“产品 + 标签类型”绑定默认值(进入模板详情页后的绑定列表)。 - **新增模板时不处理默认值**;默认值仅在后续“产品关联/编辑模板”阶段维护。 ### 4.1 分页列表 方法:`GET /api/app/label-template` 入参(`LabelTemplateGetListInputVo`,查询参数): ```json { "skipCount": 0, "maxResultCount": 10, "keyword": "测试模板", "locationId": "11111111-1111-1111-1111-111111111111", "labelType": "PRICE", "state": true } ``` ### 4.2 详情 方法:`GET /api/app/label-template/{id}` 入参: - `id`:模板编码 `TemplateCode`(字符串) ### 4.3 新增模板 方法:`POST /api/app/label-template` 入参(Body:`LabelTemplateCreateInputVo`): ```json { "id": "TPL_TEST_001", "name": "测试模板-价格签(4x6)", "labelType": "PRICE", "unit": "inch", "width": 4, "height": 6, "appliedLocation": "ALL", "showRuler": true, "showGrid": true, "state": true, "elements": [ { "id": "el-fixed-title", "elementName": "标题文本", "type": "TEXT_STATIC", "x": 32, "y": 24, "width": 160, "height": 24, "rotation": "horizontal", "border": "none", "zIndex": 1, "orderNum": 1, "valueSourceType": "FIXED", "isRequiredInput": false, "config": { "text": "商品名", "fontFamily": "Arial", "fontSize": 14, "fontWeight": "bold", "textAlign": "left" } } ], "appliedLocationIds": [] } ``` 说明: - 当 `appliedLocation=SPECIFIED` 时,`appliedLocationIds` 必须至少选择一个门店。 - `elements[].elementName` 必填;为空或空白将返回友好错误:`组件名字不能为空`。 - 新增模板时即使传了 `templateProductDefaults`,后端也不会写入默认值数据。 ### 4.4 编辑模板 方法:`PUT /api/app/label-template/{id}` 入参: - Path:`id` 是当前模板编码(TemplateCode) - Body:字段同新增(`id/name/elements/...`) 示例(编辑:同样字段,appliedLocation 切到 SPECIFIED): ```json { "id": "TPL_TEST_001", "name": "测试模板-价格签(4x6) v2", "labelType": "PRICE", "unit": "inch", "width": 4, "height": 6, "appliedLocation": "SPECIFIED", "showRuler": true, "showGrid": true, "state": true, "elements": [ { "id": "el-price", "elementName": "价格文本", "type": "TEXT_PRICE", "x": 40, "y": 120, "width": 140, "height": 28, "rotation": "horizontal", "border": "none", "zIndex": 2, "orderNum": 2, "valueSourceType": "PRINT_INPUT", "inputKey": "price", "isRequiredInput": true, "config": { "text": "", "fontFamily": "Arial", "fontSize": 18, "fontWeight": "bold", "textAlign": "left" } } ], "appliedLocationIds": ["11111111-1111-1111-1111-111111111111"], "templateProductDefaults": [ { "productId": "3a20-xxxx", "labelTypeId": "3a20-yyyy", "defaultValues": { "el-fixed-title": "Chicken", "el-price": "2.00", "el-desc": "23" }, "orderNum": 1 } ] } ``` 版本: - `VersionNo` 会在编辑时自动 `+1`。 - `elements` 会按传入内容全量重建。 - 查询/预览返回的 `elements[]` 同样会带 `elementName` 字段。 - `templateProductDefaults` 在编辑接口中**仅当显式传入时**才会重建(同一模板先删后插)。 - 若编辑时不传 `templateProductDefaults`,后端会保留数据库中原有默认值,不做覆盖。 `templateProductDefaults` 结构说明: - 每一行需传 `productId` 与 `labelTypeId`。 - `defaultValues` 建议使用 `element.id => 默认文本` 结构;页面展示时可结合模板 `elements[].config.text` 作为列头与初始值。 ### 4.6 模板与产品默认值关联表(新增) 用于存储“模板-产品-标签类型”的默认值,推荐执行以下建表 SQL: ```sql CREATE TABLE `fl_label_template_product_default` ( `Id` varchar(36) NOT NULL COMMENT '主键', `TemplateId` varchar(36) NOT NULL COMMENT '模板Id(关联 fl_label_template.Id)', `ProductId` varchar(36) NOT NULL COMMENT '产品Id(关联 fl_product.Id)', `LabelTypeId` varchar(36) NOT NULL COMMENT '标签类型Id(关联 fl_label_type.Id)', `DefaultValuesJson` text NULL COMMENT '默认值JSON(如 elementId=>默认文本)', `OrderNum` int NOT NULL DEFAULT 1 COMMENT '排序', PRIMARY KEY (`Id`), KEY `idx_fl_ltpd_template` (`TemplateId`), KEY `idx_fl_ltpd_product` (`ProductId`), KEY `idx_fl_ltpd_label_type` (`LabelTypeId`), UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductId`, `LabelTypeId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='标签模板-产品默认值关联表'; ``` 若表已存在,可执行以下 SQL 增加唯一约束: ```sql ALTER TABLE `fl_label_template_product_default` ADD UNIQUE KEY `uk_fl_ltpd_template_product_label_type` (`TemplateId`, `ProductId`, `LabelTypeId`); ``` ### 4.5 删除(逻辑删除) 方法:`DELETE /api/app/label-template/{id}` 入参: - `id`:模板编码 `TemplateCode` 删除校验: - 若该模板已被 `fl_label` 引用,则禁止删除。 --- ## 接口 5:Labels(按产品展示多个标签) 说明: - 列表接口以“标签”为维度分页展示(同一个标签会绑定多个产品)。 - 列表支持按 `ProductId` 过滤:仅返回“绑定了该产品”的标签。 - 标签详情/编辑/删除的 `id` 使用 `fl_label.LabelCode`。 ### 5.1 分页列表(按产品) 方法:`GET /api/app/label` 入参(`LabelGetListInputVo`,查询参数): ```json { "skipCount": 0, "maxResultCount": 10, "sorting": "", "keyword": "早餐", "locationId": "11111111-1111-1111-1111-111111111111", "productId": "22222222-2222-2222-2222-222222222222", "labelCategoryId": "33333333-3333-3333-3333-333333333333", "labelTypeId": "44444444-4444-4444-4444-444444444444", "templateCode": "TPL_TEST_001", "state": true } ``` 列表出参要点(`LabelGetListOutputDto`): - `products`:同一个标签下绑定的产品名称,用 `,` 分割(例如:`Chicken,Sandwich`) - 其他字段与之前一致:`labelName/locationName/category/type/template/state/lastEdited...` ### 5.2 详情 方法:`GET /api/app/label/{id}` 入参: - `id`:标签编码 `LabelCode` 返回: - `productIds`:该标签绑定的产品Id 列表 ### 5.3 新增标签 方法:`POST /api/app/label` 入参(Body:`LabelCreateInputVo`): ```json { "labelCode": "LBL_TEST_001", "labelName": "早餐标签", "templateCode": "TPL_TEST_001", "locationId": "11111111-1111-1111-1111-111111111111", "labelCategoryId": "33333333-3333-3333-3333-333333333333", "labelTypeId": "44444444-4444-4444-4444-444444444444", "productIds": ["22222222-2222-2222-2222-222222222222"], "labelInfoJson": { "note": "测试标签1" }, "state": true } ``` 校验: - `productIds` 至少 1 个 - `templateCode/locationId/labelCategoryId/labelTypeId` 不能为空 ### 5.4 编辑标签 方法:`PUT /api/app/label/{id}` 入参: - Path:`id` 为当前标签编码 `LabelCode` - Body:字段同创建(`LabelUpdateInputVo`) ```json { "labelName": "早餐标签 v2", "templateCode": "TPL_TEST_001", "locationId": "11111111-1111-1111-1111-111111111111", "labelCategoryId": "33333333-3333-3333-3333-333333333333", "labelTypeId": "44444444-4444-4444-4444-444444444444", "productIds": ["22222222-2222-2222-2222-222222222222"], "labelInfoJson": { "note": "测试标签1 v2" }, "state": true } ``` 关联维护: - `fl_label_product` 会按新 `productIds` 重建。 ### 5.5 删除标签(逻辑删除) 方法:`DELETE /api/app/label/{id}` 入参: - `id`:标签编码 `LabelCode` 删除行为: - 逻辑删除 `fl_label` - 删除该标签对应的 `fl_label_product` 关联 --- ## 接口 6:Products(产品) 说明: - 产品表:`fl_product` - 删除为逻辑删除:`IsDeleted = true` ### 6.1 分页列表 方法:`GET /api/app/product` 入参(`ProductGetListInputVo`,查询参数): ```json { "skipCount": 0, "maxResultCount": 10, "sorting": "", "keyword": "Chicken", "state": true } ``` ### 6.2 详情 方法:`GET /api/app/product/{id}` 入参: - `id`:产品Id(`fl_product.Id`) ### 6.3 新增产品 方法:`POST /api/app/product` 入参(Body:`ProductCreateInputVo`): ```json { "productCode": "PRD_TEST_001", "productName": "Chicken", "categoryName": "Meat", "productImageUrl": "https://example.com/img.png", "state": true } ``` 校验: - `productCode/productName` 不能为空 - `productCode` 不能与未删除的数据重复 ### 6.4 编辑产品 方法:`PUT /api/app/product/{id}` 入参: - Path:`id` 为当前产品Id(`fl_product.Id`) - Body:字段同新增(`ProductUpdateInputVo`) ### 6.5 删除(逻辑删除) 方法:`DELETE /api/app/product/{id}` 入参: - `id`:产品Id --- ## 接口 7:Product-Location(门店-产品关联) 说明: - 关联表:`fl_location_product` - 关联按门店进行批量替换: - `Create`:在门店下新增未存在的 product 关联 - `Update`:替换该门店下全部关联(先删后建) - `Delete`:删除该门店下全部关联 ### 7.1 分页列表 方法:`GET /api/app/product-location` 入参(`ProductLocationGetListInputVo`,查询参数): ```json { "skipCount": 0, "maxResultCount": 10, "sorting": "", "locationId": "11111111-1111-1111-1111-111111111111", "productId": "22222222-2222-2222-2222-222222222222" } ``` ### 7.2 获取门店下全部产品 方法:`GET /api/app/product-location/{id}` 入参: - `id`:门店Id(`location.Id`,string 表示) 返回: - 门店Id + 该门店关联的产品列表 ### 7.3 新增/建立门店关联 方法:`POST /api/app/product-location` 入参(Body:`ProductLocationCreateInputVo`): ```json { "locationId": "11111111-1111-1111-1111-111111111111", "productIds": ["22222222-2222-2222-2222-222222222222"] } ``` 校验: - `locationId` 对应门店必须存在 - `productIds` 必须都存在于 `fl_product` 且未删除 ### 7.4 编辑/替换门店关联 方法:`PUT /api/app/product-location/{id}` 入参: - Path:`id` 为门店Id - Body:`ProductLocationUpdateInputVo` ```json { "productIds": ["22222222-2222-2222-2222-222222222222"] } ``` ### 7.5 删除门店关联(按门店删除全部) 方法:`DELETE /api/app/product-location/{id}` 入参: - `id`:门店Id --- ## 接口 8:App Labeling 四级列表(门店打标页) **场景**:美国版 UniApp「Labeling」页:左侧 **标签分类(Label Category)** → 主区域按 **产品分类(Product Category)** 折叠分组 → **产品(Product)** 卡片 → 点选后底部弹层展示 **标签种类(Label Type)**。 **实现**:`UsAppLabelingAppService.GetLabelingTreeAsync`,约定式 API 控制器名 **`us-app-labeling`**(与 `UsAppAuth` → `us-app-auth` 同规则)。 ### 8.1 获取四级嵌套树 #### HTTP - **方法**:`GET` - **路径**:`/api/app/us-app-labeling/labeling-tree`(若与 Swagger 不一致,**以 Swagger 为准**) - **鉴权**:需要登录(`Authorization: Bearer ...`);可使用 App 登录或 Web 账号 Token,需能通过 `[Authorize]`。当前用户可选门店列表见 **`/api/app/us-app-auth/my-locations`**(说明见 `美国版App登录接口说明.md`)。 #### 入参(Query:`UsAppLabelingTreeInputVo`) | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | `locationId` | string | 是 | 当前门店 Id(`location.Id`,与 `fl_location_product.LocationId`、`fl_label.LocationId` 一致) | | `keyword` | string | 否 | 模糊过滤:标签名、产品名、产品分类、**标签分类**名、标签类型名、`labelCode` 等(实现见服务内 `WhereIF`) | | `labelCategoryId` | string | 否 | 侧边栏只展示某一 **标签分类** 时传入;不传则返回当前门店下出现的全部标签分类节点 | #### 数据范围与表关联(便于联调对照) - **门店内商品**:`fl_location_product`(先有 `locationId` 下的 `productId` 集合)。 - **参与连接的表**:`fl_label_product`(标签-产品)、`fl_label`(`LocationId` 须为当前门店且未删除、启用)、`fl_product`、`fl_label_category`、`fl_label_type`、`fl_label_template`。 - **第二级「产品分类」**:来自 `fl_product.CategoryName`,trim 后为空则归并为显示名 **`无`**。 - **第四级去重**:同一产品在同一标签分类、同一门店下,多条 `fl_label` 若 **`labelCode` 相同**,只保留一条用于列表(预览/打印仍用返回的 `labelCode` 等业务字段)。 #### 出参(`List`) 若宿主对成功结果有统一包装,业务数组一般在 **`data`** 中;下列为 **解包后的数组项** 结构。 **L1 `UsAppLabelCategoryTreeNodeDto`(标签分类)** | 字段 | 类型 | 说明 | |------|------|------| | `id` | string | `fl_label_category.Id` | | `categoryName` | string | 分类名称 | | `categoryPhotoUrl` | string \| null | 分类图标/图 | | `orderNum` | number | 排序 | | `productCategories` | array | 第二级列表(见下表) | **L2 `UsAppProductCategoryNodeDto`(产品分类)** | 字段 | 类型 | 说明 | |------|------|------| | `categoryId` | string \| null | 产品分类Id;产品未归类或分类不存在时为空 | | `categoryPhotoUrl` | string \| null | 产品分类图片地址;产品未归类或分类不存在时为空 | | `name` | string | 产品分类显示名;空源数据为 **`无`** | | `itemCount` | number | 该分类下 **产品个数**(去重后的产品数) | | `products` | array | 第三级产品列表(见下表) | **L3 `UsAppLabelingProductNodeDto`(产品)** | 字段 | 类型 | 说明 | |------|------|------| | `productId` | string | `fl_product.Id` | | `productName` | string | 产品名称 | | `productCode` | string | 产品编码 | | `productImageUrl` | string \| null | 主图 | | `subtitle` | string | 卡片副标题:**有 `productCode` 则显示编码,否则「无」**(与原型「Basic」等独立文案不同,需另行扩展字段时再对齐) | | `labelTypeCount` | number | 第四级条数,可用于角标「N Types」 | | `labelTypes` | array | 第四级(见下表) | **L4 `UsAppLabelTypeNodeDto`(标签种类 / 可选项)** | 字段 | 类型 | 说明 | |------|------|------| | `labelTypeId` | string | `fl_label_type.Id` | | `typeName` | string | 类型名称(如 Defrost) | | `orderNum` | number | 排序 | | `labelCode` | string | 业务标签编码,后续预览、打印流程使用 | | `templateCode` | string \| null | 关联模板编码 | | `labelSizeText` | string \| null | 尺寸文案;`inch` 常用格式如 `2"x2"` | #### 错误与边界 - `locationId` 为空:返回友好错误 **「门店Id不能为空」**。 - 门店下无关联产品:返回 **空数组** `[]`。 - 有产品但无任何符合条件的标签关联:返回 **空数组** `[]`。 #### 请求示例 ```http GET /api/app/us-app-labeling/labeling-tree?locationId=11111111-1111-1111-1111-111111111111&labelCategoryId=a2696b9e-2277-11f1-b4c6-00163e0c7c4f&keyword=Chicken HTTP/1.1 Host: localhost:19001 Authorization: Bearer eyJhbGciOi... ``` **curl**(Token 取自登录响应的 `data.token` 整段,已含 `Bearer ` 前缀时直接放入 Header): ```bash curl -X GET "http://localhost:19001/api/app/us-app-labeling/labeling-tree?locationId=11111111-1111-1111-1111-111111111111" \ -H "Authorization: " ``` #### 响应结构示例(解包后) ```json [ { "id": "cat-prep-id", "categoryName": "Prep", "categoryPhotoUrl": "/picture/...", "orderNum": 1, "productCategories": [ { "categoryId": "pc-meat-id", "categoryPhotoUrl": "/picture/product-category/20260325123010_xxx.png", "name": "Meat", "itemCount": 1, "products": [ { "productId": "prod-chicken-id", "productName": "Chicken", "productCode": "CHK-001", "productImageUrl": "/picture/...", "subtitle": "CHK-001", "labelTypeCount": 3, "labelTypes": [ { "labelTypeId": "lt-defrost", "typeName": "Defrost", "orderNum": 1, "labelCode": "LBL_CHICKEN_DEFROST", "templateCode": "TPL_2X2", "labelSizeText": "2\"x2\"" } ] } ] } ] } ] ``` > 前端 Axios 若项目约定 **GET 使用 `data` 配置对象** 传参,请仍绑定到与上述 Query 同名的字段(`locationId`、`keyword`、`labelCategoryId`),与 URL Query 等价即可。 ### 8.2 App 打印预览(elements 渲染结构) **场景**:用户选择某个 Product + Label Type 进入「Label Preview」页面,需要把模板预览区域渲染出来。 后端根据 `labelCode` 读取模板(`fl_label_template` + `fl_label_template_element`),并将 AUTO_DB / PRINT_INPUT 的值渲染回每个 element 的 `config`,前端按 `elements` 自行绘制预览。 #### HTTP - **方法**:`POST` - **路径**:`/api/app/us-app-labeling/preview`(若与 Swagger 不一致,**以 Swagger 为准**) - **鉴权**:需要登录(`Authorization: Bearer ...`) #### 入参(Body:`UsAppLabelPreviewInputVo`) | 参数名(JSON) | 类型 | 必填 | 说明 | |---|---|---|---| | `locationId` | string | 是 | 门店Id(校验 `fl_label.LocationId` 必须一致) | | `labelCode` | string | 是 | 标签编码(`fl_label.LabelCode`) | | `productId` | string | 否 | 预览用产品Id;不传则默认取该标签绑定的第一个产品(用于 AUTO_DB 数据填充) | | `baseTime` | string | 否 | 业务基准时间(用于 DATE/TIME 元素计算;不传则用服务器当前时间) | | `printInputJson` | object | 否 | 打印输入(用于 PRINT_INPUT 元素),key 建议与模板元素 `inputKey` 对齐 | #### 出参(`UsAppLabelPreviewDto`) 除顶部信息外,核心是 `template`(供前端画布渲染): - `template`:`LabelTemplatePreviewDto` - `width` / `height` / `unit`:模板物理尺寸 - `elements[]`:元素数组(对齐前端 editor JSON:`id/elementName/type/x/y/width/height/rotation/border/zIndex/orderNum/config`) `elements[].config` 内常用字段(示例): - 文本类(如 `TEXT_PRODUCT` / `TEXT_STATIC` / `TEXT_PRICE`):`config.text` - 条码/二维码(`BARCODE` / `QRCODE`):`config.data` - 日期/时间(`DATE` / `TIME`):`config.format`(后端已计算并写回) #### 数据来源说明 - 模板头:`fl_label_template` - 模板元素:`fl_label_template_element`(按 `OrderNum` + `ZIndex` 排序) - 标签归属:`fl_label`(校验 `labelCode` 存在且 `LocationId == locationId`) #### 错误与边界 - `locationId` 为空:友好错误 **「门店Id不能为空」**。 - `labelCode` 为空:友好错误 **「labelCode不能为空」**。 - 标签不存在:友好错误 **「标签不存在」**。 - 模板不存在:友好错误 **「模板不存在」**。 - 标签不属于当前门店:友好错误 **「该标签不属于当前门店」**。 - 标签未绑定产品且未传 `productId`:友好错误 **「该标签未绑定产品,无法预览」**。 #### 请求示例 ```json { "locationId": "11111111-1111-1111-1111-111111111111", "labelCode": "LBL_CHICKEN_DEFROST", "productId": "22222222-2222-2222-2222-222222222222", "baseTime": "2026-03-26T10:30:00", "printInputJson": { "price": "12.99" } } ``` **curl:** ```bash curl -X POST "http://localhost:19001/api/app/us-app-labeling/preview" \ -H "Authorization: " \ -H "Content-Type: application/json" \ -d '{"locationId":"11111111-1111-1111-1111-111111111111","labelCode":"LBL_CHICKEN_DEFROST","productId":"22222222-2222-2222-2222-222222222222","baseTime":"2026-03-26T10:30:00","printInputJson":{"price":"12.99"}}' ``` --- ## 接口 9:App 打印(落库打印任务与明细) **场景**:移动端预览确认后点击 **Print**。后端负责把“本次打印”写入数据库,方便追溯/统计/重打。 ### 9.1 创建打印任务并写入明细 #### HTTP - **方法**:`POST` - **路径**:`/api/app/us-app-labeling/print`(若与 Swagger 不一致,**以 Swagger 为准**) - **鉴权**:需要登录(`Authorization: Bearer ...`) #### 入参(Body:`UsAppLabelPrintInputVo`) | 参数名(JSON) | 类型 | 必填 | 说明 | |---|---|---|---| | `locationId` | string | 是 | 门店Id(校验 `fl_label.LocationId` 必须一致) | | `labelCode` | string | 是 | 标签编码(`fl_label.LabelCode`) | | `productId` | string | 否 | 打印用产品Id;不传则默认取该标签绑定的第一个产品(用于模板解析) | | `printQuantity` | number | 否 | 打印份数;`<=0` 按 1 处理 | | `baseTime` | string | 否 | 业务基准时间(用于 DATE/TIME 元素计算) | | `printInputJson` | object | 否 | 打印输入(用于模板 PRINT_INPUT 元素),key 建议与模板元素 `inputKey` 对齐 | | `printerId` | string | 否 | 打印机Id(可选,用于追踪) | | `printerMac` | string | 否 | 打印机蓝牙 MAC(可选) | | `printerAddress` | string | 否 | 打印机地址(可选) | #### 数据落库说明 - **任务表**:`fl_label_print_task` - 插入 1 条任务记录(`locationId / labelCode / productId / labelTypeId / templateCode / printQuantity / baseTime / printer...` 等)。 - **明细表**:`fl_label_print_data` - 按 `printQuantity` 插入 N 条明细记录(`copyIndex = 1..N`)。 - `printInputJson`:保存本次打印的原始输入(JSON 字符串)。 - `renderDataJson`:保存本次解析后的模板预览结构(`LabelTemplatePreviewDto`,包含 resolved 后的 `elements[].config`),供追溯/重打使用。 > 模板解析的数据源来自 `fl_label_template` + `fl_label_template_element`,与预览接口一致。 #### 出参(`UsAppLabelPrintOutputDto`) | 字段 | 类型 | 说明 | |---|---|---| | `taskId` | string | 打印任务Id(用于后续查询/重打/统计) | | `printQuantity` | number | 实际写入的份数 | #### 错误与边界 - `locationId` 为空:友好错误 **「门店Id不能为空」**。 - `labelCode` 为空:友好错误 **「labelCode不能为空」**。 - 标签不存在/不可用:友好错误 **「标签不存在或不可用」**。 - 标签不属于当前门店:友好错误 **「该标签不属于当前门店」**。 - 标签未绑定产品且未传 `productId`:友好错误 **「该标签未绑定产品,无法预览」**(模板解析阶段抛出)。 #### 请求示例 ```json { "locationId": "11111111-1111-1111-1111-111111111111", "labelCode": "LBL_CHICKEN_DEFROST", "productId": "22222222-2222-2222-2222-222222222222", "printQuantity": 2, "baseTime": "2026-03-26T10:30:00", "printInputJson": { "price": "12.99" }, "printerMac": "AA:BB:CC:DD:EE:FF" } ``` **curl:** ```bash curl -X POST "http://localhost:19001/api/app/us-app-labeling/print" \ -H "Authorization: " \ -H "Content-Type: application/json" \ -d '{"locationId":"11111111-1111-1111-1111-111111111111","labelCode":"LBL_CHICKEN_DEFROST","productId":"22222222-2222-2222-2222-222222222222","printQuantity":2,"baseTime":"2026-03-26T10:30:00","printInputJson":{"price":"12.99"}}' ```