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