|
@@ -1,5 +1,15 @@
|
|
|
<template>
|
|
|
<div class="system-graph">
|
|
|
+ <div class="system-list">
|
|
|
+ <div
|
|
|
+ class="system-item"
|
|
|
+ v-for="item in systemList"
|
|
|
+ :key="item.id"
|
|
|
+ :style="{ backgroundColor: item.color || '#f0f0f0' }"
|
|
|
+ >
|
|
|
+ <div class="system-item-label">{{ item.name }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
<div id="container" ref="container"></div>
|
|
|
<!-- <div class="control-panel">
|
|
|
<button @click="handleMatch">匹配模式</button>
|
|
@@ -17,7 +27,7 @@ const container = ref(null);
|
|
|
let graph = null;
|
|
|
|
|
|
// 系统集成数据
|
|
|
-const systemData = {
|
|
|
+/* const systemData = {
|
|
|
nodes: [
|
|
|
{
|
|
|
id: 'ERP',
|
|
@@ -268,9 +278,13 @@ const systemData = {
|
|
|
id: 'edge-pms-wms1'
|
|
|
}
|
|
|
]
|
|
|
-};
|
|
|
+}; */
|
|
|
+const systemData = ref({
|
|
|
+ nodes: [],
|
|
|
+ edges: []
|
|
|
+});
|
|
|
|
|
|
-const graphData = ref(systemData);
|
|
|
+// 移除不必要的 graphData ref
|
|
|
|
|
|
// 处理窗口大小变化
|
|
|
const handleResize = () => {
|
|
@@ -288,6 +302,11 @@ const getGraphOptions = () => {
|
|
|
width,
|
|
|
height,
|
|
|
autoFit: 'view',
|
|
|
+ animate: true, // 开启动画
|
|
|
+ animateCfg: {
|
|
|
+ duration: 1000, // 动画时长
|
|
|
+ easing: 'easeInOutQuad', // 动画曲线
|
|
|
+ },
|
|
|
modes: {
|
|
|
default: ['drag-canvas', 'zoom-canvas', 'drag-node', 'click-select'],
|
|
|
},
|
|
@@ -397,9 +416,9 @@ const getGraphOptions = () => {
|
|
|
};
|
|
|
|
|
|
const handleMatch = () => {
|
|
|
- if (!graphData.value || !graph) return;
|
|
|
+ if (!systemData.value || !graph) return;
|
|
|
|
|
|
- const matches = GADDI(graphData.value, pattern, true, undefined, undefined, 'cluster', 'cluster');
|
|
|
+ const matches = GADDI(systemData.value, pattern, true, undefined, undefined, 'cluster', 'cluster');
|
|
|
matches.forEach((match, i) => {
|
|
|
graph.updatePlugin({
|
|
|
key: `hull-${i}`,
|
|
@@ -409,13 +428,96 @@ const handleMatch = () => {
|
|
|
graph.render();
|
|
|
};
|
|
|
|
|
|
-onMounted(() => {
|
|
|
+const systemList = ref([]);
|
|
|
+
|
|
|
+const transformSystemsToArray = (systems) => {
|
|
|
+ return Object.entries(systems).map(([name, data]) => ({
|
|
|
+ id: name,
|
|
|
+ name: name,
|
|
|
+ color: data.color,
|
|
|
+ modules: data.modules,
|
|
|
+ connections: data.connections
|
|
|
+ }));
|
|
|
+};
|
|
|
+
|
|
|
+const getData = async () => {
|
|
|
try {
|
|
|
+ const res = await fetch(`${import.meta.env.VITE_BASE_AI_API}/api/agents/system-integration/`);
|
|
|
+ const data = await res.json();
|
|
|
+ systemData.value = {
|
|
|
+ nodes: data.data.nodes,
|
|
|
+ edges: data.data.edges
|
|
|
+ };
|
|
|
+
|
|
|
+ // 转换系统数据为数组格式
|
|
|
+ systemList.value = transformSystemsToArray(data.data.systems);
|
|
|
+ console.log('转换后的系统列表:', systemList.value);
|
|
|
+
|
|
|
+ // 如果图表已经初始化,更新数据
|
|
|
+ if (graph) {
|
|
|
+ graph.changeData(systemData.value);
|
|
|
+ graph.render();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取数据失败:', error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 节点入场动画
|
|
|
+const nodeAnimateConfig = {
|
|
|
+ enter: {
|
|
|
+ style: {
|
|
|
+ opacity: 0,
|
|
|
+ size: 0,
|
|
|
+ },
|
|
|
+ animate: {
|
|
|
+ size: {
|
|
|
+ value: 30,
|
|
|
+ duration: 1000,
|
|
|
+ easing: 'easeInOutQuad',
|
|
|
+ },
|
|
|
+ opacity: {
|
|
|
+ value: 1,
|
|
|
+ duration: 1000,
|
|
|
+ easing: 'easeInOutQuad',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+// 边入场动画
|
|
|
+const edgeAnimateConfig = {
|
|
|
+ enter: {
|
|
|
+ style: {
|
|
|
+ opacity: 0,
|
|
|
+ lineWidth: 0,
|
|
|
+ },
|
|
|
+ animate: {
|
|
|
+ opacity: {
|
|
|
+ value: 1,
|
|
|
+ duration: 1000,
|
|
|
+ delay: 800, // 延迟执行,等节点动画开始后
|
|
|
+ easing: 'easeInOutQuad',
|
|
|
+ },
|
|
|
+ lineWidth: {
|
|
|
+ value: 2,
|
|
|
+ duration: 1000,
|
|
|
+ delay: 800,
|
|
|
+ easing: 'easeInOutQuad',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ try {
|
|
|
+ // 先获取数据
|
|
|
+ await getData();
|
|
|
// 初始化图形
|
|
|
graph = new Graph({
|
|
|
...getGraphOptions(),
|
|
|
container: container.value,
|
|
|
- data: graphData.value,
|
|
|
+ data: systemData.value, // 使用 systemData.value
|
|
|
autoFit: 'view',
|
|
|
behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element'],
|
|
|
layout: {
|
|
@@ -423,7 +525,7 @@ onMounted(() => {
|
|
|
preventOverlap: true,
|
|
|
nodeStrength: -100,
|
|
|
edgeStrength: 0.1,
|
|
|
- nodeSize: 60,
|
|
|
+ nodeSize: 30,
|
|
|
nodeSpacing: 100,
|
|
|
linkDistance: 200,
|
|
|
gravity: 1,
|
|
@@ -434,13 +536,16 @@ onMounted(() => {
|
|
|
center: [400, 300]
|
|
|
},
|
|
|
node: {
|
|
|
+ ...nodeAnimateConfig,
|
|
|
style: {
|
|
|
labelPlacement: 'center',
|
|
|
labelText: (d) => d.label,
|
|
|
stroke: '#61DDAA',
|
|
|
lineWidth: 1,
|
|
|
labelFontSize: 6,
|
|
|
- labelFill: '#fff',
|
|
|
+ // labelOverflow: 'truncate',
|
|
|
+
|
|
|
+ /* labelFill: '#fff', */
|
|
|
},
|
|
|
labelCfg: {
|
|
|
style: {
|
|
@@ -456,6 +561,7 @@ onMounted(() => {
|
|
|
},
|
|
|
},
|
|
|
edge: {
|
|
|
+ ...edgeAnimateConfig,
|
|
|
style: {
|
|
|
endArrow: true,
|
|
|
},
|
|
@@ -482,6 +588,12 @@ onMounted(() => {
|
|
|
members: [],
|
|
|
},
|
|
|
],
|
|
|
+ layout: {
|
|
|
+ type: 'd3-force',
|
|
|
+ collide: {
|
|
|
+ strength: 0.5,
|
|
|
+ },
|
|
|
+ },
|
|
|
});
|
|
|
|
|
|
graph.render();
|
|
@@ -520,7 +632,43 @@ onUnmounted(() => {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
+}
|
|
|
|
|
|
+.system-list {
|
|
|
+ width: 100%;
|
|
|
+ padding: 20px;
|
|
|
+ display: flex;
|
|
|
+
|
|
|
+ gap: 10px;
|
|
|
+ justify-content: flex-start;
|
|
|
+ flex-wrap: wrap;
|
|
|
+}
|
|
|
+
|
|
|
+.system-item {
|
|
|
+ padding: 10px 10px;
|
|
|
+ border-radius: 6px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ min-width: 120px;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.system-item:hover {
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
|
+ opacity: 0.9;
|
|
|
+}
|
|
|
+
|
|
|
+.system-item-label {
|
|
|
+ color: #fff;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ text-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
|
|
+ text-align: center;
|
|
|
}
|
|
|
|
|
|
#container {
|