index.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <script setup lang="ts">
  2. import {
  3. ref,
  4. onMounted,
  5. InjectionKey,
  6. Ref,
  7. provide,
  8. inject,
  9. nextTick,
  10. markRaw,
  11. } from "vue";
  12. import {
  13. HJMethodName,
  14. HJNodeData,
  15. HJNodeType,
  16. nodeTypes,
  17. edgeTypes,
  18. HJMethodProvideName,
  19. CurrentHeaderOperationProvideName,
  20. CurrentSelectedEdgeProvideName,
  21. HJFlowProps,
  22. } from "../types/comTypes";
  23. import Panel from "../panel/index.vue";
  24. import CommonEdge from "../edges/commonEdge.vue";
  25. import DropzoneBackground from "../background/DropzoneBackground.vue";
  26. import { useVueFlow, VueFlow, Edge } from "@vue-flow/core";
  27. import useDragAndDrop from "../hooks/useDnD";
  28. import { sortNodesByPosition, useLayout } from "../utils/useLayout";
  29. import { useSnakeLayoutHook } from "../hooks/snakeHooks";
  30. import { ElMessage } from "element-plus";
  31. defineOptions({
  32. name: "HJFlow",
  33. });
  34. const { onConnect, addEdges, fitView, updateNode, findNode } = useVueFlow();
  35. const { onDragOver, onDrop, onDragLeave, isDragOver } = useDragAndDrop();
  36. onConnect(addEdges);
  37. // ========== 外面传进来的参数 ===========
  38. const nodes = defineModel<HJNodeData[]>("nodesData");
  39. const edges = defineModel("edgesData");
  40. const props = defineProps<HJFlowProps>();
  41. // ==========vueflow 本身的方法操作 ===========
  42. const doubleClick = ({ node }) => {
  43. // 如果通过双击弹出额外信息编辑是可以拿到jsonData的,但是如果在自定义node中是拿不到的,非要在自定义中拿就得把额外的信息放到data中。
  44. // 还要看 是否要根据 dragData中的数据生成具体 avue页面。 之后可以尝试通过useVueFlow的find能否找到
  45. console.log("doubleClick", node);
  46. };
  47. const currentClickEdgeData = ref<Edge | null>(null);
  48. provide(CurrentSelectedEdgeProvideName, currentClickEdgeData);
  49. const onClickEdge = ({ edge }) => {
  50. currentClickEdgeData.value = JSON.parse(JSON.stringify(edge));
  51. };
  52. // ======= Emits =======
  53. const emits = defineEmits<{
  54. hjMethod: [name: HJMethodName, node: HJNodeData];
  55. }>();
  56. // 提供一个孙子组件里面(比如自定义node里面) 调用本组件的方法
  57. // ======= Node操作 =======
  58. const currentHeaderOperationNodeData = ref<HJNodeData | null>(null); // 当前选中的header操作节点
  59. provide(CurrentHeaderOperationProvideName, currentHeaderOperationNodeData);
  60. provide(HJMethodProvideName, (name: HJMethodName, node: HJNodeData) => {
  61. currentHeaderOperationNodeData.value = JSON.parse(JSON.stringify(node)); // 复制一份数据
  62. console.log("provide", name, node);
  63. emits("hjMethod", name, node);
  64. });
  65. ///// === 数据历史纪录 用于回退或者重置按钮 === /////
  66. let originData: any = null;
  67. let historyList: any[] = [];
  68. ///// === 所有的node 和edge change 调用的地方 === /////
  69. const onFlowNodesChange = (data: any) => {
  70. if (
  71. data.length > 0 &&
  72. data[0].type &&
  73. (data[0].type === "add" || data[0].type === "remove")
  74. ) {
  75. historyList.unshift(
  76. JSON.stringify({
  77. nodes: markRaw(nodes.value),
  78. edges: markRaw(edges.value),
  79. })
  80. );
  81. console.log("onFlowNodesChange historyList", historyList);
  82. }
  83. };
  84. const onFlowEdgesChange = (data: any) => {
  85. if (
  86. data.length > 0 &&
  87. data[0].type &&
  88. (data[0].type === "add" || data[0].type === "remove")
  89. ) {
  90. historyList.unshift(
  91. JSON.stringify({
  92. nodes: markRaw(nodes.value),
  93. edges: markRaw(edges.value),
  94. })
  95. );
  96. }
  97. };
  98. ///// === 所有的node 和edge change 调用的地方 === /////
  99. ///// === 初始化 === /////
  100. const initFlow = (nodes: HJNodeData[], edges: any[]) => {
  101. originData = JSON.stringify({ nodes, edges }); // 复制一份数据
  102. historyList = []; // 初始化历史纪录
  103. };
  104. // 为父组件提供更新node中的data数据的操作 一般只更新information就行了 更新一个node的值
  105. const updateNodeData = (node: HJNodeData) => {
  106. console.log("updateNodeData", node);
  107. if (node.id) {
  108. const findNodeData = findNode(node.id) as HJNodeData;
  109. findNodeData.data.information = node.data.information;
  110. console.log("findNodeData", findNodeData);
  111. // updateNode(node.id, node)
  112. }
  113. };
  114. // 清空状态 比如选中某一个节点的状态
  115. const clearStatus = () => {
  116. currentHeaderOperationNodeData.value = null;
  117. currentClickEdgeData.value = null;
  118. historyList = [];
  119. };
  120. // 获取是否是正在编辑的状态 目前只判断节点的新增和删除 外面保存之后要清空状态
  121. const getChangedStatus = () => {
  122. return historyList.length > 0;
  123. };
  124. defineExpose({
  125. updateNodeData,
  126. initFlow,
  127. clearStatus,
  128. getChangedStatus
  129. });
  130. // ======= Panel =======
  131. // 测试layout 点击切换布局时候的操作
  132. const { layout } = useLayout();
  133. async function layoutGraph(direction: "LR" | "TB") {
  134. nodes.value = layout(nodes.value, edges.value, direction);
  135. console.log("排序完", nodes.value);
  136. nextTick(() => {
  137. fitView();
  138. nodes.value = sortNodesByPosition(nodes.value);
  139. });
  140. }
  141. const pannelOperations = {
  142. addNode: (nodeType: HJNodeType) => {
  143. console.log("addNode", nodeType);
  144. },
  145. layout: (direction: "LR" | "TB") => {
  146. layoutGraph(direction);
  147. },
  148. toSnake: () => {
  149. console.log("toSnake");
  150. nodes.value = useSnakeLayoutHook(nodes.value, edges.value);
  151. nextTick(() => {
  152. fitView();
  153. });
  154. },
  155. reset: () => {
  156. console.log("reset");
  157. if (originData && JSON.parse(originData)) {
  158. let data = JSON.parse(originData);
  159. nodes.value = data.nodes;
  160. edges.value = data.edges;
  161. historyList = [];
  162. }
  163. },
  164. back: () => {
  165. console.log("back");
  166. let firstHistory = historyList.shift();
  167. if (firstHistory) {
  168. let historyData = JSON.parse(firstHistory);
  169. nodes.value = historyData.nodes;
  170. edges.value = historyData.edges;
  171. } else {
  172. ElMessage.warning("已是初始线路");
  173. }
  174. },
  175. saveTemplate: () => {
  176. emits("hjMethod", HJMethodName.SaveTemplate, nodes.value[0]);
  177. },
  178. };
  179. // ======= Panel ======= ↑↑↑↑↑↑↑↑↑↑↑↑↑↑
  180. </script>
  181. <template>
  182. <VueFlow
  183. v-model:nodes="nodes"
  184. v-model:edges="edges"
  185. :node-types="nodeTypes"
  186. :edgeTypes="edgeTypes"
  187. @dragover="onDragOver"
  188. @dragleave="onDragLeave"
  189. @drop="onDrop"
  190. @nodeDoubleClick="doubleClick"
  191. @edge-click="onClickEdge"
  192. @nodes-change="onFlowNodesChange"
  193. @edges-change="onFlowEdgesChange"
  194. >
  195. <DropzoneBackground
  196. :style="{
  197. backgroundColor: isDragOver ? '#e7f3ff' : 'transparent',
  198. transition: 'background-color 0.2s ease',
  199. }"
  200. >
  201. <!-- <p v-if="isDragOver">Drop here</p>-->
  202. </DropzoneBackground>
  203. <!-- 右面上工具栏操作 -->
  204. <Panel
  205. v-if="showPanel"
  206. v-on="pannelOperations"
  207. :fun-names="panelBtns"
  208. ></Panel>
  209. </VueFlow>
  210. </template>
  211. <style scoped lang="less"></style>