graphModel.ts 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. import { useState, useRef, useEffect } from "react";
  2. import { Cell, Graph, Node } from "@antv/x6";
  3. import { Dnd } from "@antv/x6-plugin-dnd";
  4. import { Transform } from "@antv/x6-plugin-transform";
  5. import { Snapline } from "@antv/x6-plugin-snapline";
  6. import { Clipboard } from "@antv/x6-plugin-clipboard";
  7. import { Selection } from "@repo/x6-plugin-selection/src/index";
  8. import { History } from "@antv/x6-plugin-history";
  9. import { Keyboard } from "@antv/x6-plugin-keyboard";
  10. import { Export } from "@antv/x6-plugin-export";
  11. import { useModel } from "umi";
  12. import "@/components/PageContainer";
  13. import { handleGraphEvent, handleGraphApiEvent } from "@/events/flowEvent";
  14. import { pageMenu, nodeMenu, edgeMenu } from "@/utils/contentMenu";
  15. import { bindKeys } from "@/utils/fastKey";
  16. import { useSessionStorageState } from "ahooks";
  17. import { handleSetEdgeStyle } from "@/utils";
  18. export default function GraphModel() {
  19. const [graph, setGraph] = useState<Graph>();
  20. const [dnd, setDnd] = useState<Dnd>();
  21. const pageNodeRef = useRef<Node>();
  22. const dndRef = useRef(dnd);
  23. const [updateKey, setUpdateKey] = useState(0);
  24. // 画布
  25. const graphRef = useRef<Graph>();
  26. const { pageState } = useModel("appModel");
  27. // 当前选中的节点
  28. const [selectedCell, setSelectedCell] = useState<Cell[]>([]);
  29. const [canRedo, setCanRedo] = useState(false);
  30. const [canUndo, setCanUndo] = useState(false);
  31. const [projectInfo, setProjectInfo] = useSessionStorageState<{
  32. graph: Record<string, any>;
  33. elements: Cell.Properties[];
  34. }>("system-design-project");
  35. const handleChangeAll = () => {
  36. const cells = (graph?.toJSON().cells || []).filter(
  37. (cell) => !cell?.data?.isPage
  38. );
  39. projectInfo &&
  40. setProjectInfo({
  41. ...projectInfo,
  42. elements: cells,
  43. });
  44. };
  45. useEffect(() => {
  46. // 用于恢复历史后画布更新
  47. if(updateKey) {
  48. graphRef.current?.off("cell:added");
  49. graphRef.current?.off("cell:change:*");
  50. graphRef.current?.off("cell:removed");
  51. pageNodeRef?.current && graphRef.current?.resetCells([pageNodeRef?.current])
  52. }
  53. }, [updateKey]);
  54. const addPageNode = () => {
  55. const graph = graphRef.current;
  56. pageNodeRef.current = graph?.addNode({
  57. shape: "page-container-node",
  58. width: pageState.width,
  59. height: pageState.height,
  60. zIndex: -1,
  61. data: {
  62. isPage: true,
  63. ignoreDrag: true,
  64. ...pageState,
  65. },
  66. tools: [
  67. {
  68. name: "contextmenu",
  69. args: {
  70. menu: pageMenu,
  71. },
  72. },
  73. ],
  74. });
  75. };
  76. const initCells = (cells: Cell.Properties[]) => {
  77. if (graphRef.current) {
  78. // 添加节点
  79. (cells || []).forEach((item) => {
  80. if (item.shape !== "edge") {
  81. graphRef.current?.addNode({
  82. ...item,
  83. data: JSON.parse(item?.data),
  84. ports: JSON.parse(item?.ports || "{}"),
  85. size: JSON.parse(item?.size || "{}"),
  86. position: JSON.parse(item?.position || "{}"),
  87. tools: [{
  88. name: "contextmenu",
  89. args: {
  90. menu: nodeMenu,
  91. },
  92. }]
  93. });
  94. }
  95. });
  96. // 添加边
  97. (cells || []).forEach((item) => {
  98. if (item.shape === "edge") {
  99. graphRef.current?.addEdge({
  100. ...item,
  101. data: JSON.parse(item?.data),
  102. attrs: JSON.parse(item?.attrs as unknown as string || "{}"),
  103. source: JSON.parse(item?.source),
  104. target: JSON.parse(item?.target),
  105. connector: item?.connector ? JSON.parse(item.connector) : undefined,
  106. labels: item?.labels ? JSON.parse(item.labels) : undefined,
  107. router: 'manhattan',
  108. // router: JSON.parse(item?.router || "{}"),
  109. tools: [
  110. {
  111. name: "contextmenu",
  112. args: {
  113. menu: edgeMenu,
  114. },
  115. },
  116. ]
  117. });
  118. }
  119. })
  120. graphRef.current.on("cell:change:*", handleChangeAll);
  121. handleGraphApiEvent(graphRef.current);
  122. }
  123. };
  124. /**初始化页面节点 */
  125. useEffect(() => {
  126. if (pageState.width && graphRef.current && !pageNodeRef.current) {
  127. addPageNode();
  128. }
  129. }, [pageState.width, graphRef.current]);
  130. useEffect(() => {
  131. pageNodeRef.current?.setData({
  132. background: pageState.backgroundColor,
  133. ...pageState,
  134. });
  135. pageNodeRef.current?.setSize({
  136. width: pageState.width,
  137. height: pageState.height,
  138. });
  139. }, [pageState]);
  140. // 修改跨线展示
  141. useEffect(() => {
  142. const edges = graphRef.current?.getEdges();
  143. (edges || []).forEach(edge => {
  144. handleSetEdgeStyle(edge, edge.data?.connectorType, pageState.jumpOver);
  145. });
  146. }, [pageState.jumpOver]);
  147. const enabledTransform = (node: Node) => {
  148. const data = node.getData<{ isPage: boolean; lock: boolean }>();
  149. return !data?.isPage && !data?.lock;
  150. };
  151. /**初始化画布 */
  152. const initGraph = (instance: Graph) => {
  153. // 添加插件
  154. instance
  155. .use(
  156. new Transform({
  157. resizing: {
  158. enabled: enabledTransform,
  159. minWidth: 20,
  160. minHeight: 20,
  161. },
  162. rotating: {
  163. enabled: enabledTransform,
  164. grid: 1,
  165. },
  166. })
  167. )
  168. .use(
  169. new Selection({
  170. enabled: true,
  171. multiple: true,
  172. rubberband: true,
  173. movable: true,
  174. showNodeSelectionBox: true,
  175. // showEdgeSelectionBox: true,
  176. pointerEvents: "none",
  177. strict: true,
  178. filter: (cell: Cell) => {
  179. const data = cell.getData<{ isPage: boolean; lock: boolean }>();
  180. return !data?.isPage && !data?.lock;
  181. },
  182. })
  183. )
  184. .use(
  185. new Snapline({
  186. sharp: true,
  187. resizing: true,
  188. })
  189. )
  190. .use(
  191. new Keyboard({
  192. enabled: true,
  193. global: true,
  194. })
  195. )
  196. .use(new Clipboard())
  197. .use(
  198. new History({
  199. enabled: true,
  200. beforeAddCommand: (event, args) => {
  201. // @ts-ignore 排除不用创建的节点
  202. if (args?.cell?.data?.noCreate) return false;
  203. // @ts-ignore 排除页面节点
  204. return !(event === "cell:added" && args?.cell?.getData()?.isPage);
  205. },
  206. })
  207. )
  208. .use(new Export());
  209. setGraph(instance);
  210. graphRef.current = instance;
  211. // 选中的节点/边发生改变(增删)时触发
  212. instance.on(
  213. "selection:changed",
  214. ({ selected }: { added: Cell[]; removed: Cell[]; selected: Cell[] }) => {
  215. setSelectedCell(selected);
  216. }
  217. );
  218. instance.on("history:change", () => {
  219. setCanRedo(instance.canRedo());
  220. setCanUndo(instance.canUndo());
  221. });
  222. // 通用事件处理
  223. handleGraphEvent(instance);
  224. // 绑定快捷键
  225. bindKeys(instance);
  226. };
  227. /**初始化拖拽 */
  228. const initDnd = (instance: Dnd) => {
  229. setDnd(instance);
  230. dndRef.current = instance;
  231. };
  232. /**组件库拖拽生成 */
  233. const startDrag = (
  234. e: React.MouseEvent<HTMLDivElement, MouseEvent>,
  235. node: Node.Metadata
  236. ) => {
  237. if (!dndRef.current || !graphRef.current) return;
  238. // 往画布添加节点
  239. const n = graphRef.current.createNode(node);
  240. // 右键菜单
  241. n.addTools({
  242. name: "contextmenu",
  243. args: {
  244. menu: nodeMenu,
  245. },
  246. });
  247. dndRef.current.start(n, e.nativeEvent as any);
  248. };
  249. // 撤销
  250. const onUndo = () => {
  251. graphRef.current?.undo();
  252. };
  253. // 重做
  254. const onRedo = () => {
  255. graphRef.current?.redo();
  256. };
  257. return {
  258. graph,
  259. dnd,
  260. initGraph,
  261. initDnd,
  262. startDrag,
  263. selectedCell,
  264. canRedo,
  265. canUndo,
  266. onUndo,
  267. onRedo,
  268. initCells,
  269. updateKey,
  270. setUpdateKey
  271. };
  272. }