bindProcess.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922
  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. <div class="processInfo">
  9. <span>工艺名称:{{ routeOperationData?.processRouteName }}</span>
  10. <span>产品名称:{{ routeOperationData?.prodtName }}</span>
  11. <span>产品型号: {{ routeOperationData?.prodtModel }}</span>
  12. </div>
  13. <el-button :icon="Back" size="small" @click="back">返回</el-button>
  14. </div>
  15. <div class="binContainer">
  16. <div class="processTree">
  17. <el-scrollbar>
  18. <!-- <el-select-->
  19. <!-- v-model="list1SearchGroupDictKey"-->
  20. <!-- placeholder="搜索工序类型"-->
  21. <!-- clearable-->
  22. <!-- filterable-->
  23. <!-- >-->
  24. <!-- <el-option-->
  25. <!-- v-for="dict in dictsArray"-->
  26. <!-- :label="dict.dictLabel"-->
  27. <!-- :value="dict.dictValue"-->
  28. <!-- />-->
  29. <!-- </el-select>-->
  30. <el-input
  31. v-model="list1SearchStr"
  32. placeholder="搜索工序名称"
  33. clearable
  34. />
  35. <el-collapse v-model="activeNames">
  36. <el-collapse-item
  37. v-for="(pProcess, pIndex) in showList"
  38. :key="pProcess.workSection"
  39. :name="pIndex"
  40. >
  41. <template #title>
  42. <div class="title2">
  43. {{ pProcess.workSectionStr }}
  44. </div>
  45. </template>
  46. <div class="treeChild">
  47. <div
  48. class="childItem"
  49. v-for="(item, index) in pProcess?.baseOperationList"
  50. :key="index"
  51. :draggable="!editStatus && usableStatus"
  52. @dragstart="
  53. onDragStart($event, nodeType, item.operationName, item)
  54. "
  55. >
  56. {{ item.operationName }}
  57. </div>
  58. </div>
  59. </el-collapse-item>
  60. </el-collapse>
  61. </el-scrollbar>
  62. </div>
  63. <div
  64. class="flowBox"
  65. id="flowBox"
  66. style="height: 100%; width: 100%; overflow: hidden"
  67. >
  68. <div class="dnd-flow" @drop="onDrop">
  69. <VueFlow
  70. v-model:nodes="nodes"
  71. v-model:edges="edges"
  72. :apply-default="!editStatus && usableStatus"
  73. @connect="onConnectMethod"
  74. @dragover="onDragOver"
  75. @dragleave="onDragLeave"
  76. @node-click="nodeClick($event)"
  77. @edge-click="edgeClick($event)"
  78. @nodes-change="(data) => nodesChange(data)"
  79. @edges-change="(data) => edgesChange(data)"
  80. @node-drag-stop="nodeDragStop"
  81. @node-drag-start="nodeDragStart"
  82. >
  83. <MiniMap style="background-color: grey" v-show="!printStauts" />
  84. <template #edge-custom="props">
  85. <CustomConnectionLine v-bind="props" />
  86. </template>
  87. <template #connection-line="props">
  88. <CustomConnectionLine v-bind="props" />
  89. </template>
  90. <template #node-custom="props">
  91. <CustomNode v-bind="props" />
  92. </template>
  93. <DropzoneBackground
  94. :style="{
  95. backgroundColor: isDragOver ? '#e7f3ff' : 'transparent',
  96. transition: 'background-color 0.2s ease',
  97. }"
  98. >
  99. <p v-if="isDragOver">拖拽中</p>
  100. </DropzoneBackground>
  101. <Panel
  102. class="process-panel"
  103. position="top-right"
  104. v-show="!printStauts"
  105. >
  106. <div class="layout-panel">
  107. <button title="蛇形排列" @click="convertToSnakeLayout">
  108. <PannelIcon name="liucheng" />
  109. </button>
  110. <button title="整齐排列" @click="layoutGraph('TB')">
  111. <PannelIcon name="vertical" />
  112. </button>
  113. <button title="重置" @click="cancer">
  114. <PannelIcon name="chongzhi" />
  115. </button>
  116. <button title="撤回" @click="goBack">
  117. <PannelIcon name="fanhui" />
  118. </button>
  119. </div>
  120. </Panel>
  121. </VueFlow>
  122. <div
  123. :style="{
  124. width: flowBoxW - 20 + 'px',
  125. height: flowBoxH - 20 + 'px',
  126. }"
  127. ></div>
  128. </div>
  129. </div>
  130. <div class="detailInfo">
  131. <el-scrollbar>
  132. <div class="opBox">
  133. <el-text class="mx-1" type="warning"
  134. >切换和排序之前记得先保存</el-text
  135. >
  136. <el-button type="primary" @click="changeEditStatus">{{
  137. !editStatus ? "切换为工序信息编辑模式" : "切换为工序路线编辑模式"
  138. }}</el-button>
  139. <el-button @click="getPng">导出流程图 </el-button>
  140. <!-- <el-button type="success" @click="changeStyle">
  141. 按钮风格切换
  142. </el-button> -->
  143. <div style="display: flex">
  144. <el-button type="success" @click="startGroup">
  145. 进行分组
  146. </el-button>
  147. <el-button @click="toSort"> 分支排序 </el-button>
  148. </div>
  149. </div>
  150. <!-- 工艺路线编辑模式 -->
  151. <div v-if="!editStatus && usableStatus">
  152. <div :class="styleStatus ? 'btnsChange' : 'btns'">
  153. <div>
  154. <el-button type="primary" @click="saveFlow">
  155. <span>保</span>
  156. <span> &nbsp; </span>
  157. <span>存</span>
  158. </el-button>
  159. </div>
  160. <div>
  161. <el-button
  162. type="info"
  163. v-if="selectNode || selectLine"
  164. @click="deleteFlow"
  165. >
  166. <span>删</span>
  167. <span> &nbsp; </span>
  168. <span>除</span></el-button
  169. >
  170. </div>
  171. </div>
  172. </div>
  173. <!-- 工序信息编辑模式 -->
  174. <div v-else>
  175. <div
  176. v-if="currentProcess.id"
  177. style="width: 310px; padding-right: 20px"
  178. >
  179. <avue-form
  180. ref="formRef"
  181. :option="formOption"
  182. v-model="currentProcess"
  183. :key="avueKey"
  184. style="padding: 10px; background-color: transparent"
  185. >
  186. <template #tbomUrl>
  187. <FilesUpload
  188. v-model:src="currentProcess.tbomUrl"
  189. :show-tip="false"
  190. />
  191. </template>
  192. <template #customFieldName>
  193. <el-select
  194. v-model="customFieldNameValues"
  195. multiple
  196. default-first-option
  197. :reserve-keyword="false"
  198. placeholder="请选择名称"
  199. >
  200. <el-option
  201. v-for="item in customFieldOptions"
  202. :key="item.value"
  203. :label="item.value"
  204. :value="item.value"
  205. />
  206. </el-select>
  207. </template>
  208. </avue-form>
  209. <div class="btns">
  210. <el-tooltip
  211. class="box-item"
  212. effect="dark"
  213. content="会同时保存现线路情况,请确认好变更"
  214. placement="bottom"
  215. >
  216. <el-button type="primary" @click="saveInfo"
  217. >保存工序详情</el-button
  218. >
  219. </el-tooltip>
  220. <el-button type="danger" @click="cancelInfo"
  221. >重置该工序详情</el-button
  222. >
  223. </div>
  224. <div class="editProcces">
  225. <el-button type="primary" @click="editProComponent"
  226. >编辑工序组件</el-button
  227. >
  228. </div>
  229. </div>
  230. <div v-else-if="!usableStatus" class="tipContent">
  231. 已绑定工序不可编辑线路情况
  232. </div>
  233. <div v-else class="tipContent">请选择工序</div>
  234. </div>
  235. </el-scrollbar>
  236. </div>
  237. </div>
  238. <GroupProcess ref="GroupProcessRef" @success="groupFinish"></GroupProcess>
  239. <GroupSort ref="GroupSortRef" />
  240. </div>
  241. </template>
  242. <script setup>
  243. import {
  244. Back,
  245. Download,
  246. Document,
  247. Delete,
  248. Bottom,
  249. Grid,
  250. } from "@element-plus/icons-vue";
  251. import _ from "lodash-es";
  252. import { useDebounceFn } from "@vueuse/core";
  253. import { VueFlow, useVueFlow, Panel } from "@vue-flow/core";
  254. import DropzoneBackground from "./components/DropzoneBackground/index.vue";
  255. import CustomConnectionLine from "./components/CustomConnectionLine/index.vue";
  256. import CustomNode from "./components/CustomNode/index.vue";
  257. import useDragAndDrop from "@/hooks/useDnD.js";
  258. import { MiniMap } from "@vue-flow/minimap";
  259. import { processTreeList } from "@/api/craft/process/index";
  260. import { useCommonStoreHook, useDictionaryStore } from "@/store";
  261. import {
  262. processesByRouteId,
  263. saveProcessInRoute,
  264. updateProcess,
  265. } from "@/api/craft/route/index";
  266. import { formOption } from "./bindConfig";
  267. import { ElMessage } from "element-plus";
  268. import { useScreenshot } from "./screenshot.ts";
  269. import { useLayout } from "@/hooks/useLayout";
  270. import { useSnakeLayoutHook } from "@/hooks/vueflowHooks";
  271. import GroupProcess from "./components/groupProcess.vue";
  272. import GroupSort from "./components/groupSort.vue";
  273. defineOptions({
  274. name: "bindProcess/:id/:prodtCode",
  275. });
  276. const styleStatus = ref(false);
  277. const GroupSortRef = ref(null);
  278. const toSort = () => {
  279. GroupSortRef.value.drawer = true;
  280. };
  281. const changeStyle = () => {
  282. styleStatus.value = !styleStatus.value;
  283. };
  284. const addHistory = (type) => {
  285. historyList.value.unshift(JSON.parse(JSON.stringify(flowData)));
  286. };
  287. const resetHistory = () => {
  288. historyList.value = [];
  289. };
  290. const nodesChange = (data) => {
  291. if (
  292. data.length > 0 &&
  293. data[0].type &&
  294. (data[0].type === "add" || data[0].type === "remove")
  295. ) {
  296. addHistory();
  297. }
  298. };
  299. const edgesChange = (data) => {
  300. if (
  301. data.length > 0 &&
  302. data[0].type &&
  303. (data[0].type === "add" || data[0].type === "remove")
  304. ) {
  305. addHistory();
  306. }
  307. };
  308. let dragStartFlowData = null;
  309. const nodeDragStart = () => {
  310. dragStartFlowData = JSON.parse(JSON.stringify(flowData));
  311. };
  312. const nodeDragStop = () => {
  313. historyList.value.unshift(JSON.parse(JSON.stringify(dragStartFlowData)));
  314. };
  315. const goBack = () => {
  316. let firstHistory = historyList.value.shift();
  317. if (firstHistory) {
  318. flowData.nodes = firstHistory.nodes;
  319. flowData.edges = firstHistory.edges;
  320. } else {
  321. ElMessage.warning("已是初始线路");
  322. }
  323. };
  324. const historyList = ref([]);
  325. const { capture } = useScreenshot();
  326. const { layout } = useLayout();
  327. const instance = useVueFlow();
  328. const { addEdges, vueFlowRef, onEdgeUpdateEnd, applyEdgeChanges, fitView } =
  329. instance;
  330. const { onDragOver, onDrop, onDragLeave, isDragOver, onDragStart } =
  331. useDragAndDrop();
  332. // onConnect(addEdges);
  333. const onConnectMethod = (edge) => {
  334. edge.type = "custom";
  335. addEdges(edge);
  336. };
  337. const flowData = reactive({ edges: [], nodes: [] });
  338. const flowDataCopy = ref({ edges: [], nodes: [] });
  339. const currentProcess = ref({});
  340. const selectNode = ref(null);
  341. const selectLine = ref(null);
  342. //true为可编辑线路
  343. const usableStatus = ref(true);
  344. provide("selectNode", selectNode);
  345. provide("currentProcess", currentProcess);
  346. provide("selectLine", selectLine);
  347. const edgeClick = (event) => {
  348. if (usableStatus.value == false && !editStatus.value) return;
  349. if (!editStatus.value) {
  350. selectLine.value = event.edge;
  351. selectNode.value = null;
  352. }
  353. };
  354. // 工序分组功能模块
  355. const GroupProcessRef = ref(null);
  356. const startGroup = () => {
  357. GroupProcessRef.value &&
  358. GroupProcessRef.value.startGroup(
  359. flowData.nodes,
  360. route.fullPath.split("/")[4]
  361. );
  362. };
  363. const groupFinish = () => {
  364. loadProcessesFlow();
  365. };
  366. // 自定义工序项目字段名称相关方法
  367. const customFieldNameValues = ref([]);
  368. const customFieldOptions = ref([]);
  369. function isJSON(str) {
  370. if (typeof str !== "string") {
  371. return false;
  372. }
  373. try {
  374. JSON.parse(str);
  375. return true;
  376. } catch (e) {
  377. return false;
  378. }
  379. }
  380. const avueKey = ref(false);
  381. const nodeClick = (event) => {
  382. // 所有的批量报工设置为1
  383. event.node.batchReport = 1;
  384. if (usableStatus.value == false && !editStatus.value) return;
  385. if (!editStatus.value) {
  386. selectNode.value = event.node;
  387. } else {
  388. currentProcess.value = {};
  389. //让组件重新加载
  390. avueKey.value = !avueKey.value;
  391. currentProcess.value = event.node;
  392. // 设置自定义字段名称的选项内容等
  393. if (
  394. event.node.customFieldName &&
  395. isJSON(event.node.customFieldName) &&
  396. JSON.parse(event.node.customFieldName).length > 0
  397. ) {
  398. customFieldOptions.value = JSON.parse(event.node.customFieldName);
  399. customFieldNameValues.value = customFieldOptions.value
  400. .filter((item) => {
  401. return item.isSelected;
  402. })
  403. .map((item) => item.value);
  404. }
  405. }
  406. };
  407. //当使用回退时清空选择的node
  408. const handleKeydown = () => {
  409. if (selectNode.value != null) {
  410. selectNode.value = null;
  411. }
  412. if (selectLine.value != null) {
  413. selectLine.value = null;
  414. }
  415. };
  416. const printStauts = ref(false);
  417. const getPng = async () => {
  418. if (!vueFlowRef.value) {
  419. console.warn("VueFlow element not found");
  420. return;
  421. }
  422. printStauts.value = true;
  423. await nextTick();
  424. capture(vueFlowRef.value, { shouldDownload: true });
  425. setTimeout(() => {
  426. printStauts.value = false;
  427. }, 0);
  428. };
  429. const nodeType = ref("custom");
  430. const editStatus = ref(false);
  431. provide("editStatus", editStatus);
  432. const changeEditStatus = () => {
  433. editStatus.value = !editStatus.value;
  434. if (editStatus.value == false) {
  435. currentProcess.value = {};
  436. } else {
  437. selectNode.value = null;
  438. }
  439. };
  440. const router = useRouter();
  441. const route = useRoute();
  442. // 数据字典相关
  443. const { dicts } = useDictionaryStore();
  444. //获取画布盒子具体长宽
  445. const flowBoxH = ref(null);
  446. const flowBoxW = ref(null);
  447. const flowBoxScreen = () => {
  448. const flowBox = document.getElementById("flowBox");
  449. flowBoxH.value = flowBox.clientHeight;
  450. flowBoxW.value = flowBox.clientWidth;
  451. };
  452. // 顶部====================
  453. const back = () => {
  454. router.back();
  455. };
  456. const download = () => {};
  457. // 左侧工序树====================
  458. const activeNames = ref([0]);
  459. const list1 = ref([]);
  460. const list1SearchStr = ref("");
  461. const list1SearchGroupDictKey = ref("");
  462. const showList = computed(() => {
  463. let array = _.cloneDeep(list1.value);
  464. // if (list1SearchGroupDictKey.value) {
  465. // array = array.filter((item) => {
  466. // return item.workSection === list1SearchGroupDictKey.value;
  467. // });
  468. // }
  469. array.forEach((item) => {
  470. item.baseOperationList = item.baseOperationList.filter((op) => {
  471. return op.operationName.includes(list1SearchStr.value);
  472. });
  473. });
  474. return array;
  475. });
  476. const errArray = ref([]);
  477. provide("errArray", errArray);
  478. // 保存中间的工序列表
  479. const saveFlow = async () => {
  480. const { code, msg } = await saveProcessInRoute({
  481. processRouteId: route.fullPath.split("/")[4],
  482. routeData: JSON.stringify({ ...flowData }),
  483. });
  484. if (code == "200") {
  485. try {
  486. if (JSON.parse(msg.replace("err:", ""))) {
  487. errArray.value = JSON.parse(msg.replace("err:", ""));
  488. ElMessage.error("请检查标记节点情况");
  489. }
  490. } catch (e) {
  491. errArray.value = [];
  492. ElMessage.success("保存成功");
  493. loadProcessesFlow();
  494. }
  495. }
  496. };
  497. const cancer = () => {
  498. ElMessageBox.confirm("取消的话会清空本次操作记录,确定吗?", "回退", {
  499. confirmButtonText: "确定",
  500. cancelButtonText: "取消",
  501. type: "warning",
  502. })
  503. .then(() => {
  504. cancelFlow();
  505. })
  506. .catch(() => {});
  507. };
  508. const cancelFlow = () => {
  509. flowData.nodes = flowDataCopy.value.nodes;
  510. flowData.edges = flowDataCopy.value.edges;
  511. if (selectNode.value != null) {
  512. selectNode.value = null;
  513. }
  514. if (selectLine.value != null) {
  515. selectLine.value = null;
  516. }
  517. resetHistory();
  518. };
  519. const deleteFlow = () => {
  520. addHistory();
  521. if (selectNode.value != null) {
  522. flowData.nodes.forEach((item, index) => {
  523. if (item.id == selectNode.value.id) {
  524. flowData.nodes.splice(index, 1);
  525. }
  526. });
  527. }
  528. if (selectLine.value != null) {
  529. flowData.edges.forEach((item, index) => {
  530. if (item.id == selectLine.value.id) {
  531. flowData.edges.splice(index, 1);
  532. }
  533. });
  534. }
  535. selectNode.value = null;
  536. selectLine.value = null;
  537. ElMessage.success("删除成功");
  538. };
  539. const saveInfo = async () => {
  540. // 处理currentProcess的自定义对象名称字段,如果customFieldNameValues在customFieldOptions中存在,则设置isSelected为true,否则设置为false,最后转为json
  541. const customFieldArr = [];
  542. customFieldOptions.value.forEach((item) => {
  543. let obj = {
  544. value: item.value,
  545. isSelected: customFieldNameValues.value.includes(item.value),
  546. };
  547. customFieldArr.push(obj);
  548. });
  549. currentProcess.value.customFieldName = JSON.stringify(customFieldArr);
  550. flowData.nodes.forEach((item, index) => {
  551. if (item.id == currentProcess.value.id) {
  552. flowData.nodes[index] = currentProcess.value;
  553. }
  554. });
  555. saveFlow();
  556. currentProcess.value = {};
  557. };
  558. const cancelInfo = () => {
  559. flowData.nodes.forEach((item, index) => {
  560. if (item.id == currentProcess.value.id) {
  561. currentProcess.value = flowData.nodes[index];
  562. }
  563. });
  564. };
  565. const loadTreeData = () => {
  566. // processTreeList(list1SearchGroupDictKey.value).then((res) => {
  567. // list1.value = res.data ?? [];
  568. // filterDicts();
  569. // });
  570. processTreeList().then((res) => {
  571. list1.value = res.data ?? [];
  572. filterDicts();
  573. });
  574. };
  575. // 用于搜索的数据字典数组
  576. const dictsArray = ref([]);
  577. // 从dicts?.workshop_section筛选出list1中返回的字典值,用于最上方的选择框
  578. const filterDicts = () => {
  579. dictsArray.value = dicts?.process_type ?? [];
  580. // const workSectionArray = list1.value.map((item) => {
  581. // return item.workSection;
  582. // });
  583. //
  584. // const arr = [];
  585. // dicts?.workshop_section?.forEach((item) => {
  586. // if (workSectionArray.includes(item?.dictValue)) {
  587. // arr.push(_.cloneDeep(item));
  588. // }
  589. // });
  590. // dictsArray.value = arr;
  591. };
  592. const routeOperationData = ref(null);
  593. const cancelStatus = ref(true);
  594. //通过id获取现存储信息
  595. const loadProcessesFlow = async () => {
  596. const res = await processesByRouteId(route.fullPath.split("/")[4]);
  597. if (res.data) {
  598. routeOperationData.value = res.data;
  599. if (res.data.routeData && res.data.routeData != "") {
  600. let jsonData = JSON.parse(res.data.routeData);
  601. flowData.nodes = jsonData.nodes;
  602. flowData.edges = jsonData.edges;
  603. flowDataCopy.value = JSON.parse(JSON.stringify(jsonData));
  604. cancelStatus.value = true;
  605. if (res.data.usable == 1) {
  606. usableStatus.value = false;
  607. }
  608. resetHistory();
  609. } else {
  610. cancelStatus.value = false;
  611. }
  612. } else {
  613. flowDataCopy.value = JSON.parse(JSON.stringify(flowData));
  614. }
  615. };
  616. const formRef = ref(null);
  617. const reset = () => {
  618. currentProcess.value = {};
  619. editStatus.value = false;
  620. routeOperationData.value = {};
  621. };
  622. const editProComponent = async () => {
  623. if (!currentProcess.value.operationId) {
  624. ElMessage.warning("请串联保存工序流程");
  625. return;
  626. }
  627. router.push({
  628. path: `/base/craftManagement/processCom/${currentProcess.value.operationId}/${route.fullPath.split("/")[5]}/${route.fullPath.split("/")[4]}`,
  629. });
  630. };
  631. // 全局=======
  632. onMounted(async () => {
  633. await loadTreeData();
  634. await loadProcessesFlow();
  635. flowBoxScreen();
  636. window.addEventListener("keydown", handleKeydown);
  637. });
  638. onUnmounted(() => {
  639. window.removeEventListener("keydown", handleKeydown);
  640. });
  641. const getNameByDictType = (dictValue) => {
  642. let str = "";
  643. dicts?.workshop_section?.forEach((element) => {
  644. if (element?.dictValue === dictValue) {
  645. str = element?.dictLabel ?? "===";
  646. }
  647. });
  648. return str;
  649. };
  650. // watch(
  651. // () => flowData.edges,
  652. // () => {
  653. // flowData.edges.forEach((item) => {
  654. // item.type = "custom";
  655. // });
  656. // }
  657. // );
  658. watch(
  659. () => list1SearchGroupDictKey.value,
  660. () => {
  661. loadTreeData();
  662. }
  663. );
  664. const { nodes } = toRefs(flowData); //ref(initialNodes);
  665. const { edges } = toRefs(flowData); //ref(initialEdges);
  666. async function layoutGraph(direction) {
  667. addHistory();
  668. nodes.value = layout(nodes.value, edges.value, direction);
  669. nextTick(() => {
  670. fitView();
  671. });
  672. }
  673. // 转换为蛇形布局
  674. const convertToSnakeLayout = () => {
  675. addHistory();
  676. nodes.value = useSnakeLayoutHook(nodes.value, edges.value);
  677. nextTick(() => {
  678. fitView();
  679. });
  680. };
  681. </script>
  682. <style lang="scss" scoped>
  683. .showStyle {
  684. transform: translateY(10px) rotate(90deg);
  685. }
  686. .fontStyle {
  687. transform: rotate(-90deg);
  688. }
  689. .binContainer {
  690. height: calc(100vh - 165px);
  691. background-color: #f5f7f9;
  692. // background-color: white;
  693. padding: 0;
  694. display: flex;
  695. }
  696. .btns {
  697. display: flex;
  698. justify-content: center;
  699. margin: 10px 0;
  700. div {
  701. width: 80px;
  702. }
  703. }
  704. .btnsChange {
  705. margin: 10px 0;
  706. div {
  707. display: flex;
  708. justify-content: center;
  709. margin-bottom: 10px;
  710. }
  711. }
  712. .header {
  713. height: 50px;
  714. display: flex;
  715. background-color: #f5f7f9;
  716. justify-content: space-between;
  717. align-items: center;
  718. border-bottom: 1px solid rgba(0, 0, 0, 0.1);
  719. }
  720. .processTree {
  721. width: 220px;
  722. height: 100%;
  723. border-right: 1px solid rgba(0, 0, 0, 0.1);
  724. .treeChild {
  725. background: #f2f2f2;
  726. padding: 20px 8px 10px 8px;
  727. .childItem {
  728. width: 100%;
  729. height: 28px;
  730. background: #ffffff;
  731. border-radius: 4px 4px 4px 4px;
  732. border: 1px solid rgba(35, 32, 50, 0.1);
  733. margin-bottom: 10px;
  734. text-align: center;
  735. }
  736. }
  737. }
  738. .flowBox {
  739. flex: 1;
  740. display: flex;
  741. justify-content: center;
  742. overflow-y: auto;
  743. .flowItem:hover {
  744. .flowDelete {
  745. visibility: visible;
  746. }
  747. }
  748. }
  749. .detailInfo {
  750. width: 320px;
  751. height: 100%;
  752. background-color: white;
  753. border-left: 1px solid rgba(0, 0, 0, 0.1);
  754. .opBox {
  755. border-bottom: 1px solid rgba(0, 0, 0, 0.1);
  756. padding: 0 20px;
  757. height: 150px;
  758. display: flex;
  759. flex-direction: column;
  760. justify-content: space-evenly;
  761. align-items: center;
  762. }
  763. .editProcces {
  764. display: flex;
  765. justify-content: center;
  766. margin-bottom: 15px;
  767. }
  768. }
  769. .title,
  770. .processInfo {
  771. line-height: 44px;
  772. color: #6f7991;
  773. font-size: 14px;
  774. font-weight: bold;
  775. text-align: left;
  776. margin-left: 20px;
  777. }
  778. .processInfo span {
  779. margin-left: 100px;
  780. }
  781. .processInfo {
  782. position: absolute;
  783. left: 5%;
  784. }
  785. .title2 {
  786. color: #232032;
  787. font-size: 14px;
  788. text-align: left;
  789. margin-left: 19px;
  790. }
  791. .tipContent {
  792. width: 100%;
  793. text-align: center;
  794. margin-top: 20px;
  795. color: #e6a23c;
  796. }
  797. .process-panel,
  798. .layout-panel {
  799. display: flex;
  800. gap: 10px;
  801. }
  802. .process-panel {
  803. background-color: #2d3748;
  804. padding: 10px;
  805. border-radius: 8px;
  806. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  807. display: flex;
  808. flex-direction: column;
  809. }
  810. .process-panel button {
  811. border: none;
  812. cursor: pointer;
  813. background-color: #4a5568;
  814. border-radius: 8px;
  815. color: white;
  816. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  817. }
  818. .process-panel button {
  819. font-size: 16px;
  820. width: 40px;
  821. height: 40px;
  822. display: flex;
  823. align-items: center;
  824. justify-content: center;
  825. }
  826. .checkbox-panel {
  827. display: flex;
  828. align-items: center;
  829. gap: 10px;
  830. }
  831. .process-panel button:hover,
  832. .layout-panel button:hover {
  833. background-color: #2563eb;
  834. transition: background-color 0.2s;
  835. }
  836. .process-panel label {
  837. color: white;
  838. font-size: 12px;
  839. }
  840. .stop-btn svg {
  841. display: none;
  842. }
  843. .stop-btn:hover svg {
  844. display: block;
  845. }
  846. .stop-btn:hover .spinner {
  847. display: none;
  848. }
  849. .spinner {
  850. border: 3px solid #f3f3f3;
  851. border-top: 3px solid #2563eb;
  852. border-radius: 50%;
  853. width: 10px;
  854. height: 10px;
  855. animation: spin 1s linear infinite;
  856. }
  857. @keyframes spin {
  858. 0% {
  859. transform: rotate(0deg);
  860. }
  861. 100% {
  862. transform: rotate(360deg);
  863. }
  864. }
  865. </style>
  866. <style>
  867. .el-collapse-item__content {
  868. padding: 0;
  869. }
  870. </style>