New file |
| | |
| | | <script setup> |
| | | import { ref, onMounted, onUnmounted, computed } from 'vue'; |
| | | import axios from 'axios'; |
| | | |
| | | const devices = ref([]); |
| | | const ws = ref(null); |
| | | const activeTab = ref('line1'); |
| | | |
| | | const initWebSocket = () => { |
| | | const wsUrl = `ws://${window.location.host}/api/talk/mechanicalMonitor`; |
| | | ws.value = new WebSocket(wsUrl); |
| | | |
| | | ws.value.onmessage = (event) => { |
| | | const data = JSON.parse(event.data); |
| | | if (data.type === 'status_change') { |
| | | updateDeviceStatus(data.data); |
| | | } |
| | | }; |
| | | |
| | | ws.value.onclose = () => { |
| | | console.log('WebSocket连接关闭'); |
| | | setTimeout(initWebSocket, 5000); |
| | | }; |
| | | }; |
| | | |
| | | const updateDeviceStatus = (newStatus) => { |
| | | const device = devices.value.find(d => d.deviceId === newStatus.deviceId); |
| | | if (device) { |
| | | Object.assign(device, { |
| | | ...newStatus, |
| | | showAlarmInfo: device.showAlarmInfo, |
| | | alarmTime: newStatus.alarmTime ? new Date(newStatus.alarmTime).toLocaleString() : null, |
| | | disconnectTime: newStatus.disconnectTime ? new Date(newStatus.disconnectTime).toLocaleString() : null |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const fetchDeviceStatus = async () => { |
| | | try { |
| | | const response = await axios.post('/api/deviceInteraction/mechanicalMonitor/getMechanicalStatus'); |
| | | if (response.data.code === 200) { |
| | | const statusData = response.data.data.mechanicalStatus; |
| | | devices.value = statusData.map(device => ({ |
| | | ...device, |
| | | showAlarmInfo: false, |
| | | alarmTime: device.alarmTime ? new Date(device.alarmTime).toLocaleString() : null, |
| | | disconnectTime: device.disconnectTime ? new Date(device.disconnectTime).toLocaleString() : null |
| | | })); |
| | | } |
| | | } catch (error) { |
| | | console.error('获取设备状态失败:', error); |
| | | } |
| | | }; |
| | | |
| | | const deviceIcon = (device) => { |
| | | if (device.status === 2) { // 故障 |
| | | return 'https://img.ixintu.com/download/jpg/202005/641e5e0d96f770c1306d3628fe55c60b_610_605.jpg!ys'; |
| | | } else if (device.status === 1) { // 正常 |
| | | return 'https://bpic.588ku.com/element_origin_min_pic/19/03/07/e3e263faacbe0454e3a0a2a6679033ed.jpg'; |
| | | } |
| | | return 'https://img.88icon.com/download/jpg/20200720/56c3ba7ed4a120d9824f589330b82a14_512_512.jpg!bg'; // 未连接 |
| | | }; |
| | | |
| | | const toggleAlarmInfo = (device) => { |
| | | if (device.status !== 1) { // 非正常状态才显示信息 |
| | | device.showAlarmInfo = !device.showAlarmInfo; |
| | | } |
| | | }; |
| | | |
| | | // 获取生产线编号(取id的第一位) |
| | | const getLineId = (deviceId) => parseInt(deviceId.toString().charAt(0)); |
| | | |
| | | // 按生产线分组的设备 |
| | | const groupedDevices = computed(() => { |
| | | return { |
| | | line1: devices.value.filter(device => device.deviceId.toString().startsWith('1')), |
| | | line2: devices.value.filter(device => device.deviceId.toString().startsWith('2')) |
| | | }; |
| | | }); |
| | | |
| | | const switchTab = (tab) => { |
| | | activeTab.value = tab; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | fetchDeviceStatus(); |
| | | initWebSocket(); |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | | if (ws.value) { |
| | | ws.value.close(); |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="monitoring-container"> |
| | | <h1>九牧设备连接及故障报警监控</h1> |
| | | |
| | | <!-- 添加标签切换组件 --> |
| | | <div class="tab-container"> |
| | | <div |
| | | class="tab-item" |
| | | :class="{ active: activeTab === 'line1' }" |
| | | @click="switchTab('line1')" |
| | | > |
| | | 一线 |
| | | </div> |
| | | <div |
| | | class="tab-item" |
| | | :class="{ active: activeTab === 'line2' }" |
| | | @click="switchTab('line2')" |
| | | > |
| | | 二线 |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 修改生产线显示逻辑 --> |
| | | <div v-show="activeTab === 'line1'" class="line-section"> |
| | | <h2 class="line-title">一线</h2> |
| | | <div class="device-grid"> |
| | | <div v-for="device in groupedDevices.line1" :key="device.id" class="device-panel" @click="toggleAlarmInfo(device)"> |
| | | <div class="device-image"> |
| | | <img :src="deviceIcon(device)" alt="设备图标"> |
| | | <p v-if="device.status === 1" class="status-text normal-status">正常</p> |
| | | <p v-if="device.status === 0" class="status-text error-status disconnected-status">未连接</p> |
| | | <p v-if="device.status === 2" class="status-text error-status fault-status">故障</p> |
| | | </div> |
| | | <div class="device-details"> |
| | | <h3>{{ device.deviceName }}</h3> |
| | | <div class="status-line" :class="{ 'connected': device.status === 1, 'disconnected': device.status !== 1 }"></div> |
| | | <div :class="['alarm-indicator', device.status === 2 ? 'alarm' : 'normal']"></div> |
| | | </div> |
| | | <div v-if="device.showAlarmInfo"> |
| | | <div v-if="device.status === 2" class="alarm-message"> |
| | | <div class="alarm-title">故障警告</div> |
| | | <p>📋 故障类型:{{ device.alarmInfo }}</p> |
| | | <p>⏰ 发生时间:{{ device.alarmTime }}</p> |
| | | </div> |
| | | <div v-if="device.status === 0" class="alarm-message"> |
| | | <div class="alarm-title">连接异常</div> |
| | | <p>📋 故障类型:机器未连接</p> |
| | | <p>⏰ 断开时间:{{ device.disconnectTime }}</p> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-show="activeTab === 'line2'" class="line-section"> |
| | | <h2 class="line-title">二线</h2> |
| | | <div class="device-grid"> |
| | | <div v-for="device in groupedDevices.line2" :key="device.id" class="device-panel" @click="toggleAlarmInfo(device)"> |
| | | <div class="device-image"> |
| | | <img :src="deviceIcon(device)" alt="设备图标"> |
| | | <p v-if="device.status === 1" class="status-text normal-status">正常</p> |
| | | <p v-if="device.status === 0" class="status-text error-status disconnected-status">未连接</p> |
| | | <p v-if="device.status === 2" class="status-text error-status fault-status">故障</p> |
| | | </div> |
| | | <div class="device-details"> |
| | | <h3>{{ device.deviceName }}</h3> |
| | | <div class="status-line" :class="{ 'connected': device.status === 1, 'disconnected': device.status !== 1 }"></div> |
| | | <div :class="['alarm-indicator', device.status === 2 ? 'alarm' : 'normal']"></div> |
| | | </div> |
| | | <div v-if="device.showAlarmInfo"> |
| | | <div v-if="device.status === 2" class="alarm-message"> |
| | | <div class="alarm-title">故障警告</div> |
| | | <p>📋 故障类型:{{ device.alarmInfo }}</p> |
| | | <p>⏰ 发生时间:{{ device.alarmTime }}</p> |
| | | </div> |
| | | <div v-if="device.status === 0" class="alarm-message"> |
| | | <div class="alarm-title">连接异常</div> |
| | | <p>📋 故障类型:机器未连接</p> |
| | | <p>⏰ 断开时间:{{ device.disconnectTime }}</p> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | |
| | | .monitoring-container { |
| | | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| | | padding: 20px; |
| | | max-width: 1200px; |
| | | margin: 0 auto; |
| | | margin-bottom: 60px; |
| | | } |
| | | |
| | | h1 { |
| | | text-align: center; |
| | | color: #333; |
| | | margin-bottom: 30px; |
| | | font-size: 2.2rem; |
| | | } |
| | | |
| | | .line-section { |
| | | margin-bottom: 40px; |
| | | } |
| | | |
| | | .line-title { |
| | | color: #2c3e50; |
| | | font-size: 1.5rem; |
| | | margin: 20px 0; |
| | | padding-left: 10px; |
| | | border-left: 4px solid #4CAF50; |
| | | } |
| | | |
| | | .device-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); |
| | | gap: 15px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .device-panel { |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | padding: 12px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | cursor: pointer; |
| | | transition: transform 0.2s; |
| | | margin-bottom: 30px; |
| | | position: relative; |
| | | } |
| | | |
| | | .device-panel:hover { |
| | | transform: translateY(-5px); |
| | | box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2); |
| | | } |
| | | |
| | | .device-image { |
| | | text-align: center; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .device-image img { |
| | | width: 60px; |
| | | height: 60px; |
| | | object-fit: contain; |
| | | } |
| | | |
| | | .device-details h3 { |
| | | font-size: 0.9rem; |
| | | margin: 5px 0; |
| | | color: #333; |
| | | } |
| | | |
| | | .status-text { |
| | | position: absolute; |
| | | bottom: -25px; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | color: white; |
| | | padding: 3px 8px; |
| | | border-radius: 3px; |
| | | font-size: 0.9rem; |
| | | white-space: nowrap; |
| | | z-index: 1; |
| | | } |
| | | |
| | | /* 正常状态默认隐藏,hover时显示 */ |
| | | .normal-status { |
| | | background-color: rgba(0, 0, 0, 0.6); |
| | | opacity: 0; |
| | | transition: opacity 0.3s ease; |
| | | } |
| | | |
| | | .device-panel:hover .normal-status { |
| | | opacity: 1; |
| | | } |
| | | |
| | | /* 异常状态基础样式 */ |
| | | .error-status { |
| | | opacity: 1; |
| | | } |
| | | |
| | | /* 故障状态使用红色背景 */ |
| | | .fault-status { |
| | | background-color: rgba(255, 0, 0, 0.8); |
| | | } |
| | | |
| | | /* 未连接状态使用灰色背景 */ |
| | | .disconnected-status { |
| | | background-color: rgba(128, 128, 128, 0.8); |
| | | } |
| | | |
| | | .status-line { |
| | | height: 8px; |
| | | border-radius: 4px; |
| | | margin: 10px 0; |
| | | } |
| | | |
| | | .connected { |
| | | background-color: #28a745; |
| | | } |
| | | |
| | | .disconnected { |
| | | background-color: #dc3545; |
| | | } |
| | | |
| | | .alarm-indicator { |
| | | width: 25px; |
| | | height: 25px; |
| | | border-radius: 50%; |
| | | margin: 15px auto 0; |
| | | } |
| | | |
| | | .normal { |
| | | background-color: #ccc; |
| | | } |
| | | |
| | | .alarm { |
| | | background-color: #ff0000; |
| | | animation: blink 1s infinite; |
| | | } |
| | | |
| | | @keyframes blink { |
| | | 0% { |
| | | opacity: 1; |
| | | } |
| | | 50% { |
| | | opacity: 0.3; |
| | | } |
| | | 100% { |
| | | opacity: 1; |
| | | } |
| | | } |
| | | |
| | | .alarm-message { |
| | | background-color: #fff2f2; |
| | | color: #d32f2f; |
| | | border-radius: 8px; |
| | | padding: 15px; |
| | | margin-top: 20px; |
| | | text-align: left; |
| | | font-size: 14px; |
| | | line-height: 1.8; |
| | | border: 1px solid #ffcdd2; |
| | | } |
| | | |
| | | .alarm-message p { |
| | | margin: 8px 0; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .alarm-message .alarm-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | color: #c62828; |
| | | margin-bottom: 12px; |
| | | border-bottom: 1px solid #ffcdd2; |
| | | padding-bottom: 8px; |
| | | } |
| | | |
| | | /* 添加标签样式 */ |
| | | .tab-container { |
| | | display: flex; |
| | | justify-content: center; |
| | | margin-bottom: 30px; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .tab-item { |
| | | padding: 10px 30px; |
| | | border-radius: 5px; |
| | | cursor: pointer; |
| | | background-color: #f5f5f5; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .tab-item:hover { |
| | | background-color: #e0e0e0; |
| | | } |
| | | |
| | | .tab-item.active { |
| | | background-color: #4CAF50; |
| | | color: white; |
| | | } |
| | | </style> |