1
0
forked from dyf/dyf-vue-ui
Files
dyf-vue-ui/src/views/equipmentAlarmRecord/index.vue
2025-10-09 11:40:21 +08:00

480 lines
16 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="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter"
:leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover" class="btn_wev">
<el-button :type="isListView ? 'primary' : ''" @click="switchView('card')" class="btn_list">
{{ isListView ? '卡片显示' : '卡片显示' }}
</el-button>
<el-button :type="!isListView ? 'primary' : ''" @click="switchView('list')" class="btn_list">
{{ !isListView ? '列表显示' : '列表显示' }}
</el-button>
<div class="btn_search">
<div style="position: absolute; right:30px; top:30px">
<el-input v-model="queryParams.content" placeholder="报警信息" clearable
style="width: 200px; margin-right: 20px;" @keyup.enter="handleQuery" @input="handleInput" />
<el-button type="primary" plain @click="toggleFilter">高级筛选</el-button>
</div>
</div>
<el-collapse accordion v-model="activeNames">
<el-collapse-item name="1">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" style="margin-top: 20px;">
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable
@keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="设备类型" prop="deviceType">
<el-select v-model="queryParams.deviceType" placeholder="设备类型">
<el-option v-for="item in deviceTypeOptions" :key="item.value" :label="item.typeName"
:value="item.deviceTypeId" />
</el-select>
</el-form-item>
<el-form-item label="报警事项" prop="deviceAction">
<el-select v-model="queryParams.deviceAction">
<el-option value="0" label="强制报警"></el-option>
<el-option value="1" label="撞击闯入"></el-option>
<el-option value="2" label="自动报警"></el-option>
<el-option value="3" label="电子围栏告警"></el-option>
</el-select>
</el-form-item>
<el-form-item label="报警时间" style="width: 308px">
<el-date-picker v-model="dateRange" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"></el-date-picker>
</el-form-item>
<el-form-item label="处理状态" prop="treatmentState">
<el-select v-model="queryParams.treatmentState">
<el-option value="0" label="已处理"></el-option>
<el-option value="1" label="待处理"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-collapse-item>
</el-collapse>
</el-card>
</div>
</transition>
<el-card shadow="never">
<div v-if="isListView" key="card">
<el-row :gutter="20" v-if="alarmList.length > 0">
<el-col :span="6" v-for="(item, index) in alarmList" :key="index">
<el-card class="custom-alarm-card">
<div class="card-header">
<h4 class="device-name">{{ item.startTime }}</h4>
</div>
<!-- 卡片内容报警事项地点时间等信息 -->
<div class="card-body">
<div class="d_fl">
<div class="d_flex">
<div>
<img :src="item.devicePic" alt="" class="devicePicImg">
</div>
<div style="margin-left:10px;">
<div class="deviceName">{{ item.deviceName }}</div>
<div class="deviceTypeName">{{ item.deviceTypeName }}</div>
</div>
</div>
<div>
<span class="treatment-state" :class="item.treatmentState === 0 ? 'handled' : 'pending'">
{{ item.treatmentState === 0 ? '已处理' : '待处理' }}
</span>
</div>
</div>
<div class="label">报警事项</div>
<div class="alearm">
<template v-if="item.deviceAction === 0">强制报警
<span v-if="item.treatmentState === 1">({{ item.timeDiff }})</span>
</template>
<template v-else-if="item.deviceAction === 1">撞击闯入
<span v-if="item.treatmentState === 1">({{ item.timeDiff }})</span>
</template>
<template v-else-if="item.deviceAction === 2">自动报警
<span v-if="item.treatmentState === 1">({{ item.timeDiff }})</span>
</template>
<template v-else-if="item.deviceAction === 3">电子围栏告警
<span v-if="item.treatmentState === 1">({{ item.timeDiff }})</span>
</template>
</div>
<div class="label">报警地点</div>
<div class="alearmADD">
{{ item.location && item.location !== '[]' ? item.location : '无' }}
</div>
<div v-if="item.treatmentState === 0" class="dl_bot d_fl">
<div v-if="item.durationTime">时长:{{ item.durationTime }}</div>
<div v-if="item.finishTime">解除: {{ item.finishTime.split(' ')[1] || '' }}</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" v-else>
<!-- 使用flex容器实现垂直居中布局 -->
<div
style="display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; width: 100%;">
<img src="@/assets/images/noData.png" alt="暂无数据" style="max-width: 150px; margin-bottom: 16px;">
<div style="color: #999; font-size: 14px;">暂无数据</div>
</div>
</el-row>
</div>
<div v-else key="list">
<el-table v-loading="loading" border :data="alarmList">
<el-table-column label="设备名称" align="center" prop="deviceName" />
<el-table-column label="设备类型" align="center" prop="deviceTypeName" />
<el-table-column label="IMEI/MAC" align="center">
<template #default="scope">
<div>{{ scope.row.deviceImei }} {{ scope.row.deviceMac }}</div>
</template>
</el-table-column>
<el-table-column label="报警地点" align="center" prop="location" show-overflow-tooltip>
<template #default="scope">
<div>{{ scope.row.location && scope.row.location !== '[]' ? scope.row.location : '无' }}</div>
</template>
</el-table-column>
<el-table-column label="报警事项" align="center" prop="deviceAction">
<template #default="scope">
<el-tag type="danger" v-if="scope.row.deviceAction == 0">强制报警</el-tag>
<el-tag type="danger" v-if="scope.row.deviceAction == 1">撞击闯入</el-tag>
<el-tag type="danger" v-if="scope.row.deviceAction == 2">自动报警</el-tag>
<el-tag type="danger" v-if="scope.row.deviceAction == 3">电子围栏告警</el-tag>
</template>
</el-table-column>
<el-table-column label="报警持续时间" align="center" prop="durationTime" width="180">
<template #default="scope">
<el-tag type="danger">{{ scope.row.durationTime || '/' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="处理状态" align="center" prop="treatmentState">
<template #default="scope">
<div class="ysStatus" v-if="scope.row.treatmentState == 0">已处理</div>
<el-tag type="danger" v-if="scope.row.treatmentState == 1">未处理</el-tag>
</template>
</el-table-column>
<el-table-column label="报警时间" align="center" prop="startTime" />
</el-table>
</div>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
<script setup name="Alarm" lang="ts">
import { listAlarm } from '@/api/equipmentAlarmRecord/index';
import { AlarmVO, AlarmQuery, AlarmForm } from '@/api/equipmentAlarmRecord/types';
import apiTypeAll from '@/api/equipmentManagement/device/index';
import { TimeConverter } from '@/utils/timeConverter';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const alarmList = ref<AlarmVO[]>([]);
const timers = ref<Record<string, number>>({});
const isListView = ref(true);
const dateRange = ref(['', '']);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const deviceTypeOptions = ref([]); //设备类型
const queryFormRef = ref<ElFormInstance>();
const activeNames = ref([]);
const debounceTimer = ref(null) // 用于防抖的定时器
const initFormData: AlarmForm = {
id: undefined,
deviceId: undefined,
deviceAction: undefined,
deviceName: undefined,
dataSource: undefined,
content: undefined,
deviceType: undefined,
longitude: undefined,
latitude: undefined,
location: undefined,
startTime: undefined,
}
const data = reactive<PageData<AlarmForm, AlarmQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 8,
deviceId: undefined,
deviceAction: undefined,
deviceName: undefined,
dataSource: undefined,
content: undefined,
deviceType: undefined,
longitude: undefined,
latitude: undefined,
durationTime: undefined,
treatmentState: undefined,
params: {
}
},
rules: {
id: [
{ required: true, message: "ID不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
const switchView = (view: 'list' | 'card') => {
isListView.value = (view === 'card');
};
/** 查询设备告警列表 */
const getList = async () => {
loading.value = true;
const [startTime, endTime] = dateRange.value;
queryParams.value = {
...queryParams.value,
queryTime1: startTime,
queryTime2: endTime
};
const res = await listAlarm(queryParams.value);
if (res.rows) {
// 先清除已有定时器
clearAllTimers();
//alarmList.value = res.rows;
// 为每个项添加timeDiff属性并初始化
alarmList.value = res.rows.map(item => ({
...item,
timeDiff: '' // 用于存储计算出的时间差
}));
total.value = res.total;
// 为每个需要计时的项创建定时器
initTimers();
}
loading.value = false;
}
// 初始化所有定时器
const initTimers = () => {
alarmList.value.forEach(item => {
// 只对需要计时的项创建定时器(根据你的业务逻辑判断)
if (item.treatmentState === 1 && item.startTime) {
// 使用项的唯一标识作为定时器键名假设id是唯一标识
timers.value[item.id] = TimeConverter.createTimeDiffTimer(
item.startTime,
(diff) => {
// 找到对应的项并更新时间差
const index = alarmList.value.findIndex(i => i.id === item.id);
if (index !== -1) {
alarmList.value[index].timeDiff = diff;
}
}
);
}
});
};
// 清除所有定时器
const clearAllTimers = () => {
Object.values(timers.value).forEach(timerId => {
TimeConverter.clearTimeDiffTimer(timerId);
});
timers.value = {};
};
// 设备类型
const getDeviceType = () => {
apiTypeAll.deviceTypeAll().then(res => {
if (res.code == 200) {
deviceTypeOptions.value = res.data
}
}).catch(err => {
})
};
const toggleFilter = () => {
if (activeNames.value.length > 0) {
activeNames.value = [];
} else {
activeNames.value = ['1'];
}
};
const handleInput = () => {
if (debounceTimer.value) {
clearTimeout(debounceTimer.value)
}
// 300ms后执行查询避免输入过程中频繁调用接口
debounceTimer.value = setTimeout(() => {
handleQuery() // 调用查询接口的方法
}, 300)
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
const resetQuery = () => {
dateRange.value = ['', ''];
queryFormRef.value?.resetFields();
handleQuery();
}
onMounted(() => {
getList();
getDeviceType()
});
// 组件卸载时清除所有定时器
onUnmounted(() => {
clearAllTimers();
});
</script>
<style lang="scss" scoped>
:deep .el-collapse {
border-top: none
}
:deep .el-collapse-item__header {
display: none;
}
:deep .el-collapse-item__wrap {
border-top: none;
}
.btn_wev {
padding: 5px 0;
}
.btn_list {
margin-top: -5px;
margin-bottom: 10px;
}
.custom-alarm-card {
min-height: 270px;
display: flex;
flex-direction: column;
box-shadow: 0px 0px 6px 0px rgba(0, 27, 74, 0.1);
background: linear-gradient(127.63deg, rgba(255, 240, 240, 1), rgba(255, 255, 255, 1) 100%);
border: none;
margin-bottom: 16px;
border-radius: 5px;
}
/* 卡片头部:设备名称 + 处理状态 布局 */
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
border-bottom: 1px solid rgba(235, 238, 248, 1);
height: 30px;
margin-top: -11px;
}
.d_fl {
display: flex;
justify-content: space-between;
}
.d_flex {
display: flex;
}
.devicePicImg {
width: 48px;
height: 37px;
}
.deviceTypeName {
font-size: 13px;
font-weight: 400;
}
.deviceName {
color: rgba(56, 64, 79, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 700;
}
/* 处理状态标签:不同状态不同颜色 */
.treatment-state {
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
}
.handled {
/* 已处理-绿色 */
color: rgba(0, 165, 82, 1);
}
.pending {
/* 待处理-红色 */
color: rgba(224, 52, 52, 1);
}
/* 标签与值的间距 */
.label {
font-weight: 500;
margin-right: 4px;
margin: 4px 0;
font-size: 14px;
}
.value {
flex: 1;
}
.alearm {
border-radius: 4px;
background: rgba(224, 52, 52, 0.04);
padding: 8px 18px;
margin: 10px 0 10px 0;
position: relative;
color: rgb(224, 52, 52);
font-size: 13px;
}
.alearm::before {
position: absolute;
content: "";
width: 7px;
height: 7px;
background: rgb(224, 52, 52);
border-radius: 50%;
left: 6px;
top: 15px;
}
.alearmADD {
border-radius: 4px;
background: rgba(224, 52, 52, 0.04);
padding: 8px 18px;
margin: 10px 0 10px 0;
position: relative;
color: rgba(56, 64, 79, 1);
font-size: 13px;
}
.alearmADD::before {
position: absolute;
content: "";
width: 7px;
height: 7px;
background: rgba(56, 64, 79, 1);
border-radius: 50%;
left: 6px;
top: 15px;
}
.dl_bot {
border-top: 1px solid rgba(235, 238, 248, 1);
padding-top: 10px;
color: rgba(56, 64, 79, 0.6);
font-size: 12px;
}
.ysStatus {
color: #00A552;
}
</style>