weaver-e10-builder 1.5.2 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/dist/commands/form-add-detail-table.js +3 -3
  2. package/dist/commands/form-add-detail-table.js.map +1 -1
  3. package/dist/commands/form-add-physical-field.d.ts.map +1 -1
  4. package/dist/commands/form-add-physical-field.js +3 -2
  5. package/dist/commands/form-add-physical-field.js.map +1 -1
  6. package/dist/commands/form-data.d.ts.map +1 -1
  7. package/dist/commands/form-data.js +125 -0
  8. package/dist/commands/form-data.js.map +1 -1
  9. package/dist/commands/form-ebuilder-columns.d.ts.map +1 -1
  10. package/dist/commands/form-ebuilder-columns.js +3 -3
  11. package/dist/commands/form-ebuilder-columns.js.map +1 -1
  12. package/dist/commands/form-ebuilder-scope.d.ts.map +1 -1
  13. package/dist/commands/form-ebuilder-scope.js +3 -4
  14. package/dist/commands/form-ebuilder-scope.js.map +1 -1
  15. package/dist/commands/form-field-lang.d.ts.map +1 -1
  16. package/dist/commands/form-field-lang.js +3 -4
  17. package/dist/commands/form-field-lang.js.map +1 -1
  18. package/dist/commands/form-field-linkage.d.ts.map +1 -1
  19. package/dist/commands/form-field-linkage.js +4 -3
  20. package/dist/commands/form-field-linkage.js.map +1 -1
  21. package/dist/commands/form-formula-linkage.d.ts.map +1 -1
  22. package/dist/commands/form-formula-linkage.js +4 -2
  23. package/dist/commands/form-formula-linkage.js.map +1 -1
  24. package/dist/commands/form-formula.d.ts.map +1 -1
  25. package/dist/commands/form-formula.js +5 -5
  26. package/dist/commands/form-formula.js.map +1 -1
  27. package/dist/commands/form-layout.d.ts.map +1 -1
  28. package/dist/commands/form-layout.js +6 -0
  29. package/dist/commands/form-layout.js.map +1 -1
  30. package/dist/commands/form-list-click.d.ts.map +1 -1
  31. package/dist/commands/form-list-click.js +3 -2
  32. package/dist/commands/form-list-click.js.map +1 -1
  33. package/dist/commands/form-physical-table.js +3 -3
  34. package/dist/commands/form-physical-table.js.map +1 -1
  35. package/dist/commands/form-relate.d.ts.map +1 -1
  36. package/dist/commands/form-relate.js +3 -2
  37. package/dist/commands/form-relate.js.map +1 -1
  38. package/dist/commands/form-validate.d.ts.map +1 -1
  39. package/dist/commands/form-validate.js +3 -3
  40. package/dist/commands/form-validate.js.map +1 -1
  41. package/dist/commands/form-view.d.ts.map +1 -1
  42. package/dist/commands/form-view.js +2 -0
  43. package/dist/commands/form-view.js.map +1 -1
  44. package/dist/commands/form.d.ts +9 -3
  45. package/dist/commands/form.d.ts.map +1 -1
  46. package/dist/commands/form.js +116 -160
  47. package/dist/commands/form.js.map +1 -1
  48. package/dist/commands/special.d.ts +24 -0
  49. package/dist/commands/special.d.ts.map +1 -0
  50. package/dist/commands/special.js +487 -0
  51. package/dist/commands/special.js.map +1 -0
  52. package/dist/commands/wf-form.d.ts.map +1 -1
  53. package/dist/commands/wf-form.js +4 -4
  54. package/dist/commands/wf-form.js.map +1 -1
  55. package/dist/commands/workflow-message.d.ts.map +1 -1
  56. package/dist/commands/workflow-message.js +1 -2
  57. package/dist/commands/workflow-message.js.map +1 -1
  58. package/dist/index.js +24 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/services/actionflow/components/action_eb_update_compiler.d.ts.map +1 -1
  61. package/dist/services/actionflow/components/action_eb_update_compiler.js +54 -12
  62. package/dist/services/actionflow/components/action_eb_update_compiler.js.map +1 -1
  63. package/dist/services/actionflow/components/index.d.ts.map +1 -1
  64. package/dist/services/actionflow/components/index.js +6 -0
  65. package/dist/services/actionflow/components/index.js.map +1 -1
  66. package/dist/services/actionflow/components/multiple_execution.d.ts.map +1 -1
  67. package/dist/services/actionflow/components/multiple_execution.js +73 -13
  68. package/dist/services/actionflow/components/multiple_execution.js.map +1 -1
  69. package/dist/services/actionflow/components/output.d.ts.map +1 -1
  70. package/dist/services/actionflow/components/output.js +144 -5
  71. package/dist/services/actionflow/components/output.js.map +1 -1
  72. package/dist/services/actionflow/components/send_message.d.ts.map +1 -1
  73. package/dist/services/actionflow/components/send_message.js +60 -32
  74. package/dist/services/actionflow/components/send_message.js.map +1 -1
  75. package/dist/services/actionflow/components/serverless_action.d.ts +5 -0
  76. package/dist/services/actionflow/components/serverless_action.d.ts.map +1 -0
  77. package/dist/services/actionflow/components/serverless_action.js +134 -0
  78. package/dist/services/actionflow/components/serverless_action.js.map +1 -0
  79. package/dist/services/actionflow/components/webhook_trigger.d.ts +5 -0
  80. package/dist/services/actionflow/components/webhook_trigger.d.ts.map +1 -0
  81. package/dist/services/actionflow/components/webhook_trigger.js +85 -0
  82. package/dist/services/actionflow/components/webhook_trigger.js.map +1 -0
  83. package/dist/services/actionflow/context_preflight.d.ts +6 -0
  84. package/dist/services/actionflow/context_preflight.d.ts.map +1 -1
  85. package/dist/services/actionflow/context_preflight.js +247 -7
  86. package/dist/services/actionflow/context_preflight.js.map +1 -1
  87. package/dist/services/actionflow/context_provider.d.ts.map +1 -1
  88. package/dist/services/actionflow/context_provider.js +67 -3
  89. package/dist/services/actionflow/context_provider.js.map +1 -1
  90. package/dist/services/actionflow/custom_trigger_context.d.ts.map +1 -1
  91. package/dist/services/actionflow/custom_trigger_context.js +9 -2
  92. package/dist/services/actionflow/custom_trigger_context.js.map +1 -1
  93. package/dist/services/actionflow/formula_service.d.ts +2 -0
  94. package/dist/services/actionflow/formula_service.d.ts.map +1 -1
  95. package/dist/services/actionflow/formula_service.js +381 -63
  96. package/dist/services/actionflow/formula_service.js.map +1 -1
  97. package/dist/services/actionflow/generation_service.d.ts.map +1 -1
  98. package/dist/services/actionflow/generation_service.js +9 -0
  99. package/dist/services/actionflow/generation_service.js.map +1 -1
  100. package/dist/services/actionflow/param_group_dto.d.ts +5 -0
  101. package/dist/services/actionflow/param_group_dto.d.ts.map +1 -0
  102. package/dist/services/actionflow/param_group_dto.js +181 -0
  103. package/dist/services/actionflow/param_group_dto.js.map +1 -0
  104. package/dist/services/actionflow/preflight_service.d.ts.map +1 -1
  105. package/dist/services/actionflow/preflight_service.js +53 -11
  106. package/dist/services/actionflow/preflight_service.js.map +1 -1
  107. package/dist/services/actionflow/readback_verifier.d.ts.map +1 -1
  108. package/dist/services/actionflow/readback_verifier.js +14 -1
  109. package/dist/services/actionflow/readback_verifier.js.map +1 -1
  110. package/dist/services/actionflow/update_service.d.ts.map +1 -1
  111. package/dist/services/actionflow/update_service.js +14 -8
  112. package/dist/services/actionflow/update_service.js.map +1 -1
  113. package/dist/services/actionflow/webhook_source.d.ts +4 -0
  114. package/dist/services/actionflow/webhook_source.d.ts.map +1 -0
  115. package/dist/services/actionflow/webhook_source.js +99 -0
  116. package/dist/services/actionflow/webhook_source.js.map +1 -0
  117. package/dist/services/actionflow/workflow_compiler.d.ts.map +1 -1
  118. package/dist/services/actionflow/workflow_compiler.js +1 -0
  119. package/dist/services/actionflow/workflow_compiler.js.map +1 -1
  120. package/dist/services/form-create.d.ts +45 -2
  121. package/dist/services/form-create.d.ts.map +1 -1
  122. package/dist/services/form-create.js +78 -8
  123. package/dist/services/form-create.js.map +1 -1
  124. package/dist/services/form-data-openapi.d.ts +142 -0
  125. package/dist/services/form-data-openapi.d.ts.map +1 -0
  126. package/dist/services/form-data-openapi.js +363 -0
  127. package/dist/services/form-data-openapi.js.map +1 -0
  128. package/dist/services/form-field-build.d.ts.map +1 -1
  129. package/dist/services/form-field-build.js +4 -4
  130. package/dist/services/form-field-build.js.map +1 -1
  131. package/dist/services/form-field-template.d.ts +71 -0
  132. package/dist/services/form-field-template.d.ts.map +1 -0
  133. package/dist/services/form-field-template.js +103 -0
  134. package/dist/services/form-field-template.js.map +1 -0
  135. package/dist/services/form-field-validate.d.ts +26 -0
  136. package/dist/services/form-field-validate.d.ts.map +1 -0
  137. package/dist/services/form-field-validate.js +121 -0
  138. package/dist/services/form-field-validate.js.map +1 -0
  139. package/dist/services/form-id.d.ts +50 -0
  140. package/dist/services/form-id.d.ts.map +1 -0
  141. package/dist/services/form-id.js +110 -0
  142. package/dist/services/form-id.js.map +1 -0
  143. package/dist/services/form-layout-grid.d.ts +123 -0
  144. package/dist/services/form-layout-grid.d.ts.map +1 -0
  145. package/dist/services/form-layout-grid.js +177 -0
  146. package/dist/services/form-layout-grid.js.map +1 -0
  147. package/dist/services/form-layout-insert.d.ts +24 -1
  148. package/dist/services/form-layout-insert.d.ts.map +1 -1
  149. package/dist/services/form-layout-insert.js +192 -27
  150. package/dist/services/form-layout-insert.js.map +1 -1
  151. package/dist/services/form-physical-table.d.ts +1 -1
  152. package/dist/services/form-physical-table.js +1 -1
  153. package/dist/services/form-table-view.d.ts.map +1 -1
  154. package/dist/services/form-table-view.js +2 -11
  155. package/dist/services/form-table-view.js.map +1 -1
  156. package/dist/services/form-view.d.ts +2 -0
  157. package/dist/services/form-view.d.ts.map +1 -1
  158. package/dist/services/form-view.js +57 -7
  159. package/dist/services/form-view.js.map +1 -1
  160. package/dist/services/form-virtual.d.ts.map +1 -1
  161. package/dist/services/form-virtual.js +6 -1
  162. package/dist/services/form-virtual.js.map +1 -1
  163. package/dist/services/special-page.d.ts +85 -0
  164. package/dist/services/special-page.d.ts.map +1 -0
  165. package/dist/services/special-page.js +129 -0
  166. package/dist/services/special-page.js.map +1 -0
  167. package/dist/services/special-route.d.ts +92 -0
  168. package/dist/services/special-route.d.ts.map +1 -0
  169. package/dist/services/special-route.js +93 -0
  170. package/dist/services/special-route.js.map +1 -0
  171. package/dist/services/workflow-node-layout.d.ts +13 -1
  172. package/dist/services/workflow-node-layout.d.ts.map +1 -1
  173. package/dist/services/workflow-node-layout.js +11 -3
  174. package/dist/services/workflow-node-layout.js.map +1 -1
  175. package/package.json +1 -1
  176. package/skill-docs/actionflows/SKILL.md +5 -4
  177. package/skill-docs/actionflows/references/components/logic/MultipleExecution.md +38 -38
  178. package/skill-docs/actionflows/references/components/logic/Output.md +9 -2
  179. package/skill-docs/actionflows/references/components/message/SendMessage.md +3 -3
  180. package/skill-docs/actionflows/references/components/serverless/ServerlessAction.md +42 -0
  181. package/skill-docs/actionflows/references/components/trigger/WebHookTrigger.md +89 -0
  182. package/skill-docs/actionflows/references/dynamic_capability_matrix.md +2 -2
  183. package/skill-docs/references/common/ref-cli-commands.md +3 -1
  184. package/skill-docs/references/form/guide-form-data.md +30 -0
  185. package/skill-docs/references/form/guide-form-layout-incremental.md +18 -3
  186. package/skill-docs/references/form/guide-form-physical-table.md +1 -1
  187. package/skill-docs/references/form/ref-form-fields.md +1 -1
