严智鑫
2025-03-21 f1fdf0c1e332227dd41c8d85411b2edc9744b65e
Merge branch 'master' of http://159.223.171.199:10439/r/JiuMuMES
1个文件已修改
6个文件已添加
593 ■■■■■ 已修改文件
JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/controller/MechanicalMonitorController.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/entity/MechanicalMonitor.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/mapper/MechanicalMonitorMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/service/MechanicalMonitorService.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/service/impl/MechanicalMonitorServiceImpl.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
UI-Project/src/router/index.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
UI-Project/src/views/MechanicalMonitor/mechanicalMonitor.vue 384 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/controller/MechanicalMonitorController.java
New file
@@ -0,0 +1,56 @@
package com.mes.mechanicalMonitor.controller;
import com.mes.mechanicalMonitor.entity.MechanicalMonitor;
import com.mes.mechanicalMonitor.service.MechanicalMonitorService;
import com.mes.tools.WebSocketServer;
import com.mes.utils.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import cn.hutool.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
@Api(tags = "设备状态监控")
@RestController
@RequestMapping("/deviceInteraction/mechanicalMonitor")
public class MechanicalMonitorController {
    @Autowired
    private MechanicalMonitorService mechanicalMonitorService;
    @ApiOperation("获取设备监控状态")
    @PostMapping("/getMechanicalStatus")
    @ResponseBody
    public Result getMechanicalStatus() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.append("sessionMapName", "mechanicalMonitor");
        // 从数据库获取设备状态列表
        List<MechanicalMonitor> deviceList = mechanicalMonitorService.getAllDeviceStatus();
        Map<String, Object> data = new HashMap<>();
        data.put("mechanicalStatus", deviceList);
        return Result.build(200, "获取成功", data);
    }
    @ApiOperation("更新设备状态")
    @PostMapping("/updateMechanicalStatus")
    @ResponseBody
    public Result updateMechanicalStatus(@RequestBody JSONObject status) {
        try {
            ArrayList<WebSocketServer> servers = WebSocketServer.sessionMap.get("mechanicalMonitor");
            if (servers != null) {
                for (WebSocketServer server : servers) {
                    server.sendMessage(status.toString());
                }
            }
            return Result.build(200, "状态更新成功", null);
        } catch (Exception e) {
            return Result.build(500, "状态更新失败", null);
        }
    }
}
JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/entity/MechanicalMonitor.java
New file
@@ -0,0 +1,33 @@
package com.mes.mechanicalMonitor.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.util.Date;
@Data
@TableName("mechanical_monitor")
public class MechanicalMonitor {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String deviceId;
    private String deviceName;
    private Integer status; // 0-未连接 1-正常 2-故障
    private String alarmInfo;
    @TableField(fill = FieldFill.INSERT)
    private Date alarmTime;
    @TableField(fill = FieldFill.INSERT)
    private Date disconnectTime;
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}
JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/mapper/MechanicalMonitorMapper.java
New file
@@ -0,0 +1,9 @@
package com.mes.mechanicalMonitor.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mes.mechanicalMonitor.entity.MechanicalMonitor;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MechanicalMonitorMapper extends BaseMapper<MechanicalMonitor> {
}
JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/service/MechanicalMonitorService.java
New file
@@ -0,0 +1,23 @@
package com.mes.mechanicalMonitor.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.mes.mechanicalMonitor.entity.MechanicalMonitor;
import java.util.List;
public interface MechanicalMonitorService extends IService<MechanicalMonitor> {
    /**
     * 获取所有设备状态
     */
    List<MechanicalMonitor> getAllDeviceStatus();
    /**
     * 更新设备状态
     */
    void updateDeviceStatus(String deviceId, Integer status, String alarmInfo);
    /**
     * 通过WebSocket通知状态变化
     */
    void notifyStatusChange(MechanicalMonitor monitor);
}
JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/service/impl/MechanicalMonitorServiceImpl.java
New file
@@ -0,0 +1,62 @@
package com.mes.mechanicalMonitor.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mes.mechanicalMonitor.entity.MechanicalMonitor;
import com.mes.mechanicalMonitor.mapper.MechanicalMonitorMapper;
import com.mes.mechanicalMonitor.service.MechanicalMonitorService;
import com.mes.tools.WebSocketServer;
import cn.hutool.json.JSONObject;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Date;
import java.util.ArrayList;
@Service
public class MechanicalMonitorServiceImpl extends ServiceImpl<MechanicalMonitorMapper, MechanicalMonitor>
    implements MechanicalMonitorService {
    @Override
    public List<MechanicalMonitor> getAllDeviceStatus() {
        return this.list();
    }
    @Override
    public void updateDeviceStatus(String deviceId, Integer status, String alarmInfo) {
        MechanicalMonitor monitor = this.lambdaQuery()
            .eq(MechanicalMonitor::getDeviceId, deviceId)
            .one();
        if (monitor == null) {
            monitor = new MechanicalMonitor();
            monitor.setDeviceId(deviceId);
        }
        monitor.setStatus(status);
        monitor.setAlarmInfo(alarmInfo);
        if (status == 2) { // 故障状态
            monitor.setAlarmTime(new Date());
        } else if (status == 0) { // 未连接状态
            monitor.setDisconnectTime(new Date());
        }
        this.saveOrUpdate(monitor);
        // 通知状态变化
        notifyStatusChange(monitor);
    }
    @Override
    public void notifyStatusChange(MechanicalMonitor monitor) {
        JSONObject message = new JSONObject();
        message.set("type", "status_change");
        message.set("data", monitor);
        ArrayList<WebSocketServer> servers = WebSocketServer.sessionMap.get("mechanicalMonitor");
        if (servers != null) {
            for (WebSocketServer server : servers) {
                server.sendMessage(message.toString());
            }
        }
    }
}
UI-Project/src/router/index.js
@@ -76,6 +76,32 @@
          ]
        },
        {
          /*----------- 能耗管理 ----------------*/
          path: 'Energy/energyConsumption',
          name: 'energyConsumption',
          component: () => import('../views/Energy/energyConsumption.vue'),
          children: [
            {
              path: '/Energy/energyConsumption',
              name: 'energyConsumption',
              component: () => import('../views/Energy/energyConsumption.vue'),
            }
          ]
        },
        {
          /*----------- 设备状态 ----------------*/
          path: 'MechanicalMonitor/mechanicalMonitor',
          name: 'mechanicalMonitor',
          component: () => import('../views/MechanicalMonitor/mechanicalMonitor.vue'),
          children: [
            {
              path: '/MechanicalMonitor/mechanicalMonitor',
              name: 'mechanicalMonitor',
              component: () => import('../views/MechanicalMonitor/mechanicalMonitor.vue'),
            }
          ]
        },
        {
          /*----------- 清洗机 ----------------*/
          path: 'Cleaning',
          name: 'cleaning',
UI-Project/src/views/MechanicalMonitor/mechanicalMonitor.vue
New file
@@ -0,0 +1,384 @@
<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>