feat: 将入库汇总导出从本地 xlsx 重构为后端异步轮询模式(submitExportTask + checkExportStatus)

This commit is contained in:
DXC
2026-05-19 10:41:21 +08:00
parent 4d81056075
commit e977ffc42d
2 changed files with 105 additions and 35 deletions

View File

@ -17,11 +17,43 @@ export function getInboundSummaryList(params: InboundSummaryQuery) {
})
}
export function exportInboundSummary(params: any) {
// ============================================================
// 旧版:前端直接处理 Excel blob已废弃保留用于参考
// ============================================================
// export function exportInboundSummary(params: any) {
// return request({
// url: '/v1/inbound/summary/export',
// method: 'get',
// params,
// responseType: 'blob'
// })
// }
// ============================================================
// 新版:异步导出 API后端生成 + 轮询任务状态)
// ============================================================
/**
* 提交异步导出任务
* POST /api/v1/export/inventory
* 返回 { task_id: string }
*/
export function submitExportTask(filters: Record<string, any>) {
return request({
url: '/v1/inbound/summary/export',
method: 'get',
params,
responseType: 'blob'
url: '/v1/export/inventory',
method: 'post',
data: filters
})
}
/**
* 轮询导出任务状态
* GET /api/v1/export/status/<taskId>
* 返回 { status: 'processing'|'completed'|'failed', progress: number, url: string, error: string }
*/
export function checkExportStatus(taskId: string) {
return request({
url: `/v1/export/status/${taskId}`,
method: 'get'
})
}

View File

@ -104,9 +104,9 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { getInboundSummaryList, exportInboundSummary } from '@/api/inbound/inbound_summary'
import { getInboundSummaryList, submitExportTask, checkExportStatus } from '@/api/inbound/inbound_summary'
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
import { ElMessage, ElLoading } from 'element-plus'
const userStore = useUserStore()
@ -153,45 +153,83 @@ const handleFilter = () => {
fetchData()
}
// 导出 Excel
// 导出 Excel(异步轮询模式,后端生成文件后下载)
const handleExport = () => {
exportLoading.value = true
const params = {
// 如果已有任务在执行中,跳过
if (exportLoading.value) return
// 第一步:构造过滤参数,提交导出任务
const filters = {
keyword: listQuery.keyword,
source_type: listQuery.source_type,
start_date: listQuery.dateRange ? listQuery.dateRange[0] : null,
end_date: listQuery.dateRange ? listQuery.dateRange[1] : null
}
exportInboundSummary(params)
.then((response: any) => {
const blob = new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
// 开启全屏 Loading防止用户重复点击
const loadingInstance = ElLoading.service({
text: '正在后台生成报表,请稍候...',
background: 'rgba(0, 0, 0, 0.6)'
})
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
const hour = String(now.getHours()).padStart(2, '0')
const minute = String(now.getMinutes()).padStart(2, '0')
const second = String(now.getSeconds()).padStart(2, '0')
const filename = `入库记录_${year}${month}${day}_${hour}${minute}${second}.xlsx`
submitExportTask(filters)
.then((res: any) => {
const taskId: string = res.data?.task_id
if (!taskId) {
ElMessage.error('任务提交失败,未获取到 task_id')
loadingInstance.close()
return
}
link.setAttribute('download', filename)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
ElMessage.success('导出成功')
// 第二步:轮询任务状态,每 1.5 秒查一次
const POLL_INTERVAL_MS = 1500
const timer = setInterval(() => {
checkExportStatus(taskId)
.then((statusRes: any) => {
const statusData = statusRes.data || {}
const { status, progress, url, error } = statusData
// 动态更新 Loading 文字,显示当前进度
if (progress != null) {
loadingInstance.setText(`正在生成报表... ${progress}%`)
}
if (status === 'completed') {
// 第三步:完成 → 停止轮询,关闭 Loading触发下载
clearInterval(timer)
loadingInstance.close()
// 拼接完整下载地址Vite 代理已配置 /api -> 后端)
const baseUrl = import.meta.env.VITE_API_BASE_URL || window.location.origin
const downloadUrl = url.startsWith('http') ? url : `${baseUrl}${url}`
// 动态创建 <a> 标签触发浏览器下载
const link = document.createElement('a')
link.href = downloadUrl
link.setAttribute('download', '') // 让浏览器自动从 Content-Disposition 取文件名
link.style.display = 'none'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
ElMessage.success('报表已生成,正在下载')
} else if (status === 'failed') {
// 失败 → 停止轮询,关闭 Loading弹出错误原因
clearInterval(timer)
loadingInstance.close()
ElMessage.error(`生成失败:${error || '未知错误'}`)
}
// processing → 继续轮询,不做任何操作
})
.catch(() => {
// 网络错误时继续轮询,不中断流程
})
}, POLL_INTERVAL_MS)
})
.catch((err: any) => {
console.error('导出失败', err)
ElMessage.error('导出失败')
})
.finally(() => {
exportLoading.value = false
console.error('提交导出任务失败', err)
loadingInstance.close()
ElMessage.error('提交导出任务失败')
})
}