From be7f6ed23c1ffcff84856322b44b6fba86d44580 Mon Sep 17 00:00:00 2001 From: 86156 <823267011@qq.com> Date: Thu, 6 Nov 2025 17:38:46 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=8C=E7=BA=A7=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/l2/.env.staging | 2 +- apps/l2/src/api/l2/track.js | 12 + apps/l2/src/utils/websocketManager.js | 137 +++ apps/l2/src/views/l2/plan/index.vue | 2 +- apps/l2/src/views/l2/track/rect.vue | 1163 +++++++++++++++++++++++-- 5 files changed, 1220 insertions(+), 96 deletions(-) create mode 100644 apps/l2/src/utils/websocketManager.js diff --git a/apps/l2/.env.staging b/apps/l2/.env.staging index 90102e3..fe053f8 100644 --- a/apps/l2/.env.staging +++ b/apps/l2/.env.staging @@ -11,4 +11,4 @@ ENV = 'staging' # 若依管理系统/测试环境 VUE_APP_BASE_API = '/stage-api' -VUE_APP_SERVICE_BASE_API = 'http://140.143.206.120:8081' +VUE_APP_SERVICE_BASE_API = 'http://140.143.206.120:18081' diff --git a/apps/l2/src/api/l2/track.js b/apps/l2/src/api/l2/track.js index 7a1212f..01126b0 100644 --- a/apps/l2/src/api/l2/track.js +++ b/apps/l2/src/api/l2/track.js @@ -58,3 +58,15 @@ export function getTrackMatPosition() { }) } +/** + * 获取生产计划队列 + * 使用现有的计划列表接口 + */ +export function getPlanQueue(data = {}) { + return l2Request({ + method: 'post', + url: '/api/pdi/list', + data + }) +} + diff --git a/apps/l2/src/utils/websocketManager.js b/apps/l2/src/utils/websocketManager.js new file mode 100644 index 0000000..fae18fc --- /dev/null +++ b/apps/l2/src/utils/websocketManager.js @@ -0,0 +1,137 @@ +/** + * WebSocket 管理器 + * 用于管理多个 WebSocket 连接 + */ + +class WebSocketManager { + constructor() { + this.connections = new Map() + this.baseUrl = 'ws://140.143.206.120:18081/websocket' + } + + /** + * 创建 WebSocket 连接 + * @param {String} type - WebSocket 类型 (track_measure, track_position, track_signal, track_matmap, calc_setup_result) + * @param {Function} onMessage - 接收消息的回调函数 + * @param {Function} onOpen - 连接打开的回调函数 + * @param {Function} onError - 错误回调函数 + * @param {Function} onClose - 关闭回调函数 + */ + connect(type, { onMessage, onOpen, onError, onClose } = {}) { + // 如果已存在连接,先断开 + if (this.connections.has(type)) { + this.disconnect(type) + } + + const url = `${this.baseUrl}?type=${type}` + console.log(`[WebSocket] 正在连接: ${type}`) + + try { + const socket = new WebSocket(url) + + socket.onopen = (event) => { + console.log(`[WebSocket] 连接成功: ${type}`) + if (onOpen) onOpen(event) + } + + socket.onmessage = (event) => { + try { + const data = JSON.parse(event.data) + if (onMessage) onMessage(data) + } catch (error) { + console.error(`[WebSocket] 数据解析失败 (${type}):`, error) + // 如果不是 JSON,直接传递原始数据 + if (onMessage) onMessage(event.data) + } + } + + socket.onerror = (error) => { + console.error(`[WebSocket] 连接错误 (${type}):`, error) + if (onError) onError(error) + } + + socket.onclose = (event) => { + console.log(`[WebSocket] 连接关闭 (${type}):`, event.code, event.reason) + this.connections.delete(type) + + if (onClose) { + onClose(event) + } else if (event.code !== 1000) { + // 非正常关闭,3秒后自动重连 + console.log(`[WebSocket] 3秒后尝试重连: ${type}`) + setTimeout(() => { + this.connect(type, { onMessage, onOpen, onError, onClose }) + }, 3000) + } + } + + this.connections.set(type, socket) + return socket + } catch (error) { + console.error(`[WebSocket] 创建连接失败 (${type}):`, error) + // 失败后3秒重试 + setTimeout(() => { + this.connect(type, { onMessage, onOpen, onError, onClose }) + }, 3000) + } + } + + /** + * 断开指定类型的连接 + */ + disconnect(type) { + const socket = this.connections.get(type) + if (socket) { + console.log(`[WebSocket] 主动断开: ${type}`) + socket.close(1000, '主动关闭') + this.connections.delete(type) + } + } + + /** + * 断开所有连接 + */ + disconnectAll() { + console.log('[WebSocket] 断开所有连接') + this.connections.forEach((socket, type) => { + this.disconnect(type) + }) + } + + /** + * 获取指定类型的连接状态 + */ + isConnected(type) { + const socket = this.connections.get(type) + return socket && socket.readyState === WebSocket.OPEN + } + + /** + * 获取所有连接状态 + */ + getAllStatus() { + const status = {} + this.connections.forEach((socket, type) => { + status[type] = socket.readyState === WebSocket.OPEN + }) + return status + } + + /** + * 发送消息(如果需要) + */ + send(type, message) { + const socket = this.connections.get(type) + if (socket && socket.readyState === WebSocket.OPEN) { + const data = typeof message === 'string' ? message : JSON.stringify(message) + socket.send(data) + return true + } + console.warn(`[WebSocket] 无法发送消息,连接未打开: ${type}`) + return false + } +} + +// 导出单例 +export default new WebSocketManager() + diff --git a/apps/l2/src/views/l2/plan/index.vue b/apps/l2/src/views/l2/plan/index.vue index b89be19..a964c5b 100644 --- a/apps/l2/src/views/l2/plan/index.vue +++ b/apps/l2/src/views/l2/plan/index.vue @@ -469,7 +469,7 @@ export default { seqid: null, coilid: "", unitCode: "", - status: 0, + status: "NEW", // 新增计划默认状态为 NEW planid: "", planType: "", originCoilid: "", diff --git a/apps/l2/src/views/l2/track/rect.vue b/apps/l2/src/views/l2/track/rect.vue index 6c030e2..ebfb2ed 100644 --- a/apps/l2/src/views/l2/track/rect.vue +++ b/apps/l2/src/views/l2/track/rect.vue @@ -3,10 +3,91 @@ -
+ +
+ + 加载中... +
+ + +
+
+ 生产计划队列 + {{ planQueue.length }} 个计划 +
+
+ +
+
+
+ {{ index + 1 }} +
+
+
+ 计划ID: + {{ plan.planid || '-' }} +
+
+ 钢卷号: + {{ plan.coilid || '-' }} +
+
+ 钢种: + {{ plan.steelGrade || '-' }} +
+
+
+ + {{ getPlanStatusText(plan.status) }} + +
+
+
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
-
入口段
+
+ 入口段 + +
{{ device.positionNameCn }}
{{ device.positionNameEn }}
- {{ getDeviceCoilId(device.positionNameEn) }} + + {{ getDeviceStatus(device.positionNameEn) }} +
@@ -29,7 +112,10 @@
-
熔炉段
+
+ 熔炉段 + +
{{ device.positionNameCn }}
{{ device.positionNameEn }}
- {{ getDeviceCoilId(device.positionNameEn) }} + + {{ getDeviceStatus(device.positionNameEn) }} +
@@ -61,7 +149,9 @@
{{ device.positionNameCn }}
{{ device.positionNameEn }}
- {{ getDeviceCoilId(device.positionNameEn) }} + + {{ getDeviceStatus(device.positionNameEn) }} +
@@ -75,7 +165,10 @@
-
出口段
+
+ 出口段 + +
{{ device.positionNameCn }}
{{ device.positionNameEn }}
- {{ getDeviceCoilId(device.positionNameEn) }} + + {{ getDeviceStatus(device.positionNameEn) }} +
@@ -101,6 +196,94 @@
+ +
+
+ 生产计划详情 + +
+
+ + {{ selectedPlan.planid || '-' }} + {{ selectedPlan.coilid || '-' }} + {{ selectedPlan.seqid || '-' }} + + + {{ getPlanStatusText(selectedPlan.status) }} + + + {{ selectedPlan.steelGrade || '-' }} + + {{ selectedPlan.entryThick ? selectedPlan.entryThick + ' mm' : '-' }} + + + {{ selectedPlan.entryWidth ? selectedPlan.entryWidth + ' mm' : '-' }} + + + {{ selectedPlan.entryWeight ? selectedPlan.entryWeight + ' t' : '-' }} + + + {{ selectedPlan.entryLength ? selectedPlan.entryLength + ' mm' : '-' }} + + {{ selectedPlan.orderNo || '-' }} + {{ selectedPlan.unitCode || '-' }} + {{ selectedPlan.planType || '-' }} + + + +
+
时间信息
+ + + {{ formatDateTime(selectedPlan.onlineDate) }} + + + {{ formatDateTime(selectedPlan.startDate) }} + + + {{ formatDateTime(selectedPlan.endDate) }} + + +
+
+
+ + +
+
+ 最近操作 + + {{ getOperationConfig(signalData.operation).icon }} {{ getOperationText(signalData.operation) }} + +
+
+ + {{ signalData.autoFlag === 1 ? '手动操作' : '自动操作' }} + +
+
操作类型: + + {{ getOperationText(signalData.operation) }} + +
+
钢卷号: {{ signalData.entryMatId }}
+
计划ID: {{ signalData.planId || '-' }}
+
+ 开卷机: {{ signalData.porIdx }} +
+
+ 卷取机: {{ signalData.trIdx }} +
+
+ 虚拟卷 +
+
+
+
+
基本信息
@@ -180,6 +363,38 @@ + + +
+ + {{ calcSetupResult.flag ? '计算成功' : '计算失败' }} + + Key: {{ calcSetupResult.key }} +
+ + + + + + + + + + +
无计算结果数据
+
+ @@ -237,22 +452,62 @@ @@ -970,10 +1668,17 @@ export default { .coil-id { font-size: 12px; - color: #67c23a; font-weight: 500; } +.status-working { + color: #67c23a; +} + +.status-idle { + color: #909399; +} + .realtime-indicator { margin-top: 8px; padding-top: 8px; @@ -1097,4 +1802,274 @@ export default { width: 80px; height: 40px; } + +/* 加载状态 */ +.loading-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 100px 0; + color: #409eff; + font-size: 16px; +} + +.loading-container .el-icon-loading { + font-size: 40px; + margin-bottom: 15px; + animation: rotating 2s linear infinite; +} + +@keyframes rotating { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +/* 生产计划队列面板 */ +.plan-queue-panel { + background: #fff; + border-radius: 4px; + padding: 15px; + margin-bottom: 15px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.plan-queue-panel .panel-title { + font-size: 14px; + font-weight: 600; + color: #333; + margin-bottom: 15px; + display: flex; + align-items: center; + justify-content: space-between; +} + +.plan-queue-panel .panel-title i { + margin-right: 8px; + color: #409eff; +} + +.plan-queue-list { + max-height: 240px; + overflow-y: auto; +} + +.plan-items { + display: flex; + gap: 10px; + overflow-x: auto; + padding-bottom: 5px; +} + +.plan-item { + min-width: 220px; + background: #f5f7fa; + border: 2px solid #e4e7ed; + border-radius: 6px; + padding: 12px; + display: flex; + flex-direction: column; + gap: 10px; + transition: all 0.3s; + cursor: pointer; +} + +.plan-item:hover { + border-color: #409eff; + box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2); + transform: translateY(-2px); +} + +.plan-item.plan-active { + background: #f0f9ff; + border-color: #67c23a; +} + +.plan-item.plan-ready { + background: #fdf6ec; + border-color: #e6a23c; +} + +.plan-item.plan-selected { + background: #f0f9ff; + border-color: #409eff; + box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2); +} + +.plan-order { + width: 28px; + height: 28px; + background: #909399; + color: #fff; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 14px; + flex-shrink: 0; +} + +.plan-order.order-producing { + background: #67c23a; +} + +.plan-order.order-ready { + background: #e6a23c; +} + +.plan-order.order-new { + background: #909399; +} + +.plan-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 6px; +} + +.plan-row { + display: flex; + align-items: center; + font-size: 13px; +} + +.plan-label { + color: #909399; + min-width: 60px; + flex-shrink: 0; +} + +.plan-value { + color: #303133; + font-weight: 500; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.plan-status { + display: flex; + justify-content: flex-end; +} + +/* WebSocket 状态栏 */ +.ws-status-bar { + display: flex; + gap: 15px; + padding: 10px; + background: #f5f7fa; + border-radius: 4px; + margin-bottom: 10px; + align-items: center; +} + +.ws-status-bar i { + font-size: 18px; + color: #606266; + cursor: pointer; +} + +/* 分区头部增强 */ +.section-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.section-info { + font-size: 13px; + color: #67c23a; + font-weight: 500; +} + +/* 信号信息面板 */ +.signal-info { + padding: 10px 0; +} + +.signal-info.signal-warning { + background: #fef0f0; + padding: 15px; + border-radius: 4px; + border: 1px solid #fde2e2; +} + +.signal-info.signal-success { + background: #f0f9ff; + padding: 15px; + border-radius: 4px; + border: 1px solid #c6e2ff; +} + +.signal-info.signal-complete { + background: #f0f9ff; + padding: 15px; + border-radius: 4px; + border: 1px solid #b3e19d; +} + +.signal-detail { + margin-top: 10px; + font-size: 13px; + line-height: 2; +} + +.signal-detail div { + padding: 4px 0; +} + +.text-warning { + color: #e6a23c; + font-weight: 600; + font-size: 14px; +} + +.text-success { + color: #67c23a; + font-weight: 600; + font-size: 14px; +} + +/* 计算结果头部 */ +.calc-result-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + padding-bottom: 10px; + border-bottom: 1px solid #ebeef5; + font-size: 14px; +} + +/* 生产计划详情 */ +.plan-detail-content { + max-height: calc(100vh - 200px); + overflow-y: auto; +} + +.plan-time-info { + margin-top: 15px; + padding-top: 15px; + border-top: 1px solid #ebeef5; +} + +.info-subtitle { + font-size: 13px; + font-weight: 600; + color: #606266; + margin-bottom: 10px; +} + +.panel-title { + display: flex; + justify-content: space-between; + align-items: center; +} + +.panel-title i { + margin-right: 5px; +}