From 59412010dc11c55c04e547267f3e9d7ac9a1f84e Mon Sep 17 00:00:00 2001 From: Blizzard Date: Fri, 31 Jul 2026 18:32:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(fe+be):=20=E8=BA=AB=E4=BB=BD=E5=8D=A1?= =?UTF-8?q?=E5=8A=A0=E5=85=AC=E7=AB=A0=E3=80=81=E4=BF=AE=E6=80=A7=E5=88=AB?= =?UTF-8?q?=E7=AC=A6=E5=8F=B7=E3=80=81=E7=94=B5=E8=AF=9D=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E5=85=A8=E3=80=81=E7=85=A7=E6=8A=A4=E5=8D=A1=E6=A0=87=E9=A2=98?= =?UTF-8?q?=E4=B8=A4=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修正性别 ♂/♀:用名字字号量宽度再定位,不再怼到名字上(之前用小字号量偏了) - 正反两张卡右上角各盖一枚「肉垫计划 · 官方认证」红色公章(双圈+五角星,微旋转半透明) - 照护卡标题改成两行「X星地球移民 / 居民身份卡」(按猫狗取 喵/汪/宠) - 正面「物ID」改为「ID」 - 后端 IDCard.Phone 返回完整手机号,不再脱敏 Co-Authored-By: Claude Opus 5 --- pets-be/internal/service/idcard.go | 2 +- pets-fe/pages/idcard/idcard.js | 76 ++++++++++++++++++++++++++---- 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/pets-be/internal/service/idcard.go b/pets-be/internal/service/idcard.go index b026f01..493057a 100644 --- a/pets-be/internal/service/idcard.go +++ b/pets-be/internal/service/idcard.go @@ -159,7 +159,7 @@ func (s *Service) GetIDCard(userID, petID string) (*IDCard, error) { Birthday: bd, Age: pet.Age, Days: days, Zodiac: zodiac, Weight: pet.Weight, Intro: intro, Color: pet.Color, Personality: pet.Personality, AvatarURL: pet.AvatarURL, IDNo: petIDNo(pet), Validity: validity, - Owner: owner, Phone: maskPhone(user.Phone), Allergy: allergy, Notes: pet.Notes, + Owner: owner, Phone: user.Phone, Allergy: allergy, Notes: pet.Notes, VaccineState: vaccineState, DewormState: dewormState, Org: "肉垫计划", } diff --git a/pets-fe/pages/idcard/idcard.js b/pets-fe/pages/idcard/idcard.js index 4bb2392..a39876c 100644 --- a/pets-fe/pages/idcard/idcard.js +++ b/pets-fe/pages/idcard/idcard.js @@ -135,16 +135,20 @@ Page({ ctx.lineWidth = 8; ctx.stroke(); - // 名字 + 性别(头像右侧) + // 名字 + 性别(头像右侧)。性别符号必须用「名字的字号」量宽度再定位, + // 否则换成小字号后 measureText 量出的宽度偏小,符号会怼到名字上 const nx = avX + avD + 28; + const nm = c.name || '毛孩子'; ctx.textAlign = 'left'; ctx.fillStyle = '#2D2925'; ctx.font = '700 52px sans-serif'; - ctx.fillText(c.name || '毛孩子', nx, avY + 44); + ctx.fillText(nm, nx, avY + 46); + const nmW = ctx.measureText(nm).width; if (c.gender) { - ctx.fillStyle = c.gender === '女孩' ? '#E86A9B' : '#5B9BE8'; - ctx.font = '30px sans-serif'; - ctx.fillText(c.gender === '女孩' ? '♀' : '♂', nx + ctx.measureText(c.name || '毛孩子').width + 12, avY + 44); + const isF = c.gender === '女孩'; + ctx.fillStyle = isF ? '#E86A9B' : '#5B9BE8'; + ctx.font = '600 34px sans-serif'; + ctx.fillText(isF ? '♀' : '♂', nx + nmW + 16, avY + 44); } // 头像右的资料行 const kvF = (label, value, yy) => { @@ -201,19 +205,23 @@ Page({ ctx.textAlign = 'left'; ctx.fillStyle = '#9C7A3E'; ctx.font = '22px sans-serif'; - ctx.fillText('物ID ' + (c.id_no || ''), fx + 34, fY + fH - 56); + ctx.fillText('ID ' + (c.id_no || ''), fx + 34, fY + fH - 56); if (c.days > 0) { ctx.fillText('和主人一起生活的第 ' + c.days + ' 天', fx + 34, fY + fH - 22); } + // 正面印章(右上角) + this.drawStamp(ctx, fx + cardW2 - 78, fY + 82); + // ══════════ 照护卡(白 + 蓝)══════════ const bY = fY + fH + 40, bH = 566; this.roundRect(ctx, fx, bY, cardW2, bH, 30); ctx.fillStyle = '#FFFFFF'; ctx.fill(); - // 蓝色标题条 - const hb = 64; + // 蓝色标题条(两行:「X星地球移民 / 居民身份卡」) + const star = c.species === '狗狗' ? '汪' : c.species === '猫猫' ? '喵' : '宠'; + const hb = 92; const gb = ctx.createLinearGradient(fx, bY, fx + cardW2, bY); gb.addColorStop(0, '#5AA0E8'); gb.addColorStop(1, '#7FB6F0'); @@ -222,8 +230,10 @@ Page({ ctx.fill(); ctx.textAlign = 'center'; ctx.fillStyle = '#fff'; - ctx.font = '700 28px sans-serif'; - ctx.fillText('照护信息', W / 2, bY + 24 + 42); + ctx.font = '700 30px sans-serif'; + ctx.fillText(star + '星地球移民', W / 2, bY + 24 + 40); + ctx.font = '700 26px sans-serif'; + ctx.fillText('居民身份卡', W / 2, bY + 24 + 76); // 照护行(标签 + 值,不放图标) const rowX = fx + 44; @@ -267,6 +277,9 @@ Page({ ctx.fillText('待发布', qrX + qrD / 2, qrY + qrD / 2 + 24); } + // 背面印章(右上角,压在蓝条右侧) + this.drawStamp(ctx, fx + cardW2 - 78, bY + 70); + // 只导出到实际内容底部,避免卡片下面拖一条空白 const dpr = this._dpr || 2; const contentH = bY + bH + pad; @@ -297,6 +310,49 @@ Page({ ctx.closePath(); }, + // 五角星路径(调用方负责 fill/stroke) + star(ctx, cx, cy, rO, rI, n) { + ctx.beginPath(); + for (let i = 0; i < n * 2; i++) { + const r = i % 2 === 0 ? rO : rI; + const a = -Math.PI / 2 + (i * Math.PI) / n; + const x = cx + r * Math.cos(a), y = cy + r * Math.sin(a); + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + } + ctx.closePath(); + }, + + // 红色公章:双圈 + 顶部五角星 + 「肉垫计划 / 官方认证」,略微旋转、半透明 + drawStamp(ctx, cx, cy) { + ctx.save(); + ctx.translate(cx, cy); + ctx.rotate(-0.16); + ctx.globalAlpha = 0.68; + ctx.strokeStyle = '#CE4534'; + ctx.fillStyle = '#CE4534'; + ctx.lineWidth = 4; + ctx.beginPath(); + ctx.arc(0, 0, 52, 0, Math.PI * 2); + ctx.stroke(); + ctx.lineWidth = 1.5; + ctx.beginPath(); + ctx.arc(0, 0, 43, 0, Math.PI * 2); + ctx.stroke(); + this.star(ctx, 0, -23, 11, 4.6, 5); + ctx.fill(); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.font = '700 25px sans-serif'; + ctx.fillText('肉垫计划', 0, 10); + ctx.font = '13px sans-serif'; + ctx.fillText('官方认证', 0, 32); + ctx.restore(); + ctx.globalAlpha = 1; + ctx.textAlign = 'left'; + ctx.textBaseline = 'alphabetic'; + }, + // 微信取手机号:新版 getPhoneNumber 返回动态 code,后端换成真实手机号存库, // 拿回后重新取卡(此时电话就有了)并重画 onGetPhone(e) {