websocket
This commit is contained in:
parent
8d8797f54a
commit
39dd3a2f8b
2
src/components/Common/Bus/index.js
Normal file
2
src/components/Common/Bus/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
import Vue from 'vue'
|
||||
export default new Vue()
|
131
src/components/Common/Websocket/index.js
Normal file
131
src/components/Common/Websocket/index.js
Normal file
|
@ -0,0 +1,131 @@
|
|||
export default class MyWebSocket {
|
||||
/**
|
||||
* 创建 WebSocket 实例
|
||||
* @param {string} path - WebSocket 服务路径
|
||||
* @param {Function} messageCallback - 消息处理回调函数 (error, data) => void
|
||||
*/
|
||||
constructor(path, messageCallback) {
|
||||
// 拼接完整 WebSocket 地址
|
||||
const baseUrl = process.env.VUE_APP_WEBSOCKET_URL
|
||||
this.url = new URL(path, baseUrl).href.replace(/^http/, 'ws')
|
||||
this.messageCallback = messageCallback
|
||||
this.reconnectAttempts = 0
|
||||
this.maxReconnectAttempts = 5
|
||||
this.reconnectInterval = 2000
|
||||
this.messageQueue = []
|
||||
this.connectionPromise = null
|
||||
|
||||
this._connect()
|
||||
}
|
||||
|
||||
/** 建立 WebSocket 连接 */
|
||||
_connect() {
|
||||
this.ws = new WebSocket(this.url)
|
||||
|
||||
this.ws.addEventListener('open', () => {
|
||||
this.reconnectAttempts = 0 // 重置重连计数
|
||||
this._flushQueue() // 发送队列中的消息
|
||||
})
|
||||
|
||||
this.ws.addEventListener('message', (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
this.messageCallback(null, data)
|
||||
} catch (error) {
|
||||
this.messageCallback(new Error(`消息解析失败: ${error.message} | 原始数据: ${event.data}`), null)
|
||||
}
|
||||
})
|
||||
|
||||
this.ws.addEventListener('error', (error) => {
|
||||
this.messageCallback(new Error(`连接错误: ${error.message || '未知错误'}`), null)
|
||||
})
|
||||
|
||||
this.ws.addEventListener('close', (event) => {
|
||||
// 非正常关闭时尝试重连
|
||||
if (event.code !== 1000 && this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
setTimeout(() => {
|
||||
this.reconnectAttempts++
|
||||
this._connect()
|
||||
}, this.reconnectInterval)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 发送队列中缓存的消息 */
|
||||
_flushQueue() {
|
||||
while (this.messageQueue.length > 0 && this.isConnected()) {
|
||||
const message = this.messageQueue.shift()
|
||||
this._sendInternal(message)
|
||||
}
|
||||
}
|
||||
|
||||
/** 内部发送方法 */
|
||||
_sendInternal(data) {
|
||||
if (!this.isConnected()) return
|
||||
|
||||
try {
|
||||
const payload = JSON.stringify(data)
|
||||
this.ws.send(payload)
|
||||
} catch (error) {
|
||||
this.messageCallback(new Error(`消息发送失败: ${error.message}`), null)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查连接状态
|
||||
* @returns {boolean} 是否已连接
|
||||
*/
|
||||
isConnected() {
|
||||
return this.ws?.readyState === WebSocket.OPEN
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
* @param {Object} data - 要发送的数据
|
||||
*/
|
||||
send(data) {
|
||||
if (this.isConnected()) {
|
||||
this._sendInternal(data)
|
||||
} else {
|
||||
// 连接未就绪时缓存消息
|
||||
this.messageQueue.push(data)
|
||||
|
||||
// 首次连接时创建连接等待
|
||||
if (!this.connectionPromise) {
|
||||
this.connectionPromise = new Promise((resolve) => {
|
||||
const check = () => {
|
||||
if (this.isConnected()) {
|
||||
resolve()
|
||||
} else {
|
||||
setTimeout(check, 100)
|
||||
}
|
||||
}
|
||||
check()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 关闭连接 */
|
||||
close() {
|
||||
if (this.ws) {
|
||||
this.ws.close(1000, '用户主动关闭')
|
||||
this.reconnectAttempts = this.maxReconnectAttempts // 禁止重连
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // 使用示例
|
||||
// const ws = new MyWebSocket('/notification', (err, data) => {
|
||||
// if (err) {
|
||||
// console.error('WebSocket 错误:', err);
|
||||
// return;
|
||||
// }
|
||||
// console.log('收到消息:', data);
|
||||
// });
|
||||
|
||||
// // 发送消息
|
||||
// ws.send({ type: 'ping', timestamp: Date.now() });
|
||||
|
||||
// // 关闭连接(在组件销毁时调用)
|
||||
// // ws.close();
|
|
@ -10,7 +10,9 @@ import WangEditor from './WangEditor/Index.vue'
|
|||
|
||||
import Loading from './Directives/Loading'
|
||||
|
||||
import Bus from './Bus/index'
|
||||
import MyCesium from './Cesium/index'
|
||||
import MyWebsocket from './Websocket/index'
|
||||
|
||||
export default {
|
||||
install(Vue) {
|
||||
|
@ -26,6 +28,8 @@ export default {
|
|||
|
||||
Vue.directive('loading', Loading)
|
||||
|
||||
window.$bus = Bus
|
||||
window.MyCesium = MyCesium
|
||||
window.MyWebsocket = MyWebsocket
|
||||
},
|
||||
}
|
||||
|
|
|
@ -48,9 +48,9 @@
|
|||
<img class="image" :src="right.detail.image || '/mockData/fe6ad2d8-da11-04d8-f447-0d8175826e28.png'" />
|
||||
<div class="name">
|
||||
{{ right.detail.name }}
|
||||
<a-button type="text-primary" icon="edit" @click="handleOpenMcModal"></a-button>
|
||||
<a-button v-if="hasDetail" type="text-primary" icon="edit" @click="handleOpenMcModal"></a-button>
|
||||
</div>
|
||||
<div class="zt-item flex ai-c jc-sb">
|
||||
<!-- <div class="zt-item flex ai-c jc-sb">
|
||||
<span style="min-width: 100px">推演方:</span>
|
||||
<a-select
|
||||
style="width: 120px"
|
||||
|
@ -61,14 +61,18 @@
|
|||
<a-select-option value="1">红方</a-select-option>
|
||||
<a-select-option value="2">蓝方</a-select-option>
|
||||
</a-select>
|
||||
</div> -->
|
||||
<div class="zt-item flex ai-c">
|
||||
<span style="min-width: 100px">推演方:</span>
|
||||
<span>{{ right.detail.position.deduceTypeName }}</span>
|
||||
</div>
|
||||
<div class="zt-item flex ai-c">
|
||||
<span style="min-width: 100px">类型:</span>
|
||||
<span></span>
|
||||
<span>{{ right.detail.position.logisticType | logisticTypeFormat }}</span>
|
||||
</div>
|
||||
<div class="zt-item flex ai-c">
|
||||
<span style="min-width: 100px">方向:</span>
|
||||
<span></span>
|
||||
<span>{{ right.detail.position.direction | numberFormat }}°</span>
|
||||
</div>
|
||||
<div class="zt-item flex ai-c">
|
||||
<span style="min-width: 100px">速度:</span>
|
||||
|
@ -77,17 +81,17 @@
|
|||
<div class="zt-item flex ai-c">
|
||||
<span style="min-width: 100px">经度:</span>
|
||||
<span class="flex-1">{{ right.detail.position.lng | lonFormat }}</span>
|
||||
<a-button type="text-primary" icon="edit" @click="handleOpenLonlatModal"></a-button>
|
||||
<a-button v-if="hasDetail" type="text-primary" icon="edit" @click="handleOpenLonlatModal"></a-button>
|
||||
</div>
|
||||
<div class="zt-item flex ai-c">
|
||||
<span style="min-width: 100px">纬度:</span>
|
||||
<span class="flex-1">{{ right.detail.position.lat | latFormat }}</span>
|
||||
<a-button type="text-primary" icon="edit" @click="handleOpenLonlatModal"></a-button>
|
||||
<a-button v-if="hasDetail" type="text-primary" icon="edit" @click="handleOpenLonlatModal"></a-button>
|
||||
</div>
|
||||
<div class="zt-item flex ai-c">
|
||||
<span style="min-width: 100px">高度/深度:</span>
|
||||
<span class="flex-1">{{ right.detail.position.height | numberFormat }} 米(海拔)</span>
|
||||
<a-button type="text-primary" icon="edit" @click="handleOpenHdModal()"></a-button>
|
||||
<a-button v-if="hasDetail" type="text-primary" icon="edit" @click="handleOpenHdModal()"></a-button>
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel class="simulation-collapse-item" key="2" header="人员状态属性">
|
||||
|
@ -200,31 +204,40 @@
|
|||
<a-collapse-panel class="simulation-collapse-item" key="9" header="保障配置">
|
||||
<div class="zt-item flex ai-c jc-sb">
|
||||
<a-button type="primary">兵力编组</a-button>
|
||||
<a-button type="primary">关注</a-button>
|
||||
<!-- <a-button type="primary">关注</a-button> -->
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</div>
|
||||
<div v-if="right.radioType === 'zzxd'">
|
||||
<div class="zzxd-wrapper flex-v">
|
||||
<div class="zzxd-header">
|
||||
<div v-if="right.radioType === 'zzxd'" class="zzxd-wrapper flex-v">
|
||||
<div class="zzxd-header flex ai-c jc-sb">
|
||||
<div class="zzxd-title">作战行动</div>
|
||||
<div>
|
||||
<a-button type="text-primary" icon="menu"></a-button>
|
||||
<a-button type="text-primary" icon="plus"></a-button>
|
||||
<a-button type="text-primary" icon="edit"></a-button>
|
||||
<a-button type="text-primary" icon="delete"></a-button>
|
||||
<a-button type="text-primary" icon="plus" @click="handleOpenAddActionModal"></a-button>
|
||||
<a-button type="text-primary" icon="edit" @click="handleOpenEditActionModal"></a-button>
|
||||
<a-button type="text-primary" icon="delete" @click="handleDeleteAction"></a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<a-steps progress-dot :current="1" direction="vertical">
|
||||
<a-step
|
||||
<div class="flex-1" style="overflow-y: auto">
|
||||
<div
|
||||
v-for="item in right.detail.actionList"
|
||||
:key="item.id"
|
||||
:title="item.typeName"
|
||||
:description="`开始时间:${item.beginDateTime}. 结束时间:${item.endDateTime}`"
|
||||
/>
|
||||
</a-steps>
|
||||
class="action-item flex"
|
||||
@click="right.checkedAction = item"
|
||||
>
|
||||
<div class="action-icon">
|
||||
<div class="action-line"></div>
|
||||
<a-radio
|
||||
:checked="right.checkedAction && right.checkedAction.id === item.id"
|
||||
style="margin-right: 0"
|
||||
></a-radio>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="action-title">{{ item.typeName || '- -' }}</div>
|
||||
<div class="action-time">开始时间:{{ item.beginDateTime }}</div>
|
||||
<div class="action-time">结束时间:{{ item.endDateTime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -246,7 +259,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</ModuleWrapper>
|
||||
<a-modal v-model="mcModal.visible" title="编辑单元名称" :maskClosable="false" width="400px" @ok="handleSubmilMc">
|
||||
<a-modal
|
||||
v-model="mcModal.visible"
|
||||
title="编辑单元名称"
|
||||
:maskClosable="false"
|
||||
width="400px"
|
||||
:destroyOnClose="true"
|
||||
@ok="handleSubmilMc"
|
||||
>
|
||||
<a-row>
|
||||
<a-col :span="6">
|
||||
<span>名称</span>
|
||||
|
@ -261,6 +281,7 @@
|
|||
title="修改单元经纬度"
|
||||
:maskClosable="false"
|
||||
width="600px"
|
||||
:destroyOnClose="true"
|
||||
@ok="handleSubmilLonlat"
|
||||
>
|
||||
<LonLatInput :lon.sync="lonlatModal.lon" :lat.sync="lonlatModal.lat" />
|
||||
|
@ -270,6 +291,7 @@
|
|||
title="设置单元高度/深度"
|
||||
:maskClosable="false"
|
||||
width="400px"
|
||||
:destroyOnClose="true"
|
||||
@ok="handleSubmilHd"
|
||||
>
|
||||
<a-row>
|
||||
|
@ -281,6 +303,15 @@
|
|||
</a-col>
|
||||
</a-row>
|
||||
</a-modal>
|
||||
<a-modal
|
||||
v-model="actionModal.visible"
|
||||
:title="actionModal.title"
|
||||
:maskClosable="false"
|
||||
:destroyOnClose="true"
|
||||
@ok="handleSubmilAction"
|
||||
>
|
||||
<div></div>
|
||||
</a-modal>
|
||||
</Grid>
|
||||
</template>
|
||||
|
||||
|
@ -293,6 +324,18 @@ export default {
|
|||
LonLatInput,
|
||||
},
|
||||
filters: {
|
||||
logisticTypeFormat(v) {
|
||||
return {
|
||||
1: '信息对抗分队',
|
||||
2: '边防作战分队',
|
||||
3: '防化保障分队',
|
||||
4: '火力打击分队',
|
||||
5: '餐饮保障分队',
|
||||
6: '运输保障分队',
|
||||
7: '医疗分队',
|
||||
8: '工兵分队',
|
||||
}[v]
|
||||
},
|
||||
numberFormat(v) {
|
||||
if (typeof v === 'number' && v) {
|
||||
return +v.toFixed(2)
|
||||
|
@ -386,6 +429,7 @@ export default {
|
|||
actionList: [],
|
||||
equipmentList: [],
|
||||
},
|
||||
checkedAction: null,
|
||||
},
|
||||
mcModal: {
|
||||
visible: false,
|
||||
|
@ -400,8 +444,16 @@ export default {
|
|||
visible: false,
|
||||
hd: 0,
|
||||
},
|
||||
actionModal: {
|
||||
visible: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasDetail() {
|
||||
return Boolean(this.right.detail.id)
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.cesium = new window.MyCesium('cesium-container')
|
||||
this.scenarioId = 2733
|
||||
|
@ -442,6 +494,7 @@ export default {
|
|||
},
|
||||
handleSelectTree(selectedKeys, { node }) {
|
||||
this.right.detail = node.dataRef
|
||||
this.right.checkedAction = null
|
||||
},
|
||||
onSearch(e) {
|
||||
console.log('----', e, e.target.value)
|
||||
|
@ -454,13 +507,14 @@ export default {
|
|||
async handleSubmilMc() {
|
||||
try {
|
||||
this.$http({
|
||||
url: '/save',
|
||||
url: `/scenario/power/modifyUnit/${this.scenarioId}`,
|
||||
method: 'post',
|
||||
data: {
|
||||
mc: this.mcModal.mc,
|
||||
id: this.right.detail.id,
|
||||
name: this.mcModal.mc,
|
||||
},
|
||||
})
|
||||
this.$message('编辑单元名称成功')
|
||||
this.$message.success('编辑单元名称成功')
|
||||
this.right.detail.name = this.mcModal.mc
|
||||
this.mcModal.visible = false
|
||||
} catch (error) {
|
||||
|
@ -476,14 +530,17 @@ export default {
|
|||
async handleSubmilLonlat() {
|
||||
try {
|
||||
this.$http({
|
||||
url: '/save',
|
||||
url: `/scenario/power/modifyUnit/${this.scenarioId}`,
|
||||
method: 'post',
|
||||
data: {
|
||||
lon: this.lonlatModal.lon,
|
||||
id: this.right.detail.id,
|
||||
position: {
|
||||
lng: this.lonlatModal.lon,
|
||||
lat: this.lonlatModal.lat,
|
||||
},
|
||||
},
|
||||
})
|
||||
this.$message('修改单元经纬度成功')
|
||||
this.$message.success('修改单元经纬度成功')
|
||||
this.right.detail.position.lng = '' + this.lonlatModal.lon
|
||||
this.right.detail.position.lat = '' + this.lonlatModal.lat
|
||||
this.lonlatModal.visible = false
|
||||
|
@ -499,19 +556,59 @@ export default {
|
|||
async handleSubmilHd() {
|
||||
try {
|
||||
this.$http({
|
||||
url: '/save',
|
||||
url: `/scenario/power/modifyUnit/${this.scenarioId}`,
|
||||
method: 'post',
|
||||
data: {
|
||||
hd: this.hdModal.hd,
|
||||
id: this.right.detail.id,
|
||||
position: {
|
||||
height: this.hdModal.hd,
|
||||
},
|
||||
},
|
||||
})
|
||||
this.$message('修改单元高度/深度成功')
|
||||
this.$message.success('修改单元高度/深度成功')
|
||||
this.right.detail.position.height = this.hdModal.hd
|
||||
this.hdModal.visible = false
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
|
||||
handleOpenAddActionModal() {
|
||||
this.actionModal.title = '添加事件信息'
|
||||
this.actionModal.formData = {}
|
||||
this.actionModal.visible = true
|
||||
},
|
||||
handleOpenEditActionModal() {
|
||||
this.actionModal.title = '修改事件信息'
|
||||
this.actionModal.formData = { ...this.right.checkedAction }
|
||||
this.actionModal.visible = true
|
||||
},
|
||||
async handleSubmilAction() {
|
||||
try {
|
||||
await this.$http({
|
||||
url: '/save',
|
||||
method: 'post',
|
||||
data: this.actionModal.formData,
|
||||
})
|
||||
this.$message.success(`${this.actionModal.title}成功`)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
async handleDeleteAction() {
|
||||
try {
|
||||
await this.$confirm('确认删除所选事件吗?')
|
||||
await this.$http({
|
||||
url: '/delete',
|
||||
method: 'delete',
|
||||
params: { id: this.right.checkedAction.id },
|
||||
})
|
||||
this.$message.success(`删除事件成功`)
|
||||
} catch (error) {
|
||||
this.$message.success(`删除事件失败`)
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -593,6 +690,41 @@ export default {
|
|||
color: #bbdded;
|
||||
}
|
||||
}
|
||||
.zzxd-wrapper {
|
||||
height: 100%;
|
||||
.zzxd-header {
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid #00baff22;
|
||||
.zzxd-title {
|
||||
color: #00baff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.action-item {
|
||||
cursor: pointer;
|
||||
padding: 5px 0;
|
||||
.action-icon {
|
||||
padding: 5px 16px;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
.action-line {
|
||||
position: absolute;
|
||||
right: 50%;
|
||||
top: 21px;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background-color: #00baff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.action-item:last-of-type {
|
||||
.action-line {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.action-item:hover {
|
||||
background-color: #bae7ff44;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
|
|
Loading…
Reference in New Issue
Block a user