mirror of
https://gitee.com/ccnetcore/Yi
synced 2026-03-20 00:16:37 +08:00
128 lines
3.2 KiB
TypeScript
128 lines
3.2 KiB
TypeScript
|
|
import { ElMessage } from 'element-plus';
|
|||
|
|
|
|||
|
|
export interface CompressionLevel {
|
|||
|
|
maxWidth: number;
|
|||
|
|
maxHeight: number;
|
|||
|
|
quality: number;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export const DEFAULT_COMPRESSION_LEVELS: CompressionLevel[] = [
|
|||
|
|
{ maxWidth: 800, maxHeight: 800, quality: 0.6 },
|
|||
|
|
{ maxWidth: 600, maxHeight: 600, quality: 0.5 },
|
|||
|
|
{ maxWidth: 400, maxHeight: 400, quality: 0.4 },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 压缩图片
|
|||
|
|
* @param file - 要压缩的图片文件
|
|||
|
|
* @param maxWidth - 最大宽度
|
|||
|
|
* @param maxHeight - 最大高度
|
|||
|
|
* @param quality - 压缩质量 (0-1)
|
|||
|
|
* @returns 压缩后的 Blob
|
|||
|
|
*/
|
|||
|
|
export function compressImage(
|
|||
|
|
file: File,
|
|||
|
|
maxWidth = 1024,
|
|||
|
|
maxHeight = 1024,
|
|||
|
|
quality = 0.8,
|
|||
|
|
): Promise<Blob> {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
const reader = new FileReader();
|
|||
|
|
reader.onload = (e) => {
|
|||
|
|
const img = new Image();
|
|||
|
|
img.onload = () => {
|
|||
|
|
const canvas = document.createElement('canvas');
|
|||
|
|
let width = img.width;
|
|||
|
|
let height = img.height;
|
|||
|
|
|
|||
|
|
if (width > maxWidth || height > maxHeight) {
|
|||
|
|
const ratio = Math.min(maxWidth / width, maxHeight / height);
|
|||
|
|
width = width * ratio;
|
|||
|
|
height = height * ratio;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
canvas.width = width;
|
|||
|
|
canvas.height = height;
|
|||
|
|
|
|||
|
|
const ctx = canvas.getContext('2d')!;
|
|||
|
|
ctx.drawImage(img, 0, 0, width, height);
|
|||
|
|
|
|||
|
|
canvas.toBlob(
|
|||
|
|
(blob) => {
|
|||
|
|
if (blob) {
|
|||
|
|
resolve(blob);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
reject(new Error('压缩失败'));
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
file.type,
|
|||
|
|
quality,
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
img.onerror = reject;
|
|||
|
|
img.src = e.target?.result as string;
|
|||
|
|
};
|
|||
|
|
reader.onerror = reject;
|
|||
|
|
reader.readAsDataURL(file);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将 Blob 转换为 base64
|
|||
|
|
* @param blob - Blob 对象
|
|||
|
|
* @returns base64 字符串
|
|||
|
|
*/
|
|||
|
|
export function blobToBase64(blob: Blob): Promise<string> {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
const reader = new FileReader();
|
|||
|
|
reader.onload = () => {
|
|||
|
|
resolve(reader.result as string);
|
|||
|
|
};
|
|||
|
|
reader.onerror = reject;
|
|||
|
|
reader.readAsDataURL(blob);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 尝试多级压缩直到满足大小限制
|
|||
|
|
* @param file - 图片文件
|
|||
|
|
* @param currentTotalLength - 当前已使用的总长度
|
|||
|
|
* @param maxTotalLength - 最大允许总长度
|
|||
|
|
* @returns 压缩结果或 null(如果无法满足限制)
|
|||
|
|
*/
|
|||
|
|
export async function tryCompressToLimit(
|
|||
|
|
file: File,
|
|||
|
|
currentTotalLength: number,
|
|||
|
|
maxTotalLength: number,
|
|||
|
|
compressionLevels = DEFAULT_COMPRESSION_LEVELS,
|
|||
|
|
): Promise<{ blob: Blob; base64: string; estimatedLength: number } | null> {
|
|||
|
|
for (const level of compressionLevels) {
|
|||
|
|
const compressedBlob = await compressImage(
|
|||
|
|
file,
|
|||
|
|
level.maxWidth,
|
|||
|
|
level.maxHeight,
|
|||
|
|
level.quality,
|
|||
|
|
);
|
|||
|
|
const base64 = await blobToBase64(compressedBlob);
|
|||
|
|
const estimatedLength = Math.floor(base64.length * 0.5);
|
|||
|
|
|
|||
|
|
if (currentTotalLength + estimatedLength <= maxTotalLength) {
|
|||
|
|
return { blob: compressedBlob, base64, estimatedLength };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Composable: 使用图片压缩
|
|||
|
|
*/
|
|||
|
|
export function useImageCompression() {
|
|||
|
|
return {
|
|||
|
|
compressImage,
|
|||
|
|
blobToBase64,
|
|||
|
|
tryCompressToLimit,
|
|||
|
|
DEFAULT_COMPRESSION_LEVELS,
|
|||
|
|
};
|
|||
|
|
}
|