@robot-admin/naive-ui-components 0.4.2 → 0.4.4

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 (287) hide show
  1. package/dist/C_ActionBar.cjs +1 -5
  2. package/dist/C_ActionBar.js +1 -4
  3. package/dist/C_ActionBar.js.map +1 -0
  4. package/dist/C_ActionBar2.js +1 -195
  5. package/dist/C_ActionBar2.js.map +1 -1
  6. package/dist/C_AntV.cjs +1 -8
  7. package/dist/C_AntV.js +1 -4
  8. package/dist/C_AntV.js.map +1 -0
  9. package/dist/C_AntV2.js +4 -3149
  10. package/dist/C_AntV2.js.map +1 -1
  11. package/dist/C_Barcode.cjs +1 -4
  12. package/dist/C_Barcode.js +1 -3
  13. package/dist/C_Barcode.js.map +1 -0
  14. package/dist/C_Barcode2.js +1 -67
  15. package/dist/C_Barcode2.js.map +1 -1
  16. package/dist/C_Captcha.cjs +1 -4
  17. package/dist/C_Captcha.js +1 -3
  18. package/dist/C_Captcha.js.map +1 -0
  19. package/dist/C_Captcha2.js +1 -154
  20. package/dist/C_Captcha2.js.map +1 -1
  21. package/dist/C_Cascade.cjs +1 -4
  22. package/dist/C_Cascade.js +1 -3
  23. package/dist/C_Cascade.js.map +1 -0
  24. package/dist/C_Cascade2.js +1 -102
  25. package/dist/C_Cascade2.js.map +1 -1
  26. package/dist/C_City.cjs +1 -4
  27. package/dist/C_City.js +1 -3
  28. package/dist/C_City.js.map +1 -0
  29. package/dist/C_City2.js +1 -840
  30. package/dist/C_City2.js.map +1 -1
  31. package/dist/C_Code.cjs +1 -5
  32. package/dist/C_Code.js +1 -4
  33. package/dist/C_Code.js.map +1 -0
  34. package/dist/C_Code2.js +1 -346
  35. package/dist/C_Code2.js.map +1 -1
  36. package/dist/C_CollapsePanel.cjs +1 -6
  37. package/dist/C_CollapsePanel.js +1 -4
  38. package/dist/C_CollapsePanel.js.map +1 -0
  39. package/dist/C_CollapsePanel2.js +1 -318
  40. package/dist/C_CollapsePanel2.js.map +1 -1
  41. package/dist/C_Cron.cjs +1 -15
  42. package/dist/C_Cron.js +1 -4
  43. package/dist/C_Cron.js.map +1 -0
  44. package/dist/C_Cron2.js +1 -1209
  45. package/dist/C_Cron2.js.map +1 -1
  46. package/dist/C_Date.cjs +1 -4
  47. package/dist/C_Date.js +1 -3
  48. package/dist/C_Date.js.map +1 -0
  49. package/dist/C_Date2.js +1 -218
  50. package/dist/C_Date2.js.map +1 -1
  51. package/dist/C_Draggable.cjs +1 -5
  52. package/dist/C_Draggable.js +1 -3
  53. package/dist/C_Draggable.js.map +1 -0
  54. package/dist/C_Draggable2.js +1 -294
  55. package/dist/C_Draggable2.js.map +1 -1
  56. package/dist/C_Editor.cjs +1 -4
  57. package/dist/C_Editor.js +1 -3
  58. package/dist/C_Editor.js.map +1 -0
  59. package/dist/C_Editor2.js +1 -159
  60. package/dist/C_Editor2.js.map +1 -1
  61. package/dist/C_FilePreview.cjs +1 -7
  62. package/dist/C_FilePreview.js +1 -4
  63. package/dist/C_FilePreview.js.map +1 -0
  64. package/dist/C_FilePreview2.js +1 -1031
  65. package/dist/C_FilePreview2.js.map +1 -1
  66. package/dist/C_Form.cjs +1 -16
  67. package/dist/C_Form.js +1 -5
  68. package/dist/C_Form.js.map +1 -0
  69. package/dist/C_Form2.js +1 -2569
  70. package/dist/C_Form2.js.map +1 -1
  71. package/dist/C_FormSearch.cjs +1 -6
  72. package/dist/C_FormSearch.js +1 -3
  73. package/dist/C_FormSearch.js.map +1 -0
  74. package/dist/C_FormSearch2.js +1 -355
  75. package/dist/C_FormSearch2.js.map +1 -1
  76. package/dist/C_FormulaEditor.cjs +1 -13
  77. package/dist/C_FormulaEditor.js +1 -4
  78. package/dist/C_FormulaEditor.js.map +1 -0
  79. package/dist/C_FormulaEditor2.js +1 -1432
  80. package/dist/C_FormulaEditor2.js.map +1 -1
  81. package/dist/C_FullCalendar.cjs +1 -9
  82. package/dist/C_FullCalendar.js +1 -3
  83. package/dist/C_FullCalendar.js.map +1 -0
  84. package/dist/C_FullCalendar2.js +1 -376
  85. package/dist/C_FullCalendar2.js.map +1 -1
  86. package/dist/C_GlobalSearch.cjs +1 -5
  87. package/dist/C_GlobalSearch.js +1 -3
  88. package/dist/C_GlobalSearch.js.map +1 -0
  89. package/dist/C_GlobalSearch2.js +1 -335
  90. package/dist/C_GlobalSearch2.js.map +1 -1
  91. package/dist/C_Guide.cjs +1 -4
  92. package/dist/C_Guide.js +1 -3
  93. package/dist/C_Guide.js.map +1 -0
  94. package/dist/C_Guide2.js +1 -57
  95. package/dist/C_Guide2.js.map +1 -1
  96. package/dist/C_Icon.cjs +1 -4
  97. package/dist/C_Icon.js +1 -3
  98. package/dist/C_Icon.js.map +1 -0
  99. package/dist/C_Icon2.js +1 -285
  100. package/dist/C_Icon2.js.map +1 -1
  101. package/dist/C_ImageCropper.cjs +1 -6
  102. package/dist/C_ImageCropper.js +1 -4
  103. package/dist/C_ImageCropper.js.map +1 -0
  104. package/dist/C_ImageCropper2.js +1 -722
  105. package/dist/C_ImageCropper2.js.map +1 -1
  106. package/dist/C_Language.cjs +1 -4
  107. package/dist/C_Language.js +1 -3
  108. package/dist/C_Language.js.map +1 -0
  109. package/dist/C_Language2.js +1 -71
  110. package/dist/C_Language2.js.map +1 -1
  111. package/dist/C_Map.cjs +1 -7
  112. package/dist/C_Map.js +1 -3
  113. package/dist/C_Map.js.map +1 -0
  114. package/dist/C_Map2.js +1 -198
  115. package/dist/C_Map2.js.map +1 -1
  116. package/dist/C_Markdown.cjs +1 -4
  117. package/dist/C_Markdown.js +1 -3
  118. package/dist/C_Markdown.js.map +1 -0
  119. package/dist/C_Markdown2.js +1 -185
  120. package/dist/C_Markdown2.js.map +1 -1
  121. package/dist/C_NotificationCenter.cjs +1 -20
  122. package/dist/C_NotificationCenter.js +1 -4
  123. package/dist/C_NotificationCenter.js.map +1 -0
  124. package/dist/C_NotificationCenter2.js +1 -1383
  125. package/dist/C_NotificationCenter2.js.map +1 -1
  126. package/dist/C_Progress.cjs +1 -4
  127. package/dist/C_Progress.js +1 -3
  128. package/dist/C_Progress.js.map +1 -0
  129. package/dist/C_Progress2.js +1 -102
  130. package/dist/C_Progress2.js.map +1 -1
  131. package/dist/C_QRCode.cjs +1 -5
  132. package/dist/C_QRCode.js +1 -3
  133. package/dist/C_QRCode.js.map +1 -0
  134. package/dist/C_QRCode2.js +1 -217
  135. package/dist/C_QRCode2.js.map +1 -1
  136. package/dist/C_Signature.cjs +1 -8
  137. package/dist/C_Signature.js +1 -4
  138. package/dist/C_Signature.js.map +1 -0
  139. package/dist/C_Signature2.js +1 -617
  140. package/dist/C_Signature2.js.map +1 -1
  141. package/dist/C_SplitPane.cjs +1 -6
  142. package/dist/C_SplitPane.js +1 -4
  143. package/dist/C_SplitPane.js.map +1 -0
  144. package/dist/C_SplitPane2.js +1 -355
  145. package/dist/C_SplitPane2.js.map +1 -1
  146. package/dist/C_Steps.cjs +1 -4
  147. package/dist/C_Steps.js +1 -3
  148. package/dist/C_Steps.js.map +1 -0
  149. package/dist/C_Steps2.js +1 -81
  150. package/dist/C_Steps2.js.map +1 -1
  151. package/dist/C_Table.cjs +1 -20
  152. package/dist/C_Table.js +1 -6
  153. package/dist/C_Table.js.map +1 -0
  154. package/dist/C_Table2.js +1 -3023
  155. package/dist/C_Table2.js.map +1 -1
  156. package/dist/C_Theme.cjs +1 -4
  157. package/dist/C_Theme.js +1 -3
  158. package/dist/C_Theme.js.map +1 -0
  159. package/dist/C_Theme2.js +1 -59
  160. package/dist/C_Theme2.js.map +1 -1
  161. package/dist/C_Time.cjs +1 -5
  162. package/dist/C_Time.js +1 -3
  163. package/dist/C_Time.js.map +1 -0
  164. package/dist/C_Time2.js +1 -198
  165. package/dist/C_Time2.js.map +1 -1
  166. package/dist/C_Tree.cjs +1 -7
  167. package/dist/C_Tree.js +1 -4
  168. package/dist/C_Tree.js.map +1 -0
  169. package/dist/C_Tree2.js +1 -440
  170. package/dist/C_Tree2.js.map +1 -1
  171. package/dist/C_Upload.cjs +1 -12
  172. package/dist/C_Upload.js +1 -4
  173. package/dist/C_Upload.js.map +1 -0
  174. package/dist/C_Upload2.js +2 -1365
  175. package/dist/C_Upload2.js.map +1 -1
  176. package/dist/C_VideoPlayer.cjs +1 -23
  177. package/dist/C_VideoPlayer.js +1 -3
  178. package/dist/C_VideoPlayer.js.map +1 -0
  179. package/dist/C_VideoPlayer2.js +3 -1932
  180. package/dist/C_VideoPlayer2.js.map +1 -1
  181. package/dist/C_VtableGantt.cjs +1 -6
  182. package/dist/C_VtableGantt.js +1 -4
  183. package/dist/C_VtableGantt.js.map +1 -0
  184. package/dist/C_VtableGantt2.js +1 -872
  185. package/dist/C_VtableGantt2.js.map +1 -1
  186. package/dist/C_WaterFall.cjs +1 -13
  187. package/dist/C_WaterFall.js +1 -3
  188. package/dist/C_WaterFall.js.map +1 -0
  189. package/dist/C_WaterFall2.js +1 -364
  190. package/dist/C_WaterFall2.js.map +1 -1
  191. package/dist/C_WorkFlow.cjs +1 -8
  192. package/dist/C_WorkFlow.js +1 -4
  193. package/dist/C_WorkFlow.js.map +1 -0
  194. package/dist/C_WorkFlow2.js +1 -1983
  195. package/dist/C_WorkFlow2.js.map +1 -1
  196. package/dist/chunk.js +1 -18
  197. package/dist/city.js +1 -4816
  198. package/dist/constants.d.ts +3 -3
  199. package/dist/constants2.d.ts +5 -5
  200. package/dist/constants3.d.ts +2 -2
  201. package/dist/constants4.d.ts +4 -4
  202. package/dist/constants5.d.ts +2 -2
  203. package/dist/data.d.ts +1 -1
  204. package/dist/export-helper.js +1 -9
  205. package/dist/index.cjs +2 -441
  206. package/dist/index.cjs.map +1 -0
  207. package/dist/index.d.cts +2 -19
  208. package/dist/index.d.ts +2 -19
  209. package/dist/index.js +1 -258
  210. package/dist/index.js.map +1 -1
  211. package/dist/index.vue.d.ts +2 -2
  212. package/dist/index10.vue.d.ts +6 -6
  213. package/dist/index11.vue.d.ts +2 -2
  214. package/dist/index12.vue.d.ts +4 -4
  215. package/dist/index13.vue.d.ts +3 -3
  216. package/dist/index14.vue.d.ts +4 -4
  217. package/dist/index16.vue.d.ts +3 -3
  218. package/dist/index2.vue.d.ts +5 -5
  219. package/dist/index3.vue.d.ts +2 -2
  220. package/dist/index4.vue.d.ts +2 -2
  221. package/dist/index5.vue.d.ts +3 -3
  222. package/dist/index6.vue.d.ts +1 -1
  223. package/dist/index7.vue.d.ts +1 -1
  224. package/dist/index8.vue.d.ts +2 -2
  225. package/dist/resolver.cjs +2 -89
  226. package/dist/resolver.cjs.map +1 -0
  227. package/dist/resolver.js +1 -86
  228. package/dist/resolver.js.map +1 -1
  229. package/dist/storage.js +1 -30
  230. package/dist/storage.js.map +1 -1
  231. package/dist/useCollapsePanel.d.ts +2 -2
  232. package/dist/useCropperCore.d.ts +8 -8
  233. package/dist/useDraggableLayout.d.ts +3 -3
  234. package/dist/useDynamicFormState.d.ts +3 -3
  235. package/dist/useEdgeInteraction.d.ts +2 -2
  236. package/dist/useInfiniteScroll.d.ts +3 -3
  237. package/dist/useModalEdit.d.ts +3 -3
  238. package/dist/useQRCode.d.ts +4 -4
  239. package/dist/useSignatureHistory.d.ts +2 -2
  240. package/dist/useSplitResize.d.ts +5 -5
  241. package/dist/useTimeSelection.d.ts +1 -1
  242. package/dist/useTreeOperations.d.ts +4 -4
  243. package/dist/useWorkflowValidation.d.ts +2 -2
  244. package/package.json +5 -196
  245. package/dist/constants.d.ts.map +0 -1
  246. package/dist/constants2.d.ts.map +0 -1
  247. package/dist/constants3.d.ts.map +0 -1
  248. package/dist/constants4.d.ts.map +0 -1
  249. package/dist/constants5.d.ts.map +0 -1
  250. package/dist/data.d.ts.map +0 -1
  251. package/dist/index.d.cts.map +0 -1
  252. package/dist/index.d.ts.map +0 -1
  253. package/dist/index.vue.d.ts.map +0 -1
  254. package/dist/index10.vue.d.ts.map +0 -1
  255. package/dist/index11.vue.d.ts.map +0 -1
  256. package/dist/index12.vue.d.ts.map +0 -1
  257. package/dist/index13.vue.d.ts.map +0 -1
  258. package/dist/index14.vue.d.ts.map +0 -1
  259. package/dist/index15.vue.d.ts.map +0 -1
  260. package/dist/index16.vue.d.ts.map +0 -1
  261. package/dist/index2.vue.d.ts.map +0 -1
  262. package/dist/index3.vue.d.ts.map +0 -1
  263. package/dist/index4.vue.d.ts.map +0 -1
  264. package/dist/index5.vue.d.ts.map +0 -1
  265. package/dist/index6.vue.d.ts.map +0 -1
  266. package/dist/index7.vue.d.ts.map +0 -1
  267. package/dist/index8.vue.d.ts.map +0 -1
  268. package/dist/index9.vue.d.ts.map +0 -1
  269. package/dist/resolver.d.cts.map +0 -1
  270. package/dist/resolver.d.ts.map +0 -1
  271. package/dist/useCalendarEvents.d.ts.map +0 -1
  272. package/dist/useCollapsePanel.d.ts.map +0 -1
  273. package/dist/useCropperCore.d.ts.map +0 -1
  274. package/dist/useDraggableLayout.d.ts.map +0 -1
  275. package/dist/useDynamicFormState.d.ts.map +0 -1
  276. package/dist/useEdgeInteraction.d.ts.map +0 -1
  277. package/dist/useFullscreen.d.ts.map +0 -1
  278. package/dist/useGlobalSearch.d.ts.map +0 -1
  279. package/dist/useInfiniteScroll.d.ts.map +0 -1
  280. package/dist/useModalEdit.d.ts.map +0 -1
  281. package/dist/useQRCode.d.ts.map +0 -1
  282. package/dist/useSearchState.d.ts.map +0 -1
  283. package/dist/useSignatureHistory.d.ts.map +0 -1
  284. package/dist/useSplitResize.d.ts.map +0 -1
  285. package/dist/useTimeSelection.d.ts.map +0 -1
  286. package/dist/useTreeOperations.d.ts.map +0 -1
  287. package/dist/useWorkflowValidation.d.ts.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"C_AntV.js","names":["Graph","show","$emit","showToolbar","show","showToolbar","show","showToolbar"],"sources":["../src/components/C_AntV/composables/useGraphBase.ts","../src/components/C_AntV/utils/exportUtils.ts","../src/components/C_AntV/composables/useGraphExport.ts","../src/components/C_AntV/layout/ER/data.ts","../src/components/C_AntV/layout/ER/components/ERTableEditor.vue","../src/components/C_AntV/layout/ER/components/ERTableEditor.vue","../src/components/C_AntV/layout/ER/components/ERTableEditor.vue","../src/components/C_AntV/layout/ER/index.vue","../src/components/C_AntV/layout/ER/index.vue","../src/components/C_AntV/layout/ER/index.vue","../src/components/C_AntV/composables/useEdgeInteraction.ts","../src/components/C_AntV/layout/BPMN/data.ts","../src/components/C_AntV/layout/BPMN/components/BPMNPropertyEditor.vue","../src/components/C_AntV/layout/BPMN/components/BPMNPropertyEditor.vue","../src/components/C_AntV/layout/BPMN/components/BPMNPropertyEditor.vue","../src/components/C_AntV/layout/BPMN/index.vue","../src/components/C_AntV/layout/BPMN/index.vue","../src/components/C_AntV/layout/BPMN/index.vue","../src/components/C_AntV/layout/UML/components/UMLClassEditor.vue","../src/components/C_AntV/layout/UML/components/UMLClassEditor.vue","../src/components/C_AntV/layout/UML/components/UMLClassEditor.vue","../src/components/C_AntV/layout/UML/data.ts","../src/components/C_AntV/layout/UML/index.vue","../src/components/C_AntV/layout/UML/index.vue","../src/components/C_AntV/layout/UML/index.vue","../src/components/C_AntV/index.vue","../src/components/C_AntV/index.vue","../src/components/C_AntV/index.vue"],"sourcesContent":["import { Graph } from \"@antv/x6\";\r\nimport { ref, watch, nextTick, onUnmounted } from \"vue\";\r\nimport type { ComputedRef, Ref } from \"vue\";\r\n\r\n/**\r\n * 获取主题相关的颜色配置\r\n */\r\nfunction getThemeColors(isDark: boolean) {\r\n return {\r\n background: isDark ? \"#18181c\" : \"#f5f5f5\",\r\n gridPrimary: isDark ? \"rgba(255, 255, 255, 0.08)\" : \"#eee\",\r\n gridSecondary: isDark ? \"rgba(255, 255, 255, 0.04)\" : \"#ddd\",\r\n };\r\n}\r\n\r\n/**\r\n * 默认的图表配置选项\r\n */\r\nfunction getDefaultOptions(isDark: boolean = false) {\r\n const colors = getThemeColors(isDark);\r\n\r\n return {\r\n background: { color: colors.background },\r\n grid: {\r\n visible: true,\r\n type: \"doubleMesh\",\r\n args: [\r\n { color: colors.gridPrimary, thickness: 1 },\r\n { color: colors.gridSecondary, thickness: 1, factor: 4 },\r\n ],\r\n },\r\n mousewheel: {\r\n enabled: true,\r\n zoomAtMousePosition: true,\r\n modifiers: \"ctrl\",\r\n minScale: 0.5,\r\n maxScale: 2,\r\n },\r\n selecting: {\r\n enabled: true,\r\n rubberband: true,\r\n movable: true,\r\n showNodeSelectionBox: true,\r\n },\r\n resizing: true,\r\n rotating: true,\r\n snapline: true,\r\n keyboard: true,\r\n clipboard: true,\r\n };\r\n}\r\n\r\n/**\r\n * 获取容器的尺寸\r\n */\r\nfunction getContainerSize(container: HTMLElement | undefined) {\r\n if (!container) return { width: 0, height: 0 };\r\n return {\r\n width: container.clientWidth || container.offsetWidth || 800,\r\n height: container.clientHeight || container.offsetHeight || 600,\r\n };\r\n}\r\n\r\n/**\r\n * 创建和管理 AntV X6 图表实例的组合式 API\r\n */\r\nexport function useGraphBase(\r\n containerRef: Ref<HTMLElement | undefined>,\r\n isDarkRef?: Ref<boolean> | ComputedRef<boolean>,\r\n) {\r\n const MAX_RETRY = 10;\r\n let retryCount = 0;\r\n\r\n const graph: Ref<any> = ref(null);\r\n const loading = ref(false);\r\n\r\n /**\r\n * 初始化图表实例\r\n */\r\n const initGraph = async (options: any = {}) => {\r\n await nextTick();\r\n loading.value = true;\r\n\r\n try {\r\n if (!containerRef.value) return;\r\n\r\n // 销毁旧实例\r\n if (graph.value) {\r\n graph.value.dispose();\r\n graph.value = null;\r\n }\r\n\r\n const { width, height } = getContainerSize(containerRef.value);\r\n\r\n // 容器尺寸为0,延迟重试(最多 10 次)\r\n if (width === 0 || height === 0) {\r\n retryCount++;\r\n if (retryCount < MAX_RETRY) {\r\n setTimeout(() => initGraph(options), 100);\r\n }\r\n return;\r\n }\r\n retryCount = 0;\r\n\r\n const isDark = isDarkRef?.value ?? false;\r\n const defaultOptions = getDefaultOptions(isDark);\r\n\r\n const finalOptions = {\r\n container: containerRef.value,\r\n width,\r\n height,\r\n ...defaultOptions,\r\n ...options,\r\n };\r\n\r\n graph.value = new Graph(finalOptions);\r\n } catch {\r\n // 初始化失败静默处理\r\n } finally {\r\n loading.value = false;\r\n }\r\n };\r\n\r\n /**\r\n * 更新主题\r\n */\r\n function updateTheme(isDark: boolean) {\r\n if (!graph.value) return;\r\n\r\n const colors = getThemeColors(isDark);\r\n\r\n // 更新背景色\r\n graph.value.drawBackground({ color: colors.background });\r\n\r\n // 更新网格颜色\r\n graph.value.drawGrid({\r\n type: \"doubleMesh\",\r\n args: [\r\n { color: colors.gridPrimary, thickness: 1 },\r\n { color: colors.gridSecondary, thickness: 1, factor: 4 },\r\n ],\r\n });\r\n }\r\n\r\n // 监听主题变化\r\n if (isDarkRef) {\r\n watch(isDarkRef, (isDark) => {\r\n updateTheme(isDark);\r\n });\r\n }\r\n\r\n /**\r\n * 销毁当前图表实例\r\n */\r\n function destroyGraph() {\r\n if (graph.value) {\r\n graph.value.dispose();\r\n graph.value = null;\r\n }\r\n }\r\n\r\n /**\r\n * 将图表内容居中显示\r\n */\r\n function centerContent() {\r\n graph.value?.centerContent();\r\n }\r\n\r\n /**\r\n * 自适应缩放图表以适应容器\r\n */\r\n function zoomToFit() {\r\n graph.value?.zoomToFit({ padding: 20, maxScale: 1 });\r\n }\r\n\r\n /**\r\n * 按指定因子缩放图表\r\n */\r\n function zoom(factor: number) {\r\n graph.value?.zoom(factor);\r\n }\r\n\r\n /**\r\n * 调整图表尺寸以适应容器变化\r\n */\r\n function resizeGraph() {\r\n if (graph.value && containerRef.value) {\r\n const { width, height } = getContainerSize(containerRef.value);\r\n graph.value.resize(width, height);\r\n }\r\n }\r\n\r\n // 组件卸载时自动销毁图表\r\n onUnmounted(destroyGraph);\r\n\r\n return {\r\n graph,\r\n loading,\r\n initGraph,\r\n destroyGraph,\r\n centerContent,\r\n zoomToFit,\r\n zoom,\r\n resizeGraph,\r\n updateTheme,\r\n };\r\n}\r\n","import type { Graph } from \"@antv/x6\";\r\nimport html2canvas from \"html2canvas\";\r\n\r\n/**\r\n * 下载 Blob 文件到本地\r\n * @param blob - Blob 对象\r\n * @param filename - 文件名\r\n */\r\nfunction downloadBlob(blob: Blob, filename: string): void {\r\n const url = URL.createObjectURL(blob);\r\n const link = document.createElement(\"a\");\r\n link.href = url;\r\n link.download = filename;\r\n link.style.display = \"none\";\r\n document.body.appendChild(link);\r\n link.click();\r\n document.body.removeChild(link);\r\n URL.revokeObjectURL(url);\r\n}\r\n\r\n/**\r\n * 导出数据为 JSON 文件\r\n * @param data - 要导出的数据\r\n * @param filename - 文件名,默认 'diagram.json'\r\n */\r\nexport function exportJSON(data: any, filename = \"diagram.json\"): void {\r\n const jsonString = JSON.stringify(data, null, 2);\r\n const blob = new Blob([jsonString], { type: \"application/json\" });\r\n downloadBlob(blob, filename);\r\n}\r\n\r\n/**\r\n * 导出图表为 PNG 图片(基于 html2canvas 截图方案)\r\n * @param graph - AntV X6 图表实例\r\n * @param filename - 文件名,默认 'diagram.png'\r\n * @param options - 导出选项\r\n */\r\nexport async function exportPNG(\r\n graph: Graph,\r\n filename = \"diagram.png\",\r\n options: { backgroundColor?: string; scale?: number } = {},\r\n): Promise<void> {\r\n const { backgroundColor = \"#ffffff\", scale = 2 } = options;\r\n const { container } = graph;\r\n\r\n const canvas = await html2canvas(container, {\r\n backgroundColor,\r\n scale,\r\n useCORS: true,\r\n logging: false,\r\n });\r\n\r\n const blob = await canvasToBlob(canvas, \"image/png\");\r\n downloadBlob(blob, filename);\r\n}\r\n\r\n/**\r\n * 导出图表为 SVG 矢量图(从容器内 SVG 元素序列化)\r\n * @param graph - AntV X6 图表实例\r\n * @param filename - 文件名,默认 'diagram.svg'\r\n */\r\nexport function exportSVG(graph: Graph, filename = \"diagram.svg\"): void {\r\n const svgElement = graph.container.querySelector(\"svg\");\r\n if (!svgElement) {\r\n throw new Error(\"SVG导出失败: 未找到图表 SVG 元素\");\r\n }\r\n\r\n // 克隆以避免修改原始 DOM\r\n const cloned = svgElement.cloneNode(true) as SVGSVGElement;\r\n\r\n // 确保有 xmlns 属性\r\n if (!cloned.getAttribute(\"xmlns\")) {\r\n cloned.setAttribute(\"xmlns\", \"http://www.w3.org/2000/svg\");\r\n }\r\n if (!cloned.getAttribute(\"xmlns:xlink\")) {\r\n cloned.setAttribute(\"xmlns:xlink\", \"http://www.w3.org/1999/xlink\");\r\n }\r\n\r\n const serializer = new XMLSerializer();\r\n const svgString = serializer.serializeToString(cloned);\r\n const blob = new Blob([svgString], { type: \"image/svg+xml;charset=utf-8\" });\r\n downloadBlob(blob, filename);\r\n}\r\n\r\n/**\r\n * 将 Canvas 转换为 Blob\r\n */\r\nfunction canvasToBlob(\r\n canvas: HTMLCanvasElement,\r\n mimeType: string,\r\n quality = 1,\r\n): Promise<Blob> {\r\n return new Promise((resolve, reject) => {\r\n canvas.toBlob(\r\n (blob) => {\r\n if (blob) {\r\n resolve(blob);\r\n } else {\r\n reject(new Error(\"Canvas 转 Blob 失败\"));\r\n }\r\n },\r\n mimeType,\r\n quality,\r\n );\r\n });\r\n}\r\n\r\n/**\r\n * 复制内容到剪贴板\r\n * @param content - 要复制的内容\r\n * @returns 是否复制成功\r\n */\r\nexport async function copyToClipboard(content: string): Promise<boolean> {\r\n try {\r\n if (!navigator.clipboard) {\r\n return fallbackCopyToClipboard(content);\r\n }\r\n\r\n await navigator.clipboard.writeText(content);\r\n return true;\r\n } catch {\r\n return fallbackCopyToClipboard(content);\r\n }\r\n}\r\n\r\n/**\r\n * 备用的复制到剪贴板方法(适用于不支持 Clipboard API 的浏览器)\r\n */\r\nfunction fallbackCopyToClipboard(content: string): boolean {\r\n try {\r\n const textArea = document.createElement(\"textarea\");\r\n textArea.value = content;\r\n textArea.style.position = \"fixed\";\r\n textArea.style.left = \"-999999px\";\r\n textArea.style.top = \"-999999px\";\r\n\r\n document.body.appendChild(textArea);\r\n textArea.focus();\r\n textArea.select();\r\n\r\n const result = document.execCommand(\"copy\");\r\n document.body.removeChild(textArea);\r\n return result;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n","import type { Ref } from 'vue'\r\nimport type { Graph } from '@antv/x6'\r\nimport { useMessage } from 'naive-ui'\r\nimport { exportJSON, exportPNG, exportSVG } from '../utils/exportUtils'\r\n\r\n/** 导出下拉菜单选项(三个布局共享) */\r\nexport const EXPORT_OPTIONS = [\r\n { label: '导出PNG', key: 'png' },\r\n { label: '导出SVG', key: 'svg' },\r\n { label: '导出JSON', key: 'json' },\r\n] as const\r\n\r\n/**\r\n * 图表导出 composable — 统一 PNG / SVG / JSON 导出逻辑\r\n * @param graph - X6 Graph 实例引用\r\n * @param filenamePrefix - 导出文件名前缀,默认 'diagram'\r\n */\r\nexport function useGraphExport(\r\n graph: Ref<Graph | null>,\r\n filenamePrefix = 'diagram'\r\n) {\r\n const message = useMessage()\r\n /**\r\n * 处理导出操作\r\n * @param key - 导出格式 'png' | 'svg' | 'json'\r\n * @param getData - 获取当前图表数据的函数(用于 JSON 导出)\r\n */\r\n const handleExport = async (key: string, getData?: () => any) => {\r\n if (!graph.value) return\r\n\r\n try {\r\n switch (key) {\r\n case 'png':\r\n await exportPNG(graph.value, `${filenamePrefix}.png`)\r\n break\r\n case 'svg':\r\n exportSVG(graph.value, `${filenamePrefix}.svg`)\r\n break\r\n case 'json':\r\n if (getData) {\r\n exportJSON(getData(), `${filenamePrefix}.json`)\r\n }\r\n break\r\n }\r\n } catch (error) {\r\n console.error(`[useGraphExport] 导出失败 (${key}):`, error)\r\n message.error(`导出${key.toUpperCase()}失败,请重试`)\r\n }\r\n }\r\n\r\n return { exportOptions: EXPORT_OPTIONS, handleExport }\r\n}\r\n","/* 原有的常量数据 */\r\nexport const fieldTypes = [\r\n { label: \"BIGINT\", value: \"BIGINT\" },\r\n { label: \"INT\", value: \"INT\" },\r\n { label: \"SMALLINT\", value: \"SMALLINT\" },\r\n { label: \"TINYINT\", value: \"TINYINT\" },\r\n { label: \"VARCHAR(50)\", value: \"VARCHAR(50)\" },\r\n { label: \"VARCHAR(100)\", value: \"VARCHAR(100)\" },\r\n { label: \"VARCHAR(255)\", value: \"VARCHAR(255)\" },\r\n { label: \"CHAR(10)\", value: \"CHAR(10)\" },\r\n { label: \"TEXT\", value: \"TEXT\" },\r\n { label: \"LONGTEXT\", value: \"LONGTEXT\" },\r\n { label: \"DATETIME\", value: \"DATETIME\" },\r\n { label: \"TIMESTAMP\", value: \"TIMESTAMP\" },\r\n { label: \"DATE\", value: \"DATE\" },\r\n { label: \"TIME\", value: \"TIME\" },\r\n { label: \"DECIMAL(10,2)\", value: \"DECIMAL(10,2)\" },\r\n { label: \"FLOAT\", value: \"FLOAT\" },\r\n { label: \"DOUBLE\", value: \"DOUBLE\" },\r\n { label: \"BOOLEAN\", value: \"BOOLEAN\" },\r\n { label: \"JSON\", value: \"JSON\" },\r\n];\r\n","/* unplugin-vue-components disabled */<!--\r\n ERTableEditor — ER 表编辑器抽屉\r\n 负责:表名/注释编辑 + 字段 CRUD + 主键处理\r\n-->\r\n<template>\r\n <NDrawer\r\n :show=\"show\"\r\n width=\"600\"\r\n placement=\"right\"\r\n @update:show=\"$emit('update:show', $event)\"\r\n >\r\n <NDrawerContent :title=\"`编辑表: ${editingTable?.name || '新表'}`\" closable>\r\n <div class=\"table-editor\" v-if=\"editingTable\">\r\n <NForm :model=\"editingTable\" label-placement=\"top\">\r\n <NFormItem label=\"表名\">\r\n <NInput\r\n v-model:value=\"editingTable.name\"\r\n placeholder=\"请输入表名\"\r\n />\r\n </NFormItem>\r\n <NFormItem label=\"表注释\">\r\n <NInput v-model:value=\"editingTable.comment\" placeholder=\"表注释\" />\r\n </NFormItem>\r\n\r\n <NDivider>字段列表</NDivider>\r\n\r\n <div class=\"fields-container\">\r\n <NCard\r\n v-for=\"(field, index) in editingTable.fields\"\r\n :key=\"index\"\r\n size=\"small\"\r\n class=\"field-card\"\r\n >\r\n <template #header>\r\n <div class=\"field-header\">\r\n <span>#{{ index + 1 }} {{ field.name || \"新字段\" }}</span>\r\n <NButton\r\n @click=\"removeField(index)\"\r\n size=\"tiny\"\r\n type=\"error\"\r\n quaternary\r\n :disabled=\"editingTable.fields.length <= 1\"\r\n >\r\n 删除\r\n </NButton>\r\n </div>\r\n </template>\r\n\r\n <NGrid :cols=\"2\" :x-gap=\"12\">\r\n <NGi>\r\n <NFormItem label=\"字段名\" size=\"small\">\r\n <NInput\r\n v-model:value=\"field.name\"\r\n placeholder=\"字段名\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n </NGi>\r\n <NGi>\r\n <NFormItem label=\"类型\" size=\"small\">\r\n <NSelect\r\n v-model:value=\"field.type\"\r\n :options=\"fieldTypes\"\r\n size=\"small\"\r\n filterable\r\n placeholder=\"选择类型\"\r\n />\r\n </NFormItem>\r\n </NGi>\r\n </NGrid>\r\n\r\n <NSpace style=\"margin-top: 12px\">\r\n <NCheckbox\r\n v-model:checked=\"field.isPrimaryKey\"\r\n @update:checked=\"handlePrimaryKey(field, $event)\"\r\n >\r\n 主键\r\n </NCheckbox>\r\n <NCheckbox v-model:checked=\"field.isRequired\">必填</NCheckbox>\r\n <NCheckbox v-model:checked=\"field.isForeignKey\">外键</NCheckbox>\r\n </NSpace>\r\n\r\n <NFormItem label=\"注释\" size=\"small\" style=\"margin-top: 12px\">\r\n <NInput\r\n v-model:value=\"field.comment\"\r\n placeholder=\"字段注释\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n </NCard>\r\n\r\n <NButton\r\n @click=\"addField\"\r\n dashed\r\n block\r\n type=\"primary\"\r\n ghost\r\n style=\"margin-top: 16px\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </template>\r\n 添加字段\r\n </NButton>\r\n </div>\r\n </NForm>\r\n </div>\r\n\r\n <template #footer>\r\n <NSpace justify=\"end\">\r\n <NButton @click=\"$emit('update:show', false)\">取消</NButton>\r\n <NButton @click=\"$emit('save')\" type=\"primary\">保存</NButton>\r\n </NSpace>\r\n </template>\r\n </NDrawerContent>\r\n </NDrawer>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n NDrawer,\r\n NDrawerContent,\r\n NForm,\r\n NFormItem,\r\n NInput,\r\n NSelect,\r\n NCard,\r\n NGrid,\r\n NGi,\r\n NSpace,\r\n NCheckbox,\r\n NButton,\r\n NDivider,\r\n} from \"naive-ui\";\r\nimport type { ERTable, ERField } from \"../../types\";\r\nimport { fieldTypes } from \"../data\";\r\nimport C_Icon from \"../../../../C_Icon/index.vue\";\r\n\r\ndefineProps<{\r\n show: boolean;\r\n}>();\r\n\r\nconst editingTable = defineModel<ERTable>(\"editingTable\");\r\n\r\nconst emit = defineEmits<{\r\n \"update:show\": [value: boolean];\r\n save: [];\r\n \"add-field\": [];\r\n \"remove-field\": [index: number];\r\n \"handle-primary-key\": [field: ERField, isPrimaryKey: boolean];\r\n}>();\r\n\r\nconst handlePrimaryKey = (field: ERField, isPrimaryKey: boolean) =>\r\n emit(\"handle-primary-key\", field, isPrimaryKey);\r\n\r\nconst addField = () => emit(\"add-field\");\r\n\r\nconst removeField = (index: number) => emit(\"remove-field\", index);\r\n</script>\r\n","/* unplugin-vue-components disabled */<!--\r\n ERTableEditor — ER 表编辑器抽屉\r\n 负责:表名/注释编辑 + 字段 CRUD + 主键处理\r\n-->\r\n<template>\r\n <NDrawer\r\n :show=\"show\"\r\n width=\"600\"\r\n placement=\"right\"\r\n @update:show=\"$emit('update:show', $event)\"\r\n >\r\n <NDrawerContent :title=\"`编辑表: ${editingTable?.name || '新表'}`\" closable>\r\n <div class=\"table-editor\" v-if=\"editingTable\">\r\n <NForm :model=\"editingTable\" label-placement=\"top\">\r\n <NFormItem label=\"表名\">\r\n <NInput\r\n v-model:value=\"editingTable.name\"\r\n placeholder=\"请输入表名\"\r\n />\r\n </NFormItem>\r\n <NFormItem label=\"表注释\">\r\n <NInput v-model:value=\"editingTable.comment\" placeholder=\"表注释\" />\r\n </NFormItem>\r\n\r\n <NDivider>字段列表</NDivider>\r\n\r\n <div class=\"fields-container\">\r\n <NCard\r\n v-for=\"(field, index) in editingTable.fields\"\r\n :key=\"index\"\r\n size=\"small\"\r\n class=\"field-card\"\r\n >\r\n <template #header>\r\n <div class=\"field-header\">\r\n <span>#{{ index + 1 }} {{ field.name || \"新字段\" }}</span>\r\n <NButton\r\n @click=\"removeField(index)\"\r\n size=\"tiny\"\r\n type=\"error\"\r\n quaternary\r\n :disabled=\"editingTable.fields.length <= 1\"\r\n >\r\n 删除\r\n </NButton>\r\n </div>\r\n </template>\r\n\r\n <NGrid :cols=\"2\" :x-gap=\"12\">\r\n <NGi>\r\n <NFormItem label=\"字段名\" size=\"small\">\r\n <NInput\r\n v-model:value=\"field.name\"\r\n placeholder=\"字段名\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n </NGi>\r\n <NGi>\r\n <NFormItem label=\"类型\" size=\"small\">\r\n <NSelect\r\n v-model:value=\"field.type\"\r\n :options=\"fieldTypes\"\r\n size=\"small\"\r\n filterable\r\n placeholder=\"选择类型\"\r\n />\r\n </NFormItem>\r\n </NGi>\r\n </NGrid>\r\n\r\n <NSpace style=\"margin-top: 12px\">\r\n <NCheckbox\r\n v-model:checked=\"field.isPrimaryKey\"\r\n @update:checked=\"handlePrimaryKey(field, $event)\"\r\n >\r\n 主键\r\n </NCheckbox>\r\n <NCheckbox v-model:checked=\"field.isRequired\">必填</NCheckbox>\r\n <NCheckbox v-model:checked=\"field.isForeignKey\">外键</NCheckbox>\r\n </NSpace>\r\n\r\n <NFormItem label=\"注释\" size=\"small\" style=\"margin-top: 12px\">\r\n <NInput\r\n v-model:value=\"field.comment\"\r\n placeholder=\"字段注释\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n </NCard>\r\n\r\n <NButton\r\n @click=\"addField\"\r\n dashed\r\n block\r\n type=\"primary\"\r\n ghost\r\n style=\"margin-top: 16px\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </template>\r\n 添加字段\r\n </NButton>\r\n </div>\r\n </NForm>\r\n </div>\r\n\r\n <template #footer>\r\n <NSpace justify=\"end\">\r\n <NButton @click=\"$emit('update:show', false)\">取消</NButton>\r\n <NButton @click=\"$emit('save')\" type=\"primary\">保存</NButton>\r\n </NSpace>\r\n </template>\r\n </NDrawerContent>\r\n </NDrawer>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n NDrawer,\r\n NDrawerContent,\r\n NForm,\r\n NFormItem,\r\n NInput,\r\n NSelect,\r\n NCard,\r\n NGrid,\r\n NGi,\r\n NSpace,\r\n NCheckbox,\r\n NButton,\r\n NDivider,\r\n} from \"naive-ui\";\r\nimport type { ERTable, ERField } from \"../../types\";\r\nimport { fieldTypes } from \"../data\";\r\nimport C_Icon from \"../../../../C_Icon/index.vue\";\r\n\r\ndefineProps<{\r\n show: boolean;\r\n}>();\r\n\r\nconst editingTable = defineModel<ERTable>(\"editingTable\");\r\n\r\nconst emit = defineEmits<{\r\n \"update:show\": [value: boolean];\r\n save: [];\r\n \"add-field\": [];\r\n \"remove-field\": [index: number];\r\n \"handle-primary-key\": [field: ERField, isPrimaryKey: boolean];\r\n}>();\r\n\r\nconst handlePrimaryKey = (field: ERField, isPrimaryKey: boolean) =>\r\n emit(\"handle-primary-key\", field, isPrimaryKey);\r\n\r\nconst addField = () => emit(\"add-field\");\r\n\r\nconst removeField = (index: number) => emit(\"remove-field\", index);\r\n</script>\r\n","<!--\r\n ERTableEditor — ER 表编辑器抽屉\r\n 负责:表名/注释编辑 + 字段 CRUD + 主键处理\r\n-->\r\n<template>\r\n <NDrawer\r\n :show=\"show\"\r\n width=\"600\"\r\n placement=\"right\"\r\n @update:show=\"$emit('update:show', $event)\"\r\n >\r\n <NDrawerContent :title=\"`编辑表: ${editingTable?.name || '新表'}`\" closable>\r\n <div class=\"table-editor\" v-if=\"editingTable\">\r\n <NForm :model=\"editingTable\" label-placement=\"top\">\r\n <NFormItem label=\"表名\">\r\n <NInput\r\n v-model:value=\"editingTable.name\"\r\n placeholder=\"请输入表名\"\r\n />\r\n </NFormItem>\r\n <NFormItem label=\"表注释\">\r\n <NInput v-model:value=\"editingTable.comment\" placeholder=\"表注释\" />\r\n </NFormItem>\r\n\r\n <NDivider>字段列表</NDivider>\r\n\r\n <div class=\"fields-container\">\r\n <NCard\r\n v-for=\"(field, index) in editingTable.fields\"\r\n :key=\"index\"\r\n size=\"small\"\r\n class=\"field-card\"\r\n >\r\n <template #header>\r\n <div class=\"field-header\">\r\n <span>#{{ index + 1 }} {{ field.name || \"新字段\" }}</span>\r\n <NButton\r\n @click=\"removeField(index)\"\r\n size=\"tiny\"\r\n type=\"error\"\r\n quaternary\r\n :disabled=\"editingTable.fields.length <= 1\"\r\n >\r\n 删除\r\n </NButton>\r\n </div>\r\n </template>\r\n\r\n <NGrid :cols=\"2\" :x-gap=\"12\">\r\n <NGi>\r\n <NFormItem label=\"字段名\" size=\"small\">\r\n <NInput\r\n v-model:value=\"field.name\"\r\n placeholder=\"字段名\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n </NGi>\r\n <NGi>\r\n <NFormItem label=\"类型\" size=\"small\">\r\n <NSelect\r\n v-model:value=\"field.type\"\r\n :options=\"fieldTypes\"\r\n size=\"small\"\r\n filterable\r\n placeholder=\"选择类型\"\r\n />\r\n </NFormItem>\r\n </NGi>\r\n </NGrid>\r\n\r\n <NSpace style=\"margin-top: 12px\">\r\n <NCheckbox\r\n v-model:checked=\"field.isPrimaryKey\"\r\n @update:checked=\"handlePrimaryKey(field, $event)\"\r\n >\r\n 主键\r\n </NCheckbox>\r\n <NCheckbox v-model:checked=\"field.isRequired\">必填</NCheckbox>\r\n <NCheckbox v-model:checked=\"field.isForeignKey\">外键</NCheckbox>\r\n </NSpace>\r\n\r\n <NFormItem label=\"注释\" size=\"small\" style=\"margin-top: 12px\">\r\n <NInput\r\n v-model:value=\"field.comment\"\r\n placeholder=\"字段注释\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n </NCard>\r\n\r\n <NButton\r\n @click=\"addField\"\r\n dashed\r\n block\r\n type=\"primary\"\r\n ghost\r\n style=\"margin-top: 16px\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </template>\r\n 添加字段\r\n </NButton>\r\n </div>\r\n </NForm>\r\n </div>\r\n\r\n <template #footer>\r\n <NSpace justify=\"end\">\r\n <NButton @click=\"$emit('update:show', false)\">取消</NButton>\r\n <NButton @click=\"$emit('save')\" type=\"primary\">保存</NButton>\r\n </NSpace>\r\n </template>\r\n </NDrawerContent>\r\n </NDrawer>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n NDrawer,\r\n NDrawerContent,\r\n NForm,\r\n NFormItem,\r\n NInput,\r\n NSelect,\r\n NCard,\r\n NGrid,\r\n NGi,\r\n NSpace,\r\n NCheckbox,\r\n NButton,\r\n NDivider,\r\n} from \"naive-ui\";\r\nimport type { ERTable, ERField } from \"../../types\";\r\nimport { fieldTypes } from \"../data\";\r\nimport C_Icon from \"../../../../C_Icon/index.vue\";\r\n\r\ndefineProps<{\r\n show: boolean;\r\n}>();\r\n\r\nconst editingTable = defineModel<ERTable>(\"editingTable\");\r\n\r\nconst emit = defineEmits<{\r\n \"update:show\": [value: boolean];\r\n save: [];\r\n \"add-field\": [];\r\n \"remove-field\": [index: number];\r\n \"handle-primary-key\": [field: ERField, isPrimaryKey: boolean];\r\n}>();\r\n\r\nconst handlePrimaryKey = (field: ERField, isPrimaryKey: boolean) =>\r\n emit(\"handle-primary-key\", field, isPrimaryKey);\r\n\r\nconst addField = () => emit(\"add-field\");\r\n\r\nconst removeField = (index: number) => emit(\"remove-field\", index);\r\n</script>\r\n","/* unplugin-vue-components disabled */<template>\r\n <div class=\"er-layout\">\r\n <!-- 工具栏 -->\r\n <div class=\"toolbar\" v-if=\"showToolbar\">\r\n <NSpace>\r\n <NButton @click=\"addTable\" type=\"primary\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:table-plus\" :size=\"16\" />\r\n </template>\r\n 添加表\r\n </NButton>\r\n <NButton @click=\"centerContent\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:image-filter-center-focus\" :size=\"16\" />\r\n </template>\r\n 居中\r\n </NButton>\r\n <NButton @click=\"zoomToFit\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:fit-to-screen\" :size=\"16\" />\r\n </template>\r\n 适应\r\n </NButton>\r\n <NButton\r\n @click=\"toggleDeleteMode\"\r\n :type=\"deleteMode ? 'error' : 'default'\"\r\n size=\"small\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete\" :size=\"16\" />\r\n </template>\r\n {{ deleteMode ? \"退出删除\" : \"删除连线\" }}\r\n </NButton>\r\n <NDropdown\r\n :options=\"exportOptions\"\r\n @select=\"(key: string) => handleExport(key, getCurrentData)\"\r\n >\r\n <NButton size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:export\" :size=\"16\" />\r\n </template>\r\n 导出\r\n </NButton>\r\n </NDropdown>\r\n </NSpace>\r\n\r\n <div v-if=\"deleteMode\" style=\"margin-top: 8px\">\r\n <NAlert type=\"info\" size=\"small\" :show-icon=\"false\">\r\n 删除模式:点击连接线即可删除\r\n </NAlert>\r\n </div>\r\n </div>\r\n\r\n <!-- 图表容器 -->\r\n <div ref=\"containerRef\" class=\"graph-container\"></div>\r\n\r\n <!-- 表编辑器 -->\r\n <ERTableEditor\r\n :show=\"showEditor\"\r\n v-model:editing-table=\"editingTable\"\r\n @update:show=\"showEditor = $event\"\r\n @save=\"saveTable\"\r\n @add-field=\"addField\"\r\n @remove-field=\"removeField\"\r\n @handle-primary-key=\"handlePrimaryKey\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, nextTick, onMounted, onUnmounted } from \"vue\";\r\nimport { NSpace, NButton, NDropdown, NAlert } from \"naive-ui\";\r\nimport { Node, Graph, Cell, Edge } from \"@antv/x6\";\r\nimport { useGraphBase } from \"../../composables/useGraphBase\";\r\nimport { useGraphExport } from \"../../composables/useGraphExport\";\r\nimport type { ERTable, ERField, ERDiagramData, ERRelation } from \"../../types\";\r\nimport ERTableEditor from \"./components/ERTableEditor.vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\ninterface Props {\r\n data?: ERDiagramData;\r\n showToolbar?: boolean;\r\n readonly?: boolean;\r\n theme?: \"light\" | \"dark\";\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n showToolbar: true,\r\n readonly: false,\r\n});\r\n\r\nconst emit = defineEmits<{\r\n (e: \"ready\", graph: Graph): void;\r\n (e: \"data-change\", data: ERDiagramData): void;\r\n}>();\r\n\r\n// ==================== Composables ====================\r\nconst containerRef = ref<HTMLDivElement>();\r\nconst isDark = computed(() => props.theme === \"dark\");\r\nconst { graph, initGraph, centerContent, zoomToFit } = useGraphBase(\r\n containerRef,\r\n isDark,\r\n);\r\nconst { exportOptions, handleExport } = useGraphExport(graph, \"er-diagram\");\r\n\r\n// ==================== 编辑器状态 ====================\r\nconst showEditor = ref(false);\r\nconst editingTable = ref<ERTable>();\r\nconst deleteMode = ref(false);\r\n\r\n// ==================== 工具函数 ====================\r\nconst truncateText = (text: string, maxLength: number) =>\r\n text.length > maxLength ? text.substring(0, maxLength - 1) + \"..\" : text;\r\n\r\nconst createPortConfig = (table: ERTable) =>\r\n table.fields?.map((field) => {\r\n const displayName = field.isPrimaryKey\r\n ? `🔑 ${field.name}`\r\n : field.isRequired\r\n ? `* ${field.name}`\r\n : field.name;\r\n return {\r\n id: `${table.id}_${field.name}`,\r\n group: \"list\",\r\n attrs: {\r\n portNameLabel: {\r\n text: truncateText(displayName, 12),\r\n title: displayName,\r\n },\r\n portTypeLabel: {\r\n text: truncateText(field.type, 10),\r\n title: field.type,\r\n },\r\n portBody: { fill: field.isPrimaryKey ? \"#FFF7E6\" : \"#EFF4FF\" },\r\n },\r\n };\r\n }) || [];\r\n\r\nconst createNodeConfig = (table: ERTable) => ({\r\n id: table.id,\r\n shape: \"er-rect\",\r\n x: table.position.x,\r\n y: table.position.y,\r\n width: 200,\r\n height: 24 + (table.fields?.length || 0) * 24,\r\n data: table,\r\n attrs: {\r\n label: {\r\n text: truncateText(table.name, 20),\r\n refX: 0.5,\r\n refY: 10,\r\n textAnchor: \"middle\",\r\n title: table.name,\r\n },\r\n },\r\n ports: createPortConfig(table),\r\n});\r\n\r\n// ==================== 节点注册 ====================\r\nconst registerNodes = () => {\r\n if (!graph.value) return;\r\n\r\n Graph.registerPortLayout(\r\n \"erPortPosition\",\r\n (portsPositionArgs) =>\r\n portsPositionArgs.map((_, index) => ({\r\n position: { x: 0, y: (index + 1) * 24 },\r\n angle: 0,\r\n })),\r\n true,\r\n );\r\n\r\n Graph.registerNode(\r\n \"er-rect\",\r\n {\r\n inherit: \"rect\",\r\n markup: [\r\n { tagName: \"rect\", selector: \"body\" },\r\n { tagName: \"text\", selector: \"label\" },\r\n ],\r\n attrs: {\r\n rect: { strokeWidth: 1, stroke: \"#5F95FF\", fill: \"#5F95FF\" },\r\n label: { fontWeight: \"bold\", fill: \"#ffffff\", fontSize: 12 },\r\n },\r\n ports: {\r\n groups: {\r\n list: {\r\n markup: [\r\n { tagName: \"rect\", selector: \"portBody\" },\r\n { tagName: \"text\", selector: \"portNameLabel\" },\r\n { tagName: \"text\", selector: \"portTypeLabel\" },\r\n ],\r\n attrs: {\r\n portBody: {\r\n width: 200,\r\n height: 24,\r\n strokeWidth: 1,\r\n stroke: \"#5F95FF\",\r\n fill: \"#EFF4FF\",\r\n magnet: true,\r\n },\r\n portNameLabel: {\r\n ref: \"portBody\",\r\n refX: 6,\r\n refY: 6,\r\n fontSize: 9,\r\n textAnchor: \"start\",\r\n textOverflow: \"ellipsis\",\r\n },\r\n portTypeLabel: {\r\n ref: \"portBody\",\r\n refX: 120,\r\n refY: 6,\r\n fontSize: 9,\r\n textAnchor: \"start\",\r\n fill: \"#666\",\r\n },\r\n },\r\n position: \"erPortPosition\",\r\n },\r\n },\r\n },\r\n },\r\n true,\r\n );\r\n};\r\n\r\n// ==================== 删除模式 ====================\r\nconst toggleDeleteMode = () => {\r\n deleteMode.value = !deleteMode.value;\r\n if (!deleteMode.value) resetEdgeStyles();\r\n};\r\n\r\nconst resetEdgeStyles = () => {\r\n graph.value?.getEdges().forEach((edge) => {\r\n edge.attr(\"line/stroke\", \"#A2B1C3\");\r\n edge.attr(\"line/strokeWidth\", 2);\r\n });\r\n};\r\n\r\n// ==================== 表 CRUD ====================\r\nconst createTableNode = (table: ERTable) => {\r\n if (!graph.value) return;\r\n const node = graph.value.createNode(createNodeConfig(table));\r\n graph.value.resetCells([node, ...graph.value.getCells()]);\r\n return node;\r\n};\r\n\r\nconst findPosition = () => {\r\n const nodes = graph.value?.getNodes() || [];\r\n const spacing = 250;\r\n for (let row = 0; row < 10; row++) {\r\n for (let col = 0; col < 3; col++) {\r\n const pos = { x: col * spacing + 50, y: row * spacing + 50 };\r\n const hasOverlap = nodes.some((node) => {\r\n const nodePos = node.getPosition();\r\n return (\r\n Math.abs(nodePos.x - pos.x) < spacing * 0.8 &&\r\n Math.abs(nodePos.y - pos.y) < spacing * 0.8\r\n );\r\n });\r\n if (!hasOverlap) return pos;\r\n }\r\n }\r\n return { x: 50, y: 50 };\r\n};\r\n\r\nconst addTable = () => {\r\n const newTable: ERTable = {\r\n id: `table_${Date.now()}`,\r\n name: \"新表\",\r\n comment: \"\",\r\n fields: [\r\n {\r\n name: \"id\",\r\n type: \"BIGINT\",\r\n isPrimaryKey: true,\r\n isRequired: true,\r\n isForeignKey: false,\r\n comment: \"主键\",\r\n },\r\n {\r\n name: \"name\",\r\n type: \"VARCHAR(100)\",\r\n isPrimaryKey: false,\r\n isRequired: true,\r\n isForeignKey: false,\r\n comment: \"名称\",\r\n },\r\n ],\r\n position: findPosition(),\r\n };\r\n createTableNode(newTable);\r\n editTable(newTable);\r\n emitDataChange();\r\n};\r\n\r\nconst editTable = (table: ERTable) => {\r\n editingTable.value = {\r\n ...table,\r\n fields: table.fields?.map((field) => ({ ...field })) || [],\r\n };\r\n showEditor.value = true;\r\n};\r\n\r\nconst saveTable = () => {\r\n if (!graph.value || !editingTable.value) return;\r\n const node = graph.value.getCellById(editingTable.value.id) as Node;\r\n if (node) {\r\n node.setData(editingTable.value);\r\n node.prop({\r\n size: {\r\n width: 200,\r\n height: 24 + editingTable.value.fields.length * 24,\r\n },\r\n attrs: {\r\n label: {\r\n text: truncateText(editingTable.value.name, 20),\r\n title: editingTable.value.name,\r\n },\r\n },\r\n ports: createPortConfig(editingTable.value),\r\n });\r\n }\r\n showEditor.value = false;\r\n emitDataChange();\r\n};\r\n\r\n// ==================== 字段操作(由子组件事件触发) ====================\r\nconst handlePrimaryKey = (field: ERField, isPrimaryKey: boolean) => {\r\n if (!isPrimaryKey) return;\r\n field.isRequired = true;\r\n editingTable.value?.fields.forEach((f) => {\r\n if (f !== field) f.isPrimaryKey = false;\r\n });\r\n};\r\n\r\nconst addField = () => {\r\n editingTable.value?.fields.push({\r\n name: `field_${(editingTable.value?.fields.length || 0) + 1}`,\r\n type: \"VARCHAR(100)\",\r\n isPrimaryKey: false,\r\n isRequired: false,\r\n isForeignKey: false,\r\n comment: \"\",\r\n });\r\n};\r\n\r\nconst removeField = (index: number) => {\r\n if (editingTable.value && editingTable.value.fields.length > 1)\r\n editingTable.value.fields.splice(index, 1);\r\n};\r\n\r\n// ==================== 数据获取 ====================\r\nconst getCurrentData = (): ERDiagramData => {\r\n if (!graph.value) return { tables: [], relations: [] };\r\n\r\n const tables = graph.value.getNodes().map((node: any) => ({\r\n ...node.getData(),\r\n position: node.getPosition(),\r\n }));\r\n\r\n const relations: ERRelation[] = [];\r\n graph.value.getEdges().forEach((edge: any) => {\r\n const source = edge.getSourceNode();\r\n const target = edge.getTargetNode();\r\n const sourcePort = edge.getSourcePortId();\r\n const targetPort = edge.getTargetPortId();\r\n if (source && target && sourcePort && targetPort) {\r\n relations.push({\r\n id: edge.id,\r\n type: \"foreign-key\",\r\n sourceTable: source.id,\r\n sourceField: sourcePort.split(\"_\").slice(1).join(\"_\"),\r\n targetTable: target.id,\r\n targetField: targetPort.split(\"_\").slice(1).join(\"_\"),\r\n name: `${source.getData()?.name || source.id} -> ${target.getData()?.name || target.id}`,\r\n });\r\n }\r\n });\r\n return { tables, relations };\r\n};\r\n\r\nconst emitDataChange = () => emit(\"data-change\", getCurrentData());\r\n\r\n// ==================== Graph 事件绑定 ====================\r\nwatch(\r\n graph,\r\n (newGraph) => {\r\n if (!(newGraph instanceof Graph)) return;\r\n\r\n registerNodes();\r\n newGraph.on(\"node:dblclick\", ({ node }) => {\r\n if (!props.readonly) editTable(node.getData() as ERTable);\r\n });\r\n newGraph.on(\"edge:connected\", emitDataChange);\r\n newGraph.on(\"edge:removed\", emitDataChange);\r\n\r\n let selectedEdge: Edge | null = null;\r\n\r\n newGraph.on(\"edge:click\", ({ edge }) => {\r\n if (deleteMode.value) {\r\n edge.remove();\r\n emitDataChange();\r\n } else {\r\n resetEdgeStyles();\r\n edge.attr(\"line/stroke\", \"#ff4d4f\");\r\n edge.attr(\"line/strokeWidth\", 3);\r\n selectedEdge = edge;\r\n }\r\n });\r\n newGraph.on(\"edge:dblclick\", ({ edge }) => {\r\n edge.remove();\r\n emitDataChange();\r\n });\r\n newGraph.on(\"blank:click\", () => {\r\n selectedEdge = null;\r\n if (!deleteMode.value) resetEdgeStyles();\r\n });\r\n\r\n const handleKeyDown = (e: KeyboardEvent) => {\r\n // 忽略来自 input/textarea/contenteditable 元素的按键\r\n const tag = (e.target as HTMLElement)?.tagName;\r\n if (\r\n tag === \"INPUT\" ||\r\n tag === \"TEXTAREA\" ||\r\n (e.target as HTMLElement)?.isContentEditable\r\n )\r\n return;\r\n\r\n if ((e.key === \"Delete\" || e.key === \"Backspace\") && selectedEdge) {\r\n selectedEdge.remove();\r\n emitDataChange();\r\n selectedEdge = null;\r\n }\r\n };\r\n document.addEventListener(\"keydown\", handleKeyDown);\r\n onUnmounted(() => document.removeEventListener(\"keydown\", handleKeyDown));\r\n\r\n emit(\"ready\", newGraph);\r\n\r\n nextTick(() => {\r\n if (props.data?.tables) {\r\n const cells: Cell[] = props.data.tables.map((table) =>\r\n newGraph.createNode(createNodeConfig(table)),\r\n );\r\n\r\n if (props.data.relations?.length) {\r\n props.data.relations.forEach((relation) => {\r\n cells.push(\r\n newGraph.createEdge({\r\n source: {\r\n cell: relation.sourceTable,\r\n port: `${relation.sourceTable}_${relation.sourceField}`,\r\n },\r\n target: {\r\n cell: relation.targetTable,\r\n port: `${relation.targetTable}_${relation.targetField}`,\r\n },\r\n attrs: { line: { stroke: \"#A2B1C3\", strokeWidth: 2 } },\r\n }),\r\n );\r\n });\r\n }\r\n\r\n newGraph.resetCells(cells);\r\n setTimeout(() => newGraph.zoomToFit({ padding: 20, maxScale: 1 }), 300);\r\n }\r\n });\r\n },\r\n { immediate: true },\r\n);\r\n\r\nwatch(\r\n () => props.data,\r\n (newData) => {\r\n if (graph.value && newData?.tables) {\r\n const cells: Cell[] = newData.tables.map((table) =>\r\n graph.value!.createNode(createNodeConfig(table)),\r\n );\r\n\r\n // 加载关系连线\r\n if (newData.relations?.length) {\r\n newData.relations.forEach((relation) => {\r\n cells.push(\r\n graph.value!.createEdge({\r\n source: {\r\n cell: relation.sourceTable,\r\n port: `${relation.sourceTable}_${relation.sourceField}`,\r\n },\r\n target: {\r\n cell: relation.targetTable,\r\n port: `${relation.targetTable}_${relation.targetField}`,\r\n },\r\n attrs: { line: { stroke: \"#A2B1C3\", strokeWidth: 2 } },\r\n }),\r\n );\r\n });\r\n }\r\n\r\n graph.value.resetCells(cells);\r\n }\r\n },\r\n { deep: true },\r\n);\r\n\r\nonMounted(() => initGraph());\r\n\r\ndefineExpose({\r\n getGraph: () => graph.value ?? undefined,\r\n getData: getCurrentData,\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","/* unplugin-vue-components disabled */<template>\r\n <div class=\"er-layout\">\r\n <!-- 工具栏 -->\r\n <div class=\"toolbar\" v-if=\"showToolbar\">\r\n <NSpace>\r\n <NButton @click=\"addTable\" type=\"primary\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:table-plus\" :size=\"16\" />\r\n </template>\r\n 添加表\r\n </NButton>\r\n <NButton @click=\"centerContent\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:image-filter-center-focus\" :size=\"16\" />\r\n </template>\r\n 居中\r\n </NButton>\r\n <NButton @click=\"zoomToFit\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:fit-to-screen\" :size=\"16\" />\r\n </template>\r\n 适应\r\n </NButton>\r\n <NButton\r\n @click=\"toggleDeleteMode\"\r\n :type=\"deleteMode ? 'error' : 'default'\"\r\n size=\"small\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete\" :size=\"16\" />\r\n </template>\r\n {{ deleteMode ? \"退出删除\" : \"删除连线\" }}\r\n </NButton>\r\n <NDropdown\r\n :options=\"exportOptions\"\r\n @select=\"(key: string) => handleExport(key, getCurrentData)\"\r\n >\r\n <NButton size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:export\" :size=\"16\" />\r\n </template>\r\n 导出\r\n </NButton>\r\n </NDropdown>\r\n </NSpace>\r\n\r\n <div v-if=\"deleteMode\" style=\"margin-top: 8px\">\r\n <NAlert type=\"info\" size=\"small\" :show-icon=\"false\">\r\n 删除模式:点击连接线即可删除\r\n </NAlert>\r\n </div>\r\n </div>\r\n\r\n <!-- 图表容器 -->\r\n <div ref=\"containerRef\" class=\"graph-container\"></div>\r\n\r\n <!-- 表编辑器 -->\r\n <ERTableEditor\r\n :show=\"showEditor\"\r\n v-model:editing-table=\"editingTable\"\r\n @update:show=\"showEditor = $event\"\r\n @save=\"saveTable\"\r\n @add-field=\"addField\"\r\n @remove-field=\"removeField\"\r\n @handle-primary-key=\"handlePrimaryKey\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, nextTick, onMounted, onUnmounted } from \"vue\";\r\nimport { NSpace, NButton, NDropdown, NAlert } from \"naive-ui\";\r\nimport { Node, Graph, Cell, Edge } from \"@antv/x6\";\r\nimport { useGraphBase } from \"../../composables/useGraphBase\";\r\nimport { useGraphExport } from \"../../composables/useGraphExport\";\r\nimport type { ERTable, ERField, ERDiagramData, ERRelation } from \"../../types\";\r\nimport ERTableEditor from \"./components/ERTableEditor.vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\ninterface Props {\r\n data?: ERDiagramData;\r\n showToolbar?: boolean;\r\n readonly?: boolean;\r\n theme?: \"light\" | \"dark\";\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n showToolbar: true,\r\n readonly: false,\r\n});\r\n\r\nconst emit = defineEmits<{\r\n (e: \"ready\", graph: Graph): void;\r\n (e: \"data-change\", data: ERDiagramData): void;\r\n}>();\r\n\r\n// ==================== Composables ====================\r\nconst containerRef = ref<HTMLDivElement>();\r\nconst isDark = computed(() => props.theme === \"dark\");\r\nconst { graph, initGraph, centerContent, zoomToFit } = useGraphBase(\r\n containerRef,\r\n isDark,\r\n);\r\nconst { exportOptions, handleExport } = useGraphExport(graph, \"er-diagram\");\r\n\r\n// ==================== 编辑器状态 ====================\r\nconst showEditor = ref(false);\r\nconst editingTable = ref<ERTable>();\r\nconst deleteMode = ref(false);\r\n\r\n// ==================== 工具函数 ====================\r\nconst truncateText = (text: string, maxLength: number) =>\r\n text.length > maxLength ? text.substring(0, maxLength - 1) + \"..\" : text;\r\n\r\nconst createPortConfig = (table: ERTable) =>\r\n table.fields?.map((field) => {\r\n const displayName = field.isPrimaryKey\r\n ? `🔑 ${field.name}`\r\n : field.isRequired\r\n ? `* ${field.name}`\r\n : field.name;\r\n return {\r\n id: `${table.id}_${field.name}`,\r\n group: \"list\",\r\n attrs: {\r\n portNameLabel: {\r\n text: truncateText(displayName, 12),\r\n title: displayName,\r\n },\r\n portTypeLabel: {\r\n text: truncateText(field.type, 10),\r\n title: field.type,\r\n },\r\n portBody: { fill: field.isPrimaryKey ? \"#FFF7E6\" : \"#EFF4FF\" },\r\n },\r\n };\r\n }) || [];\r\n\r\nconst createNodeConfig = (table: ERTable) => ({\r\n id: table.id,\r\n shape: \"er-rect\",\r\n x: table.position.x,\r\n y: table.position.y,\r\n width: 200,\r\n height: 24 + (table.fields?.length || 0) * 24,\r\n data: table,\r\n attrs: {\r\n label: {\r\n text: truncateText(table.name, 20),\r\n refX: 0.5,\r\n refY: 10,\r\n textAnchor: \"middle\",\r\n title: table.name,\r\n },\r\n },\r\n ports: createPortConfig(table),\r\n});\r\n\r\n// ==================== 节点注册 ====================\r\nconst registerNodes = () => {\r\n if (!graph.value) return;\r\n\r\n Graph.registerPortLayout(\r\n \"erPortPosition\",\r\n (portsPositionArgs) =>\r\n portsPositionArgs.map((_, index) => ({\r\n position: { x: 0, y: (index + 1) * 24 },\r\n angle: 0,\r\n })),\r\n true,\r\n );\r\n\r\n Graph.registerNode(\r\n \"er-rect\",\r\n {\r\n inherit: \"rect\",\r\n markup: [\r\n { tagName: \"rect\", selector: \"body\" },\r\n { tagName: \"text\", selector: \"label\" },\r\n ],\r\n attrs: {\r\n rect: { strokeWidth: 1, stroke: \"#5F95FF\", fill: \"#5F95FF\" },\r\n label: { fontWeight: \"bold\", fill: \"#ffffff\", fontSize: 12 },\r\n },\r\n ports: {\r\n groups: {\r\n list: {\r\n markup: [\r\n { tagName: \"rect\", selector: \"portBody\" },\r\n { tagName: \"text\", selector: \"portNameLabel\" },\r\n { tagName: \"text\", selector: \"portTypeLabel\" },\r\n ],\r\n attrs: {\r\n portBody: {\r\n width: 200,\r\n height: 24,\r\n strokeWidth: 1,\r\n stroke: \"#5F95FF\",\r\n fill: \"#EFF4FF\",\r\n magnet: true,\r\n },\r\n portNameLabel: {\r\n ref: \"portBody\",\r\n refX: 6,\r\n refY: 6,\r\n fontSize: 9,\r\n textAnchor: \"start\",\r\n textOverflow: \"ellipsis\",\r\n },\r\n portTypeLabel: {\r\n ref: \"portBody\",\r\n refX: 120,\r\n refY: 6,\r\n fontSize: 9,\r\n textAnchor: \"start\",\r\n fill: \"#666\",\r\n },\r\n },\r\n position: \"erPortPosition\",\r\n },\r\n },\r\n },\r\n },\r\n true,\r\n );\r\n};\r\n\r\n// ==================== 删除模式 ====================\r\nconst toggleDeleteMode = () => {\r\n deleteMode.value = !deleteMode.value;\r\n if (!deleteMode.value) resetEdgeStyles();\r\n};\r\n\r\nconst resetEdgeStyles = () => {\r\n graph.value?.getEdges().forEach((edge) => {\r\n edge.attr(\"line/stroke\", \"#A2B1C3\");\r\n edge.attr(\"line/strokeWidth\", 2);\r\n });\r\n};\r\n\r\n// ==================== 表 CRUD ====================\r\nconst createTableNode = (table: ERTable) => {\r\n if (!graph.value) return;\r\n const node = graph.value.createNode(createNodeConfig(table));\r\n graph.value.resetCells([node, ...graph.value.getCells()]);\r\n return node;\r\n};\r\n\r\nconst findPosition = () => {\r\n const nodes = graph.value?.getNodes() || [];\r\n const spacing = 250;\r\n for (let row = 0; row < 10; row++) {\r\n for (let col = 0; col < 3; col++) {\r\n const pos = { x: col * spacing + 50, y: row * spacing + 50 };\r\n const hasOverlap = nodes.some((node) => {\r\n const nodePos = node.getPosition();\r\n return (\r\n Math.abs(nodePos.x - pos.x) < spacing * 0.8 &&\r\n Math.abs(nodePos.y - pos.y) < spacing * 0.8\r\n );\r\n });\r\n if (!hasOverlap) return pos;\r\n }\r\n }\r\n return { x: 50, y: 50 };\r\n};\r\n\r\nconst addTable = () => {\r\n const newTable: ERTable = {\r\n id: `table_${Date.now()}`,\r\n name: \"新表\",\r\n comment: \"\",\r\n fields: [\r\n {\r\n name: \"id\",\r\n type: \"BIGINT\",\r\n isPrimaryKey: true,\r\n isRequired: true,\r\n isForeignKey: false,\r\n comment: \"主键\",\r\n },\r\n {\r\n name: \"name\",\r\n type: \"VARCHAR(100)\",\r\n isPrimaryKey: false,\r\n isRequired: true,\r\n isForeignKey: false,\r\n comment: \"名称\",\r\n },\r\n ],\r\n position: findPosition(),\r\n };\r\n createTableNode(newTable);\r\n editTable(newTable);\r\n emitDataChange();\r\n};\r\n\r\nconst editTable = (table: ERTable) => {\r\n editingTable.value = {\r\n ...table,\r\n fields: table.fields?.map((field) => ({ ...field })) || [],\r\n };\r\n showEditor.value = true;\r\n};\r\n\r\nconst saveTable = () => {\r\n if (!graph.value || !editingTable.value) return;\r\n const node = graph.value.getCellById(editingTable.value.id) as Node;\r\n if (node) {\r\n node.setData(editingTable.value);\r\n node.prop({\r\n size: {\r\n width: 200,\r\n height: 24 + editingTable.value.fields.length * 24,\r\n },\r\n attrs: {\r\n label: {\r\n text: truncateText(editingTable.value.name, 20),\r\n title: editingTable.value.name,\r\n },\r\n },\r\n ports: createPortConfig(editingTable.value),\r\n });\r\n }\r\n showEditor.value = false;\r\n emitDataChange();\r\n};\r\n\r\n// ==================== 字段操作(由子组件事件触发) ====================\r\nconst handlePrimaryKey = (field: ERField, isPrimaryKey: boolean) => {\r\n if (!isPrimaryKey) return;\r\n field.isRequired = true;\r\n editingTable.value?.fields.forEach((f) => {\r\n if (f !== field) f.isPrimaryKey = false;\r\n });\r\n};\r\n\r\nconst addField = () => {\r\n editingTable.value?.fields.push({\r\n name: `field_${(editingTable.value?.fields.length || 0) + 1}`,\r\n type: \"VARCHAR(100)\",\r\n isPrimaryKey: false,\r\n isRequired: false,\r\n isForeignKey: false,\r\n comment: \"\",\r\n });\r\n};\r\n\r\nconst removeField = (index: number) => {\r\n if (editingTable.value && editingTable.value.fields.length > 1)\r\n editingTable.value.fields.splice(index, 1);\r\n};\r\n\r\n// ==================== 数据获取 ====================\r\nconst getCurrentData = (): ERDiagramData => {\r\n if (!graph.value) return { tables: [], relations: [] };\r\n\r\n const tables = graph.value.getNodes().map((node: any) => ({\r\n ...node.getData(),\r\n position: node.getPosition(),\r\n }));\r\n\r\n const relations: ERRelation[] = [];\r\n graph.value.getEdges().forEach((edge: any) => {\r\n const source = edge.getSourceNode();\r\n const target = edge.getTargetNode();\r\n const sourcePort = edge.getSourcePortId();\r\n const targetPort = edge.getTargetPortId();\r\n if (source && target && sourcePort && targetPort) {\r\n relations.push({\r\n id: edge.id,\r\n type: \"foreign-key\",\r\n sourceTable: source.id,\r\n sourceField: sourcePort.split(\"_\").slice(1).join(\"_\"),\r\n targetTable: target.id,\r\n targetField: targetPort.split(\"_\").slice(1).join(\"_\"),\r\n name: `${source.getData()?.name || source.id} -> ${target.getData()?.name || target.id}`,\r\n });\r\n }\r\n });\r\n return { tables, relations };\r\n};\r\n\r\nconst emitDataChange = () => emit(\"data-change\", getCurrentData());\r\n\r\n// ==================== Graph 事件绑定 ====================\r\nwatch(\r\n graph,\r\n (newGraph) => {\r\n if (!(newGraph instanceof Graph)) return;\r\n\r\n registerNodes();\r\n newGraph.on(\"node:dblclick\", ({ node }) => {\r\n if (!props.readonly) editTable(node.getData() as ERTable);\r\n });\r\n newGraph.on(\"edge:connected\", emitDataChange);\r\n newGraph.on(\"edge:removed\", emitDataChange);\r\n\r\n let selectedEdge: Edge | null = null;\r\n\r\n newGraph.on(\"edge:click\", ({ edge }) => {\r\n if (deleteMode.value) {\r\n edge.remove();\r\n emitDataChange();\r\n } else {\r\n resetEdgeStyles();\r\n edge.attr(\"line/stroke\", \"#ff4d4f\");\r\n edge.attr(\"line/strokeWidth\", 3);\r\n selectedEdge = edge;\r\n }\r\n });\r\n newGraph.on(\"edge:dblclick\", ({ edge }) => {\r\n edge.remove();\r\n emitDataChange();\r\n });\r\n newGraph.on(\"blank:click\", () => {\r\n selectedEdge = null;\r\n if (!deleteMode.value) resetEdgeStyles();\r\n });\r\n\r\n const handleKeyDown = (e: KeyboardEvent) => {\r\n // 忽略来自 input/textarea/contenteditable 元素的按键\r\n const tag = (e.target as HTMLElement)?.tagName;\r\n if (\r\n tag === \"INPUT\" ||\r\n tag === \"TEXTAREA\" ||\r\n (e.target as HTMLElement)?.isContentEditable\r\n )\r\n return;\r\n\r\n if ((e.key === \"Delete\" || e.key === \"Backspace\") && selectedEdge) {\r\n selectedEdge.remove();\r\n emitDataChange();\r\n selectedEdge = null;\r\n }\r\n };\r\n document.addEventListener(\"keydown\", handleKeyDown);\r\n onUnmounted(() => document.removeEventListener(\"keydown\", handleKeyDown));\r\n\r\n emit(\"ready\", newGraph);\r\n\r\n nextTick(() => {\r\n if (props.data?.tables) {\r\n const cells: Cell[] = props.data.tables.map((table) =>\r\n newGraph.createNode(createNodeConfig(table)),\r\n );\r\n\r\n if (props.data.relations?.length) {\r\n props.data.relations.forEach((relation) => {\r\n cells.push(\r\n newGraph.createEdge({\r\n source: {\r\n cell: relation.sourceTable,\r\n port: `${relation.sourceTable}_${relation.sourceField}`,\r\n },\r\n target: {\r\n cell: relation.targetTable,\r\n port: `${relation.targetTable}_${relation.targetField}`,\r\n },\r\n attrs: { line: { stroke: \"#A2B1C3\", strokeWidth: 2 } },\r\n }),\r\n );\r\n });\r\n }\r\n\r\n newGraph.resetCells(cells);\r\n setTimeout(() => newGraph.zoomToFit({ padding: 20, maxScale: 1 }), 300);\r\n }\r\n });\r\n },\r\n { immediate: true },\r\n);\r\n\r\nwatch(\r\n () => props.data,\r\n (newData) => {\r\n if (graph.value && newData?.tables) {\r\n const cells: Cell[] = newData.tables.map((table) =>\r\n graph.value!.createNode(createNodeConfig(table)),\r\n );\r\n\r\n // 加载关系连线\r\n if (newData.relations?.length) {\r\n newData.relations.forEach((relation) => {\r\n cells.push(\r\n graph.value!.createEdge({\r\n source: {\r\n cell: relation.sourceTable,\r\n port: `${relation.sourceTable}_${relation.sourceField}`,\r\n },\r\n target: {\r\n cell: relation.targetTable,\r\n port: `${relation.targetTable}_${relation.targetField}`,\r\n },\r\n attrs: { line: { stroke: \"#A2B1C3\", strokeWidth: 2 } },\r\n }),\r\n );\r\n });\r\n }\r\n\r\n graph.value.resetCells(cells);\r\n }\r\n },\r\n { deep: true },\r\n);\r\n\r\nonMounted(() => initGraph());\r\n\r\ndefineExpose({\r\n getGraph: () => graph.value ?? undefined,\r\n getData: getCurrentData,\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<template>\r\n <div class=\"er-layout\">\r\n <!-- 工具栏 -->\r\n <div class=\"toolbar\" v-if=\"showToolbar\">\r\n <NSpace>\r\n <NButton @click=\"addTable\" type=\"primary\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:table-plus\" :size=\"16\" />\r\n </template>\r\n 添加表\r\n </NButton>\r\n <NButton @click=\"centerContent\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:image-filter-center-focus\" :size=\"16\" />\r\n </template>\r\n 居中\r\n </NButton>\r\n <NButton @click=\"zoomToFit\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:fit-to-screen\" :size=\"16\" />\r\n </template>\r\n 适应\r\n </NButton>\r\n <NButton\r\n @click=\"toggleDeleteMode\"\r\n :type=\"deleteMode ? 'error' : 'default'\"\r\n size=\"small\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete\" :size=\"16\" />\r\n </template>\r\n {{ deleteMode ? \"退出删除\" : \"删除连线\" }}\r\n </NButton>\r\n <NDropdown\r\n :options=\"exportOptions\"\r\n @select=\"(key: string) => handleExport(key, getCurrentData)\"\r\n >\r\n <NButton size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:export\" :size=\"16\" />\r\n </template>\r\n 导出\r\n </NButton>\r\n </NDropdown>\r\n </NSpace>\r\n\r\n <div v-if=\"deleteMode\" style=\"margin-top: 8px\">\r\n <NAlert type=\"info\" size=\"small\" :show-icon=\"false\">\r\n 删除模式:点击连接线即可删除\r\n </NAlert>\r\n </div>\r\n </div>\r\n\r\n <!-- 图表容器 -->\r\n <div ref=\"containerRef\" class=\"graph-container\"></div>\r\n\r\n <!-- 表编辑器 -->\r\n <ERTableEditor\r\n :show=\"showEditor\"\r\n v-model:editing-table=\"editingTable\"\r\n @update:show=\"showEditor = $event\"\r\n @save=\"saveTable\"\r\n @add-field=\"addField\"\r\n @remove-field=\"removeField\"\r\n @handle-primary-key=\"handlePrimaryKey\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, nextTick, onMounted, onUnmounted } from \"vue\";\r\nimport { NSpace, NButton, NDropdown, NAlert } from \"naive-ui\";\r\nimport { Node, Graph, Cell, Edge } from \"@antv/x6\";\r\nimport { useGraphBase } from \"../../composables/useGraphBase\";\r\nimport { useGraphExport } from \"../../composables/useGraphExport\";\r\nimport type { ERTable, ERField, ERDiagramData, ERRelation } from \"../../types\";\r\nimport ERTableEditor from \"./components/ERTableEditor.vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\n\r\ninterface Props {\r\n data?: ERDiagramData;\r\n showToolbar?: boolean;\r\n readonly?: boolean;\r\n theme?: \"light\" | \"dark\";\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n showToolbar: true,\r\n readonly: false,\r\n});\r\n\r\nconst emit = defineEmits<{\r\n (e: \"ready\", graph: Graph): void;\r\n (e: \"data-change\", data: ERDiagramData): void;\r\n}>();\r\n\r\n// ==================== Composables ====================\r\nconst containerRef = ref<HTMLDivElement>();\r\nconst isDark = computed(() => props.theme === \"dark\");\r\nconst { graph, initGraph, centerContent, zoomToFit } = useGraphBase(\r\n containerRef,\r\n isDark,\r\n);\r\nconst { exportOptions, handleExport } = useGraphExport(graph, \"er-diagram\");\r\n\r\n// ==================== 编辑器状态 ====================\r\nconst showEditor = ref(false);\r\nconst editingTable = ref<ERTable>();\r\nconst deleteMode = ref(false);\r\n\r\n// ==================== 工具函数 ====================\r\nconst truncateText = (text: string, maxLength: number) =>\r\n text.length > maxLength ? text.substring(0, maxLength - 1) + \"..\" : text;\r\n\r\nconst createPortConfig = (table: ERTable) =>\r\n table.fields?.map((field) => {\r\n const displayName = field.isPrimaryKey\r\n ? `🔑 ${field.name}`\r\n : field.isRequired\r\n ? `* ${field.name}`\r\n : field.name;\r\n return {\r\n id: `${table.id}_${field.name}`,\r\n group: \"list\",\r\n attrs: {\r\n portNameLabel: {\r\n text: truncateText(displayName, 12),\r\n title: displayName,\r\n },\r\n portTypeLabel: {\r\n text: truncateText(field.type, 10),\r\n title: field.type,\r\n },\r\n portBody: { fill: field.isPrimaryKey ? \"#FFF7E6\" : \"#EFF4FF\" },\r\n },\r\n };\r\n }) || [];\r\n\r\nconst createNodeConfig = (table: ERTable) => ({\r\n id: table.id,\r\n shape: \"er-rect\",\r\n x: table.position.x,\r\n y: table.position.y,\r\n width: 200,\r\n height: 24 + (table.fields?.length || 0) * 24,\r\n data: table,\r\n attrs: {\r\n label: {\r\n text: truncateText(table.name, 20),\r\n refX: 0.5,\r\n refY: 10,\r\n textAnchor: \"middle\",\r\n title: table.name,\r\n },\r\n },\r\n ports: createPortConfig(table),\r\n});\r\n\r\n// ==================== 节点注册 ====================\r\nconst registerNodes = () => {\r\n if (!graph.value) return;\r\n\r\n Graph.registerPortLayout(\r\n \"erPortPosition\",\r\n (portsPositionArgs) =>\r\n portsPositionArgs.map((_, index) => ({\r\n position: { x: 0, y: (index + 1) * 24 },\r\n angle: 0,\r\n })),\r\n true,\r\n );\r\n\r\n Graph.registerNode(\r\n \"er-rect\",\r\n {\r\n inherit: \"rect\",\r\n markup: [\r\n { tagName: \"rect\", selector: \"body\" },\r\n { tagName: \"text\", selector: \"label\" },\r\n ],\r\n attrs: {\r\n rect: { strokeWidth: 1, stroke: \"#5F95FF\", fill: \"#5F95FF\" },\r\n label: { fontWeight: \"bold\", fill: \"#ffffff\", fontSize: 12 },\r\n },\r\n ports: {\r\n groups: {\r\n list: {\r\n markup: [\r\n { tagName: \"rect\", selector: \"portBody\" },\r\n { tagName: \"text\", selector: \"portNameLabel\" },\r\n { tagName: \"text\", selector: \"portTypeLabel\" },\r\n ],\r\n attrs: {\r\n portBody: {\r\n width: 200,\r\n height: 24,\r\n strokeWidth: 1,\r\n stroke: \"#5F95FF\",\r\n fill: \"#EFF4FF\",\r\n magnet: true,\r\n },\r\n portNameLabel: {\r\n ref: \"portBody\",\r\n refX: 6,\r\n refY: 6,\r\n fontSize: 9,\r\n textAnchor: \"start\",\r\n textOverflow: \"ellipsis\",\r\n },\r\n portTypeLabel: {\r\n ref: \"portBody\",\r\n refX: 120,\r\n refY: 6,\r\n fontSize: 9,\r\n textAnchor: \"start\",\r\n fill: \"#666\",\r\n },\r\n },\r\n position: \"erPortPosition\",\r\n },\r\n },\r\n },\r\n },\r\n true,\r\n );\r\n};\r\n\r\n// ==================== 删除模式 ====================\r\nconst toggleDeleteMode = () => {\r\n deleteMode.value = !deleteMode.value;\r\n if (!deleteMode.value) resetEdgeStyles();\r\n};\r\n\r\nconst resetEdgeStyles = () => {\r\n graph.value?.getEdges().forEach((edge) => {\r\n edge.attr(\"line/stroke\", \"#A2B1C3\");\r\n edge.attr(\"line/strokeWidth\", 2);\r\n });\r\n};\r\n\r\n// ==================== 表 CRUD ====================\r\nconst createTableNode = (table: ERTable) => {\r\n if (!graph.value) return;\r\n const node = graph.value.createNode(createNodeConfig(table));\r\n graph.value.resetCells([node, ...graph.value.getCells()]);\r\n return node;\r\n};\r\n\r\nconst findPosition = () => {\r\n const nodes = graph.value?.getNodes() || [];\r\n const spacing = 250;\r\n for (let row = 0; row < 10; row++) {\r\n for (let col = 0; col < 3; col++) {\r\n const pos = { x: col * spacing + 50, y: row * spacing + 50 };\r\n const hasOverlap = nodes.some((node) => {\r\n const nodePos = node.getPosition();\r\n return (\r\n Math.abs(nodePos.x - pos.x) < spacing * 0.8 &&\r\n Math.abs(nodePos.y - pos.y) < spacing * 0.8\r\n );\r\n });\r\n if (!hasOverlap) return pos;\r\n }\r\n }\r\n return { x: 50, y: 50 };\r\n};\r\n\r\nconst addTable = () => {\r\n const newTable: ERTable = {\r\n id: `table_${Date.now()}`,\r\n name: \"新表\",\r\n comment: \"\",\r\n fields: [\r\n {\r\n name: \"id\",\r\n type: \"BIGINT\",\r\n isPrimaryKey: true,\r\n isRequired: true,\r\n isForeignKey: false,\r\n comment: \"主键\",\r\n },\r\n {\r\n name: \"name\",\r\n type: \"VARCHAR(100)\",\r\n isPrimaryKey: false,\r\n isRequired: true,\r\n isForeignKey: false,\r\n comment: \"名称\",\r\n },\r\n ],\r\n position: findPosition(),\r\n };\r\n createTableNode(newTable);\r\n editTable(newTable);\r\n emitDataChange();\r\n};\r\n\r\nconst editTable = (table: ERTable) => {\r\n editingTable.value = {\r\n ...table,\r\n fields: table.fields?.map((field) => ({ ...field })) || [],\r\n };\r\n showEditor.value = true;\r\n};\r\n\r\nconst saveTable = () => {\r\n if (!graph.value || !editingTable.value) return;\r\n const node = graph.value.getCellById(editingTable.value.id) as Node;\r\n if (node) {\r\n node.setData(editingTable.value);\r\n node.prop({\r\n size: {\r\n width: 200,\r\n height: 24 + editingTable.value.fields.length * 24,\r\n },\r\n attrs: {\r\n label: {\r\n text: truncateText(editingTable.value.name, 20),\r\n title: editingTable.value.name,\r\n },\r\n },\r\n ports: createPortConfig(editingTable.value),\r\n });\r\n }\r\n showEditor.value = false;\r\n emitDataChange();\r\n};\r\n\r\n// ==================== 字段操作(由子组件事件触发) ====================\r\nconst handlePrimaryKey = (field: ERField, isPrimaryKey: boolean) => {\r\n if (!isPrimaryKey) return;\r\n field.isRequired = true;\r\n editingTable.value?.fields.forEach((f) => {\r\n if (f !== field) f.isPrimaryKey = false;\r\n });\r\n};\r\n\r\nconst addField = () => {\r\n editingTable.value?.fields.push({\r\n name: `field_${(editingTable.value?.fields.length || 0) + 1}`,\r\n type: \"VARCHAR(100)\",\r\n isPrimaryKey: false,\r\n isRequired: false,\r\n isForeignKey: false,\r\n comment: \"\",\r\n });\r\n};\r\n\r\nconst removeField = (index: number) => {\r\n if (editingTable.value && editingTable.value.fields.length > 1)\r\n editingTable.value.fields.splice(index, 1);\r\n};\r\n\r\n// ==================== 数据获取 ====================\r\nconst getCurrentData = (): ERDiagramData => {\r\n if (!graph.value) return { tables: [], relations: [] };\r\n\r\n const tables = graph.value.getNodes().map((node: any) => ({\r\n ...node.getData(),\r\n position: node.getPosition(),\r\n }));\r\n\r\n const relations: ERRelation[] = [];\r\n graph.value.getEdges().forEach((edge: any) => {\r\n const source = edge.getSourceNode();\r\n const target = edge.getTargetNode();\r\n const sourcePort = edge.getSourcePortId();\r\n const targetPort = edge.getTargetPortId();\r\n if (source && target && sourcePort && targetPort) {\r\n relations.push({\r\n id: edge.id,\r\n type: \"foreign-key\",\r\n sourceTable: source.id,\r\n sourceField: sourcePort.split(\"_\").slice(1).join(\"_\"),\r\n targetTable: target.id,\r\n targetField: targetPort.split(\"_\").slice(1).join(\"_\"),\r\n name: `${source.getData()?.name || source.id} -> ${target.getData()?.name || target.id}`,\r\n });\r\n }\r\n });\r\n return { tables, relations };\r\n};\r\n\r\nconst emitDataChange = () => emit(\"data-change\", getCurrentData());\r\n\r\n// ==================== Graph 事件绑定 ====================\r\nwatch(\r\n graph,\r\n (newGraph) => {\r\n if (!(newGraph instanceof Graph)) return;\r\n\r\n registerNodes();\r\n newGraph.on(\"node:dblclick\", ({ node }) => {\r\n if (!props.readonly) editTable(node.getData() as ERTable);\r\n });\r\n newGraph.on(\"edge:connected\", emitDataChange);\r\n newGraph.on(\"edge:removed\", emitDataChange);\r\n\r\n let selectedEdge: Edge | null = null;\r\n\r\n newGraph.on(\"edge:click\", ({ edge }) => {\r\n if (deleteMode.value) {\r\n edge.remove();\r\n emitDataChange();\r\n } else {\r\n resetEdgeStyles();\r\n edge.attr(\"line/stroke\", \"#ff4d4f\");\r\n edge.attr(\"line/strokeWidth\", 3);\r\n selectedEdge = edge;\r\n }\r\n });\r\n newGraph.on(\"edge:dblclick\", ({ edge }) => {\r\n edge.remove();\r\n emitDataChange();\r\n });\r\n newGraph.on(\"blank:click\", () => {\r\n selectedEdge = null;\r\n if (!deleteMode.value) resetEdgeStyles();\r\n });\r\n\r\n const handleKeyDown = (e: KeyboardEvent) => {\r\n // 忽略来自 input/textarea/contenteditable 元素的按键\r\n const tag = (e.target as HTMLElement)?.tagName;\r\n if (\r\n tag === \"INPUT\" ||\r\n tag === \"TEXTAREA\" ||\r\n (e.target as HTMLElement)?.isContentEditable\r\n )\r\n return;\r\n\r\n if ((e.key === \"Delete\" || e.key === \"Backspace\") && selectedEdge) {\r\n selectedEdge.remove();\r\n emitDataChange();\r\n selectedEdge = null;\r\n }\r\n };\r\n document.addEventListener(\"keydown\", handleKeyDown);\r\n onUnmounted(() => document.removeEventListener(\"keydown\", handleKeyDown));\r\n\r\n emit(\"ready\", newGraph);\r\n\r\n nextTick(() => {\r\n if (props.data?.tables) {\r\n const cells: Cell[] = props.data.tables.map((table) =>\r\n newGraph.createNode(createNodeConfig(table)),\r\n );\r\n\r\n if (props.data.relations?.length) {\r\n props.data.relations.forEach((relation) => {\r\n cells.push(\r\n newGraph.createEdge({\r\n source: {\r\n cell: relation.sourceTable,\r\n port: `${relation.sourceTable}_${relation.sourceField}`,\r\n },\r\n target: {\r\n cell: relation.targetTable,\r\n port: `${relation.targetTable}_${relation.targetField}`,\r\n },\r\n attrs: { line: { stroke: \"#A2B1C3\", strokeWidth: 2 } },\r\n }),\r\n );\r\n });\r\n }\r\n\r\n newGraph.resetCells(cells);\r\n setTimeout(() => newGraph.zoomToFit({ padding: 20, maxScale: 1 }), 300);\r\n }\r\n });\r\n },\r\n { immediate: true },\r\n);\r\n\r\nwatch(\r\n () => props.data,\r\n (newData) => {\r\n if (graph.value && newData?.tables) {\r\n const cells: Cell[] = newData.tables.map((table) =>\r\n graph.value!.createNode(createNodeConfig(table)),\r\n );\r\n\r\n // 加载关系连线\r\n if (newData.relations?.length) {\r\n newData.relations.forEach((relation) => {\r\n cells.push(\r\n graph.value!.createEdge({\r\n source: {\r\n cell: relation.sourceTable,\r\n port: `${relation.sourceTable}_${relation.sourceField}`,\r\n },\r\n target: {\r\n cell: relation.targetTable,\r\n port: `${relation.targetTable}_${relation.targetField}`,\r\n },\r\n attrs: { line: { stroke: \"#A2B1C3\", strokeWidth: 2 } },\r\n }),\r\n );\r\n });\r\n }\r\n\r\n graph.value.resetCells(cells);\r\n }\r\n },\r\n { deep: true },\r\n);\r\n\r\nonMounted(() => initGraph());\r\n\r\ndefineExpose({\r\n getGraph: () => graph.value ?? undefined,\r\n getData: getCurrentData,\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","import type { Ref } from 'vue'\r\nimport type { Graph, Node } from '@antv/x6'\r\n\r\nexport interface EdgeInteractionOptions {\r\n /** 连线默认颜色 */\r\n defaultColor?: string\r\n /** 连线高亮颜色 */\r\n highlightColor?: string\r\n /** 默认线宽 */\r\n strokeWidth?: number\r\n /** 高亮线宽 */\r\n highlightStrokeWidth?: number\r\n /** 端口位置名称列表(用于控制 mouseenter/leave 显隐) */\r\n portPositions?: string[]\r\n /** 数据变更回调 */\r\n onDataChange?: () => void\r\n}\r\n\r\nconst DEFAULT_OPTIONS: Required<EdgeInteractionOptions> = {\r\n defaultColor: '#A2B1C3',\r\n highlightColor: '#ff4d4f',\r\n strokeWidth: 2,\r\n highlightStrokeWidth: 3,\r\n portPositions: [],\r\n onDataChange: () => {},\r\n}\r\n\r\n/**\r\n * 图表交互 composable — 统一管理连线点击/高亮/删除 + 端口显隐\r\n * @param graph - X6 Graph 实例引用\r\n * @param options - 交互配置\r\n */\r\nexport function useEdgeInteraction(\r\n graph: Ref<Graph | null>,\r\n options: EdgeInteractionOptions = {}\r\n) {\r\n const config = { ...DEFAULT_OPTIONS, ...options }\r\n\r\n /** 重置所有连线颜色 */\r\n const resetEdgeStyles = () => {\r\n graph.value?.getEdges().forEach(edge => {\r\n edge.attr('line/stroke', config.defaultColor)\r\n edge.attr('line/strokeWidth', config.strokeWidth)\r\n })\r\n }\r\n\r\n /** 高亮指定连线 */\r\n const highlightEdge = (edge: any) => {\r\n resetEdgeStyles()\r\n edge.attr('line/stroke', config.highlightColor)\r\n edge.attr('line/strokeWidth', config.highlightStrokeWidth)\r\n }\r\n\r\n /** 切换端口可见性 */\r\n const togglePorts = (node: Node, opacity: number) => {\r\n config.portPositions.forEach(pos =>\r\n node.attr(`port-${pos}/style/opacity`, opacity)\r\n )\r\n }\r\n\r\n /**\r\n * 绑定标准交互事件到 graph 实例\r\n * - edge:click → 高亮\r\n * - edge:dblclick → 删除\r\n * - blank:click / node:click → 重置颜色\r\n * - node:mouseenter / mouseleave → 端口显隐(如配置了 portPositions)\r\n */\r\n const bindInteractions = () => {\r\n const g = graph.value\r\n if (!g) return\r\n\r\n // 连线点击高亮\r\n g.on('edge:click', ({ edge }) => highlightEdge(edge))\r\n\r\n // 连线双击删除\r\n g.on('edge:dblclick', ({ edge }) => {\r\n edge.remove()\r\n config.onDataChange()\r\n })\r\n\r\n // 点击空白 / 节点时重置颜色\r\n g.on('blank:click', resetEdgeStyles)\r\n g.on('node:click', resetEdgeStyles)\r\n\r\n // 连线创建时通知数据变更\r\n g.on('edge:connected', config.onDataChange)\r\n\r\n // 端口显隐(如果配置了 portPositions)\r\n if (config.portPositions.length > 0) {\r\n g.on('node:mouseenter', ({ node }) => togglePorts(node, 1))\r\n g.on('node:mouseleave', ({ node }) => togglePorts(node, 0))\r\n }\r\n }\r\n\r\n return { resetEdgeStyles, highlightEdge, togglePorts, bindInteractions }\r\n}\r\n","export const elementTypes = {\r\n \"start-event\": {\r\n name: \"开始\",\r\n title: \"开始事件\",\r\n iconClass: \"circle start-event\",\r\n },\r\n activity: { name: \"任务\", title: \"任务活动\", iconClass: \"rect activity\" },\r\n gateway: { name: \"网关\", title: \"排他网关\", iconClass: \"diamond gateway\" },\r\n \"end-event\": {\r\n name: \"结束\",\r\n title: \"结束事件\",\r\n iconClass: \"circle end-event\",\r\n },\r\n};\r\n\r\nexport const elementTypeNames = {\r\n event: \"事件\",\r\n activity: \"活动\",\r\n gateway: \"网关\",\r\n \"bpmn-edge\": \"连接线\",\r\n};\r\n\r\n/* 端口位置配置 */\r\nexport const portPositions = [\"top\", \"right\", \"bottom\", \"left\"];\r\n\r\n/* 端口属性配置函数 */\r\nexport const createPortAttrs = (\r\n positions: string[],\r\n nodeType: \"circle\" | \"rect\" | \"polygon\" = \"rect\",\r\n) =>\r\n positions.reduce((acc, pos) => {\r\n const isCircle = nodeType === \"circle\";\r\n const refConfig =\r\n pos === \"top\"\r\n ? isCircle\r\n ? { refCx: 0.5, refCy: 0 }\r\n : { refX: 0.5, refY: 0 }\r\n : pos === \"right\"\r\n ? isCircle\r\n ? { refCx: 1, refCy: 0.5 }\r\n : { refX: 1, refY: 0.5 }\r\n : pos === \"bottom\"\r\n ? isCircle\r\n ? { refCx: 0.5, refCy: 1 }\r\n : { refX: 0.5, refY: 1 }\r\n : isCircle\r\n ? { refCx: 0, refCy: 0.5 }\r\n : { refX: 0, refY: 0.5 };\r\n\r\n acc[`port-${pos}`] = {\r\n ref: \"body\",\r\n ...refConfig,\r\n r: 4,\r\n fill: \"#31d0c6\",\r\n stroke: \"#ffffff\",\r\n strokeWidth: 2,\r\n magnet: true,\r\n style: { cursor: \"crosshair\", opacity: 0, transition: \"opacity 0.2s\" },\r\n };\r\n return acc;\r\n }, {} as any);\r\n\r\n/* 节点配置 */\r\nexport const nodeConfigs = {\r\n event: {\r\n inherit: \"circle\",\r\n attrs: {\r\n body: {\r\n strokeWidth: 2,\r\n stroke: \"#5F95FF\",\r\n fill: \"#FFF\",\r\n magnet: false,\r\n style: { cursor: \"move\" },\r\n },\r\n text: {\r\n fontSize: 12,\r\n fill: \"#262626\",\r\n textAnchor: \"middle\",\r\n textVerticalAnchor: \"middle\",\r\n pointerEvents: \"none\",\r\n },\r\n },\r\n },\r\n activity: {\r\n inherit: \"rect\",\r\n attrs: {\r\n body: {\r\n rx: 6,\r\n ry: 6,\r\n stroke: \"#5F95FF\",\r\n fill: \"#EFF4FF\",\r\n strokeWidth: 1,\r\n magnet: false,\r\n style: { cursor: \"move\" },\r\n },\r\n text: {\r\n fontSize: 12,\r\n fill: \"#262626\",\r\n textAnchor: \"middle\",\r\n textVerticalAnchor: \"middle\",\r\n pointerEvents: \"none\",\r\n },\r\n },\r\n },\r\n gateway: {\r\n inherit: \"polygon\",\r\n attrs: {\r\n body: {\r\n refPoints: \"0,10 10,0 20,10 10,20\",\r\n strokeWidth: 2,\r\n stroke: \"#5F95FF\",\r\n fill: \"#EFF4FF\",\r\n magnet: false,\r\n style: { cursor: \"move\" },\r\n },\r\n text: {\r\n fontSize: 20,\r\n fill: \"#5F95FF\",\r\n textAnchor: \"middle\",\r\n textVerticalAnchor: \"middle\",\r\n pointerEvents: \"none\",\r\n },\r\n },\r\n },\r\n};\r\n\r\n/* 边配置 */\r\nexport const edgeConfig = {\r\n inherit: \"edge\",\r\n attrs: {\r\n line: {\r\n stroke: \"#A2B1C3\",\r\n strokeWidth: 2,\r\n targetMarker: {\r\n name: \"block\",\r\n width: 8,\r\n height: 6,\r\n fill: \"#A2B1C3\",\r\n },\r\n cursor: \"pointer\",\r\n },\r\n },\r\n};\r\n\r\n/**\r\n * 获取图形配置(支持主题切换)\r\n * @param isDark 是否为暗色主题\r\n * @returns Graph配置对象\r\n */\r\nexport function getGraphConfig(isDark = false) {\r\n const themeColors = {\r\n background: isDark ? \"#18181c\" : \"#ffffff\",\r\n gridPrimary: isDark ? \"rgba(255, 255, 255, 0.08)\" : \"#eee\",\r\n gridSecondary: isDark ? \"rgba(255, 255, 255, 0.04)\" : \"#ddd\",\r\n };\r\n\r\n return {\r\n background: { color: themeColors.background },\r\n grid: {\r\n visible: true,\r\n type: \"doubleMesh\",\r\n args: [\r\n { color: themeColors.gridPrimary, thickness: 1 },\r\n { color: themeColors.gridSecondary, thickness: 1, factor: 4 },\r\n ],\r\n },\r\n mousewheel: {\r\n enabled: true,\r\n zoomAtMousePosition: true,\r\n modifiers: \"ctrl\",\r\n minScale: 0.5,\r\n maxScale: 3,\r\n },\r\n connecting: {\r\n router: \"manhattan\",\r\n connector: { name: \"rounded\", args: { radius: 8 } },\r\n allowBlank: false,\r\n allowLoop: false,\r\n allowNode: false,\r\n snap: true,\r\n allowEdge: false,\r\n },\r\n highlighting: {\r\n magnetAvailable: {\r\n name: \"stroke\",\r\n args: { attrs: { fill: \"#31d0c6\", stroke: \"#31d0c6\", opacity: 1 } },\r\n },\r\n magnetAdsorbed: {\r\n name: \"stroke\",\r\n args: { attrs: { fill: \"#5F95FF\", stroke: \"#5F95FF\", opacity: 1 } },\r\n },\r\n },\r\n selecting: {\r\n enabled: true,\r\n rubberband: true,\r\n showNodeSelectionBox: true,\r\n },\r\n resizing: true,\r\n rotating: false,\r\n snapline: true,\r\n keyboard: true,\r\n clipboard: true,\r\n };\r\n}\r\n\r\n/* 保持向后兼容 */\r\nexport const graphConfig = getGraphConfig();\r\n\r\n/* 添加元素配置 */\r\nexport const addElementConfigs = {\r\n \"start-event\": {\r\n shape: \"event\",\r\n width: 50,\r\n height: 50,\r\n label: \"开始事件\",\r\n data: { type: \"start\" },\r\n },\r\n \"end-event\": {\r\n shape: \"event\",\r\n width: 50,\r\n height: 50,\r\n label: \"结束事件\",\r\n data: { type: \"end\" },\r\n },\r\n activity: {\r\n shape: \"activity\",\r\n width: 120,\r\n height: 60,\r\n label: \"新活动\",\r\n data: { description: \"\", assignee: \"\" },\r\n },\r\n gateway: {\r\n shape: \"gateway\",\r\n width: 40,\r\n height: 40,\r\n label: \"+\",\r\n data: { type: \"exclusive\" },\r\n },\r\n};\r\n\r\n/* 示例数据 */\r\nexport const sampleData = [\r\n {\r\n id: \"start-1\",\r\n shape: \"event\",\r\n x: 100,\r\n y: 200,\r\n width: 50,\r\n height: 50,\r\n label: \"流程开始\",\r\n data: { type: \"start\" },\r\n },\r\n {\r\n id: \"activity-1\",\r\n shape: \"activity\",\r\n x: 200,\r\n y: 180,\r\n width: 120,\r\n height: 60,\r\n label: \"用户申请\",\r\n data: { description: \"用户提交申请表单\", assignee: \"申请人\" },\r\n },\r\n {\r\n id: \"activity-2\",\r\n shape: \"activity\",\r\n x: 380,\r\n y: 180,\r\n width: 120,\r\n height: 60,\r\n label: \"初审\",\r\n data: { description: \"部门进行初步审核\", assignee: \"部门主管\" },\r\n },\r\n {\r\n id: \"gateway-1\",\r\n shape: \"gateway\",\r\n x: 550,\r\n y: 200,\r\n width: 40,\r\n height: 40,\r\n label: \"+\",\r\n data: { type: \"exclusive\" },\r\n },\r\n {\r\n id: \"activity-3\",\r\n shape: \"activity\",\r\n x: 650,\r\n y: 120,\r\n width: 120,\r\n height: 60,\r\n label: \"终审\",\r\n data: { description: \"高级主管最终审核\", assignee: \"总监\" },\r\n },\r\n {\r\n id: \"activity-4\",\r\n shape: \"activity\",\r\n x: 650,\r\n y: 260,\r\n width: 120,\r\n height: 60,\r\n label: \"驳回处理\",\r\n data: { description: \"处理驳回流程\", assignee: \"专员\" },\r\n },\r\n {\r\n id: \"end-1\",\r\n shape: \"event\",\r\n x: 820,\r\n y: 140,\r\n width: 50,\r\n height: 50,\r\n label: \"审批通过\",\r\n data: { type: \"end\" },\r\n },\r\n {\r\n id: \"end-2\",\r\n shape: \"event\",\r\n x: 820,\r\n y: 280,\r\n width: 50,\r\n height: 50,\r\n label: \"审批驳回\",\r\n data: { type: \"end\" },\r\n },\r\n {\r\n id: \"edge-1\",\r\n shape: \"bpmn-edge\",\r\n source: \"start-1\",\r\n target: \"activity-1\",\r\n x: 0,\r\n y: 0,\r\n },\r\n {\r\n id: \"edge-2\",\r\n shape: \"bpmn-edge\",\r\n source: \"activity-1\",\r\n target: \"activity-2\",\r\n x: 0,\r\n y: 0,\r\n },\r\n {\r\n id: \"edge-3\",\r\n shape: \"bpmn-edge\",\r\n source: \"activity-2\",\r\n target: \"gateway-1\",\r\n x: 0,\r\n y: 0,\r\n },\r\n {\r\n id: \"edge-4\",\r\n shape: \"bpmn-edge\",\r\n source: \"gateway-1\",\r\n target: \"activity-3\",\r\n label: \"通过\",\r\n x: 0,\r\n y: 0,\r\n },\r\n {\r\n id: \"edge-5\",\r\n shape: \"bpmn-edge\",\r\n source: \"gateway-1\",\r\n target: \"activity-4\",\r\n label: \"驳回\",\r\n x: 0,\r\n y: 0,\r\n },\r\n {\r\n id: \"edge-6\",\r\n shape: \"bpmn-edge\",\r\n source: \"activity-3\",\r\n target: \"end-1\",\r\n x: 0,\r\n y: 0,\r\n },\r\n {\r\n id: \"edge-7\",\r\n shape: \"bpmn-edge\",\r\n source: \"activity-4\",\r\n target: \"end-2\",\r\n x: 0,\r\n y: 0,\r\n },\r\n];\r\n","/* unplugin-vue-components disabled */<template>\r\n <NDrawer\r\n :show=\"show\"\r\n width=\"300\"\r\n title=\"属性设置\"\r\n @update:show=\"emit('update:show', $event)\"\r\n >\r\n <div v-if=\"editingElement\" class=\"property-panel\">\r\n <div class=\"property-item\">\r\n <div class=\"property-label\">名称</div>\r\n <NInput\r\n v-model:value=\"editingElement.label\"\r\n placeholder=\"请输入名称\"\r\n size=\"small\"\r\n />\r\n </div>\r\n <div class=\"property-item\">\r\n <div class=\"property-label\">类型</div>\r\n <NInput :value=\"typeName\" readonly size=\"small\" />\r\n </div>\r\n <div class=\"property-item\" v-if=\"editingElement.shape === 'activity'\">\r\n <div class=\"property-label\">描述</div>\r\n <NInput\r\n v-model:value=\"editingElement.description\"\r\n type=\"textarea\"\r\n :rows=\"3\"\r\n placeholder=\"请输入活动描述\"\r\n size=\"small\"\r\n />\r\n </div>\r\n <div class=\"property-item\" v-if=\"editingElement.shape === 'activity'\">\r\n <div class=\"property-label\">执行人</div>\r\n <NInput\r\n v-model:value=\"editingElement.assignee\"\r\n placeholder=\"请输入执行人\"\r\n size=\"small\"\r\n />\r\n </div>\r\n <div class=\"form-actions\">\r\n <NSpace>\r\n <NButton @click=\"emit('save')\" type=\"primary\" size=\"small\">\r\n 保存\r\n </NButton>\r\n <NButton @click=\"emit('update:show', false)\" size=\"small\">\r\n 取消\r\n </NButton>\r\n <NButton @click=\"emit('delete')\" type=\"error\" size=\"small\">\r\n 删除\r\n </NButton>\r\n </NSpace>\r\n </div>\r\n </div>\r\n </NDrawer>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport { NDrawer, NInput, NSpace, NButton } from \"naive-ui\";\r\nimport { elementTypeNames } from \"../data\";\r\n\r\ninterface BPMNElement {\r\n id: string;\r\n shape: string;\r\n label?: string;\r\n description?: string;\r\n assignee?: string;\r\n [key: string]: any;\r\n}\r\n\r\ndefineProps<{\r\n show: boolean;\r\n}>();\r\n\r\nconst editingElement = defineModel<BPMNElement>(\"editingElement\");\r\n\r\nconst emit = defineEmits<{\r\n \"update:show\": [value: boolean];\r\n save: [];\r\n delete: [];\r\n}>();\r\n\r\nconst typeName = computed(\r\n () =>\r\n elementTypeNames[\r\n editingElement.value?.shape as keyof typeof elementTypeNames\r\n ] ||\r\n editingElement.value?.shape ||\r\n \"\",\r\n);\r\n</script>\r\n","/* unplugin-vue-components disabled */<template>\r\n <NDrawer\r\n :show=\"show\"\r\n width=\"300\"\r\n title=\"属性设置\"\r\n @update:show=\"emit('update:show', $event)\"\r\n >\r\n <div v-if=\"editingElement\" class=\"property-panel\">\r\n <div class=\"property-item\">\r\n <div class=\"property-label\">名称</div>\r\n <NInput\r\n v-model:value=\"editingElement.label\"\r\n placeholder=\"请输入名称\"\r\n size=\"small\"\r\n />\r\n </div>\r\n <div class=\"property-item\">\r\n <div class=\"property-label\">类型</div>\r\n <NInput :value=\"typeName\" readonly size=\"small\" />\r\n </div>\r\n <div class=\"property-item\" v-if=\"editingElement.shape === 'activity'\">\r\n <div class=\"property-label\">描述</div>\r\n <NInput\r\n v-model:value=\"editingElement.description\"\r\n type=\"textarea\"\r\n :rows=\"3\"\r\n placeholder=\"请输入活动描述\"\r\n size=\"small\"\r\n />\r\n </div>\r\n <div class=\"property-item\" v-if=\"editingElement.shape === 'activity'\">\r\n <div class=\"property-label\">执行人</div>\r\n <NInput\r\n v-model:value=\"editingElement.assignee\"\r\n placeholder=\"请输入执行人\"\r\n size=\"small\"\r\n />\r\n </div>\r\n <div class=\"form-actions\">\r\n <NSpace>\r\n <NButton @click=\"emit('save')\" type=\"primary\" size=\"small\">\r\n 保存\r\n </NButton>\r\n <NButton @click=\"emit('update:show', false)\" size=\"small\">\r\n 取消\r\n </NButton>\r\n <NButton @click=\"emit('delete')\" type=\"error\" size=\"small\">\r\n 删除\r\n </NButton>\r\n </NSpace>\r\n </div>\r\n </div>\r\n </NDrawer>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport { NDrawer, NInput, NSpace, NButton } from \"naive-ui\";\r\nimport { elementTypeNames } from \"../data\";\r\n\r\ninterface BPMNElement {\r\n id: string;\r\n shape: string;\r\n label?: string;\r\n description?: string;\r\n assignee?: string;\r\n [key: string]: any;\r\n}\r\n\r\ndefineProps<{\r\n show: boolean;\r\n}>();\r\n\r\nconst editingElement = defineModel<BPMNElement>(\"editingElement\");\r\n\r\nconst emit = defineEmits<{\r\n \"update:show\": [value: boolean];\r\n save: [];\r\n delete: [];\r\n}>();\r\n\r\nconst typeName = computed(\r\n () =>\r\n elementTypeNames[\r\n editingElement.value?.shape as keyof typeof elementTypeNames\r\n ] ||\r\n editingElement.value?.shape ||\r\n \"\",\r\n);\r\n</script>\r\n","<template>\r\n <NDrawer\r\n :show=\"show\"\r\n width=\"300\"\r\n title=\"属性设置\"\r\n @update:show=\"emit('update:show', $event)\"\r\n >\r\n <div v-if=\"editingElement\" class=\"property-panel\">\r\n <div class=\"property-item\">\r\n <div class=\"property-label\">名称</div>\r\n <NInput\r\n v-model:value=\"editingElement.label\"\r\n placeholder=\"请输入名称\"\r\n size=\"small\"\r\n />\r\n </div>\r\n <div class=\"property-item\">\r\n <div class=\"property-label\">类型</div>\r\n <NInput :value=\"typeName\" readonly size=\"small\" />\r\n </div>\r\n <div class=\"property-item\" v-if=\"editingElement.shape === 'activity'\">\r\n <div class=\"property-label\">描述</div>\r\n <NInput\r\n v-model:value=\"editingElement.description\"\r\n type=\"textarea\"\r\n :rows=\"3\"\r\n placeholder=\"请输入活动描述\"\r\n size=\"small\"\r\n />\r\n </div>\r\n <div class=\"property-item\" v-if=\"editingElement.shape === 'activity'\">\r\n <div class=\"property-label\">执行人</div>\r\n <NInput\r\n v-model:value=\"editingElement.assignee\"\r\n placeholder=\"请输入执行人\"\r\n size=\"small\"\r\n />\r\n </div>\r\n <div class=\"form-actions\">\r\n <NSpace>\r\n <NButton @click=\"emit('save')\" type=\"primary\" size=\"small\">\r\n 保存\r\n </NButton>\r\n <NButton @click=\"emit('update:show', false)\" size=\"small\">\r\n 取消\r\n </NButton>\r\n <NButton @click=\"emit('delete')\" type=\"error\" size=\"small\">\r\n 删除\r\n </NButton>\r\n </NSpace>\r\n </div>\r\n </div>\r\n </NDrawer>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport { NDrawer, NInput, NSpace, NButton } from \"naive-ui\";\r\nimport { elementTypeNames } from \"../data\";\r\n\r\ninterface BPMNElement {\r\n id: string;\r\n shape: string;\r\n label?: string;\r\n description?: string;\r\n assignee?: string;\r\n [key: string]: any;\r\n}\r\n\r\ndefineProps<{\r\n show: boolean;\r\n}>();\r\n\r\nconst editingElement = defineModel<BPMNElement>(\"editingElement\");\r\n\r\nconst emit = defineEmits<{\r\n \"update:show\": [value: boolean];\r\n save: [];\r\n delete: [];\r\n}>();\r\n\r\nconst typeName = computed(\r\n () =>\r\n elementTypeNames[\r\n editingElement.value?.shape as keyof typeof elementTypeNames\r\n ] ||\r\n editingElement.value?.shape ||\r\n \"\",\r\n);\r\n</script>\r\n","/* unplugin-vue-components disabled */<template>\r\n <div class=\"bpmn-layout\">\r\n <div class=\"top-toolbar\" v-if=\"showToolbar\">\r\n <div class=\"toolbar-section\">\r\n <div class=\"toolbar-group\">\r\n <NButton @click=\"clearAll\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete-outline\" :size=\"16\" />\r\n </template>\r\n 清空\r\n </NButton>\r\n </div>\r\n <div class=\"toolbar-group\">\r\n <NButton @click=\"centerContent\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:image-filter-center-focus\" :size=\"16\" />\r\n </template>\r\n 居中\r\n </NButton>\r\n <NButton @click=\"zoomToFit\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:fit-to-screen\" :size=\"16\" />\r\n </template>\r\n 适应\r\n </NButton>\r\n </div>\r\n <div class=\"toolbar-group\">\r\n <NDropdown\r\n :options=\"exportOptions\"\r\n @select=\"(key: string) => handleExport(key, getCurrentData)\"\r\n >\r\n <NButton size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:export\" :size=\"16\" />\r\n </template>\r\n 导出\r\n </NButton>\r\n </NDropdown>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"main-content\">\r\n <div class=\"left-panel\" v-if=\"showToolbar\">\r\n <div class=\"panel-title\">组件</div>\r\n <div class=\"element-grid\">\r\n <div\r\n v-for=\"(item, key) in elementTypes\"\r\n :key=\"key\"\r\n class=\"element-item\"\r\n @click=\"addElement(key)\"\r\n :title=\"item.title\"\r\n >\r\n <div :class=\"['element-icon', item.iconClass]\"></div>\r\n <span>{{ item.name }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"graph-wrapper\">\r\n <div ref=\"containerRef\" class=\"graph-container\"></div>\r\n </div>\r\n </div>\r\n\r\n <BPMNPropertyEditor\r\n :show=\"showEditor\"\r\n v-model:editing-element=\"editingElement\"\r\n @update:show=\"showEditor = $event\"\r\n @save=\"saveElement\"\r\n @delete=\"deleteElement\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, onMounted } from \"vue\";\r\nimport { NButton, NDropdown } from \"naive-ui\";\r\nimport { Graph } from \"@antv/x6\";\r\nimport { useGraphBase } from \"../../composables/useGraphBase\";\r\nimport { useGraphExport } from \"../../composables/useGraphExport\";\r\nimport { useEdgeInteraction } from \"../../composables/useEdgeInteraction\";\r\nimport BPMNPropertyEditor from \"./components/BPMNPropertyEditor.vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\nimport {\r\n elementTypes,\r\n portPositions,\r\n createPortAttrs,\r\n nodeConfigs,\r\n edgeConfig,\r\n getGraphConfig,\r\n addElementConfigs,\r\n sampleData,\r\n} from \"./data\";\r\n\r\ninterface BPMNElement {\r\n id: string;\r\n shape: string;\r\n x: number;\r\n y: number;\r\n width?: number;\r\n height?: number;\r\n label?: string;\r\n source?: string;\r\n target?: string;\r\n description?: string;\r\n assignee?: string;\r\n data?: any;\r\n [key: string]: any;\r\n}\r\n\r\ninterface Props {\r\n data?: BPMNElement[] | Record<string, BPMNElement[]>;\r\n showToolbar?: boolean;\r\n readonly?: boolean;\r\n width?: number | string;\r\n height?: number | string;\r\n theme?: \"light\" | \"dark\";\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n showToolbar: true,\r\n readonly: false,\r\n width: \"100%\",\r\n height: \"600px\",\r\n});\r\n\r\nconst emit = defineEmits<{\r\n ready: [graph: Graph];\r\n \"data-change\": [data: BPMNElement[]];\r\n}>();\r\n\r\n// ==================== Composables ====================\r\nconst containerRef = ref<HTMLDivElement>();\r\nconst isDark = computed(() => props.theme === \"dark\");\r\nconst { graph, initGraph, centerContent, zoomToFit } = useGraphBase(\r\n containerRef,\r\n isDark,\r\n);\r\nconst { exportOptions, handleExport } = useGraphExport(graph, \"bpmn-diagram\");\r\nconst { bindInteractions } = useEdgeInteraction(graph, {\r\n portPositions,\r\n onDataChange: () => emitDataChange(),\r\n});\r\n\r\n// ==================== 编辑器状态 ====================\r\nconst showEditor = ref(false);\r\nconst editingElement = ref<BPMNElement>();\r\n\r\n// ==================== 工具函数 ====================\r\nconst getLabel = (cell: any): string =>\r\n String(cell.attr(\"text/text\") || cell.attr(\"label/text\") || \"\");\r\n\r\nconst normalizeData = (data: any): any[] => {\r\n if (!data) return [];\r\n if (Array.isArray(data)) return data;\r\n const result: any[] = [];\r\n Object.values(data).forEach((arr: any) => {\r\n if (Array.isArray(arr)) result.push(...arr);\r\n });\r\n return result;\r\n};\r\n\r\n// ==================== 节点注册 ====================\r\nconst registerNodes = () => {\r\n const portMarkup = portPositions.map((pos) => ({\r\n tagName: \"circle\",\r\n selector: `port-${pos}`,\r\n }));\r\n\r\n const nodeTypes = [\"event\", \"activity\", \"gateway\"] as const;\r\n const bodyTags = { event: \"circle\", activity: \"rect\", gateway: \"polygon\" };\r\n\r\n nodeTypes.forEach((type) => {\r\n Graph.registerNode(\r\n type,\r\n {\r\n ...nodeConfigs[type],\r\n markup: [\r\n { tagName: bodyTags[type], selector: \"body\" },\r\n { tagName: \"text\", selector: \"text\" },\r\n ...portMarkup,\r\n ],\r\n attrs: {\r\n ...nodeConfigs[type].attrs,\r\n ...createPortAttrs(portPositions, bodyTags[type]),\r\n },\r\n },\r\n true,\r\n );\r\n });\r\n\r\n Graph.registerEdge(\"bpmn-edge\", edgeConfig, true);\r\n};\r\n\r\n// ==================== 数据操作 ====================\r\nconst loadData = (data: any[]) => {\r\n if (!graph.value || !data.length) return;\r\n const cells = data.map((item) => {\r\n const { data: nodeData, ...cellProps } = item;\r\n const cell =\r\n item.shape === \"bpmn-edge\"\r\n ? graph.value!.createEdge(cellProps)\r\n : graph.value!.createNode(cellProps);\r\n if (nodeData) cell.setData(nodeData);\r\n return cell;\r\n });\r\n graph.value.resetCells(cells);\r\n setTimeout(() => graph.value!.zoomToFit({ padding: 50, maxScale: 1 }), 200);\r\n};\r\n\r\nconst addElement = (type: string) => {\r\n if (!graph.value) return;\r\n const config = addElementConfigs[type as keyof typeof addElementConfigs];\r\n if (!config) return;\r\n\r\n const centerX = (graph.value.options.width as number) / 2;\r\n const centerY = (graph.value.options.height as number) / 2;\r\n const { data: nodeData, ...nodeProps } = {\r\n id: `${type}-${Date.now()}`,\r\n x: centerX + Math.random() * 100 - 50,\r\n y: centerY + Math.random() * 100 - 50,\r\n ...config,\r\n };\r\n const node = graph.value.createNode(nodeProps);\r\n if (nodeData) node.setData(nodeData);\r\n graph.value.addCell(node);\r\n emitDataChange();\r\n};\r\n\r\nconst clearAll = () => {\r\n graph.value?.clearCells();\r\n emitDataChange();\r\n};\r\n\r\n// ==================== 元素编辑 ====================\r\nconst editElement = (cell: any) => {\r\n const cellData = cell.getData() || {};\r\n editingElement.value = {\r\n id: cell.id,\r\n shape: cell.shape,\r\n label: getLabel(cell),\r\n description: cellData.description || \"\",\r\n assignee: cellData.assignee || \"\",\r\n x: 0,\r\n y: 0,\r\n };\r\n showEditor.value = true;\r\n};\r\n\r\nconst saveElement = () => {\r\n if (!graph.value || !editingElement.value) return;\r\n const cell = graph.value.getCellById(editingElement.value.id);\r\n if (cell) {\r\n cell.setData({\r\n description: editingElement.value.description,\r\n assignee: editingElement.value.assignee,\r\n });\r\n cell.attr(\"text/text\", editingElement.value.label || \"\");\r\n cell.attr(\"label/text\", editingElement.value.label || \"\");\r\n }\r\n showEditor.value = false;\r\n emitDataChange();\r\n};\r\n\r\nconst deleteElement = () => {\r\n if (!graph.value || !editingElement.value) return;\r\n graph.value.getCellById(editingElement.value.id)?.remove();\r\n showEditor.value = false;\r\n emitDataChange();\r\n};\r\n\r\n// ==================== 数据获取 ====================\r\nconst getCurrentData = (): BPMNElement[] => {\r\n if (!graph.value) return [];\r\n return [\r\n ...graph.value.getNodes().map((node) => ({\r\n id: node.id,\r\n shape: node.shape,\r\n x: node.getPosition().x,\r\n y: node.getPosition().y,\r\n width: node.getSize().width,\r\n height: node.getSize().height,\r\n label: getLabel(node),\r\n data: node.getData() || {},\r\n })),\r\n ...graph.value.getEdges().map((edge) => ({\r\n id: edge.id,\r\n shape: \"bpmn-edge\",\r\n source: edge.getSourceCellId() || \"\",\r\n target: edge.getTargetCellId() || \"\",\r\n label: getLabel(edge),\r\n x: 0,\r\n y: 0,\r\n })),\r\n ] as BPMNElement[];\r\n};\r\n\r\nconst emitDataChange = () => emit(\"data-change\", getCurrentData());\r\n\r\n// ==================== Graph 事件绑定 ====================\r\nwatch(\r\n graph,\r\n (newGraph) => {\r\n if (!(newGraph instanceof Graph)) return;\r\n\r\n if (!props.readonly) {\r\n bindInteractions();\r\n newGraph.on(\"node:dblclick\", ({ node }) => editElement(node));\r\n newGraph.on(\"node:moved\", emitDataChange);\r\n }\r\n\r\n emit(\"ready\", newGraph);\r\n loadData(sampleData);\r\n },\r\n { immediate: true },\r\n);\r\n\r\nwatch(\r\n () => props.data,\r\n (newData) => {\r\n if (!newData || !graph.value) return;\r\n const normalized = normalizeData(newData);\r\n if (normalized.length > 0) loadData(normalized);\r\n },\r\n { deep: true },\r\n);\r\n\r\nonMounted(async () => {\r\n registerNodes();\r\n const bpmnConfig = getGraphConfig();\r\n await initGraph({\r\n ...bpmnConfig,\r\n connecting: {\r\n ...bpmnConfig.connecting,\r\n createEdge: () => graph.value!.createEdge({ shape: \"bpmn-edge\" }),\r\n validateConnection: ({\r\n sourceView,\r\n targetView,\r\n sourceMagnet,\r\n targetMagnet,\r\n }: any) =>\r\n sourceView !== targetView &&\r\n !!sourceMagnet &&\r\n !!targetMagnet &&\r\n sourceMagnet.getAttribute(\"magnet\") === \"true\" &&\r\n targetMagnet.getAttribute(\"magnet\") === \"true\",\r\n },\r\n });\r\n});\r\n\r\ndefineExpose({\r\n getGraph: () => graph.value,\r\n getData: getCurrentData,\r\n loadData,\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","/* unplugin-vue-components disabled */<template>\r\n <div class=\"bpmn-layout\">\r\n <div class=\"top-toolbar\" v-if=\"showToolbar\">\r\n <div class=\"toolbar-section\">\r\n <div class=\"toolbar-group\">\r\n <NButton @click=\"clearAll\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete-outline\" :size=\"16\" />\r\n </template>\r\n 清空\r\n </NButton>\r\n </div>\r\n <div class=\"toolbar-group\">\r\n <NButton @click=\"centerContent\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:image-filter-center-focus\" :size=\"16\" />\r\n </template>\r\n 居中\r\n </NButton>\r\n <NButton @click=\"zoomToFit\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:fit-to-screen\" :size=\"16\" />\r\n </template>\r\n 适应\r\n </NButton>\r\n </div>\r\n <div class=\"toolbar-group\">\r\n <NDropdown\r\n :options=\"exportOptions\"\r\n @select=\"(key: string) => handleExport(key, getCurrentData)\"\r\n >\r\n <NButton size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:export\" :size=\"16\" />\r\n </template>\r\n 导出\r\n </NButton>\r\n </NDropdown>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"main-content\">\r\n <div class=\"left-panel\" v-if=\"showToolbar\">\r\n <div class=\"panel-title\">组件</div>\r\n <div class=\"element-grid\">\r\n <div\r\n v-for=\"(item, key) in elementTypes\"\r\n :key=\"key\"\r\n class=\"element-item\"\r\n @click=\"addElement(key)\"\r\n :title=\"item.title\"\r\n >\r\n <div :class=\"['element-icon', item.iconClass]\"></div>\r\n <span>{{ item.name }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"graph-wrapper\">\r\n <div ref=\"containerRef\" class=\"graph-container\"></div>\r\n </div>\r\n </div>\r\n\r\n <BPMNPropertyEditor\r\n :show=\"showEditor\"\r\n v-model:editing-element=\"editingElement\"\r\n @update:show=\"showEditor = $event\"\r\n @save=\"saveElement\"\r\n @delete=\"deleteElement\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, onMounted } from \"vue\";\r\nimport { NButton, NDropdown } from \"naive-ui\";\r\nimport { Graph } from \"@antv/x6\";\r\nimport { useGraphBase } from \"../../composables/useGraphBase\";\r\nimport { useGraphExport } from \"../../composables/useGraphExport\";\r\nimport { useEdgeInteraction } from \"../../composables/useEdgeInteraction\";\r\nimport BPMNPropertyEditor from \"./components/BPMNPropertyEditor.vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\nimport {\r\n elementTypes,\r\n portPositions,\r\n createPortAttrs,\r\n nodeConfigs,\r\n edgeConfig,\r\n getGraphConfig,\r\n addElementConfigs,\r\n sampleData,\r\n} from \"./data\";\r\n\r\ninterface BPMNElement {\r\n id: string;\r\n shape: string;\r\n x: number;\r\n y: number;\r\n width?: number;\r\n height?: number;\r\n label?: string;\r\n source?: string;\r\n target?: string;\r\n description?: string;\r\n assignee?: string;\r\n data?: any;\r\n [key: string]: any;\r\n}\r\n\r\ninterface Props {\r\n data?: BPMNElement[] | Record<string, BPMNElement[]>;\r\n showToolbar?: boolean;\r\n readonly?: boolean;\r\n width?: number | string;\r\n height?: number | string;\r\n theme?: \"light\" | \"dark\";\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n showToolbar: true,\r\n readonly: false,\r\n width: \"100%\",\r\n height: \"600px\",\r\n});\r\n\r\nconst emit = defineEmits<{\r\n ready: [graph: Graph];\r\n \"data-change\": [data: BPMNElement[]];\r\n}>();\r\n\r\n// ==================== Composables ====================\r\nconst containerRef = ref<HTMLDivElement>();\r\nconst isDark = computed(() => props.theme === \"dark\");\r\nconst { graph, initGraph, centerContent, zoomToFit } = useGraphBase(\r\n containerRef,\r\n isDark,\r\n);\r\nconst { exportOptions, handleExport } = useGraphExport(graph, \"bpmn-diagram\");\r\nconst { bindInteractions } = useEdgeInteraction(graph, {\r\n portPositions,\r\n onDataChange: () => emitDataChange(),\r\n});\r\n\r\n// ==================== 编辑器状态 ====================\r\nconst showEditor = ref(false);\r\nconst editingElement = ref<BPMNElement>();\r\n\r\n// ==================== 工具函数 ====================\r\nconst getLabel = (cell: any): string =>\r\n String(cell.attr(\"text/text\") || cell.attr(\"label/text\") || \"\");\r\n\r\nconst normalizeData = (data: any): any[] => {\r\n if (!data) return [];\r\n if (Array.isArray(data)) return data;\r\n const result: any[] = [];\r\n Object.values(data).forEach((arr: any) => {\r\n if (Array.isArray(arr)) result.push(...arr);\r\n });\r\n return result;\r\n};\r\n\r\n// ==================== 节点注册 ====================\r\nconst registerNodes = () => {\r\n const portMarkup = portPositions.map((pos) => ({\r\n tagName: \"circle\",\r\n selector: `port-${pos}`,\r\n }));\r\n\r\n const nodeTypes = [\"event\", \"activity\", \"gateway\"] as const;\r\n const bodyTags = { event: \"circle\", activity: \"rect\", gateway: \"polygon\" };\r\n\r\n nodeTypes.forEach((type) => {\r\n Graph.registerNode(\r\n type,\r\n {\r\n ...nodeConfigs[type],\r\n markup: [\r\n { tagName: bodyTags[type], selector: \"body\" },\r\n { tagName: \"text\", selector: \"text\" },\r\n ...portMarkup,\r\n ],\r\n attrs: {\r\n ...nodeConfigs[type].attrs,\r\n ...createPortAttrs(portPositions, bodyTags[type]),\r\n },\r\n },\r\n true,\r\n );\r\n });\r\n\r\n Graph.registerEdge(\"bpmn-edge\", edgeConfig, true);\r\n};\r\n\r\n// ==================== 数据操作 ====================\r\nconst loadData = (data: any[]) => {\r\n if (!graph.value || !data.length) return;\r\n const cells = data.map((item) => {\r\n const { data: nodeData, ...cellProps } = item;\r\n const cell =\r\n item.shape === \"bpmn-edge\"\r\n ? graph.value!.createEdge(cellProps)\r\n : graph.value!.createNode(cellProps);\r\n if (nodeData) cell.setData(nodeData);\r\n return cell;\r\n });\r\n graph.value.resetCells(cells);\r\n setTimeout(() => graph.value!.zoomToFit({ padding: 50, maxScale: 1 }), 200);\r\n};\r\n\r\nconst addElement = (type: string) => {\r\n if (!graph.value) return;\r\n const config = addElementConfigs[type as keyof typeof addElementConfigs];\r\n if (!config) return;\r\n\r\n const centerX = (graph.value.options.width as number) / 2;\r\n const centerY = (graph.value.options.height as number) / 2;\r\n const { data: nodeData, ...nodeProps } = {\r\n id: `${type}-${Date.now()}`,\r\n x: centerX + Math.random() * 100 - 50,\r\n y: centerY + Math.random() * 100 - 50,\r\n ...config,\r\n };\r\n const node = graph.value.createNode(nodeProps);\r\n if (nodeData) node.setData(nodeData);\r\n graph.value.addCell(node);\r\n emitDataChange();\r\n};\r\n\r\nconst clearAll = () => {\r\n graph.value?.clearCells();\r\n emitDataChange();\r\n};\r\n\r\n// ==================== 元素编辑 ====================\r\nconst editElement = (cell: any) => {\r\n const cellData = cell.getData() || {};\r\n editingElement.value = {\r\n id: cell.id,\r\n shape: cell.shape,\r\n label: getLabel(cell),\r\n description: cellData.description || \"\",\r\n assignee: cellData.assignee || \"\",\r\n x: 0,\r\n y: 0,\r\n };\r\n showEditor.value = true;\r\n};\r\n\r\nconst saveElement = () => {\r\n if (!graph.value || !editingElement.value) return;\r\n const cell = graph.value.getCellById(editingElement.value.id);\r\n if (cell) {\r\n cell.setData({\r\n description: editingElement.value.description,\r\n assignee: editingElement.value.assignee,\r\n });\r\n cell.attr(\"text/text\", editingElement.value.label || \"\");\r\n cell.attr(\"label/text\", editingElement.value.label || \"\");\r\n }\r\n showEditor.value = false;\r\n emitDataChange();\r\n};\r\n\r\nconst deleteElement = () => {\r\n if (!graph.value || !editingElement.value) return;\r\n graph.value.getCellById(editingElement.value.id)?.remove();\r\n showEditor.value = false;\r\n emitDataChange();\r\n};\r\n\r\n// ==================== 数据获取 ====================\r\nconst getCurrentData = (): BPMNElement[] => {\r\n if (!graph.value) return [];\r\n return [\r\n ...graph.value.getNodes().map((node) => ({\r\n id: node.id,\r\n shape: node.shape,\r\n x: node.getPosition().x,\r\n y: node.getPosition().y,\r\n width: node.getSize().width,\r\n height: node.getSize().height,\r\n label: getLabel(node),\r\n data: node.getData() || {},\r\n })),\r\n ...graph.value.getEdges().map((edge) => ({\r\n id: edge.id,\r\n shape: \"bpmn-edge\",\r\n source: edge.getSourceCellId() || \"\",\r\n target: edge.getTargetCellId() || \"\",\r\n label: getLabel(edge),\r\n x: 0,\r\n y: 0,\r\n })),\r\n ] as BPMNElement[];\r\n};\r\n\r\nconst emitDataChange = () => emit(\"data-change\", getCurrentData());\r\n\r\n// ==================== Graph 事件绑定 ====================\r\nwatch(\r\n graph,\r\n (newGraph) => {\r\n if (!(newGraph instanceof Graph)) return;\r\n\r\n if (!props.readonly) {\r\n bindInteractions();\r\n newGraph.on(\"node:dblclick\", ({ node }) => editElement(node));\r\n newGraph.on(\"node:moved\", emitDataChange);\r\n }\r\n\r\n emit(\"ready\", newGraph);\r\n loadData(sampleData);\r\n },\r\n { immediate: true },\r\n);\r\n\r\nwatch(\r\n () => props.data,\r\n (newData) => {\r\n if (!newData || !graph.value) return;\r\n const normalized = normalizeData(newData);\r\n if (normalized.length > 0) loadData(normalized);\r\n },\r\n { deep: true },\r\n);\r\n\r\nonMounted(async () => {\r\n registerNodes();\r\n const bpmnConfig = getGraphConfig();\r\n await initGraph({\r\n ...bpmnConfig,\r\n connecting: {\r\n ...bpmnConfig.connecting,\r\n createEdge: () => graph.value!.createEdge({ shape: \"bpmn-edge\" }),\r\n validateConnection: ({\r\n sourceView,\r\n targetView,\r\n sourceMagnet,\r\n targetMagnet,\r\n }: any) =>\r\n sourceView !== targetView &&\r\n !!sourceMagnet &&\r\n !!targetMagnet &&\r\n sourceMagnet.getAttribute(\"magnet\") === \"true\" &&\r\n targetMagnet.getAttribute(\"magnet\") === \"true\",\r\n },\r\n });\r\n});\r\n\r\ndefineExpose({\r\n getGraph: () => graph.value,\r\n getData: getCurrentData,\r\n loadData,\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<template>\r\n <div class=\"bpmn-layout\">\r\n <div class=\"top-toolbar\" v-if=\"showToolbar\">\r\n <div class=\"toolbar-section\">\r\n <div class=\"toolbar-group\">\r\n <NButton @click=\"clearAll\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete-outline\" :size=\"16\" />\r\n </template>\r\n 清空\r\n </NButton>\r\n </div>\r\n <div class=\"toolbar-group\">\r\n <NButton @click=\"centerContent\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:image-filter-center-focus\" :size=\"16\" />\r\n </template>\r\n 居中\r\n </NButton>\r\n <NButton @click=\"zoomToFit\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:fit-to-screen\" :size=\"16\" />\r\n </template>\r\n 适应\r\n </NButton>\r\n </div>\r\n <div class=\"toolbar-group\">\r\n <NDropdown\r\n :options=\"exportOptions\"\r\n @select=\"(key: string) => handleExport(key, getCurrentData)\"\r\n >\r\n <NButton size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:export\" :size=\"16\" />\r\n </template>\r\n 导出\r\n </NButton>\r\n </NDropdown>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"main-content\">\r\n <div class=\"left-panel\" v-if=\"showToolbar\">\r\n <div class=\"panel-title\">组件</div>\r\n <div class=\"element-grid\">\r\n <div\r\n v-for=\"(item, key) in elementTypes\"\r\n :key=\"key\"\r\n class=\"element-item\"\r\n @click=\"addElement(key)\"\r\n :title=\"item.title\"\r\n >\r\n <div :class=\"['element-icon', item.iconClass]\"></div>\r\n <span>{{ item.name }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"graph-wrapper\">\r\n <div ref=\"containerRef\" class=\"graph-container\"></div>\r\n </div>\r\n </div>\r\n\r\n <BPMNPropertyEditor\r\n :show=\"showEditor\"\r\n v-model:editing-element=\"editingElement\"\r\n @update:show=\"showEditor = $event\"\r\n @save=\"saveElement\"\r\n @delete=\"deleteElement\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, onMounted } from \"vue\";\r\nimport { NButton, NDropdown } from \"naive-ui\";\r\nimport { Graph } from \"@antv/x6\";\r\nimport { useGraphBase } from \"../../composables/useGraphBase\";\r\nimport { useGraphExport } from \"../../composables/useGraphExport\";\r\nimport { useEdgeInteraction } from \"../../composables/useEdgeInteraction\";\r\nimport BPMNPropertyEditor from \"./components/BPMNPropertyEditor.vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\nimport {\r\n elementTypes,\r\n portPositions,\r\n createPortAttrs,\r\n nodeConfigs,\r\n edgeConfig,\r\n getGraphConfig,\r\n addElementConfigs,\r\n sampleData,\r\n} from \"./data\";\r\n\r\ninterface BPMNElement {\r\n id: string;\r\n shape: string;\r\n x: number;\r\n y: number;\r\n width?: number;\r\n height?: number;\r\n label?: string;\r\n source?: string;\r\n target?: string;\r\n description?: string;\r\n assignee?: string;\r\n data?: any;\r\n [key: string]: any;\r\n}\r\n\r\ninterface Props {\r\n data?: BPMNElement[] | Record<string, BPMNElement[]>;\r\n showToolbar?: boolean;\r\n readonly?: boolean;\r\n width?: number | string;\r\n height?: number | string;\r\n theme?: \"light\" | \"dark\";\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n showToolbar: true,\r\n readonly: false,\r\n width: \"100%\",\r\n height: \"600px\",\r\n});\r\n\r\nconst emit = defineEmits<{\r\n ready: [graph: Graph];\r\n \"data-change\": [data: BPMNElement[]];\r\n}>();\r\n\r\n// ==================== Composables ====================\r\nconst containerRef = ref<HTMLDivElement>();\r\nconst isDark = computed(() => props.theme === \"dark\");\r\nconst { graph, initGraph, centerContent, zoomToFit } = useGraphBase(\r\n containerRef,\r\n isDark,\r\n);\r\nconst { exportOptions, handleExport } = useGraphExport(graph, \"bpmn-diagram\");\r\nconst { bindInteractions } = useEdgeInteraction(graph, {\r\n portPositions,\r\n onDataChange: () => emitDataChange(),\r\n});\r\n\r\n// ==================== 编辑器状态 ====================\r\nconst showEditor = ref(false);\r\nconst editingElement = ref<BPMNElement>();\r\n\r\n// ==================== 工具函数 ====================\r\nconst getLabel = (cell: any): string =>\r\n String(cell.attr(\"text/text\") || cell.attr(\"label/text\") || \"\");\r\n\r\nconst normalizeData = (data: any): any[] => {\r\n if (!data) return [];\r\n if (Array.isArray(data)) return data;\r\n const result: any[] = [];\r\n Object.values(data).forEach((arr: any) => {\r\n if (Array.isArray(arr)) result.push(...arr);\r\n });\r\n return result;\r\n};\r\n\r\n// ==================== 节点注册 ====================\r\nconst registerNodes = () => {\r\n const portMarkup = portPositions.map((pos) => ({\r\n tagName: \"circle\",\r\n selector: `port-${pos}`,\r\n }));\r\n\r\n const nodeTypes = [\"event\", \"activity\", \"gateway\"] as const;\r\n const bodyTags = { event: \"circle\", activity: \"rect\", gateway: \"polygon\" };\r\n\r\n nodeTypes.forEach((type) => {\r\n Graph.registerNode(\r\n type,\r\n {\r\n ...nodeConfigs[type],\r\n markup: [\r\n { tagName: bodyTags[type], selector: \"body\" },\r\n { tagName: \"text\", selector: \"text\" },\r\n ...portMarkup,\r\n ],\r\n attrs: {\r\n ...nodeConfigs[type].attrs,\r\n ...createPortAttrs(portPositions, bodyTags[type]),\r\n },\r\n },\r\n true,\r\n );\r\n });\r\n\r\n Graph.registerEdge(\"bpmn-edge\", edgeConfig, true);\r\n};\r\n\r\n// ==================== 数据操作 ====================\r\nconst loadData = (data: any[]) => {\r\n if (!graph.value || !data.length) return;\r\n const cells = data.map((item) => {\r\n const { data: nodeData, ...cellProps } = item;\r\n const cell =\r\n item.shape === \"bpmn-edge\"\r\n ? graph.value!.createEdge(cellProps)\r\n : graph.value!.createNode(cellProps);\r\n if (nodeData) cell.setData(nodeData);\r\n return cell;\r\n });\r\n graph.value.resetCells(cells);\r\n setTimeout(() => graph.value!.zoomToFit({ padding: 50, maxScale: 1 }), 200);\r\n};\r\n\r\nconst addElement = (type: string) => {\r\n if (!graph.value) return;\r\n const config = addElementConfigs[type as keyof typeof addElementConfigs];\r\n if (!config) return;\r\n\r\n const centerX = (graph.value.options.width as number) / 2;\r\n const centerY = (graph.value.options.height as number) / 2;\r\n const { data: nodeData, ...nodeProps } = {\r\n id: `${type}-${Date.now()}`,\r\n x: centerX + Math.random() * 100 - 50,\r\n y: centerY + Math.random() * 100 - 50,\r\n ...config,\r\n };\r\n const node = graph.value.createNode(nodeProps);\r\n if (nodeData) node.setData(nodeData);\r\n graph.value.addCell(node);\r\n emitDataChange();\r\n};\r\n\r\nconst clearAll = () => {\r\n graph.value?.clearCells();\r\n emitDataChange();\r\n};\r\n\r\n// ==================== 元素编辑 ====================\r\nconst editElement = (cell: any) => {\r\n const cellData = cell.getData() || {};\r\n editingElement.value = {\r\n id: cell.id,\r\n shape: cell.shape,\r\n label: getLabel(cell),\r\n description: cellData.description || \"\",\r\n assignee: cellData.assignee || \"\",\r\n x: 0,\r\n y: 0,\r\n };\r\n showEditor.value = true;\r\n};\r\n\r\nconst saveElement = () => {\r\n if (!graph.value || !editingElement.value) return;\r\n const cell = graph.value.getCellById(editingElement.value.id);\r\n if (cell) {\r\n cell.setData({\r\n description: editingElement.value.description,\r\n assignee: editingElement.value.assignee,\r\n });\r\n cell.attr(\"text/text\", editingElement.value.label || \"\");\r\n cell.attr(\"label/text\", editingElement.value.label || \"\");\r\n }\r\n showEditor.value = false;\r\n emitDataChange();\r\n};\r\n\r\nconst deleteElement = () => {\r\n if (!graph.value || !editingElement.value) return;\r\n graph.value.getCellById(editingElement.value.id)?.remove();\r\n showEditor.value = false;\r\n emitDataChange();\r\n};\r\n\r\n// ==================== 数据获取 ====================\r\nconst getCurrentData = (): BPMNElement[] => {\r\n if (!graph.value) return [];\r\n return [\r\n ...graph.value.getNodes().map((node) => ({\r\n id: node.id,\r\n shape: node.shape,\r\n x: node.getPosition().x,\r\n y: node.getPosition().y,\r\n width: node.getSize().width,\r\n height: node.getSize().height,\r\n label: getLabel(node),\r\n data: node.getData() || {},\r\n })),\r\n ...graph.value.getEdges().map((edge) => ({\r\n id: edge.id,\r\n shape: \"bpmn-edge\",\r\n source: edge.getSourceCellId() || \"\",\r\n target: edge.getTargetCellId() || \"\",\r\n label: getLabel(edge),\r\n x: 0,\r\n y: 0,\r\n })),\r\n ] as BPMNElement[];\r\n};\r\n\r\nconst emitDataChange = () => emit(\"data-change\", getCurrentData());\r\n\r\n// ==================== Graph 事件绑定 ====================\r\nwatch(\r\n graph,\r\n (newGraph) => {\r\n if (!(newGraph instanceof Graph)) return;\r\n\r\n if (!props.readonly) {\r\n bindInteractions();\r\n newGraph.on(\"node:dblclick\", ({ node }) => editElement(node));\r\n newGraph.on(\"node:moved\", emitDataChange);\r\n }\r\n\r\n emit(\"ready\", newGraph);\r\n loadData(sampleData);\r\n },\r\n { immediate: true },\r\n);\r\n\r\nwatch(\r\n () => props.data,\r\n (newData) => {\r\n if (!newData || !graph.value) return;\r\n const normalized = normalizeData(newData);\r\n if (normalized.length > 0) loadData(normalized);\r\n },\r\n { deep: true },\r\n);\r\n\r\nonMounted(async () => {\r\n registerNodes();\r\n const bpmnConfig = getGraphConfig();\r\n await initGraph({\r\n ...bpmnConfig,\r\n connecting: {\r\n ...bpmnConfig.connecting,\r\n createEdge: () => graph.value!.createEdge({ shape: \"bpmn-edge\" }),\r\n validateConnection: ({\r\n sourceView,\r\n targetView,\r\n sourceMagnet,\r\n targetMagnet,\r\n }: any) =>\r\n sourceView !== targetView &&\r\n !!sourceMagnet &&\r\n !!targetMagnet &&\r\n sourceMagnet.getAttribute(\"magnet\") === \"true\" &&\r\n targetMagnet.getAttribute(\"magnet\") === \"true\",\r\n },\r\n });\r\n});\r\n\r\ndefineExpose({\r\n getGraph: () => graph.value,\r\n getData: getCurrentData,\r\n loadData,\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","/* unplugin-vue-components disabled */<template>\r\n <NDrawer\r\n :show=\"show\"\r\n width=\"450\"\r\n title=\"编辑类\"\r\n @update:show=\"emit('update:show', $event)\"\r\n >\r\n <div v-if=\"editingClass\" class=\"class-editor\">\r\n <NForm label-placement=\"top\">\r\n <NFormItem label=\"类名\">\r\n <NInput v-model:value=\"editingClass.name\" placeholder=\"请输入类名\" />\r\n </NFormItem>\r\n\r\n <NDivider style=\"margin: 20px 0\">属性</NDivider>\r\n <div class=\"section\">\r\n <div\r\n v-for=\"(attr, index) in editingClass.attributes\"\r\n :key=\"index\"\r\n class=\"item\"\r\n >\r\n <NCard size=\"small\" style=\"margin-bottom: 12px\">\r\n <template #header>\r\n <div class=\"section-header\">\r\n <span style=\"font-size: 13px\"\r\n >#{{ index + 1 }} {{ attr.name || \"新属性\" }}</span\r\n >\r\n <NButton\r\n @click=\"editingClass!.attributes.splice(index, 1)\"\r\n size=\"tiny\"\r\n type=\"error\"\r\n quaternary\r\n style=\"margin-left: auto\"\r\n >\r\n 删除\r\n </NButton>\r\n </div>\r\n </template>\r\n <NSpace vertical size=\"small\">\r\n <NSpace>\r\n <NFormItem label=\"属性名\" style=\"margin: 0; flex: 1\">\r\n <NInput\r\n v-model:value=\"attr.name\"\r\n placeholder=\"属性名\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n <NFormItem label=\"类型\" style=\"margin: 0; flex: 1\">\r\n <NInput\r\n v-model:value=\"attr.type\"\r\n placeholder=\"类型\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n </NSpace>\r\n <NFormItem label=\"可见性\" style=\"margin: 0\">\r\n <NRadioGroup v-model:value=\"attr.visibility\" size=\"small\">\r\n <NRadio value=\"public\">+ public</NRadio>\r\n <NRadio value=\"private\">- private</NRadio>\r\n <NRadio value=\"protected\"># protected</NRadio>\r\n </NRadioGroup>\r\n </NFormItem>\r\n </NSpace>\r\n </NCard>\r\n </div>\r\n <NButton\r\n @click=\"\r\n editingClass!.attributes.push({\r\n name: 'newAttribute',\r\n type: 'string',\r\n visibility: 'private',\r\n })\r\n \"\r\n dashed\r\n block\r\n type=\"primary\"\r\n ghost\r\n size=\"small\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </template>\r\n 添加属性\r\n </NButton>\r\n </div>\r\n\r\n <NDivider style=\"margin: 20px 0\">方法</NDivider>\r\n <div class=\"section\">\r\n <div\r\n v-for=\"(method, index) in editingClass.methods\"\r\n :key=\"index\"\r\n class=\"item\"\r\n >\r\n <NCard size=\"small\" style=\"margin-bottom: 12px\">\r\n <template #header>\r\n <div class=\"section-header\">\r\n <span style=\"font-size: 13px\"\r\n >#{{ index + 1 }} {{ method.name || \"新方法\" }}</span\r\n >\r\n <NButton\r\n @click=\"editingClass!.methods.splice(index, 1)\"\r\n size=\"tiny\"\r\n type=\"error\"\r\n quaternary\r\n style=\"margin-left: auto\"\r\n >\r\n 删除\r\n </NButton>\r\n </div>\r\n </template>\r\n <NSpace vertical size=\"small\">\r\n <NSpace>\r\n <NFormItem label=\"方法名\" style=\"margin: 0; flex: 1\">\r\n <NInput\r\n v-model:value=\"method.name\"\r\n placeholder=\"方法名\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n <NFormItem label=\"返回类型\" style=\"margin: 0; flex: 1\">\r\n <NInput\r\n v-model:value=\"method.returnType\"\r\n placeholder=\"返回类型\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n </NSpace>\r\n <NFormItem label=\"可见性\" style=\"margin: 0\">\r\n <NRadioGroup v-model:value=\"method.visibility\" size=\"small\">\r\n <NRadio value=\"public\">+ public</NRadio>\r\n <NRadio value=\"private\">- private</NRadio>\r\n <NRadio value=\"protected\"># protected</NRadio>\r\n </NRadioGroup>\r\n </NFormItem>\r\n </NSpace>\r\n </NCard>\r\n </div>\r\n <NButton\r\n @click=\"\r\n editingClass!.methods.push({\r\n name: 'newMethod',\r\n returnType: 'void',\r\n visibility: 'public',\r\n })\r\n \"\r\n dashed\r\n block\r\n type=\"primary\"\r\n ghost\r\n size=\"small\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </template>\r\n 添加方法\r\n </NButton>\r\n </div>\r\n\r\n <NDivider style=\"margin: 20px 0\"></NDivider>\r\n <NSpace justify=\"space-between\">\r\n <NButton @click=\"emit('delete')\" type=\"error\" ghost>\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete\" :size=\"16\" />\r\n </template>\r\n 删除类\r\n </NButton>\r\n <NSpace>\r\n <NButton @click=\"emit('update:show', false)\">取消</NButton>\r\n <NButton @click=\"emit('save')\" type=\"primary\"> 保存 </NButton>\r\n </NSpace>\r\n </NSpace>\r\n </NForm>\r\n </div>\r\n </NDrawer>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n NDrawer,\r\n NForm,\r\n NFormItem,\r\n NInput,\r\n NCard,\r\n NSpace,\r\n NButton,\r\n NDivider,\r\n NRadioGroup,\r\n NRadio,\r\n} from \"naive-ui\";\r\nimport type { UMLClass } from \"../../types\";\r\nimport C_Icon from \"../../../../C_Icon/index.vue\";\r\n\r\ndefineProps<{\r\n show: boolean;\r\n}>();\r\n\r\nconst editingClass = defineModel<UMLClass>(\"editingClass\");\r\n\r\nconst emit = defineEmits<{\r\n \"update:show\": [value: boolean];\r\n save: [];\r\n delete: [];\r\n}>();\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.class-editor {\r\n padding: 20px;\r\n height: calc(100vh - 60px);\r\n overflow-y: auto;\r\n}\r\n\r\n.section {\r\n max-height: 300px;\r\n overflow-y: auto;\r\n padding-right: 4px;\r\n\r\n &::-webkit-scrollbar {\r\n width: 6px;\r\n }\r\n\r\n &::-webkit-scrollbar-thumb {\r\n background: #d4d4d4;\r\n border-radius: 3px;\r\n }\r\n}\r\n\r\n.section-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n}\r\n\r\n.item {\r\n margin-bottom: 8px;\r\n}\r\n\r\n.section .n-card {\r\n border: 1px solid #e8e8e8;\r\n}\r\n\r\n.section .n-card-header {\r\n padding: 8px 12px;\r\n background: #fafafa;\r\n border-bottom: 1px solid #e8e8e8;\r\n}\r\n\r\n.section .n-card__content {\r\n padding: 12px;\r\n}\r\n\r\n.section .n-form-item {\r\n margin-bottom: 0;\r\n}\r\n\r\n.section .n-form-item-label {\r\n font-size: 12px;\r\n margin-bottom: 4px;\r\n color: #666;\r\n}\r\n</style>\r\n","/* unplugin-vue-components disabled */<template>\r\n <NDrawer\r\n :show=\"show\"\r\n width=\"450\"\r\n title=\"编辑类\"\r\n @update:show=\"emit('update:show', $event)\"\r\n >\r\n <div v-if=\"editingClass\" class=\"class-editor\">\r\n <NForm label-placement=\"top\">\r\n <NFormItem label=\"类名\">\r\n <NInput v-model:value=\"editingClass.name\" placeholder=\"请输入类名\" />\r\n </NFormItem>\r\n\r\n <NDivider style=\"margin: 20px 0\">属性</NDivider>\r\n <div class=\"section\">\r\n <div\r\n v-for=\"(attr, index) in editingClass.attributes\"\r\n :key=\"index\"\r\n class=\"item\"\r\n >\r\n <NCard size=\"small\" style=\"margin-bottom: 12px\">\r\n <template #header>\r\n <div class=\"section-header\">\r\n <span style=\"font-size: 13px\"\r\n >#{{ index + 1 }} {{ attr.name || \"新属性\" }}</span\r\n >\r\n <NButton\r\n @click=\"editingClass!.attributes.splice(index, 1)\"\r\n size=\"tiny\"\r\n type=\"error\"\r\n quaternary\r\n style=\"margin-left: auto\"\r\n >\r\n 删除\r\n </NButton>\r\n </div>\r\n </template>\r\n <NSpace vertical size=\"small\">\r\n <NSpace>\r\n <NFormItem label=\"属性名\" style=\"margin: 0; flex: 1\">\r\n <NInput\r\n v-model:value=\"attr.name\"\r\n placeholder=\"属性名\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n <NFormItem label=\"类型\" style=\"margin: 0; flex: 1\">\r\n <NInput\r\n v-model:value=\"attr.type\"\r\n placeholder=\"类型\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n </NSpace>\r\n <NFormItem label=\"可见性\" style=\"margin: 0\">\r\n <NRadioGroup v-model:value=\"attr.visibility\" size=\"small\">\r\n <NRadio value=\"public\">+ public</NRadio>\r\n <NRadio value=\"private\">- private</NRadio>\r\n <NRadio value=\"protected\"># protected</NRadio>\r\n </NRadioGroup>\r\n </NFormItem>\r\n </NSpace>\r\n </NCard>\r\n </div>\r\n <NButton\r\n @click=\"\r\n editingClass!.attributes.push({\r\n name: 'newAttribute',\r\n type: 'string',\r\n visibility: 'private',\r\n })\r\n \"\r\n dashed\r\n block\r\n type=\"primary\"\r\n ghost\r\n size=\"small\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </template>\r\n 添加属性\r\n </NButton>\r\n </div>\r\n\r\n <NDivider style=\"margin: 20px 0\">方法</NDivider>\r\n <div class=\"section\">\r\n <div\r\n v-for=\"(method, index) in editingClass.methods\"\r\n :key=\"index\"\r\n class=\"item\"\r\n >\r\n <NCard size=\"small\" style=\"margin-bottom: 12px\">\r\n <template #header>\r\n <div class=\"section-header\">\r\n <span style=\"font-size: 13px\"\r\n >#{{ index + 1 }} {{ method.name || \"新方法\" }}</span\r\n >\r\n <NButton\r\n @click=\"editingClass!.methods.splice(index, 1)\"\r\n size=\"tiny\"\r\n type=\"error\"\r\n quaternary\r\n style=\"margin-left: auto\"\r\n >\r\n 删除\r\n </NButton>\r\n </div>\r\n </template>\r\n <NSpace vertical size=\"small\">\r\n <NSpace>\r\n <NFormItem label=\"方法名\" style=\"margin: 0; flex: 1\">\r\n <NInput\r\n v-model:value=\"method.name\"\r\n placeholder=\"方法名\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n <NFormItem label=\"返回类型\" style=\"margin: 0; flex: 1\">\r\n <NInput\r\n v-model:value=\"method.returnType\"\r\n placeholder=\"返回类型\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n </NSpace>\r\n <NFormItem label=\"可见性\" style=\"margin: 0\">\r\n <NRadioGroup v-model:value=\"method.visibility\" size=\"small\">\r\n <NRadio value=\"public\">+ public</NRadio>\r\n <NRadio value=\"private\">- private</NRadio>\r\n <NRadio value=\"protected\"># protected</NRadio>\r\n </NRadioGroup>\r\n </NFormItem>\r\n </NSpace>\r\n </NCard>\r\n </div>\r\n <NButton\r\n @click=\"\r\n editingClass!.methods.push({\r\n name: 'newMethod',\r\n returnType: 'void',\r\n visibility: 'public',\r\n })\r\n \"\r\n dashed\r\n block\r\n type=\"primary\"\r\n ghost\r\n size=\"small\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </template>\r\n 添加方法\r\n </NButton>\r\n </div>\r\n\r\n <NDivider style=\"margin: 20px 0\"></NDivider>\r\n <NSpace justify=\"space-between\">\r\n <NButton @click=\"emit('delete')\" type=\"error\" ghost>\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete\" :size=\"16\" />\r\n </template>\r\n 删除类\r\n </NButton>\r\n <NSpace>\r\n <NButton @click=\"emit('update:show', false)\">取消</NButton>\r\n <NButton @click=\"emit('save')\" type=\"primary\"> 保存 </NButton>\r\n </NSpace>\r\n </NSpace>\r\n </NForm>\r\n </div>\r\n </NDrawer>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n NDrawer,\r\n NForm,\r\n NFormItem,\r\n NInput,\r\n NCard,\r\n NSpace,\r\n NButton,\r\n NDivider,\r\n NRadioGroup,\r\n NRadio,\r\n} from \"naive-ui\";\r\nimport type { UMLClass } from \"../../types\";\r\nimport C_Icon from \"../../../../C_Icon/index.vue\";\r\n\r\ndefineProps<{\r\n show: boolean;\r\n}>();\r\n\r\nconst editingClass = defineModel<UMLClass>(\"editingClass\");\r\n\r\nconst emit = defineEmits<{\r\n \"update:show\": [value: boolean];\r\n save: [];\r\n delete: [];\r\n}>();\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.class-editor {\r\n padding: 20px;\r\n height: calc(100vh - 60px);\r\n overflow-y: auto;\r\n}\r\n\r\n.section {\r\n max-height: 300px;\r\n overflow-y: auto;\r\n padding-right: 4px;\r\n\r\n &::-webkit-scrollbar {\r\n width: 6px;\r\n }\r\n\r\n &::-webkit-scrollbar-thumb {\r\n background: #d4d4d4;\r\n border-radius: 3px;\r\n }\r\n}\r\n\r\n.section-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n}\r\n\r\n.item {\r\n margin-bottom: 8px;\r\n}\r\n\r\n.section .n-card {\r\n border: 1px solid #e8e8e8;\r\n}\r\n\r\n.section .n-card-header {\r\n padding: 8px 12px;\r\n background: #fafafa;\r\n border-bottom: 1px solid #e8e8e8;\r\n}\r\n\r\n.section .n-card__content {\r\n padding: 12px;\r\n}\r\n\r\n.section .n-form-item {\r\n margin-bottom: 0;\r\n}\r\n\r\n.section .n-form-item-label {\r\n font-size: 12px;\r\n margin-bottom: 4px;\r\n color: #666;\r\n}\r\n</style>\r\n","<template>\r\n <NDrawer\r\n :show=\"show\"\r\n width=\"450\"\r\n title=\"编辑类\"\r\n @update:show=\"emit('update:show', $event)\"\r\n >\r\n <div v-if=\"editingClass\" class=\"class-editor\">\r\n <NForm label-placement=\"top\">\r\n <NFormItem label=\"类名\">\r\n <NInput v-model:value=\"editingClass.name\" placeholder=\"请输入类名\" />\r\n </NFormItem>\r\n\r\n <NDivider style=\"margin: 20px 0\">属性</NDivider>\r\n <div class=\"section\">\r\n <div\r\n v-for=\"(attr, index) in editingClass.attributes\"\r\n :key=\"index\"\r\n class=\"item\"\r\n >\r\n <NCard size=\"small\" style=\"margin-bottom: 12px\">\r\n <template #header>\r\n <div class=\"section-header\">\r\n <span style=\"font-size: 13px\"\r\n >#{{ index + 1 }} {{ attr.name || \"新属性\" }}</span\r\n >\r\n <NButton\r\n @click=\"editingClass!.attributes.splice(index, 1)\"\r\n size=\"tiny\"\r\n type=\"error\"\r\n quaternary\r\n style=\"margin-left: auto\"\r\n >\r\n 删除\r\n </NButton>\r\n </div>\r\n </template>\r\n <NSpace vertical size=\"small\">\r\n <NSpace>\r\n <NFormItem label=\"属性名\" style=\"margin: 0; flex: 1\">\r\n <NInput\r\n v-model:value=\"attr.name\"\r\n placeholder=\"属性名\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n <NFormItem label=\"类型\" style=\"margin: 0; flex: 1\">\r\n <NInput\r\n v-model:value=\"attr.type\"\r\n placeholder=\"类型\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n </NSpace>\r\n <NFormItem label=\"可见性\" style=\"margin: 0\">\r\n <NRadioGroup v-model:value=\"attr.visibility\" size=\"small\">\r\n <NRadio value=\"public\">+ public</NRadio>\r\n <NRadio value=\"private\">- private</NRadio>\r\n <NRadio value=\"protected\"># protected</NRadio>\r\n </NRadioGroup>\r\n </NFormItem>\r\n </NSpace>\r\n </NCard>\r\n </div>\r\n <NButton\r\n @click=\"\r\n editingClass!.attributes.push({\r\n name: 'newAttribute',\r\n type: 'string',\r\n visibility: 'private',\r\n })\r\n \"\r\n dashed\r\n block\r\n type=\"primary\"\r\n ghost\r\n size=\"small\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </template>\r\n 添加属性\r\n </NButton>\r\n </div>\r\n\r\n <NDivider style=\"margin: 20px 0\">方法</NDivider>\r\n <div class=\"section\">\r\n <div\r\n v-for=\"(method, index) in editingClass.methods\"\r\n :key=\"index\"\r\n class=\"item\"\r\n >\r\n <NCard size=\"small\" style=\"margin-bottom: 12px\">\r\n <template #header>\r\n <div class=\"section-header\">\r\n <span style=\"font-size: 13px\"\r\n >#{{ index + 1 }} {{ method.name || \"新方法\" }}</span\r\n >\r\n <NButton\r\n @click=\"editingClass!.methods.splice(index, 1)\"\r\n size=\"tiny\"\r\n type=\"error\"\r\n quaternary\r\n style=\"margin-left: auto\"\r\n >\r\n 删除\r\n </NButton>\r\n </div>\r\n </template>\r\n <NSpace vertical size=\"small\">\r\n <NSpace>\r\n <NFormItem label=\"方法名\" style=\"margin: 0; flex: 1\">\r\n <NInput\r\n v-model:value=\"method.name\"\r\n placeholder=\"方法名\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n <NFormItem label=\"返回类型\" style=\"margin: 0; flex: 1\">\r\n <NInput\r\n v-model:value=\"method.returnType\"\r\n placeholder=\"返回类型\"\r\n size=\"small\"\r\n />\r\n </NFormItem>\r\n </NSpace>\r\n <NFormItem label=\"可见性\" style=\"margin: 0\">\r\n <NRadioGroup v-model:value=\"method.visibility\" size=\"small\">\r\n <NRadio value=\"public\">+ public</NRadio>\r\n <NRadio value=\"private\">- private</NRadio>\r\n <NRadio value=\"protected\"># protected</NRadio>\r\n </NRadioGroup>\r\n </NFormItem>\r\n </NSpace>\r\n </NCard>\r\n </div>\r\n <NButton\r\n @click=\"\r\n editingClass!.methods.push({\r\n name: 'newMethod',\r\n returnType: 'void',\r\n visibility: 'public',\r\n })\r\n \"\r\n dashed\r\n block\r\n type=\"primary\"\r\n ghost\r\n size=\"small\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </template>\r\n 添加方法\r\n </NButton>\r\n </div>\r\n\r\n <NDivider style=\"margin: 20px 0\"></NDivider>\r\n <NSpace justify=\"space-between\">\r\n <NButton @click=\"emit('delete')\" type=\"error\" ghost>\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete\" :size=\"16\" />\r\n </template>\r\n 删除类\r\n </NButton>\r\n <NSpace>\r\n <NButton @click=\"emit('update:show', false)\">取消</NButton>\r\n <NButton @click=\"emit('save')\" type=\"primary\"> 保存 </NButton>\r\n </NSpace>\r\n </NSpace>\r\n </NForm>\r\n </div>\r\n </NDrawer>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n NDrawer,\r\n NForm,\r\n NFormItem,\r\n NInput,\r\n NCard,\r\n NSpace,\r\n NButton,\r\n NDivider,\r\n NRadioGroup,\r\n NRadio,\r\n} from \"naive-ui\";\r\nimport type { UMLClass } from \"../../types\";\r\nimport C_Icon from \"../../../../C_Icon/index.vue\";\r\n\r\ndefineProps<{\r\n show: boolean;\r\n}>();\r\n\r\nconst editingClass = defineModel<UMLClass>(\"editingClass\");\r\n\r\nconst emit = defineEmits<{\r\n \"update:show\": [value: boolean];\r\n save: [];\r\n delete: [];\r\n}>();\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.class-editor {\r\n padding: 20px;\r\n height: calc(100vh - 60px);\r\n overflow-y: auto;\r\n}\r\n\r\n.section {\r\n max-height: 300px;\r\n overflow-y: auto;\r\n padding-right: 4px;\r\n\r\n &::-webkit-scrollbar {\r\n width: 6px;\r\n }\r\n\r\n &::-webkit-scrollbar-thumb {\r\n background: #d4d4d4;\r\n border-radius: 3px;\r\n }\r\n}\r\n\r\n.section-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n}\r\n\r\n.item {\r\n margin-bottom: 8px;\r\n}\r\n\r\n.section .n-card {\r\n border: 1px solid #e8e8e8;\r\n}\r\n\r\n.section .n-card-header {\r\n padding: 8px 12px;\r\n background: #fafafa;\r\n border-bottom: 1px solid #e8e8e8;\r\n}\r\n\r\n.section .n-card__content {\r\n padding: 12px;\r\n}\r\n\r\n.section .n-form-item {\r\n margin-bottom: 0;\r\n}\r\n\r\n.section .n-form-item-label {\r\n font-size: 12px;\r\n margin-bottom: 4px;\r\n color: #666;\r\n}\r\n</style>\r\n","import type { UMLAttribute, UMLMethod } from \"../../types\";\r\n\r\nexport const visibilityMap = {\r\n public: \"+\",\r\n private: \"-\",\r\n protected: \"#\",\r\n};\r\n\r\n/* 连线类型配置 */\r\nexport const edgeTypes = {\r\n extends: {\r\n strokeDasharray: 0,\r\n targetMarker: {\r\n name: \"block\",\r\n width: 10,\r\n height: 8,\r\n fill: \"white\",\r\n stroke: \"#722ed1\",\r\n strokeWidth: 2,\r\n },\r\n },\r\n implement: {\r\n strokeDasharray: \"8,4\",\r\n targetMarker: {\r\n name: \"block\",\r\n width: 10,\r\n height: 8,\r\n fill: \"white\",\r\n stroke: \"#722ed1\",\r\n strokeWidth: 2,\r\n },\r\n },\r\n composition: {\r\n strokeDasharray: 0,\r\n sourceMarker: {\r\n name: \"diamond\",\r\n width: 12,\r\n height: 8,\r\n fill: \"#722ed1\",\r\n stroke: \"#722ed1\",\r\n },\r\n },\r\n aggregation: {\r\n strokeDasharray: 0,\r\n sourceMarker: {\r\n name: \"diamond\",\r\n width: 12,\r\n height: 8,\r\n fill: \"white\",\r\n stroke: \"#722ed1\",\r\n strokeWidth: 2,\r\n },\r\n },\r\n association: {\r\n strokeDasharray: 0,\r\n targetMarker: { name: \"classic\", size: 8, fill: \"#722ed1\" },\r\n },\r\n};\r\n\r\n/* 端口配置 */\r\nexport const portConfig = [\"top\", \"right\", \"bottom\", \"left\"].reduce(\r\n (acc, pos) => {\r\n const refConfig =\r\n pos === \"top\"\r\n ? { refX: 0.5, refY: 0 }\r\n : pos === \"right\"\r\n ? { refX: 1, refY: 0.5 }\r\n : pos === \"bottom\"\r\n ? { refX: 0.5, refY: 1 }\r\n : { refX: 0, refY: 0.5 };\r\n acc[`port-${pos}`] = {\r\n ref: \"body\",\r\n ...refConfig,\r\n r: 4,\r\n fill: \"#31d0c6\",\r\n stroke: \"#ffffff\",\r\n strokeWidth: 2,\r\n magnet: true,\r\n style: {\r\n cursor: \"crosshair\",\r\n opacity: 0,\r\n transition: \"opacity 0.2s\",\r\n },\r\n };\r\n return acc;\r\n },\r\n {} as any,\r\n);\r\n\r\n/* UML类节点配置 */\r\nexport const classNodeConfig = {\r\n inherit: \"rect\",\r\n markup: [\r\n { tagName: \"rect\", selector: \"body\" },\r\n { tagName: \"rect\", selector: \"name-rect\" },\r\n { tagName: \"rect\", selector: \"attrs-rect\" },\r\n { tagName: \"rect\", selector: \"methods-rect\" },\r\n { tagName: \"text\", selector: \"name-text\" },\r\n { tagName: \"text\", selector: \"attrs-text\" },\r\n { tagName: \"text\", selector: \"methods-text\" },\r\n { tagName: \"circle\", selector: \"port-top\" },\r\n { tagName: \"circle\", selector: \"port-right\" },\r\n { tagName: \"circle\", selector: \"port-bottom\" },\r\n { tagName: \"circle\", selector: \"port-left\" },\r\n ],\r\n attrs: {\r\n rect: { width: 160 },\r\n body: {\r\n stroke: \"#5f95ff\",\r\n strokeWidth: 1,\r\n fill: \"#fff\",\r\n magnet: false,\r\n style: { cursor: \"move\" },\r\n },\r\n \"name-rect\": {\r\n fill: \"#5f95ff\",\r\n stroke: \"#fff\",\r\n strokeWidth: 0.5,\r\n magnet: false,\r\n style: { cursor: \"move\" },\r\n },\r\n \"attrs-rect\": {\r\n fill: \"#eff4ff\",\r\n stroke: \"#fff\",\r\n strokeWidth: 0.5,\r\n magnet: false,\r\n style: { cursor: \"move\" },\r\n },\r\n \"methods-rect\": {\r\n fill: \"#eff4ff\",\r\n stroke: \"#fff\",\r\n strokeWidth: 0.5,\r\n magnet: false,\r\n style: { cursor: \"move\" },\r\n },\r\n \"name-text\": {\r\n ref: \"name-rect\",\r\n refY: 0.5,\r\n refX: 0.5,\r\n textAnchor: \"middle\",\r\n fontWeight: \"bold\",\r\n fill: \"#fff\",\r\n fontSize: 12,\r\n pointerEvents: \"none\",\r\n },\r\n \"attrs-text\": {\r\n ref: \"attrs-rect\",\r\n refY: 0.5,\r\n refX: 5,\r\n textAnchor: \"left\",\r\n fill: \"black\",\r\n fontSize: 10,\r\n pointerEvents: \"none\",\r\n },\r\n \"methods-text\": {\r\n ref: \"methods-rect\",\r\n refY: 0.5,\r\n refX: 5,\r\n textAnchor: \"left\",\r\n fill: \"black\",\r\n fontSize: 10,\r\n pointerEvents: \"none\",\r\n },\r\n ...portConfig,\r\n },\r\n};\r\n\r\n/* 图形配置 */\r\nexport const graphConfig = {\r\n grid: true,\r\n panning: true,\r\n mousewheel: {\r\n enabled: true,\r\n zoomAtMousePosition: true,\r\n modifiers: \"ctrl\",\r\n minScale: 0.5,\r\n maxScale: 3,\r\n },\r\n};\r\n\r\n/* 连接配置 */\r\nexport const connectingConfig = {\r\n router: \"manhattan\",\r\n connector: { name: \"rounded\", args: { radius: 8 } },\r\n allowBlank: false,\r\n allowLoop: false,\r\n allowNode: false,\r\n snap: true,\r\n allowEdge: false,\r\n};\r\n\r\n/* 高亮配置 */\r\nexport const highlightingConfig = {\r\n magnetAvailable: {\r\n name: \"stroke\",\r\n args: { attrs: { fill: \"#31d0c6\", stroke: \"#31d0c6\", opacity: 1 } },\r\n },\r\n magnetAdsorbed: {\r\n name: \"stroke\",\r\n args: { attrs: { fill: \"#5F95FF\", stroke: \"#5F95FF\", opacity: 1 } },\r\n },\r\n};\r\n\r\n/* 默认边配置 */\r\nexport const defaultEdgeConfig = {\r\n shape: \"extends\",\r\n attrs: {\r\n line: {\r\n stroke: \"#722ed1\",\r\n strokeWidth: 2,\r\n targetMarker: {\r\n name: \"block\",\r\n width: 8,\r\n height: 6,\r\n fill: \"#722ed1\",\r\n },\r\n },\r\n },\r\n};\r\n\r\n/* 示例类数据 */\r\nexport const sampleClasses = [\r\n {\r\n id: \"animal\",\r\n name: \"<<Abstract>>\\n动物\",\r\n x: 342,\r\n y: 98,\r\n attributes: [{ name: \"名字\", type: \"string\", visibility: \"public\" }],\r\n methods: [\r\n { name: \"新陈代谢\", returnType: \"void\", visibility: \"public\" },\r\n { name: \"繁殖\", returnType: \"void\", visibility: \"public\" },\r\n ],\r\n },\r\n {\r\n id: \"bird\",\r\n name: \"鸟\",\r\n x: 575,\r\n y: 280,\r\n attributes: [\r\n { name: \"羽毛\", type: \"string\", visibility: \"public\" },\r\n { name: \"下蛋\", type: \"boolean\", visibility: \"public\" },\r\n ],\r\n methods: [],\r\n },\r\n {\r\n id: \"mammal\",\r\n name: \"哺乳动物\",\r\n x: 844,\r\n y: 319,\r\n attributes: [{ name: \"翅膀\", type: \"boolean\", visibility: \"public\" }],\r\n methods: [],\r\n },\r\n {\r\n id: \"dog\",\r\n name: \"狗\",\r\n x: 656,\r\n y: 456,\r\n attributes: [{ name: \"下蛋\", type: \"boolean\", visibility: \"public\" }],\r\n methods: [],\r\n },\r\n {\r\n id: \"enterprise\",\r\n name: \"企鹅\",\r\n x: 984,\r\n y: 456,\r\n attributes: [{ name: \"下蛋\", type: \"boolean\", visibility: \"public\" }],\r\n methods: [],\r\n },\r\n {\r\n id: \"sparrow\",\r\n name: \"麻雀\",\r\n x: 531,\r\n y: 608,\r\n attributes: [{ name: \"学飞\", type: \"boolean\", visibility: \"public\" }],\r\n methods: [],\r\n },\r\n {\r\n id: \"interface-flying\",\r\n name: \"<<interface>>\\n飞翔\",\r\n x: 762,\r\n y: 608,\r\n attributes: [{ name: \"下蛋\", type: \"boolean\", visibility: \"public\" }],\r\n methods: [],\r\n },\r\n { id: \"qemu\", name: \"气球\", x: 981, y: 608, attributes: [], methods: [] },\r\n];\r\n\r\n/* 示例连接关系 */\r\nexport const sampleConnections = [\r\n { id: \"edge-1\", shape: \"extends\", source: \"bird\", target: \"animal\" },\r\n { id: \"edge-2\", shape: \"extends\", source: \"mammal\", target: \"animal\" },\r\n { id: \"edge-3\", shape: \"extends\", source: \"dog\", target: \"mammal\" },\r\n {\r\n id: \"edge-4\",\r\n shape: \"extends\",\r\n source: \"enterprise\",\r\n target: \"mammal\",\r\n },\r\n { id: \"edge-5\", shape: \"extends\", source: \"sparrow\", target: \"bird\" },\r\n {\r\n id: \"edge-6\",\r\n shape: \"implement\",\r\n source: \"sparrow\",\r\n target: \"interface-flying\",\r\n },\r\n {\r\n id: \"edge-7\",\r\n shape: \"implement\",\r\n source: \"enterprise\",\r\n target: \"interface-flying\",\r\n },\r\n {\r\n id: \"edge-8\",\r\n shape: \"association\",\r\n source: \"bird\",\r\n target: \"mammal\",\r\n label: \"1:2\",\r\n },\r\n];\r\n\r\n/* 工具函数 */\r\nexport const getVisibilitySymbol = (visibility: string) =>\r\n visibilityMap[visibility as keyof typeof visibilityMap] || \"+\";\r\n\r\n/* 新类模板 */\r\nexport const newClassTemplate = {\r\n name: \"新类\",\r\n attributes: [\r\n { name: \"attribute\", type: \"string\", visibility: \"private\" },\r\n ] as UMLAttribute[],\r\n methods: [\r\n { name: \"method\", returnType: \"void\", visibility: \"public\" },\r\n ] as UMLMethod[],\r\n position: { x: 100, y: 100 },\r\n};\r\n","/* unplugin-vue-components disabled */<template>\r\n <div class=\"uml-layout\">\r\n <div class=\"toolbar\" v-if=\"showToolbar\">\r\n <NSpace>\r\n <NButton @click=\"addClass\" type=\"primary\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:plus-box\" :size=\"16\" />\r\n </template>\r\n 添加类\r\n </NButton>\r\n <NButton @click=\"centerContent\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:image-filter-center-focus\" :size=\"16\" />\r\n </template>\r\n 居中\r\n </NButton>\r\n <NButton @click=\"zoomToFit\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:fit-to-screen\" :size=\"16\" />\r\n </template>\r\n 适应\r\n </NButton>\r\n <NDropdown\r\n :options=\"exportOptions\"\r\n @select=\"(key: string) => handleExport(key, getCurrentData)\"\r\n >\r\n <NButton size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:export\" :size=\"16\" />\r\n </template>\r\n 导出\r\n </NButton>\r\n </NDropdown>\r\n </NSpace>\r\n </div>\r\n\r\n <div\r\n ref=\"containerRef\"\r\n class=\"graph-container\"\r\n :style=\"{ width: props.width, height: props.height }\"\r\n ></div>\r\n\r\n <UMLClassEditor\r\n :show=\"showEditor\"\r\n v-model:editing-class=\"editingClass\"\r\n @update:show=\"showEditor = $event\"\r\n @save=\"saveClass\"\r\n @delete=\"deleteClass\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, onMounted } from \"vue\";\r\nimport { NSpace, NButton, NDropdown } from \"naive-ui\";\r\nimport { Graph, ObjectExt, type Node, type Cell } from \"@antv/x6\";\r\nimport { useGraphBase } from \"../../composables/useGraphBase\";\r\nimport { useGraphExport } from \"../../composables/useGraphExport\";\r\nimport { useEdgeInteraction } from \"../../composables/useEdgeInteraction\";\r\nimport type { UMLClass, UMLDiagramData } from \"../../types\";\r\nimport UMLClassEditor from \"./components/UMLClassEditor.vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\nimport {\r\n edgeTypes,\r\n classNodeConfig,\r\n connectingConfig,\r\n highlightingConfig,\r\n defaultEdgeConfig,\r\n sampleClasses,\r\n sampleConnections,\r\n getVisibilitySymbol,\r\n newClassTemplate,\r\n} from \"./data\";\r\n\r\ninterface Props {\r\n data?: UMLDiagramData;\r\n showToolbar?: boolean;\r\n readonly?: boolean;\r\n width?: number | string;\r\n height?: number | string;\r\n theme?: \"light\" | \"dark\";\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n showToolbar: true,\r\n readonly: false,\r\n width: \"100%\",\r\n height: \"600px\",\r\n});\r\n\r\nconst emit = defineEmits<{\r\n ready: [graph: any];\r\n \"data-change\": [data: UMLDiagramData];\r\n}>();\r\n\r\n// ==================== Composables ====================\r\nconst containerRef = ref<HTMLDivElement>();\r\nconst isDark = computed(() => props.theme === \"dark\");\r\nconst { graph, initGraph, centerContent, zoomToFit } = useGraphBase(\r\n containerRef,\r\n isDark,\r\n);\r\nconst { exportOptions, handleExport } = useGraphExport(graph, \"uml-diagram\");\r\nconst { bindInteractions } = useEdgeInteraction(graph, {\r\n defaultColor: \"#722ed1\",\r\n portPositions: [\"top\", \"right\", \"bottom\", \"left\"],\r\n onDataChange: () => emitDataChange(),\r\n});\r\n\r\n// ==================== 编辑器状态 ====================\r\nconst showEditor = ref(false);\r\nconst editingClass = ref<UMLClass>();\r\n\r\n// ==================== 节点注册 ====================\r\nconst registerNodes = () => {\r\n Graph.registerNode(\r\n \"class\",\r\n {\r\n ...classNodeConfig,\r\n /** @description 节点属性钩子,用于计算 UML 类图节点布局 */\r\n propHooks(meta: any) {\r\n const { name, attributes, methods, ...others } = meta;\r\n if (!(name && attributes && methods)) return meta;\r\n\r\n let offsetY = 0;\r\n [\r\n { type: \"name\", text: name },\r\n { type: \"attrs\", text: attributes },\r\n { type: \"methods\", text: methods },\r\n ].forEach((rect) => {\r\n const height = Array.isArray(rect.text)\r\n ? rect.text.length * 12 + 16\r\n : 32;\r\n ObjectExt.setByPath(\r\n others,\r\n `attrs/${rect.type}-text/text`,\r\n Array.isArray(rect.text) ? rect.text.join(\"\\n\") : rect.text,\r\n );\r\n ObjectExt.setByPath(others, `attrs/${rect.type}-rect/height`, height);\r\n ObjectExt.setByPath(\r\n others,\r\n `attrs/${rect.type}-rect/transform`,\r\n `translate(0,${offsetY})`,\r\n );\r\n offsetY += height;\r\n });\r\n others.size = { width: 160, height: offsetY };\r\n return others;\r\n },\r\n },\r\n true,\r\n );\r\n\r\n Object.entries(edgeTypes).forEach(([type, config]) => {\r\n Graph.registerEdge(\r\n type,\r\n {\r\n inherit: \"edge\",\r\n attrs: { line: { strokeWidth: 2, stroke: \"#722ed1\", ...config } },\r\n },\r\n true,\r\n );\r\n });\r\n};\r\n\r\n// ==================== 数据加载 ====================\r\nconst loadData = (data: any[]) => {\r\n if (!graph.value || !data.length) return;\r\n const edgeShapes = [\r\n \"extends\",\r\n \"composition\",\r\n \"implement\",\r\n \"aggregation\",\r\n \"association\",\r\n ];\r\n const cells: Cell[] = [];\r\n\r\n data.forEach((item) => {\r\n if (edgeShapes.includes(item.shape)) {\r\n cells.push(graph.value!.createEdge(item));\r\n } else {\r\n const { data: nodeData, ...displayProps } = item;\r\n const node = graph.value!.createNode(displayProps);\r\n if (nodeData) node.setData(nodeData);\r\n cells.push(node);\r\n }\r\n });\r\n\r\n graph.value.resetCells(cells);\r\n setTimeout(() => graph.value!.zoomToFit({ padding: 10, maxScale: 1 }), 200);\r\n};\r\n\r\nconst formatClassDisplay = (cls: {\r\n name: string;\r\n attributes: any[];\r\n methods: any[];\r\n}) => ({\r\n name: cls.name,\r\n attributes: cls.attributes.map(\r\n (attr) =>\r\n `${getVisibilitySymbol(attr.visibility)} ${attr.name}: ${attr.type}`,\r\n ),\r\n methods: cls.methods.map(\r\n (method) =>\r\n `${getVisibilitySymbol(method.visibility)} ${method.name}(): ${method.returnType}`,\r\n ),\r\n});\r\n\r\n// ==================== 类 CRUD ====================\r\nconst addClass = () => {\r\n if (!graph.value) return;\r\n const newClass: UMLClass = {\r\n id: `class_${Date.now()}`,\r\n ...newClassTemplate,\r\n };\r\n const nodeData = {\r\n id: newClass.id,\r\n shape: \"class\",\r\n x: newClass.position.x,\r\n y: newClass.position.y,\r\n ...formatClassDisplay(newClass),\r\n data: newClass,\r\n };\r\n graph.value.addCell(graph.value.createNode(nodeData));\r\n emitDataChange();\r\n};\r\n\r\nconst editClass = (umlClass: UMLClass) => {\r\n editingClass.value = {\r\n ...umlClass,\r\n attributes: [...(umlClass.attributes || [])],\r\n methods: [...(umlClass.methods || [])],\r\n };\r\n showEditor.value = true;\r\n};\r\n\r\nconst saveClass = () => {\r\n if (!graph.value || !editingClass.value) return;\r\n const node = graph.value.getCellById(editingClass.value.id) as Node;\r\n if (node) {\r\n node.setData(editingClass.value);\r\n node.prop(formatClassDisplay(editingClass.value));\r\n }\r\n showEditor.value = false;\r\n emitDataChange();\r\n};\r\n\r\nconst deleteClass = () => {\r\n if (!graph.value || !editingClass.value) return;\r\n graph.value.getCellById(editingClass.value.id)?.remove();\r\n showEditor.value = false;\r\n emitDataChange();\r\n};\r\n\r\n// ==================== 数据获取 ====================\r\nconst getCurrentData = (): UMLDiagramData => ({\r\n classes:\r\n graph.value?.getNodes().map((node: Node) => ({\r\n ...node.getData(),\r\n position: node.getPosition(),\r\n })) || [],\r\n relations:\r\n graph.value?.getEdges().map((edge: any) => ({\r\n id: edge.id,\r\n type: edge.shape as string,\r\n source: edge.getSourceCellId() || \"\",\r\n target: edge.getTargetCellId() || \"\",\r\n })) || [],\r\n});\r\n\r\nconst emitDataChange = () => emit(\"data-change\", getCurrentData());\r\n\r\n// ==================== Graph 事件绑定 ====================\r\nwatch(\r\n graph,\r\n (newGraph) => {\r\n if (!(newGraph instanceof Graph)) return;\r\n\r\n registerNodes();\r\n bindInteractions();\r\n\r\n newGraph.on(\"node:dblclick\", ({ node }: { node: Node }) => {\r\n if (!props.readonly && node.getData()) editClass(node.getData());\r\n });\r\n\r\n emit(\"ready\", newGraph);\r\n\r\n const defaultCells = [\r\n ...sampleClasses.map((cls) => ({\r\n id: cls.id,\r\n shape: \"class\",\r\n x: cls.x,\r\n y: cls.y,\r\n ...formatClassDisplay(cls),\r\n data: {\r\n id: cls.id,\r\n name: cls.name,\r\n attributes: cls.attributes,\r\n methods: cls.methods,\r\n position: { x: cls.x, y: cls.y },\r\n },\r\n })),\r\n ...sampleConnections,\r\n ];\r\n loadData(defaultCells);\r\n },\r\n { immediate: true },\r\n);\r\n\r\nwatch(\r\n () => props.data,\r\n (newData) => {\r\n if (graph.value && newData?.classes) {\r\n const edgeShapes = [\r\n \"extends\",\r\n \"composition\",\r\n \"implement\",\r\n \"aggregation\",\r\n \"association\",\r\n ];\r\n const cells: any[] = [\r\n ...newData.classes.map((cls) => ({\r\n id: cls.id,\r\n shape: \"class\",\r\n x: cls.position?.x ?? 50,\r\n y: cls.position?.y ?? 50,\r\n ...formatClassDisplay(cls),\r\n data: cls,\r\n })),\r\n ...(newData.relations || [])\r\n .filter((r) => edgeShapes.includes(r.type))\r\n .map((r) => ({\r\n shape: r.type,\r\n source: r.source,\r\n target: r.target,\r\n })),\r\n ];\r\n loadData(cells);\r\n }\r\n },\r\n { deep: true },\r\n);\r\n\r\nonMounted(() => {\r\n initGraph({\r\n interacting: true,\r\n connecting: {\r\n ...connectingConfig,\r\n createEdge: () => graph.value!.createEdge(defaultEdgeConfig),\r\n validateConnection: ({\r\n sourceView,\r\n targetView,\r\n sourceMagnet,\r\n targetMagnet,\r\n }: any) =>\r\n sourceView !== targetView &&\r\n !!sourceMagnet &&\r\n !!targetMagnet &&\r\n sourceMagnet.getAttribute(\"magnet\") === \"true\" &&\r\n targetMagnet.getAttribute(\"magnet\") === \"true\",\r\n },\r\n highlighting: highlightingConfig,\r\n });\r\n});\r\n\r\ndefineExpose({\r\n getGraph: () => graph.value,\r\n getData: getCurrentData,\r\n addClass,\r\n centerContent,\r\n zoomToFit,\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","/* unplugin-vue-components disabled */<template>\r\n <div class=\"uml-layout\">\r\n <div class=\"toolbar\" v-if=\"showToolbar\">\r\n <NSpace>\r\n <NButton @click=\"addClass\" type=\"primary\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:plus-box\" :size=\"16\" />\r\n </template>\r\n 添加类\r\n </NButton>\r\n <NButton @click=\"centerContent\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:image-filter-center-focus\" :size=\"16\" />\r\n </template>\r\n 居中\r\n </NButton>\r\n <NButton @click=\"zoomToFit\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:fit-to-screen\" :size=\"16\" />\r\n </template>\r\n 适应\r\n </NButton>\r\n <NDropdown\r\n :options=\"exportOptions\"\r\n @select=\"(key: string) => handleExport(key, getCurrentData)\"\r\n >\r\n <NButton size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:export\" :size=\"16\" />\r\n </template>\r\n 导出\r\n </NButton>\r\n </NDropdown>\r\n </NSpace>\r\n </div>\r\n\r\n <div\r\n ref=\"containerRef\"\r\n class=\"graph-container\"\r\n :style=\"{ width: props.width, height: props.height }\"\r\n ></div>\r\n\r\n <UMLClassEditor\r\n :show=\"showEditor\"\r\n v-model:editing-class=\"editingClass\"\r\n @update:show=\"showEditor = $event\"\r\n @save=\"saveClass\"\r\n @delete=\"deleteClass\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, onMounted } from \"vue\";\r\nimport { NSpace, NButton, NDropdown } from \"naive-ui\";\r\nimport { Graph, ObjectExt, type Node, type Cell } from \"@antv/x6\";\r\nimport { useGraphBase } from \"../../composables/useGraphBase\";\r\nimport { useGraphExport } from \"../../composables/useGraphExport\";\r\nimport { useEdgeInteraction } from \"../../composables/useEdgeInteraction\";\r\nimport type { UMLClass, UMLDiagramData } from \"../../types\";\r\nimport UMLClassEditor from \"./components/UMLClassEditor.vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\nimport {\r\n edgeTypes,\r\n classNodeConfig,\r\n connectingConfig,\r\n highlightingConfig,\r\n defaultEdgeConfig,\r\n sampleClasses,\r\n sampleConnections,\r\n getVisibilitySymbol,\r\n newClassTemplate,\r\n} from \"./data\";\r\n\r\ninterface Props {\r\n data?: UMLDiagramData;\r\n showToolbar?: boolean;\r\n readonly?: boolean;\r\n width?: number | string;\r\n height?: number | string;\r\n theme?: \"light\" | \"dark\";\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n showToolbar: true,\r\n readonly: false,\r\n width: \"100%\",\r\n height: \"600px\",\r\n});\r\n\r\nconst emit = defineEmits<{\r\n ready: [graph: any];\r\n \"data-change\": [data: UMLDiagramData];\r\n}>();\r\n\r\n// ==================== Composables ====================\r\nconst containerRef = ref<HTMLDivElement>();\r\nconst isDark = computed(() => props.theme === \"dark\");\r\nconst { graph, initGraph, centerContent, zoomToFit } = useGraphBase(\r\n containerRef,\r\n isDark,\r\n);\r\nconst { exportOptions, handleExport } = useGraphExport(graph, \"uml-diagram\");\r\nconst { bindInteractions } = useEdgeInteraction(graph, {\r\n defaultColor: \"#722ed1\",\r\n portPositions: [\"top\", \"right\", \"bottom\", \"left\"],\r\n onDataChange: () => emitDataChange(),\r\n});\r\n\r\n// ==================== 编辑器状态 ====================\r\nconst showEditor = ref(false);\r\nconst editingClass = ref<UMLClass>();\r\n\r\n// ==================== 节点注册 ====================\r\nconst registerNodes = () => {\r\n Graph.registerNode(\r\n \"class\",\r\n {\r\n ...classNodeConfig,\r\n /** @description 节点属性钩子,用于计算 UML 类图节点布局 */\r\n propHooks(meta: any) {\r\n const { name, attributes, methods, ...others } = meta;\r\n if (!(name && attributes && methods)) return meta;\r\n\r\n let offsetY = 0;\r\n [\r\n { type: \"name\", text: name },\r\n { type: \"attrs\", text: attributes },\r\n { type: \"methods\", text: methods },\r\n ].forEach((rect) => {\r\n const height = Array.isArray(rect.text)\r\n ? rect.text.length * 12 + 16\r\n : 32;\r\n ObjectExt.setByPath(\r\n others,\r\n `attrs/${rect.type}-text/text`,\r\n Array.isArray(rect.text) ? rect.text.join(\"\\n\") : rect.text,\r\n );\r\n ObjectExt.setByPath(others, `attrs/${rect.type}-rect/height`, height);\r\n ObjectExt.setByPath(\r\n others,\r\n `attrs/${rect.type}-rect/transform`,\r\n `translate(0,${offsetY})`,\r\n );\r\n offsetY += height;\r\n });\r\n others.size = { width: 160, height: offsetY };\r\n return others;\r\n },\r\n },\r\n true,\r\n );\r\n\r\n Object.entries(edgeTypes).forEach(([type, config]) => {\r\n Graph.registerEdge(\r\n type,\r\n {\r\n inherit: \"edge\",\r\n attrs: { line: { strokeWidth: 2, stroke: \"#722ed1\", ...config } },\r\n },\r\n true,\r\n );\r\n });\r\n};\r\n\r\n// ==================== 数据加载 ====================\r\nconst loadData = (data: any[]) => {\r\n if (!graph.value || !data.length) return;\r\n const edgeShapes = [\r\n \"extends\",\r\n \"composition\",\r\n \"implement\",\r\n \"aggregation\",\r\n \"association\",\r\n ];\r\n const cells: Cell[] = [];\r\n\r\n data.forEach((item) => {\r\n if (edgeShapes.includes(item.shape)) {\r\n cells.push(graph.value!.createEdge(item));\r\n } else {\r\n const { data: nodeData, ...displayProps } = item;\r\n const node = graph.value!.createNode(displayProps);\r\n if (nodeData) node.setData(nodeData);\r\n cells.push(node);\r\n }\r\n });\r\n\r\n graph.value.resetCells(cells);\r\n setTimeout(() => graph.value!.zoomToFit({ padding: 10, maxScale: 1 }), 200);\r\n};\r\n\r\nconst formatClassDisplay = (cls: {\r\n name: string;\r\n attributes: any[];\r\n methods: any[];\r\n}) => ({\r\n name: cls.name,\r\n attributes: cls.attributes.map(\r\n (attr) =>\r\n `${getVisibilitySymbol(attr.visibility)} ${attr.name}: ${attr.type}`,\r\n ),\r\n methods: cls.methods.map(\r\n (method) =>\r\n `${getVisibilitySymbol(method.visibility)} ${method.name}(): ${method.returnType}`,\r\n ),\r\n});\r\n\r\n// ==================== 类 CRUD ====================\r\nconst addClass = () => {\r\n if (!graph.value) return;\r\n const newClass: UMLClass = {\r\n id: `class_${Date.now()}`,\r\n ...newClassTemplate,\r\n };\r\n const nodeData = {\r\n id: newClass.id,\r\n shape: \"class\",\r\n x: newClass.position.x,\r\n y: newClass.position.y,\r\n ...formatClassDisplay(newClass),\r\n data: newClass,\r\n };\r\n graph.value.addCell(graph.value.createNode(nodeData));\r\n emitDataChange();\r\n};\r\n\r\nconst editClass = (umlClass: UMLClass) => {\r\n editingClass.value = {\r\n ...umlClass,\r\n attributes: [...(umlClass.attributes || [])],\r\n methods: [...(umlClass.methods || [])],\r\n };\r\n showEditor.value = true;\r\n};\r\n\r\nconst saveClass = () => {\r\n if (!graph.value || !editingClass.value) return;\r\n const node = graph.value.getCellById(editingClass.value.id) as Node;\r\n if (node) {\r\n node.setData(editingClass.value);\r\n node.prop(formatClassDisplay(editingClass.value));\r\n }\r\n showEditor.value = false;\r\n emitDataChange();\r\n};\r\n\r\nconst deleteClass = () => {\r\n if (!graph.value || !editingClass.value) return;\r\n graph.value.getCellById(editingClass.value.id)?.remove();\r\n showEditor.value = false;\r\n emitDataChange();\r\n};\r\n\r\n// ==================== 数据获取 ====================\r\nconst getCurrentData = (): UMLDiagramData => ({\r\n classes:\r\n graph.value?.getNodes().map((node: Node) => ({\r\n ...node.getData(),\r\n position: node.getPosition(),\r\n })) || [],\r\n relations:\r\n graph.value?.getEdges().map((edge: any) => ({\r\n id: edge.id,\r\n type: edge.shape as string,\r\n source: edge.getSourceCellId() || \"\",\r\n target: edge.getTargetCellId() || \"\",\r\n })) || [],\r\n});\r\n\r\nconst emitDataChange = () => emit(\"data-change\", getCurrentData());\r\n\r\n// ==================== Graph 事件绑定 ====================\r\nwatch(\r\n graph,\r\n (newGraph) => {\r\n if (!(newGraph instanceof Graph)) return;\r\n\r\n registerNodes();\r\n bindInteractions();\r\n\r\n newGraph.on(\"node:dblclick\", ({ node }: { node: Node }) => {\r\n if (!props.readonly && node.getData()) editClass(node.getData());\r\n });\r\n\r\n emit(\"ready\", newGraph);\r\n\r\n const defaultCells = [\r\n ...sampleClasses.map((cls) => ({\r\n id: cls.id,\r\n shape: \"class\",\r\n x: cls.x,\r\n y: cls.y,\r\n ...formatClassDisplay(cls),\r\n data: {\r\n id: cls.id,\r\n name: cls.name,\r\n attributes: cls.attributes,\r\n methods: cls.methods,\r\n position: { x: cls.x, y: cls.y },\r\n },\r\n })),\r\n ...sampleConnections,\r\n ];\r\n loadData(defaultCells);\r\n },\r\n { immediate: true },\r\n);\r\n\r\nwatch(\r\n () => props.data,\r\n (newData) => {\r\n if (graph.value && newData?.classes) {\r\n const edgeShapes = [\r\n \"extends\",\r\n \"composition\",\r\n \"implement\",\r\n \"aggregation\",\r\n \"association\",\r\n ];\r\n const cells: any[] = [\r\n ...newData.classes.map((cls) => ({\r\n id: cls.id,\r\n shape: \"class\",\r\n x: cls.position?.x ?? 50,\r\n y: cls.position?.y ?? 50,\r\n ...formatClassDisplay(cls),\r\n data: cls,\r\n })),\r\n ...(newData.relations || [])\r\n .filter((r) => edgeShapes.includes(r.type))\r\n .map((r) => ({\r\n shape: r.type,\r\n source: r.source,\r\n target: r.target,\r\n })),\r\n ];\r\n loadData(cells);\r\n }\r\n },\r\n { deep: true },\r\n);\r\n\r\nonMounted(() => {\r\n initGraph({\r\n interacting: true,\r\n connecting: {\r\n ...connectingConfig,\r\n createEdge: () => graph.value!.createEdge(defaultEdgeConfig),\r\n validateConnection: ({\r\n sourceView,\r\n targetView,\r\n sourceMagnet,\r\n targetMagnet,\r\n }: any) =>\r\n sourceView !== targetView &&\r\n !!sourceMagnet &&\r\n !!targetMagnet &&\r\n sourceMagnet.getAttribute(\"magnet\") === \"true\" &&\r\n targetMagnet.getAttribute(\"magnet\") === \"true\",\r\n },\r\n highlighting: highlightingConfig,\r\n });\r\n});\r\n\r\ndefineExpose({\r\n getGraph: () => graph.value,\r\n getData: getCurrentData,\r\n addClass,\r\n centerContent,\r\n zoomToFit,\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<template>\r\n <div class=\"uml-layout\">\r\n <div class=\"toolbar\" v-if=\"showToolbar\">\r\n <NSpace>\r\n <NButton @click=\"addClass\" type=\"primary\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:plus-box\" :size=\"16\" />\r\n </template>\r\n 添加类\r\n </NButton>\r\n <NButton @click=\"centerContent\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:image-filter-center-focus\" :size=\"16\" />\r\n </template>\r\n 居中\r\n </NButton>\r\n <NButton @click=\"zoomToFit\" size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:fit-to-screen\" :size=\"16\" />\r\n </template>\r\n 适应\r\n </NButton>\r\n <NDropdown\r\n :options=\"exportOptions\"\r\n @select=\"(key: string) => handleExport(key, getCurrentData)\"\r\n >\r\n <NButton size=\"small\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:export\" :size=\"16\" />\r\n </template>\r\n 导出\r\n </NButton>\r\n </NDropdown>\r\n </NSpace>\r\n </div>\r\n\r\n <div\r\n ref=\"containerRef\"\r\n class=\"graph-container\"\r\n :style=\"{ width: props.width, height: props.height }\"\r\n ></div>\r\n\r\n <UMLClassEditor\r\n :show=\"showEditor\"\r\n v-model:editing-class=\"editingClass\"\r\n @update:show=\"showEditor = $event\"\r\n @save=\"saveClass\"\r\n @delete=\"deleteClass\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, onMounted } from \"vue\";\r\nimport { NSpace, NButton, NDropdown } from \"naive-ui\";\r\nimport { Graph, ObjectExt, type Node, type Cell } from \"@antv/x6\";\r\nimport { useGraphBase } from \"../../composables/useGraphBase\";\r\nimport { useGraphExport } from \"../../composables/useGraphExport\";\r\nimport { useEdgeInteraction } from \"../../composables/useEdgeInteraction\";\r\nimport type { UMLClass, UMLDiagramData } from \"../../types\";\r\nimport UMLClassEditor from \"./components/UMLClassEditor.vue\";\r\nimport C_Icon from \"../../../C_Icon/index.vue\";\r\nimport {\r\n edgeTypes,\r\n classNodeConfig,\r\n connectingConfig,\r\n highlightingConfig,\r\n defaultEdgeConfig,\r\n sampleClasses,\r\n sampleConnections,\r\n getVisibilitySymbol,\r\n newClassTemplate,\r\n} from \"./data\";\r\n\r\ninterface Props {\r\n data?: UMLDiagramData;\r\n showToolbar?: boolean;\r\n readonly?: boolean;\r\n width?: number | string;\r\n height?: number | string;\r\n theme?: \"light\" | \"dark\";\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n showToolbar: true,\r\n readonly: false,\r\n width: \"100%\",\r\n height: \"600px\",\r\n});\r\n\r\nconst emit = defineEmits<{\r\n ready: [graph: any];\r\n \"data-change\": [data: UMLDiagramData];\r\n}>();\r\n\r\n// ==================== Composables ====================\r\nconst containerRef = ref<HTMLDivElement>();\r\nconst isDark = computed(() => props.theme === \"dark\");\r\nconst { graph, initGraph, centerContent, zoomToFit } = useGraphBase(\r\n containerRef,\r\n isDark,\r\n);\r\nconst { exportOptions, handleExport } = useGraphExport(graph, \"uml-diagram\");\r\nconst { bindInteractions } = useEdgeInteraction(graph, {\r\n defaultColor: \"#722ed1\",\r\n portPositions: [\"top\", \"right\", \"bottom\", \"left\"],\r\n onDataChange: () => emitDataChange(),\r\n});\r\n\r\n// ==================== 编辑器状态 ====================\r\nconst showEditor = ref(false);\r\nconst editingClass = ref<UMLClass>();\r\n\r\n// ==================== 节点注册 ====================\r\nconst registerNodes = () => {\r\n Graph.registerNode(\r\n \"class\",\r\n {\r\n ...classNodeConfig,\r\n /** @description 节点属性钩子,用于计算 UML 类图节点布局 */\r\n propHooks(meta: any) {\r\n const { name, attributes, methods, ...others } = meta;\r\n if (!(name && attributes && methods)) return meta;\r\n\r\n let offsetY = 0;\r\n [\r\n { type: \"name\", text: name },\r\n { type: \"attrs\", text: attributes },\r\n { type: \"methods\", text: methods },\r\n ].forEach((rect) => {\r\n const height = Array.isArray(rect.text)\r\n ? rect.text.length * 12 + 16\r\n : 32;\r\n ObjectExt.setByPath(\r\n others,\r\n `attrs/${rect.type}-text/text`,\r\n Array.isArray(rect.text) ? rect.text.join(\"\\n\") : rect.text,\r\n );\r\n ObjectExt.setByPath(others, `attrs/${rect.type}-rect/height`, height);\r\n ObjectExt.setByPath(\r\n others,\r\n `attrs/${rect.type}-rect/transform`,\r\n `translate(0,${offsetY})`,\r\n );\r\n offsetY += height;\r\n });\r\n others.size = { width: 160, height: offsetY };\r\n return others;\r\n },\r\n },\r\n true,\r\n );\r\n\r\n Object.entries(edgeTypes).forEach(([type, config]) => {\r\n Graph.registerEdge(\r\n type,\r\n {\r\n inherit: \"edge\",\r\n attrs: { line: { strokeWidth: 2, stroke: \"#722ed1\", ...config } },\r\n },\r\n true,\r\n );\r\n });\r\n};\r\n\r\n// ==================== 数据加载 ====================\r\nconst loadData = (data: any[]) => {\r\n if (!graph.value || !data.length) return;\r\n const edgeShapes = [\r\n \"extends\",\r\n \"composition\",\r\n \"implement\",\r\n \"aggregation\",\r\n \"association\",\r\n ];\r\n const cells: Cell[] = [];\r\n\r\n data.forEach((item) => {\r\n if (edgeShapes.includes(item.shape)) {\r\n cells.push(graph.value!.createEdge(item));\r\n } else {\r\n const { data: nodeData, ...displayProps } = item;\r\n const node = graph.value!.createNode(displayProps);\r\n if (nodeData) node.setData(nodeData);\r\n cells.push(node);\r\n }\r\n });\r\n\r\n graph.value.resetCells(cells);\r\n setTimeout(() => graph.value!.zoomToFit({ padding: 10, maxScale: 1 }), 200);\r\n};\r\n\r\nconst formatClassDisplay = (cls: {\r\n name: string;\r\n attributes: any[];\r\n methods: any[];\r\n}) => ({\r\n name: cls.name,\r\n attributes: cls.attributes.map(\r\n (attr) =>\r\n `${getVisibilitySymbol(attr.visibility)} ${attr.name}: ${attr.type}`,\r\n ),\r\n methods: cls.methods.map(\r\n (method) =>\r\n `${getVisibilitySymbol(method.visibility)} ${method.name}(): ${method.returnType}`,\r\n ),\r\n});\r\n\r\n// ==================== 类 CRUD ====================\r\nconst addClass = () => {\r\n if (!graph.value) return;\r\n const newClass: UMLClass = {\r\n id: `class_${Date.now()}`,\r\n ...newClassTemplate,\r\n };\r\n const nodeData = {\r\n id: newClass.id,\r\n shape: \"class\",\r\n x: newClass.position.x,\r\n y: newClass.position.y,\r\n ...formatClassDisplay(newClass),\r\n data: newClass,\r\n };\r\n graph.value.addCell(graph.value.createNode(nodeData));\r\n emitDataChange();\r\n};\r\n\r\nconst editClass = (umlClass: UMLClass) => {\r\n editingClass.value = {\r\n ...umlClass,\r\n attributes: [...(umlClass.attributes || [])],\r\n methods: [...(umlClass.methods || [])],\r\n };\r\n showEditor.value = true;\r\n};\r\n\r\nconst saveClass = () => {\r\n if (!graph.value || !editingClass.value) return;\r\n const node = graph.value.getCellById(editingClass.value.id) as Node;\r\n if (node) {\r\n node.setData(editingClass.value);\r\n node.prop(formatClassDisplay(editingClass.value));\r\n }\r\n showEditor.value = false;\r\n emitDataChange();\r\n};\r\n\r\nconst deleteClass = () => {\r\n if (!graph.value || !editingClass.value) return;\r\n graph.value.getCellById(editingClass.value.id)?.remove();\r\n showEditor.value = false;\r\n emitDataChange();\r\n};\r\n\r\n// ==================== 数据获取 ====================\r\nconst getCurrentData = (): UMLDiagramData => ({\r\n classes:\r\n graph.value?.getNodes().map((node: Node) => ({\r\n ...node.getData(),\r\n position: node.getPosition(),\r\n })) || [],\r\n relations:\r\n graph.value?.getEdges().map((edge: any) => ({\r\n id: edge.id,\r\n type: edge.shape as string,\r\n source: edge.getSourceCellId() || \"\",\r\n target: edge.getTargetCellId() || \"\",\r\n })) || [],\r\n});\r\n\r\nconst emitDataChange = () => emit(\"data-change\", getCurrentData());\r\n\r\n// ==================== Graph 事件绑定 ====================\r\nwatch(\r\n graph,\r\n (newGraph) => {\r\n if (!(newGraph instanceof Graph)) return;\r\n\r\n registerNodes();\r\n bindInteractions();\r\n\r\n newGraph.on(\"node:dblclick\", ({ node }: { node: Node }) => {\r\n if (!props.readonly && node.getData()) editClass(node.getData());\r\n });\r\n\r\n emit(\"ready\", newGraph);\r\n\r\n const defaultCells = [\r\n ...sampleClasses.map((cls) => ({\r\n id: cls.id,\r\n shape: \"class\",\r\n x: cls.x,\r\n y: cls.y,\r\n ...formatClassDisplay(cls),\r\n data: {\r\n id: cls.id,\r\n name: cls.name,\r\n attributes: cls.attributes,\r\n methods: cls.methods,\r\n position: { x: cls.x, y: cls.y },\r\n },\r\n })),\r\n ...sampleConnections,\r\n ];\r\n loadData(defaultCells);\r\n },\r\n { immediate: true },\r\n);\r\n\r\nwatch(\r\n () => props.data,\r\n (newData) => {\r\n if (graph.value && newData?.classes) {\r\n const edgeShapes = [\r\n \"extends\",\r\n \"composition\",\r\n \"implement\",\r\n \"aggregation\",\r\n \"association\",\r\n ];\r\n const cells: any[] = [\r\n ...newData.classes.map((cls) => ({\r\n id: cls.id,\r\n shape: \"class\",\r\n x: cls.position?.x ?? 50,\r\n y: cls.position?.y ?? 50,\r\n ...formatClassDisplay(cls),\r\n data: cls,\r\n })),\r\n ...(newData.relations || [])\r\n .filter((r) => edgeShapes.includes(r.type))\r\n .map((r) => ({\r\n shape: r.type,\r\n source: r.source,\r\n target: r.target,\r\n })),\r\n ];\r\n loadData(cells);\r\n }\r\n },\r\n { deep: true },\r\n);\r\n\r\nonMounted(() => {\r\n initGraph({\r\n interacting: true,\r\n connecting: {\r\n ...connectingConfig,\r\n createEdge: () => graph.value!.createEdge(defaultEdgeConfig),\r\n validateConnection: ({\r\n sourceView,\r\n targetView,\r\n sourceMagnet,\r\n targetMagnet,\r\n }: any) =>\r\n sourceView !== targetView &&\r\n !!sourceMagnet &&\r\n !!targetMagnet &&\r\n sourceMagnet.getAttribute(\"magnet\") === \"true\" &&\r\n targetMagnet.getAttribute(\"magnet\") === \"true\",\r\n },\r\n highlighting: highlightingConfig,\r\n });\r\n});\r\n\r\ndefineExpose({\r\n getGraph: () => graph.value,\r\n getData: getCurrentData,\r\n addClass,\r\n centerContent,\r\n zoomToFit,\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-08-01\r\n * @Description: AntV X6 图形编辑器组件(ER/BPMN/UML)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <div class=\"c-antv-container\">\r\n <component\r\n :is=\"currentComponent\"\r\n v-bind=\"componentProps\"\r\n @ready=\"handleReady\"\r\n @data-change=\"handleDataChange\"\r\n ref=\"layoutRef\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed } from \"vue\";\r\nimport ERLayout from \"./layout/ER/index.vue\";\r\nimport BPMNLayout from \"./layout/BPMN/index.vue\";\r\nimport UMLLayout from \"./layout/UML/index.vue\";\r\nimport type { DiagramConfig, DiagramData } from \"./types\";\r\n\r\ndefineOptions({ name: \"C_AntV\" });\r\n\r\ninterface Props extends DiagramConfig {\r\n type: \"er\" | \"bpmn\" | \"uml\";\r\n data?: DiagramData;\r\n width?: string | number;\r\n height?: string | number;\r\n readonly?: boolean;\r\n showToolbar?: boolean;\r\n theme?: \"light\" | \"dark\";\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n width: \"100%\",\r\n height: \"600px\",\r\n readonly: false,\r\n showToolbar: true,\r\n theme: \"light\",\r\n});\r\n\r\nconst emit = defineEmits<{\r\n (e: \"ready\", graph: any): void;\r\n (e: \"data-change\", data: DiagramData): void;\r\n}>();\r\n\r\nconst layoutRef = ref();\r\n\r\nconst components = {\r\n er: ERLayout,\r\n bpmn: BPMNLayout,\r\n uml: UMLLayout,\r\n} as const;\r\n\r\nconst currentComponent = computed(() => components[props.type]);\r\n\r\nconst componentProps = computed(() => {\r\n const baseProps = {\r\n width: props.width,\r\n height: props.height,\r\n readonly: props.readonly,\r\n showToolbar: props.showToolbar,\r\n theme: props.theme,\r\n };\r\n\r\n // 处理 BPMN 数据格式转换\r\n let adaptedData: any = props.data;\r\n\r\n if (props.type === \"bpmn\" && props.data) {\r\n // 检查是否为 BPMNDiagramData 格式\r\n const bpmnData = props.data as any;\r\n if (\r\n bpmnData &&\r\n typeof bpmnData === \"object\" &&\r\n \"nodes\" in bpmnData &&\r\n \"edges\" in bpmnData\r\n ) {\r\n // 转换为 BPMNLayout 期望的 BPMNElement[] 格式\r\n adaptedData = [\r\n ...(bpmnData.nodes || []).map((node: any) => ({\r\n ...node,\r\n shape: node.type || node.shape || \"rect\",\r\n x: node.x ?? 0,\r\n y: node.y ?? 0,\r\n })),\r\n ...(bpmnData.edges || []).map((edge: any) => ({\r\n ...edge,\r\n shape: \"edge\",\r\n x: 0,\r\n y: 0,\r\n })),\r\n ];\r\n }\r\n }\r\n\r\n return {\r\n ...baseProps,\r\n data: adaptedData,\r\n } as any;\r\n});\r\n\r\nconst handleReady = (graph: any) => {\r\n emit(\"ready\", graph);\r\n};\r\n\r\nconst handleDataChange = (data: any) => {\r\n let convertedData: DiagramData;\r\n\r\n if (props.type === \"bpmn\" && Array.isArray(data)) {\r\n // 将 BPMNElement[] 转换回 BPMNDiagramData 格式,包含必需的 flows 属性\r\n const nodes = data\r\n .filter((item: any) => item.shape !== \"edge\")\r\n .map((item: any) => {\r\n const { shape, x, y, ...node } = item;\r\n return {\r\n ...node,\r\n type: shape === \"rect\" ? \"task\" : shape,\r\n x: x || 0,\r\n y: y || 0,\r\n };\r\n });\r\n\r\n const edges = data\r\n .filter((item: any) => item.shape === \"edge\")\r\n .map((item: any) => {\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n const { shape, x, y, ...edge } = item;\r\n return edge;\r\n });\r\n\r\n // 创建完整的 BPMNDiagramData 对象,包含必需的 flows 属性\r\n convertedData = {\r\n nodes,\r\n edges,\r\n flows: [], // 添加必需的 flows 属性,根据实际需求填充\r\n } as unknown as DiagramData; // 使用双重断言确保类型转换\r\n } else {\r\n convertedData = data as DiagramData;\r\n }\r\n\r\n emit(\"data-change\", convertedData);\r\n};\r\n\r\ndefineExpose({\r\n getGraph: () => layoutRef.value?.getGraph?.(),\r\n getData: () => {\r\n const rawData = layoutRef.value?.getData?.();\r\n\r\n // 根据组件类型转换回标准格式\r\n if (props.type === \"bpmn\" && Array.isArray(rawData)) {\r\n const nodes = rawData.filter((item: any) => item.shape !== \"edge\");\r\n const edges = rawData.filter((item: any) => item.shape === \"edge\");\r\n\r\n return {\r\n nodes,\r\n edges,\r\n flows: [], // 确保包含 flows 属性\r\n } as unknown as DiagramData;\r\n }\r\n\r\n return rawData;\r\n },\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n.c-antv-container {\r\n width: 100%;\r\n height: 100%;\r\n}\r\n</style>\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-08-01\r\n * @Description: AntV X6 图形编辑器组件(ER/BPMN/UML)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <div class=\"c-antv-container\">\r\n <component\r\n :is=\"currentComponent\"\r\n v-bind=\"componentProps\"\r\n @ready=\"handleReady\"\r\n @data-change=\"handleDataChange\"\r\n ref=\"layoutRef\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed } from \"vue\";\r\nimport ERLayout from \"./layout/ER/index.vue\";\r\nimport BPMNLayout from \"./layout/BPMN/index.vue\";\r\nimport UMLLayout from \"./layout/UML/index.vue\";\r\nimport type { DiagramConfig, DiagramData } from \"./types\";\r\n\r\ndefineOptions({ name: \"C_AntV\" });\r\n\r\ninterface Props extends DiagramConfig {\r\n type: \"er\" | \"bpmn\" | \"uml\";\r\n data?: DiagramData;\r\n width?: string | number;\r\n height?: string | number;\r\n readonly?: boolean;\r\n showToolbar?: boolean;\r\n theme?: \"light\" | \"dark\";\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n width: \"100%\",\r\n height: \"600px\",\r\n readonly: false,\r\n showToolbar: true,\r\n theme: \"light\",\r\n});\r\n\r\nconst emit = defineEmits<{\r\n (e: \"ready\", graph: any): void;\r\n (e: \"data-change\", data: DiagramData): void;\r\n}>();\r\n\r\nconst layoutRef = ref();\r\n\r\nconst components = {\r\n er: ERLayout,\r\n bpmn: BPMNLayout,\r\n uml: UMLLayout,\r\n} as const;\r\n\r\nconst currentComponent = computed(() => components[props.type]);\r\n\r\nconst componentProps = computed(() => {\r\n const baseProps = {\r\n width: props.width,\r\n height: props.height,\r\n readonly: props.readonly,\r\n showToolbar: props.showToolbar,\r\n theme: props.theme,\r\n };\r\n\r\n // 处理 BPMN 数据格式转换\r\n let adaptedData: any = props.data;\r\n\r\n if (props.type === \"bpmn\" && props.data) {\r\n // 检查是否为 BPMNDiagramData 格式\r\n const bpmnData = props.data as any;\r\n if (\r\n bpmnData &&\r\n typeof bpmnData === \"object\" &&\r\n \"nodes\" in bpmnData &&\r\n \"edges\" in bpmnData\r\n ) {\r\n // 转换为 BPMNLayout 期望的 BPMNElement[] 格式\r\n adaptedData = [\r\n ...(bpmnData.nodes || []).map((node: any) => ({\r\n ...node,\r\n shape: node.type || node.shape || \"rect\",\r\n x: node.x ?? 0,\r\n y: node.y ?? 0,\r\n })),\r\n ...(bpmnData.edges || []).map((edge: any) => ({\r\n ...edge,\r\n shape: \"edge\",\r\n x: 0,\r\n y: 0,\r\n })),\r\n ];\r\n }\r\n }\r\n\r\n return {\r\n ...baseProps,\r\n data: adaptedData,\r\n } as any;\r\n});\r\n\r\nconst handleReady = (graph: any) => {\r\n emit(\"ready\", graph);\r\n};\r\n\r\nconst handleDataChange = (data: any) => {\r\n let convertedData: DiagramData;\r\n\r\n if (props.type === \"bpmn\" && Array.isArray(data)) {\r\n // 将 BPMNElement[] 转换回 BPMNDiagramData 格式,包含必需的 flows 属性\r\n const nodes = data\r\n .filter((item: any) => item.shape !== \"edge\")\r\n .map((item: any) => {\r\n const { shape, x, y, ...node } = item;\r\n return {\r\n ...node,\r\n type: shape === \"rect\" ? \"task\" : shape,\r\n x: x || 0,\r\n y: y || 0,\r\n };\r\n });\r\n\r\n const edges = data\r\n .filter((item: any) => item.shape === \"edge\")\r\n .map((item: any) => {\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n const { shape, x, y, ...edge } = item;\r\n return edge;\r\n });\r\n\r\n // 创建完整的 BPMNDiagramData 对象,包含必需的 flows 属性\r\n convertedData = {\r\n nodes,\r\n edges,\r\n flows: [], // 添加必需的 flows 属性,根据实际需求填充\r\n } as unknown as DiagramData; // 使用双重断言确保类型转换\r\n } else {\r\n convertedData = data as DiagramData;\r\n }\r\n\r\n emit(\"data-change\", convertedData);\r\n};\r\n\r\ndefineExpose({\r\n getGraph: () => layoutRef.value?.getGraph?.(),\r\n getData: () => {\r\n const rawData = layoutRef.value?.getData?.();\r\n\r\n // 根据组件类型转换回标准格式\r\n if (props.type === \"bpmn\" && Array.isArray(rawData)) {\r\n const nodes = rawData.filter((item: any) => item.shape !== \"edge\");\r\n const edges = rawData.filter((item: any) => item.shape === \"edge\");\r\n\r\n return {\r\n nodes,\r\n edges,\r\n flows: [], // 确保包含 flows 属性\r\n } as unknown as DiagramData;\r\n }\r\n\r\n return rawData;\r\n },\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n.c-antv-container {\r\n width: 100%;\r\n height: 100%;\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-08-01\r\n * @Description: AntV X6 图形编辑器组件(ER/BPMN/UML)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <div class=\"c-antv-container\">\r\n <component\r\n :is=\"currentComponent\"\r\n v-bind=\"componentProps\"\r\n @ready=\"handleReady\"\r\n @data-change=\"handleDataChange\"\r\n ref=\"layoutRef\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed } from \"vue\";\r\nimport ERLayout from \"./layout/ER/index.vue\";\r\nimport BPMNLayout from \"./layout/BPMN/index.vue\";\r\nimport UMLLayout from \"./layout/UML/index.vue\";\r\nimport type { DiagramConfig, DiagramData } from \"./types\";\r\n\r\ndefineOptions({ name: \"C_AntV\" });\r\n\r\ninterface Props extends DiagramConfig {\r\n type: \"er\" | \"bpmn\" | \"uml\";\r\n data?: DiagramData;\r\n width?: string | number;\r\n height?: string | number;\r\n readonly?: boolean;\r\n showToolbar?: boolean;\r\n theme?: \"light\" | \"dark\";\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n width: \"100%\",\r\n height: \"600px\",\r\n readonly: false,\r\n showToolbar: true,\r\n theme: \"light\",\r\n});\r\n\r\nconst emit = defineEmits<{\r\n (e: \"ready\", graph: any): void;\r\n (e: \"data-change\", data: DiagramData): void;\r\n}>();\r\n\r\nconst layoutRef = ref();\r\n\r\nconst components = {\r\n er: ERLayout,\r\n bpmn: BPMNLayout,\r\n uml: UMLLayout,\r\n} as const;\r\n\r\nconst currentComponent = computed(() => components[props.type]);\r\n\r\nconst componentProps = computed(() => {\r\n const baseProps = {\r\n width: props.width,\r\n height: props.height,\r\n readonly: props.readonly,\r\n showToolbar: props.showToolbar,\r\n theme: props.theme,\r\n };\r\n\r\n // 处理 BPMN 数据格式转换\r\n let adaptedData: any = props.data;\r\n\r\n if (props.type === \"bpmn\" && props.data) {\r\n // 检查是否为 BPMNDiagramData 格式\r\n const bpmnData = props.data as any;\r\n if (\r\n bpmnData &&\r\n typeof bpmnData === \"object\" &&\r\n \"nodes\" in bpmnData &&\r\n \"edges\" in bpmnData\r\n ) {\r\n // 转换为 BPMNLayout 期望的 BPMNElement[] 格式\r\n adaptedData = [\r\n ...(bpmnData.nodes || []).map((node: any) => ({\r\n ...node,\r\n shape: node.type || node.shape || \"rect\",\r\n x: node.x ?? 0,\r\n y: node.y ?? 0,\r\n })),\r\n ...(bpmnData.edges || []).map((edge: any) => ({\r\n ...edge,\r\n shape: \"edge\",\r\n x: 0,\r\n y: 0,\r\n })),\r\n ];\r\n }\r\n }\r\n\r\n return {\r\n ...baseProps,\r\n data: adaptedData,\r\n } as any;\r\n});\r\n\r\nconst handleReady = (graph: any) => {\r\n emit(\"ready\", graph);\r\n};\r\n\r\nconst handleDataChange = (data: any) => {\r\n let convertedData: DiagramData;\r\n\r\n if (props.type === \"bpmn\" && Array.isArray(data)) {\r\n // 将 BPMNElement[] 转换回 BPMNDiagramData 格式,包含必需的 flows 属性\r\n const nodes = data\r\n .filter((item: any) => item.shape !== \"edge\")\r\n .map((item: any) => {\r\n const { shape, x, y, ...node } = item;\r\n return {\r\n ...node,\r\n type: shape === \"rect\" ? \"task\" : shape,\r\n x: x || 0,\r\n y: y || 0,\r\n };\r\n });\r\n\r\n const edges = data\r\n .filter((item: any) => item.shape === \"edge\")\r\n .map((item: any) => {\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n const { shape, x, y, ...edge } = item;\r\n return edge;\r\n });\r\n\r\n // 创建完整的 BPMNDiagramData 对象,包含必需的 flows 属性\r\n convertedData = {\r\n nodes,\r\n edges,\r\n flows: [], // 添加必需的 flows 属性,根据实际需求填充\r\n } as unknown as DiagramData; // 使用双重断言确保类型转换\r\n } else {\r\n convertedData = data as DiagramData;\r\n }\r\n\r\n emit(\"data-change\", convertedData);\r\n};\r\n\r\ndefineExpose({\r\n getGraph: () => layoutRef.value?.getGraph?.(),\r\n getData: () => {\r\n const rawData = layoutRef.value?.getData?.();\r\n\r\n // 根据组件类型转换回标准格式\r\n if (props.type === \"bpmn\" && Array.isArray(rawData)) {\r\n const nodes = rawData.filter((item: any) => item.shape !== \"edge\");\r\n const edges = rawData.filter((item: any) => item.shape === \"edge\");\r\n\r\n return {\r\n nodes,\r\n edges,\r\n flows: [], // 确保包含 flows 属性\r\n } as unknown as DiagramData;\r\n }\r\n\r\n return rawData;\r\n },\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n.c-antv-container {\r\n width: 100%;\r\n height: 100%;\r\n}\r\n</style>\r\n"],"mappings":"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"}