index.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. <template>
  2. <div class="chart">
  3. <div class="chart-block">
  4. <div class="chart-ref" ref="ref_chart"></div>
  5. </div>
  6. </div>
  7. </template>
  8. <script setup lang="ts">
  9. import {createVNode, getCurrentInstance, inject, nextTick, onMounted, provide, reactive, ref, render, watch} from "vue";
  10. import {Graph, Markup, Shape} from '@antv/x6'
  11. import {WorkflowFunc} from "@/views/workflow/types";
  12. import {lineStyle, portStyle} from "@/views/workflow/config";
  13. import nodeAdd from './node-add.vue'
  14. import {register} from "@antv/x6-vue-shape";
  15. import WorkflowNode from "@/views/workflow/nodes/index.vue";
  16. import {merge} from "lodash";
  17. import {handleEdge, handleNode} from "@/views/workflow/handle";
  18. const workflowFuncInject = inject('workflowFunc', {} as WorkflowFunc)
  19. register({
  20. shape: 'workflow-node',
  21. component: WorkflowNode,
  22. })
  23. let edgeTimer: any = null
  24. Graph.registerPortLayout('start', (portsPositionArgs, elemBBox) => {
  25. return portsPositionArgs.map((_, index) => {
  26. return {
  27. position: {
  28. x: 3,
  29. y: 25 + 3,
  30. },
  31. }
  32. })
  33. })
  34. Graph.registerPortLayout('end', (portsPositionArgs, elemBBox) => {
  35. if (edgeTimer) {
  36. clearTimeout(edgeTimer)
  37. }
  38. edgeTimer = setTimeout(() => {
  39. initEdges()
  40. }, 100)
  41. return portsPositionArgs.map((_, index) => {
  42. return {
  43. position: {
  44. x: elemBBox.width + 3,
  45. y: 25 + 3,
  46. },
  47. }
  48. })
  49. })
  50. Graph.registerPortLayout('more', (portsPositionArgs, elemBBox) => {
  51. return portsPositionArgs.map((_, index) => {
  52. nextTick(() => {
  53. workflowFuncInject.layoutPort(_.nodeId, _.portId)
  54. if (edgeTimer) {
  55. clearTimeout(edgeTimer)
  56. }
  57. edgeTimer = setTimeout(() => {
  58. initEdges()
  59. }, 100)
  60. })
  61. return {
  62. position: {
  63. x: elemBBox.width + 3,
  64. y: _.dy,
  65. },
  66. }
  67. })
  68. })
  69. const emits = defineEmits(['init'])
  70. const props = defineProps({
  71. data: <any>{}
  72. })
  73. const {proxy}: any = getCurrentInstance()
  74. const state: any = reactive({
  75. graph: null,
  76. isPort: false,
  77. isInitEdges: false
  78. })
  79. const ref_chart = ref()
  80. const initChart = () => {
  81. state.graph = new Graph({
  82. container: ref_chart.value,
  83. autoResize: true,
  84. panning: true,
  85. mousewheel: true,
  86. grid: {
  87. visible: true,
  88. type: 'doubleMesh',
  89. args: [
  90. {
  91. color: '#eee', // 主网格线颜色
  92. thickness: 1, // 主网格线宽度
  93. },
  94. {
  95. color: '#ddd', // 次网格线颜色
  96. thickness: 1, // 次网格线宽度
  97. factor: 4, // 主次网格线间隔
  98. },
  99. ],
  100. },
  101. connecting: {
  102. snap: { // 连线过程中自动吸附
  103. radius: 20,
  104. },
  105. allowBlank: false, // 是否允许连接到画布空白位置的点
  106. allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点
  107. allowNode: false, // 是否允许边连接到节点
  108. allowEdge: false, // 是否允许边链接到另一个边
  109. allowPort: ({sourcePort, targetPort}: any) => { // 是否允许边链接到连接桩
  110. return !sourcePort?.includes('start') && targetPort?.includes('start')
  111. },
  112. allowMulti: 'withPort', // 当设置为 'withPort' 时,在起始和终止节点的相同连接桩之间只允许创建一条边(即,起始和终止节点之间可以创建多条边,但必须要要链接在不同的连接桩上)
  113. highlight: true,
  114. router: {
  115. name: 'manhattan',
  116. args: {
  117. startDirections: ['right'],
  118. endDirections: ['left'],
  119. },
  120. },
  121. connector: {
  122. name: 'rounded',
  123. args: {
  124. radius: 20,
  125. },
  126. },
  127. createEdge: () => new Shape.Edge({
  128. attrs: lineStyle
  129. })
  130. },
  131. onPortRendered: (args: any) => {
  132. const selectors = args.contentSelectors
  133. const container = selectors && selectors.foContent
  134. if (container) {
  135. render(createVNode(nodeAdd, {
  136. port: args.port,
  137. node: args.node,
  138. graph: state.graph,
  139. }), container)
  140. }
  141. }
  142. })
  143. initNodes()
  144. state.graph.zoomToFit({ maxScale: 1 })
  145. state.graph.centerContent() // 居中显示
  146. initWatch()
  147. emits('init', state.graph)
  148. }
  149. const initNodes = () => {
  150. props.data.nodes.forEach(v => {
  151. state.graph.addNode(handleNode(v))
  152. })
  153. }
  154. const initEdges = () => {
  155. if (!state.isInitEdges) {
  156. props.data.edges.forEach(v => {
  157. const targetNode = state.graph.getCellById(v.target)
  158. targetNode.setData({
  159. edgeSource: v.port || v.source
  160. }, {deep: false})
  161. state.graph.addEdge(handleEdge(v))
  162. })
  163. state.isInitEdges = true
  164. }
  165. }
  166. const initWatch = () => {
  167. state.graph.on('node:click', ({ e, x, y, node, view, port }) => {
  168. setTimeout(() => {
  169. if (!state.isPort) {
  170. workflowFuncInject.nodeClick(node)
  171. }
  172. }, 50)
  173. })
  174. state.graph.on('node:port:click', ({ e, x, y, node, view, port }) => {
  175. state.isPort = true;
  176. setTimeout(() => {
  177. state.isPort = false
  178. }, 100)
  179. })
  180. }
  181. watch(() => props.data, (n) => {
  182. if (n) {
  183. initChart()
  184. }
  185. }, {immediate: true})
  186. onMounted(() => {
  187. })
  188. defineExpose({
  189. toJSON: () => state.graph.toJSON()
  190. })
  191. </script>
  192. <style lang="scss" scoped>
  193. .chart {
  194. width: 100%;
  195. height: 100%;
  196. background-color: #f2f4f7;
  197. .chart-block {
  198. width: 100%;
  199. height: 100%;
  200. .chart-ref {
  201. width: 100%;
  202. height: 100%;
  203. }
  204. }
  205. }
  206. </style>