/** * 图片处理工具类 */ class ImageUtils { /** * 将base64数据转换为Blob对象 */ static base64ToBlob(base64Data, mimeType = 'image/jpeg') { // 移除data URL前缀 const base64 = base64Data.split(',')[1] || base64Data; // 解码base64 const byteCharacters = atob(base64); const byteNumbers = new Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); return new Blob([byteArray], { type: mimeType }); } /** * 下载图片(传统方式,下载到默认文件夹) */ static downloadImage(base64Data, filename = null) { try { const blob = this.base64ToBlob(base64Data); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename || `image_${Date.now()}.jpg`; document.body.appendChild(link); link.click(); document.body.removeChild(link); // 清理URL对象 URL.revokeObjectURL(url); return true; } catch (error) { console.error('下载图片失败:', error); return false; } } /** * 使用"另存为"对话框下载图片(支持选择保存位置) * 使用 File System Access API,如果不支持则回退到传统方式 * 注意:File System Access API 需要安全上下文(HTTPS 或 localhost) */ static async downloadImageWithPicker(base64Data, suggestedName = null) { const filename = suggestedName || `image_${Date.now()}.jpg`; const blob = this.base64ToBlob(base64Data); // 检查是否支持 File System Access API 并且在安全上下文中 const isSecureContext = window.isSecureContext; const hasAPI = 'showSaveFilePicker' in window; console.log('File System Access API 检查:', { isSecureContext, hasAPI, hostname: window.location.hostname, protocol: window.location.protocol }); if (hasAPI && isSecureContext) { try { console.log('尝试打开另存为对话框...'); const handle = await window.showSaveFilePicker({ suggestedName: filename, types: [{ description: 'JPEG图片', accept: { 'image/jpeg': ['.jpg', '.jpeg'] } }, { description: 'PNG图片', accept: { 'image/png': ['.png'] } }] }); console.log('用户选择了文件:', handle.name); const writable = await handle.createWritable(); await writable.write(blob); await writable.close(); // 返回保存的文件名用于显示 return { success: true, filename: handle.name }; } catch (error) { // 用户取消了选择 if (error.name === 'AbortError') { console.log('用户取消了保存'); return { success: false, cancelled: true }; } console.error('保存文件失败:', error); // 回退到传统方式 return { success: this.downloadImage(base64Data, filename), fallback: true }; } } else { // 不支持或不在安全上下文中,使用传统方式 console.log('不支持 File System Access API 或不在安全上下文中,使用传统下载'); return { success: this.downloadImage(base64Data, filename), fallback: true }; } } /** * 批量使用"另存为"对话框下载图片(选择文件夹) */ static async downloadMultipleWithPicker(images, filenamePrefix = 'image') { // 检查是否支持目录选择 if ('showDirectoryPicker' in window) { try { const dirHandle = await window.showDirectoryPicker({ mode: 'readwrite' }); let successCount = 0; for (let i = 0; i < images.length; i++) { const img = images[i]; const filename = `${filenamePrefix}_${i + 1}.jpg`; const blob = this.base64ToBlob(img.data); try { const fileHandle = await dirHandle.getFileHandle(filename, { create: true }); const writable = await fileHandle.createWritable(); await writable.write(blob); await writable.close(); successCount++; } catch (err) { console.error(`保存第${i + 1}张图片失败:`, err); } } // 返回保存的目录名 return { success: true, count: successCount, dirName: dirHandle.name }; } catch (error) { // 用户取消了选择 if (error.name === 'AbortError') { return { success: false, cancelled: true }; } console.error('选择目录失败:', error); return { success: false, error: error.message }; } } else { // 不支持目录选择,返回不支持状态 return { success: false, notSupported: true }; } } /** * 复制图片到剪贴板 */ static async copyImageToClipboard(base64Data) { try { const blob = this.base64ToBlob(base64Data); if (navigator.clipboard && window.ClipboardItem) { const item = new ClipboardItem({ [blob.type]: blob }); await navigator.clipboard.write([item]); return true; } else { // 降级方案:复制base64文本 await navigator.clipboard.writeText(base64Data); return true; } } catch (error) { console.error('复制图片失败:', error); return false; } } /** * 验证图片文件 */ static validateImageFile(file) { const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp']; const maxSize = 10 * 1024 * 1024; // 10MB if (!allowedTypes.includes(file.type)) { return { valid: false, error: '不支持的图片格式,请选择 JPG、PNG、GIF 或 WebP 格式的图片' }; } if (file.size > maxSize) { return { valid: false, error: '图片文件过大,请选择小于 10MB 的图片' }; } return { valid: true }; } /** * 读取文件为base64 */ static readFileAsBase64(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => { resolve(e.target.result); }; reader.onerror = (e) => { reject(new Error('读取文件失败')); }; reader.readAsDataURL(file); }); } /** * 创建图片缩略图 */ static createThumbnail(base64Data, maxWidth = 200, maxHeight = 200) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // 计算缩略图尺寸 let { width, height } = img; if (width > height) { if (width > maxWidth) { height = (height * maxWidth) / width; width = maxWidth; } } else { if (height > maxHeight) { width = (width * maxHeight) / height; height = maxHeight; } } canvas.width = width; canvas.height = height; // 绘制缩略图 ctx.drawImage(img, 0, 0, width, height); // 转换为base64 const thumbnailData = canvas.toDataURL('image/jpeg', 0.8); resolve(thumbnailData); }; img.onerror = () => { reject(new Error('创建缩略图失败')); }; img.src = base64Data; }); } /** * 批量下载图片 */ static async downloadMultipleImages(images, zipName = null) { try { // 如果只有一张图片,直接下载 if (images.length === 1) { return this.downloadImage(images[0].data, images[0].filename); } // 多张图片时,逐个下载(简化版本) let successCount = 0; for (let i = 0; i < images.length; i++) { const image = images[i]; const filename = image.filename || `image_${i + 1}_${Date.now()}.jpg`; if (this.downloadImage(image.data, filename)) { successCount++; } // 添加小延迟避免浏览器阻止多个下载 if (i < images.length - 1) { await new Promise(resolve => setTimeout(resolve, 500)); } } return successCount; } catch (error) { console.error('批量下载失败:', error); return 0; } } /** * 格式化文件大小 */ static formatFileSize(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } /** * 获取图片信息 */ static getImageInfo(base64Data) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => { resolve({ width: img.width, height: img.height, aspectRatio: img.width / img.height, size: base64Data.length }); }; img.onerror = () => { reject(new Error('获取图片信息失败')); }; img.src = base64Data; }); } } // 导出到全局 window.ImageUtils = ImageUtils;