This commit is contained in:
wintsa 2026-03-18 11:02:00 +08:00
parent 55e133852e
commit 5a11b7431f
2 changed files with 47 additions and 17 deletions

View File

@ -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<string | null>(null)
let reportExportToastTimer: ReturnType<typeof setTimeout> | null = null
const clearReportExportToastTimer = () => {
@ -495,16 +496,33 @@ 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
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({
get: () => tabStore.tabs,
@ -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<XmInfoLike>(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() }"
>
<ToastTitle class="text-sm font-semibold text-foreground">
{{ reportExportStatus === 'running' ? '导出报表' : (reportExportStatus === 'success' ? '导出成功' : '导出失败') }}
</ToastTitle>
<ToastDescription class="mt-1 text-xs text-muted-foreground">{{ reportExportText }}</ToastDescription>
<div class="mt-2 flex items-center gap-2">
<div v-if="reportExportStatus === 'success' && reportExportBlobUrl" class="mt-2 flex items-center gap-2">
<Button size="sm" class="h-7 rounded-md px-3 text-xs" @click="openExportedReport">
打开文件
</Button>
<Button variant="ghost" size="sm" class="h-7 rounded-md px-2 text-xs text-muted-foreground" @click="dismissReportToast">
关闭
</Button>
</div>
<div v-else class="mt-2 flex items-center gap-2">
<div class="h-1.5 flex-1 overflow-hidden rounded-full bg-muted">
<div
class="h-full transition-all duration-300"

View File

@ -688,7 +688,7 @@ export function getBasicFeeFromScale(
* Excel 使 File System Access API
* @returns Promise
*/
export async function exportFile(fileName: string, data: any, onSaveConfirmed?: () => void): Promise<void> {
export async function exportFile(fileName: string, data: any | (() => Promise<any>), onSaveConfirmed?: () => void): Promise<string | null> {
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;
}
}