概述
美国版后端采用 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 中如何找到
- 启动后端宿主(
Yi.Abp.Web),端口19001。 - 打开
http://localhost:19001/swagger。 - 在接口分组里搜索以下关键词之一:
label-categorylabel-typelabel-multiple-optionlabel-templatelabelus-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 字段约定:buttonAppearance 与 categoryPhotoUrl(JSON 字符串)
buttonAppearance:库中存 JSON 文本(如["TEXT","COLOR"]、仅图片["IMAGE"]等);兼容历史单行TEXT/COLOR/IMAGE(保存时规范为["TEXT"]等)。未传或空白时后端默认["TEXT"]。非法值(非 JSON 且非上述三者)会返回友好错误。categoryPhotoUrl:同样为 JSON 文本(如["Prep","#10B981"]);若传非 JSON 的纯文本(色值、/picture/...等),后端会序列化为合法 JSON 字符串再存储。列表/详情/App 树原样返回字符串,由客户端解析。- 其它常用字段:
displayText、availabilityType(ALL/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 结构说明:
- 每一行需传
productId与labelTypeId。 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,与实现一致的主要字段):
id、productCode、productName、categoryId、categoryName、productImageUrl、statelocationIds:string[],该产品在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:
ProductLocationUpdateInputVojson { "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<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 同名的字段(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:LabelTemplatePreviewDtowidth/height/unit:模板物理尺寸elements[]:元素数组(对齐前端 editor JSON:id/elementName/type/x/y/width/height/rotation/border/zIndex/orderNum/config)
templateProductDefaultValues:object | 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.Idfl_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}'