# 6-25 代码优化 本文档说明 **2026-06-25** 对美国版标签模板(Label Template)接口的 `contents` 字段改造:新建/编辑时持久化模板内各控件的 **elementName**,列表与详情接口返回该字段,供前端 **Contents** 列展示。 --- ## 一、背景与目标 | 项目 | 说明 | |------|------| | 原行为 | 列表 Contents 列依赖实时查询 `fl_label_template_element` 汇总控件名(`items` / `itemNames`) | | 新行为 | 新增 **`contents`** 字段,落库 `fl_label_template.Contents`;保存时写入,列表/详情直接返回 | | 控件名来源 | 编辑器右侧 **Element name**(对应 `elements[].elementName`),按 `orderNum` 排序、去重后逗号拼接 | | 兼容 | 未传 `contents` 时后端按 `elements` 自动推导;库内 `Contents` 为空时列表回退 element 汇总 | **示例**:模板含控件 `labelname`、`text` → `contents` = `"labelname, text"` --- ## 二、数据库迁移 脚本路径:`美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_label_template_contents.sql` | 步骤 | 内容 | |------|------| | 1 | `fl_label_template` 增加列 `Contents` varchar(2000) NULL | | 2 | 从 `fl_label_template_element.ElementName` 回填已有模板 | **执行前请备份数据库。** --- ## 二点五、列表 500:`Unknown column 'Contents'` | 项目 | 说明 | |------|------| | 现象 | `GET /api/app/label-template?SkipCount=1&MaxResultCount=10` 报 `Unknown column 'Contents' in 'field list'` | | 原因 | 代码已支持 `contents`,但库表尚未执行 `fl_label_template_contents.sql`,ORM 主查询 SELECT 了不存在的列 | | 修复 | `Contents` **不参与** SqlSugar 列投影(实体 `IsIgnore`);读写经 `LabelTemplateScopeSchemaHelper` 探测列是否存在 | | 未迁移时 | 列表/详情 `contents` **回退**为 `fl_label_template_element` 汇总(`items` / `itemNames` 逻辑),接口可正常 200 | | 已迁移时 | 保存写入 `fl_label_template.Contents`;列表优先读库内 `contents`,空时再回退 element 汇总 | **仍需执行迁移**:若要在保存后持久化 `contents`(不依赖每次查 element 表),请执行 `fl_label_template_contents.sql` 并重启服务。 --- ## 三、`contents` 字段规范 ### 入参(POST / PUT) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `contents` | `string` | 否 | 控件名称逗号拼接,如 `"labelname, text"` | | `elements` | `array` | 是(保存时) | 全量重建;未传 `contents` 时由后端从 `elements[].elementName` 推导 | **推导规则(`LabelTemplateContentsHelper.ResolveForSave`)** 1. 入参 `contents` 非空 → 原样 trim 后落库 2. 否则按 `elements` 的 `orderNum` 升序、`id` 次序排序 3. 取非空 `elementName`,**大小写不敏感去重** 4. 用 `", "` 拼接(英文逗号 + 空格) ### 出参(GET 列表 / 详情) | 字段 | 类型 | 说明 | |------|------|------| | `contents` | `string` | 持久化控件名列表;空时详情回退 elements 推导 | | `contentsCount` | `number` | 仅列表:`elements` 数量 | | `items` | `string` | **兼容**:与 `contents` 同值(列表) | | `itemNames` | `string[]` | **兼容**:展示名数组(仍来自 element 表汇总,可能与 `contents` 文案略有差异) | --- ## 四、接口说明 ### 1. 模板列表 | 项目 | 内容 | |------|------| | 方法 | `GET` | | 路径 | `/api/app/label-template` | | 鉴权 | Bearer Token | **Query / data 参数(节选)** | 参数 | 说明 | |------|------| | `SkipCount` | 页码,从 1 起 | | `MaxResultCount` | 每页条数 | | `Keyword` | 模板名称/编码模糊搜索 | | `PartnerId` / `GroupId` / `LocationId` | 适用范围筛选 | | `State` | 启用状态 | **出参 `items[]` 相关字段** | 字段 | 示例 | 说明 | |------|------|------| | `id` | `tpl_c23gov_mqri4g5a` | 即 `templateCode` | | `templateName` | `Unnamed template` | 模板名称 | | `contents` | `labelname, text` | **Contents 列展示** | | `contentsCount` | `2` | 控件数量 | | `items` | `labelname, text` | 兼容字段 | | `sizeText` | `6.00x4.00cm` | 尺寸展示 | | `location` | `All Locations` | 适用门店 | **curl 示例** ```bash curl -s -G "http://localhost:5000/api/app/label-template" \ -H "Authorization: Bearer " \ --data-urlencode "SkipCount=1" \ --data-urlencode "MaxResultCount=10" ``` --- ### 2. 模板详情 | 项目 | 内容 | |------|------| | 方法 | `GET` | | 路径 | `/api/app/label-template/{id}` | | 路径参数 | `id` = `templateCode`,如 `tpl_c23gov_mqri4g5a` | **出参新增** | 字段 | 说明 | |------|------| | `contents` | 持久化控件名;库内为空时由当前 `elements` 推导 | | `elements` | 控件明细(含 `elementName`) | **curl 示例** ```bash curl -s "http://localhost:5000/api/app/label-template/tpl_c23gov_mqri4g5a" \ -H "Authorization: Bearer " ``` --- ### 3. 新建模板 | 项目 | 内容 | |------|------| | 方法 | `POST` | | 路径 | `/api/app/label-template` | | Content-Type | `application/json` | **Body 节选** ```json { "id": "tpl_new_example", "name": "Unnamed template", "unit": "cm", "width": 6, "height": 4, "printOrientation": "vertical", "contents": "labelname, text", "elements": [ { "id": "el_1", "elementName": "labelname", "type": "TEXT_PRODUCT", "orderNum": 1, "x": 0.5, "y": 0.5, "width": 2, "height": 0.8 }, { "id": "el_2", "elementName": "text", "type": "TEXT_STATIC", "orderNum": 2, "x": 0.5, "y": 1.5, "width": 3, "height": 1 } ] } ``` --- ### 4. 编辑模板 | 项目 | 内容 | |------|------| | 方法 | `PUT` | | 路径 | `/api/app/label-template/{id}` | | 说明 | 与 POST 相同 Body 结构;`elements` 全量重建;`contents` 随保存更新 | **curl 示例** ```bash curl -s -X PUT "http://localhost:5000/api/app/label-template/tpl_c23gov_mqri4g5a" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d "{\"id\":\"tpl_c23gov_mqri4g5a\",\"name\":\"Unnamed template\",\"unit\":\"cm\",\"width\":6,\"height\":4,\"printOrientation\":\"vertical\",\"contents\":\"labelname, text\",\"elements\":[...]}" ``` --- ## 五、前端改动 | 文件 | 改动 | |------|------| | `LabelTemplateEditor/index.tsx` | 保存时提交 `contents: buildTemplateContentsFromElements(elements)` | | `labelTemplateService.ts` | 列表归一化优先读取 `contents` | | `LabelTemplatesView.tsx` | Contents 列优先展示 `contents` / `contentsText` | | `types/labelTemplate.ts` | 新增 `buildTemplateContentsFromElements` | --- ## 六、后端改动摘要 | 文件 | 改动 | |------|------| | `FlLabelTemplateDbEntity.cs` | `Contents` 为内存字段(`IsIgnore`),避免未迁移时 ORM SELECT/INSERT 报错 | | `LabelTemplateScopeSchemaHelper.cs` | 探测 `HasContentsColumn`;`GetContentsMapAsync` / `SetContentsAsync` | | `LabelTemplateCreateInputVo.cs` | 入参 `contents` | | `LabelTemplateGetListOutputDto.cs` / `LabelTemplateGetOutputDto.cs` | 出参 `contents` | | `LabelTemplateContentsHelper.cs` | 推导与保存解析 | | `LabelTemplateAppService.cs` | Create/Update 条件落库;List/Get 条件读取 + element 回退 | | `LabelTemplateQueryHelper.cs` | 列表投影**不含** `Contents`(与 scope/border 列策略一致) | --- ## 七、部署与验证 1. **重新编译并重启** `Yi.Abp.Web`(未迁移 Contents 列时列表亦应 200,contents 来自 element 汇总) 2. (推荐)执行 `fl_label_template_contents.sql`,使保存后的 `contents` 持久化到主表 3. 编辑模板 `tpl_c23gov_mqri4g5a` 保存后,GET 列表应返回 `"contents": "labelname, text"`(与编辑器 Element name 一致) 4. 模板列表 **Contents** 列应显示相同文案 --- ## 八、注意事项 - `elementName` 为必填(保存校验「组件名字不能为空」),与 `contents` 中各项一一对应 - `contents` 存的是 **elementName 原文**(如 `labelname`),不是面板展示标签(如 `Label Name`) - `itemNames` 仍保留旧汇总逻辑,新 UI 请以 **`contents`** 为准 - **未执行 SQL 迁移**:接口可用,但 `contents` 不落主表,每次列表从 element 表实时汇总;执行迁移后保存才会写入 `fl_label_template.Contents`