flowChart.vue 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. <script lang="ts" setup>
  2. import { ref, onMounted } from 'vue'
  3. import { VueFlow, Handle, Position, type Node, Edge } from '@vue-flow/core'
  4. import { Background } from '@vue-flow/background'
  5. import { post, get } from "@/utils/request";
  6. import { GET_STRUCT_BY_TABLE_NAME, GET_ALL_BUS_TABLE, GET_RELATE_TABLE_BY_FROM_COLUMN, GET_RELATE_BUS_TABLE_BY_FROM_TABLE } from "../api"
  7. import '@vue-flow/core/dist/style.css';
  8. import '@vue-flow/core/dist/theme-default.css';
  9. interface selectedNode {
  10. id: string;
  11. data: any
  12. }
  13. const FixedConfiguration = {
  14. type: 'custom',
  15. style: {
  16. width: '120px', padding: '5px 20px', fontSize: '14px',
  17. border: '1px solid #dcdfe6', borderRadius: '8px'
  18. },
  19. sourcePosition: Position.Right,
  20. targetPosition: Position.Left,
  21. hidden: false
  22. }
  23. const fixedLabel = {
  24. selected: false, // 是否选中
  25. }
  26. const connectionType = 'step'
  27. const nodes = ref<Node[]>([])
  28. const edges = ref<Edge[]>([])
  29. const clickedNodes = ref<selectedNode[]>([]) // 记录点击的节点
  30. const selectNodes = ref<any[]>([]) // 记录选中的节点
  31. const businessTableList = ref<optionType[]>([])
  32. const businessObject = ref<any>({})
  33. async function onNodeClick(event: any) {
  34. const { x, y } = event.node.position // 点击的位置
  35. const nodeId = event.node.id
  36. // 点击的节点Id
  37. const clickedNodesStr = clickedNodes.value.map(node => node.id)
  38. // 选中的节点Id
  39. const selectNodeIdList = selectNodes.value.map(node => node.id)
  40. // 点击长度相同的节点Id
  41. const clickedNodesLengthStr = clickedNodesStr.filter(strId => strId.length == nodeId.length)
  42. if (clickedNodesStr.includes(nodeId)) {
  43. if (selectNodeIdList.includes(nodeId)) {
  44. for (let i in selectNodeIdList) {
  45. if (nodeId !== selectNodeIdList[i] && nodeId.length === selectNodeIdList[i].length) {
  46. expandNode(selectNodeIdList[i], true)
  47. } else if (nodeId == selectNodeIdList[i]) {
  48. expandNode(nodeId, false)
  49. }
  50. }
  51. const notSelectedToDelete = clickedNodesLengthStr.filter(item => !selectNodeIdList.includes(item));
  52. for (let i in notSelectedToDelete) {
  53. deteleData(notSelectedToDelete[i])
  54. }
  55. }
  56. } else {
  57. clickedNodes.value.push({ id: nodeId, data: event.node.data })
  58. clickedNodesStr.push(nodeId)
  59. // 隐藏和删除其他节点
  60. const hiddenNode = clickedNodesLengthStr.filter(item => selectNodeIdList.includes(item))
  61. const deleteNode = clickedNodesLengthStr.filter(item => !selectNodeIdList.includes(item))
  62. hiddenNode.forEach(item => expandNode(item, true))
  63. deleteNode.forEach(item => deteleData(item))
  64. const eventData = event.node.data
  65. // console.log(eventData.id, '<==== id')
  66. // if(eventData.id) {
  67. const addData = await post(GET_RELATE_BUS_TABLE_BY_FROM_TABLE, { tableName: eventData.tblName })
  68. const fieldList = addData.data || []
  69. AddData(fieldList, x, y, nodeId)
  70. // }
  71. // if(!eventData.id) {
  72. // const tableName = eventData.tblName
  73. // const columnName = eventData.columnName
  74. // const addData = await post(GET_RELATE_TABLE_BY_FROM_COLUMN, { tableName, columnName })
  75. // console.log(addData, '<===== 返回的数据')
  76. // }
  77. }
  78. }
  79. // 处理级之间的联动勾选
  80. function processSelectedNodes(nodeId: string) {
  81. const item = nodes.value.find(item => item.id === nodeId)
  82. if (item?.data?.selected) { // 选中状态将之前的节点选中
  83. const selectNodes = item.id.split('-').map((_: any, index: number, arr: any) => arr.slice(0, index + 1).join('-'));
  84. nodes.value = nodes.value.map(node => {
  85. if (selectNodes.includes(node.id)) {
  86. return {
  87. ...node,
  88. data: { ...node.data, selected: true }
  89. }
  90. } else {
  91. return node
  92. }
  93. })
  94. }
  95. if(!item?.data?.selected) {
  96. const cancelSelectNodes = nodes.value.filter(node => (node.id + '').indexOf((item?.id + '')) !== -1).map(node => node.id)
  97. nodes.value = nodes.value.map(node => {
  98. if (cancelSelectNodes.includes(node.id)) {
  99. return {
  100. ...node,
  101. data: { ...node.data, selected: false }
  102. }
  103. } else {
  104. return node
  105. }
  106. })
  107. }
  108. switchColors()
  109. changeEdgeStyle()
  110. }
  111. // 更改连线样式
  112. function changeEdgeStyle() {
  113. const selectNodeIdList = selectNodes.value.map(node => node.id)
  114. edges.value = edges.value.map(edge => {
  115. const { source, target } = edge
  116. const isSelected = selectNodeIdList.includes(source) && selectNodeIdList.includes(target)
  117. return {
  118. ...edge,
  119. style: {
  120. ...edge.style,
  121. strokeWidth: isSelected ? 4 : 1,
  122. stroke: isSelected ? '#01517f' : '',
  123. class: isSelected ? 'high-z-index' : ''
  124. },
  125. }
  126. })
  127. }
  128. // 设置选中背景色
  129. function switchColors() {
  130. selectNodes.value = nodes.value.filter(node => node.data.selected)
  131. nodes.value = nodes.value.map(node => ({
  132. ...node,
  133. style: {
  134. ...node.style,
  135. backgroundColor: node.data.selected ? '#01517f' : '#fff',
  136. },
  137. }))
  138. }
  139. // 添加节点和连线数据
  140. function AddData(list: any[], x: number, y: number, nodeId: string) {
  141. const newNodes: Node[] = []
  142. const newEdges = [...edges.value]
  143. const spacing = 80
  144. const startY = y - (list.length * spacing) / 2
  145. list.forEach((item, index) => {
  146. const newNodeId = `${nodeId}-${index + 1}` // 生成唯一 ID
  147. const newY = startY + index * spacing
  148. newNodes.push({
  149. id: newNodeId,
  150. data: { label: item.fromColBusName, tblName: item.toTbl, ...item, ...fixedLabel, },
  151. position: { x: x + 240, y: newY },
  152. ...FixedConfiguration,
  153. })
  154. newEdges.push({
  155. id: `e${nodeId}-${newNodeId}`,
  156. source: nodeId,
  157. target: newNodeId,
  158. type: connectionType
  159. })
  160. })
  161. nodes.value = [...nodes.value, ...newNodes]
  162. edges.value = newEdges
  163. }
  164. // 删除节点和连线数据
  165. function deteleData(nodeId: string) {
  166. clickedNodes.value = clickedNodes.value.filter(item => (item.id + '').indexOf(nodeId) === -1)
  167. nodes.value = nodes.value.filter(node => (node.id.indexOf(nodeId) > 0 || (node.id.indexOf(nodeId) === -1 || node.id === nodeId)))
  168. edges.value = edges.value.filter(edge => edge.source !== nodeId && edge.source.indexOf(nodeId) === -1)
  169. }
  170. // 展开收起节点
  171. function expandNode(nodeId: string, val: boolean) {
  172. const filterNodes = nodes.value.filter(node => !(node.id.indexOf(nodeId) > 0 || (node.id.indexOf(nodeId) === -1 || node.id === nodeId))).map(item => item.id)
  173. nodes.value = nodes.value.map(node => {
  174. if (filterNodes.includes(node.id)) {
  175. return {
  176. ...node,
  177. hidden: val
  178. }
  179. } else {
  180. return node
  181. }
  182. })
  183. }
  184. // 获取所有业务对象
  185. function getAllBusTable() {
  186. post(GET_ALL_BUS_TABLE, {}).then(res => {
  187. businessTableList.value = (res.data || []).map((item: any) => {
  188. return {
  189. label: item.name,
  190. value: item.id,
  191. ...item
  192. }
  193. })
  194. })
  195. }
  196. // 切换事件
  197. function businessObjectSwitching(val: any) {
  198. const row = businessTableList.value.find((item: any) => item.id === val)
  199. nodes.value = [{ id: '1', data: { ...row, ...fixedLabel }, position: { x: 100, y: 50 }, ...FixedConfiguration }]
  200. edges.value = []
  201. clickedNodes.value = []
  202. selectNodes.value = []
  203. }
  204. // 新建初始化数据
  205. function initData(row: any) {
  206. businessObject.value = row.id
  207. nodes.value = [{ id: '1', data: { ...row, ...fixedLabel }, position: { x: 100, y: 50 }, ...FixedConfiguration }]
  208. edges.value = []
  209. clickedNodes.value = []
  210. selectNodes.value = []
  211. }
  212. onMounted(() => {
  213. getAllBusTable()
  214. })
  215. // 向外暴露方法
  216. defineExpose({
  217. initData,
  218. });
  219. </script>
  220. <template>
  221. <div style="width: 100%; height: 100%;" class="relative">
  222. <!-- <div class="flex items-center absolute top-0 left-2 z-10">
  223. <div class="mr-2">业务对象:</div>
  224. <el-select v-model="businessObject" placeholder="请选择" style="width: 200px;" @change="businessObjectSwitching">
  225. <el-option v-for="item in businessTableList" :key="item.value" :label="item.label" :value="item.value" />
  226. </el-select>
  227. </div> -->
  228. <VueFlow :nodes="nodes" :edges="edges" @node-click="onNodeClick" fit-view-on-init :edge-updater-layer="true">
  229. <template #node-custom="{ data, id }">
  230. <div class="flex flex-row">
  231. <Handle class="handle left-handle" type="target" :position="Position.Left" :id="`${id}`" />
  232. <div class="flex items-center">
  233. <el-checkbox v-model="data.selected" size="large" @change="processSelectedNodes(id)" @click.stop
  234. class="pr-2 tops"></el-checkbox>
  235. <span :class="{ 'text-white': data.selected }">{{ data.label }}</span>
  236. </div>
  237. <Handle class="handle right-handle" type="source" :position="Position.Right" :id="`${id}`" />
  238. </div>
  239. </template>
  240. <Background />
  241. </VueFlow>
  242. </div>
  243. </template>
  244. <style lang='scss' scoped>
  245. .tops {
  246. top: 2px
  247. }
  248. ::deep(.vue-flow__node-custom) {
  249. position: relative;
  250. }
  251. .high-z-index {
  252. position: relative;
  253. z-index: 9999 !important;
  254. /* 提高连线层级 */
  255. }
  256. </style>