@@ -2,95 +2,34 @@ import { writeFileSync, existsSync, readFileSync } from 'fs';
2
2
  import { AuthSession } from '../core/auth.js';
3
3
  import { ApiClient } from '../core/client.js';
4
4
  import { toColumnName, toTableName, resolveFieldKeys } from '../utils/column-name.js';
5
- import { createCustomPhysicalForm, addInitialFieldsToCustomPhysicalForm, placeholderFromFormat, reapplyGroupLayout, queryFormStorageSetting } from '../services/form-create.js';
6
- import { FormSubDetailService } from '../services/form-sub-detail.js';
7
- import { RICH_TYPE_MAP, getRichType, richIdUnits, BROWSER_KINDS } from '../services/rich-types.js';
5
+ import { createCustomPhysicalForm, applyInitialFieldsToNewForm, placeholderFromFormat, reapplyGroupLayout, queryFormStorageSetting } from '../services/form-create.js';
6
+ import { getRichType, richIdUnits, BROWSER_KINDS } from '../services/rich-types.js';
8
7
  import { dropFieldComponents, prepareLayoutForSave } from '../services/form-field-drop.js';
9
8
  import { describeEditBaseFailure, resolveEditBase as resolveEditBasePure } from '../services/form-edit-base.js';
10
9
  import { applyFieldPatches, platformFieldShape } from '../services/form-field-patch.js';
