@steedos-labs/plugin-workflow 3.0.83 → 3.0.85
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/manager/uuflow_manager.js +61 -8
- package/main/default/objects/instances/buttons/instance_delete_many.button.yml +1 -1
- package/main/default/pages/flow_selector.page.amis.json +2 -2
- package/main/default/pages/instance_tasks_detail.page.amis.json +4 -7
- package/main/default/pages/page_instance_print.page.amis.json +2 -2
- package/main/default/routes/api_workflow_chart.router.js +384 -59
- package/main/default/routes/api_workflow_export.router.js +2 -1
- package/main/default/server/ejs/export_instances.ejs +12 -4
- package/main/default/test/test_getApproveValues.js +221 -0
- package/package.json +3 -2
- package/public/amis-renderer/amis-renderer.js +1 -1
|
@@ -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 = {
|
|
@@ -112,12 +113,10 @@ const FlowversionAPI = {
|
|
|
112
113
|
|
|
113
114
|
getStepLabel: function (stepName, stepHandlerName) {
|
|
114
115
|
// 返回sstepName与stepHandlerName结合的步骤显示名称
|
|
116
|
+
// 注意:必须单行输出,避免 replaceErrorSymbol 把模板字符串的换行替换成 <br/> 撑高节点
|
|
115
117
|
let nodeStr = "";
|
|
116
118
|
if (stepName) {
|
|
117
|
-
nodeStr = `<div class='graph-node'
|
|
118
|
-
<div class='step-name'>${stepName}</div>
|
|
119
|
-
<div class='step-handler-name'>${stepHandlerName}</div>
|
|
120
|
-
</div>`;
|
|
119
|
+
nodeStr = `<div class='graph-node'><div class='step-name'>${stepName}</div><div class='step-handler-name'>${stepHandlerName}</div></div>`;
|
|
121
120
|
// 把特殊字符清空或替换,以避免mermaidAPI出现异常
|
|
122
121
|
nodeStr = FlowversionAPI.replaceErrorSymbol(nodeStr);
|
|
123
122
|
}
|
|
@@ -136,37 +135,118 @@ const FlowversionAPI = {
|
|
|
136
135
|
return stepName;
|
|
137
136
|
},
|
|
138
137
|
|
|
138
|
+
// 查找从fromStep到targetStepId的边路径(可能经过中间条件节点)
|
|
139
|
+
// 返回边集合,如 ["stepA->conditionB", "conditionB->stepC"]
|
|
140
|
+
findEdgePath: function (fromStep, targetStepId, stepsMap) {
|
|
141
|
+
let edges = [];
|
|
142
|
+
// 直接连线
|
|
143
|
+
if (fromStep.lines) {
|
|
144
|
+
for (let line of fromStep.lines) {
|
|
145
|
+
if (line.to_step === targetStepId) {
|
|
146
|
+
edges.push(`${fromStep._id}->${targetStepId}`);
|
|
147
|
+
return edges;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// 通过条件节点间接连线(BFS,最多2层中间节点)
|
|
151
|
+
for (let line of fromStep.lines) {
|
|
152
|
+
let midStep = stepsMap[line.to_step];
|
|
153
|
+
if (midStep && midStep.step_type === 'condition') {
|
|
154
|
+
if (midStep.lines) {
|
|
155
|
+
for (let midLine of midStep.lines) {
|
|
156
|
+
if (midLine.to_step === targetStepId) {
|
|
157
|
+
edges.push(`${fromStep._id}->${midStep._id}`);
|
|
158
|
+
edges.push(`${midStep._id}->${targetStepId}`);
|
|
159
|
+
return edges;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// 找不到直接路径,简单标记起止边
|
|
167
|
+
edges.push(`${fromStep._id}->${targetStepId}`);
|
|
168
|
+
return edges;
|
|
169
|
+
},
|
|
170
|
+
|
|
139
171
|
generateStepsGraphSyntax: async function (steps, currentStepId, isConvertToString, direction, instance_id, instance) {
|
|
140
172
|
let nodes = [`graph ${direction}`];
|
|
141
173
|
let cachedStepNames = {};
|
|
142
174
|
let skipSteps = new Set(instance?.skip_steps || []);
|
|
175
|
+
let completedStepIds = instance?._completedStepIds || new Set();
|
|
176
|
+
let traversedEdges = instance?._traversedEdges || new Set();
|
|
177
|
+
let predictedEdges = instance?._predictedEdges || new Set();
|
|
178
|
+
let isInstanceFinished = instance?._isFinished === true;
|
|
179
|
+
let linkIndex = 0;
|
|
180
|
+
let traversedLinkIndices = [];
|
|
181
|
+
let predictedLinkIndices = [];
|
|
182
|
+
// 先输出所有边,类指令统一在所有边之后输出,避免 mermaid 因
|
|
183
|
+
// “class 指令出现在节点首次声明之前”而静默忽略 class(V1/V2b 根因)
|
|
143
184
|
for (let step of steps) {
|
|
144
185
|
let lines = step.lines;
|
|
145
186
|
if (lines?.length) {
|
|
146
187
|
for (let line of lines) {
|
|
147
188
|
let stepName = "";
|
|
148
|
-
// 标记条件节点
|
|
149
189
|
if (step.name) {
|
|
150
|
-
if (step.step_type === "condition") {
|
|
151
|
-
nodes.push(` class ${step._id} condition;`);
|
|
152
|
-
}
|
|
153
190
|
stepName = await FlowversionAPI.getStepName(step, cachedStepNames, instance_id, instance);
|
|
154
191
|
} else {
|
|
155
192
|
stepName = "";
|
|
156
193
|
}
|
|
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
194
|
let toStep = steps.find(s => s._id === line.to_step);
|
|
163
195
|
let toStepName = await FlowversionAPI.getStepName(toStep, cachedStepNames, instance_id, instance);
|
|
164
|
-
|
|
196
|
+
if (step.step_type === "condition" && line.name) {
|
|
197
|
+
let lineName = FlowversionAPI.replaceErrorSymbol(line.name);
|
|
198
|
+
nodes.push(` ${step._id}("${stepName}")-->|${lineName}|${line.to_step}("${toStepName}")`);
|
|
199
|
+
} else {
|
|
200
|
+
nodes.push(` ${step._id}("${stepName}")-->${line.to_step}("${toStepName}")`);
|
|
201
|
+
}
|
|
202
|
+
if (traversedEdges.has(`${step._id}->${line.to_step}`)) {
|
|
203
|
+
traversedLinkIndices.push(linkIndex);
|
|
204
|
+
} else if (predictedEdges.has(`${step._id}->${line.to_step}`)) {
|
|
205
|
+
predictedLinkIndices.push(linkIndex);
|
|
206
|
+
}
|
|
207
|
+
linkIndex++;
|
|
165
208
|
}
|
|
166
209
|
}
|
|
167
210
|
}
|
|
168
|
-
|
|
169
|
-
|
|
211
|
+
// 覆盖 mermaid 默认主题对未流转(default)节点的紫色 inline style,使其匹配图例的灰色
|
|
212
|
+
nodes.push(` classDef default fill:#f1f5f9,stroke:#cbd5e1,stroke-width:1.5px,color:#475569;`);
|
|
213
|
+
// 统一输出 class 指令,优先级:skip-step > current-step-node > completed-step > condition
|
|
214
|
+
// 已结束流程(isInstanceFinished)不再标记 current-step-node(V3),最后一步按已完成展示
|
|
215
|
+
let classOf = new Map();
|
|
216
|
+
let assign = (id, cls) => { if (!classOf.has(id)) classOf.set(id, cls); };
|
|
217
|
+
if (!isInstanceFinished && currentStepId) {
|
|
218
|
+
assign(currentStepId, 'current-step-node');
|
|
219
|
+
}
|
|
220
|
+
for (let step of steps) {
|
|
221
|
+
if (skipSteps.has(step._id)) {
|
|
222
|
+
assign(step._id, 'skip-step');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
for (let stepId of completedStepIds) {
|
|
226
|
+
if (skipSteps.has(stepId)) continue;
|
|
227
|
+
if (!isInstanceFinished && stepId === currentStepId) continue;
|
|
228
|
+
assign(stepId, 'completed-step');
|
|
229
|
+
}
|
|
230
|
+
for (let step of steps) {
|
|
231
|
+
if (step.step_type === 'condition') {
|
|
232
|
+
assign(step._id, 'condition');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
for (let [id, cls] of classOf) {
|
|
236
|
+
nodes.push(` class ${id} ${cls};`);
|
|
237
|
+
}
|
|
238
|
+
// 先将所有连线设为灰色(不会流转的路径)
|
|
239
|
+
if (linkIndex > 0) {
|
|
240
|
+
let allIndices = Array.from({length: linkIndex}, (_, i) => i).join(',');
|
|
241
|
+
nodes.push(` linkStyle ${allIndices} stroke:#cbd5e1,stroke-width:1.5px;`);
|
|
242
|
+
}
|
|
243
|
+
// 高亮已流转的连线(绿色实线)
|
|
244
|
+
if (traversedLinkIndices.length > 0) {
|
|
245
|
+
nodes.push(` linkStyle ${traversedLinkIndices.join(',')} stroke:#10b981,stroke-width:3px;`);
|
|
246
|
+
}
|
|
247
|
+
// 高亮预测的下一步路径(蓝色虚线)
|
|
248
|
+
if (predictedLinkIndices.length > 0) {
|
|
249
|
+
nodes.push(` linkStyle ${predictedLinkIndices.join(',')} stroke:#3b82f6,stroke-width:2px,stroke-dasharray:5 3;`);
|
|
170
250
|
}
|
|
171
251
|
if (isConvertToString) {
|
|
172
252
|
let graphSyntax = nodes.join("\n");
|
|
@@ -212,12 +292,10 @@ const FlowversionAPI = {
|
|
|
212
292
|
|
|
213
293
|
getTraceName: function (traceName, approveHandlerName) {
|
|
214
294
|
// 返回trace节点名称
|
|
295
|
+
// 注意:必须单行输出,避免 replaceErrorSymbol 把模板字符串的换行替换成 <br/> 撑高节点
|
|
215
296
|
let nodeStr = "";
|
|
216
297
|
if (traceName) {
|
|
217
|
-
nodeStr = `<div class='graph-node'
|
|
218
|
-
<div class='trace-name'>${traceName}</div>
|
|
219
|
-
<div class='trace-handler-name'>${approveHandlerName}</div>
|
|
220
|
-
</div>`;
|
|
298
|
+
nodeStr = `<div class='graph-node'><div class='trace-name'>${traceName}</div><div class='trace-handler-name'>${approveHandlerName}</div></div>`;
|
|
221
299
|
nodeStr = FlowversionAPI.replaceErrorSymbol(nodeStr);
|
|
222
300
|
}
|
|
223
301
|
return nodeStr;
|
|
@@ -505,12 +583,124 @@ const FlowversionAPI = {
|
|
|
505
583
|
break;
|
|
506
584
|
}
|
|
507
585
|
default: {
|
|
508
|
-
let instance = await db.instances.findOne({ _id: instance_id }
|
|
586
|
+
let instance = await db.instances.findOne({ _id: instance_id });
|
|
509
587
|
if (instance) {
|
|
510
|
-
let
|
|
588
|
+
let traces = instance.traces || [];
|
|
589
|
+
let currentStepId = traces.length > 0 ? traces[traces.length - 1]?.step : null;
|
|
590
|
+
// 收集已完成的步骤ID和已流转的路径(from_step -> to_step)
|
|
591
|
+
let completedStepIds = new Set();
|
|
592
|
+
let traversedEdges = new Set();
|
|
593
|
+
for (let i = 0; i < traces.length; i++) {
|
|
594
|
+
let trace = traces[i];
|
|
595
|
+
if (trace.step) {
|
|
596
|
+
completedStepIds.add(trace.step);
|
|
597
|
+
}
|
|
598
|
+
// 从前一个trace到当前trace的连线视为已流转
|
|
599
|
+
if (i > 0 && traces[i - 1].step && trace.step) {
|
|
600
|
+
traversedEdges.add(`${traces[i - 1].step}->${trace.step}`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
instance._completedStepIds = completedStepIds;
|
|
604
|
+
instance._traversedEdges = traversedEdges;
|
|
605
|
+
let isInstanceFinished = instance.state === 'completed' || instance.state === 'terminated';
|
|
606
|
+
instance._isFinished = isInstanceFinished;
|
|
607
|
+
|
|
608
|
+
// 只有进行中的单子才预测后续路径,已结束的单子不需要预测
|
|
609
|
+
let predictedEdges = new Set();
|
|
610
|
+
try {
|
|
611
|
+
if (currentStepId && !isInstanceFinished) {
|
|
612
|
+
let flow = await UUFlowManager.getFlow(instance.flow);
|
|
613
|
+
let flowversion = UUFlowManager.getFlowVersion(flow, instance.flow_version);
|
|
614
|
+
// 取最新trace的最新approve的_id作为参数
|
|
615
|
+
let latestTrace = traces[traces.length - 1];
|
|
616
|
+
let latestApproveId = null;
|
|
617
|
+
if (latestTrace?.approves?.length) {
|
|
618
|
+
latestApproveId = latestTrace.approves[latestTrace.approves.length - 1]._id;
|
|
619
|
+
}
|
|
620
|
+
let values = UUFlowManager.getUpdatedValues(instance, latestApproveId);
|
|
621
|
+
if (process.env.STEEDOS_DEBUG) {
|
|
622
|
+
console.log('values===>', values, latestApproveId);
|
|
623
|
+
}
|
|
624
|
+
let stepsMap = {};
|
|
625
|
+
for (let s of flowversion.steps) {
|
|
626
|
+
stepsMap[s._id] = s;
|
|
627
|
+
}
|
|
628
|
+
// 递归预测,最多追踪20步防止死循环
|
|
629
|
+
let visited = new Set();
|
|
630
|
+
let predictFrom = [currentStepId];
|
|
631
|
+
let maxDepth = 20;
|
|
632
|
+
let depth = 0;
|
|
633
|
+
while (predictFrom.length > 0 && depth < maxDepth) {
|
|
634
|
+
depth++;
|
|
635
|
+
let nextBatch = [];
|
|
636
|
+
for (let fromStepId of predictFrom) {
|
|
637
|
+
if (visited.has(fromStepId)) continue;
|
|
638
|
+
visited.add(fromStepId);
|
|
639
|
+
let fromStep = stepsMap[fromStepId];
|
|
640
|
+
if (!fromStep || fromStep.step_type === 'end') continue;
|
|
641
|
+
|
|
642
|
+
if (fromStep.step_type === 'condition') {
|
|
643
|
+
// 条件节点:优先按条件表达式评估;若返回空(如表单字段未填,
|
|
644
|
+
// 无法判断走哪个分支),fallback 展开所有出线,保证预测路径继续延伸(V4)
|
|
645
|
+
let nextStepIds = await UUFlowManager.getNextSteps(instance, flow, fromStep, '', values);
|
|
646
|
+
if (!nextStepIds || nextStepIds.length === 0) {
|
|
647
|
+
nextStepIds = (fromStep.lines || []).map(l => l.to_step);
|
|
648
|
+
}
|
|
649
|
+
for (let nextId of nextStepIds || []) {
|
|
650
|
+
let nextStep = stepsMap[nextId];
|
|
651
|
+
if (!nextStep) continue;
|
|
652
|
+
predictedEdges.add(`${fromStepId}->${nextId}`);
|
|
653
|
+
if (!visited.has(nextId)) {
|
|
654
|
+
nextBatch.push(nextId);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
} else {
|
|
658
|
+
// 非条件步骤:直接根据lines找下一步,不通过getNextSteps(它会把条件节点的所有分支都展开)
|
|
659
|
+
let judge = fromStep.step_type === 'sign' ? 'approved' : 'submitted';
|
|
660
|
+
let lines = (fromStep.lines || []).filter(l => l.state === judge);
|
|
661
|
+
for (let line of lines) {
|
|
662
|
+
let toStep = stepsMap[line.to_step];
|
|
663
|
+
if (!toStep) continue;
|
|
664
|
+
predictedEdges.add(`${fromStepId}->${line.to_step}`);
|
|
665
|
+
if (!visited.has(line.to_step)) {
|
|
666
|
+
nextBatch.push(line.to_step);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
predictFrom = nextBatch;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
} catch (e) {
|
|
675
|
+
// 预测失败不影响主流程,但输出详细错误信息便于排查
|
|
676
|
+
console.warn('Chart prediction failed:', e.message, e.stack);
|
|
677
|
+
}
|
|
678
|
+
instance._predictedEdges = predictedEdges;
|
|
679
|
+
|
|
511
680
|
let flowversion = await WorkflowManager.getInstanceFlowVersion(instance);
|
|
512
681
|
let steps = flowversion?.steps;
|
|
513
682
|
if (steps?.length) {
|
|
683
|
+
// 解析经过条件节点的已流转边,将 stepA->stepC 展开为 stepA->conditionB, conditionB->stepC
|
|
684
|
+
if (traversedEdges.size > 0) {
|
|
685
|
+
let stepsMap = {};
|
|
686
|
+
for (let s of steps) {
|
|
687
|
+
stepsMap[s._id] = s;
|
|
688
|
+
}
|
|
689
|
+
let resolvedTraversedEdges = new Set();
|
|
690
|
+
for (let edge of traversedEdges) {
|
|
691
|
+
let [fromId, toId] = edge.split('->');
|
|
692
|
+
let fromStep = stepsMap[fromId];
|
|
693
|
+
if (fromStep) {
|
|
694
|
+
let edgePaths = FlowversionAPI.findEdgePath(fromStep, toId, stepsMap);
|
|
695
|
+
for (let e of edgePaths) {
|
|
696
|
+
resolvedTraversedEdges.add(e);
|
|
697
|
+
}
|
|
698
|
+
} else {
|
|
699
|
+
resolvedTraversedEdges.add(edge);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
instance._traversedEdges = resolvedTraversedEdges;
|
|
703
|
+
}
|
|
514
704
|
graphSyntax = await FlowversionAPI.generateStepsGraphSyntax(steps, currentStepId, false, direction, instance_id, instance);
|
|
515
705
|
} else {
|
|
516
706
|
error_msg = "没有找到当前申请单的流程步骤数据";
|
|
@@ -535,9 +725,11 @@ const FlowversionAPI = {
|
|
|
535
725
|
<script type="text/javascript" src="/unpkg.com/mermaid@9.1.2/dist/mermaid.min.js"></script>
|
|
536
726
|
<style>
|
|
537
727
|
body {
|
|
538
|
-
font-family:
|
|
728
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
539
729
|
text-align: center;
|
|
540
|
-
background-color: #
|
|
730
|
+
background-color: #f8fafc;
|
|
731
|
+
margin: 0;
|
|
732
|
+
padding: 20px;
|
|
541
733
|
}
|
|
542
734
|
.loading{
|
|
543
735
|
position: absolute;
|
|
@@ -547,8 +739,9 @@ const FlowversionAPI = {
|
|
|
547
739
|
z-index: 1100;
|
|
548
740
|
text-align: center;
|
|
549
741
|
margin-top: -30px;
|
|
550
|
-
font-size:
|
|
551
|
-
color: #
|
|
742
|
+
font-size: 20px;
|
|
743
|
+
color: #94a3b8;
|
|
744
|
+
font-weight: 500;
|
|
552
745
|
}
|
|
553
746
|
.error-msg{
|
|
554
747
|
position: absolute;
|
|
@@ -557,65 +750,143 @@ const FlowversionAPI = {
|
|
|
557
750
|
bottom: 20px;
|
|
558
751
|
z-index: 1100;
|
|
559
752
|
text-align: center;
|
|
560
|
-
font-size:
|
|
561
|
-
color: #
|
|
753
|
+
font-size: 16px;
|
|
754
|
+
color: #dc2626;
|
|
562
755
|
}
|
|
756
|
+
/* 未流转的步骤 - 浅灰色 */
|
|
563
757
|
#flow-steps-svg .node rect{
|
|
564
|
-
fill: #
|
|
565
|
-
stroke:
|
|
758
|
+
fill: #f1f5f9;
|
|
759
|
+
stroke: #cbd5e1;
|
|
760
|
+
stroke-width: 1.5px;
|
|
761
|
+
rx: 8px;
|
|
762
|
+
ry: 8px;
|
|
763
|
+
}
|
|
764
|
+
/* 已完成的步骤 - 蓝色调 */
|
|
765
|
+
#flow-steps-svg .node.completed-step rect{
|
|
766
|
+
fill: #dbeafe;
|
|
767
|
+
stroke: #3b82f6;
|
|
566
768
|
stroke-width: 2px;
|
|
567
769
|
}
|
|
770
|
+
#flow-steps-svg .node.completed-step .step-name{
|
|
771
|
+
color: #1e40af;
|
|
772
|
+
}
|
|
773
|
+
#flow-steps-svg .node.completed-step .step-handler-name{
|
|
774
|
+
color: #3b82f6;
|
|
775
|
+
}
|
|
776
|
+
/* 当前步骤 - 绿色高亮 + 呼吸动画 */
|
|
568
777
|
#flow-steps-svg .node.current-step-node rect{
|
|
569
|
-
fill: #
|
|
570
|
-
stroke: #
|
|
571
|
-
stroke-width:
|
|
778
|
+
fill: #d1fae5;
|
|
779
|
+
stroke: #10b981;
|
|
780
|
+
stroke-width: 2.5px;
|
|
781
|
+
animation: currentStepPulse 2s ease-in-out infinite;
|
|
782
|
+
}
|
|
783
|
+
@keyframes currentStepPulse {
|
|
784
|
+
0%, 100% { stroke-width: 2.5px; filter: drop-shadow(0 0 0px rgba(16,185,129,0)); }
|
|
785
|
+
50% { stroke-width: 3px; filter: drop-shadow(0 0 6px rgba(16,185,129,0.4)); }
|
|
786
|
+
}
|
|
787
|
+
#flow-steps-svg .node.current-step-node .step-name{
|
|
788
|
+
color: #065f46;
|
|
789
|
+
font-weight: 600;
|
|
572
790
|
}
|
|
791
|
+
#flow-steps-svg .node.current-step-node .step-handler-name{
|
|
792
|
+
color: #10b981;
|
|
793
|
+
}
|
|
794
|
+
/* 条件节点 */
|
|
573
795
|
#flow-steps-svg .node.condition rect{
|
|
574
|
-
fill: #
|
|
575
|
-
stroke:
|
|
576
|
-
stroke-width:
|
|
796
|
+
fill: #fef3c7;
|
|
797
|
+
stroke: #f59e0b;
|
|
798
|
+
stroke-width: 1.5px;
|
|
799
|
+
}
|
|
800
|
+
#flow-steps-svg .node.condition .step-name{
|
|
801
|
+
color: #92400e;
|
|
577
802
|
}
|
|
803
|
+
/* 跳过的步骤 */
|
|
578
804
|
#flow-steps-svg .node.skip-step rect{
|
|
579
|
-
fill: #
|
|
580
|
-
stroke: #
|
|
581
|
-
stroke-width:
|
|
805
|
+
fill: #f9fafb;
|
|
806
|
+
stroke: #d1d5db;
|
|
807
|
+
stroke-width: 1.5px;
|
|
582
808
|
stroke-dasharray: 5, 5;
|
|
583
|
-
opacity: 0.
|
|
809
|
+
opacity: 0.6;
|
|
584
810
|
}
|
|
585
811
|
#flow-steps-svg .node.skip-step .step-name{
|
|
586
|
-
color: #
|
|
812
|
+
color: #9ca3af;
|
|
587
813
|
text-decoration: line-through;
|
|
588
814
|
}
|
|
589
815
|
#flow-steps-svg .node.skip-step .step-handler-name{
|
|
590
|
-
color: #
|
|
816
|
+
color: #d1d5db;
|
|
591
817
|
}
|
|
592
|
-
|
|
593
|
-
|
|
818
|
+
/* 节点文字样式 */
|
|
819
|
+
#flow-steps-svg .node .step-name{
|
|
820
|
+
color: #64748b;
|
|
821
|
+
font-size: 14px;
|
|
594
822
|
}
|
|
595
823
|
#flow-steps-svg .node .step-handler-name{
|
|
596
|
-
color: #
|
|
824
|
+
color: #94a3b8;
|
|
825
|
+
font-size: 12px;
|
|
826
|
+
}
|
|
827
|
+
#flow-steps-svg .node .trace-handler-name{
|
|
828
|
+
color: #94a3b8;
|
|
829
|
+
font-size: 12px;
|
|
830
|
+
}
|
|
831
|
+
/* 默认连线样式 */
|
|
832
|
+
#flow-steps-svg .edgePath path{
|
|
833
|
+
stroke: #cbd5e1;
|
|
834
|
+
stroke-width: 1.5px;
|
|
835
|
+
}
|
|
836
|
+
#flow-steps-svg marker path{
|
|
837
|
+
fill: #cbd5e1;
|
|
838
|
+
}
|
|
839
|
+
/* 连线上的文字标签 */
|
|
840
|
+
#flow-steps-svg .edgeLabel{
|
|
841
|
+
background-color: transparent;
|
|
842
|
+
padding: 0;
|
|
843
|
+
font-size: 12px;
|
|
844
|
+
color: #64748b;
|
|
845
|
+
}
|
|
846
|
+
#flow-steps-svg .edgeLabel span{
|
|
847
|
+
color: #64748b !important;
|
|
848
|
+
background: transparent !important;
|
|
849
|
+
}
|
|
850
|
+
#flow-steps-svg .edgeLabel span:not(:empty){
|
|
851
|
+
background: #f8fafc !important;
|
|
852
|
+
padding: 2px 6px;
|
|
853
|
+
border-radius: 4px;
|
|
597
854
|
}
|
|
598
855
|
div.mermaidTooltip{
|
|
599
856
|
position: fixed!important;
|
|
600
857
|
text-align: left!important;
|
|
601
|
-
padding:
|
|
602
|
-
font-size:
|
|
858
|
+
padding: 8px 12px!important;
|
|
859
|
+
font-size: 13px!important;
|
|
603
860
|
max-width: 500px!important;
|
|
604
861
|
left: auto!important;
|
|
605
862
|
top: 15px!important;
|
|
606
863
|
right: 15px;
|
|
864
|
+
border-radius: 6px;
|
|
865
|
+
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
|
|
866
|
+
}
|
|
867
|
+
.graph-node {
|
|
868
|
+
padding: 4px 8px;
|
|
869
|
+
}
|
|
870
|
+
.node foreignObject > div {
|
|
871
|
+
height: 100% !important;
|
|
872
|
+
display: flex !important;
|
|
873
|
+
align-items: center;
|
|
874
|
+
justify-content: center;
|
|
875
|
+
}
|
|
876
|
+
.graph-node .step-name {
|
|
877
|
+
font-weight: 500;
|
|
878
|
+
margin-bottom: 2px;
|
|
607
879
|
}
|
|
608
880
|
.btn-zoom{
|
|
609
|
-
|
|
610
|
-
border-color: transparent;
|
|
881
|
+
border: none;
|
|
611
882
|
display: inline-block;
|
|
612
|
-
padding:
|
|
613
|
-
font-size:
|
|
614
|
-
border-radius:
|
|
615
|
-
background: #
|
|
616
|
-
color: #
|
|
883
|
+
padding: 6px 14px;
|
|
884
|
+
font-size: 20px;
|
|
885
|
+
border-radius: 8px;
|
|
886
|
+
background: #fff;
|
|
887
|
+
color: #475569;
|
|
617
888
|
position: fixed;
|
|
618
|
-
bottom:
|
|
889
|
+
bottom: 20px;
|
|
619
890
|
outline: none;
|
|
620
891
|
cursor: pointer;
|
|
621
892
|
z-index: 99999;
|
|
@@ -624,6 +895,8 @@ const FlowversionAPI = {
|
|
|
624
895
|
-ms-user-select: none;
|
|
625
896
|
user-select: none;
|
|
626
897
|
line-height: 1.2;
|
|
898
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.06);
|
|
899
|
+
transition: all 0.15s ease;
|
|
627
900
|
}
|
|
628
901
|
@media (max-width: 768px) {
|
|
629
902
|
.btn-zoom{
|
|
@@ -631,20 +904,72 @@ const FlowversionAPI = {
|
|
|
631
904
|
}
|
|
632
905
|
}
|
|
633
906
|
.btn-zoom:hover{
|
|
634
|
-
background:
|
|
907
|
+
background: #f1f5f9;
|
|
908
|
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
635
909
|
}
|
|
636
910
|
.btn-zoom-up{
|
|
637
|
-
left:
|
|
911
|
+
left: 20px;
|
|
638
912
|
}
|
|
639
913
|
.btn-zoom-down{
|
|
640
|
-
left:
|
|
641
|
-
|
|
914
|
+
left: 70px;
|
|
915
|
+
}
|
|
916
|
+
/* 图例 */
|
|
917
|
+
.chart-legend {
|
|
918
|
+
position: fixed;
|
|
919
|
+
top: 15px;
|
|
920
|
+
right: 15px;
|
|
921
|
+
background: #fff;
|
|
922
|
+
border-radius: 8px;
|
|
923
|
+
padding: 12px 16px;
|
|
924
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
925
|
+
font-size: 12px;
|
|
926
|
+
color: #475569;
|
|
927
|
+
text-align: left;
|
|
928
|
+
z-index: 99999;
|
|
929
|
+
}
|
|
930
|
+
.chart-legend .legend-item {
|
|
931
|
+
display: flex;
|
|
932
|
+
align-items: center;
|
|
933
|
+
margin-bottom: 6px;
|
|
934
|
+
}
|
|
935
|
+
.chart-legend .legend-item:last-child {
|
|
936
|
+
margin-bottom: 0;
|
|
937
|
+
}
|
|
938
|
+
.chart-legend .legend-dot {
|
|
939
|
+
width: 12px;
|
|
940
|
+
height: 12px;
|
|
941
|
+
border-radius: 3px;
|
|
942
|
+
margin-right: 8px;
|
|
943
|
+
flex-shrink: 0;
|
|
944
|
+
}
|
|
945
|
+
.legend-dot.completed { background: #dbeafe; border: 1.5px solid #3b82f6; }
|
|
946
|
+
.legend-dot.current { background: #d1fae5; border: 1.5px solid #10b981; }
|
|
947
|
+
.legend-dot.pending { background: #f1f5f9; border: 1.5px solid #cbd5e1; }
|
|
948
|
+
.legend-dot.skipped { background: #f9fafb; border: 1.5px dashed #94a3b8; }
|
|
949
|
+
.legend-dot.condition { background: #fef3c7; border: 1.5px solid #f59e0b; }
|
|
950
|
+
.chart-legend .legend-line {
|
|
951
|
+
width: 24px;
|
|
952
|
+
height: 3px;
|
|
953
|
+
margin-right: 8px;
|
|
954
|
+
flex-shrink: 0;
|
|
955
|
+
border-radius: 2px;
|
|
642
956
|
}
|
|
957
|
+
.legend-line.traversed { background: #10b981; }
|
|
958
|
+
.legend-line.predicted { background: repeating-linear-gradient(90deg, #3b82f6 0px, #3b82f6 5px, transparent 5px, transparent 8px); height: 2px; }
|
|
643
959
|
</style>
|
|
644
960
|
</head>
|
|
645
961
|
<body>
|
|
646
962
|
<div class = "loading">Loading...</div>
|
|
647
963
|
<div class = "error-msg">${error_msg}</div>
|
|
964
|
+
<div class="chart-legend">
|
|
965
|
+
<div class="legend-item"><span class="legend-dot completed"></span>已完成</div>
|
|
966
|
+
<div class="legend-item"><span class="legend-dot current"></span>当前步骤</div>
|
|
967
|
+
<div class="legend-item"><span class="legend-dot pending"></span>未流转</div>
|
|
968
|
+
<div class="legend-item"><span class="legend-dot skipped"></span>已跳过</div>
|
|
969
|
+
<div class="legend-item"><span class="legend-dot condition"></span>条件判断</div>
|
|
970
|
+
<div class="legend-item"><span class="legend-line traversed"></span>已流转路径</div>
|
|
971
|
+
<div class="legend-item"><span class="legend-line predicted"></span>预测路径</div>
|
|
972
|
+
</div>
|
|
648
973
|
<div class="mermaid"></div>
|
|
649
974
|
<script type="text/javascript">
|
|
650
975
|
mermaid.initialize({
|
|
@@ -113,7 +113,8 @@ router.get('/api/workflow/export/instances', requireAuthentication, async functi
|
|
|
113
113
|
const str = fs.readFileSync(path.resolve(__dirname, '../server/ejs/export_instances.ejs'), 'utf8');
|
|
114
114
|
const template = ejs.compile(str);
|
|
115
115
|
let lang = 'en';
|
|
116
|
-
|
|
116
|
+
const locale = String(userSession.locale || userSession.language || '').toLowerCase().replace('_', '-');
|
|
117
|
+
if (locale.startsWith('zh')) {
|
|
117
118
|
lang = 'zh-CN';
|
|
118
119
|
}
|
|
119
120
|
const utcOffset = timezoneoffset / -60;
|
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
<Worksheet ss:Name="<%= form_name %>" >
|
|
9
9
|
<Table>
|
|
10
|
+
<%
|
|
11
|
+
var getFieldDisplayName = function(field) {
|
|
12
|
+
if (!field) {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
return field.label || field.title || field.name || field.code || '';
|
|
16
|
+
};
|
|
17
|
+
%>
|
|
10
18
|
<!-- 编号 -->
|
|
11
19
|
<Column ss:Width="35"/>
|
|
12
20
|
<!-- 名称 -->
|
|
@@ -52,13 +60,13 @@
|
|
|
52
60
|
<Cell><Data ss:Type="String"><%= t('export.current_step_start_date',{},lang) %></Data></Cell>
|
|
53
61
|
<% _.each(fields, function(field) { %>
|
|
54
62
|
<% if (field.type != "table" && field.type != "section") { %>
|
|
55
|
-
<Cell><Data ss:Type="String"><%= field
|
|
63
|
+
<Cell><Data ss:Type="String"><%= getFieldDisplayName(field) %></Data></Cell>
|
|
56
64
|
<% } %>
|
|
57
65
|
|
|
58
66
|
<% if (field.type == "section" && field.fields) {
|
|
59
67
|
_.each(field.fields, function(sec) {
|
|
60
68
|
%>
|
|
61
|
-
<Cell><Data ss:Type="String"><%= sec
|
|
69
|
+
<Cell><Data ss:Type="String"><%= getFieldDisplayName(sec) %></Data></Cell>
|
|
62
70
|
<% }) %>
|
|
63
71
|
<% } %>
|
|
64
72
|
<% }) %>
|
|
@@ -259,7 +267,7 @@
|
|
|
259
267
|
</Worksheet>
|
|
260
268
|
|
|
261
269
|
<% _.each(table_fields, function(table_field) { %>
|
|
262
|
-
<Worksheet ss:Name="<%= table_field
|
|
270
|
+
<Worksheet ss:Name="<%= getFieldDisplayName(table_field) %>" >
|
|
263
271
|
<Table>
|
|
264
272
|
<!-- 编号 -->
|
|
265
273
|
<Column ss:Width="35"/>
|
|
@@ -296,7 +304,7 @@
|
|
|
296
304
|
<Cell><Data ss:Type="String"><%= t('export.current_step_name',{},lang) %></Data></Cell>
|
|
297
305
|
<Cell><Data ss:Type="String"><%= t('export.current_step_start_date',{},lang) %></Data></Cell>
|
|
298
306
|
<% _.each(table_field.fields, function(field) { %>
|
|
299
|
-
|
|
307
|
+
<Cell><Data ss:Type="String"><%= getFieldDisplayName(field) %></Data></Cell>
|
|
300
308
|
<% }) %>
|
|
301
309
|
</Row>
|
|
302
310
|
<% _.each(ins_to_xls, function(ins) { %>
|