You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
214 lines
9.1 KiB
Vue
214 lines
9.1 KiB
Vue
<template>
|
|
<div>
|
|
<!-- 统计区 -->
|
|
<el-row :gutter="16" style="margin-bottom:20px">
|
|
<el-col :span="4"><el-card shadow="hover"><div style="text-align:center;padding:8px"><div style="color:#909399;font-size:12px">未处理</div><div style="font-size:24px;font-weight:bold;color:#e6a23c">{{ stats.unresolved }}</div></div></el-card></el-col>
|
|
<el-col :span="4"><el-card shadow="hover"><div style="text-align:center;padding:8px"><div style="color:#909399;font-size:12px">采集失败</div><div style="font-size:24px;font-weight:bold">{{ stats.collectFail }}</div></div></el-card></el-col>
|
|
<el-col :span="4"><el-card shadow="hover"><div style="text-align:center;padding:8px"><div style="color:#909399;font-size:12px">设备离线</div><div style="font-size:24px;font-weight:bold">{{ stats.deviceOffline }}</div></div></el-card></el-col>
|
|
<el-col :span="4"><el-card shadow="hover"><div style="text-align:center;padding:8px"><div style="color:#909399;font-size:12px">产量异常</div><div style="font-size:24px;font-weight:bold">{{ stats.productionAnomaly }}</div></div></el-card></el-col>
|
|
<el-col :span="4"><el-card shadow="hover"><div style="text-align:center;padding:8px"><div style="color:#909399;font-size:12px">未知设备</div><div style="font-size:24px;font-weight:bold">{{ stats.unknownDevice }}</div></div></el-card></el-col>
|
|
</el-row>
|
|
|
|
<!-- 查询条件 -->
|
|
<el-form :inline="true" class="mb-16">
|
|
<el-form-item label="告警类型">
|
|
<el-select v-model="query.alertType" clearable placeholder="全部">
|
|
<el-option label="采集失败" value="collect_fail" />
|
|
<el-option label="设备离线" value="device_offline" />
|
|
<el-option label="产量异常" value="production_anomaly" />
|
|
<el-option label="未知设备" value="unknown_device" />
|
|
<el-option label="服务错误" value="service_error" />
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="处理状态">
|
|
<el-select v-model="query.isResolved" clearable placeholder="全部">
|
|
<el-option label="未处理" :value="0" />
|
|
<el-option label="已处理" :value="1" />
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="时间范围">
|
|
<el-date-picker v-model="query.dateRange" type="daterange" value-format="YYYY-MM-DD" clearable />
|
|
</el-form-item>
|
|
<el-form-item label="机床">
|
|
<el-select v-model="query.machineId" filterable clearable placeholder="全部">
|
|
<el-option v-for="m in machineList" :key="m.id" :label="m.name" :value="m.id" />
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item>
|
|
<el-input v-model="query.keyword" placeholder="标题/详情" clearable />
|
|
</el-form-item>
|
|
<el-form-item>
|
|
<el-button type="primary" @click="loadData">查询</el-button>
|
|
<el-button @click="resetQuery">重置</el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
|
|
<!-- 操作栏 -->
|
|
<div style="margin-bottom:12px">
|
|
<el-button type="primary" @click="batchResolve" :disabled="!selectedRows.length || !selectedRows.some((r: Alert) => !r.isResolved)">批量标记已处理</el-button>
|
|
</div>
|
|
|
|
<!-- 列表 -->
|
|
<el-table :data="tableData" border stripe v-loading="loading" @selection-change="(rows: Alert[]) => selectedRows = rows">
|
|
<el-table-column type="selection" width="50" fixed="left" align="center" />
|
|
<el-table-column prop="createdAt" label="告警时间" sortable />
|
|
<el-table-column label="告警类型" align="center">
|
|
<template #default="{ row }">
|
|
<el-tag :type="alertTypeTag(row.alertType)" size="small">{{ alertTypeLabel(row.alertType) }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="title" label="标题" show-overflow-tooltip />
|
|
<el-table-column prop="machineName" label="机床" show-overflow-tooltip align="center" />
|
|
<el-table-column prop="detail" label="详情" show-overflow-tooltip />
|
|
<el-table-column label="处理状态" align="center">
|
|
<template #default="{ row }">
|
|
<el-tag :type="row.isResolved ? 'success' : 'danger'" size="small">{{ row.isResolved ? '已处理' : '未处理' }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作" width="120" fixed="right" align="center">
|
|
<template #default="{ row }">
|
|
<div style="white-space:nowrap">
|
|
<el-button v-if="!row.isResolved" link type="primary" @click="handleResolve(row)">标记已处理</el-button>
|
|
<el-button v-else link type="primary" @click="viewDetail(row)">查看</el-button>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<!-- 分页 -->
|
|
<el-pagination
|
|
v-model:current-page="page.page"
|
|
v-model:page-size="page.pageSize"
|
|
:page-sizes="[20, 50, 100]"
|
|
:total="page.total"
|
|
background
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
/>
|
|
|
|
<!-- 告警详情弹窗 -->
|
|
<el-dialog v-model="detailVisible" title="告警详情" width="600px" destroy-on-close>
|
|
<el-descriptions :column="1" border size="small">
|
|
<el-descriptions-item label="告警时间">{{ detailRow.createdAt }}</el-descriptions-item>
|
|
<el-descriptions-item label="告警类型">
|
|
<el-tag :type="alertTypeTag(detailRow.alertType)" size="small">{{ alertTypeLabel(detailRow.alertType) }}</el-tag>
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="标题">{{ detailRow.title }}</el-descriptions-item>
|
|
<el-descriptions-item label="机床">{{ detailRow.machineName || '-' }}</el-descriptions-item>
|
|
<el-descriptions-item label="详情">{{ detailRow.detail }}</el-descriptions-item>
|
|
<el-descriptions-item label="处理状态">
|
|
<el-tag :type="detailRow.isResolved ? 'success' : 'danger'" size="small">{{ detailRow.isResolved ? '已处理' : '未处理' }}</el-tag>
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="处理时间">{{ detailRow.resolvedAt || '-' }}</el-descriptions-item>
|
|
</el-descriptions>
|
|
<template #footer><el-button @click="detailVisible = false">关闭</el-button></template>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import request from '@/utils/request'
|
|
import type { ApiResponse, Alert, Machine } from '@/types'
|
|
|
|
/** 告警统计信息 */
|
|
interface AlertStats {
|
|
unresolved: number
|
|
collectFail: number
|
|
deviceOffline: number
|
|
productionAnomaly: number
|
|
unknownDevice: number
|
|
}
|
|
|
|
const loading = ref(false)
|
|
const tableData = ref<Alert[]>([])
|
|
const selectedRows = ref<Alert[]>([])
|
|
const stats = ref<AlertStats>({} as AlertStats)
|
|
const machineList = ref<Machine[]>([])
|
|
const detailVisible = ref(false)
|
|
const detailRow = ref<Alert>({} as Alert)
|
|
|
|
const query = reactive({
|
|
alertType: '',
|
|
isResolved: 0 as number | undefined,
|
|
dateRange: null as string[] | null,
|
|
machineId: undefined as number | undefined,
|
|
keyword: '',
|
|
})
|
|
|
|
const page = reactive({ page: 1, pageSize: 20, total: 0 })
|
|
|
|
// 告警类型标签映射
|
|
function alertTypeTag(type: string): string {
|
|
const map: Record<string, string> = {
|
|
collect_fail: 'warning',
|
|
device_offline: 'danger',
|
|
production_anomaly: 'warning',
|
|
unknown_device: 'info',
|
|
service_error: 'danger',
|
|
}
|
|
return map[type] || 'info'
|
|
}
|
|
|
|
function alertTypeLabel(type: string): string {
|
|
const map: Record<string, string> = {
|
|
collect_fail: '采集失败',
|
|
device_offline: '设备离线',
|
|
production_anomaly: '产量异常',
|
|
unknown_device: '未知设备',
|
|
service_error: '服务错误',
|
|
}
|
|
return map[type] || type
|
|
}
|
|
|
|
async function loadData() {
|
|
loading.value = true
|
|
try {
|
|
const [s, d] = await Promise.all([
|
|
request.get<AlertStats>('/admin/alert/statistics'),
|
|
request.get<{ items: Alert[]; total: number }>('/admin/alert', { params: { ...query, ...page } }),
|
|
])
|
|
stats.value = s.data || {} as AlertStats
|
|
tableData.value = d.data?.items || []
|
|
page.total = d.data?.total || 0
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function resetQuery() {
|
|
Object.assign(query, { alertType: '', isResolved: 0, dateRange: null, machineId: undefined, keyword: '' })
|
|
loadData()
|
|
}
|
|
|
|
async function handleResolve(row: Alert) {
|
|
await ElMessageBox.confirm('确定标记为已处理?', '提示', { type: 'warning' })
|
|
await request.post('/admin/alert/resolve', { id: row.id })
|
|
ElMessage.success('已标记为已处理')
|
|
loadData()
|
|
}
|
|
|
|
async function batchResolve() {
|
|
const unresolved = selectedRows.value.filter((r: Alert) => !r.isResolved)
|
|
await ElMessageBox.confirm(`确定对选中的${unresolved.length}项标记为已处理?`, '提示', { type: 'warning' })
|
|
await request.post('/admin/alert/batch-resolve', { ids: unresolved.map((r: Alert) => r.id) })
|
|
ElMessage.success('批量标记成功')
|
|
loadData()
|
|
}
|
|
|
|
function viewDetail(row: Alert) {
|
|
detailRow.value = row
|
|
detailVisible.value = true
|
|
}
|
|
|
|
async function loadDrops() {
|
|
const r = await request.get<{ items: Machine[] }>('/admin/machine/list')
|
|
machineList.value = r.data?.items || []
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadData()
|
|
loadDrops()
|
|
})
|
|
</script>
|