11
10
  import { ensureDisplayColumns } from '../services/form-ebuilder-columns.js';
12
- import { insertFieldIntoLayout, patchFieldLayout } from '../services/form-layout-insert.js';
11
+ import { insertFieldIntoLayout, patchFieldLayout, NO_FIELD_TYPE_TEMPLATE } from '../services/form-layout-insert.js';
12
+ import { fetchFieldTypeTemplates, findTemplateByType } from '../services/form-field-template.js';
13
13
  import { buildLinkageAddPayload, addLinkage, resolveEffect, readLinkageConfig } from '../services/form-linkage-condition.js';
14
14
  // 字段 jsonObj 的构造器(含 SYSTEM_FIELD_IDS / defaultEBuilderDataTitle)——
15
15
  // 原先内联在下面 add-field 的 action 里,为了给「按物理表加字段」第二个入口复用而抽出去。
16
16
  // 本文件仍有别处用这两个符号,所以在这里回导。
17
17
  import { SYSTEM_FIELD_IDS, defaultEBuilderDataTitle, buildFieldJsonObject } from '../services/form-field-build.js';
18
+ // objId → 表单记录/formId。obj/getList 不含三方表单(专项应用),所以内部带了 getInfoById 兜底。
19
+ import { resolveFormRecord, resolveFormId, appendThirdPartyForms, resolveObjIdByFormId } from '../services/form-id.js';
20
+ // 字段类型归一 / 校验:与「专项建表」共用同一份(原为本文件内联)。
21
+ import { FIELD_TYPE_CHOICES, canonComponentKey, assertFieldType, normalizeFieldDef } from '../services/form-field-validate.js';
18
22
  import { listFormTables, readFieldRecords, mapFieldsToTables } from '../services/form-physical-table.js';
19
23
  import { buildFormulaParameters, defaultReturnType } from '../services/formula-detail.js';
20
24
  import { registerOptionTemplateCommand } from './form-option-template.js';
