From 07ead3ee73ff40936df8e3df44642c2543387fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AF=92=E5=AF=92?= <2596194220@qq.com> Date: Fri, 17 Jul 2026 12:51:00 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=8F=AD=E7=BA=A7=E6=8A=A5?= =?UTF-8?q?=E5=91=8A=E5=AF=BC=E5=87=BA=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/services/reportService.ts | 63 +++++++++++-------- src/main/types/report.ts | 2 +- src/preload/index.d.ts | 2 +- src/preload/index.ts | 2 +- .../feedback/GenerationProgressToasts.tsx | 15 ++--- src/renderer/src/pages/ClassPage.tsx | 8 +-- src/renderer/src/types/app.ts | 2 +- 7 files changed, 48 insertions(+), 46 deletions(-) diff --git a/src/main/services/reportService.ts b/src/main/services/reportService.ts index 2ff6d4b..383323f 100644 --- a/src/main/services/reportService.ts +++ b/src/main/services/reportService.ts @@ -129,6 +129,22 @@ function getReportDownloadFileName(report: ReportEntity | ReportItem): string { return `${sanitizeFileName(`${report.className}${report.studentName}`)}${report.fileExtension}` } +async function createUniqueExportFolder(parentPath: string, folderName: string): Promise { + for (let suffix = 1; ; suffix += 1) { + const candidateName = suffix === 1 ? folderName : `${folderName} (${suffix})` + const candidatePath = join(parentPath, candidateName) + + try { + await mkdir(candidatePath) + return candidatePath + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'EEXIST') { + throw error + } + } + } +} + function sendReportProgress(progress: ReportGenerationProgress): void { for (const window of BrowserWindow.getAllWindows()) { window.webContents.send('reports:generation-progress', progress) @@ -1133,18 +1149,24 @@ export async function downloadClassReports( } const className = reports[0]?.className || '班级报告' - const result = await dialog.showSaveDialog({ - title: '下载班级报告', - defaultPath: `${sanitizeFileName(className)}成长报告.zip`, - filters: [{ name: 'ZIP 压缩包', extensions: ['zip'] }] + const result = await dialog.showOpenDialog({ + title: '选择班级报告导出目录', + defaultPath: app.getPath('downloads'), + buttonLabel: '选择目录', + properties: ['openDirectory', 'createDirectory'] }) - if (result.canceled || !result.filePath) { + const parentPath = result.filePaths[0] + + if (result.canceled || !parentPath) { return { filePath: '', reportCount: reports.length, canceled: true } } const batchId = randomUUID() - const zip = new JSZip() + const exportPath = await createUniqueExportFolder( + parentPath, + `${sanitizeFileName(className)}成长报告` + ) const usedFileNames = new Map() let completedCount = 0 @@ -1164,7 +1186,7 @@ export async function downloadClassReports( status: 'started', current: 0, total: reports.length, - filePath: result.filePath + filePath: exportPath }) for (const report of reports) { @@ -1174,7 +1196,7 @@ export async function downloadClassReports( reportTitle: report.title, current: completedCount, total: reports.length, - filePath: result.filePath + filePath: exportPath }) await access(report.filePath) @@ -1186,7 +1208,7 @@ export async function downloadClassReports( ? baseFileName : `${baseFileName.replace(report.fileExtension, '')}-${usedCount + 1}${report.fileExtension}` - zip.file(fileName, await readFile(report.filePath)) + await copyFile(report.filePath, join(exportPath, fileName)) completedCount += 1 publishProgress({ @@ -1195,41 +1217,28 @@ export async function downloadClassReports( reportTitle: report.title, current: completedCount, total: reports.length, - filePath: result.filePath + filePath: exportPath }) } - publishProgress({ - status: 'writing', - current: reports.length, - total: reports.length, - filePath: result.filePath - }) - - const zipBuffer = await zip.generateAsync({ - type: 'nodebuffer', - compression: 'DEFLATE' - }) - await writeFile(result.filePath, zipBuffer) - publishProgress({ status: 'finished', current: reports.length, total: reports.length, - filePath: result.filePath + filePath: exportPath }) } catch (error) { publishProgress({ status: 'failed', current: completedCount, total: reports.length, - filePath: result.filePath, - error: error instanceof Error ? error.message : '下载班级报告失败' + filePath: exportPath, + error: error instanceof Error ? error.message : '导出班级报告失败' }) throw error } - return { filePath: result.filePath, reportCount: reports.length } + return { filePath: exportPath, reportCount: reports.length } } export async function deleteReport(id: string): Promise { diff --git a/src/main/types/report.ts b/src/main/types/report.ts index a6c27b7..480fca2 100644 --- a/src/main/types/report.ts +++ b/src/main/types/report.ts @@ -44,7 +44,7 @@ export type ReportGenerationProgress = { export type ReportDownloadProgress = { batchId: string - status: 'started' | 'file-started' | 'file-finished' | 'writing' | 'finished' | 'failed' + status: 'started' | 'file-started' | 'file-finished' | 'finished' | 'failed' classId: string className: string reportId?: string diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 5c0a5db..394b8bd 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -108,7 +108,7 @@ type ReportGenerationProgress = { type ReportDownloadProgress = { batchId: string - status: 'started' | 'file-started' | 'file-finished' | 'writing' | 'finished' | 'failed' + status: 'started' | 'file-started' | 'file-finished' | 'finished' | 'failed' classId: string className: string reportId?: string diff --git a/src/preload/index.ts b/src/preload/index.ts index 30591f0..a5af880 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -210,7 +210,7 @@ const api = { onReportDownloadProgress: ( callback: (progress: { batchId: string - status: 'started' | 'file-started' | 'file-finished' | 'writing' | 'finished' | 'failed' + status: 'started' | 'file-started' | 'file-finished' | 'finished' | 'failed' classId: string className: string reportId?: string diff --git a/src/renderer/src/components/feedback/GenerationProgressToasts.tsx b/src/renderer/src/components/feedback/GenerationProgressToasts.tsx index acd8176..61475f7 100644 --- a/src/renderer/src/components/feedback/GenerationProgressToasts.tsx +++ b/src/renderer/src/components/feedback/GenerationProgressToasts.tsx @@ -160,28 +160,21 @@ export function GenerationProgressToasts(): null { const toastId = getToastId('report-downloads', progress.batchId) const isFinished = progress.status === 'finished' const isFailed = progress.status === 'failed' - const isWriting = progress.status === 'writing' const activeReportTitle = progress.reportTitle - ? `正在打包 ${progress.reportTitle}` - : '正在打包班级报告' + ? `正在导出 ${progress.reportTitle}` + : '正在导出班级报告' showProgressToast({ id: toastId, icon: Download, - title: isFinished - ? '班级报告导出完成' - : isFailed - ? '班级报告导出失败' - : isWriting - ? '正在写入压缩包' - : activeReportTitle, + title: isFinished ? '班级报告导出完成' : isFailed ? '班级报告导出失败' : activeReportTitle, description: isFinished ? progress.filePath : `${progress.className} · ${progress.current}/${progress.total}`, current: progress.current, total: progress.total, status: isFinished ? 'success' : isFailed ? 'error' : 'running', - statusLabel: isFinished ? '已完成' : isFailed ? '失败' : isWriting ? '写入中' : '打包中', + statusLabel: isFinished ? '已完成' : isFailed ? '失败' : '导出中', error: progress.error, duration: isFinished || isFailed ? 5000 : undefined }) diff --git a/src/renderer/src/pages/ClassPage.tsx b/src/renderer/src/pages/ClassPage.tsx index 733d548..acc1d77 100644 --- a/src/renderer/src/pages/ClassPage.tsx +++ b/src/renderer/src/pages/ClassPage.tsx @@ -601,13 +601,13 @@ export function ClassPage(): React.JSX.Element { if (!response.ok) { if (!response.canceled) { - showError('下载班级报告失败', response.message) + showError('导出班级报告失败', response.message) } return } showSuccess( - `已下载「${classItem.name}」报告`, + `已导出「${classItem.name}」报告`, `${response.filePath},共 ${response.reportCount} 份` ) } finally { @@ -895,8 +895,8 @@ export function ClassPage(): React.JSX.Element { > {downloadingReportClassId === classItem.id - ? '报告打包中' - : '一键下载班级报告'} + ? '报告导出中' + : '一键导出班级报告'} diff --git a/src/renderer/src/types/app.ts b/src/renderer/src/types/app.ts index f920165..f85832c 100644 --- a/src/renderer/src/types/app.ts +++ b/src/renderer/src/types/app.ts @@ -128,7 +128,7 @@ export type ReportGenerationProgress = { export type ReportDownloadProgress = { batchId: string - status: 'started' | 'file-started' | 'file-finished' | 'writing' | 'finished' | 'failed' + status: 'started' | 'file-started' | 'file-finished' | 'finished' | 'failed' classId: string className: string reportId?: string