JavaProjectRepo/business-css/frontend/src/components/antvx6/viewx6.vue
2026-01-17 12:32:55 +08:00

1518 lines
38 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 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'
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';
const emit = defineEmits([ 'closeAntvx6']);
const props = defineProps({
projectInfo: {
required: false,
type: Object,
default: {}
},
})
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
let currentArrowStyle = 'single' // 默认单箭头
let currentLineStyle = 'solid' // 默认实线
// 设置箭头样式
// function setArrowStyle(style: 'single' | 'double' | 'none', lineStyle: 'solid' | 'dashed' = 'solid') {
// currentArrowStyle = style
// currentLineStyle = lineStyle
// // 重新配置连接线
// graph.options.connecting.createEdge = () => {
// const edge = new Shape.Edge({
// attrs: {
// line: {
// stroke: '#A2B1C3',
// strokeWidth: 2,
// strokeDasharray: lineStyle === 'dashed' ? 5 : 0,
// targetMarker: style === 'none' ? null : {
// name: 'block',
// width: 12,
// height: 8,
// },
// sourceMarker: style === 'double' ? {
// name: 'block',
// width: 12,
// height: 8,
// } : null,
// },
// },
// zIndex: 0,
// })
// return edge
// }
// }
onMounted(() => {
preWork()
if(projectInfo.value.topology != null && projectInfo.value.topology != ''){
}
// #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',
},
},
},
},
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,
},
})
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
// 创建并显示上下文菜单
// 包含箭头样式选项
}
// 监听Stencil内部的Dnd事件
stencil.on('dnd:start', (args:any) => {
console.log('Stencil节点开始拖拽', args)
})
stencil.on('dnd:drag', (args:any) => {
console.log('Stencil节点拖拽中', args)
})
stencil.on('dnd:end', (args:any) => {
console.log('Stencil节点结束拖拽', args)
})
document
.getElementById('stencil')
?.appendChild(stencil.container as HTMLElement)
// 监听Graph中节点创建事件
graph.on('node:added', (args:any) => {
console.log('节点已添加到画布', args)
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'){
return
}
nodeId.value = node.id
if(node.store.data.attrs.text.text == '扁平槽'){
deviceTypetype.value = 'FlatTank'
}else if(node.store.data.attrs.text.text == '圆柱槽'){
deviceTypetype.value = 'CylindricalTank'
}else if(node.store.data.attrs.text.text == '环形槽'){
deviceTypetype.value = 'AnnularTank'
}else if(node.store.data.attrs.text.text == '管束槽'){
deviceTypetype.value = 'TubeBundleTank'
}else if(node.store.data.attrs.text.text == '萃取柱'){
deviceTypetype.value = 'ExtractionColumn'
}else if(node.store.data.attrs.text.text == '流化床'){
deviceTypetype.value = 'FluidizedBed'
}else if(node.store.data.attrs.text.text == '锥底环形槽'){
deviceTypetype.value = 'ACFTank'
}
// 设置固定大小
node.size(160, 160)
// 去掉背景节点
node.attr('body/fill', 'none')
// 图片居中
node.attr('image/refX', 0)
node.attr('image/refY', 0)
node.attr('image/width', 160) // 修改宽度
node.attr('image/height', 160) // 修改高度
// 删除节点上的文字
node.attr('text/text', '')
node.attr('label/text', '')
isAdddevice.value = true
}
})
// #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)
}
})
// delete
graph.bindKey('backspace', () => {
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
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: '#999',
strokeWidth: 1,
strokeDasharray: '5 5', // 设置虚线样式
},
},
})
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: 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,
)
const imageShapes = [
{
label: '圆柱槽',
image: img1,
},
{
label: '扁平槽',
image:img2,
},
{
label: '环形槽',
image:img3,
},
{
label: '管束槽',
image:img4,
},
{
label: '萃取柱',
image:img5,
},
{
label: '流化床',
image:img6,
},{
label: '锥底环形槽',
image:img7,
},
]
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();
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;
}
#graph-container {
width: calc(100% - 180px);
height: 100%;
}
.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)
// 添加到画布
graph.addNode(newNode)
isMenuShow.value = false
} catch (error) {
console.error('节点复制失败:', error)
}
}
}
function closeAntvx6() {
emit('closeAntvx6')
}
function revokeClick(){
graph.undo()
}
function removeClick(){
graph.clearCells();
}
function bigClick(){
graph.zoom(0.1)
}
function smallClick(){
graph.zoom(-0.1)
}
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.store.data.deviceInfo = e
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
isEditdevice.value = 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
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】";
// 匹配*号后、+或)前的数字(支持整数和小数)
const regex = /\*(\d+\.?\d*)/g;
const coefficients = [];
let match;
// 循环提取所有匹配项
while ((match = regex.exec(formula)) !== null) {
coefficients.push(parseFloat(match[1])); // 转为数字类型
}
console.log(coefficients); // 输出: [0.2, 1.0]
return coefficients
}
function getBias(formula:any){
// 从后往前找最后一个+的位置
// 匹配最后一个+号后面的数字(支持整数、小数)
const regex = /\+(\d+\.?\d*)$/;
const match = formula.match(regex);
const constant = parseFloat(match[1]);
return constant
}
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
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++){
tempSources.push({
entityType: 'material', //圆柱槽B
entityId: cells[i].materialInfo.materialId , //materialId : cdeeca2e-1e0c-4bdd-b946-f347c104752c
property: name, // 铀浓度g/L
coefficient: setFormulaInit(cells[i].changesettings[j].formula)[k], //0.2
delay: { enabled: true, time: 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
}
}
}
}
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
}
console.log(topology)
// 添加保存信息
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
}
}
</script>
<template>
<div class="app-layout" @click="isMenuShow = false">
<!-- <div class="toolbar">
<div class="style-group">
<h4>实线</h4>
<button @click="setArrowStyle('single', 'solid')">单箭头</button>
<button @click="setArrowStyle('double', 'solid')">双箭头</button>
<button @click="setArrowStyle('none', 'solid')">无箭头</button>
</div>
<div class="style-group">
<h4>虚线</h4>
<button @click="setArrowStyle('single', 'dashed')">单箭头虚线</button>
<button @click="setArrowStyle('double', 'dashed')">双箭头虚线</button>
<button @click="setArrowStyle('none', 'dashed')">无箭头虚线</button>
</div>
</div> -->
<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="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'}">
<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="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>
</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>
</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>
.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;
}
</style>