mirror of
https://gitee.com/ccnetcore/Yi
synced 2026-03-14 05:26:38 +08:00
feat: 消息ui优化
This commit is contained in:
127
Yi.Ai.Vue3/src/composables/chat/useImageCompression.ts
Normal file
127
Yi.Ai.Vue3/src/composables/chat/useImageCompression.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user