import JSZip from 'jszip'; import { saveAs } from 'file-saver'; // 假设使用的是 Element Plus,请根据实际 UI 库调整导入 import { ElMessage } from 'element-plus'; /** * 图片数据接口 */ interface ImageItem { name: string; url: string; } /** * 批量导出图片并生成 ZIP * @param imageList 图片数组 * @param zipName 压缩包名称 * @param concurrency 并发请求数,默认 5,避免浏览器请求过多被拦截 */ export async function exportImagesToZip(imageList: ImageItem[], zipName: string = 'images', concurrency: number = 5) { if (!imageList || imageList.length === 0) { ElMessage.warning('没有可导出的图片'); return; } const zip = new JSZip(); try { // 使用并发控制处理图片获取 const tasks = imageList.map((item) => () => processImageItem(item, zip)); // 执行并发任务 await runWithConcurrency(tasks, concurrency); ElMessage.success('导出完成'); // 生成 ZIP 并下载 const content = await zip.generateAsync({ type: 'blob', compression: 'DEFLATE', compressionOptions: { level: 6 } }); saveAs(content, `${zipName}.zip`); } catch (err) { console.error('ZIP 导出失败:', err); ElMessage.error('图片导出失败,请重试'); } } /** * 处理单个图片项:获取 Blob 并添加到 Zip */ async function processImageItem(item: ImageItem, zip: JSZip): Promise { try { // 清理文件名,防止非法字符或扩展名缺失 const safeName = sanitizeFileName(item.name); const blob = await getImageBlobViaCanvas(item.url); zip.file(`${safeName}.png`, blob, { binary: true }); } catch (error) { console.warn(`图片加载失败: ${item.url}`, error); throw error; // 抛出错误以便外层统计或处理 } } /** * 通过 Canvas 获取图片 Blob (解决 OSS 跨域 fetch 问题) * 注意:这要求 OSS _bucket_ 配置了 CORS 允许 '*' 或你的域名,且允许 GET 方法。 * 如果 OSS 完全禁止跨域,此方法也会失败,但通常公共读 OSS 是允许 img 标签跨域显示的。 */ async function getImageBlobViaCanvas(url: string): Promise { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.blob(); } /** * 简单的并发控制执行器 */ async function runWithConcurrency(tasks: (() => Promise)[], limit: number): Promise { const results: Promise[] = []; let index = 0; async function worker() { while (index < tasks.length) { const currentIndex = index++; const task = tasks[currentIndex]; try { await task(); } catch (e) { // 这里可以选择忽略单个失败,或者累计错误 console.error(`Task ${currentIndex} failed`, e); } } } // 创建 limit 个工人 const workers = Array(Math.min(limit, tasks.length)).fill(null).map(worker); await Promise.all(workers); } /** * 清理文件名,确保合法且唯一(简单版) */ function sanitizeFileName(name: string): string { // 移除非法字符,保留字母、数字、中文、下划线、点、横杠 let cleanName = name.replace(/[<>:"/\\|?*]/g, '_'); // 如果名字为空或只有扩展名,给一个默认名 if (!cleanName || cleanName.startsWith('.')) { cleanName = `image_${Date.now()}_${Math.random().toString(36).substr(2, 9)}${cleanName}`; } return cleanName; }