|
@@ -1,9 +1,10 @@
|
|
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
|
-import { EventArgs, Graph, Shape } from "@antv/x6";
|
|
|
-import { History } from "@antv/x6-plugin-history";
|
|
|
+import { EventArgs, Graph, Shape, Point } from "@antv/x6";
|
|
|
import { Transform } from "@antv/x6-plugin-transform";
|
|
|
import { Scroller } from "@antv/x6-plugin-scroller";
|
|
|
import { Snapline } from "@antv/x6-plugin-snapline";
|
|
|
+import { Keyboard } from "@antv/x6-plugin-keyboard";
|
|
|
+import { Export } from "@antv/x6-plugin-export";
|
|
|
import { GetAllDesignTables } from "@/api";
|
|
|
import { useRequest } from "umi";
|
|
|
import type {
|
|
@@ -16,6 +17,7 @@ import type {
|
|
|
} from "@/type";
|
|
|
import { uuid } from "@/utils";
|
|
|
import { RelationType } from "@/enum";
|
|
|
+import { render } from "./renderer";
|
|
|
|
|
|
import "@/components/TableNode";
|
|
|
import "@/components/TopicNode";
|
|
@@ -25,7 +27,9 @@ import { message } from "antd";
|
|
|
export default function erModel() {
|
|
|
const graphRef = useRef<Graph>();
|
|
|
const [graph, setGraph] = useState<Graph>();
|
|
|
- const [project, setProject] = useState<ProjectInfo>({
|
|
|
+ const historyRef = useRef<ProjectInfo[]>([]);
|
|
|
+ const activeIndex = useRef(0);
|
|
|
+ const [project, setProjectInfo] = useState<ProjectInfo>({
|
|
|
id: "1",
|
|
|
name: "项目1",
|
|
|
folderId: "root",
|
|
@@ -50,6 +54,52 @@ export default function erModel() {
|
|
|
},
|
|
|
});
|
|
|
|
|
|
+ /**
|
|
|
+ * 统一修改数据
|
|
|
+ * @param info 模型信息
|
|
|
+ * @param ingoreHistory 忽略历史记录
|
|
|
+ * @param isInit 初始化
|
|
|
+ */
|
|
|
+ const setProject = (
|
|
|
+ info: ProjectInfo | ((state: ProjectInfo) => ProjectInfo),
|
|
|
+ ingoreHistory?: boolean,
|
|
|
+ isInit?: boolean
|
|
|
+ ) => {
|
|
|
+ if (isInit) {
|
|
|
+ historyRef.current = [];
|
|
|
+ activeIndex.current = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (info && typeof info === "function") {
|
|
|
+ setProjectInfo((state) => {
|
|
|
+ const result = info(state);
|
|
|
+ graphRef.current && render(graphRef.current, result);
|
|
|
+ // 添加记录
|
|
|
+ if (!ingoreHistory) {
|
|
|
+ historyRef.current?.push(result);
|
|
|
+ activeIndex.current = historyRef.current?.length - 1;
|
|
|
+ if (historyRef.current?.length > 20) {
|
|
|
+ historyRef.current?.shift();
|
|
|
+ activeIndex.current -= 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ setProjectInfo(info);
|
|
|
+ graphRef.current && render(graphRef.current, info);
|
|
|
+ // 添加记录
|
|
|
+ if (!ingoreHistory) {
|
|
|
+ historyRef.current?.push(info);
|
|
|
+ activeIndex.current = historyRef.current?.length - 1;
|
|
|
+ if (historyRef.current?.length > 20) {
|
|
|
+ historyRef.current?.shift();
|
|
|
+ activeIndex.current -= 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
const initGraph = (container: HTMLElement) => {
|
|
|
const instance = new Graph({
|
|
|
container,
|
|
@@ -118,16 +168,19 @@ export default function erModel() {
|
|
|
},
|
|
|
});
|
|
|
|
|
|
- instance.use(new History());
|
|
|
instance.use(new Snapline({ enabled: true }));
|
|
|
instance.use(
|
|
|
new Transform({
|
|
|
resizing: {
|
|
|
- enabled: true,
|
|
|
+ enabled: (node) => {
|
|
|
+ return node.shape !== "table-node";
|
|
|
+ },
|
|
|
},
|
|
|
})
|
|
|
);
|
|
|
instance.use(new Scroller());
|
|
|
+ instance.use(new Keyboard());
|
|
|
+ instance.use(new Export());
|
|
|
|
|
|
setGraph(instance);
|
|
|
graphRef.current = instance;
|
|
@@ -142,12 +195,67 @@ export default function erModel() {
|
|
|
}
|
|
|
}
|
|
|
);
|
|
|
+
|
|
|
+ instance.on(
|
|
|
+ "node:change:update:remark",
|
|
|
+ function (args: EventArgs["cell:change:*"]) {
|
|
|
+ console.log('修改备注:', args.current)
|
|
|
+ updateRemark(args.current);
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ // Graph.registerAnchor({
|
|
|
+ // 'custom-anchor': function(view, magnet, ref, options) {
|
|
|
+ // // console.log(view, magnet, ref);
|
|
|
+ // const source = magnet.getBoundingClientRect();
|
|
|
+ // const target = (ref as SVGAElement).getBoundingClientRect();
|
|
|
+ // console.log(source, target, this);
|
|
|
+ // if(source.x + source.width < target.x + target.width) {
|
|
|
+ // // 右侧
|
|
|
+ // // 获取右侧点
|
|
|
+ // if(this.sourceView?.cell.isNode()) {
|
|
|
+ // return new Point(this.sourceView.getBBox().x + this.sourceView.getBBox().width, this.sourceView.getBBox().y + this.sourceView.getBBox().height / 2);
|
|
|
+ // }
|
|
|
+ // } else {
|
|
|
+ // // 左侧
|
|
|
+ // return new Point(this.sourceView.getBBox().x, this.sourceView.getBBox().y + this.sourceView.getBBox().height / 2);
|
|
|
+ // }
|
|
|
+ // return new Point(100, 200);
|
|
|
+ // }
|
|
|
+ // });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 能否重做
|
|
|
+ const canRedo = useMemo(() => {
|
|
|
+ return (
|
|
|
+ historyRef.current?.length > 1 &&
|
|
|
+ activeIndex.current < historyRef.current?.length - 1
|
|
|
+ );
|
|
|
+ }, [historyRef.current, activeIndex.current]);
|
|
|
+
|
|
|
+ // 能否撤销
|
|
|
+ const canUndo = useMemo(() => {
|
|
|
+ return activeIndex.current > 0 && historyRef.current?.length > 1;
|
|
|
+ }, [historyRef.current, activeIndex.current]);
|
|
|
+
|
|
|
+ // 撤销
|
|
|
+ const onUndo = () => {
|
|
|
+ const info = historyRef.current?.[activeIndex.current - 1];
|
|
|
+ activeIndex.current -= 1;
|
|
|
+ setProject(info, true);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 重做
|
|
|
+ const onRedo = () => {
|
|
|
+ const info = historyRef.current?.[activeIndex.current + 1];
|
|
|
+ activeIndex.current += 1;
|
|
|
+ setProject(info, true);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 添加表
|
|
|
*/
|
|
|
- const addTable = () => {
|
|
|
+ const addTable = (parentId?: string) => {
|
|
|
const tableId = uuid();
|
|
|
const columnId = uuid();
|
|
|
const newTable: TableItemType = {
|
|
@@ -163,13 +271,14 @@ export default function erModel() {
|
|
|
name: "",
|
|
|
cn_name: "新建表",
|
|
|
en_name: "new table",
|
|
|
- parentBusinessTableId: "",
|
|
|
+ parentBusinessTableId: parentId || "",
|
|
|
schemaName: "new_table",
|
|
|
type: 1,
|
|
|
updateTime: "",
|
|
|
openSync: false,
|
|
|
style: {
|
|
|
- color: "#616161",
|
|
|
+ // 随机颜色
|
|
|
+ color: "#" + Math.floor(Math.random() * 0x666666).toString(16),
|
|
|
x: 0,
|
|
|
y: 0,
|
|
|
},
|
|
@@ -212,44 +321,16 @@ export default function erModel() {
|
|
|
],
|
|
|
};
|
|
|
|
|
|
+ // 子表插入到父表后面
|
|
|
+ const tables = [...project.tables];
|
|
|
+ tables.splice(
|
|
|
+ tables.findIndex((item) => item.table.id === parentId) + 1,
|
|
|
+ 0,
|
|
|
+ newTable
|
|
|
+ );
|
|
|
setProject({
|
|
|
...project,
|
|
|
- tables: [...project.tables, newTable],
|
|
|
- });
|
|
|
-
|
|
|
- graphRef.current?.addNode({
|
|
|
- shape: "table-node",
|
|
|
- x: 300,
|
|
|
- y: 100,
|
|
|
- width: 200,
|
|
|
- height: 200,
|
|
|
- id: tableId,
|
|
|
- data: newTable,
|
|
|
- zIndex: 3,
|
|
|
- ports: {
|
|
|
- groups: {
|
|
|
- // 字段名前连接桩
|
|
|
- columnPort: {
|
|
|
- markup: [
|
|
|
- {
|
|
|
- tagName: "rect",
|
|
|
- selector: "rect",
|
|
|
- },
|
|
|
- {
|
|
|
- tagName: "circle",
|
|
|
- selector: "circle",
|
|
|
- },
|
|
|
- ],
|
|
|
- position: {
|
|
|
- name: "absolute",
|
|
|
- args: {
|
|
|
- x: 12,
|
|
|
- y: 42,
|
|
|
- },
|
|
|
- },
|
|
|
- },
|
|
|
- },
|
|
|
- },
|
|
|
+ tables: parentId ? tables : [...project.tables, newTable],
|
|
|
});
|
|
|
};
|
|
|
|
|
@@ -272,13 +353,18 @@ export default function erModel() {
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
- * 删除表
|
|
|
+ * 删除表及其子表
|
|
|
* @param tableId
|
|
|
*/
|
|
|
const deleteTable = (tableId: string) => {
|
|
|
setProject({
|
|
|
...project,
|
|
|
- tables: project.tables.filter((item) => item.table.id !== tableId),
|
|
|
+ tables: project.tables.filter(
|
|
|
+ (item) =>
|
|
|
+ item.table.id !== tableId ||
|
|
|
+ item.table.parentBusinessTableId !== tableId
|
|
|
+ ),
|
|
|
+ // todo 处理子表对应关系
|
|
|
relations: project.relations.filter(
|
|
|
(item) => item.primaryTable !== tableId && item.foreignTable !== tableId
|
|
|
),
|
|
@@ -308,17 +394,6 @@ export default function erModel() {
|
|
|
...project,
|
|
|
topicAreas: [...project.topicAreas, newTopicArea],
|
|
|
});
|
|
|
-
|
|
|
- graphRef.current?.addNode({
|
|
|
- shape: "topic-node",
|
|
|
- x: 300,
|
|
|
- y: 100,
|
|
|
- width: 200,
|
|
|
- height: 200,
|
|
|
- id: topicAreaId,
|
|
|
- data: newTopicArea,
|
|
|
- zIndex: 0,
|
|
|
- });
|
|
|
};
|
|
|
|
|
|
/**
|
|
@@ -371,21 +446,6 @@ export default function erModel() {
|
|
|
...project,
|
|
|
remarks: [...project.remarks, newRemark],
|
|
|
});
|
|
|
-
|
|
|
- const notice = graphRef.current?.addNode({
|
|
|
- shape: "notice-node",
|
|
|
- x: 300,
|
|
|
- y: 100,
|
|
|
- width: 200,
|
|
|
- height: 200,
|
|
|
- id: remarkId,
|
|
|
- data: newRemark,
|
|
|
- zIndex: 1,
|
|
|
- });
|
|
|
-
|
|
|
- notice?.on("change:data", function (args) {
|
|
|
- updateRemark(args.current);
|
|
|
- });
|
|
|
};
|
|
|
|
|
|
/**
|
|
@@ -436,7 +496,7 @@ export default function erModel() {
|
|
|
);
|
|
|
}
|
|
|
});
|
|
|
- console.log(sourceColumn, targetColumn, newRelation);
|
|
|
+
|
|
|
if (!sourceColumn || !targetColumn) {
|
|
|
return {
|
|
|
relations: project.relations,
|
|
@@ -453,106 +513,29 @@ export default function erModel() {
|
|
|
|
|
|
return {
|
|
|
relations: [
|
|
|
- ...project.relations, {
|
|
|
+ ...project.relations,
|
|
|
+ {
|
|
|
...newRelation,
|
|
|
name: `${sourceTable?.table.schemaName}_${targetTable?.table.schemaName}_${sourceColumn.schemaName}`,
|
|
|
- }],
|
|
|
+ },
|
|
|
+ ],
|
|
|
canAdd: true,
|
|
|
};
|
|
|
};
|
|
|
|
|
|
- const addRelationEdge = (
|
|
|
- id: string,
|
|
|
- source: RelationItem,
|
|
|
- target: RelationItem
|
|
|
- ) => {
|
|
|
- // 添加关系连线
|
|
|
- const relationEdge = graphRef.current?.addEdge({
|
|
|
- id,
|
|
|
- router: {
|
|
|
- name: "manhattan",
|
|
|
- args: {
|
|
|
- direction: "H",
|
|
|
- },
|
|
|
- },
|
|
|
- attrs: {
|
|
|
- line: {
|
|
|
- stroke: "#333",
|
|
|
- strokeWidth: 1,
|
|
|
- targetMarker: null,
|
|
|
- },
|
|
|
- },
|
|
|
- source: {
|
|
|
- cell: source.tableId,
|
|
|
- port: source.columnId + "_port2",
|
|
|
- anchor: "left",
|
|
|
- },
|
|
|
- target: {
|
|
|
- cell: target.tableId,
|
|
|
- port: target.columnId + "_port2",
|
|
|
- anchor: "left",
|
|
|
- },
|
|
|
- data: {
|
|
|
- type: "relation",
|
|
|
- label: uuid(),
|
|
|
- },
|
|
|
- defaultLabel: {
|
|
|
- markup: [
|
|
|
- {
|
|
|
- tagName: "circle",
|
|
|
- selector: "bg",
|
|
|
- },
|
|
|
- {
|
|
|
- tagName: "text",
|
|
|
- selector: "txt",
|
|
|
- },
|
|
|
- ],
|
|
|
- attrs: {
|
|
|
- txt: {
|
|
|
- fill: "#fff",
|
|
|
- textAnchor: "middle",
|
|
|
- textVerticalAnchor: "middle",
|
|
|
- },
|
|
|
- bg: {
|
|
|
- ref: "txt",
|
|
|
- fill: "#333",
|
|
|
- r: 10,
|
|
|
- strokeWidth: 0,
|
|
|
- },
|
|
|
- },
|
|
|
- },
|
|
|
- });
|
|
|
- relationEdge?.appendLabel({
|
|
|
- attrs: {
|
|
|
- txt: {
|
|
|
- text: 1,
|
|
|
- },
|
|
|
- },
|
|
|
- position: {
|
|
|
- distance: 25,
|
|
|
- },
|
|
|
- });
|
|
|
- relationEdge?.appendLabel({
|
|
|
- attrs: {
|
|
|
- txt: {
|
|
|
- text: 1,
|
|
|
- },
|
|
|
- },
|
|
|
- position: {
|
|
|
- distance: -25,
|
|
|
- },
|
|
|
- });
|
|
|
- };
|
|
|
-
|
|
|
- type RelationItem = {
|
|
|
- tableId: string;
|
|
|
- columnId: string;
|
|
|
- };
|
|
|
-
|
|
|
/**
|
|
|
* 添加关系
|
|
|
*/
|
|
|
- const addRelation = (source: RelationItem, target: RelationItem) => {
|
|
|
+ const addRelation = (
|
|
|
+ source: {
|
|
|
+ tableId: string;
|
|
|
+ columnId: string;
|
|
|
+ },
|
|
|
+ target: {
|
|
|
+ tableId: string;
|
|
|
+ columnId: string;
|
|
|
+ }
|
|
|
+ ) => {
|
|
|
const newRelation: ColumnRelation = {
|
|
|
id: uuid(),
|
|
|
name: "",
|
|
@@ -566,9 +549,6 @@ export default function erModel() {
|
|
|
setProject((state) => {
|
|
|
const obj = getRelations(state, newRelation);
|
|
|
if (obj.canAdd) {
|
|
|
- // 添加连线
|
|
|
- addRelationEdge(newRelation.id, source, target);
|
|
|
-
|
|
|
return {
|
|
|
...state,
|
|
|
relations: obj.relations,
|
|
@@ -611,7 +591,11 @@ export default function erModel() {
|
|
|
relationEdge.setLabelAt(0, {
|
|
|
attrs: {
|
|
|
txt: {
|
|
|
- text: relation.relationType === RelationType.OneToOne || relation.relationType === RelationType.OneToMany ? '1' : 'n',
|
|
|
+ text:
|
|
|
+ relation.relationType === RelationType.OneToOne ||
|
|
|
+ relation.relationType === RelationType.OneToMany
|
|
|
+ ? "1"
|
|
|
+ : "n",
|
|
|
},
|
|
|
},
|
|
|
position: {
|
|
@@ -621,7 +605,11 @@ export default function erModel() {
|
|
|
relationEdge.setLabelAt(1, {
|
|
|
attrs: {
|
|
|
txt: {
|
|
|
- text: relation.relationType === RelationType.OneToMany || relation.relationType === RelationType.ManyToMany ? 'n' : '1',
|
|
|
+ text:
|
|
|
+ relation.relationType === RelationType.OneToMany ||
|
|
|
+ relation.relationType === RelationType.ManyToMany
|
|
|
+ ? "n"
|
|
|
+ : "1",
|
|
|
},
|
|
|
},
|
|
|
position: {
|
|
@@ -661,5 +649,9 @@ export default function erModel() {
|
|
|
addRelation,
|
|
|
updateRelation,
|
|
|
deleteRelation,
|
|
|
+ canRedo,
|
|
|
+ canUndo,
|
|
|
+ onRedo,
|
|
|
+ onUndo,
|
|
|
};
|
|
|
}
|