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