From 8543d0bb25cd4cfe94588064c1680776969a3e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=90=A6?= Date: Mon, 3 Aug 2026 15:26:51 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E4=BF=AE=E5=A4=8D=E6=B5=B7=E6=8A=A5?= =?UTF-8?q?=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/composables/drawXiSeal.js | 46 +++++++++++++ vite-tailwindcss/src/views/hb/index.vue | 13 ++++ .../src/views/hb/templates/ClassicPoster.vue | 63 ++++++++++++++--- .../src/views/hb/templates/SplitPoster.vue | 68 ++++++++++++++++--- 4 files changed, 171 insertions(+), 19 deletions(-) create mode 100644 vite-tailwindcss/src/composables/drawXiSeal.js diff --git a/vite-tailwindcss/src/composables/drawXiSeal.js b/vite-tailwindcss/src/composables/drawXiSeal.js new file mode 100644 index 0000000..7622611 --- /dev/null +++ b/vite-tailwindcss/src/composables/drawXiSeal.js @@ -0,0 +1,46 @@ +/** + * 在 canvas 上绘制圆内「囍」印章。 + * 用位图避免 html2canvas 对 CJK + transform/flex 居中错位。 + */ +export function drawXiSeal(canvas, { + size = 28, + fontSize = 13, + color = '#d4af37', + borderColor = '#d4af37', + fill = 'rgba(0, 0, 0, 0.15)', + dpr = Math.min(3, window.devicePixelRatio || 2), +} = {}) { + if (!canvas) return + const s = Math.max(12, Number(size) || 28) + const fs = Math.max(8, Number(fontSize) || Math.round(s * 0.46)) + const ratio = Math.max(1, Number(dpr) || 2) + + canvas.width = Math.round(s * ratio) + canvas.height = Math.round(s * ratio) + canvas.style.width = `${s}px` + canvas.style.height = `${s}px` + + const ctx = canvas.getContext('2d') + if (!ctx) return + ctx.setTransform(ratio, 0, 0, ratio, 0, 0) + ctx.clearRect(0, 0, s, s) + + const cx = s / 2 + const cy = s / 2 + const r = s / 2 - 0.75 + + ctx.beginPath() + ctx.arc(cx, cy, r, 0, Math.PI * 2) + ctx.fillStyle = fill + ctx.fill() + ctx.lineWidth = 1 + ctx.strokeStyle = borderColor + ctx.stroke() + + ctx.fillStyle = color + ctx.font = `700 ${fs}px "Noto Serif SC", "Source Han Serif SC", "Songti SC", "STSong", "SimSun", serif` + ctx.textAlign = 'center' + ctx.textBaseline = 'middle' + // 轻微上移做视觉光学居中(非 CSS transform,导出安全) + ctx.fillText('囍', cx, cy + fs * 0.04) +} diff --git a/vite-tailwindcss/src/views/hb/index.vue b/vite-tailwindcss/src/views/hb/index.vue index 141cd90..cd352f3 100644 --- a/vite-tailwindcss/src/views/hb/index.vue +++ b/vite-tailwindcss/src/views/hb/index.vue @@ -129,6 +129,7 @@ import html2canvas from 'html2canvas' import { getConfig, getPosterConfig } from '@/api/wedding' import { normalizePosterConfig, posterSideMeta, resolvePosterSide } from '@/composables/posterDefaults' import { renderFestiveQr } from '@/composables/renderFestiveQr' +import { drawXiSeal } from '@/composables/drawXiSeal' import { useAudio } from '@/composables/useAudio' import ClassicPoster from '@/views/hb/templates/ClassicPoster.vue' import SplitPoster from '@/views/hb/templates/SplitPoster.vue' @@ -375,6 +376,18 @@ async function savePosterImage() { try { cleanupBake = await bakeObjectFitImages(el) cleanupQr = await mountCaptureQr(el) + // 导出前重绘囍印章位图,避免 html2canvas 对 CJK 文本错位 + try { await document.fonts?.ready } catch { /* ignore */ } + el.querySelectorAll('canvas.sp-xi-mid, canvas.hb-xi-circle').forEach((c) => { + const isClassic = c.classList.contains('hb-xi-circle') + drawXiSeal(c, { + size: isClassic ? 22 : 28, + fontSize: isClassic ? 11 : 13, + color: '#d4af37', + borderColor: '#d4af37', + fill: isClassic ? 'rgba(155, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.18)', + }) + }) // 按元素实际宽度抬升倍率:目标约 1600px 宽,最高 3x,避免手机导出糊成一片 const baseW = Math.max(1, el.getBoundingClientRect().width || el.offsetWidth || 390) const scale = Math.min(3, Math.max(2.75, 1600 / baseW)) diff --git a/vite-tailwindcss/src/views/hb/templates/ClassicPoster.vue b/vite-tailwindcss/src/views/hb/templates/ClassicPoster.vue index f114278..8fcac4a 100644 --- a/vite-tailwindcss/src/views/hb/templates/ClassicPoster.vue +++ b/vite-tailwindcss/src/views/hb/templates/ClassicPoster.vue @@ -61,7 +61,7 @@ class-name="hb-name calligraphy" @animation-complete="namesRightReady = true" /> - +