244 lines
6.0 KiB
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> |