+
+
+
@@ -39,8 +44,9 @@
import { VueFlow, useVueFlow } from "@vue-flow/core";
import { Background } from "@vue-flow/background";
import { Controls } from "@vue-flow/controls";
+import DagreEdge from "./flow/DagreEdge.vue";
import MaterialNode from "./flow/MaterialNode.vue";
-import { layoutGraph } from "./flow/dagreLayout";
+import { assignEdgePorts, layoutGraphWithEdges, NODE_W } from "./flow/dagreLayout";
import "@vue-flow/core/dist/style.css";
import "@vue-flow/core/dist/theme-default.css";
import "@vue-flow/controls/dist/style.css";
@@ -54,7 +60,7 @@ let viewerSeq = 0;
*/
export default {
name: "xtFlowViewer",
- components: { VueFlow, Background, Controls, MaterialNode },
+ components: { VueFlow, Background, Controls, DagreEdge, MaterialNode },
props: {
nodes: { type: Array, default: () => [] },
edges: { type: Array, default: () => [] },
@@ -62,14 +68,17 @@ export default {
},
setup() {
const flowId = "xt-flow-viewer-" + ++viewerSeq;
- const { fitView, setNodes, setEdges } = useVueFlow(flowId);
- return { flowId, fitView, setNodes, setEdges };
+ const { setViewport, setNodes, setEdges } = useVueFlow(flowId);
+ return { flowId, setViewport, setNodes, setEdges };
},
data() {
return {
// 首次 fit 不带动画,避免打开时视图跳动
hasFitted: false,
rebuildPending: false,
+ renderedNodes: [],
+ resizeObserver: null,
+ resizeTimer: null,
};
},
computed: {
@@ -87,6 +96,19 @@ export default {
},
mounted() {
this.rebuild();
+ if (typeof ResizeObserver !== "undefined") {
+ this.resizeObserver = new ResizeObserver(() => {
+ clearTimeout(this.resizeTimer);
+ this.resizeTimer = setTimeout(() => {
+ if (this.hasFitted) this.fitToWidth(false);
+ }, 80);
+ });
+ this.resizeObserver.observe(this.$refs.canvas);
+ }
+ },
+ beforeUnmount() {
+ if (this.resizeObserver) this.resizeObserver.disconnect();
+ clearTimeout(this.resizeTimer);
},
methods: {
/** nodes/edges 通常被先后赋值,用微任务合并成一次 rebuild,避免双次 dagre 布局 */
@@ -104,19 +126,19 @@ export default {
const targetIds = new Set();
const rawEdges = (this.edges || [])
.filter((e) => e.source != null && e.target != null)
- .map((e) => {
+ .map((e, index) => {
const sid = String(e.source);
const tid = String(e.target);
sourceIds.add(sid);
targetIds.add(tid);
return {
- id: e.id != null ? String(e.id) : sid + "-" + tid,
+ id: e.id != null ? String(e.id) : `${sid}-${tid}-${edgeIndex}`,
source: sid,
target: tid,
label: e.label || "",
- type: "smoothstep",
+ type: "dagre",
markerEnd: "arrowclosed",
- style: { stroke: "#409eff", strokeWidth: 2 },
+ style: { stroke: "#409eff", strokeWidth: 2.2 },
labelStyle: { fill: "#5b6370", fontSize: "12px", fontWeight: 500 },
labelBgStyle: { fill: "#ffffff", stroke: "#e4e7ed", strokeWidth: 1 },
labelBgPadding: [7, 4],
@@ -137,19 +159,49 @@ export default {
const nodeIds = new Set(vNodes.map((n) => n.id));
const vEdges = rawEdges.filter((e) => nodeIds.has(e.source) && nodeIds.has(e.target));
+ const laidOutGraph = layoutGraphWithEdges(vNodes, vEdges, {
+ rankdir: this.rankdir,
+ nodesep: 40,
+ edgesep: 25,
+ ranksep: 20,
+ marginx: 80,
+ marginy: 10,
+ });
+ const routedGraph = assignEdgePorts(laidOutGraph.nodes, laidOutGraph.edges);
+ this.renderedNodes = routedGraph.nodes;
this.setEdges([]);
- this.setNodes(layoutGraph(vNodes, vEdges, { rankdir: this.rankdir }));
- this.setEdges(vEdges);
- // 首次挂载交给 nodes-initialized(尺寸测量完成)再 fit;数据更新时尺寸已就绪,直接 fit
- if (this.hasFitted) this.doFit();
+ this.setNodes(this.renderedNodes);
+ this.setEdges(routedGraph.edges);
+ // 首次挂载交给 nodes-initialized(尺寸测量完成)再定位;数据更新时直接重新适配宽度
+ if (this.hasFitted) this.fitToWidth();
},
onNodesInit() {
- this.doFit();
+ this.fitToWidth();
},
- doFit() {
+ /**
+ * 只按宽度缩放,长流程不再为了塞进高度而缩成缩略图。
+ * 图从画布顶部开始展示,用户可拖动画布继续向下查看。
+ */
+ fitToWidth(animated = true) {
this.$nextTick(() => {
try {
- this.fitView({ padding: 0.2, maxZoom: 1, duration: this.hasFitted ? 200 : 0 });
+ const canvas = this.$refs.canvas;
+ if (!canvas || !this.renderedNodes.length) return;
+ const xs = this.renderedNodes.map((n) => n.position.x);
+ const ys = this.renderedNodes.map((n) => n.position.y);
+ const minX = Math.min(...xs);
+ const maxX = Math.max(...xs) + NODE_W;
+ const minY = Math.min(...ys);
+ const paddingX = 48;
+ const paddingTop = 36;
+ const graphWidth = Math.max(maxX - minX, NODE_W);
+ const zoom = Math.min(1, Math.max(0.2, (canvas.clientWidth - paddingX * 2) / graphWidth));
+ const x = (canvas.clientWidth - graphWidth * zoom) / 2 - minX * zoom;
+ const y = paddingTop - minY * zoom;
+ this.setViewport(
+ { x, y, zoom },
+ { duration: animated && this.hasFitted ? 200 : 0 }
+ );
this.hasFitted = true;
} catch (e) {
/* noop */
diff --git a/src/views/mtm/process.vue b/src/views/mtm/process.vue
index 2ab1573d..8873f000 100644
--- a/src/views/mtm/process.vue
+++ b/src/views/mtm/process.vue
@@ -59,14 +59,9 @@
-
+
-
- 是
-
-
+ {{ wmScopes[scope.row.into_wm_scope] }}
@@ -211,6 +206,11 @@ export default {
20:'切分',
30:'结合'
},
+ wmScopes:{
+ 10:'工段',
+ 20:'部门',
+ 30:'全局'
+ },
currentItem:{},
selectValue:[],
visible:false,
diff --git a/src/views/mtm/process_form.vue b/src/views/mtm/process_form.vue
index 405d00d0..0b4cef92 100644
--- a/src/views/mtm/process_form.vue
+++ b/src/views/mtm/process_form.vue
@@ -84,10 +84,15 @@
-
-
+
+
+
+
@@ -133,7 +138,7 @@ const defaultForm = {
mtype:10,
belong_dept: "",
wpr_number_rule: "",
- into_wm_mgroup: true,
+ into_wm_scope: 20,
store_notok: true,
has_ok_b_defect:false,
};
@@ -165,6 +170,11 @@ export default {
{name:'切分',value:20},
{name:'结合',value:30},
],
+ wmScopeOptions:[
+ {name:'工段',value:10},
+ {name:'部门',value:20},
+ {name:'全局',value:30},
+ ],
visible: false,
isSaveing: false,
deptOptions: [],
diff --git a/src/views/mtm/route_form.vue b/src/views/mtm/route_form.vue
index aefbcd5d..5b04df8c 100644
--- a/src/views/mtm/route_form.vue
+++ b/src/views/mtm/route_form.vue
@@ -13,6 +13,7 @@
ref="dialogForm"
:model="form"
:rules="rules"
+ :disabled="mode === 'show'"
label-position="right"
label-width="100px"
>
@@ -197,7 +198,7 @@
-
+
@@ -226,9 +227,10 @@
-
+
-
+
- 提交
- 取消
+ 提交
+ {{ mode === "show" ? "关闭" : "取消" }}
@@ -347,7 +349,7 @@ export default {
fileurl_form:[],
paramsJsonFileurl:[],
materialOptions:[],
- titleMap: { add: "新增", edit: "编辑" },
+ titleMap: { add: "新增", edit: "编辑", show: "工序详情" },
setFiltersVisible: false,
routeId: "",
processName: "",
@@ -505,6 +507,9 @@ export default {
let that = this;
that.mode = mode;
that.resetForm();
+ if (that.project_code == "bxerp" && (mode === "edit" || mode === "show")) {
+ that.getMaterialOptions();
+ }
if (that.prefill) that.setData(that.prefill);
that.visible = true;
that.$nextTick(() => {
diff --git a/src/views/mtm/route_show.vue b/src/views/mtm/route_show.vue
index 95663b62..4c537ece 100644
--- a/src/views/mtm/route_show.vue
+++ b/src/views/mtm/route_show.vue
@@ -22,10 +22,9 @@
工艺路线流程图
-
+
-
+
\ No newline at end of file
+
diff --git a/src/views/wf/ticketdetail.vue b/src/views/wf/ticketdetail.vue
index 740bb269..bc1e7b3e 100644
--- a/src/views/wf/ticketdetail.vue
+++ b/src/views/wf/ticketdetail.vue
@@ -316,6 +316,14 @@