@steedos-labs/plugin-workflow 3.0.82 → 3.0.84
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/designer/dist/amis-renderer/amis-renderer.js +1 -1
- package/designer/dist/assets/{index-WAa42ctb.js → index-DpFVuLlI.js} +28 -28
- package/designer/dist/index.html +1 -1
- package/main/default/client/flow2_render.client.js +1 -1
- package/main/default/objects/instances/buttons/instance_delete_many.button.yml +1 -1
- package/main/default/objects/instances/listviews/monitor.listview.yml +1 -1
- package/main/default/pages/instance_tasks_detail.page.amis.json +4 -7
- package/main/default/pages/page_instance_print.page.amis.json +3 -3
- package/main/default/routes/api_workflow_chart.router.js +374 -51
- package/main/default/routes/api_workflow_next_step_users.router.js +16 -16
- package/main/default/test/test_formula_compat.js +28 -1
- package/package.json +1 -1
- package/public/amis-renderer/amis-renderer.js +1 -1
package/designer/dist/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<link rel="shortcut icon" type="image/svg+xml" href="/images/logo.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>designer</title>
|
|
8
|
-
<script type="module" crossorigin src="/api/workflow/designer-v2/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/api/workflow/designer-v2/assets/index-DpFVuLlI.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/api/workflow/designer-v2/assets/index-B56yvQDB.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
@@ -25,19 +25,17 @@
|
|
|
25
25
|
"type": "service",
|
|
26
26
|
"id": "u:d6db0c84f150",
|
|
27
27
|
"body": {},
|
|
28
|
-
"messages": {
|
|
29
|
-
},
|
|
28
|
+
"messages": {},
|
|
30
29
|
"schemaApi": {
|
|
31
30
|
"url": "${context.rootUrl}/graphql?recordId=${recordId}",
|
|
32
31
|
"method": "post",
|
|
33
|
-
"messages": {
|
|
34
|
-
},
|
|
32
|
+
"messages": {},
|
|
35
33
|
"dataType": "json",
|
|
36
34
|
"headers": {
|
|
37
35
|
"Authorization": "Bearer ${context.tenantId},${context.authToken}"
|
|
38
36
|
},
|
|
39
37
|
"requestAdaptor": "\nconst { recordId } = api.body;\napi.data = {\n query: `{instance_task:instance_tasks__findOne(id: \"${recordId}\"){_id,instance,is_read}}`\n};\n\nreturn api;",
|
|
40
|
-
"adaptor": "if(!payload.data.instance_task){
|
|
38
|
+
"adaptor": "if(!payload.data.instance_task){\n // 同步清掉 PageRecordDetail.tsx 加上的 loading 遮罩和滑动动画\n try{\n document.body.classList.remove('steedos-detail-loading');\n if(window.$){window.$('.page-object-detail-wrapper').removeClass('slide-out-bottom').addClass('slide-in-top');}\n }catch(e){}\n // 简易 i18n:plugins 端 adaptor 无法 import i18next,按浏览器语言切换中英文\n var isZh=(navigator.language||'').toLowerCase().indexOf('zh')===0;\n var notFoundText=isZh?'\\u65e0\\u6cd5\\u627e\\u5230\\u8bb0\\u5f55':'No records found.';\n var backText=isZh?'\\u8fd4\\u56de\\u5217\\u8868':'Back to list';\n // 警示三角图标(Heroicons ExclamationTriangle,amber-500 描边)\n var svgHtml=\"<span class='empty-record-icon' style='display:block;margin-bottom:16px'><svg xmlns='http://www.w3.org/2000/svg' width='72' height='72' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='#f59e0b'><path stroke-linecap='round' stroke-linejoin='round' d='M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.732 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z'/></svg></span>\";\n // 返回按钮统一调用全局 window.goBack(),参考 header.js 内置详情返回按钮\n var nfBody=[\n {type:'html',html:svgHtml},\n {type:'tpl',tpl:notFoundText,className:'empty-record-title text-xl text-gray-800 font-medium mb-4'},\n {type:'button',label:backText,level:'primary',className:'empty-record-back-btn',onEvent:{click:{actions:[{actionType:'custom',script:'window.goBack && window.goBack()'}]}}}\n ];\n // isRead:true 防止下游 set_have_read ajax 触发\n return {data:{type:'wrapper',className:'flex flex-col items-center justify-center bg-white p-8 text-center',style:{minHeight:'100vh'},body:nfBody,id:'u:notfound-instance-task',isRead:true}};\n}\nlet boxName = api.body.side_listview_id === 'inbox' ? 'inbox' : 'outbox';\npayload.data = {\n instanceId: payload.data.instance_task.instance,\n boxName: boxName,\n isRead: payload.data.instance_task.is_read\n};\nreturn {data: {'type':'wrapper','className':'p-0 h-full','body':[{'type':'steedos-instance-detail','id':'u:40052b3812c1','label':'Instance Detail','instanceId':payload.data.instanceId,'boxName':context.side_listview_id}],'id':'u:829a40757f0a'}};"
|
|
41
39
|
},
|
|
42
40
|
"onEvent": {
|
|
43
41
|
"fetchSchemaInited": {
|
|
@@ -64,8 +62,7 @@
|
|
|
64
62
|
"regions": [
|
|
65
63
|
"body"
|
|
66
64
|
],
|
|
67
|
-
"data": {
|
|
68
|
-
},
|
|
65
|
+
"data": {},
|
|
69
66
|
"className": "steedos-instance-wrapper",
|
|
70
67
|
"id": "u:d37465183f56",
|
|
71
68
|
"bodyClassName": "p-0 flex flex-1 overflow-hidden h-full",
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"actions": [
|
|
108
108
|
{
|
|
109
109
|
"actionType": "custom",
|
|
110
|
-
"script": "doAction({actionType: 'setValue', componentId: 'u:print-width', args: {value: Number(event.data.value)}}); $('.steedos-instance-related-view-wrapper').css('width', event.data.value + 'mm'); var el=document.getElementById('print-page-orientation'); if(el){ var v=Number(event.data.value); el.textContent = v && v !== 210 ? '@media print { .steedos-instance-related-view-wrapper { width: ' + v + 'mm !important; } }' : ''; }"
|
|
110
|
+
"script": "doAction({actionType: 'setValue', componentId: 'u:print-width', args: {value: Number(event.data.value)}}); $('.steedos-instance-related-view-wrapper').css('width', event.data.value + 'mm'); var el=document.getElementById('print-page-orientation'); if(el){ var v=Number(event.data.value); el.textContent = v && v !== 210 ? (v === 297 ? '@page { size: landscape; } ' : '') + '@media print { .steedos-instance-related-view-wrapper { width: ' + v + 'mm !important; } }' : ''; }"
|
|
111
111
|
}
|
|
112
112
|
]
|
|
113
113
|
}
|
|
@@ -201,7 +201,7 @@
|
|
|
201
201
|
{
|
|
202
202
|
"id": "u:259e5e73a3e2",
|
|
203
203
|
"type": "tpl",
|
|
204
|
-
"tpl": "无权限",
|
|
204
|
+
"tpl": "${recordNotFound ? '无法找到记录' : '无权限'}",
|
|
205
205
|
"visibleOn": "${permission === false}"
|
|
206
206
|
}
|
|
207
207
|
],
|
|
@@ -222,7 +222,7 @@
|
|
|
222
222
|
"method": "get",
|
|
223
223
|
"data": {},
|
|
224
224
|
"requestAdaptor": "",
|
|
225
|
-
"adaptor": "",
|
|
225
|
+
"adaptor": "if (payload && payload.errors && payload.errors.length) { return {data: {permission: false, recordNotFound: true}}; } return payload;",
|
|
226
226
|
"messages": {},
|
|
227
227
|
"sendOn": "${recordId}"
|
|
228
228
|
},
|
|
@@ -7,6 +7,7 @@ const _ = require('lodash');
|
|
|
7
7
|
const { getCollection } = require("../utils/collection");
|
|
8
8
|
const WorkflowManager = require('../manager/workflow_manager');
|
|
9
9
|
const getHandlersManager = require('../manager/handlers_manager');
|
|
10
|
+
const UUFlowManager = require('../manager/uuflow_manager');
|
|
10
11
|
const { t } = require('@steedos/i18n')
|
|
11
12
|
|
|
12
13
|
const FlowversionAPI = {
|
|
@@ -136,37 +137,118 @@ const FlowversionAPI = {
|
|
|
136
137
|
return stepName;
|
|
137
138
|
},
|
|
138
139
|
|
|
140
|
+
// 查找从fromStep到targetStepId的边路径(可能经过中间条件节点)
|
|
141
|
+
// 返回边集合,如 ["stepA->conditionB", "conditionB->stepC"]
|
|
142
|
+
findEdgePath: function (fromStep, targetStepId, stepsMap) {
|
|
143
|
+
let edges = [];
|
|
144
|
+
// 直接连线
|
|
145
|
+
if (fromStep.lines) {
|
|
146
|
+
for (let line of fromStep.lines) {
|
|
147
|
+
if (line.to_step === targetStepId) {
|
|
148
|
+
edges.push(`${fromStep._id}->${targetStepId}`);
|
|
149
|
+
return edges;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// 通过条件节点间接连线(BFS,最多2层中间节点)
|
|
153
|
+
for (let line of fromStep.lines) {
|
|
154
|
+
let midStep = stepsMap[line.to_step];
|
|
155
|
+
if (midStep && midStep.step_type === 'condition') {
|
|
156
|
+
if (midStep.lines) {
|
|
157
|
+
for (let midLine of midStep.lines) {
|
|
158
|
+
if (midLine.to_step === targetStepId) {
|
|
159
|
+
edges.push(`${fromStep._id}->${midStep._id}`);
|
|
160
|
+
edges.push(`${midStep._id}->${targetStepId}`);
|
|
161
|
+
return edges;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// 找不到直接路径,简单标记起止边
|
|
169
|
+
edges.push(`${fromStep._id}->${targetStepId}`);
|
|
170
|
+
return edges;
|
|
171
|
+
},
|
|
172
|
+
|
|
139
173
|
generateStepsGraphSyntax: async function (steps, currentStepId, isConvertToString, direction, instance_id, instance) {
|
|
140
174
|
let nodes = [`graph ${direction}`];
|
|
141
175
|
let cachedStepNames = {};
|
|
142
176
|
let skipSteps = new Set(instance?.skip_steps || []);
|
|
177
|
+
let completedStepIds = instance?._completedStepIds || new Set();
|
|
178
|
+
let traversedEdges = instance?._traversedEdges || new Set();
|
|
179
|
+
let predictedEdges = instance?._predictedEdges || new Set();
|
|
180
|
+
let isInstanceFinished = instance?._isFinished === true;
|
|
181
|
+
let linkIndex = 0;
|
|
182
|
+
let traversedLinkIndices = [];
|
|
183
|
+
let predictedLinkIndices = [];
|
|
184
|
+
// 先输出所有边,类指令统一在所有边之后输出,避免 mermaid 因
|
|
185
|
+
// “class 指令出现在节点首次声明之前”而静默忽略 class(V1/V2b 根因)
|
|
143
186
|
for (let step of steps) {
|
|
144
187
|
let lines = step.lines;
|
|
145
188
|
if (lines?.length) {
|
|
146
189
|
for (let line of lines) {
|
|
147
190
|
let stepName = "";
|
|
148
|
-
// 标记条件节点
|
|
149
191
|
if (step.name) {
|
|
150
|
-
if (step.step_type === "condition") {
|
|
151
|
-
nodes.push(` class ${step._id} condition;`);
|
|
152
|
-
}
|
|
153
192
|
stepName = await FlowversionAPI.getStepName(step, cachedStepNames, instance_id, instance);
|
|
154
193
|
} else {
|
|
155
194
|
stepName = "";
|
|
156
195
|
}
|
|
157
|
-
// Mark skipped steps
|
|
158
|
-
if (skipSteps.has(step._id)) {
|
|
159
|
-
nodes.push(` class ${step._id} skip-step;`);
|
|
160
|
-
}
|
|
161
|
-
// 原findPropertyByPK("_id",line.to_step),转为find
|
|
162
196
|
let toStep = steps.find(s => s._id === line.to_step);
|
|
163
197
|
let toStepName = await FlowversionAPI.getStepName(toStep, cachedStepNames, instance_id, instance);
|
|
164
|
-
|
|
198
|
+
if (step.step_type === "condition" && line.name) {
|
|
199
|
+
let lineName = FlowversionAPI.replaceErrorSymbol(line.name);
|
|
200
|
+
nodes.push(` ${step._id}("${stepName}")-->|${lineName}|${line.to_step}("${toStepName}")`);
|
|
201
|
+
} else {
|
|
202
|
+
nodes.push(` ${step._id}("${stepName}")-->${line.to_step}("${toStepName}")`);
|
|
203
|
+
}
|
|
204
|
+
if (traversedEdges.has(`${step._id}->${line.to_step}`)) {
|
|
205
|
+
traversedLinkIndices.push(linkIndex);
|
|
206
|
+
} else if (predictedEdges.has(`${step._id}->${line.to_step}`)) {
|
|
207
|
+
predictedLinkIndices.push(linkIndex);
|
|
208
|
+
}
|
|
209
|
+
linkIndex++;
|
|
165
210
|
}
|
|
166
211
|
}
|
|
167
212
|
}
|
|
168
|
-
|
|
169
|
-
|
|
213
|
+
// 覆盖 mermaid 默认主题对未流转(default)节点的紫色 inline style,使其匹配图例的灰色
|
|
214
|
+
nodes.push(` classDef default fill:#f1f5f9,stroke:#cbd5e1,stroke-width:1.5px,color:#475569;`);
|
|
215
|
+
// 统一输出 class 指令,优先级:skip-step > current-step-node > completed-step > condition
|
|
216
|
+
// 已结束流程(isInstanceFinished)不再标记 current-step-node(V3),最后一步按已完成展示
|
|
217
|
+
let classOf = new Map();
|
|
218
|
+
let assign = (id, cls) => { if (!classOf.has(id)) classOf.set(id, cls); };
|
|
219
|
+
if (!isInstanceFinished && currentStepId) {
|
|
220
|
+
assign(currentStepId, 'current-step-node');
|
|
221
|
+
}
|
|
222
|
+
for (let step of steps) {
|
|
223
|
+
if (skipSteps.has(step._id)) {
|
|
224
|
+
assign(step._id, 'skip-step');
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
for (let stepId of completedStepIds) {
|
|
228
|
+
if (skipSteps.has(stepId)) continue;
|
|
229
|
+
if (!isInstanceFinished && stepId === currentStepId) continue;
|
|
230
|
+
assign(stepId, 'completed-step');
|
|
231
|
+
}
|
|
232
|
+
for (let step of steps) {
|
|
233
|
+
if (step.step_type === 'condition') {
|
|
234
|
+
assign(step._id, 'condition');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
for (let [id, cls] of classOf) {
|
|
238
|
+
nodes.push(` class ${id} ${cls};`);
|
|
239
|
+
}
|
|
240
|
+
// 先将所有连线设为灰色(不会流转的路径)
|
|
241
|
+
if (linkIndex > 0) {
|
|
242
|
+
let allIndices = Array.from({length: linkIndex}, (_, i) => i).join(',');
|
|
243
|
+
nodes.push(` linkStyle ${allIndices} stroke:#cbd5e1,stroke-width:1.5px;`);
|
|
244
|
+
}
|
|
245
|
+
// 高亮已流转的连线(绿色实线)
|
|
246
|
+
if (traversedLinkIndices.length > 0) {
|
|
247
|
+
nodes.push(` linkStyle ${traversedLinkIndices.join(',')} stroke:#10b981,stroke-width:3px;`);
|
|
248
|
+
}
|
|
249
|
+
// 高亮预测的下一步路径(蓝色虚线)
|
|
250
|
+
if (predictedLinkIndices.length > 0) {
|
|
251
|
+
nodes.push(` linkStyle ${predictedLinkIndices.join(',')} stroke:#3b82f6,stroke-width:2px,stroke-dasharray:5 3;`);
|
|
170
252
|
}
|
|
171
253
|
if (isConvertToString) {
|
|
172
254
|
let graphSyntax = nodes.join("\n");
|
|
@@ -505,12 +587,124 @@ const FlowversionAPI = {
|
|
|
505
587
|
break;
|
|
506
588
|
}
|
|
507
589
|
default: {
|
|
508
|
-
let instance = await db.instances.findOne({ _id: instance_id }
|
|
590
|
+
let instance = await db.instances.findOne({ _id: instance_id });
|
|
509
591
|
if (instance) {
|
|
510
|
-
let
|
|
592
|
+
let traces = instance.traces || [];
|
|
593
|
+
let currentStepId = traces.length > 0 ? traces[traces.length - 1]?.step : null;
|
|
594
|
+
// 收集已完成的步骤ID和已流转的路径(from_step -> to_step)
|
|
595
|
+
let completedStepIds = new Set();
|
|
596
|
+
let traversedEdges = new Set();
|
|
597
|
+
for (let i = 0; i < traces.length; i++) {
|
|
598
|
+
let trace = traces[i];
|
|
599
|
+
if (trace.step) {
|
|
600
|
+
completedStepIds.add(trace.step);
|
|
601
|
+
}
|
|
602
|
+
// 从前一个trace到当前trace的连线视为已流转
|
|
603
|
+
if (i > 0 && traces[i - 1].step && trace.step) {
|
|
604
|
+
traversedEdges.add(`${traces[i - 1].step}->${trace.step}`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
instance._completedStepIds = completedStepIds;
|
|
608
|
+
instance._traversedEdges = traversedEdges;
|
|
609
|
+
let isInstanceFinished = instance.state === 'completed' || instance.state === 'terminated';
|
|
610
|
+
instance._isFinished = isInstanceFinished;
|
|
611
|
+
|
|
612
|
+
// 只有进行中的单子才预测后续路径,已结束的单子不需要预测
|
|
613
|
+
let predictedEdges = new Set();
|
|
614
|
+
try {
|
|
615
|
+
if (currentStepId && !isInstanceFinished) {
|
|
616
|
+
let flow = await UUFlowManager.getFlow(instance.flow);
|
|
617
|
+
let flowversion = UUFlowManager.getFlowVersion(flow, instance.flow_version);
|
|
618
|
+
// 取最新trace的最新approve的_id作为参数
|
|
619
|
+
let latestTrace = traces[traces.length - 1];
|
|
620
|
+
let latestApproveId = null;
|
|
621
|
+
if (latestTrace?.approves?.length) {
|
|
622
|
+
latestApproveId = latestTrace.approves[latestTrace.approves.length - 1]._id;
|
|
623
|
+
}
|
|
624
|
+
let values = UUFlowManager.getUpdatedValues(instance, latestApproveId);
|
|
625
|
+
if (process.env.STEEDOS_DEBUG) {
|
|
626
|
+
console.log('values===>', values, latestApproveId);
|
|
627
|
+
}
|
|
628
|
+
let stepsMap = {};
|
|
629
|
+
for (let s of flowversion.steps) {
|
|
630
|
+
stepsMap[s._id] = s;
|
|
631
|
+
}
|
|
632
|
+
// 递归预测,最多追踪20步防止死循环
|
|
633
|
+
let visited = new Set();
|
|
634
|
+
let predictFrom = [currentStepId];
|
|
635
|
+
let maxDepth = 20;
|
|
636
|
+
let depth = 0;
|
|
637
|
+
while (predictFrom.length > 0 && depth < maxDepth) {
|
|
638
|
+
depth++;
|
|
639
|
+
let nextBatch = [];
|
|
640
|
+
for (let fromStepId of predictFrom) {
|
|
641
|
+
if (visited.has(fromStepId)) continue;
|
|
642
|
+
visited.add(fromStepId);
|
|
643
|
+
let fromStep = stepsMap[fromStepId];
|
|
644
|
+
if (!fromStep || fromStep.step_type === 'end') continue;
|
|
645
|
+
|
|
646
|
+
if (fromStep.step_type === 'condition') {
|
|
647
|
+
// 条件节点:优先按条件表达式评估;若返回空(如表单字段未填,
|
|
648
|
+
// 无法判断走哪个分支),fallback 展开所有出线,保证预测路径继续延伸(V4)
|
|
649
|
+
let nextStepIds = await UUFlowManager.getNextSteps(instance, flow, fromStep, '', values);
|
|
650
|
+
if (!nextStepIds || nextStepIds.length === 0) {
|
|
651
|
+
nextStepIds = (fromStep.lines || []).map(l => l.to_step);
|
|
652
|
+
}
|
|
653
|
+
for (let nextId of nextStepIds || []) {
|
|
654
|
+
let nextStep = stepsMap[nextId];
|
|
655
|
+
if (!nextStep) continue;
|
|
656
|
+
predictedEdges.add(`${fromStepId}->${nextId}`);
|
|
657
|
+
if (!visited.has(nextId)) {
|
|
658
|
+
nextBatch.push(nextId);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
} else {
|
|
662
|
+
// 非条件步骤:直接根据lines找下一步,不通过getNextSteps(它会把条件节点的所有分支都展开)
|
|
663
|
+
let judge = fromStep.step_type === 'sign' ? 'approved' : 'submitted';
|
|
664
|
+
let lines = (fromStep.lines || []).filter(l => l.state === judge);
|
|
665
|
+
for (let line of lines) {
|
|
666
|
+
let toStep = stepsMap[line.to_step];
|
|
667
|
+
if (!toStep) continue;
|
|
668
|
+
predictedEdges.add(`${fromStepId}->${line.to_step}`);
|
|
669
|
+
if (!visited.has(line.to_step)) {
|
|
670
|
+
nextBatch.push(line.to_step);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
predictFrom = nextBatch;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
} catch (e) {
|
|
679
|
+
// 预测失败不影响主流程,但输出详细错误信息便于排查
|
|
680
|
+
console.warn('Chart prediction failed:', e.message, e.stack);
|
|
681
|
+
}
|
|
682
|
+
instance._predictedEdges = predictedEdges;
|
|
683
|
+
|
|
511
684
|
let flowversion = await WorkflowManager.getInstanceFlowVersion(instance);
|
|
512
685
|
let steps = flowversion?.steps;
|
|
513
686
|
if (steps?.length) {
|
|
687
|
+
// 解析经过条件节点的已流转边,将 stepA->stepC 展开为 stepA->conditionB, conditionB->stepC
|
|
688
|
+
if (traversedEdges.size > 0) {
|
|
689
|
+
let stepsMap = {};
|
|
690
|
+
for (let s of steps) {
|
|
691
|
+
stepsMap[s._id] = s;
|
|
692
|
+
}
|
|
693
|
+
let resolvedTraversedEdges = new Set();
|
|
694
|
+
for (let edge of traversedEdges) {
|
|
695
|
+
let [fromId, toId] = edge.split('->');
|
|
696
|
+
let fromStep = stepsMap[fromId];
|
|
697
|
+
if (fromStep) {
|
|
698
|
+
let edgePaths = FlowversionAPI.findEdgePath(fromStep, toId, stepsMap);
|
|
699
|
+
for (let e of edgePaths) {
|
|
700
|
+
resolvedTraversedEdges.add(e);
|
|
701
|
+
}
|
|
702
|
+
} else {
|
|
703
|
+
resolvedTraversedEdges.add(edge);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
instance._traversedEdges = resolvedTraversedEdges;
|
|
707
|
+
}
|
|
514
708
|
graphSyntax = await FlowversionAPI.generateStepsGraphSyntax(steps, currentStepId, false, direction, instance_id, instance);
|
|
515
709
|
} else {
|
|
516
710
|
error_msg = "没有找到当前申请单的流程步骤数据";
|
|
@@ -535,9 +729,11 @@ const FlowversionAPI = {
|
|
|
535
729
|
<script type="text/javascript" src="/unpkg.com/mermaid@9.1.2/dist/mermaid.min.js"></script>
|
|
536
730
|
<style>
|
|
537
731
|
body {
|
|
538
|
-
font-family:
|
|
732
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
539
733
|
text-align: center;
|
|
540
|
-
background-color: #
|
|
734
|
+
background-color: #f8fafc;
|
|
735
|
+
margin: 0;
|
|
736
|
+
padding: 20px;
|
|
541
737
|
}
|
|
542
738
|
.loading{
|
|
543
739
|
position: absolute;
|
|
@@ -547,8 +743,9 @@ const FlowversionAPI = {
|
|
|
547
743
|
z-index: 1100;
|
|
548
744
|
text-align: center;
|
|
549
745
|
margin-top: -30px;
|
|
550
|
-
font-size:
|
|
551
|
-
color: #
|
|
746
|
+
font-size: 20px;
|
|
747
|
+
color: #94a3b8;
|
|
748
|
+
font-weight: 500;
|
|
552
749
|
}
|
|
553
750
|
.error-msg{
|
|
554
751
|
position: absolute;
|
|
@@ -557,65 +754,137 @@ const FlowversionAPI = {
|
|
|
557
754
|
bottom: 20px;
|
|
558
755
|
z-index: 1100;
|
|
559
756
|
text-align: center;
|
|
560
|
-
font-size:
|
|
561
|
-
color: #
|
|
757
|
+
font-size: 16px;
|
|
758
|
+
color: #dc2626;
|
|
562
759
|
}
|
|
760
|
+
/* 未流转的步骤 - 浅灰色 */
|
|
563
761
|
#flow-steps-svg .node rect{
|
|
564
|
-
fill: #
|
|
565
|
-
stroke:
|
|
762
|
+
fill: #f1f5f9;
|
|
763
|
+
stroke: #cbd5e1;
|
|
764
|
+
stroke-width: 1.5px;
|
|
765
|
+
rx: 8px;
|
|
766
|
+
ry: 8px;
|
|
767
|
+
}
|
|
768
|
+
/* 已完成的步骤 - 蓝色调 */
|
|
769
|
+
#flow-steps-svg .node.completed-step rect{
|
|
770
|
+
fill: #dbeafe;
|
|
771
|
+
stroke: #3b82f6;
|
|
566
772
|
stroke-width: 2px;
|
|
567
773
|
}
|
|
774
|
+
#flow-steps-svg .node.completed-step .step-name{
|
|
775
|
+
color: #1e40af;
|
|
776
|
+
}
|
|
777
|
+
#flow-steps-svg .node.completed-step .step-handler-name{
|
|
778
|
+
color: #3b82f6;
|
|
779
|
+
}
|
|
780
|
+
/* 当前步骤 - 绿色高亮 + 呼吸动画 */
|
|
568
781
|
#flow-steps-svg .node.current-step-node rect{
|
|
569
|
-
fill: #
|
|
570
|
-
stroke: #
|
|
571
|
-
stroke-width:
|
|
782
|
+
fill: #d1fae5;
|
|
783
|
+
stroke: #10b981;
|
|
784
|
+
stroke-width: 2.5px;
|
|
785
|
+
animation: currentStepPulse 2s ease-in-out infinite;
|
|
786
|
+
}
|
|
787
|
+
@keyframes currentStepPulse {
|
|
788
|
+
0%, 100% { stroke-width: 2.5px; filter: drop-shadow(0 0 0px rgba(16,185,129,0)); }
|
|
789
|
+
50% { stroke-width: 3px; filter: drop-shadow(0 0 6px rgba(16,185,129,0.4)); }
|
|
790
|
+
}
|
|
791
|
+
#flow-steps-svg .node.current-step-node .step-name{
|
|
792
|
+
color: #065f46;
|
|
793
|
+
font-weight: 600;
|
|
572
794
|
}
|
|
795
|
+
#flow-steps-svg .node.current-step-node .step-handler-name{
|
|
796
|
+
color: #10b981;
|
|
797
|
+
}
|
|
798
|
+
/* 条件节点 */
|
|
573
799
|
#flow-steps-svg .node.condition rect{
|
|
574
|
-
fill: #
|
|
575
|
-
stroke:
|
|
576
|
-
stroke-width:
|
|
800
|
+
fill: #fef3c7;
|
|
801
|
+
stroke: #f59e0b;
|
|
802
|
+
stroke-width: 1.5px;
|
|
803
|
+
}
|
|
804
|
+
#flow-steps-svg .node.condition .step-name{
|
|
805
|
+
color: #92400e;
|
|
577
806
|
}
|
|
807
|
+
/* 跳过的步骤 */
|
|
578
808
|
#flow-steps-svg .node.skip-step rect{
|
|
579
|
-
fill: #
|
|
580
|
-
stroke: #
|
|
581
|
-
stroke-width:
|
|
809
|
+
fill: #f9fafb;
|
|
810
|
+
stroke: #d1d5db;
|
|
811
|
+
stroke-width: 1.5px;
|
|
582
812
|
stroke-dasharray: 5, 5;
|
|
583
|
-
opacity: 0.
|
|
813
|
+
opacity: 0.6;
|
|
584
814
|
}
|
|
585
815
|
#flow-steps-svg .node.skip-step .step-name{
|
|
586
|
-
color: #
|
|
816
|
+
color: #9ca3af;
|
|
587
817
|
text-decoration: line-through;
|
|
588
818
|
}
|
|
589
819
|
#flow-steps-svg .node.skip-step .step-handler-name{
|
|
590
|
-
color: #
|
|
820
|
+
color: #d1d5db;
|
|
591
821
|
}
|
|
592
|
-
|
|
593
|
-
|
|
822
|
+
/* 节点文字样式 */
|
|
823
|
+
#flow-steps-svg .node .step-name{
|
|
824
|
+
color: #64748b;
|
|
825
|
+
font-size: 14px;
|
|
594
826
|
}
|
|
595
827
|
#flow-steps-svg .node .step-handler-name{
|
|
596
|
-
color: #
|
|
828
|
+
color: #94a3b8;
|
|
829
|
+
font-size: 12px;
|
|
830
|
+
}
|
|
831
|
+
#flow-steps-svg .node .trace-handler-name{
|
|
832
|
+
color: #94a3b8;
|
|
833
|
+
font-size: 12px;
|
|
834
|
+
}
|
|
835
|
+
/* 默认连线样式 */
|
|
836
|
+
#flow-steps-svg .edgePath path{
|
|
837
|
+
stroke: #cbd5e1;
|
|
838
|
+
stroke-width: 1.5px;
|
|
839
|
+
}
|
|
840
|
+
#flow-steps-svg marker path{
|
|
841
|
+
fill: #cbd5e1;
|
|
842
|
+
}
|
|
843
|
+
/* 连线上的文字标签 */
|
|
844
|
+
#flow-steps-svg .edgeLabel{
|
|
845
|
+
background-color: transparent;
|
|
846
|
+
padding: 0;
|
|
847
|
+
font-size: 12px;
|
|
848
|
+
color: #64748b;
|
|
849
|
+
}
|
|
850
|
+
#flow-steps-svg .edgeLabel span{
|
|
851
|
+
color: #64748b !important;
|
|
852
|
+
background: transparent !important;
|
|
853
|
+
}
|
|
854
|
+
#flow-steps-svg .edgeLabel span:not(:empty){
|
|
855
|
+
background: #f8fafc !important;
|
|
856
|
+
padding: 2px 6px;
|
|
857
|
+
border-radius: 4px;
|
|
597
858
|
}
|
|
598
859
|
div.mermaidTooltip{
|
|
599
860
|
position: fixed!important;
|
|
600
861
|
text-align: left!important;
|
|
601
|
-
padding:
|
|
602
|
-
font-size:
|
|
862
|
+
padding: 8px 12px!important;
|
|
863
|
+
font-size: 13px!important;
|
|
603
864
|
max-width: 500px!important;
|
|
604
865
|
left: auto!important;
|
|
605
866
|
top: 15px!important;
|
|
606
867
|
right: 15px;
|
|
868
|
+
border-radius: 6px;
|
|
869
|
+
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
|
|
870
|
+
}
|
|
871
|
+
.graph-node {
|
|
872
|
+
padding: 4px 8px;
|
|
873
|
+
}
|
|
874
|
+
.graph-node .step-name {
|
|
875
|
+
font-weight: 500;
|
|
876
|
+
margin-bottom: 2px;
|
|
607
877
|
}
|
|
608
878
|
.btn-zoom{
|
|
609
|
-
|
|
610
|
-
border-color: transparent;
|
|
879
|
+
border: none;
|
|
611
880
|
display: inline-block;
|
|
612
|
-
padding:
|
|
613
|
-
font-size:
|
|
614
|
-
border-radius:
|
|
615
|
-
background: #
|
|
616
|
-
color: #
|
|
881
|
+
padding: 6px 14px;
|
|
882
|
+
font-size: 20px;
|
|
883
|
+
border-radius: 8px;
|
|
884
|
+
background: #fff;
|
|
885
|
+
color: #475569;
|
|
617
886
|
position: fixed;
|
|
618
|
-
bottom:
|
|
887
|
+
bottom: 20px;
|
|
619
888
|
outline: none;
|
|
620
889
|
cursor: pointer;
|
|
621
890
|
z-index: 99999;
|
|
@@ -624,6 +893,8 @@ const FlowversionAPI = {
|
|
|
624
893
|
-ms-user-select: none;
|
|
625
894
|
user-select: none;
|
|
626
895
|
line-height: 1.2;
|
|
896
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.06);
|
|
897
|
+
transition: all 0.15s ease;
|
|
627
898
|
}
|
|
628
899
|
@media (max-width: 768px) {
|
|
629
900
|
.btn-zoom{
|
|
@@ -631,20 +902,72 @@ const FlowversionAPI = {
|
|
|
631
902
|
}
|
|
632
903
|
}
|
|
633
904
|
.btn-zoom:hover{
|
|
634
|
-
background:
|
|
905
|
+
background: #f1f5f9;
|
|
906
|
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
635
907
|
}
|
|
636
908
|
.btn-zoom-up{
|
|
637
|
-
left:
|
|
909
|
+
left: 20px;
|
|
638
910
|
}
|
|
639
911
|
.btn-zoom-down{
|
|
640
|
-
left:
|
|
641
|
-
|
|
912
|
+
left: 70px;
|
|
913
|
+
}
|
|
914
|
+
/* 图例 */
|
|
915
|
+
.chart-legend {
|
|
916
|
+
position: fixed;
|
|
917
|
+
top: 15px;
|
|
918
|
+
right: 15px;
|
|
919
|
+
background: #fff;
|
|
920
|
+
border-radius: 8px;
|
|
921
|
+
padding: 12px 16px;
|
|
922
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
923
|
+
font-size: 12px;
|
|
924
|
+
color: #475569;
|
|
925
|
+
text-align: left;
|
|
926
|
+
z-index: 99999;
|
|
927
|
+
}
|
|
928
|
+
.chart-legend .legend-item {
|
|
929
|
+
display: flex;
|
|
930
|
+
align-items: center;
|
|
931
|
+
margin-bottom: 6px;
|
|
932
|
+
}
|
|
933
|
+
.chart-legend .legend-item:last-child {
|
|
934
|
+
margin-bottom: 0;
|
|
935
|
+
}
|
|
936
|
+
.chart-legend .legend-dot {
|
|
937
|
+
width: 12px;
|
|
938
|
+
height: 12px;
|
|
939
|
+
border-radius: 3px;
|
|
940
|
+
margin-right: 8px;
|
|
941
|
+
flex-shrink: 0;
|
|
942
|
+
}
|
|
943
|
+
.legend-dot.completed { background: #dbeafe; border: 1.5px solid #3b82f6; }
|
|
944
|
+
.legend-dot.current { background: #d1fae5; border: 1.5px solid #10b981; }
|
|
945
|
+
.legend-dot.pending { background: #f1f5f9; border: 1.5px solid #cbd5e1; }
|
|
946
|
+
.legend-dot.skipped { background: #f9fafb; border: 1.5px dashed #94a3b8; }
|
|
947
|
+
.legend-dot.condition { background: #fef3c7; border: 1.5px solid #f59e0b; }
|
|
948
|
+
.chart-legend .legend-line {
|
|
949
|
+
width: 24px;
|
|
950
|
+
height: 3px;
|
|
951
|
+
margin-right: 8px;
|
|
952
|
+
flex-shrink: 0;
|
|
953
|
+
border-radius: 2px;
|
|
642
954
|
}
|
|
955
|
+
.legend-line.traversed { background: #10b981; }
|
|
956
|
+
.legend-line.predicted { background: repeating-linear-gradient(90deg, #3b82f6 0px, #3b82f6 5px, transparent 5px, transparent 8px); height: 2px; }
|
|
643
957
|
</style>
|
|
644
958
|
</head>
|
|
645
959
|
<body>
|
|
646
960
|
<div class = "loading">Loading...</div>
|
|
647
961
|
<div class = "error-msg">${error_msg}</div>
|
|
962
|
+
<div class="chart-legend">
|
|
963
|
+
<div class="legend-item"><span class="legend-dot completed"></span>已完成</div>
|
|
964
|
+
<div class="legend-item"><span class="legend-dot current"></span>当前步骤</div>
|
|
965
|
+
<div class="legend-item"><span class="legend-dot pending"></span>未流转</div>
|
|
966
|
+
<div class="legend-item"><span class="legend-dot skipped"></span>已跳过</div>
|
|
967
|
+
<div class="legend-item"><span class="legend-dot condition"></span>条件判断</div>
|
|
968
|
+
<div class="legend-item"><span class="legend-line traversed"></span>已流转路径</div>
|
|
969
|
+
<div class="legend-item"><span class="legend-line predicted"></span>预测路径</div>
|
|
970
|
+
</div>
|
|
648
971
|
<div class="mermaid"></div>
|
|
649
972
|
<script type="text/javascript">
|
|
650
973
|
mermaid.initialize({
|