系统控制子系统

This commit is contained in:
liaoboping 2025-09-17 16:56:04 +08:00
parent c3e47c1738
commit 19acf4edb8
14 changed files with 481 additions and 22 deletions

View File

@ -2,5 +2,4 @@ NODE_ENV=development
VUE_APP_PREVIEW=true
VUE_APP_API_BASE_URL=/api
VUE_APP_API_URL=http://192.168.0.189:8099
VUE_APP_WEBSOCKET_URL=ws://192.168.0.96:9001
VUE_APP_MAPVIEW_URL = ws://192.168.0.189:8099

View File

@ -2,4 +2,3 @@ NODE_ENV=production
VUE_APP_PREVIEW=true
VUE_APP_API_BASE_URL=/api
VUE_APP_API_URL=http://192.168.0.189:8099
VUE_APP_WEBSOCKET_URL=ws://192.168.0.96:9001

2
public/config.js vendored
View File

@ -1,3 +1,4 @@
// @ts-ignore
window._CONFIG = {
ImageryProviderUrl: '/map/mapWX/{z}/{x}/{y}.jpg',
RoadProviderUrl: '',
@ -6,5 +7,6 @@ window._CONFIG = {
clientId: '0123456789',
evaluationSrc: 'http://127.0.0.1:8000',
VUE_APP_API_URL: 'http://192.168.0.189:8099',
VUE_APP_WEBSOCKET_URL: 'ws://192.168.0.189:8099',
VUE_APP_MAPVIEW_URL:'ws://192.168.0.189:8099'
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 B

View File

@ -47,7 +47,7 @@ export default {
font-weight: bold;
line-height: 26px;
letter-spacing: 2px;
color: #00deff;
color: #00d5fe;
padding: 0 8px;
}
}

View File

@ -6,7 +6,7 @@ export default class MyWebSocket {
*/
constructor(path, messageCallback) {
// 拼接完整 WebSocket 地址
const baseUrl = process.env.VUE_APP_WEBSOCKET_URL
const baseUrl = window._CONFIG.VUE_APP_WEBSOCKET_URL
this.url = new URL(path, baseUrl).href.replace(/^http/, 'ws')
this.messageCallback = messageCallback
this.reconnectAttempts = 0

View File

@ -82,12 +82,52 @@ export const constantRouterMap = [
},
],
},
{
path: '/subsystem',
name: 'Subsystem',
component: () => import(/* webpackChunkName: "fail" */ '@/views/subsystem/index.vue'),
redirect: '/404',
children: [
{
path: '/subsystem/control',
name: 'SubsystemControl',
component: () => import(/* webpackChunkName: "fail" */ '@/views/subsystem/control/index.vue'),
meta: { title: '系统控制子系统' },
},
// {
// path: '/subsystem/database',
// name: 'SimulationSceneDatabase',
// component: () => import(/* webpackChunkName: "fail" */ '@/views/subsystem/database/index.vue'),
// meta: { title: '数据库子系统' },
// },
// {
// path: '/subsystem/simulationModel',
// name: 'SimulationSceneSimulationModel',
// component: () => import(/* webpackChunkName: "fail" */ '@/views/subsystem/simulationModel/index.vue'),
// meta: { title: '仿真模型子系统' },
// },
// {
// path: '/subsystem/sceneEditing',
// name: 'SimulationSceneSceneEditing',
// component: () => import(/* webpackChunkName: "fail" */ '@/views/subsystem/sceneEditing/index.vue'),
// meta: { title: '场景编辑子系统' },
// },
// {
// path: '/subsystem/display',
// name: 'SimulationSceneDisplay',
// component: () => import(/* webpackChunkName: "fail" */ '@/views/subsystem/display/index.vue'),
// meta: { title: '显示子系统' },
// },
// {
// path: '/subsystem/evaluation',
// name: 'SimulationSceneEvaluation',
// component: () => import(/* webpackChunkName: "fail" */ '@/views/subsystem/evaluation/index.vue'),
// meta: { title: '评估子系统' },
// },
],
},
{
path: '/404',
component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/404'),
},
{
path: '*',
redirect: '/404',
},
]

View File

