weaver-e10-builder 1.5.1 → 1.5.2

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 (48) hide show
  1. package/dist/commands/form-add-physical-field.d.ts +3 -0
  2. package/dist/commands/form-add-physical-field.d.ts.map +1 -0
  3. package/dist/commands/form-add-physical-field.js +125 -0
  4. package/dist/commands/form-add-physical-field.js.map +1 -0
  5. package/dist/commands/form-list-permission.js +1 -1
  6. package/dist/commands/form-list-permission.js.map +1 -1
  7. package/dist/commands/form-physical-table.d.ts +3 -0
  8. package/dist/commands/form-physical-table.d.ts.map +1 -0
  9. package/dist/commands/form-physical-table.js +195 -0
  10. package/dist/commands/form-physical-table.js.map +1 -0
  11. package/dist/commands/form.d.ts.map +1 -1
  12. package/dist/commands/form.js +35 -412
  13. package/dist/commands/form.js.map +1 -1
  14. package/dist/commands/org.d.ts.map +1 -1
  15. package/dist/commands/org.js +131 -7
  16. package/dist/commands/org.js.map +1 -1
  17. package/dist/commands/workflow-operator-sign.js +1 -1
  18. package/dist/commands/workflow-operator-sign.js.map +1 -1
  19. package/dist/index.js +4 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/services/form-field-build.d.ts +71 -0
  22. package/dist/services/form-field-build.d.ts.map +1 -0
  23. package/dist/services/form-field-build.js +436 -0
  24. package/dist/services/form-field-build.js.map +1 -0
  25. package/dist/services/form-physical-table.d.ts +115 -0
  26. package/dist/services/form-physical-table.d.ts.map +1 -0
  27. package/dist/services/form-physical-table.js +153 -0
  28. package/dist/services/form-physical-table.js.map +1 -0
  29. package/dist/services/org-employee.d.ts +105 -1
  30. package/dist/services/org-employee.d.ts.map +1 -1
  31. package/dist/services/org-employee.js +186 -5
  32. package/dist/services/org-employee.js.map +1 -1
  33. package/package.json +1 -1
  34. package/skill-docs/SKILL.md +5 -5
  35. package/skill-docs/_catalog.md +1 -0
  36. package/skill-docs/references/common/ref-cli-commands.md +8 -2
  37. package/skill-docs/references/form/guide-form-association.md +1 -1
  38. package/skill-docs/references/form/guide-form-linkage.md +2 -2
  39. package/skill-docs/references/form/guide-form-physical-table.md +146 -0
  40. package/skill-docs/references/hrm/guide-org-employee.md +71 -3
  41. package/skill-docs/references/report/cell-config-echo-guide.md +1 -1
  42. package/skill-docs/references/report/dataset-query-filter-guide.md +3 -3
  43. package/skill-docs/references/report/date-range-query-reference.md +2 -2
  44. package/skill-docs/references/report/query-condition-api-guide.md +11 -11
  45. package/skill-docs/references/report/report-designer-feature-map.md +1 -1
  46. package/skill-docs/references/report/report-permission-guide.md +2 -2
  47. package/skill-docs/references/workflow/guide-workflow-odoc-setting.md +6 -6
  48. package/skill-docs/references/workflow/guide-workflow.md +1 -1
@@ -3,7 +3,7 @@ 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
5
  import { createCustomPhysicalForm, addInitialFieldsToCustomPhysicalForm, placeholderFromFormat, reapplyGroupLayout, queryFormStorageSetting } from '../services/form-create.js';
6
- import { FormSubDetailService, resolveDetailTable } from '../services/form-sub-detail.js';
6
+ import { FormSubDetailService } 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';
@@ -11,7 +11,11 @@ import { applyFieldPatches, platformFieldShape } from '../services/form-field-pa
11
11
  import { ensureDisplayColumns } from '../services/form-ebuilder-columns.js';
