From a55fa96952c00f4a8b12f4cdeccc0c24504bd9a8 Mon Sep 17 00:00:00 2001 From: HenryDu8133 <813367384@qq.com> Date: Sun, 13 Sep 2026 12:32:51 +0800 Subject: [PATCH] =?UTF-8?q?fix(build):=20SSR=20=E5=AE=89=E5=85=A8=E5=8C=96?= =?UTF-8?q?=20PlaylistCardGenerator=20canvasHeight=20=E9=81=BF=E5=85=8D=20?= =?UTF-8?q?Nitro=20=E6=9E=84=E5=BB=BA=20OOM(exit=20137)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上版在 canvasHeight computed 里对每首歌 document.createElement('canvas')+getContext('2d'),Nitro prerender/rollup bundler 在 Node 环境下要么 document undefined 抛错 fallback,要么 prerender 时对大歌单反复创建 2D context 撑爆虚拟内存 -> 5min 后 OOM Killer(exit 137)。 修复: - 新增 isBrowser = typeof document !== 'undefined' 判断 - getSharedCtx(): 浏览器环境才创建 1 个共享 2D context 并跨次复用(不再每首歌 createElement) - fitText(ctx, ...) 抽成双分支:有 ctx 用 measureText(客户端精确),无 ctx 用 CJK>0x2e80 Unicode 判断+经验字符宽度(16px 中文 1.05em/英文 0.55em)估算换行,保守估算高度 - 同时 fitText 支持文本中带 \\n 换行手动切分 - canvasHeight 中不再 new canvas/context,SSR 环境也能安全执行+算出估算高度 --- .../Admin/PlaylistCardGenerator.vue | 65 +++++++++++++++---- 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/app/components/Admin/PlaylistCardGenerator.vue b/app/components/Admin/PlaylistCardGenerator.vue index 64ddb84..e3a78f3 100644 --- a/app/components/Admin/PlaylistCardGenerator.vue +++ b/app/components/Admin/PlaylistCardGenerator.vue @@ -91,30 +91,69 @@ const songLeftX = 44 const songRightX = 360 const songAreaWidth = songRightX - songLeftX +const isBrowser = typeof document !== 'undefined' + +let _sharedCtx = null +const getSharedCtx = () => { + if (!isBrowser) return null + if (_sharedCtx) return _sharedCtx + try { + const c = document.createElement('canvas') + _sharedCtx = c.getContext('2d') + } catch { _sharedCtx = null } + return _sharedCtx +} + const fitText = (ctx, text, fontSize, maxWidth, lineHeight) => { - ctx.font = `${fontSize}px "Inter", "system-ui", "PingFang SC", "Microsoft YaHei", sans-serif` - const chars = Array.from(text) + const hasCtx = !!ctx + const fontFamily = `"Inter", "system-ui", "PingFang SC", "Microsoft YaHei", sans-serif` + const avgCharWidth = fontSize * 0.55 + const cjkCharWidth = fontSize * 1.05 + const splitLines = String(text || '').split(/\r?\n/) const lines = [] - let current = '' - for (const ch of chars) { - const tryStr = current + ch - const w = ctx.measureText(tryStr).width - if (w > maxWidth && current) { - lines.push(current) - current = ch + for (const rawLine of splitLines) { + if (hasCtx) { + ctx.font = `${fontSize}px ${fontFamily}` + const chars = Array.from(rawLine) + let current = '' + for (const ch of chars) { + const tryStr = current + ch + const w = ctx.measureText(tryStr).width + if (w > maxWidth && current) { + lines.push(current) + current = ch + } else { + current = tryStr + } + } + if (current) lines.push(current) } else { - current = tryStr + const chars = Array.from(rawLine) + let current = '' + let curW = 0 + for (const ch of chars) { + const code = ch.codePointAt(0) || 0 + const isCJK = code > 0x2e80 + const w = isCJK ? cjkCharWidth : avgCharWidth + if (curW + w > maxWidth && current) { + lines.push(current) + current = ch + curW = w + } else { + current += ch + curW += w + } + } + if (current) lines.push(current) } } - if (current) lines.push(current) return { lines, lineHeight } } const canvasHeight = computed(() => { const count = Math.min(props.songs.length, maxDisplaySongs) if (!count) return 600 - const canvas = document.createElement('canvas') - const ctx = canvas.getContext('2d') + const ctx = getSharedCtx() let totalH = 0 const maxWidthTitle = songAreaWidth const maxWidthArtist = Math.max(100, songAreaWidth - 10)