weaver-e10-builder 1.2.5 → 1.5.1

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 (201) hide show
  1. package/assets/org-employee-template.xlsx +0 -0
  2. package/dist/commands/form-button.d.ts.map +1 -1
  3. package/dist/commands/form-button.js +28 -5
  4. package/dist/commands/form-button.js.map +1 -1
  5. package/dist/commands/form-field-linkage.d.ts.map +1 -1
  6. package/dist/commands/form-field-linkage.js +27 -6
  7. package/dist/commands/form-field-linkage.js.map +1 -1
  8. package/dist/commands/form-formula-linkage.d.ts +3 -0
  9. package/dist/commands/form-formula-linkage.d.ts.map +1 -0
  10. package/dist/commands/form-formula-linkage.js +434 -0
  11. package/dist/commands/form-formula-linkage.js.map +1 -0
  12. package/dist/commands/form-formula.d.ts.map +1 -1
  13. package/dist/commands/form-formula.js +22 -3
  14. package/dist/commands/form-formula.js.map +1 -1
  15. package/dist/commands/form-list-click.d.ts.map +1 -1
  16. package/dist/commands/form-list-click.js +2 -1
  17. package/dist/commands/form-list-click.js.map +1 -1
  18. package/dist/commands/form-native.d.ts.map +1 -1
  19. package/dist/commands/form-native.js +11 -3
  20. package/dist/commands/form-native.js.map +1 -1
  21. package/dist/commands/form.d.ts.map +1 -1
  22. package/dist/commands/form.js +247 -173
  23. package/dist/commands/form.js.map +1 -1
  24. package/dist/commands/matrix.d.ts.map +1 -1
  25. package/dist/commands/matrix.js +175 -36
  26. package/dist/commands/matrix.js.map +1 -1
  27. package/dist/commands/org.d.ts +10 -0
  28. package/dist/commands/org.d.ts.map +1 -0
  29. package/dist/commands/org.js +278 -0
  30. package/dist/commands/org.js.map +1 -0
  31. package/dist/commands/role.d.ts.map +1 -1
  32. package/dist/commands/role.js +106 -62
  33. package/dist/commands/role.js.map +1 -1
  34. package/dist/commands/wf-form.d.ts.map +1 -1
  35. package/dist/commands/wf-form.js +501 -26
  36. package/dist/commands/wf-form.js.map +1 -1
  37. package/dist/commands/workflow-auth-resp.d.ts.map +1 -1
  38. package/dist/commands/workflow-auth-resp.js +53 -5
  39. package/dist/commands/workflow-auth-resp.js.map +1 -1
  40. package/dist/commands/workflow-create-auth-resp.d.ts.map +1 -1
  41. package/dist/commands/workflow-create-auth-resp.js +5 -1
  42. package/dist/commands/workflow-create-auth-resp.js.map +1 -1
  43. package/dist/commands/workflow.d.ts.map +1 -1
  44. package/dist/commands/workflow.js +78 -9
  45. package/dist/commands/workflow.js.map +1 -1
  46. package/dist/index.js +4 -0
  47. package/dist/index.js.map +1 -1
  48. package/dist/services/action-detail.d.ts.map +1 -1
  49. package/dist/services/action-detail.js +5 -4
  50. package/dist/services/action-detail.js.map +1 -1
  51. package/dist/services/actionflow/components/action_eb_update_compiler.d.ts.map +1 -1
  52. package/dist/services/actionflow/components/action_eb_update_compiler.js +22 -7
  53. package/dist/services/actionflow/components/action_eb_update_compiler.js.map +1 -1
  54. package/dist/services/actionflow/components/approve_create.d.ts.map +1 -1
  55. package/dist/services/actionflow/components/approve_create.js +6 -3
  56. package/dist/services/actionflow/components/approve_create.js.map +1 -1
  57. package/dist/services/actionflow/components/custom_set_form.d.ts.map +1 -1
  58. package/dist/services/actionflow/components/custom_set_form.js +35 -1
  59. package/dist/services/actionflow/components/custom_set_form.js.map +1 -1
  60. package/dist/services/actionflow/components/custom_trigger.d.ts.map +1 -1
  61. package/dist/services/actionflow/components/custom_trigger.js +4 -1
  62. package/dist/services/actionflow/components/custom_trigger.js.map +1 -1
  63. package/dist/services/actionflow/components/dml_execute.d.ts.map +1 -1
  64. package/dist/services/actionflow/components/dml_execute.js +7 -3
  65. package/dist/services/actionflow/components/dml_execute.js.map +1 -1
  66. package/dist/services/actionflow/components/document_actions.d.ts.map +1 -1
  67. package/dist/services/actionflow/components/document_actions.js +7 -3
  68. package/dist/services/actionflow/components/document_actions.js.map +1 -1
  69. package/dist/services/actionflow/components/hrm_actions.d.ts.map +1 -1
  70. package/dist/services/actionflow/components/hrm_actions.js +7 -3
  71. package/dist/services/actionflow/components/hrm_actions.js.map +1 -1
  72. package/dist/services/actionflow/components/multiple_execution.d.ts.map +1 -1
  73. package/dist/services/actionflow/components/multiple_execution.js +14 -5
  74. package/dist/services/actionflow/components/multiple_execution.js.map +1 -1
  75. package/dist/services/actionflow/components/output.js +1 -1
  76. package/dist/services/actionflow/components/output.js.map +1 -1
  77. package/dist/services/actionflow/components/send_message.js +2 -2
  78. package/dist/services/actionflow/components/send_message.js.map +1 -1
  79. package/dist/services/actionflow/components/workflow_actions.d.ts.map +1 -1
  80. package/dist/services/actionflow/components/workflow_actions.js +7 -3
  81. package/dist/services/actionflow/components/workflow_actions.js.map +1 -1
  82. package/dist/services/actionflow/context_preflight.d.ts.map +1 -1
  83. package/dist/services/actionflow/context_preflight.js +12 -2
  84. package/dist/services/actionflow/context_preflight.js.map +1 -1
  85. package/dist/services/actionflow/context_provider.d.ts.map +1 -1
  86. package/dist/services/actionflow/context_provider.js +21 -2
  87. package/dist/services/actionflow/context_provider.js.map +1 -1
  88. package/dist/services/actionflow/custom_trigger_context.d.ts +10 -0
  89. package/dist/services/actionflow/custom_trigger_context.d.ts.map +1 -0
  90. package/dist/services/actionflow/custom_trigger_context.js +46 -0
  91. package/dist/services/actionflow/custom_trigger_context.js.map +1 -0
  92. package/dist/services/actionflow/filter_rules.js +2 -2
  93. package/dist/services/actionflow/filter_rules.js.map +1 -1
  94. package/dist/services/actionflow/formula_service.d.ts +9 -1
  95. package/dist/services/actionflow/formula_service.d.ts.map +1 -1
  96. package/dist/services/actionflow/formula_service.js +59 -21
  97. package/dist/services/actionflow/formula_service.js.map +1 -1
  98. package/dist/services/actionflow/id_generator.d.ts +2 -0
  99. package/dist/services/actionflow/id_generator.d.ts.map +1 -1
  100. package/dist/services/actionflow/id_generator.js +13 -0
  101. package/dist/services/actionflow/id_generator.js.map +1 -1
  102. package/dist/services/actionflow/preflight_service.d.ts.map +1 -1
  103. package/dist/services/actionflow/preflight_service.js +6 -1
  104. package/dist/services/actionflow/preflight_service.js.map +1 -1
  105. package/dist/services/actionflow/workflow_compiler.d.ts.map +1 -1
  106. package/dist/services/actionflow/workflow_compiler.js +89 -35
  107. package/dist/services/actionflow/workflow_compiler.js.map +1 -1
  108. package/dist/services/auth-resp-condition.d.ts +35 -0
  109. package/dist/services/auth-resp-condition.d.ts.map +1 -0
  110. package/dist/services/auth-resp-condition.js +111 -0
  111. package/dist/services/auth-resp-condition.js.map +1 -0
  112. package/dist/services/condition-rules.d.ts.map +1 -1
  113. package/dist/services/condition-rules.js +4 -1
  114. package/dist/services/condition-rules.js.map +1 -1
  115. package/dist/services/esb-interface/document-parser.d.ts.map +1 -1
  116. package/dist/services/esb-interface/document-parser.js +62 -5
  117. package/dist/services/esb-interface/document-parser.js.map +1 -1
  118. package/dist/services/form-association.d.ts +19 -0
  119. package/dist/services/form-association.d.ts.map +1 -1
  120. package/dist/services/form-association.js +64 -0
  121. package/dist/services/form-association.js.map +1 -1
  122. package/dist/services/form-button-action.d.ts +7 -0
  123. package/dist/services/form-button-action.d.ts.map +1 -1
  124. package/dist/services/form-button-action.js +11 -1
  125. package/dist/services/form-button-action.js.map +1 -1
  126. package/dist/services/form-create.d.ts +4 -0
  127. package/dist/services/form-create.d.ts.map +1 -1
  128. package/dist/services/form-create.js +18 -12
  129. package/dist/services/form-create.js.map +1 -1
  130. package/dist/services/form-field-patch.d.ts +3 -1
  131. package/dist/services/form-field-patch.d.ts.map +1 -1
  132. package/dist/services/form-field-patch.js +23 -9
  133. package/dist/services/form-field-patch.js.map +1 -1
  134. package/dist/services/form-formula-linkage.d.ts +349 -0
  135. package/dist/services/form-formula-linkage.d.ts.map +1 -0
  136. package/dist/services/form-formula-linkage.js +546 -0
  137. package/dist/services/form-formula-linkage.js.map +1 -0
  138. package/dist/services/form-layout-insert.d.ts +84 -0
  139. package/dist/services/form-layout-insert.d.ts.map +1 -0
  140. package/dist/services/form-layout-insert.js +244 -0
  141. package/dist/services/form-layout-insert.js.map +1 -0
  142. package/dist/services/form-linkage-condition.d.ts +159 -0
  143. package/dist/services/form-linkage-condition.d.ts.map +1 -0
  144. package/dist/services/form-linkage-condition.js +307 -0
  145. package/dist/services/form-linkage-condition.js.map +1 -0
  146. package/dist/services/formula-detail.d.ts +98 -6
  147. package/dist/services/formula-detail.d.ts.map +1 -1
  148. package/dist/services/formula-detail.js +208 -26
  149. package/dist/services/formula-detail.js.map +1 -1
  150. package/dist/services/matrix-rows.d.ts +43 -0
  151. package/dist/services/matrix-rows.d.ts.map +1 -0
  152. package/dist/services/matrix-rows.js +104 -0
  153. package/dist/services/matrix-rows.js.map +1 -0
  154. package/dist/services/org-department.d.ts +114 -0
  155. package/dist/services/org-department.d.ts.map +1 -0
  156. package/dist/services/org-department.js +209 -0
  157. package/dist/services/org-department.js.map +1 -0
  158. package/dist/services/org-employee.d.ts +87 -0
  159. package/dist/services/org-employee.d.ts.map +1 -0
  160. package/dist/services/org-employee.js +219 -0
  161. package/dist/services/org-employee.js.map +1 -0
  162. package/dist/services/role-member.d.ts +99 -0
  163. package/dist/services/role-member.d.ts.map +1 -0
  164. package/dist/services/role-member.js +206 -0
  165. package/dist/services/role-member.js.map +1 -0
  166. package/dist/services/workflow-info.d.ts +83 -0
  167. package/dist/services/workflow-info.d.ts.map +1 -0
  168. package/dist/services/workflow-info.js +97 -0
  169. package/dist/services/workflow-info.js.map +1 -0
  170. package/dist/services/workflow-odoc-setting.d.ts +1 -1
  171. package/dist/services/workflow-odoc-setting.d.ts.map +1 -1
  172. package/dist/services/workflow-odoc-setting.js +15 -6
  173. package/dist/services/workflow-odoc-setting.js.map +1 -1
  174. package/dist/utils/column-name.d.ts +24 -0
  175. package/dist/utils/column-name.d.ts.map +1 -1
  176. package/dist/utils/column-name.js +16 -0
  177. package/dist/utils/column-name.js.map +1 -1
  178. package/ecodecmd/package.json +1 -2
  179. package/ecodecmd/src/loaddep.js +19 -9
  180. package/package.json +4 -4
  181. package/skill-docs/SKILL.md +13 -9
  182. package/skill-docs/_catalog.md +85 -81
  183. package/skill-docs/actionflows/SKILL.md +173 -170
  184. package/skill-docs/actionflows/references/components/logic/Output.md +1 -1
  185. package/skill-docs/actionflows/references/components/trigger/CustomTrigger.md +32 -0
  186. package/skill-docs/actionflows/references/components/workflow/ApproveCreate.md +1 -1
  187. package/skill-docs/references/common/guide-plan-template.md +9 -3
  188. package/skill-docs/references/common/ref-cli-commands.md +47 -22
  189. package/skill-docs/references/esb-interface/guide-import.md +4 -1
  190. package/skill-docs/references/form/guide-create-form.md +13 -0
  191. package/skill-docs/references/form/guide-field-linkage.md +2 -0
  192. package/skill-docs/references/form/guide-form-formula-functions.md +36 -0
  193. package/skill-docs/references/form/guide-form-layout-incremental.md +75 -0
  194. package/skill-docs/references/form/guide-form-linkage.md +61 -3
  195. package/skill-docs/references/form/guide-formula-linkage.md +250 -0
  196. package/skill-docs/references/form/ref-form-fields.md +6 -0
  197. package/skill-docs/references/hrm/guide-matrix.md +79 -16
  198. package/skill-docs/references/hrm/guide-org-employee.md +56 -0
  199. package/skill-docs/references/workflow/guide-workflow-auth-resp.md +48 -22
  200. package/skill-docs/references/workflow/guide-workflow-odoc-setting.md +248 -0
  201. package/skill-docs/references/workflow/guide-workflow-odoc.md +23 -51
