|
@@ -5,17 +5,7 @@
|
|
|
>
|
|
|
<div class="header">
|
|
|
<div class="title">绑定工序</div>
|
|
|
- <el-space>
|
|
|
- <el-button :icon="Back" size="small" @click="back">返回</el-button>
|
|
|
- <!-- <el-button type="primary" :icon="Download" size="small">下载</el-button>-->
|
|
|
- <el-button
|
|
|
- :type="isChanged ? 'danger' : 'primary'"
|
|
|
- :icon="Document"
|
|
|
- size="small"
|
|
|
- @click="saveFlow"
|
|
|
- >保存</el-button
|
|
|
- >
|
|
|
- </el-space>
|
|
|
+ <el-button :icon="Back" size="small" @click="back">返回</el-button>
|
|
|
</div>
|
|
|
<div class="binContainer">
|
|
|
<div class="processTree">
|
|
@@ -32,76 +22,120 @@
|
|
|
</div>
|
|
|
</template>
|
|
|
<div class="treeChild">
|
|
|
- <VueDraggable
|
|
|
- v-model="pProcess.baseOperationList"
|
|
|
- :animation="150"
|
|
|
- :group="{ name: 'people', pull: 'clone', put: false }"
|
|
|
- :sort="false"
|
|
|
- :clone="clone"
|
|
|
+ <div
|
|
|
+ class="childItem"
|
|
|
+ v-for="(item, index) in pProcess?.baseOperationList"
|
|
|
+ :key="index"
|
|
|
+ :draggable="!editStatus"
|
|
|
+ @dragstart="
|
|
|
+ onDragStart($event, nodeType, item.operationName, item)
|
|
|
+ "
|
|
|
>
|
|
|
- <div
|
|
|
- class="childItem"
|
|
|
- v-for="(item, index) in pProcess?.baseOperationList"
|
|
|
- :key="item.id"
|
|
|
- >
|
|
|
- {{ item.operationName }}
|
|
|
- </div>
|
|
|
- </VueDraggable>
|
|
|
+ {{ item.operationName }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</el-collapse-item>
|
|
|
</el-collapse>
|
|
|
</el-scrollbar>
|
|
|
</div>
|
|
|
- <div class="flowBox">
|
|
|
- <el-scrollbar style="padding-bottom: 20px">
|
|
|
- <VueDraggable
|
|
|
- v-model="list2"
|
|
|
- :animation="150"
|
|
|
- group="people"
|
|
|
- @update="onUpdate"
|
|
|
- style="min-width: 400px"
|
|
|
- @add="onClone"
|
|
|
+ <div
|
|
|
+ class="flowBox"
|
|
|
+ id="flowBox"
|
|
|
+ style="height: 100%; width: 100%; overflow: hidden"
|
|
|
+ >
|
|
|
+ <div class="dnd-flow" @drop="onDrop">
|
|
|
+ <VueFlow
|
|
|
+ v-model:nodes="flowData.nodes"
|
|
|
+ v-model:edges="flowData.edges"
|
|
|
+ :apply-default="!editStatus"
|
|
|
+ @dragover="onDragOver"
|
|
|
+ @dragleave="onDragLeave"
|
|
|
+ @node-click="nodeClick($event)"
|
|
|
>
|
|
|
- <div
|
|
|
- v-for="(item, index) in list2"
|
|
|
- :key="item.uuid"
|
|
|
- class="flowItem"
|
|
|
- @click="clickFlowItem(item, index)"
|
|
|
+ <template #edge-custom="props">
|
|
|
+ <CustomConnectionLine v-bind="props" />
|
|
|
+ </template>
|
|
|
+ <template #connection-line="props">
|
|
|
+ <CustomConnectionLine v-bind="props" />
|
|
|
+ </template>
|
|
|
+ <template #node-custom="props">
|
|
|
+ <CustomNode v-bind="props" />
|
|
|
+ </template>
|
|
|
+ <DropzoneBackground
|
|
|
+ :style="{
|
|
|
+ backgroundColor: isDragOver ? '#e7f3ff' : 'transparent',
|
|
|
+ transition: 'background-color 0.2s ease',
|
|
|
+ }"
|
|
|
>
|
|
|
- <div class="indexLabel">工序 {{ index + 1 }}</div>
|
|
|
- <div :class="getFlowNameClass(index)">
|
|
|
- {{ item?.operationVO?.operationName }}
|
|
|
+ <p v-if="isDragOver">拖拽中</p>
|
|
|
+ </DropzoneBackground>
|
|
|
+ </VueFlow>
|
|
|
+
|
|
|
+ <div
|
|
|
+ :style="{
|
|
|
+ width: flowBoxW - 20 + 'px',
|
|
|
+ height: flowBoxH - 20 + 'px',
|
|
|
+ }"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="detailInfo">
|
|
|
+ <el-scrollbar>
|
|
|
+ <div class="opBox">
|
|
|
+ <el-button type="primary" @click="changeEditStatus">{{
|
|
|
+ !editStatus ? "切换为工序信息编辑模式" : "切换为工序路线编辑模式"
|
|
|
+ }}</el-button>
|
|
|
+
|
|
|
+ <el-button @click="getPng">导出流程图 </el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 工艺路线编辑模式 -->
|
|
|
+ <div v-if="!editStatus">
|
|
|
+ <div class="btns">
|
|
|
+ <el-button type="primary" @click="saveFlow">保存</el-button>
|
|
|
+ <el-button type="danger" @click="cancelFlow">取消</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 工序信息编辑模式 -->
|
|
|
+ <div v-else>
|
|
|
+ <div v-if="currentProcess.id">
|
|
|
+ <avue-form
|
|
|
+ ref="formRef"
|
|
|
+ :option="formOption"
|
|
|
+ v-model="currentProcess"
|
|
|
+ style="padding: 10px; background-color: transparent"
|
|
|
+ >
|
|
|
+ <template #tbomUrl>
|
|
|
+ <FilesUpload
|
|
|
+ v-model:src="currentProcess.tbomUrl"
|
|
|
+ :show-tip="false"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </avue-form>
|
|
|
+ <div class="btns">
|
|
|
+ <el-tooltip
|
|
|
+ class="box-item"
|
|
|
+ effect="dark"
|
|
|
+ content="会同时保存现线路情况,请确认好变更"
|
|
|
+ placement="bottom"
|
|
|
+ >
|
|
|
+ <el-button type="primary" @click="saveInfo"
|
|
|
+ >保存工序详情</el-button
|
|
|
+ >
|
|
|
+ </el-tooltip>
|
|
|
+ <el-button type="danger" @click="cancelInfo"
|
|
|
+ >重置该工序详情</el-button
|
|
|
+ >
|
|
|
</div>
|
|
|
- <div>
|
|
|
- <Delete class="flowDelete" @click.stop="clickDelete(index)" />
|
|
|
+ <div class="editProcces">
|
|
|
+ <el-button type="primary" @click="editProComponent"
|
|
|
+ >编辑工序组件</el-button
|
|
|
+ >
|
|
|
</div>
|
|
|
- <Bottom class="arrow" v-show="index < list2.length - 1" />
|
|
|
</div>
|
|
|
- </VueDraggable>
|
|
|
- </el-scrollbar>
|
|
|
- </div>
|
|
|
- <div class="detailInfo">
|
|
|
- <div v-if="selectIndex > -1 && !isChanged">
|
|
|
- <avue-form
|
|
|
- ref="formRef"
|
|
|
- :option="formOption"
|
|
|
- v-model="currentProcess"
|
|
|
- style="padding: 10px; background-color: white"
|
|
|
- >
|
|
|
- <!--<template #tbomUrl>
|
|
|
- <FilesUpload
|
|
|
- v-model:src="currentProcess.tbomUrl"
|
|
|
- :show-tip="false"
|
|
|
- />
|
|
|
- </template>-->
|
|
|
- </avue-form>
|
|
|
- <div class="editProcces">
|
|
|
- <el-button type="primary" :icon="Grid" @click="editProComponent"
|
|
|
- >编辑工序组件</el-button
|
|
|
- >
|
|
|
+ <div v-else class="tipContent">请选择工序</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- <div v-else class="tipContent">{{ tipContent }}</div>
|
|
|
+ </el-scrollbar>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -115,7 +149,12 @@ import {
|
|
|
Bottom,
|
|
|
Grid,
|
|
|
} from "@element-plus/icons-vue";
|
|
|
-import { VueDraggable } from "vue-draggable-plus";
|
|
|
+import _ from "lodash-es";
|
|
|
+import { VueFlow, useVueFlow } from "@vue-flow/core";
|
|
|
+import DropzoneBackground from "./components/DropzoneBackground/index.vue";
|
|
|
+import CustomConnectionLine from "./components/CustomConnectionLine/index.vue";
|
|
|
+import CustomNode from "./components/CustomNode/index.vue";
|
|
|
+import useDragAndDrop from "@/hooks/useDnD.js";
|
|
|
import { processTreeList } from "@/api/craft/process/index";
|
|
|
import { useCommonStoreHook, useDictionaryStore } from "@/store";
|
|
|
import {
|
|
@@ -126,13 +165,47 @@ import {
|
|
|
import { v4 as uuidv4 } from "uuid";
|
|
|
import { formOption } from "./bindConfig";
|
|
|
import { ElMessage } from "element-plus";
|
|
|
-
|
|
|
+import { useScreenshot } from "./screenshot.ts";
|
|
|
+const { capture } = useScreenshot();
|
|
|
+const instance = useVueFlow();
|
|
|
+const { onConnect, addEdges, vueFlowRef, onEdgeUpdateEnd, applyEdgeChanges } =
|
|
|
+ instance;
|
|
|
+const { onDragOver, onDrop, onDragLeave, isDragOver, onDragStart } =
|
|
|
+ useDragAndDrop();
|
|
|
+const flowData = ref({ edges: [], nodes: [] });
|
|
|
+const flowDataCopy = ref({ edges: [], nodes: [] });
|
|
|
+provide("edges", flowData.value.edges);
|
|
|
+const currentProcess = ref({});
|
|
|
+provide("currentProcess", currentProcess);
|
|
|
+const nodeClick = (event) => {
|
|
|
+ if (!editStatus.value) return;
|
|
|
+ currentProcess.value = event.node;
|
|
|
+};
|
|
|
+onConnect(addEdges);
|
|
|
+const getPng = () => {
|
|
|
+ if (!vueFlowRef.value) {
|
|
|
+ console.warn("VueFlow element not found");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ capture(vueFlowRef.value, { shouldDownload: true });
|
|
|
+};
|
|
|
+const nodeType = ref("custom");
|
|
|
+const editStatus = ref(false);
|
|
|
+const changeEditStatus = () => {
|
|
|
+ editStatus.value = !editStatus.value;
|
|
|
+};
|
|
|
const router = useRouter();
|
|
|
const route = useRoute();
|
|
|
-
|
|
|
// 数据字典相关
|
|
|
const { dicts } = useDictionaryStore();
|
|
|
-
|
|
|
+//获取画布盒子具体长宽
|
|
|
+const flowBoxH = ref(null);
|
|
|
+const flowBoxW = ref(null);
|
|
|
+const flowBoxScreen = () => {
|
|
|
+ const flowBox = document.getElementById("flowBox");
|
|
|
+ flowBoxH.value = flowBox.clientHeight;
|
|
|
+ flowBoxW.value = flowBox.clientWidth;
|
|
|
+};
|
|
|
// 顶部====================
|
|
|
const back = () => {
|
|
|
router.back();
|
|
@@ -142,114 +215,63 @@ const download = () => {};
|
|
|
|
|
|
// 左侧工序树====================
|
|
|
const activeNames = ref([0]);
|
|
|
-const handleChange = (val) => {};
|
|
|
-
|
|
|
const list1 = ref([]);
|
|
|
|
|
|
-// 由于工序和(工序-工艺)模型字段不同,需要自定义克隆
|
|
|
-const clone = (origin) => {
|
|
|
- const data = JSON.parse(JSON.stringify(origin));
|
|
|
- return {
|
|
|
- created: "",
|
|
|
- creator: "",
|
|
|
- deleted: 0,
|
|
|
- deptId: "",
|
|
|
- id: null,
|
|
|
- uuid: uuidv4(),
|
|
|
- nextOperationId: "", //下一工序id
|
|
|
- operationId: "", // 当前工序id
|
|
|
- operationSort: 0, //当前工序排序号
|
|
|
- operationVO: data,
|
|
|
- orgId: "",
|
|
|
- processRouteId: route.params.id, //工艺路线id
|
|
|
- updated: "",
|
|
|
- updator: "",
|
|
|
- virtualCode: "",
|
|
|
- };
|
|
|
-};
|
|
|
-
|
|
|
// 保存中间的工序列表
|
|
|
const saveFlow = async () => {
|
|
|
- for (let i = 0; i < list2.value.length; i++) {
|
|
|
- list2.value[i].operationSort = i;
|
|
|
- list2.value[i].operationId = list2.value[i]?.operationVO?.id;
|
|
|
- list2.value[i].operation = list2.value[i]?.operationVO; //后端入参和返回不一样,在这里处理一下。
|
|
|
- if (i === list2.value.length - 1) {
|
|
|
- list2.value[i].nextOperationId = null;
|
|
|
- } else {
|
|
|
- list2.value[i].nextOperationId = list2.value[i + 1]?.operationVO?.id;
|
|
|
- }
|
|
|
- }
|
|
|
- let p = {
|
|
|
+ const { code } = await saveProcessInRoute({
|
|
|
processRouteId: route.params.id,
|
|
|
- routeOpList: list2.value,
|
|
|
- };
|
|
|
- let res = await saveProcessInRoute(p);
|
|
|
- // Elmessage.success("保存成功");
|
|
|
- loadProcessesFlow();
|
|
|
-};
|
|
|
-
|
|
|
-const loadTreeData = () => {
|
|
|
- processTreeList().then((res) => {
|
|
|
- list1.value = res.data ?? [];
|
|
|
+ routeData: JSON.stringify({ ...flowData.value }),
|
|
|
});
|
|
|
-};
|
|
|
-
|
|
|
-// 中间列表====================
|
|
|
-const list2 = ref([]);
|
|
|
-const selectIndex = ref(-1);
|
|
|
-const isChanged = ref(true); //如果中间列表发生了改变则不显示右侧信息
|
|
|
-// 点击中间列表事件,需要设置index和值
|
|
|
-const clickFlowItem = (item, index) => {
|
|
|
- if (isChanged.value) {
|
|
|
- ElMessage.warning("请先保存工序列表");
|
|
|
- return;
|
|
|
+ if (code == "200") {
|
|
|
+ ElMessage.success("保存成功");
|
|
|
}
|
|
|
- selectIndex.value = index;
|
|
|
- currentProcess.value = item.operationVO;
|
|
|
+ loadProcessesFlow();
|
|
|
};
|
|
|
-
|
|
|
-const clickDelete = (index) => {
|
|
|
- list2.value.splice(index, 1);
|
|
|
- onUpdate();
|
|
|
+const cancelFlow = () => {
|
|
|
+ flowData.value = JSON.parse(flowDataCopy.value);
|
|
|
};
|
|
|
-
|
|
|
-const onClone = () => {
|
|
|
- isChanged.value = true;
|
|
|
- tipContent.value = "请先保存工序列表,再选择工序进行编辑";
|
|
|
+const saveInfo = async () => {
|
|
|
+ flowData.value.nodes.forEach((item, index) => {
|
|
|
+ if (item.id == currentProcess.value.id) {
|
|
|
+ flowData.value.nodes[index] = currentProcess.value;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ saveFlow();
|
|
|
+ reset();
|
|
|
};
|
|
|
-
|
|
|
-const onUpdate = () => {
|
|
|
- // 中间发生了改变调用
|
|
|
- selectIndex.value = -1;
|
|
|
- isChanged.value = true;
|
|
|
- tipContent.value = "请先保存工序列表,再选择工序进行编辑";
|
|
|
+const cancelInfo = () => {
|
|
|
+ flowData.value.nodes.forEach((item, index) => {
|
|
|
+ if (item.id == currentProcess.value.id) {
|
|
|
+ currentProcess.value = flowData.value.nodes[index];
|
|
|
+ }
|
|
|
+ });
|
|
|
};
|
|
|
-
|
|
|
-const getFlowNameClass = (index) => {
|
|
|
- return index === selectIndex.value ? "nameLabelSelect" : "nameLabelCommon";
|
|
|
+const loadTreeData = () => {
|
|
|
+ processTreeList().then((res) => {
|
|
|
+ list1.value = res.data ?? [];
|
|
|
+ });
|
|
|
};
|
|
|
|
|
|
+//通过id获取现存储信息
|
|
|
const loadProcessesFlow = async () => {
|
|
|
const res = await processesByRouteId(route.params.id);
|
|
|
-
|
|
|
- list2.value = res.data ?? [];
|
|
|
- isChanged.value = false;
|
|
|
- tipContent.value = "请选择需要编辑的工序";
|
|
|
+ if (res.data) {
|
|
|
+ flowData.value = JSON.parse(res.data.routeData);
|
|
|
+ flowDataCopy.value = res.data.routeData;
|
|
|
+ } else {
|
|
|
+ flowDataCopy.value = JSON.stringify(flowData.value);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
-// 右侧具体信息====================
|
|
|
const formRef = ref(null);
|
|
|
-const currentProcess = ref({});
|
|
|
-const tipContent = ref("");
|
|
|
-const handleSubmit = () => {};
|
|
|
-
|
|
|
+const reset = () => {
|
|
|
+ currentProcess.value = {};
|
|
|
+ editStatus.value = false;
|
|
|
+};
|
|
|
const editProComponent = async () => {
|
|
|
- // saveFlow();
|
|
|
- let res = await updateProcess(currentProcess.value);
|
|
|
- ElMessage.success("保存成功");
|
|
|
router.push({
|
|
|
- path: `/base/craftManagement/processCom/${currentProcess.value.id}`,
|
|
|
+ path: `/base/craftManagement/processCom/${currentProcess.value.operationId}`,
|
|
|
query: { prodtCode: route.query.prodtCode, routeId: route.query.routeId },
|
|
|
});
|
|
|
};
|
|
@@ -259,6 +281,7 @@ const editProComponent = async () => {
|
|
|
onMounted(() => {
|
|
|
loadTreeData();
|
|
|
loadProcessesFlow();
|
|
|
+ flowBoxScreen();
|
|
|
});
|
|
|
|
|
|
const getNameByDictType = (dictValue) => {
|
|
@@ -270,18 +293,29 @@ const getNameByDictType = (dictValue) => {
|
|
|
});
|
|
|
return str;
|
|
|
};
|
|
|
+watch(
|
|
|
+ () => flowData.value.edges,
|
|
|
+ () => {
|
|
|
+ flowData.value.edges.forEach((item) => {
|
|
|
+ item.type = "custom";
|
|
|
+ });
|
|
|
+ }
|
|
|
+);
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
.binContainer {
|
|
|
height: calc(100vh - 165px);
|
|
|
background-color: #f5f7f9;
|
|
|
- overflow-y: scroll;
|
|
|
// background-color: white;
|
|
|
padding: 0;
|
|
|
display: flex;
|
|
|
}
|
|
|
-
|
|
|
+.btns {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ margin: 10px 0;
|
|
|
+}
|
|
|
.header {
|
|
|
height: 50px;
|
|
|
display: flex;
|
|
@@ -315,60 +349,6 @@ const getNameByDictType = (dictValue) => {
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
overflow-y: auto;
|
|
|
- padding-top: 47px;
|
|
|
- padding-bottom: 47px;
|
|
|
- .flowContain {
|
|
|
- width: 400px;
|
|
|
-
|
|
|
- min-height: 200px;
|
|
|
- }
|
|
|
- .flowItem {
|
|
|
- height: 50px;
|
|
|
- width: 400px;
|
|
|
- margin-bottom: 20px;
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- position: relative;
|
|
|
- .indexLabel {
|
|
|
- line-height: 50px;
|
|
|
-
|
|
|
- font-size: 14px;
|
|
|
- color: #6e7993;
|
|
|
-
|
|
|
- text-align: left;
|
|
|
- }
|
|
|
- .nameLabelCommon {
|
|
|
- width: 299px;
|
|
|
- line-height: 50px;
|
|
|
- background: #ffffff;
|
|
|
- box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.5);
|
|
|
- border-radius: 4px 4px 4px 4px;
|
|
|
- border: 1px solid #232032;
|
|
|
- text-align: center;
|
|
|
- }
|
|
|
- .nameLabelSelect {
|
|
|
- width: 299px;
|
|
|
- line-height: 50px;
|
|
|
- background: rgba(10, 89, 247, 0.2);
|
|
|
- box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.5);
|
|
|
- border-radius: 4px 4px 4px 4px;
|
|
|
- border: 2px solid #0a59f7;
|
|
|
- text-align: center;
|
|
|
- }
|
|
|
- .flowDelete {
|
|
|
- width: 1em;
|
|
|
- height: 1em;
|
|
|
- color: red;
|
|
|
- visibility: hidden;
|
|
|
- }
|
|
|
- .arrow {
|
|
|
- position: absolute;
|
|
|
- width: 10px;
|
|
|
- height: 18px;
|
|
|
- top: 50px;
|
|
|
- left: 50%;
|
|
|
- }
|
|
|
- }
|
|
|
.flowItem:hover {
|
|
|
.flowDelete {
|
|
|
visibility: visible;
|
|
@@ -379,11 +359,21 @@ const getNameByDictType = (dictValue) => {
|
|
|
.detailInfo {
|
|
|
width: 320px;
|
|
|
height: 100%;
|
|
|
+ background-color: white;
|
|
|
border-left: 1px solid rgba(0, 0, 0, 0.1);
|
|
|
+ .opBox {
|
|
|
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
|
+ padding: 0 20px;
|
|
|
+ height: 150px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: space-evenly;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
.editProcces {
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
- margin-top: 15px;
|
|
|
+ margin-bottom: 15px;
|
|
|
}
|
|
|
}
|
|
|
|