# 给已有表单改布局

给已经建好的表单加字段、改字段的显示属性时读这篇。**不是自己建的表尤其要读**：改错了别人的版式，对方要一个个调回来。

## 布局是两套数据

| | 存在哪 | 一条是什么 |
|---|---|---|
| **字段定义** | 字段管理接口 | 一个字段一条（`fieldId` / `type` / `componentKey` / `title` / `dataKey` / `columnName` …） |
| **布局组件** | 表单布局接口的 `pageLayouts[].comps` | **平铺一维数组**，每个字段占**两个**：一个标签组件、一个值组件 |

**判断「这个组件属于哪个字段」只能看 `config.associatedId`。**
标签组件的 `config.fieldId` 是布局自己另生成的一个 id，与字段定义没有对应关系 —— 拿它去找字段会找不到。

明细字段也在同一个平铺数组里，靠 `config.subFormId` 归属；它的 `excel.parent` 指向明细表容器的 `nodeId`，普通字段的 `excel.parent` 是 `ROOT`。

**EXCEL 布局还有第三份：网格。** `pageLayouts[].config.workbook.Sheet1.cells` 里每个组件占**一格**，靠 `style.cellType.comId` 认到自己的组件，位置也以格子自己的 `row` / `col` 为准 —— **设计器按网格渲染**。所以 EXCEL 表里「组件的 `config.excel.row` / `col`」和「格子的 `row` / `col`」是同一件事的两份副本，**改一份必须改两份**：只改组件层的话接口会回成功，但设计器里画不出来。

FLOW 布局**没有** `config.workbook`（它靠 `config.flow.parent` / `nodes` 排布），这条对它不适用。

> 这份同步由 CLI 负责，不用手工做。但**手工在页面上改过布局的老表**如果在 EXCEL 上有字段显示不出来，在设计器里保存一次即可 —— 设计器会按组件重算网格。

## 字段属性分两类

有些属性**只存在于布局组件里，字段定义没有这个键** —— 只写字段定义等于什么都没做：接口回成功、命令也报成功，表单上一点变化都没有。

| 只存在于布局组件 | 字段定义里也有 |
|---|---|
| `required`（必填）、`readOnly`、`singleRow`（独占一行）、`display`、`isHideTitle`、`excel`（位置）、`layout` | `title`、`dataKey`、`columnName`、`maxLen`、`componentKey`、`type`、`format` … |

必填是个典型：一张真实表 53 条字段记录里，**没有一条**带 `required`。设必填就是改布局组件。

## 加字段：默认增量，别动不动重建

`e10-cli form add-field` 默认**只往布局里加这一个字段**，不动其它组件、不动样式模板、不动位置。

`--reinit` 才是整表重建，它会：

- 把样式模板换成工具默认的那一个；
- 按字段顺序**重算所有位置**（手工调过的行列全没了）；
- 把标签组件**全部换成新 id**；
- 布局层的自定义（合并格、列宽、配色）一并丢失。

**不是自己建的表不要用 `--reinit`。** 字段越多越经不起 —— 真实的一张订单主表有 131 个组件。

### 表里还没有这个类型时：用平台给的字段模板

新组件的键集（`format`、`earlyDate`、`pointSize`、`switchType` …）各类型都不一样，**按类型名拼一个出来只会猜错，而猜错不报错、只是渲染异常**。所以增量插入一向是**照同类型的现成组件整套抄**。

表里没有同类型字段时，模板改从平台取：命令会调 `getFormFieldExist`，拿**平台按这个字段生成的组件模板**（`configPC`）—— 顶层样式键（按类型）与类型专有键平台都给全了，结构尾键从同作用域的现成组件借，位置和身份键由命令自己写。**全程不重建布局。**

两点要知道：

- 明细表字段拿不到这个接口的模板（它只返回主表与系统字段），但模板是**按类型而不是按作用域**的，此时借主表同类型的那一份。
- 拿模板只在**真的缺**时才多打一次接口；表里已有同类型字段时，路径与从前一字不差。

### 明细表的字段是横着排的

同一个「加字段」，**主表和明细表落的轴不一样**：主表是一行一个字段（新字段**往下加行**），明细表是一个字段占一列（新字段**往右加列**）。明细表还有自己的一张网格（`workbook` 的键就是 `DetailTable` 组件的 id），明细字段的格子在那张里，不在 `Sheet1`。

遇到中止时，两个选择：

1. 先在设计器里手工加一个同类型字段（它就成了可参照的样板），再重跑命令；
2. 确认这张表是自己建的、不在乎版式被重排 → 显式加 `--reinit`。

## 哪些 id 可以自己生成，哪些必须由平台发号

这一条最容易踩：**编一个 id 不会报错**，而是在之后某个看不出来的环节静默失效。

| 位置 | 能否自己生成 | 说明 |
|---|---|---|
| 布局组件的 `id` | ✅ | 布局内唯一即可，形如 32 位十六进制串 |
| 布局组件的 `excel.nodeId` | ✅ | 布局内唯一即可，短随机串 |
| 标签组件的 `config.fieldId` | ✅ | 只在布局内有意义，与字段定义无关 |
| **`labelObj.title` / `titleLabelId`** | ❌ **必须平台发号** | 见下 |

### 为什么标题的 id 必须平台发号

平台把**字段标题存的是标签 id，不是文本本身**。这个 id 要调多语言标签接口（`i18n/multilangLabel/saveLabel`）由平台分配。

自己拼一个（比如拿时间戳凑）的后果：接口**照样回成功**，之后任何要显示这个标题的地方都取不到文字 —— 字段名变成空的，看着就像这个字段不存在。

判断一个标签 id 是否有效：用 `-1` 去查标签，拿到的是一行空值（模板行）。**查一个不存在的标签，返回的结果与查 `-1` 完全一致** —— 所以「查得回来」不等于「标签存在」，要看有没有值。

现成命令：`e10-cli form field-lang set <objId> <appId> <fieldId> --zh <中文> --en <英文>` 会建标签并回写字段，写完还能回读校验。`form add-field` 建字段时也会自动建标签。

## 相关

- 建表时的布局规划见 [EB 表单布局规划](guide-form-layout-planning.md)。
- 字段类型与属性码表见 [表单字段定义参考](ref-form-fields.md)。
