JavaProjectRepo/business-css/frontend/src/components/antvx6/index.vue

1780 lines
46 KiB
Vue
Raw Normal View History

2026-01-13 15:11:39 +08:00
<!-- AntV G6 Graph Component -->
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ElMessage } from "element-plus";
import {
Clipboard,
Graph,
History,
Keyboard,
Selection,
Shape,
Snapline,
Stencil,
Transform
} from '@antv/x6'
2026-01-27 18:24:50 +08:00
2026-01-13 15:11:39 +08:00
// @ts-ignore
import insertCss from 'insert-css'
import { updateProjects} from "@/api/business/project";
import Createscenario from '@/views/component/scenario/createscenario.vue'
import ScenarioModel from '@/views/component/scenario/index.vue'
2026-01-13 17:39:09 +08:00
import img1 from '@/assets/x6/1.png'
import img2 from '@/assets/x6/2.png'
import img3 from '@/assets/x6/3.png'
import img4 from '@/assets/x6/4.png'
import img5 from '@/assets/x6/5.png'
import img6 from '@/assets/x6/6.png'
import img7 from '@/assets/x6/7.png'
2026-01-27 13:33:14 +08:00
import img11 from '@/assets/x6/11.png'
import img22 from '@/assets/x6/22.png'
import img33 from '@/assets/x6/33.png'
import img44 from '@/assets/x6/44.png'
import img55 from '@/assets/x6/55.png'
import img66 from '@/assets/x6/66.png'
import img77 from '@/assets/x6/77.png'
2026-01-13 17:39:09 +08:00
import line1 from '@/assets/x6/line1.png'
import line2 from '@/assets/x6/line2.png'
import line3 from '@/assets/x6/line3.png'
import line4 from '@/assets/x6/line4.png'
import line5 from '@/assets/x6/line5.png'
import line6 from '@/assets/x6/line6.png'
2026-01-16 10:12:14 +08:00
import AdddeviceModel from './adddevice.vue';
2026-01-16 14:43:40 +08:00
import EditdeviceModel from './editdevice.vue';
import MaterialModels from './materialmodel.vue';
2026-01-17 09:08:16 +08:00
import ChangesettingsModels from './changesettings.vue';
2026-01-16 10:12:14 +08:00
2026-01-13 15:11:39 +08:00
const emit = defineEmits([ 'closeAntvx6']);
const props = defineProps({
projectInfo: {
required: false,
type: Object,
default: {}
},
})
2026-01-26 08:30:09 +08:00
const isLock = ref(false) // 是否锁定
2026-01-16 10:12:14 +08:00
const deviceTypetype:any = ref('') // 设备类型
2026-01-16 14:43:40 +08:00
const isAdddevice = ref(false) // 是否添加设备
const isEditdevice = ref(false) // 是否编辑设备
const isMaterialModel = ref(false) // 是否物料信息
const projectInfo:any = ref(props.projectInfo) // 项目信息
const isScenario = ref(false) //是否展示历史模拟场景
2026-01-13 15:11:39 +08:00
const isDisplay = ref(true) // 是否显示
2026-01-15 17:27:26 +08:00
const isExpansionandcontraction = ref(false) // 是否显示展开收起按钮
2026-01-13 15:11:39 +08:00
// 为了协助代码演示
let graph: Graph
onMounted(() => {
preWork()
// #region 初始化画布
graph = new Graph({
container: document.getElementById('graph-container') as HTMLElement,
grid: true,
mousewheel: {
enabled: true,
zoomAtMousePosition: true,
modifiers: 'ctrl',
minScale: 0.5,
maxScale: 3,
},
connecting: {
router: 'manhattan',
connector: {
name: 'rounded',
args: {
radius: 8,
},
},
anchor: 'center',
connectionPoint: 'anchor',
allowBlank: false,
snap: {
radius: 20,
},
createEdge() {
return new Shape.Edge({
attrs: {
line: {
stroke: '#A2B1C3',
strokeWidth: 2,
strokeDasharray: 0,
targetMarker: {
name: 'block',
width: 12,
height: 8,
},
sourceMarker: null,
},
},
zIndex: 0,
})
},
validateConnection({ targetMagnet }) {
return !!targetMagnet
},
},
highlighting: {
magnetAdsorbed: {
name: 'stroke',
args: {
attrs: {
fill: '#5F95FF',
stroke: '#5F95FF',
},
},
},
},
2026-01-15 17:27:26 +08:00
interacting: function (cellView) {
// 获取当前节点的模型
const cell = cellView.cell
// 判断节点是否为 'rect' 形状
if (cell.shape === 'rect') {
// 如果是 'rect' 形状,则禁止节点移动
return { nodeMovable: false }
}
// 其他形状的节点允许所有交互
return true
}
// interacting: {
// nodeMovable: false, // 禁止所有节点移动
// },
2026-01-13 15:11:39 +08:00
})
// #endregion
// #region 使用插件
graph
2026-01-13 17:39:09 +08:00
.use(
2026-01-13 15:11:39 +08:00
new Transform({
resizing: false, // 通过拖拽边缘调整节点大小
2026-01-15 17:27:26 +08:00
rotating: false, // 允许旋转节点
2026-01-13 15:11:39 +08:00
// scaling 不是 Transform 插件的有效配置项,已移除
}),
)
.use(
new Selection({
rubberband: true,
showNodeSelectionBox: true,
}),
)
.use(new Snapline())
.use(new Keyboard())
.use(new Clipboard())
.use(new History())
// #endregion
// #region 初始化 stencil
const stencil = new Stencil({
title: '',
target: graph,
stencilGraphWidth: 300,
stencilGraphHeight: 180,
stencilGraphOptions: { panning: false },
collapsable: false,
groups: [
// {
// title: '基础流程图',
// name: 'group1',
// },
{
collapsable: false,
title: '设备',
name: 'group2',
graphHeight: 550,
layoutOptions: {
rowHeight: 120,
},
},
{
collapsable: false,
title: '管线',
name: 'group3',
2026-01-13 17:39:09 +08:00
graphHeight: 400,
2026-01-13 15:11:39 +08:00
layoutOptions: {
2026-01-13 17:39:09 +08:00
rowHeight: 120,
2026-01-13 15:11:39 +08:00
},
},
],
2026-01-27 18:24:50 +08:00
2026-01-13 15:11:39 +08:00
layoutOptions: {
columns: 2,
columnWidth: 140,
rowHeight: 100,
},
2026-01-27 18:24:50 +08:00
getDragNode: (sourceNode:any) => {
let node:any = sourceNode
if (node.data && node.data.lineStyle) {
// 应用管线样式到连接线
const lineStyle = node.data.lineStyle
graph.options.connecting.createEdge = () => {
return new Shape.Edge({
attrs: {
line: {
stroke: '#A2B1C3',
strokeWidth: 2,
strokeDasharray: lineStyle.strokeDasharray,
targetMarker: lineStyle.targetMarker,
sourceMarker: lineStyle.sourceMarker
}
},
zIndex: 0
})
}
// 移除管线节点,因为它只是一个样式选择器
setTimeout(() => {
graph.removeNode(node)
}, 100)
return
}
return sourceNode.clone({ deep: true })
},
getDropNode: (draggingNode) => {
return draggingNode.clone({ deep: true })
},
2026-01-13 15:11:39 +08:00
})
2026-01-27 18:24:50 +08:00
2026-01-13 17:39:09 +08:00
graph.on('node:contextmenu', ({ e, node }) => {
selectedNode.value = node
e.preventDefault()
// 显示自定义上下文菜单,包含箭头样式选项
const pos = node.position?.() || { x: 0, y: 0 }
showContextMenu(pos.x, pos.y)
})
2026-01-13 15:11:39 +08:00
2026-01-27 13:33:14 +08:00
function showContextMenu(x: number, y: number) {
left.value = x + 260
top.value = y - 50
isMenuShow.value = true
2026-01-13 17:39:09 +08:00
}
2026-01-27 13:33:14 +08:00
graph.on('node:click', (row:any) => {
let node:any = row.node
2026-01-28 10:08:39 +08:00
nodeId.value = node.id
2026-01-27 13:33:14 +08:00
if( node.store.data.shape == 'rect'){
2026-01-28 10:08:39 +08:00
2026-01-27 13:33:14 +08:00
styleInfo.value = {
name: node.store.data.attrs.label.text,
color: node.store.data.attrs.body.stroke,
line: node.store.data.attrs.body.strokeDasharray == ''?'实线':'虚线',
width: node.store.data.attrs.body.strokeWidth,
remark: node.store.data.remark
}
}
row.e.preventDefault()
})
2026-01-13 15:11:39 +08:00
// 监听Stencil内部的Dnd事件
document
.getElementById('stencil')
?.appendChild(stencil.container as HTMLElement)
// 监听Graph中节点创建事件
2026-01-15 17:27:26 +08:00
graph.on('node:added', (args:any) => {
2026-01-13 17:39:09 +08:00
console.log('节点已添加到画布', args)
2026-01-26 08:30:09 +08:00
if(isLock.value == true){
return
}
2026-01-27 13:33:14 +08:00
2026-01-13 17:39:09 +08:00
const { node } = args
// 检查是否是管线节点
if (node.data && node.data.lineStyle) {
// 应用管线样式到连接线
const lineStyle = node.data.lineStyle
graph.options.connecting.createEdge = () => {
return new Shape.Edge({
attrs: {
line: {
stroke: '#A2B1C3',
strokeWidth: 2,
strokeDasharray: lineStyle.strokeDasharray,
targetMarker: lineStyle.targetMarker,
sourceMarker: lineStyle.sourceMarker
}
},
zIndex: 0
})
}
// 移除管线节点,因为它只是一个样式选择器
setTimeout(() => {
graph.removeNode(node)
}, 100)
} else {
2026-01-15 17:27:26 +08:00
if(node.store && node.store.data && node.store.data.shape === 'rect'){
2026-01-27 13:33:14 +08:00
nodeId.value = node.id
styleInfo.value = {
name: node.store.data.attrs.label.text,
color: '#ff0000',
line: '虚线',
width: node.store.data.attrs.body.strokeWidth,
remark: ''
}
node.store.data.remark = ''
2026-01-15 17:27:26 +08:00
return
}
2026-01-27 13:33:14 +08:00
node.size(160, 160)
// 去掉背景节点
node.attr('body/fill', 'none')
// 图片居中
node.attr('image/refX', 0)
node.attr('image/refY', 0)
// node.attr('label/refX', 40)
node.attr('image/width', 160) // 修改宽度
node.attr('image/height', 160) // 修改高度
2026-01-16 10:12:14 +08:00
nodeId.value = node.id
2026-01-27 13:33:14 +08:00
if(node.store.data.attrs.text.text == '圆柱槽'){
2026-01-16 10:12:14 +08:00
deviceTypetype.value = 'CylindricalTank'
2026-01-27 13:33:14 +08:00
node.size(110, 140)
const width = node.size().width
node.attr('image/xlink:href', img11)
node.attr('image/width', 105)
node.attr('image/height', 135)
node.attr('label/refY', 140)
node.attr('label/refX', width/2)
node.attr('label/textAnchor', 'middle')
}else if(node.store.data.attrs.text.text == '扁平槽'){
deviceTypetype.value = 'FlatTank'
node.size(130, 80)
const width = node.size().width
node.attr('image/xlink:href', img22)
node.attr('image/width', 130)
node.attr('image/height', 80)
node.attr('label/refY', 85)
node.attr('label/refX', width/2)
node.attr('label/textAnchor', 'middle')
2026-01-16 10:12:14 +08:00
}else if(node.store.data.attrs.text.text == '环形槽'){
deviceTypetype.value = 'AnnularTank'
2026-01-27 13:33:14 +08:00
node.size(110, 140)
const width = node.size().width
node.attr('image/xlink:href', img33)
node.attr('image/width', 105)
node.attr('image/height', 135)
node.attr('label/refY', 140)
node.attr('label/refX', width/2)
node.attr('label/textAnchor', 'middle')
2026-01-16 10:12:14 +08:00
}else if(node.store.data.attrs.text.text == '管束槽'){
deviceTypetype.value = 'TubeBundleTank'
2026-01-27 13:33:14 +08:00
node.size(110, 140)
const width = node.size().width
node.attr('image/xlink:href', img44)
node.attr('image/width', 105)
node.attr('image/height', 135)
node.attr('label/refY', 140)
node.attr('label/refX', width/2)
node.attr('label/textAnchor', 'middle')
2026-01-16 10:12:14 +08:00
}else if(node.store.data.attrs.text.text == '萃取柱'){
deviceTypetype.value = 'ExtractionColumn'
2026-01-27 13:33:14 +08:00
node.size(30, 140)
const width = node.size().width
node.attr('image/xlink:href', img55)
node.attr('image/width', 30)
node.attr('image/height', 135)
node.attr('label/refY', 140)
node.attr('label/refX', width/2)
node.attr('label/textAnchor', 'middle')
2026-01-16 10:12:14 +08:00
}else if(node.store.data.attrs.text.text == '流化床'){
deviceTypetype.value = 'FluidizedBed'
2026-01-27 13:33:14 +08:00
node.size(80, 140)
const width = node.size().width
node.attr('image/xlink:href', img66)
node.attr('image/width', 80)
node.attr('image/height', 135)
node.attr('label/refY', 140)
node.attr('label/refX', width/2)
node.attr('label/textAnchor', 'middle')
2026-01-16 10:12:14 +08:00
}else if(node.store.data.attrs.text.text == '锥底环形槽'){
deviceTypetype.value = 'ACFTank'
2026-01-27 13:33:14 +08:00
node.size(80, 140)
const width = node.size().width
node.attr('image/xlink:href', img77)
node.attr('image/width', 80)
node.attr('image/height', 135)
node.attr('label/refY', 140)
node.attr('label/refX', width/2)
node.attr('label/textAnchor', 'middle')
2026-01-16 10:12:14 +08:00
}
2026-01-13 17:39:09 +08:00
// 设置固定大小
2026-01-27 13:33:14 +08:00
2026-01-13 17:39:09 +08:00
// 删除节点上的文字
2026-01-27 13:33:14 +08:00
// node.attr('text/text', '')
// node.attr('label/text', '')
2026-01-16 10:12:14 +08:00
isAdddevice.value = true
2026-01-13 15:11:39 +08:00
}
2026-01-13 17:39:09 +08:00
})
2026-01-13 15:11:39 +08:00
// #endregion
// #region 快捷键与事件
graph.bindKey(['meta+c', 'ctrl+c'], () => {
const cells = graph.getSelectedCells()
if (cells.length) {
graph.copy(cells)
}
return false
})
graph.bindKey(['meta+x', 'ctrl+x'], () => {
const cells = graph.getSelectedCells()
if (cells.length) {
graph.cut(cells)
}
return false
})
graph.bindKey(['meta+v', 'ctrl+v'], () => {
if (!graph.isClipboardEmpty()) {
const cells = graph.paste({ offset: 32 })
graph.cleanSelection()
graph.select(cells)
}
return false
})
// undo redo
graph.bindKey(['meta+z', 'ctrl+z'], () => {
2026-01-27 13:33:14 +08:00
debugger
2026-01-13 15:11:39 +08:00
if (graph.canUndo()) {
graph.undo()
}
return false
})
graph.bindKey(['meta+shift+z', 'ctrl+shift+z'], () => {
if (graph.canRedo()) {
graph.redo()
}
return false
})
// select all
graph.bindKey(['meta+a', 'ctrl+a'], () => {
const nodes = graph.getNodes()
if (nodes) {
graph.select(nodes)
}
})
2026-01-27 13:33:14 +08:00
graph.bindKey('delete', () => {
debugger
2026-01-13 15:11:39 +08:00
const cells = graph.getSelectedCells()
if (cells.length) {
graph.removeCells(cells)
}
})
// zoom
graph.bindKey(['ctrl+1', 'meta+1'], () => {
const zoom = graph.zoom()
if (zoom < 1.5) {
graph.zoom(0.1)
}
})
graph.bindKey(['ctrl+2', 'meta+2'], () => {
const zoom = graph.zoom()
if (zoom > 0.5) {
graph.zoom(-0.1)
}
})
2026-01-15 17:27:26 +08:00
let startPoint:any = null // 矩形起点
// 监听鼠标按下事件:记录起点
graph.on('blank:mousedown', (e) => {
startPoint = { x: e.x, y: e.y }
})
// 监听鼠标松开事件:绘制矩形
graph.on('blank:mouseup', (e) => {
console.log('鼠标松开事件', e)
if (!startPoint) return
const endPoint = { x: e.x, y: e.y }
if(Math.abs(endPoint.x - startPoint.x) <20 && Math.abs(endPoint.y - startPoint.y) <20){
return
}
const width = Math.abs(endPoint.x - startPoint.x)
const height = Math.abs(endPoint.y - startPoint.y)
const x = Math.min(startPoint.x, endPoint.x)
const y = Math.min(startPoint.y, endPoint.y)
// 添加矩形节点(设置 zIndex 为 -1 确保在最底层)
graph.addNode({
shape: 'rect',
x,
y,
width,
height,
zIndex: -1, // 关键:置于底层
// interactive: false, // 禁用所有交互
// selectable: false, // 明确禁止选中
draggable: false, // 明确禁止移动
interacting: {
nodeMovable: false, // 禁止所有节点移动
},
attrs: {
body: {
fill: 'transparent',
2026-01-27 13:33:14 +08:00
stroke: '#ff0000',
2026-01-15 17:27:26 +08:00
strokeWidth: 1,
strokeDasharray: '5 5', // 设置虚线样式
},
2026-01-27 13:33:14 +08:00
label: {
text: '系统边界',
refX: '50%',
refY: -15,
textAnchor: 'middle',
dominantBaseline: 'middle', // 文本自身的垂直居中
fontSize: 15,
fill: '#333333'
}
2026-01-15 17:27:26 +08:00
},
})
startPoint = null // 重置起点
})
2026-01-13 15:11:39 +08:00
// 控制连接桩显示/隐藏
const showPorts = (ports: NodeListOf<SVGElement>, show: boolean) => {
for (let i = 0, len = ports.length; i < len; i += 1) {
ports[i].style.visibility = show ? 'visible' : 'hidden'
}
}
graph.on('node:mouseenter', () => {
const container = document.getElementById('graph-container') as HTMLElement
const ports = container.querySelectorAll(
'.x6-port-body',
) as NodeListOf<SVGElement>
showPorts(ports, true)
})
graph.on('node:mouseleave', () => {
const container = document.getElementById('graph-container') as HTMLElement
const ports = container.querySelectorAll(
'.x6-port-body',
) as NodeListOf<SVGElement>
showPorts(ports, false)
})
// #endregion
// #region 初始化图形
const ports = {
groups: {
top: {
position: 'top',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
right: {
position: 'right',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
bottom: {
position: 'bottom',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
left: {
position: 'left',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
},
items: [
{
group: 'top',
},
{
group: 'right',
},
{
group: 'bottom',
},
{
group: 'left',
},
],
}
Graph.registerNode(
'custom-image',
{
inherit: 'rect',
width: 130,
height: 100,
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'image',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
body: {
stroke: 'transparent',
fill: '#f8f8f8',
},
image: {
width: 62,
height:84,
refX: 35,
refY: 0,
},
label: {
refX: 45,
refY: 80,
textAnchor: 'start',
textVerticalAnchor: 'top',
fontSize: 14,
fill: '#000',
},
},
ports: { ...ports },
},
true,
)
2026-01-13 17:39:09 +08:00
const imageShapes = [
2026-01-13 15:11:39 +08:00
{
label: '圆柱槽',
2026-01-13 17:39:09 +08:00
image: img1,
2026-01-13 15:11:39 +08:00
},
{
label: '扁平槽',
2026-01-13 17:39:09 +08:00
image:img2,
2026-01-13 15:11:39 +08:00
},
{
label: '环形槽',
2026-01-13 17:39:09 +08:00
image:img3,
2026-01-13 15:11:39 +08:00
},
{
label: '管束槽',
2026-01-13 17:39:09 +08:00
image:img4,
2026-01-13 15:11:39 +08:00
},
{
label: '萃取柱',
2026-01-13 17:39:09 +08:00
image:img5,
2026-01-13 15:11:39 +08:00
},
{
label: '流化床',
2026-01-13 17:39:09 +08:00
image:img6,
2026-01-13 15:11:39 +08:00
},{
label: '锥底环形槽',
2026-01-13 17:39:09 +08:00
image:img7,
2026-01-13 15:11:39 +08:00
},
]
const imageNodes = imageShapes.map((item) =>
graph.createNode({
shape: 'custom-image',
label: item.label,
attrs: {
image: {
'xlink:href': item.image,
},
},
}),
)
stencil.load(imageNodes, 'group2')
// 创建管线节点
const lineShapes = [
{
label: '实线',
2026-01-13 17:39:09 +08:00
image: line1,
2026-01-13 15:11:39 +08:00
targetMarker: null,
sourceMarker: null,
strokeDasharray: 0
},
{
label: '单箭头实线',
2026-01-13 17:39:09 +08:00
image: line2,
2026-01-13 15:11:39 +08:00
targetMarker: {
name: 'block',
width: 12,
height: 8
},
sourceMarker: null,
strokeDasharray: 0
},
{
label: '双箭头实线',
2026-01-13 17:39:09 +08:00
image: line3,
2026-01-13 15:11:39 +08:00
targetMarker: {
name: 'block',
width: 12,
height: 8
},
sourceMarker: {
name: 'block',
width: 12,
height: 8
},
strokeDasharray: 0
},
{
label: '虚线',
2026-01-13 17:39:09 +08:00
image: line4,
2026-01-13 15:11:39 +08:00
targetMarker: null,
sourceMarker: null,
strokeDasharray: 5
},
{
label: '单箭头虚线',
2026-01-13 17:39:09 +08:00
image: line5,
2026-01-13 15:11:39 +08:00
targetMarker: {
name: 'block',
width: 12,
height: 8
},
sourceMarker: null,
strokeDasharray: 5
},
{
label: '双箭头虚线',
2026-01-13 17:39:09 +08:00
image: line6,
2026-01-13 15:11:39 +08:00
targetMarker: {
name: 'block',
width: 12,
height: 8
},
sourceMarker: {
name: 'block',
width: 12,
height: 8
},
strokeDasharray: 5
}
]
const lineNodes = lineShapes.map((item) => {
// 创建一个节点来显示管线样式
const node = graph.createNode({
shape: 'custom-image',
label: item.label,
2026-01-13 17:39:09 +08:00
width: 130,
height: 100,
2026-01-13 15:11:39 +08:00
attrs: {
image: {
'xlink:href': item.image,
2026-01-13 17:39:09 +08:00
width: 31,
height: 31,
refX: 50,
refY: 20
2026-01-13 15:11:39 +08:00
},
body: {
stroke: 'transparent',
2026-01-13 17:39:09 +08:00
fill: '#f8f8f8'
2026-01-13 15:11:39 +08:00
},
label: {
text: item.label,
refX: 60,
2026-01-13 17:39:09 +08:00
refY: 65,
2026-01-13 15:11:39 +08:00
textAnchor: 'middle',
fontSize: 12,
fill: '#666'
}
},
data: {
lineStyle: {
strokeDasharray: item.strokeDasharray,
targetMarker: item.targetMarker,
sourceMarker: item.sourceMarker
}
}
})
return node
})
stencil.load(lineNodes, 'group3')
2026-01-13 17:39:09 +08:00
if (!graph || !projectInfo.value || !projectInfo.value.topology) return;
graph.clearCells();
const topology:any = JSON.parse(projectInfo.value.topology)
if(!topology.designData)return
graph.fromJSON(topology.designData);
2026-01-13 15:11:39 +08:00
// #endregion
})
function preWork() {
// 这里协助演示的代码,在实际项目中根据实际情况进行调整
const container = document.getElementById('container') as HTMLElement
const stencilContainer = document.createElement('div')
stencilContainer.id = 'stencil'
const graphContainer = document.createElement('div')
graphContainer.id = 'graph-container'
container.appendChild(stencilContainer)
container.appendChild(graphContainer)
insertCss(`
#container {
display: flex;
border: 1px solid #dfe3e8;
width: 100vw;
height: calc(100vh - 60px);
}
#stencil {
width: 300px;
height: 100%;
position: relative;
border-right: 1px solid #dfe3e8;
}
.x6-widget-stencil-content{
top:0px !important;
}
.x6-widget-stencil-title{
display: none;
}
2026-01-22 10:58:03 +08:00
.x6-container-editbox #graph-container {
2026-01-13 15:11:39 +08:00
width: calc(100% - 180px);
2026-01-22 10:58:03 +08:00
height: 100% !important;
2026-01-21 09:57:53 +08:00
background-color: #f1f3fe;
2026-01-13 15:11:39 +08:00
}
.x6-widget-stencil {
background-color: #fff;
}
.x6-widget-stencil-title {
background-color: #fff;
}
.x6-widget-stencil-group-title {
background-color: #fff !important;
}
.x6-widget-transform {
margin: -1px 0 0 -1px;
padding: 0px;
border: 1px solid #239edd;
}
.x6-widget-transform > div {
border: 1px solid #239edd;
}
.x6-widget-transform > div:hover {
background-color: #3dafe4;
}
.x6-widget-transform-active-handle {
background-color: #3dafe4;
}
.x6-widget-transform-resize {
border-radius: 0;
}
.x6-widget-selection-inner {
border: 1px solid #239edd;
}
.x6-widget-selection-box {
opacity: 0;
}
`)
}
const left = ref(0)
const top = ref(0)
const isMenuShow = ref(false)
const selectedNode:any = ref(null)
function deleteNode() { // 删除节点
graph.removeNode(selectedNode.value)
isMenuShow.value = false
}
function copyNode() { // 复制节点
if (selectedNode.value) {
try {
// 获取当前节点的位置
const position = selectedNode.value.position()
// 创建新节点,使用节点的原始数据
const newNode = selectedNode.value.clone()
// 设置新位置偏移50px
newNode.position(position.x + 50, position.y + 50)
2026-01-26 08:30:09 +08:00
deviceTypetype.value = newNode.store.data.deviceInfo.type
2026-01-13 15:11:39 +08:00
// 添加到画布
graph.addNode(newNode)
2026-01-26 08:30:09 +08:00
//
// :deviceId="nodeId" :projectInfo="projectInfo"
// :deviceTypetype="deviceTypetype"
2026-01-13 15:11:39 +08:00
isMenuShow.value = false
} catch (error) {
console.error('节点复制失败:', error)
}
}
}
function closeAntvx6() {
emit('closeAntvx6')
}
2026-01-17 09:08:16 +08:00
2026-01-13 17:39:09 +08:00
function revokeClick(){
2026-01-26 08:30:09 +08:00
if (graph.canUndo()) {
2026-01-27 13:33:14 +08:00
isLock.value = true
2026-01-26 08:30:09 +08:00
graph.undo()
2026-01-27 13:33:14 +08:00
setTimeout(() => {
isLock.value = false
}, 500);
2026-01-26 08:30:09 +08:00
}
2026-01-13 17:39:09 +08:00
}
function removeClick(){
2026-01-22 10:58:03 +08:00
if (graph.canRedo()) {
2026-01-26 08:30:09 +08:00
isLock.value = true
2026-01-22 10:58:03 +08:00
graph.redo()
2026-01-26 08:30:09 +08:00
setTimeout(() => {
isLock.value = false
}, 500);
2026-01-22 10:58:03 +08:00
}
2026-01-13 17:39:09 +08:00
}
function bigClick(){
graph.zoom(0.1)
}
function smallClick(){
graph.zoom(-0.1)
}
2026-01-16 10:12:14 +08:00
const nodeId = ref("")
function analysisAdd(){
dialogVisible.value = true;
}
const dialogVisible = ref(false)
function closeCreatescenario(){ // 关闭新增模拟分析弹窗
dialogVisible.value = false;
}
function simulationClick() { // 打开模拟分析
isScenario.value = true;
}
//取消按钮
function handleClose() {
isScenario.value = false;
}
2026-01-16 10:12:14 +08:00
function dialogAdddevice(){ // 打开新增设备弹窗
graph.undo()
isAdddevice.value = false;
}
const selectedDevice = ref({})
function closeAdddevice(e:any){ // 关闭新增设备弹窗
selectedDevice.value = e
let retrievedNode:any = graph.getCellById(nodeId.value)
2026-01-26 08:30:09 +08:00
retrievedNode.attr('text/text', e.name)
retrievedNode.attr('label/text', e.name)
retrievedNode.store.data.deviceInfo = e
2026-01-21 13:50:10 +08:00
saveDesign()
2026-01-16 10:12:14 +08:00
isAdddevice.value = false;
}
2026-01-16 14:43:40 +08:00
const deviceInfo:any = ref({}) // 是否编辑设备
function EditdeviceClick(){ // 打开编辑设备弹窗
deviceInfo.value = selectedNode.value.store.data.deviceInfo
nodeId.value = selectedNode.value.id
if(deviceInfo.value != null){
deviceTypetype.value = deviceInfo.value.type
}
isEditdevice.value = true;
}
function closeEditdevice(e:any){ // 关闭编辑设备弹窗
selectedDevice.value = e
let retrievedNode:any = graph.getCellById(nodeId.value)
retrievedNode.store.data.deviceInfo = e
2026-01-26 08:30:09 +08:00
retrievedNode.attr('text/text', e.name)
retrievedNode.attr('label/text', e.name)
2026-01-16 14:43:40 +08:00
isEditdevice.value = false;
2026-01-22 10:58:03 +08:00
saveDesign()
2026-01-16 14:43:40 +08:00
}
function dialogEditdevice(){ // 关闭设备弹窗
isEditdevice.value = false;
}
const materialInfo:any = ref({}) // 是否物料信息
const selectedMaterial:any = ref({})
function MaterialModelClick(){ // 打开物料信息弹窗
deviceInfo.value = selectedNode.value.store.data.deviceInfo
materialInfo.value = {}
if(selectedNode.value.store.data.materialInfo != null){
materialInfo.value = selectedNode.value.store.data.materialInfo
}
nodeId.value = selectedNode.value.id
if(materialInfo.value != null){
deviceTypetype.value = materialInfo.value.type
}
isMaterialModel.value = true;
}
function dialogMaterialModel(){ // 关闭物料信息弹窗
isMaterialModel.value = false;
}
function closeMaterialModel(e:any){ // 关闭物料信息弹窗
if(e == false){
2026-01-17 09:08:16 +08:00
isMaterialModel.value = false;
2026-01-16 14:43:40 +08:00
return
}
selectedMaterial.value = e
let retrievedNode:any = graph.getCellById(nodeId.value)
retrievedNode.store.data.materialInfo = e
2026-01-21 13:50:10 +08:00
saveDesign()
2026-01-17 09:08:16 +08:00
isMaterialModel.value = false;
}
const isChangesettings = ref(false)
const changesettingsData:any = ref([]) // 是否变动设置
function dialogChangesettings(){ // 关闭变动设置弹窗
isChangesettings.value = false;
}
function ChangesettingsClick(){ // 打开变动设置弹窗
changesettingsData.value = []
if(selectedNode.value.store.data.deviceInfo == null){
ElMessage({
type: "error",
message: "请先添加设备信息",
});
return
}
if(selectedNode.value.store.data.materialInfo == null){
ElMessage({
type: "error",
message: "请先添加物料信息",
});
return
}
deviceInfo.value = selectedNode.value.store.data.deviceInfo
materialInfo.value = selectedNode.value.store.data.materialInfo
if(selectedNode.value.store.data.changesettings != null){
changesettingsData.value = selectedNode.value.store.data.changesettings
}
nodeId.value = selectedNode.value.id
if(materialInfo.value != null){
deviceTypetype.value = materialInfo.value.type
}
isChangesettings.value = true;
}
function closeChangesettingsModel(e:any){ // 关闭变动设置弹窗
if(e == false){
isChangesettings.value = false;
return
}
selectedMaterial.value = e
let retrievedNode:any = graph.getCellById(nodeId.value)
retrievedNode.store.data.changesettings = e
isChangesettings.value = false;
}
function setFormulaInit(formula:any) {
// const formula = "【(圆柱槽B)*0.2+(环形槽A)*1.0+0.5】";
// 匹配*号后、+或)前的数字(支持整数和小数)
2026-01-22 10:58:03 +08:00
const regex = /([\*\/])(\d+\.?\d*)/g;
2026-01-17 09:08:16 +08:00
const coefficients = [];
let match;
// 循环提取所有匹配项
while ((match = regex.exec(formula)) !== null) {
2026-01-22 10:58:03 +08:00
if(!isNaN(parseFloat(match[1]))){
coefficients.push(parseFloat(match[1])); // 转为数字类型
}else if(!isNaN(parseFloat(match[2]))){
coefficients.push(parseFloat(match[2])); // 转为数字类型
}
2026-01-17 09:08:16 +08:00
}
console.log(coefficients); // 输出: [0.2, 1.0]
return coefficients
}
function getBias(formula:any){
// 从后往前找最后一个+的位置
2026-01-17 12:32:55 +08:00
// 匹配最后一个+号后面的数字(支持整数、小数)
2026-01-22 10:58:03 +08:00
if (formula.includes(' - ')){
const parts = formula.split(' - ');
// 处理以点开头的文件(如.gitignore
return parseFloat('-' + parts.pop().toLowerCase()) ;
};
if (formula.includes(' + ')){
const parts = formula.split(' + ');
// 处理以点开头的文件(如.gitignore
return parseFloat('+' + parts.pop().toLowerCase()) ;
};
return 0
// 从最后一个点分割,取后面的部分
2026-01-17 09:08:16 +08:00
}
2026-01-27 13:33:14 +08:00
const styleInfo:any = ref({
name: '',
color: '#409EFF',
line: '',
width: '',
remark: ''
})
function inputChange (e:any){
let node:any = graph.getCellById(nodeId.value)
debugger
node.attr('label/text', e)
}
function lineChange(e:any){
let node:any = graph.getCellById(nodeId.value)
if(e == '实线'){
node.attr('body/strokeDasharray', '')
}else{
node.attr('body/strokeDasharray', '5 5')
}
}
function widthChange(e:any){
let node:any = graph.getCellById(nodeId.value)
node.attr('body/strokeWidth', e)
}
function colorChangs(e:any){
let node:any = graph.getCellById(nodeId.value)
node.attr('body/stroke', e)
}
function remarkChange(e:any){
let node:any = graph.getCellById(nodeId.value)
node.store.data.remark = e
}
2026-01-17 09:08:16 +08:00
function saveDesign() { // 保存设计
try {
// 获取画布内容并转换为JSON
const designData:any = graph.toJSON()
let cells:any = []
if(designData !=null && designData.cells.length>0){
cells = designData.cells
}
let devices = []
for(let i=0;i<cells.length;i++){
if(cells[i].shape == 'custom-image'){
let tempData:any = {}
if(cells[i].changesettings && cells[i].changesettings.length > 0){
for(let j=0;j<cells[i].changesettings.length;j++){
if(cells[i].changesettings[j].formula != '' && cells[i].changesettings[j].formula != null){
const key = cells[i].changesettings[j].name as string
const unit = cells[i].changesettings[j].unit
2026-01-17 12:32:55 +08:00
const name = cells[i].changesettings[j].name
const delay= cells[i].changesettings[j].delay
2026-01-17 09:08:16 +08:00
let tempSources = []
for(let k=0;k<setFormulaInit(cells[i].changesettings[j].formula).length;k++){
2026-01-22 10:58:03 +08:00
// name: '('+getName(key).name +')',
// key: getName(key).key,
// type: getName(key).type,
// parentName : getName(key).type == 'device' ? '('+item.deviceName +')' : '('+item.materialName +')',
// parentId : getName(key).type == 'device' ? item.deviceId : item.materialId
2026-01-26 08:30:09 +08:00
2026-01-22 10:58:03 +08:00
if(cells[i].changesettings[j].deviceMaterialData[k] == null){
return
}
2026-01-17 09:08:16 +08:00
tempSources.push({
2026-01-22 10:58:03 +08:00
entityType: cells[i].changesettings[j].deviceMaterialData[k].type, //圆柱槽B
entityId: cells[i].changesettings[j].deviceMaterialData[k].parentId , //materialId : cdeeca2e-1e0c-4bdd-b946-f347c104752c
property: cells[i].changesettings[j].deviceMaterialData[k].key, // 铀浓度g/L
2026-01-17 09:08:16 +08:00
coefficient: setFormulaInit(cells[i].changesettings[j].formula)[k], //0.2
2026-01-17 12:32:55 +08:00
delay: { enabled: true, time: delay, unit: 's' } // 5
2026-01-17 09:08:16 +08:00
})
}
tempData[key] = {
name: tempData[key],
type:'influence',
unit:unit,
base:0,
bias: getBias(cells[i].changesettings[j].formula) || 0,
sources:tempSources,
expression:cells[i].changesettings[j].formula
}
}
}
}
2026-01-26 08:30:09 +08:00
if(cells[i].deviceInfo != null){
devices.push({
deviceId:cells[i].id,
name:cells[i].deviceInfo.name,
type:cells[i].deviceInfo.type,
properties:{},
material: cells[i].materialInfo != null ? {
materialId:cells[i].id,
name:cells[i].materialInfo.name,
properties:tempData
}:{}
})
}
2026-01-17 09:08:16 +08:00
}
}
2026-01-22 10:58:03 +08:00
console.log(devices)
2026-01-17 09:08:16 +08:00
const topology = {
projectId: projectInfo.value.projectId,
name: projectInfo.value.name,
devices:devices,
pipelines:[],
systemboundaries:[],
globalDisplay:{
device:{
showProperties:[]
},
material:{
showProperties:[]
},
},
designData:cells
}
// 添加保存信息
const saveData = {
projectId: projectInfo.value.projectId,
topology: JSON.stringify(topology)
}
updateProjects(saveData).then((res:any) => {
if(res === true){
ElMessage({
type: "success",
message: "保存成功",
});
}
});
// 返回保存的数据
return saveData
} catch (error) {
console.error('保存设计失败:', error)
return null
}
2026-01-16 14:43:40 +08:00
}
2026-01-13 15:11:39 +08:00
</script>
<template>
2026-01-26 08:30:09 +08:00
<div class="app-layout" @click="isMenuShow = false">
2026-01-13 15:11:39 +08:00
<div class="antvx6-header">
<div class="header-left-box">
2026-01-15 17:27:26 +08:00
<div class="return-icon-box" @click="closeAntvx6" title="返回工作台">
<img src="@/assets/x6/return.png" alt="图标" style="cursor: pointer;">
2026-01-13 15:11:39 +08:00
</div>
<div class="project-name">{{ projectInfo.name }}</div>
2026-01-15 17:27:26 +08:00
<div class="return-icon-box" @click="analysisAdd" title="新增模拟分析">
<img src="@/assets/x6/add.png" alt="图标" style="cursor: pointer;">
2026-01-13 15:11:39 +08:00
</div>
2026-01-15 17:27:26 +08:00
<div class="return-icon-box" @click="simulationClick" title="历史模拟分析">
<img src="@/assets/x6/history.png" alt="图标" style="cursor: pointer;">
2026-01-13 15:11:39 +08:00
</div>
</div>
<div class="header-content-box">
2026-01-13 17:39:09 +08:00
<div class="operation-icon-box" @click="revokeClick">
2026-01-13 15:11:39 +08:00
<img src="@/assets/x6/revoke.png">
<div class="operation-icon-text">撤销</div>
</div>
2026-01-13 17:39:09 +08:00
<div class="operation-icon-box" @click="removeClick">
2026-01-13 15:11:39 +08:00
<img src="@/assets/x6/redo.png">
<div class="operation-icon-text">重做</div>
</div>
2026-01-13 17:39:09 +08:00
<div class="operation-icon-box" @click="bigClick">
2026-01-13 15:11:39 +08:00
<img src="@/assets/x6/magnify.png">
<div class="operation-icon-text">放大</div>
</div>
2026-01-13 17:39:09 +08:00
<div class="operation-icon-box" @click="smallClick">
2026-01-13 15:11:39 +08:00
<img src="@/assets/x6/reduce.png">
<div class="operation-icon-text">缩小</div>
</div>
<div class="operation-icon-box" @click="isDisplay = !isDisplay">
<img v-if="isDisplay" src="@/assets/x6/display.png">
<img v-else src="@/assets/x6/hide.png">
<div class="operation-icon-text">显示</div>
</div>
<div class="operation-icon-box" @click="saveDesign">
<img src="@/assets/x6/save.png">
<div class="operation-icon-text">保存</div>
</div>
</div>
<div class="header-left-box"></div>
</div>
<div id="container" style="position: relative;">
<div class="context-menu" v-if="isMenuShow" :style="{left: left +'px', top: top+'px'}">
2026-01-16 14:43:40 +08:00
<img src="@/assets/x6/info.png" alt="图标" title="设备信息" style="cursor: pointer;" @click="EditdeviceClick">
<img src="@/assets/x6/material.png" alt="图标" title="物料信息" style="cursor: pointer;" @click="MaterialModelClick">
2026-01-17 09:08:16 +08:00
<img src="@/assets/x6/change.png" alt="图标" title="变动设置" style="cursor: pointer;" @click="ChangesettingsClick">
<img src="@/assets/x6/copy.png" alt="图标" title="复制" style="cursor: pointer;" @click="copyNode">
<img src="@/assets/x6/del.png" alt="图标" title="删除" style="cursor: pointer;"
@click="deleteNode">
</div>
2026-01-27 13:33:14 +08:00
<div class="line-style-box">
<!-- <div class="DisplaySettingsButton">显示设置</div> -->
2026-01-15 17:27:26 +08:00
<div class="expansionandcontraction-box" v-if="isExpansionandcontraction == false" @click="isExpansionandcontraction = true">
<img src="@/assets/x6/expansionandcontraction-left.png">
</div>
<div v-if="isExpansionandcontraction == true" style="display: flex;align-items: center;">
<div class="expansionandcontraction-box" @click="isExpansionandcontraction = false">
<img src="@/assets/x6/expansionandcontraction-right.png">
</div>
<div class="style-content-box">
<div class="style-content-box-title">样式</div>
2026-01-27 13:33:14 +08:00
<div class="style-content-box-display">
<div class="style-content-box-key">名称</div>
<el-input v-model="styleInfo.name" style="width:200px" placeholder="" @input="inputChange"></el-input>
</div>
<div class="style-content-box-display">
<div class="style-content-box-key">线形</div>
<el-select v-model="styleInfo.line" style="width:200px" @change="lineChange">
<el-option label="实线" value="实线" />
<el-option label="虚线" value="虚线" />
</el-select>
</div>
<div class="style-content-box-display">
<div class="style-content-box-key">线宽</div>
<el-select v-model="styleInfo.width" style="width:200px" @change="widthChange">
<el-option label="1" value="1" />
<el-option label="2" value="2" />
<el-option label="3" value="3" />
<el-option label="4" value="4" />
<el-option label="5" value="5" />
<el-option label="6" value="6" />
<el-option label="7" value="7" />
<el-option label="8" value="8" />
<el-option label="9" value="9" />
<el-option label="10" value="10" />
</el-select>
</div>
<div class="style-content-box-display">
<div class="style-content-box-key">边线颜色</div>
<el-color-picker v-model="styleInfo.color" @change="colorChangs"></el-color-picker>
</div>
<div class="style-content-box-display" style="align-items: self-start;">
<div class="style-content-box-key" style="padding-top: 5px;">描述信息</div>
<el-input type="textarea" :rows="4" v-model="styleInfo.remark" style="width:200px" @input="remarkChange"></el-input>
</div>
2026-01-15 17:27:26 +08:00
</div>
</div>
2026-01-27 13:33:14 +08:00
</div>
2026-01-13 15:11:39 +08:00
</div>
<Createscenario v-if="dialogVisible" :projectInfo="projectInfo" @closeCreatescenario ="closeCreatescenario"/>
<el-dialog v-model="isScenario" :close-on-click-modal="false"
:modal="false" draggable :before-close="handleClose" title="历史模拟分析"
append-to-body width="1142px">
<ScenarioModel v-if="isScenario" :projectInfo="projectInfo" ref="Scenario" />
</el-dialog>
2026-01-16 10:12:14 +08:00
<el-dialog v-model="isAdddevice" :close-on-click-modal="false"
:modal="false" draggable :before-close="dialogAdddevice" title="添加设备"
append-to-body width="1050px">
<AdddeviceModel v-if="isAdddevice == true" :deviceId="nodeId" :projectInfo="projectInfo"
:deviceTypetype="deviceTypetype" ref="Adddevice" @closeAdddevice="closeAdddevice"/>
</el-dialog>
2026-01-16 14:43:40 +08:00
2026-01-21 13:50:10 +08:00
<el-dialog v-model="isEditdevice" :close-on-click-modal="false" :modal="false" draggable :before-close="dialogEditdevice" title="设备信息" append-to-body width="1050px">
2026-01-16 14:43:40 +08:00
<EditdeviceModel v-if="isEditdevice == true" :deviceId="nodeId" :deviceInfo="deviceInfo"
:deviceTypetype="deviceTypetype" ref="Editdevice" @closeEditdevice="closeEditdevice"/>
</el-dialog>
2026-01-17 09:08:16 +08:00
<el-dialog v-model="isMaterialModel" :close-on-click-modal="false" :modal="false" draggable :before-close="dialogMaterialModel" title="物料信息" append-to-body width="1014px">
<MaterialModels v-if="isMaterialModel == true" :materialId="nodeId" :projectInfo="projectInfo"
:materialInfo="materialInfo" :deviceInfo="deviceInfo" :deviceTypetype="deviceTypetype" ref="MaterialModel" @closeMaterialModel="closeMaterialModel"/>
</el-dialog>
2026-01-16 14:43:40 +08:00
2026-01-17 09:08:16 +08:00
<el-dialog v-model="isChangesettings" :close-on-click-modal="false" :modal="false" draggable :before-close="dialogChangesettings" title="变动设置" append-to-body width="1014px">
<ChangesettingsModels v-if="isChangesettings == true" :materialId="nodeId" :projectInfo="projectInfo" :changesettingsData="changesettingsData"
:materialInfo="materialInfo" :deviceInfo="deviceInfo" ref="ChangesettingsModel" @closeChangesettingsModel="closeChangesettingsModel"/>
2026-01-16 14:43:40 +08:00
</el-dialog>
2026-01-17 09:08:16 +08:00
2026-01-13 15:11:39 +08:00
</div>
</template>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
2026-01-13 17:39:09 +08:00
2026-01-13 15:11:39 +08:00
.app-layout {
position: fixed;
2026-01-13 17:39:09 +08:00
z-index: 10;
2026-01-13 15:11:39 +08:00
display: flex;
flex-direction: column;
width: 100vw;
height: 100vh;
font-family: 'Segoe UI', sans-serif;
}
.toolbar {
padding: 10px;
background-color: #f5f5f5;
border-bottom: 1px solid #e0e0e0;
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.style-group {
display: flex;
flex-direction: column;
gap: 8px;
margin-right: 20px;
}
.style-group h4 {
margin: 0 0 5px 0;
font-size: 14px;
color: #333;
font-weight: 600;
}
.toolbar button {
padding: 8px 16px;
border: 1px solid #d9d9d9;
border-radius: 4px;
background-color: #fff;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
white-space: nowrap;
}
.toolbar button:hover {
border-color: #1890ff;
color: #1890ff;
}
.toolbar button:active {
background-color: #f0f0f0;
}
2026-01-13 17:39:09 +08:00
2026-01-13 15:11:39 +08:00
/* 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);
}
2026-01-13 17:39:09 +08:00
2026-01-13 15:11:39 +08:00
.sidebar-section {
margin-bottom: 24px;
}
2026-01-13 17:39:09 +08:00
2026-01-13 15:11:39 +08:00
.sidebar-section h3 {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
2026-01-13 17:39:09 +08:00
2026-01-13 15:11:39 +08:00
.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;
}
2026-01-13 17:39:09 +08:00
2026-01-13 15:11:39 +08:00
.device-item:hover {
background-color: #f0f5ff;
border-color: #1890ff;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(24, 144, 255, 0.2);
}
2026-01-13 17:39:09 +08:00
2026-01-13 15:11:39 +08:00
.device-item:active {
cursor: grabbing;
}
2026-01-13 17:39:09 +08:00
2026-01-13 15:11:39 +08:00
.device-icon {
font-size: 24px;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
}
2026-01-13 17:39:09 +08:00
2026-01-13 15:11:39 +08:00
.device-name {
font-size: 13px;
color: #333;
font-weight: 500;
}
2026-01-13 17:39:09 +08:00
2026-01-13 15:11:39 +08:00
/* Graph Container Styles */
.graph-container {
flex: 1;
position: relative;
background-color: #fafafa;
overflow: hidden;
width: 100%;
height: 100%;
2026-01-21 09:57:53 +08:00
2026-01-13 15:11:39 +08:00
}
2026-01-13 17:39:09 +08:00
2026-01-13 15:11:39 +08:00
/* Drag and drop visual feedback */
.graph-container.drag-over {
background-color: #e6f7ff;
border: 2px dashed #1890ff;
}
2026-01-13 17:39:09 +08:00
.context-menu {
2026-01-13 15:11:39 +08:00
position: absolute;
z-index: 100;
2026-01-13 15:11:39 +08:00
background-color: #fff;
border: 1px solid #dfe3e8;
border-radius: 4px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.15);
width: 240px;
height: 40px;
display: flex;
justify-content: space-around;
align-items: center;
}
</style>
<style>
2026-01-13 17:39:09 +08:00
.x6-widget-stencil-content {
top: 0px !important;
}
.antvx6-header {
width: 100%;
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
background-color: #ffffff;
border-bottom: 1px solid #e0e0e0;
}
.header-left-box {
width: 400px;
height: 60px;
display: flex;
align-items: center;
}
.header-content-box {
display: flex;
align-items: center;
}
.return-icon-box {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-right: 10px;
}
.return-icon-box:hover {
background-color: #eeeeee;
border-radius: 4px;
}
.antvx6-header .project-name {
font-family: 'Arial Negreta', 'Arial Normal', 'Arial';
font-weight: 700;
font-style: normal;
font-size: 16px;
color: #333333;
margin-right: 10px;
}
.antvx6-header .operation-icon-box {
width: 46px;
height: 46px;
display: flex;
align-content: center;
justify-content: center;
flex-wrap: wrap;
cursor: pointer;
margin-right: 20px;
font-family: '微软雅黑';
font-weight: 400;
font-style: normal;
font-size: 12px;
color: #4B4B4B;
padding-top: 5px;
}
.antvx6-header .operation-icon-box:hover {
background-color: #eeeeee;
border-radius: 4px;
}
.operation-icon-text {
width: 100%;
text-align: center;
padding-top: 5px;
}
2026-01-15 17:27:26 +08:00
.line-style-box{
position: absolute;
right: 0;
height: 100%;
/* width: 100px; */
/* background-color: #d9d9d9; */
z-index: 10;
display: flex;
align-items: center;
}
.expansionandcontraction-box{
width:15px;
height: 64px;
background-color: #ffffff;
border: 1px solid #cfcfcf;
border-radius: 10px 0 0 10px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.style-content-box{
width: 300px;
height: calc(100vh - 60px);
border: 1px solid #cfcfcf;
border-top: none;
border-right: none;
background: #fff;
}
.style-content-box-title{
width: 100%;
height: 40px;
line-height: 40px;
text-align: left;
font-size: 14px;
font-family: 'Arial Negreta', 'Arial Normal', 'Arial';
font-weight: 700;
font-style: normal;
font-size: 14px;
color: #282828;
background-color: rgba(255, 255, 255, 1);
box-sizing: border-box;
border-bottom:1px solid rgba(238, 238, 238, 1);
padding-left: 15px;
}
2026-01-21 09:57:53 +08:00
.DisplaySettingsButton{
position: absolute;
left: -100px;
top: 20px;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 14px;
font-family: 'Arial Negreta', 'Arial Normal', 'Arial';
font-weight: 400;
font-style: normal;
font-size: 14px;
color: #282828;
background-color: rgba(255, 255, 255, 1);
border: none;
border-radius: 4px;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
cursor: pointer;
}
2026-01-22 10:58:03 +08:00
.x6-graph-grid {
background-image: none !important;
}
2026-01-27 13:33:14 +08:00
.style-content-box-display{
display: flex;
align-items: center;
padding-top: 20px;
}
.style-content-box-key{
width: 80px;
text-align: right;
font-family: '微软雅黑';
font-weight: 400;
font-style: normal;
font-size: 14px;
color: #808080;
padding-right: 10px;
}
2026-01-27 18:24:50 +08:00
.x6-widget-stencil .x6-node.x6-node-immovable{
cursor: pointer;
}
2026-01-13 15:11:39 +08:00
</style>