erModel.tsx 25 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061
  1. import { useEffect, useMemo, useRef, useState } from "react";
  2. import { EventArgs, Graph, Shape } from "@antv/x6";
  3. import { Transform } from "@antv/x6-plugin-transform";
  4. import { Scroller } from "@antv/x6-plugin-scroller";
  5. import { Snapline } from "@antv/x6-plugin-snapline";
  6. import { Keyboard } from "@antv/x6-plugin-keyboard";
  7. import { Export } from "@antv/x6-plugin-export";
  8. import { Selection } from "@antv/x6-plugin-selection";
  9. import { SaveDataModel, UploadFile, BatchAddAICreateResult } from "@/api";
  10. import { useFullscreen, useSessionStorageState, useLocalStorageState } from "ahooks";
  11. import { createTable, createColumn } from "@/utils";
  12. import dayjs from "dayjs";
  13. import { getClassRules, base64ToFile, uuid } from "@repo/utils";
  14. import type {
  15. ColumnItem,
  16. ColumnRelation,
  17. ProjectInfo,
  18. RemarkInfo,
  19. TableItemType,
  20. TopicAreaInfo,
  21. } from "@/type";
  22. import { RelationLineType } from "@/enum";
  23. import { render } from "./renderer";
  24. import { DEFAULT_SETTING } from "@/constants";
  25. import { initInfo } from "./initInfo";
  26. import "@/components/TableNode";
  27. import "@/components/TopicNode";
  28. import "@/components/NoticeNode";
  29. import { message } from "antd";
  30. export default function erModel() {
  31. const graphRef = useRef<Graph>();
  32. const [graph, setGraph] = useState<Graph>();
  33. const historyRef = useRef<ProjectInfo[]>([]);
  34. const activeIndex = useRef(0);
  35. const [_isFullscreen, { enterFullscreen, exitFullscreen }] = useFullscreen(
  36. document.body
  37. );
  38. const [playModeEnable, setPlayModeEnable] = useSessionStorageState(
  39. "playModeEnable",
  40. {
  41. defaultValue: false,
  42. listenStorageChange: true,
  43. }
  44. );
  45. // 更新画布标识
  46. const [updateKey, setUpdateKey] = useSessionStorageState('update-key', { listenStorageChange: true, defaultValue: 0 });
  47. const [saveTime, setSaveTime] = useState<string>();
  48. const [project, setProjectInfo] = useState<ProjectInfo>({
  49. id: "",
  50. name: "新建模型",
  51. directory: "",
  52. type: 3,
  53. description: "",
  54. isTemplate: false,
  55. industry: "",
  56. publishStatus: "",
  57. tables: [],
  58. relations: [],
  59. topicAreas: [],
  60. remarkInfos: [],
  61. todos: [],
  62. setting: {
  63. ...DEFAULT_SETTING,
  64. },
  65. });
  66. const [_tabActiveKey, setTabActiveKey] =
  67. useSessionStorageState("tabs-active-key");
  68. const [_relationActive, setRelationActive] =
  69. useSessionStorageState("relation-active");
  70. const [tableActive, setTableActive] = useSessionStorageState<string>(
  71. "table-active",
  72. {
  73. defaultValue: "",
  74. listenStorageChange: true,
  75. }
  76. );
  77. const timer = useRef<any>();
  78. const saveData = (info: ProjectInfo) => {
  79. // 提交服务器
  80. // 清除定时器
  81. clearTimeout(timer.current);
  82. timer.current = setTimeout(() => {
  83. SaveDataModel({ erDataModel: info});
  84. // 格式化当前时间
  85. setSaveTime(dayjs().format("YYYY-MM-DD HH:mm:ss"));
  86. }, 500);
  87. };
  88. /**
  89. * 统一修改数据
  90. * @param info 模型信息
  91. * @param ingoreHistory 忽略历史记录
  92. * @param isInit 初始化
  93. */
  94. const setProject = (
  95. info: ProjectInfo | ((state: ProjectInfo) => ProjectInfo),
  96. ingoreHistory?: boolean,
  97. isInit?: boolean,
  98. ingoreRender?: boolean
  99. ) => {
  100. if (isInit && typeof info === "object" && typeof info !== null) {
  101. historyRef.current = [];
  102. activeIndex.current = 0;
  103. initInfo(info);
  104. }
  105. if (info && typeof info === "function") {
  106. setProjectInfo((state) => {
  107. const result = info(state);
  108. if (!isInit) {
  109. saveData(result);
  110. }
  111. graphRef.current && !ingoreRender && render(graphRef.current, result);
  112. // 添加记录
  113. if (!ingoreHistory) {
  114. historyRef.current?.push(result);
  115. activeIndex.current = historyRef.current?.length - 1;
  116. if (historyRef.current?.length > 20) {
  117. historyRef.current?.shift();
  118. activeIndex.current -= 1;
  119. }
  120. }
  121. return result;
  122. });
  123. } else {
  124. setProjectInfo(info);
  125. graphRef.current && !ingoreRender && render(graphRef.current, info);
  126. // 添加记录
  127. if (!ingoreHistory) {
  128. historyRef.current?.push(info);
  129. activeIndex.current = historyRef.current?.length - 1;
  130. if (historyRef.current?.length > 20) {
  131. historyRef.current?.shift();
  132. activeIndex.current -= 1;
  133. }
  134. }
  135. if (!isInit) {
  136. saveData(info);
  137. }
  138. }
  139. };
  140. useEffect(() => {
  141. graphRef.current?.setGridSize(project.setting.showGrid ? 10 : 0);
  142. }, [project.setting.showGrid]);
  143. useEffect(() => {
  144. graphRef.current && render(graphRef.current, project);
  145. }, [project.setting.showRelation]);
  146. const [hideDefaultColumn] = useLocalStorageState(
  147. "er-hideDefaultColumn",
  148. {
  149. defaultValue: false,
  150. listenStorageChange: true
  151. }
  152. );
  153. useEffect(() => {
  154. if(graph) {
  155. requestAnimationFrame(() => {
  156. render(graph, project);
  157. });
  158. }
  159. }, [hideDefaultColumn, graph]);
  160. /**
  161. * 初始化画布
  162. * @param container
  163. */
  164. const initGraph = (
  165. container: HTMLElement,
  166. width?: number,
  167. height?: number,
  168. preview?: boolean
  169. ) => {
  170. graphRef.current?.dispose?.();
  171. const instance = new Graph({
  172. container,
  173. width: width || document.documentElement.clientWidth,
  174. height: height || document.documentElement.clientHeight,
  175. autoResize: true,
  176. async: false,
  177. virtual: false,
  178. mousewheel: {
  179. enabled: true,
  180. modifiers: "ctrl",
  181. minScale: 0.2,
  182. maxScale: 2,
  183. },
  184. highlighting: {
  185. nodeAvailable: {
  186. name: "stroke",
  187. args: {
  188. padding: 4,
  189. attrs: {
  190. "stroke-width": 2,
  191. stroke: "red",
  192. },
  193. },
  194. },
  195. },
  196. connecting: {
  197. allowBlank: false,
  198. allowEdge: false,
  199. allowLoop: false,
  200. router: {
  201. name: "normal",
  202. },
  203. createEdge() {
  204. return new Shape.Edge({
  205. attrs: {
  206. line: {
  207. stroke: "#ff0000",
  208. strokeWidth: 1,
  209. strokeDasharray: 5,
  210. targetMarker: null,
  211. },
  212. },
  213. data: {
  214. type: "refer",
  215. },
  216. });
  217. },
  218. },
  219. grid: {
  220. visible: true,
  221. size: 10,
  222. },
  223. background: {
  224. color: "#F2F7FA",
  225. },
  226. interacting: {
  227. nodeMovable: (view) => {
  228. const data = view.cell.getData<{
  229. ignoreDrag: boolean;
  230. lock: boolean;
  231. }>();
  232. // 禁止拖拽或锁节点
  233. if (data?.ignoreDrag || data?.lock) return false;
  234. return true;
  235. },
  236. },
  237. });
  238. if (project.id) {
  239. render(instance, project);
  240. }
  241. instance.use(new Snapline({ enabled: true }));
  242. instance.use(
  243. new Transform({
  244. resizing: {
  245. enabled: (node) => {
  246. return node.shape !== "table-node" && !preview;
  247. },
  248. },
  249. })
  250. );
  251. instance.use(new Scroller({
  252. pannable: true
  253. }));
  254. instance.use(new Keyboard());
  255. instance.use(new Export());
  256. instance.use(
  257. new Selection({
  258. enabled: true,
  259. showNodeSelectionBox: true,
  260. multiple: false,
  261. })
  262. );
  263. setGraph(instance);
  264. graphRef.current = instance;
  265. instance.on(
  266. "node:change:add:relation",
  267. (args: EventArgs["cell:change:*"]) => {
  268. console.log("node:change:add:relation", args.current);
  269. const { source, target } = args.current;
  270. if (source && target) {
  271. addRelation(source, target);
  272. }
  273. }
  274. );
  275. instance.on("edge:dblclick", (args: EventArgs["edge:dblclick"]) => {
  276. console.log("edge:dblclick", args);
  277. setTabActiveKey("2");
  278. setRelationActive(args.cell.id);
  279. });
  280. instance.on(
  281. "node:change:update:remark",
  282. function (args: EventArgs["cell:change:*"]) {
  283. console.log("修改备注:", args.current);
  284. updateRemark(args.current);
  285. }
  286. );
  287. instance.on("node:moved", (args) => {
  288. const position = args.node.position();
  289. const data = args.node.data;
  290. if (data.isTable) {
  291. updateTable({
  292. ...data,
  293. table: {
  294. ...data.table,
  295. style: {
  296. ...data.table.style,
  297. x: position.x,
  298. y: position.y,
  299. },
  300. },
  301. });
  302. }
  303. if (data.isTopicArea) {
  304. updateTopicArea({
  305. ...data,
  306. style: {
  307. ...data.style,
  308. x: position.x,
  309. y: position.y,
  310. },
  311. });
  312. }
  313. if (data.isRemark) {
  314. updateRemark({
  315. ...data,
  316. style: {
  317. ...data.style,
  318. x: position.x,
  319. y: position.y,
  320. },
  321. });
  322. }
  323. });
  324. instance.on("node:resized", (args) => {
  325. console.log("node:resized", args);
  326. const size = args.node.getSize();
  327. const position = args.node.position();
  328. const data = args.node.data;
  329. if (data.isTopicArea) {
  330. updateTopicArea({
  331. ...data,
  332. style: {
  333. ...data.style,
  334. width: size.width,
  335. height: size.height,
  336. x: position.x,
  337. y: position.y,
  338. },
  339. });
  340. }
  341. if (data.isRemark) {
  342. updateRemark({
  343. ...data,
  344. style: {
  345. ...data.style,
  346. width: size.width,
  347. height: size.height,
  348. x: position.x,
  349. y: position.y,
  350. },
  351. });
  352. }
  353. });
  354. instance.bindKey("ctrl+z", onUndo);
  355. instance.bindKey("ctrl+y", onRedo);
  356. instance.bindKey("ctrl+c", onCopy);
  357. instance.bindKey("ctrl+x", onCut);
  358. instance.bindKey("ctrl+v", onPaste);
  359. instance.bindKey("delete", onDelete);
  360. instance.bindKey("ctrl+down", () => {
  361. const scale = instance.zoom() - 0.1;
  362. instance.zoomTo(scale < 0.2 ? 0.2 : scale);
  363. });
  364. instance.bindKey("ctrl+up", () => {
  365. const scale = instance.zoom() + 0.1;
  366. instance.zoomTo(scale > 2 ? 2 : scale);
  367. });
  368. instance.bindKey("ctrl+n", () => {
  369. // todo 新建
  370. });
  371. instance.bindKey("ctrl+s", () => {
  372. onSave();
  373. });
  374. };
  375. // 能否重做
  376. const canRedo = useMemo(() => {
  377. return (
  378. historyRef.current?.length > 1 &&
  379. activeIndex.current < historyRef.current?.length - 1
  380. );
  381. }, [historyRef.current, activeIndex.current]);
  382. // 能否撤销
  383. const canUndo = useMemo(() => {
  384. return activeIndex.current > 0 && historyRef.current?.length > 1;
  385. }, [historyRef.current, activeIndex.current]);
  386. // 撤销
  387. const onUndo = () => {
  388. const info = historyRef.current?.[activeIndex.current - 1];
  389. activeIndex.current -= 1;
  390. setProject(info, true);
  391. };
  392. // 重做
  393. const onRedo = () => {
  394. const info = historyRef.current?.[activeIndex.current + 1];
  395. activeIndex.current += 1;
  396. setProject(info, true);
  397. };
  398. /**
  399. * 添加表
  400. */
  401. const addTable = (parentId?: string) => {
  402. // const area = graphRef.current?.getGraphArea();
  403. const rect = graph?.getAllCellsBBox();
  404. const x = 100;
  405. const y = (rect?.height || 0) + (rect?.y || 0) + 20;
  406. // 数据表类型动态路由传参
  407. const newTable = createTable(project.type || 3, project.id, parentId);
  408. newTable.table.style.x = x;
  409. newTable.table.style.y = y;
  410. // 子表插入到父表后面
  411. const list = [...project.tables];
  412. if (parentId) {
  413. const index = list.findIndex((item) => item.table.id === parentId);
  414. list.splice(index + 1, 0, newTable);
  415. } else {
  416. list.unshift(newTable);
  417. }
  418. setProject({
  419. ...project,
  420. tables: list,
  421. });
  422. setTabActiveKey("1");
  423. setTableActive(newTable.table.id);
  424. const cell = graphRef.current?.getCellById(newTable.table.id);
  425. if(cell) {
  426. graphRef.current?.select(cell);
  427. graphRef.current?.centerCell(cell);
  428. }
  429. };
  430. /**
  431. * 更新表
  432. * @param table
  433. */
  434. const updateTable = (table: TableItemType) => {
  435. setProject((project) => {
  436. return {
  437. ...project,
  438. tables: project.tables.map((item) => {
  439. if (item.table.id === table.table.id) {
  440. return table;
  441. }
  442. return item;
  443. }),
  444. };
  445. });
  446. };
  447. /**
  448. * 删除表及其子表
  449. * @param tableId
  450. */
  451. const deleteTable = (tableId: string) => {
  452. const childTableIds = project.tables
  453. .filter((item) => item.table.parentBusinessTableId === tableId)
  454. .map((item) => item.table.id);
  455. const newInfo = {
  456. ...project,
  457. tables: project.tables.filter(
  458. (item) =>
  459. item.table.id !== tableId &&
  460. item.table.parentBusinessTableId !== tableId
  461. ),
  462. // 对应关系
  463. relations: project.relations.filter(
  464. (item) =>
  465. item.primaryTable !== tableId &&
  466. item.foreignTable !== tableId &&
  467. !childTableIds.includes(item.primaryTable) &&
  468. !childTableIds.includes(item.foreignTable)
  469. ),
  470. };
  471. setProject(newInfo);
  472. };
  473. /**
  474. * 增加主题域
  475. */
  476. const addTopicArea = () => {
  477. const topicAreaId = uuid();
  478. const newTopicArea = {
  479. isTopicArea: true,
  480. id: topicAreaId,
  481. dataModelId: project.id,
  482. name: "主题域_" + (project.topicAreas.length + 1),
  483. style: {
  484. background: "#175e7a",
  485. x: 300,
  486. y: 300,
  487. width: 200,
  488. height: 200,
  489. },
  490. };
  491. setProject({
  492. ...project,
  493. topicAreas: [...project.topicAreas, newTopicArea],
  494. });
  495. setTabActiveKey("3");
  496. const cell = graphRef.current?.getCellById(topicAreaId);
  497. if(cell) {
  498. graphRef.current?.select(cell);
  499. graphRef.current?.centerCell(cell);
  500. }
  501. };
  502. /**
  503. * 修改主题域
  504. */
  505. const updateTopicArea = (topicArea: TopicAreaInfo) => {
  506. setProject((project) => {
  507. return {
  508. ...project,
  509. topicAreas: project.topicAreas.map((item) => {
  510. if (item.id === topicArea.id) {
  511. return topicArea;
  512. }
  513. return item;
  514. }),
  515. };
  516. });
  517. };
  518. /**
  519. * 删除主题域
  520. * @param topicAreaId
  521. */
  522. const deleteTopicArea = (topicAreaId: string) => {
  523. setProject({
  524. ...project,
  525. topicAreas: project.topicAreas.filter((item) => item.id !== topicAreaId),
  526. });
  527. };
  528. /**
  529. * 添加备注
  530. */
  531. const addRemark = () => {
  532. const remarkId = uuid();
  533. const newRemark = {
  534. isRemark: true,
  535. id: remarkId,
  536. name: "备注_" + (project.remarkInfos.length + 1),
  537. text: "",
  538. dataModelId: project.id,
  539. style: {
  540. x: 300,
  541. y: 300,
  542. width: 200,
  543. height: 200,
  544. background: "#fcf7ac",
  545. },
  546. };
  547. setProject({
  548. ...project,
  549. remarkInfos: [...project.remarkInfos, newRemark],
  550. });
  551. setTabActiveKey("4");
  552. const cell = graphRef.current?.getCellById(remarkId);
  553. if(cell) {
  554. graphRef.current?.select(cell);
  555. graphRef.current?.centerCell(cell);
  556. }
  557. };
  558. /**
  559. * 修改备注
  560. */
  561. const updateRemark = (remark: RemarkInfo) => {
  562. console.log(remark);
  563. setProject((state) => ({
  564. ...(state || {}),
  565. remarkInfos: state.remarkInfos.map((item) =>
  566. item.id === remark.id ? remark : item
  567. ),
  568. }));
  569. };
  570. /**
  571. * 删除备注
  572. * @param remarkId
  573. */
  574. const deleteRemark = (remarkId: string) => {
  575. setProject({
  576. ...project,
  577. remarkInfos: project.remarkInfos.filter((item) => item.id !== remarkId),
  578. });
  579. graphRef.current?.removeCell(remarkId);
  580. };
  581. const getRelations = (project: ProjectInfo, newRelation: ColumnRelation) => {
  582. let sourceColumn: ColumnItem | undefined;
  583. let targetColumn: ColumnItem | undefined;
  584. let sourceTable: TableItemType | undefined;
  585. let targetTable: TableItemType | undefined;
  586. project.tables.forEach((table) => {
  587. if (table.table.id === newRelation.primaryTable) {
  588. sourceTable = table;
  589. sourceColumn = table.tableColumnList.find(
  590. (item) => item.id === newRelation.primaryKey
  591. );
  592. }
  593. if (table.table.id === newRelation.foreignTable) {
  594. targetTable = table;
  595. targetColumn = table.tableColumnList.find(
  596. (item) => item.id === newRelation.foreignKey
  597. );
  598. }
  599. });
  600. if (!sourceColumn || !targetColumn) {
  601. return {
  602. relations: project.relations,
  603. canAdd: false,
  604. };
  605. }
  606. if (sourceColumn.type !== targetColumn.type) {
  607. message.warning("数据类型不一致");
  608. return {
  609. relations: project.relations,
  610. canAdd: false,
  611. };
  612. }
  613. if ( sourceColumn.tableId === targetColumn.tableId) {
  614. return {
  615. relations: project.relations,
  616. canAdd: false,
  617. };
  618. }
  619. return {
  620. relations: [
  621. ...project.relations,
  622. {
  623. ...newRelation,
  624. name: `${sourceTable?.table.schemaName}_${targetTable?.table.schemaName}_${sourceColumn.schemaName}`,
  625. },
  626. ],
  627. canAdd: true,
  628. };
  629. };
  630. /**
  631. * 添加关系
  632. */
  633. const addRelation = (
  634. source: {
  635. tableId: string;
  636. columnId: string;
  637. },
  638. target: {
  639. tableId: string;
  640. columnId: string;
  641. }
  642. ) => {
  643. const newRelation: ColumnRelation = {
  644. id: uuid(),
  645. name: "",
  646. primaryKey: source.columnId,
  647. primaryTable: source.tableId,
  648. foreignKey: target.columnId,
  649. foreignTable: target.tableId,
  650. relationType: 1,
  651. style: {
  652. color: "#333",
  653. lineType: RelationLineType.Solid,
  654. width: 1,
  655. },
  656. };
  657. setProject((state) => {
  658. const obj = getRelations(state, {
  659. ...newRelation,
  660. dataModelId: state.id,
  661. });
  662. if (obj.canAdd) {
  663. return {
  664. ...state,
  665. relations: obj.relations,
  666. };
  667. } else {
  668. return state;
  669. }
  670. });
  671. setTabActiveKey("2");
  672. };
  673. /**
  674. * 更新关系
  675. */
  676. const updateRelation = (relation: ColumnRelation) => {
  677. setProject((state) => {
  678. return {
  679. ...state,
  680. relations: state.relations.map((item) => {
  681. if (item.id === relation.id) {
  682. return relation;
  683. }
  684. return item;
  685. }),
  686. };
  687. });
  688. };
  689. /**
  690. * 删除关系
  691. */
  692. const deleteRelation = (relationId: string) => {
  693. setProject({
  694. ...project,
  695. relations: project.relations.filter((item) => item.id !== relationId),
  696. });
  697. };
  698. /**
  699. * 清空画布
  700. */
  701. const onClean = () => {
  702. setProject(
  703. (project) => {
  704. return {
  705. ...project,
  706. tables: [],
  707. relations: [],
  708. topicAreas: [],
  709. remarkInfos: [],
  710. };
  711. },
  712. true,
  713. true
  714. );
  715. graph?.clearCells();
  716. };
  717. const clipboardCache = useRef<any>(null);
  718. /**
  719. * 剪切
  720. */
  721. const onCut = () => {
  722. const cells = graphRef.current?.getSelectedCells();
  723. if (cells?.[0]?.isNode()) {
  724. const cell = cells[0];
  725. const data = cell.data;
  726. clipboardCache.current = data;
  727. // 表
  728. if (data?.isTable) {
  729. const childTableIds = project.tables
  730. .filter((item) => item.table.parentBusinessTableId === cell.id)
  731. .map((item) => item.table.id);
  732. setProject({
  733. ...project,
  734. tables: project.tables.filter(
  735. (item) =>
  736. item.table.id !== cell.id &&
  737. !childTableIds.includes(item.table.id)
  738. ),
  739. relations: project.relations.filter(
  740. (item) =>
  741. item.primaryTable !== cell.id &&
  742. item.foreignTable !== cell.id &&
  743. !childTableIds.includes(item.primaryTable) &&
  744. !childTableIds.includes(item.foreignTable)
  745. ),
  746. });
  747. }
  748. // 主题区域
  749. if (data?.isTopicArea) {
  750. setProject({
  751. ...project,
  752. topicAreas: project.topicAreas.filter((item) => item.id !== cell.id),
  753. });
  754. }
  755. // 备注
  756. if (data?.isRemark) {
  757. setProject({
  758. ...project,
  759. remarkInfos: project.remarkInfos.filter(
  760. (item) => item.id !== cell.id
  761. ),
  762. });
  763. }
  764. }
  765. };
  766. /**
  767. * 复制
  768. */
  769. const onCopy = () => {
  770. const cells = graphRef.current?.getSelectedCells();
  771. if (cells?.[0]?.isNode()) {
  772. const cell = cells[0];
  773. const data = cell.data;
  774. clipboardCache.current = data;
  775. message.success("已复制");
  776. }
  777. };
  778. /**
  779. * 粘贴
  780. */
  781. const onPaste = () => {
  782. if (clipboardCache.current) {
  783. const data = clipboardCache.current;
  784. // 表格
  785. if (data?.isTable) {
  786. const tableId = uuid();
  787. const newTable = {
  788. ...data,
  789. table: {
  790. ...data.table,
  791. schemaName: data.table.schemaName + '_copy',
  792. aliasName: data.table.aliasName + 'Copy',
  793. id: tableId,
  794. style: {
  795. ...data.table.style,
  796. x: data.table.style.x + 20,
  797. y: data.table.style.y + 20,
  798. },
  799. },
  800. tableColumnList: data.tableColumnList.map((item: ColumnItem) => {
  801. return {
  802. ...item,
  803. id: uuid(),
  804. tableId,
  805. };
  806. }),
  807. };
  808. setProject((project) => ({
  809. ...project,
  810. tables: [...project.tables, newTable],
  811. }));
  812. }
  813. // 主题区域
  814. if (data?.isTopicArea) {
  815. const topicAreaId = uuid();
  816. const newTopicArea = {
  817. ...data,
  818. name: data.name + '_copy',
  819. id: topicAreaId,
  820. style: {
  821. ...data.style,
  822. x: data.style.x + 20,
  823. y: data.style.y + 20,
  824. },
  825. };
  826. setProject((project) => ({
  827. ...project,
  828. topicAreas: [...project.topicAreas, newTopicArea],
  829. }));
  830. }
  831. // 注释节点
  832. if (data?.isRemark) {
  833. const remarkId = uuid();
  834. const newRemark = {
  835. ...data,
  836. name: data.name + '_copy',
  837. id: remarkId,
  838. style: {
  839. ...data.style,
  840. x: data.style.x + 20,
  841. y: data.style.y + 20,
  842. },
  843. };
  844. setProject((project) => ({
  845. ...project,
  846. remarkInfos: [...project.remarkInfos, newRemark],
  847. }));
  848. }
  849. }
  850. };
  851. /**
  852. * 删除
  853. */
  854. const onDelete = () => {
  855. const cell = graphRef.current?.getSelectedCells();
  856. if (cell?.[0]?.isNode()) {
  857. const data = cell[0].data;
  858. if (data?.isTable) {
  859. setProject((project) => ({
  860. ...project,
  861. tables: project.tables.filter((item) => item.table.id !== cell[0].id),
  862. }));
  863. }
  864. if (data?.isTopicArea) {
  865. setProject((project) => ({
  866. ...project,
  867. topicAreas: project.topicAreas.filter(
  868. (item) => item.id !== cell[0].id
  869. ),
  870. }));
  871. }
  872. if (data?.isRemark) {
  873. setProject((project) => ({
  874. ...project,
  875. remarkInfos: project.remarkInfos.filter(
  876. (item) => item.id !== cell[0].id
  877. ),
  878. }));
  879. }
  880. }
  881. };
  882. /**
  883. * 演示模式
  884. */
  885. const enterPlayMode = () => {
  886. enterFullscreen();
  887. setPlayModeEnable(true);
  888. setTimeout(() => {
  889. graphRef.current?.centerContent();
  890. }, 100);
  891. };
  892. /**
  893. * 退出演示模式
  894. */
  895. const exitPlayMode = () => {
  896. exitFullscreen();
  897. graphRef.current?.enableKeyboard();
  898. setPlayModeEnable(false);
  899. };
  900. /**
  901. * 保存项目
  902. */
  903. const onSave = async () => {
  904. setProjectInfo((state) => {
  905. message.loading("保存中...", 0);
  906. graph?.toPNG(
  907. async (dataUri) => {
  908. const file = base64ToFile(
  909. dataUri,
  910. project?.id || "封面图",
  911. "image/png"
  912. );
  913. const formData = new FormData();
  914. formData.append("file", file);
  915. const res = await UploadFile(formData);
  916. await SaveDataModel({
  917. erDataModel: {
  918. ...state,
  919. coverImage: res?.result?.[0]?.id,
  920. }
  921. }).finally(() => {
  922. message.destroy();
  923. });
  924. setProjectInfo({
  925. ...state,
  926. coverImage: res?.result?.[0]?.id,
  927. });
  928. setSaveTime(dayjs().format("YYYY-MM-DD HH:mm:ss"));
  929. message.success("保存成功");
  930. },
  931. {
  932. width: 300,
  933. height: 150,
  934. quality: 0.2,
  935. copyStyles: false,
  936. serializeImages: true,
  937. stylesheet: getClassRules(),
  938. }
  939. );
  940. return state;
  941. });
  942. };
  943. /* AI创作 返回结果
  944. {
  945. tables: [{table: {}, tableColumnList: []}],
  946. relations: []
  947. }
  948. */
  949. const onCreateByAi = async (data: any) => {
  950. console.log(data);
  951. // if(data?.tables?.length) {
  952. // data.tables.forEach((tableItem: TableItemType) => {
  953. // const newTable = createTable(project.type || 3, project.id);
  954. // merge(newTable.table, tableItem.table);
  955. // table
  956. // })
  957. // }
  958. await BatchAddAICreateResult({
  959. ...data,
  960. dataModelId: project.id
  961. });
  962. setUpdateKey((state) => (state || 0) + 1);
  963. }
  964. return {
  965. initGraph,
  966. graph,
  967. graphRef,
  968. project,
  969. setProject,
  970. addTable,
  971. updateTable,
  972. deleteTable,
  973. addTopicArea,
  974. updateTopicArea,
  975. deleteTopicArea,
  976. addRemark,
  977. updateRemark,
  978. deleteRemark,
  979. addRelation,
  980. updateRelation,
  981. deleteRelation,
  982. canRedo,
  983. canUndo,
  984. onRedo,
  985. onUndo,
  986. onClean,
  987. onCut,
  988. onCopy,
  989. onPaste,
  990. onDelete,
  991. enterPlayMode,
  992. playModeEnable,
  993. setPlayModeEnable,
  994. exitPlayMode,
  995. saveTime,
  996. onSave,
  997. tableActive,
  998. setTableActive,
  999. onCreateByAi
  1000. };
  1001. }