417 lines
15 KiB
Vue
417 lines
15 KiB
Vue
<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 label="区域主控" min-width="120">
|
||
<template #default="scope">{{ getAreaControllerName(scope.row.area_controller_id) }}</template>
|
||
</el-table-column>
|
||
<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 label="设备" min-width="120">
|
||
<template #default="scope">{{ getDeviceNameWithArea(scope.row.device_id) }}</template>
|
||
</el-table-column>
|
||
<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="区域主控" prop="area_controller_id" required>
|
||
<el-select v-model="form.area_controller_id" placeholder="请选择区域主控" :disabled="isEdit">
|
||
<el-option
|
||
v-for="item in areaControllers"
|
||
:key="item.id"
|
||
:label="item.name"
|
||
:value="item.id"
|
||
></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item v-if="activeTab === 'device'" label="设备" prop="device_id" required>
|
||
<el-select v-model="form.device_id" placeholder="请选择设备" :disabled="isEdit">
|
||
<el-option
|
||
v-for="item in devices"
|
||
:key="item.id"
|
||
:label="`${item.name} (${item.area_controller_name})`"
|
||
:value="item.id"
|
||
></el-option>
|
||
</el-select>
|
||
</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="label"></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="label"></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="label"></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 { DeviceApi, AreaControllerApi } from '../../api/device.js'; // 引入新的API
|
||
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 },
|
||
},
|
||
areaControllers: [], // 新增:存储区域主控列表
|
||
devices: [], // 新增:存储设备列表
|
||
dialogVisible: false,
|
||
isEdit: false,
|
||
form: {
|
||
id: null,
|
||
area_controller_id: null,
|
||
device_id: null,
|
||
sensor_type: '',
|
||
level: '',
|
||
operator: '',
|
||
thresholds: 0,
|
||
},
|
||
};
|
||
},
|
||
async mounted() {
|
||
// 在加载告警数据之前,先加载区域主控和设备列表
|
||
await this.loadAreaControllers();
|
||
await this.loadDevices();
|
||
await this.loadData();
|
||
},
|
||
methods: {
|
||
/**
|
||
* 加载区域主控列表
|
||
*/
|
||
async loadAreaControllers() {
|
||
try {
|
||
const response = await AreaControllerApi.list();
|
||
this.areaControllers = response.data || [];
|
||
} catch (error) {
|
||
console.error('加载区域主控列表失败:', error);
|
||
this.$message.error('加载区域主控列表失败');
|
||
}
|
||
},
|
||
/**
|
||
* 加载设备列表
|
||
*/
|
||
async loadDevices() {
|
||
try {
|
||
const response = await DeviceApi.list();
|
||
this.devices = response.data || [];
|
||
} catch (error) {
|
||
console.error('加载设备列表失败:', error);
|
||
this.$message.error('加载设备列表失败');
|
||
}
|
||
},
|
||
/**
|
||
* 根据区域主控ID获取名称
|
||
* @param {number} id - 区域主控ID
|
||
* @returns {string} 区域主控名称
|
||
*/
|
||
getAreaControllerName(id) {
|
||
const controller = this.areaControllers.find(item => item.id === id);
|
||
return controller ? controller.name : `未知区域主控 (${id})`;
|
||
},
|
||
/**
|
||
* 根据设备ID获取设备名称及所属区域主控名称
|
||
* @param {number} id - 设备ID
|
||
* @returns {string} 设备名称 (区域主控名称)
|
||
*/
|
||
getDeviceNameWithArea(id) {
|
||
const device = this.devices.find(item => item.id === id);
|
||
return device ? `${device.name} (${device.area_controller_name})` : `未知设备 (${id})`;
|
||
},
|
||
/**
|
||
* 加载阈值告警数据
|
||
* @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 });
|
||
// 从 response.data 中获取 list 和 pagination
|
||
this.areaAlarms.list = response.data.list || [];
|
||
this.areaAlarms.pagination.total = response.data.pagination?.total || 0;
|
||
} else {
|
||
const { currentPage, pageSize } = this.deviceAlarms.pagination;
|
||
const response = await AlarmApi.getDeviceThresholdAlarms({ page: currentPage, page_size: pageSize });
|
||
// 从 response.data 中获取 list 和 pagination
|
||
this.deviceAlarms.list = response.data.list || [];
|
||
this.deviceAlarms.pagination.total = response.data.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') {
|
||
// 区域告警只传递区域主控相关的字段
|
||
const areaRequestBody = {
|
||
area_controller_id: requestBody.area_controller_id,
|
||
sensor_type: requestBody.sensor_type,
|
||
level: requestBody.level,
|
||
operator: requestBody.operator,
|
||
thresholds: requestBody.thresholds,
|
||
};
|
||
await AlarmApi.createAreaThresholdAlarm(areaRequestBody);
|
||
} else {
|
||
// 设备告警只传递设备相关的字段
|
||
const deviceRequestBody = {
|
||
device_id: requestBody.device_id,
|
||
sensor_type: requestBody.sensor_type,
|
||
level: requestBody.level,
|
||
operator: requestBody.operator,
|
||
thresholds: requestBody.thresholds,
|
||
};
|
||
await AlarmApi.createDeviceThresholdAlarm(deviceRequestBody);
|
||
}
|
||
},
|
||
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> |