openyida 2026.7.18 → 2026.7.21

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 (200) hide show
  1. package/README.md +37 -2
  2. package/bin/yida.js +113 -18
  3. package/docs/capabilities.md +202 -0
  4. package/lib/agent-center/agent-center.js +2 -3
  5. package/lib/app/app-list.js +3 -4
  6. package/lib/app/build-page.js +13 -6
  7. package/lib/app/check-page.js +16 -6
  8. package/lib/app/compile.js +17 -7
  9. package/lib/app/create-app.js +14 -2
  10. package/lib/app/create-form/api-path.js +25 -0
  11. package/lib/app/create-form/args.js +13 -0
  12. package/lib/app/create-form/commands.js +4 -0
  13. package/lib/app/create-form/definition-reader.js +23 -23
  14. package/lib/app/create-form/field-normalizers.js +26 -7
  15. package/lib/app/create-form/schema-patch.js +4 -1
  16. package/lib/app/create-form.js +155 -68
  17. package/lib/app/create-page.js +47 -10
  18. package/lib/app/generate-page.js +20 -1
  19. package/lib/app/get-form-config.js +2 -1
  20. package/lib/app/get-schema.js +67 -15
  21. package/lib/app/import-app.js +94 -42
  22. package/lib/app/list-forms.js +4 -8
  23. package/lib/app/page-ir.js +140 -0
  24. package/lib/app/publish.js +289 -556
  25. package/lib/app/schema-field-resolution.js +370 -0
  26. package/lib/app/services/app-reader.js +85 -0
  27. package/lib/app/services/app-service.js +235 -0
  28. package/lib/app/services/canvas-page-compiler.js +91 -0
  29. package/lib/app/services/canvas-page-schema-builder.js +149 -0
  30. package/lib/app/services/field-bindings.js +68 -0
  31. package/lib/app/services/form-compiler.js +1922 -0
  32. package/lib/app/services/form-mode-service.js +182 -0
  33. package/lib/app/services/form-schema-patcher.js +333 -0
  34. package/lib/app/services/form-schema-reader.js +59 -0
  35. package/lib/app/services/form-service.js +379 -0
  36. package/lib/app/services/form-validation.js +562 -0
  37. package/lib/app/services/native-page-compiler.js +92 -0
  38. package/lib/app/services/native-page-schema-builder.js +428 -0
  39. package/lib/app/services/page-resource-service.js +185 -0
  40. package/lib/app/update-app.js +22 -9
  41. package/lib/app/update-form-config.js +4 -2
  42. package/lib/asset/asset-cmd.js +19 -6
  43. package/lib/auth/oauth-loopback.js +376 -15
  44. package/lib/auth/token-auth.js +34 -3
  45. package/lib/auth/token-store.js +22 -4
  46. package/lib/basic-info/basic-info.js +2 -1
  47. package/lib/bridge/bridge.js +43 -18
  48. package/lib/core/agent-capabilities.js +5 -3
  49. package/lib/core/command-errors.js +38 -0
  50. package/lib/core/command-manifest.js +59 -1
  51. package/lib/core/copy.js +12 -0
  52. package/lib/core/doctor.js +15 -4
  53. package/lib/core/env-cmd.js +1 -1
  54. package/lib/core/env.js +7 -6
  55. package/lib/core/i18n.js +66 -8
  56. package/lib/core/legacy-schema-guard.js +408 -0
  57. package/lib/core/locales/en.js +38 -46
  58. package/lib/core/locales/zh.js +30 -46
  59. package/lib/core/sample.js +12 -2
  60. package/lib/core/utils.js +334 -66
  61. package/lib/core/yida-client.js +108 -5
  62. package/lib/corp-efficiency/corp-efficiency.js +2 -2
  63. package/lib/corp-manager/corp-manager.js +3 -3
  64. package/lib/formula/evaluate.js +11 -68
  65. package/lib/formula/field-refs.js +83 -0
  66. package/lib/i18n-management/i18n-management.js +2 -3
  67. package/lib/process/configure-process.js +44 -1992
  68. package/lib/process/create-process.js +54 -87
  69. package/lib/process/preview-process.js +20 -8
  70. package/lib/process/services/process-compiler.js +2056 -0
  71. package/lib/process/services/process-reader.js +232 -0
  72. package/lib/process/services/process-resource-service.js +571 -0
  73. package/lib/process/services/process-service.js +122 -0
  74. package/lib/process/services/process-stage-checkpoint.js +246 -0
  75. package/lib/report/append.js +7 -26
  76. package/lib/report/http.js +31 -5
  77. package/lib/report/index.js +10 -2
  78. package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
  79. package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
  80. package/lib/samples/yida-canvas-custom-page/dashboard-starter.canvas.jsx +5 -2
  81. package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
  82. package/lib/samples/yida-canvas-custom-page/portal-shell-home.canvas.jsx +2 -2
  83. package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +206 -10
  84. package/lib/samples/yida-custom-page/data-management.oyd.jsx +14 -3
  85. package/lib/samples/yida-custom-page/design-tokens.js +2 -2
  86. package/lib/samples/yida-custom-page/detail-profile.oyd.jsx +1 -1
  87. package/lib/samples/yida-custom-page/portal-shell-home.oyd.jsx +7 -2
  88. package/lib/samples/yida-custom-page/split-pane-detail.oyd.jsx +2 -1
  89. package/lib/schema/adapters/app-adapter.js +210 -0
  90. package/lib/schema/adapters/form-adapter.js +1362 -0
  91. package/lib/schema/adapters/keys.js +19 -0
  92. package/lib/schema/adapters/page-adapter.js +566 -0
  93. package/lib/schema/adapters/process-adapter.js +519 -0
  94. package/lib/schema/applier.js +1878 -0
  95. package/lib/schema/apply-store.js +1083 -0
  96. package/lib/schema/command.js +645 -0
  97. package/lib/schema/dependency-graph.js +83 -0
  98. package/lib/schema/errors.js +481 -0
  99. package/lib/schema/hash.js +9 -0
  100. package/lib/schema/manifest-limits.js +177 -0
  101. package/lib/schema/manifest-loader.js +309 -0
  102. package/lib/schema/manifest-schema-v1.json +193 -0
  103. package/lib/schema/normalize-manifest.js +281 -0
  104. package/lib/schema/page-canvas-foundation.js +447 -0
  105. package/lib/schema/page-data-source-builder.js +561 -0
  106. package/lib/schema/page-foundation.js +556 -0
  107. package/lib/schema/page-source-loader.js +314 -0
  108. package/lib/schema/planner.js +691 -0
  109. package/lib/schema/remote-dispatch-boundary.js +39 -0
  110. package/lib/schema/remote-missing.js +21 -0
  111. package/lib/schema/remote-reader.js +189 -0
  112. package/lib/schema/resource-registry.js +157 -0
  113. package/lib/schema/server-revision.js +71 -0
  114. package/lib/schema/sort.js +28 -0
  115. package/lib/schema/state-store.js +589 -0
  116. package/package.json +10 -8
  117. package/scripts/check-release-risks.js +254 -0
  118. package/scripts/generate-command-docs.js +6 -1
  119. package/scripts/i18n-baseline.json +11 -11
  120. package/scripts/postinstall.js +8 -12
  121. package/scripts/validate-ci.sh +15 -11
  122. package/scripts/validate-i18n.js +30 -9
  123. package/scripts/validate-package-size.js +5 -2
  124. package/scripts/validate-skills.js +4 -0
  125. package/scripts/validate-structure.js +15 -0
  126. package/yida-skills/SKILL.md +117 -30
  127. package/yida-skills/references/schema-as-code-phase1.md +99 -0
  128. package/yida-skills/references/setup-and-env.md +59 -59
  129. package/yida-skills/references/task-retrospective.md +6 -6
  130. package/yida-skills/skills/yida-app/SKILL.md +78 -17
  131. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
  132. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
  133. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +15 -0
  134. package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
  135. package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
  136. package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
  137. package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
  138. package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
  139. package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
  140. package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
  141. package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
  142. package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
  143. package/yida-skills/skills/yida-integration/SKILL.md +7 -1
  144. package/yida-skills/skills/yida-login/SKILL.md +50 -99
  145. package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
  146. package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
  147. package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
  148. package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
  149. package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
  150. package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
  151. package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
  152. package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
  153. package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
  154. package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
  155. package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
  156. package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
  157. package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
  158. package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
  159. package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
  160. package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
  161. package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
  162. package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
  163. package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
  164. package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
  165. package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
  166. package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
  167. package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
  168. package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
  169. package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
  170. package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
  171. package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
  172. package/yida-skills/skills/yida-report/SKILL.md +9 -235
  173. package/yida-skills/skills/yida-report/references/schema-builder-details.md +239 -0
  174. package/yida-skills/skills/yida-skill-evaluator/SKILL.md +170 -0
  175. package/yida-skills/skills-index.json +238 -54
  176. package/lib/core/locales/ar.js +0 -834
  177. package/lib/core/locales/de.js +0 -834
  178. package/lib/core/locales/es.js +0 -834
  179. package/lib/core/locales/fr.js +0 -834
  180. package/lib/core/locales/hi.js +0 -834
  181. package/lib/core/locales/ja.js +0 -1227
  182. package/lib/core/locales/ko.js +0 -836
  183. package/lib/core/locales/pt.js +0 -834
  184. package/lib/core/locales/vi.js +0 -834
  185. package/lib/core/locales/zh-HK.js +0 -1263
  186. package/project/pages/src/demo-birthday-game.oyd.jsx +0 -832
  187. package/project/pages/src/demo-chip-insight.oyd.jsx +0 -983
  188. package/project/pages/src/demo-compat-smoke.oyd.jsx +0 -58
  189. package/project/pages/src/demo-crm-batch-entry.oyd.jsx +0 -805
  190. package/project/pages/src/demo-crm-dashboard.oyd.jsx +0 -677
  191. package/project/pages/src/demo-future-vision-2026.oyd.jsx +0 -1102
  192. package/project/pages/src/demo-ppt.oyd.jsx +0 -1192
  193. package/project/pages/src/demo-salary-calculator.oyd.jsx +0 -904
  194. package/project/pages/src/openyida-knowledge-doc.oyd.jsx +0 -1714
  195. package/yida-skills/skills/large-file-write/SKILL.md +0 -93
  196. package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
  197. package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
  198. package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
  199. package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
  200. package/yida-skills/skills/sls-log-workbench/sls-query.js +0 -295
