openyida 0.1.2 → 1.0.0-beta.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.
Files changed (61) hide show
  1. package/README.md +68 -38
  2. package/bin/yida.js +164 -761
  3. package/lib/babel-transform/index.js +244 -0
  4. package/lib/babel-transform/jsx-utils.js +89 -0
  5. package/lib/check-update.js +72 -0
  6. package/lib/copy.js +258 -0
  7. package/lib/create-app.js +174 -0
  8. package/lib/create-form.js +2244 -0
  9. package/lib/create-page.js +89 -0
  10. package/lib/env.js +164 -0
  11. package/lib/get-page-config.js +102 -0
  12. package/lib/get-schema.js +76 -0
  13. package/lib/login.js +323 -0
  14. package/lib/publish.js +610 -0
  15. package/lib/save-share-config.js +268 -0
  16. package/lib/update-form-config.js +237 -0
  17. package/lib/utils.js +443 -0
  18. package/lib/verify-short-url.js +279 -0
  19. package/package.json +20 -7
  20. package/project/.cache/demo-schema.json +2353 -0
  21. package/project/pages/src/demo-birthday-game.js +833 -0
  22. package/project/pages/src/demo-future-vision-2026.js +1102 -0
  23. package/project/pages/src/demo-salary-calculator.js +904 -0
  24. package/project/prd/demo-birthday-game.md +39 -0
  25. package/project/prd/demo-future-vision-2026.md +78 -0
  26. package/project/prd/demo-salary-calculator.md +101 -0
  27. package/scripts/postinstall.js +114 -0
  28. package/yida-skills/SKILL.md +273 -0
  29. package/yida-skills/reference/association-form-field.md +469 -0
  30. package/yida-skills/reference/employee-field.md +17 -0
  31. package/yida-skills/reference/model-api.md +73 -0
  32. package/yida-skills/reference/serial-number-field.md +132 -0
  33. package/yida-skills/reference/yida-api.md +1208 -0
  34. package/yida-skills/skills/yida-app/SKILL.md +394 -0
  35. package/yida-skills/skills/yida-create-app/SKILL.md +158 -0
  36. package/yida-skills/skills/yida-create-form-page/SKILL.md +598 -0
  37. package/yida-skills/skills/yida-create-page/SKILL.md +103 -0
  38. package/yida-skills/skills/yida-custom-page/SKILL.md +533 -0
  39. package/yida-skills/skills/yida-get-schema/SKILL.md +90 -0
  40. package/yida-skills/skills/yida-login/SKILL.md +200 -0
  41. package/yida-skills/skills/yida-logout/SKILL.md +58 -0
  42. package/yida-skills/skills/yida-page-config/SKILL.md +261 -0
  43. package/yida-skills/skills/yida-publish-page/SKILL.md +113 -0
  44. package/.eslintrc.json +0 -25
  45. package/.github/workflows/ci.yml +0 -123
  46. package/.github/workflows/publish.yml +0 -105
  47. package/.github/workflows/update-contributors.yml +0 -151
  48. package/.openclaw/skills/yida-issue/SKILL.md +0 -27
  49. package/.openclaw/skills/yida-issue/scripts/create-issue.js +0 -317
  50. package/CLAUDE.md +0 -168
  51. package/CONTRIBUTING.md +0 -59
  52. package/install-skills.ps1 +0 -162
  53. package/install-skills.sh +0 -175
  54. package/pages/dist/.gitkeep +0 -0
  55. package/pages/src/.gitkeep +0 -0
  56. package/prd/salary-calculator.md +0 -15
  57. package/tests/cli.test.js +0 -930
  58. package/tests/install.test.js +0 -277
  59. package/tests/yida-issue.test.js +0 -314
  60. /package/{config.json → project/config.json} +0 -0
  61. /package/{.cache → project/pages/dist}/.gitkeep +0 -0
