From 3aca5c42c57c41d84d7e990ee08f0d5f55952c35 Mon Sep 17 00:00:00 2001 From: freefire Date: Mon, 3 Aug 2026 18:30:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=B4=E5=83=8F=E8=A3=81=E5=89=AA=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E4=BF=9D=E7=95=99=E6=BA=90=E6=A0=BC=E5=BC=8F=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=EF=BC=8C=E9=81=BF=E5=85=8D=E6=9C=8D=E5=8A=A1=E7=AB=AF?= =?UTF-8?q?=E5=8F=AA=E5=AD=98=20WebP=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- frontend/src/components/AvatarCropDialog.tsx | 2 +- frontend/src/utils/avatarCrop.ts | 24 ++++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/AvatarCropDialog.tsx b/frontend/src/components/AvatarCropDialog.tsx index dbdc862..78a7ea2 100644 --- a/frontend/src/components/AvatarCropDialog.tsx +++ b/frontend/src/components/AvatarCropDialog.tsx @@ -73,7 +73,7 @@ export default function AvatarCropDialog({ 裁剪头像 - 拖动图片调整位置,滚轮或滑块缩放。GIF 裁剪后将变为静态 JPG。 + 拖动图片调整位置,滚轮或滑块缩放。裁剪结果按原图格式保存(JPG→JPEG,PNG 保留透明);GIF 裁剪后变为静态 JPG。服务端会额外生成 WebP。 diff --git a/frontend/src/utils/avatarCrop.ts b/frontend/src/utils/avatarCrop.ts index 02144a7..cb40049 100644 --- a/frontend/src/utils/avatarCrop.ts +++ b/frontend/src/utils/avatarCrop.ts @@ -40,11 +40,13 @@ export function validateAvatarOutput(file: File, maxMb: number): string | null { return null; } -/** 将裁剪区域渲染为 WebP 文件(体积更小;不支持时回退 JPEG) */ +/** 将裁剪区域渲染为上传「原图」;服务端再衍生 WebP。 + * 优先保留源格式:PNG 保透明;其余(含 JPG/GIF/WebP)导出为 JPEG。 + */ export async function getCroppedAvatarFile( imageSrc: string, pixelCrop: Area, - originalName = 'avatar.webp', + originalName = 'avatar.jpg', ): Promise { const image = await loadImage(imageSrc); const canvas = document.createElement('canvas'); @@ -67,13 +69,21 @@ export async function getCroppedAvatarFile( size, ); - const tryTypes: { mime: string; quality: number; ext: string }[] = [ - { mime: 'image/webp', quality: 0.86, ext: 'webp' }, - { mime: 'image/jpeg', quality: 0.92, ext: 'jpg' }, - ]; + const srcExt = (originalName.match(/\.([^.]+)$/)?.[1] || '').toLowerCase(); + const preferPng = srcExt === 'png'; + // 勿优先 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 picked = tryTypes[1]; + let picked = tryTypes[0]; for (const t of tryTypes) { blob = await new Promise(resolve => { canvas.toBlob(b => resolve(b), t.mime, t.quality);