楼宇能效监测控制系统
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.
 
 
 
 
 

1514 lines
46 KiB

<template>
<div class="energy-warning">
<!-- 预警统计卡片 -->
<div class="warning-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">{{ warningStats.total }}</div>
<div class="stat-label">预警总数</div>
</div>
</div>
<div class="stat-card critical">
<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">{{ warningStats.critical }}</div>
<div class="stat-label">严重预警</div>
</div>
</div>
<div class="stat-card major">
<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">{{ warningStats.major }}</div>
<div class="stat-label">重要预警</div>
</div>
</div>
<div class="stat-card minor">
<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">{{ warningStats.minor }}</div>
<div class="stat-label">一般预警</div>
</div>
</div>
</div>
<!-- 预警筛选 -->
<div class="filter-section">
<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="major"></el-option>
<el-option label="一般" value="minor"></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.department" placeholder="全部" clearable class="custom-select">
<el-option label="全部" value=""></el-option>
<el-option label="办公楼A" value="OA"></el-option>
<el-option label="办公楼B" value="OB"></el-option>
<el-option label="商场区域" value="SM"></el-option>
<el-option label="停车场" value="PK"></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>
<div class="primary-btn"><el-button type="primary" @click="handleSearch" class="custom-btn">查询</el-button></div>
<el-button @click="handleReset" class="custom-btn">重置</el-button>
<div class="success-btn"><el-button type="success" @click="handleExport" class="custom-btn">导出</el-button></div>
</div>
</div>
<!-- 预警列表 -->
<div class="warning-list">
<div class="list-header">
<div class="list-title">预警列表</div>
<div class="list-info">
共 {{ filteredWarnings.length }} 条预警
</div>
</div>
<div class="table-container">
<table class="warning-table">
<thead>
<tr>
<th>预警ID</th>
<th>预警级别</th>
<th>部门名称</th>
<th>项目名称</th>
<th>能耗类型</th>
<th>实际值</th>
<th>阈值</th>
<th>超出比例</th>
<th>预警时间</th>
<th>处理状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="warning in paginatedWarnings" :key="warning.id">
<td>{{ warning.id }}</td>
<td>
<span class="level-badge" :class="warning.level">
{{ getLevelText(warning.level) }}
</span>
</td>
<td>{{ warning.department }}</td>
<td>{{ warning.projectName }}</td>
<td>{{ warning.energyType }}</td>
<td class="value-cell">{{ warning.actualValue }} kWh</td>
<td class="value-cell">{{ warning.threshold }} kWh</td>
<td>
<span class="exceed-badge">{{ warning.exceedRatio }}%</span>
</td>
<td>{{ warning.warningTime }}</td>
<td>
<span class="status-badge" :class="warning.status">
{{ getStatusText(warning.status) }}
</span>
</td>
<td class="action-cell">
<div class="action-buttons">
<div class="primary-btn" v-if="warning.status === 'pending'"><el-button
type="primary"
size="small"
@click="handleWarning(warning)"
class="action-btn"
>
处理
</el-button></div>
<el-button
size="small"
@click="viewDetail(warning)"
class="action-btn"
>
详情
</el-button>
<el-dropdown @command="(cmd) => handleCommand(cmd, warning)" 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="markResolved" v-if="warning.status !== 'resolved'">标记已处理</el-dropdown-item>
<el-dropdown-item command="exportReport" 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="filteredWarnings.length"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
class="custom-pagination"
></el-pagination>
</div>
</div>
<!-- 预警趋势图 -->
<div class="chart-section">
<div class="section-header">
<div class="section-title">
<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>
<div class="chart-container">
<div ref="warningTrendChart" class="chart"></div>
</div>
</div>
<!-- 预警详情弹窗 -->
<el-dialog
v-model="detailDialogVisible"
title="预警详情"
width="700px"
class="custom-dialog"
>
<div class="detail-content" v-if="selectedWarning">
<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">{{ selectedWarning.id }}</span>
</div>
<div class="info-item">
<span class="item-label">预警级别:</span>
<span class="item-value level-badge" :class="selectedWarning.level">
{{ getLevelText(selectedWarning.level) }}
</span>
</div>
<div class="info-item">
<span class="item-label">部门名称:</span>
<span class="item-value">{{ selectedWarning.department }}</span>
</div>
<div class="info-item">
<span class="item-label">项目名称:</span>
<span class="item-value">{{ selectedWarning.projectName }}</span>
</div>
<div class="info-item">
<span class="item-label">能耗类型:</span>
<span class="item-value">{{ selectedWarning.energyType }}</span>
</div>
<div class="info-item">
<span class="item-label">预警时间:</span>
<span class="item-value">{{ selectedWarning.warningTime }}</span>
</div>
<div class="info-item">
<span class="item-label">实际值:</span>
<span class="item-value">{{ selectedWarning.actualValue }} kWh</span>
</div>
<div class="info-item">
<span class="item-label">阈值:</span>
<span class="item-value">{{ selectedWarning.threshold }} kWh</span>
</div>
<div class="info-item full-width">
<span class="item-label">超出比例:</span>
<span class="item-value exceed-badge">{{ selectedWarning.exceedRatio }}%</span>
</div>
<div class="info-item full-width">
<span class="item-label">预警描述:</span>
<span class="item-value">{{ selectedWarning.description }}</span>
</div>
</div>
</div>
<div class="detail-section" v-if="selectedWarning.handleRecord">
<div class="section-title">处理记录</div>
<div class="process-timeline">
<div
v-for="(record, index) in selectedWarning.handleRecord"
: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="handleDialogVisible"
title="处理预警"
width="600px"
class="custom-dialog"
>
<el-form :model="handleForm" :rules="handleRules" ref="handleFormRef" label-width="100px">
<el-form-item label="处理方式" prop="handleType">
<el-select v-model="handleForm.handleType" class="custom-select">
<el-option label="调整设备参数" value="adjust"></el-option>
<el-option label="关闭部分设备" value="close"></el-option>
<el-option label="检查设备状态" value="check"></el-option>
<el-option label="通知负责人" value="notify"></el-option>
</el-select>
</el-form-item>
<el-form-item label="处理说明" prop="handleDescription">
<el-input
v-model="handleForm.handleDescription"
type="textarea"
:rows="4"
placeholder="请输入处理说明"
class="custom-input"
></el-input>
</el-form-item>
<el-form-item label="处理人" prop="handler">
<el-input v-model="handleForm.handler" placeholder="请输入处理人" class="custom-input"></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleDialogVisible = false" class="custom-btn">取消</el-button>
<el-button type="primary" @click="submitHandle" class="custom-btn">提交</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import * as echarts from 'echarts';
export default {
name: "EnergyWarning",
data() {
return {
warningStats: {
total: 56,
critical: 8,
major: 18,
minor: 30
},
filterForm: {
level: "",
status: "",
department: "",
keyword: "",
dateRange: []
},
currentPage: 1,
pageSize: 10,
warningList: [],
detailDialogVisible: false,
handleDialogVisible: false,
selectedWarning: null,
handleForm: {
handleType: "",
handleDescription: "",
handler: ""
},
handleRules: {
handleType: [{ required: true, message: "请选择处理方式", trigger: "change" }],
handleDescription: [{ required: true, message: "请输入处理说明", trigger: "blur" }],
handler: [{ required: true, message: "请输入处理人", trigger: "blur" }]
},
warningTrendChart: null
};
},
computed: {
filteredWarnings() {
let warnings = [...this.warningList];
if (this.filterForm.level) {
warnings = warnings.filter(w => w.level === this.filterForm.level);
}
if (this.filterForm.status) {
warnings = warnings.filter(w => w.status === this.filterForm.status);
}
if (this.filterForm.department) {
warnings = warnings.filter(w => w.departmentCode === this.filterForm.department);
}
if (this.filterForm.keyword) {
const keyword = this.filterForm.keyword.toLowerCase();
warnings = warnings.filter(w =>
w.projectName.toLowerCase().includes(keyword) ||
w.department.toLowerCase().includes(keyword) ||
w.id.toLowerCase().includes(keyword)
);
}
if (this.filterForm.dateRange && this.filterForm.dateRange.length === 2) {
const [start, end] = this.filterForm.dateRange;
warnings = warnings.filter(w => w.warningDate >= start && w.warningDate <= end);
}
return warnings;
},
paginatedWarnings() {
const start = (this.currentPage - 1) * this.pageSize;
const end = start + this.pageSize;
return this.filteredWarnings.slice(start, end);
}
},
mounted() {
this.initWarningList();
this.$nextTick(() => {
this.initCharts();
});
window.addEventListener('resize', this.handleResize);
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize);
this.destroyCharts();
},
methods: {
getLevelText(level) {
const map = {
critical: "严重",
major: "重要",
minor: "一般"
};
return map[level] || level;
},
getStatusText(status) {
const map = {
pending: "待处理",
processing: "处理中",
resolved: "已处理"
};
return map[status] || status;
},
initWarningList() {
const now = new Date();
this.warningList = [
{
id: "WRN202403040001",
level: "critical",
department: "办公楼A",
departmentCode: "OA",
projectName: "办公楼A照明系统",
energyType: "LED照明",
actualValue: 1256.8,
threshold: 1000,
exceedRatio: 25.68,
warningTime: "2024-03-04 10:30:25",
warningDate: "2024-03-04",
status: "pending",
description: "办公楼A照明系统能耗超出阈值25.68%,请及时检查",
handleRecord: [
{ type: "warning", title: "预警触发", time: "2024-03-04 10:30:25", description: "系统能耗超出阈值" }
]
},
{
id: "WRN202403040002",
level: "critical",
department: "商场区域",
departmentCode: "SM",
projectName: "商场区域照明系统",
energyType: "景观照明",
actualValue: 856.5,
threshold: 650,
exceedRatio: 31.77,
warningTime: "2024-03-04 09:15:42",
warningDate: "2024-03-04",
status: "processing",
description: "商场区域景观照明能耗超出阈值31.77%,正在进行处理",
handleRecord: [
{ type: "warning", title: "预警触发", time: "2024-03-04 09:15:42", description: "系统能耗超出阈值" },
{ type: "process", title: "开始处理", time: "2024-03-04 09:20:00", description: "已通知负责人" }
]
},
{
id: "WRN202403040003",
level: "major",
department: "办公楼B",
departmentCode: "OB",
projectName: "办公楼B照明系统",
energyType: "LED照明",
actualValue: 1125.6,
threshold: 1000,
exceedRatio: 12.56,
warningTime: "2024-03-04 08:45:18",
warningDate: "2024-03-04",
status: "pending",
description: "办公楼B照明系统能耗超出阈值12.56%",
handleRecord: [
{ type: "warning", title: "预警触发", time: "2024-03-04 08:45:18", description: "系统能耗超出阈值" }
]
},
{
id: "WRN202403030004",
level: "major",
department: "停车场",
departmentCode: "PK",
projectName: "停车场照明系统",
energyType: "LED照明",
actualValue: 928.5,
threshold: 850,
exceedRatio: 9.24,
warningTime: "2024-03-03 16:22:33",
warningDate: "2024-03-03",
status: "resolved",
description: "停车场照明系统能耗超出阈值9.24%,已处理",
handleRecord: [
{ type: "warning", title: "预警触发", time: "2024-03-03 16:22:33", description: "系统能耗超出阈值" },
{ type: "process", title: "调整设备参数", time: "2024-03-03 16:30:00", description: "已调整照明亮度" },
{ type: "resolved", title: "预警已处理", time: "2024-03-03 17:15:00", description: "能耗已恢复正常" }
]
},
{
id: "WRN202403030005",
level: "minor",
department: "办公楼A",
departmentCode: "OA",
projectName: "办公楼A照明系统",
energyType: "应急照明",
actualValue: 256.8,
threshold: 240,
exceedRatio: 7.00,
warningTime: "2024-03-03 14:10:22",
warningDate: "2024-03-03",
status: "resolved",
description: "办公楼A应急照明能耗超出阈值7.00%,已处理",
handleRecord: [
{ type: "warning", title: "预警触发", time: "2024-03-03 14:10:22", description: "系统能耗超出阈值" },
{ type: "process", title: "检查设备", time: "2024-03-03 14:20:00", description: "设备运行正常" },
{ type: "resolved", title: "预警已处理", time: "2024-03-03 14:30:00", description: "已恢复正常" }
]
},
{
id: "WRN202403020006",
level: "minor",
department: "商场区域",
departmentCode: "SM",
projectName: "商场区域照明系统",
energyType: "走廊照明",
actualValue: 385.2,
threshold: 360,
exceedRatio: 7.00,
warningTime: "2024-03-02 11:30:45",
warningDate: "2024-03-02",
status: "resolved",
description: "商场区域走廊照明能耗超出阈值7.00%,已处理",
handleRecord: [
{ type: "warning", title: "预警触发", time: "2024-03-02 11:30:45", description: "系统能耗超出阈值" },
{ type: "process", title: "调整亮度", time: "2024-03-02 11:40:00", description: "已降低亮度" },
{ type: "resolved", title: "预警已处理", time: "2024-03-02 12:00:00", description: "能耗已恢复正常" }
]
}
];
for (let i = 7; i <= 56; i++) {
const levels = ["critical", "major", "minor"];
const statuses = ["pending", "processing", "resolved"];
const departments = [
{ name: "办公楼A", code: "OA" },
{ name: "办公楼B", code: "OB" },
{ name: "商场区域", code: "SM" },
{ name: "停车场", code: "PK" }
];
const energyTypes = ["LED照明", "应急照明", "景观照明", "走廊照明"];
const date = new Date(now.getTime() - Math.random() * 30 * 24 * 60 * 60 * 1000);
const dept = departments[Math.floor(Math.random() * departments.length)];
const energyType = energyTypes[Math.floor(Math.random() * energyTypes.length)];
const threshold = 200 + Math.random() * 800;
const exceedRatio = 5 + Math.random() * 30;
const actualValue = threshold * (1 + exceedRatio / 100);
this.warningList.push({
id: `WRN2024${String(i).padStart(7, '0')}`,
level: levels[Math.floor(Math.random() * levels.length)],
department: dept.name,
departmentCode: dept.code,
projectName: `${dept.name}照明系统`,
energyType: energyType,
actualValue: actualValue.toFixed(1),
threshold: threshold.toFixed(1),
exceedRatio: exceedRatio.toFixed(2),
warningTime: date.toISOString().slice(0, 19).replace('T', ' '),
warningDate: date.toISOString().slice(0, 10),
status: statuses[Math.floor(Math.random() * statuses.length)],
description: `${dept.name}${energyType}能耗超出阈值${exceedRatio.toFixed(2)}%`,
handleRecord: [
{ type: "warning", title: "预警触发", time: date.toISOString().slice(0, 19).replace('T', ' '), description: "系统能耗超出阈值" }
]
});
}
},
initCharts() {
this.initWarningTrendChart();
},
initWarningTrendChart() {
if (!this.$refs.warningTrendChart) return;
if (this.warningTrendChart) {
this.warningTrendChart.dispose();
}
this.warningTrendChart = echarts.init(this.$refs.warningTrendChart);
const dates = [];
const criticalData = [];
const majorData = [];
const minorData = [];
const now = new Date();
for (let i = 29; i >= 0; i--) {
const date = new Date(now);
date.setDate(date.getDate() - i);
dates.push(date.toISOString().slice(5, 10));
criticalData.push(Math.round(Math.random() * 5));
majorData.push(Math.round(Math.random() * 10));
minorData.push(Math.round(Math.random() * 15));
}
const option = {
backgroundColor: "transparent",
tooltip: {
trigger: "axis",
backgroundColor: "rgba(30, 30, 50, 0.95)",
borderColor: "rgba(255, 255, 255, 0.2)",
textStyle: {
color: "#ffffff"
}
},
legend: {
data: ["严重预警", "重要预警", "一般预警"],
textStyle: {
color: "#ffffff"
},
top: 10
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true
},
xAxis: {
type: "category",
boundaryGap: false,
data: dates,
axisLine: {
lineStyle: {
color: "rgba(255, 255, 255, 0.3)"
}
},
axisLabel: {
color: "#ffffff"
}
},
yAxis: {
type: "value",
name: "预警数量",
nameTextStyle: {
color: "#ffffff"
},
axisLine: {
lineStyle: {
color: "rgba(255, 255, 255, 0.3)"
}
},
axisLabel: {
color: "#ffffff"
},
splitLine: {
lineStyle: {
color: "rgba(255, 255, 255, 0.1)"
}
}
},
series: [
{
name: "严重预警",
type: "line",
smooth: true,
data: criticalData,
itemStyle: {
color: "#EE6666"
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "rgba(238, 102, 102, 0.5)" },
{ offset: 1, color: "rgba(238, 102, 102, 0.05)" }
])
}
},
{
name: "重要预警",
type: "line",
smooth: true,
data: majorData,
itemStyle: {
color: "#FAC858"
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "rgba(250, 200, 88, 0.5)" },
{ offset: 1, color: "rgba(250, 200, 88, 0.05)" }
])
}
},
{
name: "一般预警",
type: "line",
smooth: true,
data: minorData,
itemStyle: {
color: "#91CC75"
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "rgba(145, 204, 117, 0.5)" },
{ offset: 1, color: "rgba(145, 204, 117, 0.05)" }
])
}
}
]
};
this.warningTrendChart.setOption(option);
},
handleSearch() {
this.currentPage = 1;
},
handleReset() {
this.filterForm = {
level: "",
status: "",
department: "",
keyword: "",
dateRange: []
};
this.currentPage = 1;
},
handleExport() {
this.$message.success("预警列表导出成功");
},
handleSizeChange(size) {
this.pageSize = size;
this.currentPage = 1;
},
handleCurrentChange(page) {
this.currentPage = page;
},
viewDetail(warning) {
this.selectedWarning = warning;
this.detailDialogVisible = true;
},
handleWarning(warning) {
this.selectedWarning = warning;
this.handleForm = {
handleType: "",
handleDescription: "",
handler: ""
};
this.handleDialogVisible = true;
},
submitHandle() {
this.$refs.handleFormRef.validate((valid) => {
if (valid) {
const warning = this.warningList.find(w => w === this.selectedWarning);
if (warning) {
warning.status = "processing";
warning.handleRecord.push({
type: "process",
title: "开始处理",
time: new Date().toISOString().slice(0, 19).replace('T', ' '),
description: `${this.handleForm.handler}${this.handleForm.handleDescription}`
});
}
this.$message.success("预警处理已提交");
this.handleDialogVisible = false;
}
});
},
handleCommand(command, warning) {
if (command === "markResolved") {
warning.status = "resolved";
warning.handleRecord.push({
type: "resolved",
title: "标记已处理",
time: new Date().toISOString().slice(0, 19).replace('T', ' '),
description: "用户手动标记为已处理"
});
this.$message.success("已标记为已处理");
} else if (command === "exportReport") {
this.$message.success(`导出预警${warning.id}的报告成功`);
}
},
handleResize() {
if (this.warningTrendChart) this.warningTrendChart.resize();
},
destroyCharts() {
if (this.warningTrendChart) {
this.warningTrendChart.dispose();
this.warningTrendChart = null;
}
}
}
};
</script>
<style lang="scss" scoped>
.energy-warning {
padding: 0.16rem;
}
.warning-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);
}
&.critical {
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);
}
&.major {
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);
}
&.minor {
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;
}
}
}
}
.filter-section {
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.5rem;
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: 2rem;
}
::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%);
}
}
}
}
}
.warning-list {
background: rgba(255, 255, 255, 0.03);
border-radius: 0.08rem;
padding: 0.2rem;
margin-bottom: 0.16rem;
.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);
.warning-table {
width: 100%;
border-collapse: collapse;
min-width: 12rem;
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.12rem 0.1rem;
text-align: left;
font-size: 0.13rem;
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.12rem 0.1rem;
font-size: 0.12rem;
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;
}
.value-cell {
font-weight: 500;
color: #FAC858;
}
.level-badge {
display: inline-block;
padding: 0.04rem 0.12rem;
border-radius: 0.04rem;
font-size: 0.11rem;
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);
}
&.major {
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);
}
&.minor {
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);
}
}
.status-badge {
display: inline-block;
padding: 0.04rem 0.12rem;
border-radius: 0.04rem;
font-size: 0.11rem;
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);
}
}
.exceed-badge {
display: inline-block;
padding: 0.04rem 0.12rem;
border-radius: 0.04rem;
font-size: 0.11rem;
font-weight: 500;
color: #EE6666;
background: rgba(238, 102, 102, 0.2);
border: 1px solid rgba(238, 102, 102, 0.4);
}
.action-cell {
display: flex;
align-items: center;
gap: 0.06rem;
flex-wrap: nowrap;
.action-btn,
.action-dropdown {
::v-deep .el-button--small {
padding: 0.04rem 0.1rem;
font-size: 0.11rem;
border-radius: 0.04rem;
font-weight: 500;
transition: all 0.3s ease;
white-space: nowrap;
&:not(.el-button--primary):not(.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);
}
}
&.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);
}
&:active {
transform: translateY(0);
}
}
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);
}
}
}
}
}
.chart-section {
background: rgba(255, 255, 255, 0.03);
border-radius: 0.08rem;
padding: 0.2rem;
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.2rem;
.section-title {
font-size: 0.16rem;
color: #ffffff;
font-weight: bold;
padding-left: 0.12rem;
border-left: 0.04rem solid #15e1fd;
display: flex;
align-items: center;
gap: 0.08rem;
svg {
width: 0.18rem;
height: 0.18rem;
fill: #15e1fd;
}
}
}
.chart-container {
background: rgba(255, 255, 255, 0.02);
border-radius: 0.04rem;
padding: 0.1rem;
.chart {
width: 100%;
height: 4rem;
}
}
}
::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.13rem;
color: #ffffff;
opacity: 0.7;
}
.item-value {
font-size: 0.13rem;
color: #ffffff;
&.level-badge {
padding: 0.03rem 0.1rem;
border-radius: 0.03rem;
font-size: 0.11rem;
&.critical {
background: rgba(238, 102, 102, 0.2);
color: #EE6666;
}
&.major {
background: rgba(250, 200, 88, 0.2);
color: #FAC858;
}
&.minor {
background: rgba(145, 204, 117, 0.2);
color: #91CC75;
}
}
&.exceed-badge {
padding: 0.03rem 0.1rem;
border-radius: 0.03rem;
font-size: 0.11rem;
color: #EE6666;
background: rgba(238, 102, 102, 0.2);
}
}
}
}
.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;
&.warning {
background: #EE6666;
}
&.process {
background: #FAC858;
}
&.resolved {
background: #91CC75;
}
}
.timeline-content {
flex: 1;
.timeline-title {
font-size: 0.13rem;
color: #ffffff;
font-weight: bold;
margin-bottom: 0.05rem;
}
.timeline-time {
font-size: 0.11rem;
color: #ffffff;
opacity: 0.6;
margin-bottom: 0.05rem;
}
.timeline-desc {
font-size: 0.12rem;
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) {
.warning-stats {
grid-template-columns: repeat(2, 1fr);
}
.filter-section {
flex-direction: column;
}
.filter-actions {
width: 100%;
margin-top: 0.1rem;
.search-input {
flex: 1;
}
}
.table-container {
.warning-table {
thead tr th,
tbody tr td {
font-size: 0.11rem;
padding: 0.08rem;
}
}
}
}
</style>