lan-low-code-cli 2.1.0 → 2.3.0
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.
- package/README.md +248 -7
- package/bin/llc.js +0 -0
- package/dist/api/formDefine.js +12 -0
- package/dist/commands/control.js +34 -3
- package/dist/commands/form.js +52 -6
- package/dist/constants.js +770 -4
- package/dist/generator/index.js +473 -41
- package/dist/index.js +10 -1
- package/dist/relation.js +129 -0
- package/dist/widgets.js +39 -4
- package/package.json +48 -43
package/dist/generator/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GLOBAL_ACTION_EVENTS = exports.ACTION_GLOBAL_TARGET = void 0;
|
|
4
|
+
exports.buildRelationProps = buildRelationProps;
|
|
4
5
|
exports.resolveLocales = resolveLocales;
|
|
5
6
|
exports.completeOptionsI18n = completeOptionsI18n;
|
|
6
7
|
exports.buildViewModel = buildViewModel;
|
|
@@ -15,6 +16,62 @@ const random_1 = require("../utils/random");
|
|
|
15
16
|
const widgets_1 = require("../widgets");
|
|
16
17
|
/** `$global`:动作目标为表单全局(onLoad / onBeforeSubmit / onAfterSubmit) */
|
|
17
18
|
exports.ACTION_GLOBAL_TARGET = '$global';
|
|
19
|
+
/** 建模表单的数据模型类名(业务关联的 `modelName`,取自真实表单) */
|
|
20
|
+
const MODELING_MODEL_NAME = 'com.landray.sys.xform.core.entity.design.SysXFormDesign';
|
|
21
|
+
/**
|
|
22
|
+
* 构建业务关联的 `controlProps` 片段(`relationCfg` + `datasource` + 显示方式)。
|
|
23
|
+
*
|
|
24
|
+
* 骨架逐键对齐**设计器手动创建的真实字段**(物料入库单 `fd_col_9bg1ms`):
|
|
25
|
+
* - `refFieldName` 是 `$<字段名>$` 引用(运行时解析为所选记录的显示值)——
|
|
26
|
+
* 早期版本误写成 `$<表名>.<字段名>$`,运行时解析不到就会把字面量显示出来;
|
|
27
|
+
* - 不写 `showFields`(设计器不生成该键);
|
|
28
|
+
* - `datasource.queryCondition` 是 `{ linkType, query: {} }`——带 `conditionGroup` 的
|
|
29
|
+
* 完整条件结构会导致运行时**列表加载失败**;
|
|
30
|
+
* - `isDisplayMode` 必须为 `false`(true 是「仅展示」模式);
|
|
31
|
+
* - 不写 `queryCollection` / `sorters` / `querySorters`(设计器未配置时不生成)。
|
|
32
|
+
*/
|
|
33
|
+
function buildRelationProps(spec) {
|
|
34
|
+
const { target } = spec;
|
|
35
|
+
const columns = spec.columns;
|
|
36
|
+
return {
|
|
37
|
+
renderMode: 'singlelist',
|
|
38
|
+
modelName: MODELING_MODEL_NAME,
|
|
39
|
+
relationCfg: {
|
|
40
|
+
appCode: target.appCode,
|
|
41
|
+
xformName: target.xformName,
|
|
42
|
+
modelId: target.modelId,
|
|
43
|
+
refFieldName: spec.refFieldName,
|
|
44
|
+
tableType: 'main',
|
|
45
|
+
tableName: target.tableName,
|
|
46
|
+
fdFormType: target.fdFormType ?? 0,
|
|
47
|
+
},
|
|
48
|
+
datasource: {
|
|
49
|
+
queryCondition: { linkType: '$and', query: {} },
|
|
50
|
+
columns,
|
|
51
|
+
filters: spec.filters ?? [],
|
|
52
|
+
tree: {
|
|
53
|
+
treeLoop: true,
|
|
54
|
+
querySorters: [],
|
|
55
|
+
conditionSetting: false,
|
|
56
|
+
relationCfg: {},
|
|
57
|
+
relationConditions: [{ operator: { value: '$eq' }, value: { format: 'field' } }],
|
|
58
|
+
queryCondition: {
|
|
59
|
+
linkType: '$and',
|
|
60
|
+
query: { type: 'conditionGroup', condition: [] },
|
|
61
|
+
},
|
|
62
|
+
navigationMode: 'custom',
|
|
63
|
+
// 树导航的实体引用(sys-modeling#<表单fdId>),设计器创建的字段必有
|
|
64
|
+
entityName: `sys-modeling#${target.modelId}`,
|
|
65
|
+
},
|
|
66
|
+
isListThrough: true,
|
|
67
|
+
isDisplayMode: false,
|
|
68
|
+
viewId: '',
|
|
69
|
+
conditionSetting: false,
|
|
70
|
+
isForwardView: true,
|
|
71
|
+
},
|
|
72
|
+
...(spec.outParams ? { outParams: spec.outParams } : {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
18
75
|
/** 全局动作支持的(前端注册的)事件类型 */
|
|
19
76
|
exports.GLOBAL_ACTION_EVENTS = ['onLoad', 'onBeforeSubmit', 'onAfterSubmit'];
|
|
20
77
|
/** 字段级多语言的 prop 集合(沿用旧视图时这些条目由生成器重建,不从旧 lang 携带) */
|
|
@@ -68,6 +125,28 @@ function collectUnreferencedFields(referencedKeys, built, details) {
|
|
|
68
125
|
}
|
|
69
126
|
return missing;
|
|
70
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* 由生成器统一管理的控件属性键:沿用已有 controlProps 时跳过(否则会覆盖新生成的身份信息、
|
|
130
|
+
* 选项、长度等)。其余键(如 address 的 range/multi/org、money 的 precision/symbol)
|
|
131
|
+
* 视为设计器配置,同控件包时原样沿用。
|
|
132
|
+
*/
|
|
133
|
+
const CONTROL_PROP_MANAGED_KEYS = new Set([
|
|
134
|
+
'id',
|
|
135
|
+
'desktop',
|
|
136
|
+
'mobile',
|
|
137
|
+
'name',
|
|
138
|
+
'uuid',
|
|
139
|
+
'title',
|
|
140
|
+
'span',
|
|
141
|
+
'$$tableType',
|
|
142
|
+
'$$tableName',
|
|
143
|
+
'$$lockMode',
|
|
144
|
+
'options',
|
|
145
|
+
'placeholder',
|
|
146
|
+
'maxLength',
|
|
147
|
+
]);
|
|
148
|
+
/** 不写入 fdFontExtendData 的键(options 由选项逻辑单独维护) */
|
|
149
|
+
const FONT_EXTEND_SKIP_KEYS = new Set(['options', 'rules']);
|
|
71
150
|
/** 控件配置面板默认值中,不应写入 controlProps 的键(属于样式/布局,存放在 controlStyle) */
|
|
72
151
|
const DEFAULT_EXCLUDE_KEYS = new Set([
|
|
73
152
|
'layout',
|
|
@@ -235,12 +314,25 @@ function completeOptionsI18n(field, lang, locales, messages) {
|
|
|
235
314
|
field.fdFontExtendData = JSON.stringify(fontExtend);
|
|
236
315
|
return count;
|
|
237
316
|
}
|
|
238
|
-
/**
|
|
317
|
+
/**
|
|
318
|
+
* 依据数据项类型/逻辑类型解析控件包与元数据。
|
|
319
|
+
*
|
|
320
|
+
* 匹配顺序(前者优先):
|
|
321
|
+
* 1. `CONTROL_TYPE_MAP[fdType]`:按控件逻辑类型精确匹配(避免「金额/评分/下拉」
|
|
322
|
+
* 被降级成同数据项类型的默认控件,如 number -> 数字);
|
|
323
|
+
* 2. `controlMap[fdType]`:用户自定义映射;
|
|
324
|
+
* 3. **字段已有属性中的控件包**:CLI 未登记的环境控件按原样保留(不被降级破坏);
|
|
325
|
+
* 4. `DEFAULT_CONTROL_MAP[fdDataType]`:按数据项类型兜底;
|
|
326
|
+
* 5. 自定义映射中 logicalType 相同的条目;
|
|
327
|
+
* 6. `FALLBACK_CONTROL`(输入框)。
|
|
328
|
+
*/
|
|
239
329
|
async function resolveControl(field, options) {
|
|
240
330
|
const map = controlMapOf(options);
|
|
241
|
-
// 优先按控件逻辑类型(xform.type)匹配,避免把「金额/评分/下拉」等
|
|
242
|
-
// 错误降级成同数据项类型的默认控件(如 number -> 数字)
|
|
243
331
|
let entry = constants_1.CONTROL_TYPE_MAP[field.fdType] ?? map[field.fdType];
|
|
332
|
+
// 未登记控件:从字段已有属性还原控件包(并保留原字段类型,不被控件包声明覆盖)
|
|
333
|
+
const preserved = entry ? undefined : preservedControlOf(field);
|
|
334
|
+
if (!entry)
|
|
335
|
+
entry = preserved;
|
|
244
336
|
if (!entry)
|
|
245
337
|
entry = map[field.fdDataType];
|
|
246
338
|
if (!entry)
|
|
@@ -253,11 +345,35 @@ async function resolveControl(field, options) {
|
|
|
253
345
|
return {
|
|
254
346
|
desktopPkg: entry.desktop,
|
|
255
347
|
mobilePkg: entry.mobile,
|
|
256
|
-
logicalType:
|
|
348
|
+
logicalType: preserved
|
|
349
|
+
? field.fdType || preserved.logicalType
|
|
350
|
+
: meta?.xform?.type || entry.logicalType,
|
|
351
|
+
kind: entry.kind ?? 'control',
|
|
257
352
|
meta,
|
|
258
353
|
metaMobile,
|
|
259
354
|
};
|
|
260
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* 从字段已有 `fdAttribute` 中还原控件包。
|
|
358
|
+
*
|
|
359
|
+
* 用于 **CLI 未登记的环境控件**(平台新增或定制控件包):此时若直接回落到
|
|
360
|
+
* `FALLBACK_CONTROL`(输入框),`autofill` 会把表单里的高级控件**降级破坏**。
|
|
361
|
+
* 只要字段属性里带着控件包名,就原样沿用,控件类型得以保全。
|
|
362
|
+
*/
|
|
363
|
+
function preservedControlOf(field) {
|
|
364
|
+
if (typeof field.fdAttribute !== 'string')
|
|
365
|
+
return undefined;
|
|
366
|
+
const attr = (0, random_1.safeJsonParse)(field.fdAttribute, {});
|
|
367
|
+
const config = attr?.config;
|
|
368
|
+
const desktop = config?.controlProps?.desktop?.type;
|
|
369
|
+
if (!desktop)
|
|
370
|
+
return undefined;
|
|
371
|
+
return {
|
|
372
|
+
desktop,
|
|
373
|
+
mobile: config?.controlProps?.mobile?.type || desktop,
|
|
374
|
+
logicalType: config?.type || field.fdType || '',
|
|
375
|
+
};
|
|
376
|
+
}
|
|
261
377
|
/** 从 fdFontExtendData 提取需要注入 controlProps 的前端扩展属性 */
|
|
262
378
|
function pickFontExtend(data) {
|
|
263
379
|
const picked = {};
|
|
@@ -271,6 +387,61 @@ function pickFontExtend(data) {
|
|
|
271
387
|
function fontExtendOf(field) {
|
|
272
388
|
return pickFontExtend((0, random_1.safeJsonParse)(field.fdFontExtendData, {}));
|
|
273
389
|
}
|
|
390
|
+
/** 按点号路径取值(用于语义校验规则) */
|
|
391
|
+
function getByPath(source, path) {
|
|
392
|
+
return path.split('.').reduce((acc, key) => {
|
|
393
|
+
if (!acc || typeof acc !== 'object')
|
|
394
|
+
return undefined;
|
|
395
|
+
return acc[key];
|
|
396
|
+
}, source);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* 控件语义校验:`config.json` 必填属性之外、由控件属性面板/数据源弹窗强制的规则
|
|
400
|
+
* (`CONTROL_SEMANTIC_RULES`,如业务关联的「关联表单」与「显示项」)。
|
|
401
|
+
*
|
|
402
|
+
* @returns 未通过规则的报错文案
|
|
403
|
+
*/
|
|
404
|
+
function validateSemanticProps(logicalType, controlProps) {
|
|
405
|
+
const rules = constants_1.CONTROL_SEMANTIC_RULES[logicalType];
|
|
406
|
+
if (!rules?.length)
|
|
407
|
+
return [];
|
|
408
|
+
const get = (path) => getByPath(controlProps, path);
|
|
409
|
+
const renderMode = String(controlProps.renderMode ?? '');
|
|
410
|
+
const errors = [];
|
|
411
|
+
for (const rule of rules) {
|
|
412
|
+
if (rule.renderModes && !rule.renderModes.includes(renderMode))
|
|
413
|
+
continue;
|
|
414
|
+
const pass = rule.ok ? rule.ok(get) : Boolean(get(rule.path ?? ''));
|
|
415
|
+
if (!pass)
|
|
416
|
+
errors.push(rule.message);
|
|
417
|
+
}
|
|
418
|
+
return errors;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* 把 `controlProps` 中「前端扩展数据」同步进 `fdFontExtendData`。
|
|
422
|
+
*
|
|
423
|
+
* 平台约定两处并存(前端 `syncApi.frontExtendData2Config` 反向同步、公式/接口 Schema 的
|
|
424
|
+
* `$ref` 依赖 `fdFontExtendData`)——例如 address(地址本)的 `orgTypeArr` 决定公式里
|
|
425
|
+
* 该字段是 `ORG_PERSON` 还是 `ORG_ALL`。缺了它,控件渲染仍走 controlProps,但公式取值
|
|
426
|
+
* 会退化。已有的其它键(如 options)原样保留。
|
|
427
|
+
*/
|
|
428
|
+
function syncFontExtendData(field, meta, controlProps) {
|
|
429
|
+
const keys = (0, widgets_1.collectFrontExtendKeys)(meta).filter(({ key }) => !FONT_EXTEND_SKIP_KEYS.has(key));
|
|
430
|
+
if (!keys.length)
|
|
431
|
+
return;
|
|
432
|
+
const fontExtend = field.fdFontExtendData
|
|
433
|
+
? (0, random_1.safeJsonParse)(field.fdFontExtendData, {})
|
|
434
|
+
: {};
|
|
435
|
+
for (const { path, key } of keys) {
|
|
436
|
+
const [head, child] = path.split('.');
|
|
437
|
+
const value = child
|
|
438
|
+
? controlProps[head]?.[child]
|
|
439
|
+
: controlProps[head];
|
|
440
|
+
if (value !== undefined)
|
|
441
|
+
fontExtend[key] = value;
|
|
442
|
+
}
|
|
443
|
+
field.fdFontExtendData = JSON.stringify(fontExtend);
|
|
444
|
+
}
|
|
274
445
|
/**
|
|
275
446
|
* 生成数据项的 fdAttribute。
|
|
276
447
|
*
|
|
@@ -293,11 +464,39 @@ function buildFieldAttribute(params) {
|
|
|
293
464
|
controlDefaults[k] = defaults[k];
|
|
294
465
|
});
|
|
295
466
|
const showText = defaults.showText;
|
|
467
|
+
// 必填属性兜底默认值:支持 $title / $name 占位符(如「描述文字」默认取字段标题)
|
|
468
|
+
const requiredDefaults = {};
|
|
469
|
+
for (const [propKey, value] of Object.entries(constants_1.REQUIRED_PROP_DEFAULTS[resolved.desktopPkg] ?? {})) {
|
|
470
|
+
requiredDefaults[propKey] =
|
|
471
|
+
value === '$title' ? langKey : value === '$name' ? fieldName : value;
|
|
472
|
+
}
|
|
473
|
+
// 沿用同控件包的已有属性(设计器配置过的值优先于 config.json 默认值)
|
|
474
|
+
const carried = {};
|
|
475
|
+
const prev = params.prevControlProps;
|
|
476
|
+
const prevPkg = prev?.desktop?.type;
|
|
477
|
+
if (prev && prevPkg === resolved.desktopPkg) {
|
|
478
|
+
// 业务关联重建时(extraControlProps 提供 relationCfg/datasource),旧字段可能带着
|
|
479
|
+
// 早期版本写入的空 outParams——设计器创建的字段没有这个键,不沿用
|
|
480
|
+
const skipStaleOutParams = params.extraControlProps != null && !('outParams' in params.extraControlProps);
|
|
481
|
+
for (const [propKey, value] of Object.entries(prev)) {
|
|
482
|
+
if (skipStaleOutParams && propKey === 'outParams')
|
|
483
|
+
continue;
|
|
484
|
+
if (!CONTROL_PROP_MANAGED_KEYS.has(propKey))
|
|
485
|
+
carried[propKey] = value;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
296
488
|
const controlProps = {
|
|
297
489
|
...controlDefaults,
|
|
490
|
+
// 设计器默认属性(控件包 config.json 未声明的,如业务关联的 renderMode=单一列表)
|
|
491
|
+
...(constants_1.CONTROL_DEFAULT_PROPS[resolved.desktopPkg] ?? {}),
|
|
298
492
|
// 必填属性兜底默认值(config.json 声明 required 但无 default 的项)
|
|
299
|
-
...
|
|
493
|
+
...requiredDefaults,
|
|
494
|
+
// 设计器新建控件的默认属性(逐键取自设计器落库,见 CONTROL_DESIGNER_DEFAULTS)——
|
|
495
|
+
// 按控件逻辑类型匹配,修正 CLI 与设计器新建控件的默认值差异
|
|
496
|
+
...(constants_1.CONTROL_DESIGNER_DEFAULTS[resolved.logicalType] ?? {}),
|
|
497
|
+
...carried,
|
|
300
498
|
...params.fontExtend,
|
|
499
|
+
...(params.extraControlProps ?? {}),
|
|
301
500
|
// 保留已有选项:本函数会重建 controlProps,不显式带回会丢失用户配置的 options
|
|
302
501
|
...(params.options && params.options.length ? { options: params.options } : {}),
|
|
303
502
|
...(params.placeholderKey ? { placeholder: params.placeholderKey } : {}),
|
|
@@ -328,7 +527,7 @@ function buildFieldAttribute(params) {
|
|
|
328
527
|
const config = {
|
|
329
528
|
key,
|
|
330
529
|
type: logicalType,
|
|
331
|
-
kind:
|
|
530
|
+
kind: resolved.kind,
|
|
332
531
|
label: langKey,
|
|
333
532
|
controlProps,
|
|
334
533
|
labelProps,
|
|
@@ -374,10 +573,9 @@ function enrichField(field, model, resolved) {
|
|
|
374
573
|
passValue: false,
|
|
375
574
|
trace: false,
|
|
376
575
|
encrypt: false,
|
|
377
|
-
// 初始值模式:config.json 默认 'null'(无初始值)。不能写 'formula'——
|
|
378
|
-
// 公式模式下 defaultValueFormulaVO 必填,设计器提交会被拦截
|
|
379
|
-
defaultValueType: 'null',
|
|
380
576
|
encryptDefinition: {},
|
|
577
|
+
// 注意:不写 defaultValueType——由 CONTROL_DESIGNER_DEFAULTS 提供各控件
|
|
578
|
+
// 的设计器默认值(formula/fixed/defalut…),并在构建后回同步到此处
|
|
381
579
|
});
|
|
382
580
|
}
|
|
383
581
|
if (!field.fdIsSystem && (field.fdOrder === undefined || field.fdOrder === null)) {
|
|
@@ -386,6 +584,16 @@ function enrichField(field, model, resolved) {
|
|
|
386
584
|
}
|
|
387
585
|
/** 依据数据项生成(或补全)控件配置,并回写 fdAttribute / fdLabelLangKey */
|
|
388
586
|
async function buildField(field, model, options, lang, locales) {
|
|
587
|
+
// 业务关联配置(--relation)命中该字段:先把数据类型改写为 relation,
|
|
588
|
+
// 使控件解析走业务关联控件(无论原字段是文本还是下拉)
|
|
589
|
+
const relationSpec = options.relations?.find((item) => item.fieldName === field.fdName);
|
|
590
|
+
if (relationSpec) {
|
|
591
|
+
field.fdType = 'relation';
|
|
592
|
+
field.fdDataType = 'relation';
|
|
593
|
+
field.fdDictType = 'querySourceDict';
|
|
594
|
+
if (!field.fdLength)
|
|
595
|
+
field.fdLength = 200;
|
|
596
|
+
}
|
|
389
597
|
const resolved = await resolveControl(field, options);
|
|
390
598
|
const span = options.span ?? constants_1.DEFAULT_SPAN;
|
|
391
599
|
enrichField(field, model, resolved);
|
|
@@ -400,24 +608,65 @@ async function buildField(field, model, options, lang, locales) {
|
|
|
400
608
|
type: 'input',
|
|
401
609
|
content: buildLangContent(locales, field.fdLabel || field.fdName, field.fdName, options.messages),
|
|
402
610
|
};
|
|
403
|
-
//
|
|
611
|
+
// 已有控件属性(沿用选项、提示文字等)
|
|
612
|
+
const prevAttr = (0, random_1.safeJsonParse)(field.fdAttribute, {});
|
|
613
|
+
// 控件提示文字多语言。
|
|
614
|
+
// ① 已有提示文字优先沿用(设计器里的自定义文案不能丢,如「请选择交办日期」);
|
|
615
|
+
// ② 否则取控件自带译文——资源键**因控件而异**(datetime=pleaseEnter、m-datetime=enter、
|
|
616
|
+
// area=pleaseSelect,其余=placeholder),键名不对就取不到文案(日期控件提示文字缺失即此原因)。
|
|
404
617
|
let placeholderKey;
|
|
405
618
|
const registry = options.registry;
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
if (
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
619
|
+
// 门控:config.json 声明了 placeholder 的控件 + 面板由设计器拼装(config.json 未声明
|
|
620
|
+
// 但设计器新建时同样写提示文字)的控件。两者都不满足的(radio/checkbox/button 等)
|
|
621
|
+
// 设计器也不写提示文字——不能仅凭资源探测,否则会多写(已与设计器新建表单核对)
|
|
622
|
+
if (registry &&
|
|
623
|
+
((0, widgets_1.hasConfigItem)(resolved.meta, 'placeholder') || constants_1.PLACEHOLDER_PANEL_PKGS[resolved.desktopPkg])) {
|
|
624
|
+
const prevPlaceholder = prevAttr?.config?.controlProps?.placeholder;
|
|
625
|
+
const prevLangEntry = typeof prevPlaceholder === 'string' ? options.prevLang?.[prevPlaceholder] : undefined;
|
|
626
|
+
if (typeof prevPlaceholder === 'string' &&
|
|
627
|
+
prevPlaceholder &&
|
|
628
|
+
(prevLangEntry || !(0, random_1.isMessageKey)(prevPlaceholder))) {
|
|
629
|
+
placeholderKey = prevPlaceholder;
|
|
630
|
+
if (prevLangEntry)
|
|
631
|
+
lang[prevPlaceholder] = prevLangEntry;
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
const mapped = constants_1.CONTROL_PLACEHOLDER_LANG_KEYS[resolved.desktopPkg];
|
|
635
|
+
const candidates = [
|
|
636
|
+
...new Set([mapped, ...constants_1.PLACEHOLDER_LANG_KEY_CANDIDATES].filter(Boolean)),
|
|
637
|
+
];
|
|
638
|
+
for (const keyName of candidates) {
|
|
639
|
+
const langs = await registry.getElementLangs((0, widgets_1.elementLangKey)(resolved.desktopPkg, keyName));
|
|
640
|
+
// 控件自带的多语言(zh-cn/en-us/...),用于填充非主语言的真实译文
|
|
641
|
+
const elementLangs = langs ? (0, locale_1.normalizeLangMap)(langs) : undefined;
|
|
642
|
+
const value = elementLangs?.[locales[0]] ??
|
|
643
|
+
elementLangs?.[constants_1.DEFAULT_LOCALE] ??
|
|
644
|
+
(elementLangs ? Object.values(elementLangs)[0] : undefined);
|
|
645
|
+
if (!value)
|
|
646
|
+
continue;
|
|
647
|
+
placeholderKey = (0, random_1.generateMessageKey)();
|
|
648
|
+
lang[placeholderKey] = {
|
|
649
|
+
prop: 'placeholder',
|
|
650
|
+
name: field.fdName,
|
|
651
|
+
type: 'input',
|
|
652
|
+
content: buildLangContent(locales, value, undefined, options.messages, elementLangs),
|
|
653
|
+
};
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
// 拼装面板控件(config.json 未声明 placeholder):控件资源探测不到时使用
|
|
657
|
+
// 设计器的通用默认文案(选择类=请选择、输入类=请输入,见 PLACEHOLDER_PANEL_PKGS)
|
|
658
|
+
if (!placeholderKey) {
|
|
659
|
+
const fallbackText = constants_1.PLACEHOLDER_PANEL_PKGS[resolved.desktopPkg];
|
|
660
|
+
if (fallbackText) {
|
|
661
|
+
placeholderKey = (0, random_1.generateMessageKey)();
|
|
662
|
+
lang[placeholderKey] = {
|
|
663
|
+
prop: 'placeholder',
|
|
664
|
+
name: field.fdName,
|
|
665
|
+
type: 'input',
|
|
666
|
+
content: buildLangContent(locales, fallbackText, undefined, options.messages),
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
}
|
|
421
670
|
}
|
|
422
671
|
}
|
|
423
672
|
// 新建选项(--option):写入 fdFontExtendData.options,作为选项的唯一来源
|
|
@@ -437,7 +686,6 @@ async function buildField(field, model, options, lang, locales) {
|
|
|
437
686
|
field.fdFontExtendData = JSON.stringify(fontExtend);
|
|
438
687
|
}
|
|
439
688
|
// 保留已有的控件选项(数据项/设计器中配置),否则重建 attribute 会丢
|
|
440
|
-
const prevAttr = (0, random_1.safeJsonParse)(field.fdAttribute, {});
|
|
441
689
|
const attrOptions = prevAttr?.config?.controlProps?.options;
|
|
442
690
|
const fontOptions = field.fdFontExtendData
|
|
443
691
|
? ((0, random_1.safeJsonParse)(field.fdFontExtendData, {}).options ?? [])
|
|
@@ -450,6 +698,21 @@ async function buildField(field, model, options, lang, locales) {
|
|
|
450
698
|
label: item?.label,
|
|
451
699
|
value: item?.value,
|
|
452
700
|
}));
|
|
701
|
+
// 选项类控件缺省选项:对齐设计器新建时的默认选项(选项1/2/3,值 1/2/3,含多语言)。
|
|
702
|
+
// CLI 此前不写默认选项,下拉/单选/多选运行时是空选项(真实缺陷,已与设计器新建表单核对)
|
|
703
|
+
const defaultOptions = !prevOptions.length && !specified?.length &&
|
|
704
|
+
['radio', 'checkbox', 'select', 'select~multi'].includes(String(field.fdType))
|
|
705
|
+
? ['1', '2', '3'].map((value, i) => {
|
|
706
|
+
const key = (0, random_1.generateMessageKey)();
|
|
707
|
+
lang[key] = {
|
|
708
|
+
prop: 'option',
|
|
709
|
+
name: field.fdName,
|
|
710
|
+
type: 'input',
|
|
711
|
+
content: buildLangContent(locales, `选项${i + 1}`, undefined, options.messages),
|
|
712
|
+
};
|
|
713
|
+
return { label: key, value };
|
|
714
|
+
})
|
|
715
|
+
: undefined;
|
|
453
716
|
const { attribute, config } = buildFieldAttribute({
|
|
454
717
|
fieldUuid: field.uuid,
|
|
455
718
|
langKey,
|
|
@@ -461,14 +724,49 @@ async function buildField(field, model, options, lang, locales) {
|
|
|
461
724
|
resolved,
|
|
462
725
|
placeholderKey,
|
|
463
726
|
fontExtend: fontExtendOf(field),
|
|
464
|
-
options: prevOptions.length ? prevOptions :
|
|
727
|
+
options: prevOptions.length ? prevOptions : defaultOptions,
|
|
728
|
+
prevControlProps: prevAttr?.config?.controlProps,
|
|
729
|
+
extraControlProps: relationSpec ? buildRelationProps(relationSpec) : undefined,
|
|
465
730
|
});
|
|
466
731
|
field.fdAttribute = attribute;
|
|
732
|
+
// 前端扩展数据同步(如 address 的 orgTypeArr/multi/range)
|
|
733
|
+
syncFontExtendData(field, resolved.meta, (config.controlProps ?? {}));
|
|
734
|
+
// fdFontExtendData.defaultValueType 与 controlProps 保持一致(设计器行为:
|
|
735
|
+
// 新建控件的默认初始值模式为 formula/fixed/defalut 等,见 CONTROL_DESIGNER_DEFAULTS)
|
|
736
|
+
{
|
|
737
|
+
const feObj = (0, random_1.safeJsonParse)(field.fdFontExtendData, {});
|
|
738
|
+
const cpObj = (config.controlProps ?? {});
|
|
739
|
+
if (cpObj.defaultValueType !== undefined && feObj.defaultValueType !== cpObj.defaultValueType) {
|
|
740
|
+
feObj.defaultValueType = cpObj.defaultValueType;
|
|
741
|
+
field.fdFontExtendData = JSON.stringify(feObj);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
// 业务关联的前端扩展数据(控件源码 JSDoc 声明的 @frontExtendData 路径):
|
|
745
|
+
// modelId/refFieldName/tableType/tableName、datasource 各键、renderMode、outParams
|
|
746
|
+
if (relationSpec) {
|
|
747
|
+
const fontExtend = field.fdFontExtendData
|
|
748
|
+
? (0, random_1.safeJsonParse)(field.fdFontExtendData, {})
|
|
749
|
+
: {};
|
|
750
|
+
// 早期版本生成、现在不再写的键:清掉,避免运行时拿到过期结构
|
|
751
|
+
for (const stale of ['queryCollection', 'querySorters', 'sorters'])
|
|
752
|
+
delete fontExtend[stale];
|
|
753
|
+
if (!relationSpec.outParams)
|
|
754
|
+
delete fontExtend.outParams;
|
|
755
|
+
const props = (config.controlProps ?? {});
|
|
756
|
+
for (const path of constants_1.RELATION_FRONT_EXTEND_PATHS) {
|
|
757
|
+
const value = getByPath(props, path);
|
|
758
|
+
if (value !== undefined)
|
|
759
|
+
fontExtend[path.split('.').pop()] = value;
|
|
760
|
+
}
|
|
761
|
+
field.fdFontExtendData = JSON.stringify(fontExtend);
|
|
762
|
+
}
|
|
467
763
|
// 选项多语言(下拉/单选/多选)
|
|
468
764
|
const optionCount = completeOptionsI18n(field, lang, locales, options.messages);
|
|
469
765
|
// 控件必填属性校验(对齐设计器发布前校验)
|
|
470
766
|
const propErrors = (0, widgets_1.validateRequiredProps)(resolved.meta, config);
|
|
471
|
-
|
|
767
|
+
// 控件语义校验(属性面板/数据源弹窗强制的规则,如业务关联的关联表单与显示项)
|
|
768
|
+
const semanticErrors = validateSemanticProps(resolved.logicalType, (config.controlProps ?? {}));
|
|
769
|
+
return { field, model, resolved, langKey, optionCount, propErrors, semanticErrors };
|
|
472
770
|
}
|
|
473
771
|
/** 构建虚拟行:控件节点在持久化格式中**只保留 key 引用** */
|
|
474
772
|
function buildRows(built, options) {
|
|
@@ -489,7 +787,7 @@ function buildRows(built, options) {
|
|
|
489
787
|
return rows;
|
|
490
788
|
}
|
|
491
789
|
/**
|
|
492
|
-
* 栅格布局:`layout` → `@elem/layout-grid` →
|
|
790
|
+
* 栅格布局:`layout` → `@elem/layout-grid` → `columns × rows` 个 `@elem/layout-grid.GridItem`。
|
|
493
791
|
*
|
|
494
792
|
* 结构依据真实环境表单(`layout~xxxx` / `controlProps:{id}`):
|
|
495
793
|
* ```
|
|
@@ -497,6 +795,9 @@ function buildRows(built, options) {
|
|
|
497
795
|
* └ @elem/layout-grid { controlProps:{ id, columns, rows } }
|
|
498
796
|
* └ GridItem { controlProps:{ id, column, row, rowSpan, columnSpan, style:{backgroundColor:''} }, children:[{key:<fieldUuid>}] }
|
|
499
797
|
* ```
|
|
798
|
+
* **最后一行的空位也要保留**:`GridItem` 总数 = `columns × rows`,未放控件的格子为
|
|
799
|
+
* `children: []`、`controlProps: { id, column, row }`(与设计器一致,已核对真实表单——
|
|
800
|
+
* 如《业务审批管理》cols=3 而字段只有 2 个,第 3 个格子为空仍在)。
|
|
500
801
|
* 注意:栅格布局控件**不进** `controlMappings`(已核对真实表单)。
|
|
501
802
|
*/
|
|
502
803
|
function buildGrid(built, columns) {
|
|
@@ -504,6 +805,7 @@ function buildGrid(built, columns) {
|
|
|
504
805
|
const rows = Math.max(1, Math.ceil(built.length / cols));
|
|
505
806
|
const layoutKey = (0, random_1.generateCmptKey)(constants_1.LAYOUT);
|
|
506
807
|
const gridKey = (0, random_1.generateCmptKey)(constants_1.LAYOUT_GRID);
|
|
808
|
+
// 先放控件,再补齐最后一行的空位(总数 = cols × rows;无字段时不生成格子)
|
|
507
809
|
const items = built.map((item, index) => {
|
|
508
810
|
const itemKey = (0, random_1.generateCmptKey)(constants_1.LAYOUT_GRID_ITEM);
|
|
509
811
|
return {
|
|
@@ -521,6 +823,21 @@ function buildGrid(built, columns) {
|
|
|
521
823
|
children: [{ key: item.field.uuid }],
|
|
522
824
|
};
|
|
523
825
|
});
|
|
826
|
+
for (let index = built.length; index < (built.length ? rows * cols : 0); index++) {
|
|
827
|
+
const itemKey = (0, random_1.generateCmptKey)(constants_1.LAYOUT_GRID_ITEM);
|
|
828
|
+
items.push({
|
|
829
|
+
key: itemKey,
|
|
830
|
+
type: constants_1.LAYOUT_GRID_ITEM,
|
|
831
|
+
kind: 'container',
|
|
832
|
+
// 空位与设计器一致:仅 column/row/id,children 为空
|
|
833
|
+
controlProps: {
|
|
834
|
+
column: (index % cols) + 1,
|
|
835
|
+
row: Math.floor(index / cols) + 1,
|
|
836
|
+
id: itemKey,
|
|
837
|
+
},
|
|
838
|
+
children: [],
|
|
839
|
+
});
|
|
840
|
+
}
|
|
524
841
|
return [
|
|
525
842
|
{
|
|
526
843
|
key: layoutKey,
|
|
@@ -676,6 +993,9 @@ function wrapAppearance(tableName, children) {
|
|
|
676
993
|
key: constants_1.MAIN,
|
|
677
994
|
type: constants_1.MAIN,
|
|
678
995
|
kind: 'container',
|
|
996
|
+
// 设计器保存时经 setMainCmpt 写入(ide/editor/runtime/business/feature.ts),
|
|
997
|
+
// 值为空也必须带键:CLI 曾缺失该属性,与设计器发布的视图不一致
|
|
998
|
+
controlProps: { importBtn: null, onDesktopImport: null },
|
|
679
999
|
children,
|
|
680
1000
|
},
|
|
681
1001
|
],
|
|
@@ -687,7 +1007,7 @@ function wrapAppearance(tableName, children) {
|
|
|
687
1007
|
* 注意:栅格布局(`layout` / `@elem/layout-grid` / `GridItem`)**不写入**此处,
|
|
688
1008
|
* 而多页签(`tab-appearance` / `tabs` / `tabpane`)**需要写入** —— 均为真实表单核对结论。
|
|
689
1009
|
*/
|
|
690
|
-
function buildControlMappings(built, options = {}) {
|
|
1010
|
+
function buildControlMappings(built, options = {}, details = []) {
|
|
691
1011
|
/** 布局控件的兜底元数据(控件包无 xform 时用,与真实表单的 xform.type 一致) */
|
|
692
1012
|
const layoutDefaults = {
|
|
693
1013
|
[constants_1.TAB_APPEARANCE]: 'tabAppearance',
|
|
@@ -697,11 +1017,13 @@ function buildControlMappings(built, options = {}) {
|
|
|
697
1017
|
const build = (platform) => {
|
|
698
1018
|
const seen = new Set();
|
|
699
1019
|
const list = [];
|
|
700
|
-
|
|
1020
|
+
// **主表 + 明细字段**的控件都要进映射:运行时按 controlMappings 解析控件,
|
|
1021
|
+
// 明细字段的控件缺失时明细表渲染不完整(如顶部操作栏不出现)——真实缺陷已核对
|
|
1022
|
+
for (const item of [...built, ...details.flatMap((d) => d.fields)]) {
|
|
701
1023
|
const pkg = platform === 'desktop' ? item.resolved.desktopPkg : item.resolved.mobilePkg;
|
|
702
1024
|
const meta = platform === 'desktop' ? item.resolved.meta : item.resolved.metaMobile;
|
|
703
1025
|
if (!pkg || seen.has(pkg))
|
|
704
|
-
|
|
1026
|
+
continue;
|
|
705
1027
|
seen.add(pkg);
|
|
706
1028
|
const xform = { ...(meta?.xform ?? {}), type: item.resolved.logicalType };
|
|
707
1029
|
// 前端 handleFindXFormWidgets 会把包级 env 同步进 xform.env
|
|
@@ -710,7 +1032,7 @@ function buildControlMappings(built, options = {}) {
|
|
|
710
1032
|
xform.env = meta.env;
|
|
711
1033
|
}
|
|
712
1034
|
list.push({ name: pkg, platform, xform });
|
|
713
|
-
}
|
|
1035
|
+
}
|
|
714
1036
|
if (options.layout === 'tabs') {
|
|
715
1037
|
const pkgs = platform === 'desktop'
|
|
716
1038
|
? [constants_1.TAB_APPEARANCE, constants_1.TABS, constants_1.TAB_PANE]
|
|
@@ -745,7 +1067,7 @@ function buildViewModel(built, mainTableName, options = {}, lang = {}, locales =
|
|
|
745
1067
|
});
|
|
746
1068
|
// 页签多语言只生成一次,供桌面端渲染树复用
|
|
747
1069
|
const panes = options.layout === 'tabs' ? resolveTabs(built, options, lang, locales) : [];
|
|
748
|
-
const mappings = buildControlMappings(built, options);
|
|
1070
|
+
const mappings = buildControlMappings(built, options, details);
|
|
749
1071
|
if (detailMappings?.desktop.length)
|
|
750
1072
|
mappings.desktop.push(...detailMappings.desktop);
|
|
751
1073
|
if (detailMappings?.mobile.length)
|
|
@@ -820,7 +1142,7 @@ function buildAuth(models, options = {}) {
|
|
|
820
1142
|
hide: false,
|
|
821
1143
|
...perm.edit?.[field.fdName],
|
|
822
1144
|
};
|
|
823
|
-
viewFields[field.fdName] = { visible: true, ...perm.view?.[field.fdName] };
|
|
1145
|
+
viewFields[field.fdName] = { visible: true, hide: false, ...perm.view?.[field.fdName] };
|
|
824
1146
|
});
|
|
825
1147
|
add[model.fdTableName] = {
|
|
826
1148
|
...constants_1.DETAIL_ROW_FLAGS.add,
|
|
@@ -857,7 +1179,28 @@ function buildAuth(models, options = {}) {
|
|
|
857
1179
|
hide: false,
|
|
858
1180
|
...perm.edit?.[field.fdName],
|
|
859
1181
|
};
|
|
860
|
-
viewFields[field.fdName] = { visible: true, ...perm.view?.[field.fdName] };
|
|
1182
|
+
viewFields[field.fdName] = { visible: true, hide: false, ...perm.view?.[field.fdName] };
|
|
1183
|
+
});
|
|
1184
|
+
// 明细表在主表 auth 中以「字段条目」出现,携带行操作(添加行/删除行/导入/导出)。
|
|
1185
|
+
// 缺失时运行时明细表顶部操作栏不渲染(真实缺陷,设计器保存的结构已核对)
|
|
1186
|
+
models
|
|
1187
|
+
.filter((m) => m.fdType === 'detail')
|
|
1188
|
+
.forEach((detail) => {
|
|
1189
|
+
addFields[detail.fdTableName] = {
|
|
1190
|
+
...constants_1.DETAIL_MAIN_FIELD_BASE.add,
|
|
1191
|
+
...perm.add?.[detail.fdTableName],
|
|
1192
|
+
operations: constants_1.DETAIL_MAIN_FIELD_OPERATIONS.add,
|
|
1193
|
+
};
|
|
1194
|
+
editFields[detail.fdTableName] = {
|
|
1195
|
+
...constants_1.DETAIL_MAIN_FIELD_BASE.add,
|
|
1196
|
+
...perm.edit?.[detail.fdTableName],
|
|
1197
|
+
operations: constants_1.DETAIL_MAIN_FIELD_OPERATIONS.add,
|
|
1198
|
+
};
|
|
1199
|
+
viewFields[detail.fdTableName] = {
|
|
1200
|
+
...constants_1.DETAIL_MAIN_FIELD_BASE.view,
|
|
1201
|
+
...perm.view?.[detail.fdTableName],
|
|
1202
|
+
operations: constants_1.DETAIL_MAIN_FIELD_OPERATIONS.view,
|
|
1203
|
+
};
|
|
861
1204
|
});
|
|
862
1205
|
add[model.fdTableName] = { fields: addFields };
|
|
863
1206
|
edit[model.fdTableName] = { fields: editFields };
|
|
@@ -1119,6 +1462,9 @@ async function buildFdConfig(models, options = {}, stats) {
|
|
|
1119
1462
|
for (const error of builtField.propErrors) {
|
|
1120
1463
|
validationErrors.push(`字段 ${field.fdName}(${builtField.resolved.desktopPkg}):请输入${error.label}(${error.key})`);
|
|
1121
1464
|
}
|
|
1465
|
+
for (const error of builtField.semanticErrors) {
|
|
1466
|
+
validationErrors.push(`字段 ${field.fdName}(${builtField.resolved.desktopPkg}):${error}`);
|
|
1467
|
+
}
|
|
1122
1468
|
}
|
|
1123
1469
|
// ---------- 明细表 ----------
|
|
1124
1470
|
const detailModels = models.filter((m) => m.fdType === 'detail');
|
|
@@ -1144,6 +1490,9 @@ async function buildFdConfig(models, options = {}, stats) {
|
|
|
1144
1490
|
for (const error of builtField.propErrors) {
|
|
1145
1491
|
validationErrors.push(`明细字段 ${model.fdTableName}.${field.fdName}(${builtField.resolved.desktopPkg}):请输入${error.label}(${error.key})`);
|
|
1146
1492
|
}
|
|
1493
|
+
for (const error of builtField.semanticErrors) {
|
|
1494
|
+
validationErrors.push(`明细字段 ${model.fdTableName}.${field.fdName}(${builtField.resolved.desktopPkg}):${error}`);
|
|
1495
|
+
}
|
|
1147
1496
|
}
|
|
1148
1497
|
// 明细表标题多语言(prop:label,content 额外带 default)
|
|
1149
1498
|
const langKey = (0, random_1.isMessageKey)(model.fdLabelLangKey)
|
|
@@ -1214,6 +1563,54 @@ async function buildFdConfig(models, options = {}, stats) {
|
|
|
1214
1563
|
];
|
|
1215
1564
|
}
|
|
1216
1565
|
}
|
|
1566
|
+
// 沿用视图时同样补齐 `<main>` 节点的 controlProps(设计器保存时经 setMainCmpt 写入,
|
|
1567
|
+
// CLI 早期版本重建的视图缺失;详见 wrapAppearance 注释),并把 controlMappings
|
|
1568
|
+
// 与重建的映射做并集——旧视图的映射可能缺明细字段的控件(真实缺陷)
|
|
1569
|
+
const rebuiltMappings = buildControlMappings(built, options, details);
|
|
1570
|
+
for (const vm of viewModel) {
|
|
1571
|
+
const viewConfig = (0, random_1.safeJsonParse)(vm.fdConfig, {});
|
|
1572
|
+
const render = viewConfig?.view?.render;
|
|
1573
|
+
if (!render)
|
|
1574
|
+
continue;
|
|
1575
|
+
let patched = false;
|
|
1576
|
+
const patchMain = (nodes) => {
|
|
1577
|
+
for (const node of nodes) {
|
|
1578
|
+
if (!node || typeof node !== 'object')
|
|
1579
|
+
continue;
|
|
1580
|
+
if (node.type === constants_1.MAIN) {
|
|
1581
|
+
node.controlProps = {
|
|
1582
|
+
importBtn: null,
|
|
1583
|
+
onDesktopImport: null,
|
|
1584
|
+
...(node.controlProps ?? {}),
|
|
1585
|
+
};
|
|
1586
|
+
patched = true;
|
|
1587
|
+
return true;
|
|
1588
|
+
}
|
|
1589
|
+
if (Array.isArray(node.children) && patchMain(node.children))
|
|
1590
|
+
return true;
|
|
1591
|
+
}
|
|
1592
|
+
return false;
|
|
1593
|
+
};
|
|
1594
|
+
for (const platform of Object.keys(render)) {
|
|
1595
|
+
if (Array.isArray(render[platform]))
|
|
1596
|
+
patchMain(render[platform]);
|
|
1597
|
+
}
|
|
1598
|
+
// controlMappings 并集:旧条目保留(如页签控件),重建条目按 name 覆盖(元数据最新)
|
|
1599
|
+
const cm = viewConfig.view?.controlMappings;
|
|
1600
|
+
if (cm) {
|
|
1601
|
+
for (const platform of ['desktop', 'mobile']) {
|
|
1602
|
+
const byName = new Map((Array.isArray(cm[platform]) ? cm[platform] : []).map((e) => [e.name, e]));
|
|
1603
|
+
for (const entry of rebuiltMappings[platform] ?? [])
|
|
1604
|
+
byName.set(entry.name, entry);
|
|
1605
|
+
for (const entry of detailMappings?.[platform] ?? [])
|
|
1606
|
+
byName.set(entry.name, entry);
|
|
1607
|
+
cm[platform] = [...byName.values()];
|
|
1608
|
+
patched = true;
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
if (patched)
|
|
1612
|
+
vm.fdConfig = JSON.stringify(viewConfig);
|
|
1613
|
+
}
|
|
1217
1614
|
}
|
|
1218
1615
|
else {
|
|
1219
1616
|
viewModel = buildViewModel(built, mainTableName, options, lang, locales, details, detailMappings);
|
|
@@ -1227,7 +1624,28 @@ async function buildFdConfig(models, options = {}, stats) {
|
|
|
1227
1624
|
stats.actionWarnings = actionResult.warnings;
|
|
1228
1625
|
if (validationErrors.length)
|
|
1229
1626
|
stats.validationErrors = validationErrors;
|
|
1627
|
+
// 需到设计器完成配置的高级控件(关键属性是环境对象选择,CLI 无法替选)。
|
|
1628
|
+
// 仅在该字段确实缺配置(有必填/语义校验错误)时提示——已配置好的字段不该被误导;
|
|
1629
|
+
// associatedFields 无必填属性、问题只在发布时暴露,故无条件提示。
|
|
1630
|
+
const notes = new Set();
|
|
1631
|
+
for (const item of [...built, ...details.flatMap((d) => d.fields)]) {
|
|
1632
|
+
const logicalType = String(item.field.fdType);
|
|
1633
|
+
const note = constants_1.CONTROL_DESIGNER_NOTES[logicalType];
|
|
1634
|
+
if (!note)
|
|
1635
|
+
continue;
|
|
1636
|
+
const hasErrors = item.propErrors.length > 0 || item.semanticErrors.length > 0;
|
|
1637
|
+
if (!hasErrors && !constants_1.CONTROL_NOTE_WITHOUT_ERRORS.has(logicalType))
|
|
1638
|
+
continue;
|
|
1639
|
+
notes.add(`${item.field.fdLabel || item.field.fdName}(${logicalType})—— ${note}`);
|
|
1640
|
+
}
|
|
1641
|
+
if (notes.size)
|
|
1642
|
+
stats.controlWarnings = [...notes];
|
|
1230
1643
|
}
|
|
1644
|
+
// attribute:formAttr 重建,其余顶层键(dataUnique / subjectRule 等)从已有设计整体保留
|
|
1645
|
+
let prevAttributeObj = {};
|
|
1646
|
+
if (options.prevAttribute)
|
|
1647
|
+
prevAttributeObj = (0, random_1.safeJsonParse)(options.prevAttribute, {});
|
|
1648
|
+
delete prevAttributeObj.formAttr;
|
|
1231
1649
|
return {
|
|
1232
1650
|
id: options.formId ?? '',
|
|
1233
1651
|
name: options.formName ?? '',
|
|
@@ -1236,7 +1654,7 @@ async function buildFdConfig(models, options = {}, stats) {
|
|
|
1236
1654
|
entityCode: options.entityCode ?? constants_1.ENTITY_CODE,
|
|
1237
1655
|
dataModel: models,
|
|
1238
1656
|
auth: buildAuth(models, options),
|
|
1239
|
-
attribute: { formAttr: JSON.stringify(formAttr) },
|
|
1657
|
+
attribute: { ...prevAttributeObj, formAttr: JSON.stringify(formAttr) },
|
|
1240
1658
|
error: JSON.stringify({}),
|
|
1241
1659
|
lang: JSON.stringify(lang),
|
|
1242
1660
|
authFilter: { detailAuthDefine: {} },
|
|
@@ -1259,6 +1677,14 @@ async function createDataField(spec, options = {}) {
|
|
|
1259
1677
|
controlType = dataType;
|
|
1260
1678
|
dataType = undefined;
|
|
1261
1679
|
}
|
|
1680
|
+
// 容器类控件不是数据项(明细表/大数据明细/分页),给出明确指引而不是模糊报错。
|
|
1681
|
+
// 第三段既可写数据项类型也可写控件类型(见下方容错),两个位置都要拦。
|
|
1682
|
+
for (const token of [spec.controlType, spec.dataType]) {
|
|
1683
|
+
const container = token ? constants_1.CONTAINER_CONTROL_TYPES[token] : undefined;
|
|
1684
|
+
if (container) {
|
|
1685
|
+
throw new Error(`"${token}" 是容器类控件,不能用 --add-field 创建:${container.hint}`);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1262
1688
|
let entry;
|
|
1263
1689
|
if (controlType) {
|
|
1264
1690
|
entry = alias[controlType];
|
|
@@ -1278,16 +1704,22 @@ async function createDataField(spec, options = {}) {
|
|
|
1278
1704
|
const registry = options.registry;
|
|
1279
1705
|
const meta = registry ? await registry.get(entry.desktop) : null;
|
|
1280
1706
|
const xform = meta?.xform ?? {};
|
|
1707
|
+
// 元数据(控件包 config.json)优先,映射表兜底:高级控件中 desc/serial-number
|
|
1708
|
+
// 等控件包未声明 dataType,必须靠映射表补齐,否则数据项类型为空会导致保存失败
|
|
1709
|
+
const resolvedDataType = xform.dataType || entry.dataType || dataType || 'varchar';
|
|
1710
|
+
const resolvedDictType = xform.dictType || entry.dictType;
|
|
1711
|
+
const resolvedLength = xform.defaultLength ?? entry.defaultLength ?? 0;
|
|
1712
|
+
const resolvedStored = xform.isStored ?? entry.isStored ?? true;
|
|
1281
1713
|
return {
|
|
1282
1714
|
fdName: spec.name || `${constants_1.FIELD_NAME_PREFIX}${Math.random().toString(36).slice(-6)}`,
|
|
1283
1715
|
fdLabel: spec.label,
|
|
1284
|
-
fdDataType:
|
|
1716
|
+
fdDataType: resolvedDataType,
|
|
1285
1717
|
fdType: xform.type || entry.logicalType,
|
|
1286
|
-
fdDictType:
|
|
1287
|
-
fdLength:
|
|
1718
|
+
...(resolvedDictType ? { fdDictType: resolvedDictType } : {}),
|
|
1719
|
+
fdLength: resolvedLength,
|
|
1288
1720
|
fdIsSystem: false,
|
|
1289
1721
|
fdDisplay: true,
|
|
1290
|
-
fdIsStored:
|
|
1722
|
+
fdIsStored: resolvedStored,
|
|
1291
1723
|
fdState: 'effective',
|
|
1292
1724
|
};
|
|
1293
1725
|
}
|