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

2298 lines
62 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- 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'
// @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'
import { addDevices } from "@/api/business/database/device";
import { saveOrUpdate} from "@/api/business/database/material";
import { projectsById} from "@/api/business/project";
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'
import AdddeviceModel from './adddevice.vue';
import EditdeviceModel from './editdevice.vue';
import MaterialModels from './materialmodel.vue';
import ChangesettingsModels from './changesettings.vue';
import ConnectingwireModel from './connectingwire.vue';
import { TurnOff } from '@element-plus/icons-vue/dist/types';
const emit = defineEmits([ 'closeAntvx6']);
const props = defineProps({
projectInfo: {
required: false,
type: Object,
default: {}
},
})
const isLock = ref(false) // 是否锁定
const deviceTypetype:any = ref('') // 设备类型
const isAdddevice = ref(false) // 是否添加设备
const isEditdevice = ref(false) // 是否编辑设备
const isMaterialModel = ref(false) // 是否物料信息
const projectInfo:any = ref(props.projectInfo) // 项目信息
const isScenario = ref(false) //是否展示历史模拟场景
const isDisplay = ref(true) // 是否显示
const isExpansionandcontraction = ref(false) // 是否显示展开收起按钮
// 为了协助代码演示
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: 1,
},
},
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,
interactive: {
vertexMovable: true,
vertexAddable: true,
vertexDeletable: true,
arrowheadMovable: true,
edgeMovable: true,
},
})
},
validateConnection({ targetMagnet }) {
return !!targetMagnet
},
},
highlighting: {
magnetAdsorbed: {
name: 'stroke',
args: {
attrs: {
fill: '#5F95FF',
stroke: '#5F95FF',
},
},
},
},
interacting: function (cellView) {
// 获取当前节点的模型
const cell = cellView.cell
// 判断节点是否为 'rect' 形状
if (cell.shape === 'rect') {
// 如果是 'rect' 形状,则禁止节点移动
return { nodeMovable: false }
}
// 其他形状的节点允许所有交互
return true
}
// interacting: {
// nodeMovable: false, // 禁止所有节点移动
// },
})
// #endregion
// #region 使用插件
graph
.use(
new Transform({
resizing: false, // 通过拖拽边缘调整节点大小
rotating: false, // 允许旋转节点
// 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',
// graphHeight: 400,
// layoutOptions: {
// rowHeight: 120,
// },
// },
],
layoutOptions: {
columns: 2,
columnWidth: 140,
rowHeight: 100,
},
getDragNode: (sourceNode:any) => {
console.log('1111')
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 null
}
return sourceNode.clone({ deep: true })
},
// getDropNode: (draggingNode) => {
// return draggingNode.clone({ deep: true })
// },
})
graph.on('node:contextmenu', ({ e, node }) => {
selectedNode.value = node
e.preventDefault()
// 显示自定义上下文菜单,包含箭头样式选项
const pos = node.position?.() || { x: 0, y: 0 }
showContextMenu(pos.x, pos.y)
})
function showContextMenu(x: number, y: number) {
left.value = x + 260
top.value = y - 50
isMenuShow.value = true
}
graph.on('node:click', (row:any) => {
let node:any = row.node
nodeId.value = node.id
if( node.store.data.shape == 'rect'){
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()
})
// 监听Stencil内部的Dnd事件
document
.getElementById('stencil')
?.appendChild(stencil.container as HTMLElement)
// 监听Graph中节点创建事件
graph.on('node:added', (args:any) => {
console.log('节点已添加到画布', args)
if(isLock.value == true){
return
}
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 {
if(node.store && node.store.data && node.store.data.shape === 'rect'){
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 = ''
return
}
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) // 修改高度
nodeId.value = node.id
if(node.store.data.attrs.text.text == '圆柱槽'){
deviceTypetype.value = 'CylindricalTank'
node.size(110, 140)
const width = node.size().width
node.attr('image/xlink:href', '/assets/11.png')
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, 60)
const width = node.size().width
node.attr('image/xlink:href', '/assets/22.png')
node.attr('image/width', 130)
node.attr('image/height', 60)
node.attr('label/refY', 65)
node.attr('label/refX', width/2)
node.attr('label/textAnchor', 'middle')
}else if(node.store.data.attrs.text.text == '环形槽'){
deviceTypetype.value = 'AnnularTank'
node.size(90, 140)
const width = node.size().width
node.attr('image/xlink:href', '/assets/33.png')
node.attr('image/width', 90)
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 = 'TubeBundleTank'
node.size(80, 140)
const width = node.size().width
node.attr('image/xlink:href', '/assets/44.png')
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')
}else if(node.store.data.attrs.text.text == '萃取柱'){
deviceTypetype.value = 'ExtractionColumn'
node.size(30, 140)
const width = node.size().width
node.attr('image/xlink:href', '/assets/55.png')
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')
}else if(node.store.data.attrs.text.text == '流化床'){
deviceTypetype.value = 'FluidizedBed'
node.size(60, 140)
const width = node.size().width
node.attr('image/xlink:href', '/assets/66.png')
node.attr('image/width', 60)
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 = 'ACFTank'
node.size(40, 140)
const width = node.size().width
node.attr('image/xlink:href', '/assets/77.png')
node.attr('image/width', 40)
node.attr('image/height', 135)
node.attr('label/refY', 140)
node.attr('label/refX', width/2)
node.attr('label/textAnchor', 'middle')
}
// 设置固定大小
// 删除节点上的文字
// node.attr('text/text', '')
// node.attr('label/text', '')
isAdddevice.value = true
}
})
graph.on('edge:selected', ({ edge }) => {
let edgeTemp :any = edge
edge.addTools({
name: 'button-remove',
args: {
x: 0,
y: 0,
offset: { x: 10, y: 10 },
},
})
edge.addTools({
name: 'vertices', // 指定使用 vertices 工具
args: {
// 可选配置参数
snapRadius: 20, // 移动时的吸附半径
addable: true, // 是否允许单击边添加顶点
removable: true, // 是否允许双击顶点删除
attrs: { // 自定义顶点的样式(小圆点)
r: 6,
fill: edgeTemp.store.data.attrs.line.stroke,
},
},
})
});
graph.on('edge:unselected', ({ edge }) => {
edge.removeTools()
})
// #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'], () => {
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)
}
})
graph.bindKey('delete', () => {
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)
}
})
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
if(isBoundary.value == false){
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',
stroke: '#ff0000',
strokeWidth: 1,
strokeDasharray: '5 5', // 设置虚线样式
},
label: {
text: '系统边界',
refX: '50%',
refY: -15,
textAnchor: 'middle',
dominantBaseline: 'middle', // 文本自身的垂直居中
fontSize: 15,
fill: '#333333'
}
},
})
startPoint = null // 重置起点
})
// 控制连接桩显示/隐藏
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: 8,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
right: {
position: 'right',
attrs: {
circle: {
r: 8,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
bottom: {
position: 'bottom',
attrs: {
circle: {
r: 8,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
left: {
position: 'left',
attrs: {
circle: {
r: 8,
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,
)
Graph.registerNode(
'rect-text',
{
inherit: 'rect',
width: 120,
height: 60,
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'image',
selector: 'img',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
body: {
stroke: 'transparent',
strokeWidth: 1,
fill: 'transparent',
rx: 6,
ry: 6,
},
label: {
text: '',
fill: '#333',
fontSize: 12,
refX: 0.5,
refY: 0.5,
textAnchor: 'middle',
dy: 10,
},
'.': {
class: 'custom-image-node',
},
},
},
true,
)
const imageShapes = [
{
label: '圆柱槽',
image: '/assets/1.png',
},
{
label: '扁平槽',
image: '/assets/2.png',
},
{
label: '环形槽',
image: '/assets/3.png',
},
{
label: '管束槽',
image: '/assets/4.png',
},
{
label: '萃取柱',
image: '/assets/5.png',
},
{
label: '流化床',
image: '/assets/6.png',
},{
label: '锥底环形槽',
image: '/assets/7.png',
},
]
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: '实线',
image: line1,
targetMarker: null,
sourceMarker: null,
strokeDasharray: 0
},
{
label: '单箭头实线',
image: line2,
targetMarker: {
name: 'block',
width: 12,
height: 8
},
sourceMarker: null,
strokeDasharray: 0
},
{
label: '双箭头实线',
image: line3,
targetMarker: {
name: 'block',
width: 12,
height: 8
},
sourceMarker: {
name: 'block',
width: 12,
height: 8
},
strokeDasharray: 0
},
{
label: '虚线',
image: line4,
targetMarker: null,
sourceMarker: null,
strokeDasharray: 5
},
{
label: '单箭头虚线',
image: line5,
targetMarker: {
name: 'block',
width: 12,
height: 8
},
sourceMarker: null,
strokeDasharray: 5
},
{
label: '双箭头虚线',
image: line6,
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,
width: 130,
height: 100,
attrs: {
image: {
'xlink:href': item.image,
width: 31,
height: 31,
refX: 50,
refY: 20
},
body: {
stroke: 'transparent',
fill: '#f8f8f8'
},
label: {
text: item.label,
refX: 60,
refY: 65,
textAnchor: 'middle',
fontSize: 12,
fill: '#666'
}
},
data: {
lineStyle: {
strokeDasharray: item.strokeDasharray,
targetMarker: item.targetMarker,
sourceMarker: item.sourceMarker
}
}
})
return node
})
stencil.load(lineNodes, 'group3')
if (!graph || !projectInfo.value || !projectInfo.value.topology) return;
graph.clearCells();
projectsById({projectId:props.projectInfo.projectId}).then((res:any) => {
if(res.topology != null && res.topology != ''){
projectInfo.value = res
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);
}
})
// #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;
}
.x6-container-editbox #graph-container {
width: calc(100% - 180px);
height: 100% !important;
background-color: #f1f3fe;
}
.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
}
const isCopy = ref(false)
const copyNodeInfo:any = ref(null)
function copyNode() { // 复制节点
if (selectedNode.value) {
try {
// 获取当前节点的位置
const position = selectedNode.value.position()
// 创建新节点,使用节点的原始数据
copyNodeInfo.value = selectedNode.value.clone()
// 设置新位置偏移50px
copyNodeInfo.value.position(position.x + 50, position.y + 50)
// deviceTypetype.value = newNode.store.data.deviceInfo.type
// 添加到画布
// graph.addNode(newNode)
copyDeviceInfo.value = {
oldname: copyNodeInfo.value.store.data.deviceInfo.name,
name:''
}
isMenuShow.value = false
isCopy.value = true
} catch (error) {
console.error('节点复制失败:', error)
}
}
}
function closeAntvx6() {
// let isSaveDesign = false
// const designData:any = graph.toJSON()
// for(let i = 0;i<designData.cells.length;i++){
// if(designData.cells[i].shape == 'rect-text' || designData.cells[i].shape == 'image-node'){
// isSaveDesign = true
// }
// }
// if(isSaveDesign == true){
// saveDesign(true)
// }
emit('closeAntvx6')
}
function revokeClick(){
if (graph.canUndo()) {
isLock.value = true
graph.undo()
setTimeout(() => {
isLock.value = false
}, 500);
}
}
function removeClick(){
if (graph.canRedo()) {
isLock.value = true
graph.redo()
setTimeout(() => {
isLock.value = false
}, 500);
}
}
function bigClick(){ // 放大画布
graph.zoom(0.1)
}
function smallClick(){ // 缩小画布
graph.zoom(-0.1)
}
function ConnectingwireClick(){ // 打开连接线弹窗
isConnectingwire.value = true;
}
function dialogConnectingwire(){ // 关闭连接线弹窗
isConnectingwire.value = false;
}
const connectingwireInfo:any = ref({
label: "实线",
strokeDasharray: "0",
arrow: "1",
strokeWidth: 2,
color: "#a2b1c3",
})
const lineLine:any = ref( "#a2b1c3")
function closeConnectingwireModel(e:any){ // 关闭连接线弹窗
if(e != false){
connectingwireInfo.value = e
lineLine.value = e.color
let lineStyle:any = {
strokeDasharray: 0,
targetMarker: {},
sourceMarker: {}
}
if(e.arrow == 0){
lineStyle = {
sourceMarker: null,
strokeDasharray: e.strokeDasharray,
targetMarker: null
}
}
if(e.arrow == 1){
lineStyle = {
sourceMarker: null,
strokeDasharray:e.strokeDasharray,
targetMarker: {
height: 8,
name: "block",
width: 12
}
}
}
if(e.arrow == 2){
lineStyle = {
sourceMarker: {
height: 8,
name: "block",
width: 12
},
strokeDasharray:e.strokeDasharray,
targetMarker: {
height: 8,
name: "block",
width: 12
}
}
}
graph.options.connecting.createEdge = () => {
return new Shape.Edge({
attrs: {
line: {
stroke: lineLine.value,
strokeWidth: e.strokeWidth,
strokeDasharray: lineStyle.strokeDasharray,
targetMarker: lineStyle.targetMarker,
sourceMarker: lineStyle.sourceMarker
}
},
zIndex: 0
})
}
}
isConnectingwire.value = false;
}
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;
}
function dialogAdddevice(){ // 打开新增设备弹窗
graph.undo()
isAdddevice.value = false;
}
const selectedDevice = ref({})
function closeAdddevice(e:any){ // 关闭新增设备弹窗
selectedDevice.value = e
let retrievedNode:any = graph.getCellById(nodeId.value)
retrievedNode.attr('text/text', e.name)
retrievedNode.attr('label/text', e.name)
retrievedNode.store.data.deviceInfo = e
saveDesign(false)
isAdddevice.value = false;
}
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
retrievedNode.attr('text/text', e.name)
retrievedNode.attr('label/text', e.name)
isEditdevice.value = false;
saveDesign(false)
}
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){
isMaterialModel.value = false;
return
}
selectedMaterial.value = e
let retrievedNode:any = graph.getCellById(nodeId.value)
retrievedNode.store.data.materialInfo = e
saveDesign(false)
isMaterialModel.value = false;
}
const isConnectingwire = ref(false) // 是否连接线
// const connectingwireData:any = ref([]) // 是否连接线数据
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】";
// 匹配*号后、+或)前的数字(支持整数和小数)
const regex = /([\*\/])(\d+\.?\d*)/g;
const coefficients = [];
let match;
// 循环提取所有匹配项
while ((match = regex.exec(formula)) !== null) {
if(!isNaN(parseFloat(match[1]))){
coefficients.push(parseFloat(match[1])); // 转为数字类型
}else if(!isNaN(parseFloat(match[2]))){
coefficients.push(parseFloat(match[2])); // 转为数字类型
}
}
console.log(coefficients); // 输出: [0.2, 1.0]
return coefficients
}
function getBias(formula:any){
// 从后往前找最后一个+的位置
// 匹配最后一个+号后面的数字(支持整数、小数)
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
// 从最后一个点分割,取后面的部分
}
const styleInfo:any = ref({
name: '',
color: '#409EFF',
line: '',
width: '',
remark: ''
})
function inputChange (e:any){
let node:any = graph.getCellById(nodeId.value)
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
}
function saveDesign(is:any) { // 保存设计
try {
// 获取画布内容并转换为JSON
const designData:any = graph.toJSON()
// if(is == true){
// isDisplay.value = true
// for(let i = 0;i<designData.cells.length;i++){
// if(designData.cells[i].shape == 'rect-text' || designData.cells[i].shape == 'image-node'){
// graph.removeCell(designData.cells[i].id)
// }
// }
// }
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
const name = cells[i].changesettings[j].name
const delay= cells[i].changesettings[j].delay
let tempSources = []
for(let k=0;k<setFormulaInit(cells[i].changesettings[j].formula).length;k++){
// 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
if(cells[i].changesettings[j].deviceMaterialData[k] == null){
return
}
tempSources.push({
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
coefficient: setFormulaInit(cells[i].changesettings[j].formula)[k], //0.2
delay: { enabled: true, time: cells[i].changesettings[j].deviceMaterialData[k].delay, unit: 's' } // 5
})
}
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
}
}
}
}
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
}:{}
})
}
}
}
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
}
}
const isBoundary = ref(false)
const boundaryClick = () => {
isBoundary.value = !isBoundary.value
}
const copyDeviceInfo:any = ref({})
function dialogCopy(){
isCopy.value = false
}
const loading = ref(false)
const confirmCopyClick = async ()=>{
if(copyDeviceInfo.value.name == ''){
ElMessage({
type: "warning",
message: "请输入新设备名称",
});
return
}
loading.value = true
copyNodeInfo.value.store.data.attrs.label.text = copyDeviceInfo.value.name
copyNodeInfo.value.store.data.deviceInfo.name = copyDeviceInfo.value.name
copyNodeInfo.value.store.data.deviceInfo.code = copyDeviceInfo.value.code + '-copy'
copyNodeInfo.value.store.data.deviceInfo.deviceId = copyNodeInfo.value.id
if(copyNodeInfo.value.store.data.materialInfo != null && copyNodeInfo.value.store.data.materialInfo.materialId != null){
copyNodeInfo.value.store.data.materialInfo.materialId = copyNodeInfo.value.id
}
if(copyNodeInfo.value.store.data.changesettings != null &&
copyNodeInfo.value.store.data.changesettings.length >0){
for(let i=0;i<copyNodeInfo.value.store.data.changesettings.length;i++){
let changeSetting = copyNodeInfo.value.store.data.changesettings[i]
if(changeSetting.delayList != null && changeSetting.delayList.length >0){
for(let j=0;j<changeSetting.delayList.length;j++){
let delayList = changeSetting.delayList[j]
if(delayList.type == 'device'){
delayList.parentId = copyNodeInfo.value.id
delayList.parentName = copyDeviceInfo.value.name
}else if(delayList.type == 'material'){
delayList.parentId = copyNodeInfo.value.id
}
}
}
if(changeSetting.deviceMaterialData != null && changeSetting.deviceMaterialData.length >0){
for(let k=0;k<changeSetting.deviceMaterialData.length;k++){
let deviceMaterialData = changeSetting.deviceMaterialData[k]
if(deviceMaterialData.type == 'device'){
deviceMaterialData.parentId = copyNodeInfo.value.id
deviceMaterialData.parentName = copyDeviceInfo.value.name
}else if(deviceMaterialData.type == 'material'){
deviceMaterialData.parentId = copyNodeInfo.value.id
}
}
}
if(changeSetting.formulaData != null && changeSetting.formulaData.length >0){
for(let l=0;l<changeSetting.formulaData.length;l++){
let formulaData = changeSetting.formulaData[l]
if(typeof formulaData === 'object') {
if(formulaData.type == 'device'){
formulaData.parentId = copyNodeInfo.value.id
formulaData.parentName = copyDeviceInfo.value.name
}else if(formulaData.type == 'material'){
formulaData.parentId = copyNodeInfo.value.id
}
}
}
}
}
}
const deviceInfo = copyNodeInfo.value.store.data.deviceInfo
const materialInfo = copyNodeInfo.value.store.data.materialInfo
const result:any = await addDevices(deviceInfo);
if(result == false || result == undefined){
loading.value = false
ElMessage({
type: "success",
message: "复制失败",
});
return
}
if(materialInfo !=null && materialInfo.materialId != null){
const results:any = await saveOrUpdate(materialInfo);
if(results == false || results == undefined){
loading.value = false
ElMessage({
type: "success",
message: "复制失败",
});
return
}
}
isLock.value = true
graph.addNode(copyNodeInfo.value)
isLock.value = false
loading.value = false
saveDesign(false)
isCopy.value = false
}
function isDisplayClick(){
let tempGraph:any = graph.toJSON().cells
if(isDisplay.value == false){
isDisplay.value = true
for(let i = 0;i<tempGraph.length;i++){
if(tempGraph[i].shape == 'rect-text' || tempGraph[i].shape == 'image-node'){
graph.removeCell(tempGraph[i].id)
}
}
}else{
isDisplay.value = false
isLock.value = true
tempGraph.forEach((item:any,index:any) => {
let num = -1
let tempInfo = {...item.deviceInfo}
if(tempInfo.size != null && tempInfo.size != ''){
tempInfo = {...tempInfo, ...JSON.parse(tempInfo.size)}
}
if(item.materialInfo != null && item.materialInfo.materialId != null){
tempInfo = {...tempInfo,...item.materialInfo}
}
for (const key in tempInfo) {
if (!Object.hasOwn(tempInfo, key)) continue;
const element = tempInfo[key];
if(getName(key) != '' && tempInfo[key] != null){
if(isDisplay.value == true){
return
}
num = num + 1
graph.addNode({
shape: 'rect-text',
x: item.position.x,
y: item.position.y + 153 + num * 25,
width: 260,
height: 30,
// label: data[i].name + '' + data[i].value,
attrs: {
body: {
stroke: 'transparent',
fill: 'transparent',
strokeWidth: 1,
},
label: {
textAnchor: 'left',
refX: 0,
text: getName(key) + '' + element,
textWrap: {
width: 250,
height: 30,
ellipsis: true,
},
},
text: {
text: '',
fill: '#363636', // 蓝色文字
fontSize: 12,
},
},
})
}
}
})
isLock.value = false
}
}
function appendAttrText(key:any,element:any){
}
function getName(code:any) {
let name = ''
switch (code) {
case 'width':
return name = "宽度cm";
break;
case 'outer_diameter':
return name = "外径cm";
break;
case 'height':
return name = "高度cm";
break;
case 'length':
return name = "长度cm";
break;
case 'diameter':
return name = "外径cm";
break;
case 'volume':
return name = "容量单位L";
break;
case 'flow_rate':
return name = "流量单位m3/h";
break;
case 'flowRate':
return name = "流量单位m3/h";
break;
case 'pulse_velocity':
return name = "脉冲速度单位Hz";
break;
case 'pulseVelocity':
return name = "脉冲速度单位Hz";
break;
case 'u_concentration':
return name = "铀浓度g/L";
break;
case 'uConcentration':
return name = "铀浓度g/L";
break;
case 'uo2_density':
return name = "氧化铀密度g/cm3";
break;
case 'uo2Density':
return name = "氧化铀密度g/cm3";
break;
case 'u_enrichment':
return name = "铀富集度(%";
break;
case 'uEnrichment':
return name = "铀富集度(%";
break;
case 'pu_concentration':
return name = "钚浓度g/L";
break;
case 'puConcentration':
return name = "钚浓度g/L";
break;
case 'puo2_density':
return name = "氧化钚密度g/cm3";
break;
case 'puo2Density':
return name = "氧化钚密度g/cm3";
break;
case 'pu_isotope':
return name = "钚同位素比例PU-240占比%";
break;
case 'puIsotope':
return name = "钚同位素比例PU-240占比%";
break;
case 'hno3_acidity':
return name = "硝酸酸度mol/L";
break;
case 'hno3Acidity':
return name = "硝酸酸度mol/L";
break;
case 'h2c2o4_concentration':
return name = "草酸浓度mol/L";
break;
case 'h2c2o4Concentration':
return name = "草酸浓度mol/L";
break;
case 'organic_ratio':
return name = "有机相比例%";
break;
case 'organicRatio':
return name = "有机相比例%";
break;
case 'moisture_content':
return name = "含水率%";
break;
case 'moistureContent':
return name = "含水率%";
break;
default:
return name = "";
}
return name
}
</script>
<template>
<div class="app-layout" @click="isMenuShow = false" >
<div class="antvx6-header">
<div class="header-left-box">
<div class="return-icon-box" @click="closeAntvx6" title="返回工作台">
<img src="@/assets/x6/return.png" alt="图标" style="cursor: pointer;">
</div>
<div class="project-name">{{ projectInfo.name }}</div>
<div class="return-icon-box" @click="analysisAdd" title="新增模拟分析">
<img src="@/assets/x6/add.png" alt="图标" style="cursor: pointer;">
</div>
<div class="return-icon-box" @click="simulationClick" title="历史模拟分析">
<img src="@/assets/x6/history.png" alt="图标" style="cursor: pointer;">
</div>
</div>
<div class="header-content-box">
<div class="operation-icon-box" @click="revokeClick">
<img src="@/assets/x6/revoke.png">
<div class="operation-icon-text">撤销</div>
</div>
<div class="operation-icon-box" @click="removeClick">
<img src="@/assets/x6/redo.png">
<div class="operation-icon-text">重做</div>
</div>
<div class="operation-icon-box" @click="bigClick">
<img src="@/assets/x6/magnify.png">
<div class="operation-icon-text">放大</div>
</div>
<div class="operation-icon-box" @click="smallClick">
<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="isDisplayClick">
<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" style="width: 60px;" @click="boundaryClick">
<img v-if="!isBoundary" src="@/assets/x6/display.png">
<img v-else src="@/assets/x6/hide.png">
<div v-if="!isBoundary" class="operation-icon-text">绘制边界</div>
<div v-else class="operation-icon-text">停止绘制</div>
</div>
<div class="operation-icon-box" @click="ConnectingwireClick">
<img src="@/assets/x6/line.png" style="margin: 5px 0;">
<div class="operation-icon-text">连接线</div>
</div>
<div class="operation-icon-box" @click="saveDesign(true)">
<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'}">
<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">
<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>
<div class="line-style-box">
<!-- <div class="DisplaySettingsButton">显示设置</div> -->
<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>
<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>
</div>
</div>
</div>
</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>
<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>
<el-dialog v-model="isEditdevice" :close-on-click-modal="false" :modal="false" draggable :before-close="dialogEditdevice" title="设备信息" append-to-body width="1050px">
<EditdeviceModel v-if="isEditdevice == true" :deviceId="nodeId" :deviceInfo="deviceInfo"
:deviceTypetype="deviceTypetype" ref="Editdevice" @closeEditdevice="closeEditdevice"/>
</el-dialog>
<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>
<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"/>
</el-dialog>
<el-dialog v-model="isConnectingwire" :close-on-click-modal="false" :modal="false" draggable :before-close="dialogConnectingwire" title="连接线" append-to-body width="1014px">
<div style="height: calc(100vh - 200px); overflow: auto;">
<ConnectingwireModel v-if="isConnectingwire == true" :connectingwireInfo="connectingwireInfo"
@closeConnectingwireModel="closeConnectingwireModel"/>
</div>
</el-dialog>
<el-dialog v-model="isCopy" :close-on-click-modal="false"
:modal="false"
draggable
:before-close="dialogCopy"
title="复制设备"
append-to-body width="600px">
<el-form ref="infoForm" :model="copyDeviceInfo" label-width="100px"
style="width: 100%;height: 200px;margin-top: 30px;" v-loading="loading" element-loading-text="正在复制中...">
<el-form-item label="复制设备" prop="code" style="width: 100%;">
<el-input v-model="copyDeviceInfo.oldname" style="width: 100%" placeholder="" disabled></el-input>
</el-form-item>
<el-form-item label="新设备名称" prop="name" style="width: 100%;">
<el-input v-model="copyDeviceInfo.name" style="width: 100%" placeholder="请输入设备名称"></el-input>
</el-form-item>
<span class="dialog-footer"
style="display: flex;display: -webkit-flex; justify-content: flex-end;-webkit-justify-content: flex-end;">
<el-button @click="dialogCopy">取 消</el-button>
<el-button type="primary" @click="confirmCopyClick"> </el-button>
</span>
</el-form>
</el-dialog>
</div>
</template>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.app-layout {
position: fixed;
z-index: 10;
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;
}
/* 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;
}
.context-menu {
position: absolute;
z-index: 100;
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>
.antvx6-content #graph-container {
flex: 1;
position: relative;
background-color: #fafafa;
overflow: hidden;
width: 100% !important;
height: calc(100%) !important;
}
.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;
}
.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;
}
.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;
}
.x6-graph-grid {
background-image: none !important;
}
.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;
}
.x6-widget-stencil .x6-node.x6-node-immovable{
cursor: pointer;
}
</style>