diff --git a/src/pages/mine/MyPets.vue b/src/pages/mine/MyPets.vue index fe707e4..e1bc203 100644 --- a/src/pages/mine/MyPets.vue +++ b/src/pages/mine/MyPets.vue @@ -41,7 +41,7 @@ - + @@ -134,7 +134,7 @@ const isCustomer = computed(() => userInfo.role === 'customer') const heroSub = computed(() => { if (isCustomer.value) return '管理您的宠物档案,创建预约时可关联,方便门店识别。' - return '以下为在本店预约中曾关联过的宠物(客户需在预约里选择档案)。' + return '以下为在本店预约中曾关联过的宠物。点击卡片可上传头像或编辑档案。' }) const navSafeStyle = (() => { @@ -440,6 +440,9 @@ onShow(() => loadPets()) margin-bottom: 10px; box-shadow: 0 1px 3px rgba(0,0,0,0.04); } +.pet-card--hover { + opacity: 0.92; +} .pet-row { display: flex; align-items: flex-start; diff --git a/src/pages/report-view/reportView.vue b/src/pages/report-view/reportView.vue index 8248532..ede46ab 100644 --- a/src/pages/report-view/reportView.vue +++ b/src/pages/report-view/reportView.vue @@ -268,7 +268,7 @@ import { ref, computed, onMounted, getCurrentInstance, nextTick } from 'vue' import { onLoad, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app' import { getReportByToken, imgUrl } from '../../api/index.js' import { formatDateTimeYMDHM } from '../../utils/datetime.js' -import { drawReportPoster, POSTER_W, POSTER_H } from '../../utils/reportPosterDraw.js' +import { drawReportPoster, POSTER_W, computePosterLogicalHeight } from '../../utils/reportPosterDraw.js' import AppIcon from '../../components/AppIcon.vue' import { isLoggedIn } from '../../utils/session.js' @@ -437,8 +437,9 @@ const generatePoster = async () => { const canvas = posterCanvas.value if (!canvas) return const ctx = canvas.getContext('2d') + const posterH = computePosterLogicalHeight(false) canvas.width = POSTER_W - canvas.height = POSTER_H + canvas.height = posterH drawReportPoster(ctx, { storeName: r.store?.name || '', storePhone: r.store?.phone || '', @@ -557,8 +558,9 @@ const generatePoster = async () => { if (win && win.pixelRatio != null) dpr = Math.min(win.pixelRatio, 3) else dpr = Math.min(uni.getSystemInfoSync().pixelRatio || 2, 3) } catch (_) { dpr = 2 } + const posterH = computePosterLogicalHeight(false) canvas.width = POSTER_W * dpr - canvas.height = POSTER_H * dpr + canvas.height = posterH * dpr ctx.scale(dpr, dpr) const beforeImg = await loadCanvasImage(canvas, beforePath) @@ -591,7 +593,7 @@ const generatePoster = async () => { fileType: 'png', quality: 1, destWidth: POSTER_W, - destHeight: POSTER_H, + destHeight: posterH, success: (out) => resolve(out.tempFilePath), fail: (e) => reject(e) } diff --git a/src/utils/reportPosterDraw.js b/src/utils/reportPosterDraw.js index 162ad64..c034d34 100644 --- a/src/utils/reportPosterDraw.js +++ b/src/utils/reportPosterDraw.js @@ -1,14 +1,30 @@ /** - * 洗美报告分享海报 — 朋友圈向:大图对比为主,弱化服务信息 + * 洗美报告分享海报 — 朋友圈向:大图对比为主,高度随内容收紧,避免底部大块留白 * H5 / 小程序 canvas 2d 共用 */ export const POSTER_W = 750 -/** 竖版比例偏手机屏,利于发朋友圈 */ +/** 兼容旧引用:实际高度请用 computePosterLogicalHeight */ export const POSTER_H = 1180 const FONT = '"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif' +/** 绘制前设置 canvas 高度用(与 drawReportPoster 内布局常量保持一致) */ +export function computePosterLogicalHeight(hasQr) { + const PAD = 28 + const frameY = 98 + const innerPad = 10 + const photoH = 520 + const photoY = frameY + innerPad + const petBaseline = photoY + photoH + innerPad + 28 + const subBaseline = petBaseline + 40 + let contentBottom = subBaseline + 22 + if (hasQr) contentBottom = subBaseline + 24 + 108 + 22 + const dividerY = contentBottom + 18 + const sloganY = dividerY + 40 + return sloganY + 18 +} + function roundRect(ctx, x, y, w, h, r) { const rr = Math.min(r, w / 2, h / 2) ctx.beginPath() @@ -26,34 +42,49 @@ function fillRR(ctx, x, y, w, h, r, fill) { ctx.fill() } -function drawBgGradient(ctx) { - const g = ctx.createLinearGradient(0, 0, 0, POSTER_H) +function drawBgGradient(ctx, height) { + const g = ctx.createLinearGradient(0, 0, 0, height) g.addColorStop(0, '#fffefb') g.addColorStop(0.42, '#f4faf6') g.addColorStop(1, '#e5f4ec') ctx.fillStyle = g - ctx.fillRect(0, 0, POSTER_W, POSTER_H) + ctx.fillRect(0, 0, POSTER_W, height) } /** * @param {CanvasRenderingContext2D} ctx + * @returns {number} 实际画布高度(生成图片、canvas 尺寸用) */ export function drawReportPoster(ctx, data) { - const { - storeName, - petName, - beforeImg, - afterImg, - qrImg - } = data + const { storeName, petName, beforeImg, afterImg, qrImg } = data const name = storeName || '宠伴生活馆' const PAD = 28 const pet = petName || '宝贝' - drawBgGradient(ctx) + const frameX = PAD + const frameY = 98 + const frameW = POSTER_W - PAD * 2 + const frameR = 28 + const innerPad = 10 + const photoY = frameY + innerPad + const photoH = 520 + const gap = 6 + const innerW = frameW - innerPad * 2 + const halfW = (innerW - gap) / 2 - // ── 顶部:轻量品牌(不抢镜)── + const posterHeight = computePosterLogicalHeight(!!qrImg) + const petBaseline = photoY + photoH + innerPad + 28 + const subBaseline = petBaseline + 40 + let contentBottom = subBaseline + 22 + if (qrImg) contentBottom = subBaseline + 24 + 108 + 22 + const dividerY = contentBottom + 18 + const shopNameY = dividerY + 22 + const sloganY = dividerY + 40 + + drawBgGradient(ctx, posterHeight) + + // ── 顶部品牌 ── ctx.textAlign = 'center' ctx.fillStyle = 'rgba(22, 101, 52, 0.88)' ctx.font = `600 22px ${FONT}` @@ -63,19 +94,7 @@ export function drawReportPoster(ctx, data) { ctx.font = `13px ${FONT}` ctx.fillText('洗护美容 · 真实对比', POSTER_W / 2, 76) - // ── 主视觉:大尺寸双图 + 相框感 ── - const frameX = PAD - const frameY = 98 - const frameW = POSTER_W - PAD * 2 - const frameR = 28 - const innerPad = 10 - const photoY = frameY + innerPad - const photoH = 560 - const gap = 6 - const innerW = frameW - innerPad * 2 - const halfW = (innerW - gap) / 2 - - // 外框阴影(模拟卡片) + // ── 主视觉 ── ctx.save() ctx.shadowColor = 'rgba(15, 23, 42, 0.08)' ctx.shadowBlur = 24 @@ -121,12 +140,10 @@ export function drawReportPoster(ctx, data) { ctx.fillText('洗后', ax + halfW / 2, photoY + photoH / 2) } - // 中间分隔亮条(比纯黑块柔和) ctx.restore() ctx.fillStyle = 'rgba(255,255,255,0.95)' ctx.fillRect(frameX + innerPad + halfW - gap / 2, photoY, gap, photoH) - // 中间缝上的小标:偏小、偏下,少挡脸 const cx = POSTER_W / 2 const cy = photoY + Math.floor(photoH * 0.58) const badgeR = 30 @@ -148,7 +165,6 @@ export function drawReportPoster(ctx, data) { ctx.fillStyle = '#166534' ctx.fillText('蜕变', cx, cy + 14) - // 洗前 / 洗后 角标(中文更接地气) const tagW = 100 const tagH = 34 const tagY = photoY + photoH - 16 - tagH @@ -163,52 +179,46 @@ export function drawReportPoster(ctx, data) { ctx.fillStyle = '#fff' ctx.fillText('洗 后', ax0 + halfW - 14 - tagW / 2, tagY + 23) - // ── 下方:只保留情绪与宠物名,弱化服务项目 ── - let textY = photoY + photoH + innerPad + 52 - + // ── 文案 ── ctx.textAlign = 'center' ctx.fillStyle = '#0f172a' ctx.font = `bold 44px ${FONT}` - ctx.fillText(pet, POSTER_W / 2, textY) - textY += 46 + ctx.fillText(pet, POSTER_W / 2, petBaseline) ctx.fillStyle = '#64748b' ctx.font = `16px ${FONT}` - ctx.fillText('护理前后 · 精神又可爱 ✨', POSTER_W / 2, textY) - textY += 36 + ctx.fillText('护理前后 · 精神又可爱 ✨', POSTER_W / 2, subBaseline) - // 二维码(若业务后续传入) if (qrImg) { const qrSize = 108 + const qy = subBaseline + 24 const qx = POSTER_W / 2 - qrSize / 2 - const qy = textY + 8 ctx.save() roundRect(ctx, qx, qy, qrSize, qrSize, 12) ctx.clip() ctx.drawImage(qrImg, qx, qy, qrSize, qrSize) ctx.restore() - textY = qy + qrSize + 22 ctx.fillStyle = '#94a3b8' ctx.font = `12px ${FONT}` - ctx.fillText('长按识别 · 查看完整报告', POSTER_W / 2, textY) - textY += 36 + ctx.fillText('长按识别 · 查看完整报告', POSTER_W / 2, qy + qrSize + 22) } - // ── 底部品牌(轻)── - const footY = POSTER_H - 64 + // ── 底部品牌(紧跟内容,不用 POSTER_H 硬顶底)── ctx.textAlign = 'center' ctx.strokeStyle = 'rgba(45, 185, 109, 0.25)' ctx.lineWidth = 1 ctx.beginPath() - ctx.moveTo(POSTER_W / 2 - 40, footY) - ctx.lineTo(POSTER_W / 2 + 40, footY) + ctx.moveTo(POSTER_W / 2 - 40, dividerY) + ctx.lineTo(POSTER_W / 2 + 40, dividerY) ctx.stroke() ctx.fillStyle = 'rgba(30, 41, 59, 0.85)' ctx.font = `600 15px ${FONT}` - ctx.fillText(name, POSTER_W / 2, footY + 26) + ctx.fillText(name, POSTER_W / 2, shopNameY) ctx.fillStyle = 'rgba(148, 163, 184, 0.95)' ctx.font = `11px ${FONT}` - ctx.fillText('宠物洗护美容 · 值得晒一晒', POSTER_W / 2, footY + 46) + ctx.fillText('宠物洗护美容 · 值得晒一晒', POSTER_W / 2, sloganY) + + return posterHeight }