12
12
  import { insertFieldIntoLayout, patchFieldLayout } from '../services/form-layout-insert.js';
13
13
  import { buildLinkageAddPayload, addLinkage, resolveEffect, readLinkageConfig } from '../services/form-linkage-condition.js';
14
- import { createMultilangLabel } from '../services/form-i18n-label.js';
14
+ // 字段 jsonObj 的构造器(含 SYSTEM_FIELD_IDS / defaultEBuilderDataTitle)——
15
+ // 原先内联在下面 add-field 的 action 里,为了给「按物理表加字段」第二个入口复用而抽出去。
16
+ // 本文件仍有别处用这两个符号,所以在这里回导。
17
+ import { SYSTEM_FIELD_IDS, defaultEBuilderDataTitle, buildFieldJsonObject } from '../services/form-field-build.js';
18
+ import { listFormTables, readFieldRecords, mapFieldsToTables } from '../services/form-physical-table.js';
15
19
  import { buildFormulaParameters, defaultReturnType } from '../services/formula-detail.js';
16
20
  import { registerOptionTemplateCommand } from './form-option-template.js';
17
21
  /**
@@ -87,12 +91,6 @@ function assertFieldOptions(fieldName, options) {
87
91
  }
88
92
  });
89
93
  }
90
- const SYSTEM_FIELD_IDS = new Set([
91
- 'name', 'creator', 'create_time', 'update_time', 'id',
92
- 'updater', 'data_status', 'flow_status', 'current_step',
93
- 'batchpubtags', 'batchmytags', 'operate', 'data_read_status',
94
- 'flow_system_number',
95
- ]);
96
94
  /** 系统字段的中文名 → 物理字段 id(form read 实测的平台自带字段)。建表时字段与此完全重名 → 跳过新建、直接复用系统字段。 */
97
95
  const SYSTEM_FIELD_NAME_TO_ID = {
98
96
  '标题': 'name',
@@ -117,14 +115,6 @@ const SYSTEM_FIELD_CFG_ID = {
117
115
  '最后更新人': 'updater',
118
116
  '数据状态': 'dataStatus',
119
117
  };
120
- // EBuilder dataTitle(关联字段标题栏)默认解析:优先字段名含「名称」的业务字段,无则第一个业务字段。
121
- // 需求:默认选带"名称"二字的字段(如 客户名称/类型名称),没有就默认选第一个。
122
- function defaultEBuilderDataTitle(fields) {
123
- const isBiz = (id) => /^\d{15,}$/.test(id) && !SYSTEM_FIELD_IDS.has(id);
124
- const biz = (fields || []).filter((f) => isBiz(String(f.fieldId ?? '')));
125
- const byName = biz.find((f) => String(f.fieldName ?? f.title ?? '').includes('名称'));
126
- return byName?.fieldId || biz[0]?.fieldId || 'name';
127
- }
128
118
  /**
129
119
  * 只读地看一眼 `getFields` 眼里这个字段长什么样 —— **仅用于把错误信息说清楚,绝不作为写回基底**。
130
120
  *
@@ -284,6 +274,22 @@ export function registerFormCommands(program) {
284
274
  }
285
275
  catch { /* 布局属性补读失败不影响主流程 */ }
286
276
  }
