LSSE-front/src/views/subsystem/model/index.vue

536 lines
16 KiB
Vue

<template>
<Flex fd="co" class="page-model">
<Flex ai="c" jc="sb" class="page-model-header">
<div class="page-model-title">仿真模型子系统</div>
<div class="page-model-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-model-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-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">
<a-menu :selectedKeys="[]" mode="horizontal" theme="dark">
<a-menu-item @click="() => {}"> 统计分析 </a-menu-item>
</a-menu>
</div>
<div
ref="model-cesium-container"
class="model-cesium-container"
id="model-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 height="55%" title="保障需求清单">
<!-- <template #title>
<a-radio-group v-model="qd.qdlx" button-style="solid">
<a-radio-button value="xqqd">需求清单</a-radio-button>
<a-radio-button value="bzqd">保障清单</a-radio-button>
</a-radio-group>
</template> -->
<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>
</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.id"
:originResourceId="zzbzllSelectedFd.resourceId"
:resourceName="zzbzllSelectedFd.title"
:resourceType="zzbzllSelectedFd.resourceType"
:type="zzbzllSelectedFd.type"
:modelData="jcsxModelData"
:readonly="true"
/>
</div>
</ModuleWrapper>
<ModuleWrapper :title="xdrw.resourceTypeMapTitle[zzbzllSelectedFd?.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.id"
:resourceType="zzbzllSelectedFd.resourceType"
:actionList="xdrwActionList"
:readonly="true"
/>
</div>
</ModuleWrapper>
</Grid>
</Grid>
</Flex>
</template>
<script>
import { getAction } from '@/api/manage'
import Zoom from '../scene/components/Zoom.vue'
import Jcsx from '../scene/components/Jcsx.vue'
import Zzxd from '../scene/components/Zzxd.vue'
export default {
name: 'SubsystemModel',
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: {
qdlx: 'xqqd',
qdColumns: [
// {
// dataIndex: 'dj',
// title: '等级',
// width: '62px',
// align: 'center',
// customRender: (text) => ['', '紧急', '加急', '一般'][text],
// customCell: (record) => ({ style: { color: ['', '#ff3838', '#feba29', '#ffffff'][record.dj] } }),
// },
// { dataIndex: 'bzxq', title: '保障需求' },
{ title: '保障需求', dataIndex: 'supplierType' },
{ title: '保障数量', dataIndex: 'supplierNum' },
],
qdList: [
{ id: '1', dj: '1', bzxq: '500枚防空导弹、6辆装甲车' },
{ id: '2', dj: '2', bzxq: '5吨汽油、100套军装' },
{ id: '3', dj: '3', bzxq: '15箱消炎药、6箱葡萄糖滴' },
{ id: '4', dj: '3', bzxq: '一吨饮用水' },
{ id: '5', dj: '3', bzxq: '1吨燃油、厨房抽烟机设备一' },
],
},
blbz: {
treeData: [],
},
rightViewer: '',
xdrw: {
resourceTypeMapTitle: {
5: '作战行动',
6: '保障任务',
},
},
}
},
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()
},
mounted() {
this.cesium = new window.MyCesium('model-cesium-container')
this.getScenarioDetail()
this.initWebsocket()
window.addEventListener('beforeunload', (e) => {
this.closeWebsocket()
return true
})
},
methods: {
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)
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,
})
},
},
}
</script>
<style lang="less" scoped>
.page-display {
height: 100%;
color: #ffffff;
.page-display-header {
height: 50px;
background-color: #0a2a3d;
padding: 0 20px;
.page-display-title {
color: #00d5fe;
font-size: 18px;
font-weight: bolder;
letter-spacing: 2px;
}
}
.page-display-main {
background-color: #022234;
}
}
.page-model {
height: 100%;
color: #ffffff;
.page-model-header {
height: 50px;
background-color: #0a2a3d;
padding: 0 20px;
.page-model-title {
color: #00d5fe;
font-size: 18px;
font-weight: bolder;
letter-spacing: 2px;
}
}
.page-model-main {
background-color: #022234;
}
}
.ant-menu-horizontal {
line-height: 30px;
}
.model-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>