增加告警页面(带bug)
This commit is contained in:
245
src/views/alarm/AlarmList.vue
Normal file
245
src/views/alarm/AlarmList.vue
Normal file
@@ -0,0 +1,245 @@
|
||||
<template>
|
||||
<div class="alarm-list">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<div class="title-container">
|
||||
<h2 class="page-title">告警管理</h2>
|
||||
<el-button type="text" @click="loadAlarms()" class="refresh-btn" title="刷新告警列表">
|
||||
<el-icon :size="20"><Refresh /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="未解决告警" name="unresolved"></el-tab-pane>
|
||||
<el-tab-pane label="已解决告警" name="resolved"></el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading">
|
||||
<el-skeleton animated />
|
||||
</div>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<div v-else-if="error" class="error">
|
||||
<el-alert
|
||||
title="获取告警数据失败"
|
||||
:description="error"
|
||||
type="error"
|
||||
show-icon
|
||||
closable
|
||||
@close="error = null"
|
||||
/>
|
||||
<el-button type="primary" @click="loadAlarms()" class="retry-btn">重新加载</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 告警列表 -->
|
||||
<el-table v-else :data="alarmData" style="width: 100%" :fit="true" table-layout="auto">
|
||||
<el-table-column prop="alarm_summary" label="告警摘要" min-width="150" />
|
||||
<el-table-column prop="level" label="告警级别" min-width="100">
|
||||
<template #default="scope">{{ formatSeverity(scope.row.level) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="source_type" label="告警来源类型" min-width="120">
|
||||
<template #default="scope">{{ formatSourceType(scope.row.source_type) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="source_id" label="告警来源ID" min-width="100" />
|
||||
<el-table-column prop="trigger_time" label="触发时间" min-width="180">
|
||||
<template #default="scope">{{ formatTime(scope.row.trigger_time) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="activeTab === 'resolved'" prop="resolve_time" label="解决时间" min-width="180">
|
||||
<template #default="scope">{{ formatTime(scope.row.resolve_time) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="activeTab === 'unresolved'" label="操作" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleIgnore(scope.row)" :disabled="scope.row.is_ignored">
|
||||
{{ scope.row.is_ignored ? '已忽略' : '忽略' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-if="!loading && !error"
|
||||
class="pagination-container"
|
||||
:current-page="pagination.currentPage"
|
||||
:page-size="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Refresh } from '@element-plus/icons-vue';
|
||||
import { AlarmApi } from '../../api/alarm.js';
|
||||
import { AlarmSourceType, SeverityLevel } from '../../enums.js';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
|
||||
export default {
|
||||
name: 'AlarmList',
|
||||
components: {
|
||||
Refresh,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
alarmData: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
activeTab: 'unresolved',
|
||||
pagination: {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadAlarms();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 加载告警列表数据
|
||||
* @param {string} [tabName] - 可选参数,当前激活的tab名称,用于确保调用正确的API
|
||||
*/
|
||||
async loadAlarms(tabName) {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
try {
|
||||
const params = {
|
||||
page: this.pagination.currentPage,
|
||||
page_size: this.pagination.pageSize,
|
||||
order_by: 'trigger_time DESC',
|
||||
};
|
||||
|
||||
let response;
|
||||
// 使用传入的tabName或组件的activeTab来判断
|
||||
const currentTab = tabName || this.activeTab;
|
||||
if (currentTab === 'unresolved') {
|
||||
response = await AlarmApi.getActiveAlarms(params);
|
||||
} else {
|
||||
response = await AlarmApi.getHistoricalAlarms(params);
|
||||
}
|
||||
|
||||
this.alarmData = response.list || [];
|
||||
this.pagination.total = response.pagination?.total || 0;
|
||||
} catch (err) {
|
||||
this.error = err.message || '未知错误';
|
||||
console.error('加载告警列表失败:', err);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 处理标签页切换事件
|
||||
* @param {object} tab - 当前激活的tab对象
|
||||
*/
|
||||
handleTabClick(tab) {
|
||||
this.pagination.currentPage = 1;
|
||||
this.loadAlarms(tab.paneName); // 显式传递paneName
|
||||
},
|
||||
handleSizeChange(newSize) {
|
||||
this.pagination.pageSize = newSize;
|
||||
this.loadAlarms();
|
||||
},
|
||||
handlePageChange(newPage) {
|
||||
this.pagination.currentPage = newPage;
|
||||
this.loadAlarms();
|
||||
},
|
||||
async handleIgnore(alarm) {
|
||||
try {
|
||||
const { value } = await ElMessageBox.prompt('请输入忽略时长(分钟)', '忽略告警', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputType: 'number',
|
||||
inputPattern: /^[1-9]\d*$/,
|
||||
inputErrorMessage: '请输入正整数',
|
||||
});
|
||||
|
||||
const duration = parseInt(value, 10);
|
||||
await AlarmApi.snoozeAlarm(alarm.id, { duration_minutes: duration });
|
||||
this.$message.success('告警已忽略');
|
||||
await this.loadAlarms();
|
||||
} catch (err) {
|
||||
if (err !== 'cancel') {
|
||||
this.$message.error('操作失败: ' + (err.message || '未知错误'));
|
||||
}
|
||||
}
|
||||
},
|
||||
formatTime(timeStr) {
|
||||
if (!timeStr) return '-';
|
||||
return new Date(timeStr).toLocaleString();
|
||||
},
|
||||
formatSeverity(level) {
|
||||
return SeverityLevel[level] || level;
|
||||
},
|
||||
formatSourceType(type) {
|
||||
return AlarmSourceType[type] || type;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.alarm-list {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.title-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
color: black;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.loading {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.error {
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
333
src/views/alarm/ThresholdAlarmList.vue
Normal file
333
src/views/alarm/ThresholdAlarmList.vue
Normal file
@@ -0,0 +1,333 @@
|
||||
<template>
|
||||
<div class="threshold-alarm-list">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<div class="title-container">
|
||||
<h2 class="page-title">阈值告警配置</h2>
|
||||
<el-button type="text" @click="loadData()" class="refresh-btn" title="刷新列表">
|
||||
<el-icon :size="20"><Refresh /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<el-button type="primary" @click="handleAddRule">新增规则</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<!-- 区域告警规则 -->
|
||||
<el-tab-pane label="区域告警" name="area">
|
||||
<el-table :data="areaAlarms.list" v-loading="loading">
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="area_controller_id" label="区域主控ID" min-width="120" />
|
||||
<el-table-column prop="sensor_type" label="传感器类型" min-width="120">
|
||||
<template #default="scope">{{ formatSensorType(scope.row.sensor_type) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="level" label="告警等级" min-width="100">
|
||||
<template #default="scope">{{ formatSeverity(scope.row.level) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="触发条件" min-width="150">
|
||||
<template #default="scope">
|
||||
{{ `${scope.row.operator} ${scope.row.thresholds}` }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleEditRule(scope.row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDeleteRule(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="pagination-container"
|
||||
:current-page="areaAlarms.pagination.currentPage"
|
||||
:page-size="areaAlarms.pagination.pageSize"
|
||||
:total="areaAlarms.pagination.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 设备告警规则 -->
|
||||
<el-tab-pane label="设备告警" name="device">
|
||||
<el-table :data="deviceAlarms.list" v-loading="loading">
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="device_id" label="设备ID" min-width="120" />
|
||||
<el-table-column prop="sensor_type" label="传感器类型" min-width="120">
|
||||
<template #default="scope">{{ formatSensorType(scope.row.sensor_type) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="level" label="告警等级" min-width="100">
|
||||
<template #default="scope">{{ formatSeverity(scope.row.level) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="触发条件" min-width="150">
|
||||
<template #default="scope">
|
||||
{{ `${scope.row.operator} ${scope.row.thresholds}` }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleEditRule(scope.row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDeleteRule(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="pagination-container"
|
||||
:current-page="deviceAlarms.pagination.currentPage"
|
||||
:page-size="deviceAlarms.pagination.pageSize"
|
||||
:total="deviceAlarms.pagination.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑规则' : '新增规则'" width="500px" @close="resetForm">
|
||||
<el-form :model="form" ref="ruleForm" label-width="100px">
|
||||
<el-form-item v-if="activeTab === 'area'" label="区域主控ID" prop="area_controller_id" required>
|
||||
<el-input v-model.number="form.area_controller_id" :disabled="isEdit"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeTab === 'device'" label="设备ID" prop="device_id" required>
|
||||
<el-input v-model.number="form.device_id" :disabled="isEdit"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="传感器类型" prop="sensor_type" required>
|
||||
<el-select v-model="form.sensor_type" placeholder="请选择" :disabled="isEdit">
|
||||
<el-option v-for="(label, key) in SensorType" :key="key" :label="label" :value="key"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="告警等级" prop="level" required>
|
||||
<el-select v-model="form.level" placeholder="请选择">
|
||||
<el-option v-for="(label, key) in SeverityLevel" :key="key" :label="label" :value="key"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作符" prop="operator" required>
|
||||
<el-select v-model="form.operator" placeholder="请选择">
|
||||
<el-option v-for="(label, key) in Operator" :key="key" :label="label" :value="key"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="阈值" prop="thresholds" required>
|
||||
<el-input-number v-model="form.thresholds"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Refresh } from '@element-plus/icons-vue';
|
||||
import { AlarmApi } from '../../api/alarm.js';
|
||||
import { SensorType, SeverityLevel, Operator } from '../../enums.js';
|
||||
|
||||
export default {
|
||||
name: 'ThresholdAlarmList',
|
||||
components: {
|
||||
Refresh,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
SensorType,
|
||||
SeverityLevel,
|
||||
Operator,
|
||||
activeTab: 'area',
|
||||
loading: false,
|
||||
areaAlarms: {
|
||||
list: [],
|
||||
pagination: { currentPage: 1, pageSize: 10, total: 0 },
|
||||
},
|
||||
deviceAlarms: {
|
||||
list: [],
|
||||
pagination: { currentPage: 1, pageSize: 10, total: 0 },
|
||||
},
|
||||
dialogVisible: false,
|
||||
isEdit: false,
|
||||
form: {
|
||||
id: null,
|
||||
area_controller_id: null,
|
||||
device_id: null,
|
||||
sensor_type: '',
|
||||
level: '',
|
||||
operator: '',
|
||||
thresholds: 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadData();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 加载阈值告警数据
|
||||
* @param {string} [tabName] - 可选参数,当前激活的tab名称,用于确保调用正确的API
|
||||
*/
|
||||
async loadData(tabName) {
|
||||
this.loading = true;
|
||||
try {
|
||||
const currentTab = tabName || this.activeTab;
|
||||
if (currentTab === 'area') {
|
||||
const { currentPage, pageSize } = this.areaAlarms.pagination;
|
||||
const response = await AlarmApi.getAreaThresholdAlarms({ page: currentPage, page_size: pageSize });
|
||||
this.areaAlarms.list = response.list || [];
|
||||
this.areaAlarms.pagination.total = response.pagination?.total || 0;
|
||||
} else {
|
||||
const { currentPage, pageSize } = this.deviceAlarms.pagination;
|
||||
const response = await AlarmApi.getDeviceThresholdAlarms({ page: currentPage, page_size: pageSize });
|
||||
this.deviceAlarms.list = response.list || [];
|
||||
this.deviceAlarms.pagination.total = response.pagination?.total || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('加载告警规则失败: ' + (error.message || '未知错误'));
|
||||
console.error('加载告警规则失败:', error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 处理标签页切换事件
|
||||
* @param {object} tab - 当前激活的tab对象
|
||||
*/
|
||||
handleTabClick(tab) {
|
||||
// 重置分页到第一页
|
||||
if (tab.paneName === 'area') {
|
||||
this.areaAlarms.pagination.currentPage = 1;
|
||||
} else {
|
||||
this.deviceAlarms.pagination.currentPage = 1;
|
||||
}
|
||||
this.loadData(tab.paneName); // 显式传递paneName
|
||||
},
|
||||
handleSizeChange(newSize) {
|
||||
const pagination = this.activeTab === 'area' ? this.areaAlarms.pagination : this.deviceAlarms.pagination;
|
||||
pagination.pageSize = newSize;
|
||||
this.loadData();
|
||||
},
|
||||
handlePageChange(newPage) {
|
||||
const pagination = this.activeTab === 'area' ? this.areaAlarms.pagination : this.deviceAlarms.pagination;
|
||||
pagination.currentPage = newPage;
|
||||
this.loadData();
|
||||
},
|
||||
handleAddRule() {
|
||||
this.isEdit = false;
|
||||
this.resetForm();
|
||||
this.dialogVisible = true;
|
||||
},
|
||||
handleEditRule(rule) {
|
||||
this.isEdit = true;
|
||||
this.form = { ...rule };
|
||||
this.dialogVisible = true;
|
||||
},
|
||||
async handleDeleteRule(rule) {
|
||||
try {
|
||||
await this.$confirm('确认删除这条规则吗?', '提示', { type: 'warning' });
|
||||
if (this.activeTab === 'area') {
|
||||
await AlarmApi.deleteAreaThresholdAlarm(rule.id);
|
||||
} else {
|
||||
// 注意:设备告警删除接口需要 sensor_type
|
||||
await AlarmApi.deleteDeviceThresholdAlarm(rule.id, { sensor_type: rule.sensor_type });
|
||||
}
|
||||
this.$message.success('删除成功');
|
||||
await this.loadData();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
this.$message.error('删除失败: ' + (error.message || '未知错误'));
|
||||
}
|
||||
}
|
||||
},
|
||||
async submitForm() {
|
||||
try {
|
||||
await this.$refs.ruleForm.validate();
|
||||
const apiCall = this.isEdit ? this.updateRule : this.createRule;
|
||||
await apiCall();
|
||||
this.dialogVisible = false;
|
||||
this.$message.success(this.isEdit ? '更新成功' : '创建成功');
|
||||
} catch (error) {
|
||||
if (error) { // validation error
|
||||
console.log('表单验证失败');
|
||||
}
|
||||
} finally {
|
||||
await this.loadData();
|
||||
}
|
||||
},
|
||||
async createRule() {
|
||||
const { id, ...requestBody } = this.form;
|
||||
if (this.activeTab === 'area') {
|
||||
await AlarmApi.createAreaThresholdAlarm(requestBody);
|
||||
} else {
|
||||
await AlarmApi.createDeviceThresholdAlarm(requestBody);
|
||||
}
|
||||
},
|
||||
async updateRule() {
|
||||
const { id, ...requestBody } = this.form;
|
||||
if (this.activeTab === 'area') {
|
||||
await AlarmApi.updateAreaThresholdAlarm(id, requestBody);
|
||||
} else {
|
||||
await AlarmApi.updateDeviceThresholdAlarm(id, requestBody);
|
||||
}
|
||||
},
|
||||
resetForm() {
|
||||
this.form = {
|
||||
id: null,
|
||||
area_controller_id: null,
|
||||
device_id: null,
|
||||
sensor_type: '',
|
||||
level: '',
|
||||
operator: '',
|
||||
thresholds: 0,
|
||||
};
|
||||
if (this.$refs.ruleForm) {
|
||||
this.$refs.ruleForm.resetFields();
|
||||
}
|
||||
},
|
||||
formatSensorType(type) {
|
||||
return SensorType[type] || type;
|
||||
},
|
||||
formatSeverity(level) {
|
||||
return SeverityLevel[level] || level;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.threshold-alarm-list {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.title-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
.refresh-btn {
|
||||
color: black;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
}
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user