From 39dd3a2f8b97bd163f4894a705dc7eed7e195314 Mon Sep 17 00:00:00 2001 From: liaoboping <2824044657@qq.com> Date: Wed, 20 Aug 2025 10:23:39 +0800 Subject: [PATCH] websocket --- src/components/Common/Bus/index.js | 2 + src/components/Common/Websocket/index.js | 131 +++++++++++ src/components/Common/register.js | 4 + .../simulationScene/sceneEditing/index.vue | 208 ++++++++++++++---- 4 files changed, 307 insertions(+), 38 deletions(-) create mode 100644 src/components/Common/Bus/index.js create mode 100644 src/components/Common/Websocket/index.js diff --git a/src/components/Common/Bus/index.js b/src/components/Common/Bus/index.js new file mode 100644 index 0000000..b0230b5 --- /dev/null +++ b/src/components/Common/Bus/index.js @@ -0,0 +1,2 @@ +import Vue from 'vue' +export default new Vue() diff --git a/src/components/Common/Websocket/index.js b/src/components/Common/Websocket/index.js new file mode 100644 index 0000000..11767f9 --- /dev/null +++ b/src/components/Common/Websocket/index.js @@ -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(); diff --git a/src/components/Common/register.js b/src/components/Common/register.js index 0f45927..9fc907d 100644 --- a/src/components/Common/register.js +++ b/src/components/Common/register.js @@ -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 }, } diff --git a/src/views/simulationScene/sceneEditing/index.vue b/src/views/simulationScene/sceneEditing/index.vue index 7dd7a59..0858df5 100644 --- a/src/views/simulationScene/sceneEditing/index.vue +++ b/src/views/simulationScene/sceneEditing/index.vue @@ -48,9 +48,9 @@
{{ right.detail.name }} - +
-
+ +
+ 推演方: + {{ right.detail.position.deduceTypeName }}
类型: - + {{ right.detail.position.logisticType | logisticTypeFormat }}
方向: - + {{ right.detail.position.direction | numberFormat }}°
速度: @@ -77,17 +81,17 @@
经度: {{ right.detail.position.lng | lonFormat }} - +
纬度: {{ right.detail.position.lat | latFormat }} - +
高度/深度: {{ right.detail.position.height | numberFormat }} 米(海拔) - +
@@ -200,31 +204,40 @@
兵力编组 - 关注 +
-
-
-
-
作战行动
-
- - - - -
+
+
+
作战行动
+
+ + + +
-
- - - +
+
+
+
+
+ +
+
+
{{ item.typeName || '- -' }}
+
开始时间:{{ item.beginDateTime }}
+
结束时间:{{ item.endDateTime }}
+
@@ -246,7 +259,14 @@
- + 名称 @@ -261,6 +281,7 @@ title="修改单元经纬度" :maskClosable="false" width="600px" + :destroyOnClose="true" @ok="handleSubmilLonlat" > @@ -270,6 +291,7 @@ title="设置单元高度/深度" :maskClosable="false" width="400px" + :destroyOnClose="true" @ok="handleSubmilHd" > @@ -281,6 +303,15 @@ + +
+
@@ -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, - lat: this.lonlatModal.lat, + 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) + } + }, }, } @@ -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; +}