From f1fdf0c1e332227dd41c8d85411b2edc9744b65e Mon Sep 17 00:00:00 2001
From: 严智鑫 <test>
Date: 星期五, 21 三月 2025 09:59:10 +0800
Subject: [PATCH] Merge branch 'master' of http://159.223.171.199:10439/r/JiuMuMES

---
 JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/service/MechanicalMonitorService.java          |   23 ++
 JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/entity/MechanicalMonitor.java                  |   33 +++
 UI-Project/src/router/index.js                                                                                                              |   26 ++
 JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/mapper/MechanicalMonitorMapper.java            |    9 
 UI-Project/src/views/MechanicalMonitor/mechanicalMonitor.vue                                                                                |  384 ++++++++++++++++++++++++++++++++++++++
 JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/controller/MechanicalMonitorController.java    |   56 +++++
 JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/service/impl/MechanicalMonitorServiceImpl.java |   62 ++++++
 7 files changed, 593 insertions(+), 0 deletions(-)

diff --git a/JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/controller/MechanicalMonitorController.java b/JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/controller/MechanicalMonitorController.java
new file mode 100644
index 0000000..eab9b9e
--- /dev/null
+++ b/JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/controller/MechanicalMonitorController.java
@@ -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);
+        }
+    }
+} 
\ No newline at end of file
diff --git a/JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/entity/MechanicalMonitor.java b/JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/entity/MechanicalMonitor.java
new file mode 100644
index 0000000..62895e6
--- /dev/null
+++ b/JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/entity/MechanicalMonitor.java
@@ -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-姝e父 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;
+} 
\ No newline at end of file
diff --git a/JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/mapper/MechanicalMonitorMapper.java b/JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/mapper/MechanicalMonitorMapper.java
new file mode 100644
index 0000000..e2b8bc0
--- /dev/null
+++ b/JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/mapper/MechanicalMonitorMapper.java
@@ -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> {
+} 
\ No newline at end of file
diff --git a/JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/service/MechanicalMonitorService.java b/JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/service/MechanicalMonitorService.java
new file mode 100644
index 0000000..29ad2f5
--- /dev/null
+++ b/JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/service/MechanicalMonitorService.java
@@ -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);
+} 
\ No newline at end of file
diff --git a/JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/service/impl/MechanicalMonitorServiceImpl.java b/JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/service/impl/MechanicalMonitorServiceImpl.java
new file mode 100644
index 0000000..140050b
--- /dev/null
+++ b/JiuMuMesParent/moduleService/DeviceInteractionModule/src/main/java/com/mes/mechanicalMonitor/service/impl/MechanicalMonitorServiceImpl.java
@@ -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());
+            }
+        }
+    }
+} 
\ No newline at end of file
diff --git a/UI-Project/src/router/index.js b/UI-Project/src/router/index.js
index f00420f..fc72dc3 100644
--- a/UI-Project/src/router/index.js
+++ b/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',
diff --git a/UI-Project/src/views/MechanicalMonitor/mechanicalMonitor.vue b/UI-Project/src/views/MechanicalMonitor/mechanicalMonitor.vue
new file mode 100644
index 0000000..02524a4
--- /dev/null
+++ b/UI-Project/src/views/MechanicalMonitor/mechanicalMonitor.vue
@@ -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) { // 姝e父
+        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;
+    }
+};
+
+// 鑾峰彇鐢熶骇绾跨紪鍙凤紙鍙杋d鐨勭涓�浣嶏級
+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">姝e父</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">姝e父</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;
+}
+
+/* 姝e父鐘舵�侀粯璁ら殣钘忥紝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>    
\ No newline at end of file

--
Gitblit v1.8.0