21
25
  /**
22
- * componentKey 大小写归一:用户/示例常写 "Textarea"/"Ebuilder" 等小写变体,但代码各处
23
- * `=== 'TextArea'`、storageFieldType 按 canonical 大小写匹配。不归一会导致 type 存成小写
24
- * "Textarea"(FLOW 渲染器按 type 查组件查不到 → 多行文本在流式布局下渲染异常)、TextArea
25
- * 分支(content/maxLen)漏跑。建表 create 和加字段 add-field 都用。
26
- */
27
- const CK_CANON = {
28
- textarea: 'TextArea', ebuilder: 'EBuilder', datetime: 'DateTime',
29
- idcard: 'IDCard', mobile: 'Mobile', email: 'Email', phone: 'Phone',
30
- employee: 'Employee', department: 'Department', subcompany: 'SubCompany', location: 'Location',
31
- switch: 'Switch', select: 'Select', checkbox: 'CheckBox', radiobox: 'RadioBox',
32
- money: 'Money', number: 'Number', date: 'Date', text: 'Text',
33
- daterange: 'DateRange', detailtable: 'DetailTable', city: 'City',
34
- };
35
- function canonComponentKey(ck) {
36
- return CK_CANON[String(ck).toLowerCase()] || ck;
37
- }
38
- /**
39
- * 代码里另有分支/别名、不在 CK_CANON 与 RICH_TYPE_MAP 两处的字段类型。
40
- * **宁宽勿窄**:多列一个只是照旧送服务器,漏一个会把合法类型误杀,所以这里尽量收全。
41
- * 新增字段类型时记得同步。
42
- */
43
- const EXTRA_FIELD_TYPES = [
44
- 'Flow', 'SelectMultiple', 'DateInterval', 'ColorPicker', 'DataTable',
45
- 'DocFolder', 'DocFloder', 'Fold', 'Placeholder', 'Sharer', 'FileComponent',
46
- 'EbuilderAppTag', 'Attachment', 'Image', 'Signature', 'RichText', 'RelateBrowser',
47
- // EInvoice 电子发票:下方 add-field 有专属构建分支(isSingle/invoiceType),此前漏登记 →
48
- // 被 assertFieldType 挡成死代码。AudioRecord/VideoRecord 语音视频:极简 jsonObj,平台原样透传
49
- // (见 rich-types.ts 的说明),无需富组件模板。
50
- 'EInvoice', 'AudioRecord', 'VideoRecord',
51
- ];
52
- /** 可用的字段类型 = 基础组件(CK_CANON 的值)∪ 富组件(RICH_TYPE_MAP 的键)∪ 上述补充。
53
- * 导出仅为让测试能盯住"平台支持的合法类型不许漏登记"(漏了会被 assertFieldType 误杀)。 */
54
- export const FIELD_TYPE_CHOICES = [...new Set([
55
- ...Object.values(CK_CANON),
56
- ...Object.keys(RICH_TYPE_MAP),
57
- ...EXTRA_FIELD_TYPES,
58
- ])].sort();
59
- /**
60
- * 校验字段类型名。不校验的话错类型会**直送服务器**,换回一段
61
- * `Cannot invoke "ComponentType.name()" because ... getComponentKey() is null`——
62
- * 既看不出哪错了,也看不出该填什么。这里先拦下来并列出可选值。
63
- */
64
- function assertFieldType(fieldType, where = '') {
65
- if (FIELD_TYPE_CHOICES.includes(fieldType))
66
- return;
67
- console.error(`${where}不支持的字段类型: ${fieldType}`);
68
- console.error(` 可选: ${FIELD_TYPE_CHOICES.join(' / ')}`);
69
- process.exit(1);
70
- }
71
- /**
72
- * 校验选项定义。**键名必须是 `name` / `valueKey`**——写成 `{value,label}` 这类
73
- * 不会被拦下,而是静默存成「没有名字的选项」(平台侧 fieldOptionName 为空),
74
- * 之后 `form data add` 按选项名写数据会直接失效,且读回也看不出哪里错了。
26
+ * 字段类型归一 / 校验 / 校验清单已抽到 `services/form-field-validate.ts` ——
27
+ * 「标准建表」与「专项建表」两个入口共用同一份,避免两处真相。
28
+ *
29
+ * `FIELD_TYPE_CHOICES` 在这里**原样 re-export**:
30
+ * `form-add-physical-field.ts` 与 `test/form-field-types.test.ts` 仍从本模块取它。
75
31
  */
76
- function assertFieldOptions(fieldName, options) {
77
- options.forEach((o, i) => {
78
- if (typeof o === 'string')
79
- return; // 字符串简写,归一化时会补成 {name}
80
- const where = `字段「${fieldName}」第 ${i + 1} 个选项`;
81
- if (typeof o?.name !== 'string' || !o.name.trim()) {
82
- const got = o && typeof o === 'object' ? Object.keys(o).join(' / ') : String(o);
83
- console.error(`${where}缺少 name(收到键:${got || '(无)'})`);
84
- console.error(' 正确写法: {"name":"待入职","valueKey":"1"}(valueKey 省略则由平台生成)');
85
- process.exit(1);
86
- }
87
- const vk = o.valueKey;
88
- if (vk !== undefined && vk !== null && String(vk) !== '' && !/^\d+$/.test(String(vk))) {
89
- console.error(`${where}的 valueKey「${vk}」必须是数字(平台规范,下游按数字引用)`);
90
- process.exit(1);
91
- }
92
- });
93
- }
32
+ export { FIELD_TYPE_CHOICES };
94
33
  /** 系统字段的中文名 → 物理字段 id(form read 实测的平台自带字段)。建表时字段与此完全重名 → 跳过新建、直接复用系统字段。 */
