LSSE-front/src/views/subsystem/scene/index.vue
2025-10-21 05:07:47 +08:00

591 lines
17 KiB
Vue

<template>
<Flex fd="co" class="page-scene">
<Flex ai="c" jc="sb" class="page-scene-header">
<div class="page-scene-title">场景编辑子系统</div>
<div class="page-scene-title">
推演想定{{ roomName }}-{{ scenarioName }}<a-popover v-if="scenarioDetail" title="想定信息">
<template slot="content">
<div>
<Flex>
<span>开始时间:</span>
<span style="max-width: 200px">{{ scenarioDetail.startTime }}</span>
</Flex>
<Flex>
<span>结束时间:</span>
<span style="max-width: 200px">{{ scenarioDetail.endTime }}</span>
</Flex>
<Flex>
<span>想定说明:</span>
<span style="max-width: 200px">{{ scenarioDetail.mark }}</span>
</Flex>
</div>
</template>
<a-button type="text-primary" icon="exclamation-circle"></a-button>
</a-popover>
</div>
<div class="page-scene-title">
<span v-if="roomInfo.mag">{{ roomInfo.mag }}倍速</span>&nbsp;
<span>{{ roomInfo.statusMapText[roomInfo.status] }}</span>&nbsp;
<span>想定时间 {{ roomInfo.scenarioTime }}</span>&nbsp;
<span>推演时间 {{ roomInfo.currentTime }}</span>&nbsp;
<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="handleOpenStatisticPage()"> 统计分析 </a-menu-item>
</a-menu>
</div>
<div
ref="scene-cesium-container"
class="scene-cesium-container"
id="scene-cesium-container"
style="grid-area: 2 / 1 / 3 / 4"
></div>
<div class="pr zi1" style="grid-area: 2 / 1 / 3 / 2">
<ModuleWrapper title="作战/保障力量" height="45%">
<div class="normal" style="padding: 5px; overflow-y: auto">
<a-tree
class="simulation-tree"
:treeData="zzbzllTreeData"
:selectedKeys.sync="zzbzll.selectedKeys"
:replaceFields="{ children: 'children', title: 'resourceName', key: 'id' }"
@select="handleSelectZzbzll"
>
</a-tree>
</div>
</ModuleWrapper>
<ModuleWrapper title="统计报表" height="10%"><div> <a-button type="primary" @click="downloadExcel1" >支援保障仿真效果统计表</a-button></div></ModuleWrapper>
<ModuleWrapper height="55%" title="保障需求清单">
<div class="normal" style="padding: 5px; overflow-y: auto">
<a-table
class="simulation-table-plain"
rowKey="id"
:columns="qd.qdColumns"
:dataSource="supplierRequests"
:pagination="false"
:bordered="true"
></a-table>
</div>
</ModuleWrapper>
<!-- <ModuleWrapper title="日志" height="40%">
<div class="normal"></div>
</ModuleWrapper> -->
</div>
<Grid class="pr zi1" :rows="rightRows" gap="0px" style="grid-area: 2 / 3 / 3 / 4">
<ModuleWrapper title="兵力编组">
<template #extra>
<Zoom :max="rightViewer === 'blbz'" @zoom-max="rightViewer = 'blbz'" @zoom-min="rightViewer = ''" />
</template>
<div class="normal" style="padding: 5px; overflow-y: auto">
<a-tree
class="simulation-tree"
:treeData="showBlbzCheckedTreeData"
:selectable="false"
:replaceFields="{ children: 'children', title: 'title', key: 'id' }"
>
</a-tree>
</div>
</ModuleWrapper>
<ModuleWrapper title="基础属性">
<template #extra>
<Zoom :max="rightViewer === 'jcsx'" @zoom-max="rightViewer = 'jcsx'" @zoom-min="rightViewer = ''" />
</template>
<div class="normal" style="padding: 15px 0">
<Jcsx
ref="jcsx"
v-if="zzbzllSelectedFd"
:scenarioId="scenarioId"
:resourceId="zzbzllSelectedFd.sdzy.id"
:originResourceId="zzbzllSelectedFd.sdzy.resourceId"
:resourceName="zzbzllSelectedFd.sdzy.resourceName"
:resourceType="zzbzllSelectedFd.sdzy.resourceType"
:type="zzbzllSelectedFd.sdzy.type"
:modelData="jcsxModelData"
:readonly="true"
/>
</div>
</ModuleWrapper>
<ModuleWrapper :title="xdrw.resourceTypeMapTitle[zzbzllSelectedFd?.sdzy.resourceType] || '行动任务'">
<template #extra>
<Zoom :max="rightViewer === 'xdrw'" @zoom-max="rightViewer = 'xdrw'" @zoom-min="rightViewer = ''" />
</template>
<div class="normal" style="padding: 0">
<Zzxd
ref="xdrw"
v-if="zzbzllSelectedFd"
:scenarioId="scenarioId"
:resourceId="zzbzllSelectedFd.sdzy.id"
:resourceType="zzbzllSelectedFd.sdzy.resourceType"
:actionList="xdrwActionList"
:readonly="true"
/>
</div>
</ModuleWrapper>
</Grid>
</Grid>
</Flex>
</template>
<script>
import { getAction } from '@/api/manage'
import Zoom from './components/Zoom.vue'
import Jcsx from './components/Jcsx.vue'
import Zzxd from './components/Zzxd.vue'
export default {
name: 'SubsystemScene',
components: {
Zoom,
Jcsx,
Zzxd,
},
data() {
return {
initial: false,
ws: null,
roomInfo: {
mag: '',
status: '',
statusMapText: {
0: '待推演',
1: '推演中',
2: '暂停推演',
3: '推演结束',
},
remainTimeStr: '',
remainTime: '',
duringTime: '',
currentTime: '',
scenarioTime: '',
roomData: [],
},
roomId: '',
roomName: '',
scenarioId: '',
scenarioName: '',
cesium: null,
scenarioDetail: null,
zzbzll: {
selectedKeys: [],
},
qd: {
qdColumns: [
{ title: '保障需求', dataIndex: 'supplierType' },
{ title: '保障数量', dataIndex: 'supplierNum' },
],
},
blbz: {
treeData: [],
},
rightViewer: '',
xdrw: {
resourceTypeMapTitle: {
5: '作战行动',
6: '保障任务',
},
},
childWindow: null,
}
},
computed: {
zzbzllTreeData() {
const zzbzllTreeData = [
{
id: '0',
resourceName: '红方',
selectable: false,
children: [],
},
{
id: '1',
resourceName: '蓝方',
selectable: false,
children: [],
},
]
this.roomInfo.roomData.forEach((item) => {
zzbzllTreeData[item.sdzy.type].children.push(item.sdzy)
})
return zzbzllTreeData
},
zzbzllSelectedFd() {
if (this.zzbzll.selectedKeys.length === 0) {
return null
}
return this.roomInfo.roomData.find((item) => item.sdzy.id === this.zzbzll.selectedKeys[0])
},
supplierRequests() {
return (this.zzbzllSelectedFd?.supplierRequests || []).map(item => ({
id: item.id,
supplierType:
{
fuel: '油料',
injured: '伤员',
ammunition: '弹药',
death: '死亡',
}[item.supplierType] || item.supplierType,
supplierNum: item.supplierNum,
}))
},
rightRows() {
switch (this.rightViewer) {
case 'blbz':
return [8, 1, 1]
case 'jcsx':
return [1, 8, 1]
case 'xdrw':
return [1, 1, 8]
default:
return [2, 4, 4]
}
},
blbzShowKeys() {
return this.zzbzllSelectedFd?.orgPostList.map((item) => item.orgId) || []
},
showBlbzCheckedTreeData() {
const target = []
this.getTree(target, this.blbz.treeData, this.blbzShowKeys)
return target
},
jcsxModelData() {
return this.zzbzllSelectedFd?.jbxx || {}
},
xdrwActionList() {
return this.zzbzllSelectedFd?.scenarioTasks || []
},
},
created() {
this.roomId = this.$route.params.roomId
this.roomName = this.$route.params.roomName
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')
this.getScenarioDetail()
this.initWebsocket()
window.addEventListener('beforeunload', (e) => {
this.closeWebsocket()
return true
})
},
methods: {
downloadExcel1() {
this.axios({
method: 'get',
url: '/downloadExcel1',
responseType: 'blob'
}).then(res => {
const blob = new Blob([res], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = '支援保障仿真效果统计表.xlsx'
link.click()
URL.revokeObjectURL(link.href)
this.loading = false
}).catch(() => {
this.loading = false
this.$message.error('下载失败')
})
},
getWeathers(){
this.$http({
url: `/baseData/weatherResource/list`,
method: 'get',
params: { scenarioId: this.scenarioId},
}).then(res => {
this.data = res.data
})
},
handleChange(value, key, column) {
const newData = [...this.data];
const target = newData.find(item => key === item.key);
if (target) {
target[column] = value;
this.data = newData;
}
},
edit(key) {
const newData = [...this.data];
const target = newData.find(item => key === item.key);
if (target) {
target.editable = true;
this.data = newData;
}
},
save(key) {
const newData = [...this.data];
const target = newData.find(item => key === item.key);
if (target) {
delete target.editable;
this.data = newData;
}
},
cancel(key) {
const newData = [...this.data];
const target = newData.find(item => key === item.key);
if (target) {
// Object.assign(target, this.newData.find(item => key === item.key));
delete target.editable;
this.data = newData;
}
},
handleDelete(key) {
this.data = this.data.filter(item => item.key !== key);
},
handleAdd() {
this.data.push({
key: Date.now(),
name: '',
age: null,
address: '',
editable: true
});
},
getTree(target, treeData, showKeys) {
treeData.forEach((item) => {
const newChildren = []
if (item.children && item.children.length > 0) {
this.getTree(newChildren, item.children, showKeys)
}
if (newChildren.length > 0 || showKeys.includes(+item.key)) {
target.push({ ...item, children: newChildren })
}
})
},
async getScenarioDetail() {
try {
const res = await getAction(`/baseData/scenario/${this.scenarioId}`)
this.scenarioDetail = res.data
const {
leftUpLng,
leftUpLat,
rightUpLng,
rightUpLat,
rightBottomLng,
rightBottomLat,
leftBottomLng,
leftBottomLat,
} = res.data
this.cesium.setClientByAllCorner(
[+leftUpLng, +leftUpLat],
[+rightUpLng, +rightUpLat],
[+rightBottomLng, +rightBottomLat],
[+leftBottomLng, +leftBottomLat]
)
} catch (error) {
console.log(error)
}
},
initWebsocket() {
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
this.roomInfo.duringTime = response.data.during_time
this.roomInfo.currentTime = response.data.current_time
this.roomInfo.scenarioTime = response.data.scenario_time
break
case 'start_rain':
this.cesium.addRain()
break
case 'stop_rain':
this.cesium.removeRain()
break
case 'start_snow':
this.cesium.addSnow()
break
case 'stop_snow':
this.cesium.removeSnow()
break
case 'path_init':
this.cesium.drawRouteByCoordinates(
response.data.points,
response.data.resourceId,
['red', 'blue'][response.data.teamType]
)
break
case 'path_update':
this.cesium.movePlotByCoordinates(response.data.resourceId, response.data.points)
break
case 'editScenarioInfo':
if (this.initial === false) {
this.roomInfo.roomData = JSON.parse(response.data)
console.log(this.roomInfo.roomData)
this.initPlots()
this.initial = true
}
break
case 'updScenarioInfo':
{
const data = JSON.parse(response.data)
const targetIndex = this.roomInfo.roomData.findIndex((item) => item.sdzy.id === data.sdzy.id)
this.$set(this.roomInfo.roomData, targetIndex, data)
}
break
default:
// console.log(response.cmdType, response.data)
break
}
})
// 初始化数据
this.ws.send({ cmdType: 'editScenarioInfo' })
// 初始化路径
this.ws.send({ cmdType: 'get_init_path' })
// 初始化倍速、状态
this.ws.send({ cmdType: 'get_room_info' })
// 初始化气象状态
this.ws.send({ cmdType: 'get_weather' })
},
closeWebsocket() {
this.ws && this.ws.close()
this.ws = null
},
async getBlbzTreeData() {
try {
const res = await getAction('/tree/organization')
this.blbz.treeData = res.data
} catch (error) {
console.log(error)
}
},
initPlots() {
this.roomInfo.roomData.forEach((item) => {
if (item.sdzy.lng && item.sdzy.lat) {
this.cesium.addPlotByLonLat(item.sdzy.imgBase64, { lon: +item.sdzy.lng, lat: +item.sdzy.lat }, item.sdzy.id)
}
})
},
handleSelectZzbzll(selectedKeys, { node }) {
this.cesium.setClientByCenter({
longitude: +this.zzbzllSelectedFd.sdzy.lng,
latitude: +this.zzbzllSelectedFd.sdzy.lat,
})
},
handleOpenStatisticPage() {
this.childWindow = window.open(
`/?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'
)
},
},
}
</script>
<style lang="less" scoped>
.page-scene {
height: 100%;
color: #ffffff;
.page-scene-header {
height: 50px;
background-color: #0a2a3d;
padding: 0 20px;
.page-scene-title {
color: #00d5fe;
font-size: 18px;
font-weight: bolder;
letter-spacing: 2px;
}
}
.page-scene-main {
background-color: #022234;
}
}
.ant-menu-horizontal {
line-height: 30px;
}
.scene-cesium-container {
width: 100%;
height: 100%;
overflow: hidden;
}
::v-deep {
.distance-legend {
left: 320px;
}
.compass {
right: 320px;
}
.navigation-controls {
right: 350px;
}
}
.simulation-tree::v-deep {
color: #a1c2d0;
li .ant-tree-node-content-wrapper {
color: #a1c2d0;
}
li .ant-tree-node-content-wrapper.ant-tree-node-selected {
background-color: #bae7ff44;
}
li .ant-tree-node-content-wrapper:hover {
background-color: #bae7ff22;
}
}
.simulation-table-plain::v-deep {
.ant-table {
color: #a1c2d0;
}
.ant-table-body > table {
border: none;
}
.ant-table-thead > tr > th {
background-color: #083a52;
color: #ffffff;
border-bottom-color: #05628e;
border-top: 1px solid #05628e;
&:last-child {
border-right-color: #05628e;
}
&:not(:last-child) {
border-right-color: #05628e3b;
}
&:first-child {
border-left: 1px solid #05628e;
}
&:not(:first-child) {
}
}
.ant-table-tbody > tr > td {
border-bottom-color: #064766;
&:last-child {
border-right-color: #064766;
}
&:not(:last-child) {
border-right-color: #05628e3b;
}
&:first-child {
border-left: 1px solid #064766;
}
&:not(:first-child) {
}
}
}
</style>