From 5a11b7431fa963e327eaf6d5ab01be61c8532944 Mon Sep 17 00:00:00 2001 From: wintsa <770775984@qq.com> Date: Wed, 18 Mar 2026 11:02:00 +0800 Subject: [PATCH] 1 --- src/layout/tab.vue | 46 +++++++++++++++++++++++++++++++++++++--------- src/sql.ts | 18 ++++++++++-------- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/layout/tab.vue b/src/layout/tab.vue index e749627..20f901f 100644 --- a/src/layout/tab.vue +++ b/src/layout/tab.vue @@ -479,6 +479,7 @@ const reportExportToastOpen = ref(false) const reportExportProgress = ref(0) const reportExportStatus = ref<'running' | 'success' | 'error'>('running') const reportExportText = ref('') +const reportExportBlobUrl = ref(null) let reportExportToastTimer: ReturnType | null = null const clearReportExportToastTimer = () => { @@ -495,15 +496,32 @@ const showReportExportProgress = (progress: number, text: string) => { reportExportToastOpen.value = true } -const finishReportExportProgress = (success: boolean, text: string) => { +const finishReportExportProgress = (success: boolean, text: string, blobUrl?: string | null) => { clearReportExportToastTimer() reportExportStatus.value = success ? 'success' : 'error' reportExportProgress.value = 100 reportExportText.value = text + reportExportBlobUrl.value = success && blobUrl ? blobUrl : null reportExportToastOpen.value = true - reportExportToastTimer = setTimeout(() => { - reportExportToastOpen.value = false - }, success ? 1200 : 1800) + if (!success || !blobUrl) { + reportExportToastTimer = setTimeout(() => { + reportExportToastOpen.value = false + }, success ? 1200 : 1800) + } +} + +const openExportedReport = () => { + if (!reportExportBlobUrl.value) return + window.open(reportExportBlobUrl.value, '_blank') + reportExportToastOpen.value = false +} + +const dismissReportToast = () => { + if (reportExportBlobUrl.value) { + URL.revokeObjectURL(reportExportBlobUrl.value) + reportExportBlobUrl.value = null + } + reportExportToastOpen.value = false } const tabsModel = computed({ @@ -1609,12 +1627,13 @@ const exportData = async () => { const exportReport = async () => { try { const now = new Date() - const payload = await buildExportReportPayload() - const fileName = `${sanitizeFileNamePart(payload.name)}-报表-${formatExportTimestamp(now)}` - await exportFile(fileName, payload, () => { + const projectInfoRaw = await kvStore.getItem(PROJECT_INFO_DB_KEY) + const projectName = isNonEmptyString(projectInfoRaw?.projectName) ? sanitizeFileNamePart(projectInfoRaw.projectName) : '造价项目' + const fileName = `${projectName}-报表-${formatExportTimestamp(now)}` + const blobUrl = await exportFile(fileName, () => buildExportReportPayload(), () => { showReportExportProgress(30, '正在生成报表文件...') }) - finishReportExportProgress(true, '报表导出完成') + finishReportExportProgress(true, '报表导出完成', blobUrl) } catch (error) { console.error('export report failed:', error) if (reportExportToastOpen.value) { @@ -2012,12 +2031,21 @@ watch( v-model:open="reportExportToastOpen" :duration="0" class="pointer-events-auto rounded-xl border border-border bg-card px-4 py-3 text-foreground shadow-lg" + @update:open="(val) => { if (!val) dismissReportToast() }" > {{ reportExportStatus === 'running' ? '导出报表' : (reportExportStatus === 'success' ? '导出成功' : '导出失败') }} {{ reportExportText }} -
+
+ + +
+
void): Promise { +export async function exportFile(fileName: string, data: any | (() => Promise), onSaveConfirmed?: () => void): Promise { if (window.showSaveFilePicker) { const handle = await window.showSaveFilePicker({ suggestedName: fileName, @@ -699,25 +699,26 @@ export async function exportFile(fileName: string, data: any, onSaveConfirmed?: }); try { onSaveConfirmed?.() - - const workbook = await generateTemplate(data); + const resolvedData = typeof data === 'function' ? await data() : data + const workbook = await generateTemplate(resolvedData); const buffer = await workbook.xlsx.writeBuffer(); const writable = await handle.createWritable(); await writable.write(buffer); await writable.close(); + // 返回 blob URL 供打开文件使用 + const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); + return URL.createObjectURL(blob); // ecCom.WeaLoadingGlobal.destroy(); // antd.notification['success']({ // message: '下载成功!', // }); } catch (err) { console.log('err:' + err); - // ecCom.WeaLoadingGlobal.destroy(); - // antd.notification['error']({ - // message: '下载失败!', - // }); + return null; } } else { - const workbook = await generateTemplate(data); + const resolvedData = typeof data === 'function' ? await data() : data + const workbook = await generateTemplate(resolvedData); const buffer = await workbook.xlsx.writeBuffer(); const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); const url1 = window.URL.createObjectURL(blob); @@ -726,6 +727,7 @@ export async function exportFile(fileName: string, data: any, onSaveConfirmed?: a.download = `${fileName}.xlsx`; a.click(); URL.revokeObjectURL(url1); + return null; } }