95
34
  const SYSTEM_FIELD_NAME_TO_ID = {
96
35
  '标题': 'name',
@@ -139,11 +78,21 @@ async function peekViaGetFields(client, baseUrl, objId, fieldId, auth) {
139
78
  * 单独抽出来是为了让「读不到 → 中止」这条路清晰:**失败要能被调用方看见**,
140
79
  * 不能吞掉之后改走别的接口重建一个定义。
141
80
  */
142
- async function fetchFieldPageRecords(client, baseUrl, appId, formId) {
81
+ /**
82
+ * 读表单的字段记录。
83
+ *
84
+ * ⚠ **主表与明细表是两套结果,不带 `subFormId` 时只返主表字段。**
85
+ * 明细字段必须显式带 `subFormId` 才查得到 —— 少了这个参数,刚建好的明细字段会「查不到」,
86
+ * 表现为「字段已建立,但读回字段定义失败」。
87
+ */
88
+ async function fetchFieldPageRecords(client, baseUrl, appId, formId, subFormId) {
143
89
  const r = await client.post(`${baseUrl}/api/ebuilder/form/form/field/manage/getFormFieldPage?apid=${appId}`, {
144
90
  pageNo: 1, pageSize: 500, module: 'ebuilderform',
145
91
  customParam: { appId },
146
- formFieldSearchEntity: { isDelete: 0, status: 'enable', formId },
92
+ formFieldSearchEntity: {
93
+ isDelete: 0, status: 'enable', formId,
94
+ ...(subFormId ? { subFormId: String(subFormId) } : {}),
95
+ },
147
96
  }, { ebbusinessid: formId });
148
97
  return r?.data?.pageDatas?.result || r?.data?.data?.records || r?.data?.data || [];
149
98
  }
@@ -194,8 +143,7 @@ export function registerFormCommands(program) {
194
143
  if (cmdOpts?.appId) {
195
144
  try {
196
145
  // Get formId from appId
197
- const listData = await client.get(`${baseUrl}/api/bs/ebuilder/form/obj/getList/${cmdOpts.appId}?apid=${cmdOpts.appId}`, { ebbusinessid: cmdOpts.appId });
198
- const formInfo = (listData?.data || []).find((f) => f.id === objId);
146
+ const formInfo = await resolveFormRecord(client, baseUrl, cmdOpts.appId, objId);
199
147
  formId = formInfo?.formId || '';
200
148
  if (formId) {
201
149
  // Fetch options for Select/RadioBox/CheckBox fields (skip system fields with string IDs)
@@ -342,6 +290,8 @@ export function registerFormCommands(program) {
342
290
  process.exit(1);
343
291
  }
344
292
  const forms = data.data || [];
293
+ // obj/getList 不含三方表单(专项应用)—— 补上。标准应用没有三方表单,等于空转。
294
+ await appendThirdPartyForms(client, baseUrl, appId, forms);
345
295
  if (cmdOpts?.out) {
346
296
  writeFileSync(cmdOpts.out, JSON.stringify(forms, null, 2), 'utf-8');
347
297
  console.error(`已写入: ${cmdOpts.out}`);
@@ -405,22 +355,8 @@ export function registerFormCommands(program) {
405
355
  // componentKey 大小写归一(canonComponentKey):用户/示例常写 "Textarea"/"Ebuilder" 等,
406
356
  // 但 storageFieldType 与各 `=== 'TextArea'` 分支按 canonical 大小写匹配,不归一会导致 type 存成
407
357
  // 小写 "Textarea"(FLOW 渲染异常)、TextArea 分支(content/maxLen)漏跑。
408
- // 归一化 + 就地校验(类型名/选项键名)。顶层与明细子字段共用,避免两边校验不一致。
409
- const normalizeFieldDef = (raw) => {
410
- const out = { ...raw };
411
- if (!out.componentKey && out.fieldType)
412
- out.componentKey = out.fieldType;
413
- if (out.componentKey)
414
- out.componentKey = canonComponentKey(out.componentKey);
415
- const label = out.fieldName || out.title || '(未命名)';
416
- assertFieldType(out.componentKey || '', `字段「${label}」`);
417
- if (Array.isArray(out.options) && !out.fieldOptions) {
418
- out.fieldOptions = out.options.map((o) => typeof o === 'string' ? { name: o } : o);
419
- }
420
- if (Array.isArray(out.fieldOptions))
421
- assertFieldOptions(label, out.fieldOptions);
422
- return out;
423
- };
358
+ // 归一化 + 就地校验(类型名/选项键名)见 services/form-field-validate.ts 的 normalizeFieldDef ——
359
+ // 顶层与明细子字段共用它,且与「专项建表」共用同一份实现。
424
360
  fieldDefs = fieldDefs.map((f) => {
425
361
  const out = normalizeFieldDef(f);
426
362
  // DetailTable 子字段同样归一化:之前只转顶层,子表 Select 用 options 简写时
@@ -638,42 +574,10 @@ export function registerFormCommands(program) {
638
574
  else {
639
575
  console.error(` 数据库表名: ${result.tableName}`);
640
576
  }
641
- // Create fields and layout
642
- const fieldDefsForStandard = fieldDefs.map(f => ({
643
- componentKey: f.componentKey, fieldName: f.fieldName,
644
- required: f.required, readOnly: f.readOnly, unique: f.unique,
645
- hidden: f.hidden, formFields: f.formFields,
646
- enableSubDetail: f.enableSubDetail, defaultExpandRow: f.defaultExpandRow,
647
- subDetailConfig: f.subDetailConfig,
648
- precision: f.precision, pointSize: f.pointSize,
649
- percent: f.percent, defaultValue: f.defaultValue,
650
- group: f.group, multiple: f.multiple,
651
- singleRow: f.singleRow,
652
- // 文本最大长度(Text 缺省 100、TextArea 缺省 4000)+ 日期精度 format(Date/DateTime)需透传,
653
- // 否则 fields.json 标注的 maxLen/format 被丢(bug:长度默认 100 不生效、日期精度丢失)。
654
- maxLen: f.maxLen, format: f.format,
655
- // 字段编码 dataKey / 数据库列名 columnName —— 两者是两个东西,可分别指定。
656
- // 这里是**白名单透传**,漏一项就会静默丢(maxLen/format 历史上就漏过)。
657
- dataKey: f.dataKey, columnName: f.columnName,
658
- }));
659
- const fieldResult = await addInitialFieldsToCustomPhysicalForm(client, baseUrl, appId, objId, formId, fieldDefsForStandard, auth.tenantKey, layoutType, systemOverrides);
660
- fieldGroups = fieldResult.fieldGroups || [];
661
- console.error(` 字段+布局已写入: ${fieldDefsForStandard.length} 个字段`);
662
- const detailTables = fieldResult.detailTables || [];
663
- for (const dt of detailTables) {
664
- console.error(` 明细表: ${dt.definition.fieldName} subFormId: ${dt.subFormId}`);
665
- }
666
- const subDetailService = new FormSubDetailService(client, baseUrl, appId, objId, formId);
667
- for (const dt of detailTables) {
668
- const def = dt.definition;
669
- if (def.enableSubDetail && Array.isArray(def.subDetailConfig) && def.subDetailConfig.length > 0) {
670
- await subDetailService.set(def.fieldName, def.subDetailConfig, Boolean(def.defaultExpandRow));
671
- console.error(` 二级明细已配置: ${def.fieldName} → ${def.subDetailConfig.map((x) => x.subForm).join(', ')}`);
672
- }
673
- else if (def.enableSubDetail) {
674
- throw new Error(`明细表 ${def.fieldName} 已声明 enableSubDetail,但 subDetailConfig 为空`);
675
- }
676
- }
577
+ // Create fields and layout —— 白名单映射 + 明细/二级明细那一整步已抽到
578
+ // services/form-create.ts 的 applyInitialFieldsToNewForm,「专项建表」共用同一条链。
579
+ const fieldResult = await applyInitialFieldsToNewForm(client, baseUrl, appId, objId, formId, fieldDefs, { tenantKey: auth.tenantKey, layoutType, systemOverrides });
580
+ fieldGroups = fieldResult.fieldGroups;
677
581
  }
678
582
  catch (e) {
679
583
  console.error(`创建失败: ${e.message}`);
@@ -717,8 +621,7 @@ export function registerFormCommands(program) {
717
621
  let resolvedFormId = formId || '';
718
622
  const resolveFormId = async () => {
719
623
  if (!resolvedFormId) {
720
- const formList = await client.get(`${baseUrl}/api/bs/ebuilder/form/obj/getList/${appId}?apid=${appId}`, { ebbusinessid: appId });
721
- const formInfo = (formList?.data || []).find((f) => f.id === objId);
624
+ const formInfo = await resolveFormRecord(client, baseUrl, appId, objId);
722
625
  resolvedFormId = formInfo?.formId || '';
723
626
  }
724
627
  return resolvedFormId;
@@ -738,6 +641,8 @@ export function registerFormCommands(program) {
738
641
  // Resolve target form names and field IDs from sformId
739
642
  const formList = await client.get(`${baseUrl}/api/bs/ebuilder/form/obj/getList/${appId}?apid=${appId}`, { ebbusinessid: appId });
740
643
  const allForms = formList?.data || [];
644
+ // 目标表可能是三方表单(专项应用),它不在 getList 里 —— 补上
645
+ await appendThirdPartyForms(client, baseUrl, appId, allForms);
741
646
  const formNameById = new Map();
742
647
  for (const fm of allForms) {
743
648
  formNameById.set(fm.id, fm.formName || fm.name || fm.id);
@@ -772,8 +677,7 @@ export function registerFormCommands(program) {
772
677
  };
773
678
  await resolveFormId();
774
679
  if (!resolvedFormId) {
775
- const formList = await client.get(`${baseUrl}/api/bs/ebuilder/form/obj/getList/${appId}?apid=${appId}`, { ebbusinessid: appId });
776
- const formInfo = (formList?.data || []).find((f) => f.id === objId);
680
+ const formInfo = await resolveFormRecord(client, baseUrl, appId, objId);
777
681
  resolvedFormId = formInfo?.formId || '';
778
682
  }
779
683
  // Use getFormFieldPage to get full field info including columnName
@@ -988,6 +892,8 @@ export function registerFormCommands(program) {
988
892
  try {
989
893
  const formList = await client.get(`${baseUrl}/api/bs/ebuilder/form/obj/getList/${appId}?apid=${appId}`, { ebbusinessid: appId });
990
894
  allForms = formList?.data || [];
895
+ // 目标可能是三方表单(专项应用),它不在 getList 里 —— 补上
896
+ await appendThirdPartyForms(client, baseUrl, appId, allForms);
991
897
  }
992
898
  catch { /* 查不到则 formName 用 sformId */ }
993
899
  // 读已建字段(拿 fieldId)
@@ -1879,8 +1785,7 @@ export function registerFormCommands(program) {
1879
1785
  process.exit(1);
1880
1786
  }
1881
1787
  // Get formId from appId
1882
- const listData = await client.get(`${baseUrl}/api/bs/ebuilder/form/obj/getList/${appId}?apid=${appId}`, { ebbusinessid: appId });
1883
- const formInfo = (listData?.data || []).find((f) => f.id === objId);
1788
+ const formInfo = await resolveFormRecord(client, baseUrl, appId, objId);
1884
1789
  const formId = formInfo?.formId || '';
1885
1790
  if (!formId) {
1886
1791
  console.error('无法获取 formId,请确认 appId 和 objId 正确');
@@ -1925,8 +1830,7 @@ export function registerFormCommands(program) {
1925
1830
  const client = new ApiClient(auth);
1926
1831
  const baseUrl = auth.getBaseUrl();
1927
1832
  // objId → formId
1928
- const listData = await client.get(`${baseUrl}/api/bs/ebuilder/form/obj/getList/${appId}?apid=${appId}`, { ebbusinessid: appId });
1929
- const formInfo = (listData?.data || []).find((f) => String(f.id) === String(objId));
1833
+ const formInfo = await resolveFormRecord(client, baseUrl, appId, objId);
1930
1834
  const formId = formInfo?.formId || '';
1931
1835
  if (!formId) {
1932
1836
  console.error('无法获取 formId,请确认 objId 正确');
@@ -1973,8 +1877,7 @@ export function registerFormCommands(program) {
1973
1877
  const client = new ApiClient(auth);
1974
1878
  const baseUrl = auth.getBaseUrl();
1975
1879
  // Get formId
1976
- const formList = await client.get(`${baseUrl}/api/bs/ebuilder/form/obj/getList/${appId}?apid=${appId}`, { ebbusinessid: appId });
1977
- const formInfo = (formList?.data || []).find((f) => f.id === objId);
1880
+ const formInfo = await resolveFormRecord(client, baseUrl, appId, objId);
1978
1881
  const formId = formInfo?.formId || '';
1979
1882
  const formTableId = formInfo?.formTableId || '';
1980
1883
  if (!formId) {
@@ -2003,13 +1906,33 @@ export function registerFormCommands(program) {
2003
1906
  console.error('--options 必须是非空数组');
2004
1907
  process.exit(1);
2005
1908
  }
2006
- // Get current field info for title, dataKey etc.
2007
- let currentField = {};
1909
+ // 读**现状**(title / dataKey / type / labelObj …)—— 这是「读现状 → 改 → 写回」的底。
1910
+ //
1911
+ // ⚠ 这里**不许降级成 `{}`**。以前读失败只在 catch 里吞掉、`currentField` 变成空对象,
1912
+ // 于是 title 写成空串,平台回「字段标题不能为空」—— 报的是标题,真因是**读没成功**,
1913
+ // 用户怎么去补 title 都没用。宁可中止:拿不到现状就写,等于拿一份空对象覆盖整个字段。
1914
+ let currentField = null;
1915
+ let readErr = '';
2008
1916
  try {
2009
1917
  const fData = await client.post(`${baseUrl}/api/ebuilder/form/form/field/manage/getFormField`, { module: 'ebuilderform', formTableField: { formField: { id: fieldId, form: { id: formId } } } }, { ebbusinessid: formId });
2010
- currentField = fData?.data || {};
1918
+ if (fData?.code === 200 && fData?.status !== false && fData?.data)
1919
+ currentField = fData.data;
1920
+ else
1921
+ readErr = fData?.msg || `code=${fData?.code}`;
1922
+ }
1923
+ catch (e) {
1924
+ readErr = e.message;
1925
+ }
1926
+ if (!currentField || !Object.keys(currentField).length) {
1927
+ console.error(`读取字段现状失败(${readErr || '接口未返回字段'}),已中止、未改动任何东西。`);
1928
+ console.error(' 本命令是「读现状 → 改 → 写回」,读不到现状就写会把字段的其余属性清空。');
1929
+ process.exit(1);
1930
+ }
1931
+ if (!String(currentField.title ?? '').trim()) {
1932
+ console.error(`读回的字段没有 title(fieldId=${fieldId}),已中止、未改动任何东西。`);
1933
+ console.error(' 标题在平台里存的是多语言标签 id(labelObj.title);请先用 `form read` 确认这个字段的状态。');
1934
+ process.exit(1);
2011
1935
  }
2012
- catch { /* best effort */ }
2013
1936
  // Build options with defOption
2014
1937
  const { randomUUID } = await import('crypto');
2015
1938
  // valueKey=用户自定义选项值,必须是纯数字(平台规范+下游引用按数字处理),非数字直接报错;
@@ -2061,7 +1984,7 @@ export function registerFormCommands(program) {
2061
1984
  hideScore: true,
2062
1985
  groupId: currentField.groupId || '-1',
2063
1986
  hasDisplayFormat: currentField.hasDisplayFormat ?? false,
2064
- title: currentField.title || '',
1987
+ title: currentField.title,
2065
1988
  dataCount: currentField.dataCount || 1,
2066
1989
  dataKey: currentField.dataKey || '',
2067
1990
  systemField: currentField.systemField ?? true,
@@ -2139,8 +2062,7 @@ export function registerFormCommands(program) {
2139
2062
  const client = new ApiClient(auth);
2140
2063
  const baseUrl = auth.getBaseUrl();
2141
2064
  // Get formId from objId
2142
- const formList = await client.get(`${baseUrl}/api/bs/ebuilder/form/obj/getList/${appId}?apid=${appId}`, { ebbusinessid: appId });
2143
- const formInfo = (formList?.data || []).find((f) => f.id === objId);
2065
+ const formInfo = await resolveFormRecord(client, baseUrl, appId, objId);
2144
2066
  const formId = formInfo?.formId || '';
2145
2067
  const formTableId = formInfo?.formTableId || '';
2146
2068
  if (!formId) {
@@ -2208,11 +2130,13 @@ export function registerFormCommands(program) {
2208
2130
  // 把错误讲清楚,让用户自己决定要不要 --reinit。
2209
2131
  if (!cmdOpts.reinit) {
2210
2132
  try {
2211
- const fieldDef = (await fetchFieldPageRecords(client, baseUrl, appId, formId))
2133
+ // ⚠ 明细字段**必须带 subFormId** 才查得到(不带只返主表字段)——
2134
+ // 少了它,刚建好的明细字段会「查不到」,报成「读回字段定义失败」。
2135
+ const fieldDef = (await fetchFieldPageRecords(client, baseUrl, appId, formId, cmdOpts.subFormId))
2212
2136
  .find((f) => String(f.fieldId) === String(fieldId));
2213
2137
  if (!fieldDef)
2214
2138
  throw new Error('字段已建立,但读回字段定义失败');
2215
- const placed = await insertFieldIntoLayout(client, baseUrl, formId, {
2139
+ const insertArgs = {
2216
2140
  fieldId, labelFieldId, title: title || fieldName, fieldDef,
2217
2141
  subFormId: cmdOpts.subFormId,
2218
2142
  // 对齐 initLayout 那条路对新字段的默认:可编辑、显示。
@@ -2222,12 +2146,38 @@ export function registerFormCommands(program) {
2222
2146
  required: cmdOpts.required === true,
2223
2147
  ...(cmdOpts.singleRow === true ? { singleRow: true } : {}),
2224
2148
  },
2225
- });
2149
+ };
2150
+ let placed;
2151
+ try {
2152
+ placed = await insertFieldIntoLayout(client, baseUrl, formId, insertArgs);
2153
+ }
2154
+ catch (e) {
2155
+ // 本表还没有这个类型 → 去要一份**平台按该字段生成的模板**再试一次。
2156
+ // 只在真缺模板时才多打这一次接口,正常路径零开销。
2157
+ if (e?.code !== NO_FIELD_TYPE_TEMPLATE)
2158
+ throw e;
2159
+ const templates = await fetchFieldTypeTemplates(client, baseUrl, appId, formId);
2160
+ // 主表字段按 fieldId 命中;明细表字段拿不到(接口不返回明细),
2161
+ // 退而借**主表同类型**的那一份 —— 模板是按类型而不是按作用域的
2162
+ // (`config.flow` 是空对象、没有 `excel`),作用域由插入方自己写。
2163
+ const tpl = templates.get(String(fieldId))?.comp
2164
+ ?? findTemplateByType(templates, String(fieldDef.componentKey ?? fieldType))?.comp;
2165
+ if (!tpl)
2166
+ throw e;
2167
+ console.error(' 本表还没有这个类型,改用平台给的字段模板合成组件...');
2168
+ placed = await insertFieldIntoLayout(client, baseUrl, formId, { ...insertArgs, fieldTypeTemplate: tpl });
2169
+ }
2226
2170
  console.error(` 布局已更新: excel[${placed.row},${placed.col}](增量插入,未重建)`);
2171
+ // 网格补格这类「改成功了但不干净」的情况要让人看见,不能闷掉。
2172
+ for (const w of placed.warnings)
2173
+ console.error(` ⚠ ${w}`);
2227
2174
  }
2228
2175
  catch (e) {
2229
2176
  console.error(` 布局更新失败: ${e.message}`);
2230
- console.error(' 字段本身已建立,但没进布局。按上面那条修好重跑本命令即可(重跑不会重复建字段)。');
2177
+ // ⚠ 别写「重跑不会重复建字段」——字段已经建出来了,重跑这条命令会再次去建同一个
2178
+ // 字段,撞库列重名而失败。要说清是「先删掉再重跑」。
2179
+ console.error(' 字段本身已建立,但没进布局。要重试请先 `form delete-field` 删掉这个字段再跑,');
2180
+ console.error(' 或直接按上面那条修好布局(字段不用重建)。');
2231
2181
  }
2232
2182
  }
2233
2183
  else {
@@ -2352,8 +2302,11 @@ export function registerFormCommands(program) {
2352
2302
  // Get formId — try getList first, fall back to searching by fieldId
2353
2303
  let formId = '';
2354
2304
  let resolvedObjId = objId;
2305
+ // 这份列表下面还要再用一次(formId → objId 的反查),所以保留不动;
2306
+ // formInfo 走共用解析 —— obj/getList 命不中时按 id 直取,覆盖三方表单。
2355
2307
  const formList = await client.get(`${baseUrl}/api/bs/ebuilder/form/obj/getList/${appId}?apid=${appId}`, { ebbusinessid: appId });
2356
- const formInfo = (formList?.data || []).find((f) => String(f.id) === String(objId));
2308
+ const formInfo = (formList?.data || []).find((f) => String(f.id) === String(objId))
2309
+ || await resolveFormRecord(client, baseUrl, appId, objId);
2357
2310
  formId = formInfo?.formId || '';
2358
2311
  // If not found in getList (e.g. workflow sub-table objId), search by fieldId
2359
2312
  if (!formId) {
@@ -2370,6 +2323,12 @@ export function registerFormCommands(program) {
2370
2323
  const matchForm = (formList?.data || []).find((f) => f.formId === formId);
2371
2324
  if (matchForm)
2372
2325
  resolvedObjId = matchForm.id;
2326
+ else {
2327
+ // 三方表单不在 getList 里 —— 按 formId 反查补一次
2328
+ const byFormId = await resolveObjIdByFormId(client, baseUrl, appId, formId);
2329
+ if (byFormId)
2330
+ resolvedObjId = byFormId;
2331
+ }
2373
2332
  }
2374
2333
  }
2375
2334
  if (!formId) {
@@ -2586,8 +2545,7 @@ export function registerFormCommands(program) {
2586
2545
  let formName = editOpts.formName || fallbackFormName || '';
2587
2546
  if (!formName) {
2588
2547
  try {
2589
- const tlist = await client.get(`${baseUrl}/api/bs/ebuilder/form/obj/getList/${appId}?apid=${appId}`, { ebbusinessid: appId });
2590
- const tf = (tlist?.data || []).find((f) => String(f.id) === String(sformId));
2548
+ const tf = await resolveFormRecord(client, baseUrl, appId, sformId);
2591
2549
  formName = tf?.formName || tf?.name || sformId;
2592
2550
  }
2593
2551
  catch {
@@ -2783,8 +2741,7 @@ export function registerFormCommands(program) {
2783
2741
  process.exit(1);
2784
2742
  const client = new ApiClient(auth);
2785
2743
  const baseUrl = auth.getBaseUrl();
2786
- const formList = await client.get(`${baseUrl}/api/bs/ebuilder/form/obj/getList/${appId}?apid=${appId}`, { ebbusinessid: appId });
2787
- const formInfo = (formList?.data || []).find((f) => String(f.id) === String(objId));
2744
+ const formInfo = await resolveFormRecord(client, baseUrl, appId, objId);
2788
2745
  const formId = formInfo?.formId || '';
2789
2746
  if (!formId) {
2790
2747
  console.error('无法获取 formId,请确认 objId 和 appId 正确');
@@ -2892,9 +2849,8 @@ export function registerFormCommands(program) {
2892
2849
  // eb 表 layout/field-manage 接口认 formId(≠objId):从应用表单列表按 objId 解析 formId
2893
2850
  let formId = '';
2894
2851
  try {
2895
- const listRes = await client.get(`${baseUrl}/api/bs/ebuilder/form/obj/getList/${encodeURIComponent(appId)}?apid=${encodeURIComponent(appId)}`);
2896
- const hit = (listRes?.data || []).find((f) => String(f.id) === String(objId));
2897
- formId = hit?.formId || '';
2852
+ // 原实现这条请求没带 ebbusinessid 头;走共用解析后会带上(与其余 12 处一致)。
2853
+ formId = await resolveFormId(client, baseUrl, appId, objId);
2898
2854
  }
2899
2855
  catch { /* 忽略,下面报错 */ }
2900
2856
  if (!formId)