Files
estate/src/utils/exportZip.ts
2026-05-13 15:28:25 +08:00

118 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<void> {
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<Blob> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.blob();
}
/**
* 简单的并发控制执行器
*/
async function runWithConcurrency(tasks: (() => Promise<void>)[], limit: number): Promise<void> {
const results: Promise<void>[] = [];
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;
}