FlowChart.vue 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. <template>
  2. <div class="flowchart-container" ref="container">
  3. <div class="flowchart" :style="flowchartStyle">
  4. <FlowNode
  5. v-for="node in nodes"
  6. :key="node.id"
  7. :node="node"
  8. @add-child="handleAddChild"
  9. @node-drag="handleNodeDrag"
  10. />
  11. <svg class="connectors">
  12. <path
  13. v-for="connector in connectors"
  14. :key="connector.id"
  15. :d="connector.path"
  16. stroke="#999"
  17. stroke-width="2"
  18. fill="none"
  19. marker-end="url(#arrowhead)"
  20. />
  21. </svg>
  22. </div>
  23. <defs>
  24. <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
  25. <polygon points="0 0, 10 3.5, 0 7" fill="#999" />
  26. </marker>
  27. </defs>
  28. </div>
  29. </template>
  30. <script setup>
  31. import { ref, computed, onMounted, watch } from 'vue'
  32. import { useDraggable } from '@vueuse/core'
  33. import FlowNode from './FlowNode.vue'
  34. // 节点类型定义
  35. const nodeTypes = {
  36. start: { name: '开始', color: '#4CAF50' },
  37. process: { name: '处理', color: '#2196F3' },
  38. decision: { name: '判断', color: '#FFC107' },
  39. end: { name: '结束', color: '#F44336' }
  40. }
  41. // 初始节点数据
  42. const nodes = ref([
  43. {
  44. id: '1',
  45. type: 'start',
  46. x: 300,
  47. y: 50,
  48. children: ['2']
  49. },
  50. {
  51. id: '2',
  52. type: 'process',
  53. x: 300,
  54. y: 150,
  55. parent: '1'
  56. }
  57. ])
  58. // 容器拖拽
  59. const container = ref(null)
  60. // 修改容器拖拽初始化
  61. const { x: containerX, y: containerY } = useDraggable(container, {
  62. onStart: (e) => {
  63. // 只有当点击的不是节点时才允许拖拽容器
  64. return !e.target.closest('.flow-node')
  65. }
  66. })
  67. // 修改节点拖拽处理
  68. const handleNodeDrag = (nodeId, newX, newY) => {
  69. const nodeIndex = nodes.value.findIndex(n => n.id === nodeId)
  70. if (nodeIndex !== -1) {
  71. // 创建新数组确保响应性
  72. const newNodes = [...nodes.value]
  73. newNodes[nodeIndex] = {
  74. ...newNodes[nodeIndex],
  75. x: newX,
  76. y: newY
  77. }
  78. nodes.value = newNodes
  79. }
  80. }
  81. const flowchartStyle = computed(() => ({
  82. transform: `translate(${containerX.value}px, ${containerY.value}px)`
  83. }))
  84. // 计算连接线
  85. const connectors = computed(() => {
  86. const result = []
  87. nodes.value.forEach(node => {
  88. if (node.children) {
  89. node.children.forEach(childId => {
  90. const childNode = nodes.value.find(n => n.id === childId)
  91. if (childNode) {
  92. result.push({
  93. id: `${node.id}-${childId}`,
  94. path: calculatePath(node, childNode)
  95. })
  96. }
  97. })
  98. }
  99. })
  100. return result
  101. })
  102. function calculatePath(startNode, endNode) {
  103. const startX = startNode.x + 100
  104. const startY = startNode.y + 40
  105. const endX = endNode.x + 100
  106. const endY = endNode.y
  107. // 简单的贝塞尔曲线路径
  108. const controlY = (startY + endY) / 2
  109. return `M${startX},${startY} C${startX},${controlY} ${endX},${controlY} ${endX},${endY}`
  110. }
  111. // 添加子节点
  112. function handleAddChild(parentId, nodeType) {
  113. const parentNode = nodes.value.find(n => n.id === parentId)
  114. if (!parentNode) return
  115. const newNodeId = Date.now().toString()
  116. const newNode = {
  117. id: newNodeId,
  118. type: nodeType,
  119. x: parentNode.x,
  120. y: parentNode.y + 120,
  121. parent: parentId
  122. }
  123. if (!parentNode.children) {
  124. parentNode.children = []
  125. }
  126. parentNode.children.push(newNodeId)
  127. nodes.value.push(newNode)
  128. }
  129. // 自动排列
  130. function autoLayout() {
  131. // 这里可以实现自动排列算法,如树状布局等
  132. // 简化版:简单垂直排列
  133. nodes.value.forEach((node, index) => {
  134. if (index > 0) {
  135. node.x = 300
  136. node.y = 50 + index * 120
  137. }
  138. })
  139. }
  140. // 初始化时自动排列
  141. onMounted(() => {
  142. autoLayout()
  143. })
  144. </script>
  145. <style>
  146. .flowchart-container {
  147. width: 100%;
  148. height: 100vh;
  149. overflow: hidden;
  150. cursor: grab;
  151. }
  152. .flowchart {
  153. position: relative;
  154. width: max-content;
  155. height: max-content;
  156. }
  157. .connectors {
  158. position: absolute;
  159. top: 0;
  160. left: 0;
  161. width: 100%;
  162. height: 100%;
  163. pointer-events: none;
  164. z-index: 0;
  165. }
  166. </style>