@@ -1,14 +1,17 @@
1
1
  import { writeFileSync, existsSync, readFileSync } from 'fs';
2
2
  import { AuthSession } from '../core/auth.js';
3
3
  import { ApiClient } from '../core/client.js';
4
- import { toColumnName, toTableName } from '../utils/column-name.js';
5
- import { createCustomPhysicalForm, addInitialFieldsToCustomPhysicalForm, placeholderFromFormat, reapplyGroupLayout } from '../services/form-create.js';
4
+ import { toColumnName, toTableName, resolveFieldKeys } from '../utils/column-name.js';
5
+ import { createCustomPhysicalForm, addInitialFieldsToCustomPhysicalForm, placeholderFromFormat, reapplyGroupLayout, queryFormStorageSetting } from '../services/form-create.js';
6
6
  import { FormSubDetailService, resolveDetailTable } from '../services/form-sub-detail.js';
7
7
  import { RICH_TYPE_MAP, getRichType, richIdUnits, BROWSER_KINDS } from '../services/rich-types.js';
8
8
  import { dropFieldComponents, prepareLayoutForSave } from '../services/form-field-drop.js';
9
9
  import { describeEditBaseFailure, resolveEditBase as resolveEditBasePure } from '../services/form-edit-base.js';
10
10
  import { applyFieldPatches, platformFieldShape } from '../services/form-field-patch.js';
