feat: 将入库汇总导出从本地 xlsx 重构为后端异步轮询模式(submitExportTask + checkExportStatus)
This commit is contained in:
@ -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'
|
||||
})
|
||||
}
|
||||
@ -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('提交导出任务失败')
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user