index.vue 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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 '@vue-flow/core/dist/style.css';
  6. import '@vue-flow/core/dist/theme-default.css';
  7. interface selectedNode {
  8. id: string;
  9. data: any
  10. }
  11. const FixedConfiguration = {
  12. type: 'custom',
  13. style: {
  14. width: '120px', padding: '5px 20px', fontSize: '14px',
  15. border: '1px solid #dcdfe6', borderRadius: '8px'
  16. },
  17. sourcePosition: Position.Right,
  18. targetPosition: Position.Left,
  19. hidden: false
  20. }
  21. const fixedLabel = {
  22. selected: false, // 是否选中
  23. }
  24. const connectionType = 'step'
  25. const nodes = ref<Node[]>([
  26. {
  27. id: '1', data: { label: '客户', ...fixedLabel }, position: { x: 100, y: 30 },
  28. ...FixedConfiguration
  29. },
  30. ])
  31. const edges = ref<Edge[]>([
  32. { id: 'e1-1', source: '1', target: '2', type: connectionType },
  33. ])
  34. const clickedNodes = ref<selectedNode[]>([]) // 记录点击的节点
  35. const selectNodes = ref<any[]>([]) // 记录选中的节点
  36. function onNodeClick(event: any) {
  37. const { x, y } = event.node.position // 点击的位置
  38. const nodeId = event.node.id
  39. // 点击的节点Id
  40. const clickedNodesStr = clickedNodes.value.map(node => node.id)
  41. // 选中的节点Id
  42. const selectNodeIdList = selectNodes.value.map(node => node.id)
  43. // 点击长度相同的节点Id
  44. const clickedNodesLengthStr = clickedNodesStr.filter(strId => strId.length == nodeId.length)
  45. if (clickedNodesStr.includes(nodeId)) {
  46. if (selectNodeIdList.includes(nodeId)) {
  47. for (let i in selectNodeIdList) {
  48. if (nodeId !== selectNodeIdList[i] && nodeId.length === selectNodeIdList[i].length) {
  49. expandNode(selectNodeIdList[i], true)
  50. } else if (nodeId == selectNodeIdList[i]) {
  51. expandNode(nodeId, false)
  52. }
  53. }
  54. const notSelectedToDelete = clickedNodesLengthStr.filter(item => !selectNodeIdList.includes(item));
  55. for (let i in notSelectedToDelete) {
  56. deteleData(notSelectedToDelete[i])
  57. }
  58. }
  59. } else {
  60. clickedNodes.value.push({ id: nodeId, data: event.node.data })
  61. clickedNodesStr.push(nodeId)
  62. // 隐藏和删除其他节点
  63. const hiddenNode = clickedNodesLengthStr.filter(item => selectNodeIdList.includes(item))
  64. const deleteNode = clickedNodesLengthStr.filter(item => !selectNodeIdList.includes(item))
  65. hiddenNode.forEach(item => expandNode(item, true))
  66. deleteNode.forEach(item => deteleData(item))
  67. const strList = ['拜访', '拜访计划', '协议', '合同订单', '工单', '项目', '报销费用']
  68. AddData(strList, x, y, nodeId)
  69. }
  70. }
  71. // 处理级之间的联动勾选
  72. function processSelectedNodes(nodeId: string) {
  73. const item = nodes.value.find(item => item.id === nodeId)
  74. if (item?.data?.selected) { // 选中状态将之前的节点选中
  75. const selectNodes = item.id.split('-').map((_: any, index: number, arr: any) => arr.slice(0, index + 1).join('-'));
  76. nodes.value = nodes.value.map(node => {
  77. if (selectNodes.includes(node.id)) {
  78. return {
  79. ...node,
  80. data: { ...node.data, selected: true }
  81. }
  82. } else {
  83. return node
  84. }
  85. })
  86. }
  87. if(!item?.data?.selected) {
  88. const cancelSelectNodes = nodes.value.filter(node => (node.id + '').indexOf((item?.id + '')) !== -1).map(node => node.id)
  89. nodes.value = nodes.value.map(node => {
  90. if (cancelSelectNodes.includes(node.id)) {
  91. return {
  92. ...node,
  93. data: { ...node.data, selected: false }
  94. }
  95. } else {
  96. return node
  97. }
  98. })
  99. }
  100. switchColors()
  101. changeEdgeStyle()
  102. }
  103. // 更改连线样式
  104. function changeEdgeStyle() {
  105. const selectNodeIdList = selectNodes.value.map(node => node.id)
  106. edges.value = edges.value.map(edge => {
  107. const { source, target } = edge
  108. const isSelected = selectNodeIdList.includes(source) && selectNodeIdList.includes(target)
  109. return {
  110. ...edge,
  111. style: {
  112. ...edge.style,
  113. strokeWidth: isSelected ? 4 : 1,
  114. stroke: isSelected ? '#01517f' : '',
  115. class: isSelected ? 'high-z-index' : ''
  116. },
  117. }
  118. })
  119. }
  120. // 设置选中背景色
  121. function switchColors() {
  122. selectNodes.value = nodes.value.filter(node => node.data.selected)
  123. nodes.value = nodes.value.map(node => ({
  124. ...node,
  125. style: {
  126. ...node.style,
  127. backgroundColor: node.data.selected ? '#01517f' : '#fff',
  128. },
  129. }))
  130. }
  131. // 添加节点和连线数据
  132. function AddData(list: any[], x: number, y: number, nodeId: string) {
  133. const newNodes: Node[] = []
  134. const newEdges = [...edges.value]
  135. const spacing = 80
  136. const startY = y - (list.length * spacing) / 2
  137. list.forEach((label, index) => {
  138. const newNodeId = `${nodeId}-${index + 1}` // 生成唯一 ID
  139. const newY = startY + index * spacing
  140. newNodes.push({
  141. id: newNodeId,
  142. data: { label, ...fixedLabel },
  143. position: { x: x + 240, y: newY },
  144. ...FixedConfiguration,
  145. })
  146. newEdges.push({
  147. id: `e${nodeId}-${newNodeId}`,
  148. source: nodeId,
  149. target: newNodeId,
  150. type: connectionType
  151. })
  152. })
  153. nodes.value = [...nodes.value, ...newNodes]
  154. edges.value = newEdges
  155. }
  156. // 删除节点和连线数据
  157. function deteleData(nodeId: string) {
  158. clickedNodes.value = clickedNodes.value.filter(item => (item.id + '').indexOf(nodeId) === -1)
  159. nodes.value = nodes.value.filter(node => (node.id.indexOf(nodeId) > 0 || (node.id.indexOf(nodeId) === -1 || node.id === nodeId)))
  160. edges.value = edges.value.filter(edge => edge.source !== nodeId && edge.source.indexOf(nodeId) === -1)
  161. }
  162. // 展开收起节点
  163. function expandNode(nodeId: string, val: boolean) {
  164. const filterNodes = nodes.value.filter(node => !(node.id.indexOf(nodeId) > 0 || (node.id.indexOf(nodeId) === -1 || node.id === nodeId))).map(item => item.id)
  165. nodes.value = nodes.value.map(node => {
  166. if (filterNodes.includes(node.id)) {
  167. return {
  168. ...node,
  169. hidden: val
  170. }
  171. } else {
  172. return node
  173. }
  174. })
  175. }
  176. </script>
  177. <template>
  178. <div style="width: 100%; height: 100%;">
  179. <VueFlow :nodes="nodes" :edges="edges" @node-click="onNodeClick" fit-view-on-init :edge-updater-layer="true">
  180. <template #node-custom="{ data, id }">
  181. <div class="flex flex-row">
  182. <Handle class="handle left-handle" type="target" :position="Position.Left" :id="`${id}`" />
  183. <div class="flex items-center">
  184. <el-checkbox v-model="data.selected" size="large" @change="processSelectedNodes(id)" @click.stop
  185. class="pr-2 tops"></el-checkbox>
  186. <span :class="{ 'text-white': data.selected }">{{ data.label }}</span>
  187. </div>
  188. <Handle class="handle right-handle" type="source" :position="Position.Right" :id="`${id}`" />
  189. </div>
  190. </template>
  191. <Background />
  192. </VueFlow>
  193. </div>
  194. </template>
  195. <style lang='scss' scoped>
  196. .tops {
  197. top: 2px
  198. }
  199. ::deep(.vue-flow__node-custom) {
  200. position: relative;
  201. }
  202. .high-z-index {
  203. position: relative;
  204. z-index: 9999 !important;
  205. /* 提高连线层级 */
  206. }
  207. </style>