数据同步逻辑修改
This commit is contained in:
parent
5c50d114b4
commit
cedc6ca7a1
|
|
@ -1,9 +1,9 @@
|
|||
import type { Edge, Node, NodeConfig } from '@antv/x6';
|
||||
import { Graph } from '@antv/x6';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import type { Edge, Node, NodeConfig } from "@antv/x6";
|
||||
import { Graph } from "@antv/x6";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
// 层级类型定义
|
||||
export type NodeLevel = 'level2' | 'level3' | 'level4';
|
||||
export type NodeLevel = "level2" | "level3" | "level4";
|
||||
|
||||
// 目标灌库数据的类型定义
|
||||
interface ImportDataItem {
|
||||
|
|
@ -50,14 +50,14 @@ export class X6Graph {
|
|||
private readonly nodeStyle = {
|
||||
width: 100,
|
||||
height: 40,
|
||||
fill: '#0a4a5c', // 深色背景(示例中的深青色)
|
||||
stroke: '#0a4a5c', // 边框与背景同色,视觉上隐藏边框
|
||||
fill: "#0a4a5c", // 深色背景(示例中的深青色)
|
||||
stroke: "#0a4a5c", // 边框与背景同色,视觉上隐藏边框
|
||||
strokeWidth: 2,
|
||||
radius: 4,
|
||||
label: {
|
||||
fill: '#93CDD5', // 白色文字
|
||||
fill: "#93CDD5", // 白色文字
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold', // 可选:文字加粗
|
||||
fontWeight: "bold", // 可选:文字加粗
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -69,16 +69,16 @@ export class X6Graph {
|
|||
|
||||
// 层级X坐标映射
|
||||
private readonly levelXMap = new Map<NodeLevel, number>([
|
||||
['level2', 300],
|
||||
['level3', 550],
|
||||
['level4', 1000],
|
||||
["level2", 300],
|
||||
["level3", 550],
|
||||
["level4", 1000],
|
||||
]);
|
||||
|
||||
// 层级节点数量记录
|
||||
private readonly levelCountMap = new Map<NodeLevel, number>([
|
||||
['level2', 0],
|
||||
['level3', 0],
|
||||
['level4', 0],
|
||||
["level2", 0],
|
||||
["level3", 0],
|
||||
["level4", 0],
|
||||
]);
|
||||
|
||||
constructor(containerId: string) {
|
||||
|
|
@ -87,7 +87,12 @@ export class X6Graph {
|
|||
}
|
||||
|
||||
private createMenuElement() {
|
||||
this.menuElement = document.createElement('div');
|
||||
this.menuElement = document.getElementById("ant-x6-menu4");
|
||||
if(this.menuElement){
|
||||
this.menuElement.remove();
|
||||
}
|
||||
this.menuElement = document.createElement("div");
|
||||
this.menuElement.id = "ant-x6-menu4";
|
||||
this.menuElement.style.cssText = `
|
||||
position: absolute;
|
||||
width: 120px;
|
||||
|
|
@ -101,18 +106,22 @@ export class X6Graph {
|
|||
font-family: sans-serif;
|
||||
`;
|
||||
|
||||
// 1. 配置同步字段:新增“配置”菜单项
|
||||
const configItem = document.createElement('div');
|
||||
configItem.id = 'ant-x6-menu-1';
|
||||
configItem.style.cssText = `
|
||||
// 3. 配置
|
||||
let pzItem = document.getElementById("ant-x6-menu");
|
||||
if (pzItem) {
|
||||
pzItem.remove();
|
||||
}
|
||||
pzItem = document.createElement("div");
|
||||
pzItem.id = "ant-x6-menu";
|
||||
pzItem.style.cssText = `
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
border-bottom: 1px solid #f0f0f0; /* 分隔线,区分两个选项 */
|
||||
`;
|
||||
configItem.textContent = '配置同步字段';
|
||||
configItem.addEventListener('click', () => {
|
||||
const nodeId = this.menuElement?.getAttribute('data-node-id');
|
||||
pzItem.textContent = "配置";
|
||||
pzItem.addEventListener("click", () => {
|
||||
const nodeId = this.menuElement?.getAttribute("data-node-id");
|
||||
if (nodeId) {
|
||||
const graph = this.getGraph();
|
||||
const targetNode = graph.getCellById(nodeId) as Node;
|
||||
|
|
@ -122,35 +131,41 @@ export class X6Graph {
|
|||
// 2. 高亮样式(保持原有)
|
||||
targetNode.attr({
|
||||
body: {
|
||||
stroke: 'rgb(12,129,123)',
|
||||
stroke: "rgb(12,129,123)",
|
||||
strokeWidth: 2,
|
||||
strokeDasharray: '5, 5',
|
||||
strokeDasharray: "5, 5",
|
||||
},
|
||||
});
|
||||
|
||||
// ======================== 新增:获取所有父级信息 ========================
|
||||
// 1. 定义追溯父级的方法
|
||||
const getAllParents = (targetNodeId: string): Array<{ id: string; label: string; level?: NodeLevel }> => {
|
||||
const parents: Array<{ id: string; label: string; level?: NodeLevel }> = [];
|
||||
const getAllParents = (
|
||||
targetNodeId: string,
|
||||
): Array<{ id: string; label: string; level?: NodeLevel }> => {
|
||||
const parents: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
level?: NodeLevel;
|
||||
}> = [];
|
||||
let currentNodeId = targetNodeId;
|
||||
|
||||
// 循环追溯:直到找不到父节点(根节点)
|
||||
while (true) {
|
||||
// 找到指向当前节点的连线(来源即父节点)
|
||||
const parentEdge = graph.getEdges().find(edge => edge.getTargetCellId() === currentNodeId);
|
||||
if (!parentEdge)
|
||||
break; // 无父节点,终止循环
|
||||
const parentEdge = graph
|
||||
.getEdges()
|
||||
.find((edge) => edge.getTargetCellId() === currentNodeId);
|
||||
if (!parentEdge) break; // 无父节点,终止循环
|
||||
|
||||
// 获取父节点ID和父节点实例
|
||||
const parentNodeId = parentEdge.getSourceCellId();
|
||||
const parentNode = graph.getCellById(parentNodeId) as Node;
|
||||
if (!parentNode)
|
||||
break;
|
||||
if (!parentNode) break;
|
||||
|
||||
// 收集父节点信息(ID、标签、层级)
|
||||
parents.push({
|
||||
id: parentNodeId,
|
||||
label: parentNode.attr('label/text') || '', // 节点显示的文字
|
||||
label: parentNode.attr("label/text") || "", // 节点显示的文字
|
||||
level: parentNode.getData().level, // 父节点的层级
|
||||
});
|
||||
|
||||
|
|
@ -169,45 +184,155 @@ export class X6Graph {
|
|||
// 关键:添加当前节点信息(含label),拼接成“父级+当前节点”的完整链
|
||||
const currentNodeInfo = {
|
||||
id: nodeId,
|
||||
label: targetNode.attr('label/text') || '', // 从当前节点获取label
|
||||
label: targetNode.attr("label/text") || "", // 从当前节点获取label
|
||||
level: nodeLevel,
|
||||
};
|
||||
allParentsOrdered = [...allParentsOrdered, currentNodeInfo];
|
||||
|
||||
console.log('目标节点(父级+自身)完整链:', allParentsOrdered);
|
||||
console.log("目标节点(父级+自身)完整链:", allParentsOrdered);
|
||||
// ========================================================================
|
||||
|
||||
// 3. 事件总线传递:携带完整链信息
|
||||
EventBusTool.getEventBus().publish('antvX6Event', {
|
||||
key: 'configNode',
|
||||
EventBusTool.getEventBus().publish("antvX6Event", {
|
||||
key: "configNode",
|
||||
id: nodeId,
|
||||
label: targetNode.attr('label/text') || '',
|
||||
label: targetNode.attr("label/text") || "",
|
||||
level: nodeLevel,
|
||||
allParents: allParentsOrdered, // 包含父级+当前节点(均有label)
|
||||
});
|
||||
}
|
||||
this.hideMenu();
|
||||
});
|
||||
// 配置项悬停效果
|
||||
configItem.addEventListener('mouseenter', () => {
|
||||
configItem.style.background = '#f0f0f0';
|
||||
// 配置菜单悬停效果
|
||||
pzItem.addEventListener("mouseenter", () => {
|
||||
pzItem.style.background = "#f0f0f0";
|
||||
});
|
||||
configItem.addEventListener('mouseleave', () => {
|
||||
configItem.style.background = 'white';
|
||||
pzItem.addEventListener("mouseleave", () => {
|
||||
pzItem.style.background = "white";
|
||||
});
|
||||
|
||||
// 1. 配置同步字段:新增“配置”菜单项
|
||||
let configItem = document.getElementById("ant-x6-menu-1");
|
||||
if (configItem) {
|
||||
configItem.remove();
|
||||
}
|
||||
configItem = document.createElement("div");
|
||||
configItem.id = "ant-x6-menu-1";
|
||||
configItem.style.cssText = `
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
border-bottom: 1px solid #f0f0f0; /* 分隔线,区分两个选项 */
|
||||
`;
|
||||
configItem.textContent = "配置同步字段";
|
||||
configItem.addEventListener("click", () => {
|
||||
const nodeId = this.menuElement?.getAttribute("data-node-id");
|
||||
if (nodeId) {
|
||||
const graph = this.getGraph();
|
||||
const targetNode = graph.getCellById(nodeId) as Node;
|
||||
const nodeData = targetNode.getData();
|
||||
const nodeLevel = nodeData.level;
|
||||
|
||||
// 2. 高亮样式(保持原有)
|
||||
targetNode.attr({
|
||||
body: {
|
||||
stroke: "rgb(12,129,123)",
|
||||
strokeWidth: 2,
|
||||
strokeDasharray: "5, 5",
|
||||
},
|
||||
});
|
||||
|
||||
// ======================== 新增:获取所有父级信息 ========================
|
||||
// 1. 定义追溯父级的方法
|
||||
const getAllParents = (
|
||||
targetNodeId: string,
|
||||
): Array<{ id: string; label: string; level?: NodeLevel }> => {
|
||||
const parents: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
level?: NodeLevel;
|
||||
}> = [];
|
||||
let currentNodeId = targetNodeId;
|
||||
|
||||
// 循环追溯:直到找不到父节点(根节点)
|
||||
while (true) {
|
||||
// 找到指向当前节点的连线(来源即父节点)
|
||||
const parentEdge = graph
|
||||
.getEdges()
|
||||
.find((edge) => edge.getTargetCellId() === currentNodeId);
|
||||
if (!parentEdge) break; // 无父节点,终止循环
|
||||
|
||||
// 获取父节点ID和父节点实例
|
||||
const parentNodeId = parentEdge.getSourceCellId();
|
||||
const parentNode = graph.getCellById(parentNodeId) as Node;
|
||||
if (!parentNode) break;
|
||||
|
||||
// 收集父节点信息(ID、标签、层级)
|
||||
parents.push({
|
||||
id: parentNodeId,
|
||||
label: parentNode.attr("label/text") || "", // 节点显示的文字
|
||||
level: parentNode.getData().level, // 父节点的层级
|
||||
});
|
||||
|
||||
// 下一轮追溯:以父节点为当前节点,找它的父节点
|
||||
currentNodeId = parentNodeId;
|
||||
}
|
||||
|
||||
return parents; // 返回所有父级(顺序:直接父级 → 祖父级 → ... → 根节点父级)
|
||||
};
|
||||
|
||||
// 2. 执行追溯,获取所有父级
|
||||
const allParentNodes = getAllParents(nodeId);
|
||||
// 反转数组,让顺序变为“根节点父级 → 祖父级 → 直接父级”
|
||||
let allParentsOrdered = allParentNodes.reverse();
|
||||
|
||||
// 关键:添加当前节点信息(含label),拼接成“父级+当前节点”的完整链
|
||||
const currentNodeInfo = {
|
||||
id: nodeId,
|
||||
label: targetNode.attr("label/text") || "", // 从当前节点获取label
|
||||
level: nodeLevel,
|
||||
};
|
||||
allParentsOrdered = [...allParentsOrdered, currentNodeInfo];
|
||||
|
||||
console.log("目标节点(父级+自身)完整链:", allParentsOrdered);
|
||||
// ========================================================================
|
||||
|
||||
// 3. 事件总线传递:携带完整链信息
|
||||
EventBusTool.getEventBus().publish("antvX6Event", {
|
||||
key: "configNode",
|
||||
id: nodeId,
|
||||
label: targetNode.attr("label/text") || "",
|
||||
level: nodeLevel,
|
||||
allParents: allParentsOrdered, // 包含父级+当前节点(均有label)
|
||||
isTbsl:false,
|
||||
});
|
||||
}
|
||||
this.hideMenu();
|
||||
});
|
||||
// 配置项悬停效果
|
||||
configItem.addEventListener("mouseenter", () => {
|
||||
configItem.style.background = "#f0f0f0";
|
||||
});
|
||||
configItem.addEventListener("mouseleave", () => {
|
||||
configItem.style.background = "white";
|
||||
});
|
||||
|
||||
// 2. 配置同步数量
|
||||
const syncNumItem = document.createElement('div');
|
||||
syncNumItem.id = 'ant-x6-menu-2';
|
||||
let syncNumItem = document.getElementById("ant-x6-menu-2");
|
||||
if (syncNumItem) {
|
||||
syncNumItem.remove();
|
||||
}
|
||||
syncNumItem = document.createElement("div");
|
||||
syncNumItem.id = "ant-x6-menu-2";
|
||||
syncNumItem.style.cssText = `
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
border-bottom: 1px solid #f0f0f0; /* 分隔线,区分两个选项 */
|
||||
`;
|
||||
syncNumItem.textContent = '配置同步数量';
|
||||
syncNumItem.addEventListener('click', () => {
|
||||
const nodeId = this.menuElement?.getAttribute('data-node-id');
|
||||
syncNumItem.textContent = "配置同步数量";
|
||||
syncNumItem.addEventListener("click", () => {
|
||||
const nodeId = this.menuElement?.getAttribute("data-node-id");
|
||||
if (nodeId) {
|
||||
const graph = this.getGraph();
|
||||
const targetNode = graph.getCellById(nodeId) as Node;
|
||||
|
|
@ -215,28 +340,40 @@ export class X6Graph {
|
|||
|
||||
// TODO:配置同步数量逻辑
|
||||
|
||||
// 3. 事件总线传递:携带完整链信息
|
||||
EventBusTool.getEventBus().publish("antvX6Event", {
|
||||
key: "configNode",
|
||||
id: nodeId,
|
||||
label: targetNode.attr("label/text") || "",
|
||||
level: nodeLevel,
|
||||
isTbsl:true,
|
||||
});
|
||||
}
|
||||
this.hideMenu();
|
||||
});
|
||||
// 配置同步数量菜单悬停效果
|
||||
syncNumItem.addEventListener('mouseenter', () => {
|
||||
syncNumItem.style.background = '#f0f0f0';
|
||||
syncNumItem.addEventListener("mouseenter", () => {
|
||||
syncNumItem.style.background = "#f0f0f0";
|
||||
});
|
||||
syncNumItem.addEventListener('mouseleave', () => {
|
||||
syncNumItem.style.background = 'white';
|
||||
syncNumItem.addEventListener("mouseleave", () => {
|
||||
syncNumItem.style.background = "white";
|
||||
});
|
||||
|
||||
// 3. 原有“删除”菜单项(保持不变,调整顺序在配置项下方)
|
||||
const deleteItem = document.createElement('div');
|
||||
deleteItem.id = 'ant-x6-menu-3';
|
||||
let deleteItem = document.getElementById("ant-x6-menu-3");
|
||||
if (deleteItem) {
|
||||
deleteItem.remove();
|
||||
}
|
||||
deleteItem = document.createElement("div");
|
||||
deleteItem.id = "ant-x6-menu-3";
|
||||
deleteItem.style.cssText = `
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
`;
|
||||
deleteItem.textContent = '删除';
|
||||
deleteItem.addEventListener('click', () => {
|
||||
const nodeId = this.menuElement?.getAttribute('data-node-id');
|
||||
deleteItem.textContent = "删除";
|
||||
deleteItem.addEventListener("click", () => {
|
||||
const nodeId = this.menuElement?.getAttribute("data-node-id");
|
||||
if (nodeId) {
|
||||
// 获取图形实例和目标节点(和配置项逻辑一致)
|
||||
const graph = this.getGraph();
|
||||
|
|
@ -244,8 +381,8 @@ export class X6Graph {
|
|||
// 从节点数据中提取level
|
||||
const nodeLevel = targetNode.getData().level;
|
||||
this.removeNode(nodeId);
|
||||
EventBusTool.getEventBus().publish('antvX6Event', {
|
||||
key: 'removeNode',
|
||||
EventBusTool.getEventBus().publish("antvX6Event", {
|
||||
key: "removeNode",
|
||||
id: nodeId,
|
||||
level: nodeLevel, // 新增:传递level信息
|
||||
});
|
||||
|
|
@ -253,14 +390,15 @@ export class X6Graph {
|
|||
this.hideMenu();
|
||||
});
|
||||
// 删除项悬停效果
|
||||
deleteItem.addEventListener('mouseenter', () => {
|
||||
deleteItem.style.background = '#f0f0f0';
|
||||
deleteItem.addEventListener("mouseenter", () => {
|
||||
deleteItem.style.background = "#f0f0f0";
|
||||
});
|
||||
deleteItem.addEventListener('mouseleave', () => {
|
||||
deleteItem.style.background = 'white';
|
||||
deleteItem.addEventListener("mouseleave", () => {
|
||||
deleteItem.style.background = "white";
|
||||
});
|
||||
|
||||
// 3. 将两个菜单项添加到菜单容器(配置项在上,删除项在下)
|
||||
this.menuElement.appendChild(pzItem);
|
||||
this.menuElement.appendChild(configItem);
|
||||
this.menuElement.appendChild(syncNumItem);
|
||||
this.menuElement.appendChild(deleteItem);
|
||||
|
|
@ -271,12 +409,11 @@ export class X6Graph {
|
|||
* 显示右键菜单
|
||||
*/
|
||||
private showMenu(nodeId: string, x: number, y: number) {
|
||||
if (!this.menuElement)
|
||||
return;
|
||||
this.menuElement.setAttribute('data-node-id', nodeId);
|
||||
if (!this.menuElement) return;
|
||||
this.menuElement.setAttribute("data-node-id", nodeId);
|
||||
this.menuElement.style.left = `${x}px`;
|
||||
this.menuElement.style.top = `${y}px`;
|
||||
this.menuElement.style.display = 'block';
|
||||
this.menuElement.style.display = "block";
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -284,7 +421,7 @@ export class X6Graph {
|
|||
*/
|
||||
private hideMenu() {
|
||||
if (this.menuElement) {
|
||||
this.menuElement.style.display = 'none';
|
||||
this.menuElement.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -302,14 +439,14 @@ export class X6Graph {
|
|||
container,
|
||||
width: container.clientWidth,
|
||||
height: container.clientHeight,
|
||||
grid: options.grid ?? { size: 10, visible: false, type: 'dot' },
|
||||
grid: options.grid ?? { size: 10, visible: false, type: "dot" },
|
||||
mousewheel: options.zoom ?? {
|
||||
enabled: true,
|
||||
modifiers: 'ctrl',
|
||||
modifiers: "ctrl",
|
||||
minScale: 0.5,
|
||||
maxScale: 2,
|
||||
},
|
||||
panning: { enabled: true, modifier: 'shift' },
|
||||
panning: { enabled: true, modifier: "shift" },
|
||||
snapline: true,
|
||||
// 关键:完善选择配置,确保单击和框选生效
|
||||
selecting: {
|
||||
|
|
@ -318,20 +455,24 @@ export class X6Graph {
|
|||
});
|
||||
|
||||
// 注册“直接曲线虚线”自定义边
|
||||
Graph.registerEdge('direct-curved-dashed-edge', {
|
||||
inherit: 'edge',
|
||||
// 连接器:使用 smooth(平滑曲线,无额外拐点)
|
||||
connector: { name: 'smooth' },
|
||||
attrs: {
|
||||
line: {
|
||||
targetMarker: '', // 移除目标标记(可选)
|
||||
stroke: '#A2B1C3', // 线条颜色
|
||||
strokeWidth: 2, // 线条宽度
|
||||
strokeDasharray: '5, 5', // 虚线:线段5px + 间隔5px
|
||||
Graph.registerEdge(
|
||||
"direct-curved-dashed-edge",
|
||||
{
|
||||
inherit: "edge",
|
||||
// 连接器:使用 smooth(平滑曲线,无额外拐点)
|
||||
connector: { name: "smooth" },
|
||||
attrs: {
|
||||
line: {
|
||||
targetMarker: "", // 移除目标标记(可选)
|
||||
stroke: "#A2B1C3", // 线条颜色
|
||||
strokeWidth: 2, // 线条宽度
|
||||
strokeDasharray: "5, 5", // 虚线:线段5px + 间隔5px
|
||||
},
|
||||
},
|
||||
zIndex: 0, // 层级(避免遮挡节点)
|
||||
},
|
||||
zIndex: 0, // 层级(避免遮挡节点)
|
||||
}, true);
|
||||
true,
|
||||
);
|
||||
|
||||
// 绑定右键菜单事件
|
||||
this.bindRightClickEvent();
|
||||
|
|
@ -346,7 +487,7 @@ export class X6Graph {
|
|||
const graph = this.getGraph();
|
||||
|
||||
// 节点右键点击
|
||||
graph.on('node:contextmenu', (args) => {
|
||||
graph.on("node:contextmenu", (args) => {
|
||||
args.e.preventDefault(); // 阻止浏览器默认右键菜单
|
||||
const { cell, e } = args;
|
||||
const node = cell as Node;
|
||||
|
|
@ -360,25 +501,40 @@ export class X6Graph {
|
|||
return;
|
||||
}
|
||||
|
||||
if (nodeData.level === 'level2') {
|
||||
|
||||
} else if (nodeData.level === 'level3') {
|
||||
|
||||
} else if (nodeData.level === 'level4') {
|
||||
|
||||
if (nodeData.level === "level2") {
|
||||
const item2 = document.getElementById("ant-x6-menu");
|
||||
item2.style.display = "block";
|
||||
const item = document.getElementById("ant-x6-menu-1");
|
||||
item.style.display = "none";
|
||||
const item1 = document.getElementById("ant-x6-menu-2");
|
||||
item1.style.display = "none";
|
||||
} else if (nodeData.level === "level3") {
|
||||
const item = document.getElementById("ant-x6-menu-1");
|
||||
item.style.display = "block";
|
||||
const item1 = document.getElementById("ant-x6-menu-2");
|
||||
item1.style.display = "block";
|
||||
const item2 = document.getElementById("ant-x6-menu");
|
||||
item2.style.display = "none";
|
||||
} else if (nodeData.level === "level4") {
|
||||
const item = document.getElementById("ant-x6-menu-1");
|
||||
item.style.display = "none";
|
||||
const item1 = document.getElementById("ant-x6-menu-2");
|
||||
item1.style.display = "none";
|
||||
const item2 = document.getElementById("ant-x6-menu");
|
||||
item2.style.display = "none";
|
||||
}
|
||||
// 显示菜单(位置为点击坐标)
|
||||
this.showMenu(node.id, e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
// 画布空白处右键点击(隐藏菜单)
|
||||
graph.on('blank:contextmenu', (args) => {
|
||||
graph.on("blank:contextmenu", (args) => {
|
||||
args.e.preventDefault();
|
||||
this.hideMenu();
|
||||
});
|
||||
|
||||
// 点击其他区域隐藏菜单
|
||||
document.addEventListener('click', () => {
|
||||
document.addEventListener("click", () => {
|
||||
this.hideMenu();
|
||||
});
|
||||
}
|
||||
|
|
@ -388,7 +544,7 @@ export class X6Graph {
|
|||
*/
|
||||
private calculateTextWidth(text: string, fontSize = 14): number {
|
||||
// 创建临时元素计算文字宽度
|
||||
const tempSpan = document.createElement('span');
|
||||
const tempSpan = document.createElement("span");
|
||||
tempSpan.style.cssText = `
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
|
|
@ -430,9 +586,11 @@ export class X6Graph {
|
|||
private addNode(config: SimpleNodeConfig): Node {
|
||||
const graph = this.getGraph();
|
||||
// 计算根节点自适应宽度
|
||||
const rootWidth = this.calculateTextWidth(config.label, this.nodeStyle.label.fontSize) + this.nodePadding * 2;
|
||||
const rootWidth =
|
||||
this.calculateTextWidth(config.label, this.nodeStyle.label.fontSize) +
|
||||
this.nodePadding * 2;
|
||||
const node = graph.addNode({
|
||||
shape: 'rect',
|
||||
shape: "rect",
|
||||
id: config.id,
|
||||
x: config.x,
|
||||
y: config.y,
|
||||
|
|
@ -451,11 +609,11 @@ export class X6Graph {
|
|||
// 文字的样式(颜色、位置、字号等)
|
||||
label: {
|
||||
text: config.label,
|
||||
fill: '#ffffff', // 白色文字
|
||||
fill: "#ffffff", // 白色文字
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
textAnchor: 'middle', // 文字水平居中
|
||||
verticalAnchor: 'middle', // 文字垂直居中
|
||||
fontWeight: "bold",
|
||||
textAnchor: "middle", // 文字水平居中
|
||||
verticalAnchor: "middle", // 文字垂直居中
|
||||
},
|
||||
},
|
||||
data: {
|
||||
|
|
@ -473,7 +631,10 @@ export class X6Graph {
|
|||
/**
|
||||
* 添加子节点(按层级动态分配位置)
|
||||
*/
|
||||
addChildNode(parentId: string, config: ChildNodeConfig): { node: Node; edge: Edge } {
|
||||
addChildNode(
|
||||
parentId: string,
|
||||
config: ChildNodeConfig,
|
||||
): { node: Node; edge: Edge } {
|
||||
const graph = this.getGraph();
|
||||
|
||||
// 校验父节点
|
||||
|
|
@ -500,20 +661,20 @@ export class X6Graph {
|
|||
|
||||
// 创建连线
|
||||
const edge = graph.addEdge({
|
||||
shape: 'direct-curved-dashed-edge',
|
||||
shape: "direct-curved-dashed-edge",
|
||||
source: {
|
||||
cell: parentId,
|
||||
anchor: { name: 'right' },
|
||||
anchor: { name: "right" },
|
||||
},
|
||||
// 目标节点(子节点)的锚点:左侧
|
||||
target: {
|
||||
cell: childNode.id,
|
||||
anchor: { name: 'left' },
|
||||
anchor: { name: "left" },
|
||||
},
|
||||
style: {
|
||||
stroke: '#888',
|
||||
stroke: "#888",
|
||||
strokeWidth: 1.2,
|
||||
targetMarker: { name: 'block', size: 8 },
|
||||
targetMarker: { name: "block", size: 8 },
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -524,8 +685,8 @@ export class X6Graph {
|
|||
// 重排同层级节点
|
||||
this.rearrangeLevelNodes(config.level);
|
||||
// 新增:如果添加的是第三层级,同步重排第二层级
|
||||
if (config.level === 'level3') {
|
||||
this.rearrangeLevelNodes('level2');
|
||||
if (config.level === "level3") {
|
||||
this.rearrangeLevelNodes("level2");
|
||||
}
|
||||
return { node: childNode, edge: edge as Edge };
|
||||
}
|
||||
|
|
@ -539,28 +700,28 @@ export class X6Graph {
|
|||
const centerY = (containerHeight - this.nodeStyle.height) / 2; // 画布中心点
|
||||
|
||||
// 获取当前层级的所有节点(按标签排序)
|
||||
const levelNodes = graph.getNodes()
|
||||
.filter(node => node.getData().level === level)
|
||||
const levelNodes = graph
|
||||
.getNodes()
|
||||
.filter((node) => node.getData().level === level)
|
||||
.sort((a, b) => {
|
||||
const labelA = a.attr('label/text') || '';
|
||||
const labelB = b.attr('label/text') || '';
|
||||
const labelA = a.attr("label/text") || "";
|
||||
const labelB = b.attr("label/text") || "";
|
||||
return labelA.localeCompare(labelB, undefined, { numeric: true });
|
||||
});
|
||||
const totalNodes = levelNodes.length;
|
||||
|
||||
if (totalNodes === 0)
|
||||
return;
|
||||
if (totalNodes === 0) return;
|
||||
|
||||
// ======================== 第二层级:间距根据自身第三层级子节点数量动态调整 ========================
|
||||
if (level === 'level2') {
|
||||
// 1. 统计每个第二层级节点的第三层级子节点数量
|
||||
if (level === "level2") {
|
||||
// 1. 统计每个第二层级节点的第三层级子节点数量
|
||||
const level2ChildCount = new Map<string, number>();
|
||||
levelNodes.forEach((node) => {
|
||||
const childIds = this.getChildNodeIds(node.id);
|
||||
// 筛选出属于第三层级的子节点
|
||||
const level3ChildCount = childIds.filter((childId) => {
|
||||
const childNode = graph.getCellById(childId) as Node;
|
||||
return childNode?.getData().level === 'level3';
|
||||
return childNode?.getData().level === "level3";
|
||||
}).length;
|
||||
level2ChildCount.set(node.id, level3ChildCount);
|
||||
});
|
||||
|
|
@ -588,58 +749,73 @@ export class X6Graph {
|
|||
|
||||
// 3. 计算整体偏移量,让第二层级节点整体垂直居中
|
||||
const lastNodeY = nodePositions.get(levelNodes[totalNodes - 1].id) || 0;
|
||||
const totalHeight = lastNodeY - nodePositions.get(levelNodes[0].id) || 0 + this.nodeStyle.height;
|
||||
const offset = centerY - (nodePositions.get(levelNodes[0].id) || 0) - totalHeight / 2;
|
||||
const totalHeight =
|
||||
lastNodeY - nodePositions.get(levelNodes[0].id) ||
|
||||
0 + this.nodeStyle.height;
|
||||
const offset =
|
||||
centerY - (nodePositions.get(levelNodes[0].id) || 0) - totalHeight / 2;
|
||||
|
||||
// 4. 应用最终位置(加上偏移量,确保整体居中)
|
||||
levelNodes.forEach((node) => {
|
||||
const nodeY = (nodePositions.get(node.id) || 0) + offset;
|
||||
const nodeX = this.levelXMap.get('level2') || 300;
|
||||
const nodeX = this.levelXMap.get("level2") || 300;
|
||||
node.setPosition(nodeX, nodeY);
|
||||
});
|
||||
}
|
||||
|
||||
// ======================== 第三层级:跟随父节点垂直分布 ========================
|
||||
else if (level === 'level3') {
|
||||
else if (level === "level3") {
|
||||
const parentGroups = new Map<string, Node[]>();
|
||||
levelNodes.forEach((node) => {
|
||||
const parentEdge = graph.getEdges().find(edge => edge.getTargetCellId() === node.id);
|
||||
const parentEdge = graph
|
||||
.getEdges()
|
||||
.find((edge) => edge.getTargetCellId() === node.id);
|
||||
if (parentEdge) {
|
||||
const parentId = parentEdge.getSourceCellId();
|
||||
parentGroups.set(parentId, [...(parentGroups.get(parentId) || []), node]);
|
||||
parentGroups.set(parentId, [
|
||||
...(parentGroups.get(parentId) || []),
|
||||
node,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
parentGroups.forEach((children, parentId) => {
|
||||
const parentNode = graph.getCellById(parentId) as Node;
|
||||
if (!parentNode)
|
||||
return;
|
||||
if (!parentNode) return;
|
||||
|
||||
const parentY = parentNode.getPosition().y;
|
||||
const childCount = children.length;
|
||||
const verticalGap = 20; // 第三层级内部固定间距
|
||||
|
||||
const totalChildHeight = this.nodeStyle.height * childCount + verticalGap * (childCount - 1);
|
||||
const totalChildHeight =
|
||||
this.nodeStyle.height * childCount + verticalGap * (childCount - 1);
|
||||
const startOffset = -totalChildHeight / 2 + this.nodeStyle.height / 2;
|
||||
|
||||
children.forEach((child, index) => {
|
||||
const childY = parentY + startOffset + (this.nodeStyle.height + verticalGap) * index;
|
||||
const childX = this.levelXMap.get('level3') || 550;
|
||||
const childY =
|
||||
parentY +
|
||||
startOffset +
|
||||
(this.nodeStyle.height + verticalGap) * index;
|
||||
const childX = this.levelXMap.get("level3") || 550;
|
||||
child.setPosition(childX, childY);
|
||||
});
|
||||
});
|
||||
}
|
||||
else if (level === 'level4') {
|
||||
// 1. 按父节点(level3)分组(每个level3只对应1个level4)
|
||||
} else if (level === "level4") {
|
||||
// 1. 按父节点(level3)分组(每个level3只对应1个level4)
|
||||
const parentGroups = new Map<string, Node[]>();
|
||||
levelNodes.forEach((node) => {
|
||||
const parentEdge = graph.getEdges().find(edge => edge.getTargetCellId() === node.id);
|
||||
const parentEdge = graph
|
||||
.getEdges()
|
||||
.find((edge) => edge.getTargetCellId() === node.id);
|
||||
if (parentEdge) {
|
||||
const parentId = parentEdge.getSourceCellId();
|
||||
// 只保留level3的父节点(确保父节点层级正确)
|
||||
const parentNode = graph.getCellById(parentId) as Node;
|
||||
if (parentNode?.getData().level === 'level3') {
|
||||
parentGroups.set(parentId, [...(parentGroups.get(parentId) || []), node]);
|
||||
if (parentNode?.getData().level === "level3") {
|
||||
parentGroups.set(parentId, [
|
||||
...(parentGroups.get(parentId) || []),
|
||||
node,
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -647,12 +823,11 @@ export class X6Graph {
|
|||
// 2. 每个level3父节点的level4子节点:放在父节点右侧(固定间距)
|
||||
parentGroups.forEach((children, parentId) => {
|
||||
const parentNode = graph.getCellById(parentId) as Node;
|
||||
if (!parentNode)
|
||||
return;
|
||||
if (!parentNode) return;
|
||||
|
||||
// level4固定在level3父节点右侧,X坐标=level3X + 节点宽度 + 间距(20px)
|
||||
const parentPos = parentNode.getPosition();
|
||||
const level4X = this.levelXMap.get('level4') || 800;
|
||||
const level4X = this.levelXMap.get("level4") || 800;
|
||||
// Y坐标与父节点完全对齐(上下居中)
|
||||
const level4Y = parentPos.y;
|
||||
|
||||
|
|
@ -662,8 +837,8 @@ export class X6Graph {
|
|||
targetChild.setPosition(level4X, level4Y);
|
||||
// 多余节点直接删除(避免多个level4)
|
||||
if (children.length > 1) {
|
||||
children.slice(1).forEach(excessNode => excessNode.remove());
|
||||
this.resetLevelCount('level4'); // 重置数量统计
|
||||
children.slice(1).forEach((excessNode) => excessNode.remove());
|
||||
this.resetLevelCount("level4"); // 重置数量统计
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -676,8 +851,7 @@ export class X6Graph {
|
|||
removeNode(nodeId: string): void {
|
||||
const graph = this.getGraph();
|
||||
const cell = graph.getCellById(nodeId);
|
||||
if (!cell || !graph.isNode(cell))
|
||||
return;
|
||||
if (!cell || !graph.isNode(cell)) return;
|
||||
|
||||
const node = cell as Node;
|
||||
const nodeLevel = node.getData().level as NodeLevel;
|
||||
|
|
@ -702,33 +876,30 @@ export class X6Graph {
|
|||
* @param importData - 目标灌库数据(格式如 [{userName: "...", tables: [...]}])
|
||||
* @param rootConfig - 根节点配置(需提前确定根节点的 id 和 label,如 sourceOwner)
|
||||
*/
|
||||
importData(
|
||||
importData: ImportDataItem[],
|
||||
rootId,
|
||||
): void {
|
||||
importData(importData: ImportDataItem[], rootId): void {
|
||||
// 3. 遍历灌库数据,创建 level2→level3→level4 层级
|
||||
importData.forEach((item) => {
|
||||
// 3.1 创建 level2 节点(userName 作为 label,父节点是根节点)
|
||||
// 3.1 创建 level2 节点(userName 作为 label,父节点是根节点)
|
||||
const { node: level2Node } = this.addChildNode(rootId, {
|
||||
id: uuidv4(),
|
||||
label: item.userName,
|
||||
level: 'level2',
|
||||
level: "level2",
|
||||
});
|
||||
|
||||
// 3.2 遍历当前 level2 的 tables,创建 level3 和 level4 节点
|
||||
item.tables.forEach((table) => {
|
||||
// 3.2.1 创建 level3 节点(tableName 作为 label,父节点是当前 level2)
|
||||
// 3.2.1 创建 level3 节点(tableName 作为 label,父节点是当前 level2)
|
||||
const { node: level3Node } = this.addChildNode(level2Node.id, {
|
||||
id: uuidv4(),
|
||||
label: table.tableName,
|
||||
level: 'level3',
|
||||
level: "level3",
|
||||
});
|
||||
|
||||
// 3.2.2 创建 level4 节点(columnName 作为 label,父节点是当前 level3)
|
||||
this.addChildNode(level3Node.id, {
|
||||
id: uuidv4(),
|
||||
label: table.columnName,
|
||||
level: 'level4',
|
||||
level: "level4",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -740,55 +911,72 @@ export class X6Graph {
|
|||
* @param name - 传入的targetOwner字段值
|
||||
* @returns 符合格式的目标表格数组 / 错误信息
|
||||
*/
|
||||
getTargetTableData(taskId: string, name: string): { data?: TargetTableItem[]; error?: string } {
|
||||
getTargetTableData(
|
||||
taskId: string,
|
||||
name: string,
|
||||
): { data?: TargetTableItem[]; error?: string } {
|
||||
const graph = this.getGraph();
|
||||
const result: TargetTableItem[] = [];
|
||||
|
||||
// 1. 找到所有 level2 节点(sourceOwner 来源于 level2 的 label)
|
||||
const level2Nodes = graph.getNodes().filter(node => node.getData().level === 'level2');
|
||||
const level2Nodes = graph
|
||||
.getNodes()
|
||||
.filter((node) => node.getData().level === "level2");
|
||||
if (level2Nodes.length === 0) {
|
||||
return { error: '未找到任何 level2 节点,无法组装数据' };
|
||||
return { error: "未找到任何 level2 节点,无法组装数据" };
|
||||
}
|
||||
|
||||
// 2. 遍历每个 level2 节点,校验子级并组装数据
|
||||
for (const level2Node of level2Nodes) {
|
||||
const sourceOwner = level2Node.attr('label/text') || '';
|
||||
const sourceOwner = level2Node.attr("label/text") || "";
|
||||
if (!sourceOwner) {
|
||||
return { error: `存在未设置 label 的 level2 节点(ID: ${level2Node.id})` };
|
||||
return {
|
||||
error: `存在未设置 label 的 level2 节点(ID: ${level2Node.id})`,
|
||||
};
|
||||
}
|
||||
|
||||
// 2.1 校验 level2 是否有子级(必须包含 level3)
|
||||
const level2ChildIds = this.getChildNodeIds(level2Node.id);
|
||||
const level3Nodes = level2ChildIds
|
||||
.map(id => graph.getCellById(id) as Node)
|
||||
.filter(node => node?.getData().level === 'level3');
|
||||
.map((id) => graph.getCellById(id) as Node)
|
||||
.filter((node) => node?.getData().level === "level3");
|
||||
|
||||
if (level3Nodes.length === 0) {
|
||||
return { error: `level2 节点「${sourceOwner}」下无 level3 子节点,请完善配置` };
|
||||
return {
|
||||
error: `level2 节点「${sourceOwner}」下无 level3 子节点,请完善配置`,
|
||||
};
|
||||
}
|
||||
|
||||
// 2.2 遍历每个 level3 节点,校验子级并组装数据
|
||||
for (const level3Node of level3Nodes) {
|
||||
const tableName = level3Node.attr('label/text') || '';
|
||||
const tableName = level3Node.attr("label/text") || "";
|
||||
if (!tableName) {
|
||||
return { error: `存在未设置 label 的 level3 节点(父级:${sourceOwner})` };
|
||||
return {
|
||||
error: `存在未设置 label 的 level3 节点(父级:${sourceOwner})`,
|
||||
};
|
||||
}
|
||||
|
||||
const syncCount = level3Node.getData().syncCount||"";
|
||||
|
||||
// 2.2.1 校验 level3 是否有子级(必须包含 level4)
|
||||
const level3ChildIds = this.getChildNodeIds(level3Node.id);
|
||||
const level4Nodes = level3ChildIds
|
||||
.map(id => graph.getCellById(id) as Node)
|
||||
.filter(node => node?.getData().level === 'level4');
|
||||
.map((id) => graph.getCellById(id) as Node)
|
||||
.filter((node) => node?.getData().level === "level4");
|
||||
|
||||
if (level4Nodes.length === 0) {
|
||||
return { error: `level3 节点「${tableName}」下无 level4 子节点,请完善配置` };
|
||||
return {
|
||||
error: `level3 节点「${tableName}」下无 level4 子节点,请完善配置`,
|
||||
};
|
||||
}
|
||||
|
||||
// 2.2.2 遍历每个 level4 节点(每个level3仅取第一个有效level4,避免重复)
|
||||
const validLevel4Node = level4Nodes[0];
|
||||
const columnName = validLevel4Node.attr('label/text') || '';
|
||||
const columnName = validLevel4Node.attr("label/text") || "";
|
||||
if (!columnName) {
|
||||
return { error: `存在未设置 label 的 level4 节点(父级:${tableName})` };
|
||||
return {
|
||||
error: `存在未设置 label 的 level4 节点(父级:${tableName})`,
|
||||
};
|
||||
}
|
||||
|
||||
// 2.2.3 组装单条数据
|
||||
|
|
@ -798,6 +986,7 @@ export class X6Graph {
|
|||
targetOwner: name, // 传入的name字段作为targetOwner
|
||||
tableName,
|
||||
columnName,
|
||||
syncCount
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -821,7 +1010,10 @@ export class X6Graph {
|
|||
* 获取子节点ID列表
|
||||
*/
|
||||
private getChildNodeIds(parentId: string): string[] {
|
||||
return this.getGraph().getEdges().filter(edge => edge.getSourceCellId() === parentId).map(edge => edge.getTargetCellId());
|
||||
return this.getGraph()
|
||||
.getEdges()
|
||||
.filter((edge) => edge.getSourceCellId() === parentId)
|
||||
.map((edge) => edge.getTargetCellId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -836,9 +1028,9 @@ export class X6Graph {
|
|||
*/
|
||||
clear(): void {
|
||||
this.getGraph().clearCells();
|
||||
this.levelCountMap.set('level2', 0);
|
||||
this.levelCountMap.set('level3', 0);
|
||||
this.levelCountMap.set('level4', 0);
|
||||
this.levelCountMap.set("level2", 0);
|
||||
this.levelCountMap.set("level3", 0);
|
||||
this.levelCountMap.set("level4", 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -858,7 +1050,7 @@ export class X6Graph {
|
|||
*/
|
||||
private getGraph(): Graph {
|
||||
if (!this.graph) {
|
||||
throw new Error('请先调用 init() 初始化画布');
|
||||
throw new Error("请先调用 init() 初始化画布");
|
||||
}
|
||||
return this.graph;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
<script setup lang="ts">
|
||||
import { ElMessage, ElSelect } from 'element-plus';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { createTargetTables, getFileId, getMindMap, getTable, getUser, targetUser } from '@/utils/axios/sync/configTask/index';
|
||||
import { ElMessage, ElSelect } from "element-plus";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import {
|
||||
createTargetTables,
|
||||
getFileId,
|
||||
getMindMap,
|
||||
getTable,
|
||||
getUser,
|
||||
targetUser,
|
||||
} from "@/utils/axios/sync/configTask/index";
|
||||
|
||||
import { X6Graph } from './antvX6';
|
||||
import { X6Graph } from "./antvX6";
|
||||
|
||||
// 接收一个名为 data 的对象 props,类型为 DataProps
|
||||
const props = defineProps<{
|
||||
data: DataProps; // 这里的 data 就是父组件传递的整个对象
|
||||
}>();
|
||||
const emit = defineEmits(['cancel']);
|
||||
const emit = defineEmits(["cancel"]);
|
||||
// 定义一个接口描述对象结构
|
||||
interface DataProps {
|
||||
taskId: string;
|
||||
|
|
@ -35,17 +42,17 @@ const level3 = ref<InstanceType<typeof ElSelect> | null>(null);
|
|||
const level4 = ref<InstanceType<typeof ElSelect> | null>(null);
|
||||
|
||||
// 手动输入模式名
|
||||
const manualModeInput = ref('');
|
||||
const manualModeInput = ref("");
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
taskId: '',
|
||||
sourceId: '', // 默认选中“mySql”
|
||||
sourceName: '',
|
||||
taskId: "",
|
||||
sourceId: "", // 默认选中“mySql”
|
||||
sourceName: "",
|
||||
mode: [],
|
||||
dataValue: [],
|
||||
field: '',
|
||||
targetUserName: '',
|
||||
field: "",
|
||||
targetUserName: "",
|
||||
});
|
||||
|
||||
// 当前配置项Id
|
||||
|
|
@ -56,7 +63,14 @@ const prevModeValues = ref<string[]>([]);
|
|||
// 记录上一次的选中值(数据表列表)
|
||||
const prevDataValues = ref<string[]>([]);
|
||||
// 记录上一次的选中值(字段列表)
|
||||
const prevFieldValues = ref<string>('');
|
||||
const prevFieldValues = ref<string>("");
|
||||
|
||||
// 配置同步数量弹框
|
||||
const pztbslFlag = ref(false);
|
||||
const pztbslValue = ref("");
|
||||
|
||||
// 当前配置同步数量的节点id
|
||||
const nowPzTbslId = ref(null);
|
||||
|
||||
// 配置的数据表集合
|
||||
const configDataValues = ref<string[]>([]);
|
||||
|
|
@ -64,7 +78,7 @@ const configDataValues = ref<string[]>([]);
|
|||
// 初始化思维导图画布
|
||||
function initGraph() {
|
||||
// 初始化画布(容器ID为 graph-container)
|
||||
graph.value = new X6Graph('graphContainer').init({ grid: false, zoom: true });
|
||||
graph.value = new X6Graph("graphContainer").init({ grid: false, zoom: true });
|
||||
|
||||
// 1. 创建根节点
|
||||
const root = graph.value.addRootNode({
|
||||
|
|
@ -77,16 +91,15 @@ function initGraph() {
|
|||
|
||||
// 模式选择变化(正确的事件参数:仅新值)
|
||||
function handleModeChange(newValues: string[]) {
|
||||
if (!graph.value)
|
||||
return;
|
||||
if (!graph.value) return;
|
||||
|
||||
// 旧值从 prevModeValues 中获取
|
||||
const oldValues = prevModeValues.value;
|
||||
|
||||
// 1. 找出新增的选项(需要新增节点)
|
||||
const added = newValues.filter(v => !oldValues.includes(v));
|
||||
const added = newValues.filter((v) => !oldValues.includes(v));
|
||||
// 2. 找出移除的选项(需要删除节点)
|
||||
const removed = oldValues.filter(v => !newValues.includes(v));
|
||||
const removed = oldValues.filter((v) => !newValues.includes(v));
|
||||
|
||||
// 新增节点
|
||||
added.forEach((value) => {
|
||||
|
|
@ -95,7 +108,7 @@ function handleModeChange(newValues: string[]) {
|
|||
graph.value?.addChildNode(formData.sourceId, {
|
||||
id: value,
|
||||
label: item.label,
|
||||
level: 'level2',
|
||||
level: "level2",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -111,21 +124,20 @@ function handleModeChange(newValues: string[]) {
|
|||
|
||||
// 数据选择变化(模仿 handleModeChange 逻辑)
|
||||
function handleDataListChange(newValues: string[]) {
|
||||
if (!graph.value)
|
||||
return;
|
||||
if (!graph.value) return;
|
||||
|
||||
// 旧值从 prevDataValues 中获取
|
||||
const oldValues = prevDataValues.value;
|
||||
|
||||
// 1. 找出新增的选项(需要新增节点)
|
||||
const added = newValues.filter(v => !oldValues.includes(v));
|
||||
const added = newValues.filter((v) => !oldValues.includes(v));
|
||||
// 2. 找出移除的选项(需要删除节点)
|
||||
const removed = oldValues.filter(v => !newValues.includes(v));
|
||||
const removed = oldValues.filter((v) => !newValues.includes(v));
|
||||
|
||||
// 新增节点(假设添加到 level3 层级,父节点为某个 level2 节点,可按需修改)
|
||||
added.forEach((value) => {
|
||||
// 从数据列表中找到对应项的 label
|
||||
const item = dataList.value.find(item => item.value === value);
|
||||
const item = dataList.value.find((item) => item.value === value);
|
||||
if (item) {
|
||||
// 假设父节点 ID 为某个 level2 节点(例如:取第一个 level2 节点的 ID,或通过参数传入)
|
||||
// 实际场景中需根据业务确定父节点 ID
|
||||
|
|
@ -134,7 +146,7 @@ function handleDataListChange(newValues: string[]) {
|
|||
graph.value?.addChildNode(parentNodeId, {
|
||||
id: `${value}`, // 避免与其他节点 ID 冲突,加前缀区分
|
||||
label: item.label,
|
||||
level: 'level3', // 假设数据节点为 level3 层级
|
||||
level: "level3", // 假设数据节点为 level3 层级
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -152,11 +164,11 @@ function handleDataListChange(newValues: string[]) {
|
|||
}
|
||||
|
||||
// 数据选择变化(单选逻辑)
|
||||
function handleFieldListChange(newValue: string) { // 注意:参数从 string[] 改为 string
|
||||
console.log('当前选中值:', newValue);
|
||||
function handleFieldListChange(newValue: string) {
|
||||
// 注意:参数从 string[] 改为 string
|
||||
console.log("当前选中值:", newValue);
|
||||
|
||||
if (!graph.value)
|
||||
return;
|
||||
if (!graph.value) return;
|
||||
|
||||
// 1. 旧值从 prevFieldValues 中获取(同样存单个值,非数组)
|
||||
const oldValue = prevFieldValues.value;
|
||||
|
|
@ -175,7 +187,7 @@ function handleFieldListChange(newValue: string) { // 注意:参数从 string[
|
|||
// 第二步:再新增新值对应的节点(如果新值存在)
|
||||
if (newValue) {
|
||||
// 从数据列表找到新值对应的label
|
||||
const item = fieldList.value.find(item => item.value === newValue);
|
||||
const item = fieldList.value.find((item) => item.value === newValue);
|
||||
if (item) {
|
||||
// 动态获取父节点ID(这里逻辑保持你原有,可根据业务调整)
|
||||
const parentNodeId = configData.value[2].id;
|
||||
|
|
@ -183,7 +195,7 @@ function handleFieldListChange(newValue: string) { // 注意:参数从 string[
|
|||
graph.value.addChildNode(parentNodeId, {
|
||||
id: newValue,
|
||||
label: item.label,
|
||||
level: 'level4', // 单选节点层级为level4
|
||||
level: "level4", // 单选节点层级为level4
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -202,67 +214,130 @@ onMounted(() => {
|
|||
// 根据源数据库获取(模式/用户名)列表
|
||||
nextTick(() => {
|
||||
getTargetUserName(formData.taskId);
|
||||
getMindMap({
|
||||
taskId: formData.taskId,
|
||||
}, (res) => {
|
||||
initGraph();
|
||||
getModelList((value: any) => {
|
||||
console.log(value);
|
||||
if (res.result.userInfos.length) {
|
||||
console.log(res);
|
||||
graph.value?.importData(res.result.userInfos, formData.sourceId);
|
||||
res.result.userInfos.forEach((item: any) => {
|
||||
modeList.value.forEach((mode: any) => {
|
||||
if (mode.label === item.userName) {
|
||||
formData.mode.push(mode.value);
|
||||
getMindMap(
|
||||
{
|
||||
taskId: formData.taskId,
|
||||
},
|
||||
(res) => {
|
||||
initGraph();
|
||||
getModelList((value: any) => {
|
||||
console.log(value);
|
||||
if (res.result.userInfos.length) {
|
||||
graph.value?.importData(res.result.userInfos, formData.sourceId);
|
||||
|
||||
res.result.userInfos.forEach((item: any) => {
|
||||
const graphSl = graph.value?.getGraph();
|
||||
|
||||
// 获取全部节点实例
|
||||
const allNodes = graphSl.getNodes();
|
||||
console.log(allNodes, "allNodes");
|
||||
// 过滤:根据你存名称的字段,这里假设字段是 label;如果你的字段是 name,就改成 item.name
|
||||
const matchNodesData = allNodes
|
||||
.map((node) => {
|
||||
return {
|
||||
label: node.label,
|
||||
id: node.id,
|
||||
};
|
||||
})
|
||||
.filter((item1) => {
|
||||
return item1.label == item.userName;
|
||||
});
|
||||
const exists = modeList.value.find(
|
||||
(item1: any) => item1.label === item.userName
|
||||
);
|
||||
|
||||
if (!exists) {
|
||||
const newItem = {
|
||||
value: matchNodesData[0].id,
|
||||
label: item.userName,
|
||||
};
|
||||
modeList.value.push(newItem);
|
||||
formData.mode.push(newItem.value);
|
||||
} else {
|
||||
formData.mode.push(exists.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// mindMap.value = res.result;
|
||||
});
|
||||
// mindMap.value = res.result;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// 注册antvX6Event回调事件
|
||||
EventBusTool.getEventBus().subscribe('antvX6Event', (res: any) => {
|
||||
EventBusTool.getEventBus().subscribe("antvX6Event", (res: any) => {
|
||||
// 如果进入全屏,全屏icon没有高亮的话,需要高亮
|
||||
if (res.key === 'removeNode') {
|
||||
if (res.key === "removeNode") {
|
||||
console.log(res.id);
|
||||
|
||||
if (res.level === 'level2') {
|
||||
if (res.level === "level2") {
|
||||
// 3. 从下拉选中值中移除(关键:同步下拉选择器的选中状态)
|
||||
formData.mode = formData.mode.filter(v => v !== res.id);
|
||||
prevModeValues.value = prevModeValues.value.filter(v => v !== res.id);
|
||||
formData.mode = formData.mode.filter((v) => v !== res.id);
|
||||
prevModeValues.value = prevModeValues.value.filter((v) => v !== res.id);
|
||||
formData.dataValue = [];
|
||||
formData.field = '';
|
||||
}
|
||||
else if (res.level === 'level3') {
|
||||
formData.field = "";
|
||||
} else if (res.level === "level3") {
|
||||
// 3. 从下拉选中值中移除(关键:同步下拉选择器的选中状态)
|
||||
formData.dataValue = formData.dataValue.filter(v => v !== res.id);
|
||||
prevDataValues.value = prevDataValues.value.filter(v => v !== res.id);
|
||||
formData.field = '';
|
||||
}
|
||||
else if (res.level === 'level4') {
|
||||
formData.field = '';
|
||||
prevFieldValues.value = '';
|
||||
formData.dataValue = formData.dataValue.filter((v) => v !== res.id);
|
||||
prevDataValues.value = prevDataValues.value.filter((v) => v !== res.id);
|
||||
formData.field = "";
|
||||
} else if (res.level === "level4") {
|
||||
formData.field = "";
|
||||
prevFieldValues.value = "";
|
||||
}
|
||||
}
|
||||
if (res.key === 'configNode') {
|
||||
if (res.key === "configNode") {
|
||||
configData.value = res.allParents;
|
||||
configDataValues.value = [...formData.dataValue, formData.field];
|
||||
|
||||
console.log(
|
||||
configDataValues.value,
|
||||
configData.value,
|
||||
"2configData.value"
|
||||
);
|
||||
// 请求
|
||||
if (res.level === 'level2') {
|
||||
if (res.level === "level2") {
|
||||
getDataListById(res.label);
|
||||
}
|
||||
if (res.level === 'level3') {
|
||||
getFieldById(res.label);
|
||||
if (res.level === "level3") {
|
||||
if (res.isTbsl) {
|
||||
const graphSl = graph.value?.getGraph();
|
||||
const jdItem = graphSl.getCellById(res.id);
|
||||
nowPzTbslId.value = res.id;
|
||||
|
||||
const oldSyncCount = jdItem.getData().syncCount;
|
||||
pztbslFlag.value = true;
|
||||
if (oldSyncCount) {
|
||||
pztbslValue.value = oldSyncCount;
|
||||
} else {
|
||||
pztbslValue.value = "";
|
||||
}
|
||||
} else {
|
||||
getFieldById(res.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function paTbslMethod() {
|
||||
if (!pztbslValue.value) {
|
||||
ElMessage({
|
||||
type: "error",
|
||||
message: `请配置同步数量`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const graphSl = graph.value?.getGraph();
|
||||
const jdItem = graphSl.getCellById(nowPzTbslId.value);
|
||||
jdItem.setData({ ...jdItem.getData(), syncCount:pztbslValue.value })
|
||||
pztbslFlag.value = false;
|
||||
pztbslValue.value = "";
|
||||
nowPzTbslId.value = null;
|
||||
}
|
||||
|
||||
// 根据taskId获取目标用户名
|
||||
function getTargetUserName(taskId: any) {
|
||||
targetUser({ taskId }, (res) => {
|
||||
|
|
@ -299,6 +374,8 @@ function getDataListById(name: any) {
|
|||
};
|
||||
});
|
||||
|
||||
console.log(formData, "2141");
|
||||
|
||||
level3.value?.toggleMenu();
|
||||
formData.dataValue = [];
|
||||
});
|
||||
|
|
@ -307,22 +384,29 @@ function getDataListById(name: any) {
|
|||
function getFieldById(name: any) {
|
||||
console.log(configData.value[1].label, name);
|
||||
|
||||
getFileId({ sourceId: formData.sourceId, username: configData.value[1].label, tableName: name }, (res) => {
|
||||
fieldList.value = res.result.map((item: any) => {
|
||||
return {
|
||||
value: uuidv4(),
|
||||
label: item,
|
||||
};
|
||||
});
|
||||
level4.value?.toggleMenu();
|
||||
formData.field = '';
|
||||
});
|
||||
getFileId(
|
||||
{
|
||||
sourceId: formData.sourceId,
|
||||
username: configData.value[1].label,
|
||||
tableName: name,
|
||||
},
|
||||
(res) => {
|
||||
fieldList.value = res.result.map((item: any) => {
|
||||
return {
|
||||
value: uuidv4(),
|
||||
label: item,
|
||||
};
|
||||
});
|
||||
level4.value?.toggleMenu();
|
||||
formData.field = "";
|
||||
}
|
||||
);
|
||||
}
|
||||
// 手动添加模式
|
||||
function handleAddMode() {
|
||||
const inputVal = manualModeInput.value.trim();
|
||||
if (!inputVal) {
|
||||
ElMessage({ type: 'warning', message: '请输入模式名称' });
|
||||
ElMessage({ type: "warning", message: "请输入模式名称" });
|
||||
return;
|
||||
}
|
||||
// 检查是否已存在相同 label 的模式
|
||||
|
|
@ -332,97 +416,152 @@ function handleAddMode() {
|
|||
if (!formData.mode.includes(exists.value)) {
|
||||
formData.mode.push(exists.value);
|
||||
handleModeChange([...formData.mode]);
|
||||
} else {
|
||||
ElMessage({ type: "warning", message: "该模式已添加" });
|
||||
}
|
||||
else {
|
||||
ElMessage({ type: 'warning', message: '该模式已添加' });
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// 新建项并选中
|
||||
const newItem = { value: uuidv4(), label: inputVal };
|
||||
modeList.value.push(newItem);
|
||||
formData.mode.push(newItem.value);
|
||||
handleModeChange([...formData.mode]);
|
||||
}
|
||||
manualModeInput.value = '';
|
||||
manualModeInput.value = "";
|
||||
}
|
||||
|
||||
// 保存事件
|
||||
function handleSave() {
|
||||
if (!formData.targetUserName) {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
type: "error",
|
||||
message: `请选择目标用户名`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const data = graph.value?.getTargetTableData(formData.taskId, formData.targetUserName);
|
||||
const data = graph.value?.getTargetTableData(
|
||||
formData.taskId,
|
||||
formData.targetUserName
|
||||
);
|
||||
console.log(data);
|
||||
|
||||
if (data) {
|
||||
createTargetTables(data, (res) => {
|
||||
console.log(res);
|
||||
if (res === true) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: `配置数据表成功`,
|
||||
});
|
||||
emit('cancel');
|
||||
}
|
||||
}, 30 * 60 * 1000);
|
||||
}
|
||||
else {
|
||||
createTargetTables(
|
||||
data,
|
||||
(res) => {
|
||||
console.log(res);
|
||||
if (res === true) {
|
||||
ElMessage({
|
||||
type: "success",
|
||||
message: `配置数据表成功`,
|
||||
});
|
||||
emit("cancel");
|
||||
}
|
||||
},
|
||||
30 * 60 * 1000
|
||||
);
|
||||
} else {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
type: "error",
|
||||
message: `配置有误,请检查`,
|
||||
});
|
||||
}
|
||||
}
|
||||
// 取消事件
|
||||
function handleCancel() {
|
||||
emit('cancel');
|
||||
emit("cancel");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dataTable">
|
||||
<ElForm :inline="true" :model="formData" class="custom-form" style="margin-bottom: 20px;">
|
||||
<ElForm
|
||||
:inline="true"
|
||||
:model="formData"
|
||||
class="custom-form"
|
||||
style="margin-bottom: 20px"
|
||||
>
|
||||
<ElFormItem :label="t('database')">
|
||||
<ElInput v-model="formData.sourceName" disabled style="width: 180px" />
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="t('mode')">
|
||||
<!-- <ElFormItem :label="t('mode')">
|
||||
<ElSelect v-model="formData.mode" style="width: 220px" multiple :max-collapse-tags="0" collapse-tags
|
||||
collapse-tags-tooltip filterable :placeholder="t('pleaseSelect')" class="custom-select"
|
||||
allow-create default-first-option :reserve-keyword="false"
|
||||
@change="handleModeChange">
|
||||
<ElOption v-for="(item, index) in modeList" :key="index" :label="item.label" :value="item.value" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<!--<ElFormItem label="手动输入模式" v-show="!formData.mode || !formData.mode.length">
|
||||
</ElFormItem> -->
|
||||
<!-- <ElFormItem label="手动输入模式" v-show="!formData.mode || !formData.mode.length">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<ElInput v-model="manualModeInput" style="width: 160px" placeholder="输入模式名" @keyup.enter="handleAddMode" />
|
||||
<ElButton type="primary" style="margin-left: 4px; height: 34px; border-radius: 4px;" @click="handleAddMode">
|
||||
添加
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElFormItem>-->
|
||||
</ElFormItem> -->
|
||||
<ElFormItem :label="t('mode')">
|
||||
<ElInput
|
||||
v-model="manualModeInput"
|
||||
style="width: 160px"
|
||||
placeholder="输入模式名"
|
||||
@keyup.enter="handleAddMode"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="t('dataTable')">
|
||||
<ElSelect ref="level3" v-model="formData.dataValue" style="width: 220px" multiple :max-collapse-tags="0"
|
||||
collapse-tags collapse-tags-tooltip filterable :placeholder="t('pleaseSelect')" class="custom-select"
|
||||
@change="handleDataListChange">
|
||||
<ElOption v-for="(item, index) in dataList" :key="index" :label="item.label" :value="item.value" />
|
||||
<ElSelect
|
||||
ref="level3"
|
||||
v-model="formData.dataValue"
|
||||
style="width: 220px"
|
||||
multiple
|
||||
:max-collapse-tags="0"
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
filterable
|
||||
:placeholder="t('pleaseSelect')"
|
||||
class="custom-select"
|
||||
@change="handleDataListChange"
|
||||
>
|
||||
<ElOption
|
||||
v-for="(item, index) in dataList"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="t('field')">
|
||||
<ElSelect ref="level4" v-model="formData.field" style="width: 220px" filterable :placeholder="t('pleaseSelect')"
|
||||
class="custom-select" @change="handleFieldListChange">
|
||||
<ElOption v-for="(item, index) in fieldList" :key="index" :label="item.label" :value="item.value"
|
||||
:disabled="formData.dataValue.length === 0" />
|
||||
<ElSelect
|
||||
ref="level4"
|
||||
v-model="formData.field"
|
||||
style="width: 220px"
|
||||
filterable
|
||||
:placeholder="t('pleaseSelect')"
|
||||
class="custom-select"
|
||||
@change="handleFieldListChange"
|
||||
>
|
||||
<ElOption
|
||||
v-for="(item, index) in fieldList"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:disabled="formData.dataValue.length === 0"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="t('targetUserName')">
|
||||
<ElSelect v-model="formData.targetUserName" style="width: 220px" filterable :placeholder="t('pleaseSelect')"
|
||||
class="custom-select">
|
||||
<ElOption v-for="(item, index) in targetUserNameList" :key="index" :label="item.label" :value="item.value" />
|
||||
<ElSelect
|
||||
v-model="formData.targetUserName"
|
||||
style="width: 220px"
|
||||
filterable
|
||||
:placeholder="t('pleaseSelect')"
|
||||
class="custom-select"
|
||||
>
|
||||
<ElOption
|
||||
v-for="(item, index) in targetUserNameList"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
|
|
@ -432,13 +571,32 @@ function handleCancel() {
|
|||
</div>
|
||||
|
||||
<div class="btns">
|
||||
<div class="save" @click="handleSave">
|
||||
保存
|
||||
</div>
|
||||
<div class="close" @click="handleCancel">
|
||||
取消
|
||||
</div>
|
||||
<div class="save" @click="handleSave">保存</div>
|
||||
<div class="close" @click="handleCancel">取消</div>
|
||||
</div>
|
||||
|
||||
<Dialog
|
||||
v-model="pztbslFlag"
|
||||
title="配置同步数量"
|
||||
width="20vw"
|
||||
class="config-dialog"
|
||||
>
|
||||
<el-input
|
||||
v-model="pztbslValue"
|
||||
style="width: 240px"
|
||||
type="number"
|
||||
placeholder="请配置同步数量"
|
||||
/>
|
||||
<template #footer>
|
||||
<ElButton
|
||||
type="text"
|
||||
style="color: #fff; cursor: no-drop"
|
||||
@click="paTbslMethod"
|
||||
>
|
||||
配置
|
||||
</ElButton>
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -447,6 +605,14 @@ function handleCancel() {
|
|||
width: 100%;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
::v-deep .config-dialog {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.custom-form {
|
||||
margin-bottom: 20px;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import type { Edge, Node, NodeConfig } from '@antv/x6';
|
||||
import { Graph } from '@antv/x6';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import type { Edge, Node, NodeConfig } from "@antv/x6";
|
||||
import { Graph } from "@antv/x6";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
// 层级类型定义
|
||||
export type NodeLevel = 'level2' | 'level3' | 'level4';
|
||||
export type NodeLevel = "level2" | "level3" | "level4";
|
||||
|
||||
// 目标灌库数据的类型定义
|
||||
interface ImportDataItem {
|
||||
|
|
@ -50,14 +50,14 @@ export class X6Graph {
|
|||
private readonly nodeStyle = {
|
||||
width: 100,
|
||||
height: 40,
|
||||
fill: '#0a4a5c', // 深色背景(示例中的深青色)
|
||||
stroke: '#0a4a5c', // 边框与背景同色,视觉上隐藏边框
|
||||
fill: "#0a4a5c", // 深色背景(示例中的深青色)
|
||||
stroke: "#0a4a5c", // 边框与背景同色,视觉上隐藏边框
|
||||
strokeWidth: 2,
|
||||
radius: 4,
|
||||
label: {
|
||||
fill: '#93CDD5', // 白色文字
|
||||
fill: "#93CDD5", // 白色文字
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold', // 可选:文字加粗
|
||||
fontWeight: "bold", // 可选:文字加粗
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -69,16 +69,16 @@ export class X6Graph {
|
|||
|
||||
// 层级X坐标映射
|
||||
private readonly levelXMap = new Map<NodeLevel, number>([
|
||||
['level2', 300],
|
||||
['level3', 550],
|
||||
['level4', 800],
|
||||
["level2", 300],
|
||||
["level3", 550],
|
||||
["level4", 800],
|
||||
]);
|
||||
|
||||
// 层级节点数量记录
|
||||
private readonly levelCountMap = new Map<NodeLevel, number>([
|
||||
['level2', 0],
|
||||
['level3', 0],
|
||||
['level4', 0],
|
||||
["level2", 0],
|
||||
["level3", 0],
|
||||
["level4", 0],
|
||||
]);
|
||||
|
||||
constructor(containerId: string) {
|
||||
|
|
@ -87,7 +87,7 @@ export class X6Graph {
|
|||
}
|
||||
|
||||
private createMenuElement() {
|
||||
this.menuElement = document.createElement('div');
|
||||
this.menuElement = document.createElement("div");
|
||||
this.menuElement.style.cssText = `
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
|
|
@ -102,16 +102,16 @@ export class X6Graph {
|
|||
`;
|
||||
|
||||
// 1. 新增“配置”菜单项
|
||||
const configItem = document.createElement('div');
|
||||
const configItem = document.createElement("div");
|
||||
configItem.style.cssText = `
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
border-bottom: 1px solid #f0f0f0; /* 分隔线,区分两个选项 */
|
||||
`;
|
||||
configItem.textContent = '配置';
|
||||
configItem.addEventListener('click', () => {
|
||||
const nodeId = this.menuElement?.getAttribute('data-node-id');
|
||||
configItem.textContent = "配置";
|
||||
configItem.addEventListener("click", () => {
|
||||
const nodeId = this.menuElement?.getAttribute("data-node-id");
|
||||
if (nodeId) {
|
||||
const graph = this.getGraph();
|
||||
const targetNode = graph.getCellById(nodeId) as Node;
|
||||
|
|
@ -121,35 +121,41 @@ export class X6Graph {
|
|||
// 2. 高亮样式(保持原有)
|
||||
targetNode.attr({
|
||||
body: {
|
||||
stroke: 'rgb(12,129,123)',
|
||||
stroke: "rgb(12,129,123)",
|
||||
strokeWidth: 2,
|
||||
strokeDasharray: '5, 5',
|
||||
strokeDasharray: "5, 5",
|
||||
},
|
||||
});
|
||||
|
||||
// ======================== 新增:获取所有父级信息 ========================
|
||||
// 1. 定义追溯父级的方法
|
||||
const getAllParents = (targetNodeId: string): Array<{ id: string; label: string; level?: NodeLevel }> => {
|
||||
const parents: Array<{ id: string; label: string; level?: NodeLevel }> = [];
|
||||
const getAllParents = (
|
||||
targetNodeId: string,
|
||||
): Array<{ id: string; label: string; level?: NodeLevel }> => {
|
||||
const parents: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
level?: NodeLevel;
|
||||
}> = [];
|
||||
let currentNodeId = targetNodeId;
|
||||
|
||||
// 循环追溯:直到找不到父节点(根节点)
|
||||
while (true) {
|
||||
// 找到指向当前节点的连线(来源即父节点)
|
||||
const parentEdge = graph.getEdges().find(edge => edge.getTargetCellId() === currentNodeId);
|
||||
if (!parentEdge)
|
||||
break; // 无父节点,终止循环
|
||||
const parentEdge = graph
|
||||
.getEdges()
|
||||
.find((edge) => edge.getTargetCellId() === currentNodeId);
|
||||
if (!parentEdge) break; // 无父节点,终止循环
|
||||
|
||||
// 获取父节点ID和父节点实例
|
||||
const parentNodeId = parentEdge.getSourceCellId();
|
||||
const parentNode = graph.getCellById(parentNodeId) as Node;
|
||||
if (!parentNode)
|
||||
break;
|
||||
if (!parentNode) break;
|
||||
|
||||
// 收集父节点信息(ID、标签、层级)
|
||||
parents.push({
|
||||
id: parentNodeId,
|
||||
label: parentNode.attr('label/text') || '', // 节点显示的文字
|
||||
label: parentNode.attr("label/text") || "", // 节点显示的文字
|
||||
level: parentNode.getData().level, // 父节点的层级
|
||||
});
|
||||
|
||||
|
|
@ -168,19 +174,19 @@ export class X6Graph {
|
|||
// 关键:添加当前节点信息(含label),拼接成“父级+当前节点”的完整链
|
||||
const currentNodeInfo = {
|
||||
id: nodeId,
|
||||
label: targetNode.attr('label/text') || '', // 从当前节点获取label
|
||||
label: targetNode.attr("label/text") || "", // 从当前节点获取label
|
||||
level: nodeLevel,
|
||||
};
|
||||
allParentsOrdered = [...allParentsOrdered, currentNodeInfo];
|
||||
|
||||
console.log('目标节点(父级+自身)完整链:', allParentsOrdered);
|
||||
console.log("目标节点(父级+自身)完整链:", allParentsOrdered);
|
||||
// ========================================================================
|
||||
|
||||
// 3. 事件总线传递:携带完整链信息
|
||||
EventBusTool.getEventBus().publish('antvX6Event', {
|
||||
key: 'configNode',
|
||||
EventBusTool.getEventBus().publish("antvX6Event", {
|
||||
key: "configNode",
|
||||
id: nodeId,
|
||||
label: targetNode.attr('label/text') || '',
|
||||
label: targetNode.attr("label/text") || "",
|
||||
level: nodeLevel,
|
||||
allParents: allParentsOrdered, // 包含父级+当前节点(均有label)
|
||||
});
|
||||
|
|
@ -188,23 +194,23 @@ export class X6Graph {
|
|||
this.hideMenu();
|
||||
});
|
||||
// 配置项悬停效果
|
||||
configItem.addEventListener('mouseenter', () => {
|
||||
configItem.style.background = '#f0f0f0';
|
||||
configItem.addEventListener("mouseenter", () => {
|
||||
configItem.style.background = "#f0f0f0";
|
||||
});
|
||||
configItem.addEventListener('mouseleave', () => {
|
||||
configItem.style.background = 'white';
|
||||
configItem.addEventListener("mouseleave", () => {
|
||||
configItem.style.background = "white";
|
||||
});
|
||||
|
||||
// 2. 原有“删除”菜单项(保持不变,调整顺序在配置项下方)
|
||||
const deleteItem = document.createElement('div');
|
||||
const deleteItem = document.createElement("div");
|
||||
deleteItem.style.cssText = `
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
`;
|
||||
deleteItem.textContent = '删除';
|
||||
deleteItem.addEventListener('click', () => {
|
||||
const nodeId = this.menuElement?.getAttribute('data-node-id');
|
||||
deleteItem.textContent = "删除";
|
||||
deleteItem.addEventListener("click", () => {
|
||||
const nodeId = this.menuElement?.getAttribute("data-node-id");
|
||||
if (nodeId) {
|
||||
// 获取图形实例和目标节点(和配置项逻辑一致)
|
||||
const graph = this.getGraph();
|
||||
|
|
@ -212,8 +218,8 @@ export class X6Graph {
|
|||
// 从节点数据中提取level
|
||||
const nodeLevel = targetNode.getData().level;
|
||||
this.removeNode(nodeId);
|
||||
EventBusTool.getEventBus().publish('antvX6Event', {
|
||||
key: 'removeNode',
|
||||
EventBusTool.getEventBus().publish("antvX6Event", {
|
||||
key: "removeNode",
|
||||
id: nodeId,
|
||||
level: nodeLevel, // 新增:传递level信息
|
||||
});
|
||||
|
|
@ -221,11 +227,11 @@ export class X6Graph {
|
|||
this.hideMenu();
|
||||
});
|
||||
// 删除项悬停效果
|
||||
deleteItem.addEventListener('mouseenter', () => {
|
||||
deleteItem.style.background = '#f0f0f0';
|
||||
deleteItem.addEventListener("mouseenter", () => {
|
||||
deleteItem.style.background = "#f0f0f0";
|
||||
});
|
||||
deleteItem.addEventListener('mouseleave', () => {
|
||||
deleteItem.style.background = 'white';
|
||||
deleteItem.addEventListener("mouseleave", () => {
|
||||
deleteItem.style.background = "white";
|
||||
});
|
||||
|
||||
// 3. 将两个菜单项添加到菜单容器(配置项在上,删除项在下)
|
||||
|
|
@ -238,12 +244,11 @@ export class X6Graph {
|
|||
* 显示右键菜单
|
||||
*/
|
||||
private showMenu(nodeId: string, x: number, y: number) {
|
||||
if (!this.menuElement)
|
||||
return;
|
||||
this.menuElement.setAttribute('data-node-id', nodeId);
|
||||
if (!this.menuElement) return;
|
||||
this.menuElement.setAttribute("data-node-id", nodeId);
|
||||
this.menuElement.style.left = `${x}px`;
|
||||
this.menuElement.style.top = `${y}px`;
|
||||
this.menuElement.style.display = 'block';
|
||||
this.menuElement.style.display = "block";
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -251,7 +256,7 @@ export class X6Graph {
|
|||
*/
|
||||
private hideMenu() {
|
||||
if (this.menuElement) {
|
||||
this.menuElement.style.display = 'none';
|
||||
this.menuElement.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -269,14 +274,14 @@ export class X6Graph {
|
|||
container,
|
||||
width: container.clientWidth,
|
||||
height: container.clientHeight,
|
||||
grid: options.grid ?? { size: 10, visible: false, type: 'dot' },
|
||||
grid: options.grid ?? { size: 10, visible: false, type: "dot" },
|
||||
mousewheel: options.zoom ?? {
|
||||
enabled: true,
|
||||
modifiers: 'ctrl',
|
||||
modifiers: "ctrl",
|
||||
minScale: 0.5,
|
||||
maxScale: 2,
|
||||
},
|
||||
panning: { enabled: true, modifier: 'shift' },
|
||||
panning: { enabled: true, modifier: "shift" },
|
||||
snapline: true,
|
||||
// 关键:完善选择配置,确保单击和框选生效
|
||||
selecting: {
|
||||
|
|
@ -285,20 +290,24 @@ export class X6Graph {
|
|||
});
|
||||
|
||||
// 注册“直接曲线虚线”自定义边
|
||||
Graph.registerEdge('direct-curved-dashed-edge', {
|
||||
inherit: 'edge',
|
||||
// 连接器:使用 smooth(平滑曲线,无额外拐点)
|
||||
connector: { name: 'smooth' },
|
||||
attrs: {
|
||||
line: {
|
||||
targetMarker: '', // 移除目标标记(可选)
|
||||
stroke: '#A2B1C3', // 线条颜色
|
||||
strokeWidth: 2, // 线条宽度
|
||||
strokeDasharray: '5, 5', // 虚线:线段5px + 间隔5px
|
||||
Graph.registerEdge(
|
||||
"direct-curved-dashed-edge",
|
||||
{
|
||||
inherit: "edge",
|
||||
// 连接器:使用 smooth(平滑曲线,无额外拐点)
|
||||
connector: { name: "smooth" },
|
||||
attrs: {
|
||||
line: {
|
||||
targetMarker: "", // 移除目标标记(可选)
|
||||
stroke: "#A2B1C3", // 线条颜色
|
||||
strokeWidth: 2, // 线条宽度
|
||||
strokeDasharray: "5, 5", // 虚线:线段5px + 间隔5px
|
||||
},
|
||||
},
|
||||
zIndex: 0, // 层级(避免遮挡节点)
|
||||
},
|
||||
zIndex: 0, // 层级(避免遮挡节点)
|
||||
}, true);
|
||||
true,
|
||||
);
|
||||
|
||||
// 绑定右键菜单事件
|
||||
this.bindRightClickEvent();
|
||||
|
|
@ -313,7 +322,7 @@ export class X6Graph {
|
|||
const graph = this.getGraph();
|
||||
|
||||
// 节点右键点击
|
||||
graph.on('node:contextmenu', (args) => {
|
||||
graph.on("node:contextmenu", (args) => {
|
||||
args.e.preventDefault(); // 阻止浏览器默认右键菜单
|
||||
const { cell, e } = args;
|
||||
const node = cell as Node;
|
||||
|
|
@ -331,13 +340,13 @@ export class X6Graph {
|
|||
});
|
||||
|
||||
// 画布空白处右键点击(隐藏菜单)
|
||||
graph.on('blank:contextmenu', (args) => {
|
||||
graph.on("blank:contextmenu", (args) => {
|
||||
args.e.preventDefault();
|
||||
this.hideMenu();
|
||||
});
|
||||
|
||||
// 点击其他区域隐藏菜单
|
||||
document.addEventListener('click', () => {
|
||||
document.addEventListener("click", () => {
|
||||
this.hideMenu();
|
||||
});
|
||||
}
|
||||
|
|
@ -368,7 +377,7 @@ export class X6Graph {
|
|||
private addNode(config: SimpleNodeConfig): Node {
|
||||
const graph = this.getGraph();
|
||||
const node = graph.addNode({
|
||||
shape: 'rect',
|
||||
shape: "rect",
|
||||
id: config.id,
|
||||
x: config.x,
|
||||
y: config.y,
|
||||
|
|
@ -387,11 +396,11 @@ export class X6Graph {
|
|||
// 文字的样式(颜色、位置、字号等)
|
||||
label: {
|
||||
text: config.label,
|
||||
fill: '#ffffff', // 白色文字
|
||||
fill: "#ffffff", // 白色文字
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
textAnchor: 'middle', // 文字水平居中
|
||||
verticalAnchor: 'middle', // 文字垂直居中
|
||||
fontWeight: "bold",
|
||||
textAnchor: "middle", // 文字水平居中
|
||||
verticalAnchor: "middle", // 文字垂直居中
|
||||
},
|
||||
},
|
||||
data: {
|
||||
|
|
@ -409,7 +418,10 @@ export class X6Graph {
|
|||
/**
|
||||
* 添加子节点(按层级动态分配位置)
|
||||
*/
|
||||
addChildNode(parentId: string, config: ChildNodeConfig): { node: Node; edge: Edge } {
|
||||
addChildNode(
|
||||
parentId: string,
|
||||
config: ChildNodeConfig,
|
||||
): { node: Node; edge: Edge } {
|
||||
const graph = this.getGraph();
|
||||
|
||||
// 校验父节点
|
||||
|
|
@ -436,20 +448,20 @@ export class X6Graph {
|
|||
|
||||
// 创建连线
|
||||
const edge = graph.addEdge({
|
||||
shape: 'direct-curved-dashed-edge',
|
||||
shape: "direct-curved-dashed-edge",
|
||||
source: {
|
||||
cell: parentId,
|
||||
anchor: { name: 'right' },
|
||||
anchor: { name: "right" },
|
||||
},
|
||||
// 目标节点(子节点)的锚点:左侧
|
||||
target: {
|
||||
cell: childNode.id,
|
||||
anchor: { name: 'left' },
|
||||
anchor: { name: "left" },
|
||||
},
|
||||
style: {
|
||||
stroke: '#888',
|
||||
stroke: "#888",
|
||||
strokeWidth: 1.2,
|
||||
targetMarker: { name: 'block', size: 8 },
|
||||
targetMarker: { name: "block", size: 8 },
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -460,8 +472,8 @@ export class X6Graph {
|
|||
// 重排同层级节点
|
||||
this.rearrangeLevelNodes(config.level);
|
||||
// 新增:如果添加的是第三层级,同步重排第二层级
|
||||
if (config.level === 'level3') {
|
||||
this.rearrangeLevelNodes('level2');
|
||||
if (config.level === "level3") {
|
||||
this.rearrangeLevelNodes("level2");
|
||||
}
|
||||
return { node: childNode, edge: edge as Edge };
|
||||
}
|
||||
|
|
@ -475,28 +487,28 @@ export class X6Graph {
|
|||
const centerY = (containerHeight - this.nodeStyle.height) / 2; // 画布中心点
|
||||
|
||||
// 获取当前层级的所有节点(按标签排序)
|
||||
const levelNodes = graph.getNodes()
|
||||
.filter(node => node.getData().level === level)
|
||||
const levelNodes = graph
|
||||
.getNodes()
|
||||
.filter((node) => node.getData().level === level)
|
||||
.sort((a, b) => {
|
||||
const labelA = a.attr('label/text') || '';
|
||||
const labelB = b.attr('label/text') || '';
|
||||
const labelA = a.attr("label/text") || "";
|
||||
const labelB = b.attr("label/text") || "";
|
||||
return labelA.localeCompare(labelB, undefined, { numeric: true });
|
||||
});
|
||||
const totalNodes = levelNodes.length;
|
||||
|
||||
if (totalNodes === 0)
|
||||
return;
|
||||
if (totalNodes === 0) return;
|
||||
|
||||
// ======================== 第二层级:间距根据自身第三层级子节点数量动态调整 ========================
|
||||
if (level === 'level2') {
|
||||
// 1. 统计每个第二层级节点的第三层级子节点数量
|
||||
if (level === "level2") {
|
||||
// 1. 统计每个第二层级节点的第三层级子节点数量
|
||||
const level2ChildCount = new Map<string, number>();
|
||||
levelNodes.forEach((node) => {
|
||||
const childIds = this.getChildNodeIds(node.id);
|
||||
// 筛选出属于第三层级的子节点
|
||||
const level3ChildCount = childIds.filter((childId) => {
|
||||
const childNode = graph.getCellById(childId) as Node;
|
||||
return childNode?.getData().level === 'level3';
|
||||
return childNode?.getData().level === "level3";
|
||||
}).length;
|
||||
level2ChildCount.set(node.id, level3ChildCount);
|
||||
});
|
||||
|
|
@ -524,58 +536,73 @@ export class X6Graph {
|
|||
|
||||
// 3. 计算整体偏移量,让第二层级节点整体垂直居中
|
||||
const lastNodeY = nodePositions.get(levelNodes[totalNodes - 1].id) || 0;
|
||||
const totalHeight = lastNodeY - nodePositions.get(levelNodes[0].id) || 0 + this.nodeStyle.height;
|
||||
const offset = centerY - (nodePositions.get(levelNodes[0].id) || 0) - totalHeight / 2;
|
||||
const totalHeight =
|
||||
lastNodeY - nodePositions.get(levelNodes[0].id) ||
|
||||
0 + this.nodeStyle.height;
|
||||
const offset =
|
||||
centerY - (nodePositions.get(levelNodes[0].id) || 0) - totalHeight / 2;
|
||||
|
||||
// 4. 应用最终位置(加上偏移量,确保整体居中)
|
||||
levelNodes.forEach((node) => {
|
||||
const nodeY = (nodePositions.get(node.id) || 0) + offset;
|
||||
const nodeX = this.levelXMap.get('level2') || 300;
|
||||
const nodeX = this.levelXMap.get("level2") || 300;
|
||||
node.setPosition(nodeX, nodeY);
|
||||
});
|
||||
}
|
||||
|
||||
// ======================== 第三层级:跟随父节点垂直分布 ========================
|
||||
else if (level === 'level3') {
|
||||
else if (level === "level3") {
|
||||
const parentGroups = new Map<string, Node[]>();
|
||||
levelNodes.forEach((node) => {
|
||||
const parentEdge = graph.getEdges().find(edge => edge.getTargetCellId() === node.id);
|
||||
const parentEdge = graph
|
||||
.getEdges()
|
||||
.find((edge) => edge.getTargetCellId() === node.id);
|
||||
if (parentEdge) {
|
||||
const parentId = parentEdge.getSourceCellId();
|
||||
parentGroups.set(parentId, [...(parentGroups.get(parentId) || []), node]);
|
||||
parentGroups.set(parentId, [
|
||||
...(parentGroups.get(parentId) || []),
|
||||
node,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
parentGroups.forEach((children, parentId) => {
|
||||
const parentNode = graph.getCellById(parentId) as Node;
|
||||
if (!parentNode)
|
||||
return;
|
||||
if (!parentNode) return;
|
||||
|
||||
const parentY = parentNode.getPosition().y;
|
||||
const childCount = children.length;
|
||||
const verticalGap = 20; // 第三层级内部固定间距
|
||||
|
||||
const totalChildHeight = this.nodeStyle.height * childCount + verticalGap * (childCount - 1);
|
||||
const totalChildHeight =
|
||||
this.nodeStyle.height * childCount + verticalGap * (childCount - 1);
|
||||
const startOffset = -totalChildHeight / 2 + this.nodeStyle.height / 2;
|
||||
|
||||
children.forEach((child, index) => {
|
||||
const childY = parentY + startOffset + (this.nodeStyle.height + verticalGap) * index;
|
||||
const childX = this.levelXMap.get('level3') || 550;
|
||||
const childY =
|
||||
parentY +
|
||||
startOffset +
|
||||
(this.nodeStyle.height + verticalGap) * index;
|
||||
const childX = this.levelXMap.get("level3") || 550;
|
||||
child.setPosition(childX, childY);
|
||||
});
|
||||
});
|
||||
}
|
||||
else if (level === 'level4') {
|
||||
// 1. 按父节点(level3)分组(每个level3只对应1个level4)
|
||||
} else if (level === "level4") {
|
||||
// 1. 按父节点(level3)分组(每个level3只对应1个level4)
|
||||
const parentGroups = new Map<string, Node[]>();
|
||||
levelNodes.forEach((node) => {
|
||||
const parentEdge = graph.getEdges().find(edge => edge.getTargetCellId() === node.id);
|
||||
const parentEdge = graph
|
||||
.getEdges()
|
||||
.find((edge) => edge.getTargetCellId() === node.id);
|
||||
if (parentEdge) {
|
||||
const parentId = parentEdge.getSourceCellId();
|
||||
// 只保留level3的父节点(确保父节点层级正确)
|
||||
const parentNode = graph.getCellById(parentId) as Node;
|
||||
if (parentNode?.getData().level === 'level3') {
|
||||
parentGroups.set(parentId, [...(parentGroups.get(parentId) || []), node]);
|
||||
if (parentNode?.getData().level === "level3") {
|
||||
parentGroups.set(parentId, [
|
||||
...(parentGroups.get(parentId) || []),
|
||||
node,
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -583,12 +610,11 @@ export class X6Graph {
|
|||
// 2. 每个level3父节点的level4子节点:放在父节点右侧(固定间距)
|
||||
parentGroups.forEach((children, parentId) => {
|
||||
const parentNode = graph.getCellById(parentId) as Node;
|
||||
if (!parentNode)
|
||||
return;
|
||||
if (!parentNode) return;
|
||||
|
||||
// level4固定在level3父节点右侧,X坐标=level3X + 节点宽度 + 间距(20px)
|
||||
const parentPos = parentNode.getPosition();
|
||||
const level4X = this.levelXMap.get('level4') || 800;
|
||||
const level4X = this.levelXMap.get("level4") || 800;
|
||||
// Y坐标与父节点完全对齐(上下居中)
|
||||
const level4Y = parentPos.y;
|
||||
|
||||
|
|
@ -598,8 +624,8 @@ export class X6Graph {
|
|||
targetChild.setPosition(level4X, level4Y);
|
||||
// 多余节点直接删除(避免多个level4)
|
||||
if (children.length > 1) {
|
||||
children.slice(1).forEach(excessNode => excessNode.remove());
|
||||
this.resetLevelCount('level4'); // 重置数量统计
|
||||
children.slice(1).forEach((excessNode) => excessNode.remove());
|
||||
this.resetLevelCount("level4"); // 重置数量统计
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -612,8 +638,7 @@ export class X6Graph {
|
|||
removeNode(nodeId: string): void {
|
||||
const graph = this.getGraph();
|
||||
const cell = graph.getCellById(nodeId);
|
||||
if (!cell || !graph.isNode(cell))
|
||||
return;
|
||||
if (!cell || !graph.isNode(cell)) return;
|
||||
|
||||
const node = cell as Node;
|
||||
const nodeLevel = node.getData().level as NodeLevel;
|
||||
|
|
@ -638,33 +663,30 @@ export class X6Graph {
|
|||
* @param importData - 目标灌库数据(格式如 [{userName: "...", tables: [...]}])
|
||||
* @param rootConfig - 根节点配置(需提前确定根节点的 id 和 label,如 sourceOwner)
|
||||
*/
|
||||
importData(
|
||||
importData: ImportDataItem[],
|
||||
rootId,
|
||||
): void {
|
||||
importData(importData: ImportDataItem[], rootId): void {
|
||||
// 3. 遍历灌库数据,创建 level2→level3→level4 层级
|
||||
importData.forEach((item) => {
|
||||
// 3.1 创建 level2 节点(userName 作为 label,父节点是根节点)
|
||||
// 3.1 创建 level2 节点(userName 作为 label,父节点是根节点)
|
||||
const { node: level2Node } = this.addChildNode(rootId, {
|
||||
id: uuidv4(),
|
||||
label: item.userName,
|
||||
level: 'level2',
|
||||
level: "level2",
|
||||
});
|
||||
|
||||
// 3.2 遍历当前 level2 的 tables,创建 level3 和 level4 节点
|
||||
item.tables.forEach((table) => {
|
||||
// 3.2.1 创建 level3 节点(tableName 作为 label,父节点是当前 level2)
|
||||
// 3.2.1 创建 level3 节点(tableName 作为 label,父节点是当前 level2)
|
||||
const { node: level3Node } = this.addChildNode(level2Node.id, {
|
||||
id: uuidv4(),
|
||||
label: table.tableName,
|
||||
level: 'level3',
|
||||
level: "level3",
|
||||
});
|
||||
|
||||
// 3.2.2 创建 level4 节点(columnName 作为 label,父节点是当前 level3)
|
||||
this.addChildNode(level3Node.id, {
|
||||
id: uuidv4(),
|
||||
label: table.columnName,
|
||||
level: 'level4',
|
||||
level: "level4",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -676,55 +698,70 @@ export class X6Graph {
|
|||
* @param name - 传入的targetOwner字段值
|
||||
* @returns 符合格式的目标表格数组 / 错误信息
|
||||
*/
|
||||
getTargetTableData(taskId: string, name: string): { data?: TargetTableItem[]; error?: string } {
|
||||
getTargetTableData(
|
||||
taskId: string,
|
||||
name: string,
|
||||
): { data?: TargetTableItem[]; error?: string } {
|
||||
const graph = this.getGraph();
|
||||
const result: TargetTableItem[] = [];
|
||||
|
||||
// 1. 找到所有 level2 节点(sourceOwner 来源于 level2 的 label)
|
||||
const level2Nodes = graph.getNodes().filter(node => node.getData().level === 'level2');
|
||||
const level2Nodes = graph
|
||||
.getNodes()
|
||||
.filter((node) => node.getData().level === "level2");
|
||||
if (level2Nodes.length === 0) {
|
||||
return { error: '未找到任何 level2 节点,无法组装数据' };
|
||||
return { error: "未找到任何 level2 节点,无法组装数据" };
|
||||
}
|
||||
|
||||
// 2. 遍历每个 level2 节点,校验子级并组装数据
|
||||
for (const level2Node of level2Nodes) {
|
||||
const sourceOwner = level2Node.attr('label/text') || '';
|
||||
const sourceOwner = level2Node.attr("label/text") || "";
|
||||
if (!sourceOwner) {
|
||||
return { error: `存在未设置 label 的 level2 节点(ID: ${level2Node.id})` };
|
||||
return {
|
||||
error: `存在未设置 label 的 level2 节点(ID: ${level2Node.id})`,
|
||||
};
|
||||
}
|
||||
|
||||
// 2.1 校验 level2 是否有子级(必须包含 level3)
|
||||
const level2ChildIds = this.getChildNodeIds(level2Node.id);
|
||||
const level3Nodes = level2ChildIds
|
||||
.map(id => graph.getCellById(id) as Node)
|
||||
.filter(node => node?.getData().level === 'level3');
|
||||
.map((id) => graph.getCellById(id) as Node)
|
||||
.filter((node) => node?.getData().level === "level3");
|
||||
|
||||
if (level3Nodes.length === 0) {
|
||||
return { error: `level2 节点「${sourceOwner}」下无 level3 子节点,请完善配置` };
|
||||
return {
|
||||
error: `level2 节点「${sourceOwner}」下无 level3 子节点,请完善配置`,
|
||||
};
|
||||
}
|
||||
|
||||
// 2.2 遍历每个 level3 节点,校验子级并组装数据
|
||||
for (const level3Node of level3Nodes) {
|
||||
const tableName = level3Node.attr('label/text') || '';
|
||||
const tableName = level3Node.attr("label/text") || "";
|
||||
if (!tableName) {
|
||||
return { error: `存在未设置 label 的 level3 节点(父级:${sourceOwner})` };
|
||||
return {
|
||||
error: `存在未设置 label 的 level3 节点(父级:${sourceOwner})`,
|
||||
};
|
||||
}
|
||||
|
||||
// 2.2.1 校验 level3 是否有子级(必须包含 level4)
|
||||
const level3ChildIds = this.getChildNodeIds(level3Node.id);
|
||||
const level4Nodes = level3ChildIds
|
||||
.map(id => graph.getCellById(id) as Node)
|
||||
.filter(node => node?.getData().level === 'level4');
|
||||
.map((id) => graph.getCellById(id) as Node)
|
||||
.filter((node) => node?.getData().level === "level4");
|
||||
|
||||
if (level4Nodes.length === 0) {
|
||||
return { error: `level3 节点「${tableName}」下无 level4 子节点,请完善配置` };
|
||||
return {
|
||||
error: `level3 节点「${tableName}」下无 level4 子节点,请完善配置`,
|
||||
};
|
||||
}
|
||||
|
||||
// 2.2.2 遍历每个 level4 节点(每个level3仅取第一个有效level4,避免重复)
|
||||
const validLevel4Node = level4Nodes[0];
|
||||
const columnName = validLevel4Node.attr('label/text') || '';
|
||||
const columnName = validLevel4Node.attr("label/text") || "";
|
||||
if (!columnName) {
|
||||
return { error: `存在未设置 label 的 level4 节点(父级:${tableName})` };
|
||||
return {
|
||||
error: `存在未设置 label 的 level4 节点(父级:${tableName})`,
|
||||
};
|
||||
}
|
||||
|
||||
// 2.2.3 组装单条数据
|
||||
|
|
@ -757,7 +794,10 @@ export class X6Graph {
|
|||
* 获取子节点ID列表
|
||||
*/
|
||||
private getChildNodeIds(parentId: string): string[] {
|
||||
return this.getGraph().getEdges().filter(edge => edge.getSourceCellId() === parentId).map(edge => edge.getTargetCellId());
|
||||
return this.getGraph()
|
||||
.getEdges()
|
||||
.filter((edge) => edge.getSourceCellId() === parentId)
|
||||
.map((edge) => edge.getTargetCellId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -772,9 +812,9 @@ export class X6Graph {
|
|||
*/
|
||||
clear(): void {
|
||||
this.getGraph().clearCells();
|
||||
this.levelCountMap.set('level2', 0);
|
||||
this.levelCountMap.set('level3', 0);
|
||||
this.levelCountMap.set('level4', 0);
|
||||
this.levelCountMap.set("level2", 0);
|
||||
this.levelCountMap.set("level3", 0);
|
||||
this.levelCountMap.set("level4", 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -794,7 +834,7 @@ export class X6Graph {
|
|||
*/
|
||||
private getGraph(): Graph {
|
||||
if (!this.graph) {
|
||||
throw new Error('请先调用 init() 初始化画布');
|
||||
throw new Error("请先调用 init() 初始化画布");
|
||||
}
|
||||
return this.graph;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user