@mx-sose-front/mx-sose-graph 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +343 -0
  3. package/dist/index.d.ts +3937 -0
  4. package/dist/index.esm.js +74367 -0
  5. package/dist/index.esm.js.map +1 -0
  6. package/dist/index.umd.js +38 -0
  7. package/dist/index.umd.js.map +1 -0
  8. package/dist/style.css +1 -0
  9. package/package.json +70 -0
  10. package/src/components/ContextMenu.vue +475 -0
  11. package/src/components/Diagram/StrategicTaxonomyDiagram.vue +141 -0
  12. package/src/components/Edge/Edge.vue +366 -0
  13. package/src/components/InteractionLayer.vue +2033 -0
  14. package/src/components/Label.vue +0 -0
  15. package/src/components/LineStyle/ConnectionLine.vue +126 -0
  16. package/src/components/LineStyle/LineStyleMarker.vue +87 -0
  17. package/src/components/Pin/Pin.vue +220 -0
  18. package/src/components/Pin/Port.vue +172 -0
  19. package/src/components/Shape/Action.vue +121 -0
  20. package/src/components/Shape/ActivityAction.vue +155 -0
  21. package/src/components/Shape/Block.vue +306 -0
  22. package/src/components/Shape/ConceptualRole.vue +266 -0
  23. package/src/components/Shape/Diagram.vue +220 -0
  24. package/src/components/Shape/DividingLine.vue +594 -0
  25. package/src/components/Shape/DogEar.vue +224 -0
  26. package/src/components/Shape/Package.vue +340 -0
  27. package/src/constants/edgeShapeKeys.ts +81 -0
  28. package/src/constants/index.ts +440 -0
  29. package/src/index.ts +28 -0
  30. package/src/render/shape-registry.ts +17 -0
  31. package/src/render/shape-renderer.ts +103 -0
  32. package/src/statics/icons/childIcons/relations@3x.png +0 -0
  33. package/src/statics/icons/childIcons/role@3x.png +0 -0
  34. package/src/statics/icons/childIcons//344/270/232/345/212/241/344/277/241/345/217/267@3x.png +0 -0
  35. package/src/statics/icons/childIcons//344/270/232/345/212/241/344/277/241/346/201/257@3x.png +0 -0
  36. package/src/statics/icons/childIcons//344/270/232/345/212/241/344/277/241/346/201/257/345/233/276@3x.png +0 -0
  37. package/src/statics/icons/childIcons//344/270/232/345/212/241/345/206/205/351/203/250/346/265/201/347/250/213/345/233/276@3x.png +0 -0
  38. package/src/statics/icons/childIcons//344/270/232/345/212/241/345/206/205/351/203/250/350/277/236/351/200/232/345/233/276@3x.png +0 -0
  39. package/src/statics/icons/childIcons//344/270/232/345/212/241/345/212/250/344/275/234@3x.png +0 -0
  40. package/src/statics/icons/childIcons//344/270/232/345/212/241/345/217/202/346/225/260@3x.png +0 -0
  41. package/src/statics/icons/childIcons//344/270/232/345/212/241/345/217/202/346/225/260/345/233/276@3x.png +0 -0
  42. package/src/statics/icons/childIcons//344/270/232/345/212/241/345/257/271/350/261/241/346/265/201@3x.png +0 -0
  43. package/src/statics/icons/childIcons//344/270/232/345/212/241/346/211/247/350/241/214/350/200/205@3x.png +0 -0
  44. package/src/statics/icons/childIcons//344/270/232/345/212/241/346/216/245/345/217/243@3x.png +0 -0
  45. package/src/statics/icons/childIcons//344/270/232/345/212/241/346/216/247/345/210/266/346/265/201@3x.png +0 -0
  46. package/src/statics/icons/childIcons//344/270/232/345/212/241/346/246/202/345/277/265/345/233/276@3x.png +0 -0
  47. package/src/statics/icons/childIcons//344/270/232/345/212/241/346/246/202/345/277/265/350/241/250@3x.png +0 -0
  48. package/src/statics/icons/childIcons//344/270/232/345/212/241/346/264/273/345/212/250@3x.png +0 -0
  49. package/src/statics/icons/childIcons//344/270/232/345/212/241/346/264/273/345/212/250/344/270/216/350/203/275/345/212/233/346/230/240/345/260/204/347/237/251/351/230/265@3x.png +0 -0
  50. package/src/statics/icons/childIcons//344/270/232/345/212/241/346/264/273/345/212/250/345/212/250/344/275/234@3x.png +0 -0
  51. package/src/statics/icons/childIcons//344/270/232/345/212/241/346/264/273/345/212/250/346/265/201/347/250/213/345/233/276@3x.png +0 -0
  52. package/src/statics/icons/childIcons//344/270/232/345/212/241/346/264/273/345/212/250/347/273/223/346/236/204/345/233/276@3x.png +0 -0
  53. package/src/statics/icons/childIcons//344/270/232/345/212/241/346/265/201/347/250/213/345/233/276@3x.png +0 -0
  54. package/src/statics/icons/childIcons//344/270/232/345/212/241/347/212/266/346/200/201/345/233/276@3x.png +0 -0
  55. package/src/statics/icons/childIcons//344/270/232/345/212/241/347/253/257/345/217/243@3x.png +0 -0
  56. package/src/statics/icons/childIcons//344/270/232/345/212/241/347/272/246/346/235/237@3x.png +0 -0
  57. package/src/statics/icons/childIcons//344/270/232/345/212/241/347/272/246/346/235/237/345/233/276@3x.png +0 -0
  58. package/src/statics/icons/childIcons//344/270/232/345/212/241/347/272/246/346/235/237/345/256/232/344/271/211/345/233/276@3x.png +0 -0
  59. package/src/statics/icons/childIcons//344/270/232/345/212/241/347/272/246/346/235/237/350/241/250@3x.png +0 -0
  60. package/src/statics/icons/childIcons//344/270/232/345/212/241/347/273/223/346/236/204@3x.png +0 -0
  61. package/src/statics/icons/childIcons//344/270/232/345/212/241/350/207/252/347/224/261/345/210/206/347/261/273/345/233/276@3x.png +0 -0
  62. package/src/statics/icons/childIcons//344/270/232/345/212/241/350/247/222/350/211/262@3x.png +0 -0
  63. package/src/statics/icons/childIcons//344/270/232/345/212/241/350/277/236/346/216/245/345/231/250@3x.png +0 -0
  64. package/src/statics/icons/childIcons//344/270/232/345/212/241/350/277/236/351/200/232/345/233/276@3x.png +0 -0
  65. package/src/statics/icons/childIcons//344/270/232/345/212/241/350/277/236/351/200/232/350/241/250@3x.png +0 -0
  66. package/src/statics/icons/childIcons//344/270/232/345/212/241/351/253/230/347/272/247/346/246/202/345/277/265/345/233/276@3x.png +0 -0
  67. package/src/statics/icons/childIcons//344/272/272/345/221/230@3x.png +0 -0
  68. package/src/statics/icons/childIcons//344/272/272/345/221/230/345/206/205/351/203/250/346/265/201/347/250/213/345/233/276@3x.png +0 -0
  69. package/src/statics/icons/childIcons//344/272/272/345/221/230/345/206/205/351/203/250/350/277/236/351/200/232/345/233/276@3x.png +0 -0
  70. package/src/statics/icons/childIcons//344/272/272/345/221/230/345/217/202/346/225/260/345/233/276@3x.png +0 -0
  71. package/src/statics/icons/childIcons//344/272/272/345/221/230/346/246/202/345/277/265/345/233/276@3x.png +0 -0
  72. package/src/statics/icons/childIcons//344/272/272/345/221/230/346/246/202/345/277/265/350/241/250@3x.png +0 -0
  73. package/src/statics/icons/childIcons//344/272/272/345/221/230/346/265/201/347/250/213/345/233/276@3x.png +0 -0
  74. package/src/statics/icons/childIcons//344/272/272/345/221/230/347/212/266/346/200/201/345/233/276@3x.png +0 -0
  75. package/src/statics/icons/childIcons//344/272/272/345/221/230/347/272/246/346/235/237/345/233/276@3x-2.png +0 -0
  76. package/src/statics/icons/childIcons//344/272/272/345/221/230/347/272/246/346/235/237/345/233/276@3x.png +0 -0
  77. package/src/statics/icons/childIcons//344/272/272/345/221/230/347/273/223/346/236/204/345/233/276@3x.png +0 -0
  78. package/src/statics/icons/childIcons//344/272/272/345/221/230/350/277/236/351/200/232/345/233/276@3x.png +0 -0
  79. package/src/statics/icons/childIcons//344/272/272/345/221/230/350/277/236/351/200/232/350/241/250@3x.png +0 -0
  80. package/src/statics/icons/childIcons//344/273/267/345/200/274/346/265/201@3x.png +0 -0
  81. package/src/statics/icons/childIcons//344/273/273/346/204/217/345/205/263/347/263/273@3x.png +0 -0
  82. package/src/statics/icons/childIcons//344/273/273/346/204/217/350/277/236/346/216/245/345/231/250@3x.png +0 -0
  83. package/src/statics/icons/childIcons//344/274/201/344/270/232/344/275/277/345/221/275@3x.png +0 -0
  84. package/src/statics/icons/childIcons//344/274/201/344/270/232/345/205/250/347/224/237/345/221/275/345/221/250/346/234/237@3x.png +0 -0
  85. package/src/statics/icons/childIcons//344/274/201/344/270/232/346/204/277/346/231/257@3x.png +0 -0
  86. package/src/statics/icons/childIcons//344/274/201/344/270/232/347/233/256/346/240/207@3x.png +0 -0
  87. package/src/statics/icons/childIcons//344/275/215/347/275/256@3x.png +0 -0
  88. package/src/statics/icons/childIcons//344/275/223/347/263/273/346/236/266/346/236/204@3x.png +0 -0
  89. package/src/statics/icons/childIcons//344/276/235/350/265/226@3x.png +0 -0
  90. package/src/statics/icons/childIcons//344/277/235/346/212/244@3x.png +0 -0
  91. package/src/statics/icons/childIcons//344/277/241/346/201/257/346/250/241/345/236/213@3x.png +0 -0
  92. package/src/statics/icons/childIcons//345/210/233/351/200/240@3x.png +0 -0
  93. package/src/statics/icons/childIcons//345/212/237/350/203/275@3x.png +0 -0
  94. package/src/statics/icons/childIcons//345/212/237/350/203/275/344/270/216/344/270/232/345/212/241/346/264/273/345/212/250/346/230/240/345/260/204/347/237/251/351/230/265@3x.png +0 -0
  95. package/src/statics/icons/childIcons//345/212/237/350/203/275/345/212/250/344/275/234@3x.png +0 -0
  96. package/src/statics/icons/childIcons//345/212/237/350/203/275/345/257/271/350/261/241/346/265/201@3x.png +0 -0
  97. package/src/statics/icons/childIcons//345/212/237/350/203/275/346/216/247/345/210/266/346/265/201@3x.png +0 -0
  98. package/src/statics/icons/childIcons//345/217/214/345/220/221/345/205/263/350/201/224@3x.png +0 -0
  99. package/src/statics/icons/childIcons//345/217/227/345/210/260/345/275/261/345/223/215@3x.png +0 -0
  100. package/src/statics/icons/childIcons//345/234/260/347/220/206/346/224/277/346/262/273/350/214/203/345/233/264/347/261/273/345/236/213@3x.png +0 -0
  101. package/src/statics/icons/childIcons//345/241/253/345/206/231/350/201/214/344/275/215/347/224/263/350/257/267@3x.png +0 -0
  102. package/src/statics/icons/childIcons//345/256/211/345/205/250/345/206/205/351/203/250/346/265/201/347/250/213/345/233/276@3x.png +0 -0
  103. package/src/statics/icons/childIcons//345/256/211/345/205/250/346/216/247/345/210/266@3x.png +0 -0
  104. package/src/statics/icons/childIcons//345/256/211/345/205/250/346/246/202/345/277/265/345/233/276@3x.png +0 -0
  105. package/src/statics/icons/childIcons//345/256/211/345/205/250/346/246/202/345/277/265/350/241/250@3x.png +0 -0
  106. package/src/statics/icons/childIcons//345/256/211/345/205/250/346/265/201/347/250/213@3x.png +0 -0
  107. package/src/statics/icons/childIcons//345/256/211/345/205/250/346/265/201/347/250/213/345/212/250/344/275/234@3x.png +0 -0
  108. package/src/statics/icons/childIcons//345/256/211/345/205/250/346/265/201/347/250/213/345/233/276@3x.png +0 -0
  109. package/src/statics/icons/childIcons//345/256/211/345/205/250/347/272/246/346/235/237@3x.png +0 -0
  110. package/src/statics/icons/childIcons//345/256/211/345/205/250/347/272/246/346/235/237/345/233/276@3x.png +0 -0
  111. package/src/statics/icons/childIcons//345/256/211/345/205/250/347/272/246/346/235/237/345/256/232/344/271/211/345/233/276@3x.png +0 -0
  112. package/src/statics/icons/childIcons//345/256/211/345/205/250/347/273/223/346/236/204/345/233/276@3x.png +0 -0
  113. package/src/statics/icons/childIcons//345/256/211/345/205/250/350/277/236/351/200/232/345/233/276@3x.png +0 -0
  114. package/src/statics/icons/childIcons//345/256/211/345/205/250/350/277/236/351/200/232/350/241/250@3x.png +0 -0
  115. package/src/statics/icons/childIcons//345/256/211/345/205/250/351/232/224/347/246/273@3x.png +0 -0
  116. package/src/statics/icons/childIcons//345/256/211/345/205/250/351/243/216/351/231/251@3x.png +0 -0
  117. package/src/statics/icons/childIcons//345/256/232/345/220/221/345/205/263/347/263/273@3x.png +0 -0
  118. package/src/statics/icons/childIcons//345/256/232/345/220/221/345/205/263/350/201/224@3x.png +0 -0
  119. package/src/statics/icons/childIcons//345/256/232/345/220/221/347/273/204/346/210/220@3x.png +0 -0
  120. package/src/statics/icons/childIcons//345/256/232/345/220/221/350/201/232/345/220/210@3x.png +0 -0
  121. package/src/statics/icons/childIcons//345/256/236/351/231/205/344/272/272/345/221/230@3x.png +0 -0
  122. package/src/statics/icons/childIcons//345/256/236/351/231/205/344/274/201/344/270/232/351/230/266/346/256/265@3x.png +0 -0
  123. package/src/statics/icons/childIcons//345/256/236/351/231/205/344/275/215/347/275/256@3x.png +0 -0
  124. package/src/statics/icons/childIcons//345/256/236/351/231/205/345/261/236/346/200/247/350/256/276/347/275/256@3x.png +0 -0
  125. package/src/statics/icons/childIcons//345/256/236/351/231/205/346/210/230/347/225/245/351/230/266/346/256/265/347/224/230/347/211/271/345/233/276@3x.png +0 -0
  126. package/src/statics/icons/childIcons//345/256/236/351/231/205/346/214/201/347/273/255/344/273/273/345/212/241@3x.png +0 -0
  127. package/src/statics/icons/childIcons//345/256/236/351/231/205/346/234/215/345/212/241@3x.png +0 -0
  128. package/src/statics/icons/childIcons//345/256/236/351/231/205/346/235/241/344/273/266@3x.png +0 -0
  129. package/src/statics/icons/childIcons//345/256/236/351/231/205/347/216/257/345/242/203@3x.png +0 -0
  130. package/src/statics/icons/childIcons//345/256/236/351/231/205/347/273/204/347/273/207@3x.png +0 -0
  131. package/src/statics/icons/childIcons//345/256/236/351/231/205/350/201/214/344/275/215@3x.png +0 -0
  132. package/src/statics/icons/childIcons//345/256/236/351/231/205/350/201/214/350/264/243@3x.png +0 -0
  133. package/src/statics/icons/childIcons//345/256/236/351/231/205/350/264/243/344/273/273@3x.png +0 -0
  134. package/src/statics/icons/childIcons//345/256/236/351/231/205/350/265/204/346/272/220@3x.png +0 -0
  135. package/src/statics/icons/childIcons//345/256/236/351/231/205/350/265/204/346/272/220/344/270/216/345/256/236/351/231/205/351/241/271/347/233/256/345/257/271/345/272/224/345/205/263/347/263/273/347/237/251/351/230/265@3x.png +0 -0
  136. package/src/statics/icons/childIcons//345/256/236/351/231/205/350/265/204/346/272/220/346/246/202/345/277/265/347/237/251/351/230/265@3x.png +0 -0
  137. package/src/statics/icons/childIcons//345/256/236/351/231/205/350/265/204/346/272/220/347/273/223/346/236/204/345/233/276@3x.png +0 -0
  138. package/src/statics/icons/childIcons//345/256/236/351/231/205/350/265/204/346/272/220/350/277/236/351/200/232/345/233/276@3x.png +0 -0
  139. package/src/statics/icons/childIcons//345/256/236/351/231/205/351/241/271/347/233/256@3x.png +0 -0
  140. package/src/statics/icons/childIcons//345/256/236/351/231/205/351/241/271/347/233/256/344/270/216/350/203/275/345/212/233/347/232/204/346/230/240/345/260/204/347/237/251/351/230/265@3x.png +0 -0
  141. package/src/statics/icons/childIcons//345/256/236/351/231/205/351/241/271/347/233/256/351/207/214/347/250/213/347/242/221@3x.png +0 -0
  142. package/src/statics/icons/childIcons//345/256/236/351/231/205/351/241/271/347/233/256/351/207/214/347/250/213/347/242/221/346/261/207/346/200/273/350/241/250@3x.png +0 -0
  143. package/src/statics/icons/childIcons//345/256/236/351/231/205/351/243/216/351/231/251@3x.png +0 -0
  144. package/src/statics/icons/childIcons//345/261/225/347/244/272@3x.png +0 -0
  145. package/src/statics/icons/childIcons//345/267/262/347/237/245/350/265/204/346/272/220@3x.png +0 -0
  146. package/src/statics/icons/childIcons//345/274/200/345/261/225/345/267/245/344/275/234/350/203/275/345/212/233@3x.png +0 -0
  147. package/src/statics/icons/childIcons//345/274/225/347/224/250/345/261/236/346/200/247@3x.png +0 -0
  148. package/src/statics/icons/childIcons//345/275/261/345/223/215@3x-2.png +0 -0
  149. package/src/statics/icons/childIcons//345/275/261/345/223/215@3x.png +0 -0
  150. package/src/statics/icons/childIcons//346/204/277/346/231/257/345/256/243/350/250/200@3x.png +0 -0
  151. package/src/statics/icons/childIcons//346/210/230/347/225/245@3x.png +0 -0
  152. package/src/statics/icons/childIcons//346/210/230/347/225/245/344/277/241/346/201/257@3x.png +0 -0
  153. package/src/statics/icons/childIcons//346/210/230/347/225/245/344/277/241/346/201/257/345/233/276@3x-2.png +0 -0
  154. package/src/statics/icons/childIcons//346/210/230/347/225/245/344/277/241/346/201/257/345/233/276@3x.png +0 -0
  155. package/src/statics/icons/childIcons//346/210/230/347/225/245/345/217/202/346/225/260/345/233/276@3x.png +0 -0
  156. package/src/statics/icons/childIcons//346/210/230/347/225/245/345/256/236/351/231/205/346/210/230/347/225/245/351/230/266/346/256/265/345/210/206/347/261/273/350/241/250@3x.png +0 -0
  157. package/src/statics/icons/childIcons//346/210/230/347/225/245/345/256/236/351/231/205/351/203/250/347/275/262/345/233/276@3x.png +0 -0
  158. package/src/statics/icons/childIcons//346/210/230/347/225/245/346/246/202/345/277/265/345/233/276@3x.png +0 -0
  159. package/src/statics/icons/childIcons//346/210/230/347/225/245/346/265/201/347/250/213/345/233/276@3x-2.png +0 -0
  160. package/src/statics/icons/childIcons//346/210/230/347/225/245/346/265/201/347/250/213/345/233/276@3x.png +0 -0
  161. package/src/statics/icons/childIcons//346/210/230/347/225/245/347/212/266/346/200/201/345/233/276@3x.png +0 -0
  162. package/src/statics/icons/childIcons//346/210/230/347/225/245/347/272/246/346/235/237@3x.png +0 -0
  163. package/src/statics/icons/childIcons//346/210/230/347/225/245/347/272/246/346/235/237/345/233/276@3x.png +0 -0
  164. package/src/statics/icons/childIcons//346/210/230/347/225/245/347/272/246/346/235/237/345/256/232/344/271/211/345/233/276@3x.png +0 -0
  165. package/src/statics/icons/childIcons//346/210/230/347/225/245/347/273/223/346/236/204/345/233/276@3x.png +0 -0
  166. package/src/statics/icons/childIcons//346/210/230/347/225/245/350/277/236/351/200/232/345/233/276@3x-2.png +0 -0
  167. package/src/statics/icons/childIcons//346/210/230/347/225/245/350/277/236/351/200/232/345/233/276@3x.png +0 -0
  168. package/src/statics/icons/childIcons//346/210/230/347/225/245/350/277/236/351/200/232/347/237/251/351/230/265@3x-2.png +0 -0
  169. package/src/statics/icons/childIcons//346/210/230/347/225/245/350/277/236/351/200/232/347/237/251/351/230/265@3x.png +0 -0
  170. package/src/statics/icons/childIcons//346/210/230/347/225/245/351/230/266/346/256/265@3x.png +0 -0
  171. package/src/statics/icons/childIcons//346/211/247/350/241/214@3x.png +0 -0
  172. package/src/statics/icons/childIcons//346/211/247/350/241/214/350/200/205/345/206/205/351/203/250/350/277/236/351/200/232/345/233/276@3x.png +0 -0
  173. package/src/statics/icons/childIcons//346/211/247/350/241/214/350/200/205/346/246/202/345/277/265/345/233/276@3x.png +0 -0
  174. package/src/statics/icons/childIcons//346/211/247/350/241/214/350/200/205/347/273/223/346/236/204/345/233/276@3x-2.png +0 -0
  175. package/src/statics/icons/childIcons//346/211/247/350/241/214/350/200/205/347/273/223/346/236/204/345/233/276@3x.png +0 -0
  176. package/src/statics/icons/childIcons//346/211/247/350/241/214/350/200/205/350/277/236/351/200/232/345/233/276@3x.png +0 -0
  177. package/src/statics/icons/childIcons//346/211/277/346/213/205/351/243/216/351/231/251@3x.png +0 -0
  178. package/src/statics/icons/childIcons//346/212/200/346/234/257@3x.png +0 -0
  179. package/src/statics/icons/childIcons//346/213/245/346/234/211/346/265/201/347/250/213@3x.png +0 -0
  180. package/src/statics/icons/childIcons//346/217/220/344/276/233/346/235/203/351/231/220@3x.png +0 -0
  181. package/src/statics/icons/childIcons//346/227/266/346/234/272@3x.png +0 -0
  182. package/src/statics/icons/childIcons//346/234/215/345/212/241@3x.png +0 -0
  183. package/src/statics/icons/childIcons//346/234/215/345/212/241/344/270/216/344/270/232/345/212/241/346/264/273/345/212/250/346/230/240/345/260/204/347/237/251/351/230/265@3x.png +0 -0
  184. package/src/statics/icons/childIcons//346/234/215/345/212/241/344/270/216/346/234/215/345/212/241/345/220/210/345/220/214/346/230/240/345/260/204/347/237/251/351/230/265@3x.png +0 -0
  185. package/src/statics/icons/childIcons//346/234/215/345/212/241/344/270/216/350/203/275/345/212/233/346/230/240/345/260/204/347/237/251/351/230/265@3x.png +0 -0
  186. package/src/statics/icons/childIcons//346/234/215/345/212/241/344/277/241/345/217/267@3x.png +0 -0
  187. package/src/statics/icons/childIcons//346/234/215/345/212/241/345/206/205/351/203/250/346/265/201/347/250/213/345/233/276@3x.png +0 -0
  188. package/src/statics/icons/childIcons//346/234/215/345/212/241/345/206/205/351/203/250/350/277/236/351/200/232/345/233/276@3x.png +0 -0
  189. package/src/statics/icons/childIcons//346/234/215/345/212/241/345/212/237/350/203/275@3x.png +0 -0
  190. package/src/statics/icons/childIcons//346/234/215/345/212/241/345/212/237/350/203/275/345/212/250/344/275/234@3x.png +0 -0
  191. package/src/statics/icons/childIcons//346/234/215/345/212/241/345/212/250/344/275/234@3x.png +0 -0
  192. package/src/statics/icons/childIcons//346/234/215/345/212/241/345/217/202/346/225/260@3x.png +0 -0
  193. package/src/statics/icons/childIcons//346/234/215/345/212/241/345/217/202/346/225/260/345/233/276@3x.png +0 -0
  194. package/src/statics/icons/childIcons//346/234/215/345/212/241/345/257/271/350/261/241/346/265/201@3x.png +0 -0
  195. package/src/statics/icons/childIcons//346/234/215/345/212/241/346/216/245/345/217/243@3x.png +0 -0
  196. package/src/statics/icons/childIcons//346/234/215/345/212/241/346/216/247/345/210/266/346/265/201@3x.png +0 -0
  197. package/src/statics/icons/childIcons//346/234/215/345/212/241/346/246/202/345/277/265/345/233/276@3x.png +0 -0
  198. package/src/statics/icons/childIcons//346/234/215/345/212/241/346/246/202/345/277/265/350/241/250@3x.png +0 -0
  199. package/src/statics/icons/childIcons//346/234/215/345/212/241/346/265/201/347/250/213/345/233/276@3x.png +0 -0
  200. package/src/statics/icons/childIcons//346/234/215/345/212/241/347/212/266/346/200/201/345/233/276@3x.png +0 -0
  201. package/src/statics/icons/childIcons//346/234/215/345/212/241/347/253/257/345/217/243@3x.png +0 -0
  202. package/src/statics/icons/childIcons//346/234/215/345/212/241/347/272/246/346/235/237/345/233/276@3x.png +0 -0
  203. package/src/statics/icons/childIcons//346/234/215/345/212/241/347/272/246/346/235/237/345/256/232/344/271/211/345/233/276@3x.png +0 -0
  204. package/src/statics/icons/childIcons//346/234/215/345/212/241/347/273/223/346/236/204@3x.png +0 -0
  205. package/src/statics/icons/childIcons//346/234/215/345/212/241/347/273/223/346/236/204/345/233/276@3x.png +0 -0
  206. package/src/statics/icons/childIcons//346/234/215/345/212/241/350/247/222/350/211/262@3x.png +0 -0
  207. package/src/statics/icons/childIcons//346/234/215/345/212/241/350/267/257/347/272/277/345/233/276@3x.png +0 -0
  208. package/src/statics/icons/childIcons//346/234/215/345/212/241/350/277/236/346/216/245/345/231/250@3x.png +0 -0
  209. package/src/statics/icons/childIcons//346/234/215/345/212/241/350/277/236/351/200/232/345/233/276@3x.png +0 -0
  210. package/src/statics/icons/childIcons//346/234/215/345/212/241/350/277/236/351/200/232/350/241/250@3x-2.png +0 -0
  211. package/src/statics/icons/childIcons//346/234/215/345/212/241/350/277/236/351/200/232/350/241/250@3x.png +0 -0
  212. package/src/statics/icons/childIcons//346/235/203/351/231/220@3x.png +0 -0
  213. package/src/statics/icons/childIcons//346/235/241/344/273/266@3x.png +0 -0
  214. package/src/statics/icons/childIcons//346/240/207/345/207/206/344/270/232/345/212/241/346/264/273/345/212/250@3x.png +0 -0
  215. package/src/statics/icons/childIcons//346/240/207/345/207/206/346/246/202/345/277/265/345/233/276@3x.png +0 -0
  216. package/src/statics/icons/childIcons//346/240/207/345/207/206/346/246/202/345/277/265/350/241/250@3x.png +0 -0
  217. package/src/statics/icons/childIcons//346/240/207/345/207/206/347/273/223/346/236/204/345/233/276@3x.png +0 -0
  218. package/src/statics/icons/childIcons//346/240/207/345/207/206/350/267/257/347/272/277/345/233/276@3x.png +0 -0
  219. package/src/statics/icons/childIcons//346/240/207/345/207/206/350/277/275/346/272/257/345/233/276@3x.png +0 -0
  220. package/src/statics/icons/childIcons//346/246/202/345/277/265/350/247/222/350/211/262@3x.png +0 -0
  221. package/src/statics/icons/childIcons//346/263/233/345/214/226@3x.png +0 -0
  222. package/src/statics/icons/childIcons//347/212/266/345/206/265@3x.png +0 -0
  223. package/src/statics/icons/childIcons//347/216/257/345/242/203@3x.png +0 -0
  224. package/src/statics/icons/childIcons//347/233/270/346/257/224@3x (1).png +0 -0
  225. package/src/statics/icons/childIcons//347/263/273/347/273/237@3x.png +0 -0
  226. package/src/statics/icons/childIcons//347/273/204/346/210/220@3x.png +0 -0
  227. package/src/statics/icons/childIcons//347/273/204/347/273/207@3x.png +0 -0
  228. package/src/statics/icons/childIcons//347/273/204/347/273/207/351/230/266/346/256/265@3x.png +0 -0
  229. package/src/statics/icons/childIcons//347/273/221/345/256/232/350/277/236/346/216/245/345/231/250@3x.png +0 -0
  230. package/src/statics/icons/childIcons//347/274/223/350/247/243@3x.png +0 -0
  231. package/src/statics/icons/childIcons//350/201/214/344/275/215@3x.png +0 -0
  232. package/src/statics/icons/childIcons//350/201/232/345/220/210@3x.png +0 -0
  233. package/src/statics/icons/childIcons//350/203/275/345/212/233@3x.png +0 -0
  234. package/src/statics/icons/childIcons//350/203/275/345/212/233/350/201/214/350/264/243@3x.png +0 -0
  235. package/src/statics/icons/childIcons//350/203/275/345/212/233/351/205/215/347/275/256@3x.png +0 -0
  236. package/src/statics/icons/childIcons//350/203/275/345/244/237/350/203/234/344/273/273@3x.png +0 -0
  237. package/src/statics/icons/childIcons//350/207/252/347/204/266/350/265/204/346/272/220@3x.png +0 -0
  238. package/src/statics/icons/childIcons//350/246/201/346/261/202@3x.png +0 -0
  239. package/src/statics/icons/childIcons//350/256/276/347/275/256/347/261/273/345/236/213.png +0 -0
  240. package/src/statics/icons/childIcons//350/264/237/350/264/243@3x.png +0 -0
  241. package/src/statics/icons/childIcons//350/264/243/344/273/273@3x.png +0 -0
  242. package/src/statics/icons/childIcons//350/265/204/344/272/247/351/243/216/351/231/251/346/230/240/345/260/204/347/237/251/351/230/265@3x.png +0 -0
  243. package/src/statics/icons/childIcons//350/265/204/346/272/220/344/270/216/344/270/232/345/212/241/346/264/273/345/212/250/346/230/240/345/260/204/347/237/251/351/230/265@3x.png +0 -0
  244. package/src/statics/icons/childIcons//350/265/204/346/272/220/344/270/216/350/203/275/345/212/233/346/230/240/345/260/204/347/237/251/351/230/265@3x.png +0 -0
  245. package/src/statics/icons/childIcons//350/265/204/346/272/220/344/277/241/345/217/267@3x.png +0 -0
  246. package/src/statics/icons/childIcons//350/265/204/346/272/220/344/277/241/346/201/257@3x.png +0 -0
  247. package/src/statics/icons/childIcons//350/265/204/346/272/220/344/277/241/346/201/257/345/233/276@3x.png +0 -0
  248. package/src/statics/icons/childIcons//350/265/204/346/272/220/345/206/205/351/203/250/346/265/201/347/250/213/345/233/276@3x.png +0 -0
  249. package/src/statics/icons/childIcons//350/265/204/346/272/220/345/206/205/351/203/250/350/277/236/351/200/232/345/233/276@3x.png +0 -0
  250. package/src/statics/icons/childIcons//350/265/204/346/272/220/345/212/250/344/275/234@3x.png +0 -0
  251. package/src/statics/icons/childIcons//350/265/204/346/272/220/345/217/202/346/225/260@3x.png +0 -0
  252. package/src/statics/icons/childIcons//350/265/204/346/272/220/345/217/202/346/225/260/345/233/276@3x.png +0 -0
  253. package/src/statics/icons/childIcons//350/265/204/346/272/220/345/267/245/344/273/266@3x.png +0 -0
  254. package/src/statics/icons/childIcons//350/265/204/346/272/220/346/216/245/345/217/243@3x.png +0 -0
  255. package/src/statics/icons/childIcons//350/265/204/346/272/220/346/234/215/345/212/241@3x.png +0 -0
  256. package/src/statics/icons/childIcons//350/265/204/346/272/220/346/236/266/346/236/204@3x.png +0 -0
  257. package/src/statics/icons/childIcons//350/265/204/346/272/220/346/246/202/345/277/265/345/233/276@3x.png +0 -0
  258. package/src/statics/icons/childIcons//350/265/204/346/272/220/346/246/202/345/277/265/350/241/250@3x.png +0 -0
  259. package/src/statics/icons/childIcons//350/265/204/346/272/220/346/264/273/345/212/250/346/265/201/347/250/213/345/233/276@3x.png +0 -0
  260. package/src/statics/icons/childIcons//350/265/204/346/272/220/346/264/273/345/212/250/347/273/223/346/236/204/345/233/276@3x.png +0 -0
  261. package/src/statics/icons/childIcons//350/265/204/346/272/220/346/265/201/347/250/213/345/233/276@3x.png +0 -0
  262. package/src/statics/icons/childIcons//350/265/204/346/272/220/347/212/266/346/200/201/345/233/276@3x.png +0 -0
  263. package/src/statics/icons/childIcons//350/265/204/346/272/220/347/253/257/345/217/243@3x.png +0 -0
  264. package/src/statics/icons/childIcons//350/265/204/346/272/220/347/272/246/346/235/237@3x.png +0 -0
  265. package/src/statics/icons/childIcons//350/265/204/346/272/220/347/273/223/346/236/204@3x.png +0 -0
  266. package/src/statics/icons/childIcons//350/265/204/346/272/220/347/273/223/346/236/204/345/233/276@3x.png +0 -0
  267. package/src/statics/icons/childIcons//350/265/204/346/272/220/347/274/223/350/247/243/346/216/252/346/226/275@3x.png +0 -0
  268. package/src/statics/icons/childIcons//350/265/204/346/272/220/350/247/222/350/211/262@3x.png +0 -0
  269. package/src/statics/icons/childIcons//350/265/204/346/272/220/350/277/236/346/216/245/345/231/250@3x.png +0 -0
  270. package/src/statics/icons/childIcons//350/265/204/346/272/220/350/277/236/351/200/232/345/233/276@3x.png +0 -0
  271. package/src/statics/icons/childIcons//350/265/204/346/272/220/350/277/236/351/200/232/350/241/250@3x.png +0 -0
  272. package/src/statics/icons/childIcons//350/275/257/344/273/266@3x.png +0 -0
  273. package/src/statics/icons/childIcons//350/276/223/345/205/245/346/240/223@3x.png +0 -0
  274. package/src/statics/icons/childIcons//350/276/223/345/207/272/346/240/223@3x.png +0 -0
  275. package/src/statics/icons/childIcons//350/276/276/345/210/260@3x.png +0 -0
  276. package/src/statics/icons/childIcons//350/277/236/346/216/245/345/231/250@3x.png +0 -0
  277. package/src/statics/icons/childIcons//350/277/236/347/272/277@3x.png +0 -0
  278. package/src/statics/icons/childIcons//351/207/214/347/250/213/347/242/221/344/276/235/350/265/226@3x.png +0 -0
  279. package/src/statics/icons/childIcons//351/230/266/346/256/265@3x.png +0 -0
  280. package/src/statics/icons/childIcons//351/234/200/346/261/202/346/235/203/351/231/220@3x.png +0 -0
  281. package/src/statics/icons/childIcons//351/241/266/347/272/247/344/270/232/345/212/241/346/246/202/345/277/265/345/233/276@3x.png +0 -0
  282. package/src/statics/icons/childIcons//351/241/271/347/233/256@3x.png +0 -0
  283. package/src/statics/icons/childIcons//351/241/271/347/233/256/344/270/273/351/242/230@3x.png +0 -0
  284. package/src/statics/icons/childIcons//351/241/271/347/233/256/345/206/205/351/203/250/346/265/201/347/250/213/345/233/276@3x.png +0 -0
  285. package/src/statics/icons/childIcons//351/241/271/347/233/256/346/246/202/345/277/265/345/233/276@3x.png +0 -0
  286. package/src/statics/icons/childIcons//351/241/271/347/233/256/346/246/202/345/277/265/350/241/250@3x.png +0 -0
  287. package/src/statics/icons/childIcons//351/241/271/347/233/256/346/264/273/345/212/250@3x.png +0 -0
  288. package/src/statics/icons/childIcons//351/241/271/347/233/256/346/264/273/345/212/250/344/270/216/350/203/275/345/212/233/347/232/204/346/230/240/345/260/204/347/237/251/351/230/265@3x.png +0 -0
  289. package/src/statics/icons/childIcons//351/241/271/347/233/256/346/264/273/345/212/250/345/212/250/344/275/234@3x.png +0 -0
  290. package/src/statics/icons/childIcons//351/241/271/347/233/256/346/265/201/347/250/213/345/233/276@3x.png +0 -0
  291. package/src/statics/icons/childIcons//351/241/271/347/233/256/347/273/223/346/236/204/345/233/276@3x.png +0 -0
  292. package/src/statics/icons/childIcons//351/241/271/347/233/256/350/247/222/350/211/262@3x.png +0 -0
  293. package/src/statics/icons/childIcons//351/241/271/347/233/256/350/267/257/347/272/277/345/233/276@3x.png +0 -0
  294. package/src/statics/icons/childIcons//351/241/271/347/233/256/350/277/236/351/200/232/345/233/276@3x.png +0 -0
  295. package/src/statics/icons/childIcons//351/241/271/347/233/256/351/207/214/347/250/213/347/242/221@3x.png +0 -0
  296. package/src/statics/icons/childIcons//351/241/271/347/233/256/351/207/214/347/250/213/347/242/221/350/247/222/350/211/262@3x.png +0 -0
  297. package/src/statics/icons/childIcons//351/241/272/345/272/217@3x-2.png +0 -0
  298. package/src/statics/icons/childIcons//351/241/272/345/272/217@3x.png +0 -0
  299. package/src/statics/icons/childIcons//351/243/216/351/231/251@3x.png +0 -0
  300. package/src/statics/icons/childIcons//351/243/216/351/231/251/344/270/216/345/256/211/345/205/250/346/216/247/345/210/266/346/230/240/345/260/204/347/237/251/351/230/265@3x.png +0 -0
  301. package/src/statics/icons/childIcons//351/253/230/347/272/247/344/270/232/345/212/241/346/246/202/345/277/265@3x.png +0 -0
  302. package/src/statics/icons/createMenu/config.png +0 -0
  303. package/src/statics/icons/createMenu/contact.png +0 -0
  304. package/src/statics/icons/createMenu/copy.png +0 -0
  305. package/src/statics/icons/createMenu/delete.png +0 -0
  306. package/src/statics/icons/createMenu/diagram.png +0 -0
  307. package/src/statics/icons/createMenu/element.png +0 -0
  308. package/src/statics/icons/createMenu/locateChart.png +0 -0
  309. package/src/statics/icons/createMenu/paste.png +0 -0
  310. package/src/statics/icons/createMenu/rename.png +0 -0
  311. package/src/statics/icons/createMenu/scissors.png +0 -0
  312. package/src/store/eventBus.ts +38 -0
  313. package/src/store/graphStore.ts +782 -0
  314. package/src/store/index.ts +8 -0
  315. package/src/style/index.css +1 -0
  316. package/src/style/tailwind.css +33 -0
  317. package/src/types/index.ts +200 -0
  318. package/src/utils/autoExpandParent.ts +94 -0
  319. package/src/utils/colorUtils.ts +137 -0
  320. package/src/utils/compartment.ts +534 -0
  321. package/src/utils/containers.ts +910 -0
  322. package/src/utils/diagram.ts +403 -0
  323. package/src/utils/dom.ts +21 -0
  324. package/src/utils/drag.ts +224 -0
  325. package/src/utils/edgeUtils.ts +787 -0
  326. package/src/utils/geom.ts +177 -0
  327. package/src/utils/graphDragService.ts +329 -0
  328. package/src/utils/highlightUtils.ts +162 -0
  329. package/src/utils/hittest.ts +135 -0
  330. package/src/utils/iconLoader.ts +105 -0
  331. package/src/utils/index.ts +20 -0
  332. package/src/utils/packgeMap.ts +1 -0
  333. package/src/utils/pinUtils.ts +484 -0
  334. package/src/utils/policy.ts +212 -0
  335. package/src/utils/zorder.ts +39 -0
  336. package/src/view/graph.vue +419 -0
  337. package/src/vite-env.d.ts +33 -0
