标签模块接口对接说明.md 37.2 KB

概述

美国版后端采用 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,可选)

示例(查询参数):

{
  "skipCount": 0,
  "maxResultCount": 10,
  "keyword": "Prep",
  "state": true
}

1.1.1 字段约定:buttonAppearancecategoryPhotoUrl(JSON 字符串)

  • buttonAppearance:库中存 JSON 文本(如 ["TEXT","COLOR"]、仅图片 ["IMAGE"] 等);兼容历史单行 TEXT/COLOR/IMAGE(保存时规范为 ["TEXT"] 等)。未传或空白时后端默认 ["TEXT"]。非法值(非 JSON 且非上述三者)会返回友好错误。
  • categoryPhotoUrl:同样为 JSON 文本(如 ["Prep","#10B981"]);若传非 JSON 的纯文本(色值、/picture/... 等),后端会序列化为合法 JSON 字符串再存储。列表/详情/App 树原样返回字符串,由客户端解析。
  • 其它常用字段:displayTextavailabilityTypeALL/SPECIFIED)、locationIds(指定门店时必填),与产品类别接口语义一致(见 项目相关文档/产品模块Categories接口对接说明.md)。

1.2 详情

方法:GET /api/app/label-category/{id}

入参:

  • id:分类 Id(字符串)

1.3 新增

方法:POST /api/app/label-category

入参(Body:LabelCategoryCreateInputVo):

{
  "categoryCode": "CAT_PREP",
  "categoryName": "Prep",
  "displayText": "Prep",
  "buttonAppearance": "[\"TEXT\",\"COLOR\"]",
  "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]",
  "availabilityType": "ALL",
  "locationIds": [],
  "state": true,
  "orderNum": 1
}

1.4 编辑

方法:PUT /api/app/label-category/{id}

入参(Body:LabelCategoryUpdateInputVo,字段同创建):