11
11
  import { ensureDisplayColumns } from '../services/form-ebuilder-columns.js';
12
+ import { insertFieldIntoLayout, patchFieldLayout } from '../services/form-layout-insert.js';
13
+ import { buildLinkageAddPayload, addLinkage, resolveEffect, readLinkageConfig } from '../services/form-linkage-condition.js';
14
+ import { createMultilangLabel } from '../services/form-i18n-label.js';
12
15
  import { buildFormulaParameters, defaultReturnType } from '../services/formula-detail.js';
13
16
  import { registerOptionTemplateCommand } from './form-option-template.js';
14
17
  /**
@@ -20,7 +23,7 @@ import { registerOptionTemplateCommand } from './form-option-template.js';
20
23
  const CK_CANON = {
21
24
  textarea: 'TextArea', ebuilder: 'EBuilder', datetime: 'DateTime',
22
25
  idcard: 'IDCard', mobile: 'Mobile', email: 'Email', phone: 'Phone',
23
- employee: 'Employee', department: 'Department', location: 'Location',
26
+ employee: 'Employee', department: 'Department', subcompany: 'SubCompany', location: 'Location',
24
27
  switch: 'Switch', select: 'Select', checkbox: 'CheckBox', radiobox: 'RadioBox',
25
28
  money: 'Money', number: 'Number', date: 'Date', text: 'Text',
26
29
  daterange: 'DateRange', detailtable: 'DetailTable', city: 'City',
@@ -354,6 +357,8 @@ export function registerFormCommands(program) {
354
357
  + 'valueKey 须为数字、省略则平台生成)')
355
358
  .option('--menu', '建表后添加台账菜单(默认不加;漏加可用 `e10-cli menu add-table` 补)')
356
359
  .option('--layout <type>', '表单布局类型:FLOW(流式)/EXCEL(表格,缺省)。布局类型建表后不可切换', 'EXCEL')
360
+ .option('--table-name <name>', '自定义数据库表名。平台前缀(如 uf_)由 CLI 自动补,传 uf_xxx 或只传自定义部分都可以;'
361
+ + '须字母开头、只含字母数字下划线。缺省按表单名拼音首字母 + 随机数生成')
357
362
  .action(async (appId, name, cmdOpts) => {
358
363
  const opts = program.opts();
359
364
  const auth = await AuthSession.fromOptions(opts);
@@ -560,8 +565,10 @@ export function registerFormCommands(program) {
560
565
  if (f.formName)
561
566
  field.formName = f.formName;
562
567
  }
563
- field.dataKey = toColumnName(f.fieldName);
564
- field.columnName = field.dataKey;
568
+ // fields.json 可分别指定 dataKey / columnName;默认同值(都是拼音首字母)
569
+ const fKeys = resolveFieldKeys(f.fieldName, f);
570
+ field.dataKey = fKeys.dataKey;
571
+ field.columnName = fKeys.columnName;
565
572
  formFields.push(field);
566
573
  // DetailTable occupies 2 rows of layout space
567
574
  rowIdx += isDetail ? 2 : 1;
@@ -592,7 +599,17 @@ export function registerFormCommands(program) {
592
599
  formValues: { formName: name, formTitle: name, tableName, tableType: 'physical' },
593
600
  };
594
601
  // Use standard creation flow for controllable table names
595
- const tableSuffix = toTableName(name);
602
+ // --table-name 指定物理表名(uf_ 前缀由服务层 resolveCustomPhysicalTableName 补);缺省按表单名生成
603
+ const customTableName = cmdOpts.tableName?.trim();
604
+ if (customTableName) {
605
+ const setting = await queryFormStorageSetting(client, baseUrl, appId);
606
+ if (!setting.storageSettings.includes('physical')) {
607
+ console.error(`该应用未启用物理表(storageSettings=${setting.storageSettings.join('/') || '空'}),`
608
+ + '没有独立的数据库表名,--table-name 不适用');
609
+ process.exit(1);
610
+ }
611
+ }
612
+ const requestedTableName = customTableName || toTableName(name);
596
613
  const layoutType = String(cmdOpts.layout || 'EXCEL').toUpperCase();
597
614
  if (layoutType !== 'FLOW' && layoutType !== 'EXCEL') {
598
615
  console.error(`--layout 只支持 FLOW(流式) 或 EXCEL(表格),收到: ${cmdOpts.layout}`);
@@ -602,7 +619,7 @@ export function registerFormCommands(program) {
602
619
  let formId = '';
603
620
  let fieldGroups = [];
604
621
  try {
605
- const result = await createCustomPhysicalForm(client, baseUrl, appId, name, tableSuffix, false, layoutType);
622
+ const result = await createCustomPhysicalForm(client, baseUrl, appId, name, requestedTableName, false, layoutType);
606
623
  objId = result.objId;
607
624
  formId = result.formId;
608
625
  console.error(`表单已创建,ID: ${objId}` + (formId ? ` formId: ${formId}` : ''));
@@ -626,6 +643,9 @@ export function registerFormCommands(program) {
626
643
  // 文本最大长度(Text 缺省 100、TextArea 缺省 4000)+ 日期精度 format(Date/DateTime)需透传,
627
644
  // 否则 fields.json 标注的 maxLen/format 被丢(bug:长度默认 100 不生效、日期精度丢失)。
628
645
  maxLen: f.maxLen, format: f.format,
646
+ // 字段编码 dataKey / 数据库列名 columnName —— 两者是两个东西,可分别指定。
647
+ // 这里是**白名单透传**,漏一项就会静默丢(maxLen/format 历史上就漏过)。
648
+ dataKey: f.dataKey, columnName: f.columnName,
629
649
  }));
630
650
  const fieldResult = await addInitialFieldsToCustomPhysicalForm(client, baseUrl, appId, objId, formId, fieldDefsForStandard, auth.tenantKey, layoutType, systemOverrides);
631
651
  fieldGroups = fieldResult.fieldGroups || [];
@@ -769,7 +789,8 @@ export function registerFormCommands(program) {
769
789
  const colName = df?.columnName || df?.dataKey || '';
770
790
  fieldByName.set(f.fieldName, {
771
791
  fieldId: f.fieldId,
772
- dataKey: colName,
792
+ // dataKey 与 columnName 是两个东西,各自保留真值 —— 回写时不能互相顶替
793
+ dataKey: df?.dataKey || colName,
773
794
  columnName: colName,
774
795
  formTableId: df?.formTableId || formTableId,
775
796
  });
@@ -805,7 +826,7 @@ export function registerFormCommands(program) {
805
826
  openRelateView: false,
806
827
  title: ef.fieldName, type: 'EBuilder',
807
828
  dataCount: 0,
808
- dataKey: finfo.columnName,
829
+ dataKey: finfo.dataKey,
809
830
  systemField: true,
810
831
  columnTypeSwitch: false,
811
832
  appId,
@@ -1041,7 +1062,8 @@ export function registerFormCommands(program) {
1041
1062
  const colName = df?.columnName || df?.dataKey || '';
1042
1063
  rfByName.set(f.fieldName, {
1043
1064
  fieldId: f.fieldId,
1044
- dataKey: colName,
1065
+ // dataKey 与 columnName 是两个东西,各自保留真值
1066
+ dataKey: df?.dataKey || colName,
1045
1067
  columnName: colName,
1046
1068
  formTableId: df?.formTableId || rfFormTableId,
1047
1069
  });
@@ -1066,7 +1088,7 @@ export function registerFormCommands(program) {
1066
1088
  title: rf.fieldName,
1067
1089
  type: rf.componentKey === 'Text' ? 'Text' : rf.componentKey,
1068
1090
  dataCount: 0,
1069
- dataKey: myInfo.columnName,
1091
+ dataKey: myInfo.dataKey,
1070
1092
  systemField: true,
1071
1093
  readonlyTransLink: false,
1072
1094
  columnTypeSwitch: false,
@@ -1111,7 +1133,7 @@ export function registerFormCommands(program) {
1111
1133
  // (batch/save 初建不带 isSingle 到 layout comp)。之前只对 defaultValue:true 的跑 → 没标默认值
1112
1134
  // 的 Employee/Department 漏 isSingle → 平台默认多选(bug:标记单选仍多选)。现对所有 Employee/Department
1113
1135
  // 跑设 isSingle;defaultExtend(默认当前用户/部门)仅 defaultValue:true 时加。
1114
- const personFields = fieldDefs.filter(f => f.componentKey === 'Employee' || f.componentKey === 'Department');
1136
+ const personFields = fieldDefs.filter(f => f.componentKey === 'Employee' || f.componentKey === 'Department' || f.componentKey === 'SubCompany');
1115
1137
  if (personFields.length > 0) {
1116
1138
  const withDefault = personFields.filter(f => f.defaultValue === true);
1117
1139
  console.error(` 配置 ${personFields.length} 个 人员/部门 字段 isSingle${withDefault.length ? `(其中 ${withDefault.length} 个带默认当前用户/部门)` : ''}...`);
@@ -1140,10 +1162,12 @@ export function registerFormCommands(program) {
1140
1162
  const colName = perDetail?.columnName || perDetail?.dataKey || '';
1141
1163
  const colType = perDetail?.columnType || 'BIGINT';
1142
1164
  const isDept = pf.componentKey === 'Department';
1143
- const typeLabel = isDept ? 'Department' : 'Employee';
1144
- const labelCN = isDept ? '部门' : '人员';
1165
+ const isSubCompany = pf.componentKey === 'SubCompany';
1166
+ const typeLabel = isSubCompany ? 'SubCompany' : isDept ? 'Department' : 'Employee';
1167
+ const labelCN = isSubCompany ? '分部' : isDept ? '部门' : '人员';
1145
1168
  const jsonObj = {
1146
1169
  type: typeLabel,
1170
+ ...(isSubCompany ? { componentKey: 'Subcompany', defaultExtend: { type: 'custom', relation: 'current' } } : {}),
1147
1171
  title: pf.fieldName,
1148
1172
  isSingle: pf.multiple ? false : true,
1149
1173
  allList: [],
@@ -1154,7 +1178,8 @@ export function registerFormCommands(program) {
1154
1178
  pcDisplaySelectedCount: { type: 'default' },
1155
1179
  mobileDisplaySelectedCount: { type: 'default' },
1156
1180
  formTableId: perDetail?.formTableId || '',
1157
- dataKey: colName,
1181
+ // dataKey 与 columnName 是两个东西,各自保留真值
1182
+ dataKey: perDetail?.dataKey || colName,
1158
1183
  columnName: colName,
1159
1184
  fieldId: perField.fieldId,
1160
1185
  formId: resolvedFormId,
@@ -1190,7 +1215,7 @@ export function registerFormCommands(program) {
1190
1215
  const detailPersonDefs = fieldDefs
1191
1216
  .filter(f => f.componentKey === 'DetailTable' && Array.isArray(f.formFields))
1192
1217
  .flatMap(dt => (dt.formFields || [])
1193
- .filter(sf => sf.componentKey === 'Employee' || sf.componentKey === 'Department')
1218
+ .filter(sf => sf.componentKey === 'Employee' || sf.componentKey === 'Department' || sf.componentKey === 'SubCompany')
1194
1219
  .map(sf => ({ ...sf, __detailTable: dt.fieldName })));
1195
1220
  if (detailPersonDefs.length > 0) {
1196
1221
  const withDefault = detailPersonDefs.filter(sf => sf.defaultValue === true);
@@ -1241,14 +1266,16 @@ export function registerFormCommands(program) {
1241
1266
  continue;
1242
1267
  }
1243
1268
  const isDept = sfDef.componentKey === 'Department';
1244
- const typeLabel = isDept ? 'Department' : 'Employee';
1245
- const labelCN = isDept ? '部门' : '人员';
1269
+ const isSubCompany = sfDef.componentKey === 'SubCompany';
1270
+ const typeLabel = isSubCompany ? 'SubCompany' : isDept ? 'Department' : 'Employee';
1271
+ const labelCN = isSubCompany ? '分部' : isDept ? '部门' : '人员';
1246
1272
  // spread cur 全量(保留子字段所有结构,防最小 jsonObj 覆盖破坏)+ 设 isSingle。
1247
1273
  // 子字段单/多选由布局 comp 的 isSingle 控制(实测:getFields 的 multiSelect 是 dataset 元数据,
1248
1274
  // 不控制渲染;布局 comp isSingle 才是渲染源)。saveFormFieldNew 会同步更新布局 comp 的 isSingle。
1249
1275
  const jsonObj = {
1250
1276
  ...cur,
1251
1277
  type: typeLabel,
1278
+ ...(isSubCompany ? { componentKey: 'Subcompany', defaultExtend: { type: 'custom', relation: 'current' } } : {}),
1252
1279
  title: sfDef.fieldName,
1253
1280
  isSingle: sfDef.multiple ? false : true,
1254
1281
  formTableId: subFormTableId || cur.formTableId || '',
@@ -1324,7 +1351,8 @@ export function registerFormCommands(program) {
1324
1351
  ...(dtMinTargetId ? { min: { type: 'dateField', checkValue: String(dtMinTargetId) }, earlyDate: true } : {}),
1325
1352
  ...(dtMaxTargetId ? { max: { type: 'dateField', checkValue: String(dtMaxTargetId) }, lateDate: true } : {}),
1326
1353
  formTableId: dtDetail?.formTableId || '',
1327
- dataKey: colName, columnName: colName,
1354
+ // dataKey 与 columnName 是两个东西,各自保留真值
1355
+ dataKey: dtDetail?.dataKey || colName, columnName: colName,
1328
1356
  fieldId: dtField.fieldId, formId: resolvedFormId,
1329
1357
  showOrder: '0.00', groupId: '-1',
1330
1358
  };
@@ -1814,23 +1842,13 @@ export function registerFormCommands(program) {
1814
1842
  process.exit(1);
1815
1843
  const client = new ApiClient(auth);
1816
1844
  const baseUrl = auth.getBaseUrl();
1817
- // Parse config
1818
- let configStr = cmdOpts.config;
1819
- if (configStr.startsWith('@')) {
1820
- const { readFileSync, existsSync } = await import('fs');
1821
- const filePath = configStr.slice(1);
1822
- if (!existsSync(filePath)) {
1823
- console.error(`配置文件不存在: ${filePath}`);
1824
- process.exit(1);
1825
- }
1826
- configStr = readFileSync(filePath, 'utf-8');
1827
- }
1845
+ // Parse config(@文件 / 内联 JSON 两种写法与 `wf-form linkage` 共用同一份实现)
1828
1846
  let config;
1829
1847
  try {
1830
- config = JSON.parse(configStr);
1848
+ config = readLinkageConfig(cmdOpts.config);
1831
1849
  }
1832
- catch {
1833
- console.error('JSON 解析失败');
1850
+ catch (e) {
1851
+ console.error(e.message);
1834
1852
  process.exit(1);
1835
1853
  }
1836
1854
  // 校验联动 prop:必填且平台支持 hide/show/edit/readonly/required/hideLine(隐藏行,抓包实测平台枚举是 hideLine;
@@ -1859,59 +1877,21 @@ export function registerFormCommands(program) {
1859
1877
  console.error('无法获取 formId,请确认 appId 和 objId 正确');
1860
1878
  process.exit(1);
1861
1879
  }
1862
- // Build payload
1863
- const payload = {
1864
- fdEffect: {
1865
- formId,
1866
- effectModule: 'ebuilderform',
1867
- effectType: 'form',
1868
- effectName: '当前表单',
1869
- optionList: [{ effectValue: formId, effectName: '当前表单' }],
1870
- },
1871
- condition: {
1872
- targetId: formId,
1873
- targetName: formInfo?.formName || '表单',
1874
- targetModule: 'ebuilderform',
1875
- conditionType: 'FIELD',
1876
- conditionDetails: config.conditions.map((c) => ({
1877
- targetId: c.fieldId,
1878
- detailSymbol: c.operator || 'EQUAL',
1879
- sourceType: 'CONSTANT',
1880
- sourceContent: JSON.stringify({
1881
- formField: { id: c.fieldId },
1882
- ...(c.options ? { dataOptions: c.options } : { content: c.value }),
1883
- }),
1884
- })),
1885
- formulaId: '',
1886
- formulaName: '',
1887
- },
1888
- linkageDetails: config.actions.map((a) => ({
1889
- formId,
1890
- id: '0',
1891
- linkageObj: 'field',
1892
- linkageProp: a.prop, // hide, show, edit, readonly, required, hideRow
1893
- linkageShowValue: a.fields.map((f) => ({
1894
- dataKey: f.dataKey || '',
1895
- groupName: '表单字段',
1896
- address: f.isDetail ? '明细表' : '主表',
1897
- addressType: f.isDetail ? 'detail' : 'form',
1898
- groupId: 'formField',
1899
- name: f.name,
1900
- descType: f.fieldType || '',
1901
- id: f.fieldId,
1902
- content: f.name,
1903
- desc: f.desc || '',
1904
- })),
1905
- detailOptions: [],
1906
- })),
1907
- module: 'ebuilderform',
1908
- formId,
1909
- refId: formId,
1910
- enableState: '1',
1911
- linkageName: name,
1912
- customParam: { appId, objId },
1913
- };
1914
- const data = await client.post(`${baseUrl}/api/ebuilder/form/form/setting/viewlinkage/add?apid=${appId}`, payload, { ebbusinessid: formId });
1880
+ // 报文的拼法(尤其条件的算子与生效维度)收在 services/form-linkage-condition.ts ——
1881
+ // 那里记着「为空要写 NULL 且不能带 sourceContent」「表单布局维度的值取 layoutId」这类
1882
+ // 不看设计器实际报文就想不到的约束。
1883
+ const formName = formInfo?.formName || formInfo?.objName || '表单';
1884
+ const effect = await resolveEffect(client, baseUrl, formId, formName, config.effect);
1885
+ const { payload, warnings } = buildLinkageAddPayload({
1886
+ appId, objId, formId, formName, name,
1887
+ conditions: config.conditions || [],
1888
+ detailType: config.detailType,
1889
+ actions: config.actions || [],
1890
+ effect,
1891
+ });
1892
+ for (const w of warnings)
1893
+ console.error(` ⚠ ${w}`);
1894
+ const data = await addLinkage(client, baseUrl, appId, formId, payload);
1915
1895
  if (data?.code === 200 && data?.status) {
1916
1896
  const linkageId = data?.data || '';
1917
1897
  console.log(opts.json
@@ -2122,15 +2102,20 @@ export function registerFormCommands(program) {
2122
2102
  .option('--precision <n>', '小数位数(Number/Money 类型,缺省 Number=0 整数、Money=2、百分比=1)')
2123
2103
  .option('--percent', 'Number 百分比显示(numberType=percentage)')
2124
2104
  .option('--default <value>', '默认值(Text / TextArea / Number / Money,如 工时单价默认 1000)')
2125
- .option('--data-key <key>', '数据库字段名(默认按字段名生成拼音;字母数字下划线,不以数字开头)')
2105
+ .option('--data-key <key>', '字段编码 dataKey(默认按字段名生成拼音;字母数字下划线,不以数字开头)')
2106
+ .option('--column-name <name>', '数据库列名 columnName。**与 dataKey 是两个东西,可分别指定**;'
2107
+ + '不给时跟随 --data-key(两者都没给则同为拼音首字母)')
2126
2108
  .option('--desc <text>', '字段描述(设计器字段属性里的「描述」)')
2127
2109
  .option('--delimiter', 'Number/Money 千分位(Money 默认开,--no-delimiter 关闭)')
2128
2110
  .option('--capital', 'Number/Money 大写金额显示')
2129
2111
  .option('--sub-form-id <id>', '明细表 subFormId(在指定明细表里加字段)')
2130
- .option('--multiple', '多选(人员/部门/EBuilder/关联类字段/下拉Select;下拉多选=SelectMultiple 类型)')
2112
+ .option('--multiple', '多选(人员/部门/分部/EBuilder/关联类字段/下拉Select;下拉多选=SelectMultiple 类型)')
2131
2113
  .option('--max-len <n>', '文本最大长度(Text 缺省 100、TextArea 缺省 4000)')
2132
2114
  .option('--single-row', '独占一行显示(布局 singleRow:true,其余字段仍两列)')
2133
2115
  .option('--option-template <idOrName>', 'Select 绑定选项预设模板(ID 或名称;选项不内联,options=[])')
2116
+ .option('--required', '设为必填(同时写字段记录与布局组件,两处都写才完整)')
2117
+ .option('--reinit', '整表重建布局(initLayout)而不是增量插入:会换样式模板、重排所有已有位置、'
2118
+ + '并重建标签组件。只在自己建的表上用;不是自己建的表用默认的增量插入')
2134
2119
  .action(async (objId, appId, fieldName, fieldType, title, cmdOptsRaw) => {
2135
2120
  // fieldType 大小写归一(与建表一致):用户常传 "Textarea"/"Ebuilder",不归一则 type 存小写
2136
2121
  // "Textarea"(FLOW 渲染异常)、TextArea 分支漏跑。
@@ -2156,25 +2141,33 @@ export function registerFormCommands(program) {
2156
2141
  // DateRange 需要 3 个字段 ID(主 + 开始 + 结束),普通字段 1 个,富组件按 richIdUnits
2157
2142
  const isDateRange = fieldType === 'DateRange' || fieldType === 'DateInterval';
2158
2143
  const idCount = isDateRange ? 3 : richIdUnits(fieldType, {});
2159
- const genData = await client.post(`${baseUrl}/api/form/local/generatorIds?size=${idCount}&apid=${appId}`, {});
2144
+ // 多要一个:布局里字段占**两个** comp,值组件用 fieldId,标签组件的 `config.fieldId`
2145
+ // 是布局自己另生成的一个雪花 id(与字段定义无关,只需在布局内唯一)。
2146
+ const genData = await client.post(`${baseUrl}/api/form/local/generatorIds?size=${idCount + 1}&apid=${appId}`, {});
2160
2147
  const genIds = genData?.data?.generatorIds || [];
2161
2148
  const fieldId = genIds[0] || String(Date.now());
2162
2149
  const startId = genIds[1] || '';
2163
2150
  const endId = genIds[2] || '';
2151
+ const labelFieldId = genIds[idCount] || String(Date.now());
2164
2152
  const cmdOpts = cmdOptsRaw || {};
2165
2153
  const sformId = cmdOpts.sformId || '';
2166
2154
  const dataTitle = cmdOpts.dataTitle || '';
2167
2155
  const formName = cmdOpts.formName || '';
2168
- // Generate column name: pinyin initials + timestamp suffix
2169
- const colName = toColumnName(fieldName);
2156
+ // dataKey(字段编码)与 columnName(数据库列名)是两个东西,默认同值。
2157
+ // 只给 --data-key 时列名跟随(历史行为);给了 --column-name 才分开。
2158
+ const keys = resolveFieldKeys(fieldName, {
2159
+ dataKey: cmdOpts.dataKey,
2160
+ columnName: cmdOpts.columnName,
2161
+ });
2162
+ const colName = keys.dataKey; // 布局 comp 等仍按字段编码引用
2170
2163
  // Build jsonObj based on field type
2171
2164
  const jsonObj = {
2172
2165
  type: fieldType,
2173
2166
  title: title || fieldName,
2174
2167
  formTableId, fieldId, groupId: '-1', formId,
2175
2168
  showOrder: '0.00',
2176
- dataKey: colName,
2177
- columnName: colName,
2169
+ dataKey: keys.dataKey,
2170
+ columnName: keys.columnName,
2178
2171
  };
2179
2172
  // 明细表字段:--sub-form-id 时 jsonObj 带 subFormId —— **字段落点由它决定**。
2180
2173
  // 必须用 resolveDetailTable 而不是直接查 getEbField:后者列不出空明细表,
@@ -2208,7 +2201,7 @@ export function registerFormCommands(program) {
2208
2201
  multiple: cmdOpts.multiple,
2209
2202
  browserType: cmdOpts.browserType,
2210
2203
  browserFormSource: cmdOpts.browserFormSource,
2211
- }, fieldId, formId, formTableId, dataKey: colName, columnName: colName,
2204
+ }, fieldId, formId, formTableId, dataKey: keys.dataKey, columnName: keys.columnName,
2212
2205
  showOrder: '0.00', extraIds,
2213
2206
  });
2214
2207
  Object.assign(jsonObj, extraCfg);
@@ -2305,6 +2298,19 @@ export function registerFormCommands(program) {
2305
2298
  delete jsonObj.scan;
2306
2299
  delete jsonObj.readonlyTransLink;
2307
2300
  }
2301
+ else if (fieldType === 'SubCompany') {
2302
+ Object.assign(jsonObj, {
2303
+ componentKey: 'Subcompany', isSingle: cmdOpts.multiple ? false : true,
2304
+ displayType: 'space', defaultExtend: { type: 'custom', relation: 'current' },
2305
+ viewDataDetail: true, showDimension: true,
2306
+ pcDisplaySelectedCount: { type: 'default' },
2307
+ mobileDisplaySelectedCount: { type: 'default' },
2308
+ });
2309
+ delete jsonObj.maxLen;
2310
+ delete jsonObj.unique;
2311
+ delete jsonObj.scan;
2312
+ delete jsonObj.readonlyTransLink;
2313
+ }
2308
2314
  else if (fieldType === 'City') {
2309
2315
  // 城市/地区选择(对齐浏览器 payload:region 模式 + 系统级联层级 + 字母索引)
2310
2316
  Object.assign(jsonObj, {
@@ -2522,6 +2528,7 @@ export function registerFormCommands(program) {
2522
2528
  const patch = applyFieldPatches(jsonObj, {
2523
2529
  defaultValue: cmdOpts.default,
2524
2530
  dataKey: cmdOpts.dataKey,
2531
+ columnName: cmdOpts.columnName,
2525
2532
  desc: cmdOpts.desc,
2526
2533
  });
2527
2534
  if (patch.errors.length > 0) {
@@ -2532,6 +2539,23 @@ export function registerFormCommands(program) {
2532
2539
  }
2533
2540
  for (const w of patch.warnings)
2534
2541
  console.error(` ⚠ ${w}`);
2542
+ // 必填**只存在于布局 comp 里**(`config.required`)—— 字段记录没有这个键。所以下面那句
2543
+ // 只是形式上的补齐,真正生效的是布局落点:增量插入的 `overrides.required`,
2544
+ // 或 --reinit 时 configs 里那条 newFieldConfig。两处都写全了用户才看不出差别。
2545
+ if (cmdOpts.required === true)
2546
+ jsonObj.required = true;
2547
+ // 字段标题在平台里存的是**多语言标签 id**(字段记录的 `labelObj.title`),不是文本本身。
2548
+ // 不建标签也能显示(设计器退回读 `title`),但只要有谁引用了标签而这个标签不存在,
2549
+ // 标题就会**静默变空** —— 平台既不校验也不报错。所以顺手建一个真的,不留自造 id 的口子。
2550
+ // 建失败只告警:字段本身才是主操作,不该被标签服务拖垮。
2551
+ try {
2552
+ const titleLabelId = await createMultilangLabel(client, baseUrl, title || fieldName, 'ebuilderform');
2553
+ jsonObj.labelObj = { ...(jsonObj.labelObj || {}), title: titleLabelId };
2554
+ jsonObj.titleLabelId = titleLabelId;
2555
+ }
2556
+ catch (e) {
2557
+ console.error(` ⚠ 标题的多语言标签未建立(字段不受影响,设计器会退回用 title 显示): ${e.message}`);
2558
+ }
2535
2559
  const payload = {
2536
2560
  formId, module: 'ebuilderform',
2537
2561
  customParam: { appId, objId },
@@ -2553,83 +2577,116 @@ export function registerFormCommands(program) {
2553
2577
  console.error(` 关联显示列设置失败: ${e.message}`);
2554
2578
  }
2555
2579
  }
2556
- // Always relayout so new field appears in the form
2557
- console.error(' 重新生成布局...');
2558
- try {
2559
- const preLayout = await client.post(`${baseUrl}/api/ebuilder/form/builder/getFormLayout`, {
2560
- formId, module: 'ebuilderform', customParam: {}, formTableIdList: [],
2561
- designerParam: { onlyLayout: false, convert: false }, form: { id: formId },
2562
- }, { ebbusinessid: formId });
2563
- const layoutId = preLayout?.data?.formLayout?.id || '';
2564
- const layoutMultiId = preLayout?.data?.formLayout?.multipleLayoutId || '';
2565
- const layoutName = preLayout?.data?.formLayoutMultiple?.name || preLayout?.data?.formLayout?.name || '';
2566
- const layoutType = preLayout?.data?.formLayoutMultiple?.layoutType || 'EXCEL';
2567
- const configRes = await client.post(`${baseUrl}/api/ebuilder/form/form/view/relate/getConfig`, {
2568
- formId, module: 'ebuilderform', apiModule: 'ebuilder/form',
2569
- layoutId, customParam: { terminal: '', objId: formId },
2570
- initMode: 5, groupFieldIds: [], designerParam: { sourceModule: 'ebuilderform' },
2571
- }, { ebbusinessid: formId });
2572
- const rawConfigs = configRes?.data?.configs || [];
2573
- // saveFormFieldNew fields may not appear in getConfig — manually append
2574
- const CK = {
2575
- Text: 'Text', Textarea: 'TextArea', Number: 'NumberComponent', Money: 'Money',
2576
- Date: 'DateComponent', DateTime: 'DateComponent', Select: 'Select', CheckBox: 'CheckBox', RadioBox: 'RadioBox',
2577
- Employee: 'Employee', Department: 'Department', EBuilder: 'Ebuilder',
2578
- Phone: 'Phone', Email: 'Email', Mobile: 'Mobile', Location: 'Location', Switch: 'Switch', IDCard: 'IDCard',
2579
- };
2580
- const newFieldConfig = {
2581
- id: fieldId, name: title || fieldName,
2582
- componentKey: jsonObj.componentKey || CK[fieldType] || fieldType,
2583
- display: true, show: true, readOnly: false,
2584
- required: false, disableEdit: false, singleRow: cmdOpts.singleRow === true,
2585
- isDelete: false, type: fieldType, dataKey: colName,
2586
- };
2587
- const existingIds = new Set(rawConfigs.map((c) => String(c.id)));
2588
- if (cmdOpts.subFormId) {
2589
- // 明细字段:加进对应 DetailTable 的 children(明细字段嵌在 DetailTable 里,不是顶层)
2590
- const dtComp = rawConfigs.find((c) => String(c.id) === String(cmdOpts.subFormId) && c.type === 'DetailTable');
2591
- if (dtComp) {
2592
- const childIds = new Set((dtComp.children || []).map((x) => String(x.id)));
2593
- if (!childIds.has(fieldId)) {
2594
- dtComp.children = dtComp.children || [];
2595
- dtComp.children.push({ ...newFieldConfig, showOrder: dtComp.children.length });
2580
+ // 布局落点:**默认走增量插入** —— 读快照 → 塞一对 comp → saveFormAndLayout,全程不碰
2581
+ // initLayout。initLayout 是整表重建(换样式模板、重排所有位置、把标签组件全换成新 id),
2582
+ // 对不是我们建的表是破坏性的;详见 services/form-layout-insert.ts。
2583
+ //
2584
+ // 增量失败**不自动回落**到 initLayout —— 那等于把「悄悄毁掉别人的布局」变成默认行为。
2585
+ // 把错误讲清楚,让用户自己决定要不要 --reinit。
2586
+ if (!cmdOpts.reinit) {
2587
+ try {
2588
+ const fieldDef = (await fetchFieldPageRecords(client, baseUrl, appId, formId))
2589
+ .find((f) => String(f.fieldId) === String(fieldId));
2590
+ if (!fieldDef)
2591
+ throw new Error('字段已建立,但读回字段定义失败');
2592
+ const placed = await insertFieldIntoLayout(client, baseUrl, formId, {
2593
+ fieldId, labelFieldId, title: title || fieldName, fieldDef,
2594
+ subFormId: cmdOpts.subFormId,
2595
+ // 对齐 initLayout 那条路对新字段的默认:可编辑、显示。
2596
+ // 不写的话会**继承模板字段的状态** —— 模板若恰好是只读的,新字段一出生就只读。
2597
+ overrides: {
2598
+ readOnly: false, disableEdit: false, display: true,
2599
+ required: cmdOpts.required === true,
2600
+ ...(cmdOpts.singleRow === true ? { singleRow: true } : {}),
2601
+ },
2602
+ });
2603
+ console.error(` 布局已更新: excel[${placed.row},${placed.col}](增量插入,未重建)`);
2604
+ }
2605
+ catch (e) {
2606
+ console.error(` 布局更新失败: ${e.message}`);
2607
+ console.error(' 字段本身已建立,但没进布局。按上面那条修好重跑本命令即可(重跑不会重复建字段)。');
2608
+ }
2609
+ }
2610
+ else {
2611
+ console.error(' 重新生成布局(整表重建)...');
2612
+ try {
2613
+ const preLayout = await client.post(`${baseUrl}/api/ebuilder/form/builder/getFormLayout`, {
2614
+ formId, module: 'ebuilderform', customParam: {}, formTableIdList: [],
2615
+ designerParam: { onlyLayout: false, convert: false }, form: { id: formId },
2616
+ }, { ebbusinessid: formId });
2617
+ const layoutId = preLayout?.data?.formLayout?.id || '';
2618
+ const layoutMultiId = preLayout?.data?.formLayout?.multipleLayoutId || '';
2619
+ const layoutName = preLayout?.data?.formLayoutMultiple?.name || preLayout?.data?.formLayout?.name || '';
2620
+ const layoutType = preLayout?.data?.formLayoutMultiple?.layoutType || 'EXCEL';
2621
+ const configRes = await client.post(`${baseUrl}/api/ebuilder/form/form/view/relate/getConfig`, {
2622
+ formId, module: 'ebuilderform', apiModule: 'ebuilder/form',
2623
+ layoutId, customParam: { terminal: '', objId: formId },
2624
+ initMode: 5, groupFieldIds: [], designerParam: { sourceModule: 'ebuilderform' },
2625
+ }, { ebbusinessid: formId });
2626
+ const rawConfigs = configRes?.data?.configs || [];
2627
+ // saveFormFieldNew fields may not appear in getConfig — manually append
2628
+ const CK = {
2629
+ Text: 'Text', Textarea: 'TextArea', Number: 'NumberComponent', Money: 'Money',
2630
+ Date: 'DateComponent', DateTime: 'DateComponent', Select: 'Select', CheckBox: 'CheckBox', RadioBox: 'RadioBox',
2631
+ Employee: 'Employee', Department: 'Department', SubCompany: 'Subcompany', EBuilder: 'Ebuilder',
2632
+ Phone: 'Phone', Email: 'Email', Mobile: 'Mobile', Location: 'Location', Switch: 'Switch', IDCard: 'IDCard',
2633
+ };
2634
+ const newFieldConfig = {
2635
+ id: fieldId, name: title || fieldName,
2636
+ componentKey: jsonObj.componentKey || CK[fieldType] || fieldType,
2637
+ display: true, show: true, readOnly: false,
2638
+ required: cmdOpts.required === true, disableEdit: false, singleRow: cmdOpts.singleRow === true,
2639
+ isDelete: false, type: fieldType, dataKey: colName,
2640
+ };
2641
+ const existingIds = new Set(rawConfigs.map((c) => String(c.id)));
2642
+ if (cmdOpts.subFormId) {
2643
+ // 明细字段:加进对应 DetailTable 的 children(明细字段嵌在 DetailTable 里,不是顶层)
2644
+ const dtComp = rawConfigs.find((c) => String(c.id) === String(cmdOpts.subFormId) && c.type === 'DetailTable');
2645
+ if (dtComp) {
2646
+ const childIds = new Set((dtComp.children || []).map((x) => String(x.id)));
2647
+ if (!childIds.has(fieldId)) {
2648
+ dtComp.children = dtComp.children || [];
2649
+ dtComp.children.push({ ...newFieldConfig, showOrder: dtComp.children.length });
2650
+ }
2651
+ }
2652
+ else {
2653
+ console.error(` ⚠ 布局里未找到明细表 subFormId=${cmdOpts.subFormId},字段已加但未进布局`);
2596
2654
  }
2597
2655
  }
2598
- else {
2599
- console.error(` ⚠ 布局里未找到明细表 subFormId=${cmdOpts.subFormId},字段已加但未进布局`);
2656
+ else if (!existingIds.has(fieldId)) {
2657
+ rawConfigs.push({ ...newFieldConfig, showOrder: rawConfigs.length });
2600
2658
  }
2659
+ const configs = rawConfigs.map((c) => ({
2660
+ ...c,
2661
+ display: c.system ? c.display : true,
2662
+ show: c.system ? c.show : true,
2663
+ // 新加字段 saveFormFieldNew 后 getConfig 返回 readOnly:true(未铺列默认),
2664
+ // existingIds 命中时 newFieldConfig(readOnly:false) 会被丢弃,这里强制覆盖回 false,
2665
+ // 否则任何 add-field 新字段(Text/Employee/EBuilder 都中招)默认不可编辑。
2666
+ ...(String(c.id) === String(fieldId)
2667
+ ? { readOnly: false, required: cmdOpts.required === true }
2668
+ : {}),
2669
+ }));
2670
+ // DetailTable 置末(对齐 form create / form layout init):getConfig 可能把明细表夹中间,
2671
+ // 重排避免明细跑到普通字段上面。
2672
+ const orderedConfigs = [
2673
+ ...configs.filter((c) => c.type !== 'DetailTable' && c.componentKey !== 'DataTable'),
2674
+ ...configs.filter((c) => c.type === 'DetailTable' || c.componentKey === 'DataTable'),
2675
+ ];
2676
+ await client.post(`${baseUrl}/api/ebuilder/form/form/view/relate/initLayout?apid=${appId}`, {
2677
+ formId, module: 'ebuilderform', apiModule: 'ebuilder/form',
2678
+ layoutId, generateCollapse: true,
2679
+ styleTemplateId: layoutType === 'FLOW' ? '820000000000000001' : '700000000000000008', styleTemplateType: 1,
2680
+ configs: orderedConfigs, addLayout: false, ecodeType: ['js', 'css'],
2681
+ initMode: '1', customParam: { terminal: '', objId: formId },
2682
+ formLayoutMultiple: { isSys: true, id: layoutMultiId, name: layoutName, terminalType: 'PC', layoutType },
2683
+ designerParam: { onlyLayout: false },
2684
+ }, { ebbusinessid: formId });
2685
+ console.error(' 布局已更新');
2686
+ }
2687
+ catch (e) {
2688
+ console.error(` 布局更新失败: ${e.message}`);
2601
2689
  }
2602
- else if (!existingIds.has(fieldId)) {
2603
- rawConfigs.push({ ...newFieldConfig, showOrder: rawConfigs.length });
2604
- }
2605
- const configs = rawConfigs.map((c) => ({
2606
- ...c,
2607
- display: c.system ? c.display : true,
2608
- show: c.system ? c.show : true,
2609
- // 新加字段 saveFormFieldNew 后 getConfig 返回 readOnly:true(未铺列默认),
2610
- // existingIds 命中时 newFieldConfig(readOnly:false) 会被丢弃,这里强制覆盖回 false,
2611
- // 否则任何 add-field 新字段(Text/Employee/EBuilder 都中招)默认不可编辑。
2612
- ...(String(c.id) === String(fieldId) ? { readOnly: false } : {}),
2613
- }));
2614
- // DetailTable 置末(对齐 form create / form layout init):getConfig 可能把明细表夹中间,
2615
- // 重排避免明细跑到普通字段上面。
2616
- const orderedConfigs = [
2617
- ...configs.filter((c) => c.type !== 'DetailTable' && c.componentKey !== 'DataTable'),
2618
- ...configs.filter((c) => c.type === 'DetailTable' || c.componentKey === 'DataTable'),
2619
- ];
2620
- await client.post(`${baseUrl}/api/ebuilder/form/form/view/relate/initLayout?apid=${appId}`, {
2621
- formId, module: 'ebuilderform', apiModule: 'ebuilder/form',
2622
- layoutId, generateCollapse: true,
2623
- styleTemplateId: layoutType === 'FLOW' ? '820000000000000001' : '700000000000000008', styleTemplateType: 1,
2624
- configs: orderedConfigs, addLayout: false, ecodeType: ['js', 'css'],
2625
- initMode: '1', customParam: { terminal: '', objId: formId },
2626
- formLayoutMultiple: { isSys: true, id: layoutMultiId, name: layoutName, terminalType: 'PC', layoutType },
2627
- designerParam: { onlyLayout: false },
2628
- }, { ebbusinessid: formId });
2629
- console.error(' 布局已更新');
2630
- }
2631
- catch (e) {
2632
- console.error(` 布局更新失败: ${e.message}`);
2633
2690
  }
2634
2691
  }
2635
2692
  else {
@@ -2658,7 +2715,9 @@ export function registerFormCommands(program) {
2658
2715
  .option('--max-len <n>', '最大长度(正整数)。日期/时间/省市区/身份证/电子发票/下拉多选这几类没有该属性')
2659
2716
  .option('--default <value>', '默认值(Text / TextArea / Number / Money)')
2660
2717
  .option('--clear-default', '清空默认值')
2661
- .option('--data-key <key>', '数据库字段名(同时改 dataKey 与 columnName;字段已有数据时平台会拒绝)')
2718
+ .option('--data-key <key>', '字段编码 dataKey(字母数字下划线,不以数字开头;字段已有数据时平台会拒绝)')
2719
+ .option('--column-name <name>', '数据库列名 columnName。**与 dataKey 是两个东西,可分别指定**;'
2720
+ + '不给时跟随 --data-key。字段已有数据时平台会拒绝改列名')
2662
2721
  .option('--desc <text>', '字段描述(设计器字段属性里的「描述」)')
2663
2722
  .action(async (objId, appId, fieldId, editOpts) => {
2664
2723
  const opts = program.opts();
@@ -2842,7 +2901,8 @@ export function registerFormCommands(program) {
2842
2901
  }
2843
2902
  }
2844
2903
  }
2845
- // Apply required
2904
+ // `required` 不是字段记录的键(整张表的字段记录里都没有),写到 jsonObj 只是**保持与
2905
+ // 既有代码一致的形式**,真正生效的是保存成功之后那段布局 comp 补丁 —— 见下面 `patchFieldLayout`。
2846
2906
  if (editOpts.required) {
2847
2907
  jsonObj.required = true;
2848
2908
  }
@@ -2990,6 +3050,7 @@ export function registerFormCommands(program) {
2990
3050
  defaultValue: editOpts.default,
2991
3051
  clearDefault: editOpts.clearDefault,
2992
3052
  dataKey: editOpts.dataKey,
3053
+ columnName: editOpts.columnName,
2993
3054
  desc: editOpts.desc,
2994
3055
  });
2995
3056
  if (patch.errors.length > 0) {
@@ -3015,10 +3076,10 @@ export function registerFormCommands(program) {
3015
3076
  if (editOpts.type) {
3016
3077
  changes.push(typeChangeApplied ? `type=${editOpts.type}` : `type=${editOpts.type}(未变化)`);
3017
3078
  }
3018
- if (editOpts.required)
3019
- changes.push('required');
3020
- if (editOpts.noRequired)
3021
- changes.push('!required');
3079
+ // commander 把 `--no-required` 映射成 `required: false`(不是 `noRequired`),
3080
+ // 所以只判 noRequired 的话反向操作会漏报成「no changes」—— 改了却说没改。
3081
+ if (editOpts.required !== undefined)
3082
+ changes.push(editOpts.required ? 'required' : '!required');
3022
3083
  if (editOpts.sformId)
3023
3084
  changes.push(`EBuilder→${editOpts.sformId}`);
3024
3085
  if (editOpts.dataTitle !== undefined)
@@ -3028,6 +3089,19 @@ export function registerFormCommands(program) {
3028
3089
  if (editOpts.multiple !== undefined)
3029
3090
  changes.push(editOpts.multiple ? 'multiple' : '!multiple');
3030
3091
  changes.push(...patch.applied);
3092
+ // 必填**只存在于布局 comp 里**:字段记录没有 `required` 这个键(整张表的字段记录里都没有),
3093
+ // 所以上面写进 jsonObj 的那一下落不了库。只写字段记录 = 接口回 200、命令报成功、表单上行为
3094
+ // 一点没变。真正生效的是布局 comp 的 `config.required`,这里补上。
3095
+ if (editOpts.required !== undefined) {
3096
+ try {
3097
+ const { updated } = await patchFieldLayout(client, baseUrl, formId, fieldId, { required: editOpts.required === true });
3098
+ if (!updated)
3099
+ console.error(' 警告: 布局里没找到该字段的组件,必填未生效');
3100
+ }
3101
+ catch (e) {
3102
+ console.error(` 警告: 布局里的必填未更新(字段本身已改好): ${e.message}`);
3103
+ }
3104
+ }
3031
3105
  // EBuilder 字段的浏览显示列:add-field 建出来的会自动配,edit-field 改出来的此前漏了这一步
3032
3106
  // —— 字段能建出来,但浏览弹窗回落平台默认列,会把目标表的系统字段也列出来。
3033
3107
  // ensureDisplayColumns 只在「没配过」或「旧列对不上当前目标表」时才写,不会冲掉手工配好的列。