Files
front/src/views/ops/pages/report/history/index.vue
2026-08-10 20:55:56 +08:00

357 lines
8.8 KiB
Vue

<template>
<div class="container">
<search-table
:form-model="formModel"
:form-items="formItems"
:data="tableData"
:columns="tableColumns"
:loading="loading"
:pagination="pagination"
:title="pageTitle"
@update:form-model="handleFormModelUpdate"
@search="handleSearch"
@reset="handleReset"
@refresh="handleRefresh"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<template #form-items>
<a-col :span="8">
<a-form-item label="创建时间" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }">
<a-range-picker
v-model="formModel.timeRange"
show-time
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</a-form-item>
</a-col>
</template>
<template #reportType="{ record }">
{{ reportTypeLabel[record.report_type] || record.report_type || '—' }}
</template>
<template #status="{ record }">
<a-tag :color="reportStatusColor(record.status)">
{{ reportStatusLabel[record.status] || record.status || '—' }}
</a-tag>
</template>
<template #failureReason="{ record }">
{{ record.error_message || '—' }}
</template>
<template #operations="{ record }">
<a-button
v-if="canDownloadPDF(record)"
type="text"
size="small"
:loading="downloadingIds.has(record.id)"
@click="handleDownloadPDF(record)"
>
下载 PDF
</a-button>
<span v-else-if="record.status === 'failed'" class="operation-note">生成失败</span>
<span v-else-if="record.status === 'pending' || record.status === 'running'" class="operation-note">生成中</span>
<span v-else class="operation-note"> PDF</span>
</template>
</search-table>
</div>
</template>
<script lang="ts" setup>
import { computed, onBeforeUnmount, reactive, ref } from 'vue'
import { Message } from '@arco-design/web-vue'
import SearchTable from '@/components/search-table/index.vue'
import type { FormItem } from '@/components/search-form/types'
import { fetchReportList, ReportStatus, ReportType, type ReportListParams, type ReportRecord } from '@/api/ops/report'
import { downloadReportFile } from '../useReportExport'
import { normalizeReportRows, reportStatusColor, reportStatusLabel } from '../useReportListRow'
const pageTitle = '历史报告'
const reportTypeLabel: Record<string, string> = {
topn: 'TopN报表',
statistics: '统计报告',
traffic: '流量统计报告',
fault: '故障报告',
server: '服务器报告',
network_device: '网络设备报告',
operations: '综合运维报告',
evidence: '证据导出',
}
const pdfReportTypes = new Set([
ReportType.TOPN,
ReportType.STATISTICS,
ReportType.TRAFFIC,
ReportType.FAULT,
ReportType.SERVER,
ReportType.NETWORK_DEVICE,
ReportType.OPERATIONS,
])
const formModel = ref<{
report_type: ReportType | ''
status: ReportStatus | ''
keyword: string
timeRange?: string[]
}>({
report_type: '',
status: '',
keyword: '',
timeRange: [],
})
const formItems = computed<FormItem[]>(() => [
{
field: 'report_type',
label: '报表类型',
type: 'select',
span: 8,
placeholder: '请选择',
options: [
{ value: '', label: '全部' },
{ value: ReportType.TOPN, label: reportTypeLabel.topn },
{ value: ReportType.STATISTICS, label: reportTypeLabel.statistics },
{ value: ReportType.TRAFFIC, label: reportTypeLabel.traffic },
{ value: ReportType.FAULT, label: reportTypeLabel.fault },
{ value: ReportType.SERVER, label: reportTypeLabel.server },
{ value: ReportType.NETWORK_DEVICE, label: reportTypeLabel.network_device },
{ value: ReportType.OPERATIONS, label: reportTypeLabel.operations },
],
},
{
field: 'status',
label: '状态',
type: 'select',
span: 8,
placeholder: '请选择',
options: [
{ value: '', label: '全部' },
{ value: ReportStatus.PENDING, label: reportStatusLabel.pending },
{ value: ReportStatus.RUNNING, label: reportStatusLabel.running },
{ value: ReportStatus.SUCCESS, label: reportStatusLabel.success },
{ value: ReportStatus.FAILED, label: reportStatusLabel.failed },
],
},
{
field: 'keyword',
label: '标题',
type: 'input',
span: 8,
placeholder: '请输入标题关键字',
},
])
const loading = ref(false)
const downloadingIds = reactive(new Set<number>())
const tableData = ref<ReportRecord[]>([])
let latestRequestId = 0
onBeforeUnmount(() => {
latestRequestId += 1
})
const pagination = reactive({
current: 1,
pageSize: 20,
total: 0,
showTotal: true,
showJumper: true,
showPageSize: true,
})
const tableColumns = computed(() => [
{
title: '报告编号',
dataIndex: 'id',
width: 100,
},
{
title: '标题',
dataIndex: 'title',
width: 220,
},
{
title: '报表类型',
dataIndex: 'report_type',
width: 150,
slotName: 'reportType',
},
{
title: '状态',
dataIndex: 'status',
width: 100,
slotName: 'status',
},
{
title: '创建时间',
dataIndex: 'created_at',
width: 180,
},
{
title: '完成时间',
dataIndex: 'finished_at',
width: 180,
},
{
title: '失败原因',
dataIndex: 'error_message',
width: 220,
slotName: 'failureReason',
},
{
title: '操作',
dataIndex: 'operations',
width: 120,
slotName: 'operations',
fixed: 'right' as const,
},
])
const canDownloadPDF = (record: ReportRecord) =>
record.status === ReportStatus.SUCCESS && pdfReportTypes.has(record.report_type as ReportType)
const fetchList = async (): Promise<boolean> => {
const requestId = ++latestRequestId
loading.value = true
const reportType = formModel.value.report_type
const status = formModel.value.status
const keyword = formModel.value.keyword.trim()
const timeRange = formModel.value.timeRange
const page = pagination.current
const size = pagination.pageSize
const params: ReportListParams = {
page,
size,
}
if (reportType) {
params.report_type = reportType
}
if (status) {
params.status = status
}
if (keyword) {
params.keyword = keyword
}
if (timeRange?.length === 2 && timeRange[0] && timeRange[1]) {
params.created_from = timeRange[0]
params.created_to = timeRange[1]
}
try {
while (true) {
const res = await fetchReportList(params)
if (requestId !== latestRequestId) return false
if (res.code !== 0 || !res.details) {
tableData.value = []
pagination.total = 0
Message.error(res.message || '获取报表列表失败')
return false
}
const total = res.details.total || 0
const maximumPage = Math.max(1, Math.ceil(total / size))
if ((params.page || 1) > maximumPage) {
pagination.current = maximumPage
params.page = maximumPage
continue
}
tableData.value = normalizeReportRows(res.details.data || [])
pagination.total = total
return true
}
} catch (error: any) {
if (requestId !== latestRequestId) return false
tableData.value = []
pagination.total = 0
console.error('获取报表列表失败:', error)
Message.error(error.message || '获取报表列表失败')
return false
} finally {
if (requestId === latestRequestId) {
loading.value = false
}
}
}
const handleFormModelUpdate = (value: Record<string, any>) => {
formModel.value = {
...formModel.value,
...value,
}
}
const handleSearch = () => {
pagination.current = 1
fetchList()
}
const handleReset = () => {
formModel.value = {
report_type: '',
status: '',
keyword: '',
timeRange: [],
}
pagination.current = 1
fetchList()
}
const handleRefresh = async () => {
if (await fetchList()) {
Message.success('数据已刷新')
}
}
const handlePageChange = (current: number) => {
pagination.current = current
fetchList()
}
const handlePageSizeChange = (pageSize: number) => {
pagination.pageSize = pageSize
pagination.current = 1
fetchList()
}
const handleDownloadPDF = async (record: ReportRecord) => {
downloadingIds.add(record.id)
try {
await downloadReportFile(record, 'pdf')
Message.success('PDF 下载成功')
} catch (error: any) {
console.error('PDF 下载失败:', error)
Message.error(error.message || 'PDF 下载失败')
} finally {
downloadingIds.delete(record.id)
}
}
fetchList()
</script>
<script lang="ts">
export default {
name: 'ReportHistory',
}
</script>
<style scoped lang="less">
.container {
padding: 20px;
.operation-note {
color: var(--color-text-3);
}
}
</style>