feat: 建立历史报告档案中心
This commit is contained in:
@@ -43,6 +43,8 @@ export interface ReportRecord {
|
|||||||
file_path?: string
|
file_path?: string
|
||||||
file_size?: number
|
file_size?: number
|
||||||
file_mime?: string
|
file_mime?: string
|
||||||
|
started_at?: string
|
||||||
|
finished_at?: string
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at: string
|
updated_at: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,324 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="wrap">
|
<div class="container">
|
||||||
<a-result
|
<search-table
|
||||||
status="info"
|
:form-model="formModel"
|
||||||
title="历史报表入口已下线"
|
:form-items="formItems"
|
||||||
sub-title="多指标、多目标时序请使用「统计报告」:output_mode=timeseries,并按接口文档配置 interval 与 bucket_aggregation。旧类型记录仍可在各报表列表中按 report_type 筛选查看(若库中有数据)。"
|
: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 #extra>
|
<template #form-items>
|
||||||
<a-space>
|
<a-col :span="8">
|
||||||
<a-button type="primary" @click="goStatistics">前往统计报告</a-button>
|
<a-form-item label="创建时间" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }">
|
||||||
<a-button @click="goTopn">前往 TopN</a-button>
|
<a-range-picker
|
||||||
</a-space>
|
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>
|
||||||
</a-result>
|
|
||||||
|
<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="downloadingId === 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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useRouter } from 'vue-router'
|
import { computed, 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 router = useRouter()
|
const pageTitle = '历史报告'
|
||||||
|
|
||||||
const goStatistics = () => {
|
const reportTypeLabel: Record<string, string> = {
|
||||||
router.push('/report/statistics')
|
topn: 'TopN报表',
|
||||||
|
statistics: '统计报告',
|
||||||
|
traffic: '流量统计报告',
|
||||||
|
fault: '故障报告',
|
||||||
|
server: '服务器报告',
|
||||||
|
network_device: '网络设备报告',
|
||||||
|
evidence: '证据导出',
|
||||||
}
|
}
|
||||||
|
|
||||||
const goTopn = () => {
|
const pdfReportTypes = new Set([
|
||||||
router.push('/report/topn')
|
ReportType.TOPN,
|
||||||
|
ReportType.STATISTICS,
|
||||||
|
ReportType.TRAFFIC,
|
||||||
|
ReportType.FAULT,
|
||||||
|
ReportType.SERVER,
|
||||||
|
ReportType.NETWORK_DEVICE,
|
||||||
|
])
|
||||||
|
|
||||||
|
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 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 downloadingId = ref<number | null>(null)
|
||||||
|
const tableData = ref<ReportRecord[]>([])
|
||||||
|
|
||||||
|
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 () => {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params: ReportListParams = {
|
||||||
|
page: pagination.current,
|
||||||
|
size: pagination.pageSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formModel.value.report_type) {
|
||||||
|
params.report_type = formModel.value.report_type
|
||||||
|
}
|
||||||
|
if (formModel.value.status) {
|
||||||
|
params.status = formModel.value.status
|
||||||
|
}
|
||||||
|
const keyword = formModel.value.keyword.trim()
|
||||||
|
if (keyword) {
|
||||||
|
params.keyword = keyword
|
||||||
|
}
|
||||||
|
if (formModel.value.timeRange.length === 2) {
|
||||||
|
params.created_from = formModel.value.timeRange[0]
|
||||||
|
params.created_to = formModel.value.timeRange[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetchReportList(params)
|
||||||
|
if (res.code === 0 && res.details) {
|
||||||
|
tableData.value = normalizeReportRows(res.details.data || [])
|
||||||
|
pagination.total = res.details.total || 0
|
||||||
|
} else {
|
||||||
|
Message.error(res.message || '获取报表列表失败')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('获取报表列表失败:', error)
|
||||||
|
Message.error(error.message || '获取报表列表失败')
|
||||||
|
} finally {
|
||||||
|
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 = () => {
|
||||||
|
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) => {
|
||||||
|
downloadingId.value = record.id
|
||||||
|
try {
|
||||||
|
await downloadReportFile(record, 'pdf')
|
||||||
|
Message.success('PDF 下载成功')
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('PDF 下载失败:', error)
|
||||||
|
Message.error(error.message || 'PDF 下载失败')
|
||||||
|
} finally {
|
||||||
|
downloadingId.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchList()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'ReportHistory',
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.wrap {
|
.container {
|
||||||
padding: 48px 24px;
|
padding: 20px;
|
||||||
max-width: 720px;
|
|
||||||
margin: 0 auto;
|
.operation-note {
|
||||||
|
color: var(--color-text-3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user