先附上效果图
// 节点内属性的点击事件:node:port:click
graph.on(‘node:port:click’, ({ node, port }) => {
resetAllHighlights();
highlightPort(node, port, true);
highlightEdgesForPort(port, new Set());
});
// 以下为源码
<template><div id="container"></div>
</template>
<script setup lang="ts">
import {onMounted} from "vue";
import { Graph, Cell, Shape } from '@antv/x6'const LINE_HEIGHT = 24
const NODE_WIDTH = 150Graph.registerPortLayout('erPortPosition',(portsPositionArgs) => {return portsPositionArgs.map((_, index) => {return {position: {x: 0,y: (index + 1) * LINE_HEIGHT,},angle: 0,}})},true,
)Graph.registerNode('er-rect',{inherit: 'rect',markup: [{tagName: 'rect',selector: 'body',},{tagName: 'text',selector: 'label',},],attrs: {rect: {strokeWidth: 1,stroke: '#5F95FF',fill: '#5F95FF',},label: {fontWeight: 'bold',fill: '#ffffff',fontSize: 12,},},ports: {groups: {list: {markup: [{tagName: 'rect',selector: 'portBody',},{tagName: 'text',selector: 'portNameLabel',},{tagName: 'text',selector: 'portTypeLabel',},],attrs: {portBody: {width: NODE_WIDTH,height: LINE_HEIGHT,strokeWidth: 1,stroke: '#5F95FF',fill: '#EFF4FF',cursor: 'pointer',},portNameLabel: {ref: 'portBody',refX: 6,refY: 6,fontSize: 10,},portTypeLabel: {ref: 'portBody',refX: 95,refY: 6,fontSize: 10,},},position: 'erPortPosition',},},},},true,
)onMounted(() => {const graph = new Graph({container: document.getElementById('container'),interacting: false, // 节点不可拖动connecting: {router: {name: 'er',args: {offset: 25,direction: 'H',},},createEdge() {return new Shape.Edge({attrs: {line: {stroke: '#A2B1C3',strokeWidth: 2,},},})},},})const resetAllHighlights = () => {const nodes = graph.getNodes(); // 获取所有nodesnodes.forEach((node) => {const ports = node.getPorts(); // 获取所有portsports.forEach((port) => {node.portProp(port.id, 'attrs/portBody/fill', '#EFF4FF');node.portProp(port.id, 'attrs/portNameLabel/fill', '#000000');node.portProp(port.id, 'attrs/portTypeLabel/fill', '#000000');});});const edges = graph.getEdges();edges.forEach((edge) => {edge.attr('line/stroke', '#A2B1C3');edge.attr('line/strokeWidth', 2);});};const highlightPort = (node: Cell, portId: string, highlight: boolean) => {if (highlight) {node.portProp(portId, 'attrs/portBody/fill', 'yellow');node.portProp(portId, 'attrs/portNameLabel/fill', 'red');node.portProp(portId, 'attrs/portTypeLabel/fill', 'red');} else {node.portProp(portId, 'attrs/portBody/fill', '#EFF4FF');node.portProp(portId, 'attrs/portNameLabel/fill', '#000000');node.portProp(portId, 'attrs/portTypeLabel/fill', '#000000');}};const highlightEdgesForPort = (portId: string, visitedPorts: Set<string>) => {if (visitedPorts.has(portId)) return;visitedPorts.add(portId);const edges = graph.getEdges();edges.forEach((edge) => {const sourcePortId = edge.getSourcePortId();const targetPortId = edge.getTargetPortId();if (sourcePortId === portId || targetPortId === portId) {edge.attr('line/stroke', 'red');edge.attr('line/strokeWidth', 2);const sourceNode = edge.getSourceNode();const targetNode = edge.getTargetNode();// 递归if (sourcePortId === portId && targetNode) {highlightPort(targetNode, targetPortId, true);highlightEdgesForPort(targetPortId, visitedPorts); // Recursively highlight connected ports} else if (targetPortId === portId && sourceNode) {highlightPort(sourceNode, sourcePortId, true);highlightEdgesForPort(sourcePortId, visitedPorts); // Recursively highlight connected ports}}});};graph.on('node:port:click', ({ node, port }) => {resetAllHighlights();highlightPort(node, port, true);highlightEdgesForPort(port, new Set());});const data = [{"id": "1","shape": "er-rect","label": "学生","width": 150,"height": 24,"position": {"x": 24,"y": 150},"ports": [{"id": "1-1","group": "list","attrs": {"portNameLabel": {"text": "ID"},"portTypeLabel": {"text": "STRING"}}},{"id": "1-2","group": "list","attrs": {"portNameLabel": {"text": "Name"},"portTypeLabel": {"text": "STRING"}}},{"id": "1-3","group": "list","attrs": {"portNameLabel": {"text": "Class"},"portTypeLabel": {"text": "NUMBER"}}},{"id": "1-4","group": "list","attrs": {"portNameLabel": {"text": "Gender"},"portTypeLabel": {"text": "BOOLEAN"}}}]},{"id": "2","shape": "er-rect","label": "课程","width": 150,"height": 24,"position": {"x": 250,"y": 210},"ports": [{"id": "2-1","group": "list","attrs": {"portNameLabel": {"text": "ID"},"portTypeLabel": {"text": "STRING"}}},{"id": "2-2","group": "list","attrs": {"portNameLabel": {"text": "Name"},"portTypeLabel": {"text": "STRING"}}},{"id": "2-3","group": "list","attrs": {"portNameLabel": {"text": "StudentID"},"portTypeLabel": {"text": "STRING"}}},{"id": "2-4","group": "list","attrs": {"portNameLabel": {"text": "TeacherID"},"portTypeLabel": {"text": "STRING"}}},{"id": "2-5","group": "list","attrs": {"portNameLabel": {"text": "Description"},"portTypeLabel": {"text": "STRING"}}}]},{"id": "3","shape": "er-rect","label": "老师","width": 150,"height": 24,"position": {"x": 480,"y": 350},"ports": [{"id": "3-1","group": "list","attrs": {"portNameLabel": {"text": "ID"},"portTypeLabel": {"text": "STRING"}}},{"id": "3-2","group": "list","attrs": {"portNameLabel": {"text": "Name"},"portTypeLabel": {"text": "STRING"}}},{"id": "3-3","group": "list","attrs": {"portNameLabel": {"text": "Age"},"portTypeLabel": {"text": "NUMBER"}}}]},{"id": "4","shape": "edge","source": {"cell": "1","port": "1-1"},"target": {"cell": "2","port": "2-4"},"attrs": {"line": {"stroke": "#A2B1C3","strokeWidth": 2}},"zIndex": 0},{"id": "5","shape": "edge","source": {"cell": "2","port": "2-4"},"target": {"cell": "3","port": "3-1"},"attrs": {"line": {"stroke": "#A2B1C3","strokeWidth": 2}},"zIndex": 0},{"id": "6","shape": "edge","source": {"cell": "1","port": "1-2"},"target": {"cell": "2","port": "2-2"},"attrs": {"line": {"stroke": "#A2B1C3","strokeWidth": 2}},"zIndex": 0},]const cells: Cell[] = []data.forEach((item: any) => {if (item.shape === 'edge') {cells.push(graph.createEdge(item))} else {cells.push(graph.createNode(item))}})graph.resetCells(cells)graph.zoomToFit({ padding: 10, maxScale: 1 })})
</script>