Files
pig-farm-controller-fe/src/views/alarm/ThresholdAlarmList.vue
2025-11-16 19:31:07 +08:00

417 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>