@@ -0,0 +1,1922 @@
1
+ 'use strict';
2
+
3
+ const { buildYidaI18n } = require('../../core/yida-i18n');
4
+ const {
5
+ collectInputValidationRules,
6
+ dedupeValidationRules,
7
+ deepMerge,
8
+ normalizeFieldValidationRules,
9
+ } = require('./form-validation');
10
+
11
+ const OPTION_FIELD_TYPES = Object.freeze(['RadioField', 'SelectField', 'CheckboxField', 'MultiSelectField']);
12
+ const TABLE_FIELD_TYPE = 'TableField';
13
+ const ASSOCIATION_FORM_FIELD_TYPE = 'AssociationFormField';
14
+
15
+ // ── 自增 ID 计数器 ───────────────────────────────────
16
+ let nodeIdCounter = 1;
17
+
18
+ function nextNodeId() {
19
+ return 'node_oc' + Date.now().toString(36) + (nodeIdCounter++).toString(36);
20
+ }
21
+
22
+ let _fieldIdCounter = 0;
23
+
24
+ function generateFieldId(componentName) {
25
+ const prefix = componentName.charAt(0).toLowerCase() + componentName.slice(1);
26
+ // 使用时间戳 + 递增计数器 + 随机数,确保唯一性
27
+ _fieldIdCounter++;
28
+ const timePart = Date.now().toString(36).slice(-4);
29
+ const counterPart = _fieldIdCounter.toString(36);
30
+ const randomPart = Math.random().toString(36).substring(2, 6);
31
+ const suffix = timePart + counterPart + randomPart;
32
+ return prefix + '_' + suffix;
33
+ }
34
+
35
+ function createFormCompilerError(message, code, details) {
36
+ const error = new Error(message);
37
+ error.code = code || 'FORM_COMPILER_FAILED';
38
+ if (details) {
39
+ error.details = details;
40
+ }
41
+ return error;
42
+ }
43
+
44
+ function hasOwn(object, key) {
45
+ return Object.prototype.hasOwnProperty.call(object || {}, key);
46
+ }
47
+
48
+ function fieldIdPrefixForComponent(componentName) {
49
+ return componentName.charAt(0).toLowerCase() + componentName.slice(1) + '_';
50
+ }
51
+
52
+ function validateSemanticKey(rawKey) {
53
+ if (rawKey === undefined || rawKey === null || rawKey === '') {
54
+ return '';
55
+ }
56
+ const key = String(rawKey).trim();
57
+ if (!key) {
58
+ return '';
59
+ }
60
+ if (key.indexOf('.') !== -1) {
61
+ throw createFormCompilerError('表单字段 semantic key 不能包含点号: ' + key, 'FORM_COMPILER_INVALID_SEMANTIC_KEY', { key });
62
+ }
63
+ if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(key)) {
64
+ throw createFormCompilerError('表单字段 semantic key 格式非法: ' + key, 'FORM_COMPILER_INVALID_SEMANTIC_KEY', { key });
65
+ }
66
+ return key;
67
+ }
68
+
69
+ function collectExplicitSemanticKeys(field) {
70
+ const entries = [];
71
+ ['key', 'semanticKey', 'semantic_key'].forEach(function (propertyName) {
72
+ if (!hasOwn(field, propertyName)) {
73
+ return;
74
+ }
75
+ const key = validateSemanticKey(field[propertyName]);
76
+ if (key) {
77
+ entries.push({ propertyName, key });
78
+ }
79
+ });
80
+ return entries;
81
+ }
82
+
83
+ function normalizeSemanticKey(field) {
84
+ if (!field || typeof field !== 'object') {
85
+ return '';
86
+ }
87
+ const entries = collectExplicitSemanticKeys(field);
88
+ if (entries.length === 0) {
89
+ return '';
90
+ }
91
+ const firstKey = entries[0].key;
92
+ const conflicting = entries.find(function (entry) {
93
+ return entry.key !== firstKey;
94
+ });
95
+ if (conflicting) {
96
+ throw createFormCompilerError(
97
+ '字段 semantic key 声明冲突: ' + firstKey + ' / ' + conflicting.key,
98
+ 'FORM_COMPILER_SEMANTIC_KEY_CONFLICT',
99
+ {
100
+ key: firstKey,
101
+ conflictingKey: conflicting.key,
102
+ propertyName: conflicting.propertyName,
103
+ }
104
+ );
105
+ }
106
+ return firstKey;
107
+ }
108
+
109
+ function normalizeExistingBindings(existingBindings) {
110
+ const source = existingBindings && typeof existingBindings === 'object'
111
+ ? (existingBindings.fieldBindings || existingBindings.fields || existingBindings)
112
+ : {};
113
+ const normalized = {};
114
+ Object.keys(source || {}).forEach(function (semanticPath) {
115
+ const value = source[semanticPath];
116
+ if (typeof value === 'string') {
117
+ normalized[semanticPath] = { fieldId: value };
118
+ } else if (value && typeof value === 'object') {
119
+ const fieldId = value.fieldId || value.id || value.value;
120
+ if (fieldId) {
121
+ normalized[semanticPath] = Object.assign({}, value, { fieldId: String(fieldId) });
122
+ }
123
+ }
124
+ });
125
+ return normalized;
126
+ }
127
+
128
+ function createCompilerOptions(options) {
129
+ const compilerOptions = options || {};
130
+ return {
131
+ existingBindings: normalizeExistingBindings(compilerOptions.existingBindings),
132
+ fieldBindings: compilerOptions.fieldBindings || {},
133
+ fieldBindingComponents: compilerOptions.fieldBindingComponents || {},
134
+ semanticPath: compilerOptions.semanticPath || '',
135
+ requireParentSemanticPath: !!compilerOptions.requireParentSemanticPath,
136
+ onSerialNumberFormula: compilerOptions.onSerialNumberFormula,
137
+ };
138
+ }
139
+
140
+ function childCompilerOptions(options, semanticPath) {
141
+ return Object.assign({}, options, {
142
+ semanticPath: semanticPath || '',
143
+ requireParentSemanticPath: true,
144
+ });
145
+ }
146
+
147
+ function assertFieldIdMatchesComponent(fieldId, componentName, semanticPath) {
148
+ if (!fieldId) {
149
+ return;
150
+ }
151
+ const expectedPrefix = fieldIdPrefixForComponent(componentName);
152
+ if (!String(fieldId).startsWith(expectedPrefix)) {
153
+ throw createFormCompilerError(
154
+ 'existing binding 的 fieldId 与字段类型不匹配: ' + semanticPath,
155
+ 'FORM_COMPILER_FIELD_BINDING_TYPE_MISMATCH',
156
+ {
157
+ semanticPath,
158
+ fieldId,
159
+ componentName,
160
+ expectedPrefix,
161
+ }
162
+ );
163
+ }
164
+ }
165
+
166
+ function resolveFieldId(componentName, semanticPath, options) {
167
+ if (!semanticPath) {
168
+ return generateFieldId(componentName);
169
+ }
170
+ const binding = options.existingBindings[semanticPath];
171
+ if (binding && binding.fieldId) {
172
+ assertFieldIdMatchesComponent(binding.fieldId, componentName, semanticPath);
173
+ return binding.fieldId;
174
+ }
175
+ return generateFieldId(componentName);
176
+ }
177
+
178
+ function recordFieldBinding(options, semanticPath, fieldId, componentName) {
179
+ if (!semanticPath) {
180
+ return;
181
+ }
182
+ if (hasOwn(options.fieldBindings, semanticPath)) {
183
+ throw createFormCompilerError(
184
+ '重复的字段 semantic path: ' + semanticPath,
185
+ 'FORM_COMPILER_DUPLICATE_SEMANTIC_PATH',
186
+ {
187
+ semanticPath,
188
+ existingFieldId: options.fieldBindings[semanticPath],
189
+ fieldId,
190
+ componentName,
191
+ }
192
+ );
193
+ }
194
+ options.fieldBindings[semanticPath] = fieldId;
195
+ options.fieldBindingComponents[semanticPath] = componentName;
196
+ }
197
+
198
+ function normalizeFieldsObject(fieldsObject) {
199
+ return Object.keys(fieldsObject || {}).map(function (key) {
200
+ const field = fieldsObject[key];
201
+ if (!field || typeof field !== 'object' || Array.isArray(field)) {
202
+ throw createFormCompilerError('字段定义必须是对象: ' + key, 'FORM_COMPILER_INVALID_DEFINITION', { key });
203
+ }
204
+ const objectKey = validateSemanticKey(key);
205
+ const explicitKey = normalizeSemanticKey(field);
206
+ if (explicitKey && explicitKey !== objectKey) {
207
+ throw createFormCompilerError(
208
+ '对象式 fields 的属性名与字段内部 semantic key 冲突: ' + objectKey + ' / ' + explicitKey,
209
+ 'FORM_COMPILER_SEMANTIC_KEY_CONFLICT',
210
+ {
211
+ objectKey,
212
+ explicitKey,
213
+ }
214
+ );
215
+ }
216
+ return Object.assign({}, field, { key: objectKey });
217
+ });
218
+ }
219
+
220
+ function normalizeFormDefinition(definition, options) {
221
+ const compilerOptions = options || {};
222
+ if (Array.isArray(definition)) {
223
+ return {
224
+ fields: definition,
225
+ formTitle: compilerOptions.formTitle || compilerOptions.title || '',
226
+ formUuid: compilerOptions.formUuid || '',
227
+ corpId: compilerOptions.corpId || '',
228
+ appType: compilerOptions.appType || '',
229
+ layout: compilerOptions.layout,
230
+ theme: compilerOptions.theme,
231
+ labelAlign: compilerOptions.labelAlign,
232
+ };
233
+ }
234
+
235
+ if (!definition || typeof definition !== 'object') {
236
+ throw createFormCompilerError('表单定义必须是对象或字段数组', 'FORM_COMPILER_INVALID_DEFINITION');
237
+ }
238
+
239
+ const fields = Array.isArray(definition.fields)
240
+ ? definition.fields
241
+ : normalizeFieldsObject(definition.fields || {});
242
+
243
+ return {
244
+ fields,
245
+ formTitle: definition.formTitle || definition.title || compilerOptions.formTitle || compilerOptions.title || '',
246
+ formUuid: definition.formUuid || compilerOptions.formUuid || '',
247
+ corpId: definition.corpId || compilerOptions.corpId || '',
248
+ appType: definition.appType || compilerOptions.appType || '',
249
+ layout: definition.layout || compilerOptions.layout,
250
+ theme: definition.theme || compilerOptions.theme,
251
+ labelAlign: definition.labelAlign || compilerOptions.labelAlign,
252
+ };
253
+ }
254
+
255
+ // ── i18n 辅助 ────────────────────────────────────────
256
+
257
+ function i18n(text, enText, jaText) {
258
+ return buildYidaI18n(text, {
259
+ en_US: enText || text,
260
+ ja_JP: jaText || text,
261
+ });
262
+ }
263
+
264
+ // ── 默认占位符 ───────────────────────────────────────
265
+
266
+ const PLACEHOLDER_INPUT = i18n('请输入', 'Please enter', '入力してください');
267
+ const PLACEHOLDER_SELECT = i18n('请选择', 'Please select', '選択してください');
268
+
269
+ // ── 生成选项数据源 ───────────────────────────────────
270
+
271
+ function buildOptionDataSource(options) {
272
+ return options.map(function (optionText, optionIndex) {
273
+ return {
274
+ text: i18n(optionText, optionText, optionText),
275
+ value: optionText,
276
+ sid: 'serial_' + Date.now().toString(36) + optionIndex,
277
+ disable: false,
278
+ defaultChecked: false,
279
+ };
280
+ });
281
+ }
282
+
283
+ function normalizeOptionItem(option, optionIndex) {
284
+ if (typeof option === 'string' || typeof option === 'number' || typeof option === 'boolean') {
285
+ const optionText = String(option);
286
+ return {
287
+ text: i18n(optionText, optionText, optionText),
288
+ value: optionText,
289
+ sid: 'serial_' + Date.now().toString(36) + optionIndex,
290
+ disable: false,
291
+ defaultChecked: false,
292
+ };
293
+ }
294
+ if (option && typeof option === 'object') {
295
+ const rawText = option.text || option.label || option.name || option.title || option.value || '';
296
+ const rawValue = option.value !== undefined ? option.value :
297
+ option.id !== undefined ? option.id :
298
+ option.key !== undefined ? option.key : rawText;
299
+ return {
300
+ text: rawText && typeof rawText === 'object' ? rawText : i18n(String(rawText), String(rawText), String(rawText)),
301
+ value: String(rawValue),
302
+ sid: option.sid || 'serial_' + Date.now().toString(36) + optionIndex,
303
+ disable: option.disable || false,
304
+ defaultChecked: option.defaultChecked || false,
305
+ };
306
+ }
307
+ const fallbackText = '选项' + (optionIndex + 1);
308
+ return {
309
+ text: i18n(fallbackText, fallbackText, fallbackText),
310
+ value: fallbackText,
311
+ sid: 'serial_' + Date.now().toString(36) + optionIndex,
312
+ disable: false,
313
+ defaultChecked: false,
314
+ };
315
+ }
316
+
317
+ function normalizeOptionDataSource(options) {
318
+ if (!Array.isArray(options)) {
319
+ return [];
320
+ }
321
+ return options.map(normalizeOptionItem);
322
+ }
323
+
324
+ function normalizeSearchDataType(value, fallback) {
325
+ const raw = String(value || fallback || 'json').trim();
326
+ if (!raw) {
327
+ return 'json';
328
+ }
329
+ return raw.toLowerCase() === 'jsonp' ? 'jsonp' : 'json';
330
+ }
331
+
332
+ function buildDefaultBeforeFetchSource(config) {
333
+ if (config.beforeFetch !== undefined) {
334
+ return String(config.beforeFetch);
335
+ }
336
+ const queryParam = config.queryParam || config.keywordParam || 'key';
337
+ const queryParamLiteral = JSON.stringify(queryParam);
338
+ return [
339
+ 'function willFetch(params) {',
340
+ ' params = params || {};',
341
+ ' var keyword = params.key || params.q || params.keyword || "";',
342
+ ' params[' + queryParamLiteral + '] = keyword;',
343
+ ' return params;',
344
+ '}',
345
+ ].join('\n');
346
+ }
347
+
348
+ function buildDefaultAfterFetchSource(config) {
349
+ if (config.afterFetch !== undefined) {
350
+ return String(config.afterFetch);
351
+ }
352
+ const listPath = JSON.stringify(config.listPath || config.arrayPath || 'data');
353
+ const labelField = JSON.stringify(config.labelField || config.textField || config.labelKey || 'label');
354
+ const valueField = JSON.stringify(config.valueField || config.valueKey || 'value');
355
+ return [
356
+ 'function didFetch(content) {',
357
+ ' function readPath(obj, path) {',
358
+ ' if (!path) { return obj; }',
359
+ ' var parts = String(path).split(".");',
360
+ ' var current = obj;',
361
+ ' for (var i = 0; i < parts.length; i++) {',
362
+ ' if (current == null) { return undefined; }',
363
+ ' current = current[parts[i]];',
364
+ ' }',
365
+ ' return current;',
366
+ ' }',
367
+ ' var list = readPath(content, ' + listPath + ');',
368
+ ' if (!Array.isArray(list)) {',
369
+ ' list = content && (content.list || content.items || content.values || content.result || content.data);',
370
+ ' }',
371
+ ' if (!Array.isArray(list)) { list = []; }',
372
+ ' return list.map(function (item) {',
373
+ ' if (item && typeof item === "object") {',
374
+ ' var text = readPath(item, ' + labelField + ');',
375
+ ' var value = readPath(item, ' + valueField + ');',
376
+ ' if (value === undefined || value === null || value === "") { value = text; }',
377
+ ' if (text === undefined || text === null || text === "") { text = value; }',
378
+ ' return { text: String(text || ""), value: String(value || "") };',
379
+ ' }',
380
+ ' return { text: String(item), value: String(item) };',
381
+ ' });',
382
+ '}',
383
+ ].join('\n');
384
+ }
385
+
386
+ function normalizeSelectDataSourceConfig(config) {
387
+ const rawConfig = config && typeof config === 'object'
388
+ ? (config.remoteDataSource || config.searchDataSource || config.dataSourceConfig || config)
389
+ : {};
390
+ const searchConfig = rawConfig.searchConfig || {};
391
+ const url = rawConfig.url || rawConfig.endpoint || rawConfig.searchUrl || searchConfig.url || '';
392
+ const dataType = normalizeSearchDataType(rawConfig.dataType || searchConfig.dataType || searchConfig.type, 'json');
393
+ const beforeFetch = buildDefaultBeforeFetchSource(Object.assign({}, rawConfig, searchConfig));
394
+ const afterFetch = buildDefaultAfterFetchSource(Object.assign({}, rawConfig, searchConfig));
395
+ const options = normalizeOptionDataSource(rawConfig.options || rawConfig.initialOptions || rawConfig.dataSource || []);
396
+
397
+ return {
398
+ url,
399
+ dataType,
400
+ beforeFetch,
401
+ afterFetch,
402
+ options,
403
+ dataSourceType: rawConfig.dataSourceType || 'custom',
404
+ filterLocal: rawConfig.filterLocal !== undefined ? !!rawConfig.filterLocal : !url,
405
+ showSearch: rawConfig.showSearch !== false,
406
+ placeholder: rawConfig.placeholder,
407
+ notFoundContent: rawConfig.notFoundContent,
408
+ props: rawConfig.props && typeof rawConfig.props === 'object' ? rawConfig.props : null,
409
+ };
410
+ }
411
+
412
+ function applySelectDataSourceConfig(props, config) {
413
+ const normalized = normalizeSelectDataSourceConfig(config);
414
+ props.dataSource = normalized.options;
415
+ props.dataSourceType = normalized.dataSourceType;
416
+ props.showSearch = normalized.showSearch;
417
+ props.filterLocal = normalized.filterLocal;
418
+
419
+ if (normalized.placeholder !== undefined) {
420
+ props.placeholder = i18n(normalized.placeholder);
421
+ }
422
+ if (normalized.notFoundContent !== undefined) {
423
+ props.notFoundContent = i18n(normalized.notFoundContent);
424
+ }
425
+
426
+ props.searchConfig = {
427
+ dataType: normalized.dataType,
428
+ url: normalized.url,
429
+ beforeFetch: normalized.beforeFetch,
430
+ afterFetch: normalized.afterFetch,
431
+ };
432
+ props.defaultDataSource = Object.assign({}, props.defaultDataSource || {}, {
433
+ customStashOptions: props.defaultDataSource && props.defaultDataSource.customStashOptions || [],
434
+ complexType: 'custom',
435
+ options: normalized.options,
436
+ formula: props.defaultDataSource && props.defaultDataSource.formula || { data: [], event: { 'onPageReady,onChange': [] } },
437
+ url: normalized.url,
438
+ searchConfig: {
439
+ type: normalized.dataType.toUpperCase(),
440
+ url: normalized.url,
441
+ beforeFetch: normalized.beforeFetch,
442
+ afterFetch: normalized.afterFetch,
443
+ },
444
+ });
445
+
446
+ if (normalized.props) {
447
+ deepMerge(props, normalized.props);
448
+ }
449
+ return normalized;
450
+ }
451
+
452
+ // ── 字段类型名容错映射 ───────────────────────────────
453
+ // 宜搭后端枚举对大小写敏感,此映射自动纠正常见的拼写差异
454
+ const FIELD_TYPE_ALIAS = {
455
+ TextAreaField: 'TextareaField',
456
+ Textareafield: 'TextareaField',
457
+ textareaField: 'TextareaField',
458
+ textAreaField: 'TextareaField',
459
+ Textfield: 'TextField',
460
+ textfield: 'TextField',
461
+ Numberfield: 'NumberField',
462
+ numberfield: 'NumberField',
463
+ Selectfield: 'SelectField',
464
+ selectfield: 'SelectField',
465
+ Radiofield: 'RadioField',
466
+ radiofield: 'RadioField',
467
+ Checkboxfield: 'CheckboxField',
468
+ checkboxfield: 'CheckboxField',
469
+ Datefield: 'DateField',
470
+ datefield: 'DateField',
471
+ Tablefield: 'TableField',
472
+ tablefield: 'TableField',
473
+ Ratefield: 'RateField',
474
+ ratefield: 'RateField',
475
+ Imagefield: 'ImageField',
476
+ imagefield: 'ImageField',
477
+ Attachmentfield: 'AttachmentField',
478
+ attachmentfield: 'AttachmentField',
479
+ Employeefield: 'EmployeeField',
480
+ employeefield: 'EmployeeField',
481
+ MultiSelectfield: 'MultiSelectField',
482
+ Multiselectfield: 'MultiSelectField',
483
+ multiselectfield: 'MultiSelectField',
484
+ SerialNumberfield: 'SerialNumberField',
485
+ Serialnumberfield: 'SerialNumberField',
486
+ serialnumberfield: 'SerialNumberField',
487
+ };
488
+
489
+ const SUPPORTED_FIELD_TYPES = Object.freeze([
490
+ 'TextField',
491
+ 'TextareaField',
492
+ 'NumberField',
493
+ 'RateField',
494
+ 'DateField',
495
+ 'CascadeDateField',
496
+ 'RadioField',
497
+ 'SelectField',
498
+ 'CheckboxField',
499
+ 'MultiSelectField',
500
+ 'EmployeeField',
501
+ 'DepartmentSelectField',
502
+ 'CountrySelectField',
503
+ 'AddressField',
504
+ 'AttachmentField',
505
+ 'ImageField',
506
+ TABLE_FIELD_TYPE,
507
+ ASSOCIATION_FORM_FIELD_TYPE,
508
+ 'SerialNumberField',
509
+ ]);
510
+
511
+ function normalizeFieldType(fieldType) {
512
+ return FIELD_TYPE_ALIAS[fieldType] || fieldType;
513
+ }
514
+
515
+ function isSupportedFieldType(fieldType) {
516
+ return SUPPORTED_FIELD_TYPES.indexOf(normalizeFieldType(fieldType)) !== -1;
517
+ }
518
+
519
+ function isOptionFieldType(fieldType) {
520
+ return OPTION_FIELD_TYPES.indexOf(normalizeFieldType(fieldType)) !== -1;
521
+ }
522
+
523
+ const COMPONENT_ALIAS_META = Symbol('openyida.componentAlias');
524
+
525
+ function normalizeComponentAlias(field) {
526
+ if (!field || typeof field !== 'object') {
527
+ return '';
528
+ }
529
+ const rawAlias = field.componentAlias !== undefined
530
+ ? field.componentAlias
531
+ : field.component_alias !== undefined
532
+ ? field.component_alias
533
+ : field.alias;
534
+ if (rawAlias === undefined || rawAlias === null || rawAlias === false) {
535
+ return '';
536
+ }
537
+ if (typeof rawAlias === 'object') {
538
+ return String(rawAlias.alias || rawAlias.name || '').trim();
539
+ }
540
+ return String(rawAlias).trim();
541
+ }
542
+
543
+ // ── 生成字段组件 ─────────────────────────────────────
544
+
545
+ function buildFieldComponent(field, options) {
546
+ const compilerOptions = createCompilerOptions(options);
547
+ const componentName = normalizeFieldType(field.type);
548
+ const semanticKey = normalizeSemanticKey(field);
549
+ if (semanticKey && compilerOptions.requireParentSemanticPath && !compilerOptions.semanticPath) {
550
+ throw createFormCompilerError(
551
+ '子表字段声明 semantic key 时,父级 TableField 也必须声明 semantic key',
552
+ 'FORM_COMPILER_MISSING_PARENT_SEMANTIC_KEY',
553
+ { key: semanticKey }
554
+ );
555
+ }
556
+ const semanticPath = semanticKey
557
+ ? (compilerOptions.semanticPath ? compilerOptions.semanticPath + '.' + semanticKey : semanticKey)
558
+ : '';
559
+ const fieldId = resolveFieldId(componentName, semanticPath, compilerOptions);
560
+ const nodeId = nextNodeId();
561
+ recordFieldBinding(compilerOptions, semanticPath, fieldId, componentName);
562
+
563
+ // 基础 validation
564
+ const validation = normalizeFieldValidationRules(field);
565
+
566
+ // 基础 props(所有字段通用)
567
+ const props = {
568
+ __useMediator: 'value',
569
+ fieldId: fieldId,
570
+ label: i18n(field.label, componentName),
571
+ __category__: 'form',
572
+ behavior: 'NORMAL',
573
+ visibility: ['PC', 'MOBILE'],
574
+ dataEntryMode: false,
575
+ validation: validation,
576
+ labelAlign: 'top',
577
+ labelTextAlign: 'left',
578
+ labelColSpan: 4,
579
+ size: 'medium',
580
+ submittable: 'ALWAYS',
581
+ };
582
+
583
+ // 文本类字段
584
+ if (componentName === 'TextField' || componentName === 'TextareaField') {
585
+ props.hasClear = true;
586
+ props.placeholder = field.placeholder ? i18n(field.placeholder) : PLACEHOLDER_INPUT;
587
+ props.valueType = 'custom';
588
+ props.validationType = 'text';
589
+ props.value = i18n('', '');
590
+ props.hasLimitHint = false;
591
+ props.maxLength = 200;
592
+ props.rows = 4;
593
+ props.linkage = '';
594
+ props.__gridSpan = 1;
595
+ props.tips = i18n('', '');
596
+ props.autoHeight = false;
597
+ props.scanCode = { enabled: false, type: 'all', editable: true };
598
+ props.complexValue = {
599
+ complexType: 'custom',
600
+ formula: '',
601
+ value: i18n('', '', ''),
602
+ };
603
+ props.variable = '';
604
+ props.formula = '';
605
+ props.isCustomStore = true;
606
+
607
+ // TextareaField 特有属性
608
+ if (componentName === 'TextareaField') {
609
+ props.htmlType = 'textarea';
610
+ props.showEmptyRows = false;
611
+ }
612
+ }
613
+
614
+ // 数字字段
615
+ if (componentName === 'NumberField') {
616
+ props.hasClear = true;
617
+ props.placeholder = field.placeholder ? i18n(field.placeholder) : i18n('请输入数字', 'Please enter a number', '数値を入力してください');
618
+ props.valueType = 'custom';
619
+ props.__gridSpan = 1;
620
+ props.tips = i18n('', '');
621
+ props.linkage = '';
622
+ props.precision = 0;
623
+ props.step = 1;
624
+ props.thousandsSeparators = false;
625
+ props.innerAfter = field.innerAfter || '';
626
+ props.value = '';
627
+ props.labelColOffset = 0;
628
+ props.wrapperColSpan = 0;
629
+ props.wrapperColOffset = 0;
630
+ props.complexValue = {
631
+ complexType: 'custom',
632
+ formula: '',
633
+ value: '',
634
+ };
635
+ props.variable = '';
636
+ props.formula = '';
637
+ props.isCustomStore = true;
638
+ }
639
+
640
+ // 评分字段
641
+ if (componentName === 'RateField') {
642
+ props.count = 5;
643
+ props.allowHalf = false;
644
+ props.showGrade = false;
645
+ props.__gridSpan = 1;
646
+ props.tips = i18n('', '');
647
+ }
648
+
649
+ // 日期字段
650
+ if (componentName === 'DateField') {
651
+ props.placeholder = field.placeholder ? i18n(field.placeholder) : PLACEHOLDER_SELECT;
652
+ props.__gridSpan = 1;
653
+ props.tips = i18n('', '');
654
+ props.linkage = '';
655
+ props.format = field.format || 'YYYY-MM-DD';
656
+ props.hasClear = true;
657
+ props.disabledDate = { type: 'none' };
658
+ props.valueType = 'custom';
659
+ props.value = '';
660
+ props.formula = '';
661
+ props.variable = '';
662
+ props.resetTime = false;
663
+ props.complexValue = {
664
+ complexType: 'custom',
665
+ value: '',
666
+ formula: '',
667
+ };
668
+ }
669
+
670
+ // 级联日期字段
671
+ if (componentName === 'CascadeDateField') {
672
+ props.__gridSpan = 1;
673
+ props.tips = i18n('', '');
674
+ props.format = field.format || 'YYYY-MM-DD';
675
+ props.hasClear = true;
676
+ props.resetTime = false;
677
+ props.disabledDate = false;
678
+ }
679
+
680
+ // 选项类字段(RadioField、SelectField、CheckboxField、MultiSelectField)
681
+ if (OPTION_FIELD_TYPES.indexOf(componentName) !== -1) {
682
+ const rawDataSource = field.dataSource || field.options;
683
+ let dataSource;
684
+ if (Array.isArray(rawDataSource) && rawDataSource.length > 0) {
685
+ if (typeof rawDataSource[0] === 'string') {
686
+ dataSource = buildOptionDataSource(rawDataSource);
687
+ } else if (typeof rawDataSource[0] === 'object' && rawDataSource[0].value !== undefined) {
688
+ dataSource = rawDataSource.map(normalizeOptionItem);
689
+ } else {
690
+ dataSource = buildOptionDataSource(['选项一', '选项二', '选项三']);
691
+ }
692
+ } else {
693
+ dataSource = buildOptionDataSource(['选项一', '选项二', '选项三']);
694
+ }
695
+
696
+ props.dataSource = dataSource;
697
+ props.dataSourceType = 'custom';
698
+ props.defaultDataSource = {
699
+ customStashOptions: [],
700
+ complexType: 'custom',
701
+ options: dataSource,
702
+ formula: { data: [], event: { 'onPageReady,onChange': [] } },
703
+ url: '',
704
+ searchConfig: { afterFetch: '', type: 'JSONP', beforeFetch: '', url: '' },
705
+ };
706
+ props.__gridSpan = 1;
707
+ props.tips = i18n('', '');
708
+ props.linkage = '';
709
+
710
+ if (componentName === 'RadioField' || componentName === 'CheckboxField') {
711
+ props.value = '';
712
+ props.valueType = 'custom';
713
+ props.complexValue = { complexType: 'custom', formula: '', value: '' };
714
+ props.variable = '';
715
+ props.formula = '';
716
+ }
717
+
718
+ if (componentName === 'SelectField' || componentName === 'MultiSelectField') {
719
+ props.hasClear = true;
720
+ props.showSearch = true;
721
+ props.autoWidth = true;
722
+ props.placeholder = field.placeholder ? i18n(field.placeholder) : PLACEHOLDER_SELECT;
723
+ props.value = '';
724
+ props.valueType = 'custom';
725
+ props.reusePrivilege = false;
726
+ props.isUseDataSourceColor = false;
727
+ props.dataSourceLinkage = '';
728
+ props.filterLocal = true;
729
+ props.notFoundContent = i18n('无数据', 'No data', 'データがありません');
730
+ props.searchConfig = {
731
+ dataType: 'jsonp',
732
+ url: '',
733
+ beforeFetch: 'function willFetch(params) {\n return params;\n}',
734
+ afterFetch: 'function didFetch(content) {\n return content;\n}',
735
+ };
736
+ props.complexValue = { complexType: 'custom', formula: '', value: '' };
737
+ props.variable = '';
738
+ props.formula = '';
739
+ }
740
+
741
+ if (componentName === 'SelectField') {
742
+ props.mode = 'single';
743
+ } else if (componentName === 'MultiSelectField') {
744
+ props.mode = 'multiple';
745
+ }
746
+
747
+ if (field.remoteDataSource || field.searchDataSource || field.dataSourceConfig || field.dataSourceUrl || field.searchConfig) {
748
+ applySelectDataSourceConfig(props, field.remoteDataSource || field.searchDataSource || field.dataSourceConfig || {
749
+ url: field.dataSourceUrl,
750
+ searchConfig: field.searchConfig,
751
+ dataType: field.dataType,
752
+ beforeFetch: field.beforeFetch,
753
+ afterFetch: field.afterFetch,
754
+ queryParam: field.queryParam,
755
+ listPath: field.listPath,
756
+ labelField: field.labelField,
757
+ valueField: field.valueField,
758
+ options: rawDataSource,
759
+ dataSourceType: field.dataSourceType,
760
+ filterLocal: field.filterLocal,
761
+ showSearch: field.showSearch,
762
+ });
763
+ }
764
+ }
765
+
766
+ // 成员字段
767
+ if (componentName === 'EmployeeField') {
768
+ props.placeholder = PLACEHOLDER_SELECT;
769
+ props.__gridSpan = 1;
770
+ props.tips = i18n('', '');
771
+ props.multiple = field.multiple || false;
772
+ props.hasClear = true;
773
+ props.userRangeType = 'ALL';
774
+ props.roleRange = [];
775
+ props.userRange = [];
776
+ props.showEmpIdType = 'NAME';
777
+ props.startWithDepartmentId = 'SELF';
778
+ props.renderLinkForView = true;
779
+ props.showEmplId = false;
780
+ props.closeOnSelect = false;
781
+ props.useAliworkUrl = false;
782
+ props.linkage = '';
783
+
784
+ props.valueType = 'variable';
785
+ props.complexValue = {
786
+ complexType: 'formula',
787
+ formula: 'USER()',
788
+ value: [],
789
+ };
790
+ props.variable = { type: 'user' };
791
+ props.formula = '';
792
+ props.value = [];
793
+ }
794
+
795
+ // 部门字段
796
+ if (componentName === 'DepartmentSelectField') {
797
+ props.placeholder = i18n('请输入关键字进行搜索', 'Please enter keyword', 'キーワードを入力してください');
798
+ props.__gridSpan = 1;
799
+ props.tips = i18n('', '');
800
+ props.multiple = field.multiple || false;
801
+ props.valueType = 'custom';
802
+ props.value = [];
803
+ props.deptRangeType = 'ALL';
804
+ props.deptRange = [];
805
+ props.mode = 'single';
806
+ props.hasClear = true;
807
+ props.dataSource = {
808
+ searchConfig: {
809
+ dataType: 'json',
810
+ url: '/query/deptService/searchDepts.json',
811
+ beforeFetch: 'function willFetch(data) {\n data.key = data.key || data.q || "";\n return data;\n}',
812
+ afterFetch: 'function didFetch(content) {\n var data = [];\n if (content && content.values) {\n content.values.forEach(function (item) {\n data.push({ value: item.emplId, text: item.name, deptFullPath: item.deptFullPath });\n });\n }\n return data;\n}',
813
+ },
814
+ };
815
+ props.complexValue = {
816
+ complexType: 'custom',
817
+ value: [],
818
+ formula: '',
819
+ };
820
+ props.variable = '';
821
+ props.formula = '';
822
+ props.linkage = '';
823
+ props.isShowDeptFullName = false;
824
+ props.hasSelectAll = false;
825
+ }
826
+
827
+ // 国家字段
828
+ if (componentName === 'CountrySelectField') {
829
+ props.placeholder = PLACEHOLDER_SELECT;
830
+ props.__gridSpan = 1;
831
+ props.tips = i18n('', '');
832
+ props.multiple = field.multiple || false;
833
+ props.value = [];
834
+ props.mode = 'single';
835
+ props.hasClear = true;
836
+ props.showSearch = true;
837
+ props.hasSelectAll = false;
838
+ }
839
+
840
+ // 地址字段
841
+ if (componentName === 'AddressField') {
842
+ props.__gridSpan = 1;
843
+ props.tips = i18n('', '');
844
+ props.placeholder = field.placeholder ? i18n(field.placeholder) : PLACEHOLDER_SELECT;
845
+ props.countryMode = 'default';
846
+ props.countryScope = 1;
847
+ props.addressType = 'ADDRESS';
848
+ props.subLabel = i18n('详细地址', 'Detailed Address', '詳細住所');
849
+ props.detailPlaceholder = i18n('请输入详细地址', 'Please input detailed address', '詳細住所を入力してください');
850
+ props.hasClear = true;
851
+ props.enableLocation = true;
852
+ props.value = {};
853
+ props.optionAutoWidth = true;
854
+ props.showCountry = false;
855
+ }
856
+
857
+ // 附件字段
858
+ if (componentName === 'AttachmentField') {
859
+ props.__gridSpan = 1;
860
+ props.tips = i18n('', '');
861
+ props.valueType = 'custom';
862
+ props.value = '';
863
+ props.complexValue = {
864
+ complexType: 'custom',
865
+ value: '',
866
+ formula: '',
867
+ };
868
+ props.type = 'normal';
869
+ props.listType = 'text';
870
+ props.buttonText = i18n('上传文件', 'Upload file', 'ファイルをアップロード');
871
+ props.buttonSize = 'medium';
872
+ props.buttonType = 'normal';
873
+ props.multiple = true;
874
+ props.method = 'post';
875
+ props.limit = 9;
876
+ props.maxFileSize = 100;
877
+ props.autoUpload = true;
878
+ props.accept = '';
879
+ props.formula = '';
880
+ props.linkage = '';
881
+ props.variable = '';
882
+ props.onlineEdit = false;
883
+ props.withCredentials = false;
884
+ }
885
+
886
+ // 图片上传字段
887
+ if (componentName === 'ImageField') {
888
+ props.__gridSpan = 1;
889
+ props.tips = i18n('', '');
890
+ props.valueType = 'custom';
891
+ props.value = '';
892
+ props.complexValue = {
893
+ complexType: 'custom',
894
+ value: '',
895
+ formula: '',
896
+ };
897
+ props.aiRecognitionConfig = {};
898
+ props.type = 'normal';
899
+ props.normalListType = 'image';
900
+ props.cardListType = 'card';
901
+ props.listType = 'image';
902
+ props.buttonText = i18n('图片上传', 'Upload image', '画像をアップロード');
903
+ props.buttonSize = 'medium';
904
+ props.buttonType = 'normal';
905
+ props.enableCameraDate = true;
906
+ props.enableCameraLocation = true;
907
+ props.saveCameraImageToLocal = true;
908
+ props.multiple = true;
909
+ props.method = 'post';
910
+ props.limit = 9;
911
+ props.maxFileSize = 50;
912
+ props.autoUpload = true;
913
+ props.accept = 'image/*';
914
+ props.formula = '';
915
+ props.linkage = '';
916
+ props.variable = '';
917
+ props.aiRecognitionSwitch = false;
918
+ props.onlyCameraUpload = false;
919
+ props.enableCameraWatermark = false;
920
+ props.enableCameraCompression = false;
921
+ }
922
+
923
+ // 子表字段
924
+ if (componentName === 'TableField') {
925
+ props.__gridSpan = 1;
926
+ props.linkage = '';
927
+ props.tips = i18n('', '');
928
+ props.showIndex = true;
929
+ props.copyButtonText = i18n('复制', 'Copy', 'コピー');
930
+ props.addButtonBehavior = 'NORMAL';
931
+ props.pageSize = 20;
932
+ props.addButtonText = i18n('新增一项', 'Add item', '項目を追加');
933
+ props.enableExport = true;
934
+ props.addButtonPosition = 'bottom';
935
+ props.actionsColumnWidth = 70;
936
+ props.theme = 'split';
937
+ props.delButtonText = i18n('删除', 'Remove', '削除');
938
+ props.useCustomColumnsWidth = false;
939
+ props.showSortable = false;
940
+ props.moveUp = i18n('上移', 'Up', '上へ');
941
+ props.maxItems = 500;
942
+ props.tableLayout = 'fixed';
943
+ props.showActions = true;
944
+ props.indexName = i18n('项目', 'Line', '項目');
945
+ props.showCopyAction = false;
946
+ props.showDelAction = true;
947
+ props.showTableHead = true;
948
+ props.moveDown = i18n('下移', 'Down', '下へ');
949
+ props.pcFreezeColumnStartCounts = '0';
950
+ props.layout = 'TABLE';
951
+ props.showDeleteConfirm = true;
952
+ props.minItems = 1;
953
+ props.enableImport = true;
954
+ props.defaultCollapseStatus = true;
955
+ props.isFreezeOperateColumn = true;
956
+ props.actions = [];
957
+ props.complexValue = { complexType: 'custom', formula: '' };
958
+ props.valueType = 'custom';
959
+ props.__designerDevice = 'pc';
960
+ props.mobileLayout = 'TILED';
961
+ props.mobileFreezeColumnStartCounts = '0';
962
+ props.enableBatchDelete = false;
963
+ props.filterEmptyRowData = false;
964
+ props.enableSummary = false;
965
+ }
966
+
967
+ // 关联表单字段
968
+ if (componentName === 'AssociationFormField') {
969
+ const assocConfig = field.associationForm || {};
970
+
971
+ props.__gridSpan = 1;
972
+ props.tips = i18n('', '');
973
+ props.placeholder = PLACEHOLDER_SELECT;
974
+ props.notFoundContent = i18n('无数据', 'No data', 'データがありません');
975
+ props.hasClear = true;
976
+ props.multiple = field.multiple || false;
977
+ props.dataEntryMode = false;
978
+ props.submittable = 'ALWAYS';
979
+ props.isCustomStore = true;
980
+ props.isShowSearchBar = true;
981
+ props.validateFilter = false;
982
+ props.__useMediator = 'value';
983
+
984
+ // 关联表单核心配置
985
+ props.associationForm = {
986
+ formType: 'receipt',
987
+ formUuid: assocConfig.formUuid || '',
988
+ appType: assocConfig.appType || '',
989
+ appName: assocConfig.appName || '',
990
+ formTitle: assocConfig.formTitle || '',
991
+ mainFieldId: assocConfig.mainFieldId || '',
992
+ mainFieldLabel: assocConfig.mainFieldLabel
993
+ ? i18n(assocConfig.mainFieldLabel)
994
+ : i18n('', ''),
995
+ mainComponentName: assocConfig.mainComponentName || 'TextField',
996
+ tableShowType: assocConfig.tableShowType || 'all',
997
+ customTableFields: assocConfig.customTableFields || [],
998
+ subFieldId: assocConfig.subFieldId || '',
999
+ subComponentName: assocConfig.subComponentName || '',
1000
+ linkageFields: assocConfig.linkageFields || [],
1001
+ };
1002
+
1003
+ // 数据过滤规则(条件筛选)
1004
+ const hasFilterRules = assocConfig.dataFilterRules &&
1005
+ assocConfig.dataFilterRules.rules &&
1006
+ assocConfig.dataFilterRules.rules.length > 0;
1007
+ props.dataFilterRules = hasFilterRules ? assocConfig.dataFilterRules : {
1008
+ condition: 'AND',
1009
+ rules: [],
1010
+ ruleId: 'group-' + Date.now().toString(36),
1011
+ instanceFieldId: '',
1012
+ version: 'v2',
1013
+ };
1014
+ props.supportDataFilter = hasFilterRules;
1015
+
1016
+ // 数据回填规则(选中后自动填充本表单字段)
1017
+ // 规范化每条规则,补充 source/target/sourceType/targetType 字段(宜搭回填必须)
1018
+ const hasFillingRules = assocConfig.dataFillingRules &&
1019
+ ((assocConfig.dataFillingRules.mainRules && assocConfig.dataFillingRules.mainRules.length > 0) ||
1020
+ (assocConfig.dataFillingRules.tableRules && assocConfig.dataFillingRules.tableRules.length > 0));
1021
+ props.dataFillingRules = hasFillingRules ? normalizeFillingRules(assocConfig.dataFillingRules) : {
1022
+ mainRules: [],
1023
+ tableRules: [],
1024
+ version: 'v2',
1025
+ };
1026
+ props.supportDataFilling = hasFillingRules;
1027
+
1028
+ // 排序配置
1029
+ props.orderEnable = !!(assocConfig.orderConfig && assocConfig.orderConfig.length > 0);
1030
+ props.orderConfig = assocConfig.orderConfig || [];
1031
+ }
1032
+
1033
+ // 流水号字段
1034
+ if (componentName === 'SerialNumberField') {
1035
+ props.__gridSpan = 1;
1036
+ props.tips = i18n('', '');
1037
+ props.dataEntryMode = false;
1038
+ props.submittable = 'DEFAULT';
1039
+ // 流水号字段固定为空校验规则,不支持 required
1040
+ props.validation = [];
1041
+
1042
+ // 默认流水号规则:前缀 + 自动递增数字
1043
+ const defaultSerialNumberRule = [
1044
+ {
1045
+ __hide_delete__: false,
1046
+ ruleType: 'character',
1047
+ content: 'serial',
1048
+ formField: '',
1049
+ dateFormat: 'yyyyMMdd',
1050
+ timeZone: '+8',
1051
+ digitCount: 4,
1052
+ isFixed: true,
1053
+ isFixedTips: '',
1054
+ resetPeriod: 'noClean',
1055
+ resetPeriodTips: '',
1056
+ initialValue: 1,
1057
+ __sid: 'item_' + Date.now().toString(36) + '1',
1058
+ __sid__: 'serial_' + Date.now().toString(36) + '1'
1059
+ },
1060
+ {
1061
+ __hide_delete__: true,
1062
+ ruleType: 'autoCount',
1063
+ content: '',
1064
+ formField: '',
1065
+ dateFormat: 'yyyyMMdd',
1066
+ timeZone: '+8',
1067
+ digitCount: 5,
1068
+ isFixed: true,
1069
+ isFixedTips: '',
1070
+ resetPeriod: 'noClean',
1071
+ resetPeriodTips: '',
1072
+ initialValue: 1,
1073
+ __sid: 'item_' + Date.now().toString(36) + '2',
1074
+ __sid__: 'serial_' + Date.now().toString(36) + '2'
1075
+ }
1076
+ ];
1077
+
1078
+ props.serialNumberRule = field.serialNumberRule || defaultSerialNumberRule;
1079
+ props.serialNumPreview = 'serial00001';
1080
+ props.serialNumReset = 1;
1081
+ props.syncSerialConfig = false;
1082
+
1083
+ // formula 字段需要在 buildFormSchema 中设置,因为需要 corpId 和 formUuid
1084
+ // 这里先设置为空对象,后续会被替换
1085
+ props.formula = {};
1086
+ }
1087
+
1088
+ // ── 通用属性覆盖(字段定义中显式传入的属性优先级最高)──────────
1089
+
1090
+ // behavior:NORMAL / READONLY / HIDDEN
1091
+ if (field.behavior !== undefined) {
1092
+ props.behavior = field.behavior;
1093
+ }
1094
+
1095
+ // visibility:控制在哪些端显示,如 ["PC", "MOBILE"] / ["PC"] / ["MOBILE"]
1096
+ if (field.visibility !== undefined) {
1097
+ props.visibility = field.visibility;
1098
+ }
1099
+
1100
+ // labelAlign:标签对齐方式,top / left / right
1101
+ if (field.labelAlign !== undefined) {
1102
+ props.labelAlign = field.labelAlign;
1103
+ }
1104
+
1105
+ // placeholder:占位提示文本(部分字段类型已在上方按类型设置,这里统一覆盖)
1106
+ if (field.placeholder !== undefined) {
1107
+ props.placeholder = i18n(field.placeholder);
1108
+ }
1109
+
1110
+ const component = {
1111
+ componentName: componentName,
1112
+ id: nodeId,
1113
+ fieldId: fieldId,
1114
+ props: props,
1115
+ condition: true,
1116
+ hidden: false,
1117
+ title: '',
1118
+ isLocked: false,
1119
+ conditionGroup: '',
1120
+ };
1121
+
1122
+ const componentAlias = normalizeComponentAlias(field);
1123
+ if (componentAlias) {
1124
+ component[COMPONENT_ALIAS_META] = componentAlias;
1125
+ }
1126
+
1127
+ // TableField:递归处理子字段
1128
+ if (componentName === 'TableField' && field.children) {
1129
+ component.children = field.children.map(function (childField) {
1130
+ return buildFieldComponent(childField, childCompilerOptions(compilerOptions, semanticPath));
1131
+ });
1132
+ }
1133
+
1134
+ return component;
1135
+ }
1136
+
1137
+ // ── 收集使用到的组件名称 ─────────────────────────────
1138
+
1139
+ function collectComponentNames(fields) {
1140
+ const names = new Set(['Page', 'RootHeader', 'RootContent', 'RootFooter', 'FooterYida', 'FormContainer']);
1141
+ fields.forEach(function (field) {
1142
+ names.add(field.type);
1143
+ if (field.type === 'TableField' && field.children) {
1144
+ field.children.forEach(function (child) {
1145
+ names.add(child.type);
1146
+ });
1147
+ }
1148
+ });
1149
+ return Array.from(names);
1150
+ }
1151
+
1152
+ // ── 生成 componentsMap ───────────────────────────────
1153
+
1154
+ function buildComponentsMap(componentNames) {
1155
+ return componentNames.map(function (name) {
1156
+ return {
1157
+ package: '@ali/vc-deep-yida',
1158
+ version: '1.5.169',
1159
+ componentName: name,
1160
+ };
1161
+ });
1162
+ }
1163
+
1164
+ function buildComponentAliasItems(components) {
1165
+ const items = [];
1166
+ const usedAliases = Object.create(null);
1167
+
1168
+ function visit(component) {
1169
+ if (!component || typeof component !== 'object') {
1170
+ return;
1171
+ }
1172
+ const fieldId = component.fieldId || (component.props && component.props.fieldId);
1173
+ const alias = component[COMPONENT_ALIAS_META];
1174
+ if (fieldId && alias) {
1175
+ let finalAlias = alias;
1176
+ if (usedAliases[finalAlias] && usedAliases[finalAlias] !== fieldId) {
1177
+ finalAlias = finalAlias + '_' + fieldId;
1178
+ }
1179
+ usedAliases[finalAlias] = fieldId;
1180
+ items.push({ fieldId, alias: finalAlias });
1181
+ }
1182
+ if (Array.isArray(component.children)) {
1183
+ component.children.forEach(visit);
1184
+ }
1185
+ }
1186
+
1187
+ (components || []).forEach(visit);
1188
+ return items;
1189
+ }
1190
+
1191
+ // ── 从 fieldId 前缀推断组件类型 ─────────────────────
1192
+ // 例如:serialNumberField_xxx → SerialNumberField,textField_xxx → TextField
1193
+
1194
+ function inferComponentNameFromFieldId(fieldId) {
1195
+ if (!fieldId || typeof fieldId !== 'string') {return '';}
1196
+ // fieldId 格式:camelCaseComponentName_xxxxxxxx
1197
+ const underscoreIndex = fieldId.lastIndexOf('_');
1198
+ if (underscoreIndex === -1) {return '';}
1199
+ const prefix = fieldId.slice(0, underscoreIndex);
1200
+ // 将首字母大写,还原为 PascalCase 组件名
1201
+ return prefix.charAt(0).toUpperCase() + prefix.slice(1);
1202
+ }
1203
+
1204
+ // ── 规范化单条回填规则,补充 source/target/sourceType/targetType ──
1205
+ // 宜搭要求 mainRules 和 tableRules 中的每条规则同时包含:
1206
+ // sourceFieldId、targetFieldId(旧格式)
1207
+ // source(同 sourceFieldId)、target(同 targetFieldId)
1208
+ // sourceType(源字段组件类型)、targetType(目标字段组件类型)
1209
+
1210
+ function normalizeFillingRule(rule) {
1211
+ // 兼容两种格式:旧格式用 sourceFieldId/targetFieldId,新格式用 source/target
1212
+ const sourceId = rule.sourceFieldId || rule.source || '';
1213
+ const targetId = rule.targetFieldId || rule.target || '';
1214
+ const sourceType = rule.sourceType || inferComponentNameFromFieldId(sourceId);
1215
+ const targetType = rule.targetType || inferComponentNameFromFieldId(targetId);
1216
+
1217
+ return {
1218
+ sourceFieldId: sourceId,
1219
+ targetFieldId: targetId,
1220
+ source: sourceId,
1221
+ sourceType: sourceType,
1222
+ target: targetId,
1223
+ targetType: targetType,
1224
+ };
1225
+ }
1226
+
1227
+ // ── 规范化整个 dataFillingRules 对象 ─────────────────
1228
+
1229
+ function normalizeFillingRules(fillingRules) {
1230
+ if (!fillingRules) {return fillingRules;}
1231
+ const normalized = Object.assign({}, fillingRules);
1232
+
1233
+ if (Array.isArray(normalized.mainRules)) {
1234
+ normalized.mainRules = normalized.mainRules.map(normalizeFillingRule);
1235
+ }
1236
+
1237
+ if (Array.isArray(normalized.tableRules)) {
1238
+ normalized.tableRules = normalized.tableRules.map(function (tableRule) {
1239
+ const normalizedTableRule = Object.assign({}, tableRule);
1240
+ if (Array.isArray(normalizedTableRule.rules)) {
1241
+ normalizedTableRule.rules = normalizedTableRule.rules.map(normalizeFillingRule);
1242
+ }
1243
+ return normalizedTableRule;
1244
+ });
1245
+ }
1246
+
1247
+ return normalized;
1248
+ }
1249
+
1250
+ // ── 解析 @label:字段名 语法,将其替换为对应字段的真实 fieldId ──
1251
+
1252
+ function resolveFieldIdReferences(fieldComponents) {
1253
+ // 构建 label → fieldId 的映射表
1254
+ const labelToFieldId = {};
1255
+ fieldComponents.forEach(function (component) {
1256
+ const labelText = extractLabelText(component);
1257
+ if (labelText && component.props && component.props.fieldId) {
1258
+ labelToFieldId[labelText] = component.props.fieldId;
1259
+ }
1260
+ });
1261
+
1262
+ // 遍历所有 AssociationFormField,解析回填规则中的 @label:xxx 引用
1263
+ fieldComponents.forEach(function (component) {
1264
+ if (component.componentName !== 'AssociationFormField') {return;}
1265
+ const fillingRules = component.props.dataFillingRules;
1266
+ if (!fillingRules) {return;}
1267
+
1268
+ /**
1269
+ * 解析普通规则(mainRules 或 tableRules 中的 rules 数组)
1270
+ * 支持格式: [{source, target, sourceType, targetType}, ...]
1271
+ */
1272
+ function resolveRules(rules) {
1273
+ if (!Array.isArray(rules)) {return;}
1274
+ rules.forEach(function (rule) {
1275
+ // 解析 target 中的 @label:xxx 引用
1276
+ if (rule.target && typeof rule.target === 'string' && rule.target.startsWith('@label:')) {
1277
+ const targetLabel = rule.target.slice(7);
1278
+ const resolvedId = labelToFieldId[targetLabel];
1279
+ if (resolvedId) {
1280
+ rule.target = resolvedId;
1281
+ }
1282
+ }
1283
+ // 解析 source 中的 @label:xxx 引用
1284
+ if (rule.source && typeof rule.source === 'string' && rule.source.startsWith('@label:')) {
1285
+ const sourceLabel = rule.source.slice(7);
1286
+ const resolvedSourceId = labelToFieldId[sourceLabel];
1287
+ if (resolvedSourceId) {
1288
+ rule.source = resolvedSourceId;
1289
+ }
1290
+ }
1291
+ });
1292
+ }
1293
+
1294
+ /**
1295
+ * 解析子表填充子表规则(tableRules)
1296
+ * 支持格式: [{tableId, rules: [{source, target, sourceType, targetType}], filters}, ...]
1297
+ */
1298
+ function resolveTableRules(tableRules) {
1299
+ if (!Array.isArray(tableRules)) {return;}
1300
+ tableRules.forEach(function (tableRule) {
1301
+ if (!tableRule.rules || !Array.isArray(tableRule.rules)) {return;}
1302
+
1303
+ tableRule.rules.forEach(function (rule) {
1304
+ if (rule.target && typeof rule.target === 'string' && rule.target.startsWith('@label:')) {
1305
+ const targetLabel = rule.target.slice(7);
1306
+ const resolvedId = labelToFieldId[targetLabel];
1307
+ if (resolvedId) {
1308
+ rule.target = resolvedId;
1309
+ }
1310
+ }
1311
+ });
1312
+ });
1313
+ }
1314
+
1315
+ // 解析主表回填规则
1316
+ if (fillingRules.mainRules) {
1317
+ resolveRules(fillingRules.mainRules);
1318
+ }
1319
+
1320
+ // 解析子表回填规则(支持子表填充子表)
1321
+ if (fillingRules.tableRules) {
1322
+ resolveTableRules(fillingRules.tableRules);
1323
+ }
1324
+
1325
+ // 解析完 @label 后,规范化规则(补充 source/target/sourceType/targetType)
1326
+ component.props.dataFillingRules = normalizeFillingRules(fillingRules);
1327
+
1328
+ // 解析后重新判断是否有有效回填规则
1329
+ const hasMainRules = fillingRules.mainRules && fillingRules.mainRules.length > 0;
1330
+ const hasTableRules = fillingRules.tableRules && fillingRules.tableRules.some(function (tr) {
1331
+ return tr.rules && tr.rules.length > 0;
1332
+ });
1333
+ component.props.supportDataFilling = hasMainRules || hasTableRules;
1334
+ });
1335
+ }
1336
+
1337
+ // ── 布局配置映射 ─────────────────────────────────────
1338
+
1339
+ /**
1340
+ * 获取布局配置
1341
+ * @param {string} layout - 布局类型:single/double/card/section
1342
+ * @returns {object} 布局配置对象 { columns, formLayout, groupFields }
1343
+ */
1344
+ function getLayoutConfig(layout) {
1345
+ const layoutMap = {
1346
+ single: { columns: 1, formLayout: 'default', groupFields: false },
1347
+ '1': { columns: 1, formLayout: 'default', groupFields: false },
1348
+ double: { columns: 2, formLayout: 'default', groupFields: false },
1349
+ '2': { columns: 2, formLayout: 'default', groupFields: false },
1350
+ card: { columns: 1, formLayout: 'card', groupFields: true },
1351
+ section: { columns: 1, formLayout: 'section', groupFields: true },
1352
+ };
1353
+ return layoutMap[layout] || layoutMap.single;
1354
+ }
1355
+
1356
+ /**
1357
+ * 获取主题样式配置
1358
+ * @param {string} theme - 主题类型:default/compact/comfortable
1359
+ * @param {string} labelAlign - 标签对齐:top/left/right
1360
+ * @returns {object} 样式配置对象
1361
+ */
1362
+ function getThemeConfig(theme, labelAlign) {
1363
+ const baseConfig = {
1364
+ labelAlignPc: labelAlign || 'top',
1365
+ labelWidthPc: labelAlign === 'left' || labelAlign === 'right' ? '130px' : 'auto',
1366
+ labelWeightPc: 'normal',
1367
+ contentMargin: '20',
1368
+ contentPadding: '20',
1369
+ fieldSpacing: 'medium',
1370
+ };
1371
+
1372
+ const themeMap = {
1373
+ default: {
1374
+ ...baseConfig,
1375
+ contentMargin: '20',
1376
+ contentPadding: '20',
1377
+ },
1378
+ compact: {
1379
+ ...baseConfig,
1380
+ contentMargin: '12',
1381
+ contentPadding: '12',
1382
+ fieldSpacing: 'small',
1383
+ },
1384
+ comfortable: {
1385
+ ...baseConfig,
1386
+ contentMargin: '32',
1387
+ contentPadding: '32',
1388
+ fieldSpacing: 'large',
1389
+ },
1390
+ };
1391
+
1392
+ return themeMap[theme] || themeMap.default;
1393
+ }
1394
+
1395
+ // ── 按 group 分组字段 ─────────────────────────────────
1396
+
1397
+ /**
1398
+ * 将字段按 group 属性分组
1399
+ * @param {Array} fields - 字段定义数组
1400
+ * @returns {Array} 分组后的字段数组,每个元素是 { groupName, fields }
1401
+ */
1402
+ function groupFieldsByGroup(fields) {
1403
+ const groups = [];
1404
+ const groupMap = new Map();
1405
+
1406
+ fields.forEach((field) => {
1407
+ const groupName = field.group || '基本信息';
1408
+ if (!groupMap.has(groupName)) {
1409
+ groupMap.set(groupName, []);
1410
+ }
1411
+ groupMap.get(groupName).push(field);
1412
+ });
1413
+
1414
+ groupMap.forEach((groupFields, groupName) => {
1415
+ groups.push({ groupName, fields: groupFields });
1416
+ });
1417
+
1418
+ return groups;
1419
+ }
1420
+
1421
+ // ── 构建分组字段组件 ─────────────────────────────────
1422
+
1423
+ /**
1424
+ * 构建分组/卡片布局的字段组件
1425
+ * @param {Array} fields - 字段定义数组
1426
+ * @param {string} formLayout - 布局类型:card/section
1427
+ * @returns {Array} 分组后的组件数组
1428
+ */
1429
+ function buildGroupedFieldComponents(fields, formLayout, options) {
1430
+ const groups = groupFieldsByGroup(fields);
1431
+
1432
+ return groups.map((group, groupIndex) => {
1433
+ // 构建分组内的字段组件
1434
+ const groupFieldComponents = group.fields.map((field) => buildFieldComponent(field, options));
1435
+
1436
+ if (formLayout === 'card') {
1437
+ // 卡片式布局:每个分组是一个卡片容器
1438
+ return {
1439
+ componentName: 'CardContainer',
1440
+ id: nextNodeId(),
1441
+ props: {
1442
+ title: i18n(group.groupName, group.groupName),
1443
+ collapsible: true,
1444
+ defaultCollapsed: false,
1445
+ showTitle: true,
1446
+ cardStyle: 'default',
1447
+ headerStyle: 'default',
1448
+ __gridSpan: 1,
1449
+ },
1450
+ condition: true,
1451
+ hidden: false,
1452
+ title: '',
1453
+ isLocked: false,
1454
+ conditionGroup: '',
1455
+ children: groupFieldComponents,
1456
+ };
1457
+ } else {
1458
+ // 分组式布局:每个分组是一个区块
1459
+ return {
1460
+ componentName: 'SectionContainer',
1461
+ id: nextNodeId(),
1462
+ props: {
1463
+ title: i18n(group.groupName, group.groupName),
1464
+ collapsible: true,
1465
+ defaultCollapsed: false,
1466
+ showTitle: true,
1467
+ sectionStyle: 'default',
1468
+ divider: groupIndex > 0, // 第一个分组不显示分隔线
1469
+ __gridSpan: 1,
1470
+ },
1471
+ condition: true,
1472
+ hidden: false,
1473
+ title: '',
1474
+ isLocked: false,
1475
+ conditionGroup: '',
1476
+ children: groupFieldComponents,
1477
+ };
1478
+ }
1479
+ });
1480
+ }
1481
+
1482
+ // ── 生成表单 Schema ──────────────────────────────────
1483
+
1484
+ function buildFormSchema(formTitle, fields, formUuid, corpId, appType, layout, theme, labelAlign, options) {
1485
+ const compilerOptions = createCompilerOptions(options);
1486
+ // 解析布局配置
1487
+ const layoutConfig = getLayoutConfig(layout || 'single');
1488
+ const columns = layoutConfig.columns;
1489
+
1490
+ // 解析主题配置
1491
+ const themeConfig = getThemeConfig(theme || 'default', labelAlign || 'top');
1492
+ const formContainerChildren = layoutConfig.groupFields
1493
+ ? buildGroupedFieldComponents(fields, layoutConfig.formLayout, compilerOptions)
1494
+ : fields.map(function (field) {
1495
+ return buildFieldComponent(field, compilerOptions);
1496
+ });
1497
+
1498
+ // 为 SerialNumberField 设置 formula(需要 corpId、appType 和 formUuid)
1499
+ fillSerialNumberFormulas(formContainerChildren, corpId, appType, formUuid, compilerOptions);
1500
+
1501
+ // 解析 @label:字段名 引用(必须在所有字段构建完成后执行)
1502
+ resolveFieldIdReferences(formContainerChildren);
1503
+ const componentAliasItems = buildComponentAliasItems(formContainerChildren);
1504
+
1505
+ const componentNames = collectComponentNames(fields);
1506
+
1507
+ // 构造函数代码(与模板完全一致)
1508
+ const constructorCode = "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}";
1509
+
1510
+ // actions 模块代码(与模板一致的默认空实现)
1511
+ const actionsCompiled = '"use strict";\n\nexports.__esModule = true;\nexports.didMount = didMount;\nfunction didMount() {\n console.log("\\u300C\\u9875\\u9762 JS\\u300D\\uFF1A\\u5F53\\u524D\\u9875\\u9762\\u5730\\u5740 " + location.href);\n}\n';
1512
+ const actionsSource = 'export function didMount() {\n console.log(`「页面 JS」:当前页面地址 ${location.href}`);\n}';
1513
+
1514
+ // Page 组件树(FormContainer 外层结构与模板保持一致,仅 id 随机生成)
1515
+ const pageComponentsTree = [
1516
+ {
1517
+ componentName: 'Page',
1518
+ id: nextNodeId(),
1519
+ props: {
1520
+ contentBgColor: 'white',
1521
+ pageStyle: { backgroundColor: '#f2f3f5' },
1522
+ contentMargin: '20',
1523
+ contentPadding: '20',
1524
+ showTitle: false,
1525
+ contentPaddingMobile: '0',
1526
+ templateVersion: '1.0.0',
1527
+ contentMarginMobile: '0',
1528
+ className: 'page_' + Date.now().toString(36),
1529
+ contentBgColorMobile: 'white',
1530
+ titleName: i18n('标题名称', 'title', 'タイトル'),
1531
+ titleDesc: i18n('标题描述', 'description', '説明'),
1532
+ titleColor: 'light',
1533
+ titleBg: 'https://img.alicdn.com/imgextra/i2/O1CN0143ATPP1wIa9TrVvzN_!!6000000006285-2-tps-3360-400.png_.webp',
1534
+ backgroundColorCustom: '#f1f2f3',
1535
+ sizePc: themeConfig.fieldSpacing === 'small' ? 'small' : themeConfig.fieldSpacing === 'large' ? 'large' : 'medium',
1536
+ labelAlignPc: themeConfig.labelAlignPc,
1537
+ labelWidthPc: themeConfig.labelWidthPc,
1538
+ labelWeightPc: themeConfig.labelWeightPc,
1539
+ labelAlignMobile: labelAlign || 'top',
1540
+ labelWidthMobile: labelAlign === 'left' || labelAlign === 'right' ? '80px' : 'auto',
1541
+ labelWeightMobile: 'normal',
1542
+ },
1543
+ condition: true,
1544
+ css: 'body{background-color:#f2f3f5}',
1545
+ methods: {
1546
+ __initMethods__: {
1547
+ type: 'js',
1548
+ source: 'function (exports, module) { /*set actions code here*/ }',
1549
+ compiled: 'function (exports, module) { /*set actions code here*/ }',
1550
+ },
1551
+ },
1552
+ dataSource: {
1553
+ offline: [],
1554
+ globalConfig: {
1555
+ fit: {
1556
+ compiled: "'use strict';\n\nvar __preParser__ = function fit(response) {\n var content = response.content !== undefined ? response.content : response;\n var error = {\n message: response.errorMsg || response.errors && response.errors[0] && response.errors[0].msg || response.content || '远程数据源请求出错,success is false'\n };\n var success = true;\n if (response.success !== undefined) {\n success = response.success;\n } else if (response.hasError !== undefined) {\n success = !response.hasError;\n }\n return {\n content: content,\n success: success,\n error: error\n };\n};",
1557
+ source: "function fit(response) {\r\n const content = (response.content !== undefined) ? response.content : response;\r\n const error = {\r\n message: response.errorMsg ||\r\n (response.errors && response.errors[0] && response.errors[0].msg) ||\r\n response.content || '远程数据源请求出错,success is false',\r\n };\r\n let success = true;\r\n if (response.success !== undefined) {\r\n success = response.success;\r\n } else if (response.hasError !== undefined) {\r\n success = !response.hasError;\r\n }\r\n return {\r\n content,\r\n success,\r\n error,\r\n };\r\n}",
1558
+ type: 'js',
1559
+ error: {},
1560
+ },
1561
+ },
1562
+ online: [],
1563
+ list: [],
1564
+ sync: true,
1565
+ },
1566
+ lifeCycles: {
1567
+ constructor: {
1568
+ type: 'js',
1569
+ compiled: constructorCode,
1570
+ source: constructorCode,
1571
+ },
1572
+ componentDidMount: { name: 'didMount', id: 'didMount', params: {}, type: 'actionRef' },
1573
+ },
1574
+ hidden: false,
1575
+ title: '',
1576
+ isLocked: false,
1577
+ conditionGroup: '',
1578
+ children: [
1579
+ {
1580
+ componentName: 'RootHeader',
1581
+ id: nextNodeId(),
1582
+ props: {},
1583
+ condition: true,
1584
+ hidden: false,
1585
+ title: '',
1586
+ isLocked: false,
1587
+ conditionGroup: '',
1588
+ },
1589
+ {
1590
+ componentName: 'RootContent',
1591
+ id: nextNodeId(),
1592
+ props: {},
1593
+ condition: true,
1594
+ hidden: false,
1595
+ title: '',
1596
+ isLocked: false,
1597
+ conditionGroup: '',
1598
+ children: [
1599
+ {
1600
+ componentName: 'FormContainer',
1601
+ id: nextNodeId(),
1602
+ props: {
1603
+ formLabel: i18n(formTitle, formTitle),
1604
+ formLabelVisible: true,
1605
+ columns: columns,
1606
+ labelAlign: labelAlign || 'top',
1607
+ submitText: i18n('提交', 'Submit', '送信'),
1608
+ stageText: i18n('暂存', 'Save draft', '下書き保存'),
1609
+ submitAndNewText: i18n('提交并继续', 'Submit and New', '送信して続ける'),
1610
+ fieldId: 'formContainer_' + Date.now().toString(36) + 'a',
1611
+ aiFormConfig: { systemPrompt: '', model: 'qwen' },
1612
+ beforeSubmit: false,
1613
+ afterSubmit: false,
1614
+ onProcessActionValidate: false,
1615
+ afterFormDataInit: false,
1616
+ },
1617
+ condition: true,
1618
+ hidden: false,
1619
+ title: '',
1620
+ isLocked: false,
1621
+ conditionGroup: '',
1622
+ children: formContainerChildren,
1623
+ },
1624
+ ],
1625
+ },
1626
+ {
1627
+ componentName: 'RootFooter',
1628
+ id: nextNodeId(),
1629
+ props: {},
1630
+ condition: true,
1631
+ hidden: false,
1632
+ title: '',
1633
+ isLocked: false,
1634
+ conditionGroup: '',
1635
+ children: [
1636
+ {
1637
+ componentName: 'FooterYida',
1638
+ id: nextNodeId(),
1639
+ props: {},
1640
+ condition: true,
1641
+ hidden: false,
1642
+ title: '',
1643
+ isLocked: false,
1644
+ conditionGroup: '',
1645
+ },
1646
+ ],
1647
+ },
1648
+ ],
1649
+ },
1650
+ ];
1651
+
1652
+ // 页面 Schema(与模板结构一致)- utils 放在 pages[0] 内
1653
+ const pageSchema = {
1654
+ utils: [
1655
+ {
1656
+ name: 'legaoBuiltin',
1657
+ type: 'npm',
1658
+ content: {
1659
+ package: '@ali/vu-legao-builtin',
1660
+ version: '3.0.0',
1661
+ exportName: 'legaoBuiltin',
1662
+ },
1663
+ },
1664
+ {
1665
+ name: 'yidaPlugin',
1666
+ type: 'npm',
1667
+ content: {
1668
+ package: '@ali/vu-yida-plugin',
1669
+ version: '1.1.0',
1670
+ exportName: 'yidaPlugin',
1671
+ },
1672
+ },
1673
+ ],
1674
+ componentsMap: buildComponentsMap(componentNames),
1675
+ componentsTree: pageComponentsTree,
1676
+ componentAlias: {
1677
+ items: componentAliasItems,
1678
+ },
1679
+ id: formUuid,
1680
+ connectComponent: [],
1681
+ };
1682
+
1683
+ // 顶层 Schema(与模板结构完全一致)- actions 和 config 与 pages 平级
1684
+ return {
1685
+ schemaType: 'superform',
1686
+ schemaVersion: '5.0',
1687
+ pages: [pageSchema],
1688
+ actions: {
1689
+ module: {
1690
+ compiled: actionsCompiled,
1691
+ source: actionsSource,
1692
+ },
1693
+ type: 'FUNCTION',
1694
+ list: [
1695
+ {
1696
+ id: nextNodeId(),
1697
+ type: 'lifeCycleEvent',
1698
+ name: 'didMount',
1699
+ relatedEventId: 'lifecycle:didMount',
1700
+ params: {},
1701
+ },
1702
+ ],
1703
+ },
1704
+ config: {
1705
+ connectComponent: [],
1706
+ },
1707
+ };
1708
+ }
1709
+
1710
+ function buildEmptyFormSchema() {
1711
+ const constructorCode = "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}";
1712
+ const actionsCompiled = '"use strict";\n\nexports.__esModule = true;\nexports.didMount = didMount;\nfunction didMount() {\n console.log("\\u300C\\u9875\\u9762 JS\\u300D\\uFF1A\\u5F53\\u524D\\u9875\\u9762\\u5730\\u5740 " + location.href);\n}\n';
1713
+ const actionsSource = 'export function didMount() {\n console.log(`「页面 JS」:当前页面地址 ${location.href}`);\n}';
1714
+
1715
+ return {
1716
+ schemaType: 'superform',
1717
+ schemaVersion: '5.0',
1718
+ actions: {
1719
+ module: { compiled: actionsCompiled, source: actionsSource },
1720
+ type: 'FUNCTION',
1721
+ list: [{ id: nextNodeId(), type: 'lifeCycleEvent', name: 'didMount', relatedEventId: 'lifecycle:didMount', params: {} }],
1722
+ },
1723
+ pages: [{
1724
+ utils: [
1725
+ { name: 'legaoBuiltin', type: 'npm', content: { package: '@ali/vu-legao-builtin', version: '3.0.0', exportName: 'legaoBuiltin' } },
1726
+ { name: 'yidaPlugin', type: 'npm', content: { package: '@ali/vu-yida-plugin', version: '1.1.0', exportName: 'yidaPlugin' } },
1727
+ ],
1728
+ componentsTree: [
1729
+ {
1730
+ componentName: 'Page',
1731
+ id: nextNodeId(),
1732
+ props: {
1733
+ contentBgColor: 'white',
1734
+ pageStyle: { backgroundColor: '#f2f3f5' },
1735
+ contentMargin: '20',
1736
+ contentPadding: '20',
1737
+ showTitle: false,
1738
+ contentPaddingMobile: '0',
1739
+ templateVersion: '1.0.0',
1740
+ contentMarginMobile: '0',
1741
+ className: 'page_' + Date.now().toString(36),
1742
+ contentBgColorMobile: 'white',
1743
+ },
1744
+ condition: true,
1745
+ css: 'body{background-color:#f2f3f5}',
1746
+ methods: {
1747
+ __initMethods__: {
1748
+ type: 'js',
1749
+ source: 'function (exports, module) { /*set actions code here*/ }',
1750
+ compiled: 'function (exports, module) { /*set actions code here*/ }',
1751
+ },
1752
+ },
1753
+ dataSource: { offline: [], globalConfig: {}, online: [], list: [], sync: true },
1754
+ lifeCycles: {
1755
+ constructor: { type: 'js', compiled: constructorCode, source: constructorCode },
1756
+ componentDidMount: { name: 'didMount', id: 'didMount', params: {}, type: 'actionRef' },
1757
+ },
1758
+ hidden: false,
1759
+ title: '',
1760
+ isLocked: false,
1761
+ conditionGroup: '',
1762
+ children: [
1763
+ { componentName: 'RootHeader', id: nextNodeId(), props: {}, condition: true, hidden: false, title: '', isLocked: false, conditionGroup: '' },
1764
+ {
1765
+ componentName: 'RootContent',
1766
+ id: nextNodeId(),
1767
+ props: {},
1768
+ condition: true,
1769
+ hidden: false,
1770
+ title: '',
1771
+ isLocked: false,
1772
+ conditionGroup: '',
1773
+ children: [
1774
+ {
1775
+ componentName: 'FormContainer',
1776
+ id: nextNodeId(),
1777
+ props: {
1778
+ beforeSubmit: false,
1779
+ 'submitProps.text': i18n('提交', 'Submit', '送信'),
1780
+ submitText: i18n('提交', 'Submit', '送信'),
1781
+ submitProps: { text: i18n('提交', 'Submit', '送信') },
1782
+ labelAlign: 'top',
1783
+ columns: 1,
1784
+ afterSubmit: false,
1785
+ fieldId: 'formContainer_' + Date.now().toString(36) + 'b',
1786
+ stageText: i18n('暂存', 'Save draft', '下書き保存'),
1787
+ submitAndNewText: i18n('提交并继续', 'Submit and New', '送信して続ける'),
1788
+ onProcessActionValidate: false,
1789
+ afterFormDataInit: false,
1790
+ },
1791
+ condition: true,
1792
+ hidden: false,
1793
+ title: '',
1794
+ isLocked: false,
1795
+ conditionGroup: '',
1796
+ children: [],
1797
+ },
1798
+ ],
1799
+ },
1800
+ {
1801
+ componentName: 'RootFooter',
1802
+ id: nextNodeId(),
1803
+ props: {},
1804
+ condition: true,
1805
+ hidden: false,
1806
+ title: '',
1807
+ isLocked: false,
1808
+ conditionGroup: '',
1809
+ children: [
1810
+ { componentName: 'FooterYida', id: nextNodeId(), props: {}, condition: true, hidden: false, title: '', isLocked: false, conditionGroup: '' },
1811
+ ],
1812
+ },
1813
+ ],
1814
+ },
1815
+ ],
1816
+ componentsMap: [
1817
+ { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'RootHeader' },
1818
+ { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'FormContainer' },
1819
+ { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'RootContent' },
1820
+ { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'FooterYida' },
1821
+ { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'RootFooter' },
1822
+ { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'Page' },
1823
+ ],
1824
+ },
1825
+ ],
1826
+ };
1827
+ }
1828
+
1829
+ function extractLabelText(component) {
1830
+ if (!component || !component.props || !component.props.label) {
1831
+ return '';
1832
+ }
1833
+ const label = component.props.label;
1834
+ if (typeof label === 'string') {
1835
+ return label;
1836
+ }
1837
+ return label.zh_CN || label.ja_JP || label.en_US || label.pureEn_US || '';
1838
+ }
1839
+
1840
+ function fillSerialNumberFormulas(components, corpId, appType, formUuid, options) {
1841
+ const compilerOptions = options || {};
1842
+ if (!Array.isArray(components)) {return;}
1843
+ components.forEach(function (component) {
1844
+ if (component.componentName === 'SerialNumberField' && component.props) {
1845
+ const existingFormula = component.props.formula;
1846
+ const hasValidFormula = existingFormula &&
1847
+ typeof existingFormula === 'object' &&
1848
+ typeof existingFormula.expression === 'string' &&
1849
+ existingFormula.expression.length > 0;
1850
+
1851
+ if (!hasValidFormula) {
1852
+ const fieldId = component.props.fieldId;
1853
+ const serialNumberRule = component.props.serialNumberRule;
1854
+ if (serialNumberRule) {
1855
+ const ruleJson = JSON.stringify({ type: 'custom', value: serialNumberRule });
1856
+ const escapedRuleJson = ruleJson.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
1857
+ component.props.formula = {
1858
+ expression: 'SERIALNUMBER("' + corpId + '", "' + appType + '", "' + formUuid + '", "' + fieldId + '", "' + escapedRuleJson + '")'
1859
+ };
1860
+ if (typeof compilerOptions.onSerialNumberFormula === 'function') {
1861
+ compilerOptions.onSerialNumberFormula(component.props.label && component.props.label.zh_CN || fieldId);
1862
+ }
1863
+ }
1864
+ }
1865
+ }
1866
+ // 递归处理子表、分组、卡片等容器内的字段
1867
+ if (Array.isArray(component.children)) {
1868
+ fillSerialNumberFormulas(component.children, corpId, appType, formUuid, compilerOptions);
1869
+ }
1870
+ });
1871
+ }
1872
+
1873
+ function compileFormDefinition(definition, options) {
1874
+ const normalized = normalizeFormDefinition(definition, options);
1875
+ if (!Array.isArray(normalized.fields) || normalized.fields.length === 0) {
1876
+ throw createFormCompilerError('表单字段定义不能为空', 'FORM_COMPILER_INVALID_DEFINITION');
1877
+ }
1878
+ const compilerOptions = createCompilerOptions(options);
1879
+ const schema = buildFormSchema(
1880
+ normalized.formTitle,
1881
+ normalized.fields,
1882
+ normalized.formUuid,
1883
+ normalized.corpId,
1884
+ normalized.appType,
1885
+ normalized.layout,
1886
+ normalized.theme,
1887
+ normalized.labelAlign,
1888
+ compilerOptions
1889
+ );
1890
+
1891
+ return {
1892
+ schema,
1893
+ fieldBindings: Object.assign({}, compilerOptions.fieldBindings),
1894
+ fieldBindingComponents: Object.assign({}, compilerOptions.fieldBindingComponents),
1895
+ };
1896
+ }
1897
+
1898
+ module.exports = {
1899
+ ASSOCIATION_FORM_FIELD_TYPE,
1900
+ FIELD_TYPE_ALIAS,
1901
+ OPTION_FIELD_TYPES,
1902
+ SUPPORTED_FIELD_TYPES,
1903
+ TABLE_FIELD_TYPE,
1904
+ i18n,
1905
+ compileFormDefinition,
1906
+ generateFieldId,
1907
+ buildFieldComponent,
1908
+ buildFormSchema,
1909
+ buildEmptyFormSchema,
1910
+ fillSerialNumberFormulas,
1911
+ extractLabelText,
1912
+ applySelectDataSourceConfig,
1913
+ resolveFieldIdReferences,
1914
+ normalizeFieldValidationRules,
1915
+ collectInputValidationRules,
1916
+ dedupeValidationRules,
1917
+ normalizeFillingRules,
1918
+ inferComponentNameFromFieldId,
1919
+ isOptionFieldType,
1920
+ isSupportedFieldType,
1921
+ normalizeFieldType,
1922
+ };