| | |
| | | import type { QrExportTask } from '#/api/mes/pro/qrExport'; |
| | | |
| | | import { onBeforeUnmount, onMounted, ref } from 'vue'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | import { Button, message, Modal, Progress } from 'ant-design-vue'; |
| | | |
| | | import { |
| | | cancelQrExportTask, |
| | | downloadQrExportTask, |
| | | getQrExportTask, |
| | | } from '#/api/mes/pro/qrExport'; |
| | | import { downloadQrExportZip, pickQrExportSaveFile } from '../qrexport/download-qr-export'; |
| | | |
| | | const storageKey = 'mes-pro-last-qr-export-task-id'; |
| | | const visible = ref(false); |
| | | const task = ref<QrExportTask>(); |
| | | const downloading = ref(false); |
| | | const downloadLoaded = ref(0); |
| | | const downloadTotal = ref(0); |
| | | let downloadController: AbortController | undefined; |
| | | let timer: ReturnType<typeof setTimeout> | undefined; |
| | | |
| | | function stopPolling() { |
| | |
| | | |
| | | async function download() { |
| | | if (!task.value?.downloadable || downloading.value) return; |
| | | const currentTask = task.value; |
| | | // 文件选择器必须由用户点击直接触发,不能等网络请求之后再调用。 |
| | | const saveHandle = await pickQrExportSaveFile(`${currentTask.taskName}.zip`); |
| | | downloading.value = true; |
| | | downloadLoaded.value = 0; |
| | | downloadTotal.value = currentTask.fileSize ?? 0; |
| | | const controller = new AbortController(); |
| | | downloadController = controller; |
| | | try { |
| | | const data = await downloadQrExportTask(task.value.id); |
| | | downloadFileFromBlobPart({ fileName: `${task.value.taskName}.zip`, source: data }); |
| | | localStorage.removeItem(storageKey); |
| | | await refresh(); |
| | | await downloadQrExportZip(currentTask, { |
| | | saveHandle, |
| | | signal: controller.signal, |
| | | onProgress(loaded, total) { |
| | | downloadLoaded.value = loaded; |
| | | downloadTotal.value = total; |
| | | }, |
| | | }); |
| | | message.success('ZIP 下载完成'); |
| | | } catch (error: any) { |
| | | if (!controller.signal.aborted) { |
| | | message.error(error?.message ?? '下载失败,请重试'); |
| | | } |
| | | } finally { |
| | | downloading.value = false; |
| | | if (downloadController === controller) downloadController = undefined; |
| | | } |
| | | } |
| | | |
| | | function formatSize(bytes: number) { |
| | | return `${(bytes / 1024 / 1024).toFixed(1)} MB`; |
| | | } |
| | | |
| | | onMounted(async () => { |
| | |
| | | localStorage.removeItem(storageKey); |
| | | } |
| | | }); |
| | | onBeforeUnmount(stopPolling); |
| | | onBeforeUnmount(() => { |
| | | stopPolling(); |
| | | downloadController?.abort(); |
| | | }); |
| | | |
| | | defineExpose({ show, showLast }); |
| | | </script> |
| | |
| | | <p>任务 #{{ task.id }} · {{ task.taskName }}</p> |
| | | <p>已生成 {{ task.finishedCount }} / {{ task.totalCount }} 张,{{ task.statusName }}</p> |
| | | <Progress :percent="task.progress" :status="task.status === 50 ? 'exception' : task.downloadable ? 'success' : 'active'" /> |
| | | <p v-if="downloading" class="mt-2 text-sm"> |
| | | 正在下载 {{ formatSize(downloadLoaded) }} / {{ formatSize(downloadTotal) }} |
| | | </p> |
| | | <p v-if="task.errorMsg" class="text-red-600">{{ task.errorMsg }}</p> |
| | | <div class="mt-4 flex justify-end gap-2"> |
| | | <Button @click="refresh">刷新</Button> |