lan-low-code-cli 2.7.0 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -273,6 +273,9 @@ llc operation create --form-id <fdId> --name 批量归档 --code archive \
273
273
  --type script --script @archive.js
274
274
  llc operation create --form-id <fdId> --name 转办 --code transfer \
275
275
  --type dialog --dialog-id <对话框id>
276
+ # 绑定动作流(按钮由后端执行动作流)
277
+ llc operation create --form-id <fdId> --name 运行闭环验证 --code runFlow \
278
+ --trigger-flow <动作流fdId>
276
279
  llc operation get --id <operationId>
277
280
  llc operation update --id <operationId> --type script \
278
281
  --script @examples/operation-script-modal-table.js # 把已有按钮改成脚本模式
@@ -283,7 +286,10 @@ llc operation delete --id <operationId> --yes # 被页面引用时拒绝
283
286
  ```
284
287
 
285
288
  - `--type`:`link`(fdType=2 / fdViewType=4,需 `--url`)、`script`(3 / 5,需 `--script`,支持 `@文件`)、
286
- `dialog`(3 / 1,需 `--dialog-id`)
289
+ `dialog`(3 / 1,需 `--dialog-id`);或改用 `--trigger-flow <动作流fdId>` 绑定动作流
290
+ (写入 `fdTriggerType=3`、`fdTriggerId`、`fdTriggerCode`、`fdTriggerModel`,可配 `--inc-params`)
291
+ - `--trigger-flow` 只接受 `StartActiveNode` + 已启用(`fdStatus=1`)+ `fdOperateTrigger=true` 的动作流;
292
+ 挂载后用 `llc flow run --operation-id <opId> --view-id <listViewId> --yes` 可实际执行并校验 `success`
287
293
  - `--position`:`list`(默认,列表视图)/ `view`(查看视图)
288
294
  - 默认不要求勾选数据;需要“先勾选数据再执行”时加 `--need-select`(链接会按接口调用,`fdUrlType=2`)
289
295
  - `--dry-run` 只打印请求体;新建操作默认仅管理员可用
@@ -333,6 +339,7 @@ llc flow apply --spec @flow.json --app-code cli-test --resolve-schema #
333
339
  llc flow apply --spec @flow.json --app-code cli-test --resolve-schema --yes --enable --json
334
340
  llc flow diff --id <flowId> --spec @flow.json --resolve-schema # 回读比对(默认忽略坐标)
335
341
  llc flow script --id <flowId> --out call-flow.js # 生成 MKXFORM.flow_<code> 脚本
342
+ llc flow run --operation-id <opId> --view-id <listViewId> --yes --json # 执行绑定动作流的按钮
336
343
  llc flow disable --id <flowId> --yes
337
344
  llc flow delete --id <flowId> --yes
338
345
  ```
@@ -356,6 +363,16 @@ llc flow delete --id <flowId> --yes
356
363
  这两个端点要求 `entityId` 带 `sys-modeling#` 前缀(CLI 自动补),裸 id 会返回 500。
357
364
  失败默认只告警,`--strict-schema` 升级为错误。
358
365
 
366
+ **节点必填校验**(复刻 IDE 规则表与豁免逻辑):
367
+
368
+ - `TransformNode`:必填 `outParam` 与 `outputParamMapping`(`valueFormula` 或非空 `pointMappings`);
369
+ `outParam` 为空时平台会豁免映射校验,但 `outParam` 本身仍必填
370
+ - `ExecuteNode` / `InterfaceNode` / `SubActionNode` / `FlowBotsNode`:必填各自编码字段,存在入参 Schema 时必填参数映射
371
+ - `BranchNode`:必填 `conditionType` / `flowWay` 及匹配的 `formulaConfig` / `ruleConfig`(有分支出边时自动生成)
372
+ - `LoopNode`:必填 `loopDefine.type`;`MessageNode`:按 `subjectType` / `messageLink.linkType` 校验内容与链接
373
+ - `AddDataNode` / `AddOrUpdateNode`:分类表单或未填 `commitType` 时豁免 `flow`
374
+ - 校验失败时输出 `field` 与修复建议,`flow validate` / `flow compile` 退出码为 `1`,不会写入环境
375
+
359
376
  **边界与已知限制**:
360
377
 
361
378
  - `AddDataNode` / `AddOrUpdateNode` 的 `fields` 是平台真实结构
@@ -24,4 +24,12 @@ exports.operationApi = {
24
24
  offset: 0,
25
25
  pageSize: 1000,
26
26
  }),
27
+ /**
28
+ * 执行业务操作(平台运行入口,`sysModelingMain/other`)。
29
+ *
30
+ * 实测:绑定动作流的按钮(`fdConfig.fdTriggerType=3`)会由后端执行动作流,
31
+ * 返回 `{ success, type:'msg', responseMsg, sysModelingOperationVO }`;
32
+ * 前端在 `desktop/operation/other.ts:348-357` 正是以 `data.success` 判定动作流执行成功。
33
+ */
34
+ execute: (client, request) => client.post('sys-modeling/sysModelingMain/other', request),
27
35
  };
@@ -4,6 +4,7 @@ exports.registerFlowCommand = registerFlowCommand;
4
4
  const fs_1 = require("fs");
5
5
  const path_1 = require("path");
6
6
  const flow_1 = require("../api/flow");
7
+ const operation_1 = require("../api/operation");
7
8
  const client_1 = require("../client");
8
9
  const config_1 = require("../config");
9
10
  const flow_2 = require("../generator/flow");