@@ -0,0 +1,2033 @@
1
+ <template>
2
+ <!-- 交互层:放在图元之上,统一接管交互与鼠标样式 -->
3
+ <div class="interaction-layer" ref="layerRef" @dragover="onCanvasDragOver" @drop="onCanvasDrop"
4
+ :style="{ cursor: cursorStyle }" @mousedown="onLayerMouseDown" @mouseup="onLayerMouseUp" @click="onLayerClick"
5
+ @contextmenu.prevent="handleContextMenu">
6
+ <!-- 只在"选中对象是画布(diagram)"时显示四个角手柄 -->
7
+ <div v-for="s in graphStore.marqueeShapes" :key="s.id" class="selection-box" :style="getSelectionBoxStyle(s)">
8
+ <!-- 只有当shapeType不是edge且不是conceptualRole时才渲染四个角手柄 -->
9
+ <div class="resize-handles" v-show="!isBusy && s.shapeType !== 'edge'">
10
+ <div v-for="h in resizeHandles" :key="h.position" class="resize-handle"
11
+ :class="[`resize-${h.position}`, { 'is-disabled': isMultiSelected }]" :style="getHandleStyle(h, s)"
12
+ @mousedown.stop.prevent="startResize($event, h.position, s)" />
13
+ </div>
14
+ <div class="action-buttons"
15
+ v-show="!isMultiSelected && s.scenarioMenus && s.scenarioMenus.length > 0 && s.shapeType != ShapeConfig.SHAPE_TYPE"
16
+ :style="getActionButtonsStyle(s, s.id)" :ref="(el) => setActionButtonsRef(el, s.id)">
17
+ <div v-if="s.modelTypePropertyId" class="border-btn">
18
+ <button class="action-btn edit-btn"
19
+ @mousedown.stop.prevent="clickModelTypePropertyIdButton(s.modelTypePropertyId, s)" title="设置类型">
20
+ <img src="../statics/icons/childIcons/设置类型.png" alt="设置类型">
21
+ </button>
22
+ </div>
23
+ <button v-for="value in s.scenarioMenus" class="action-btn edit-btn"
24
+ @mousedown.stop.prevent="clickActionButton($event, value.code, s)" @click.stop.prevent :title="value.name">
25
+ <img :src="getIcon('childIcons', value.icon || '')" />
26
+ </button>
27
+ </div>
28
+ </div>
29
+ <!-- 命中容器的高亮矩形(虚线框) -->
30
+ <div v-if="hoverRect" :class="[
31
+ 'hover-container-outline',
32
+ {
33
+ 'is-invalid': graphStore.hoverNestable === false,
34
+ 'is-valid': graphStore.hoverNestable === true,
35
+ },
36
+ ]" :style="{
37
+ left: hoverRect.x - 5 + 'px',
38
+ top: hoverRect.y - 5 + 'px',
39
+ width: hoverRect.width + 10 + 'px',
40
+ height: hoverRect.height + 10 + 'px',
41
+ }" />
42
+ <!-- 框选预览矩形 -->
43
+ <div v-if="marqueeRect" class="marquee-rect" :style="getMarqueeStyle(marqueeRect)" />
44
+ <!-- 拖动和缩放的预览框 -->
45
+ <component v-for="g in allGhosts" :key="g.id" class="ghost-shape" :is="getShapeComponent(g)" :shape="g"
46
+ :style="getGhostShapeStyle(g)" />
47
+ <!-- 名称虚线框(选中时显示) -->
48
+ <div v-if="
49
+ graphStore.selectedShape &&
50
+ graphStore.selectedShape.nameBounds &&
51
+ !isEditingName && graphStore.selectedShape.shapeKey !== 'ConceptRole'
52
+ && !graphStore.pendingNestedIds.includes(graphStore.selectedShape.id)
53
+ " class="name-text-box-container" :style="nameTextBoxContainerStyle(graphStore.selectedShape.id)">
54
+ <div class="name-text-box" :style="nameTextBoxStyle(graphStore.selectedShape)" title="点击编辑名称"></div>
55
+ </div>
56
+
57
+ <!-- 名称编辑输入框 -->
58
+ <div v-if="isEditingName && graphStore.selectedShape && graphStore.selectedShape.shapeKey !== 'ConceptRole'"
59
+ class="name-editor-container" :style="nameEditorContainerStyle(graphStore.selectedShape)">
60
+ <input ref="nameInput" v-model="editingName" class="name-input" :style="nameInputStyle" @blur="finishEditName"
61
+ @keyup.enter="finishEditName" @keyup.escape="cancelEditName" />
62
+ </div>
63
+
64
+ <!-- 使用右键菜单组件 -->
65
+ <ContextMenu v-if="selectedShape && !isMultiSelected" :visible="showContextMenu" :selected-shape="selectedShape"
66
+ :position="contextMenuPosition" @update:visible="showContextMenu = $event" @delete="handleMenuDelete"
67
+ @show-property-panel="onLayerDblClick(true)" />
68
+
69
+ <!-- 连接层逻辑 - 当 connectShapeData 存在时显示 -->
70
+ <div v-if="connectShapeData && diagramBounds" class="connect-layer" :style="layerStyle">
71
+ <!-- 连线起点的黑点 - 只在连接时显示 -->
72
+ <div v-if="isConnecting" class="connect-dot-direct" :style="dotStyle"></div>
73
+
74
+ <!-- 连线 -->
75
+ <ConnectionLine v-if="showLine" :show-line="showLine" :points="linePoints" :shape-key="connectShapeData?.shapeKey"
76
+ :style="svgStyle" />
77
+ </div>
78
+ </div>
79
+ </template>
80
+
81
+ <script setup lang="ts">
82
+ import {
83
+ ref,
84
+ nextTick,
85
+ onUnmounted,
86
+ computed,
87
+ onMounted,
88
+ type CSSProperties,
89
+ watch,
90
+ onBeforeUnmount,
91
+ type ComponentPublicInstance,
92
+ } from "vue";
93
+ import type { Shape } from "../types";
94
+ import { useGraphStore } from "../store/graphStore";
95
+
96
+ // 工具:几何/命中/样式/拖拽
97
+ import {
98
+ toLocalPoint,
99
+ getBounds,
100
+ getDiagramRect,
101
+ ghostResizeStep,
102
+ rectFromPoints,
103
+ rectContainsRect,
104
+ clampPointToRect,
105
+ } from "../utils/geom";
106
+ import { pickTarget } from "../utils/hittest";
107
+ import {
108
+ selectionBoxStyle,
109
+ handleStyle,
110
+ adjustCanvasToFitAllShapes,
111
+ actionButtonsStyle,
112
+ nameTextBoxContainerStyle,
113
+ nameTextBoxStyle,
114
+ nameEditorContainerStyle
115
+ } from "../utils/diagram";
116
+ import { withDrag } from "../utils/dom";
117
+ import { checkNestViaFront, getPolicy } from "../utils/policy";
118
+ import { getShapeComponent, getShapeStyle } from "../render/shape-renderer";
119
+ import _ from "lodash";
120
+ import { eventBus } from "../store";
121
+ import { ShapeConfig } from "../utils/diagram";
122
+ import ContextMenu from "./ContextMenu.vue";
123
+ import { finalizeAfterTransform } from "../utils/drag";
124
+ import { normalizeZOrder } from "../utils/zorder";
125
+ import { clampParentRectToChildrenGap } from "../utils/containers";
126
+ import { storeToRefs } from "pinia";
127
+ import { EdgeUtils } from "../utils/edgeUtils";
128
+ import ConnectionLine from "./LineStyle/ConnectionLine.vue";
129
+ import { addChildShapeToCompartment, clampCompartmentResize, isCompartment } from "../utils/compartment";
130
+ import { collectDescendantIds } from "../utils/containers";
131
+ import { HighlightUtils } from "../utils/highlightUtils";
132
+ // 静态导入图片资源
133
+ import { getIcon } from "../utils/iconLoader";
134
+ import { getUuid } from "../utils/index";
135
+ import { ElMessage } from "element-plus";
136
+ import { snapPinToParentEdge, snapPinPointerOnMove } from '../utils/pinUtils';
137
+ const emit = defineEmits([
138
+ "propertyPanel",
139
+ "editName",
140
+ "update-shape",
141
+ "connectEnd",
142
+ "actionButtonClick",
143
+ "diagramDoubleClick",
144
+ "modelTypePropertyIdButtonClick",
145
+ "edge-click",
146
+ "property-panel",
147
+ "actionButtonAdd"
148
+ ]);
149
+
150
+ // Props
151
+ interface Props {
152
+ connectShapeData?: Shape;
153
+ diagramBounds?: any;
154
+ resShape: Shape;
155
+ lines?: String[];
156
+ packages?: String[];
157
+ diagram?: String[];
158
+ taggedValueLabels?: String[];
159
+ actionButtonShapeDataId?: string;
160
+ edgeCheck?: boolean;
161
+ }
162
+ const props = defineProps<Props>();
163
+
164
+ //情景菜单dom实例
165
+ const actionButtonsRefs = ref<Record<string, Element | null>>({});
166
+
167
+ // 取得 graphStore 实例
168
+ const graphStore = useGraphStore();
169
+
170
+ const { selectedShape } = storeToRefs(graphStore);
171
+
172
+ // 是否正在“外部创建拖拽”(
173
+ const isExternalCreateDragging = ref(false)
174
+ // 光标样式(仅在按下时切换)
175
+ const cursorStyle = ref<"default" | "pointer">("default");
176
+ // 缩放时使用的预览框
177
+ const resizeGhostShadow = computed<Shape[]>(() => {
178
+ const out: Shape[] = [];
179
+ for (const id in groupGhost.value) {
180
+ const s = graphStore.shapes.find((x) => x.id === id);
181
+ if (!s) continue;
182
+ out.push({
183
+ ...s,
184
+ bounds: { ...groupGhost.value[id] },
185
+ meta: { ...(s as any).meta, isGhost: true } as any,
186
+ } as Shape);
187
+ }
188
+ return out;
189
+ });
190
+ //判断嵌套预览框
191
+ const hoverRect = computed(() => {
192
+ const id = graphStore.hoverContainerId;
193
+ if (!id) return null;
194
+ const s = graphStore.shapes.find((x) => x.id === id);
195
+ if (!s) return null;
196
+ const b = getBounds(s);
197
+ return { x: b.x, y: b.y, width: b.width, height: b.height };
198
+ });
199
+ // 把拖动缩放预览框合并,
200
+ const allGhosts = computed<Shape[]>(() => {
201
+ const byId = new Map<string, Shape>();
202
+ // 收集所有Ghost形状
203
+ if (!isExternalCreateDragging.value) {
204
+ graphStore.ghostShadow.forEach(g => byId.set(g.id, g))
205
+ }
206
+ // 缩放时的预览
207
+ resizeGhostShadow.value.forEach((g) => byId.set(g.id, g));
208
+ // 添加高亮图元
209
+ if (highlightedShape.value) {
210
+ byId.set(highlightedShape.value.id, {
211
+ ...highlightedShape.value,
212
+ meta: {
213
+ ...(highlightedShape.value as any).meta,
214
+ isHighlighted: true,
215
+ isGhost: true,
216
+ },
217
+ });
218
+ }
219
+
220
+ return Array.from(byId.values());
221
+ });
222
+
223
+ const getGhostShapeStyle = (shape: Shape): CSSProperties => {
224
+ const baseStyle = getShapeStyle(shape);
225
+ return baseStyle;
226
+ };
227
+ // 根层引用:用于本地坐标换算
228
+ const layerRef = ref<HTMLDivElement | null>(null);
229
+
230
+ // 名称编辑状态
231
+ const isEditingName = ref(false);
232
+ const editingName = ref("");
233
+ const nameInput = ref<HTMLInputElement>();
234
+
235
+ // Pin 名称编辑输入框样式(放大,随字体自适应),非 Pin 返回空由 CSS 控制
236
+ const nameInputStyle = computed(() => {
237
+ const s = graphStore.selectedShape as any
238
+ if (!s) return {}
239
+ const isPin = String(s?.shapeType || '').toLowerCase() === 'pin'
240
+ const ns = s?.nameStyle || {}
241
+ const nb = s?.nameBounds || {}
242
+ const fs = Number(ns.fontSize || nb.height || 12)
243
+ if (!isPin) return {}
244
+ return {
245
+ width: '100%',
246
+ height: '100%',
247
+ fontSize: `${fs}px`,
248
+ lineHeight: `${Math.ceil(fs + 8)}px`,
249
+ padding: '4px 6px',
250
+ boxSizing: 'border-box' as const,
251
+ }
252
+ })
253
+
254
+ // 缩放状态
255
+ const isResizing = ref(false);
256
+ // 点击情景菜单状态
257
+ const actionButtonMode = ref(false);
258
+ const resizeDirection = ref<"nw" | "ne" | "sw" | "se" | "">("");
259
+ const startPos = ref({ x: 0, y: 0 });
260
+ const startBounds = ref({ x: 0, y: 0, width: 0, height: 0 });
261
+ let offDrag: null | (() => void) = null;
262
+ // 框选状态
263
+ const marqueeRect = ref<Rect | null>(null);
264
+ const marqueeAnchor = ref<{ x: number; y: number } | null>(null);
265
+ // 是否多选
266
+ const isMultiSelected = computed(() => graphStore.selectedIds.length > 1);
267
+
268
+ // 连接层相关状态
269
+ const mousePosition = ref({ x: 0, y: 0 });
270
+ const showLine = ref(false);
271
+ const currentConnectPoint = ref({ x: 0, y: 0 });
272
+ const isConnecting = ref(false); // 是否正在连接状态
273
+ const targetConnectPoint = ref({ x: 0, y: 0 }); // 目标连接点
274
+ const targetShape = ref<Shape | null>(null); // 目标图形
275
+
276
+ // 高亮工具实例
277
+ const highlightUtils = new HighlightUtils(graphStore);
278
+
279
+ // 高亮相关状态 - 使用计算属性从工具类获取
280
+ const highlightedShape = computed(() => highlightUtils.getHighlightedShape());
281
+ const sourceShape = ref<Shape | null>(null)
282
+ const recordClickPoint = ref({ x: 0, y: 0 })
283
+ // 高亮相关状态
284
+ const highlightTimeout = ref<ReturnType<typeof setTimeout> | null>(null); // 高亮定时器
285
+
286
+ // 组拖拽的预览
287
+ const groupBase = ref<Record<string, Rect>>({}); // 按下时各 shape 的快照
288
+ const groupGhost = ref<Record<string, Rect>>({}); // 拖动中的预览矩形
289
+ const resizingTarget = ref<Shape | null>(null);
290
+ // 框选矩形的样式
291
+ const getMarqueeStyle = (r: Rect): CSSProperties => ({
292
+ position: "absolute",
293
+ left: `${r.x}px`,
294
+ top: `${r.y}px`,
295
+ width: `${r.width}px`,
296
+ height: `${r.height}px`,
297
+ // background: 'rgba(52,152,219,0.12)',
298
+ border: "2px dashed #a6ddff",
299
+ pointerEvents: "none",
300
+ zIndex: 20,
301
+ });
302
+ // 正在交互:缩放中 或 元素拖动中
303
+ const isBusy = computed(() =>
304
+ isResizing.value || (graphStore.isDragging && graphStore.ghostShadow.length > 0)
305
+ )
306
+ // 四个角手柄(常量)
307
+ const resizeHandles: { position: "nw" | "ne" | "sw" | "se" }[] = [
308
+ { position: "nw" },
309
+ { position: "ne" },
310
+ { position: "sw" },
311
+ { position: "se" },
312
+ ];
313
+
314
+ // 监听所有可能影响菜单显示的操作状态
315
+ const shouldCloseMenu = computed(() => {
316
+ return graphStore.isDragging || isResizing.value || isEditingName.value;
317
+ });
318
+
319
+ // 预览框(ghost)的 bounds,仅在缩放时存在
320
+ type Rect = { x: number; y: number; width: number; height: number };
321
+
322
+ //动态绑定ref
323
+ const setActionButtonsRef = (el: Element | ComponentPublicInstance | null, shapeId: string) => {
324
+ // 只存储 DOM 元素(过滤掉组件实例)
325
+ // 当el为null时(元素销毁),清空对应引用
326
+ if (el === null) {
327
+ actionButtonsRefs.value[shapeId] = null;
328
+ return;
329
+ }
330
+ // 只存储DOM元素,过滤组件实例
331
+ if (el instanceof Element) {
332
+ actionButtonsRefs.value[shapeId] = el;
333
+ } else {
334
+ actionButtonsRefs.value[shapeId] = null;
335
+ }
336
+ };
337
+
338
+
339
+ // 计算操作按钮的位置样式
340
+ const getActionButtonsStyle = (shape: Shape, shapeId: string): CSSProperties => {
341
+ // 前置校验:shapeId不存在/元素已销毁,直接返回空样式
342
+ if (!shapeId || !actionButtonsRefs.value[shapeId] || !layerRef.value) {
343
+ return {
344
+ position: "absolute",
345
+ left: `${(shape.bounds?.width ?? 100) + 15}px`,
346
+ top: '0px',
347
+ zIndex: 1000,
348
+ };
349
+ }
350
+
351
+ const shapeBounds = shape.bounds ?? {};
352
+ const shapeWidth = shapeBounds.width ?? 100;
353
+ const shapeHeight = shapeBounds.height ?? 40;
354
+ if (!shape.scenarioMenus) {
355
+ return {}
356
+ }
357
+ // 按钮宽度:28px,间距4px,共5个按钮 (根据接口动态替换)
358
+ const hasTypeButton = !!shape.modelTypePropertyId;
359
+ const totalButtons = (shape.scenarioMenus ? shape.scenarioMenus!.length : 0) + (hasTypeButton ? 1 : 0);
360
+ const buttonsHeight = 28 * totalButtons + 4 * (totalButtons - (totalButtons > 0 ? 1 : 0));
361
+ const shapeTop = shape.bounds?.y ?? 0;
362
+ if (shapeTop < 10) { // 固定阈值,不受按钮DOM样式修改影响
363
+ return {
364
+ position: "absolute",
365
+ left: `${shapeWidth + 15}px`, // 紧贴形状右侧,间距5px
366
+ top: `10px`, // 顶部对齐
367
+ zIndex: 1000,
368
+ };
369
+ }
370
+ return {
371
+ position: "absolute",
372
+ left: `${shapeWidth + 15}px`, // 紧贴形状右侧,间距5px
373
+ top: `${(shapeHeight - buttonsHeight) / 2}px`, // 垂直居中
374
+ zIndex: 1000,
375
+ };
376
+ };
377
+
378
+ const clickActionButton = (event: MouseEvent, value: string, shape: Shape) => {
379
+ // 阻止事件冒泡,避免触发 onLayerClick
380
+ event.stopPropagation();
381
+ event.preventDefault();
382
+
383
+ // 清除选中状态,避免第一次点击时取消选中导致需要点击两次
384
+ graphStore.clearSelection();
385
+
386
+ actionButtonMode.value = true
387
+ emit('actionButtonClick', value, shape);
388
+ }
389
+
390
+ const clickModelTypePropertyIdButton = (value: string, shape: Shape) => {
391
+ emit('modelTypePropertyIdButtonClick', value, shape);
392
+ }
393
+
394
+ // 计算样式:调用 utils(保持单一职责)
395
+ const getSelectionBoxStyle = (shape: Shape) => selectionBoxStyle(shape);
396
+ const getHandleStyle = (h: any, shape: Shape) => handleStyle(h.position, shape);
397
+
398
+
399
+
400
+ // 名称编辑
401
+ const startEditName = async () => {
402
+ if (!graphStore.selectedShape) return;
403
+ isEditingName.value = true;
404
+ editingName.value = graphStore.selectedShape.name;
405
+ await nextTick();
406
+ nameInput.value?.focus();
407
+ nameInput.value?.select();
408
+ };
409
+
410
+ const finishEditName = () => {
411
+ if (!graphStore.selectedShape || !isEditingName.value) return;
412
+ const next = editingName.value.trim();
413
+ if (next && next !== graphStore.selectedShape.name) {
414
+ emit("editName", graphStore.selectedShape, next);
415
+ graphStore.updateShape(graphStore.selectedShape.id, { name: next });
416
+ }
417
+ isEditingName.value = false;
418
+ };
419
+
420
+ const cancelEditName = () => {
421
+ isEditingName.value = false;
422
+ editingName.value = "";
423
+ };
424
+
425
+ // 属性面板
426
+ const onLayerDblClick = (payload: boolean) => {
427
+ emit("propertyPanel", payload);
428
+ };
429
+
430
+ // 处理点击事件,检查是否点击了text元素
431
+ const onLayerClick = (evt: MouseEvent) => {
432
+ const target = evt.target as HTMLElement;
433
+
434
+ // 检查是否点击了右键菜单
435
+ const contextMenu = document.querySelector(".context-menu");
436
+ if (contextMenu && contextMenu.contains(target)) {
437
+ // 点击了菜单内部,不处理
438
+ return;
439
+ }
440
+
441
+ // 检查目标元素本身或其父元素是否是text元素
442
+ let currentElement: HTMLElement | null = target;
443
+ while (currentElement && currentElement !== layerRef.value) {
444
+ if (currentElement.tagName === "text") {
445
+ // 如果是text元素,不处理点击事件,让事件穿透到Capability组件
446
+ // 不调用stopPropagation,让事件继续冒泡
447
+ return;
448
+ }
449
+ currentElement = currentElement.parentElement;
450
+ }
451
+
452
+ // 如果点击了非菜单区域,关闭菜单
453
+ if (showContextMenu.value) {
454
+ showContextMenu.value = false;
455
+ }
456
+
457
+ // 检查是否点击了name-text-box
458
+ if (target.classList.contains("name-text-box")) {
459
+ startEditName();
460
+ return;
461
+ }
462
+
463
+ // 处理连接状态下的点击事件
464
+ if (isConnecting.value && props.connectShapeData && props.diagramBounds) {
465
+ const canvas = document.querySelector(".diagram-content");
466
+ if (!canvas || !props.diagramBounds) {
467
+ handleConnectLayerClick(evt);
468
+ return;
469
+ }
470
+ recordClickPoint.value = { x: evt.clientX, y: evt.clientY };
471
+ // 获取 sourceShape(先获取,确保存在)
472
+ const foundSourceShape = graphStore.shapes.find(
473
+ (shape) => shape.id === props.connectShapeData?.sourceId
474
+ );
475
+ if (!foundSourceShape) {
476
+ // 如果找不到 sourceShape,不处理
477
+ handleConnectLayerClick(evt);
478
+ return;
479
+ }
480
+ sourceShape.value = foundSourceShape;
481
+ // 确保连接点已初始化
482
+ initializeConnectPoint();
483
+
484
+ const canvasRect = canvas.getBoundingClientRect();
485
+ const clickX = evt.clientX - canvasRect.left - (props.diagramBounds?.x || 0);
486
+ const clickY = evt.clientY - canvasRect.top - (props.diagramBounds?.y || 0);
487
+
488
+ // 判断点击位置是否有图形
489
+ const hasShapeAtPoint = EdgeUtils.isEndPointInShape(graphStore.shapes, { x: clickX, y: clickY });
490
+
491
+ // 修改:无论是否在actionButtonMode模式下,只要在连接状态且点击空白处,都创建新图元
492
+ if (!hasShapeAtPoint) {
493
+ // 使用 cloneDeep 克隆 sourceShape
494
+ console.log(props.connectShapeData, 'props.connectShapeData');
495
+ console.log(props.connectShapeData.scenarioMenus, 'scenarioMenus');
496
+ const newShape = _.cloneDeep(foundSourceShape);
497
+
498
+ // 修改 id(使用 getUuid)
499
+ newShape.id = getUuid();
500
+
501
+ // 特殊情况:如果 targetModels 有且只有一个,且找到对应的 menu,使用 targetModel 作为 shapeKey
502
+ // 在 scenarioMenus 中查找 code 为当前 connectShapeData.shapeKey 的 menu
503
+ const currentMenu = props.connectShapeData?.scenarioMenus?.find(
504
+ (menu) => menu.code === props.connectShapeData?.shapeKey
505
+ );
506
+ // 获取 targetModels,优先使用 currentMenu 中的值
507
+ const targetModels = currentMenu?.targetCreateModel?.split(',')
508
+ || props.connectShapeData?.targetCreateModel?.split(',');
509
+
510
+ // 如果只有一个 targetModel,使用它作为 shapeKey
511
+ if (targetModels?.length === 1) {
512
+ newShape.shapeKey = targetModels[0];
513
+ }
514
+
515
+ // 修改 bounds 的 x, y, width, height
516
+ const defaultWidth = foundSourceShape.bounds?.width || 100;
517
+ const defaultHeight = foundSourceShape.bounds?.height || 50;
518
+
519
+ // 鼠标位置是新 shape 的左边中点,所以 x = clickX, y = clickY - height/2
520
+ const newShapeX = clickX;
521
+ const newShapeY = clickY - defaultHeight / 2;
522
+ const diagramId = newShape.diagramId;
523
+
524
+ newShape.bounds = {
525
+ ...newShape.bounds,
526
+ x: newShapeX,
527
+ y: newShapeY,
528
+ width: defaultWidth,
529
+ height: defaultHeight,
530
+ modelId: '',
531
+ };
532
+
533
+ // 添加新 shape 到画布,只传递 shapeKey 和 x, y 坐标
534
+ emit('actionButtonAdd', { shapeKey: newShape.shapeKey, x: newShapeX, y: newShapeY, diagramId: diagramId });
535
+ return;
536
+ } else {
537
+ handleConnectLayerClick(evt);
538
+ return;
539
+ }
540
+ }
541
+ };
542
+ // 开始拖拽改变图元大小
543
+ const startResize = (
544
+ e: MouseEvent,
545
+ dir: "nw" | "ne" | "sw" | "se",
546
+ target: Shape
547
+ ) => {
548
+ // 为conceptualRole禁用拖拽放大功能
549
+ if (target.shapeKey === 'ConceptRole') {
550
+ e.preventDefault();
551
+ e.stopPropagation();
552
+ return;
553
+ }
554
+
555
+ if (graphStore.selectedIds.length > 1) {
556
+ // 阻止事件冒泡到外层,避免触发拖拽或框选
557
+ e.preventDefault();
558
+ e.stopPropagation();
559
+ return;
560
+ }
561
+ // const s = graphStore.selectedShape;
562
+ e.preventDefault();
563
+ e.stopPropagation();
564
+
565
+ isResizing.value = true;
566
+ resizingTarget.value = target;
567
+ resizeDirection.value = dir;
568
+
569
+ // 发送缩放开始事件
570
+ eventBus.emit('resize-start', { target });
571
+ // 记录 anchor(按下时的指针位置)
572
+ const anchor = toLocalPoint(e, layerRef.value);
573
+ startPos.value = { x: anchor.x, y: anchor.y };
574
+ // 记录基准 bounds(按下那一刻)
575
+ const b = target.bounds ?? {};
576
+ startBounds.value = {
577
+ x: b.x ?? 0,
578
+ y: b.y ?? 0,
579
+ width: b.width ?? 100,
580
+ height: b.height ?? 50,
581
+ };
582
+ // 初始 ghost = 起始尺寸
583
+ groupBase.value = { [target.id]: { ...startBounds.value } };
584
+ groupGhost.value = { [target.id]: { ...startBounds.value } };
585
+ offDrag = withDrag(handleResize, stopResize);
586
+ };
587
+
588
+ // 最小尺寸
589
+ const minW = 50,
590
+ minH = 30;
591
+
592
+ // 计算文本所需最小宽度 - 应用于所有图形类型
593
+ const calculateTextMinWidth = (shape: Shape): number => {
594
+ // 对于所有图形类型,使用与Block.vue中相同的宽度计算逻辑
595
+ // 获取字体大小(与Block.vue中逻辑一致)
596
+ const nameStyle = shape.nameStyle || {};
597
+ const nameBounds = shape.nameBounds || {};
598
+ const fontSize = nameStyle.fontSize || nameBounds.height || 12;
599
+ // 假设每个字符的平均宽度是字体大小的0.6倍
600
+ const charWidth = fontSize * 1;
601
+
602
+ // 计算名称文本的宽度
603
+ const nameTextWidth = (shape.name?.length || 0) * charWidth;
604
+ // 计算关键词文本的宽度
605
+ const keywordsTextWidth = (shape.keywords?.length || 0) * charWidth;
606
+
607
+ // 返回较大的宽度,并添加一些边距(左右各10px)
608
+ const maxTextWidth = Math.max(nameTextWidth, keywordsTextWidth);
609
+ const estimatedTextWidth = maxTextWidth + 60; // 左右各10px边距
610
+
611
+ // 确保最小宽度不小于minW
612
+ return Math.max(minW, estimatedTextWidth);
613
+ };
614
+
615
+ /**
616
+ * 缩放过程中:只更新 ghostBounds(预览框),不直接改实体。
617
+ * 松开鼠标时在方法里 里一次性把 ghost 落盘到元素上。
618
+ */
619
+ const handleResize = (e: MouseEvent) => {
620
+ // 没在缩放 or 没有有效的缩放目标 => 不处理
621
+ if (!isResizing.value || !resizingTarget.value) return;
622
+ // 当前指针在交互层的本地坐标
623
+ const curr = toLocalPoint(e, layerRef.value);
624
+ // 按下那一刻的元素矩形(作为缩放基准)
625
+ const base = startBounds.value;
626
+ // 当前激活的手柄方向
627
+ const handle = resizeDirection.value as "nw" | "ne" | "sw" | "se";
628
+ // 当前被缩放的 shape
629
+ const shape = resizingTarget.value;
630
+ // 容器(画布)的约束:只限制left和top不小于0,不限制right和bottom以允许无限放大
631
+ const container = {
632
+ left: 0,
633
+ top: 0,
634
+ right: Infinity,
635
+ bottom: Infinity,
636
+ };
637
+ // 通过策略读取本图元需要约束的边(left/top/right/bottom)
638
+ const edges = getPolicy(shape).constrainToDiagram;
639
+
640
+ // 计算基于文本内容的最小宽度
641
+ const dynamicMinW = calculateTextMinWidth(shape);
642
+
643
+ // 关键修复:在缩放过程中,我们需要区分当前是在放大还是缩小
644
+ // 对于缩小操作,使用基于文本内容的最小宽度约束
645
+ // 对于放大操作,暂时使用更小的最小宽度约束,允许用户自由放大
646
+ const dx = curr.x - startPos.value.x;
647
+ const isEnlarging = (handle === 'ne' || handle === 'se') ? dx > 0 : dx < 0;
648
+
649
+ // 如果是放大操作,使用一个很小的值作为临时最小宽度
650
+ // 这样可以允许用户从任何宽度开始自由放大
651
+ let effectiveMinW = isEnlarging ? minW / 2 : Math.max(minW, dynamicMinW);
652
+
653
+ // 根据shapeKey确定最小高度
654
+ let effectiveMinH = 70; // 默认block组件的最小高度
655
+ if (shape.shapeKey) {
656
+ if (props.packages && props.packages.includes(shape.shapeKey)) {
657
+ effectiveMinH = 85;
658
+ } else if (props.diagram && props.diagram.includes(shape.shapeKey)) {
659
+ effectiveMinH = 80;
660
+ } else if (props.taggedValueLabels && props.taggedValueLabels.includes(shape.shapeKey)) {
661
+ effectiveMinH = 125;
662
+ } else if (graphStore.pinsTypes.includes(shape.shapeKey)) {
663
+ effectiveMinH = 22;
664
+ effectiveMinW = 22
665
+ } else if (graphStore.portsTypes.includes(shape.shapeKey)) {
666
+ effectiveMinH = 22;
667
+ effectiveMinW = 22
668
+ }
669
+ }
670
+
671
+ // 判断是否为diagram组件
672
+ const isDiagramComponent = shape.shapeKey && props.diagram && props.diagram.includes(shape.shapeKey);
673
+
674
+ // 用通用几何函数计算“下一帧的ghost矩形”
675
+ // - anchor 用按下时的指针坐标 startPos
676
+ // - 使用最小可能的约束以允许自由缩放
677
+ let next = ghostResizeStep(
678
+ { x: base.x, y: base.y, width: base.width, height: base.height },
679
+ handle,
680
+ { x: startPos.value.x, y: startPos.value.y },
681
+ curr,
682
+ container,
683
+ // 只限制left和top,不限制right和bottom,允许自由放大
684
+ { left: edges.left, top: edges.top, right: false, bottom: false },
685
+ effectiveMinW,
686
+ effectiveMinH
687
+ );
688
+
689
+ // 对于diagram组件,保持高度不变
690
+ if (isDiagramComponent && shape.shapeType === 'shape') {
691
+ next = { ...next, height: base.height };
692
+ }
693
+
694
+ // 移除额外的宽度调整,因为ghostResizeStep已经处理了最小宽度约束
695
+ // 这样可以避免重复调整导致的预览框闪烁
696
+ // 兼容旧逻辑:如果是「画布」图元,缩放时不移动其 (x,y)
697
+ const isDiagram = (shape as any).shapeType?.toLowerCase?.() === "diagram";
698
+ if (isDiagram) {
699
+ next = { ...next, x: base.x, y: base.y };
700
+ next.x = Math.max(0, next.x);
701
+ next.y = Math.max(0, next.y);
702
+ }
703
+ // 判断是否是隔间组件
704
+ if (isCompartment(shape)) {
705
+ next = clampCompartmentResize(
706
+ graphStore.shapes, // 所有图元,用于找子
707
+ shape, // 正在缩放的父
708
+ next, // ghost 下一帧
709
+ resizeDirection.value as "nw" | "ne" | "sw" | "se"
710
+ )
711
+ }
712
+
713
+ next = clampParentRectToChildrenGap(
714
+ graphStore.shapes,
715
+ shape, // 正在被缩放的父
716
+ next,
717
+ resizeDirection.value as any,
718
+ 0, // gap
719
+ effectiveMinW, // 使用effectiveMinW而不是minW
720
+ minH,
721
+ groupGhost.value // 若有子也在 ghost,用它保证更准确
722
+ );
723
+
724
+ // 写到统一的 groupGhost(与拖动预览共用一条通路)
725
+ groupGhost.value = {
726
+ ...groupGhost.value,
727
+ [shape.id]: next,
728
+ };
729
+ };
730
+ // 停止缩放:复位状态并清理监听
731
+ const stopResize = () => {
732
+ if (!isResizing.value || !resizingTarget.value) return;
733
+ const id = resizingTarget.value.id;
734
+ const shape = resizingTarget.value;
735
+ const final = groupGhost.value[id] ?? startBounds.value;
736
+
737
+ // 计算基于文本内容的最小宽度
738
+ let dynamicMinW = calculateTextMinWidth(shape);
739
+
740
+ // 根据shapeKey确定最小高度
741
+ // 如果shapeKey属于packages数组,最小高度为90
742
+ // 如果shapeKey属于diagram数组,最小高度为50
743
+ // 其他情况(block组件)最小高度为70
744
+ let minHeight = 70; // 默认block组件的最小高度
745
+ if (shape.shapeKey) {
746
+ if (props.packages && props.packages.includes(shape.shapeKey)) {
747
+ minHeight = 90;
748
+ } else if (props.diagram && props.diagram.includes(shape.shapeKey)) {
749
+ minHeight = 50;
750
+ } else if (graphStore.pinsTypes.includes(shape.shapeKey)) {
751
+ minHeight = 22;
752
+ dynamicMinW = 22
753
+ } else if (graphStore.portsTypes.includes(shape.shapeKey)) {
754
+ minHeight = 22;
755
+ dynamicMinW = 22
756
+ }
757
+ }
758
+
759
+ // 判断是否为diagram组件
760
+ const isDiagramComponent = shape.shapeKey && props.diagram && props.diagram.includes(shape.shapeKey);
761
+
762
+ // 确保宽度不小于文本所需最小宽度,高度不小于对应组件的最小高度
763
+ let finalBounds = { ...final };
764
+ if (final.width < dynamicMinW) {
765
+ finalBounds.width = dynamicMinW;
766
+ }
767
+
768
+ // 对于非diagram组件,保持最小高度限制
769
+ if (!isDiagramComponent && final.height < minHeight) {
770
+ finalBounds.height = minHeight;
771
+ }
772
+
773
+ // 对于diagram组件,确保最终应用时保持原始高度
774
+ if (isDiagramComponent && shape.shapeType === 'shape') {
775
+ finalBounds = { ...finalBounds, height: startBounds.value.height };
776
+ }
777
+
778
+ // 移除任何可能的容器约束限制,允许图元无限放大
779
+
780
+ // 获取原始形状以检查是否真的有变化
781
+ const shapeBefore = graphStore.shapes.find((s) => s.id === id);
782
+ const changed = shapeBefore && !_.isEqual(shapeBefore.bounds, finalBounds);
783
+
784
+ if (changed) {
785
+ // 只有确实有变化时才更新
786
+ graphStore.updateShape(id, { bounds: finalBounds });
787
+
788
+ // 统一收尾并检查是否有重新父子关系
789
+ const containerForFinalize =
790
+ graphStore.hoverContainerId && graphStore.hoverNestable ? graphStore.hoverContainerId : null;
791
+
792
+ const didReparent = finalizeAfterTransform(
793
+ graphStore.shapes,
794
+ [id],
795
+ { [id]: finalBounds },
796
+ [id],
797
+ graphStore.currentDiagramId,
798
+ containerForFinalize,
799
+ graphStore.updateShape
800
+ );
801
+
802
+ if (didReparent) {
803
+ normalizeZOrder(
804
+ graphStore.shapes,
805
+ graphStore.currentDiagramId,
806
+ graphStore.updateShape,
807
+ 1,
808
+ 1
809
+ );
810
+ }
811
+
812
+ // 调用graphStore中的方法处理缩放结束后的事件发射
813
+ graphStore.endResizeShape(id);
814
+ }
815
+
816
+ // 发送缩放结束事件
817
+ eventBus.emit('resize-end', { target: resizingTarget.value });
818
+
819
+ // 清理状态
820
+ isResizing.value = false;
821
+ resizeDirection.value = "";
822
+ resizingTarget.value = null;
823
+ groupBase.value = {};
824
+ groupGhost.value = {};
825
+ offDrag?.();
826
+ offDrag = null;
827
+ };
828
+
829
+ // 处理线条点击事件
830
+ const handleEdgeClick = (shape: Shape, event: MouseEvent) => {
831
+ console.log('通过edge-click事件选中的线条数据:', shape);
832
+ graphStore.selectShape(shape);
833
+ event.stopPropagation();
834
+ };
835
+
836
+ // 根层按下:命中测试 + 选中 + 切换光标
837
+ const DRAG_THRESHOLD = 4;
838
+ const onLayerMouseDown = (evt: MouseEvent) => {
839
+ // 若点击的是名称虚线框/容器,避免触发清选或框选(Pin 的名称可能在外部)
840
+ const t = evt.target as HTMLElement | null
841
+ if (t && (t.classList?.contains('name-text-box') || t.closest('.name-text-box-container'))) {
842
+ // 不改变当前选中;让后续 click 事件去触发 startEditName
843
+ evt.stopPropagation()
844
+ evt.preventDefault()
845
+ return
846
+ }
847
+ if (isResizing.value || isEditingName.value) return;
848
+ if (graphStore.isDragging) graphStore.endDragShape();
849
+ if (offDrag) {
850
+ offDrag();
851
+ offDrag = null;
852
+ }
853
+ const pt = toLocalPoint(evt, layerRef.value);
854
+ // 添加详细的点击位置日志
855
+ // console.log('鼠标点击位置:', pt);
856
+
857
+ // 执行命中测试
858
+ const hit = pickTarget(graphStore.shapes, pt);
859
+ // console.log('命中测试结果:', hit.kind, hit.shape?.id, hit.shape?.shapeType);
860
+ // 如果右键菜单已显示且点击了其他图元,关闭菜单
861
+ if (
862
+ showContextMenu.value &&
863
+ (hit.kind === "shape" || hit.kind === "pin") &&
864
+ (!contextMenuTarget.value || hit.shape.id !== contextMenuTarget.value.id)
865
+ ) {
866
+ showContextMenu.value = false;
867
+ }
868
+
869
+ cursorStyle.value = (hit.kind === "shape" || hit.kind === "edge" || hit.kind === "pin") ? "pointer" : "default";
870
+ // 进入“框选”的条件:
871
+ const wantMarquee = evt.shiftKey || hit.kind !== "shape" && hit.kind !== "edge" && hit.kind !== "pin";
872
+ if (wantMarquee) {
873
+ if ((hit.kind !== "shape" && hit.kind !== "edge" && hit.kind !== "pin") && !evt.shiftKey) {
874
+ graphStore.clearSelection();
875
+ }
876
+ startMarquee(pt);
877
+ evt.preventDefault();
878
+ return;
879
+ }
880
+ // 双击触发不同逻辑
881
+ if (evt.detail === 2 && graphStore.marqueeShapes.length == 1) {
882
+ const selectedShape = graphStore.selectedShape;
883
+ console.log('双击选中的图元:', selectedShape);
884
+
885
+ // 判断是否为Diagram组件
886
+ if (selectedShape && selectedShape.shapeType !== 'edge' && props.diagram?.includes(selectedShape.shapeKey)) {
887
+ // Diagram组件的特殊双击逻辑
888
+ // 这里可以添加你想要的其他逻辑,例如发射自定义事件
889
+ emit('diagramDoubleClick', selectedShape);
890
+ // console.log(selectedShape,'Diagram组件双击事件');
891
+ // 不打开属性面板
892
+ } else {
893
+ // 所有其他组件(包括连线)都打开属性面板
894
+ onLayerDblClick(true);
895
+ }
896
+ evt.preventDefault();
897
+ evt.stopPropagation();
898
+ return;
899
+ }
900
+ if (hit.kind === "shape" || hit.kind === "edge" || hit.kind === "pin") {
901
+ const { shape } = hit;
902
+
903
+ // 打印选中元素的数据信息 - 确保每次点击都能看到
904
+ console.log('点击选中的' + (hit.kind === 'edge' ? '线条' : hit.kind === 'pin' ? 'Pin' : '图元') + '数据信息:', shape);
905
+
906
+ const isMulti = graphStore.selectedIds.length > 1;
907
+ const clickedInSelection = graphStore.selectedIds.includes(shape.id);
908
+
909
+ if (isMulti && clickedInSelection) {
910
+ // 多选状态下点击已选中元素,保持现状
911
+ } else {
912
+ // 其他情况:单选当前元素
913
+ graphStore.clearSelection();
914
+ graphStore.selectShape(shape);
915
+ }
916
+ // 选区此时已是“正确”的:要么多选集合,要么当前单选
917
+ const ids = graphStore.selectedIds.length
918
+ ? graphStore.selectedIds.slice()
919
+ : [hit.shape.id];
920
+ // 准备“潜在拖拽”,但先不触发 store.startDrag
921
+ let started = false;
922
+ // 绑定拖拽生命周期,把 move/up 转发给 store
923
+ offDrag = withDrag(
924
+ (e) => {
925
+ const curr = toLocalPoint(e, layerRef.value);
926
+ const dx = curr.x - pt.x;
927
+ const dy = curr.y - pt.y;
928
+ const dist2 = dx * dx + dy * dy;
929
+
930
+ if (!started && dist2 >= DRAG_THRESHOLD * DRAG_THRESHOLD) {
931
+ // 真正超过阈值,再启动 store 的拖拽生命周期
932
+ started = true;
933
+ graphStore.startDrag(ids, pt);
934
+ }
935
+ if (started) {
936
+ // 如果是 pin 类型,需要在移动过程中将“指针位置”校正为吸附后的指针坐标
937
+ let targetPt = curr;
938
+ if (ids.length === 1) {
939
+ const draggedShape = graphStore.shapes.find(x => x.id === ids[0]);
940
+ if (draggedShape && draggedShape.shapeType === 'pin' && draggedShape.parenShapeId) {
941
+ const parentShape = graphStore.shapes.find(x => x.id === draggedShape.parenShapeId);
942
+ if (parentShape) {
943
+ // 使用移动专用的吸附方法:根据 dragOffset 计算应传入 moveDraggedShape 的指针坐标
944
+ targetPt = snapPinPointerOnMove(curr, parentShape, draggedShape, graphStore.dragOffset || undefined);
945
+ }
946
+ }
947
+ }
948
+ graphStore.moveDraggedShape(targetPt);
949
+ }
950
+ },
951
+ () => {
952
+ // 只有在“真的开始拖拽”后,才结束拖拽
953
+ if (started) {
954
+ graphStore.endDragShape();
955
+ } else {
956
+ // 纯点击:啥也不做(已完成选中),避免误触发 reparent/zIndex
957
+ cursorStyle.value = "default";
958
+ emit("property-panel", false);
959
+ }
960
+ offDrag = null;
961
+ }
962
+ );
963
+ }
964
+ };
965
+ // 框选部分
966
+ const startMarquee = (anchor: { x: number; y: number }) => {
967
+ // 读取画布容器(diagram 的边界)
968
+ const container = getDiagramRect(graphStore.shapes);
969
+ // 只限制“左/上”,右/下允许溢出
970
+ const edges = { left: true, top: true } as const;
971
+ //将按下点也先做一次夹取(避免从负坐标起框)
972
+ const anchorClamped = clampPointToRect(
973
+ anchor,
974
+ { x: 0, y: 0, width: 0, height: 0 },
975
+ container,
976
+ edges
977
+ );
978
+ // 记录锚点与初始化预览矩形
979
+ marqueeAnchor.value = anchorClamped;
980
+ marqueeRect.value = {
981
+ x: anchorClamped.x,
982
+ y: anchorClamped.y,
983
+ width: 0,
984
+ height: 0,
985
+ };
986
+ // 拖拽生命周期
987
+ offDrag?.();
988
+ offDrag = null;
989
+ offDrag = withDrag(
990
+ // move:更新框选矩形 + 实时刷新选区
991
+ (e) => {
992
+ // 当前指针的本地坐标
993
+ const currRaw = toLocalPoint(e, layerRef.value);
994
+ // 只对左/上做夹取(右/下不限制)
995
+ const currClamped = clampPointToRect(
996
+ currRaw,
997
+ { x: 0, y: 0, width: 0, height: 0 },
998
+ container,
999
+ edges
1000
+ );
1001
+ // 根据“锚点 & 当前点”得到框选矩形(会自动处理反向拖拽)
1002
+ const rect = rectFromPoints(marqueeAnchor.value!, currClamped);
1003
+ marqueeRect.value = rect;
1004
+ // 只选“完全位于框内”的图元(排除 diagram)
1005
+ const ids = graphStore.shapes
1006
+ .filter((s) => s.shapeType?.toLowerCase?.() !== "diagram")
1007
+ .filter((s) => rectContainsRect(rect, getBounds(s)))
1008
+ .map((s) => s.id);
1009
+ // 多选:所有被框中的图元都进入选中态
1010
+ graphStore.selectMany(ids);
1011
+ },
1012
+ // 结束框选,清理预览
1013
+ () => {
1014
+ marqueeAnchor.value = null;
1015
+ marqueeRect.value = null;
1016
+ cursorStyle.value = "default";
1017
+ }
1018
+ );
1019
+ };
1020
+
1021
+ // 鼠标抬起:恢复光标
1022
+ const onLayerMouseUp = () => {
1023
+ if (!isResizing.value) cursorStyle.value = "default";
1024
+ };
1025
+
1026
+ // 右键菜单状态
1027
+ const showContextMenu = ref(false);
1028
+ const contextMenuPosition = ref({ x: 0, y: 0 });
1029
+ const contextMenuTarget = ref<Shape | null>(null);
1030
+
1031
+ // 是否允许连接当前高亮的图元
1032
+ const isConnectAllowed = ref(false);
1033
+
1034
+ // 高亮图元的边框样式 - 使用工具类实现
1035
+ const highlightShape = (shape: Shape | null, isHighlight: boolean, isValidSource: boolean = true) => {
1036
+ highlightUtils.highlightShape(shape, isHighlight, isValidSource);
1037
+ };
1038
+
1039
+ // 处理右键点击事件
1040
+ const handleContextMenu = (event: MouseEvent) => {
1041
+ // 如果图元正在移动或缩放,则不显示菜单
1042
+ if (graphStore.isDragging || isResizing.value) {
1043
+ return;
1044
+ }
1045
+
1046
+ event.preventDefault();
1047
+
1048
+ // 获取点击位置
1049
+ const point = toLocalPoint(event, layerRef.value);
1050
+
1051
+ // 使用命中测试找到点击的图元
1052
+ const hit = pickTarget(graphStore.shapes, point);
1053
+
1054
+ if (hit.kind === "shape") {
1055
+ // 显示右键菜单
1056
+ showContextMenu.value = true;
1057
+ contextMenuPosition.value = {
1058
+ x: event.clientX,
1059
+ y: event.clientY,
1060
+ };
1061
+ contextMenuTarget.value = hit.shape;
1062
+ graphStore.selectShape(hit.shape);
1063
+ } else {
1064
+ // 如果没有点击图元,隐藏菜单
1065
+ showContextMenu.value = false;
1066
+ }
1067
+ };
1068
+
1069
+ // 菜单项处理函数
1070
+ const handleMenuDelete = () => {
1071
+ if (contextMenuTarget.value) {
1072
+ // removeSelected();
1073
+ graphStore.removeSelected();
1074
+ }
1075
+ showContextMenu.value = false;
1076
+ };
1077
+
1078
+ // 关闭右键菜单
1079
+ const closeMenu = () => {
1080
+ showContextMenu.value = false;
1081
+ contextMenuTarget.value = null;
1082
+ };
1083
+
1084
+ watch(
1085
+ () => graphStore.shapes,
1086
+ () => {
1087
+ nextTick(adjustCanvasToFitAllShapes);
1088
+ },
1089
+ { deep: true, immediate: true }
1090
+ );
1091
+
1092
+ // 监听图元状态
1093
+ watch(shouldCloseMenu, (shouldClose) => {
1094
+ if (shouldClose && showContextMenu.value) {
1095
+ closeMenu();
1096
+ }
1097
+ });
1098
+ watch(() => props.actionButtonShapeDataId, (newVal) => {
1099
+ if (newVal) {
1100
+ const foundShape = graphStore.shapes.find((x) => x.id === newVal);
1101
+ if (foundShape) {
1102
+ // 连接 sourceShape 和新 shape
1103
+ if (sourceShape.value) {
1104
+ const connectionData = EdgeUtils.completeConnection(
1105
+ sourceShape.value,
1106
+ foundShape,
1107
+ { x: recordClickPoint.value.x, y: recordClickPoint.value.y },
1108
+ currentConnectPoint.value,
1109
+ graphStore.shapes
1110
+ );
1111
+
1112
+ if (connectionData) {
1113
+ emit("connectEnd", connectionData);
1114
+ isConnecting.value = false;
1115
+ highlightUtils.clearHighlightTimeout();
1116
+ actionButtonMode.value = false;
1117
+ if (highlightTimeout.value) {
1118
+ clearTimeout(highlightTimeout.value);
1119
+ highlightTimeout.value = null;
1120
+ }
1121
+ }
1122
+ }
1123
+ }
1124
+ // 处理 actionButtonShapeData 的逻辑
1125
+ }
1126
+ });
1127
+ // 监听delete键移除图元
1128
+ const onKeyDownDelete = (e: KeyboardEvent) => {
1129
+ const target = e.target as HTMLElement | null
1130
+ if (target) {
1131
+ const tag = target.tagName
1132
+ // 如果在原生输入类控件里(input / textarea)
1133
+ if (tag === 'INPUT' || tag === 'TEXTAREA') {
1134
+ return // 让浏览器自己处理 Delete,删文字,不动图元
1135
+ }
1136
+ }
1137
+ // 仅处理 Delete;
1138
+ if (e.key !== "Delete") return;
1139
+ e.preventDefault(); // 避免浏览器默认行为
1140
+ //选中的图元中如果有画布(diagram),则不允许删除
1141
+ const selectedShapes = graphStore.marqueeShapes ?? []
1142
+ // 判断是否包含画布
1143
+ const hasDiagram = selectedShapes.some(s =>
1144
+ s.shapeType?.toLowerCase?.() === 'diagram'
1145
+ )
1146
+ if (hasDiagram) {
1147
+ // 选中了画布,直接不处理删除
1148
+ return
1149
+ }
1150
+ const items = selectedShapes
1151
+ .map(s => ({
1152
+ modelId: s.modelId,
1153
+ shapeId: s.id, // 图元 id
1154
+ shapeType: s.shapeType, // 图元类型
1155
+ isRemoveModelTree: false
1156
+ }))
1157
+
1158
+ if (!items.length) return;
1159
+ eventBus.emit('shapes-remove', items);
1160
+ };
1161
+
1162
+ // 鼠标移动事件处理
1163
+ const handleMouseMove = (event: MouseEvent) => {
1164
+ const { clientX, clientY } = event;
1165
+
1166
+ // 使用工具类检查是否在画布内
1167
+ if (EdgeUtils.isWithinCanvas(clientX, clientY)) {
1168
+ mousePosition.value = { x: clientX, y: clientY };
1169
+
1170
+ // 只有在连接状态下才显示线条
1171
+ if (isConnecting.value) {
1172
+ showLine.value = true;
1173
+ updateConnectPointToNearest(clientX, clientY);
1174
+ checkHoverTarget(clientX, clientY);
1175
+ }
1176
+ } else {
1177
+ if (isConnecting.value) {
1178
+ showLine.value = false;
1179
+ }
1180
+ highlightShape(null, false); // 使用highlightShape函数取消高亮,恢复原始样式
1181
+ }
1182
+ };
1183
+
1184
+ // 用于跟踪上一次的hoverShape,避免重复发射事件
1185
+ let lastHoverShapeId: string | null = null;
1186
+
1187
+ const checkHoverTarget = (clientX: number, clientY: number) => {
1188
+ if (!props.diagramBounds) return;
1189
+
1190
+ // 使用工具类检查悬停目标
1191
+ const hoverShape = EdgeUtils.checkHoverTarget(
1192
+ clientX,
1193
+ clientY,
1194
+ graphStore.shapes,
1195
+ props.diagramBounds,
1196
+ props.connectShapeData?.sourceId
1197
+ );
1198
+ highlightUtils.clearHighlightTimeout();
1199
+
1200
+ if (hoverShape) {
1201
+ // 检查连接有效性
1202
+ let targetModels = props.connectShapeData?.targetModels;
1203
+ if (actionButtonMode.value) {
1204
+ targetModels = props.connectShapeData?.scenarioMenus?.find(
1205
+ (menu) => menu.code === props.connectShapeData?.shapeKey
1206
+ )?.targetModels;
1207
+ }
1208
+ let isAllowed = true;
1209
+ if (targetModels && Array.isArray(targetModels) && targetModels.length > 0) {
1210
+ const hoverShapeType = hoverShape.shapeKey || '';
1211
+ isAllowed = targetModels.includes(hoverShapeType);
1212
+ }
1213
+ // 只有当hoverShape改变且connectShapeData存在时才发射事件
1214
+ if (lastHoverShapeId !== hoverShape.id && props.connectShapeData) {
1215
+ lastHoverShapeId = hoverShape.id;
1216
+ // 使用更严格的优先级判断逻辑,确保只要有一个属性有实际值就使用它
1217
+ let sourceModelId;
1218
+ // 如果modelId是有效字符串或数字,使用modelId
1219
+ if (props.connectShapeData.modelId && props.connectShapeData.modelId.toString().trim() !== '') {
1220
+ sourceModelId = props.connectShapeData.modelId;
1221
+ }
1222
+ // 否则,如果sourceModelId是有效字符串或数字,使用sourceModelId
1223
+ else if (props.connectShapeData.sourceModelId && props.connectShapeData.sourceModelId.toString().trim() !== '') {
1224
+ sourceModelId = props.connectShapeData.sourceModelId;
1225
+ }
1226
+
1227
+ // 只有当sourceModelId有值时就发射事件,并将isAllowed的值一并传递
1228
+ if (sourceModelId) {
1229
+ eventBus.emit('edge-check', {
1230
+ sourceModelId: sourceModelId, // 显式指定键值对,避免属性简写可能带来的混淆
1231
+ targetModelId: hoverShape.modelId,
1232
+ isAllowed: isAllowed // 将验证结果一并发射
1233
+ });
1234
+ }
1235
+ }
1236
+ // 高亮目标图元 - 根据edgeCheck属性决定高亮颜色
1237
+ // true为蓝色,false为红色
1238
+ highlightUtils.setHighlightTimeout(() => {
1239
+ highlightShape(hoverShape, true, props.edgeCheck);
1240
+ isConnectAllowed.value = isAllowed;
1241
+ }, 10);
1242
+ } else {
1243
+ // 如果没有悬停在目标上,重置lastHoverShapeId
1244
+ lastHoverShapeId = null;
1245
+ // 延迟取消高亮
1246
+ highlightUtils.setHighlightTimeout(() => {
1247
+ highlightShape(null, false);
1248
+ isConnectAllowed.value = false;
1249
+ }, 60);
1250
+ }
1251
+ };
1252
+
1253
+ // 更新连接点到最近的点
1254
+ const updateConnectPointToNearest = (mouseX: number, mouseY: number) => {
1255
+ if (!props.connectShapeData?.sourceId || !props.diagramBounds) return;
1256
+
1257
+ const sourceShape = graphStore.shapes.find(
1258
+ (shape) => shape.id === props.connectShapeData?.sourceId
1259
+ );
1260
+
1261
+ // 使用工具类找到最近连接点
1262
+ const nearestPoint = EdgeUtils.findNearestConnectPoint(
1263
+ { x: mouseX, y: mouseY },
1264
+ sourceShape,
1265
+ props.diagramBounds
1266
+ );
1267
+
1268
+ if (
1269
+ nearestPoint &&
1270
+ nearestPoint.x !== undefined &&
1271
+ nearestPoint.y !== undefined
1272
+ ) {
1273
+ currentConnectPoint.value = {
1274
+ x: nearestPoint.x,
1275
+ y: nearestPoint.y,
1276
+ };
1277
+ }
1278
+ };
1279
+
1280
+ // 监听isConnecting变化,重置连接状态
1281
+ watch(
1282
+ () => isConnecting.value,
1283
+ (newVal) => {
1284
+ if (!newVal) {
1285
+ isConnectAllowed.value = false;
1286
+ highlightShape(null, false); // 取消图元高亮
1287
+ }
1288
+ }
1289
+ );
1290
+
1291
+ // 处理连接层点击事件
1292
+ const handleConnectLayerClick = (event: MouseEvent) => {
1293
+ const { clientX, clientY } = event;
1294
+
1295
+ const canvas = document.querySelector(".diagram-content");
1296
+ if (!canvas || !props.diagramBounds) return;
1297
+
1298
+ const canvasRect = canvas.getBoundingClientRect();
1299
+ const clickX = clientX - canvasRect.left - (props.diagramBounds?.x || 0);
1300
+ const clickY = clientY - canvasRect.top - (props.diagramBounds?.y || 0);
1301
+
1302
+ const hit = pickTarget(graphStore.shapes, { x: clickX, y: clickY });
1303
+
1304
+ if (
1305
+ hit.kind === "shape" &&
1306
+ hit.shape.id &&
1307
+ props.connectShapeData?.sourceId &&
1308
+ hit.shape.id !== props.connectShapeData.sourceId
1309
+ ) {
1310
+ event.stopPropagation();
1311
+ completeConnection(hit.shape, { x: clickX, y: clickY });
1312
+ }
1313
+ };
1314
+
1315
+ // 完成连接
1316
+ const completeConnection = (
1317
+ clickedShape: Shape,
1318
+ clickPoint: { x: number; y: number }
1319
+ ) => {
1320
+ const sourceShape = graphStore.shapes.find(
1321
+ (shape: { id: string }) => shape.id === props.connectShapeData?.sourceId
1322
+ );
1323
+
1324
+ // 检查目标图元类型是否符合targetModels要求
1325
+ const targetModels = props.connectShapeData?.targetModels;
1326
+ if (targetModels && Array.isArray(targetModels) && targetModels.length > 0) {
1327
+
1328
+ const clickedShapeType = clickedShape.shapeKey || '';
1329
+ if (!targetModels.includes(clickedShapeType)) {
1330
+ isConnecting.value = false;
1331
+ highlightShape(null, false); // 取消图元高亮
1332
+ highlightUtils.clearHighlightTimeout();
1333
+ // alert('当前目标图元类型不符合连接要求');
1334
+ ElMessage.error('当前目标图元类型不符合连接要求');
1335
+ return;
1336
+ }
1337
+ }
1338
+
1339
+ // 检查是否已存在相同类型的边
1340
+ const existingEdge = graphStore.shapes.find((shape: Shape) =>
1341
+ shape.shapeType === 'edge' &&
1342
+ shape.sourceId === props.connectShapeData?.sourceId &&
1343
+ shape.targetId === clickedShape.id &&
1344
+ shape.shapeKey === props.connectShapeData?.shapeKey
1345
+ );
1346
+
1347
+
1348
+ // 如果边已存在,错误提示并返回
1349
+ if (existingEdge) {
1350
+ // alert('同类型的边已经存在,不能重复添加');
1351
+ ElMessage.error('同类型的边已经存在,不能重复添加');
1352
+ isConnecting.value = false;
1353
+ highlightShape(null, false); // 取消图元高亮
1354
+ highlightUtils.clearHighlightTimeout();
1355
+ return;
1356
+ }
1357
+
1358
+ // 使用工具类完成连接,传递当前的shapes列表以支持差异化路由
1359
+ const connectionData = EdgeUtils.completeConnection(
1360
+ sourceShape,
1361
+ clickedShape,
1362
+ clickPoint,
1363
+ currentConnectPoint.value,
1364
+ graphStore.shapes
1365
+ );
1366
+
1367
+ if (connectionData) {
1368
+ emit("connectEnd", connectionData);
1369
+ isConnecting.value = false;
1370
+ highlightShape(null, false); // 取消图元高亮
1371
+ highlightUtils.clearHighlightTimeout();
1372
+ actionButtonMode.value = false;
1373
+
1374
+ }
1375
+ };
1376
+ // 鼠标离开事件处理
1377
+ const handleMouseLeave = () => {
1378
+ if (isConnecting.value) {
1379
+ showLine.value = false;
1380
+ }
1381
+ highlightShape(null, false); // 取消图元高亮
1382
+ highlightUtils.clearHighlightTimeout();
1383
+ };
1384
+
1385
+ // 初始化连接点位置
1386
+ const initializeConnectPoint = () => {
1387
+ if (props.connectShapeData?.sourceId && props.diagramBounds) {
1388
+ const sourceShape = graphStore.shapes.find(
1389
+ (shape) => shape.id === props.connectShapeData?.sourceId
1390
+ );
1391
+
1392
+ // 使用工具类初始化连接点,传递当前鼠标位置和diagramBounds
1393
+ // 这样可以确保即使是第一次连线,也能计算出图元边界上的合适连接点,而不是使用中心点
1394
+ const initialPoint = EdgeUtils.initializeConnectPoint(
1395
+ sourceShape,
1396
+ recordClickPoint.value, // 传递鼠标点击位置
1397
+ props.diagramBounds // 传递图表边界
1398
+ );
1399
+
1400
+ if (initialPoint && sourceShape) { // 确保sourceShape存在,防止从工具栏拖拽时错误初始化
1401
+ currentConnectPoint.value = initialPoint;
1402
+
1403
+ if (!isConnecting.value) {
1404
+ // 进入连线状态前清空选中状态
1405
+ graphStore.clearSelection();
1406
+ isConnecting.value = true;
1407
+ showLine.value = true;
1408
+ }
1409
+ } else {
1410
+ // 如果没有找到sourceShape,重置连接状态
1411
+ isConnecting.value = false;
1412
+ showLine.value = false;
1413
+ }
1414
+ }
1415
+ };
1416
+
1417
+ // 直接计算黑点样式 - 使用动态连接点
1418
+ const dotStyle = computed(() => {
1419
+ if (!props.connectShapeData || !props.diagramBounds) {
1420
+ return { display: "none" };
1421
+ }
1422
+
1423
+ // 使用动态连接点位置
1424
+ const { x, y } = currentConnectPoint.value;
1425
+
1426
+ return {
1427
+ position: "absolute" as const,
1428
+ left: `${x}px`,
1429
+ top: `${y}px`,
1430
+ width: "8px",
1431
+ height: "8px",
1432
+ pointerEvents: "none" as const,
1433
+ zIndex: 1001,
1434
+ transform: "translate(-50%, -50%)",
1435
+ };
1436
+ });
1437
+
1438
+ // SVG 样式
1439
+ const svgStyle = computed(() => {
1440
+ if (
1441
+ !props.connectShapeData ||
1442
+ !props.connectShapeData.bounds ||
1443
+ !props.diagramBounds
1444
+ ) {
1445
+ return { display: "none" };
1446
+ }
1447
+
1448
+ return {
1449
+ position: "absolute" as const,
1450
+ left: "0px",
1451
+ top: "0px",
1452
+ width: "100%",
1453
+ height: "100%",
1454
+ pointerEvents: "none" as const,
1455
+ zIndex: 1000,
1456
+ };
1457
+ });
1458
+
1459
+ // 计算直角线路径点(增加状态有效性判断)
1460
+ const linePoints = computed(() => {
1461
+ // 新增:若未处于连线状态 / 线条未显示 / 无有效连接点,直接返回空值
1462
+ if (
1463
+ !isConnecting.value ||
1464
+ !showLine.value ||
1465
+ !props.connectShapeData ||
1466
+ !props.diagramBounds ||
1467
+ currentConnectPoint.value.x === 0 || // 排除无效默认值
1468
+ currentConnectPoint.value.y === 0
1469
+ ) {
1470
+ return ""; // 返回空字符串,SVG 不会渲染任何线条
1471
+ }
1472
+
1473
+ const canvas = document.querySelector(".diagram-content");
1474
+ if (!canvas) return "";
1475
+
1476
+ const canvasRect = canvas.getBoundingClientRect();
1477
+ const { x: startX, y: startY } = currentConnectPoint.value;
1478
+
1479
+ let endX, endY;
1480
+ // 仅在连线状态下计算终点(避免无效值)
1481
+ if (isConnecting.value) {
1482
+ endX = mousePosition.value.x - canvasRect.left - props.diagramBounds.x;
1483
+ endY = mousePosition.value.y - canvasRect.top - props.diagramBounds.y;
1484
+ } else if (targetConnectPoint.value) {
1485
+ endX = targetConnectPoint.value.x;
1486
+ endY = targetConnectPoint.value.y;
1487
+ } else {
1488
+ return "";
1489
+ }
1490
+
1491
+ // 使用工具类计算线路径
1492
+ return EdgeUtils.calculateLinePoints(
1493
+ { x: startX, y: startY },
1494
+ { x: endX, y: endY }
1495
+ );
1496
+ });
1497
+
1498
+ // 连接层样式 - 定位到 diagramBounds 位置,确保在最顶层
1499
+ const layerStyle = computed(() => {
1500
+ if (!props.diagramBounds) {
1501
+ return { display: "none" };
1502
+ }
1503
+
1504
+ return {
1505
+ position: "absolute" as const,
1506
+ left: `${props.diagramBounds.x}px`,
1507
+ top: `${props.diagramBounds.y}px`,
1508
+ width: `${props.diagramBounds.width}px`,
1509
+ height: `${props.diagramBounds.height}px`,
1510
+ pointerEvents: "none" as const,
1511
+ // zIndex: 10000, // 确保在最顶层
1512
+ };
1513
+ });
1514
+
1515
+ // 处理键盘按键事件
1516
+ const handleKeyDown = (e: KeyboardEvent) => {
1517
+ // 按下Esc且正在连线时,取消连线(增加 event.preventDefault 避免浏览器默认行为)
1518
+ if (e.key === 'Escape' && isConnecting.value) {
1519
+ e.preventDefault(); // 阻止浏览器默认的Esc行为(如关闭弹窗)
1520
+ cancelConnection();
1521
+ }
1522
+ };
1523
+
1524
+ // 组件卸载时清理高亮工具
1525
+ onUnmounted(() => {
1526
+ // 清理高亮工具实例
1527
+ highlightUtils.dispose();
1528
+ });
1529
+
1530
+
1531
+ // 取消连线的方法(完全重置所有相关状态)
1532
+ const cancelConnection = () => {
1533
+ // 1. 先停止连线状态,避免后续计算触发
1534
+ isConnecting.value = false;
1535
+
1536
+ // 2. 重置关键坐标状态(核心:清空无效坐标,让 linePoints 无值可算)
1537
+ currentConnectPoint.value = { x: 0, y: 0 }; // 重置为无效默认值
1538
+ mousePosition.value = { x: 0, y: 0 }; // 重置鼠标位置
1539
+ targetConnectPoint.value = { x: 0, y: 0 }; // 重置目标连接点
1540
+ targetShape.value = null; // 清空目标图元
1541
+
1542
+ // 3. 清除高亮状态和定时器
1543
+ highlightShape(null, false); // 取消图元高亮,恢复原始样式
1544
+ highlightUtils.clearHighlightTimeout();
1545
+
1546
+ // 4. 最后隐藏线条(确保状态重置后再隐藏,避免延迟渲染)
1547
+ showLine.value = false;
1548
+ };
1549
+
1550
+ // 监听 connectShapeData 变化,重新初始化连接点
1551
+ watch(
1552
+ () => props.connectShapeData,
1553
+ () => {
1554
+ initializeConnectPoint();
1555
+ },
1556
+ { deep: true }
1557
+ );
1558
+ let externalCreatingId: string | null // 当前正在拖拽的(已加入画布的)元素 id
1559
+ let externalPendingShape: any | null // 首帧/未入画布时的草稿(等待进入画布再 add)
1560
+ let entranceCheckInFlight = false // 是否正在做第一次进画布的校验
1561
+ let entranceDeniedInThisDrag = false // 本次拖拽是否已经被判定为“不允许进入画布”
1562
+ // 转换坐标
1563
+ const clientToLocalPoint = ({ clientX, clientY }: { clientX: number; clientY: number }) => {
1564
+ const el = layerRef.value
1565
+ if (!el) return { x: clientX, y: clientY } // 若还没挂载,直接用屏幕坐标兜底
1566
+ const rect = el.getBoundingClientRect()
1567
+ // 获取当前缩放比例
1568
+ const scale = graphStore.getScale() || 1
1569
+ // 计算本地坐标并除以缩放比例
1570
+ return {
1571
+ x: (clientX - rect.left) / scale,
1572
+ y: (clientY - rect.top) / scale
1573
+ } // 屏幕坐标转画布本地坐标
1574
+ }
1575
+ // 本地坐标转 client 坐标
1576
+ const localToClientPoint = ({ x, y }: { x: number; y: number }) => {
1577
+ const el = layerRef.value
1578
+ if (!el) return { clientX: x, clientY: y } // 兜底
1579
+ const rect = el.getBoundingClientRect()
1580
+ const scale = graphStore.getScale() || 1
1581
+ return {
1582
+ clientX: rect.left + x * scale,
1583
+ clientY: rect.top + y * scale,
1584
+ }
1585
+ }
1586
+ // 判断当前指针是否在画布(交互层)范围内
1587
+ const isInsideCanvasClient = (p: { clientX: number; clientY: number }) => {
1588
+ const el = layerRef.value
1589
+ if (!el) return false
1590
+ const r = el.getBoundingClientRect()
1591
+ return (
1592
+ p.clientX >= r.left &&
1593
+ p.clientX <= r.right &&
1594
+ p.clientY >= r.top &&
1595
+ p.clientY <= r.bottom
1596
+ )
1597
+ }
1598
+ //拖动中添加元素并触发嵌套逻辑,
1599
+ const continueExternalCreateDrag = async (payload: { clientX: number; clientY: number; shapeData?: any }) => {
1600
+ isExternalCreateDragging.value = true
1601
+ const pt = clientToLocalPoint(payload)
1602
+ if (payload.shapeData) {
1603
+ const s = payload.shapeData
1604
+ const isCmp = isCompartment(s as Shape)
1605
+ externalPendingShape = {
1606
+ ...s,
1607
+ bounds: {
1608
+ // 坐标始终用当前指针位置
1609
+ x: pt.x,
1610
+ y: pt.y,
1611
+ // 宽度优先用传入的,否则给个默认
1612
+ width: s.bounds?.width ?? 180,
1613
+ // 隔间组件固定高度 120;否则用原值或默认 80
1614
+ height: isCmp ? 120 : (s.bounds?.height ?? 80),
1615
+ },
1616
+ inert: false,
1617
+ }
1618
+ }
1619
+ if (!externalCreatingId && externalPendingShape && isInsideCanvasClient(payload)) {
1620
+ // entranceCheckInFlight = true
1621
+ const draft = externalPendingShape
1622
+ try {
1623
+ graphStore.addShape(draft)
1624
+ graphStore.startDrag([draft.id], pt)
1625
+ externalCreatingId = draft.id
1626
+ externalPendingShape = null
1627
+ } finally {
1628
+ // entranceCheckInFlight = false
1629
+ }
1630
+ externalPendingShape = null // 草稿已用完,清空
1631
+ }
1632
+ externalPendingShape = null
1633
+
1634
+ if (!externalCreatingId) return // 还没开始拖拽(不在画布内),直接返回
1635
+
1636
+ // 如果是pin类型,需要计算吸附位置
1637
+ let targetPt = pt
1638
+ if (externalCreatingId) {
1639
+ const s = graphStore.shapes.find(x => x.id === externalCreatingId)
1640
+ if (s && s.shapeType === 'pin') {
1641
+ // 检查是否有父容器
1642
+ if (graphStore.hoverContainerId && graphStore.hoverNestable !== false) {
1643
+ const parent = graphStore.shapes.find(x => x.id === graphStore.hoverContainerId)
1644
+ if (parent) {
1645
+ const { x: adjustedX, y: adjustedY } = snapPinToParentEdge(pt, parent, s)
1646
+ targetPt = { x: adjustedX, y: adjustedY }
1647
+ }
1648
+ }
1649
+ }
1650
+ }
1651
+
1652
+ graphStore.moveDraggedShape(targetPt) // 已在拖拽:更新位置
1653
+ }
1654
+
1655
+ //拖拽结束后重新发送事件到front中调用接口
1656
+ const finishExternalCreateDrag = async (payload: { clientX: number; clientY: number }) => {
1657
+ try {
1658
+ if (!externalCreatingId) return // 未开始则无事可做
1659
+ const pt = clientToLocalPoint(payload)
1660
+ if (isInsideCanvasClient(payload)) {
1661
+ graphStore.moveDraggedShape(pt) // 补最后一次位置
1662
+ await nextTick()
1663
+ // 结束后,把该元素标记为 inert=true(仅对 shapeType==='shape' 生效)
1664
+ const s: any = (graphStore.shapes || []).find((x: any) => x.id == externalCreatingId)
1665
+ if (s && s.shapeType == 'shape' || s.shapeType == 'pin') {
1666
+ // s.inert = true // 直接改对象属性(Vue3 响应式)
1667
+ const pure = _.omit(s, ['inert'])
1668
+ pure.bounds = { // 覆盖为新的 bounds
1669
+ ...(pure.bounds),
1670
+ x: pt.x,
1671
+ y: pt.y,
1672
+ }
1673
+ // 先推断这次 drop 的“候选父节点”
1674
+ let parent: Shape | null = null
1675
+ if (graphStore.hoverContainerId && graphStore.hoverNestable !== false) {
1676
+ parent = (graphStore.shapes as any[]).find(
1677
+ (x: any) => x.id === graphStore.hoverContainerId
1678
+ ) || null
1679
+ }
1680
+ // 如果是 pin 类型,调整位置吸附到父图元最近的边
1681
+ if (pure.shapeType === 'pin' && parent) {
1682
+ const { x: adjustedX, y: adjustedY } = snapPinToParentEdge(pt, parent, pure)
1683
+ pure.bounds.x = adjustedX
1684
+ pure.bounds.y = adjustedY
1685
+ pure.parenShapeId = parent.id;
1686
+ // 将吸附后的坐标同步回 ghost
1687
+ graphStore.moveDraggedShape({ x: adjustedX, y: adjustedY })
1688
+ }
1689
+ try {
1690
+ const { ok } = await checkNestViaFront(pure as Shape, parent, graphStore.shapes[0])
1691
+ if (!ok) {
1692
+ graphStore.setHoverState(null, false) // 根不高亮就行
1693
+ //立即删除临时数据,避免后续重复
1694
+ await sweepShapesWithInert()
1695
+ externalCreatingId = null // 清理状态
1696
+ isExternalCreateDragging.value = false
1697
+ graphStore.canDropOnCanvas = false
1698
+ return
1699
+ } else {
1700
+ graphStore.canDropOnCanvas = true
1701
+ }
1702
+ } catch (error) {
1703
+ //立即删除临时数据,避免后续重复
1704
+ await sweepShapesWithInert()
1705
+ // await graphStore.removeShape(externalCreatingId as string)
1706
+ // graphStore.clearSelection()
1707
+ externalCreatingId = null // 清理状态
1708
+ isExternalCreateDragging.value = false
1709
+ }
1710
+ // 同步对外通知
1711
+ // 先构造基础数据
1712
+ const payloadData: any = {
1713
+ coordinate: {
1714
+ clientX: payload.clientX,
1715
+ clientY: payload.clientY,
1716
+ },
1717
+ shapeData: _.cloneDeep(pure),
1718
+ type: s.type,
1719
+ nodeType: s.shapeKey,
1720
+ icon: s.icon,
1721
+ }
1722
+ // 如果是 pin 类型,更新 coordinate 为吸附后的 client 坐标
1723
+ if (pure.shapeType === 'pin' && parent) {
1724
+ const adjustedClientPt = localToClientPoint({ x: pure.bounds.x, y: pure.bounds.y })
1725
+ payloadData.coordinate.clientX = adjustedClientPt.clientX
1726
+ payloadData.coordinate.clientY = adjustedClientPt.clientY
1727
+ }
1728
+ // 如果当前图元的shapeKey存在于ownerRequiredShapeKeys当中,再补 ownerId 字段
1729
+ // @todo OperationalPort合并到ownerRequiredShapeKeys
1730
+ if (graphStore.ownerRequiredShapeKeys.includes(pure.shapeKey)) {
1731
+ payloadData.ownerId = parent?.modelId
1732
+ }
1733
+ // 发送事件
1734
+ await new Promise<void>((resolve, reject) => {
1735
+ eventBus.emit('addShape', {
1736
+ ...payloadData,
1737
+ resolve,
1738
+ reject,
1739
+ })
1740
+ })
1741
+ if (pure.shapeType === 'pin') {
1742
+ graphStore.endDragShape('pinDrop')
1743
+ } else {
1744
+ graphStore.endDragShape('addEntity') // 提交拖拽(触发嵌套/吸附等 finalize)
1745
+ }
1746
+ }
1747
+ }
1748
+ } catch (error) {
1749
+ graphStore.setHoverState(null, false)
1750
+ //立即删除临时数据,避免后续重复
1751
+ await sweepShapesWithInert()
1752
+ } finally {
1753
+ await nextTick()
1754
+ //立即删除临时数据,避免后续重复
1755
+ await sweepShapesWithInert()
1756
+ graphStore.setHoverState(null, false)
1757
+ // await graphStore.removeShape(externalCreatingId as string)
1758
+ // graphStore.clearSelection()
1759
+ externalCreatingId = null // 清理状态
1760
+ isExternalCreateDragging.value = false
1761
+ }
1762
+ }
1763
+ // 根据 graphStore.canDropOnCanvas 决定是否显示禁用放置图标
1764
+ const onCanvasDragOver = (e: DragEvent) => {
1765
+ e.preventDefault() // 必须,否则不会触发 drop
1766
+ if (!e.dataTransfer) return
1767
+ if (graphStore.canDropOnCanvas) {
1768
+ e.dataTransfer.dropEffect = 'copy' // 显示带 + 的复制光标
1769
+ } else {
1770
+ e.dataTransfer.dropEffect = 'none' //显示禁止的圈圈光标
1771
+ }
1772
+ }
1773
+
1774
+ const onCanvasDrop = (e: DragEvent) => {
1775
+ if (!graphStore.canDropOnCanvas) {
1776
+ // 不允许丢,直接 return
1777
+ return
1778
+ }
1779
+ }
1780
+ //清掉前端存的脏数据
1781
+ function sweepShapesWithInert() {
1782
+ const arr = graphStore.shapes as any[]
1783
+ let removed = 0
1784
+ for (let i = arr.length - 1; i >= 0; i--) {
1785
+ if ('inert' in arr[i]) {
1786
+ arr.splice(i, 1); // 直接删,避免 removeShape 的事件/副作用导致回填
1787
+ removed++
1788
+ }
1789
+ }
1790
+ }
1791
+ onMounted(() => {
1792
+ // 监听delete键移除图元
1793
+ document.addEventListener("keydown", onKeyDownDelete, { capture: true });
1794
+ window.addEventListener('keydown', handleKeyDown);
1795
+ // 添加连接层相关的事件监听
1796
+ if (props.connectShapeData) {
1797
+ document.addEventListener("mousemove", handleMouseMove);
1798
+ document.addEventListener("mouseleave", handleMouseLeave);
1799
+ initializeConnectPoint();
1800
+ }
1801
+
1802
+ //拖动结束后更新情景菜单
1803
+ eventBus.on('shape-drag-end-updateScenarioMenu', getActionButtonsStyle)
1804
+ });
1805
+ onUnmounted(() => {
1806
+ offDrag?.();
1807
+ // 监听delete键移除图元
1808
+ document.removeEventListener("keydown", onKeyDownDelete, { capture: true });
1809
+ // 清理连接层相关的事件监听
1810
+ document.removeEventListener("mousemove", handleMouseMove);
1811
+ document.removeEventListener("mouseleave", handleMouseLeave);
1812
+ window.removeEventListener('keydown', handleKeyDown);
1813
+ eventBus.off('shape-drag-end-updateScenarioMenu', getActionButtonsStyle)
1814
+ highlightUtils.clearHighlightTimeout();
1815
+ });
1816
+
1817
+ defineExpose({
1818
+ continueExternalCreateDrag,
1819
+ finishExternalCreateDrag,
1820
+ handleEdgeClick,
1821
+ getBoundingClientRect: () => layerRef.value?.getBoundingClientRect(),
1822
+ })
1823
+ </script>
1824
+
1825
+ <style scoped>
1826
+ .interaction-layer {
1827
+ position: absolute;
1828
+ top: 0;
1829
+ left: 0;
1830
+ width: 100%;
1831
+ height: 100%;
1832
+ /* 应用与画布相同的缩放变换 */
1833
+ transform-origin: 0 0;
1834
+ transform: scale(v-bind('graphStore.currentScale'));
1835
+ pointer-events: all;
1836
+ z-index: 999;
1837
+ }
1838
+
1839
+ .selection-box {
1840
+ pointer-events: none;
1841
+ background: transparent;
1842
+ }
1843
+
1844
+ .resize-handles {
1845
+ position: relative;
1846
+ width: 100%;
1847
+ height: 100%;
1848
+ }
1849
+
1850
+ .hover-container-outline {
1851
+ position: absolute;
1852
+ pointer-events: none;
1853
+ padding: 6px;
1854
+ top: 6px;
1855
+ }
1856
+
1857
+ /* 允许嵌套(后端 ok) */
1858
+ .hover-container-outline.is-valid {
1859
+ border: 2px solid #0000ff;
1860
+ }
1861
+
1862
+ /* 不允许嵌套(后端 false) */
1863
+ .hover-container-outline.is-invalid {
1864
+ border: 2px solid #f1eded;
1865
+ background: rgba(240, 237, 237, 0.842);
1866
+ }
1867
+
1868
+ .resize-handle {
1869
+ position: absolute;
1870
+ width: 10px;
1871
+ height: 10px;
1872
+ background-color: #007bff;
1873
+ border: 2px solid #fff;
1874
+ border-radius: 50%;
1875
+ pointer-events: all;
1876
+ transition: all 0.2s ease;
1877
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
1878
+ z-index: 999;
1879
+ }
1880
+
1881
+ .resize-handle.is-disabled {
1882
+ cursor: default !important;
1883
+ }
1884
+
1885
+ .resize-handle:hover {
1886
+ background-color: #0056b3;
1887
+ transform: scale(1.2);
1888
+ }
1889
+
1890
+ .action-buttons {
1891
+ display: flex;
1892
+ flex-direction: column;
1893
+ gap: 4px;
1894
+ pointer-events: all;
1895
+ background: rgba(255, 255, 255, 0.95);
1896
+ padding: 6px;
1897
+ border-radius: 4px;
1898
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
1899
+ border: 1px solid #e0e0e0;
1900
+ backdrop-filter: blur(2px);
1901
+ }
1902
+
1903
+ .action-btn {
1904
+ width: 28px;
1905
+ height: 28px;
1906
+ border: 1px solid #d0d0d0;
1907
+ border-radius: 3px;
1908
+ background: linear-gradient(to bottom, #f8f9fa, #e9ecef);
1909
+ cursor: pointer;
1910
+ display: flex;
1911
+ align-items: center;
1912
+ justify-content: center;
1913
+ font-size: 12px;
1914
+ font-weight: bold;
1915
+ font-family: "Arial", sans-serif;
1916
+ color: #495057;
1917
+ transition: all 0.2s ease;
1918
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
1919
+ }
1920
+
1921
+ .action-btn:hover {
1922
+ background: linear-gradient(to bottom, #e3f2fd, #bbdefb);
1923
+ border-color: #90caf9;
1924
+ transform: translateY(-1px);
1925
+ box-shadow: 0 2px 4px rgba(33, 150, 243, 0.3);
1926
+ color: #1976d2;
1927
+ }
1928
+
1929
+ .edit-btn:hover {
1930
+ background: #e3f2fd;
1931
+ }
1932
+
1933
+ .delete-btn:hover {
1934
+ background: #ffebee;
1935
+ border-color: #f44336;
1936
+ }
1937
+
1938
+ .name-editor-container {
1939
+ pointer-events: all;
1940
+ }
1941
+
1942
+ .name-input {
1943
+ width: calc(100% - 20px);
1944
+ padding: 2px 4px;
1945
+ border: 2px solid #007bff;
1946
+ border-radius: 6px;
1947
+ font-size: 12px;
1948
+ font-weight: 600;
1949
+ background: #fff;
1950
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
1951
+ outline: none;
1952
+ text-align: center;
1953
+ }
1954
+
1955
+ .name-input:focus {
1956
+ border-color: #0056b3;
1957
+ box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
1958
+ }
1959
+
1960
+ .name-text-box-container {
1961
+ pointer-events: all;
1962
+ }
1963
+
1964
+ .name-text-box {
1965
+ border: 1px dashed #007bff;
1966
+ background: rgba(255, 255, 255, 0.2);
1967
+ cursor: pointer;
1968
+ pointer-events: all;
1969
+ transition: all 0.2s ease;
1970
+ border-radius: 4px;
1971
+ box-shadow: 0 0 0 1px rgba(0, 123, 255, 0.1);
1972
+ height: 100%;
1973
+ display: flex;
1974
+ align-items: center;
1975
+ justify-content: center;
1976
+ }
1977
+
1978
+ .name-text-box:hover {
1979
+ border-color: #0056b3;
1980
+ background: rgba(0, 123, 255, 0.05);
1981
+ box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.2);
1982
+ transform: scale(1.02);
1983
+ }
1984
+
1985
+ .resize-ghost {
1986
+ position: absolute;
1987
+ border: 2px solid #8a6d3b;
1988
+ background: transparent;
1989
+ box-shadow: 0 0 0 2px rgba(138, 109, 59, 0.15) inset;
1990
+ pointer-events: none;
1991
+ z-index: 9999;
1992
+ }
1993
+
1994
+ /* 连接层样式 */
1995
+ .connect-layer {
1996
+ position: absolute;
1997
+ pointer-events: none;
1998
+ z-index: 1000;
1999
+ }
2000
+
2001
+ .connect-dot-direct {
2002
+ width: 8px;
2003
+ height: 8px;
2004
+ background-color: #000;
2005
+ border-radius: 50%;
2006
+ border: 1px solid #fff;
2007
+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
2008
+ }
2009
+
2010
+ .connect-dot-target {
2011
+ width: 8px;
2012
+ height: 8px;
2013
+ background-color: #000;
2014
+ border-radius: 50%;
2015
+ border: 1px solid #fff;
2016
+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
2017
+ }
2018
+
2019
+ .connection-line {
2020
+ position: absolute;
2021
+ top: 0;
2022
+ left: 0;
2023
+ width: 100%;
2024
+ height: 100%;
2025
+ pointer-events: none;
2026
+ z-index: 1000;
2027
+ }
2028
+
2029
+ .border-btn {
2030
+ padding-bottom: 4px;
2031
+ border-bottom: 1px solid #e0e0e0;
2032
+ }
2033
+ </style>