From f4b417bcaf4ac57939a15176cf35a5f4fe54318d Mon Sep 17 00:00:00 2001 From: cc Date: Wed, 24 Jun 2026 23:24:55 +0800 Subject: [PATCH] =?UTF-8?q?@=20feat(upload):=20=E5=89=8D=E7=AB=AF=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E5=89=8D=20Canvas=20=E5=8E=8B=E7=BC=A9=E5=9B=BE?= =?UTF-8?q?=E7=89=87=EF=BC=88=E5=A4=A7=E5=9B=BE=E7=BC=A9=E8=87=B31920=20+?= =?UTF-8?q?=20JPEG=20q=3D0.78=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude @ --- Client/src/components/PhotoWallPage.tsx | 31 +++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/Client/src/components/PhotoWallPage.tsx b/Client/src/components/PhotoWallPage.tsx index 2b96322..0100a82 100644 --- a/Client/src/components/PhotoWallPage.tsx +++ b/Client/src/components/PhotoWallPage.tsx @@ -498,6 +498,31 @@ export function PhotoWallPage({ onExit, token, folderName, isAdmin, onForbidden }) }, [addEl, spawnPos]) + /* ── 图片压缩(Canvas:缩小 + 转 JPEG)─────────────────────── */ + /** 把 data URL 图片压缩后返回新 data URL(宽 > 1920 的缩到 1920,JPEG q=0.78)。 + * 失败时返回原图。 */ + function compressImage(dataUrl: string): Promise { + return new Promise((resolve) => { + const img = new Image() + img.onload = () => { + try { + let w = img.naturalWidth + let h = img.naturalHeight + // 大图缩小 + if (w > 1920) { h = Math.round(h * 1920 / w); w = 1920 } + const canvas = document.createElement('canvas') + canvas.width = w; canvas.height = h + const ctx = canvas.getContext('2d') + if (!ctx) { resolve(dataUrl); return } + ctx.drawImage(img, 0, 0, w, h) + resolve(canvas.toDataURL('image/jpeg', 0.78)) + } catch { resolve(dataUrl) } + } + img.onerror = () => resolve(dataUrl) + img.src = dataUrl + }) + } + /* ── 图片上传 ─────────────────────────────────────────────── */ const handleFiles = useCallback( (files: FileList | File[]) => { @@ -511,8 +536,8 @@ export function PhotoWallPage({ onExit, token, folderName, isAdmin, onForbidden imgs.forEach((file) => { if (file.size > 10 * 1024 * 1024) showToast('图片较大,建议压缩后上传') const reader = new FileReader() - reader.onload = (ev) => { - const src = ev.target?.result as string + reader.onload = async (ev) => { + const rawSrc = ev.target?.result as string // 个人墙有张数上限 if (maxPhotos > 0) { const count = elsRef.current.filter((e) => e.type === 'photo').length @@ -526,6 +551,8 @@ export function PhotoWallPage({ onExit, token, folderName, isAdmin, onForbidden showToast(`已达上限(${maxItems} 条)`) return } + // 客户端压缩(大图缩小 + 转 JPEG q=0.78) + const src = await compressImage(rawSrc) const uploadFn = isPersonal ? (t: string, d: string) => uploadPhoto(t, d) : (t: string, d: string) => uploadFolderPhoto(t, folderName!, d)