bindProcess.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. <template>
  2. <div
  3. class="mainContentBox"
  4. style="padding: 0; box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1)"
  5. >
  6. <div class="header">
  7. <div class="title">绑定工序</div>
  8. <el-button :icon="Back" size="small" @click="back">返回</el-button>
  9. </div>
  10. <div class="binContainer">
  11. <div class="processTree">
  12. <el-scrollbar>
  13. <el-collapse v-model="activeNames">
  14. <el-collapse-item
  15. v-for="(pProcess, pIndex) in list1"
  16. :key="pProcess.workSection"
  17. :name="pIndex"
  18. >
  19. <template #title>
  20. <div class="title2">
  21. {{ getNameByDictType(pProcess.workSection) }}
  22. </div>
  23. </template>
  24. <div class="treeChild">
  25. <div
  26. class="childItem"
  27. v-for="(item, index) in pProcess?.baseOperationList"
  28. :key="index"
  29. :draggable="!editStatus && usableStatus"
  30. @dragstart="
  31. onDragStart($event, nodeType, item.operationName, item)
  32. "
  33. >
  34. {{ item.operationName }}
  35. </div>
  36. </div>
  37. </el-collapse-item>
  38. </el-collapse>
  39. </el-scrollbar>
  40. </div>
  41. <div
  42. class="flowBox"
  43. id="flowBox"
  44. style="height: 100%; width: 100%; overflow: hidden"
  45. >
  46. <div class="dnd-flow" @drop="onDrop">
  47. <VueFlow
  48. v-model:nodes="flowData.nodes"
  49. v-model:edges="flowData.edges"
  50. :apply-default="!editStatus && usableStatus"
  51. @dragover="onDragOver"
  52. @dragleave="onDragLeave"
  53. @node-click="nodeClick($event)"
  54. @edge-click="edgeClick($event)"
  55. >
  56. <MiniMap style="background-color: grey" />
  57. <template #edge-custom="props">
  58. <CustomConnectionLine v-bind="props" />
  59. </template>
  60. <template #connection-line="props">
  61. <CustomConnectionLine v-bind="props" />
  62. </template>
  63. <template #node-custom="props">
  64. <CustomNode v-bind="props" />
  65. </template>
  66. <DropzoneBackground
  67. :style="{
  68. backgroundColor: isDragOver ? '#e7f3ff' : 'transparent',
  69. transition: 'background-color 0.2s ease',
  70. }"
  71. >
  72. <p v-if="isDragOver">拖拽中</p>
  73. </DropzoneBackground>
  74. </VueFlow>
  75. <div
  76. :style="{
  77. width: flowBoxW - 20 + 'px',
  78. height: flowBoxH - 20 + 'px',
  79. }"
  80. ></div>
  81. </div>
  82. </div>
  83. <div class="detailInfo">
  84. <el-scrollbar>
  85. <div class="opBox">
  86. <el-button type="primary" @click="changeEditStatus">{{
  87. !editStatus ? "切换为工序信息编辑模式" : "切换为工序路线编辑模式"
  88. }}</el-button>
  89. <el-button @click="getPng">导出流程图 </el-button>
  90. </div>
  91. <!-- 工艺路线编辑模式 -->
  92. <div v-if="!editStatus && usableStatus">
  93. <div class="btns">
  94. <el-button type="primary" @click="saveFlow">保存</el-button>
  95. <el-button type="danger" v-if="cancelStatus" @click="cancelFlow"
  96. >取消</el-button
  97. >
  98. <el-button
  99. type="info"
  100. v-if="selectNode || selectLine"
  101. @click="deleteFlow"
  102. >删除</el-button
  103. >
  104. </div>
  105. </div>
  106. <!-- 工序信息编辑模式 -->
  107. <div v-else>
  108. <div v-if="currentProcess.id">
  109. <avue-form
  110. ref="formRef"
  111. :option="formOption"
  112. v-model="currentProcess"
  113. style="padding: 10px; background-color: transparent"
  114. >
  115. <template #tbomUrl>
  116. <FilesUpload
  117. v-model:src="currentProcess.tbomUrl"
  118. :show-tip="false"
  119. />
  120. </template>
  121. </avue-form>
  122. <div class="btns">
  123. <el-tooltip
  124. class="box-item"
  125. effect="dark"
  126. content="会同时保存现线路情况,请确认好变更"
  127. placement="bottom"
  128. >
  129. <el-button type="primary" @click="saveInfo"
  130. >保存工序详情</el-button
  131. >
  132. </el-tooltip>
  133. <el-button type="danger" @click="cancelInfo"
  134. >重置该工序详情</el-button
  135. >
  136. </div>
  137. <div class="editProcces">
  138. <el-button type="primary" @click="editProComponent"
  139. >编辑工序组件</el-button
  140. >
  141. </div>
  142. </div>
  143. <div v-else-if="!usableStatus" class="tipContent">
  144. 已绑定工序不可编辑线路情况
  145. </div>
  146. <div v-else class="tipContent">请选择工序</div>
  147. </div>
  148. </el-scrollbar>
  149. </div>
  150. </div>
  151. </div>
  152. </template>
  153. <script setup>
  154. import {
  155. Back,
  156. Download,
  157. Document,
  158. Delete,
  159. Bottom,
  160. Grid,
  161. } from "@element-plus/icons-vue";
  162. import _ from "lodash-es";
  163. import { VueFlow, useVueFlow } from "@vue-flow/core";
  164. import DropzoneBackground from "./components/DropzoneBackground/index.vue";
  165. import CustomConnectionLine from "./components/CustomConnectionLine/index.vue";
  166. import CustomNode from "./components/CustomNode/index.vue";
  167. import useDragAndDrop from "@/hooks/useDnD.js";
  168. import { MiniMap } from "@vue-flow/minimap";
  169. import { processTreeList } from "@/api/craft/process/index";
  170. import { useCommonStoreHook, useDictionaryStore } from "@/store";
  171. import {
  172. processesByRouteId,
  173. saveProcessInRoute,
  174. updateProcess,
  175. } from "@/api/craft/route/index";
  176. import { formOption } from "./bindConfig";
  177. import { ElMessage } from "element-plus";
  178. import { useScreenshot } from "./screenshot.ts";
  179. defineOptions({
  180. name: "bindProcess/:id/:prodtCode",
  181. });
  182. const { capture } = useScreenshot();
  183. const instance = useVueFlow();
  184. const { onConnect, addEdges, vueFlowRef, onEdgeUpdateEnd, applyEdgeChanges } =
  185. instance;
  186. const { onDragOver, onDrop, onDragLeave, isDragOver, onDragStart } =
  187. useDragAndDrop();
  188. const flowData = ref({ edges: [], nodes: [] });
  189. const flowDataCopy = ref({ edges: [], nodes: [] });
  190. const currentProcess = ref({});
  191. const selectNode = ref(null);
  192. const selectLine = ref(null);
  193. //true为可编辑线路
  194. const usableStatus = ref(true);
  195. provide("selectNode", selectNode);
  196. provide("currentProcess", currentProcess);
  197. provide("selectLine", selectLine);
  198. const edgeClick = (event) => {
  199. if (usableStatus.value == false && !editStatus.value) return;
  200. if (!editStatus.value) {
  201. selectLine.value = event.edge;
  202. selectNode.value = null;
  203. }
  204. };
  205. const nodeClick = (event) => {
  206. if (usableStatus.value == false && !editStatus.value) return;
  207. if (!editStatus.value) {
  208. selectNode.value = event.node;
  209. } else {
  210. currentProcess.value = event.node;
  211. }
  212. };
  213. //当使用回退时清空选择的node
  214. const handleKeydown = () => {
  215. if (selectNode.value != null) {
  216. selectNode.value = null;
  217. }
  218. if (selectLine.value != null) {
  219. selectLine.value = null;
  220. }
  221. };
  222. onConnect(addEdges);
  223. const getPng = () => {
  224. if (!vueFlowRef.value) {
  225. console.warn("VueFlow element not found");
  226. return;
  227. }
  228. capture(vueFlowRef.value, { shouldDownload: true });
  229. };
  230. const nodeType = ref("custom");
  231. const editStatus = ref(false);
  232. provide("editStatus", editStatus);
  233. const changeEditStatus = () => {
  234. editStatus.value = !editStatus.value;
  235. if (editStatus.value == false) {
  236. currentProcess.value = {};
  237. } else {
  238. selectNode.value = null;
  239. }
  240. };
  241. const router = useRouter();
  242. const route = useRoute();
  243. // 数据字典相关
  244. const { dicts } = useDictionaryStore();
  245. //获取画布盒子具体长宽
  246. const flowBoxH = ref(null);
  247. const flowBoxW = ref(null);
  248. const flowBoxScreen = () => {
  249. const flowBox = document.getElementById("flowBox");
  250. flowBoxH.value = flowBox.clientHeight;
  251. flowBoxW.value = flowBox.clientWidth;
  252. };
  253. // 顶部====================
  254. const back = () => {
  255. router.back();
  256. };
  257. const download = () => {};
  258. // 左侧工序树====================
  259. const activeNames = ref([0]);
  260. const list1 = ref([]);
  261. // 保存中间的工序列表
  262. const saveFlow = async () => {
  263. const { code } = await saveProcessInRoute({
  264. processRouteId: route.fullPath.split("/")[4],
  265. routeData: JSON.stringify({ ...flowData.value }),
  266. });
  267. if (code == "200") {
  268. ElMessage.success("保存成功");
  269. }
  270. loadProcessesFlow();
  271. };
  272. const cancelFlow = () => {
  273. flowData.value = JSON.parse(flowDataCopy.value);
  274. if (selectNode.value != null) {
  275. selectNode.value = null;
  276. }
  277. if (selectLine.value != null) {
  278. selectLine.value = null;
  279. }
  280. };
  281. const deleteFlow = () => {
  282. if (selectNode.value != null) {
  283. flowData.value.nodes.forEach((item, index) => {
  284. if (item.id == selectNode.value.id) {
  285. flowData.value.nodes.splice(index, 1);
  286. }
  287. });
  288. }
  289. if (selectLine.value != null) {
  290. flowData.value.edges.forEach((item, index) => {
  291. if (item.id == selectLine.value.id) {
  292. flowData.value.edges.splice(index, 1);
  293. }
  294. });
  295. }
  296. selectNode.value = null;
  297. selectLine.value = null;
  298. ElMessage.success("删除成功");
  299. };
  300. const saveInfo = async () => {
  301. flowData.value.nodes.forEach((item, index) => {
  302. if (item.id == currentProcess.value.id) {
  303. flowData.value.nodes[index] = currentProcess.value;
  304. }
  305. });
  306. saveFlow();
  307. reset();
  308. };
  309. const cancelInfo = () => {
  310. flowData.value.nodes.forEach((item, index) => {
  311. if (item.id == currentProcess.value.id) {
  312. currentProcess.value = flowData.value.nodes[index];
  313. }
  314. });
  315. };
  316. const loadTreeData = () => {
  317. processTreeList().then((res) => {
  318. list1.value = res.data ?? [];
  319. });
  320. };
  321. const cancelStatus = ref(true);
  322. //通过id获取现存储信息
  323. const loadProcessesFlow = async () => {
  324. const res = await processesByRouteId(route.fullPath.split("/")[4]);
  325. if (res.data) {
  326. if (res.data.routeData && res.data.routeData != "") {
  327. flowData.value = JSON.parse(res.data.routeData);
  328. flowDataCopy.value = res.data.routeData;
  329. cancelStatus.value = true;
  330. if (res.data.usable == 1) {
  331. usableStatus.value = false;
  332. }
  333. } else {
  334. cancelStatus.value = false;
  335. }
  336. } else {
  337. flowDataCopy.value = JSON.stringify(flowData.value);
  338. }
  339. };
  340. const formRef = ref(null);
  341. const reset = () => {
  342. currentProcess.value = {};
  343. editStatus.value = false;
  344. };
  345. const editProComponent = async () => {
  346. router.push({
  347. path: `/base/craftManagement/processCom/${route.fullPath.split("/")[4]}/${route.fullPath.split("/")[5]}`,
  348. });
  349. };
  350. // 全局=======
  351. onMounted(() => {
  352. loadTreeData();
  353. loadProcessesFlow();
  354. flowBoxScreen();
  355. window.addEventListener("keydown", handleKeydown);
  356. });
  357. onUnmounted(() => {
  358. window.removeEventListener("keydown", handleKeydown);
  359. });
  360. const getNameByDictType = (dictValue) => {
  361. let str = "";
  362. dicts?.workshop_section?.forEach((element) => {
  363. if (element?.dictValue === dictValue) {
  364. str = element?.dictLabel ?? "===";
  365. }
  366. });
  367. return str;
  368. };
  369. watch(
  370. () => flowData.value.edges,
  371. () => {
  372. flowData.value.edges.forEach((item) => {
  373. item.type = "custom";
  374. });
  375. }
  376. );
  377. </script>
  378. <style lang="scss" scoped>
  379. .binContainer {
  380. height: calc(100vh - 165px);
  381. background-color: #f5f7f9;
  382. // background-color: white;
  383. padding: 0;
  384. display: flex;
  385. }
  386. .btns {
  387. display: flex;
  388. justify-content: center;
  389. margin: 10px 0;
  390. }
  391. .header {
  392. height: 50px;
  393. display: flex;
  394. background-color: #f5f7f9;
  395. justify-content: space-between;
  396. align-items: center;
  397. border-bottom: 1px solid rgba(0, 0, 0, 0.1);
  398. }
  399. .processTree {
  400. width: 220px;
  401. height: 100%;
  402. border-right: 1px solid rgba(0, 0, 0, 0.1);
  403. .treeChild {
  404. background: #f2f2f2;
  405. padding: 20px 8px 10px 8px;
  406. .childItem {
  407. width: 100%;
  408. height: 28px;
  409. background: #ffffff;
  410. border-radius: 4px 4px 4px 4px;
  411. border: 1px solid rgba(35, 32, 50, 0.1);
  412. margin-bottom: 10px;
  413. text-align: center;
  414. }
  415. }
  416. }
  417. .flowBox {
  418. flex: 1;
  419. display: flex;
  420. justify-content: center;
  421. overflow-y: auto;
  422. .flowItem:hover {
  423. .flowDelete {
  424. visibility: visible;
  425. }
  426. }
  427. }
  428. .detailInfo {
  429. width: 320px;
  430. height: 100%;
  431. background-color: white;
  432. border-left: 1px solid rgba(0, 0, 0, 0.1);
  433. .opBox {
  434. border-bottom: 1px solid rgba(0, 0, 0, 0.1);
  435. padding: 0 20px;
  436. height: 150px;
  437. display: flex;
  438. flex-direction: column;
  439. justify-content: space-evenly;
  440. align-items: center;
  441. }
  442. .editProcces {
  443. display: flex;
  444. justify-content: center;
  445. margin-bottom: 15px;
  446. }
  447. }
  448. .title {
  449. line-height: 44px;
  450. color: #6f7991;
  451. font-size: 14px;
  452. font-weight: bold;
  453. text-align: left;
  454. margin-left: 20px;
  455. }
  456. .title2 {
  457. color: #232032;
  458. font-size: 14px;
  459. text-align: left;
  460. margin-left: 19px;
  461. }
  462. .tipContent {
  463. width: 100%;
  464. text-align: center;
  465. margin-top: 20px;
  466. color: #e6a23c;
  467. }
  468. </style>
  469. <style>
  470. .el-collapse-item__content {
  471. padding: 0;
  472. }
  473. </style>