头像裁剪改为保留源格式上传,避免服务端只存 WebP。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-03 18:30:27 +08:00
parent 2c06eaccb2
commit 3aca5c42c5
2 changed files with 18 additions and 8 deletions

View File

@@ -73,7 +73,7 @@ export default function AvatarCropDialog({
<DialogHeader> <DialogHeader>
<DialogTitle></DialogTitle> <DialogTitle></DialogTitle>
<DialogDescription> <DialogDescription>
GIF JPG JPGJPEGPNG GIF JPG WebP
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>

View File

@@ -40,11 +40,13 @@ export function validateAvatarOutput(file: File, maxMb: number): string | null {
return null; return null;
} }
/** 将裁剪区域渲染为 WebP 文件(体积更小;不支持时回退 JPEG */ /** 将裁剪区域渲染为上传「原图」;服务端再衍生 WebP。
* 优先保留源格式PNG 保透明;其余(含 JPG/GIF/WebP导出为 JPEG。
*/
export async function getCroppedAvatarFile( export async function getCroppedAvatarFile(
imageSrc: string, imageSrc: string,
pixelCrop: Area, pixelCrop: Area,
originalName = 'avatar.webp', originalName = 'avatar.jpg',
): Promise<File> { ): Promise<File> {
const image = await loadImage(imageSrc); const image = await loadImage(imageSrc);
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
@@ -67,13 +69,21 @@ export async function getCroppedAvatarFile(
size, size,
); );
const tryTypes: { mime: string; quality: number; ext: string }[] = [ const srcExt = (originalName.match(/\.([^.]+)$/)?.[1] || '').toLowerCase();
{ mime: 'image/webp', quality: 0.86, ext: 'webp' }, const preferPng = srcExt === 'png';
{ mime: 'image/jpeg', quality: 0.92, ext: 'jpg' }, // 勿优先 WebP否则服务端只会存一份 WebP丢失用户原图格式
]; const tryTypes: { mime: string; quality?: number; ext: string }[] = preferPng
? [
{ mime: 'image/png', ext: 'png' },
{ mime: 'image/jpeg', quality: 0.92, ext: 'jpg' },
]
: [
{ mime: 'image/jpeg', quality: 0.92, ext: 'jpg' },
{ mime: 'image/png', ext: 'png' },
];
let blob: Blob | null = null; let blob: Blob | null = null;
let picked = tryTypes[1]; let picked = tryTypes[0];
for (const t of tryTypes) { for (const t of tryTypes) {
blob = await new Promise<Blob | null>(resolve => { blob = await new Promise<Blob | null>(resolve => {
canvas.toBlob(b => resolve(b), t.mime, t.quality); canvas.toBlob(b => resolve(b), t.mime, t.quality);