提交前端

This commit is contained in:
limengnan 2026-01-17 13:41:47 +08:00
parent 45f5a28775
commit f38acfc857
4 changed files with 180 additions and 655 deletions

View File

@ -9,6 +9,13 @@ export function searchProjectsLsit(queryParams:any){
});
}
export function projectsById(queryParams:any){
return request({
url: '/projects/'+ queryParams.projectId ,
method: 'get'
});
}

View File

@ -57,3 +57,12 @@ export function getActiveAlgorithms(){
});
}
//获取所有项目列表
export function getByScenario(queryParams:any){
return request({
url: '/scenario-results/by-scenario' ,
method: 'get',
params: queryParams
});
}

View File

@ -2,6 +2,8 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ElMessage } from "element-plus";
import { useRoute,useRouter } from 'vue-router';
import {
Clipboard,
Graph,
@ -10,34 +12,24 @@ import {
Selection,
Shape,
Snapline,
Stencil,
Transform
} from '@antv/x6'
// @ts-ignore
import insertCss from 'insert-css'
import { updateProjects} from "@/api/business/project";
import { updateProjects,projectsById} from "@/api/business/project";
import { getByScenario } from "@/api/business/scenario";
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 route = useRoute();
const router = useRouter()
const emit = defineEmits([ 'closeAntvx6']);
const props = defineProps({
projectInfo: {
@ -50,52 +42,26 @@ const deviceTypetype:any = ref('') // 设备类型
const isAdddevice = ref(false) //
const isEditdevice = ref(false) //
const isMaterialModel = ref(false) //
const projectInfo:any = ref(props.projectInfo) //
const projectInfo:any = ref({}) //
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
// }
// }
const scenarioId:any = ref('') // id
function getScenarioResults(){
getByScenario({
scenarioId: scenarioId.value,
pageNum:1,
pageSize:999
}).then((res:any) => {
console.log(res.data.records)
res.data.records
})
}
onMounted(() => {
preWork()
if(projectInfo.value.topology != null && projectInfo.value.topology != ''){
}
scenarioId.value = route.query.scenarioId
// #region
graph = new Graph({
container: document.getElementById('graph-container') as HTMLElement,
@ -155,13 +121,8 @@ onMounted(() => {
},
},
interacting: function (cellView) {
//
const cell = cellView.cell
// 'rect'
if (cell.shape === 'rect') {
// 'rect'
return { nodeMovable: false }
}
//
return true
}
@ -169,314 +130,6 @@ onMounted(() => {
// 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
//
//
}
// StencilDnd
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: {
@ -598,235 +251,144 @@ graph.on('blank:mouseup', (e) => {
},
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')
projectsById({projectId:route.query.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
// shape
// const validCells = Array.isArray(topology.designData) ? topology.designData.filter(cell => cell.shape) : []
//
graph.fromJSON(topology.designData);
getScenarioResults()
}
})
// #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
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
// 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
// #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)
@ -855,7 +417,7 @@ function copyNode() { // 复制节点
function closeAntvx6() {
emit('closeAntvx6')
router.push('/business/project/index')
}
function revokeClick(){
@ -1126,42 +688,20 @@ function saveDesign() { // 保存设计
<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="新增模拟分析">
<!-- <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>
<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>
@ -1175,14 +715,10 @@ function saveDesign() { // 保存设计
<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 id="graph-container" style="width: 100%; height: calc(100% - 60px);">
<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">
@ -1206,35 +742,6 @@ function saveDesign() { // 保存设计
</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>
@ -1391,9 +898,6 @@ function saveDesign() { // 保存设计
}
</style>
<style>
.x6-widget-stencil-content {
top: 0px !important;
}
.antvx6-header {
width: 100%;

View File

@ -26,7 +26,12 @@ export const constantRoutes: RouteRecordRaw[] = [
component: () => import('@/views/error-page/404.vue'),
meta: { hidden: true }
},
{
name: '图形化分析',
path: '/viewanalysis',
component: () => import('@/components/antvx6/viewx6.vue'),
meta: { hidden: true }
},
{
path: '/',
component: Layout,