From a332e6204b68288e027787cc52112bc08d6f0d2a Mon Sep 17 00:00:00 2001 From: MaDaLei Date: Mon, 13 Apr 2026 18:51:35 +0800 Subject: [PATCH] feat: add reportPosterDraw utility --- src/utils/reportPosterDraw.js | 173 ++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 src/utils/reportPosterDraw.js diff --git a/src/utils/reportPosterDraw.js b/src/utils/reportPosterDraw.js new file mode 100644 index 0000000..179702c --- /dev/null +++ b/src/utils/reportPosterDraw.js @@ -0,0 +1,173 @@ +/** 洗美报告分享海报绘制(H5 / 小程序 canvas 2d 共用逻辑) */ + +export const POSTER_W = 750 +export const POSTER_H = 1100 + +export function roundRect(ctx, x, y, w, h, r) { + ctx.beginPath() + ctx.moveTo(x + r, y) + ctx.lineTo(x + w - r, y) + ctx.quadraticCurveTo(x + w, y, x + w, y + r) + ctx.lineTo(x + w, y + h - r) + ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h) + ctx.lineTo(x + r, y + h) + ctx.quadraticCurveTo(x, y + h, x, y + h - r) + ctx.lineTo(x, y + r) + ctx.quadraticCurveTo(x, y, x + r, y) + ctx.closePath() +} + +/** + * @param {CanvasRenderingContext2D} ctx + * @param {{ + * storeName: string + * storePhone: string + * storeAddr: string + * petName: string + * serviceType: string + * timeStr: string + * staffName: string + * remark: string + * beforeImg: any | null + * afterImg: any | null + * }} data + */ +export function drawReportPoster(ctx, data) { + const { + storeName, + storePhone, + storeAddr, + petName, + serviceType, + timeStr, + staffName, + remark, + beforeImg, + afterImg + } = data + + ctx.fillStyle = '#ffffff' + ctx.fillRect(0, 0, POSTER_W, POSTER_H) + + const gradient = ctx.createLinearGradient(0, 0, POSTER_W, 300) + gradient.addColorStop(0, '#07c160') + gradient.addColorStop(1, '#10b76f') + ctx.fillStyle = gradient + ctx.fillRect(0, 0, POSTER_W, 300) + + const name = storeName || '宠伴生活馆' + ctx.fillStyle = '#ffffff' + ctx.font = 'bold 36px sans-serif' + ctx.textAlign = 'center' + ctx.fillText(name, POSTER_W / 2, 70) + ctx.font = '20px sans-serif' + ctx.globalAlpha = 0.7 + ctx.fillText('宠物服务,让爱更专业', POSTER_W / 2, 105) + ctx.globalAlpha = 1 + + if (storePhone || storeAddr) { + ctx.font = '18px sans-serif' + ctx.globalAlpha = 0.85 + const contactLine = [storePhone, storeAddr].filter(Boolean).join(' | ') + ctx.fillText(contactLine, POSTER_W / 2, 138) + ctx.globalAlpha = 1 + } + + ctx.fillStyle = '#333333' + ctx.font = 'bold 36px sans-serif' + ctx.fillText('服务报告', POSTER_W / 2, 220) + + ctx.fillStyle = '#f8f6f3' + ctx.beginPath() + roundRect(ctx, 40, 260, 670, 220, 20) + ctx.fill() + + const infoItems = [ + ['宠物名字', petName || '-'], + ['服务项目', serviceType || '-'], + ['服务时间', timeStr || '-'], + ['服务技师', staffName || '-'] + ] + let y = 310 + ctx.textAlign = 'left' + infoItems.forEach(([label, val]) => { + ctx.fillStyle = '#999999' + ctx.font = '22px sans-serif' + ctx.fillText(label, 80, y) + ctx.fillStyle = '#333333' + ctx.font = 'bold 24px sans-serif' + ctx.fillText(String(val), 220, y) + y += 48 + }) + + ctx.fillStyle = '#f8f6f3' + ctx.beginPath() + roundRect(ctx, 40, 500, 670, 360, 20) + ctx.fill() + + ctx.fillStyle = '#333333' + ctx.font = 'bold 24px sans-serif' + ctx.textAlign = 'center' + ctx.fillText('服务前后对比', POSTER_W / 2, 545) + + const imgY = 575 + const imgH = 260 + const imgW = 300 + + ctx.fillStyle = '#e0e0e0' + ctx.beginPath() + roundRect(ctx, 60, imgY, imgW, imgH, 16) + ctx.fill() + ctx.fillStyle = '#999999' + ctx.font = '20px sans-serif' + ctx.fillText('服务前', 210, imgY + imgH / 2) + + ctx.fillStyle = '#e0e0e0' + ctx.beginPath() + roundRect(ctx, 390, imgY, imgW, imgH, 16) + ctx.fill() + ctx.fillStyle = '#999999' + ctx.fillText('服务后', 540, imgY + imgH / 2) + + if (remark) { + ctx.fillStyle = '#f8f6f3' + ctx.beginPath() + roundRect(ctx, 40, 880, 670, 100, 20) + ctx.fill() + ctx.fillStyle = '#666666' + ctx.font = '22px sans-serif' + ctx.textAlign = 'left' + if (remark.length > 30) { + ctx.fillText(remark.substring(0, 30), 70, 920) + ctx.fillText(remark.substring(30), 70, 955) + } else { + ctx.fillText(remark, 70, 930) + } + } + + ctx.fillStyle = '#0f172a' + ctx.font = '20px sans-serif' + ctx.textAlign = 'center' + ctx.fillText('长按识别小程序 · 欢迎再次预约', POSTER_W / 2, 1020) + + ctx.fillStyle = '#07c160' + ctx.font = 'bold 22px sans-serif' + ctx.fillText(`— ${name} —`, POSTER_W / 2, 1055) + + if (beforeImg) { + ctx.save() + ctx.beginPath() + roundRect(ctx, 60, imgY, imgW, imgH, 16) + ctx.clip() + ctx.drawImage(beforeImg, 60, imgY, imgW, imgH) + ctx.restore() + } + if (afterImg) { + ctx.save() + ctx.beginPath() + roundRect(ctx, 390, imgY, imgW, imgH, 16) + ctx.clip() + ctx.drawImage(afterImg, 390, imgY, imgW, imgH) + ctx.restore() + } +}