楼宇能效监测控制系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1748 lines
56 KiB

<template>
<div class="alarm-manage">
<!-- 告警统计卡片 -->
<div class="alarm-stats">
<div class="stat-card total">
<div class="stat-icon">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" fill="currentColor"/>
<path d="M688 464H336c-8.8 0-16 7.2-16 16v64c0 8.8 7.2 16 16 16h352c8.8 0 16-7.2 16-16v-64c0-8.8-7.2-16-16-16z" fill="currentColor"/>
</svg>
</div>
<div class="stat-info">
<div class="stat-value">{{ alarmStats.total }}</div>
<div class="stat-label">告警总数</div>
</div>
</div>
<div class="stat-card pending">
<div class="stat-icon">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" fill="currentColor"/>
<path d="M672 464H352c-8.8 0-16 7.2-16 16v64c0 8.8 7.2 16 16 16h320c8.8 0 16-7.2 16-16v-64c0-8.8-7.2-16-16-16z" fill="currentColor"/>
<path d="M512 592c-4.4 0-8 3.6-8 8v256c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V600c0-4.4-3.6-8-8-8h-48z" fill="currentColor"/>
</svg>
</div>
<div class="stat-info">
<div class="stat-value">{{ alarmStats.pending }}</div>
<div class="stat-label">待处理</div>
</div>
</div>
<div class="stat-card processing">
<div class="stat-icon">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" fill="currentColor"/>
<path d="M512 352c-8.8 0-16 7.2-16 16v224c0 8.8 7.2 16 16 16h48c8.8 0 16-7.2 16-16V368c0-8.8-7.2-16-16-16h-48z" fill="currentColor"/>
</svg>
</div>
<div class="stat-info">
<div class="stat-value">{{ alarmStats.processing }}</div>
<div class="stat-label">处理中</div>
</div>
</div>
<div class="stat-card resolved">
<div class="stat-icon">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" fill="currentColor"/>
<path d="M688 464H336c-8.8 0-16 7.2-16 16v64c0 8.8 7.2 16 16 16h352c8.8 0 16-7.2 16-16v-64c0-8.8-7.2-16-16-16z" fill="currentColor"/>
<path d="M512 592c-4.4 0-8 3.6-8 8v256c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V600c0-4.4-3.6-8-8-8h-48z" fill="currentColor"/>
</svg>
</div>
<div class="stat-info">
<div class="stat-value">{{ alarmStats.resolved }}</div>
<div class="stat-label">已解决</div>
</div>
</div>
</div>
<!-- 工单系统对接配置 -->
<div class="workorder-config">
<div class="config-header">
<div class="config-title">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-600 72h560v208H232V136zm560 480H232V408h560v208zm0 272H232V680h560v208z" fill="currentColor"/>
</svg>
工单系统对接配置
</div>
<el-switch
v-model="workOrderConfig.enabled"
active-text="已启用"
inactive-text="已禁用"
class="custom-switch"
></el-switch>
</div>
<div class="config-content" v-if="workOrderConfig.enabled">
<div class="config-row">
<div class="config-item">
<label>工单系统类型</label>
<el-select v-model="workOrderConfig.systemType" class="custom-select">
<el-option label="业主工单系统A" value="systemA"></el-option>
<el-option label="业主工单系统B" value="systemB"></el-option>
<el-option label="第三方工单平台" value="thirdParty"></el-option>
</el-select>
</div>
<div class="config-item">
<label>API地址</label>
<el-input v-model="workOrderConfig.apiUrl" placeholder="https://api.workorder.com" class="custom-input"></el-input>
</div>
<div class="config-item">
<label>API密钥</label>
<el-input v-model="workOrderConfig.apiKey" placeholder="请输入API密钥" show-password class="custom-input"></el-input>
</div>
</div>
<div class="config-row">
<div class="config-item">
<label>自动推送</label>
<el-switch v-model="workOrderConfig.autoPush" class="custom-switch"></el-switch>
</div>
<div class="config-item">
<label>推送告警等级</label>
<el-select v-model="workOrderConfig.pushLevel" class="custom-select" multiple>
<el-option label="严重" value="critical"></el-option>
<el-option label="警告" value="warning"></el-option>
<el-option label="信息" value="info"></el-option>
</el-select>
</div>
<div class="config-item">
<label>工单优先级映射</label>
<el-select v-model="workOrderConfig.priorityMapping" class="custom-select">
<el-option label="告警等级=工单优先级" value="direct"></el-option>
<el-option label="根据规则映射" value="rule"></el-option>
</el-select>
</div>
</div>
<div class="config-actions">
<el-button type="primary" @click="testConnection" class="custom-btn">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M864 256H160c-17.7 0-32 14.3-32 32v448c0 17.7 14.3 32 32 32h704c17.7 0 32-14.3 32-32V288c0-17.7-14.3-32-32-32zm0 64H192V384h672v-64z" fill="currentColor"/>
</svg>
测试连接
</el-button>
<el-button type="success" @click="saveConfig" class="custom-btn">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M864 256H160c-17.7 0-32 14.3-32 32v448c0 17.7 14.3 32 32 32h704c17.7 0 32-14.3 32-32V288c0-17.7-14.3-32-32-32zm0 64H192V384h672v-64z" fill="currentColor"/>
</svg>
保存配置
</el-button>
</div>
</div>
</div>
<!-- 告警筛选 -->
<div class="alarm-filter">
<div class="filter-group">
<div class="filter-item">
<label>告警级别</label>
<el-select v-model="filterForm.level" placeholder="全部" clearable class="custom-select">
<el-option label="全部" value=""></el-option>
<el-option label="严重" value="critical"></el-option>
<el-option label="警告" value="warning"></el-option>
<el-option label="信息" value="info"></el-option>
</el-select>
</div>
<div class="filter-item">
<label>告警状态</label>
<el-select v-model="filterForm.status" placeholder="全部" clearable class="custom-select">
<el-option label="全部" value=""></el-option>
<el-option label="待处理" value="pending"></el-option>
<el-option label="处理中" value="processing"></el-option>
<el-option label="已解决" value="resolved"></el-option>
</el-select>
</div>
<div class="filter-item">
<label>工单状态</label>
<el-select v-model="filterForm.workOrderStatus" placeholder="全部" clearable class="custom-select">
<el-option label="全部" value=""></el-option>
<el-option label="未创建" value="notCreated"></el-option>
<el-option label="待派发" value="pending"></el-option>
<el-option label="进行中" value="inProgress"></el-option>
<el-option label="已完成" value="completed"></el-option>
</el-select>
</div>
<div class="filter-item">
<label>告警时间</label>
<el-date-picker
v-model="filterForm.dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
class="custom-picker"
></el-date-picker>
</div>
</div>
<div class="filter-actions">
<el-input
v-model="filterForm.keyword"
placeholder="搜索告警标题/设备/描述"
clearable
class="search-input custom-input"
></el-input>
<el-button type="primary" @click="handleSearch" class="custom-btn">查询</el-button>
<el-button @click="handleReset" class="custom-btn">重置</el-button>
<el-button type="success" @click="handleExport" class="custom-btn">导出</el-button>
</div>
</div>
<!-- 告警列表 -->
<div class="alarm-list">
<div class="list-header">
<div class="list-title">告警列表</div>
<div class="list-info">
共 {{ filteredAlarms.length }} 条告警
</div>
</div>
<div class="table-container">
<table class="alarm-table">
<thead>
<tr>
<th>告警ID</th>
<th>告警级别</th>
<th>告警标题</th>
<th>设备名称</th>
<th>告警时间</th>
<th>告警状态</th>
<th>工单编号</th>
<th>工单状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="alarm in paginatedAlarms" :key="alarm.id">
<td>{{ alarm.id }}</td>
<td>
<span class="level-badge" :class="alarm.level">
{{ getLevelText(alarm.level) }}
</span>
</td>
<td class="title-cell">{{ alarm.title }}</td>
<td>{{ alarm.deviceName }}</td>
<td>{{ alarm.alarmTime }}</td>
<td>
<span class="status-badge" :class="alarm.status">
{{ getStatusText(alarm.status) }}
</span>
</td>
<td>
<span v-if="alarm.workOrderId" class="workorder-link" @click="viewWorkOrder(alarm)">
{{ alarm.workOrderId }}
</span>
<span v-else class="no-workorder">未创建</span>
</td>
<td>
<span v-if="alarm.workOrderStatus" class="workorder-status" :class="alarm.workOrderStatus">
{{ getWorkOrderStatusText(alarm.workOrderStatus) }}
</span>
<span v-else>-</span>
</td>
<td class="action-cell">
<div class="action-buttons">
<el-button
v-if="!alarm.workOrderId"
type="primary"
size="small"
@click="createWorkOrder(alarm)"
class="action-btn"
>
创建工单
</el-button>
<el-button
size="small"
@click="viewAlarmDetail(alarm)"
class="action-btn"
>
详情
</el-button>
<el-dropdown @command="(cmd) => handleAlarmCommand(cmd, alarm)" class="action-dropdown">
<el-button size="small" class="action-btn">
更多
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" style="margin-left: 0.04rem; width: 0.1rem; height: 0.1rem;">
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" fill="currentColor"/>
<path d="M688 464H336c-8.8 0-16 7.2-16 16v64c0 8.8 7.2 16 16 16h352c8.8 0 16-7.2 16-16v-64c0-8.8-7.2-16-16-16z" fill="currentColor"/>
</svg>
</el-button>
<template #dropdown>
<el-dropdown-menu class="custom-dropdown-menu">
<el-dropdown-item command="viewWorkOrder" v-if="alarm.workOrderId">查看工单</el-dropdown-item>
<el-dropdown-item command="createWorkOrder" v-if="!alarm.workOrderId">创建工单</el-dropdown-item>
<el-dropdown-item command="resolve" v-if="alarm.status !== 'resolved'" divided>标记已解决</el-dropdown-item>
<el-dropdown-item command="close" v-if="alarm.status !== 'resolved'">关闭告警</el-dropdown-item>
<el-dropdown-item command="reopen" v-if="alarm.status === 'resolved'">重新打开</el-dropdown-item>
<el-dropdown-item command="export" divided>导出报告</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="filteredAlarms.length"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
class="custom-pagination"
></el-pagination>
</div>
</div>
<!-- 告警详情弹窗 -->
<el-dialog
v-model="detailDialogVisible"
title="告警详情"
width="800px"
class="custom-dialog"
>
<div class="detail-content" v-if="selectedAlarm">
<div class="detail-section">
<div class="section-title">基本信息</div>
<div class="info-grid">
<div class="info-item">
<span class="item-label">告警ID:</span>
<span class="item-value">{{ selectedAlarm.id }}</span>
</div>
<div class="info-item">
<span class="item-label">告警级别:</span>
<span class="item-value level-badge" :class="selectedAlarm.level">
{{ getLevelText(selectedAlarm.level) }}
</span>
</div>
<div class="info-item">
<span class="item-label">告警标题:</span>
<span class="item-value">{{ selectedAlarm.title }}</span>
</div>
<div class="info-item">
<span class="item-label">告警状态:</span>
<span class="item-value status-badge" :class="selectedAlarm.status">
{{ getStatusText(selectedAlarm.status) }}
</span>
</div>
<div class="info-item">
<span class="item-label">设备名称:</span>
<span class="item-value">{{ selectedAlarm.deviceName }}</span>
</div>
<div class="info-item">
<span class="item-label">告警时间:</span>
<span class="item-value">{{ selectedAlarm.alarmTime }}</span>
</div>
<div class="info-item full-width">
<span class="item-label">告警描述:</span>
<span class="item-value">{{ selectedAlarm.description }}</span>
</div>
</div>
</div>
<div class="detail-section" v-if="selectedAlarm.workOrderId">
<div class="section-title">关联工单</div>
<div class="workorder-info">
<div class="info-row">
<span class="info-label">工单编号:</span>
<span class="info-value workorder-link" @click="viewWorkOrder(selectedAlarm)">
{{ selectedAlarm.workOrderId }}
</span>
</div>
<div class="info-row">
<span class="info-label">工单状态:</span>
<span class="info-value workorder-status" :class="selectedAlarm.workOrderStatus">
{{ getWorkOrderStatusText(selectedAlarm.workOrderStatus) }}
</span>
</div>
<div class="info-row">
<span class="info-label">工单类型:</span>
<span class="item-value">{{ selectedAlarm.workOrderType }}</span>
</div>
<div class="info-row">
<span class="info-label">创建时间:</span>
<span class="item-value">{{ selectedAlarm.workOrderCreateTime }}</span>
</div>
<div class="info-row">
<span class="info-label">处理人:</span>
<span class="item-value">{{ selectedAlarm.workOrderHandler }}</span>
</div>
</div>
</div>
<div class="detail-section">
<div class="section-title">处理记录</div>
<div class="process-timeline">
<div
v-for="(record, index) in selectedAlarm.processRecords"
:key="index"
class="timeline-item"
>
<div class="timeline-dot" :class="record.type"></div>
<div class="timeline-content">
<div class="timeline-title">{{ record.title }}</div>
<div class="timeline-time">{{ record.time }}</div>
<div class="timeline-desc" v-if="record.description">{{ record.description }}</div>
</div>
</div>
</div>
</div>
</div>
<template #footer>
<el-button @click="detailDialogVisible = false" class="custom-btn">关闭</el-button>
</template>
</el-dialog>
<!-- 创建工单弹窗 -->
<el-dialog
v-model="workOrderDialogVisible"
title="创建工单"
width="600px"
class="custom-dialog"
>
<el-form :model="workOrderForm" :rules="workOrderRules" ref="workOrderFormRef" label-width="100px">
<el-form-item label="工单标题" prop="title">
<el-input v-model="workOrderForm.title" placeholder="请输入工单标题" class="custom-input"></el-input>
</el-form-item>
<el-form-item label="工单类型" prop="type">
<el-select v-model="workOrderForm.type" class="custom-select">
<el-option label="设备故障" value="deviceFault"></el-option>
<el-option label="系统异常" value="systemError"></el-option>
<el-option label="维护保养" value="maintenance"></el-option>
<el-option label="其他" value="other"></el-option>
</el-select>
</el-form-item>
<el-form-item label="优先级" prop="priority">
<el-select v-model="workOrderForm.priority" class="custom-select">
<el-option label="紧急" value="urgent"></el-option>
<el-option label="高" value="high"></el-option>
<el-option label="中" value="medium"></el-option>
<el-option label="低" value="low"></el-option>
</el-select>
</el-form-item>
<el-form-item label="指派人" prop="assignee">
<el-select v-model="workOrderForm.assignee" class="custom-select">
<el-option label="张三" value="user1"></el-option>
<el-option label="李四" value="user2"></el-option>
<el-option label="王五" value="user3"></el-option>
</el-select>
</el-form-item>
<el-form-item label="工单描述" prop="description">
<el-input
v-model="workOrderForm.description"
type="textarea"
:rows="4"
placeholder="请输入工单描述"
class="custom-input"
></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="workOrderDialogVisible = false" class="custom-btn">取消</el-button>
<el-button type="primary" @click="submitWorkOrder" class="custom-btn">提交工单</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
export default {
name: "AlarmManage",
data() {
return {
alarmStats: {
total: 156,
pending: 12,
processing: 28,
resolved: 116
},
workOrderConfig: {
enabled: true,
systemType: "systemA",
apiUrl: "https://api.workorder.example.com",
apiKey: "",
autoPush: true,
pushLevel: ["critical", "warning"],
priorityMapping: "direct"
},
filterForm: {
level: "",
status: "",
workOrderStatus: "",
keyword: "",
dateRange: []
},
currentPage: 1,
pageSize: 10,
alarmList: [],
detailDialogVisible: false,
workOrderDialogVisible: false,
selectedAlarm: null,
workOrderForm: {
title: "",
type: "",
priority: "",
assignee: "",
description: ""
},
workOrderRules: {
title: [{ required: true, message: "请输入工单标题", trigger: "blur" }],
type: [{ required: true, message: "请选择工单类型", trigger: "change" }],
priority: [{ required: true, message: "请选择优先级", trigger: "change" }],
assignee: [{ required: true, message: "请选择指派人", trigger: "change" }],
description: [{ required: true, message: "请输入工单描述", trigger: "blur" }]
}
};
},
computed: {
filteredAlarms() {
let alarms = [...this.alarmList];
if (this.filterForm.level) {
alarms = alarms.filter(a => a.level === this.filterForm.level);
}
if (this.filterForm.status) {
alarms = alarms.filter(a => a.status === this.filterForm.status);
}
if (this.filterForm.workOrderStatus) {
alarms = alarms.filter(a => a.workOrderStatus === this.filterForm.workOrderStatus);
}
if (this.filterForm.keyword) {
const keyword = this.filterForm.keyword.toLowerCase();
alarms = alarms.filter(a =>
a.title.toLowerCase().includes(keyword) ||
a.deviceName.toLowerCase().includes(keyword) ||
a.description.toLowerCase().includes(keyword)
);
}
if (this.filterForm.dateRange && this.filterForm.dateRange.length === 2) {
const [start, end] = this.filterForm.dateRange;
alarms = alarms.filter(a => a.alarmTime >= start && a.alarmTime <= end);
}
return alarms;
},
paginatedAlarms() {
const start = (this.currentPage - 1) * this.pageSize;
const end = start + this.pageSize;
return this.filteredAlarms.slice(start, end);
}
},
mounted() {
this.initAlarmList();
},
methods: {
initAlarmList() {
const now = new Date();
this.alarmList = [
{
id: "ALM202403040001",
level: "critical",
title: "主机过载告警",
description: "1号主机负载超过额定值105%,持续运行10分钟",
deviceName: "1号主机",
deviceCode: "CH-001",
alarmTime: "2024-03-04 10:30:25",
status: "pending",
workOrderId: "WO202403040001",
workOrderStatus: "inProgress",
workOrderType: "deviceFault",
workOrderCreateTime: "2024-03-04 10:35:00",
workOrderHandler: "张三",
processRecords: [
{ type: "create", title: "告警产生", time: "2024-03-04 10:30:25", description: "系统检测到1号主机过载" },
{ type: "push", title: "自动推送到工单系统", time: "2024-03-04 10:30:30", description: "成功创建工单WO202403040001" },
{ type: "process", title: "工单已派发", time: "2024-03-04 10:45:00", description: "指派给运维人员张三" },
{ type: "processing", title: "运维人员已接单", time: "2024-03-04 11:00:00", description: "开始处理主机过载问题" }
]
},
{
id: "ALM202403040002",
level: "critical",
title: "冷冻泵故障告警",
description: "4号冷冻泵电机过载保护触发",
deviceName: "4号冷冻泵",
deviceCode: "CP-004",
alarmTime: "2024-03-04 09:15:42",
status: "processing",
workOrderId: "WO202403040002",
workOrderStatus: "pending",
workOrderType: "deviceFault",
workOrderCreateTime: "2024-03-04 09:20:00",
workOrderHandler: "李四",
processRecords: [
{ type: "create", title: "告警产生", time: "2024-03-04 09:15:42", description: "检测到冷冻泵电机过载" },
{ type: "push", title: "自动推送到工单系统", time: "2024-03-04 09:15:45", description: "成功创建工单WO202403040002" },
{ type: "pending", title: "等待派发", time: "2024-03-04 09:20:00", description: "工单待派发" }
]
},
{
id: "ALM202403040003",
level: "warning",
title: "冷却塔风机效率下降",
description: "1号冷却塔风机效率低于设计值15%",
deviceName: "1号冷却塔",
deviceCode: "CT-001",
alarmTime: "2024-03-04 08:45:18",
status: "pending",
workOrderId: null,
workOrderStatus: null,
processRecords: [
{ type: "create", title: "告警产生", time: "2024-03-04 08:45:18", description: "检测到风机效率下降" }
]
},
{
id: "ALM202403040004",
level: "info",
title: "温度传感器数据异常",
description: "18号温度传感器读数波动较大",
deviceName: "18号温度传感器",
deviceCode: "TS-018",
alarmTime: "2024-03-04 07:30:55",
status: "resolved",
workOrderId: null,
workOrderStatus: null,
processRecords: [
{ type: "create", title: "告警产生", time: "2024-03-04 07:30:55", description: "传感器读数异常" },
{ type: "resolve", title: "告警已解决", time: "2024-03-04 08:15:00", description: "传感器已恢复正常" }
]
},
{
id: "ALM202403030005",
level: "critical",
title: "主机冷却水流量异常",
description: "2号主机冷却水流量低于设定值50%",
deviceName: "2号主机",
deviceCode: "CH-002",
alarmTime: "2024-03-03 16:22:33",
status: "resolved",
workOrderId: "WO202403030001",
workOrderStatus: "completed",
workOrderType: "deviceFault",
workOrderCreateTime: "2024-03-03 16:30:00",
workOrderHandler: "王五",
processRecords: [
{ type: "create", title: "告警产生", time: "2024-03-03 16:22:33", description: "检测到冷却水流量异常" },
{ type: "push", title: "自动推送到工单系统", time: "2024-03-03 16:22:40", description: "成功创建工单WO202403030001" },
{ type: "process", title: "工单已派发", time: "2024-03-03 16:45:00", description: "指派给运维人员王五" },
{ type: "processing", title: "运维人员已接单", time: "2024-03-03 17:00:00", description: "开始处理冷却水流量问题" },
{ type: "resolve", title: "问题已解决", time: "2024-03-03 18:30:00", description: "冷却水流量恢复正常" },
{ type: "complete", title: "工单已完成", time: "2024-03-03 18:35:00", description: "工单已归档" }
]
},
{
id: "ALM202403030006",
level: "warning",
title: "系统EER值低于阈值",
description: "机房整体EER值为4.2,低于设定值5.0",
deviceName: "机房系统",
deviceCode: "SYS-001",
alarmTime: "2024-03-03 14:10:22",
status: "resolved",
workOrderId: "WO202403030002",
workOrderStatus: "completed",
workOrderType: "systemError",
workOrderCreateTime: "2024-03-03 14:20:00",
workOrderHandler: "张三",
processRecords: [
{ type: "create", title: "告警产生", time: "2024-03-03 14:10:22", description: "系统能效低于阈值" },
{ type: "push", title: "自动推送到工单系统", time: "2024-03-03 14:10:30", description: "成功创建工单WO202403030002" },
{ type: "process", title: "工单已派发", time: "2024-03-03 14:30:00", description: "指派给运维人员张三" },
{ type: "resolve", title: "问题已解决", time: "2024-03-03 15:45:00", description: "EER值已恢复到6.5" },
{ type: "complete", title: "工单已完成", time: "2024-03-03 15:50:00", description: "工单已归档" }
]
}
];
// 添加更多模拟数据
for (let i = 7; i <= 20; i++) {
const levels = ["critical", "warning", "info"];
const statuses = ["pending", "processing", "resolved"];
const workOrderStatuses = ["notCreated", "pending", "inProgress", "completed"];
const date = new Date(now.getTime() - Math.random() * 7 * 24 * 60 * 60 * 1000);
this.alarmList.push({
id: `ALM2024030${String(i).padStart(5, '0')}`,
level: levels[Math.floor(Math.random() * levels.length)],
title: ["设备温度异常", "传感器离线", "电流波动异常", "冷却塔效率下降", "泵运行异常"][Math.floor(Math.random() * 5)],
description: `告警描述详情 ${i}`,
deviceName: `${["主机", "冷冻泵", "冷却泵", "冷却塔", "温度传感器"][Math.floor(Math.random() * 5)]}`,
deviceCode: `DEV-${String(i).padStart(3, '0')}`,
alarmTime: date.toISOString().slice(0, 19).replace('T', ' '),
status: statuses[Math.floor(Math.random() * statuses.length)],
workOrderId: Math.random() > 0.5 ? `WO2024030${String(i).padStart(5, '0')}` : null,
workOrderStatus: Math.random() > 0.3 ? workOrderStatuses[Math.floor(Math.random() * workOrderStatuses.length)] : null,
workOrderType: "deviceFault",
workOrderCreateTime: date.toISOString().slice(0, 19).replace('T', ' '),
workOrderHandler: ["张三", "李四", "王五"][Math.floor(Math.random() * 3)],
processRecords: [
{ type: "create", title: "告警产生", time: date.toISOString().slice(0, 19).replace('T', ' '), description: "告警已产生" }
]
});
}
},
getLevelText(level) {
const map = {
critical: "严重",
warning: "警告",
info: "信息"
};
return map[level] || level;
},
getStatusText(status) {
const map = {
pending: "待处理",
processing: "处理中",
resolved: "已解决"
};
return map[status] || status;
},
getWorkOrderStatusText(status) {
const map = {
notCreated: "未创建",
pending: "待派发",
inProgress: "进行中",
completed: "已完成"
};
return map[status] || status;
},
handleSearch() {
this.currentPage = 1;
},
handleReset() {
this.filterForm = {
level: "",
status: "",
workOrderStatus: "",
keyword: "",
dateRange: []
};
this.currentPage = 1;
},
handleExport() {
this.$message.success("告警列表导出成功");
},
handleSizeChange(size) {
this.pageSize = size;
this.currentPage = 1;
},
handleCurrentChange(page) {
this.currentPage = page;
},
viewAlarmDetail(alarm) {
this.selectedAlarm = alarm;
this.detailDialogVisible = true;
},
createWorkOrder(alarm) {
this.workOrderForm.title = `[告警] ${alarm.title} - ${alarm.deviceName}`;
this.workOrderForm.type = "deviceFault";
this.workOrderForm.priority = alarm.level === "critical" ? "urgent" : alarm.level === "warning" ? "high" : "medium";
this.workOrderForm.assignee = "";
this.workOrderForm.description = alarm.description;
this.workOrderDialogVisible = true;
},
submitWorkOrder() {
this.$refs.workOrderFormRef.validate((valid) => {
if (valid) {
const alarm = this.alarmList.find(a => a === this.selectedAlarm);
if (alarm) {
alarm.workOrderId = `WO${new Date().getTime()}`;
alarm.workOrderStatus = "pending";
alarm.workOrderType = this.workOrderForm.type;
alarm.workOrderCreateTime = new Date().toISOString().slice(0, 19).replace('T', ' ');
alarm.workOrderHandler = this.workOrderForm.assignee;
alarm.status = "processing";
alarm.processRecords.push({
type: "push",
title: "手动创建工单",
time: new Date().toISOString().slice(0, 19).replace('T', ' '),
description: `成功创建工单${alarm.workOrderId}`
});
}
this.$message.success("工单创建成功");
this.workOrderDialogVisible = false;
}
});
},
viewWorkOrder(alarm) {
this.$message.info(`查看工单: ${alarm.workOrderId}`);
},
handleAlarmCommand(command, alarm) {
if (command === "resolve") {
alarm.status = "resolved";
alarm.processRecords.push({
type: "resolve",
title: "标记为已解决",
time: new Date().toISOString().slice(0, 19).replace('T', ' '),
description: "用户手动标记为已解决"
});
this.$message.success("已标记为已解决");
} else if (command === "close") {
alarm.status = "resolved";
this.$message.success("告警已关闭");
} else if (command === "reopen") {
alarm.status = "pending";
alarm.processRecords.push({
type: "reopen",
title: "重新打开告警",
time: new Date().toISOString().slice(0, 19).replace('T', ' '),
description: "用户重新打开告警"
});
this.$message.success("告警已重新打开");
} else if (command === "createWorkOrder") {
this.createWorkOrder(alarm);
} else if (command === "viewWorkOrder") {
this.viewWorkOrder(alarm);
} else if (command === "export") {
this.$message.success(`导出告警${alarm.id}的报告成功`);
}
},
testConnection() {
this.$message.success("工单系统连接测试成功");
},
saveConfig() {
this.$message.success("配置已保存");
}
}
};
</script>
<style lang="scss" scoped>
.alarm-manage {
padding: 0.16rem;
}
.alarm-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(2rem, 1fr));
gap: 0.15rem;
margin-bottom: 0.16rem;
.stat-card {
display: flex;
align-items: center;
padding: 0.2rem;
background: rgba(255, 255, 255, 0.03);
border-radius: 0.08rem;
&.total {
background: linear-gradient(135deg, rgba(84, 112, 198, 0.2) 0%, rgba(115, 192, 222, 0.2) 100%);
border: 1px solid rgba(84, 112, 198, 0.3);
}
&.pending {
background: linear-gradient(135deg, rgba(238, 102, 102, 0.2) 0%, rgba(229, 57, 53, 0.2) 100%);
border: 1px solid rgba(238, 102, 102, 0.3);
}
&.processing {
background: linear-gradient(135deg, rgba(250, 200, 88, 0.2) 0%, rgba(255, 179, 0, 0.2) 100%);
border: 1px solid rgba(250, 200, 88, 0.3);
}
&.resolved {
background: linear-gradient(135deg, rgba(145, 204, 117, 0.2) 0%, rgba(102, 187, 106, 0.2) 100%);
border: 1px solid rgba(145, 204, 117, 0.3);
}
.stat-icon {
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 0.15rem;
background: rgba(255, 255, 255, 0.1);
svg {
width: 0.26rem;
height: 0.26rem;
fill: #ffffff;
}
}
.stat-info {
flex: 1;
.stat-value {
font-size: 0.28rem;
color: #15e1fd;
font-weight: bold;
margin-bottom: 0.03rem;
}
.stat-label {
font-size: 0.13rem;
color: #ffffff;
opacity: 0.7;
}
}
}
}
.workorder-config {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.2rem;
background: rgba(255, 255, 255, 0.05);
border-radius: 0.08rem;
margin-bottom: 0.16rem;
.config-header {
display: flex;
align-items: center;
gap: 0.12rem;
.config-title {
font-size: 0.16rem;
color: #ffffff;
font-weight: bold;
svg {
width: 0.18rem;
height: 0.18rem;
fill: #15e1fd;
}
}
}
}
.config-content {
background: rgba(255, 255, 255, 0.03);
border-radius: 0.08rem;
padding: 0.2rem;
margin-bottom: 0.16rem;
.config-row {
display: flex;
gap: 0.15rem;
margin-bottom: 0.15rem;
flex-wrap: wrap;
.config-item {
display: flex;
flex-direction: column;
gap: 0.08rem;
min-width: 2rem;
label {
font-size: 0.13rem;
color: #ffffff;
opacity: 0.7;
}
::v-deep .custom-select,
::v-deep .custom-input {
.el-input__wrapper,
.el-select__wrapper {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
.el-input__inner {
color: #ffffff;
}
}
}
::v-deep .custom-switch {
.el-switch__core {
background: rgba(255, 255, 255, 0.2);
}
}
}
}
.config-actions {
display: flex;
gap: 0.1rem;
margin-top: 0.15rem;
::v-deep .custom-btn {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
color: #ffffff;
&:hover {
background: rgba(255, 255, 255, 0.2);
}
&.el-button--primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
&:hover {
background: linear-gradient(135deg, #5a67d8 0%, #6b46c1 100%);
}
}
&.el-button--success {
background: linear-gradient(135deg, #91CC75 0%, #66BB6A 100%);
border: none;
&:hover {
background: linear-gradient(135deg, #7CB342 0%, #558B2F 100%);
}
}
svg {
width: 0.14rem;
height: 0.14rem;
fill: #ffffff;
margin-right: 0.04rem;
}
}
}
}
.alarm-filter {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 0.2rem;
background: rgba(255, 255, 255, 0.05);
border-radius: 0.08rem;
margin-bottom: 0.16rem;
flex-wrap: wrap;
gap: 0.15rem;
.filter-group {
display: flex;
gap: 0.15rem;
flex-wrap: wrap;
flex: 1;
min-width: 0;
.filter-item {
display: flex;
flex-direction: column;
gap: 0.08rem;
min-width: 1.8rem;
label {
font-size: 0.13rem;
color: #ffffff;
opacity: 0.7;
}
::v-deep .custom-select,
::v-deep .custom-picker {
.el-input__wrapper,
.el-select__wrapper {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
.el-input__inner {
color: #ffffff;
}
}
}
}
}
.filter-actions {
display: flex;
gap: 0.1rem;
.search-input {
width: 2.5rem;
}
::v-deep .custom-btn {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
color: #ffffff;
&:hover {
background: rgba(255, 255, 255, 0.2);
}
&.el-button--primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
&:hover {
background: linear-gradient(135deg, #5a67d8 0%, #6b46c1 100%);
}
}
&.el-button--success {
background: linear-gradient(135deg, #91CC75 0%, #66BB6A 100%);
border: none;
&:hover {
background: linear-gradient(135deg, #7CB342 0%, #558B2F 100%);
}
}
}
}
}
.alarm-list {
background: rgba(255, 255, 255, 0.03);
border-radius: 0.08rem;
padding: 0.2rem;
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.2rem;
.list-title {
font-size: 0.16rem;
color: #ffffff;
font-weight: bold;
padding-left: 0.12rem;
border-left: 0.04rem solid #15e1fd;
}
.list-info {
font-size: 0.14rem;
color: #ffffff;
opacity: 0.7;
}
}
.table-container {
overflow-x: auto;
border-radius: 0.04rem;
background: rgba(255, 255, 255, 0.02);
.alarm-table {
width: 100%;
border-collapse: collapse;
min-width: 10rem;
thead {
position: sticky;
top: 0;
z-index: 10;
tr {
background: linear-gradient(135deg, rgba(21, 225, 253, 0.1) 0%, rgba(145, 204, 117, 0.1) 100%);
th {
padding: 0.14rem 0.12rem;
text-align: left;
font-size: 0.14rem;
color: #15e1fd;
font-weight: 600;
border-bottom: 2px solid rgba(21, 225, 253, 0.3);
white-space: nowrap;
&:first-child {
padding-left: 0.15rem;
border-top-left-radius: 0.04rem;
}
&:last-child {
padding-right: 0.15rem;
border-top-right-radius: 0.04rem;
}
}
}
}
tbody {
tr {
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.02);
&:nth-child(even) {
background: rgba(255, 255, 255, 0.04);
}
&:hover {
background: rgba(21, 225, 253, 0.1);
transform: translateX(0.02rem);
}
td {
padding: 0.14rem 0.12rem;
font-size: 0.13rem;
color: #ffffff;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
vertical-align: middle;
&:first-child {
padding-left: 0.15rem;
}
&:last-child {
padding-right: 0.15rem;
}
.level-badge {
display: inline-block;
padding: 0.05rem 0.15rem;
border-radius: 0.05rem;
font-size: 0.12rem;
font-weight: 500;
white-space: nowrap;
box-shadow: 0 0.02rem 0.04rem rgba(0, 0, 0, 0.2);
&.critical {
background: linear-gradient(135deg, rgba(238, 102, 102, 0.25) 0%, rgba(229, 57, 53, 0.25) 100%);
color: #FF8A80;
border: 1px solid rgba(238, 102, 102, 0.4);
}
&.warning {
background: linear-gradient(135deg, rgba(250, 200, 88, 0.25) 0%, rgba(255, 179, 0, 0.25) 100%);
color: #FFE082;
border: 1px solid rgba(250, 200, 88, 0.4);
}
&.info {
background: linear-gradient(135deg, rgba(84, 112, 198, 0.25) 0%, rgba(103, 126, 234, 0.25) 100%);
color: #90CAF9;
border: 1px solid rgba(84, 112, 198, 0.4);
}
}
.status-badge {
display: inline-block;
padding: 0.05rem 0.15rem;
border-radius: 0.05rem;
font-size: 0.12rem;
font-weight: 500;
white-space: nowrap;
box-shadow: 0 0.02rem 0.04rem rgba(0, 0, 0, 0.2);
&.pending {
background: linear-gradient(135deg, rgba(238, 102, 102, 0.25) 0%, rgba(229, 57, 53, 0.25) 100%);
color: #FF8A80;
border: 1px solid rgba(238, 102, 102, 0.4);
}
&.processing {
background: linear-gradient(135deg, rgba(250, 200, 88, 0.25) 0%, rgba(255, 179, 0, 0.25) 100%);
color: #FFE082;
border: 1px solid rgba(250, 200, 88, 0.4);
}
&.resolved {
background: linear-gradient(135deg, rgba(145, 204, 117, 0.25) 0%, rgba(102, 187, 106, 0.25) 100%);
color: #A5D6A7;
border: 1px solid rgba(145, 204, 117, 0.4);
}
}
.workorder-link {
color: #15e1fd;
cursor: pointer;
text-decoration: underline;
font-weight: 500;
transition: all 0.3s ease;
&:hover {
color: #91CC75;
text-shadow: 0 0 0.04rem rgba(145, 204, 117, 0.5);
}
}
.no-workorder {
color: #ffffff;
opacity: 0.5;
font-style: italic;
}
.workorder-status {
display: inline-block;
padding: 0.05rem 0.15rem;
border-radius: 0.05rem;
font-size: 0.12rem;
font-weight: 500;
white-space: nowrap;
box-shadow: 0 0.02rem 0.04rem rgba(0, 0, 0, 0.2);
&.notCreated {
background: rgba(255, 255, 255, 0.1);
color: #ffffff;
opacity: 0.5;
}
&.pending {
background: linear-gradient(135deg, rgba(238, 102, 102, 0.25) 0%, rgba(229, 57, 53, 0.25) 100%);
color: #FF8A80;
border: 1px solid rgba(238, 102, 102, 0.4);
}
&.inProgress {
background: linear-gradient(135deg, rgba(250, 200, 88, 0.25) 0%, rgba(255, 179, 0, 0.25) 100%);
color: #FFE082;
border: 1px solid rgba(250, 200, 88, 0.4);
}
&.completed {
background: linear-gradient(135deg, rgba(145, 204, 117, 0.25) 0%, rgba(102, 187, 106, 0.25) 100%);
color: #A5D6A7;
border: 1px solid rgba(145, 204, 117, 0.4);
}
}
.title-cell {
max-width: 2.5rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
color: #15e1fd;
}
}
.action-buttons {
display: flex;
align-items: center;
gap: 0.08rem;
flex-wrap: nowrap;
.action-btn {
padding: 0.05rem 0.12rem;
font-size: 0.12rem;
border-radius: 0.04rem;
font-weight: 500;
transition: all 0.3s ease;
white-space: nowrap;
&.el-button--primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
color: #ffffff;
box-shadow: 0 0.04rem 0.08rem rgba(102, 126, 234, 0.3);
&:hover {
background: linear-gradient(135deg, #5a67d8 0%, #6b46c1 100%);
transform: translateY(-0.02rem);
box-shadow: 0 0.06rem 0.12rem rgba(102, 126, 234, 0.4);
}
}
&.el-button--success {
background: linear-gradient(135deg, #91CC75 0%, #66BB6A 100%);
border: none;
color: #ffffff;
box-shadow: 0 0.04rem 0.08rem rgba(145, 204, 117, 0.3);
&:hover {
background: linear-gradient(135deg, #7CB342 0%, #558B2F 100%);
transform: translateY(-0.02rem);
box-shadow: 0 0.06rem 0.12rem rgba(145, 204, 117, 0.4);
}
}
&.el-button--default {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
color: #ffffff;
&:hover {
background: rgba(255, 255, 255, 0.2);
}
}
&:active {
transform: translateY(0);
}
svg {
width: 0.1rem;
height: 0.1rem;
fill: #ffffff;
}
}
.action-dropdown {
::v-deep .el-button--small {
padding: 0.05rem 0.1rem;
font-size: 0.12rem;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
color: #ffffff;
border-radius: 0.04rem;
font-weight: 500;
&:hover {
background: rgba(255, 255, 255, 0.2);
}
svg {
width: 0.1rem;
height: 0.1rem;
fill: #ffffff;
}
}
}
}
}
}
}
}
}
.pagination-container {
display: flex;
justify-content: center;
padding-top: 0.2rem;
::v-deep .custom-pagination {
.el-pagination__total,
.el-pagination__jump,
.el-pager li {
color: #ffffff;
}
.el-pager li {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
&:hover {
background: rgba(255, 255, 255, 0.1);
}
&.is-active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-color: transparent;
}
}
.btn-prev,
.btn-next {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
color: #ffffff;
&:hover {
background: rgba(255, 255, 255, 0.1);
}
}
}
}
}
::v-deep .custom-dialog {
.el-dialog {
background: rgba(30, 30, 50, 0.95);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.el-dialog__header {
background: rgba(255, 255, 255, 0.05);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.el-dialog__title {
color: #ffffff;
}
.el-dialog__headerbtn .el-dialog__close {
color: #ffffff;
}
.detail-content {
.detail-section {
margin-bottom: 0.3rem;
.section-title {
font-size: 0.15rem;
color: #15e1fd;
font-weight: bold;
margin-bottom: 0.15rem;
padding-left: 0.12rem;
border-left: 0.04rem solid #15e1fd;
}
.info-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.15rem;
.info-item {
display: flex;
justify-content: space-between;
padding: 0.12rem;
background: rgba(255, 255, 255, 0.03);
border-radius: 0.06rem;
&.full-width {
grid-column: 1 / -1;
}
.item-label {
font-size: 0.14rem;
color: #ffffff;
opacity: 0.7;
}
.item-value {
font-size: 0.14rem;
color: #ffffff;
&.level-badge {
padding: 0.04rem 0.12rem;
border-radius: 0.04rem;
font-size: 0.12rem;
&.critical {
background: rgba(238, 102, 102, 0.2);
color: #EE6666;
}
&.warning {
background: rgba(250, 200, 88, 0.2);
color: #FAC858;
}
&.info {
background: rgba(84, 112, 198, 0.2);
color: #5470C6;
}
}
&.status-badge {
padding: 0.04rem 0.12rem;
border-radius: 0.04rem;
font-size: 0.12rem;
&.pending {
background: rgba(238, 102, 102, 0.2);
color: #EE6666;
}
&.processing {
background: rgba(250, 200, 88, 0.2);
color: #FAC858;
}
&.resolved {
background: rgba(145, 204, 117, 0.2);
color: #91CC75;
}
}
}
}
}
.workorder-info {
background: rgba(255, 255, 255, 0.05);
border-radius: 0.06rem;
padding: 0.15rem;
.info-row {
display: flex;
justify-content: space-between;
margin-bottom: 0.1rem;
&:last-child {
margin-bottom: 0;
}
.info-label {
font-size: 0.14rem;
color: #ffffff;
opacity: 0.7;
}
.info-value {
font-size: 0.14rem;
color: #ffffff;
&.workorder-link {
color: #15e1fd;
cursor: pointer;
text-decoration: underline;
&:hover {
color: #91CC75;
}
}
&.workorder-status {
padding: 0.04rem 0.12rem;
border-radius: 0.04rem;
font-size: 0.12rem;
&.pending {
background: rgba(238, 102, 102, 0.2);
color: #EE6666;
}
&.inProgress {
background: rgba(250, 200, 88, 0.2);
color: #FAC858;
}
&.completed {
background: rgba(145, 204, 117, 0.2);
color: #91CC75;
}
}
}
}
}
.process-timeline {
.timeline-item {
display: flex;
margin-bottom: 0.2rem;
.timeline-dot {
width: 0.1rem;
height: 0.1rem;
border-radius: 50%;
margin-right: 0.15rem;
margin-top: 0.03rem;
&.create {
background: #5470C6;
}
&.push {
background: #91CC75;
}
&.process,
&.processing {
background: #FAC858;
}
&.resolve {
background: #73C0DE;
}
&.complete {
background: #3BA272;
}
&.pending {
background: #EE6666;
}
&.reopen {
background: #F37336;
}
}
.timeline-content {
flex: 1;
.timeline-title {
font-size: 0.14rem;
color: #ffffff;
font-weight: bold;
margin-bottom: 0.05rem;
}
.timeline-time {
font-size: 0.12rem;
color: #ffffff;
opacity: 0.6;
margin-bottom: 0.05rem;
}
.timeline-desc {
font-size: 0.13rem;
color: #ffffff;
opacity: 0.8;
}
}
}
}
}
}
}
::v-deep .el-form {
.el-form-item__label {
color: #ffffff;
opacity: 0.7;
}
.custom-input,
.custom-select {
.el-input__wrapper,
.el-select__wrapper {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
.el-input__inner {
color: #ffffff;
}
}
}
}
::v-deep .el-dropdown-menu {
background: rgba(30, 30, 50, 0.98);
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 0.08rem 0.24rem rgba(0, 0, 0, 0.4);
border-radius: 0.06rem;
padding: 0.08rem 0;
.el-dropdown-menu__item {
color: #ffffff;
padding: 0.1rem 0.16rem;
font-size: 0.13rem;
transition: all 0.3s ease;
&:hover {
background: linear-gradient(135deg, rgba(21, 225, 253, 0.15) 0%, rgba(145, 204, 117, 0.15) 100%);
color: #15e1fd;
}
&.is-divided {
position: relative;
margin-top: 0.08rem;
&::before {
content: "";
position: absolute;
top: -0.04rem;
left: 0.1rem;
right: 0.1rem;
height: 1px;
background: rgba(255, 255, 255, 0.1);
}
}
}
}
@media (max-width: 1485px) {
.alarm-stats {
grid-template-columns: repeat(2, 1fr);
}
.alarm-filter {
flex-direction: column;
}
.filter-actions {
width: 100%;
margin-top: 0.1rem;
.search-input {
flex: 1;
}
}
.table-container {
.alarm-table {
thead tr th,
tbody tr td {
font-size: 0.12rem;
padding: 0.08rem;
}
}
}
}
</style>