Files
auto-solution/modeler/src/views/decision/communication/communication.vue

419 lines
12 KiB
Vue
Raw Normal View History

2026-02-08 15:59:14 +08:00
<template>
<Wrapper>
<a-layout class="bg-transparent" style="background: transparent">
<Header />
<a-layout class="ks-layout-body">
<div class="ks-model-builder-body">
<div class="ks-model-builder-left">
2026-03-14 21:37:07 +08:00
<PlatformCard
2026-03-15 19:32:20 +08:00
ref="scenariosCardRef"
2026-03-15 16:15:23 +08:00
@create="handleCreate"
@select="handleSelect"
2026-02-08 15:59:14 +08:00
/>
<NodesCard
2026-03-15 20:20:56 +08:00
v-if="currentScenario && currentScenario.id >0"
:scenario="currentScenario"
2026-02-08 15:59:14 +08:00
@drag-item-start="handleDragStart"
@drag-item-end="handleDragEnd"
/>
</div>
2026-03-14 21:37:07 +08:00
<div class="ks-model-builder-content" style="width: calc(100% - 250px);">
2026-02-08 15:59:14 +08:00
<div class="ks-model-builder-actions">
<a-space>
2026-03-14 22:35:21 +08:00
<a-button v-if="graph && currentScenario" class="ks-model-builder-save" size="small" @click="handleSave">
2026-02-09 15:06:04 +08:00
<CheckOutlined />
<span>保存</span>
</a-button>
2026-02-08 15:59:14 +08:00
</a-space>
</div>
<!-- 画布容器添加拖放事件 -->
<div
ref="canvas"
class="ks-model-builder-canvas"
@dragenter="handleDragEnter"
@dragleave="handleDragLeave"
@drop="handleDrop"
@dragover.prevent
></div>
<TeleportContainer />
</div>
</div>
</a-layout>
</a-layout>
</Wrapper>
</template>
<script lang="ts">
import { defineComponent, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
import { message } from 'ant-design-vue';
import { getTeleport } from '@antv/x6-vue-shape';
2026-02-08 17:11:03 +08:00
import { Graph, Node, type NodeProperties } from '@antv/x6';
2026-02-08 15:59:14 +08:00
import { CheckCircleOutlined, CheckOutlined, RollbackOutlined, SaveOutlined } from '@ant-design/icons-vue';
import { Wrapper } from '@/components/wrapper';
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
2026-03-14 21:37:07 +08:00
import Header from '../header.vue';
2026-03-14 22:35:21 +08:00
2026-03-15 19:32:20 +08:00
import type { PlatformWithComponents, Scenario } from './types';
2026-03-15 20:20:56 +08:00
import { createGraphTaskElement, createLineOptions, type GraphContainer, type GraphTaskElement, resolveGraph, useGraphCanvas } from '../graph';
2026-03-15 19:32:20 +08:00
import { registerScenarioElement } from './register';
2026-03-15 20:20:56 +08:00
import { createGraphScenarioElement, createGraphTaskElementFromScenario } from './utils';
2026-03-14 22:35:21 +08:00
2026-03-14 21:37:07 +08:00
import PlatformCard from './platform-card.vue';
2026-02-08 15:59:14 +08:00
import NodesCard from './nodes-card.vue';
2026-03-15 20:20:56 +08:00
import { saveScenario } from './api';
2026-02-08 15:59:14 +08:00
const TeleportContainer = defineComponent(getTeleport());
2026-03-15 19:32:20 +08:00
registerScenarioElement();
2026-02-08 15:59:14 +08:00
export default defineComponent({
components: {
2026-03-14 21:37:07 +08:00
PlatformCard,
2026-02-08 15:59:14 +08:00
NodesCard,
Wrapper,
Header,
SaveOutlined,
CheckCircleOutlined,
CheckOutlined,
RollbackOutlined,
TeleportContainer,
},
setup() {
const canvas = ref<HTMLDivElement | null>(null);
const graph = ref<Graph | null>(null);
const currentZoom = ref<number>(1);
2026-03-15 19:32:20 +08:00
const draggedNodeData = ref<PlatformWithComponents | null>(null);
2026-02-08 15:59:14 +08:00
const isDraggingOver = ref(false);
2026-03-15 19:32:20 +08:00
const currentScenarioEditing = ref<boolean>(false);
2026-03-15 16:15:23 +08:00
const currentScenario = ref<Scenario | null>(null);
2026-03-15 19:32:20 +08:00
const currentGraph = ref<GraphContainer | null>(null);
2026-02-08 15:59:14 +08:00
const selectedModelNode = ref<Node<NodeProperties> | null>(null);
2026-02-08 20:27:40 +08:00
const selectedNodeTaskElement = ref<GraphTaskElement | null>(null);
2026-02-08 22:16:22 +08:00
const changed = ref<boolean>(false);
2026-03-15 19:32:20 +08:00
const scenariosCardRef = ref<InstanceType<typeof PlatformCard> | null>(null);
2026-02-08 15:59:14 +08:00
const {
handleGraphEvent,
createCanvas,
zoomIn,
zoomOut,
fitToScreen,
centerContent,
resizeCanvas,
} = useGraphCanvas();
// 处理拖动开始
2026-03-15 19:32:20 +08:00
const handleDragStart = (nm: PlatformWithComponents) => {
2026-02-08 15:59:14 +08:00
draggedNodeData.value = nm;
};
// 处理拖动结束
const handleDragEnd = () => {
isDraggingOver.value = false;
};
// 处理拖动进入
const handleDragEnter = (e: DragEvent) => {
safePreventDefault(e);
safeStopPropagation(e);
isDraggingOver.value = true;
};
// 处理拖动离开
const handleDragLeave = (e: DragEvent) => {
safePreventDefault(e);
safeStopPropagation(e);
if (canvas.value && e.relatedTarget &&
typeof e.relatedTarget === 'object' &&
'nodeType' in e.relatedTarget) {
// 使用 Element 类型而不是 x6 的 Node 类型
if (!canvas.value.contains(e.relatedTarget as Element)) {
isDraggingOver.value = false;
}
}
};
// 处理放置
const handleDrop = (e: DragEvent) => {
console.info('handleDrop', e);
safePreventDefault(e);
safeStopPropagation(e);
isDraggingOver.value = false;
2026-03-15 19:32:20 +08:00
currentScenarioEditing.value = false;
2026-02-08 15:59:14 +08:00
2026-03-14 22:35:21 +08:00
if (!currentScenario.value) {
2026-03-15 20:20:56 +08:00
message.error('请先选择场景.');
2026-02-08 15:59:14 +08:00
return;
}
if (!graph.value || !canvas.value || !draggedNodeData.value) {
message.error('无法放置节点,缺少必要数据');
return;
}
try {
// 获取拖动的数据
2026-03-15 19:32:20 +08:00
const pwc = draggedNodeData.value as PlatformWithComponents;
2026-02-08 15:59:14 +08:00
2026-03-15 19:32:20 +08:00
// if (!hasElements(graph.value as Graph)) {
// message.error('请先添加根节点.');
// return;
// }
2026-02-08 15:59:14 +08:00
2026-03-15 19:32:20 +08:00
// if (hasRootElementNode(graph.value as Graph)) {
// message.error('根节点已经存在.');
// return;
// }
2026-02-08 15:59:14 +08:00
// 计算相对于画布的位置(考虑缩放)
const rect = canvas.value.getBoundingClientRect();
const scale = currentZoom.value || 1;
const x = (e.clientX - rect.left) / scale;
const y = (e.clientY - rect.top) / scale;
2026-03-15 19:32:20 +08:00
console.log('放置节点:', { ...pwc, x, y });
2026-02-08 15:59:14 +08:00
// 创建节点数据
2026-03-15 19:32:20 +08:00
const settingTaskElement: GraphTaskElement = createGraphTaskElementFromScenario(pwc, { x, y });
2026-02-08 15:59:14 +08:00
// 创建节点
2026-03-15 20:20:56 +08:00
const settingTaskNode = createGraphScenarioElement(settingTaskElement);
2026-02-08 15:59:14 +08:00
console.info('create settingTaskNode: ', settingTaskElement, settingTaskNode);
// 将节点添加到画布
graph.value?.addNode(settingTaskNode as any);
console.log('节点已添加到画布:', settingTaskNode.id);
// 重置拖动数据
draggedNodeData.value = null;
} catch (error) {
console.error('放置节点时出错:', error);
}
};
2026-03-15 19:32:20 +08:00
const handleSelect = (scenario: Scenario) => {
console.info('handleSelect', scenario);
let nodeGraph: GraphContainer | null = null;
try {
nodeGraph = JSON.parse(scenario.communicationGraph as unknown as string) as unknown as GraphContainer;
} catch (e: any) {
console.error('parse error,cause:', e);
}
if (!nodeGraph) {
nodeGraph = {
nodes: [],
edges: [],
};
}
currentScenario.value = {
...scenario,
graph: nodeGraph,
};
currentScenarioEditing.value = true;
createElements();
2026-02-08 17:11:03 +08:00
};
2026-02-08 15:59:14 +08:00
const createElements = () => {
2026-02-08 17:57:40 +08:00
nextTick(() => {
2026-02-08 22:31:13 +08:00
try {
2026-02-08 22:16:22 +08:00
graph.value?.clearCells();
2026-02-08 22:31:13 +08:00
} catch (e: any) {
console.error('clear cells error, cause:', e);
2026-02-08 22:16:22 +08:00
}
2026-02-08 22:31:13 +08:00
setTimeout(() => {
2026-03-14 22:35:21 +08:00
if (currentScenario.value?.graph && graph.value) {
if (currentScenario.value?.graph.nodes) {
currentScenario.value?.graph.nodes.forEach(ele => {
2026-03-15 20:20:56 +08:00
const node = createGraphScenarioElement(ele as GraphTaskElement);
2026-02-08 22:16:22 +08:00
console.info('create node: ', ele);
// 将节点添加到画布
graph.value?.addNode(node as Node);
2026-02-08 17:57:40 +08:00
});
2026-02-08 22:16:22 +08:00
}
2026-03-14 22:35:21 +08:00
if (currentScenario.value?.graph.edges) {
2026-02-08 22:16:22 +08:00
// 然后添加所有边,确保包含桩点信息
setTimeout(() => {
2026-03-14 22:35:21 +08:00
currentScenario.value?.graph.edges.forEach(edgeData => {
2026-02-08 22:16:22 +08:00
graph.value?.addEdge({
...edgeData,
...createLineOptions(),
});
});
}, 100); // 延迟一会儿,免得连线错位
}
2026-02-08 17:57:40 +08:00
}
2026-02-08 22:31:13 +08:00
}, 100);
2026-02-08 17:57:40 +08:00
});
2026-02-08 15:59:14 +08:00
};
2026-03-15 16:15:23 +08:00
const handleCreate = () => {
2026-03-14 22:35:21 +08:00
currentScenario.value = {
2026-02-08 22:28:52 +08:00
id: 0,
2026-03-15 19:32:20 +08:00
name: null,
2026-02-08 22:28:52 +08:00
description: null,
2026-03-15 19:32:20 +08:00
communicationGraph: null,
2026-02-08 22:28:52 +08:00
graph: {
edges: [],
nodes: [],
2026-03-15 19:32:20 +08:00
}
2026-02-08 22:31:13 +08:00
};
2026-03-15 19:32:20 +08:00
currentGraph.value = {
2026-02-08 22:28:52 +08:00
edges: [],
nodes: [],
2026-02-08 22:31:13 +08:00
};
2026-02-08 22:28:52 +08:00
selectedModelNode.value = null;
selectedNodeTaskElement.value = null;
createElements();
2026-02-08 22:31:13 +08:00
};
2026-02-08 22:28:52 +08:00
2026-02-08 15:59:14 +08:00
// 初始化X6画布
const initGraph = () => {
if (!canvas.value) {
console.error('画布容器不存在');
return;
}
try {
graph.value = createCanvas(canvas.value);
console.log('画布初始化成功');
createElements();
// 监听缩放变化
handleGraphEvent('scale', ({ sx }: { sx: number }) => {
currentZoom.value = sx;
});
2026-02-08 22:09:28 +08:00
handleGraphEvent('blank:click', () => {
selectedModelNode.value = null;
selectedNodeTaskElement.value = null;
2026-03-15 19:32:20 +08:00
currentScenarioEditing.value = null !== currentScenario.value;
2026-02-08 22:09:28 +08:00
});
2026-02-08 15:59:14 +08:00
handleGraphEvent('node:click', (args: any) => {
const node = args.node as Node<NodeProperties>;
2026-02-08 20:27:40 +08:00
const newElement = node.getData() as GraphTaskElement;
2026-02-08 15:59:14 +08:00
selectedModelNode.value = node;
2026-02-08 20:27:40 +08:00
selectedNodeTaskElement.value = JSON.parse(JSON.stringify(newElement || {})) as GraphTaskElement;
2026-02-08 15:59:14 +08:00
});
// 监听节点鼠标事件,显示/隐藏连接点
handleGraphEvent('node:mouseenter', (_ctx: any) => {
});
handleGraphEvent('node:mouseleave', (_ctx: any) => {
});
} catch (error) {
console.error('初始化画布失败:', error);
}
};
// 监听窗口大小变化
const handleResize = () => {
nextTick(() => {
resizeCanvas();
});
};
const init = () => {
console.info('init');
nextTick(() => {
initGraph();
window.addEventListener('resize', handleResize);
console.log('节点挂载完成');
});
};
2026-02-08 20:27:40 +08:00
const handleUpdateElement = (element: GraphTaskElement) => {
2026-02-08 15:59:14 +08:00
// 更新选中的节点数据
if (selectedModelNode.value) {
selectedModelNode.value.replaceData(element);
}
console.info('handleUpdateElement', element);
// 更新本地引用
selectedNodeTaskElement.value = element;
2026-02-08 22:31:13 +08:00
changed.value = true;
2026-02-08 15:59:14 +08:00
};
const handleSave = () => {
2026-03-15 19:32:20 +08:00
const graphData: GraphContainer = resolveGraph(graph.value as Graph);
2026-02-08 15:59:14 +08:00
console.info('handleSave', graphData);
2026-03-14 22:35:21 +08:00
if (!currentScenario.value) {
2026-02-08 15:59:14 +08:00
message.error('当前决策树不存在');
return;
}
2026-03-15 19:32:20 +08:00
const newScenario: Scenario = {
2026-03-14 22:35:21 +08:00
...currentScenario.value,
2026-02-08 15:59:14 +08:00
graph: graphData,
2026-03-15 19:32:20 +08:00
communicationGraph: JSON.stringify(graphData),
2026-02-08 15:59:14 +08:00
};
2026-03-15 19:32:20 +08:00
if (!newScenario.name) {
message.error('场景名称不能为空.');
2026-02-08 22:28:52 +08:00
return;
}
2026-02-08 15:59:14 +08:00
let res = null;
2026-03-14 22:35:21 +08:00
if (currentScenario.value.id > 0) {
2026-03-15 19:32:20 +08:00
res = saveScenario(newScenario);
2026-02-08 15:59:14 +08:00
} else {
2026-03-15 19:32:20 +08:00
res = saveScenario(newScenario);
2026-02-08 15:59:14 +08:00
}
res.then(r => {
if (r.code === 200) {
2026-03-15 19:32:20 +08:00
scenariosCardRef.value?.refresh();
2026-02-08 17:11:03 +08:00
message.success(r.msg ?? '操作成功.');
2026-02-08 15:59:14 +08:00
} else {
2026-02-08 17:11:03 +08:00
message.error(r.msg ?? '操作失败.');
2026-02-08 15:59:14 +08:00
}
});
};
// 初始化
onMounted(() => {
init();
});
// 清理
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize);
if (graph.value) {
try {
graph.value.clearCells();
} catch (error) {
console.warn('销毁画布时出错:', error);
}
graph.value = null;
console.log('画布已销毁');
}
});
return {
2026-03-15 19:32:20 +08:00
scenariosCardRef,
2026-03-15 16:15:23 +08:00
handleCreate,
2026-03-15 19:32:20 +08:00
currentScenarioEditing,
2026-03-14 22:35:21 +08:00
currentScenario,
2026-03-15 19:32:20 +08:00
currentGraph,
2026-02-08 15:59:14 +08:00
selectedNodeTaskElement,
selectedModelNode,
graph,
canvas,
zoomIn,
zoomOut,
fitToScreen,
centerContent,
handleDragStart,
handleDragEnd,
handleDragEnter,
handleDragLeave,
handleDrop,
isDraggingOver,
handleSave,
handleUpdateElement,
2026-03-15 16:15:23 +08:00
handleSelect,
2026-02-08 15:59:14 +08:00
};
},
});
</script>