diff --git a/package.json b/package.json index 2a12475..2b4ec72 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,8 @@ "npm": "^6.9.0", "pubsub-js": "^1.9.3", "sass-loader": "6.0.6", + "sockjs-client": "1.6.1", + "stompjs": "2.3.3", "svg-sprite-loader": "3.7.3", "vue": "2.5.16", "vue-cookie": "1.1.4", diff --git a/src/router/index.js b/src/router/index.js index e88f82d..2e20782 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -28,7 +28,7 @@ const globalRoutes = [ { path: '/dashboard-buffer-board', component: _import('modules/dashboard/buffer-board'), name: 'dashboard-buffer-board', meta: { title: '缓存区看板' } }, { path: '/dashboard-workshop-feeding-board', component: _import('modules/dashboard/workshop-feeding-board'), name: 'dashboard-workshop-feeding-board', meta: { title: '车间AGV放料区看板' } }, { path: '/dashboard-exception-board', component: _import('modules/dashboard/exception-board'), name: 'dashboard-exception-board', meta: { title: '异常处理区看板' } }, - { path: '/dashboard-master-board', component: _import('modules/dashboard/inventory-board'), name: 'dashboard-inventory-board', meta: { title: '库存分析看板' } } + { path: '/dashboard-master-board', component: _import('modules/dashboard/warehouse-3d-board'), name: 'dashboard-inventory-board', meta: { title: '库存分析看板' } } ] // 主入口路由(需嵌套上左右整体布局) diff --git a/src/utils/websocket.js b/src/utils/websocket.js new file mode 100644 index 0000000..01c0b71 --- /dev/null +++ b/src/utils/websocket.js @@ -0,0 +1,219 @@ +/** + * WebSocket 工具类 + * + * 功能说明: + * 1. 封装WebSocket连接管理 + * 2. 自动重连机制 + * 3. 心跳检测 + * 4. 订阅管理 + * + * 使用示例: + * import WebSocketClient from '@/utils/websocket' + * + * // 连接 + * WebSocketClient.connect('http://localhost:8080/ws/dashboard', () => { + * console.log('连接成功') + * // 订阅主题 + * WebSocketClient.subscribe('/topic/dashboard/robot-picking', (data) => { + * console.log('收到数据:', data) + * }) + * }) + */ + +import SockJS from 'sockjs-client' +import Stomp from 'stompjs' + +class WebSocketClient { + constructor() { + this.stompClient = null + this.connected = false + this.reconnectAttempts = 0 + this.maxReconnectAttempts = 20 + this.reconnectInterval = 3000 + this.subscriptions = new Map() + this.heartbeatInterval = null + this.url = null + this.onConnectedCallback = null + } + + /** + * 连接WebSocket服务器 + * + * @param {string} url WebSocket服务器地址 + * @param {function} onConnected 连接成功回调 + * @param {function} onError 连接错误回调 + */ + connect(url, onConnected, onError) { + this.url = url + this.onConnectedCallback = onConnected + + console.log('[WebSocket] 正在连接服务器...', url) + + try { + const socket = new SockJS(url) + this.stompClient = Stomp.over(socket) + + // 禁用调试日志(生产环境) + // 开发环境可以设置为 console.log + this.stompClient.debug = null + + this.stompClient.connect( + {}, + frame => { + console.log('[WebSocket] ✅ 连接成功') + this.connected = true + this.reconnectAttempts = 0 + this.startHeartbeat() + + if (onConnected) { + onConnected(frame) + } + }, + error => { + console.error('[WebSocket] ❌ 连接失败:', error) + this.connected = false + this.handleDisconnect() + + if (onError) { + onError(error) + } + } + ) + } catch (error) { + console.error('[WebSocket] 连接异常:', error) + if (onError) { + onError(error) + } + } + } + + /** + * 订阅主题 + * + * @param {string} topic 主题名称 + * @param {function} callback 消息回调函数 + * @returns {string} 订阅ID + */ + subscribe(topic, callback) { + if (!this.connected || !this.stompClient) { + console.warn('[WebSocket] 未连接,无法订阅主题:', topic) + return null + } + + console.log('[WebSocket] 订阅主题:', topic) + + const subscription = this.stompClient.subscribe(topic, message => { + try { + const data = JSON.parse(message.body) + callback(data) + } catch (error) { + console.error('[WebSocket] 解析消息失败:', error) + } + }) + + const subscriptionId = topic + this.subscriptions.set(subscriptionId, subscription) + + return subscriptionId + } + + /** + * 取消订阅 + * + * @param {string} subscriptionId 订阅ID + */ + unsubscribe(subscriptionId) { + const subscription = this.subscriptions.get(subscriptionId) + if (subscription) { + subscription.unsubscribe() + this.subscriptions.delete(subscriptionId) + console.log('[WebSocket] 取消订阅:', subscriptionId) + } + } + + /** + * 发送消息 + * + * @param {string} destination 目标地址 + * @param {object} data 消息数据 + */ + send(destination, data) { + if (!this.connected || !this.stompClient) { + console.warn('[WebSocket] 未连接,无法发送消息') + return + } + + this.stompClient.send(destination, {}, JSON.stringify(data)) + } + + /** + * 断开连接 + */ + disconnect() { + if (this.stompClient) { + this.stompClient.disconnect(() => { + console.log('[WebSocket] 已断开连接') + }) + this.connected = false + this.stopHeartbeat() + } + } + + /** + * 处理断开连接 + */ + handleDisconnect() { + this.connected = false + this.stopHeartbeat() + + // 自动重连 + if (this.reconnectAttempts < this.maxReconnectAttempts) { + this.reconnectAttempts++ + console.log(`[WebSocket] 尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`) + + setTimeout(() => { + if (this.url && this.onConnectedCallback) { + this.connect(this.url, this.onConnectedCallback) + } + }, this.reconnectInterval) + } else { + console.error('[WebSocket] 达到最大重连次数,放弃重连') + } + } + + /** + * 启动心跳检测 + */ + startHeartbeat() { + this.heartbeatInterval = setInterval(() => { + if (this.connected && this.stompClient) { + // 发送心跳消息 + // 注意:如果后端没有心跳接口,可以注释掉这行 + // this.send('/app/heartbeat', { timestamp: Date.now() }) + } + }, 30000) // 每30秒发送一次心跳 + } + + /** + * 停止心跳检测 + */ + stopHeartbeat() { + if (this.heartbeatInterval) { + clearInterval(this.heartbeatInterval) + this.heartbeatInterval = null + } + } + + /** + * 检查连接状态 + * + * @returns {boolean} 是否已连接 + */ + isConnected() { + return this.connected + } +} + +// 导出单例 +export default new WebSocketClient() + diff --git a/src/views/modules/dashboard/inventory-board.vue b/src/views/modules/dashboard/inventory-board.vue index f716eed..e69de29 100644 --- a/src/views/modules/dashboard/inventory-board.vue +++ b/src/views/modules/dashboard/inventory-board.vue @@ -1,1370 +0,0 @@ - - - - - - diff --git a/src/views/modules/dashboard/robot-picking-board.vue b/src/views/modules/dashboard/robot-picking-board.vue index 0638405..0d5bb51 100644 --- a/src/views/modules/dashboard/robot-picking-board.vue +++ b/src/views/modules/dashboard/robot-picking-board.vue @@ -131,6 +131,7 @@ + + +