7083cd6d
李曜臣
优化代码
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
|
# 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 <TOKEN>" \
--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 <TOKEN>"
```
---
### 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 <TOKEN>" \
-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`
|