修改班级报告导出方式
This commit is contained in:
@@ -129,6 +129,22 @@ function getReportDownloadFileName(report: ReportEntity | ReportItem): string {
|
|||||||
return `${sanitizeFileName(`${report.className}${report.studentName}`)}${report.fileExtension}`
|
return `${sanitizeFileName(`${report.className}${report.studentName}`)}${report.fileExtension}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createUniqueExportFolder(parentPath: string, folderName: string): Promise<string> {
|
||||||
|
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 {
|
function sendReportProgress(progress: ReportGenerationProgress): void {
|
||||||
for (const window of BrowserWindow.getAllWindows()) {
|
for (const window of BrowserWindow.getAllWindows()) {
|
||||||
window.webContents.send('reports:generation-progress', progress)
|
window.webContents.send('reports:generation-progress', progress)
|
||||||
@@ -1133,18 +1149,24 @@ export async function downloadClassReports(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const className = reports[0]?.className || '班级报告'
|
const className = reports[0]?.className || '班级报告'
|
||||||
const result = await dialog.showSaveDialog({
|
const result = await dialog.showOpenDialog({
|
||||||
title: '下载班级报告',
|
title: '选择班级报告导出目录',
|
||||||
defaultPath: `${sanitizeFileName(className)}成长报告.zip`,
|
defaultPath: app.getPath('downloads'),
|
||||||
filters: [{ name: 'ZIP 压缩包', extensions: ['zip'] }]
|
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 }
|
return { filePath: '', reportCount: reports.length, canceled: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
const batchId = randomUUID()
|
const batchId = randomUUID()
|
||||||
const zip = new JSZip()
|
const exportPath = await createUniqueExportFolder(
|
||||||
|
parentPath,
|
||||||
|
`${sanitizeFileName(className)}成长报告`
|
||||||
|
)
|
||||||
const usedFileNames = new Map<string, number>()
|
const usedFileNames = new Map<string, number>()
|
||||||
let completedCount = 0
|
let completedCount = 0
|
||||||
|
|
||||||
@@ -1164,7 +1186,7 @@ export async function downloadClassReports(
|
|||||||
status: 'started',
|
status: 'started',
|
||||||
current: 0,
|
current: 0,
|
||||||
total: reports.length,
|
total: reports.length,
|
||||||
filePath: result.filePath
|
filePath: exportPath
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const report of reports) {
|
for (const report of reports) {
|
||||||
@@ -1174,7 +1196,7 @@ export async function downloadClassReports(
|
|||||||
reportTitle: report.title,
|
reportTitle: report.title,
|
||||||
current: completedCount,
|
current: completedCount,
|
||||||
total: reports.length,
|
total: reports.length,
|
||||||
filePath: result.filePath
|
filePath: exportPath
|
||||||
})
|
})
|
||||||
|
|
||||||
await access(report.filePath)
|
await access(report.filePath)
|
||||||
@@ -1186,7 +1208,7 @@ export async function downloadClassReports(
|
|||||||
? baseFileName
|
? baseFileName
|
||||||
: `${baseFileName.replace(report.fileExtension, '')}-${usedCount + 1}${report.fileExtension}`
|
: `${baseFileName.replace(report.fileExtension, '')}-${usedCount + 1}${report.fileExtension}`
|
||||||
|
|
||||||
zip.file(fileName, await readFile(report.filePath))
|
await copyFile(report.filePath, join(exportPath, fileName))
|
||||||
completedCount += 1
|
completedCount += 1
|
||||||
|
|
||||||
publishProgress({
|
publishProgress({
|
||||||
@@ -1195,41 +1217,28 @@ export async function downloadClassReports(
|
|||||||
reportTitle: report.title,
|
reportTitle: report.title,
|
||||||
current: completedCount,
|
current: completedCount,
|
||||||
total: reports.length,
|
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({
|
publishProgress({
|
||||||
status: 'finished',
|
status: 'finished',
|
||||||
current: reports.length,
|
current: reports.length,
|
||||||
total: reports.length,
|
total: reports.length,
|
||||||
filePath: result.filePath
|
filePath: exportPath
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
publishProgress({
|
publishProgress({
|
||||||
status: 'failed',
|
status: 'failed',
|
||||||
current: completedCount,
|
current: completedCount,
|
||||||
total: reports.length,
|
total: reports.length,
|
||||||
filePath: result.filePath,
|
filePath: exportPath,
|
||||||
error: error instanceof Error ? error.message : '下载班级报告失败'
|
error: error instanceof Error ? error.message : '导出班级报告失败'
|
||||||
})
|
})
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
return { filePath: result.filePath, reportCount: reports.length }
|
return { filePath: exportPath, reportCount: reports.length }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteReport(id: string): Promise<void> {
|
export async function deleteReport(id: string): Promise<void> {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export type ReportGenerationProgress = {
|
|||||||
|
|
||||||
export type ReportDownloadProgress = {
|
export type ReportDownloadProgress = {
|
||||||
batchId: string
|
batchId: string
|
||||||
status: 'started' | 'file-started' | 'file-finished' | 'writing' | 'finished' | 'failed'
|
status: 'started' | 'file-started' | 'file-finished' | 'finished' | 'failed'
|
||||||
classId: string
|
classId: string
|
||||||
className: string
|
className: string
|
||||||
reportId?: string
|
reportId?: string
|
||||||
|
|||||||
Vendored
+1
-1
@@ -108,7 +108,7 @@ type ReportGenerationProgress = {
|
|||||||
|
|
||||||
type ReportDownloadProgress = {
|
type ReportDownloadProgress = {
|
||||||
batchId: string
|
batchId: string
|
||||||
status: 'started' | 'file-started' | 'file-finished' | 'writing' | 'finished' | 'failed'
|
status: 'started' | 'file-started' | 'file-finished' | 'finished' | 'failed'
|
||||||
classId: string
|
classId: string
|
||||||
className: string
|
className: string
|
||||||
reportId?: string
|
reportId?: string
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ const api = {
|
|||||||
onReportDownloadProgress: (
|
onReportDownloadProgress: (
|
||||||
callback: (progress: {
|
callback: (progress: {
|
||||||
batchId: string
|
batchId: string
|
||||||
status: 'started' | 'file-started' | 'file-finished' | 'writing' | 'finished' | 'failed'
|
status: 'started' | 'file-started' | 'file-finished' | 'finished' | 'failed'
|
||||||
classId: string
|
classId: string
|
||||||
className: string
|
className: string
|
||||||
reportId?: string
|
reportId?: string
|
||||||
|
|||||||
@@ -160,28 +160,21 @@ export function GenerationProgressToasts(): null {
|
|||||||
const toastId = getToastId('report-downloads', progress.batchId)
|
const toastId = getToastId('report-downloads', progress.batchId)
|
||||||
const isFinished = progress.status === 'finished'
|
const isFinished = progress.status === 'finished'
|
||||||
const isFailed = progress.status === 'failed'
|
const isFailed = progress.status === 'failed'
|
||||||
const isWriting = progress.status === 'writing'
|
|
||||||
const activeReportTitle = progress.reportTitle
|
const activeReportTitle = progress.reportTitle
|
||||||
? `正在打包 ${progress.reportTitle}`
|
? `正在导出 ${progress.reportTitle}`
|
||||||
: '正在打包班级报告'
|
: '正在导出班级报告'
|
||||||
|
|
||||||
showProgressToast({
|
showProgressToast({
|
||||||
id: toastId,
|
id: toastId,
|
||||||
icon: Download,
|
icon: Download,
|
||||||
title: isFinished
|
title: isFinished ? '班级报告导出完成' : isFailed ? '班级报告导出失败' : activeReportTitle,
|
||||||
? '班级报告导出完成'
|
|
||||||
: isFailed
|
|
||||||
? '班级报告导出失败'
|
|
||||||
: isWriting
|
|
||||||
? '正在写入压缩包'
|
|
||||||
: activeReportTitle,
|
|
||||||
description: isFinished
|
description: isFinished
|
||||||
? progress.filePath
|
? progress.filePath
|
||||||
: `${progress.className} · ${progress.current}/${progress.total}`,
|
: `${progress.className} · ${progress.current}/${progress.total}`,
|
||||||
current: progress.current,
|
current: progress.current,
|
||||||
total: progress.total,
|
total: progress.total,
|
||||||
status: isFinished ? 'success' : isFailed ? 'error' : 'running',
|
status: isFinished ? 'success' : isFailed ? 'error' : 'running',
|
||||||
statusLabel: isFinished ? '已完成' : isFailed ? '失败' : isWriting ? '写入中' : '打包中',
|
statusLabel: isFinished ? '已完成' : isFailed ? '失败' : '导出中',
|
||||||
error: progress.error,
|
error: progress.error,
|
||||||
duration: isFinished || isFailed ? 5000 : undefined
|
duration: isFinished || isFailed ? 5000 : undefined
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -601,13 +601,13 @@ export function ClassPage(): React.JSX.Element {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
if (!response.canceled) {
|
if (!response.canceled) {
|
||||||
showError('下载班级报告失败', response.message)
|
showError('导出班级报告失败', response.message)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
showSuccess(
|
showSuccess(
|
||||||
`已下载「${classItem.name}」报告`,
|
`已导出「${classItem.name}」报告`,
|
||||||
`${response.filePath},共 ${response.reportCount} 份`
|
`${response.filePath},共 ${response.reportCount} 份`
|
||||||
)
|
)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -895,8 +895,8 @@ export function ClassPage(): React.JSX.Element {
|
|||||||
>
|
>
|
||||||
<Download />
|
<Download />
|
||||||
{downloadingReportClassId === classItem.id
|
{downloadingReportClassId === classItem.id
|
||||||
? '报告打包中'
|
? '报告导出中'
|
||||||
: '一键下载班级报告'}
|
: '一键导出班级报告'}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export type ReportGenerationProgress = {
|
|||||||
|
|
||||||
export type ReportDownloadProgress = {
|
export type ReportDownloadProgress = {
|
||||||
batchId: string
|
batchId: string
|
||||||
status: 'started' | 'file-started' | 'file-finished' | 'writing' | 'finished' | 'failed'
|
status: 'started' | 'file-started' | 'file-finished' | 'finished' | 'failed'
|
||||||
classId: string
|
classId: string
|
||||||
className: string
|
className: string
|
||||||
reportId?: string
|
reportId?: string
|
||||||
|
|||||||
Reference in New Issue
Block a user