roomStatistics,CustomChart,气象环境

This commit is contained in:
liaoboping 2025-09-21 11:55:44 +08:00
parent b53fa17ab4
commit 7e05491c5f
12 changed files with 535 additions and 82 deletions

View File

@ -167,7 +167,7 @@ export default class MyCesium {
*/
setClientByCenter(position) {
this.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 100000), // 目标点坐标
destination: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 50000), // 目标点坐标
orientation: {
heading: 0, // 航向角弧度0表示正北
pitch: Cesium.Math.toRadians(-90), // 俯仰角(弧度),-90度表示垂直向下看

View File

@ -0,0 +1,128 @@
<template>
<div
class="chart-wrapper"
:style="`--height:${typeof height === 'string' ? height : `${height}px`};`"
v-resize="handleResize"
>
<div :id="chartId" class="chart-body" />
</div>
</template>
<script>
import * as Echarts from 'echarts'
import { loadingOptions, defaultOptions } from './constant'
export default {
props: {
height: { type: [String, Number], default: '100%' },
chartId: { type: String, required: true },
mapData: { type: Array, default: () => [] },
loadingOptions: { type: Object, default: () => ({}) },
options: { type: Object, default: () => ({}) },
immediate: { type: Boolean, default: false },
},
data () {
return {
chart: null,
loading: true,
bindAction: null,
}
},
computed: {
loadingOpts () {
return Object.deepAssign({}, loadingOptions, this.loadingOptions)
},
chartOptions () {
return Object.deepAssign(defaultOptions, this.loadingOptions, this.options)
},
},
watch: {
loadingOptions () {
this.initChart(true, false)
},
options () {
this.updateChart()
},
},
created () {
this.initMap()
},
mounted () {
this.initChart(!this.immediate, true)
this.bindAction = this.handleVisibilityChange.bind(this)
document.addEventListener('visibilitychange', this.bindAction)
},
beforeDestroy () {
this.dispose()
document.removeEventListener('visibilitychange', this.bindAction)
},
methods: {
dispose () {
this.chart && this.chart.dispose()
this.chart = null
},
handleVisibilityChange () {
if (document.hidden) {
this.dispose()
} else {
this.dispose()
this.$nextTick(() => {
this.initChart(this.loading, false)
})
}
},
handleResize () {
this.dispose()
this.$nextTick(() => {
this.initChart(this.loading, true)
})
},
initMap () {
this.mapData.forEach((map) => {
if (Echarts.getMap(map.mapName)) return
Echarts.registerMap(map.mapName, {
type: 'FeatureCollection',
features: map.features,
UTF8Encoding: true,
})
})
},
initChart (loading, force) {
if (force || (!this.chart && document.querySelector(`#${this.chartId}`))) {
this.dispose()
this.chart = Echarts.init(document.querySelector(`#${this.chartId}`), { width: 'auto', height: 'auto' })
}
if (loading) {
this.loading = true
this.chart.setOption(this.loadingOpts)
} else {
this.chart.setOption(this.chartOptions)
this.$emit('binding-event', this.chart)
}
},
updateChart () {
if (!this.chart && document.querySelector(`#${this.chartId}`)) {
this.dispose()
this.chart = Echarts.init(document.querySelector(`#${this.chartId}`), { width: 'auto', height: 'auto' })
}
if (this.chart) {
this.loading = false
// console.log(`----${this.chartId}----`, this.chartOptions)
this.chart.setOption(this.chartOptions, true)
this.$emit('binding-event', this.chart)
}
},
},
}
</script>
<style lang="less" scoped>
.chart-wrapper {
height: var(--height);
display: flex;
flex-direction: column;
.chart-body {
flex: 1;
}
}
</style>

View File

@ -0,0 +1,45 @@
export const loadingOptions = {
graphic: {
elements: [
{
type: 'group',
left: 'center',
top: 'center',
children: new Array(7).fill(0).map((val, i) => ({
type: 'rect',
x: i * 20,
shape: {
x: 0,
y: -40,
width: 10,
height: 80,
},
style: {
fill: '#5470c6',
},
keyframeAnimation: {
duration: 1000,
delay: i * 200,
loop: true,
keyframes: [
{
percent: 0.5,
scaleY: 0.3,
easing: 'cubicIn',
},
{
percent: 1,
scaleY: 1,
easing: 'cubicOut',
},
],
},
})),
},
],
},
}
export const defaultOptions = {
grid: { left: 10, right: 10, top: 40, bottom: 10 },
}

View File

@ -14,6 +14,7 @@ import LatitudeInput from './Form/LatitudeInput.vue'
import DurationInput from './Form/DurationInput.vue'
import WangEditor from './WangEditor/Index.vue'
import CustomChart from './Chart/CustomChart.vue'
import Loading from './Directives/Loading'
import resize from 'vue-resize-directive'
@ -21,6 +22,7 @@ import resize from 'vue-resize-directive'
import Bus from './Bus/index'
import MyCesium from './Cesium/index'
import MyWebsocket from './Websocket/index'
import './utils/extends'
export default {
install(Vue) {
@ -40,6 +42,7 @@ export default {
Vue.component('DurationInput', DurationInput)
Vue.component('WangEditor', WangEditor)
Vue.component('CustomChart', CustomChart)
Vue.directive('loading', Loading)
Vue.directive('resize', resize)

View File

@ -0,0 +1,32 @@
Object.isObject = function (target) {
return target !== null && typeof target === 'object' && target.toString() === '[object Object]'
}
Object.deepAssign = function (...resources) {
let res
for (var i = 0; i < resources.length;) {
const resource = resources[i]
if (Object.isObject(resource)) {
i++
} else {
res = resources.splice(0, i + 1).reverse()[0]
i = 0
continue
}
}
if (resources.length === 0) {
return res
} else if (resources.length === 1) {
return resources[0]
}
const allKeys = [...new Set(resources.flatMap(resource => Object.keys(resource)))]
res = {}
allKeys.forEach(key => {
res[key] = Object.deepAssign(
...resources
.filter(resource => Object.prototype.hasOwnProperty.call(resource, key))
.map(resource => resource[key])
)
})
return res
}

View File

@ -112,6 +112,10 @@ export const constantRouterMap = [
path: '/guaranteeProcess',
component: () => import(/* webpackChunkName: "fail" */ '@/views/functional/guaranteeProcess.vue'),
},
{
path: '/roomStatistics',
component: () => import(/* webpackChunkName: "fail" */ '@/views/functional/roomStatistics.vue'),
},
{
path: '/404',
component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/404'),

View File

@ -49,7 +49,7 @@
display: none;
}
.ant-pro-basicLayout-content {
margin: 0;
margin: 0 !important;
> .ant-pro-basicLayout-children-content-wrap {
height: 100%;
> .ant-pro-grid-content {

View File

@ -0,0 +1,182 @@
<template>
<Grid :rows="[1, 1]" style="height: 100%">
<ModuleWrapper title="保障趋势">
<CustomChart chartId="bzqs-chart" :options="bzqsOptions"></CustomChart>
</ModuleWrapper>
<ModuleWrapper title="保障单位">
<CustomChart chartId="bzdw-chart" :options="bzdwOptions"></CustomChart>
</ModuleWrapper>
</Grid>
</template>
<script>
import { getAction } from '@/api/manage'
export default {
data() {
return {
roomId: '',
bzqsData: [],
bzdwData: [],
}
},
computed: {
bzqsOptions() {
// ammunition: 130
// food: 115
// fuel: 180
// medical: 80
// minute: "2025-09-19 21:37"
// resourceId: null
// resourceName: null
// water: 180
const ammunitionData = []
const foodData = []
const fuelData = []
const medicalData = []
const waterData = []
this.bzqsData.forEach((item) => {
ammunitionData.push([item.minute, item.ammunition])
foodData.push([item.minute, item.food])
fuelData.push([item.minute, item.fuel])
medicalData.push([item.minute, item.medical])
waterData.push([item.minute, item.water])
})
return {
grid: {
left: 60,
bottom: 40,
right: 40,
},
legend: {
data: ['弹药', '食物', '油料', '药材', '水'],
},
xAxis: {
type: 'time',
},
yAxis: {},
series: [
{
type: 'line',
name: '弹药',
data: ammunitionData,
},
{
type: 'line',
name: '食物',
data: foodData,
},
{
type: 'line',
name: '油料',
data: fuelData,
},
{
type: 'line',
name: '药材',
data: medicalData,
},
{
type: 'line',
name: '水',
data: waterData,
},
],
tooltip: {
show: true,
},
dataZoom: [{ type: 'inside' }],
}
},
bzdwOptions() {
const xAxisData = []
const ammunitionData = []
const foodData = []
const fuelData = []
const medicalData = []
const waterData = []
this.bzdwData.forEach((item) => {
xAxisData.push(item.resourceName)
ammunitionData.push(item.ammunition)
foodData.push(item.food)
fuelData.push(item.fuel)
medicalData.push(item.medical)
waterData.push(item.water)
})
return {
grid: {
left: 60,
bottom: 40,
right: 40,
},
legend: {
data: ['弹药', '食物', '油料', '药材', '水'],
},
xAxis: {
data: xAxisData,
},
yAxis: {},
series: [
{
type: 'bar',
barWidth: 30,
name: '弹药',
data: ammunitionData,
},
{
type: 'bar',
barWidth: 30,
name: '食物',
data: foodData,
},
{
type: 'bar',
barWidth: 30,
name: '油料',
data: fuelData,
},
{
type: 'bar',
barWidth: 30,
name: '药材',
data: medicalData,
},
{
type: 'bar',
barWidth: 30,
name: '水',
data: waterData,
},
],
tooltip: {
show: true,
},
}
},
},
created() {
this.roomId = this.$route.roomId
this.getBzqsData()
this.getBzdwData()
},
methods: {
async getBzqsData() {
try {
const res = await getAction('/scenario/battleConsume/statistic/minute')
this.bzqsData = res.data
} catch (error) {
console.log(error)
}
},
async getBzdwData() {
try {
const res = await getAction('/scenario/battleConsume/statistic/resource')
this.bzdwData = res.data
} catch (error) {
console.log(error)
}
},
},
}
</script>
<style lang="less" scoped></style>

View File

@ -80,6 +80,7 @@
<AntFormModal
:visible.sync="dchjModal.visible"
:title="dchjModal.title"
width="900px"
:formItems="dchjModal.formItems"
:formRules="dchjModal.formRules"
:formData="dchjModal.formData"
@ -108,24 +109,16 @@ export default {
immediate: false,
query: (queryParams) =>
this.$http({
url: `/environment/weather/list`,
url: `/baseData/weatherResource/list`,
method: 'get',
params: { id: this.xd.selectedKeys[0], ...queryParams },
}),
columns: [
{ dataIndex: 'serial' },
{ title: '区域', dataIndex: 'area' },
{ title: '日期', dataIndex: 'date' },
{ title: '天气', dataIndex: 'weather' },
{ title: '大气压', dataIndex: 'airPressure' },
{ title: '空气质量', dataIndex: 'airQuality' },
{ title: '湿度', dataIndex: 'humidity' },
{ title: '降水量', dataIndex: 'precipitation' },
{ title: '能见度', dataIndex: 'visibility' },
{ title: '风向', dataIndex: 'windDirection' },
{ title: '风力', dataIndex: 'windPower' },
{ title: '风速', dataIndex: 'windSpeed' },
{ dataIndex: 'action', width: 100 },
{ title: '天气类型', dataIndex: 'weatherType' },
{ title: '持续开始时间', dataIndex: 'lastBegTime' },
{ title: '持续结束时间', dataIndex: 'lastEndTime' },
// { dataIndex: 'action', width: 100 },
],
},
pageConfig: true,
@ -137,26 +130,30 @@ export default {
mode: '',
formItems: [
{
label: '区域',
prop: 'area',
label: '天气类型',
prop: 'weatherType',
component: 'AntOriginSelect',
options: { dataSource: () => this.$http({ url: `/system/area/getTreeSelect`, method: 'get' }) },
options: {
dataSource: () => ({
data: [
{ title: 'rain', id: 'rain' },
{ title: 'snow', id: 'snow' },
],
}),
},
},
{
label: '日期',
prop: 'date',
label: '持续开始时间',
prop: 'lastBegTimeStr',
component: 'a-date-picker',
options: { format: 'YYYY/MM/DD', valueFormat: 'YYYY/MM/DD' },
options: { showTime: true, valueFormat: 'YYYY-MM-DD HH:mm:ss' },
},
{
label: '持续结束时间',
prop: 'lastEndTimeStr',
component: 'a-date-picker',
options: { showTime: true, valueFormat: 'YYYY-MM-DD HH:mm:ss' },
},
{ label: '天气', prop: 'weather' },
{ label: '大气压', prop: 'airPressure' },
{ label: '空气质量', prop: 'airQuality' },
{ label: '湿度', prop: 'humidity' },
{ label: '降水量', prop: 'precipitation' },
{ label: '能见度', prop: 'visibility' },
{ label: '风向', prop: 'windDirection' },
{ label: '风力', prop: 'windPower' },
{ label: '风速', prop: 'windSpeed' },
],
formRules: {},
formData: {},
@ -175,17 +172,10 @@ export default {
}),
columns: [
{ dataIndex: 'serial' },
{ title: '区域', dataIndex: 'area' },
{ title: '持续时间', dataIndex: 'duration' },
{ title: '环境变化趋势', dataIndex: 'environmentalChangeTrends' },
{ title: '环境复杂度', dataIndex: 'environmentalComplexity' },
{ title: '磁场强度', dataIndex: 'fieldStrength' },
{ title: '频率', dataIndex: 'frequency' },
{ title: '频率区间', dataIndex: 'frequencyRang' },
{ title: '干扰幅度', dataIndex: 'interferenceAmplitude' },
{ title: '干扰源', dataIndex: 'interferenceSource' },
{ title: '干扰类型', dataIndex: 'interferenceType' },
{ title: '波型', dataIndex: 'waveType' },
{ title: '电磁强度', dataIndex: 'fieldStrength' },
{ title: '频率', dataIndex: 'ebeR' },
{ title: '经度', dataIndex: 'ebeLon' },
{ title: '纬度', dataIndex: 'ebeLat' },
{ dataIndex: 'action', width: 100 },
],
},
@ -198,21 +188,22 @@ export default {
mode: '',
formItems: [
{
label: '区域',
prop: 'area',
label: '电磁强度',
prop: 'fieldStrength',
component: 'AntOriginSelect',
options: { dataSource: () => this.$http({ url: `/system/area/getTreeSelect`, method: 'get' }) },
options: {
dataSource: () => ({
data: [
{ title: '高', id: '高' },
{ title: '中', id: '中' },
{ title: '低', id: '低' },
],
}),
},
},
{ label: '持续时间', prop: 'duration' },
{ label: '环境变化趋势', prop: 'environmentalChangeTrends' },
{ label: '环境复杂度', prop: 'environmentalComplexity' },
{ label: '磁场强度', prop: 'fieldStrength' },
{ label: '频率', prop: 'frequency' },
{ label: '频率区间', prop: 'frequencyRang' },
{ label: '干扰幅度', prop: 'interferenceAmplitude' },
{ label: '干扰源', prop: 'interferenceSource' },
{ label: '干扰类型', prop: 'interferenceType' },
{ label: '波型', prop: 'waveType' },
{ label: '频率', prop: 'ebeR' },
{ label: '经度', prop: 'ebeLon', component: 'LongitudeInput' },
{ label: '纬度', prop: 'ebeLat', component: 'LatitudeInput' },
],
formRules: {},
formData: {},
@ -258,26 +249,18 @@ export default {
handleOpenAddQxhjModal() {
this.qxhjModal.title = '新建气象环境'
this.qxhjModal.mode = 'add'
this.qxhjModal.formData = { sceneId: this.xd.selectedKeys[0] }
this.qxhjModal.formData = { scenarioId: this.xd.selectedKeys[0] }
this.qxhjModal.visible = true
},
async handleOpenEditQxhjModal(record) {
try {
const res = await this.$http({
url: `/environment/weather/${record.id}`,
method: 'get',
})
this.qxhjModal.title = `编辑气象环境`
this.qxhjModal.mode = 'edit'
this.qxhjModal.formData = { ...res.data }
this.qxhjModal.visible = true
} catch (error) {
console.log(error)
}
handleOpenEditQxhjModal(record) {
this.qxhjModal.title = `编辑气象环境`
this.qxhjModal.mode = 'edit'
this.qxhjModal.formData = { ...record }
this.qxhjModal.visible = true
},
handleSubmitQxhj(formData) {
return this.$http({
url: `/environment/weather/save`,
url: `/baseData/weatherResource/add`,
method: 'post',
data: formData,
})

View File

@ -1,8 +1,8 @@
<template>
<Flex fd="co" class="page-display">
<Flex ai="c" jc="sb" class="page-display-header">
<span class="page-display-title">显示子系统</span>
<span class="page-display-title">
<div class="page-display-title">显示子系统</div>
<div class="page-display-title">
推演想定{{ roomName }}-{{ scenarioName }}
<a-popover v-if="scenarioDetail" title="想定信息">
<template slot="content">
@ -23,8 +23,12 @@
</template>
<a-button type="text-primary" icon="exclamation-circle"></a-button>
</a-popover>
</span>
<span class="page-display-title">剩余 {{ roomInfo.remainTimeStr }}</span>
</div>
<div class="page-display-title">
<span v-if="roomInfo.mag">{{ roomInfo.mag }}倍速</span>
<span>{{ roomInfo.statusMapText[roomInfo.status] }}</span>
<span>剩余 {{ roomInfo.remainTimeStr }}</span>
</div>
</Flex>
<Grid class="page-display-main flex-1 oh" :rows="['30px', 1]" gap="0px">
<div class="tool-wrapper" style="grid-area: 1 / 1 / 2 / 2">
@ -50,6 +54,14 @@ export default {
initial: false,
ws: null,
roomInfo: {
mag: '',
status: '',
statusMapText: {
0: '待推演',
1: '推演中',
2: '暂停推演',
3: '推演结束',
},
remainTimeStr: '',
remainTime: '',
duringTime: '',
@ -107,6 +119,10 @@ export default {
this.ws = new window.MyWebsocket(`/ws/${this.scenarioId}/${this.roomId}`, (error, response) => {
if (error) return this.$message.error(error.message)
switch (response.cmdType) {
case 'room_info':
this.roomInfo.mag = response.data.mag
this.roomInfo.status = response.data.status
break
case 'update_time':
this.roomInfo.remainTimeStr = response.data.update_time_str
this.roomInfo.remainTime = response.data.remain_time
@ -145,6 +161,8 @@ export default {
this.ws.send({ cmdType: 'editScenarioInfo' })
//
this.ws.send({ cmdType: 'get_init_path' })
//
this.ws.send({ cmdType: 'get_room_info' })
},
closeWebsocket() {
this.ws && this.ws.close()

View File

@ -1,8 +1,8 @@
<template>
<Flex fd="co" class="page-model">
<Flex ai="c" jc="sb" class="page-model-header">
<span class="page-scene-title">场景编辑子系统</span>
<span class="page-scene-title">
<div class="page-scene-title">场景编辑子系统</div>
<div class="page-scene-title">
推演想定{{ roomName }}-{{ scenarioName }}
<a-popover v-if="scenarioDetail" title="想定信息">
<template slot="content">
@ -23,8 +23,12 @@
</template>
<a-button type="text-primary" icon="exclamation-circle"></a-button>
</a-popover>
</span>
<span class="page-scene-title">剩余 {{ roomInfo.remainTimeStr }}</span>
</div>
<div class="page-display-title">
<span v-if="roomInfo.mag">{{ roomInfo.mag }}倍速</span>
<span>{{ roomInfo.statusMapText[roomInfo.status] }}</span>
<span>剩余 {{ roomInfo.remainTimeStr }}</span>
</div>
</Flex>
<Grid class="page-model-main flex-1 oh" :columns="['320px', 1, '320px']" :rows="['30px', 1]" gap="0px">
<div class="tool-wrapper" style="grid-area: 1 / 1 / 2 / 4">
@ -143,6 +147,14 @@ export default {
initial: false,
ws: null,
roomInfo: {
mag: '',
status: '',
statusMapText: {
0: '待推演',
1: '推演中',
2: '暂停推演',
3: '推演结束',
},
remainTimeStr: '',
remainTime: '',
duringTime: '',
@ -301,6 +313,10 @@ export default {
this.ws = new window.MyWebsocket(`/ws/${this.scenarioId}/${this.roomId}`, (error, response) => {
if (error) return this.$message.error(error.message)
switch (response.cmdType) {
case 'room_info':
this.roomInfo.mag = response.data.mag
this.roomInfo.status = response.data.status
break
case 'update_time':
this.roomInfo.remainTimeStr = response.data.update_time_str
this.roomInfo.remainTime = response.data.remain_time
@ -339,6 +355,8 @@ export default {
this.ws.send({ cmdType: 'editScenarioInfo' })
//
this.ws.send({ cmdType: 'get_init_path' })
//
this.ws.send({ cmdType: 'get_room_info' })
},
closeWebsocket() {
this.ws && this.ws.close()

View File

@ -1,8 +1,8 @@
<template>
<Flex fd="co" class="page-scene">
<Flex ai="c" jc="sb" class="page-scene-header">
<span class="page-scene-title">场景编辑子系统</span>
<span class="page-scene-title">
<div class="page-scene-title">场景编辑子系统</div>
<div class="page-scene-title">
推演想定{{ roomName }}-{{ scenarioName }}<a-popover v-if="scenarioDetail" title="想定信息">
<template slot="content">
<div>
@ -22,13 +22,17 @@
</template>
<a-button type="text-primary" icon="exclamation-circle"></a-button>
</a-popover>
</span>
<span class="page-scene-title">剩余 {{ roomInfo.remainTimeStr }}</span>
</div>
<div class="page-display-title">
<span v-if="roomInfo.mag">{{ roomInfo.mag }}倍速</span>
<span>{{ roomInfo.statusMapText[roomInfo.status] }}</span>
<span>剩余 {{ roomInfo.remainTimeStr }}</span>
</div>
</Flex>
<Grid class="page-scene-main flex-1 oh" :columns="['320px', 1, '320px']" :rows="['30px', 1]" gap="0px">
<div class="tool-wrapper" style="grid-area: 1 / 1 / 2 / 4">
<a-menu :selectedKeys="[]" mode="horizontal" theme="dark">
<a-menu-item @click="() => {}"> 统计分析 </a-menu-item>
<a-menu-item @click="handleOpenStatisticPage()"> 统计分析 </a-menu-item>
</a-menu>
</div>
<div
@ -127,6 +131,14 @@ export default {
initial: false,
ws: null,
roomInfo: {
mag: '',
status: '',
statusMapText: {
0: '待推演',
1: '推演中',
2: '暂停推演',
3: '推演结束',
},
remainTimeStr: '',
remainTime: '',
duringTime: '',
@ -151,6 +163,7 @@ export default {
6: '保障任务',
},
},
childWindow: null,
}
},
computed: {
@ -213,6 +226,10 @@ export default {
this.scenarioId = this.$route.params.scenarioId
this.scenarioName = this.$route.params.scenarioName
this.getBlbzTreeData()
window.addEventListener('beforeunload', (e) => {
this.childWindow && this.childWindow.close()
return true // returnValue
})
},
mounted() {
this.cesium = new window.MyCesium('scene-cesium-container')
@ -264,6 +281,10 @@ export default {
this.ws = new window.MyWebsocket(`/ws/${this.scenarioId}/${this.roomId}`, (error, response) => {
if (error) return this.$message.error(error.message)
switch (response.cmdType) {
case 'room_info':
this.roomInfo.mag = response.data.mag
this.roomInfo.status = response.data.status
break
case 'update_time':
this.roomInfo.remainTimeStr = response.data.update_time_str
this.roomInfo.remainTime = response.data.remain_time
@ -302,6 +323,8 @@ export default {
this.ws.send({ cmdType: 'editScenarioInfo' })
//
this.ws.send({ cmdType: 'get_init_path' })
//
this.ws.send({ cmdType: 'get_room_info' })
},
closeWebsocket() {
this.ws && this.ws.close()
@ -331,6 +354,23 @@ export default {
latitude: +this.zzbzllSelectedFd.sdzy.lat,
})
},
handleOpenStatisticPage() {
if (this.roomInfo.status === 3) {
this.childWindow = window.open(
`/roomStatistics?roomId=${this.roomId}&scenarioId=${this.scenarioId}`,
'roomStatistics',
'height=' +
window.screen.height +
',width=600,' +
',top=0,left=0,toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,status=no'
)
} else if (this.roomInfo.status === 0) {
this.$message.error('请完成推演后再进行操作!')
} else {
this.$message.error('推演进行中,请在推演结束后再进行操作!')
}
},
},
}
</script>