JavaProjectRepo/business-css/frontend/src/components/antvg6/GraphCanvas.vue

244 lines
6.0 KiB
Vue

<!-- components/GraphCanvas.vue -->
<script setup lang="ts">
import { onMounted, ref, onBeforeUnmount } from 'vue';
import { Graph, registerNode, Brush } from '@antv/g6';
import { NodeItem, NodeType } from '@/types/graph';
// 定义图形映射名称
const NODE_LABEL_MAP: Record<NodeType, string> = {
cylinder_slot: '圆柱槽',
flat_slot: '扁平槽',
ring_slot: '环形槽',
tube_slot: '管形槽',
bar_stock: '棒料',
tapered_head: '锥形头',
monk_ring_slot: '僧帽环形槽',
};
// 自定义 G6 节点:根据 type 渲染不同形状和颜色
const registerCustomNode = () => {
registerNode(
'custom-mechanical-node',
{
draw: (cfg, group) => {
const type = cfg?.type as NodeType;
let shape, color;
// 根据类型设置外观
switch (type) {
case 'cylinder_slot':
shape = group!.addShape('ellipse', {
attrs: {
x: 0,
y: 0,
rx: 40,
ry: 20,
fill: '#ffeaa7',
stroke: '#fab1a0',
lineWidth: 1.5,
},
});
break;
case 'flat_slot':
shape = group!.addShape('rect', {
attrs: {
x: -50,
y: -15,
width: 100,
height: 30,
fill: '#fdcb6e',
stroke: '#e17055',
radius: 4,
lineWidth: 1.5,
},
});
break;
case 'ring_slot':
shape = group!.addShape('circle', {
attrs: {
x: 0,
y: 0,
r: 30,
fill: '#a29bfe',
stroke: '#6c5ce7',
lineWidth: 2,
shadowBlur: 4,
shadowColor: 'rgba(108, 92, 231, 0.3)',
},
});
break;
case 'tube_slot':
shape = group!.addShape('path', {
attrs: {
path:
'M -40 -20 L 40 -20 C 50 -20 50 20 40 20 L -40 20 C -50 20 -50 -20 -40 -20 Z M -30 -10 L 30 -10 L 30 10 L -30 10 Z',
fill: '#7bed9f',
stroke: '#00b894',
lineWidth: 1,
},
});
break;
case 'bar_stock':
shape = group!.addShape('rect', {
attrs: {
x: -45,
y: -10,
width: 90,
height: 20,
fill: '#fab1a0',
stroke: '#d63031',
lineWidth: 1.2,
},
});
break;
case 'tapered_head':
shape = group!.addShape('path', {
attrs: {
path: 'M -40 20 L 0 -25 L 40 20 L 20 20 L 0 -5 L -20 20 Z',
fill: '#fd79a8',
stroke: '#e84393',
lineWidth: 1.5,
},
});
break;
case 'monk_ring_slot':
shape = group!.addShape('path', {
attrs: {
path:
'M -35 20 C -35 10, -25 -25, 0 -25 C 25 -25, 35 10, 35 20 C 35 20, 25 -10, 0 -10 C -25 -10, -35 20, -35 20 Z',
fill: '#6c5ce7',
stroke: '#55efc4',
lineWidth: 2,
},
});
break;
default:
shape = group!.addShape('circle', {
attrs: {
x: 0,
y: 0,
r: 20,
fill: '#ccc',
stroke: '#999',
},
});
}
// 添加文字标签
group!.addShape('text', {
attrs: {
text: NODE_LABEL_MAP[type] || '未知',
x: 0,
y: 40,
fontSize: 12,
textAlign: 'center',
textBaseline: 'middle',
fill: '#555',
},
});
return shape!;
},
},
'single-shape'
);
};
let graph: Graph | null = null;
const container = ref<HTMLDivElement>();
onMounted(() => {
if (!container.value) return;
// 注册自定义节点
registerCustomNode();
// 初始化 G6 图实例
graph = new Graph({
container: container.value!,
width: container.value.offsetWidth,
height: container.value.offsetHeight,
fitView: true,
fitViewPadding: 40,
// 默认节点配置
defaultNode: {
shape: 'custom-mechanical-node',
size: [80, 40],
},
// 交互模式
modes: {
default: [
'drag-canvas', // 拖动画布
'zoom-canvas', // 缩放
'drag-node', // 拖动节点
],
},
// 启用 Brush 插件实现框选
plugins: [
new Brush({
brushType: 'rect', // 矩形框选
onSelect: (items) => {
console.log('Selected items:', items.map((i) => i.getModel()));
},
onDeselect: () => {
console.log('Deselected all');
},
})
brushType: 'rect', // 矩形框选
onSelect: (items) => {
console.log('Selected items:', items.map((i) => i.getModel()));
},
onDeselect: () => {
console.log('Deselected all');
},
}),
],
});
// 初始空图
graph.data({ nodes: [], edges: [] });
graph.render();
});
onBeforeUnmount(() => {
if (graph) {
graph.destroy();
graph = null;
}
});
// 接收拖入事件
defineExpose({
addNodeAtPosition(type: NodeType, x: number, y: number) {
if (!graph) return;
const modelId = `${type}_${Date.now()}`;
const nodeModel = {
id: modelId,
shape: 'custom-mechanical-node',
x,
y,
type: type,
};
graph.addItem('node', nodeModel);
},
});
</script>
<template>
<div class="graph-container" ref="container"></div>
</template>
<style scoped>
.graph-container {
flex: 1;
height: 100vh;
overflow: hidden;
background-color: #f0f2f5;
position: relative;
}
</style>