From f97eacdc70fc512558448779866230dea5e8692d Mon Sep 17 00:00:00 2001 From: hailin Date: Fri, 6 Feb 2026 00:01:40 -0800 Subject: [PATCH] =?UTF-8?q?feat(admin-web):=20=E6=89=B9=E9=87=8F=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E6=94=AF=E6=8C=81=E9=80=89=E6=8B=A9=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用 File System Access API 让管理员可以选择文件保存位置, 而不是自动下载到默认目录。 Co-Authored-By: Claude Opus 4.5 --- .../src/app/(dashboard)/contracts/page.tsx | 88 +++++++++++++++---- 1 file changed, 72 insertions(+), 16 deletions(-) diff --git a/frontend/admin-web/src/app/(dashboard)/contracts/page.tsx b/frontend/admin-web/src/app/(dashboard)/contracts/page.tsx index 8b6071ab..cbe864d1 100644 --- a/frontend/admin-web/src/app/(dashboard)/contracts/page.tsx +++ b/frontend/admin-web/src/app/(dashboard)/contracts/page.tsx @@ -167,6 +167,38 @@ export default function ContractsPage() { throw new Error('任务处理超时'); }; + // 使用 File System Access API 让用户选择保存位置(Chrome/Edge 支持) + const saveFileWithPicker = async (url: string, suggestedName: string): Promise => { + try { + // 检查浏览器是否支持 File System Access API + if ('showSaveFilePicker' in window) { + // 先下载文件内容 + const response = await fetch(url); + const blob = await response.blob(); + + // 弹出保存对话框让用户选择位置 + const fileHandle = await (window as unknown as { showSaveFilePicker: (options: unknown) => Promise }).showSaveFilePicker({ + suggestedName, + types: [{ + description: 'ZIP 压缩文件', + accept: { 'application/zip': ['.zip'] }, + }], + }); + + // 写入文件 + const writable = await fileHandle.createWritable(); + await writable.write(blob); + await writable.close(); + + return true; + } + } catch (error) { + // 用户取消或浏览器不支持,回退到普通下载 + console.log('File System Access API 不可用或用户取消,使用普通下载'); + } + return false; + }; + // 批量下载(创建下载任务并等待完成后自动下载) const handleBatchDownload = async () => { setBatchDownloading(true); @@ -188,14 +220,26 @@ export default function ContractsPage() { const downloadUrl = await pollTaskUntilComplete(result.taskNo); if (downloadUrl) { - // 创建隐藏的 a 标签触发浏览器下载(会弹出另存为对话框) - const link = document.createElement('a'); - link.href = downloadUrl; - link.download = ''; // 设置 download 属性触发下载 - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - toast.success('请选择保存位置'); + // 生成文件名 + const today = new Date().toISOString().split('T')[0].replace(/-/g, ''); + const suggestedName = `contracts_${today}.zip`; + + // 优先使用 File System Access API 让用户选择保存位置 + toast.info('请选择保存位置...'); + const saved = await saveFileWithPicker(downloadUrl, suggestedName); + + if (saved) { + toast.success('文件保存成功!'); + } else { + // 浏览器不支持或用户取消,使用备用方法 + const link = document.createElement('a'); + link.href = downloadUrl; + link.download = suggestedName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + toast.success('下载已开始'); + } // 保存当前时间作为最后下载时间 const now = new Date().toISOString(); @@ -236,14 +280,26 @@ export default function ContractsPage() { const downloadUrl = await pollTaskUntilComplete(result.taskNo); if (downloadUrl) { - // 创建隐藏的 a 标签触发浏览器下载(会弹出另存为对话框) - const link = document.createElement('a'); - link.href = downloadUrl; - link.download = ''; // 设置 download 属性触发下载 - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - toast.success('请选择保存位置'); + // 生成文件名 + const today = new Date().toISOString().split('T')[0].replace(/-/g, ''); + const suggestedName = `contracts_incremental_${today}.zip`; + + // 优先使用 File System Access API 让用户选择保存位置 + toast.info('请选择保存位置...'); + const saved = await saveFileWithPicker(downloadUrl, suggestedName); + + if (saved) { + toast.success('文件保存成功!'); + } else { + // 浏览器不支持或用户取消,使用备用方法 + const link = document.createElement('a'); + link.href = downloadUrl; + link.download = suggestedName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + toast.success('下载已开始'); + } // 更新最后下载时间 const now = new Date().toISOString();