{
  "categoryCode": "CAT_PREP",
  "categoryName": "Prep",
  "displayText": "Prep",
  "buttonAppearance": "[\"TEXT\",\"COLOR\"]",
  "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]",
  "availabilityType": "ALL",
  "locationIds": [],
  "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,查询参数):

{
  "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):

{
  "typeCode": "TYPE_DEFROST",
  "typeName": "Defrost",
  "state": true,
  "orderNum": 1
}

2.4 编辑

方法:PUT /api/app/label-type/{id}

入参(Body:LabelTypeUpdateInputVo,字段同创建):

{
  "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,查询参数):

{
  "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):

{
  "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,字段同创建):

{
  "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,查询参数):

{
  "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):

{
  "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):

{
  "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 结构说明:

  • 每一行需传 productIdlabelTypeId
  • defaultValues 建议使用 element.id => 默认文本 结构;页面展示时可结合模板 elements[].config.text 作为列头与初始值。

4.6 模板与产品默认值关联表(新增)

用于存储“模板-产品-标签类型”的默认值,推荐执行以下建表 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 增加唯一约束:

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,查询参数):

{
  "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):

{
  "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
{
  "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,查询参数):

{
  "skipCount": 0,
  "maxResultCount": 10,
  "sorting": "",
  "keyword": "Chicken",
  "state": true
}

6.2 详情

方法:GET /api/app/product/{id}

入参:

  • id:产品Id(fl_product.Id

返回(ProductGetOutputDto,与实现一致的主要字段):

  • idproductCodeproductNamecategoryIdcategoryNameproductImageUrlstate
  • locationIdsstring[],该产品在 fl_location_product 中绑定的门店 Id(去重);无关联时为空数组

6.3 新增产品

方法:POST /api/app/product

入参(Body:ProductCreateInputVo):

{
  "productCode": "PRD_TEST_001",
  "productName": "Chicken",
  "categoryId": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f",
  "productImageUrl": "https://example.com/img.png",
  "state": true,
  "locationIds": [
    "11111111-1111-1111-1111-111111111111",
    "22222222-2222-2222-2222-222222222222"
  ]
}

字段说明: | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | productCode | string | 是 | 产品编码 | | productName | string | 是 | 产品名称 | | categoryId | string | null | 否 | 产品分类 Id(fl_product_category.id) | | productImageUrl | string | null | 否 | 主图 URL | | state | bool | 否 | 默认 true | | locationIds | string[] | 省略 | 否 | 可选。 有该字段时:在同一事务内按列表批量写入 fl_location_product每个门店 Id 一行,即「一产品一门店一条关联」)。请求体中省略该字段时:本接口不写门店关联,仍可通过 §7 Product-Location 维护。传空数组 [] 表示新建产品后不绑定任何门店。 |

校验:

  • productCode / productName 不能为空
  • productCode 不能与未删除的数据重复
  • 若传入 locationIds 且含非空项:每个 Id 须为合法 Guid,且对应门店存在于 Location 主数据且未删除;否则返回友好错误(如「门店Id格式不正确」「门店不存在」)

6.4 编辑产品

方法:PUT /api/app/product/{id}

入参:

  • Path:id 为当前产品Id(fl_product.Id
  • Body:字段同新增(ProductUpdateInputVo,继承 ProductCreateInputVo

locationIds 行为(与新增不同,请注意):

  • 请求体中省略 locationIds 属性:不修改 fl_location_product(仅更新 fl_product 主表字段;兼容原「先 PUT 产品再调 §7 同步门店」的调用方式)。
  • 请求体中包含 locationIds 属性(含空数组 []):对该产品的门店关联做 整表替换——先删除本产品下全部 fl_location_product 行,再按列表逐条插入;[] 表示解除该产品与所有门店的关联。

其它校验同 §6.3(含门店存在性校验,当 locationIds 含非空项时)。

6.5 删除(逻辑删除)

方法:DELETE /api/app/product/{id}

入参:

  • id:产品Id

接口 7:Product-Location(门店-产品关联)

说明:

  • 关联表:fl_location_product
  • 也可在 §6.3 / §6.4 通过产品 Body 的 locationIds 一次性维护本产品在各门店的关联(与 §7 写入同一张表);二者可并存,按需选择调用方式。
  • 关联按门店进行批量替换:
    • Create:在门店下新增未存在的 product 关联
    • Update:替换该门店下全部关联(先删后建)
    • Delete:删除该门店下全部关联

7.1 分页列表

方法:GET /api/app/product-location

入参(ProductLocationGetListInputVo,查询参数):

{
  "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):

{
  "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(与 UsAppAuthus-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.LocationIdfl_label.LocationId 一致)
keyword string 模糊过滤:标签名、产品名、产品分类、标签分类名、标签类型名、labelCode 等(实现见服务内 WhereIF
labelCategoryId string 侧边栏只展示某一 标签分类 时传入;不传则返回当前门店下出现的全部标签分类节点

数据范围与表关联(便于联调对照)

  • 门店内商品fl_location_product(先有 locationId 下的 productId 集合)。
  • 参与连接的表fl_label_product(标签-产品)、fl_labelLocationId 须为当前门店且未删除、启用)、fl_productfl_label_categoryfl_label_typefl_label_template
  • 第二级「产品分类」:来自 fl_product.CategoryName,trim 后为空则归并为显示名
  • 第四级去重:同一产品在同一标签分类、同一门店下,多条 fl_labellabelCode 相同,只保留一条用于列表(预览/打印仍用返回的 labelCode 等业务字段)。

出参(List<UsAppLabelCategoryTreeNodeDto>

若宿主对成功结果有统一包装,业务数组一般在 data 中;下列为 解包后的数组项 结构。

L1 UsAppLabelCategoryTreeNodeDto(标签分类)

字段 类型 说明
id string fl_label_category.Id
categoryName string 分类名称
categoryPhotoUrl string \ null
buttonAppearance string 按钮外观,JSON 格式字符串(与库中 ButtonAppearance 一致;空时后端默认 "TEXT"
orderNum number 排序
productCategories array 第二级列表(见下表)

L2 UsAppProductCategoryNodeDto(产品分类)

字段 类型 说明
categoryId string \ null
categoryPhotoUrl string \ null
name string 产品分类显示名;空源数据为
displayText string \ null
buttonAppearance string JSON 格式字符串(与库中一致;空时默认 "TEXT"
availabilityType string ALL / SPECIFIED(树已按当前门店过滤,仍返回供客户端展示)
orderNum number 排序
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

错误与边界

  • locationId 为空:返回友好错误 「门店Id不能为空」
  • 门店下无关联产品:返回 空数组 []
  • 有产品但无任何符合条件的标签关联:返回 空数组 []

请求示例

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):

curl -X GET "http://localhost:19001/api/app/us-app-labeling/labeling-tree?locationId=11111111-1111-1111-1111-111111111111" \
  -H "Authorization: <data.token>"

响应结构示例(解包后)

[
  {
    "id": "cat-prep-id",
    "categoryName": "Prep",
    "categoryPhotoUrl": "[\"Prep\",\"#10B981\"]",
    "buttonAppearance": "[\"TEXT\",\"COLOR\"]",
    "orderNum": 1,
    "productCategories": [
      {
            "categoryId": "pc-meat-id",
            "categoryPhotoUrl": "[\"/picture/product-category/20260325123010_xxx.png\"]",
        "name": "Meat",
        "displayText": "Meat",
        "buttonAppearance": "[\"IMAGE\"]",
        "availabilityType": "ALL",
        "orderNum": 10,
        "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 同名的字段(locationIdkeywordlabelCategoryId),与 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(供前端画布渲染):

  • templateLabelTemplatePreviewDto
    • width / height / unit:模板物理尺寸
    • elements[]:元素数组(对齐前端 editor JSON:id/elementName/type/x/y/width/height/rotation/border/zIndex/orderNum/config
  • templateProductDefaultValuesobject | null
    • 来源:fl_label_template_product_default.DefaultValuesJson
    • 命中条件:当前预览上下文的 templateId + productId + labelTypeId
    • 建议结构:{ "elementId": "默认值" }
    • 未命中时返回 null(向后兼容)

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:友好错误 「该标签未绑定产品,无法预览」

请求示例

{
  "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:

curl -X POST "http://localhost:19001/api/app/us-app-labeling/preview" \
  -H "Authorization: <data.token>" \
  -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 处理
clientRequestId string 客户端幂等请求Id;同一个值重复调用会直接返回首次创建的 batchId/taskIds,避免重复写库 用法:前端/客户端每次点击 Print 生成一个稳定的 clientRequestId(比如 uuid)
baseTime string 业务基准时间(用于 DATE/TIME 元素计算)
printInputJson object 打印输入(用于模板 PRINT_INPUT 元素),key 建议与模板元素 inputKey 对齐
printerId string 打印机Id(可选,用于追踪)
printerMac string 打印机蓝牙 MAC(可选)
printerAddress string 打印机地址(可选)

数据落库说明

  • 任务表fl_label_print_task
    • 一份打印 = 一条任务:当 printQuantity = N 时,后端会插入 N 条任务记录(同一次点击 Print 共享一个 BatchId,并记录 CopyIndex=1..N)。
    • 任务表会保存:本次打印的输入、命中的模板默认值、以及整份 resolved 后的模板快照 JSON,便于追溯/重打。
  • 明细表fl_label_print_data
    • 按组件写快照:每个任务会按模板 elements[] 逐个插入明细记录(ElementId/ElementName/RenderValue/RenderConfigJson)。
    • 适用于按组件维度审计/统计/追溯。

模板解析的数据源来自 fl_label_template + fl_label_template_element,与预览接口一致。

出参(UsAppLabelPrintOutputDto

字段 类型 说明
taskId string 第 1 份打印任务Id(兼容旧逻辑)
printQuantity number 实际写入的份数
batchId string 本次点击 Print 的批次Id
taskIds string[] 本次生成的所有任务Id(长度=printQuantity)

错误与边界

  • locationId 为空:友好错误 「门店Id不能为空」
  • labelCode 为空:友好错误 「labelCode不能为空」
  • 标签不存在/不可用:友好错误 「标签不存在或不可用」
  • 标签不属于当前门店:友好错误 「该标签不属于当前门店」
  • 标签未绑定产品且未传 productId:友好错误 「该标签未绑定产品,无法预览」(模板解析阶段抛出)。

请求示例

{
  "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:

curl -X POST "http://localhost:19001/api/app/us-app-labeling/print" \
  -H "Authorization: <data.token>" \
  -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"}}'

接口 10:App 打印日志(当前登录账号 + 当前门店)

场景:移动端“打印记录/历史”页面。只展示当前登录账号当前门店打印的记录,便于追溯/重打。

10.1 分页获取打印日志

HTTP

  • 方法POST(与本模块其它复杂入参接口一致;若与 Swagger 不一致,以 Swagger 为准
  • 路径/api/app/us-app-labeling/get-print-log-list
  • 鉴权:需要登录(Authorization: Bearer ...

入参(Body:PrintLogGetListInputVo

本项目分页约定:skipCount 表示 页码(从 1 开始),不是 0 基 offset。

参数名(JSON) 类型 必填 说明
locationId string 当前门店Id(仅返回该门店记录)
skipCount number 页码,从 1 开始;默认 1
maxResultCount number 每页条数;默认按后端/ABP 默认

过滤条件(后端固定逻辑)

  • fl_label_print_task.CreatedBy == CurrentUser.Id
  • fl_label_print_task.LocationId == locationId
  • 按时间倒序:PrintedAt ?? CreationTime(越新的越靠前)

出参(PagedResultWithPageDto<PrintLogItemDto>

字段 类型 说明
pageIndex number 当前页码(从 1 开始)
pageSize number 每页条数
totalCount number 总条数
totalPages number 总页数
items PrintLogItemDto[] 列表

PrintLogItemDto

字段 类型 说明
taskId string 任务Id(fl_label_print_task.Id)
batchId string 批次Id(同一次点击 Print 共享)
copyIndex number 第几份(从 1 开始)
labelId string 标签Id
labelCode string 标签编码(来自 fl_label.LabelCode)
productId string 产品Id
productName string 产品名(来自 fl_product.ProductName;无则 “无”)
typeName string 标签类型名称(来自 fl_label_type.TypeName)
labelSizeText string 模板尺寸(宽高+单位,如 2.00x2.00inch / 6.00x4.00cm
printInputJson string 直接来自表 fl_label_print_task.PrintInputJson(前端打印时传入的打印输入 JSON 字符串;无则为 null)
printedAt string 打印时间(PrintedAt ?? CreationTime)
operatorName string 操作人姓名(当前登录账号 Name)
locationName string 门店名称

本接口返回 printDataList;逐元素快照仍在表 fl_label_print_data 中,可按 PrintTaskId 自行查询。

curl

curl -X POST "http://localhost:19001/api/app/us-app-labeling/get-print-log-list" \
  -H "Authorization: <data.token>" \
  -H "Content-Type: application/json" \
  -d '{"locationId":"11111111-1111-1111-1111-111111111111","skipCount":1,"maxResultCount":20}'

接口 11:App 重新打印(根据任务Id重打)

场景:移动端“打印记录/历史”页面点击 Reprint。后端根据历史任务 taskId 创建一批新的打印任务与明细。

11.1 重打

HTTP

  • 方法POST
  • 路径/api/app/us-app-labeling/reprint(若与 Swagger 不一致,以 Swagger 为准
  • 鉴权:需要登录(Authorization: Bearer ...

入参(Body:UsAppLabelReprintInputVo

参数名(JSON) 类型 必填 说明
locationId string 当前门店Id(后端校验历史任务必须属于该门店)
taskId string 历史打印任务Id(fl_label_print_task.Id
printQuantity number 重新打印份数;<=0 按 1 处理;默认 1
clientRequestId string 客户端幂等请求Id;同一个值重复调用会直接返回首次创建的 batchId/taskIds,避免重复写库
printerId string 可选,覆盖历史任务的打印机Id
printerMac string 可选,覆盖历史任务的打印机MAC
printerAddress string 可选,覆盖历史任务的打印机地址

权限校验(后端固定逻辑)

  • 历史任务必须满足:CreatedBy == CurrentUser.Id
  • LocationId == locationId

出参(UsAppLabelPrintOutputDto

字段与接口 9 一致:taskId / printQuantity / batchId / taskIds

curl

curl -X POST "http://localhost:19001/api/app/us-app-labeling/reprint" \
  -H "Authorization: <data.token>" \
  -H "Content-Type: application/json" \
  -d '{"locationId":"11111111-1111-1111-1111-111111111111","taskId":"3a205389-78dd-4750-51ab-720344c9f607","printQuantity":1}'