feat:告警界面
This commit is contained in:
267
src/views/device/alerts/index.vue
Normal file
267
src/views/device/alerts/index.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, computed } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { DeleteOutlined, ToolOutlined } from '@ant-design/icons-vue'
|
||||
// 假设API与portal页面一致
|
||||
import { fetchSiteList, fetchAlertList, deleteAlerts, markAlertsResolved } from '@/service/api/auth'
|
||||
|
||||
const siteList = ref<{ siteId: string, name: string }[]>([])
|
||||
const selectedSiteId = ref('')
|
||||
const siteLoading = ref(false)
|
||||
|
||||
const getSiteList = async () => {
|
||||
siteLoading.value = true
|
||||
const { data, error } = await fetchSiteList({ pageNum: 1, pageSize: 100 })
|
||||
if (!error) {
|
||||
siteList.value = data.rows || []
|
||||
selectedSiteId.value = siteList.value[0]?.siteId || ''
|
||||
}
|
||||
siteLoading.value = false
|
||||
}
|
||||
|
||||
// 日期选择器相关
|
||||
const now = dayjs()
|
||||
const startOfToday = now.startOf('day')
|
||||
const dateRange = ref<[any, any]>([startOfToday, now])
|
||||
|
||||
// 告警相关
|
||||
const activeMainTab = ref('Alerts')
|
||||
// const activeSubTab = ref('Resolved') // 已不再需要
|
||||
const statusTab = ref('Unresolved') // Unresolved/Resolved
|
||||
const typeTab = ref('All') // All/System/Device
|
||||
const alerts = ref<any[]>([])
|
||||
const selectedRowKeys = ref<string[]>([])
|
||||
const pageNum = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
|
||||
const getAlerts = async () => {
|
||||
if (!selectedSiteId.value) return
|
||||
const filters: any = {
|
||||
timeStart: dateRange.value[0] ? dateRange.value[0].valueOf() : undefined,
|
||||
timeEnd: dateRange.value[1] ? dateRange.value[1].valueOf() : undefined,
|
||||
module: typeTab.value === 'All' ? undefined : typeTab.value,
|
||||
resolved: statusTab.value === 'Resolved' ? true : false
|
||||
}
|
||||
const { data, error } = await fetchAlertList(selectedSiteId.value, {
|
||||
pageNum: pageNum.value,
|
||||
pageSize: pageSize.value,
|
||||
filters
|
||||
})
|
||||
if (!error && data) {
|
||||
alerts.value = (data.rows || []).map((item: any) => ({ ...item, id: String(item.id) }))
|
||||
total.value = data.total || 0
|
||||
}
|
||||
}
|
||||
|
||||
const handleSiteChange = async (value: string) => {
|
||||
selectedSiteId.value = value
|
||||
pageNum.value = 1
|
||||
await getAlerts()
|
||||
}
|
||||
|
||||
const onResolve = async (id: string | number) => {
|
||||
if (!selectedSiteId.value) {
|
||||
message.warning('请先选择站点')
|
||||
return
|
||||
}
|
||||
const { error } = await markAlertsResolved(selectedSiteId.value, {
|
||||
selectType: 'all',
|
||||
startTime: dateRange.value[0].valueOf(),
|
||||
endTime: dateRange.value[1].valueOf(),
|
||||
logs: [String(id)]
|
||||
})
|
||||
if (!error) {
|
||||
await getAlerts()
|
||||
message.success('已标记为已解决')
|
||||
} else {
|
||||
message.error('标记失败')
|
||||
}
|
||||
}
|
||||
|
||||
const onDelete = async (ids: number | number[]) => {
|
||||
if (!selectedSiteId.value) {
|
||||
message.warning('请先选择站点')
|
||||
return
|
||||
}
|
||||
|
||||
const deleteIds = Array.isArray(ids) ? ids : [ids]
|
||||
const deleteCount = deleteIds.length
|
||||
|
||||
const { error } = await deleteAlerts(selectedSiteId.value, {
|
||||
selectType: 'all', // 固定传 all
|
||||
startTime: dateRange.value[0].valueOf(),
|
||||
endTime: dateRange.value[1].valueOf(),
|
||||
logs: deleteIds
|
||||
})
|
||||
if (!error) {
|
||||
await getAlerts()
|
||||
message.success(`成功删除${deleteCount}条记录`)
|
||||
} else {
|
||||
message.error('删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
const onBatchDelete = () => {
|
||||
if (selectedRowKeys.value.length === 0) {
|
||||
message.warning('请先选择要删除的记录')
|
||||
return
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: `确定要删除选中的 ${selectedRowKeys.value.length} 条记录吗?`,
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => onDelete(selectedRowKeys.value)
|
||||
})
|
||||
}
|
||||
|
||||
watch([dateRange, statusTab, typeTab, selectedSiteId], () => {
|
||||
pageNum.value = 1
|
||||
getAlerts()
|
||||
})
|
||||
|
||||
const rowSelection = {
|
||||
selectedRowKeys: selectedRowKeys,
|
||||
onChange: (keys: string[]) => {
|
||||
selectedRowKeys.value = keys
|
||||
},
|
||||
type: 'checkbox'
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getSiteList()
|
||||
await getAlerts()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- 一级Tab + 时间控件 -->
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;">
|
||||
<div style="flex: 1; min-width: 0;">
|
||||
<a-tabs v-model:activeKey="activeMainTab" style="margin-bottom: 0;">
|
||||
<a-tab-pane key="Alerts" tab="Alerts" />
|
||||
</a-tabs>
|
||||
</div>
|
||||
<a-range-picker
|
||||
v-model:value="dateRange"
|
||||
show-time
|
||||
:allowClear="false"
|
||||
style="margin-left: 16px;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 二级分组 + 站点选择 + 操作按钮 -->
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px;">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<a-radio-group v-model:value="statusTab" style="margin-right: 16px;">
|
||||
<a-radio-button value="Unresolved">Unresolved</a-radio-button>
|
||||
<a-radio-button value="Resolved">Resolved</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-radio-group v-model:value="typeTab" style="margin-right: 16px;">
|
||||
<a-radio-button value="All">All</a-radio-button>
|
||||
<a-radio-button value="System">System</a-radio-button>
|
||||
<a-radio-button value="Device">Device</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-select
|
||||
v-model:value="selectedSiteId"
|
||||
:loading="siteLoading"
|
||||
placeholder="请选择站点"
|
||||
style="width: 200px; margin-right: 16px;"
|
||||
@change="handleSiteChange"
|
||||
>
|
||||
<a-select-option v-for="site in siteList" :key="site.siteId" :value="site.siteId">
|
||||
{{ site.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<div>
|
||||
<a-button @click="onBatchDelete">Batch Delete</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<a-table
|
||||
:dataSource="alerts"
|
||||
:rowSelection="rowSelection"
|
||||
:pagination="false"
|
||||
:rowKey="record => record.id"
|
||||
bordered
|
||||
size="middle"
|
||||
>
|
||||
<a-table-column title="TYPE" dataIndex="key" key="key" />
|
||||
<a-table-column title="LEVEL" dataIndex="level" key="level">
|
||||
<template #default="{ record }">
|
||||
<span>
|
||||
<span :style="{
|
||||
color: record.level === 'Critical' ? 'red' :
|
||||
record.level === 'Error' ? 'orange' :
|
||||
record.level === 'Warning' ? '#faad14' :
|
||||
record.level === 'Info' ? '#52c41a' : '#d9d9d9',
|
||||
fontSize: '16px'
|
||||
}">●</span>
|
||||
<span style="margin-left: 4px;">{{ record.level }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="CONTENT" dataIndex="content" key="content" />
|
||||
<a-table-column title="TIME" dataIndex="time" key="time">
|
||||
<template #default="{ record }">
|
||||
<span>{{ record.time ? dayjs(record.time).format('YYYY-MM-DD HH:mm:ss') : '' }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="ACTION" key="action" width="120">
|
||||
<template #default="{ record }">
|
||||
<div style="display: flex; gap: 8px; justify-content: center;">
|
||||
<!-- 未解决状态显示解决按钮 -->
|
||||
<a-tooltip v-if="statusTab === 'Unresolved'" title="标记为已解决">
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
style="color: #52c41a"
|
||||
@click="onResolve(record.id)"
|
||||
>
|
||||
<template #icon>
|
||||
<ToolOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<!-- 所有状态都显示删除按钮 -->
|
||||
<a-tooltip title="删除">
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
style="color: #ff4d4f"
|
||||
@click="() => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除该条告警记录吗?',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => onDelete(record.id)
|
||||
})
|
||||
}"
|
||||
>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</a-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div style="display: flex; justify-content: flex-end; margin-top: 16px;">
|
||||
<a-pagination :total="total" :pageSize="pageSize" :current="pageNum" showQuickJumper @change="(p, ps) => { pageNum = p; pageSize = ps; getAlerts(); }" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 可根据实际需求自定义样式 */
|
||||
</style>
|
||||
Reference in New Issue
Block a user