/** OSS 单文件上限(与后端 OSS_MAX_UPLOAD_BYTES 默认一致) */ export const DEFAULT_OSS_MAX_UPLOAD_BYTES = 10 * 1024 * 1024; function isCompressibleImage(file: File): boolean { if (!file.type.startsWith('image/')) return false; if (file.type === 'image/svg+xml' || file.type === 'image/gif') return false; return true; } function loadImage(file: File): Promise { return new Promise((resolve, reject) => { const url = URL.createObjectURL(file); const img = new Image(); img.onload = () => { URL.revokeObjectURL(url); resolve(img); }; img.onerror = () => { URL.revokeObjectURL(url); reject(new Error('图片读取失败')); }; img.src = url; }); } function canvasToBlob(canvas: HTMLCanvasElement, type: string, quality: number): Promise { return new Promise((resolve, reject) => { canvas.toBlob( (blob) => (blob ? resolve(blob) : reject(new Error('图片压缩失败'))), type, quality, ); }); } async function renderScaled( img: HTMLImageElement, maxEdge: number, quality: number, mime: string, ): Promise { const w = img.naturalWidth; const h = img.naturalHeight; const scale = Math.min(1, maxEdge / Math.max(w, h)); const cw = Math.max(1, Math.round(w * scale)); const ch = Math.max(1, Math.round(h * scale)); const canvas = document.createElement('canvas'); canvas.width = cw; canvas.height = ch; const ctx = canvas.getContext('2d'); if (!ctx) throw new Error('图片压缩失败'); ctx.drawImage(img, 0, 0, cw, ch); return canvasToBlob(canvas, mime, quality); } /** * 超过 maxBytes 的图片用 canvas 压缩后再上传;不可压缩或非图片原样返回。 */ export async function compressImageFileIfNeeded( file: File, maxBytes = DEFAULT_OSS_MAX_UPLOAD_BYTES, ): Promise { if (!isCompressibleImage(file) || file.size <= maxBytes) return file; const img = await loadImage(file); const mime = file.type === 'image/png' || file.type === 'image/webp' ? 'image/jpeg' : file.type || 'image/jpeg'; const edges = [4096, 3072, 2048, 1536, 1280, 1024, 800]; let best: Blob | null = null; outer: for (const edge of edges) { for (let quality = 0.92; quality >= 0.5; quality -= 0.08) { const blob = await renderScaled(img, edge, quality, mime); if (!best || blob.size < best.size) best = blob; if (blob.size <= maxBytes) { best = blob; break outer; } } } if (!best || best.size >= file.size) return file; const ext = mime === 'image/png' ? '.png' : '.jpg'; const baseName = file.name.replace(/\.[^.]+$/, '') || 'image'; return new File([best], `${baseName}${ext}`, { type: mime, lastModified: Date.now() }); } export function formatOssMaxSizeMb(maxBytes = DEFAULT_OSS_MAX_UPLOAD_BYTES): number { return Math.floor(maxBytes / 1024 / 1024); }