<script lang="ts" setup>
|
import type { QrExportTask } from '#/api/mes/pro/qrExport';
|
|
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
import { Button, message, Modal, Progress } from 'ant-design-vue';
|
|
import {
|
cancelQrExportTask,
|
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() {
|
if (timer) clearTimeout(timer);
|
timer = undefined;
|
}
|
|
async function refresh() {
|
if (!task.value) return;
|
stopPolling();
|
try {
|
task.value = await getQrExportTask(task.value.id);
|
if (task.value.status === 10 || task.value.status === 20) {
|
timer = setTimeout(refresh, 2000);
|
}
|
} catch {
|
message.error('获取导出进度失败,请点击刷新重试');
|
}
|
}
|
|
function show(value: QrExportTask) {
|
task.value = value;
|
localStorage.setItem(storageKey, String(value.id));
|
visible.value = true;
|
void refresh();
|
}
|
|
function showLast() {
|
if (task.value) visible.value = true;
|
else message.info('暂无后台导出任务');
|
}
|
|
async function cancel() {
|
if (!task.value) return;
|
await cancelQrExportTask(task.value.id);
|
message.success('已申请取消导出任务');
|
await refresh();
|
}
|
|
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 {
|
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 () => {
|
const id = Number(localStorage.getItem(storageKey));
|
if (!Number.isSafeInteger(id) || id <= 0) return;
|
try {
|
const previous = await getQrExportTask(id);
|
task.value = previous;
|
if (previous.status === 10 || previous.status === 20 || previous.downloadable) {
|
visible.value = true;
|
}
|
if (previous.status === 10 || previous.status === 20) timer = setTimeout(refresh, 2000);
|
} catch {
|
localStorage.removeItem(storageKey);
|
}
|
});
|
onBeforeUnmount(() => {
|
stopPolling();
|
downloadController?.abort();
|
});
|
|
defineExpose({ show, showLast });
|
</script>
|
|
<template>
|
<Modal v-model:open="visible" title="二维码后台导出" :footer="null" :mask-closable="false">
|
<template v-if="task">
|
<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>
|
<Button v-if="task.status === 10 || task.status === 20" danger @click="cancel">取消任务</Button>
|
<Button v-if="task.downloadable" type="primary" :loading="downloading" @click="download">下载 ZIP</Button>
|
</div>
|
</template>
|
</Modal>
|
</template>
|