@@ -56,12 +57,20 @@ function buildOptions(opts) {
56
57
  function printDiagnostics(result, force) {
57
58
  const errors = result.diagnostics.filter((d) => d.level === 'error');
58
59
  const warnings = result.diagnostics.filter((d) => d.level === 'warn');
59
- warnings.forEach((d) => logger_1.logger.warn(`[${d.code}] ${d.message}`));
60
+ warnings.forEach((d) => {
61
+ logger_1.logger.warn(`[${d.code}] ${d.message}`);
62
+ if (d.hint)
63
+ logger_1.logger.info(logger_1.color.gray(` ↳ ${d.hint}`));
64
+ });
60
65
  if (errors.length === 0) {
61
66
  logger_1.logger.success(`离线校验通过(${result.diagnostics.length - errors.length} 条提示)`);
62
67
  return;
63
68
  }
64
- errors.forEach((d) => logger_1.logger.error(`[${d.code}] ${d.message}`));
69
+ errors.forEach((d) => {
70
+ logger_1.logger.error(`[${d.code}] ${d.message}`);
71
+ if (d.hint)
72
+ logger_1.logger.info(logger_1.color.gray(` ↳ ${d.hint}`));
73
+ });
65
74
  if (!force) {
66
75
  throw new Error(`离线校验未通过(${errors.length} 项错误);修正 spec 后重试,或追加 --force 仅用于查看输出`);
67
76
  }
@@ -178,6 +187,19 @@ function stripInstanceFields(node) {
178
187
  }
179
188
  return out;
180
189
  }
190
+ function parseFdConfig(value) {
191
+ if (!value)
192
+ return {};
193
+ if (typeof value === 'object')
194
+ return value;
195
+ try {
196
+ const parsed = JSON.parse(String(value));
197
+ return parsed && typeof parsed === 'object' ? parsed : {};
198
+ }
199
+ catch {
200
+ return {};
201
+ }
202
+ }
181
203
  function writeTextFile(file, text) {
182
204
  const target = (0, path_1.resolve)(process.cwd(), file.startsWith('@') ? file.slice(1) : file);
183
205
  (0, fs_1.mkdirSync)((0, path_1.dirname)(target), { recursive: true });
@@ -580,6 +602,12 @@ function registerFlowCommand(program) {
580
602
  printDiagnostics(result, Boolean(opts.force));
581
603
  }
582
604
  const isUpdate = Boolean(spec.fdId);
605
+ let current;
606
+ if (isUpdate) {
607
+ current = await flow_1.flowApi.get(client, String(spec.fdId));
608
+ if (!current?.fdId)
609
+ throw new Error(`动作流不存在:${spec.fdId}`);
610
+ }
583
611
  if (!isUpdate) {
584
612
  let unique;
585
613
  try {
@@ -592,7 +620,15 @@ function registerFlowCommand(program) {
592
620
  throw new Error(`流程编码 ${spec.code} 在应用 ${spec.appCode} 中已存在;更换 code,或更新已有流程时在 spec 中写 fdId`);
593
621
  }
594
622
  }
595
- const payload = buildPublishPayload(result);
623
+ // 更新时合并现有 VO,避免把 fdStatus / dynamicProps / fdCreator 等服务端字段覆盖掉
624
+ const payload = isUpdate
625
+ ? {
626
+ ...current,
627
+ ...result.basicInfo,
628
+ fdStatus: current?.fdStatus ?? result.basicInfo.fdStatus,
629
+ fdDesign: result.fdDesign,
630
+ }
631
+ : buildPublishPayload(result);
596
632
  if (!opts.yes) {
597
633
  logger_1.logger.warn(`预览模式:未写入。目标应用 ${spec.appCode};确认 payload 后追加 --yes`);
598
634
  logger_1.logger.json(payload);
@@ -752,4 +788,52 @@ function registerFlowCommand(program) {
752
788
  if (opts.json)
753
789
  logger_1.logger.json({ fdId: opts.id, deleted: true });
754
790
  });
791
+ addCommonOptions(flow
792
+ .command('run')
793
+ .description('通过绑定动作流的业务操作执行一次(sysModelingMain/other)')
794
+ .requiredOption('--operation-id <fdId>', '绑定动作流的业务操作 id')
795
+ .option('--view-id <fdId>', '列表视图 id(fdListviewId)')
796
+ .option('--doc-id <fdId>', '当前文档 id')
797
+ .option('--doc-ids <ids>', '勾选文档 id,逗号分隔')
798
+ .option('--inc-params <json|@file>', '附加参数(commonIncParams)')
799
+ .option('--yes', '确认执行(动作流可能有副作用)')
800
+ .option('--json', '以 JSON 输出结果')).action(async (opts) => {
801
+ if (!opts.yes) {
802
+ logger_1.logger.warn('已取消:执行动作流可能有副作用,请追加 --yes 确认');
803
+ return;
804
+ }
805
+ const client = createClient(opts);
806
+ const op = await operation_1.operationApi.get(client, String(opts.operationId));
807
+ if (!op?.fdId)
808
+ throw new Error(`业务操作不存在:${opts.operationId}`);
809
+ const config = parseFdConfig(op.fdConfig);
810
+ if (Number(config.fdTriggerType) !== 3) {
811
+ throw new Error('该业务操作未绑定动作流(fdConfig.fdTriggerType ≠ 3)');
812
+ }
813
+ const params = { fdOprId: op.fdId };
814
+ if (opts.viewId)
815
+ params.fdListviewId = String(opts.viewId);
816
+ if (opts.docId)
817
+ params.fdId = String(opts.docId);
818
+ if (opts.docIds) {
819
+ params.fdIds = String(opts.docIds).split(',').map((s) => s.trim()).filter(Boolean);
820
+ }
821
+ if (opts.incParams)
822
+ params.commonIncParams = readJsonInput(String(opts.incParams), '--inc-params');
823
+ logger_1.logger.step(`执行动作流按钮:${op.fdName ?? op.fdId}`);
824
+ const result = await operation_1.operationApi.execute(client, params);
825
+ if (opts.json) {
826
+ logger_1.logger.json({
827
+ operationId: op.fdId,
828
+ success: result.success,
829
+ type: result.type,
830
+ responseMsg: result.responseMsg,
831
+ });
832
+ return;
833
+ }
834
+ if (result.success === false) {
835
+ throw new Error(`执行失败:${result.responseMsg ?? result.type ?? '未知原因'}`);
836
+ }
837
+ logger_1.logger.success(result.responseMsg ?? '动作流执行成功');
838
+ });
755
839
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerOperationCommand = registerOperationCommand;
4
4
  const fs_1 = require("fs");
5
5
  const path_1 = require("path");
6
+ const flow_1 = require("../api/flow");
6
7
  const operation_1 = require("../api/operation");
7
8
  const view_1 = require("../api/view");
8
9
  const client_1 = require("../client");
@@ -18,12 +19,16 @@ const KIND_ALIAS = {
18
19
  js: 'script',
19
20
  dialog: 'dialog',
20
21
  modal: 'dialog',
22
+ flow: 'flow',
21
23
  };
22
24
  const KIND_META = {
23
25
  link: { fdType: 2, fdViewType: 4, label: '打开链接' },
24
26
  script: { fdType: 3, fdViewType: 5, label: '执行脚本' },
25
27
  dialog: { fdType: 3, fdViewType: 1, label: '执行业务操作(对话框)' },
28
+ flow: { fdType: 3, fdViewType: 1, label: '触发动作流' },
26
29
  };
30
+ /** 动作流 fdTriggerModel.fdModelName(addOperationModal/content.tsx:981-985) */
31
+ const FLOW_MODEL_NAME = 'com.landray.sys.modeling.process.entity.SysModelingProcess';
27
32
  /** 解析 fdConfig(后端实体不一定有顶层 fdViewType,视图类型存在 fdConfig 中) */
28
33
  function readOperationConfig(op) {
29
34
  if (!op.fdConfig)
@@ -43,7 +48,10 @@ function operationTypeLabel(op) {
43
48
  return '对话框';
44
49
  if (op.fdType === 6)
45
50
  return 'AI 能力';
46
- const viewType = Number(readOperationConfig(op).fdViewType ?? op.fdViewType);
51
+ const config = readOperationConfig(op);
52
+ if (op.fdType === 3 && Number(config.fdTriggerType) === 3)
53
+ return '触发动作流';
54
+ const viewType = Number(config.fdViewType ?? op.fdViewType);
47
55
  if (op.fdType === 3)
48
56
  return viewType === 5 ? '脚本' : '无视图';
49
57
  return '有视图';
@@ -212,19 +220,22 @@ function assertOperationCode(code) {
212
220
  throw new Error('--code 需以字母开头,仅允许字母、数字、下划线、连字符');
213
221
  }
214
222
  }
215
- /** 从已有操作推导类型(fdType=2 为链接;fdType=3 + fdViewType=5 为脚本,其余按对话框) */
223
+ /** 从已有操作推导类型(fdType=2 链接;fdTriggerType=3 动作流;fdViewType=5 脚本;其余对话框) */
216
224
  function detectKind(op) {
217
225
  if (op.fdType === 2)
218
226
  return 'link';
219
- const viewType = Number(readOperationConfig(op).fdViewType ?? op.fdViewType);
227
+ const config = readOperationConfig(op);
228
+ if (op.fdType === 3 && Number(config.fdTriggerType) === 3)
229
+ return 'flow';
230
+ const viewType = Number(config.fdViewType ?? op.fdViewType);
220
231
  return op.fdType === 3 && viewType === 5 ? 'script' : 'dialog';
221
232
  }
222
- function buildCreatePayload(opts) {
233
+ function buildCreatePayload(opts, flow) {
223
234
  const formId = String(opts.formId ?? '').trim();
224
235
  const name = String(opts.name ?? '').trim();
225
236
  const code = String(opts.code ?? '').trim();
226
237
  const rawKind = String(opts.type ?? '').trim().toLowerCase();
227
- const kind = KIND_ALIAS[rawKind];
238
+ const kind = opts.triggerFlow ? 'flow' : KIND_ALIAS[rawKind];
228
239
  const positionRaw = String(opts.position ?? 'list').trim().toLowerCase();
229
240
  const fdPosition = positionRaw === 'view' ? 2 : positionRaw === 'list' ? 1 : 0;
230
241
  if (!formId)
@@ -232,7 +243,10 @@ function buildCreatePayload(opts) {
232
243
  assertOperationName(name);
233
244
  assertOperationCode(code);
234
245
  if (!kind)
235
- throw new Error(`未知 --type:${opts.type}(可选 link | script | dialog)`);
246
+ throw new Error(`请指定 --type(link | script | dialog)或 --trigger-flow <动作流 fdId>`);
247
+ if (opts.triggerFlow && rawKind) {
248
+ throw new Error('--trigger-flow 与 --type 不能同时使用;触发动作流请省略 --type');
249
+ }
236
250
  if (!fdPosition)
237
251
  throw new Error(`未知 --position:${opts.position}(可选 list | view)`);
238
252
  const desc = opts.desc === undefined ? '' : String(opts.desc);
@@ -264,6 +278,31 @@ function buildCreatePayload(opts) {
264
278
  throw new Error('--type script 需指定 --script <代码|@文件>');
265
279
  config.fdJavaScript = readScriptValue(script);
266
280
  }
281
+ else if (kind === 'flow') {
282
+ if (!flow)
283
+ throw new Error('--trigger-flow 需要先加载动作流详情');
284
+ assertTriggerableFlow(flow);
285
+ config.fdTriggerType = 3;
286
+ config.fdTriggerId = flow.fdId;
287
+ config.fdTriggerCode = flow.fdCode;
288
+ config.fdTriggerModel = {
289
+ fdId: flow.fdId,
290
+ fdName: flow.fdName,
291
+ fdModelName: FLOW_MODEL_NAME,
292
+ fdCode: flow.fdCode,
293
+ };
294
+ const incParams = buildIncParams(opts.incParams);
295
+ if (incParams !== undefined)
296
+ config.incParams = incParams;
297
+ const active = flow.fdProcessActive ?? {};
298
+ if (active.fdFormId) {
299
+ // 动作流绑定了表单:按钮按当前文档 id/ids 传参
300
+ config.incParams = [{
301
+ field: { name: active.fdDataType === 2 ? 'fdIds' : 'fdId' },
302
+ value: { val: '${fdId}' },
303
+ }];
304
+ }
305
+ }
267
306
  else {
268
307
  const dialogId = String(opts.dialogId ?? '').trim();
269
308
  if (!dialogId)
@@ -288,19 +327,54 @@ function buildCreatePayload(opts) {
288
327
  payload.isModal = true;
289
328
  return { payload, kind };
290
329
  }
330
+ /**
331
+ * 只有「主动触发 + 已启用 + 允许操作触发」的动作流才能挂到按钮上
332
+ * (addOperationModal/content.tsx:332-335)。
333
+ */
334
+ function assertTriggerableFlow(flow) {
335
+ if (flow.fdStartWay !== 'StartActiveNode') {
336
+ throw new Error(`动作流 ${flow.fdName ?? flow.fdId} 的启动方式是 ${flow.fdStartWay ?? '(空)'},只有 StartActiveNode 才能挂到按钮`);
337
+ }
338
+ if (flow.fdStatus !== 1) {
339
+ throw new Error(`动作流 ${flow.fdName ?? flow.fdId} 当前状态为 ${flow.fdStatus ?? '(空)'},请先发布并启用(llc flow enable --id ${flow.fdId})`);
340
+ }
341
+ const active = flow.fdProcessActive ?? {};
342
+ if (active.fdOperateTrigger !== true) {
343
+ throw new Error(`动作流 ${flow.fdName ?? flow.fdId} 未开启「操作触发」,请在 trigger.fdRunType 中包含 operate 后重新发布`);
344
+ }
345
+ }
346
+ /** --inc-params 支持 @file 或内联 JSON;返回平台要求的 [{field:{name},value:{val}}] */
347
+ function buildIncParams(input) {
348
+ if (input === undefined || input === null || input === '')
349
+ return undefined;
350
+ const raw = typeof input === 'string' ? readScriptValue(input) : JSON.stringify(input);
351
+ let parsed;
352
+ try {
353
+ parsed = JSON.parse(raw);
354
+ }
355
+ catch (err) {
356
+ throw new Error(`--inc-params 不是合法 JSON:${err.message}`);
357
+ }
358
+ if (!Array.isArray(parsed))
359
+ throw new Error('--inc-params 需为数组:[{field:{name},value:{val}}]');
360
+ return parsed;
361
+ }
291
362
  /**
292
363
  * 基于现有操作构造 update 请求体。
293
364
  * 显式切换类型(--type)时会重建 fdConfig 的类型相关字段,
294
365
  * 但保留权限、默认值等与类型无关的配置。
295
366
  */
296
- function buildUpdatePayload(current, opts) {
367
+ function buildUpdatePayload(current, opts, flow) {
297
368
  if (current.fdType !== 2 && current.fdType !== 3) {
298
- throw new Error(`该操作类型为「${operationTypeLabel(current)}」,仅支持修改自定义的链接 / 脚本 / 对话框操作`);
369
+ throw new Error(`该操作类型为「${operationTypeLabel(current)}」,仅支持修改自定义的链接 / 脚本 / 对话框 / 动作流操作`);
299
370
  }
300
371
  const currentKind = detectKind(current);
301
372
  const rawKind = String(opts.type ?? '').trim().toLowerCase();
302
373
  let kind = currentKind;
303
- if (rawKind) {
374
+ if (opts.triggerFlow) {
375
+ kind = 'flow';
376
+ }
377
+ else if (rawKind) {
304
378
  const resolved = KIND_ALIAS[rawKind];
305
379
  if (!resolved)
306
380
  throw new Error(`未知 --type:${opts.type}(可选 link | script | dialog)`);
@@ -353,6 +427,23 @@ function buildUpdatePayload(current, opts) {
353
427
  throw new Error('脚本操作需指定 --script <代码|@文件>');
354
428
  config.fdJavaScript = script;
355
429
  }
430
+ else if (kind === 'flow') {
431
+ if (!flow)
432
+ throw new Error('--trigger-flow 需要先加载动作流详情');
433
+ assertTriggerableFlow(flow);
434
+ config.fdTriggerType = 3;
435
+ config.fdTriggerId = flow.fdId;
436
+ config.fdTriggerCode = flow.fdCode;
437
+ config.fdTriggerModel = {
438
+ fdId: flow.fdId,
439
+ fdName: flow.fdName,
440
+ fdModelName: FLOW_MODEL_NAME,
441
+ fdCode: flow.fdCode,
442
+ };
443
+ const incParams = buildIncParams(opts.incParams);
444
+ if (incParams !== undefined)
445
+ config.incParams = incParams;
446
+ }
356
447
  else {
357
448
  const dialogId = opts.dialogId !== undefined
358
449
  ? String(opts.dialogId).trim()
@@ -397,6 +488,14 @@ function buildUpdatePayload(current, opts) {
397
488
  payload.isModal = true;
398
489
  else
399
490
  delete payload.isModal;
491
+ if (kind === 'flow') {
492
+ // 切换为动作流时清理对话框残留字段
493
+ delete config.fdModal;
494
+ delete config.fdModalType;
495
+ delete config.fdModalSizeType;
496
+ delete config.fdModalDetail;
497
+ payload.fdConfig = JSON.stringify(config);
498
+ }
400
499
  if (opts.mobileIcon !== undefined)
401
500
  payload.fdMobileIcon = String(opts.mobileIcon);
402
501
  if (opts.enable === true)
@@ -504,16 +603,18 @@ function registerOperationCommand(program) {
504
603
  // ---------------------------------------------------------------- create
505
604
  operation
506
605
  .command('create')
507
- .description('新建业务操作(支持打开链接 / 执行脚本 / 执行对话框)')
606
+ .description('新建业务操作(打开链接 / 执行脚本 / 执行对话框 / 触发动作流)')
508
607
  .requiredOption('--form-id <fdId>', '表单 id')
509
608
  .requiredOption('--name <name>', '操作名称(≤30)')
510
609
  .requiredOption('--code <code>', '自定义操作编码(≤15,字母开头,可含数字/下划线/连字符)')
511
- .requiredOption('--type <type>', '类型:link | script | dialog')
610
+ .option('--type <type>', '类型:link | script | dialog;触发动作流时改用 --trigger-flow')
512
611
  .option('--position <position>', '使用位置:list(列表视图)| view(查看视图)', 'list')
513
612
  .option('--need-select', '点击前需勾选数据(默认否;仅列表视图的自定义操作有效)')
514
613
  .option('--url <url>', '打开链接地址(--type link 必填)')
515
614
  .option('--script <code|@file>', '脚本内容或 @文件(--type script 必填)')
516
615
  .option('--dialog-id <fdId>', '对话框 id(--type dialog 必填,可在平台对话框设计中获取)')
616
+ .option('--trigger-flow <fdId>', '绑定动作流(需 StartActiveNode + 已启用 + operateTrigger)')
617
+ .option('--inc-params <json|@file>', '动作流入参:[{field:{name},value:{val}}]')
517
618
  .option('--desc <text>', '描述(≤500)')
518
619
  .option('--mobile-icon <name>', '移动端图标名')
519
620
  .option('--dry-run', '只打印请求体,不提交')
@@ -521,13 +622,16 @@ function registerOperationCommand(program) {
521
622
  .option('--token <token>', '鉴权 Token(覆盖配置)')
522
623
  .option('--json', '以 JSON 输出结果')
523
624
  .action(async (opts) => {
524
- const { payload, kind } = buildCreatePayload(opts);
625
+ const client = createClient(opts);
626
+ const flow = opts.triggerFlow
627
+ ? await flow_1.flowApi.get(client, String(opts.triggerFlow))
628
+ : undefined;
629
+ const { payload, kind } = buildCreatePayload(opts, flow);
525
630
  if (opts.dryRun) {
526
631
  logger_1.logger.info(logger_1.color.gray(`# ${KIND_META[kind].label} · 表单 ${String(opts.formId)}(dry-run)`));
527
632
  logger_1.logger.json(payload);
528
633
  return;
529
634
  }
530
- const client = createClient(opts);
531
635
  logger_1.logger.step(`新建业务操作:${String(opts.name)}(${KIND_META[kind].label})`);
532
636
  const result = await operation_1.operationApi.add(client, payload);
533
637
  const fdId = typeof result === 'string' ? result : result?.fdId;
@@ -551,6 +655,8 @@ function registerOperationCommand(program) {
551
655
  .option('--url <url>', '打开链接地址(link)')
552
656
  .option('--script <code|@file>', '脚本内容或 @文件(script)')
553
657
  .option('--dialog-id <fdId>', '对话框 id(dialog)')
658
+ .option('--trigger-flow <fdId>', '切换为触发动作流(需 StartActiveNode + 已启用)')
659
+ .option('--inc-params <json|@file>', '动作流入参:[{field:{name},value:{val}}]')
554
660
  .option('--need-select', '点击前需勾选数据')
555
661
  .option('--no-need-select', '不需要勾选数据')
556
662
  .option('--enable', '启用该操作')
@@ -570,7 +676,10 @@ function registerOperationCommand(program) {
570
676
  const current = await operation_1.operationApi.get(client, fdId);
571
677
  if (!current?.fdId)
572
678
  throw new Error(`业务操作不存在:${fdId}`);
573
- const { payload, kind } = buildUpdatePayload(current, opts);
679
+ const flow = opts.triggerFlow
680
+ ? await flow_1.flowApi.get(client, String(opts.triggerFlow))
681
+ : undefined;
682
+ const { payload, kind } = buildUpdatePayload(current, opts, flow);
574
683
  if (opts.dryRun) {
575
684
  logger_1.logger.info(logger_1.color.gray(`# ${KIND_META[kind].label} · ${current.fdName}(dry-run)`));
576
685
  logger_1.logger.json(payload);
@@ -7,7 +7,7 @@
7
7
  * 必填规则来自 `configuration/nodes/utils.tsx:37-288` 与 `validate.ts:13-17`。
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.CONTEXT_KEYS = exports.FLOW_CODE_PATTERN = exports.NODE_ID_PATTERN = exports.KNOWN_START_WAYS = exports.MULTI_OUT_NODE_TYPES = exports.NODE_REQUIRED = exports.NODE_DEFAULTS = exports.NODE_FIELD_WHITELIST = void 0;
10
+ exports.CONTEXT_KEYS = exports.FLOW_CODE_PATTERN = exports.NODE_ID_PATTERN = exports.KNOWN_START_WAYS = exports.MULTI_OUT_NODE_TYPES = exports.NODE_REQUIRED_RULES = exports.NODE_DEFAULTS = exports.NODE_FIELD_WHITELIST = void 0;
11
11
  /** 节点专属字段白名单;未列出的 nodeType 视为未知类型(透传 config 并告警) */
12
12
  exports.NODE_FIELD_WHITELIST = {
13
13
  StartNode: ['startNodeDef'],
@@ -53,25 +53,166 @@ exports.NODE_DEFAULTS = {
53
53
  LoopNode: { loopDefine: { type: 'LoopArray' } },
54
54
  MessageNode: { grantPermission: true },
55
55
  };
56
- /** 发布前 IDE 强校验的必填字段(configuration/nodes/utils.tsx:37-288) */
57
- exports.NODE_REQUIRED = {
58
- AddDataNode: ['dataSourceVO', 'flow', 'fields'],
59
- QueryDataNode: ['target'],
60
- UpdateDataNode: ['bindNode'],
61
- DeleteDataNode: ['bindNode'],
62
- FilterDataNode: ['bindNode'],
63
- BranchNode: ['conditionType', 'flowWay'],
64
- SubActionNode: ['actionFlowCode'],
65
- InterfaceNode: ['apiType', 'apiMethodPath'],
66
- ExecuteNode: ['executeCode'],
67
- QueryStatisticsNode: ['target'],
68
- AddOrUpdateNode: ['dataSourceVO', 'flow', 'fields'],
69
- LoopNode: ['loopDefine'],
70
- AssignValueNode: ['assignValueRuleConfigs'],
71
- CreateKmReviewNode: ['kmReviewTemplate', 'fdSubject'],
72
- FlowBotsNode: ['flowBotsCode'],
73
- MessageNode: ['subject', 'messageTarget'],
74
- MessageEliminateNode: ['eliminateRange', 'eliminateType'],
56
+ const record = (value) => value && typeof value === 'object' && !Array.isArray(value)
57
+ ? value
58
+ : {};
59
+ /** 节点是否有入参 Schema(决定参数映射是否必填) */
60
+ const hasInParam = (ctx) => {
61
+ const inParam = record(record(ctx.node.context).context_fdInParam);
62
+ return Object.keys(record(inParam.properties)).length > 0;
63
+ };
64
+ /** Execute/Interface/SubAction/FlowBots 共用的「code + 参数映射」规则 */
65
+ const mappingRules = (codeField, codeMessage) => [
66
+ { field: codeField, message: codeMessage },
67
+ {
68
+ field: 'paramMappingWay',
69
+ message: '参数映射不能为空',
70
+ read: (config) => record(config.inputParamMapping).paramMappingWay ?? 'point',
71
+ skipIf: (ctx) => !hasInParam(ctx),
72
+ },
73
+ {
74
+ field: 'fdParam',
75
+ message: '参数映射不能为空',
76
+ hint: 'inputParamMapping 需提供 valueFormula(非 point)或非空 pointMappings',
77
+ read: (config) => {
78
+ const mapping = record(config.inputParamMapping);
79
+ if (Object.keys(mapping).length === 0)
80
+ return undefined;
81
+ if ((mapping.paramMappingWay ?? 'point') !== 'point')
82
+ return mapping.valueFormula;
83
+ const points = mapping.pointMappings;
84
+ return Array.isArray(points) && points.length > 0 ? points : undefined;
85
+ },
86
+ skipIf: (ctx) => !hasInParam(ctx),
87
+ },
88
+ ];
89
+ /** 分类表单 / 未选提交方式时,AddData 不要求选流程 */
90
+ const skipFlowRequired = (ctx) => {
91
+ if (!ctx.config.commitType)
92
+ return true;
93
+ return String(record(ctx.config.dataSourceVO).entityId ?? '').includes('sys-modeling-main');
94
+ };
95
+ exports.NODE_REQUIRED_RULES = {
96
+ AddDataNode: [
97
+ { field: 'dataSourceVO', message: '目标表单不能为空' },
98
+ { field: 'flow', message: '选择流程不能为空', skipIf: skipFlowRequired },
99
+ { field: 'fields', message: '表单字段不能为空' },
100
+ ],
101
+ AddOrUpdateNode: [
102
+ { field: 'dataSourceVO', message: '选择表单不能为空' },
103
+ { field: 'flow', message: '选择流程不能为空', skipIf: skipFlowRequired },
104
+ ],
105
+ QueryDataNode: [{ field: 'target', message: '查询对象不能为空' }],
106
+ QueryLoopNode: [{ field: 'target', message: '查询对象不能为空' }],
107
+ UpdateDataNode: [{ field: 'bindNode', message: '请选择节点' }],
108
+ DeleteDataNode: [{ field: 'bindNode', message: '请选择删除节点' }],
109
+ FilterDataNode: [{ field: 'bindNode', message: '过滤节点不能为空' }],
110
+ BranchNode: [
111
+ { field: 'conditionType', message: '请设置条件' },
112
+ { field: 'flowWay', message: '请设置流转方式' },
113
+ {
114
+ field: 'formulaConfig',
115
+ message: '请设置公式定义',
116
+ // rule 模式免公式;有出边时编译器会按 branchFormulas 自动生成
117
+ skipIf: (ctx) => ctx.config.conditionType === 'rule' || ctx.outgoing > 0,
118
+ },
119
+ { field: 'ruleConfig', message: '请设置匹配规则', skipIf: (ctx) => ctx.config.conditionType === 'formula' },
120
+ ],
121
+ LoopNode: [
122
+ { field: 'loopType', message: '请设置循环方式', read: (config) => record(config.loopDefine).type },
123
+ ],
124
+ TransformNode: [
125
+ {
126
+ field: 'outParam',
127
+ message: '出参设置不能为空',
128
+ hint: 'config.outParam 需为输出参数 JSON Schema 字符串;可用 llc flow templates 从真实流程抽取',
129
+ },
130
+ {
131
+ field: 'paramMappingWay',
132
+ message: '参数映射不能为空',
133
+ read: (config) => record(config.outputParamMapping).paramMappingWay ?? 'point',
134
+ skipIf: (ctx) => !ctx.config.outParam,
135
+ },
136
+ {
137
+ field: 'fdParam',
138
+ message: '参数映射不能为空',
139
+ hint: 'outputParamMapping 需提供 valueFormula(非 point)或非空 pointMappings',
140
+ read: (config) => {
141
+ const mapping = record(config.outputParamMapping);
142
+ if (Object.keys(mapping).length === 0)
143
+ return undefined;
144
+ if ((mapping.paramMappingWay ?? 'point') !== 'point')
145
+ return mapping.valueFormula;
146
+ const points = mapping.pointMappings;
147
+ return Array.isArray(points) && points.length > 0 ? points : undefined;
148
+ },
149
+ skipIf: (ctx) => !ctx.config.outParam,
150
+ },
151
+ ],
152
+ AssignValueNode: [{ field: 'assignValueRuleConfigs', message: '赋值规则不能为空' }],
153
+ ExecuteNode: mappingRules('executeCode', '集成函数不能为空'),
154
+ InterfaceNode: mappingRules('apiMethodPath', '内部接口不能为空'),
155
+ SubActionNode: mappingRules('actionFlowCode', '子动作不能为空'),
156
+ FlowBotsNode: mappingRules('flowBotsCode', 'flowBots不能为空'),
157
+ MessageNode: [
158
+ { field: 'notifyType', message: '通知方式不能为空' },
159
+ { field: 'subject', message: '消息内容不能为空', skipIf: (ctx) => ctx.config.subjectType === 'TEMPLATE' },
160
+ {
161
+ field: 'templateCode',
162
+ message: '请选择通知模板',
163
+ skipIf: (ctx) => {
164
+ const subjectType = ctx.config.subjectType;
165
+ return subjectType === undefined || subjectType === '' || subjectType === 'CUSTOM';
166
+ },
167
+ },
168
+ {
169
+ field: 'linkPage',
170
+ message: '视图不能为空',
171
+ skipIf: (ctx) => {
172
+ const linkType = record(ctx.config.messageLink).linkType;
173
+ return linkType === undefined || linkType === 'default';
174
+ },
175
+ },
176
+ {
177
+ field: 'linkUrl',
178
+ message: '链接不能为空',
179
+ read: (config) => record(config.messageLink).linkUrl,
180
+ skipIf: (ctx) => {
181
+ const linkType = record(ctx.config.messageLink).linkType;
182
+ return linkType === undefined || linkType === 'view' || linkType === 'edit';
183
+ },
184
+ },
185
+ ],
186
+ MessageEliminateNode: [{ field: 'eventCode', message: '事件编码不允许为空' }],
187
+ QueryStatisticsNode: [
188
+ { field: 'target', message: '查询目标不能为空' },
189
+ {
190
+ field: 'statisticsConfig',
191
+ message: '统计维度和统计项不能全为空',
192
+ read: (config) => {
193
+ const dimensions = Array.isArray(config.statisticsDimensions) ? config.statisticsDimensions : [];
194
+ const items = Array.isArray(config.statisticsItems) ? config.statisticsItems : [];
195
+ return dimensions.length + items.length > 0 ? { dimensions, items } : undefined;
196
+ },
197
+ },
198
+ ],
199
+ CreateKmReviewNode: [
200
+ { field: 'kmReviewTemplate', message: '流程模板不能为空' },
201
+ { field: 'fdSubject', message: '流程标题不能为空' },
202
+ {
203
+ field: 'orgElement',
204
+ message: '创建人不能为空',
205
+ read: (config) => record(config.fdCreator).orgElement,
206
+ skipIf: (ctx) => (record(ctx.config.fdCreator).valueRule ?? 'Fixed') === 'Var',
207
+ },
208
+ {
209
+ field: 'formula',
210
+ message: '创建人不能为空',
211
+ read: (config) => record(config.fdCreator).formula,
212
+ skipIf: (ctx) => (record(ctx.config.fdCreator).valueRule ?? 'Fixed') === 'Fixed',
213
+ },
214
+ { field: 'fields', message: '表单字段不能为空' },
215
+ ],
75
216
  };
76
217
  /** 允许多条出边的节点(validateCommonVertex 的 getwayMap,另有 StartNode) */
77
218
  exports.MULTI_OUT_NODE_TYPES = new Set(['BranchNode', 'parallelGateway', 'StartNode']);
@@ -17,6 +17,16 @@ const err = (code, message, nodeId) => ({
17
17
  const warn = (code, message, nodeId) => ({
18
18
  level: 'warn', code, message, nodeId,
19
19
  });
20
+ /** 与平台 validateRequired 一致:空串、空数组、null/undefined 视为未填 */
21
+ function isBlank(value) {
22
+ if (value === undefined || value === null)
23
+ return true;
24
+ if (typeof value === 'string')
25
+ return value.trim() === '';
26
+ if (Array.isArray(value))
27
+ return value.length === 0;
28
+ return false;
29
+ }
20
30
  /** 基础信息校验 */
21
31
  function validateSpec(spec) {
22
32
  const out = [];
@@ -80,9 +90,21 @@ function validateGraph(graph, spec, startWay = 'StartActiveNode') {
80
90
  }
81
91
  // 与编译器一致:默认值(如 BranchNode.conditionType / flowWay)也算已填
82
92
  const config = { ...(schema_1.NODE_DEFAULTS[node.type] ?? {}), ...(node.config ?? {}) };
83
- for (const field of schema_1.NODE_REQUIRED[node.type] ?? []) {
84
- if (config[field] === undefined || config[field] === null || config[field] === '') {
85
- out.push(err('NODE_FIELD_REQUIRED', `节点 ${node.id}(${node.type})缺少必填字段 ${field}`, node.id));
93
+ const outgoing = graph.edges.filter((edge) => edge.from === node.id).length;
94
+ const ruleContext = { config, node: { context: node.context }, outgoing };
95
+ for (const rule of schema_1.NODE_REQUIRED_RULES[node.type] ?? []) {
96
+ if (rule.skipIf?.(ruleContext))
97
+ continue;
98
+ const value = rule.read ? rule.read(config) : config[rule.field];
99
+ if (isBlank(value)) {
100
+ out.push({
101
+ level: 'error',
102
+ code: 'NODE_FIELD_REQUIRED',
103
+ message: `节点 ${node.id}(${node.type})缺少必填字段 ${rule.field}:${rule.message}`,
104
+ nodeId: node.id,
105
+ field: rule.field,
106
+ ...(rule.hint ? { hint: rule.hint } : {}),
107
+ });
86
108
  }
87
109
  }
88
110
  }
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ const locale_1 = require("./commands/locale");
12
12
  const operation_1 = require("./commands/operation");
13
13
  const template_1 = require("./commands/template");
14
14
  const logger_1 = require("./utils/logger");
15
- const VERSION = '2.7.0';
15
+ const VERSION = '2.8.1';
16
16
  const program = new commander_1.Command();
17
17
  exports.program = program;
18
18
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lan-low-code-cli",
3
- "version": "2.7.0",
3
+ "version": "2.8.1",
4
4
  "description": "lan-low-code cli:低代码表单引擎命令行工具(创建表单、获取可用字段、自动完善表单字段)",
5
5
  "keywords": [
6
6
  "low-code",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: lan-low-code-cli
3
- version: 2.7.0
3
+ version: 2.8.1
4
4
  author: Pig George
5
5
  description: Operate the Landray low-code form engine through the `llc` CLI. Use when the user asks to create, inspect, modify, publish, or delete low-code apps and forms; generate field configs; configure layouts (grid/tabs), field permissions, options, i18n, or control actions (onChange/onLoad); or run pre-publish validation. Also use for troubleshooting form loading/rendering errors.
6
6
  tags:
@@ -93,11 +93,11 @@ Always run `llc <command> --help` before inventing flags — the CLI is the sour
93
93
  | `llc app list / create / forms / delete / delete-data` | business apps (`--code` allows letters/digits/hyphen only, **no underscore**) |
94
94
  | `llc form create / list / get / publish / autofill / delete / delete-data` | form lifecycle; `create --type category\|simpleCategory` builds a category form (no business fields; templates are created in the platform designer) |
95
95
  | `llc field list / gen-key / library` | available fields |
96
- | `llc operation list / get / create / update / delete / attach / detach` | form business operations (link, script, dialog), including attaching to list/view pages |
96
+ | `llc operation list / get / create / update / delete / attach / detach` | form business operations (link, script, dialog); `--trigger-flow <flowId>` binds an action flow, `--inc-params` supplies its input |
97
97
  | `llc template list / create / add-fields / delete` | category-form templates: create/append/delete templates, porting fields/views/permissions from an existing form (auto category node, bypasses official init) |
98
98
  | `llc flow spec / compile / validate` | **offline**: generate a Flow Spec, compile it into `fdDesign` with IDE-style layout, run offline validation |
99
99
  | `llc flow list / get / category / logs / schema / templates` | **read-only** action-flow queries; `templates` extracts golden node skeletons (coordinates stripped) |
100
- | `llc flow apply / diff / script / enable / disable / delete` | **write** action-flow lifecycle; `apply` previews by default and needs `--yes` + `--app-code` |
100
+ | `llc flow apply / diff / script / run / enable / disable / delete` | **write** action-flow lifecycle; `apply` previews by default and needs `--yes` + `--app-code`; `run` executes a bound button via `sysModelingMain/other` |
101
101
  | `llc control list / show` | controls installed in the environment (basis for `--add-field` third segment) |
102
102
  | `llc locale list` | languages with translations available (from control i18n resources) |
103
103
  | `llc config set / show / clear` | global credentials and defaults |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: lan-low-code-cli
3
- version: 2.7.0
3
+ version: 2.8.1
4
4
  author: Pig George
5
5
  description: 通过 `llc` CLI 操作蓝凌低代码表单引擎。适用于:创建、查看、修改、发布或删除低代码应用与表单;生成字段配置;配置布局(栅格/页签)、字段权限、选项、多语言、控件动作(onChange/onLoad);或执行发布前校验、排查表单加载/渲染报错。
6
6
  tags:
@@ -90,11 +90,11 @@ llc config set --base-url http://<host>/data --token <X-AUTH-TOKEN> # 一次
90
90
  | `llc app list / create / forms / delete / delete-data` | 业务应用(`--code` 仅允许字母/数字/连字符,**不允许下划线**) |
91
91
  | `llc form create / list / get / publish / autofill / delete / delete-data` | 表单生命周期;`create --type category\|simpleCategory` 建分类表单(无业务字段,模板需在平台设计器创建) |
92
92
  | `llc field list / gen-key / library` | 可用字段 |
93
- | `llc operation list / get / create / update / delete / attach / detach` | 表单业务操作(链接、脚本、对话框),含挂载到列表/查看视图 |
93
+ | `llc operation list / get / create / update / delete / attach / detach` | 表单业务操作(链接、脚本、对话框);`--trigger-flow <动作流fdId>` 绑定动作流,`--inc-params` 传入参 |
94
94
  | `llc template list / create / add-fields / delete` | 分类表单模板:创建模板并把既有表单的字段/视图/权限移植进来,追加字段,或清理模板(自动建分类节点、跳过官方 init) |
95
95
  | `llc flow spec / compile / validate` | **纯离线**:生成 Flow Spec、编译出符合 IDE 编排布局的 `fdDesign`、离线校验 |
96
96
  | `llc flow list / get / category / logs / schema / templates` | **只读**查询动作流;`templates` 抽取 golden 节点骨架(已剥离坐标) |
97
- | `llc flow apply / diff / script / enable / disable / delete` | **写入**动作流全生命周期;`apply` 默认预览,需 `--yes` + `--app-code` 才提交 |
97
+ | `llc flow apply / diff / script / run / enable / disable / delete` | **写入**动作流全生命周期;`apply` 默认预览,需 `--yes` + `--app-code`;`run` 通过 `sysModelingMain/other` 执行绑定按钮 |
98
98
  | `llc control list / show` | 环境已安装控件(`--add-field` 第三段的取值依据) |
99
99
  | `llc locale list` | 环境中有现成译文的语言(来自控件多语言资源) |
100
100
  | `llc config set / show / clear` | 全局凭据与默认值 |
@@ -123,8 +123,8 @@ maintained by the platform; the CLI only lists them and manages **custom** opera
123
123
  |---|---|---|---|
124
124
  | `llc operation list` | `--form-id <fdId>` | `--keyword <kw>` `--page-no <n>=1` `--page-size <n>=100` `--json` | lists a form's operations (built-ins included) |
125
125
  | `llc operation get` | `--id <fdId>` | `--json` | detail; prints the parsed `fdConfig` |
126
- | `llc operation create` | `--form-id` `--name` `--code` `--type` | `--position list\|view` `--need-select` `--url` `--script <code\|@file>` `--dialog-id` `--desc` `--mobile-icon` `--dry-run` `--json` | creates an operation definition |
127
- | `llc operation update` | `--id <fdId>` | `--name` `--code` `--desc` `--position` `--type` `--url` `--script <code\|@file>` `--dialog-id` `--need-select`/`--no-need-select` `--enable`/`--disable` `--mobile-icon` `--dry-run` `--json` | edits an existing operation; `--type script` switches a button to script mode |
126
+ | `llc operation create` | `--form-id` `--name` `--code` | `--type link\|script\|dialog` **or** `--trigger-flow <flowId>`; `--position list\|view` `--need-select` `--url` `--script <code\|@file>` `--dialog-id` `--inc-params <json\|@file>` `--desc` `--mobile-icon` `--dry-run` `--json` | creates an operation definition; `--trigger-flow` binds an action flow (`fdTriggerType=3`) |
127
+ | `llc operation update` | `--id <fdId>` | `--name` `--code` `--desc` `--position` `--type` `--url` `--script <code\|@file>` `--dialog-id` `--trigger-flow <flowId>` `--inc-params` `--need-select`/`--no-need-select` `--enable`/`--disable` `--mobile-icon` `--dry-run` `--json` | edits an existing operation; `--type script` switches to script mode, `--trigger-flow` switches to action-flow trigger |
128
128
  | `llc operation delete` | `--id <fdId>` `--yes` | — | checks references (`listByRefer`) and refuses while still in use |
129
129
  | `llc operation attach` | `--form-id` `--operation-id` | `--target list\|view` `--view-id` `--platform desktop\|mobile` `--position top\|rightHand\|rightSide` | writes the button into the view `operationList`; auto-detects the single view when `--view-id` is omitted |
130
130
  | `llc operation detach` | `--form-id` `--operation-id` | `--target list\|view` `--view-id` `--platform` | removes the button from a view (definition kept) |
@@ -241,6 +241,7 @@ Application-scoped action flows. Every write command needs `--yes` and an explic
241
241
  | `llc flow apply` | `--spec` `--app-code` | `--draft` `--enable` `--resolve-schema` `--strict-schema` layout options `--force` `--yes` `--json` | **preview by default**; `--yes` calls `addPublish`/`addDraft` (or `update*` when the spec has `fdId`), then reads back |
242
242
  | `llc flow diff` | `--id` `--spec` | `--no-ignore-bounds` `--resolve-schema` `--strict-schema` layout options `--json` | compares local design with remote; ignores coordinates by default |
243
243
  | `llc flow script` | `--id` | `--out` `--json` | generates a `MKXFORM.flow_<fdCode>` button script |
244
+ | `llc flow run` | `--operation-id` `--yes` | `--view-id` `--doc-id` `--doc-ids` `--inc-params` `--json` | executes a button bound to a flow via `sysModelingMain/other`; returns `success` / `responseMsg` |
244
245
  | `llc flow enable` | `--id` | `--json` | `startup` |
245
246
  | `llc flow disable` | `--id` `--yes` | `--json` | `stop` |
246
247
  | `llc flow delete` | `--id` `--yes` | `--json` | checks `listByRefer` before deleting |
@@ -284,6 +285,17 @@ Application-scoped action flows. Every write command needs `--yes` and an explic
284
285
  - `fields` of `AddDataNode` / `AddOrUpdateNode` uses the platform's real shape
285
286
  (`{field:{...},value:{format,valueJson}}`); the CLI passes it through and only validates field
286
287
  names — it does **not** synthesise formulas. Use `llc flow templates` / `flow get` for golden shapes.
288
+ - **Per-node required fields** mirror the IDE rules table (`configuration/nodes/utils.tsx`) plus its
289
+ exemptions (`validate.ts`):
290
+ `TransformNode` requires `outParam` **and** `outputParamMapping` (`valueFormula`, or non-empty
291
+ `pointMappings`; mapping is exempt only when `outParam` is empty);
292
+ `ExecuteNode`/`InterfaceNode`/`SubActionNode`/`FlowBotsNode` require their code field
293
+ (`executeCode`/`apiMethodPath`/`actionFlowCode`/`flowBotsCode`) and a mapping when an inbound
294
+ Schema exists; `BranchNode` requires `conditionType`/`flowWay` plus the matching
295
+ `formulaConfig`/`ruleConfig` (auto-generated when branch edges exist); `LoopNode` requires
296
+ `loopDefine.type`; `MessageNode` validates `notifyType`/`subject`/`templateCode`/`linkPage`/`linkUrl`
297
+ by `subjectType` and `messageLink.linkType`; `AddDataNode`/`AddOrUpdateNode` skip `flow` for
298
+ category forms or when `commitType` is absent. Diagnostics carry `field` and `hint`; errors exit `1`.
287
299
  - Publishing derives `fdProcessActive` on the backend from `startNodeDef`; `fdStatus=1` means enabled.
288
300
  `flow logs` endpoints vary by deployment (`ModelingProcessLog/findAll` vs
289
301
  `sysModelingLog/findAllWithAppId`); use `--endpoint` if the defaults fail.
@@ -116,8 +116,8 @@ llc form create --name "简版分类表单" --app-id <appId> --type simpleCatego
116
116
  |---|---|---|---|
117
117
  | `llc operation list` | `--form-id <fdId>` | `--keyword <kw>` `--page-no <n>=1` `--page-size <n>=100` `--json` | 按表单列出操作(含内置) |
118
118
  | `llc operation get` | `--id <fdId>` | `--json` | 详情;默认展开 `fdConfig` JSON |
119
- | `llc operation create` | `--form-id` `--name` `--code` `--type` | `--position list\|view` `--need-select` `--url` `--script <code\|@file>` `--dialog-id` `--desc` `--mobile-icon` `--dry-run` `--json` | 新建操作定义 |
120
- | `llc operation update` | `--id <fdId>` | `--name` `--code` `--desc` `--position` `--type` `--url` `--script <code\|@file>` `--dialog-id` `--need-select`/`--no-need-select` `--enable`/`--disable` `--mobile-icon` `--dry-run` `--json` | 修改已有操作;`--type script` 可把按钮切换为脚本模式 |
119
+ | `llc operation create` | `--form-id` `--name` `--code` | `--type link\|script\|dialog` **或** `--trigger-flow <动作流fdId>`;`--position list\|view` `--need-select` `--url` `--script <code\|@file>` `--dialog-id` `--inc-params <json\|@file>` `--desc` `--mobile-icon` `--dry-run` `--json` | 新建操作定义;`--trigger-flow` 绑定动作流(`fdTriggerType=3`) |
120
+ | `llc operation update` | `--id <fdId>` | `--name` `--code` `--desc` `--position` `--type` `--url` `--script <code\|@file>` `--dialog-id` `--trigger-flow <动作流fdId>` `--inc-params` `--need-select`/`--no-need-select` `--enable`/`--disable` `--mobile-icon` `--dry-run` `--json` | 修改已有操作;`--type script` 切脚本、`--trigger-flow` 切动作流触发 |
121
121
  | `llc operation delete` | `--id <fdId>` `--yes` | — | 先查引用(`listByRefer`),被引用时拒绝删除 |
122
122
  | `llc operation attach` | `--form-id` `--operation-id` | `--target list\|view` `--view-id` `--platform desktop\|mobile` `--position top\|rightHand\|rightSide` | 把按钮写入视图 `operationList`;省略 `--view-id` 时自动定位唯一视图 |
123
123
  | `llc operation detach` | `--form-id` `--operation-id` | `--target list\|view` `--view-id` `--platform` | 从视图移除按钮(保留操作定义) |
@@ -230,6 +230,7 @@ llc operation detach --form-id <fdId> --operation-id <operationId> #
230
230
  | `llc flow apply` | `--spec` `--app-code` | `--draft` `--enable` `--resolve-schema` `--strict-schema` 布局选项 `--force` `--yes` `--json` | **默认预览**;`--yes` 才调用 `addPublish`/`addDraft`(spec 带 `fdId` 时走 `update*`),随后回读 |
231
231
  | `llc flow diff` | `--id` `--spec` | `--no-ignore-bounds` `--resolve-schema` `--strict-schema` 布局选项 `--json` | 对比本地与远端设计;默认忽略坐标 |
232
232
  | `llc flow script` | `--id` | `--out` `--json` | 生成 `MKXFORM.flow_<fdCode>` 按钮脚本 |
233
+ | `llc flow run` | `--operation-id` `--yes` | `--view-id` `--doc-id` `--doc-ids` `--inc-params` `--json` | 通过绑定动作流的业务操作执行一次(`sysModelingMain/other`),返回 `success` / `responseMsg` |
233
234
  | `llc flow enable` | `--id` | `--json` | `startup` |
234
235
  | `llc flow disable` | `--id` `--yes` | `--json` | `stop` |
235
236
  | `llc flow delete` | `--id` `--yes` | `--json` | 删除前先查 `listByRefer` |
@@ -271,6 +272,16 @@ llc operation detach --form-id <fdId> --operation-id <operationId> #
271
272
  - `AddDataNode` / `AddOrUpdateNode` 的 `fields` 使用平台真实结构
272
273
  (`{field:{...},value:{format,valueJson}}`);CLI 原样透传并只校验字段名,**不臆造公式**。
273
274
  需要真实结构请用 `llc flow templates` / `flow get` 抽取 golden 骨架。
275
+ - **节点级必填校验**复刻 IDE 规则表(`configuration/nodes/utils.tsx`)及其豁免逻辑(`validate.ts`):
276
+ `TransformNode` 必填 `outParam` **和** `outputParamMapping`(`valueFormula` 或非空
277
+ `pointMappings`;仅当 `outParam` 为空时豁免映射);`ExecuteNode` / `InterfaceNode` /
278
+ `SubActionNode` / `FlowBotsNode` 必填各自的编码字段(`executeCode` / `apiMethodPath` /
279
+ `actionFlowCode` / `flowBotsCode`),且存在入参 Schema 时必填参数映射;`BranchNode` 必填
280
+ `conditionType` / `flowWay` 及对应的 `formulaConfig` / `ruleConfig`(有分支出边时自动生成);
281
+ `LoopNode` 必填 `loopDefine.type`;`MessageNode` 按 `subjectType` 与 `messageLink.linkType`
282
+ 校验 `notifyType` / `subject` / `templateCode` / `linkPage` / `linkUrl`;
283
+ `AddDataNode` / `AddOrUpdateNode` 在分类表单或未填 `commitType` 时豁免 `flow`。
284
+ 诊断会带 `field` 与 `hint`,有错误时退出码为 `1`。
274
285
  - 发布时后端从 `startNodeDef` 派生 `fdProcessActive`;`fdStatus=1` 表示已启用。
275
286
  `flow logs` 端点因部署而异(`ModelingProcessLog/findAll` 与
276
287
  `sysModelingLog/findAllWithAppId`);默认都失败时用 `--endpoint` 指定。