添加分析设计页面
@ -10,6 +10,8 @@
|
|||||||
"prettier": "prettier --write ."
|
"prettier": "prettier --write ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@antv/g6": "^5.0.51",
|
||||||
|
"@antv/x6": "^3.1.4",
|
||||||
"@element-plus/icons-vue": "^2.0.10",
|
"@element-plus/icons-vue": "^2.0.10",
|
||||||
"@types/js-cookie": "^3.0.2",
|
"@types/js-cookie": "^3.0.2",
|
||||||
"@vueuse/core": "^9.1.1",
|
"@vueuse/core": "^9.1.1",
|
||||||
@ -20,6 +22,7 @@
|
|||||||
"default-passive-events": "^2.0.0",
|
"default-passive-events": "^2.0.0",
|
||||||
"echarts": "^5.2.2",
|
"echarts": "^5.2.2",
|
||||||
"element-plus": "^2.2.27",
|
"element-plus": "^2.2.27",
|
||||||
|
"insert-css": "^2.0.0",
|
||||||
"js-base64": "^3.7.5",
|
"js-base64": "^3.7.5",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
|
|||||||
BIN
business-css/frontend/src/assets/x6/add.png
Normal file
|
After Width: | Height: | Size: 544 B |
BIN
business-css/frontend/src/assets/x6/change.png
Normal file
|
After Width: | Height: | Size: 708 B |
BIN
business-css/frontend/src/assets/x6/copy.png
Normal file
|
After Width: | Height: | Size: 424 B |
BIN
business-css/frontend/src/assets/x6/del.png
Normal file
|
After Width: | Height: | Size: 264 B |
BIN
business-css/frontend/src/assets/x6/display.png
Normal file
|
After Width: | Height: | Size: 672 B |
BIN
business-css/frontend/src/assets/x6/hide.png
Normal file
|
After Width: | Height: | Size: 732 B |
BIN
business-css/frontend/src/assets/x6/history.png
Normal file
|
After Width: | Height: | Size: 643 B |
BIN
business-css/frontend/src/assets/x6/info.png
Normal file
|
After Width: | Height: | Size: 619 B |
BIN
business-css/frontend/src/assets/x6/magnify.png
Normal file
|
After Width: | Height: | Size: 715 B |
BIN
business-css/frontend/src/assets/x6/material.png
Normal file
|
After Width: | Height: | Size: 738 B |
BIN
business-css/frontend/src/assets/x6/redo.png
Normal file
|
After Width: | Height: | Size: 560 B |
BIN
business-css/frontend/src/assets/x6/reduce.png
Normal file
|
After Width: | Height: | Size: 675 B |
BIN
business-css/frontend/src/assets/x6/return.png
Normal file
|
After Width: | Height: | Size: 379 B |
BIN
business-css/frontend/src/assets/x6/revoke.png
Normal file
|
After Width: | Height: | Size: 536 B |
BIN
business-css/frontend/src/assets/x6/save.png
Normal file
|
After Width: | Height: | Size: 396 B |
244
business-css/frontend/src/components/antvg6/GraphCanvas.vue
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
<!-- 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>
|
||||||
101
business-css/frontend/src/components/antvg6/LeftPanel.vue
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<!-- components/LeftPanel.vue -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { NodeItem, NodeType } from '@/types/graph';
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'drag-start', nodeType: NodeType): void;
|
||||||
|
(e: 'drag-end'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const nodes: NodeItem[] = [
|
||||||
|
{ type: 'cylinder_slot', label: '圆柱槽' },
|
||||||
|
{ type: 'flat_slot', label: '扁平槽' },
|
||||||
|
{ type: 'ring_slot', label: '环形槽' },
|
||||||
|
{ type: 'tube_slot', label: '管形槽' },
|
||||||
|
{ type: 'bar_stock', label: '棒料' },
|
||||||
|
{ type: 'tapered_head', label: '锥形头' },
|
||||||
|
{ type: 'monk_ring_slot', label: '僧帽环形槽' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleDragStart = (e: DragEvent, type: NodeType) => {
|
||||||
|
if (!e.dataTransfer) return;
|
||||||
|
e.dataTransfer.setData('text/plain', type);
|
||||||
|
emit('drag-start', type);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="left-panel">
|
||||||
|
<h3 class="panel-title">图形元件库</h3>
|
||||||
|
<div class="node-list">
|
||||||
|
<div
|
||||||
|
v-for="node in nodes"
|
||||||
|
:key="node.type"
|
||||||
|
class="node-item"
|
||||||
|
draggable
|
||||||
|
@dragstart="handleDragStart($event, node.type)"
|
||||||
|
>
|
||||||
|
<span class="icon">⚙️</span>
|
||||||
|
<span class="label">{{ node.label }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.left-panel {
|
||||||
|
width: 200px;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #f7f9fc;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #1a1a1a;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
margin: 0;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-list {
|
||||||
|
padding: 8px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: grab;
|
||||||
|
user-select: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-item:hover {
|
||||||
|
background-color: #e6f7ff;
|
||||||
|
border-color: #91d5ff;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
414
business-css/frontend/src/components/antvg6/index.vue
Normal file
@ -0,0 +1,414 @@
|
|||||||
|
<!-- AntV G6 Graph Component -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { Graph,NodeEvent } from '@antv/g6';
|
||||||
|
|
||||||
|
const container = ref<HTMLElement | null>(null);
|
||||||
|
let graph: Graph | null = null;
|
||||||
|
// Device types data
|
||||||
|
const deviceTypes = [
|
||||||
|
{ id: 'device1', name: '圆柱槽', icon: '🟢',
|
||||||
|
img: 'http://localhost:3000/dev-api/avatar/1.png'
|
||||||
|
},
|
||||||
|
{ id: 'device2', name: '扁平槽', icon: '🔵',
|
||||||
|
img: 'http://localhost:3000/dev-api/avatar/2.png'
|
||||||
|
},
|
||||||
|
{ id: 'device3', name: '环形槽', icon: '🟢',
|
||||||
|
img: 'http://localhost:3000/dev-api/avatar/3.png'
|
||||||
|
},
|
||||||
|
{ id: 'device4', name: '管束槽', icon: '🟢',
|
||||||
|
img: 'http://localhost:3000/dev-api/avatar/4.png'
|
||||||
|
},
|
||||||
|
{ id: 'device5', name: '萃取柱', icon: '🟦',
|
||||||
|
img: 'http://localhost:3000/dev-api/avatar/5.png'
|
||||||
|
},
|
||||||
|
{ id: 'device6', name: '流化床', icon: '🟦',
|
||||||
|
img: 'http://localhost:3000/dev-api/avatar/6.png'
|
||||||
|
},
|
||||||
|
{ id: 'device7', name: '锥底环形槽', icon: '🟦',
|
||||||
|
img: 'http://localhost:3000/dev-api/avatar/7.png'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const data = {
|
||||||
|
nodes: [
|
||||||
|
],
|
||||||
|
edges: [
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Drag and drop functions
|
||||||
|
const handleDragStart = (event: DragEvent, device: any) => {
|
||||||
|
if (event.dataTransfer) {
|
||||||
|
event.dataTransfer.setData('application/json', JSON.stringify(device));
|
||||||
|
event.dataTransfer.effectAllowed = 'copy';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (event: DragEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (event.dataTransfer) {
|
||||||
|
event.dataTransfer.dropEffect = 'copy';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrop = (event: DragEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!graph || !event.dataTransfer || !container.value) return;
|
||||||
|
|
||||||
|
let newNode = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deviceData = JSON.parse(event.dataTransfer.getData('application/json'));
|
||||||
|
let imgUrl = deviceData.img;
|
||||||
|
|
||||||
|
// Calculate coordinates relative to graph container
|
||||||
|
const rect = container.value.getBoundingClientRect();
|
||||||
|
const x = event.clientX - rect.left;
|
||||||
|
const y = event.clientY - rect.top;
|
||||||
|
// const point = graph.getPointByClient(event.clientX, event.clientY);
|
||||||
|
// const x = point.x;
|
||||||
|
// const y = point.y;
|
||||||
|
const nodeId = 'node1-' + Date.now();
|
||||||
|
graph.addNodeData([
|
||||||
|
{
|
||||||
|
type: 'image',
|
||||||
|
id: nodeId,
|
||||||
|
img: imgUrl, // 图片地址
|
||||||
|
data: { label: '自定义图片1', status: 'online' },
|
||||||
|
style: {
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
size: 60,
|
||||||
|
src: imgUrl, // 图片地址
|
||||||
|
labelText: '111111111111',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
graph.draw();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error handling drop:', error);
|
||||||
|
console.error('Graph instance:', graph);
|
||||||
|
if (newNode) {
|
||||||
|
console.error('New node:', JSON.stringify(newNode, null, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const dragelement = ref('');
|
||||||
|
function setDragElement(type: string) {
|
||||||
|
dragelement.value = type;
|
||||||
|
}
|
||||||
|
const dragLineData = ref([{
|
||||||
|
stroke: '#333',
|
||||||
|
lineWidth: 2,
|
||||||
|
endArrow: false,
|
||||||
|
endArrowType: '',
|
||||||
|
endArrowSize: 10,
|
||||||
|
},{
|
||||||
|
stroke: '#333',
|
||||||
|
lineWidth: 2,
|
||||||
|
endArrow: true,
|
||||||
|
endArrowType: 'vee',
|
||||||
|
endArrowSize: 10,
|
||||||
|
},{
|
||||||
|
startArrow: true,
|
||||||
|
startArrowType: 'vee',
|
||||||
|
startArrowSize: 10,
|
||||||
|
endArrow: true,
|
||||||
|
endArrowType: 'vee',
|
||||||
|
endArrowSize: 10,
|
||||||
|
stroke: '#333',
|
||||||
|
lineWidth: 2
|
||||||
|
},{
|
||||||
|
stroke: '#333',
|
||||||
|
lineWidth: 2,
|
||||||
|
lineDash: [5, 5],
|
||||||
|
},{
|
||||||
|
stroke: '#333',
|
||||||
|
lineWidth: 2,
|
||||||
|
lineDash: [5, 5],
|
||||||
|
endArrow: true,
|
||||||
|
endArrowType: 'vee',
|
||||||
|
endArrowSize: 10,
|
||||||
|
},{
|
||||||
|
startArrow: true,
|
||||||
|
startArrowType: 'vee',
|
||||||
|
startArrowSize: 10,
|
||||||
|
lineDash: [5, 5],
|
||||||
|
endArrow: true,
|
||||||
|
endArrowType: 'vee',
|
||||||
|
endArrowSize: 10,
|
||||||
|
stroke: '#333',
|
||||||
|
lineWidth: 2
|
||||||
|
}]);
|
||||||
|
const dragLine:any = ref({
|
||||||
|
stroke: '#333',
|
||||||
|
lineWidth: 2,
|
||||||
|
lineDash: [5, 5],
|
||||||
|
});
|
||||||
|
const dragLineType = ref('实线');
|
||||||
|
function setDragLineStyle(index: any, type: string) {
|
||||||
|
dragLine.value = dragLineData.value[index];
|
||||||
|
dragLineType.value = type;
|
||||||
|
// graph.updateBehavior({
|
||||||
|
// key: 'create-edge',
|
||||||
|
// style: (edgeData:any) => {
|
||||||
|
// debugger
|
||||||
|
// return {
|
||||||
|
// ...dragLine.value
|
||||||
|
// };
|
||||||
|
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
if (container.value) {
|
||||||
|
// Create G6 graph instance with data in constructor - G6 v4 compatible
|
||||||
|
graph = new Graph({
|
||||||
|
container: container.value,
|
||||||
|
width: container.value.offsetWidth,
|
||||||
|
height: container.value.offsetHeight,
|
||||||
|
node: {
|
||||||
|
type: 'image',
|
||||||
|
style: {
|
||||||
|
size: 80,
|
||||||
|
labelText: (d) => '',
|
||||||
|
src: (d: any) => d.img
|
||||||
|
},
|
||||||
|
},
|
||||||
|
edge: {
|
||||||
|
type: 'line',
|
||||||
|
style: (datum) => {
|
||||||
|
// 正确的动态样式配置方式
|
||||||
|
return dragLine.value
|
||||||
|
},
|
||||||
|
// style: {
|
||||||
|
// lineDash: function (datum) {
|
||||||
|
// if (dragLineType.value === '虚线' || dragLineType.value === '单箭头虚线' || dragLineType.value === '双箭头虚线') {
|
||||||
|
// return [5, 5];
|
||||||
|
// }
|
||||||
|
// return [];
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
behaviors: [
|
||||||
|
// 'drag-canvas', // 允许拖拽画布
|
||||||
|
// 'zoom-canvas', // 允许缩放画布
|
||||||
|
{
|
||||||
|
type: 'drag-element',
|
||||||
|
enable: (event:any) => {
|
||||||
|
// 只有当拖拽目标是节点且其id不为'fixedNode'时才允许拖拽
|
||||||
|
return dragelement.value === 'drag-element' ;
|
||||||
|
},
|
||||||
|
}, // 允许拖拽节点
|
||||||
|
{
|
||||||
|
type: 'create-edge',
|
||||||
|
trigger: 'drag',
|
||||||
|
enable: (event:any) => {
|
||||||
|
// 动态判断是否允许创建边
|
||||||
|
// 例如:只有按住 Shift 键时才允许连线
|
||||||
|
return dragelement.value === '' ;
|
||||||
|
},
|
||||||
|
// style: dragLine.value,
|
||||||
|
style: (edgeData:any) => {
|
||||||
|
debugger
|
||||||
|
// 动态设置虚线还是实线
|
||||||
|
const baseStyle = dragLine.value;
|
||||||
|
if (edgeData.data && edgeData.data.isDashed) {
|
||||||
|
return {
|
||||||
|
...baseStyle,
|
||||||
|
lineDash: [5, 5], // 虚线
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...baseStyle,
|
||||||
|
lineDash: [], // 实线
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
data: data // Pass data directly in constructor
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.on(NodeEvent.CLICK, (evt) => {
|
||||||
|
debugger
|
||||||
|
const { target } = evt;
|
||||||
|
const nodeId = target.id;
|
||||||
|
|
||||||
|
// 在控制台输出点击的节点信息
|
||||||
|
console.log(`节点 ${nodeId} 被点击了`);
|
||||||
|
|
||||||
|
// 获取节点数据
|
||||||
|
const nodeData = graph.getNodeData(nodeId);
|
||||||
|
console.log('节点数据:', nodeData);
|
||||||
|
|
||||||
|
// 设置节点选中状态
|
||||||
|
graph.setElementState(nodeId, 'selected', true);
|
||||||
|
});
|
||||||
|
// Just render, no need for separate data() call
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="app-layout">
|
||||||
|
<!-- Left Sidebar -->
|
||||||
|
<div class="sidebar">
|
||||||
|
<div class="sidebar-section">
|
||||||
|
<h3>设备</h3>
|
||||||
|
<div
|
||||||
|
v-for="device in deviceTypes"
|
||||||
|
:key="device.id"
|
||||||
|
class="device-item"
|
||||||
|
draggable="true"
|
||||||
|
@dragstart="handleDragStart($event, device)"
|
||||||
|
>
|
||||||
|
<div class="device-icon">
|
||||||
|
<img :src="device.img" alt="设备图标" style="width: 40px; height: 40px;">
|
||||||
|
</div>
|
||||||
|
<div class="device-name">{{ device.name }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar-section">
|
||||||
|
<h3>链接</h3>
|
||||||
|
|
||||||
|
<div class="device-item" @click="setDragElement('drag-element')">
|
||||||
|
<div class="device-icon">↔️</div>
|
||||||
|
<div class="device-name">移动</div>
|
||||||
|
</div>
|
||||||
|
<div class="device-item" @click="setDragElement('')">
|
||||||
|
<div class="device-icon">↔️</div>
|
||||||
|
<div class="device-name">不移动</div>
|
||||||
|
</div>
|
||||||
|
<div class="device-item" @click="setDragLineStyle(0,'实线')">
|
||||||
|
<div class="device-icon">↔️</div>
|
||||||
|
<div class="device-name">实线</div>
|
||||||
|
</div>
|
||||||
|
<div class="device-item" @click="setDragLineStyle(1,'单箭头实线')">
|
||||||
|
<div class="device-icon">↔️</div>
|
||||||
|
<div class="device-name">单箭头实线</div>
|
||||||
|
</div>
|
||||||
|
<div class="device-item" @click="setDragLineStyle(2,'双箭头实线')">
|
||||||
|
<div class="device-icon">⇄</div>
|
||||||
|
<div class="device-name">双箭头实线</div>
|
||||||
|
</div>
|
||||||
|
<div class="device-item" @click="setDragLineStyle(3,'虚线')">
|
||||||
|
<div class="device-icon">↔️</div>
|
||||||
|
<div class="device-name">虚线</div>
|
||||||
|
</div>
|
||||||
|
<div class="device-item" @click="setDragLineStyle(4,'单箭头虚线')">
|
||||||
|
<div class="device-icon">↔️</div>
|
||||||
|
<div class="device-name">单箭头虚线</div>
|
||||||
|
</div>
|
||||||
|
<div class="device-item" @click="setDragLineStyle(5,'双箭头虚线')">
|
||||||
|
<div class="device-icon">⇄</div>
|
||||||
|
<div class="device-name">双箭头虚线</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Graph Container -->
|
||||||
|
<div
|
||||||
|
class="graph-container"
|
||||||
|
ref="container"
|
||||||
|
@dragover="handleDragOver"
|
||||||
|
@drop="handleDrop"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-layout {
|
||||||
|
display: flex;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar Styles */
|
||||||
|
.sidebar {
|
||||||
|
width: 200px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-right: 1px solid #e0e0e0;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-section {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-section h3 {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: grab;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-item:hover {
|
||||||
|
background-color: #f0f5ff;
|
||||||
|
border-color: #1890ff;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 4px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-item:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-right: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-name {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Graph Container Styles */
|
||||||
|
.graph-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
background-color: #fafafa;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drag and drop visual feedback */
|
||||||
|
.graph-container.drag-over {
|
||||||
|
background-color: #e6f7ff;
|
||||||
|
border: 2px dashed #1890ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1086
business-css/frontend/src/components/antvx6/index.vue
Normal file
15
business-css/frontend/src/types/graph.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// types/graph.ts
|
||||||
|
export type NodeType =
|
||||||
|
| 'cylinder_slot'
|
||||||
|
| 'flat_slot'
|
||||||
|
| 'ring_slot'
|
||||||
|
| 'tube_slot'
|
||||||
|
| 'bar_stock'
|
||||||
|
| 'tapered_head'
|
||||||
|
| 'monk_ring_slot';
|
||||||
|
|
||||||
|
export interface NodeItem {
|
||||||
|
type: NodeType;
|
||||||
|
label: string;
|
||||||
|
icon?: string; // 可选图标字符或SVG路径
|
||||||
|
}
|
||||||
@ -10,6 +10,8 @@ import { ElForm, ElMessage, ElMessageBox } from "element-plus";
|
|||||||
import { searchProjectsLsit,addProjects,updateProjects,deleteProjects,deleteBatchProjects} from "@/api/business/project";
|
import { searchProjectsLsit,addProjects,updateProjects,deleteProjects,deleteBatchProjects} from "@/api/business/project";
|
||||||
import Page from '@/components/Pagination/page.vue'
|
import Page from '@/components/Pagination/page.vue'
|
||||||
import ScenarioModel from '@/views/component/scenario/index.vue'
|
import ScenarioModel from '@/views/component/scenario/index.vue'
|
||||||
|
import Antvx6 from '@/components/antvx6/index.vue'
|
||||||
|
const isShowAntvx6 = ref(false); // 是否展示分析设计弹窗
|
||||||
const isScenario = ref(false) //是否展示历史模拟场景
|
const isScenario = ref(false) //是否展示历史模拟场景
|
||||||
// 搜索框
|
// 搜索框
|
||||||
const queryParams = ref({
|
const queryParams = ref({
|
||||||
@ -111,12 +113,16 @@ const rules = ref({
|
|||||||
code: [{ required: true, message: "请输入项目编码", trigger: "blur" }],
|
code: [{ required: true, message: "请输入项目编码", trigger: "blur" }],
|
||||||
});
|
});
|
||||||
//修改项目
|
//修改项目
|
||||||
function editClick(row: any) {
|
function editClick(row: any) { // 打开修改项目弹窗
|
||||||
title.value = "修改项目";
|
title.value = "修改项目";
|
||||||
info.value = JSON.parse(JSON.stringify(row));
|
info.value = JSON.parse(JSON.stringify(row));
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
}
|
}
|
||||||
function simulationClick(row: any) {
|
function designClick(row: any) { // 打开分析设计
|
||||||
|
info.value = JSON.parse(JSON.stringify(row));
|
||||||
|
isShowAntvx6.value = true;
|
||||||
|
}
|
||||||
|
function simulationClick(row: any) { // 打开模拟分析
|
||||||
info.value = JSON.parse(JSON.stringify(row));
|
info.value = JSON.parse(JSON.stringify(row));
|
||||||
isScenario.value = true;
|
isScenario.value = true;
|
||||||
}
|
}
|
||||||
@ -185,6 +191,12 @@ function dateFormat(row: any) {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
gettableData();
|
gettableData();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 关闭分析设计弹窗
|
||||||
|
function closeAntvx6() {
|
||||||
|
gettableData();
|
||||||
|
isShowAntvx6.value = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -224,7 +236,7 @@ onMounted(() => {
|
|||||||
<img src="@/assets/table/edit.png" alt="" title="修改"
|
<img src="@/assets/table/edit.png" alt="" title="修改"
|
||||||
@click="editClick(scope.row)" style="cursor: pointer; ">
|
@click="editClick(scope.row)" style="cursor: pointer; ">
|
||||||
<img src="@/assets/table/design.png" alt="" title="分析设计"
|
<img src="@/assets/table/design.png" alt="" title="分析设计"
|
||||||
style="cursor: pointer; ">
|
style="cursor: pointer; " @click="designClick(scope.row)">
|
||||||
<img src="@/assets/table/simulation.png" alt="" title="模拟分析"
|
<img src="@/assets/table/simulation.png" alt="" title="模拟分析"
|
||||||
@click="simulationClick(scope.row)"
|
@click="simulationClick(scope.row)"
|
||||||
style="cursor: pointer; ">
|
style="cursor: pointer; ">
|
||||||
@ -237,9 +249,7 @@ onMounted(() => {
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<Page :total="total" v-model:size="queryParams.size" v-model:current="queryParams.current" @pagination="gettableData()" ></Page>
|
<Page :total="total" v-model:size="queryParams.size" v-model:current="queryParams.current" @pagination="gettableData()" ></Page>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-dialog v-model="dialogVisible" :close-on-click-modal="false"
|
<el-dialog v-model="dialogVisible" :close-on-click-modal="false"
|
||||||
:modal="false" draggable :before-close="handleClose" :title="title"
|
:modal="false" draggable :before-close="handleClose" :title="title"
|
||||||
append-to-body width="620px" height="600px">
|
append-to-body width="620px" height="600px">
|
||||||
@ -267,7 +277,9 @@ onMounted(() => {
|
|||||||
<ScenarioModel v-if="isScenario" :projectInfo="info" ref="Scenario" />
|
<ScenarioModel v-if="isScenario" :projectInfo="info" ref="Scenario" />
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<div v-if="isShowAntvx6" style="position: fixed;top: 0px;left: 10px; width: 100vw;height: 100vh;background-color: #fff;z-index: 9999;">
|
||||||
|
<Antvx6 ref="antvx6" v-if="isShowAntvx6" :projectInfo="info" @closeAntvx6="closeAntvx6"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
15
business-css/frontend/types/graph.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// types/graph.ts
|
||||||
|
export type NodeType =
|
||||||
|
| 'cylinder_slot'
|
||||||
|
| 'flat_slot'
|
||||||
|
| 'ring_slot'
|
||||||
|
| 'tube_slot'
|
||||||
|
| 'bar_stock'
|
||||||
|
| 'tapered_head'
|
||||||
|
| 'monk_ring_slot';
|
||||||
|
|
||||||
|
export interface NodeItem {
|
||||||
|
type: NodeType;
|
||||||
|
label: string;
|
||||||
|
icon?: string; // 可选图标字符或SVG路径
|
||||||
|
}
|
||||||