@ -0,0 +1,382 @@
<template>
<Flex fd="co" class="page-control">
<Flex ai="c" class="page-control-header">
<span class="page-control-title">系统控制子系统</span>
</Flex>
<Grid :columns="[2, 3]" class="page-control-main flex-1 oh">
<ModuleWrapper title="房间列表">
<template #extra>
<a-button
type="text-primary"
icon="sync"
style="font-size: 24px; line-height: 1.2"
@click="getRoomList"
></a-button>
</template>
<Flex v-loading="room.loading" fd="co" class="normal">
<Flex fw="w" ac="fs" class="room-list flex-1">
<div class="room-item" v-for="item in room.listData" :key="item.id">
<Flex class="room-card">
<Flex fd="co" jc="c" class="room-info flex-1">
<Flex ai="c" jc="sb">
<div class="room-name">
<a-icon type="home" style="margin-right: 10px" />
{{ item.roomName }}
</div>
<div class="room-status" :style="{ color: statusMapColor[item.status] }">
{{ statusMpText[item.status] }}
</div>
</Flex>
<div class="room-scenario-relation">想定名称{{ item.scenarioName }}</div>
<div class="room-create-time">{{ item.createTime.slice(0, 19).replace('T', ' ') }}</div>
</Flex>
<Flex class="room-enter" ai="c" title="查看详情" @click="handleSelectRoom(item)">
<a-icon type="right" style="font-size: 20px" />
</Flex>
</Flex>
</div>
</Flex>
<Flex ai="c" jc="c">
<a-pagination
v-model="room.pagination.pageNum"
:total="room.pagination.total"
:pageSize="room.pagination.pageSize"
show-less-items
:showTotal="(total) => `共${total}条`"
@change="getRoomList"
/>
</Flex>
</Flex>
</ModuleWrapper>
<ModuleWrapper title="详情信息">
<template #extra>
<a-button
v-if="detail.roomId"
type="text-primary"
icon="sync"
style="font-size: 24px; line-height: 1.2"
@click="getRoomDetail()"
></a-button>
</template>
<Flex fd="co" class="normal detail-module">
<div class="room-name">推演实例{{ detail.data.roomName }}</div>
<Flex fd="co" class="detail-module-item">
<span class="label"> 推演控制{{ detail.data.mag }}倍速 {{ statusMpText[detail.data.status] }} </span>
<Flex jc="sa" class="content-wrapper">
<a-button
type="text"
class="room-control-btn flex ai-c"
:disabled="!(controlable && able1)"
@click="sendWensocket('1')"
>
<img src="~@/assets/images/control/u157.png" alt="" />
<span>减速</span>
</a-button>
<a-button
type="text"
class="room-control-btn flex ai-c"
:disabled="!(controlable && able2)"
@click="sendWensocket('2')"
>
<img src="~@/assets/images/control/u155.png" alt="" />
<span>开始</span>
</a-button>
<a-button
type="text"
class="room-control-btn flex ai-c"
:disabled="!(controlable && able3)"
@click="sendWensocket('3')"
>
<img src="~@/assets/images/control/u161.png" alt="" />
<span>暂停</span>
</a-button>
<a-button
type="text"
class="room-control-btn flex ai-c"
:disabled="!(controlable && able4)"
@click="sendWensocket('4')"
>
<img src="~@/assets/images/control/u163.png" alt="" />
<span>终止</span>
</a-button>
<a-button
type="text"
class="room-control-btn flex ai-c"
:disabled="!(controlable && able5)"
@click="sendWensocket('5')"
>
<img src="~@/assets/images/control/u159.png" alt="" />
<span>加速</span>
</a-button>
</Flex>
</Flex>
<Flex class="detail-module-item">
<span class="label">想定名称</span>
<div class="content-wrapper flex-1">{{ detail.data.scenario.startTime }}</div>
</Flex>
<Flex class="detail-module-item">
<span class="label">开始时间</span>
<div class="content-wrapper flex-1">{{ detail.data.scenario.startTime }}</div>
</Flex>
<Flex class="detail-module-item">
<span class="label">结束时间</span>
<div class="content-wrapper flex-1">{{ detail.data.scenario.endTime }}</div>
</Flex>
<Flex class="detail-module-item">
<span class="label">想定地点</span>
<div class="content-wrapper flex-1">{{ detail.data.scenario.location }}</div>
</Flex>
<Flex class="detail-module-item">
<span class="label">想定描述</span>
<div class="content-wrapper flex-1" style="height: 96px">{{ detail.data.scenario.desc }}</div>
</Flex>
<Flex fd="co" class="detail-module-item flex-1">
<span class="label">消息日志</span>
<Flex fd="co" class="content-wrapper flex-1">
<div class="log-list flex-1 scroller-y">
<Flex v-for="item in detail.data.roomLogs" :key="item.id" class="log-item">
<div class="log-time">{{ item.logTime.replace('T', ' ') }}</div>
<div class="log-message">{{ item.message }}</div>
</Flex>
</div>
</Flex>
</Flex>
</Flex>
</ModuleWrapper>
</Grid>
</Flex>
</template>
<script>
import { getAction } from '@/api/manage'
export default {
name: 'SubsystemControl',
data() {
return {
ws: null,
room: {
loading: false,
listData: [],
pagination: {
total: 0,
pageNum: 1,
pageSize: 10,
},
},
detail: {
loading: false,
roomId: '',
scenarioId: '',
data: { scenario: {}, roomLogs: [] },
},
statusMpText: {
0: '待推演',
1: '推演中',
2: '暂停中',
3: '已结束',
},
statusMapColor: {
0: '#00C7FE',
1: '#33FF00',
2: '#FFFF99',
3: '#FF0000',
},
}
},
computed: {
controlable() {
return this.detail.roomId
},
able1() {
return this.detail.data.mag > 1
},
able2() {
return this.detail.data.status !== 1
},
able3() {
return this.detail.data.status === 1
},
able4() {
return this.detail.data.status === 1
},
able5() {
return this.detail.data.mag < 64
},
},
created() {
this.getRoomList()
},
beforeDestroy() {
this.closeWebsocket()
},
methods: {
async getRoomList() {
try {
this.room.loading = true
const res = await getAction('/scenario/room/list', {
pageNum: this.room.pagination.pageNum,
pageSize: this.room.pagination.pageSize,
})
this.room.listData = res.data.data
this.room.pagination.total = res.data.totalCount
} catch (error) {
console.log(error)
} finally {
this.room.loading = false
}
},
handleSelectRoom(item) {
this.detail.roomId = item.id
this.detail.scenarioId = item.scenarioId
this.closeWebsocket()
this.initWebsocket()
this.getRoomDetail()
},
initWebsocket() {
this.ws = new window.MyWebsocket(`/ws/${this.detail.scenarioId}/${this.detail.roomId}`, (...rest) => {
console.log('----rest----', ...rest)
})
},
sendWensocket(cmdType, data = {}) {
this.ws.send({
cmdType,
room: this.detail.roomId,
scenariold: this.detail.scenarioId,
data,
})
},
closeWebsocket() {
this.ws && this.ws.close()
},
async getRoomDetail() {
try {
this.detail.loading = true
const res = await getAction('/scenario/room/view', { id: this.detail.roomId })
this.detail.data = res.data
} catch (error) {
console.log(error)
} finally {
this.detail.loading = false
}
},
},
}
</script>
<style lang="less" scoped>
.page-control {
height: 100%;
color: #ffffff;
.page-control-header {
height: 50px;
background-color: #0a2a3d;
padding: 0 20px;
.page-control-title {
color: #00d5fe;
font-size: 18px;
font-weight: bolder;
letter-spacing: 2px;
}
}
.page-control-main {
background-color: #022234;
padding: 40px 64px;
}
}
.room-item {
width: 50%;
height: 20%;
padding: 10px;
.room-card {
width: 100%;
height: 100%;
border-radius: 10px;
border: 1px solid #00d5fe;
.room-info {
padding: 10px;
border-right: 1px solid #00d5fe;
}
.room-name {
font-size: 26px;
font-weight: bolder;
}
.room-status {
font-size: 20px;
font-weight: bolder;
}
.room-create-time {
font-size: 14px;
font-style: italic;
color: #888888;
}
.room-enter {
border-radius: 0 10px 10px 0;
cursor: pointer;
padding: 0 10px;
}
.room-enter:hover {
background-color: #10475f;
}
}
}
::v-deep {
.ant-pagination-total-text {
font-size: 16px;
color: #ffffff;
}
}
.detail-module {
.detail-module-item {
margin-bottom: 10px;
}
.room-name {
font-size: 18px;
line-height: 32px;
font-weight: bolder;
color: #00d5fe;
text-align: center;
letter-spacing: 1px;
}
.label {
color: #00c7fe;
font-size: 14px;
line-height: 35px;
margin-right: 10px;
}
.content-wrapper {
background-color: rgba(16, 71, 95, 1);
border: 2px solid rgba(5, 57, 82, 1);
font-size: 14px;
line-height: 21px;
padding: 5px 16px;
color: #03b1f2;
}
.room-control-btn {
color: #03b1f2;
font-size: 32px;
font-weight: bolder;
letter-spacing: 2px;
display: flex;
margin: 10px 0;
img + span {
margin-left: 10px;
}
}
.room-control-btn[disabled='disabled'] {
img {
opacity: 0.3;
}
}
.room-control-btn + .room-control-btn {
margin-left: 20px;
}
.log-list {
margin: 10px 0;
color: #00c7fe;
.log-time {
margin-right: 5px;
}
}
}
</style>

