DisposalProgressContent copy 5.vue 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. <template>
  2. <div class="container">
  3. <div id="graph_container" ref="graphRef"></div>
  4. </div>
  5. </template>
  6. <script setup>
  7. import { ref, onMounted } from 'vue'
  8. import { Graph } from '@antv/x6'
  9. import { register } from '@antv/x6-vue-shape'
  10. import ProgressNode from './common/ProgressNode.vue'
  11. import ProgressChildrenNode from './common/ProgressChildrenNode.vue'
  12. import imgIcon from '../../../../assets/img/节点连线箭头.png'
  13. // **注册 Vue 组件节点**
  14. register({
  15. shape: 'custom-vue-node',
  16. width: 140,
  17. height: 60,
  18. component: ProgressNode,
  19. getComponent(node) {
  20. return {
  21. component: ProgressNode,
  22. props: {
  23. label: node.getData().label,
  24. state: node.getData().state
  25. }
  26. }
  27. }
  28. })
  29. register({
  30. shape: 'custom-vue-children-node',
  31. width: 140,
  32. height: 60,
  33. component: ProgressChildrenNode,
  34. getComponent(node) {
  35. return {
  36. component: ProgressChildrenNode,
  37. props: {
  38. label: node.getData().label,
  39. state: node.getData().state
  40. }
  41. }
  42. }
  43. })
  44. // **画布**
  45. const graphRef = ref(null)
  46. let graph = null
  47. // **数据**
  48. const NodeData = {
  49. dagNode: [
  50. {
  51. level: 1,
  52. fId: 1,
  53. nodeId: 1,
  54. nodeName: '发现报告',
  55. state: '已完成',
  56. x: 10,
  57. y: 80
  58. },
  59. {
  60. level: 2,
  61. nodeId: '1-1',
  62. nodeName: '信息接报',
  63. state: '已完成',
  64. x: 240,
  65. y: 0
  66. },
  67. {
  68. level: 2,
  69. nodeId: '1-2',
  70. nodeName: '信息核实',
  71. state: '已完成',
  72. x: 240,
  73. y: 55
  74. },
  75. {
  76. level: 2,
  77. nodeId: '1-3',
  78. nodeName: '快速评估',
  79. state: '已完成',
  80. x: 240,
  81. y: 110
  82. },
  83. {
  84. level: 2,
  85. nodeId: '1-4',
  86. nodeName: '信息上报',
  87. state: '已完成',
  88. x: 240,
  89. y: 165
  90. },
  91. {
  92. level: 1,
  93. fId: 2,
  94. nodeId: 2,
  95. nodeName: '个案调查处置',
  96. state: '进行中',
  97. x: 10,
  98. y: 295
  99. },
  100. {
  101. level: 2,
  102. twoFId: '2-1',
  103. nodeId: '2-1',
  104. nodeName: '李梦康',
  105. state: '已完成',
  106. x: 240,
  107. y: 240
  108. },
  109. {
  110. level: 2,
  111. twoFId: '2-2',
  112. nodeId: '2-2',
  113. nodeName: '毛超',
  114. state: '已完成',
  115. x: 240,
  116. y: 295
  117. },
  118. {
  119. level: 2,
  120. twoFId: '2-3',
  121. nodeId: '2-3',
  122. nodeName: '贾子敏',
  123. state: '进行中',
  124. x: 240,
  125. y: 350
  126. },
  127. {
  128. level: 3,
  129. nodeId: '2-2-1',
  130. nodeName: '病例管理',
  131. state: '已完成',
  132. x: 420,
  133. y: 210
  134. },
  135. {
  136. level: 3,
  137. nodeId: '2-2-2',
  138. nodeName: '流行病学调查',
  139. state: '已完成',
  140. x: 420,
  141. y: 265
  142. },
  143. {
  144. level: 3,
  145. nodeId: '2-2-3',
  146. nodeName: '实验室检测',
  147. state: '已完成',
  148. x: 420,
  149. y: 320
  150. },
  151. {
  152. level: 3,
  153. nodeId: '2-2-4',
  154. nodeName: '风险评估',
  155. state: '已完成',
  156. x: 420,
  157. y: 375
  158. },
  159. {
  160. level: 1,
  161. fId: 3,
  162. nodeId: 3,
  163. nodeName: '风险人员和环境排查管控',
  164. state: '进行中',
  165. x: 10,
  166. y: 480
  167. },
  168. {
  169. level: 2,
  170. twoFId: '3-1',
  171. nodeId: '3-1',
  172. nodeName: '段伟',
  173. state: '已完成',
  174. x: 240,
  175. y: 420
  176. },
  177. {
  178. level: 2,
  179. twoFId: '3-2',
  180. nodeId: '3-2',
  181. nodeName: '陆成奇',
  182. state: '已完成',
  183. x: 240,
  184. y: 475
  185. },
  186. {
  187. level: 2,
  188. twoFId: '3-3',
  189. nodeId: '3-3',
  190. nodeName: '曾强',
  191. state: '进行中',
  192. x: 240,
  193. y: 530
  194. },
  195. {
  196. level: 3,
  197. twoFId: '3-3-1',
  198. nodeId: '3-3-1',
  199. nodeName: '病例管理',
  200. state: '已完成',
  201. x: 420,
  202. y:450
  203. },
  204. {
  205. level: 3,
  206. twoFId: '3-3-2',
  207. nodeId: '3-3-2',
  208. nodeName: '流行病学调查',
  209. state: '已完成',
  210. x: 420,
  211. y:505
  212. },
  213. {
  214. level: 3,
  215. twoFId: '3-3-2',
  216. nodeId: '3-3-3',
  217. nodeName: '实验室检测',
  218. state: '进行中',
  219. x: 420,
  220. y:560
  221. },
  222. {
  223. level: 3,
  224. twoFId: '3-3-3',
  225. nodeId: '3-3-4',
  226. nodeName: '风险人员管控',
  227. state: '进行中',
  228. x: 420,
  229. y:615
  230. },
  231. {
  232. level: 3,
  233. twoFId: '3-3-4',
  234. nodeId: '3-3-5',
  235. nodeName: '风险场所管控',
  236. state: '进行中',
  237. x: 420,
  238. y:670
  239. },
  240. { level: 1, fId: 4, nodeId: 4, nodeName: '区域风险排查管控', state: '未开始', x: 10, y: 620 },
  241. { level: 1, fId: 5, nodeId: 5, nodeName: '结案', x: 10, y: 720 }
  242. ],
  243. dagLine: [
  244. { from: 1, to: 2 },
  245. { from: 2, to: 3 },
  246. { from: 3, to: 4 },
  247. { from: 4, to: 5 },
  248. { from: 1, to: '1-1' },
  249. { from: 1, to: '1-2' },
  250. { from: 1, to: '1-3' },
  251. { from: 1, to: '1-4' },
  252. { from: 2, to: '2-1' },
  253. { from: 2, to: '2-2' },
  254. { from: 2, to: '2-3' },
  255. { from: '2-2', to: '2-2-1' },
  256. { from: '2-2', to: '2-2-2' },
  257. { from: '2-2', to: '2-2-3' },
  258. { from: '2-2', to: '2-2-4' },
  259. { from: 3, to: '3-1' },
  260. { from: 3, to: '3-2' },
  261. { from: 3, to: '3-3' },
  262. { from: '3-3', to: '3-3-1' },
  263. { from: '3-3', to: '3-3-2' },
  264. { from: '3-3', to: '3-3-3' },
  265. { from: '3-3', to: '3-3-4' },
  266. { from: '3-3', to: '3-3-5' },
  267. ]
  268. }
  269. // **自动合并多条连线**
  270. const mergeConnections = edges => {
  271. const targetMap = {}
  272. // 统计指向同一 target 的 source
  273. edges.forEach(edge => {
  274. if (!targetMap[edge.to]) {
  275. targetMap[edge.to] = []
  276. }
  277. targetMap[edge.to].push(edge.from)
  278. })
  279. const newEdges = []
  280. Object.keys(targetMap).forEach(target => {
  281. const sources = targetMap[target]
  282. if (sources.length > 1) {
  283. // 1. 创建“合并节点”
  284. const mergeNodeId = `merge_${target}`
  285. newEdges.push(...sources.map(src => ({ from: src, to: mergeNodeId })))
  286. newEdges.push({ from: mergeNodeId, to: target })
  287. } else {
  288. newEdges.push({ from: sources[0], to: target })
  289. }
  290. })
  291. return newEdges
  292. }
  293. // **计算竖排三列布局**
  294. // const computeLayout = () => {
  295. // const levels = {}
  296. // const columnSpacing = 160 // 列间距
  297. // const rowSpacing = 72 // 行间距
  298. // // **按 level 存储节点**
  299. // NodeData.dagNode.forEach(node => {
  300. // if (!levels[node.level]) {
  301. // levels[node.level] = []
  302. // }
  303. // levels[node.level].push(node)
  304. // })
  305. // const nodePositions = {}
  306. // let x = 50 // 第一列的 x 轴位置
  307. // Object.keys(levels).forEach(level => {
  308. // let y = 50 // 第一行 y 轴初始位置
  309. // const nodes = levels[level]
  310. // nodes.forEach(node => {
  311. // nodePositions[node.nodeId] = { x, y }
  312. // y += rowSpacing // **每个节点往下排列**
  313. // })
  314. // x += columnSpacing // **每列向右移动**
  315. // })
  316. // return nodePositions
  317. // }
  318. // **初始化 X6 画布**
  319. onMounted(() => {
  320. graph = new Graph({
  321. container: graphRef.value,
  322. width: 580,
  323. height: 801,
  324. connecting: {
  325. router: 'orth' // 自动优化连线
  326. // connector: 'smooth', // 平滑曲线
  327. // anchor: 'center',
  328. // connectionPoint: 'anchor',
  329. // allowBlank: false,
  330. // snap: true
  331. }
  332. })
  333. // const nodePositions = computeLayout()
  334. // **添加节点**
  335. NodeData.dagNode.forEach(node => {
  336. graph.addNode({
  337. id: node.nodeId,
  338. shape: node.level === 1 ? 'custom-vue-node' : 'custom-vue-children-node',
  339. x: node.x,
  340. y: node.y,
  341. data: {
  342. label: node.nodeName,
  343. state: node.state
  344. }
  345. })
  346. })
  347. // **处理合并连线**
  348. const mergedEdges = mergeConnections(NodeData.dagLine)
  349. // **添加合并后的连线**
  350. mergedEdges.forEach(edge => {
  351. graph.addEdge({
  352. source: String(edge.from),
  353. target: String(edge.to),
  354. attrs: {
  355. line: {
  356. stroke: '#5B8FF9',
  357. strokeDasharray: '5 5',
  358. strokeWidth: 2,
  359. targetMarker: {
  360. tagName: 'image',
  361. 'xlink:href': imgIcon,
  362. width: 24,
  363. height: 24,
  364. y: -12,
  365. x:-10,
  366. transform: 'rotate(0)' // 旋转箭头图片
  367. }
  368. }
  369. },
  370. router: { name: 'manhattan' }, // 自动贴合边缘
  371. connector: { name: 'rounded' } // 让线条更圆滑
  372. })
  373. })
  374. })
  375. </script>
  376. <style scoped>
  377. .container {
  378. width: 100%;
  379. height: 100%;
  380. }
  381. #graph_container {
  382. width: 100%;
  383. height: 100%;
  384. }
  385. </style>