From 473c9f9200472e05b9408cd423307e58ddbd5ccb Mon Sep 17 00:00:00 2001 From: Blizzard Date: Fri, 31 Jul 2026 18:35:11 +0800 Subject: [PATCH] =?UTF-8?q?style(fe):=20=E7=85=A7=E6=8A=A4=E5=8D=A1?= =?UTF-8?q?=E6=94=B9=E8=AF=81=E4=BB=B6=E7=89=88=E5=BC=8F=20=E2=80=94=20?= =?UTF-8?q?=E5=A4=A7=E5=AD=97=E3=80=8CX=E6=98=9F=E4=BA=BA=E5=9C=B0?= =?UTF-8?q?=E7=90=83=E7=A7=BB=E6=B0=91/=E5=B1=85=E6=B0=91=E8=BA=AB?= =?UTF-8?q?=E4=BB=BD=E5=8D=A1=E3=80=8D=E5=B7=A6=E5=AF=B9=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 去掉蓝色标题条,照护卡整体换淡紫底 - 标题两行左对齐:小字带字距「汪星人/喵星人/宠星人地球移民」+ 大字「居民身份卡」 - 按物种动态取字:狗→汪星人、猫→喵星人、其它→宠星人 - 发证机构→签发机构、有效期→有效期限;小程序码落到右下角淡紫底上 - 新增 spacedText 逐字字距工具 Co-Authored-By: Claude Opus 5 --- pets-fe/pages/idcard/idcard.js | 94 ++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/pets-fe/pages/idcard/idcard.js b/pets-fe/pages/idcard/idcard.js index a39876c..6549d67 100644 --- a/pets-fe/pages/idcard/idcard.js +++ b/pets-fe/pages/idcard/idcard.js @@ -213,72 +213,67 @@ Page({ // 正面印章(右上角) this.drawStamp(ctx, fx + cardW2 - 78, fY + 82); - // ══════════ 照护卡(白 + 蓝)══════════ - const bY = fY + fH + 40, bH = 566; + // ══════════ 照护卡(淡紫,仿证件版式)══════════ + const bY = fY + fH + 40, bH = 630; + const gbc = ctx.createLinearGradient(fx, bY, fx, bY + bH); + gbc.addColorStop(0, '#E6E7FB'); + gbc.addColorStop(1, '#F2F0FA'); this.roundRect(ctx, fx, bY, cardW2, bH, 30); - ctx.fillStyle = '#FFFFFF'; + ctx.fillStyle = gbc; ctx.fill(); - // 蓝色标题条(两行:「X星地球移民 / 居民身份卡」) + // 大标题(两行左对齐):小字「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'); - this.roundRect(ctx, fx + 24, bY + 24, cardW2 - 48, hb, 18); - ctx.fillStyle = gb; - ctx.fill(); - ctx.textAlign = 'center'; - ctx.fillStyle = '#fff'; - 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); + ctx.textAlign = 'left'; + ctx.fillStyle = '#8A90AE'; + ctx.font = '600 27px sans-serif'; + this.spacedText(ctx, star + '星人地球移民', fx + 44, bY + 82, 8); + ctx.fillStyle = '#3E4A63'; + ctx.font = '800 66px sans-serif'; + ctx.fillText('居民身份卡', fx + 44, bY + 168); - // 照护行(标签 + 值,不放图标) + // 资料行(标签 + 值) const rowX = fx + 44; const kvB = (label, value, yy) => { ctx.textAlign = 'left'; - ctx.font = '24px sans-serif'; - ctx.fillStyle = '#8D8277'; + ctx.font = '25px sans-serif'; + ctx.fillStyle = '#9A9FB5'; ctx.fillText(label, rowX, yy); - ctx.font = '600 25px sans-serif'; - ctx.fillStyle = '#2D2925'; - ctx.fillText(value || '—', rowX + 150, yy); + ctx.font = '600 26px sans-serif'; + ctx.fillStyle = '#3E4A63'; + ctx.fillText(value || '—', rowX + 160, yy); }; - let ry = bY + 140; - kvB('主人', c.owner, ry); ry += 48; - kvB('联系电话', c.phone || '未验证', ry); ry += 48; - kvB('备注', c.notes || '无', ry); ry += 48; - kvB('发证机构', c.org || '肉垫计划', ry); ry += 48; - kvB('有效期', c.validity || '永久有效', ry); ry += 42; + let ry = bY + 244; + kvB('主人', c.owner, ry); ry += 52; + kvB('联系电话', c.phone || '未验证', ry); ry += 52; + kvB('备注', c.notes || '无', ry); ry += 52; + kvB('签发机构', c.org || '肉垫计划', ry); ry += 52; + kvB('有效期限', c.validity || '永久有效', ry); - // 走失联系框 + 小程序码 - const lostY = ry + 16, lostH = bY + bH - lostY - 30; - this.roundRect(ctx, fx + 24, lostY, cardW2 - 48, lostH, 18); - ctx.fillStyle = '#EAF3FE'; - ctx.fill(); - ctx.textAlign = 'left'; - ctx.fillStyle = '#5B7A9C'; - ctx.font = '22px sans-serif'; - ctx.fillText('微信扫一扫,查看' + (c.name || '') + '的身份信息', fx + 44, lostY + lostH / 2 + 8); - // 小程序码 - const qrD = 118, qrX = fx + cardW2 - 24 - 20 - qrD, qrY = lostY + (lostH - qrD) / 2; + // 小程序码(右下角,直接落在淡紫底上) + const qrD = 128, qrX = fx + cardW2 - 44 - qrD, qrY = bY + bH - 40 - qrD; ctx.fillStyle = '#fff'; - this.roundRect(ctx, qrX, qrY, qrD, qrD, 12); + this.roundRect(ctx, qrX, qrY, qrD, qrD, 14); ctx.fill(); if (qr) { - ctx.drawImage(qr, qrX + 8, qrY + 8, qrD - 16, qrD - 16); + ctx.drawImage(qr, qrX + 9, qrY + 9, qrD - 18, qrD - 18); } else { - ctx.fillStyle = '#9CB4CC'; + ctx.fillStyle = '#A7ABC4'; ctx.font = '18px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('小程序码', qrX + qrD / 2, qrY + qrD / 2 - 2); ctx.fillText('待发布', qrX + qrD / 2, qrY + qrD / 2 + 24); } + // 扫码说明(左侧,和小程序码同高) + ctx.textAlign = 'left'; + ctx.fillStyle = '#8A90AE'; + ctx.font = '22px sans-serif'; + ctx.fillText('微信扫一扫', fx + 44, qrY + qrD / 2 - 6); + ctx.fillText('查看' + (c.name || '') + '的身份信息', fx + 44, qrY + qrD / 2 + 28); - // 背面印章(右上角,压在蓝条右侧) - this.drawStamp(ctx, fx + cardW2 - 78, bY + 70); + // 背面印章(右上角) + this.drawStamp(ctx, fx + cardW2 - 84, bY + 92); // 只导出到实际内容底部,避免卡片下面拖一条空白 const dpr = this._dpr || 2; @@ -310,6 +305,15 @@ Page({ ctx.closePath(); }, + // 逐字加字距地画一行(canvas 的 letterSpacing 各端支持不一,手动排最稳) + spacedText(ctx, text, x, y, gap) { + let cx = x; + for (const ch of text) { + ctx.fillText(ch, cx, y); + cx += ctx.measureText(ch).width + gap; + } + }, + // 五角星路径(调用方负责 fill/stroke) star(ctx, cx, cy, rO, rI, n) { ctx.beginPath();