bindProcess.vue 23 KB

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