277
+ // 所属物理表(设计器「字段管理」的主表/其他)—— getFields 不带这个信息,
278
+ // 得用 getFormAGroupList(表清单)+ getFormFieldPage(字段记录上的 formTableId)拼出来。
279
+ if (formId && cmdOpts?.appId) {
280
+ try {
281
+ const scope = { formId, appId: cmdOpts.appId, objId };
282
+ const tables = await listFormTables(client, baseUrl, cmdOpts.appId, scope);
283
+ const records = await readFieldRecords(client, baseUrl, cmdOpts.appId, scope);
284
+ const byField = mapFieldsToTables(records, tables);
285
+ for (const f of allFields) {
286
+ const t = byField[String(f.fieldId)];
287
+ if (t)
288
+ f.formTable = { id: t.formTableId, name: t.name, type: t.tableType, tableName: t.tableName };
289
+ }
290
+ }
291
+ catch { /* 物理表归属读不到不影响主流程 */ }
292
+ }
287
293
  if (cmdOpts?.out) {
288
294
  // Write JSON to file with UTF-8 encoding, bypassing terminal encoding
289
295
  writeFileSync(cmdOpts.out, JSON.stringify(allFields, null, 2), 'utf-8');
@@ -294,10 +300,13 @@ export function registerFormCommands(program) {
294
300
  }
295
301
  else {
296
302
  console.log(`表单 ${objId} 共 ${fields.length} 个主表字段${subFields.length ? ` + ${subFields.length} 个明细表字段` : ''}:`);
303
+ // 主表那排 chip 上写的是表单名,逐行重复没信息量,显示成「主表」;附属物理表用它的真名
304
+ const tableLabel = (f) => (f.formTable ? (f.formTable.type === 'MAIN' ? '主表' : f.formTable.name) : '');
297
305
  for (const f of fields) {
298
306
  if (SYSTEM_FIELD_IDS.has(f.fieldId))
299
307
  continue;
300
- console.log(` [${f.fieldKey || f.fieldType}] ${f.fieldName} (fieldId: ${f.fieldId || f.id})`);
308
+ const t = tableLabel(f);
309
+ console.log(` [${f.fieldKey || f.fieldType}] ${f.fieldName} (fieldId: ${f.fieldId || f.id})${t ? ` · 物理表: ${t}` : ''}`);
301
310
  if (f.options)
302
311
  console.log(` 选项: ${JSON.stringify(f.options)}`);
303
312
  if (f.optionTemplate)
@@ -2160,402 +2169,16 @@ export function registerFormCommands(program) {
2160
2169
  columnName: cmdOpts.columnName,
2161
2170
  });
2162
2171
  const colName = keys.dataKey; // 布局 comp 等仍按字段编码引用
2163
- // Build jsonObj based on field type
2164
- const jsonObj = {
2165
- type: fieldType,
2166
- title: title || fieldName,
2167
- formTableId, fieldId, groupId: '-1', formId,
2168
- showOrder: '0.00',
2169
- dataKey: keys.dataKey,
2170
- columnName: keys.columnName,
2171
- };
2172
- // 明细表字段:--sub-form-id 时 jsonObj 带 subFormId —— **字段落点由它决定**。
2173
- // 必须用 resolveDetailTable 而不是直接查 getEbField:后者列不出空明细表,
2174
- // 用户把明细字段删光后就再也加不回去(详见 services/form-sub-detail.ts)。
2175
- if (cmdOpts.subFormId) {
2176
- const sub = await resolveDetailTable(client, baseUrl, appId, objId, formId, cmdOpts.subFormId);
2177
- if (!sub) {
2178
- console.error(`未找到明细表 subFormId=${cmdOpts.subFormId}(getEbField 与 querySubForms 均无此表,请确认 id)`);
2179
- process.exit(1);
2180
- }
2181
- Object.assign(jsonObj, { subFormId: cmdOpts.subFormId });
2182
- // 空表拿不到物理表 id,保持主表的值不动:实测带主表 formTableId 一样落进明细表。
2183
- if (sub.formTableId)
2184
- jsonObj.formTableId = sub.formTableId;
2185
- }
2186
- // 富组件:映射到平台真实 type/componentKey + 附加配置(对齐 batch/save/formField 抓包)。
2187
- // 之前 jsonObj 只有 type 没有 componentKey → 平台 FormFieldDto.getComponentKey() 为 null → NPE。
2188
- const rich = getRichType(fieldType);
2189
- if (rich) {
2190
- jsonObj.type = rich.platformType;
2191
- jsonObj.componentKey = rich.platformComponentKey;
2192
- if (rich.build) {
2193
- const extra = richIdUnits(fieldType, {}) - 1;
2194
- let extraIds = [];
2195
- if (extra > 0) {
2196
- const extraRes = await client.post(`${baseUrl}/api/form/local/generatorIds?size=${extra}&apid=${appId}`, {});
2197
- extraIds = (extraRes?.data?.generatorIds || []).map(String);
2198
- }
2199
- const extraCfg = await rich.build({
2200
- def: {
2201
- multiple: cmdOpts.multiple,
2202
- browserType: cmdOpts.browserType,
2203
- browserFormSource: cmdOpts.browserFormSource,
2204
- }, fieldId, formId, formTableId, dataKey: keys.dataKey, columnName: keys.columnName,
2205
- showOrder: '0.00', extraIds,
2206
- });
2207
- Object.assign(jsonObj, extraCfg);
2208
- }
2209
- }
2210
- // 非富组件也补 componentKey(平台要求,否则 FormFieldDto.getComponentKey() 为 null → NPE)。
2211
- // 富组件已在上方设置 platformComponentKey;此处 fallback 到类型名(平台对常见类型有归一容错)。
2212
- if (!jsonObj.componentKey)
2213
- jsonObj.componentKey = fieldType;
2214
- // Number/Money decimal precision: --precision 指定,缺省 Number=0 整数、Money=2、百分比=1
2215
- // 百分比:--percent → numberType=percentage;默认值:--default → content+isDefault
2216
- // 实测:设默认值必须连 isDelimiter/stepMode/step/isCapital 一起,否则平台丢默认(只设 content+isDefault 不落库)
2217
- if (fieldType === 'Number' || fieldType === 'Money') {
2218
- const defaultPrec = fieldType === 'Money' ? '2' : '0';
2219
- const isPercent = fieldType === 'Number' && cmdOpts.percent === true;
2220
- // 注意:commander 把 --default 映射到 opts.default(非 defaultValue)
2221
- const hasDefault = cmdOpts.default !== undefined && cmdOpts.default !== null && cmdOpts.default !== '';
2222
- // 千分位:Money 默认开(--no-delimiter 关闭)、Number 默认关(--delimiter 开启)
2223
- const isDelim = fieldType === 'Money' ? cmdOpts.delimiter !== false : cmdOpts.delimiter === true;
2224
- const isCap = cmdOpts.capital === true;
2225
- const patch = {
2226
- pointSize: cmdOpts.precision !== undefined ? cmdOpts.precision : (isPercent ? '1' : defaultPrec),
2227
- isDefault: hasDefault,
2228
- isDelimiter: isDelim,
2229
- isCapital: isCap,
2230
- capitalFormat: 'onlyUpperCase',
2231
- capitalPosition: 'outside',
2232
- capitalSetting: 'system',
2233
- capitalPrecision: 'yuan',
2234
- isExchangeRates: false,
2235
- };
2236
- if (fieldType === 'Number')
2237
- patch.numberType = isPercent ? 'percentage' : 'number';
2238
- // content 始终带(无默认值时空串)——浏览器抓包 Number 字段 getFormField 恒有 content:"",
2239
- // 缺 content 在 FLOW 渲染异常(与多行文本同因)。
2240
- if (fieldType === 'Number')
2241
- patch.content = hasDefault ? String(cmdOpts.default) : '';
2242
- if (hasDefault) {
2243
- patch.stepMode = false;
2244
- patch.step = 1;
2245
- }
2246
- Object.assign(jsonObj, patch);
2247
- }
2248
- if (fieldType === 'TextArea') {
2249
- // 多行文本:对齐浏览器抓包——content 恒 ""(无默认值时空串)、maxLen=4000。
2250
- // 缺 content 在 FLOW 渲染异常;maxLen 不发则前端最大长度回显异常。
2251
- Object.assign(jsonObj, { content: '', maxLen: 4000 });
2252
- }
2253
- if (fieldType === 'EInvoice') {
2254
- Object.assign(jsonObj, {
2255
- isSingle: false,
2256
- invoiceType: 1,
2257
- });
2258
- delete jsonObj.maxLen;
2259
- delete jsonObj.unique;
2260
- delete jsonObj.scan;
2261
- delete jsonObj.readonlyTransLink;
2262
- }
2263
- else if (fieldType === 'Date' || fieldType === 'DateTime') {
2264
- // 日期:默认精度 年月日(yyyy-MM-dd,对齐浏览器抓包);DateTime:默认带时:分(yyyy-MM-dd HH:mm 不含秒)。
2265
- // 默认值不填(isDefault:false);--format 覆盖精度。
2266
- const isDt = fieldType === 'DateTime';
2267
- const fmt = cmdOpts.format || (isDt ? 'yyyy-MM-dd HH:mm' : 'yyyy-MM-dd');
2268
- Object.assign(jsonObj, {
2269
- type: 'Date',
2270
- format: fmt,
2271
- placeholder: placeholderFromFormat(fmt),
2272
- timePanelStyle: { type: 'system' },
2273
- isDefault: false,
2274
- });
2275
- // 日期限制:--min-field 最早不得早于 / --max-field 最晚不得晚于另一字段(对齐抓包 min.dateField)。
2276
- // 目标字段 fieldId 从 getFields mainFields 按字段名解析。
2277
- if (cmdOpts.minField || cmdOpts.maxField) {
2278
- try {
2279
- const tfd = await client.post(`${baseUrl}/api/ebuilder/form/dataset/v1/getFields`, {
2280
- objId, userid: auth.userId, tenantKey: auth.tenantKey,
2281
- });
2282
- const tfields = tfd?.data?.mainFields || [];
2283
- const minTarget = cmdOpts.minField ? tfields.find((f) => f.fieldName === cmdOpts.minField)?.fieldId : '';
2284
- const maxTarget = cmdOpts.maxField ? tfields.find((f) => f.fieldName === cmdOpts.maxField)?.fieldId : '';
2285
- if (minTarget) {
2286
- jsonObj.min = { type: 'dateField', checkValue: String(minTarget) };
2287
- jsonObj.earlyDate = true;
2288
- }
2289
- if (maxTarget) {
2290
- jsonObj.max = { type: 'dateField', checkValue: String(maxTarget) };
2291
- jsonObj.lateDate = true;
2292
- }
2293
- }
2294
- catch { /* 解析失败则不加限制 */ }
2295
- }
2296
- delete jsonObj.maxLen;
2297
- delete jsonObj.unique;
2298
- delete jsonObj.scan;
2299
- delete jsonObj.readonlyTransLink;
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
- }
2314
- else if (fieldType === 'City') {
2315
- // 城市/地区选择(对齐浏览器 payload:region 模式 + 系统级联层级 + 字母索引)
2316
- Object.assign(jsonObj, {
2317
- isSingle: false,
2318
- levelSetting: 'system',
2319
- isDefault: false,
2320
- useLetterIndex: true,
2321
- mode: 'region',
2322
- });
2323
- delete jsonObj.maxLen;
2324
- delete jsonObj.unique;
2325
- delete jsonObj.scan;
2326
- delete jsonObj.readonlyTransLink;
2327
- }
2328
- else if (fieldType === 'EBuilder') {
2329
- if (!sformId) {
2330
- console.error('EBuilder 类型必须指定 --sform-id(目标表单 objId)');
2331
- process.exit(1);
2332
- }
2333
- // Resolve formName from target form if not provided
2334
- let resolvedFormName = formName;
2335
- if (!resolvedFormName) {
2336
- try {
2337
- const tlist = await client.get(`${baseUrl}/api/bs/ebuilder/form/obj/getList/${appId}?apid=${appId}`, { ebbusinessid: appId });
2338
- const tf = (tlist?.data || []).find((f) => String(f.id) === String(sformId));
2339
- resolvedFormName = tf?.formName || tf?.name || sformId;
2340
- }
2341
- catch {
2342
- resolvedFormName = sformId;
2343
- }
2344
- }
2345
- // Resolve dataTitle:纯数字 fieldId 直接用;'name'/字段名 → 查目标表字段。
2346
- // 未传 --data-title 时按 name 处理:默认优先字段名含「名称」的业务字段(如 客户名称/类型名称),无则第一个业务字段。
2347
- let resolvedDataTitle = dataTitle || 'name';
2348
- if (!/^\d{15,}$/.test(resolvedDataTitle)) {
2349
- try {
2350
- const tfd = await client.post(`${baseUrl}/api/ebuilder/form/dataset/v1/getFields`, {
2351
- objId: sformId, userid: auth.userId, tenantKey: auth.tenantKey,
2352
- });
2353
- const tfields = tfd?.data?.mainFields || [];
2354
- if (resolvedDataTitle === 'name') {
2355
- resolvedDataTitle = defaultEBuilderDataTitle(tfields);
2356
- }
2357
- else {
2358
- const m = tfields.find((f) => f.fieldName === resolvedDataTitle)
2359
- || tfields.find((f) => String(f.fieldName || '').includes(resolvedDataTitle));
2360
- resolvedDataTitle = m?.fieldId || resolvedDataTitle;
2361
- }
2362
- }
2363
- catch { /* 解析失败保留原值 */ }
2364
- }
2365
- Object.assign(jsonObj, {
2366
- sformId,
2367
- dataTitle: resolvedDataTitle || 'name',
2368
- formName: resolvedFormName,
2369
- virtualForm: false,
2370
- displayType: 'line',
2371
- isAddForRelation: true,
2372
- isSingle: cmdOpts.multiple ? false : true, // 默认单选;--multiple 多选
2373
- viewDataDetail: true,
2374
- pcDisplaySelectedCount: { type: 'default' },
2375
- mobileDisplaySelectedCount: { type: 'default' },
2376
- quickAddParams: [],
2377
- relateViewData: [],
2378
- relateField: null,
2379
- appId,
2380
- virtualTable: false,
2381
- });
2382
- }
2383
- else if (fieldType === 'Flow') {
2384
- // 浏览-流程字段(对齐浏览器 payload:type=Flow + relateExt.module=workflow)
2385
- if (!sformId) {
2386
- console.error('Flow 类型必须指定 --sform-id(目标流程绑定的表单 objId)');
2387
- process.exit(1);
2388
- }
2389
- let resolvedFormName = formName;
2390
- if (!resolvedFormName) {
2391
- try {
2392
- const tlist = await client.get(`${baseUrl}/api/bs/ebuilder/form/obj/getList/${appId}?apid=${appId}`, { ebbusinessid: appId });
2393
- const tf = (tlist?.data || []).find((f) => String(f.id) === String(sformId));
2394
- resolvedFormName = tf?.formName || tf?.name || sformId;
2395
- }
2396
- catch {
2397
- resolvedFormName = sformId;
2398
- }
2399
- }
2400
- Object.assign(jsonObj, {
2401
- isAddForRelation: true,
2402
- sformId,
2403
- formName: resolvedFormName,
2404
- conditionOperate: 'and',
2405
- filterParams: '{}',
2406
- filterQueryParams: '{}',
2407
- columnFields: [],
2408
- displayType: 'line',
2409
- viewDataDetail: true,
2410
- pcDisplaySelectedCount: { type: 'default' },
2411
- mobileDisplaySelectedCount: { type: 'default' },
2412
- relateExt: { module: 'workflow' },
2413
- relateField: null,
2414
- quickAddParams: [],
2415
- });
2416
- }
2417
- else if (isDateRange) {
2418
- // 日期区间:补 start/end 两个 DateComponent + rangeInterval(对齐浏览器 payload)
2419
- const format = cmdOpts.format || 'yyyy-MM-dd';
2420
- const rangeTitle = title || fieldName;
2421
- const start = {
2422
- componentKey: 'DateComponent', content: '', describe: '', earlyDate: 'false',
2423
- format, isDefault: 'false', lateDate: 'false',
2424
- placeholder: placeholderFromFormat(format),
2425
- position: '', relateMultiple: 'false', required: 'false', size: 'medium',
2426
- title: `${rangeTitle}(开始时间)`, fieldId: startId,
2427
- min: { type: 'currentHour' }, max: { type: 'currentHour' },
2428
- };
2429
- const end = { ...start, title: `${rangeTitle}(结束时间)`, fieldId: endId };
2430
- Object.assign(jsonObj, {
2431
- isDefault: false,
2432
- start, end,
2433
- hasDisplayFormat: true,
2434
- rangeInterval: { limitType: 'max', timeUnit: 'day' },
2435
- });
2436
- }
2437
- else if (fieldType === 'IDCard') {
2438
- // 身份证号:平台原生简单字段,浏览器抓包 payload 极简(type/title/formTableId/dataKey/
2439
- // columnName/fieldId/formId/showOrder/groupId 即全部)——无 componentKey、无文本长度配置。
2440
- delete jsonObj.componentKey;
2441
- delete jsonObj.maxLen;
2442
- delete jsonObj.unique;
2443
- delete jsonObj.scan;
2444
- delete jsonObj.readonlyTransLink;
2445
- }
2446
- else {
2447
- // 文本最大长度:--max-len 指定,缺省 Text=100、TextArea=4000(对齐浏览器抓包;TextArea 在上面分支已设 content:"")
2448
- const defaultMaxLen = fieldType === 'TextArea' ? 4000 : 100;
2449
- const maxLen = cmdOpts.maxLen !== undefined ? Number(cmdOpts.maxLen) : defaultMaxLen;
2450
- Object.assign(jsonObj, {
2451
- maxLen: Number.isFinite(maxLen) && maxLen > 0 ? maxLen : defaultMaxLen,
2452
- unique: false, scan: false,
2453
- readonlyTransLink: false,
2454
- });
2455
- }
2456
- // 人员/部门多选:默认单选,--multiple 改多选(isSingle:false)。EBuilder 已在上文分支设过。
2457
- if (fieldType === 'Employee' || fieldType === 'Department') {
2458
- jsonObj.isSingle = cmdOpts.multiple ? false : true;
2459
- }
2460
- // 下拉多选是独立类型 SelectMultiple(非 Select + 开关)。浏览器抓包:type:"SelectMultiple",
2461
- // 无 componentKey/maxLen/unique/scan(else 分支给 Select 加的 junk 要清掉),options 单独配。
2462
- if (fieldType === 'Select' && cmdOpts.multiple) {
2463
- jsonObj.type = 'SelectMultiple';
2464
- jsonObj.componentKey = 'SelectMultiple';
2465
- delete jsonObj.maxLen;
2466
- delete jsonObj.unique;
2467
- delete jsonObj.scan;
2468
- delete jsonObj.readonlyTransLink;
2469
- }
2470
- // Select 绑定选项预设模板:--option-template(ID 或名称)。
2471
- // 浏览器抓包(2026-09-09):jsonObj 带 templateId+templateName+publicTemplate:false+options:[](选项不内联),
2472
- // 平台渲染时按模板拉选项。模板管理见 `form option-template`。
2473
- if (fieldType === 'Select' && cmdOpts.optionTemplate) {
2474
- // 先清 else 分支给文本类加的 junk + options 占位(模板字段不发选项)
2475
- delete jsonObj.maxLen;
2476
- delete jsonObj.unique;
2477
- delete jsonObj.scan;
2478
- delete jsonObj.readonlyTransLink;
2479
- const tplIdOrName = cmdOpts.optionTemplate;
2480
- // 解析模板:纯数字按 ID 直查详情;否则在 custom+system 列表按名称精确匹配
2481
- let tplId = '';
2482
- let tplName = '';
2483
- const readTpl = async (id) => {
2484
- const r = await client.post(`${baseUrl}/api/form/datasources/queryDataSourcesByTemplateValueKey`, { templateId: id });
2485
- return r?.formDataTemplate?.id ? r.formDataTemplate : null;
2486
- };
2487
- if (/^\d+$/.test(tplIdOrName)) {
2488
- const t = await readTpl(tplIdOrName);
2489
- if (!t) {
2490
- console.error(`选项模板 ${tplIdOrName} 不存在`);
2491
- process.exit(1);
2492
- }
2493
- tplId = String(t.id);
2494
- tplName = String(t.name);
2495
- }
2496
- else {
2497
- for (const type of ['custom', 'system']) {
2498
- const list = await client.post(`${baseUrl}/api/form/datasources/queryDataList`, { pageNo: 1, pageSize: 100, name: tplIdOrName, type });
2499
- const hit = (list?.templatePage?.result || []).find((r) => r.name === tplIdOrName);
2500
- if (hit) {
2501
- tplId = String(hit.id);
2502
- tplName = String(hit.name);
2503
- break;
2504
- }
2505
- }
2506
- if (!tplId) {
2507
- console.error(`未找到名为「${tplIdOrName}」的选项模板(custom+system 均已查)`);
2508
- process.exit(1);
2509
- }
2510
- }
2511
- Object.assign(jsonObj, {
2512
- hideScore: true,
2513
- optionBackgroundStyle: 'default',
2514
- options: [],
2515
- templateScore: {},
2516
- currentLevel: '1',
2517
- templateId: tplId,
2518
- templateName: tplName,
2519
- publicTemplate: false,
2520
- parentId: [],
2521
- });
2522
- if (cmdOpts.multiple) {
2523
- console.error('提示:模板下拉当前按单选(currentLevel=1)绑定;多选+模板组合未验证');
2524
- }
2525
- }
2526
- // 默认值 / 数据库字段名 / 描述:与 edit-field 共用同一套补丁规则。
2527
- // 放在最后 —— 前面各类型分支会写 content(如 TextArea 恒置 ''),先打补丁会被冲掉。
2528
- const patch = applyFieldPatches(jsonObj, {
2529
- defaultValue: cmdOpts.default,
2530
- dataKey: cmdOpts.dataKey,
2531
- columnName: cmdOpts.columnName,
2532
- desc: cmdOpts.desc,
2172
+ // 字段 jsonObj 的构造抽到 services/form-field-build.ts —— 与「按物理表加字段」共用同一份。
2173
+ // 原先这约 340 行内联在这里,多一个入口就会变成两处真相。
2174
+ const jsonObj = await buildFieldJsonObject({
2175
+ client, baseUrl, appId, objId,
2176
+ userId: auth.userId, tenantKey: auth.tenantKey,
2177
+ fieldType, fieldName, title, formId, formTableId,
2178
+ fieldId, startId, endId,
2179
+ dataKey: keys.dataKey, columnName: keys.columnName,
2180
+ opts: cmdOpts,
2533
2181
  });
2534
- if (patch.errors.length > 0) {
2535
- for (const e of patch.errors)
2536
- console.error(` ❌ ${e}`);
2537
- console.error(' 已中止,未做任何改动。');
2538
- process.exit(1);
2539
- }
2540
- for (const w of patch.warnings)
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
- }
2559
2182
  const payload = {
2560
2183
  formId, module: 'ebuilderform',
2561
2184
  customParam: { appId, objId },