lan-low-code-cli 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -1
- package/dist/api/flow.js +119 -0
- package/dist/api/operation.js +8 -0
- package/dist/commands/flow.js +817 -0
- package/dist/commands/operation.js +123 -14
- package/dist/generator/flow/compiler.js +397 -0
- package/dist/generator/flow/index.js +22 -0
- package/dist/generator/flow/layout.js +136 -0
- package/dist/generator/flow/schema-resolver.js +133 -0
- package/dist/generator/flow/schema.js +96 -0
- package/dist/generator/flow/types.js +10 -0
- package/dist/generator/flow/validate.js +129 -0
- package/dist/index.js +12 -1
- package/package.json +1 -1
- package/skills/lan-low-code-cli/SKILL.md +14 -4
- package/skills/lan-low-code-cli/SKILL.zh-CN.md +13 -3
- package/skills/lan-low-code-cli/references/cli-contract.md +76 -5
- package/skills/lan-low-code-cli/references/cli-contract.zh-CN.md +72 -4
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 动作流编排布局引擎
|
|
4
|
+
*
|
|
5
|
+
* 源码依据(均来自 action-flow-ide):
|
|
6
|
+
* - 尺寸表 styleSheet.ts:339-347:BranchNode 56×56,其余 180×38;回显时宽高被强制覆盖
|
|
7
|
+
* - 种子 mxgraph/utils.tsx:93-135:N1 在 (C-90, 80)、N2 在 (C-90, 166),L1 直连
|
|
8
|
+
* - 向下插入 R4(mxgraph/utils.tsx:422-465,1408-1428):distance = h_prev + 52,
|
|
9
|
+
* cy_new = cy_prev + distance,下游整体 y += distance
|
|
10
|
+
* - 左右插入 R5(mxgraph/utils.tsx:442-458,568-584):与源节点同层中心线,
|
|
11
|
+
* x = C - w/2 ± 2*distance
|
|
12
|
+
* - 无整图自动布局、无后端布局服务;坐标是持久化契约,IDE 回显原样使用
|
|
13
|
+
*
|
|
14
|
+
* 因此这里按「rank(逻辑层)+ lane(层内泳道)+ 中心线」复刻编排习惯:
|
|
15
|
+
* 主链居中,分支的直接后继与分支节点同层、左右对称,汇聚节点下移一层。
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.DEFAULT_CENTER_X = exports.MIN_GAP = exports.RANK_PADDING = exports.START_Y = exports.NODE_SIZE = void 0;
|
|
19
|
+
exports.nodeSize = nodeSize;
|
|
20
|
+
exports.layoutGraph = layoutGraph;
|
|
21
|
+
/** 与 styleSheet.ts nodeBounds 一致 */
|
|
22
|
+
exports.NODE_SIZE = {
|
|
23
|
+
BranchNode: { width: 56, height: 56 },
|
|
24
|
+
general: { width: 180, height: 38 },
|
|
25
|
+
};
|
|
26
|
+
/** 主画布开始节点的 y(mxgraph/utils.tsx:130) */
|
|
27
|
+
exports.START_Y = 80;
|
|
28
|
+
/** R4 的层间距增量:distance = h_prev + 52 */
|
|
29
|
+
exports.RANK_PADDING = 52;
|
|
30
|
+
/**
|
|
31
|
+
* 最小边缘间隙,默认 40。
|
|
32
|
+
*
|
|
33
|
+
* IDE 常规编排的垂直间隙由 R4 推导:gap = 52 + (h_prev - h_new)/2;
|
|
34
|
+
* 38 高节点后紧跟 56 高 BranchNode 时最小为 52 - (56-38)/2 = 43px。
|
|
35
|
+
* 因此不能照搬 mxHierarchicalLayout 的 interRankCellSpacing=50(该值只在
|
|
36
|
+
* 折叠/展开时生效),否则正常分支流程会被误判为布局重叠。
|
|
37
|
+
*/
|
|
38
|
+
exports.MIN_GAP = 40;
|
|
39
|
+
/** W=1920 时 (1920-562)/2;CLI 无浏览器宽度,可用 --center-x 覆盖 */
|
|
40
|
+
exports.DEFAULT_CENTER_X = 679;
|
|
41
|
+
function nodeSize(nodeType) {
|
|
42
|
+
return exports.NODE_SIZE[nodeType] ?? exports.NODE_SIZE.general;
|
|
43
|
+
}
|
|
44
|
+
const round = (v) => Math.round(v * 100) / 100;
|
|
45
|
+
function layoutGraph(graph, options = {}) {
|
|
46
|
+
const mode = options.mode ?? 'ide';
|
|
47
|
+
const centerX = options.centerX ?? exports.DEFAULT_CENTER_X;
|
|
48
|
+
const padding = options.rankPadding ?? exports.RANK_PADDING;
|
|
49
|
+
const minGap = options.minGap ?? exports.MIN_GAP;
|
|
50
|
+
const diagnostics = [];
|
|
51
|
+
const bounds = {};
|
|
52
|
+
// passthrough:原样沿用已有坐标(P1 克隆 / UI 导出流程回写)
|
|
53
|
+
if (mode === 'passthrough') {
|
|
54
|
+
for (const node of graph.nodes) {
|
|
55
|
+
const size = nodeSize(node.type);
|
|
56
|
+
const given = node.bounds;
|
|
57
|
+
if (!given || typeof given.x !== 'number' || typeof given.y !== 'number') {
|
|
58
|
+
diagnostics.push({
|
|
59
|
+
level: 'error',
|
|
60
|
+
code: 'LAYOUT_MISSING_BOUNDS',
|
|
61
|
+
message: `passthrough 模式要求节点提供 bounds.{x,y}:${node.id}`,
|
|
62
|
+
nodeId: node.id,
|
|
63
|
+
});
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
bounds[node.id] = {
|
|
67
|
+
x: given.x,
|
|
68
|
+
y: given.y,
|
|
69
|
+
width: size.width,
|
|
70
|
+
height: size.height,
|
|
71
|
+
relative: given.relative ?? false,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return { bounds, rankCenters: [], diagnostics };
|
|
75
|
+
}
|
|
76
|
+
const maxRank = graph.nodes.reduce((max, n) => Math.max(max, n._rank), 0);
|
|
77
|
+
// 每层取最高节点,决定「该层到下一层」的间距:38→90、56→108
|
|
78
|
+
const rankHeight = [];
|
|
79
|
+
for (let r = 0; r <= maxRank; r++) {
|
|
80
|
+
const heights = graph.nodes.filter((n) => n._rank === r).map((n) => nodeSize(n.type).height);
|
|
81
|
+
rankHeight[r] = heights.length > 0 ? Math.max(...heights) : exports.NODE_SIZE.general.height;
|
|
82
|
+
}
|
|
83
|
+
const gapOf = (r) => (rankHeight[r] ?? exports.NODE_SIZE.general.height) + padding;
|
|
84
|
+
// 中心线:cy(0) = 80 + h_start/2 = 99;cy(r) = cy(r-1) + gap(r-1)
|
|
85
|
+
const rankCenters = [];
|
|
86
|
+
const startNode = graph.nodes.find((n) => n._isStart);
|
|
87
|
+
rankCenters[0] = exports.START_Y + (startNode ? nodeSize(startNode.type).height : exports.NODE_SIZE.general.height) / 2;
|
|
88
|
+
for (let r = 1; r <= maxRank; r++) {
|
|
89
|
+
rankCenters[r] = rankCenters[r - 1] + gapOf(r - 1);
|
|
90
|
+
}
|
|
91
|
+
for (const node of graph.nodes) {
|
|
92
|
+
const size = nodeSize(node.type);
|
|
93
|
+
const r = node._rank;
|
|
94
|
+
// R5 的 ±2*distance:distance 取当前层最高节点对应的层间距
|
|
95
|
+
const laneGap = options.laneGap ?? 2 * gapOf(r);
|
|
96
|
+
const x = centerX - size.width / 2 + node._lane * laneGap;
|
|
97
|
+
const y = (rankCenters[r] ?? rankCenters[rankCenters.length - 1]) - size.height / 2;
|
|
98
|
+
bounds[node.id] = {
|
|
99
|
+
x: round(x),
|
|
100
|
+
y: round(y),
|
|
101
|
+
width: size.width,
|
|
102
|
+
height: size.height,
|
|
103
|
+
relative: false,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// 矩形重叠检查(含最小间隙)
|
|
107
|
+
const placed = graph.nodes.filter((n) => bounds[n.id]);
|
|
108
|
+
for (let i = 0; i < placed.length; i++) {
|
|
109
|
+
for (let j = i + 1; j < placed.length; j++) {
|
|
110
|
+
const a = bounds[placed[i].id];
|
|
111
|
+
const b = bounds[placed[j].id];
|
|
112
|
+
const horizontal = a.x < b.x + b.width + minGap && b.x < a.x + a.width + minGap;
|
|
113
|
+
const vertical = a.y < b.y + b.height + minGap && b.y < a.y + a.height + minGap;
|
|
114
|
+
if (horizontal && vertical) {
|
|
115
|
+
diagnostics.push({
|
|
116
|
+
level: 'error',
|
|
117
|
+
code: 'LAYOUT_OVERLAP',
|
|
118
|
+
message: `节点 ${placed[i].id} 与 ${placed[j].id} 的布局重叠或间距不足 ${minGap}px`,
|
|
119
|
+
nodeId: placed[j].id,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
for (const node of placed) {
|
|
125
|
+
const b = bounds[node.id];
|
|
126
|
+
if (b.x < 0 || b.y < 0) {
|
|
127
|
+
diagnostics.push({
|
|
128
|
+
level: 'warn',
|
|
129
|
+
code: 'LAYOUT_NEGATIVE',
|
|
130
|
+
message: `节点 ${node.id} 坐标为负 (${b.x}, ${b.y}),IDE 中可能显示不全;可增大 --center-x / --lane-gap`,
|
|
131
|
+
nodeId: node.id,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { bounds, rankCenters, diagnostics };
|
|
136
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SchemaResolver:联网补齐动作流节点的运行时上下文(extraAttr.context_*)
|
|
4
|
+
*
|
|
5
|
+
* 真实环境实测结论(2026-11-10,mkylxt):
|
|
6
|
+
* - `getSourceJsonSchema` / `getProcessDataOutJsonSchema` 的 entityId 必须带
|
|
7
|
+
* `sys-modeling#` 前缀,裸 id 返回 HTTP 500;返回值即 JSON Schema。
|
|
8
|
+
* - `getSourceFields` / `getConditionFields` / `loadDataModel` 用裸 id 正常。
|
|
9
|
+
* - 平台保存的 `extraAttr.context_bindFormId` 形如 `sys-modeling#<fdId>`。
|
|
10
|
+
* - 前端 `edit/content.tsx:55-87` 的 tempFormatFn 也会在打开编辑器时自动补
|
|
11
|
+
* extraAttr,因此这里采用 best-effort:调用失败只告警,不阻断发布。
|
|
12
|
+
*
|
|
13
|
+
* 不做的事:不臆造 `fields` 的公式映射。真实 fields 是
|
|
14
|
+
* `{ field:{name,label,tableName,tableType,type}, value:{format:'formula'|'fixed',valueJson:'...'} }`
|
|
15
|
+
* 数组,公式引用形如 `${data.node.N1.fd_xxx}`,必须由 spec 原样提供或从
|
|
16
|
+
* golden 模板复制;本模块只校验字段名是否在目标表 Schema 中存在。
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.resolveFlowSchema = resolveFlowSchema;
|
|
20
|
+
const flow_1 = require("../../api/flow");
|
|
21
|
+
function diagnose(out, level, code, message, nodeId) {
|
|
22
|
+
out.push({ level, code, message, nodeId });
|
|
23
|
+
}
|
|
24
|
+
async function tryCall(fn, label, out, strict, nodeId) {
|
|
25
|
+
try {
|
|
26
|
+
return await fn();
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
diagnose(out, strict ? 'error' : 'warn', 'SCHEMA_FETCH_FAILED', `${label} 调用失败:${err.message.split('\n')[0]}`, nodeId);
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** 从节点扁平字段里取出目标实体 id(真实结构见 startNodeDef / dataSourceVO / target) */
|
|
34
|
+
function entityIdOf(node) {
|
|
35
|
+
const candidates = [
|
|
36
|
+
node.startNodeDef,
|
|
37
|
+
node.dataSourceVO,
|
|
38
|
+
node.target,
|
|
39
|
+
];
|
|
40
|
+
for (const candidate of candidates) {
|
|
41
|
+
const value = candidate?.entityId;
|
|
42
|
+
if (typeof value === 'string' && value.trim())
|
|
43
|
+
return value.trim();
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
function parseInParam(raw, out, nodeId) {
|
|
48
|
+
if (typeof raw !== 'string')
|
|
49
|
+
return raw;
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(raw);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
diagnose(out, 'warn', 'SCHEMA_INPARAM_INVALID', 'startNodeDef.inParam 不是合法 JSON 字符串,已跳过注入', nodeId);
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** 校验 fields[].field.name 是否存在于目标表 Schema(不改写字段结构) */
|
|
59
|
+
function validateFieldNames(node, schema, out, nodeId) {
|
|
60
|
+
if (!schema || !Array.isArray(node.fields))
|
|
61
|
+
return;
|
|
62
|
+
const properties = schema.properties;
|
|
63
|
+
if (!properties)
|
|
64
|
+
return;
|
|
65
|
+
for (const item of node.fields) {
|
|
66
|
+
const field = item?.field;
|
|
67
|
+
const name = field?.name;
|
|
68
|
+
if (typeof name === 'string' && !(name in properties)) {
|
|
69
|
+
diagnose(out, 'warn', 'SCHEMA_FIELD_MISSING', `字段 ${name} 不在目标表 Schema 中,请核对 fdName`, nodeId);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function resolveFlowSchema(client, spec, result, options = {}) {
|
|
74
|
+
const strict = Boolean(options.strict);
|
|
75
|
+
const diagnostics = [...result.diagnostics];
|
|
76
|
+
const startWay = spec.startWay ?? 'StartActiveNode';
|
|
77
|
+
const resolveNode = async (node, isRootCanvas) => {
|
|
78
|
+
const nodeId = String(node.id ?? '?');
|
|
79
|
+
const extra = (node.extraAttr ?? {});
|
|
80
|
+
node.extraAttr = extra;
|
|
81
|
+
const entityId = entityIdOf(node);
|
|
82
|
+
const isStart = node.nodeType === 'StartNode' && isRootCanvas;
|
|
83
|
+
let schema;
|
|
84
|
+
if (entityId) {
|
|
85
|
+
const qualified = (0, flow_1.qualifyEntityId)(entityId);
|
|
86
|
+
if (isStart && startWay === 'StartDataNode') {
|
|
87
|
+
const fetched = await tryCall(() => flow_1.flowApi.getDataOutJsonSchema(client, entityId), 'getProcessDataOutJsonSchema', diagnostics, strict, nodeId);
|
|
88
|
+
schema = fetched;
|
|
89
|
+
if (fetched) {
|
|
90
|
+
const sourceForm = fetched.properties?.sourceForm;
|
|
91
|
+
if (extra.context_fdInParam === undefined)
|
|
92
|
+
extra.context_fdInParam = fetched;
|
|
93
|
+
if (extra.context_nodeParam === undefined)
|
|
94
|
+
extra.context_nodeParam = sourceForm ?? null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (!isStart) {
|
|
98
|
+
const fetched = await tryCall(() => flow_1.flowApi.getJsonSchema(client, entityId), 'getSourceJsonSchema', diagnostics, strict, nodeId);
|
|
99
|
+
schema = fetched;
|
|
100
|
+
if (fetched && extra.context_outputParam === undefined) {
|
|
101
|
+
extra.context_outputParam = fetched;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (extra.context_bindFormId === undefined)
|
|
105
|
+
extra.context_bindFormId = qualified;
|
|
106
|
+
}
|
|
107
|
+
if (isStart) {
|
|
108
|
+
const startDef = node.startNodeDef;
|
|
109
|
+
if (extra.context_fdInParam === undefined && startDef?.inParam !== undefined) {
|
|
110
|
+
const parsed = parseInParam(startDef.inParam, diagnostics, nodeId);
|
|
111
|
+
if (parsed !== undefined)
|
|
112
|
+
extra.context_fdInParam = parsed;
|
|
113
|
+
}
|
|
114
|
+
if (extra.context_isMulti === undefined) {
|
|
115
|
+
extra.context_isMulti = startDef?.dataType === 2;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
validateFieldNames(node, schema, diagnostics, nodeId);
|
|
119
|
+
const subProcess = node.subProcess;
|
|
120
|
+
if (subProcess && Array.isArray(subProcess.nodes)) {
|
|
121
|
+
for (const child of subProcess.nodes)
|
|
122
|
+
await resolveNode(child, false);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
for (const node of result.design.nodes) {
|
|
126
|
+
await resolveNode(node, true);
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
...result,
|
|
130
|
+
fdDesign: JSON.stringify(result.design),
|
|
131
|
+
diagnostics,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 动作流节点元数据表
|
|
4
|
+
*
|
|
5
|
+
* 逐 case 翻译 `action-flow-ide/enter.tsx:217-336` 的 generateNodeData:
|
|
6
|
+
* IDE 保存时只挑每种 nodeType 的专属字段,其余字段丢弃。
|
|
7
|
+
* 必填规则来自 `configuration/nodes/utils.tsx:37-288` 与 `validate.ts:13-17`。
|
|
8
|
+
*/
|
|
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;
|
|
11
|
+
/** 节点专属字段白名单;未列出的 nodeType 视为未知类型(透传 config 并告警) */
|
|
12
|
+
exports.NODE_FIELD_WHITELIST = {
|
|
13
|
+
StartNode: ['startNodeDef'],
|
|
14
|
+
EndNode: ['outParamType', 'outParam', 'outputParamMapping'],
|
|
15
|
+
BranchNode: ['conditionType', 'flowWay', 'defaultTargetRef', 'ruleConfig', 'formulaConfig'],
|
|
16
|
+
LoopNode: ['loopDefine', 'subProcess'],
|
|
17
|
+
QueryLoopNode: [
|
|
18
|
+
'target', 'conditions', 'sortType', 'containsDeleted', 'executeTime',
|
|
19
|
+
'loopDefine', 'context_outputParam', 'limitTime',
|
|
20
|
+
],
|
|
21
|
+
FilterDataNode: ['bindNode', 'formula'],
|
|
22
|
+
MessageNode: [
|
|
23
|
+
'bindNode', 'subject', 'notifyType', 'todoType', 'messageTarget', 'messageLink',
|
|
24
|
+
'eventCode', 'codeValue', 'notifyCategoryCode', 'subjectType', 'templateCode',
|
|
25
|
+
'templateFields', 'grantPermission',
|
|
26
|
+
],
|
|
27
|
+
AddDataNode: ['bindNode', 'dataSourceVO', 'flow', 'commitType', 'fields', 'autoMatchField', 'fastExecute'],
|
|
28
|
+
DeleteDataNode: ['bindNode'],
|
|
29
|
+
QueryDataNode: ['target', 'conditions', 'sorts', 'resultType', 'maxSize', 'containsDeleted'],
|
|
30
|
+
UpdateDataNode: ['bindNode', 'fields', 'fastExecute'],
|
|
31
|
+
CreateKmReviewNode: ['bindNode', 'kmReviewTemplate', 'fdCreator', 'fdSubject', 'commitType', 'fields'],
|
|
32
|
+
AddOrUpdateNode: [
|
|
33
|
+
'bindNode', 'dataSourceVO', 'flow', 'commitType', 'fields', 'condition',
|
|
34
|
+
'autoMatchField', 'fastExecute',
|
|
35
|
+
],
|
|
36
|
+
AssignValueNode: ['assignValueRuleConfigs'],
|
|
37
|
+
TransformNode: ['outParamType', 'outParam', 'outputParamMapping'],
|
|
38
|
+
ExecuteNode: ['outParamType', 'executeCode', 'inputParamMapping'],
|
|
39
|
+
FlowBotsNode: ['outParamType', 'flowBotsCode', 'inputParamMapping'],
|
|
40
|
+
InterfaceNode: ['apiType', 'apiMethodPath', 'inputParamMapping'],
|
|
41
|
+
SubActionNode: ['outParam', 'outParamType', 'actionFlowCode', 'inputParamMapping'],
|
|
42
|
+
MessageEliminateNode: ['eliminateRange', 'eliminateType', 'eventCode', 'bindNode'],
|
|
43
|
+
QueryStatisticsNode: [
|
|
44
|
+
'nativeNodeType', 'resultType', 'target', 'conditions', 'sorts',
|
|
45
|
+
'columns', 'statisticsDimensions', 'statisticsItems',
|
|
46
|
+
],
|
|
47
|
+
DispatchNode: [],
|
|
48
|
+
};
|
|
49
|
+
/** 与 IDE 一致的缺省值(styleSheet.ts:404-445) */
|
|
50
|
+
exports.NODE_DEFAULTS = {
|
|
51
|
+
BranchNode: { conditionType: 'formula', flowWay: 'singleBranch' },
|
|
52
|
+
QueryDataNode: { resultType: 'single' },
|
|
53
|
+
LoopNode: { loopDefine: { type: 'LoopArray' } },
|
|
54
|
+
MessageNode: { grantPermission: true },
|
|
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'],
|
|
75
|
+
};
|
|
76
|
+
/** 允许多条出边的节点(validateCommonVertex 的 getwayMap,另有 StartNode) */
|
|
77
|
+
exports.MULTI_OUT_NODE_TYPES = new Set(['BranchNode', 'parallelGateway', 'StartNode']);
|
|
78
|
+
exports.KNOWN_START_WAYS = [
|
|
79
|
+
'StartScheduledNode',
|
|
80
|
+
'StartDataNode',
|
|
81
|
+
'StartEventNode',
|
|
82
|
+
'StartActiveNode',
|
|
83
|
+
];
|
|
84
|
+
/** 节点编码 /^[0-9a-zA-Z_]{2,32}$/(configuration/nodes/utils.tsx:12-30) */
|
|
85
|
+
exports.NODE_ID_PATTERN = /^[0-9a-zA-Z_]{2,32}$/;
|
|
86
|
+
/** 动作流编码 /^[a-zA-Z0-9_]{1,30}$/(topbar/index.tsx:262-279) */
|
|
87
|
+
exports.FLOW_CODE_PATTERN = /^[a-zA-Z0-9_]{1,30}$/;
|
|
88
|
+
/** extraAttr 六个上下文键(enter.tsx:383-390) */
|
|
89
|
+
exports.CONTEXT_KEYS = [
|
|
90
|
+
'context_bindFormId',
|
|
91
|
+
'context_bindNode',
|
|
92
|
+
'context_nodeParam',
|
|
93
|
+
'context_isMulti',
|
|
94
|
+
'context_fdInParam',
|
|
95
|
+
'context_outputParam',
|
|
96
|
+
];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 动作流编译内核 · 类型定义
|
|
4
|
+
*
|
|
5
|
+
* 依据 `sys-modeling/src/components/action-flow-ide/enter.tsx:339-428`:
|
|
6
|
+
* `fdDesign` 是 JSON 字符串,顶层 `{ nodes, lines, processVar? }`;
|
|
7
|
+
* 节点为扁平结构:`drawAttr.bounds` + `extraAttr.context_*` + 各 nodeType 专属字段。
|
|
8
|
+
* IDE 回显时原样使用 x/y,不重算坐标,因此坐标必须由本内核生成。
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 动作流离线校验器
|
|
4
|
+
*
|
|
5
|
+
* 移植 IDE 发布前的 validate(true):连线数量、必填、编码格式、无环
|
|
6
|
+
* (enter.tsx:468-529、validate.ts:150-327、mxgraph/utils.tsx:1206-1258)。
|
|
7
|
+
* 目的是在发包之前把错误挡住——CLI 直接调 addPublish 会绕过 IDE 校验。
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.validateSpec = validateSpec;
|
|
11
|
+
exports.validateGraph = validateGraph;
|
|
12
|
+
exports.hasErrors = hasErrors;
|
|
13
|
+
const schema_1 = require("./schema");
|
|
14
|
+
const err = (code, message, nodeId) => ({
|
|
15
|
+
level: 'error', code, message, nodeId,
|
|
16
|
+
});
|
|
17
|
+
const warn = (code, message, nodeId) => ({
|
|
18
|
+
level: 'warn', code, message, nodeId,
|
|
19
|
+
});
|
|
20
|
+
/** 基础信息校验 */
|
|
21
|
+
function validateSpec(spec) {
|
|
22
|
+
const out = [];
|
|
23
|
+
if (!spec.name || !String(spec.name).trim())
|
|
24
|
+
out.push(err('SPEC_NAME_REQUIRED', '缺少流程名称 name'));
|
|
25
|
+
if (!spec.code || !schema_1.FLOW_CODE_PATTERN.test(spec.code)) {
|
|
26
|
+
out.push(err('SPEC_CODE_INVALID', `流程编码 code 必须匹配 /^[a-zA-Z0-9_]{1,30}$/:${spec.code ?? '(空)'}`));
|
|
27
|
+
}
|
|
28
|
+
const startWay = spec.startWay ?? 'StartActiveNode';
|
|
29
|
+
if (!schema_1.KNOWN_START_WAYS.includes(startWay)) {
|
|
30
|
+
out.push(err('SPEC_START_WAY_INVALID', `未知启动方式:${startWay}`));
|
|
31
|
+
}
|
|
32
|
+
if (!Array.isArray(spec.nodes))
|
|
33
|
+
out.push(err('SPEC_NODES_REQUIRED', '缺少 nodes 数组'));
|
|
34
|
+
if (!Array.isArray(spec.edges))
|
|
35
|
+
out.push(err('SPEC_EDGES_REQUIRED', '缺少 edges 数组'));
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
/** 开始节点按启动方式的必填项(configuration/nodes/utils.tsx:40-61、validate.ts:13-17) */
|
|
39
|
+
function validateStartNode(graph, spec, startWay) {
|
|
40
|
+
const out = [];
|
|
41
|
+
const start = graph.nodes.find((n) => n._isStart);
|
|
42
|
+
if (!start)
|
|
43
|
+
return out;
|
|
44
|
+
const provided = start.config?.startNodeDef;
|
|
45
|
+
const def = provided ?? spec.trigger ?? {};
|
|
46
|
+
const has = (k) => def[k] !== undefined && def[k] !== null && def[k] !== '';
|
|
47
|
+
if (startWay === 'StartScheduledNode' && !has('scheduledDef')) {
|
|
48
|
+
out.push(err('START_SCHEDULE_REQUIRED', '定时触发必须在 trigger 中提供 scheduledDef', start.id));
|
|
49
|
+
}
|
|
50
|
+
if (startWay === 'StartDataNode' && !has('target')) {
|
|
51
|
+
out.push(err('START_DATA_TARGET_REQUIRED', '数据监听必须在 trigger 中提供 target(触发表单)', start.id));
|
|
52
|
+
}
|
|
53
|
+
if (startWay === 'StartEventNode' && !has('eventCode')) {
|
|
54
|
+
out.push(err('START_EVENT_REQUIRED', '事件监听必须在 trigger 中提供 eventCode', start.id));
|
|
55
|
+
}
|
|
56
|
+
if (startWay === 'StartActiveNode') {
|
|
57
|
+
const runTypes = def.fdRunType;
|
|
58
|
+
const byFlags = def.operateTrigger === true || def.bpmTrigger === true || def.apiTrigger === true;
|
|
59
|
+
const byList = Array.isArray(runTypes) && runTypes.length > 0;
|
|
60
|
+
if (!byFlags && !byList) {
|
|
61
|
+
out.push(err('START_ACTIVE_TRIGGER_REQUIRED', '主动触发必须提供 trigger.fdRunType(operate / bpm / api 至少一项)或对应布尔开关', start.id));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
/** 图结构 + 节点必填校验 */
|
|
67
|
+
function validateGraph(graph, spec, startWay = 'StartActiveNode') {
|
|
68
|
+
const out = [];
|
|
69
|
+
const ids = new Set();
|
|
70
|
+
const byId = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
71
|
+
for (const node of graph.nodes) {
|
|
72
|
+
if (!schema_1.NODE_ID_PATTERN.test(node.id)) {
|
|
73
|
+
out.push(err('NODE_ID_INVALID', `节点 id 必须匹配 /^[0-9a-zA-Z_]{2,32}$/:${node.id}`, node.id));
|
|
74
|
+
}
|
|
75
|
+
if (ids.has(node.id))
|
|
76
|
+
out.push(err('NODE_ID_DUPLICATE', `节点 id 重复:${node.id}`, node.id));
|
|
77
|
+
ids.add(node.id);
|
|
78
|
+
if (!schema_1.NODE_FIELD_WHITELIST[node.type]) {
|
|
79
|
+
out.push(warn('NODE_TYPE_UNKNOWN', `未知节点类型 ${node.type},config 将原样透传;请用 llc flow templates list 核对环境支持的节点`, node.id));
|
|
80
|
+
}
|
|
81
|
+
// 与编译器一致:默认值(如 BranchNode.conditionType / flowWay)也算已填
|
|
82
|
+
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));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const inCount = new Map();
|
|
90
|
+
const outCount = new Map();
|
|
91
|
+
const seenEdge = new Set();
|
|
92
|
+
for (const edge of graph.edges) {
|
|
93
|
+
const key = `${edge.from}->${edge.to}`;
|
|
94
|
+
if (seenEdge.has(key)) {
|
|
95
|
+
out.push(err('EDGE_DUPLICATE', `重复连线:${key}(IDE 设置 setMultigraph(false))`));
|
|
96
|
+
}
|
|
97
|
+
seenEdge.add(key);
|
|
98
|
+
if (!byId.has(edge.from))
|
|
99
|
+
out.push(err('EDGE_SOURCE_MISSING', `连线起点不存在:${edge.from}`));
|
|
100
|
+
if (!byId.has(edge.to))
|
|
101
|
+
out.push(err('EDGE_TARGET_MISSING', `连线终点不存在:${edge.to}`));
|
|
102
|
+
if (edge.from === edge.to)
|
|
103
|
+
out.push(err('EDGE_SELF_LOOP', `禁止自连:${edge.from}`));
|
|
104
|
+
inCount.set(edge.to, (inCount.get(edge.to) ?? 0) + 1);
|
|
105
|
+
outCount.set(edge.from, (outCount.get(edge.from) ?? 0) + 1);
|
|
106
|
+
}
|
|
107
|
+
for (const node of graph.nodes) {
|
|
108
|
+
const incoming = inCount.get(node.id) ?? 0;
|
|
109
|
+
const outgoing = outCount.get(node.id) ?? 0;
|
|
110
|
+
if (node._isStart && incoming > 0)
|
|
111
|
+
out.push(err('START_HAS_IN_EDGE', '开始节点不能有入边', node.id));
|
|
112
|
+
if (node._isEnd && outgoing > 0)
|
|
113
|
+
out.push(err('END_HAS_OUT_EDGE', '结束节点不能有出边', node.id));
|
|
114
|
+
if (!node._isEnd && outgoing === 0) {
|
|
115
|
+
out.push(err('NODE_NO_OUT_EDGE', `节点 ${node.id} 没有出边`, node.id));
|
|
116
|
+
}
|
|
117
|
+
if (!node._isStart && incoming === 0) {
|
|
118
|
+
out.push(err('NODE_NO_IN_EDGE', `节点 ${node.id} 没有入边`, node.id));
|
|
119
|
+
}
|
|
120
|
+
if (outgoing > 1 && !schema_1.MULTI_OUT_NODE_TYPES.has(node.type)) {
|
|
121
|
+
out.push(err('NODE_MULTI_OUT_EDGE', `节点 ${node.id}(${node.type})有 ${outgoing} 条出边;仅 BranchNode / StartNode / parallelGateway 允许多出边`, node.id));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
out.push(...validateStartNode(graph, spec, startWay));
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
function hasErrors(diagnostics) {
|
|
128
|
+
return diagnostics.some((d) => d.level === 'error');
|
|
129
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -6,12 +6,13 @@ const app_1 = require("./commands/app");
|
|
|
6
6
|
const config_1 = require("./commands/config");
|
|
7
7
|
const control_1 = require("./commands/control");
|
|
8
8
|
const field_1 = require("./commands/field");
|
|
9
|
+
const flow_1 = require("./commands/flow");
|
|
9
10
|
const form_1 = require("./commands/form");
|
|
10
11
|
const locale_1 = require("./commands/locale");
|
|
11
12
|
const operation_1 = require("./commands/operation");
|
|
12
13
|
const template_1 = require("./commands/template");
|
|
13
14
|
const logger_1 = require("./utils/logger");
|
|
14
|
-
const VERSION = '2.
|
|
15
|
+
const VERSION = '2.8.0';
|
|
15
16
|
const program = new commander_1.Command();
|
|
16
17
|
exports.program = program;
|
|
17
18
|
program
|
|
@@ -38,6 +39,7 @@ program.hook('preAction', (thisCommand, actionCommand) => {
|
|
|
38
39
|
(0, field_1.registerFieldCommand)(program);
|
|
39
40
|
(0, operation_1.registerOperationCommand)(program);
|
|
40
41
|
(0, template_1.registerTemplateCommand)(program);
|
|
42
|
+
(0, flow_1.registerFlowCommand)(program);
|
|
41
43
|
(0, locale_1.registerLocaleCommand)(program);
|
|
42
44
|
(0, control_1.registerControlCommand)(program);
|
|
43
45
|
/**
|
|
@@ -114,6 +116,15 @@ ${logger_1.color.bold('常用查询')}
|
|
|
114
116
|
llc operation create --form-id <fdId> --name 打开帮助 --code openHelp --type link --url https://example.com
|
|
115
117
|
llc operation delete --id <operationId> --yes 删除业务操作(被引用时会拒绝)
|
|
116
118
|
llc operation attach --form-id <fdId> --operation-id <opId> 挂到列表视图(--target view 挂查看视图)
|
|
119
|
+
llc flow list --app-code <appCode> 应用下的动作流(只读)
|
|
120
|
+
llc flow get --id <flowId> --with-design 查看动作流并解析 fdDesign
|
|
121
|
+
llc flow spec --out flow.json 生成 Flow Spec 模板
|
|
122
|
+
llc flow validate --spec @flow.json 离线校验(图结构 / 必填 / 布局)
|
|
123
|
+
llc flow compile --spec @flow.json --json 编译出含编排布局的 fdDesign
|
|
124
|
+
llc flow apply --spec @flow.json --app-code <code> 预览提交体(追加 --yes 才写入)
|
|
125
|
+
llc flow diff --id <flowId> --spec @flow.json 对比本地与远端设计(忽略坐标)
|
|
126
|
+
llc flow script --id <flowId> 生成 flow_{fdCode} 按钮脚本
|
|
127
|
+
llc flow enable/disable/delete --id <flowId> 生命周期管理
|
|
117
128
|
|
|
118
129
|
${logger_1.color.gray('高级控件(desc / tags / mdm / association / mechnumber …)与普通控件同样写法:')}
|
|
119
130
|
llc form autofill --id <fdId> --apply --add-field "字段名:标题:控件类型"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lan-low-code-cli
|
|
3
|
-
version: 2.
|
|
3
|
+
version: 2.8.0
|
|
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:
|
|
@@ -41,11 +41,11 @@ Verify: `llc -v` should print the version.
|
|
|
41
41
|
### Version check before upgrading
|
|
42
42
|
|
|
43
43
|
Before upgrading (and whenever `llc -v` does not match this skill), compare the **major** version of the
|
|
44
|
-
installed CLI with this skill's frontmatter
|
|
44
|
+
installed CLI with the `version:` in this skill's frontmatter:
|
|
45
45
|
|
|
46
46
|
| CLI version vs skill | Action |
|
|
47
47
|
|---|---|
|
|
48
|
-
| Same major (`2.
|
|
48
|
+
| Same major (e.g. skill `2.x` → CLI `2.y`) | **Upgrade directly**: `npm i -g lan-low-code-cli@latest`. Non-major releases keep the flags in this skill valid. |
|
|
49
49
|
| CLI major is higher (`2.x` skill vs `3.x` CLI) | A **major** version has been crossed: **this skill may be out of date**. Do not assume its flags/fields still exist — tell the user the skill may be stale and update it (see **Self-update** below) before running write operations. |
|
|
50
50
|
| CLI major is lower | The CLI is older than the skill: upgrade the CLI first (`npm i -g lan-low-code-cli@latest`); never downgrade the skill. |
|
|
51
51
|
|
|
@@ -93,8 +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),
|
|
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
|
+
| `llc flow spec / compile / validate` | **offline**: generate a Flow Spec, compile it into `fdDesign` with IDE-style layout, run offline validation |
|
|
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 / 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` |
|
|
98
101
|
| `llc control list / show` | controls installed in the environment (basis for `--add-field` third segment) |
|
|
99
102
|
| `llc locale list` | languages with translations available (from control i18n resources) |
|
|
100
103
|
| `llc config set / show / clear` | global credentials and defaults |
|
|
@@ -103,6 +106,13 @@ Always run `llc <command> --help` before inventing flags — the CLI is the sour
|
|
|
103
106
|
>
|
|
104
107
|
> Runtime `context` of operation scripts (selected rows, list refresh, query, navigation, event bus,
|
|
105
108
|
> `window.MKXFORM`) is documented in [references/operation-script-context.md](references/operation-script-context.md) (Chinese).
|
|
109
|
+
>
|
|
110
|
+
> Action flows are **application-scoped** (`--app-code`, `fdAppCode`), not form-scoped. Always start with
|
|
111
|
+
> `llc flow spec --out flow.json` → `llc flow validate --spec @flow.json` → `llc flow compile --spec @flow.json --json`.
|
|
112
|
+
> `llc flow apply` **only previews** until `--yes` is added, and requires an explicit `--app-code`.
|
|
113
|
+
> `--layout ide` (default) reproduces the platform's branch arrangement; never hand-write `drawAttr.bounds`.
|
|
114
|
+
> Full Flow Spec syntax: **§10 of [references/cli-contract.md](references/cli-contract.md)**.
|
|
115
|
+
> Add `--resolve-schema` when publishing nodes that reference a data model (`sys-modeling#<fdId>`).
|
|
106
116
|
|
|
107
117
|
## 2. Core workflow
|
|
108
118
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lan-low-code-cli
|
|
3
|
-
version: 2.
|
|
3
|
+
version: 2.8.0
|
|
4
4
|
author: Pig George
|
|
5
5
|
description: 通过 `llc` CLI 操作蓝凌低代码表单引擎。适用于:创建、查看、修改、发布或删除低代码应用与表单;生成字段配置;配置布局(栅格/页签)、字段权限、选项、多语言、控件动作(onChange/onLoad);或执行发布前校验、排查表单加载/渲染报错。
|
|
6
6
|
tags:
|
|
@@ -45,7 +45,7 @@ npm i -g lan-low-code-cli # 全局安装,获得 `llc` 命令
|
|
|
45
45
|
|
|
46
46
|
| CLI 版本 vs skill | 处理 |
|
|
47
47
|
|---|---|
|
|
48
|
-
|
|
|
48
|
+
| 同一大版本(如 skill `2.x` → CLI `2.y`) | **直接升级**:`npm i -g lan-low-code-cli@latest`。非大版本升级不影响本 skill 的参数与写法。 |
|
|
49
49
|
| CLI 大版本更高(skill `2.x` vs CLI `3.x`) | 已**跨大版本**:**本 skill 可能已过期**。不要假设其中的参数/字段仍然有效——告知用户 skill 可能过期,按下方**自更新**更新后再执行写操作。 |
|
|
50
50
|
| CLI 大版本更低 | CLI 比 skill 旧:先升级 CLI(`npm i -g lan-low-code-cli@latest`),不要把 skill 降级。 |
|
|
51
51
|
|
|
@@ -90,8 +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
|
+
| `llc flow spec / compile / validate` | **纯离线**:生成 Flow Spec、编译出符合 IDE 编排布局的 `fdDesign`、离线校验 |
|
|
96
|
+
| `llc flow list / get / category / logs / schema / templates` | **只读**查询动作流;`templates` 抽取 golden 节点骨架(已剥离坐标) |
|
|
97
|
+
| `llc flow apply / diff / script / run / enable / disable / delete` | **写入**动作流全生命周期;`apply` 默认预览,需 `--yes` + `--app-code`;`run` 通过 `sysModelingMain/other` 执行绑定按钮 |
|
|
95
98
|
| `llc control list / show` | 环境已安装控件(`--add-field` 第三段的取值依据) |
|
|
96
99
|
| `llc locale list` | 环境中有现成译文的语言(来自控件多语言资源) |
|
|
97
100
|
| `llc config set / show / clear` | 全局凭据与默认值 |
|
|
@@ -100,6 +103,13 @@ llc config set --base-url http://<host>/data --token <X-AUTH-TOKEN> # 一次
|
|
|
100
103
|
>
|
|
101
104
|
> 业务操作「脚本模式」的运行时 `context`(选中数据、刷新列表、查询、跳转、事件总线、`window.MKXFORM`)
|
|
102
105
|
> 见 [references/operation-script-context.md](references/operation-script-context.md)。
|
|
106
|
+
>
|
|
107
|
+
> 动作流是**应用级**对象(`--app-code` 即 `fdAppCode`),不是表单级。固定起手式:
|
|
108
|
+
> `llc flow spec --out flow.json` → `llc flow validate --spec @flow.json` → `llc flow compile --spec @flow.json --json`。
|
|
109
|
+
> `llc flow apply` **默认只预览**,必须追加 `--yes`,且必须显式给 `--app-code`。
|
|
110
|
+
> `--layout ide`(默认)复刻平台分支编排,不要手写 `drawAttr.bounds`。
|
|
111
|
+
> Flow Spec 完整语法见 [references/cli-contract.md](references/cli-contract.md) **第 10 节**。
|
|
112
|
+
> 节点引用数据模型(`sys-modeling#<fdId>`)时,发布请加 `--resolve-schema`。
|
|
103
113
|
|
|
104
114
|
## 2. 核心流程
|
|
105
115
|
|