@@ -0,0 +1,2244 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * create-form-page.js - 宜搭表单页面创建 & 更新工具
4
+ *
5
+ * 支持两种模式:
6
+ *
7
+ * 1. create 模式 - 创建新表单页面:
8
+ * node create-form-page.js create <appType> <formTitle> <fieldsJsonFile>
9
+ *
10
+ * 2. update 模式 - 更新已有表单页面:
11
+ * node create-form-page.js update <appType> <formUuid> <changesJsonOrFile>
12
+ *
13
+ * create 模式参数:
14
+ * appType - 应用 ID(必填),如 APP_XXX
15
+ * formTitle - 表单名称(必填)
16
+ * fieldsJsonFile - 字段定义 JSON 文件路径(必填)
17
+ *
18
+ * update 模式参数:
19
+ * appType - 应用 ID(必填)
20
+ * formUuid - 表单 UUID(必填),如 FORM-XXX
21
+ * changesJsonOrFile - 修改定义,支持 JSON 字符串(以 [ 开头)或 JSON 文件路径
22
+ *
23
+ * 字段定义 JSON 格式(create 模式):
24
+ * [
25
+ * { "type": "TextField", "label": "姓名", "required": true },
26
+ * { "type": "SelectField", "label": "部门", "options": ["技术部", "产品部"] },
27
+ * { "type": "DateField", "label": "入职日期" },
28
+ * { "type": "TableField", "label": "费用明细", "children": [
29
+ * { "type": "TextField", "label": "项目" },
30
+ * { "type": "NumberField", "label": "金额" }
31
+ * ]}
32
+ * ]
33
+ *
34
+ * 修改定义 JSON 格式(update 模式):
35
+ * [
36
+ * { "action": "add", "field": { "type": "TextField", "label": "备注" } },
37
+ * { "action": "add", "field": { "type": "SelectField", "label": "部门", "options": ["技术部", "产品部"] }, "after": "姓名" },
38
+ * { "action": "delete", "label": "备注" },
39
+ * { "action": "update", "label": "年龄", "changes": { "required": true, "placeholder": "请输入年龄" } }
40
+ * ]
41
+ *
42
+ * 支持的字段类型:
43
+ * TextField, TextareaField, RadioField, SelectField, CheckboxField,
44
+ * MultiSelectField, NumberField, RateField, DateField, CascadeDateField,
45
+ * EmployeeField, DepartmentSelectField, CountrySelectField, AddressField,
46
+ * AttachmentField, ImageField, TableField, AssociationFormField, SerialNumberField
47
+ *
48
+ * 前置条件:
49
+ * 项目根目录下需存在 .cache/cookies.json(由 yida-login 生成)。
50
+ * 若接口返回 302(登录失效),脚本会自动调用 login.py 重新登录后重试。
51
+ *
52
+ * 示例:
53
+ * # 创建表单
54
+ * node .claude/skills/yida-create-form-page/scripts/create-form-page.js create "APP_xxx" "员工信息登记" fields.json
55
+ * # 更新表单
56
+ * node .claude/skills/yida-create-form-page/scripts/create-form-page.js update "APP_XXX" "FORM-YYY" '[{"action":"add","field":{"type":"TextField","label":"备注"}}]'
57
+ */
58
+
59
+ const https = require("https");
60
+ const http = require("http");
61
+ const fs = require("fs");
62
+ const path = require("path");
63
+ const querystring = require("querystring");
64
+ const { execSync } = require("child_process");
65
+ const { findProjectRoot, loadCookieData, triggerLogin, refreshCsrfToken, resolveBaseUrl, isLoginExpired, isCsrfTokenExpired } = require('./utils');
66
+
67
+ // ── 配置读取 ──────────────────────────────────────────
68
+ const CONFIG = fs.existsSync(path.resolve(findProjectRoot(), "config.json")) ? JSON.parse(fs.readFileSync(path.resolve(findProjectRoot(), "config.json"), "utf-8")) : {};
69
+ const DEFAULT_BASE_URL = CONFIG.defaultBaseUrl || "https://www.aliwork.com";
70
+ const PROJECT_ROOT = findProjectRoot();
71
+ const COOKIE_FILE = path.join(PROJECT_ROOT, ".cache", "cookies.json");
72
+ // ── 选项类字段类型 ───────────────────────────────────
73
+ const OPTION_FIELD_TYPES = ["RadioField", "SelectField", "CheckboxField", "MultiSelectField"];
74
+
75
+ // ── 接口路径生成 ──────────────────────────────────────
76
+
77
+ /**
78
+ * 生成宜搭接口请求路径
79
+ * @param {string} appType - 应用 ID
80
+ * @param {string} apiName - 接口名称,如 'saveFormSchema', 'getFormSchema', 'saveFormSchemaInfo', 'updateFormConfig'
81
+ * @param {Object} options - 可选参数
82
+ * @param {string} options.prefix - 路径前缀,如 '_view',默认为空
83
+ * @param {string} options.namespace - 命名空间,如 'alibaba' 或 'dingtalk',默认 'dingtalk'
84
+ * @param {boolean} options.addTimestamp - 是否添加时间戳参数,默认 false
85
+ * @returns {string} 完整的接口路径
86
+ */
87
+ function buildApiPath(appType, apiName, options = {}) {
88
+ const { prefix = "", namespace = "dingtalk", addTimestamp = false } = options;
89
+ const prefixPath = prefix ? `/${prefix}` : "";
90
+ const timestamp = addTimestamp ? `?_stamp=${Date.now()}` : "";
91
+ return `/${namespace}/web/${appType}${prefixPath}/query/formdesign/${apiName}.json${timestamp}`;
92
+ }
93
+
94
+ // ── 参数解析 ─────────────────────────────────────────
95
+
96
+ function parseArgs() {
97
+ const args = process.argv.slice(2);
98
+ const mode = args[0];
99
+
100
+ if (mode === "create") {
101
+ if (args.length < 4) {
102
+ console.error("用法: node create-form-page.js create <appType> <formTitle> <fieldsJsonFile>");
103
+ console.error('示例:node .claude/skills/yida-create-form-page/scripts/create-form-page.js create "APP_XXX" "员工信息登记" fields.json');
104
+ process.exit(1);
105
+ }
106
+ return { mode: "create", appType: args[1], formTitle: args[2], fieldsJsonOrFile: args[3] };
107
+ }
108
+
109
+ if (mode === "update") {
110
+ if (args.length < 4) {
111
+ console.error("用法: node create-form-page.js update <appType> <formUuid> <changesJsonOrFile>");
112
+ console.error('示例:node .claude/skills/yida-create-form-page/scripts/create-form-page.js update "APP_XXX" "FORM-YYY" \'[{"action":"add","field":{"type":"TextField","label":"备注"}}]\'');
113
+ process.exit(1);
114
+ }
115
+ return { mode: "update", appType: args[1], formUuid: args[2], changesJsonOrFile: args[3] };
116
+ }
117
+
118
+ // 兼容旧用法(无 mode 参数,默认 create 模式)
119
+ if (args.length >= 3 && mode !== "create" && mode !== "update") {
120
+ return { mode: "create", appType: args[0], formTitle: args[1], fieldsJsonOrFile: args[2] };
121
+ }
122
+
123
+ console.error("用法:");
124
+ console.error(" 创建: node create-form-page.js create <appType> <formTitle> <fieldsJsonFile>");
125
+ console.error(" 更新: node create-form-page.js update <appType> <formUuid> <changesJsonOrFile>");
126
+ console.error("\n示例:");
127
+ console.error(' node .claude/skills/yida-create-form-page/scripts/create-form-page.js create "APP_XXX" "员工信息登记" fields.json');
128
+ console.error(' node .claude/skills/yida-create-form-page/scripts/create-form-page.js update "APP_XXX" "FORM-YYY" \'[{"action":"add","field":{"type":"TextField","label":"备注"}}]\'');
129
+ process.exit(1);
130
+ }
131
+
132
+ // ── 登录态管理 ───────────────────────────────────────
133
+
134
+ /**
135
+ * 从 Cookie 列表中提取 csrf_token 和 corp_id
136
+ * - csrf_token:name="tianshu_csrf_token" 的 cookie value
137
+ * - corp_id:name="tianshu_corp_user" 的 cookie value,格式 "{corpId}_{userId}",按最后一个 "_" 分隔
138
+ */
139
+ function extractInfoFromCookies(cookies) {
140
+ let csrfToken = null;
141
+ let corpId = null;
142
+ for (const cookie of cookies) {
143
+ if (cookie.name === "tianshu_csrf_token") {
144
+ csrfToken = cookie.value;
145
+ } else if (cookie.name === "tianshu_corp_user") {
146
+ const lastUnderscore = cookie.value.lastIndexOf("_");
147
+ if (lastUnderscore > 0) {
148
+ corpId = cookie.value.slice(0, lastUnderscore);
149
+ }
150
+ }
151
+ }
152
+ return { csrfToken, corpId };
153
+ }
154
+
155
+
156
+ // ── 读取字段定义 ─────────────────────────────────────
157
+
158
+ function readFieldsDefinition(fieldsJsonOrFile) {
159
+ var rawContent;
160
+
161
+ // 判断是 JSON 字符串还是文件路径
162
+ if (fieldsJsonOrFile.trimStart().startsWith("[")) {
163
+ rawContent = fieldsJsonOrFile;
164
+ } else if (fieldsJsonOrFile.trimStart().startsWith("{")) {
165
+ rawContent = fieldsJsonOrFile;
166
+ } else {
167
+ var resolvedPath = path.resolve(fieldsJsonOrFile);
168
+ if (!fs.existsSync(resolvedPath)) {
169
+ console.error(" ❌ 字段定义文件不存在: " + resolvedPath);
170
+ process.exit(1);
171
+ }
172
+ rawContent = fs.readFileSync(resolvedPath, "utf-8");
173
+ }
174
+
175
+ try {
176
+ const parsed = JSON.parse(rawContent);
177
+
178
+ // 支持两种格式:
179
+ // 1. 数组格式: [{type: "TextField", label: "姓名"}, ...]
180
+ // 2. 对象格式: { columns: 2, fields: [{type: "TextField", label: "姓名"}, ...] }
181
+ let fields;
182
+ let columns = 1; // 默认单列
183
+
184
+ if (Array.isArray(parsed)) {
185
+ fields = parsed;
186
+ } else if (typeof parsed === "object" && parsed !== null) {
187
+ fields = parsed.fields || [];
188
+ columns = parsed.columns !== undefined ? parsed.columns : 1;
189
+ } else {
190
+ throw new Error("字段定义格式不正确");
191
+ }
192
+
193
+ if (!Array.isArray(fields) || fields.length === 0) {
194
+ throw new Error("字段定义必须是非空数组");
195
+ }
196
+
197
+ return { fields, columns };
198
+ } catch (parseError) {
199
+ console.error(" ❌ 解析字段定义失败: " + parseError.message);
200
+ process.exit(1);
201
+ }
202
+ }
203
+
204
+ // ── 读取修改定义(update 模式) ─────────────────────
205
+
206
+ function readChangesDefinition(changesJsonOrFile) {
207
+ var rawContent;
208
+
209
+ // 判断是 JSON 字符串还是文件路径
210
+ if (changesJsonOrFile.trimStart().startsWith("[")) {
211
+ rawContent = changesJsonOrFile;
212
+ } else {
213
+ var resolvedPath = path.resolve(changesJsonOrFile);
214
+ if (!fs.existsSync(resolvedPath)) {
215
+ console.error(" ❌ 修改定义文件不存在: " + resolvedPath);
216
+ process.exit(1);
217
+ }
218
+ rawContent = fs.readFileSync(resolvedPath, "utf-8");
219
+ }
220
+
221
+ try {
222
+ var changes = JSON.parse(rawContent);
223
+ if (!Array.isArray(changes) || changes.length === 0) {
224
+ throw new Error("修改定义必须是非空数组");
225
+ }
226
+ return changes;
227
+ } catch (parseError) {
228
+ console.error(" ❌ 解析修改定义失败: " + parseError.message);
229
+ process.exit(1);
230
+ }
231
+ }
232
+
233
+ // ── 自增 ID 计数器 ───────────────────────────────────
234
+ let nodeIdCounter = 1;
235
+
236
+ function nextNodeId() {
237
+ return "node_oc" + Date.now().toString(36) + (nodeIdCounter++).toString(36);
238
+ }
239
+
240
+ function generateFieldId(componentName) {
241
+ const prefix = componentName.charAt(0).toLowerCase() + componentName.slice(1);
242
+ // 使用 4 位时间戳 + 4 位随机数,共 8 位
243
+ const timePart = Date.now().toString(36).slice(-4);
244
+ const randomPart = Math.random().toString(36).substring(2, 6);
245
+ const suffix = timePart + randomPart;
246
+ return prefix + "_" + suffix;
247
+ }
248
+
249
+ // ── i18n 辅助 ────────────────────────────────────────
250
+
251
+ function i18n(text, enText) {
252
+ return { type: "i18n", zh_CN: text, en_US: enText || text };
253
+ }
254
+
255
+ // ── 默认占位符 ───────────────────────────────────────
256
+
257
+ const PLACEHOLDER_INPUT = i18n("请输入", "Please enter");
258
+ const PLACEHOLDER_SELECT = i18n("请选择", "please select");
259
+
260
+ // ── 生成选项数据源 ───────────────────────────────────
261
+
262
+ function buildOptionDataSource(options) {
263
+ return options.map(function (optionText, optionIndex) {
264
+ return {
265
+ text: { zh_CN: optionText, en_US: optionText, type: "i18n" },
266
+ value: optionText,
267
+ sid: "serial_" + Date.now().toString(36) + optionIndex,
268
+ disable: false,
269
+ defaultChecked: false,
270
+ };
271
+ });
272
+ }
273
+
274
+ // ── 生成字段组件 ─────────────────────────────────────
275
+
276
+ function buildFieldComponent(field) {
277
+ const componentName = field.type;
278
+ const fieldId = generateFieldId(componentName);
279
+ const nodeId = nextNodeId();
280
+
281
+ // 基础 validation
282
+ const validation = [];
283
+ if (field.required) {
284
+ validation.push({ type: "required" });
285
+ }
286
+
287
+ // 基础 props(所有字段通用)
288
+ const props = {
289
+ __useMediator: "value",
290
+ fieldId: fieldId,
291
+ label: i18n(field.label, componentName),
292
+ __category__: "form",
293
+ behavior: "NORMAL",
294
+ visibility: ["PC", "MOBILE"],
295
+ dataEntryMode: false,
296
+ submittable: "DEFAULT",
297
+ validation: validation,
298
+ labelAlign: "top",
299
+ labelTextAlign: "left",
300
+ labelColSpan: 4,
301
+ size: "medium",
302
+ submittable: "ALWAYS",
303
+ };
304
+
305
+ // 文本类字段
306
+ if (componentName === "TextField" || componentName === "TextareaField") {
307
+ props.hasClear = true;
308
+ props.placeholder = field.placeholder ? i18n(field.placeholder) : PLACEHOLDER_INPUT;
309
+ props.valueType = "custom";
310
+ props.validationType = "text";
311
+ props.value = i18n("", "");
312
+ props.hasLimitHint = false;
313
+ props.maxLength = 200;
314
+ props.rows = 4;
315
+ props.linkage = "";
316
+ props.__gridSpan = 1;
317
+ props.tips = i18n("", "");
318
+ props.autoHeight = false;
319
+ props.scanCode = { enabled: false, type: "all", editable: true };
320
+ props.complexValue = {
321
+ complexType: "custom",
322
+ formula: "",
323
+ value: { en_US: "", zh_CN: "", type: "i18n" },
324
+ };
325
+ props.variable = "";
326
+ props.formula = "";
327
+ props.isCustomStore = true;
328
+
329
+ // TextareaField 特有属性
330
+ if (componentName === "TextareaField") {
331
+ props.htmlType = "textarea";
332
+ props.showEmptyRows = false;
333
+ }
334
+ }
335
+
336
+ // 数字字段
337
+ if (componentName === "NumberField") {
338
+ props.hasClear = true;
339
+ props.placeholder = field.placeholder ? i18n(field.placeholder) : i18n("请输入数字", "Please enter a number");
340
+ props.valueType = "custom";
341
+ props.__gridSpan = 1;
342
+ props.tips = i18n("", "");
343
+ props.linkage = "";
344
+ props.precision = 0;
345
+ props.step = 1;
346
+ props.thousandsSeparators = false;
347
+ props.innerAfter = field.innerAfter || "";
348
+ props.value = "";
349
+ props.labelColOffset = 0;
350
+ props.wrapperColSpan = 0;
351
+ props.wrapperColOffset = 0;
352
+ props.complexValue = {
353
+ complexType: "custom",
354
+ formula: "",
355
+ value: "",
356
+ };
357
+ props.variable = "";
358
+ props.formula = "";
359
+ props.isCustomStore = true;
360
+ }
361
+
362
+ // 评分字段
363
+ if (componentName === "RateField") {
364
+ props.count = 5;
365
+ props.allowHalf = false;
366
+ props.showGrade = false;
367
+ props.__gridSpan = 1;
368
+ props.tips = i18n("", "");
369
+ }
370
+
371
+ // 日期字段
372
+ if (componentName === "DateField") {
373
+ props.placeholder = field.placeholder ? i18n(field.placeholder) : PLACEHOLDER_SELECT;
374
+ props.__gridSpan = 1;
375
+ props.tips = i18n("", "");
376
+ props.linkage = "";
377
+ props.format = field.format || "YYYY-MM-DD";
378
+ props.hasClear = true;
379
+ props.disabledDate = { type: "none" };
380
+ props.valueType = "custom";
381
+ props.value = "";
382
+ props.formula = "";
383
+ props.variable = "";
384
+ props.resetTime = false;
385
+ props.complexValue = {
386
+ complexType: "custom",
387
+ value: "",
388
+ formula: "",
389
+ };
390
+ }
391
+
392
+ // 级联日期字段
393
+ if (componentName === "CascadeDateField") {
394
+ props.__gridSpan = 1;
395
+ props.tips = i18n("", "");
396
+ props.format = field.format || "YYYY-MM-DD";
397
+ props.hasClear = true;
398
+ props.resetTime = false;
399
+ props.disabledDate = false;
400
+ }
401
+
402
+ // 选项类字段(RadioField、SelectField、CheckboxField、MultiSelectField)
403
+ if (OPTION_FIELD_TYPES.indexOf(componentName) !== -1) {
404
+ const options = field.options || ["选项一", "选项二", "选项三"];
405
+ const dataSource = buildOptionDataSource(options);
406
+
407
+ props.dataSource = dataSource;
408
+ props.dataSourceType = "custom";
409
+ props.defaultDataSource = {
410
+ customStashOptions: [],
411
+ complexType: "custom",
412
+ options: dataSource,
413
+ formula: { data: [], event: { "onPageReady,onChange": [] } },
414
+ url: "",
415
+ searchConfig: { afterFetch: "", type: "JSONP", beforeFetch: "", url: "" },
416
+ };
417
+ props.__gridSpan = 1;
418
+ props.tips = i18n("", "");
419
+ props.linkage = "";
420
+
421
+ if (componentName === "RadioField" || componentName === "CheckboxField") {
422
+ props.value = "";
423
+ props.valueType = "custom";
424
+ props.complexValue = { complexType: "custom", formula: "", value: "" };
425
+ props.variable = "";
426
+ props.formula = "";
427
+ }
428
+
429
+ if (componentName === "SelectField" || componentName === "MultiSelectField") {
430
+ props.hasClear = true;
431
+ props.showSearch = true;
432
+ props.autoWidth = true;
433
+ props.placeholder = field.placeholder ? i18n(field.placeholder) : PLACEHOLDER_SELECT;
434
+ props.value = "";
435
+ props.valueType = "custom";
436
+ props.reusePrivilege = false;
437
+ props.isUseDataSourceColor = false;
438
+ props.dataSourceLinkage = "";
439
+ props.filterLocal = true;
440
+ props.notFoundContent = i18n("无数据", "Not Found");
441
+ props.searchConfig = {
442
+ dataType: "jsonp",
443
+ url: "",
444
+ beforeFetch: "function willFetch(params) {\n return params;\n}",
445
+ afterFetch: "function didFetch(content) {\n return content;\n}",
446
+ };
447
+ props.complexValue = { complexType: "custom", formula: "", value: "" };
448
+ props.variable = "";
449
+ props.formula = "";
450
+ }
451
+
452
+ if (componentName === "SelectField") {
453
+ props.mode = "single";
454
+ } else if (componentName === "MultiSelectField") {
455
+ props.mode = "multiple";
456
+ }
457
+ }
458
+
459
+ // 成员字段
460
+ if (componentName === "EmployeeField") {
461
+ props.placeholder = PLACEHOLDER_SELECT;
462
+ props.__gridSpan = 1;
463
+ props.tips = i18n("", "");
464
+ props.multiple = field.multiple || false;
465
+ props.hasClear = true;
466
+ props.userRangeType = "ALL";
467
+ props.roleRange = [];
468
+ props.userRange = [];
469
+ props.showEmpIdType = "NAME";
470
+ props.startWithDepartmentId = "SELF";
471
+ props.renderLinkForView = true;
472
+ props.showEmplId = false;
473
+ props.closeOnSelect = false;
474
+ props.useAliworkUrl = false;
475
+ props.linkage = "";
476
+
477
+ props.valueType = "variable";
478
+ props.complexValue = {
479
+ complexType: "formula",
480
+ formula: "USER()",
481
+ value: [],
482
+ };
483
+ props.variable = { type: "user" };
484
+ props.formula = "";
485
+ props.value = [];
486
+ }
487
+
488
+ // 部门字段
489
+ if (componentName === "DepartmentSelectField") {
490
+ props.placeholder = i18n("请输入关键字进行搜索", "Please enter keyword");
491
+ props.__gridSpan = 1;
492
+ props.tips = i18n("", "");
493
+ props.multiple = field.multiple || false;
494
+ props.valueType = "custom";
495
+ props.value = [];
496
+ props.deptRangeType = "ALL";
497
+ props.deptRange = [];
498
+ props.mode = "single";
499
+ props.hasClear = true;
500
+ props.dataSource = {
501
+ searchConfig: {
502
+ dataType: "json",
503
+ url: "/query/deptService/searchDepts.json",
504
+ beforeFetch: "function willFetch(data) {\n data.key = data.key || data.q || \"\";\n return data;\n}",
505
+ 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}",
506
+ },
507
+ };
508
+ props.complexValue = {
509
+ complexType: "custom",
510
+ value: [],
511
+ formula: "",
512
+ };
513
+ props.variable = "";
514
+ props.formula = "";
515
+ props.linkage = "";
516
+ props.isShowDeptFullName = false;
517
+ props.hasSelectAll = false;
518
+ }
519
+
520
+ // 国家字段
521
+ if (componentName === "CountrySelectField") {
522
+ props.placeholder = PLACEHOLDER_SELECT;
523
+ props.__gridSpan = 1;
524
+ props.tips = i18n("", "");
525
+ props.multiple = field.multiple || false;
526
+ props.value = [];
527
+ props.mode = "single";
528
+ props.hasClear = true;
529
+ props.showSearch = true;
530
+ props.hasSelectAll = false;
531
+ }
532
+
533
+ // 地址字段
534
+ if (componentName === "AddressField") {
535
+ props.__gridSpan = 1;
536
+ props.tips = i18n("", "");
537
+ props.placeholder = field.placeholder ? i18n(field.placeholder) : PLACEHOLDER_SELECT;
538
+ props.countryMode = "default";
539
+ props.countryScope = 1;
540
+ props.addressType = "ADDRESS";
541
+ props.subLabel = i18n("详细地址", "Detailed Address");
542
+ props.detailPlaceholder = i18n("请输入详细地址", "Please input detailed address");
543
+ props.hasClear = true;
544
+ props.enableLocation = true;
545
+ props.value = {};
546
+ props.optionAutoWidth = true;
547
+ props.showCountry = false;
548
+ }
549
+
550
+ // 附件字段
551
+ if (componentName === "AttachmentField") {
552
+ props.__gridSpan = 1;
553
+ props.tips = i18n("", "");
554
+ props.valueType = "custom";
555
+ props.value = "";
556
+ props.complexValue = {
557
+ complexType: "custom",
558
+ value: "",
559
+ formula: "",
560
+ };
561
+ props.type = "normal";
562
+ props.listType = "text";
563
+ props.buttonText = i18n("上传文件", "Upload");
564
+ props.buttonSize = "medium";
565
+ props.buttonType = "normal";
566
+ props.multiple = true;
567
+ props.method = "post";
568
+ props.limit = 9;
569
+ props.maxFileSize = 100;
570
+ props.autoUpload = true;
571
+ props.accept = "";
572
+ props.formula = "";
573
+ props.linkage = "";
574
+ props.variable = "";
575
+ props.onlineEdit = false;
576
+ props.withCredentials = false;
577
+ }
578
+
579
+ // 图片上传字段
580
+ if (componentName === "ImageField") {
581
+ props.__gridSpan = 1;
582
+ props.tips = i18n("", "");
583
+ props.valueType = "custom";
584
+ props.value = "";
585
+ props.complexValue = {
586
+ complexType: "custom",
587
+ value: "",
588
+ formula: "",
589
+ };
590
+ props.aiRecognitionConfig = {};
591
+ props.type = "normal";
592
+ props.normalListType = "image";
593
+ props.cardListType = "card";
594
+ props.listType = "image";
595
+ props.buttonText = i18n("图片上传", "Upload");
596
+ props.buttonSize = "medium";
597
+ props.buttonType = "normal";
598
+ props.enableCameraDate = true;
599
+ props.enableCameraLocation = true;
600
+ props.saveCameraImageToLocal = true;
601
+ props.multiple = true;
602
+ props.method = "post";
603
+ props.limit = 9;
604
+ props.maxFileSize = 50;
605
+ props.autoUpload = true;
606
+ props.accept = "image/*";
607
+ props.formula = "";
608
+ props.linkage = "";
609
+ props.variable = "";
610
+ props.aiRecognitionSwitch = false;
611
+ props.onlyCameraUpload = false;
612
+ props.enableCameraWatermark = false;
613
+ props.enableCameraCompression = false;
614
+ }
615
+
616
+ // 子表字段
617
+ if (componentName === "TableField") {
618
+ props.__gridSpan = 1;
619
+ props.linkage = "";
620
+ props.tips = i18n("", "");
621
+ props.showIndex = true;
622
+ props.copyButtonText = i18n("复制", "Copy");
623
+ props.addButtonBehavior = "NORMAL";
624
+ props.pageSize = 20;
625
+ props.addButtonText = i18n("新增一项", "Add item");
626
+ props.enableExport = true;
627
+ props.addButtonPosition = "bottom";
628
+ props.actionsColumnWidth = 70;
629
+ props.theme = "split";
630
+ props.delButtonText = i18n("删除", "Remove");
631
+ props.useCustomColumnsWidth = false;
632
+ props.showSortable = false;
633
+ props.moveUp = i18n("上移", "Up");
634
+ props.maxItems = 500;
635
+ props.tableLayout = "fixed";
636
+ props.showActions = true;
637
+ props.indexName = i18n("项目", "Line");
638
+ props.showCopyAction = false;
639
+ props.showDelAction = true;
640
+ props.showTableHead = true;
641
+ props.moveDown = i18n("下移", "Down");
642
+ props.pcFreezeColumnStartCounts = "0";
643
+ props.layout = "TABLE";
644
+ props.showDeleteConfirm = true;
645
+ props.minItems = 1;
646
+ props.enableImport = true;
647
+ props.defaultCollapseStatus = true;
648
+ props.isFreezeOperateColumn = true;
649
+ props.actions = [];
650
+ props.complexValue = { complexType: "custom", formula: "" };
651
+ props.valueType = "custom";
652
+ props.__designerDevice = "pc";
653
+ props.mobileLayout = "TILED";
654
+ props.mobileFreezeColumnStartCounts = "0";
655
+ props.enableBatchDelete = false;
656
+ props.filterEmptyRowData = false;
657
+ props.enableSummary = false;
658
+ }
659
+
660
+ // 关联表单字段
661
+ if (componentName === "AssociationFormField") {
662
+ var assocConfig = field.associationForm || {};
663
+
664
+ props.__gridSpan = 1;
665
+ props.tips = i18n("", "");
666
+ props.placeholder = PLACEHOLDER_SELECT;
667
+ props.notFoundContent = i18n("无数据", "Not Found");
668
+ props.hasClear = true;
669
+ props.multiple = field.multiple || false;
670
+ props.dataEntryMode = false;
671
+ props.submittable = "ALWAYS";
672
+ props.isCustomStore = true;
673
+ props.isShowSearchBar = true;
674
+ props.validateFilter = false;
675
+ props.__useMediator = "value";
676
+
677
+ // 关联表单核心配置
678
+ props.associationForm = {
679
+ formType: "receipt",
680
+ formUuid: assocConfig.formUuid || "",
681
+ appType: assocConfig.appType || "",
682
+ appName: assocConfig.appName || "",
683
+ formTitle: assocConfig.formTitle || "",
684
+ mainFieldId: assocConfig.mainFieldId || "",
685
+ mainFieldLabel: assocConfig.mainFieldLabel
686
+ ? i18n(assocConfig.mainFieldLabel)
687
+ : i18n("", ""),
688
+ mainComponentName: assocConfig.mainComponentName || "TextField",
689
+ tableShowType: assocConfig.tableShowType || "all",
690
+ customTableFields: assocConfig.customTableFields || [],
691
+ subFieldId: assocConfig.subFieldId || "",
692
+ subComponentName: assocConfig.subComponentName || "",
693
+ linkageFields: assocConfig.linkageFields || [],
694
+ };
695
+
696
+ // 数据过滤规则(条件筛选)
697
+ var hasFilterRules = assocConfig.dataFilterRules &&
698
+ assocConfig.dataFilterRules.rules &&
699
+ assocConfig.dataFilterRules.rules.length > 0;
700
+ props.dataFilterRules = hasFilterRules ? assocConfig.dataFilterRules : {
701
+ condition: "AND",
702
+ rules: [],
703
+ ruleId: "group-" + Date.now().toString(36),
704
+ instanceFieldId: "",
705
+ version: "v2",
706
+ };
707
+ props.supportDataFilter = hasFilterRules;
708
+
709
+ // 数据回填规则(选中后自动填充本表单字段)
710
+ // 规范化每条规则,补充 source/target/sourceType/targetType 字段(宜搭回填必须)
711
+ var hasFillingRules = assocConfig.dataFillingRules &&
712
+ ((assocConfig.dataFillingRules.mainRules && assocConfig.dataFillingRules.mainRules.length > 0) ||
713
+ (assocConfig.dataFillingRules.tableRules && assocConfig.dataFillingRules.tableRules.length > 0));
714
+ props.dataFillingRules = hasFillingRules ? normalizeFillingRules(assocConfig.dataFillingRules) : {
715
+ mainRules: [],
716
+ tableRules: [],
717
+ version: "v2",
718
+ };
719
+ props.supportDataFilling = hasFillingRules;
720
+
721
+ // 排序配置
722
+ props.orderEnable = !!(assocConfig.orderConfig && assocConfig.orderConfig.length > 0);
723
+ props.orderConfig = assocConfig.orderConfig || [];
724
+ }
725
+
726
+ // 流水号字段
727
+ if (componentName === "SerialNumberField") {
728
+ props.__gridSpan = 1;
729
+ props.tips = i18n("", "");
730
+ props.dataEntryMode = false;
731
+ props.submittable = "DEFAULT";
732
+ // 流水号字段固定为空校验规则,不支持 required
733
+ props.validation = [];
734
+
735
+ // 默认流水号规则:前缀 + 自动递增数字
736
+ var defaultSerialNumberRule = [
737
+ {
738
+ __hide_delete__: false,
739
+ ruleType: "character",
740
+ content: "serial",
741
+ formField: "",
742
+ dateFormat: "yyyyMMdd",
743
+ timeZone: "+8",
744
+ digitCount: 4,
745
+ isFixed: true,
746
+ isFixedTips: "",
747
+ resetPeriod: "noClean",
748
+ resetPeriodTips: "",
749
+ initialValue: 1,
750
+ __sid: "item_" + Date.now().toString(36) + "1",
751
+ __sid__: "serial_" + Date.now().toString(36) + "1"
752
+ },
753
+ {
754
+ __hide_delete__: true,
755
+ ruleType: "autoCount",
756
+ content: "",
757
+ formField: "",
758
+ dateFormat: "yyyyMMdd",
759
+ timeZone: "+8",
760
+ digitCount: 5,
761
+ isFixed: true,
762
+ isFixedTips: "",
763
+ resetPeriod: "noClean",
764
+ resetPeriodTips: "",
765
+ initialValue: 1,
766
+ __sid: "item_" + Date.now().toString(36) + "2",
767
+ __sid__: "serial_" + Date.now().toString(36) + "2"
768
+ }
769
+ ];
770
+
771
+ props.serialNumberRule = field.serialNumberRule || defaultSerialNumberRule;
772
+ props.serialNumPreview = "serial00001";
773
+ props.serialNumReset = 1;
774
+ props.syncSerialConfig = false;
775
+
776
+ // formula 字段需要在 buildFormSchema 中设置,因为需要 corpId 和 formUuid
777
+ // 这里先设置为空对象,后续会被替换
778
+ props.formula = {};
779
+ }
780
+
781
+ // ── 通用属性覆盖(字段定义中显式传入的属性优先级最高)──────────
782
+
783
+ // behavior:NORMAL / READONLY / HIDDEN
784
+ if (field.behavior !== undefined) {
785
+ props.behavior = field.behavior;
786
+ }
787
+
788
+ // visibility:控制在哪些端显示,如 ["PC", "MOBILE"] / ["PC"] / ["MOBILE"]
789
+ if (field.visibility !== undefined) {
790
+ props.visibility = field.visibility;
791
+ }
792
+
793
+ // labelAlign:标签对齐方式,top / left / right
794
+ if (field.labelAlign !== undefined) {
795
+ props.labelAlign = field.labelAlign;
796
+ }
797
+
798
+ // placeholder:占位提示文本(部分字段类型已在上方按类型设置,这里统一覆盖)
799
+ if (field.placeholder !== undefined) {
800
+ props.placeholder = i18n(field.placeholder);
801
+ }
802
+
803
+ const component = {
804
+ componentName: componentName,
805
+ id: nodeId,
806
+ fieldId: fieldId,
807
+ props: props,
808
+ condition: true,
809
+ hidden: false,
810
+ title: "",
811
+ isLocked: false,
812
+ conditionGroup: "",
813
+ };
814
+
815
+ // TableField:递归处理子字段
816
+ if (componentName === "TableField" && field.children) {
817
+ component.children = field.children.map(function (childField) {
818
+ return buildFieldComponent(childField);
819
+ });
820
+ }
821
+
822
+ return component;
823
+ }
824
+
825
+ // ── 收集使用到的组件名称 ─────────────────────────────
826
+
827
+ function collectComponentNames(fields) {
828
+ const names = new Set(["Page", "RootHeader", "RootContent", "RootFooter", "FooterYida", "FormContainer"]);
829
+ fields.forEach(function (field) {
830
+ names.add(field.type);
831
+ if (field.type === "TableField" && field.children) {
832
+ field.children.forEach(function (child) {
833
+ names.add(child.type);
834
+ });
835
+ }
836
+ });
837
+ return Array.from(names);
838
+ }
839
+
840
+ // ── 生成 componentsMap ───────────────────────────────
841
+
842
+ function buildComponentsMap(componentNames) {
843
+ return componentNames.map(function (name) {
844
+ return {
845
+ package: "@ali/vc-deep-yida",
846
+ version: "1.5.169",
847
+ componentName: name,
848
+ };
849
+ });
850
+ }
851
+
852
+ // ── 从 fieldId 前缀推断组件类型 ─────────────────────
853
+ // 例如:serialNumberField_xxx → SerialNumberField,textField_xxx → TextField
854
+
855
+ function inferComponentNameFromFieldId(fieldId) {
856
+ if (!fieldId || typeof fieldId !== "string") return "";
857
+ // fieldId 格式:camelCaseComponentName_xxxxxxxx
858
+ var underscoreIndex = fieldId.lastIndexOf("_");
859
+ if (underscoreIndex === -1) return "";
860
+ var prefix = fieldId.slice(0, underscoreIndex);
861
+ // 将首字母大写,还原为 PascalCase 组件名
862
+ return prefix.charAt(0).toUpperCase() + prefix.slice(1);
863
+ }
864
+
865
+ // ── 规范化单条回填规则,补充 source/target/sourceType/targetType ──
866
+ // 宜搭要求 mainRules 和 tableRules 中的每条规则同时包含:
867
+ // sourceFieldId、targetFieldId(旧格式)
868
+ // source(同 sourceFieldId)、target(同 targetFieldId)
869
+ // sourceType(源字段组件类型)、targetType(目标字段组件类型)
870
+
871
+ function normalizeFillingRule(rule) {
872
+ // 兼容两种格式:旧格式用 sourceFieldId/targetFieldId,新格式用 source/target
873
+ var sourceId = rule.sourceFieldId || rule.source || "";
874
+ var targetId = rule.targetFieldId || rule.target || "";
875
+ var sourceType = rule.sourceType || inferComponentNameFromFieldId(sourceId);
876
+ var targetType = rule.targetType || inferComponentNameFromFieldId(targetId);
877
+
878
+ return {
879
+ sourceFieldId: sourceId,
880
+ targetFieldId: targetId,
881
+ source: sourceId,
882
+ sourceType: sourceType,
883
+ target: targetId,
884
+ targetType: targetType,
885
+ };
886
+ }
887
+
888
+ // ── 规范化整个 dataFillingRules 对象 ─────────────────
889
+
890
+ function normalizeFillingRules(fillingRules) {
891
+ if (!fillingRules) return fillingRules;
892
+ var normalized = Object.assign({}, fillingRules);
893
+
894
+ if (Array.isArray(normalized.mainRules)) {
895
+ normalized.mainRules = normalized.mainRules.map(normalizeFillingRule);
896
+ }
897
+
898
+ if (Array.isArray(normalized.tableRules)) {
899
+ normalized.tableRules = normalized.tableRules.map(function (tableRule) {
900
+ var normalizedTableRule = Object.assign({}, tableRule);
901
+ if (Array.isArray(normalizedTableRule.rules)) {
902
+ normalizedTableRule.rules = normalizedTableRule.rules.map(normalizeFillingRule);
903
+ }
904
+ return normalizedTableRule;
905
+ });
906
+ }
907
+
908
+ return normalized;
909
+ }
910
+
911
+ // ── 解析 @label:字段名 语法,将其替换为对应字段的真实 fieldId ──
912
+
913
+ function resolveFieldIdReferences(fieldComponents) {
914
+ // 构建 label → fieldId 的映射表
915
+ var labelToFieldId = {};
916
+ fieldComponents.forEach(function (component) {
917
+ var labelText = extractLabelText(component);
918
+ if (labelText && component.props && component.props.fieldId) {
919
+ labelToFieldId[labelText] = component.props.fieldId;
920
+ }
921
+ });
922
+
923
+ // 遍历所有 AssociationFormField,解析回填规则中的 @label:xxx 引用
924
+ fieldComponents.forEach(function (component) {
925
+ if (component.componentName !== "AssociationFormField") return;
926
+ var fillingRules = component.props.dataFillingRules;
927
+ if (!fillingRules) return;
928
+
929
+ /**
930
+ * 解析普通规则(mainRules 或 tableRules 中的 rules 数组)
931
+ * 支持格式: [{source, target, sourceType, targetType}, ...]
932
+ */
933
+ function resolveRules(rules) {
934
+ if (!Array.isArray(rules)) return;
935
+ rules.forEach(function (rule) {
936
+ // 解析 target 中的 @label:xxx 引用
937
+ if (rule.target && typeof rule.target === "string" && rule.target.startsWith("@label:")) {
938
+ var targetLabel = rule.target.slice(7);
939
+ var resolvedId = labelToFieldId[targetLabel];
940
+ if (resolvedId) {
941
+ console.error(" 🔗 回填规则解析: @label:" + targetLabel + " → " + resolvedId);
942
+ rule.target = resolvedId;
943
+ } else {
944
+ console.error(" ⚠️ 回填规则解析失败: 找不到标签为「" + targetLabel + "」的字段,请检查字段名是否正确");
945
+ }
946
+ }
947
+ // 解析 source 中的 @label:xxx 引用
948
+ if (rule.source && typeof rule.source === "string" && rule.source.startsWith("@label:")) {
949
+ var sourceLabel = rule.source.slice(7);
950
+ var resolvedSourceId = labelToFieldId[sourceLabel];
951
+ if (resolvedSourceId) {
952
+ console.error(" 🔗 回填规则解析: @label:" + sourceLabel + " → " + resolvedSourceId);
953
+ rule.source = resolvedSourceId;
954
+ } else {
955
+ console.error(" ⚠️ 回填规则解析失败: 找不到标签为「" + sourceLabel + "」的字段,请检查字段名是否正确");
956
+ }
957
+ }
958
+ });
959
+ }
960
+
961
+ /**
962
+ * 解析子表填充子表规则(tableRules)
963
+ * 支持格式: [{tableId, rules: [{source, target, sourceType, targetType}], filters}, ...]
964
+ */
965
+ function resolveTableRules(tableRules) {
966
+ if (!Array.isArray(tableRules)) return;
967
+ tableRules.forEach(function (tableRule, tableIndex) {
968
+ if (!tableRule.rules || !Array.isArray(tableRule.rules)) return;
969
+
970
+ console.error(" 📋 处理子表回填规则 [" + (tableIndex + 1) + "]: tableId=" + tableRule.tableId);
971
+
972
+ tableRule.rules.forEach(function (rule, ruleIndex) {
973
+ if (rule.target && typeof rule.target === "string" && rule.target.startsWith("@label:")) {
974
+ var targetLabel = rule.target.slice(7);
975
+ var resolvedId = labelToFieldId[targetLabel];
976
+ if (resolvedId) {
977
+ console.error(" 🔗 子表规则解析 [" + (ruleIndex + 1) + "]: @label:" + targetLabel + " → " + resolvedId);
978
+ rule.target = resolvedId;
979
+ } else {
980
+ console.error(" ⚠️ 子表规则解析失败: 找不到标签为「" + targetLabel + "」的字段,请检查字段名是否正确");
981
+ }
982
+ }
983
+ });
984
+ });
985
+ }
986
+
987
+ // 解析主表回填规则
988
+ if (fillingRules.mainRules) {
989
+ resolveRules(fillingRules.mainRules);
990
+ }
991
+
992
+ // 解析子表回填规则(支持子表填充子表)
993
+ if (fillingRules.tableRules) {
994
+ resolveTableRules(fillingRules.tableRules);
995
+ }
996
+
997
+ // 解析完 @label 后,规范化规则(补充 source/target/sourceType/targetType)
998
+ component.props.dataFillingRules = normalizeFillingRules(fillingRules);
999
+
1000
+ // 解析后重新判断是否有有效回填规则
1001
+ var hasMainRules = fillingRules.mainRules && fillingRules.mainRules.length > 0;
1002
+ var hasTableRules = fillingRules.tableRules && fillingRules.tableRules.some(function (tr) {
1003
+ return tr.rules && tr.rules.length > 0;
1004
+ });
1005
+ component.props.supportDataFilling = hasMainRules || hasTableRules;
1006
+ });
1007
+ }
1008
+
1009
+ // ── 生成表单 Schema ──────────────────────────────────
1010
+
1011
+ function buildFormSchema(formTitle, fields, formUuid, corpId, appType, columns) {
1012
+ columns = columns || 1;
1013
+ const fieldComponents = fields.map(function (field) {
1014
+ return buildFieldComponent(field);
1015
+ });
1016
+
1017
+ // 为 SerialNumberField 设置 formula(需要 corpId、appType 和 formUuid)
1018
+ fieldComponents.forEach(function (component) {
1019
+ if (component.componentName === "SerialNumberField" && component.props) {
1020
+ var fieldId = component.props.fieldId;
1021
+ var serialNumberRule = component.props.serialNumberRule;
1022
+
1023
+ // 直接使用 serialNumberRule 构建 formula.expression
1024
+ // value 的值就是 serialNumberRule 数组本身
1025
+ var ruleJson = JSON.stringify({
1026
+ type: "custom",
1027
+ value: serialNumberRule
1028
+ });
1029
+
1030
+ // 转义 JSON 字符串中的引号和反斜杠,用于嵌入到 expression 字符串中
1031
+ var escapedRuleJson = ruleJson.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1032
+
1033
+ component.props.formula = {
1034
+ expression: 'SERIALNUMBER("' + corpId + '", "' + appType + '", "' + formUuid + '", "' + fieldId + '", "' + escapedRuleJson + '")'
1035
+ };
1036
+ }
1037
+ });
1038
+
1039
+ // 解析 @label:字段名 引用(必须在所有字段构建完成后执行)
1040
+ resolveFieldIdReferences(fieldComponents);
1041
+
1042
+ const componentNames = collectComponentNames(fields);
1043
+
1044
+ // 构造函数代码(与模板完全一致)
1045
+ 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}";
1046
+
1047
+ // actions 模块代码(与模板一致的默认空实现)
1048
+ 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";
1049
+ const actionsSource = "export function didMount() {\n console.log(`「页面 JS」:当前页面地址 ${location.href}`);\n}";
1050
+
1051
+ // Page 组件树(FormContainer 外层结构与模板保持一致,仅 id 随机生成)
1052
+ const pageComponentsTree = [
1053
+ {
1054
+ componentName: "Page",
1055
+ id: nextNodeId(),
1056
+ props: {
1057
+ contentBgColor: "white",
1058
+ pageStyle: { backgroundColor: "#f2f3f5" },
1059
+ contentMargin: "20",
1060
+ contentPadding: "20",
1061
+ showTitle: false,
1062
+ contentPaddingMobile: "0",
1063
+ templateVersion: "1.0.0",
1064
+ contentMarginMobile: "0",
1065
+ className: "page_" + Date.now().toString(36),
1066
+ contentBgColorMobile: "white",
1067
+ titleName: i18n("标题名称", "title"),
1068
+ titleDesc: i18n("标题描述", "title"),
1069
+ titleColor: "light",
1070
+ titleBg: "https://img.alicdn.com/imgextra/i2/O1CN0143ATPP1wIa9TrVvzN_!!6000000006285-2-tps-3360-400.png_.webp",
1071
+ backgroundColorCustom: "#f1f2f3",
1072
+ sizePc: "medium",
1073
+ labelAlignPc: "top",
1074
+ labelWidthPc: "130px",
1075
+ labelWeightPc: "normal",
1076
+ labelAlignMobile: "top",
1077
+ labelWidthMobile: "80px",
1078
+ labelWeightMobile: "normal",
1079
+ },
1080
+ condition: true,
1081
+ css: "body{background-color:#f2f3f5}",
1082
+ methods: {
1083
+ __initMethods__: {
1084
+ type: "js",
1085
+ source: "function (exports, module) { /*set actions code here*/ }",
1086
+ compiled: "function (exports, module) { /*set actions code here*/ }",
1087
+ },
1088
+ },
1089
+ dataSource: {
1090
+ offline: [],
1091
+ globalConfig: {
1092
+ fit: {
1093
+ 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};",
1094
+ 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}",
1095
+ type: "js",
1096
+ error: {},
1097
+ },
1098
+ },
1099
+ online: [],
1100
+ list: [],
1101
+ sync: true,
1102
+ },
1103
+ lifeCycles: {
1104
+ constructor: {
1105
+ type: "js",
1106
+ compiled: constructorCode,
1107
+ source: constructorCode,
1108
+ },
1109
+ },
1110
+ hidden: false,
1111
+ title: "",
1112
+ isLocked: false,
1113
+ conditionGroup: "",
1114
+ children: [
1115
+ {
1116
+ componentName: "RootHeader",
1117
+ id: nextNodeId(),
1118
+ props: {},
1119
+ condition: true,
1120
+ hidden: false,
1121
+ title: "",
1122
+ isLocked: false,
1123
+ conditionGroup: "",
1124
+ },
1125
+ {
1126
+ componentName: "RootContent",
1127
+ id: nextNodeId(),
1128
+ props: {},
1129
+ condition: true,
1130
+ hidden: false,
1131
+ title: "",
1132
+ isLocked: false,
1133
+ conditionGroup: "",
1134
+ children: [
1135
+ {
1136
+ componentName: "FormContainer",
1137
+ id: nextNodeId(),
1138
+ props: {
1139
+ formLabel: i18n(formTitle, formTitle),
1140
+ formLabelVisible: true,
1141
+ columns: columns,
1142
+ labelAlign: "top",
1143
+ submitText: i18n("提交", "Submit"),
1144
+ stageText: i18n("暂存", "Stage"),
1145
+ submitAndNewText: i18n("提交并继续", "Submit and New"),
1146
+ fieldId: "formContainer_" + Date.now().toString(36),
1147
+ aiFormConfig: { systemPrompt: "", model: "qwen" },
1148
+ beforeSubmit: false,
1149
+ afterSubmit: false,
1150
+ onProcessActionValidate: false,
1151
+ afterFormDataInit: false,
1152
+ },
1153
+ condition: true,
1154
+ hidden: false,
1155
+ title: "",
1156
+ isLocked: false,
1157
+ conditionGroup: "",
1158
+ // ★ 核心:FormContainer 内层的字段组件
1159
+ children: fieldComponents,
1160
+ },
1161
+ ],
1162
+ },
1163
+ {
1164
+ componentName: "RootFooter",
1165
+ id: nextNodeId(),
1166
+ props: {},
1167
+ condition: true,
1168
+ hidden: false,
1169
+ title: "",
1170
+ isLocked: false,
1171
+ conditionGroup: "",
1172
+ children: [
1173
+ {
1174
+ componentName: "FooterYida",
1175
+ id: nextNodeId(),
1176
+ props: {},
1177
+ condition: true,
1178
+ hidden: false,
1179
+ title: "",
1180
+ isLocked: false,
1181
+ conditionGroup: "",
1182
+ },
1183
+ ],
1184
+ },
1185
+ ],
1186
+ },
1187
+ ];
1188
+
1189
+ // 页面 Schema(与模板结构一致)- utils 放在 pages[0] 内
1190
+ const pageSchema = {
1191
+ utils: [
1192
+ {
1193
+ name: "legaoBuiltin",
1194
+ type: "npm",
1195
+ content: {
1196
+ package: "@ali/vu-legao-builtin",
1197
+ version: "3.0.0",
1198
+ exportName: "legaoBuiltin",
1199
+ },
1200
+ },
1201
+ {
1202
+ name: "yidaPlugin",
1203
+ type: "npm",
1204
+ content: {
1205
+ package: "@ali/vu-yida-plugin",
1206
+ version: "1.1.0",
1207
+ exportName: "yidaPlugin",
1208
+ },
1209
+ },
1210
+ ],
1211
+ componentsMap: buildComponentsMap(componentNames),
1212
+ componentsTree: pageComponentsTree,
1213
+ componentAlias: {
1214
+ items: [],
1215
+ },
1216
+ id: formUuid,
1217
+ connectComponent: [],
1218
+ };
1219
+
1220
+ // 顶层 Schema(与模板结构完全一致)- actions 和 config 与 pages 平级
1221
+ return {
1222
+ schemaType: "superform",
1223
+ schemaVersion: "5.0",
1224
+ pages: [pageSchema],
1225
+ actions: {
1226
+ module: {
1227
+ compiled: actionsCompiled,
1228
+ source: actionsSource,
1229
+ },
1230
+ type: "FUNCTION",
1231
+ list: [
1232
+ {
1233
+ id: "didMount",
1234
+ title: "didMount",
1235
+ },
1236
+ ],
1237
+ },
1238
+ config: {
1239
+ connectComponent: [],
1240
+ },
1241
+ };
1242
+ }
1243
+
1244
+ // ── 发送 GET 请求(支持 302 自动重登录) ─────────────
1245
+
1246
+ function sendGetRequest(baseUrl, cookies, requestPath, queryParams) {
1247
+ return new Promise((resolve, reject) => {
1248
+ const queryString = querystring.stringify(queryParams);
1249
+ const fullPath = requestPath + "?" + queryString;
1250
+
1251
+ const cookieHeader = cookies
1252
+ .map((cookie) => cookie.name + "=" + cookie.value)
1253
+ .join("; ");
1254
+
1255
+ const parsedUrl = new URL(baseUrl);
1256
+ const isHttps = parsedUrl.protocol === "https:";
1257
+ const requestModule = isHttps ? https : http;
1258
+
1259
+ const requestOptions = {
1260
+ hostname: parsedUrl.hostname,
1261
+ port: parsedUrl.port || (isHttps ? 443 : 80),
1262
+ path: fullPath,
1263
+ method: "GET",
1264
+ headers: {
1265
+ Origin: baseUrl,
1266
+ Referer: baseUrl + "/",
1267
+ Cookie: cookieHeader,
1268
+ },
1269
+ timeout: 30000,
1270
+ };
1271
+
1272
+ const request = requestModule.request(requestOptions, (response) => {
1273
+ let responseData = "";
1274
+ response.on("data", (chunk) => { responseData += chunk; });
1275
+ response.on("end", () => {
1276
+ console.error(" HTTP 状态码: " + response.statusCode);
1277
+ let parsed;
1278
+ try {
1279
+ parsed = JSON.parse(responseData);
1280
+ } catch (parseError) {
1281
+ console.error(" 响应内容: " + responseData.substring(0, 500));
1282
+ resolve({ success: false, errorMsg: "HTTP " + response.statusCode + ": 响应非 JSON" });
1283
+ return;
1284
+ }
1285
+ // 检测登录过期(errorCode: "307")
1286
+ if (isLoginExpired(parsed)) {
1287
+ console.error(" 检测到登录过期: " + parsed.errorMsg);
1288
+ resolve({ __needLogin: true });
1289
+ return;
1290
+ }
1291
+ // 检测 csrf_token 过期(errorCode: "TIANSHU_000030")
1292
+ if (isCsrfTokenExpired(parsed)) {
1293
+ console.error(" 检测到 csrf_token 过期: " + parsed.errorMsg);
1294
+ resolve({ __csrfExpired: true });
1295
+ return;
1296
+ }
1297
+ resolve(parsed);
1298
+ });
1299
+ });
1300
+
1301
+ request.on("timeout", () => {
1302
+ console.error(" ❌ 请求超时");
1303
+ request.destroy();
1304
+ reject(new Error("请求超时"));
1305
+ });
1306
+
1307
+ request.on("error", (requestError) => {
1308
+ reject(requestError);
1309
+ });
1310
+
1311
+ request.end();
1312
+ });
1313
+ }
1314
+
1315
+ // ── 空白表单 Schema 模板(update 模式) ─────────────
1316
+
1317
+ function buildEmptyFormSchema() {
1318
+ var 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}";
1319
+ var 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";
1320
+ var actionsSource = "export function didMount() {\n console.log(`「页面 JS」:当前页面地址 ${location.href}`);\n}";
1321
+
1322
+ return {
1323
+ schemaType: "superform",
1324
+ schemaVersion: "5.0",
1325
+ actions: {
1326
+ module: { compiled: actionsCompiled, source: actionsSource },
1327
+ type: "FUNCTION",
1328
+ list: [{ id: nextNodeId(), type: "lifeCycleEvent", name: "didMount", relatedEventId: "lifecycle:didMount", params: {} }],
1329
+ },
1330
+ pages: [{
1331
+ utils: [
1332
+ { name: "legaoBuiltin", type: "npm", content: { package: "@ali/vu-legao-builtin", version: "3.0.0", exportName: "legaoBuiltin" } },
1333
+ { name: "yidaPlugin", type: "npm", content: { package: "@ali/vu-yida-plugin", version: "1.1.0", exportName: "yidaPlugin" } },
1334
+ ],
1335
+ componentsTree: [
1336
+ {
1337
+ componentName: "Page",
1338
+ id: nextNodeId(),
1339
+ props: {
1340
+ contentBgColor: "white",
1341
+ pageStyle: { backgroundColor: "#f2f3f5" },
1342
+ contentMargin: "20",
1343
+ contentPadding: "20",
1344
+ showTitle: false,
1345
+ contentPaddingMobile: "0",
1346
+ templateVersion: "1.0.0",
1347
+ contentMarginMobile: "0",
1348
+ className: "page_" + Date.now().toString(36),
1349
+ contentBgColorMobile: "white",
1350
+ },
1351
+ condition: true,
1352
+ css: "body{background-color:#f2f3f5}",
1353
+ methods: {
1354
+ __initMethods__: {
1355
+ type: "js",
1356
+ source: "function (exports, module) { /*set actions code here*/ }",
1357
+ compiled: "function (exports, module) { /*set actions code here*/ }",
1358
+ },
1359
+ },
1360
+ dataSource: { offline: [], globalConfig: {}, online: [], list: [], sync: true },
1361
+ lifeCycles: {
1362
+ constructor: { type: "js", compiled: constructorCode, source: constructorCode },
1363
+ componentDidMount: { name: "didMount", id: "didMount", params: {}, type: "actionRef" },
1364
+ },
1365
+ hidden: false,
1366
+ title: "",
1367
+ isLocked: false,
1368
+ conditionGroup: "",
1369
+ children: [
1370
+ { componentName: "RootHeader", id: nextNodeId(), props: {}, condition: true, hidden: false, title: "", isLocked: false, conditionGroup: "" },
1371
+ {
1372
+ componentName: "RootContent",
1373
+ id: nextNodeId(),
1374
+ props: {},
1375
+ condition: true,
1376
+ hidden: false,
1377
+ title: "",
1378
+ isLocked: false,
1379
+ conditionGroup: "",
1380
+ children: [
1381
+ {
1382
+ componentName: "FormContainer",
1383
+ id: nextNodeId(),
1384
+ props: {
1385
+ beforeSubmit: false,
1386
+ "submitProps.text": i18n("提交", "Submit"),
1387
+ submitText: i18n("提交", "Submit"),
1388
+ submitProps: { text: i18n("提交", "Submit") },
1389
+ labelAlign: "top",
1390
+ columns: 1,
1391
+ afterSubmit: false,
1392
+ fieldId: "formContainer_" + Date.now().toString(36),
1393
+ stageText: i18n("暂存", "Stage"),
1394
+ submitAndNewText: i18n("提交并继续", "Submit and New"),
1395
+ onProcessActionValidate: false,
1396
+ afterFormDataInit: false,
1397
+ },
1398
+ condition: true,
1399
+ hidden: false,
1400
+ title: "",
1401
+ isLocked: false,
1402
+ conditionGroup: "",
1403
+ children: [],
1404
+ },
1405
+ ],
1406
+ },
1407
+ {
1408
+ componentName: "RootFooter",
1409
+ id: nextNodeId(),
1410
+ props: {},
1411
+ condition: true,
1412
+ hidden: false,
1413
+ title: "",
1414
+ isLocked: false,
1415
+ conditionGroup: "",
1416
+ children: [
1417
+ { componentName: "FooterYida", id: nextNodeId(), props: {}, condition: true, hidden: false, title: "", isLocked: false, conditionGroup: "" },
1418
+ ],
1419
+ },
1420
+ ],
1421
+ },
1422
+ ],
1423
+ componentsMap: [
1424
+ { package: "@ali/vc-deep-yida", version: "1.5.169", componentName: "RootHeader" },
1425
+ { package: "@ali/vc-deep-yida", version: "1.5.169", componentName: "FormContainer" },
1426
+ { package: "@ali/vc-deep-yida", version: "1.5.169", componentName: "RootContent" },
1427
+ { package: "@ali/vc-deep-yida", version: "1.5.169", componentName: "FooterYida" },
1428
+ { package: "@ali/vc-deep-yida", version: "1.5.169", componentName: "RootFooter" },
1429
+ { package: "@ali/vc-deep-yida", version: "1.5.169", componentName: "Page" },
1430
+ ],
1431
+ },
1432
+ ],
1433
+ };
1434
+ }
1435
+
1436
+ // ── Schema 字段操作辅助函数(update 模式) ──────────
1437
+
1438
+ function extractLabelText(component) {
1439
+ if (!component || !component.props || !component.props.label) {
1440
+ return "";
1441
+ }
1442
+ var label = component.props.label;
1443
+ if (typeof label === "string") {
1444
+ return label;
1445
+ }
1446
+ if (label.zh_CN) {
1447
+ return label.zh_CN;
1448
+ }
1449
+ return "";
1450
+ }
1451
+
1452
+ function findFormContainer(node) {
1453
+ if (node.componentName === "FormContainer") {
1454
+ return node;
1455
+ }
1456
+ if (node.children && Array.isArray(node.children)) {
1457
+ for (var childIndex = 0; childIndex < node.children.length; childIndex++) {
1458
+ var found = findFormContainer(node.children[childIndex]);
1459
+ if (found) return found;
1460
+ }
1461
+ }
1462
+ return null;
1463
+ }
1464
+
1465
+ function findFieldIndexByLabel(fields, label) {
1466
+ for (var fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
1467
+ if (extractLabelText(fields[fieldIndex]) === label) {
1468
+ return fieldIndex;
1469
+ }
1470
+ }
1471
+ return -1;
1472
+ }
1473
+
1474
+ function applyFieldChanges(component, changes) {
1475
+ var props = component.props;
1476
+
1477
+ // 需要特殊处理的属性 key 集合(不走通用透传)
1478
+ var specialKeys = ["label", "required", "placeholder", "options", "associationForm",
1479
+ "linkageFields", "mainFieldId", "mainComponentName", "mainFieldLabel",
1480
+ "subFieldId", "subComponentName", "dataFillingRules"];
1481
+
1482
+ // ── 特殊处理:label(需要 i18n 包装)
1483
+ if (changes.label !== undefined) {
1484
+ props.label = i18n(changes.label, component.componentName);
1485
+ }
1486
+
1487
+ // ── 特殊处理:required(操作 validation 数组)
1488
+ if (changes.required !== undefined) {
1489
+ if (changes.required) {
1490
+ var hasRequired = (props.validation || []).some(function (rule) {
1491
+ return rule.type === "required";
1492
+ });
1493
+ if (!hasRequired) {
1494
+ props.validation = props.validation || [];
1495
+ props.validation.push({ type: "required" });
1496
+ }
1497
+ } else {
1498
+ props.validation = (props.validation || []).filter(function (rule) {
1499
+ return rule.type !== "required";
1500
+ });
1501
+ }
1502
+ }
1503
+
1504
+ // ── 特殊处理:placeholder(需要 i18n 包装)
1505
+ if (changes.placeholder !== undefined) {
1506
+ props.placeholder = i18n(changes.placeholder);
1507
+ }
1508
+
1509
+ // ── 特殊处理:dataSource(选项类字段直接更新 dataSource)
1510
+ if (changes.dataSource !== undefined && OPTION_FIELD_TYPES.indexOf(component.componentName) !== -1) {
1511
+ var newDataSource = changes.dataSource;
1512
+ props.dataSource = newDataSource;
1513
+ if (props.defaultDataSource) {
1514
+ props.defaultDataSource.options = newDataSource;
1515
+ }
1516
+ }
1517
+
1518
+ // ── 特殊处理:AssociationFormField 的 associationForm 内部属性
1519
+ if (component.componentName === "AssociationFormField") {
1520
+ if (!props.associationForm) {
1521
+ props.associationForm = {};
1522
+ }
1523
+ if (changes.linkageFields !== undefined) {
1524
+ props.associationForm.linkageFields = changes.linkageFields;
1525
+ }
1526
+ if (changes.mainFieldId !== undefined) {
1527
+ props.associationForm.mainFieldId = changes.mainFieldId;
1528
+ }
1529
+ if (changes.mainComponentName !== undefined) {
1530
+ props.associationForm.mainComponentName = changes.mainComponentName;
1531
+ }
1532
+ if (changes.mainFieldLabel !== undefined) {
1533
+ props.associationForm.mainFieldLabel = i18n(changes.mainFieldLabel);
1534
+ }
1535
+ if (changes.subFieldId !== undefined) {
1536
+ props.associationForm.subFieldId = changes.subFieldId;
1537
+ }
1538
+ if (changes.subComponentName !== undefined) {
1539
+ props.associationForm.subComponentName = changes.subComponentName;
1540
+ }
1541
+ // dataFillingRules:直接替换整个回填规则对象,并同步更新 supportDataFilling
1542
+ // 同时规范化每条规则,补充 source/target/sourceType/targetType 字段
1543
+ if (changes.dataFillingRules !== undefined) {
1544
+ props.dataFillingRules = normalizeFillingRules(changes.dataFillingRules);
1545
+ var hasMainRules = changes.dataFillingRules.mainRules && changes.dataFillingRules.mainRules.length > 0;
1546
+ var hasTableRules = changes.dataFillingRules.tableRules && changes.dataFillingRules.tableRules.some(function (tr) {
1547
+ return tr.rules && tr.rules.length > 0;
1548
+ });
1549
+ props.supportDataFilling = hasMainRules || hasTableRules;
1550
+ }
1551
+ }
1552
+
1553
+ // ── 通用透传:将 changes 中所有未被特殊处理的属性直接写入 props
1554
+ // 新增属性支持时无需修改此函数,直接在 changes 中传入对应 key 即可
1555
+ Object.keys(changes).forEach(function (key) {
1556
+ if (specialKeys.indexOf(key) === -1 && changes[key] !== undefined) {
1557
+ props[key] = changes[key];
1558
+ }
1559
+ });
1560
+ }
1561
+
1562
+ function ensureComponentsMap(schema, componentName) {
1563
+ var pageSchema = schema.pages[0];
1564
+ var existingNames = pageSchema.componentsMap.map(function (entry) {
1565
+ return entry.componentName;
1566
+ });
1567
+ if (existingNames.indexOf(componentName) === -1) {
1568
+ pageSchema.componentsMap.push({
1569
+ package: "@ali/vc-deep-yida",
1570
+ version: "1.5.169",
1571
+ componentName: componentName,
1572
+ });
1573
+ }
1574
+ }
1575
+
1576
+ // ── 应用修改操作(update 模式) ─────────────────────
1577
+
1578
+ function applyChangesToSchema(schema, changes) {
1579
+ var componentsTree = schema.pages[0].componentsTree;
1580
+ if (!componentsTree || componentsTree.length === 0) {
1581
+ console.error(" ❌ Schema 中未找到 componentsTree");
1582
+ process.exit(1);
1583
+ }
1584
+
1585
+ var formContainer = findFormContainer(componentsTree[0]);
1586
+ if (!formContainer) {
1587
+ console.error(" ❌ Schema 中未找到 FormContainer");
1588
+ process.exit(1);
1589
+ }
1590
+
1591
+ var formFields = formContainer.children || [];
1592
+ var appliedChanges = [];
1593
+
1594
+ changes.forEach(function (change, changeIndex) {
1595
+ var actionDesc = "操作 " + (changeIndex + 1) + ": " + change.action;
1596
+
1597
+ if (change.action === "add") {
1598
+ if (!change.field || !change.field.type || !change.field.label) {
1599
+ console.error(" ⚠️ " + actionDesc + " - 缺少 field.type 或 field.label,跳过");
1600
+ return;
1601
+ }
1602
+
1603
+ var newComponent = buildFieldComponent(change.field);
1604
+ ensureComponentsMap(schema, change.field.type);
1605
+
1606
+ if (change.field.type === "TableField" && change.field.children) {
1607
+ change.field.children.forEach(function (childField) {
1608
+ ensureComponentsMap(schema, childField.type);
1609
+ });
1610
+ }
1611
+
1612
+ if (change.after) {
1613
+ var afterIndex = findFieldIndexByLabel(formFields, change.after);
1614
+ if (afterIndex !== -1) {
1615
+ formFields.splice(afterIndex + 1, 0, newComponent);
1616
+ console.error(" ✅ " + actionDesc + " - 在「" + change.after + "」后新增字段「" + change.field.label + "」(" + change.field.type + ")");
1617
+ } else {
1618
+ formFields.push(newComponent);
1619
+ console.error(" ⚠️ " + actionDesc + " - 未找到「" + change.after + "」,字段「" + change.field.label + "」追加到末尾");
1620
+ }
1621
+ } else if (change.before) {
1622
+ var beforeIndex = findFieldIndexByLabel(formFields, change.before);
1623
+ if (beforeIndex !== -1) {
1624
+ formFields.splice(beforeIndex, 0, newComponent);
1625
+ console.error(" ✅ " + actionDesc + " - 在「" + change.before + "」前新增字段「" + change.field.label + "」(" + change.field.type + ")");
1626
+ } else {
1627
+ formFields.push(newComponent);
1628
+ console.error(" ⚠️ " + actionDesc + " - 未找到「" + change.before + "」,字段「" + change.field.label + "」追加到末尾");
1629
+ }
1630
+ } else {
1631
+ formFields.push(newComponent);
1632
+ console.error(" ✅ " + actionDesc + " - 新增字段「" + change.field.label + "」(" + change.field.type + ")");
1633
+ }
1634
+
1635
+ appliedChanges.push({ action: "add", label: change.field.label, type: change.field.type });
1636
+
1637
+ } else if (change.action === "delete") {
1638
+ if (!change.label) {
1639
+ console.error(" ⚠️ " + actionDesc + " - 缺少 label,跳过");
1640
+ return;
1641
+ }
1642
+
1643
+ var deleteIndex = findFieldIndexByLabel(formFields, change.label);
1644
+ if (deleteIndex !== -1) {
1645
+ formFields.splice(deleteIndex, 1);
1646
+ console.error(" ✅ " + actionDesc + " - 删除字段「" + change.label + "」");
1647
+ appliedChanges.push({ action: "delete", label: change.label });
1648
+ } else {
1649
+ console.error(" ⚠️ " + actionDesc + " - 未找到字段「" + change.label + "」,跳过删除");
1650
+ }
1651
+
1652
+ } else if (change.action === "update") {
1653
+ if (!change.label) {
1654
+ console.error(" ⚠️ " + actionDesc + " - 缺少 label,跳过");
1655
+ return;
1656
+ }
1657
+ if (!change.changes || Object.keys(change.changes).length === 0) {
1658
+ console.error(" ⚠️ " + actionDesc + " - 缺少 changes,跳过");
1659
+ return;
1660
+ }
1661
+
1662
+ // 支持通过 tableLabel 指定父子表,在子表 children 中查找字段
1663
+ var searchFields = formFields;
1664
+ var locationDesc = "";
1665
+ if (change.tableLabel) {
1666
+ var tableIndex = findFieldIndexByLabel(formFields, change.tableLabel);
1667
+ if (tableIndex === -1) {
1668
+ console.error(" ⚠️ " + actionDesc + " - 未找到子表「" + change.tableLabel + "」,跳过更新");
1669
+ return;
1670
+ }
1671
+ var tableComponent = formFields[tableIndex];
1672
+ if (tableComponent.componentName !== "TableField" || !tableComponent.children) {
1673
+ console.error(" ⚠️ " + actionDesc + " - 「" + change.tableLabel + "」不是有效的子表字段,跳过更新");
1674
+ return;
1675
+ }
1676
+ searchFields = tableComponent.children;
1677
+ locationDesc = "子表「" + change.tableLabel + "」中的";
1678
+ }
1679
+
1680
+ var updateIndex = findFieldIndexByLabel(searchFields, change.label);
1681
+ if (updateIndex !== -1) {
1682
+ applyFieldChanges(searchFields[updateIndex], change.changes);
1683
+ var changedProps = Object.keys(change.changes).join(", ");
1684
+ console.error(" ✅ " + actionDesc + " - 更新" + locationDesc + "字段「" + change.label + "」的属性: " + changedProps);
1685
+ appliedChanges.push({ action: "update", label: change.label, tableLabel: change.tableLabel || null, changedProps: changedProps });
1686
+ } else {
1687
+ console.error(" ⚠️ " + actionDesc + " - 未找到" + locationDesc + "字段「" + change.label + "」,跳过更新");
1688
+ }
1689
+
1690
+ } else {
1691
+ console.error(" ⚠️ " + actionDesc + " - 未知操作类型「" + change.action + "」,跳过");
1692
+ }
1693
+ });
1694
+
1695
+ // 遍历所有字段,确保顶层 fieldId 存在(宜搭回填引擎依赖顶层 fieldId)
1696
+ function ensureTopLevelFieldId(comps) {
1697
+ comps.forEach(function (comp) {
1698
+ if (!comp.fieldId && comp.props && comp.props.fieldId) {
1699
+ comp.fieldId = comp.props.fieldId;
1700
+ }
1701
+ if (comp.children && Array.isArray(comp.children)) {
1702
+ ensureTopLevelFieldId(comp.children);
1703
+ }
1704
+ });
1705
+ }
1706
+ ensureTopLevelFieldId(formFields);
1707
+
1708
+ // 解析 @label:xxx 引用并规范化回填规则
1709
+ resolveFieldIdReferences(formFields);
1710
+
1711
+ formContainer.children = formFields;
1712
+ return appliedChanges;
1713
+ }
1714
+
1715
+ // ── 发送 POST 请求(支持 302 自动重登录) ────────────
1716
+
1717
+ function sendPostRequest(baseUrl, csrfToken, cookies, requestPath, extraParams, formUuid) {
1718
+ return new Promise((resolve, reject) => {
1719
+ const postData = querystring.stringify(
1720
+ Object.assign({ _csrf_token: csrfToken }, extraParams)
1721
+ );
1722
+
1723
+ const cookieHeader = cookies
1724
+ .map((cookie) => `${cookie.name}=${cookie.value}`)
1725
+ .join("; ");
1726
+
1727
+ const parsedUrl = new URL(baseUrl);
1728
+ const isHttps = parsedUrl.protocol === "https:";
1729
+ const requestModule = isHttps ? https : http;
1730
+
1731
+ // 构建 Referer,如果提供了 formUuid 则使用页面设计器的地址
1732
+ const referer = formUuid
1733
+ ? `${baseUrl}/alibaba/web/${extraParams.appType || ""}/design/pageDesigner?formUuid=${formUuid}`
1734
+ : baseUrl + "/";
1735
+
1736
+ const requestOptions = {
1737
+ hostname: parsedUrl.hostname,
1738
+ port: parsedUrl.port || (isHttps ? 443 : 80),
1739
+ path: requestPath,
1740
+ method: "POST",
1741
+ headers: {
1742
+ "Content-Type": "application/x-www-form-urlencoded",
1743
+ "Content-Length": Buffer.byteLength(postData),
1744
+ Origin: baseUrl,
1745
+ Referer: referer,
1746
+ Cookie: cookieHeader,
1747
+ },
1748
+ timeout: 30000,
1749
+ };
1750
+
1751
+ const request = requestModule.request(requestOptions, (response) => {
1752
+ let responseData = "";
1753
+ response.on("data", (chunk) => { responseData += chunk; });
1754
+ response.on("end", () => {
1755
+ console.error(` HTTP 状态码: ${response.statusCode}`);
1756
+ let parsed;
1757
+ try {
1758
+ parsed = JSON.parse(responseData);
1759
+ } catch (parseError) {
1760
+ console.error(` 响应内容: ${responseData.substring(0, 500)}`);
1761
+ resolve({ success: false, errorMsg: `HTTP ${response.statusCode}: 响应非 JSON` });
1762
+ return;
1763
+ }
1764
+ // 检测登录过期(errorCode: "307")
1765
+ if (isLoginExpired(parsed)) {
1766
+ console.error(` 检测到登录过期: ${parsed.errorMsg}`);
1767
+ resolve({ __needLogin: true });
1768
+ return;
1769
+ }
1770
+ // 检测 csrf_token 过期(errorCode: "TIANSHU_000030")
1771
+ if (isCsrfTokenExpired(parsed)) {
1772
+ console.error(` 检测到 csrf_token 过期: ${parsed.errorMsg}`);
1773
+ resolve({ __csrfExpired: true });
1774
+ return;
1775
+ }
1776
+ resolve(parsed);
1777
+ });
1778
+ });
1779
+
1780
+ request.on("timeout", () => {
1781
+ console.error(" ❌ 请求超时");
1782
+ request.destroy();
1783
+ reject(new Error("请求超时"));
1784
+ });
1785
+
1786
+ request.on("error", (requestError) => {
1787
+ reject(requestError);
1788
+ });
1789
+
1790
+ request.write(postData);
1791
+ request.end();
1792
+ });
1793
+ }
1794
+
1795
+ // ── 发送 updateFormConfig 请求 ───────────────────────
1796
+
1797
+ function sendUpdateConfigRequest(baseUrl, csrfToken, cookies, appType, formUuid, version, value) {
1798
+ return new Promise((resolve, reject) => {
1799
+ const postData = querystring.stringify({
1800
+ _csrf_token: csrfToken,
1801
+ formUuid: formUuid,
1802
+ version: version,
1803
+ configType: "MINI_RESOURCE",
1804
+ value: value,
1805
+ });
1806
+
1807
+ const cookieHeader = cookies
1808
+ .map((cookie) => `${cookie.name}=${cookie.value}`)
1809
+ .join("; ");
1810
+
1811
+ const parsedUrl = new URL(baseUrl);
1812
+ const isHttps = parsedUrl.protocol === "https:";
1813
+ const requestModule = isHttps ? https : http;
1814
+
1815
+ const requestOptions = {
1816
+ hostname: parsedUrl.hostname,
1817
+ port: parsedUrl.port || (isHttps ? 443 : 80),
1818
+ path: `/dingtalk/web/${appType}/query/formdesign/updateFormConfig.json`,
1819
+ method: "POST",
1820
+ headers: {
1821
+ "Content-Type": "application/x-www-form-urlencoded",
1822
+ "Content-Length": Buffer.byteLength(postData),
1823
+ Origin: baseUrl,
1824
+ Referer: baseUrl + "/",
1825
+ Cookie: cookieHeader,
1826
+ },
1827
+ timeout: 30000,
1828
+ };
1829
+
1830
+ const request = requestModule.request(requestOptions, (response) => {
1831
+ let responseData = "";
1832
+ response.on("data", (chunk) => { responseData += chunk; });
1833
+ response.on("end", () => {
1834
+ console.error(` HTTP 状态码: ${response.statusCode}`);
1835
+ let parsed;
1836
+ try {
1837
+ parsed = JSON.parse(responseData);
1838
+ } catch (parseError) {
1839
+ console.error(` 响应内容: ${responseData.substring(0, 500)}`);
1840
+ resolve({ success: false, errorMsg: `HTTP ${response.statusCode}: 响应非 JSON` });
1841
+ return;
1842
+ }
1843
+ // 检测登录过期(errorCode: "307")
1844
+ if (isLoginExpired(parsed)) {
1845
+ console.error(` 检测到登录过期: ${parsed.errorMsg}`);
1846
+ resolve({ __needLogin: true });
1847
+ return;
1848
+ }
1849
+ // 检测 csrf_token 过期(errorCode: "TIANSHU_000030")
1850
+ if (isCsrfTokenExpired(parsed)) {
1851
+ console.error(` 检测到 csrf_token 过期: ${parsed.errorMsg}`);
1852
+ resolve({ __csrfExpired: true });
1853
+ return;
1854
+ }
1855
+ resolve(parsed);
1856
+ });
1857
+ });
1858
+
1859
+ request.on("timeout", () => {
1860
+ console.error(" ❌ 请求超时");
1861
+ request.destroy();
1862
+ reject(new Error("请求超时"));
1863
+ });
1864
+
1865
+ request.on("error", (requestError) => {
1866
+ reject(requestError);
1867
+ });
1868
+
1869
+ request.write(postData);
1870
+ request.end();
1871
+ });
1872
+ }
1873
+
1874
+ // ── 登录态辅助:从 cookieData 中提取 corpId ──────────
1875
+
1876
+ function resolveCorpId(cookieData) {
1877
+ // 优先使用已提取的 corp_id 字段
1878
+ if (cookieData.corp_id) return cookieData.corp_id;
1879
+ // 从 tianshu_corp_user Cookie 中提取(格式:"{corpId}_{userId}")
1880
+ if (cookieData.cookies) {
1881
+ var corpUserCookie = cookieData.cookies.find(function (c) {
1882
+ return c.name === "tianshu_corp_user";
1883
+ });
1884
+ if (corpUserCookie && corpUserCookie.value) {
1885
+ var lastUnderscore = corpUserCookie.value.lastIndexOf("_");
1886
+ if (lastUnderscore > 0) {
1887
+ return corpUserCookie.value.slice(0, lastUnderscore);
1888
+ }
1889
+ }
1890
+ }
1891
+ return "";
1892
+ }
1893
+
1894
+ // ── 带自动重登录的请求封装 ────────────────────────────
1895
+ //
1896
+ // 接受一个返回 Promise 的工厂函数 requestFn,以及一个持有当前登录态的对象 authRef。
1897
+ // 若接口返回 __needLogin,自动触发重登录并用新登录态重试一次。
1898
+ // authRef 是一个对象引用,重登录后会原地更新其 csrfToken / cookies / baseUrl / cookieData 字段,
1899
+ // 调用方无需额外处理。
1900
+
1901
+ async function requestWithAutoLogin(requestFn, authRef) {
1902
+ var result = await requestFn(authRef);
1903
+ // 307:csrf_token 过期,刷新后重试
1904
+ if (result && result.__csrfExpired) {
1905
+ var refreshedData = refreshCsrfToken();
1906
+ authRef.cookieData = refreshedData;
1907
+ authRef.csrfToken = refreshedData.csrf_token;
1908
+ authRef.cookies = refreshedData.cookies;
1909
+ authRef.baseUrl = resolveBaseUrl(refreshedData);
1910
+ console.error(" 🔄 csrf_token 已刷新,重试...");
1911
+ result = await requestFn(authRef);
1912
+ }
1913
+ // 302/301:登录态失效,重新登录后重试
1914
+ if (result && result.__needLogin) {
1915
+ var newCookieData = triggerLogin();
1916
+ authRef.cookieData = newCookieData;
1917
+ authRef.csrfToken = newCookieData.csrf_token;
1918
+ authRef.cookies = newCookieData.cookies;
1919
+ authRef.baseUrl = resolveBaseUrl(newCookieData);
1920
+ console.error(" 🔄 重新登录后重试...");
1921
+ result = await requestFn(authRef);
1922
+ }
1923
+ return result;
1924
+ }
1925
+
1926
+ // ── 保存 Schema 并更新表单配置(create/update 共用)──
1927
+ //
1928
+ // 封装了 saveFormSchema + updateFormConfig 两步,以及各自的 302 自动重登录重试。
1929
+ // 返回 { saveResult, configResult }。
1930
+
1931
+ async function saveSchemaAndUpdateConfig(authRef, appType, formUuid, schema, version, stepOffset) {
1932
+ var saveStep = stepOffset || 4;
1933
+ var configStep = saveStep + 1;
1934
+
1935
+ console.error("\n📝 Step " + saveStep + ": 保存表单 Schema");
1936
+ console.error(" 发送 saveFormSchema 请求...");
1937
+
1938
+ var saveResult = await requestWithAutoLogin(function (auth) {
1939
+ return sendPostRequest(
1940
+ auth.baseUrl, auth.csrfToken, auth.cookies,
1941
+ buildApiPath(appType, "saveFormSchema", { prefix: "_view" }),
1942
+ { appType: appType, formUuid: formUuid, content: JSON.stringify(schema), schemaVersion: "V5", prefix: "_view" },
1943
+ formUuid
1944
+ );
1945
+ }, authRef);
1946
+
1947
+ if (!saveResult || !saveResult.success) {
1948
+ var saveErrorMsg = saveResult ? saveResult.errorMsg || "未知错误" : "请求失败";
1949
+ console.error("\n❌ 保存 Schema 失败: " + saveErrorMsg);
1950
+ if (saveResult && !saveResult.__needLogin) {
1951
+ console.error(" 响应详情: " + JSON.stringify(saveResult, null, 2));
1952
+ }
1953
+ console.error("=".repeat(50));
1954
+ console.log(JSON.stringify({ success: false, formUuid: formUuid, error: saveErrorMsg }));
1955
+ process.exit(1);
1956
+ }
1957
+
1958
+ console.error(" ✅ Schema 保存成功!");
1959
+ if (version !== undefined) {
1960
+ console.error(" 当前版本号: " + version);
1961
+ }
1962
+
1963
+ console.error("\n⚙️ Step " + configStep + ": 更新表单配置");
1964
+ console.error(" 发送 updateFormConfig 请求...");
1965
+
1966
+ var configResult = await requestWithAutoLogin(function (auth) {
1967
+ return sendUpdateConfigRequest(auth.baseUrl, auth.csrfToken, auth.cookies, appType, formUuid, version || 1, 0);
1968
+ }, authRef);
1969
+
1970
+ return { saveResult: saveResult, configResult: configResult };
1971
+ }
1972
+
1973
+ // ── create 模式主流程 ─────────────────────────────────
1974
+
1975
+ async function mainCreate(parsedArgs, csrfToken, cookies, baseUrl, cookieData) {
1976
+ const { appType, formTitle, fieldsJsonOrFile } = parsedArgs;
1977
+
1978
+ console.error("=".repeat(50));
1979
+ console.error(" yida-create-form-page - 宜搭表单页面创建工具");
1980
+ console.error("=".repeat(50));
1981
+ console.error("\n 应用 ID: " + appType);
1982
+ console.error(" 表单名称: " + formTitle);
1983
+ console.error(" 字段定义: " + fieldsJsonOrFile);
1984
+
1985
+ // 登录态引用对象,供 requestWithAutoLogin 原地更新
1986
+ var authRef = { csrfToken: csrfToken, cookies: cookies, baseUrl: baseUrl, cookieData: cookieData };
1987
+
1988
+ // Step 2: 读取字段定义
1989
+ console.error("\n📋 Step 2: 读取字段定义");
1990
+ const { fields, columns } = readFieldsDefinition(fieldsJsonOrFile);
1991
+ console.error(" ✅ 已读取 " + fields.length + " 个字段定义");
1992
+ console.error(" PC端列数: " + columns);
1993
+ fields.forEach(function (field, index) {
1994
+ console.error(" " + (index + 1) + ". " + field.type + ": " + field.label);
1995
+ });
1996
+
1997
+ // Step 3: 创建空白表单
1998
+ console.error("\n📄 Step 3: 创建空白表单");
1999
+ console.error(" 发送 saveFormSchemaInfo 请求...");
2000
+ var createResult = await requestWithAutoLogin(function (auth) {
2001
+ return sendPostRequest(
2002
+ auth.baseUrl, auth.csrfToken, auth.cookies,
2003
+ buildApiPath(appType, "saveFormSchemaInfo"),
2004
+ { formType: "receipt", title: JSON.stringify(i18n(formTitle)) }
2005
+ );
2006
+ }, authRef);
2007
+
2008
+ if (!createResult || !createResult.success || !createResult.content) {
2009
+ const errorMsg = createResult ? createResult.errorMsg || "未知错误" : "请求失败";
2010
+ console.error(" ❌ 创建空白表单失败: " + errorMsg);
2011
+ console.log(JSON.stringify({ success: false, error: errorMsg }));
2012
+ process.exit(1);
2013
+ }
2014
+
2015
+ const formUuid = createResult.content.formUuid || createResult.content;
2016
+ console.error(" ✅ 空白表单已创建: " + formUuid);
2017
+
2018
+ // Step 4 & 5: 生成 Schema 并保存,然后更新表单配置
2019
+ var corpId = resolveCorpId(authRef.cookieData);
2020
+ if (!corpId) {
2021
+ console.error(" ⚠️ 警告: 未能获取 corpId,SerialNumberField 的 formula 可能无法正常工作");
2022
+ } else {
2023
+ console.error(" ✅ corpId: " + corpId);
2024
+ }
2025
+
2026
+ const schema = buildFormSchema(formTitle, fields, formUuid, corpId, appType, columns);
2027
+ var { configResult } = await saveSchemaAndUpdateConfig(authRef, appType, formUuid, schema, 1, 4);
2028
+
2029
+ // 输出结果
2030
+ console.error("\n" + "=".repeat(50));
2031
+ const formUrl = authRef.baseUrl + "/" + appType + "/workbench/" + formUuid;
2032
+ if (configResult && configResult.success) {
2033
+ console.error(" ✅ 表单创建成功!");
2034
+ console.error(" formUuid: " + formUuid);
2035
+ console.error(" 访问地址: " + formUrl);
2036
+ console.error(" 配置已更新: MINI_RESOURCE = 0");
2037
+ console.error("=".repeat(50));
2038
+ console.log(JSON.stringify({ success: true, formUuid, formTitle, appType, fieldCount: fields.length, url: formUrl }));
2039
+ } else {
2040
+ const errorMsg = configResult ? configResult.errorMsg || "未知错误" : "请求失败";
2041
+ console.error(" ⚠️ 配置更新失败: " + errorMsg);
2042
+ console.error(" Schema 已保存,但配置更新失败");
2043
+ console.error(" formUuid: " + formUuid);
2044
+ console.error(" 访问地址: " + formUrl);
2045
+ if (configResult && !configResult.__needLogin) {
2046
+ console.error(" 响应详情: " + JSON.stringify(configResult, null, 2));
2047
+ }
2048
+ console.error("=".repeat(50));
2049
+ console.log(JSON.stringify({ success: true, formUuid, formTitle, appType, fieldCount: fields.length, url: formUrl, configWarning: errorMsg }));
2050
+ }
2051
+ }
2052
+
2053
+ // ── 为 SerialNumberField 补全 formula(递归处理子表)──
2054
+ //
2055
+ // 遍历字段列表,对每个 SerialNumberField:
2056
+ // - 若 formula 已有有效的 expression(从宜搭获取的已有字段),则跳过,不覆盖
2057
+ // - 若 formula 为空对象 {} 或 expression 为空(新增字段),则自动构建 expression
2058
+ // 同时递归处理 TableField 的子字段(子表内也可能有流水号字段)
2059
+
2060
+ function fillSerialNumberFormulas(components, corpId, appType, formUuid) {
2061
+ if (!Array.isArray(components)) return;
2062
+ components.forEach(function (component) {
2063
+ if (component.componentName === "SerialNumberField" && component.props) {
2064
+ var existingFormula = component.props.formula;
2065
+ var hasValidFormula = existingFormula &&
2066
+ typeof existingFormula === "object" &&
2067
+ typeof existingFormula.expression === "string" &&
2068
+ existingFormula.expression.length > 0;
2069
+
2070
+ if (!hasValidFormula) {
2071
+ var fieldId = component.props.fieldId;
2072
+ var serialNumberRule = component.props.serialNumberRule;
2073
+ if (serialNumberRule) {
2074
+ var ruleJson = JSON.stringify({ type: "custom", value: serialNumberRule });
2075
+ var escapedRuleJson = ruleJson.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
2076
+ component.props.formula = {
2077
+ expression: 'SERIALNUMBER("' + corpId + '", "' + appType + '", "' + formUuid + '", "' + fieldId + '", "' + escapedRuleJson + '")'
2078
+ };
2079
+ console.error(" 🔢 SerialNumberField 「" + (component.props.label && component.props.label.zh_CN || fieldId) + "」formula 已设置");
2080
+ }
2081
+ }
2082
+ }
2083
+ // 递归处理子表内的字段
2084
+ if (component.componentName === "TableField" && Array.isArray(component.children)) {
2085
+ fillSerialNumberFormulas(component.children, corpId, appType, formUuid);
2086
+ }
2087
+ });
2088
+ }
2089
+
2090
+ // ── update 模式主流程 ─────────────────────────────────
2091
+
2092
+ async function mainUpdate(parsedArgs, csrfToken, cookies, baseUrl, cookieData) {
2093
+ const { appType, formUuid, changesJsonOrFile } = parsedArgs;
2094
+
2095
+ console.error("=".repeat(50));
2096
+ console.error(" yida-create-form-page - 宜搭表单页面更新工具");
2097
+ console.error("=".repeat(50));
2098
+ console.error("\n 应用 ID: " + appType);
2099
+ console.error(" 表单 UUID: " + formUuid);
2100
+ console.error(" 修改定义: " + changesJsonOrFile);
2101
+
2102
+ // 登录态引用对象,供 requestWithAutoLogin 原地更新
2103
+ var authRef = { csrfToken: csrfToken, cookies: cookies, baseUrl: baseUrl, cookieData: cookieData };
2104
+
2105
+ // Step 2: 获取当前表单 Schema
2106
+ console.error("\n📄 Step 2: 获取当前表单 Schema");
2107
+ console.error(" 发送 getFormSchema 请求...");
2108
+ var schemaResult = await requestWithAutoLogin(function (auth) {
2109
+ return sendGetRequest(
2110
+ auth.baseUrl, auth.cookies,
2111
+ buildApiPath(appType, "getFormSchema", { prefix: "_view", namespace: "alibaba" }),
2112
+ { formUuid: formUuid, schemaVersion: "V5" }
2113
+ );
2114
+ }, authRef);
2115
+
2116
+ if (!schemaResult || schemaResult.success === false || schemaResult.__needLogin) {
2117
+ const errorMsg = schemaResult ? schemaResult.errorMsg || "未知错误" : "请求失败";
2118
+ console.error(" ❌ 获取表单 Schema 失败: " + errorMsg);
2119
+ console.log(JSON.stringify({ success: false, error: errorMsg }));
2120
+ process.exit(1);
2121
+ }
2122
+
2123
+ // 从返回结果中提取 schema 内容和版本号
2124
+ var schema;
2125
+ var version = 1;
2126
+
2127
+ if (schemaResult.content && typeof schemaResult.content === "object" && schemaResult.content.version !== undefined) {
2128
+ version = schemaResult.content.version;
2129
+ } else if (schemaResult.version !== undefined) {
2130
+ version = schemaResult.version;
2131
+ }
2132
+
2133
+ if (schemaResult.content) {
2134
+ schema = typeof schemaResult.content === "string" ? JSON.parse(schemaResult.content) : schemaResult.content;
2135
+ } else if (schemaResult.pages) {
2136
+ schema = schemaResult;
2137
+ } else {
2138
+ console.error(" ❌ 无法从返回结果中提取 Schema");
2139
+ console.error(" 响应结构: " + JSON.stringify(Object.keys(schemaResult)));
2140
+ console.log(JSON.stringify({ success: false, error: "无法解析 Schema 结构" }));
2141
+ process.exit(1);
2142
+ }
2143
+
2144
+ if (!schema.pages || !Array.isArray(schema.pages) || schema.pages.length === 0) {
2145
+ console.error(" ⚠️ Schema 为空,使用基础表单模板初始化");
2146
+ schema = buildEmptyFormSchema();
2147
+ }
2148
+
2149
+ const formContainer = findFormContainer(schema.pages[0].componentsTree[0]);
2150
+ if (formContainer && formContainer.children) {
2151
+ console.error(" ✅ Schema 获取成功,当前共 " + formContainer.children.length + " 个字段:");
2152
+ formContainer.children.forEach(function (child, childIndex) {
2153
+ const labelText = extractLabelText(child);
2154
+ console.error(" " + (childIndex + 1) + ". " + child.componentName + ": " + labelText);
2155
+ });
2156
+ } else {
2157
+ console.error(" ✅ Schema 获取成功(表单暂无字段)");
2158
+ }
2159
+
2160
+ // Step 3: 读取修改定义
2161
+ console.error("\n📋 Step 3: 读取修改定义");
2162
+ const changes = readChangesDefinition(changesJsonOrFile);
2163
+ console.error(" ✅ 已读取 " + changes.length + " 条修改操作");
2164
+ changes.forEach(function (change, changeIndex) {
2165
+ if (change.action === "add") {
2166
+ console.error(" " + (changeIndex + 1) + ". [新增] " + change.field.type + ": " + change.field.label);
2167
+ } else if (change.action === "delete") {
2168
+ console.error(" " + (changeIndex + 1) + ". [删除] " + change.label);
2169
+ } else if (change.action === "update") {
2170
+ console.error(" " + (changeIndex + 1) + ". [修改] " + change.label + " → " + Object.keys(change.changes || {}).join(", "));
2171
+ }
2172
+ });
2173
+
2174
+ // Step 4: 应用修改
2175
+ console.error("\n🔧 Step 4: 应用修改");
2176
+ const appliedChanges = applyChangesToSchema(schema, changes);
2177
+
2178
+ // 为 SerialNumberField 补全 formula(若尚未设置)
2179
+ var corpId = resolveCorpId(authRef.cookieData);
2180
+ if (!corpId) {
2181
+ console.error(" ⚠️ 警告: 未能获取 corpId,SerialNumberField 的 formula 可能无法正常工作");
2182
+ }
2183
+
2184
+ const formContainerUpdate = findFormContainer(schema.pages[0].componentsTree[0]);
2185
+ if (formContainerUpdate && formContainerUpdate.children) {
2186
+ fillSerialNumberFormulas(formContainerUpdate.children, corpId, appType, formUuid);
2187
+ }
2188
+
2189
+ // Step 5 & 6: 保存 Schema 并更新表单配置
2190
+ var { configResult } = await saveSchemaAndUpdateConfig(authRef, appType, formUuid, schema, version, 5);
2191
+
2192
+ // 输出结果
2193
+ console.error("\n" + "=".repeat(50));
2194
+ const formUrl = authRef.baseUrl + "/" + appType + "/workbench/" + formUuid;
2195
+ if (configResult && configResult.success) {
2196
+ console.error(" ✅ 表单更新成功!");
2197
+ console.error(" formUuid: " + formUuid);
2198
+ console.error(" 访问地址: " + formUrl);
2199
+ console.error(" 应用修改: " + appliedChanges.length + " 条");
2200
+ console.error(" 配置已更新: MINI_RESOURCE = 0");
2201
+ console.error("=".repeat(50));
2202
+ console.log(JSON.stringify({ success: true, formUuid, appType, changesApplied: appliedChanges.length, changes: appliedChanges, url: formUrl }));
2203
+ } else {
2204
+ const errorMsg = configResult ? configResult.errorMsg || "未知错误" : "请求失败";
2205
+ console.error(" ⚠️ 配置更新失败: " + errorMsg);
2206
+ console.error(" Schema 已保存,但配置更新失败");
2207
+ console.error(" formUuid: " + formUuid);
2208
+ console.error(" 访问地址: " + formUrl);
2209
+ console.error(" 应用修改: " + appliedChanges.length + " 条");
2210
+ if (configResult && !configResult.__needLogin) {
2211
+ console.error(" 响应详情: " + JSON.stringify(configResult, null, 2));
2212
+ }
2213
+ console.error("=".repeat(50));
2214
+ console.log(JSON.stringify({ success: true, formUuid, appType, changesApplied: appliedChanges.length, changes: appliedChanges, url: formUrl, configWarning: errorMsg }));
2215
+ }
2216
+ }
2217
+
2218
+ // ── 主入口 ────────────────────────────────────────────
2219
+
2220
+ async function main() {
2221
+ const parsedArgs = parseArgs();
2222
+
2223
+ // Step 1: 读取本地登录态
2224
+ console.error("\n🔑 Step 1: 读取登录态");
2225
+ let cookieData = loadCookieData();
2226
+ if (!cookieData) {
2227
+ console.error(" ⚠️ 未找到本地登录态,触发登录...");
2228
+ cookieData = triggerLogin();
2229
+ }
2230
+ let { csrf_token: csrfToken, cookies } = cookieData;
2231
+ let baseUrl = resolveBaseUrl(cookieData);
2232
+ console.error(" ✅ 登录态已就绪(" + baseUrl + ")");
2233
+
2234
+ if (parsedArgs.mode === "update") {
2235
+ await mainUpdate(parsedArgs, csrfToken, cookies, baseUrl, cookieData);
2236
+ } else {
2237
+ await mainCreate(parsedArgs, csrfToken, cookies, baseUrl, cookieData);
2238
+ }
2239
+ }
2240
+
2241
+ main().catch((error) => {
2242
+ console.error("\n❌ 异常: " + error.message);
2243
+ process.exit(1);
2244
+ });