View File

@ -0,0 +1,26 @@
<template>
<router-view></router-view>
</template>
<script>
export default {
name: 'Subsystem',
}
</script>
<style lang="less" scoped>
::v-deep {
::-webkit-scrollbar {
width: 15px;
background-color: #0c2e3d;
}
::-webkit-scrollbar-button {
width: 15px;
height: 25px;
background-color: transparent;
}
::-webkit-scrollbar-thumb {
background-color: #386477;
}
}
</style>

View File

@ -28,6 +28,7 @@ import { mapState } from 'vuex'
export default {
data() {
return {
openChildren: [],
systemPathMap: {
db_system: '/simulationScene/database',
simulation_system: '/simulationScene/simulationModel',
@ -40,7 +41,7 @@ export default {
moduleCode: 'db_system',
moduleName: '数据库子系统',
icon: require('@/assets/images/simulation-scene/system-icon/database.png'),
modulePath: '/simulationScene/database',
modulePath: '/databaseSystem/zzllsjk',
},
{
moduleCode: 'simulation_system',
@ -64,13 +65,13 @@ export default {
moduleCode: 'evaluation_system',
moduleName: '评估子系统',
icon: require('@/assets/images/simulation-scene/system-icon/evaluation.png'),
modulePath: '/simulationScene/evaluation',
modulePath: window._CONFIG.evaluationSrc,
},
{
moduleCode: 'control_system',
moduleName: '系统控制子系统',
icon: require('@/assets/images/simulation-scene/system-icon/database.png'),
modulePath: '/simulationScene/centralControl',
modulePath: '/subSystem/control',
},
],
}
@ -80,16 +81,26 @@ export default {
userInfo: (state) => state.user.info,
}),
},
created() {
window.addEventListener('beforeunload', (e) => {
this.openChildren.forEach((childWindow) => {
childWindow && childWindow.close()
})
return true // returnValue
})
},
methods: {
handleClick(module) {
window.open(
window.location.origin + module.modulePath,
'_blank',
'height=' +
window.screen.height +
',width=' +
window.screen.width +
',top=0,left=0,toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,status=no'
this.openChildren.push(
window.open(
module.modulePath,
'_blank',
'height=' +
window.screen.height +
',width=' +
window.screen.width +
',top=0,left=0,toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,status=no'
)
)
},
},
@ -101,7 +112,7 @@ export default {
font-size: 20px;
line-height: 50px;
font-weight: bolder;
color: #00deff;
color: #00d5fe;
letter-spacing: 2px;
margin: 0 20px;
}
@ -120,6 +131,6 @@ export default {
}
}
.module-block:hover {
border-color: #00deff;
border-color: #00d5fe;
}
</style>