From fa53a0f4f9ea4566996fff61229190d2ab17b56c Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 28 Sep 2026 21:58:43 +0800 Subject: [PATCH] fix(revenue): align detail card, poster modal and copy feedback with reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - detail-tab 累计佣金 row now shows 累计 + 已提现 (was 累计/可提现/已提现) - rebuild poster canvas to match reference layout (768×1168, top banner, date overlay, blue title bar, bottom bordered box with QR + link) - copy-link feedback moves from inline text to a toast (notyf), matching the reference; drop the now-unused data-affiliate-feedback element --- assets/js/credits.js | 124 +++++++++++------- .../user-center-renderers/profile-revenue.php | 3 +- .../referral-qr-contract.test.cjs | 6 +- tools/admin-contract-report.json | 2 +- 4 files changed, 87 insertions(+), 48 deletions(-) diff --git a/assets/js/credits.js b/assets/js/credits.js index 218fce17..93f88d22 100644 --- a/assets/js/credits.js +++ b/assets/js/credits.js @@ -123,8 +123,6 @@ root.dataset.affiliateBound = '1'; var input = root.querySelector('[data-affiliate-link]'); var copy = root.querySelector('[data-affiliate-copy]'); - var feedback = root.querySelector('[data-affiliate-feedback]'); - function setFeedback(text, error) { if (feedback) { feedback.textContent = text; feedback.classList.toggle('is-error', !!error); } } var url = input ? input.value : ''; if (copy) copy.disabled = !url; api('/revenue/me').then(function (r) { @@ -135,9 +133,10 @@ }).catch(function () { /* 海报与比例为增强项,静默失败即可。 */ }); if (copy) copy.addEventListener('click', function () { if (!input || !input.value) return; - var done = function () { setFeedback('推广链接已复制。'); }; - if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(input.value).then(done).catch(function () { input.focus(); input.select(); setFeedback('复制失败,请手动复制。', true); }); - else { input.focus(); input.select(); try { document.execCommand('copy'); done(); } catch (e) { setFeedback('复制失败,请手动复制。', true); } } + var done = function () { notyf('推广链接已复制', 'success', 3, 'aksrt-affiliate-copy-toast'); }; + var fail = function () { input.focus(); input.select(); notyf('复制失败,请手动复制', 'danger', 3, 'aksrt-affiliate-copy-toast'); }; + if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(input.value).then(done).catch(fail); + else { input.focus(); input.select(); try { document.execCommand('copy'); done(); } catch (e) { fail(); } } }); } @@ -191,8 +190,10 @@ }); } + // 参考主题 poster-share.js 布局 1:1:768×1168,顶部主图占上半屏, + // 左上角日期,标题竖蓝线 + 标题,正文,底部带边框盒(logo/站名 + 二维码 + 链接)。 function buildPosterCanvas(url, poster, siteName) { - var width = 750, height = 1000; + var width = 768, height = 1168; var canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; @@ -212,69 +213,104 @@ loadImage = Promise.resolve(null); } return loadImage.then(function (image) { - // 顶部图(cover 裁切)或纯色占位。 - if (image) { - var ratio = Math.max(width / image.width, 360 / image.height); - var drawWidth = image.width * ratio; - var drawHeight = image.height * ratio; - ctx.drawImage(image, (width - drawWidth) / 2, 0, drawWidth, drawHeight, 0, 0, width, 360); + var imgHeight = height / 2; // 参考主题 rebate(非 product):顶部主图占上半屏。 + // 顶部主图(cover 裁切)或纯色占位。 + if (image && image.width && image.height) { + var rect = coverRect(width, imgHeight, image.width, image.height); + ctx.drawImage(image, rect.sx, rect.sy, rect.sWidth, rect.sHeight, 0, 0, width, imgHeight); } else { ctx.fillStyle = '#0f172a'; - ctx.fillRect(0, 0, width, 200); + ctx.fillRect(0, 0, width, imgHeight); } - var cursorY = image ? 420 : 280; - ctx.fillStyle = '#0f172a'; - ctx.font = 'bold 38px sans-serif'; - var title = String(poster.title || siteName || ''); - var titleLines = wrapText(ctx, title, width - 80).slice(0, 2); + // 左上角日期(白字,覆盖在主图上)。 + var now = new Date(); + var week = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']; + ctx.textBaseline = 'top'; + ctx.textAlign = 'left'; + ctx.fillStyle = 'rgba(255,255,255,0.95)'; + ctx.font = 'bold 35px Arial, sans-serif'; + ctx.fillText(week[now.getDay()], 40, 35); + ctx.font = '28px Arial, sans-serif'; + ctx.fillText((now.getMonth() + 1) + ' / ' + now.getDate(), 40, 88); + // 标题竖蓝线。 + ctx.strokeStyle = '#00a1ff'; + ctx.lineWidth = 7; + ctx.lineCap = 'round'; + ctx.beginPath(); + ctx.moveTo(40, imgHeight + 38); + ctx.lineTo(40, imgHeight + 74); + ctx.stroke(); + // 标题(粗体,最多 2 行)。 + ctx.fillStyle = '#424242'; + ctx.font = 'bold 35px Arial, sans-serif'; + var titleLines = wrapText(ctx, String(poster.title || siteName || ''), width - 100).slice(0, 2); + var cursorY = imgHeight + 40; for (var t = 0; t < titleLines.length; t++) { - ctx.fillText(titleLines[t], 40, cursorY); - cursorY += 56; + ctx.fillText(titleLines[t], 60, cursorY); + cursorY += 52; } - cursorY += 8; + // 正文(简介,最多 3 行)。 var description = String(poster.description || ''); if (description) { - ctx.fillStyle = '#475569'; - ctx.font = '26px sans-serif'; - var lines = wrapText(ctx, description, width - 80, 26).slice(0, 3); + ctx.fillStyle = '#585858'; + ctx.font = '25px Arial, sans-serif'; + var lines = wrapText(ctx, description, width - 80).slice(0, 3); + var contentY = imgHeight + 160; for (var i = 0; i < lines.length; i++) { - ctx.fillText(lines[i], 40, cursorY); - cursorY += 40; + ctx.fillText(lines[i], 40, contentY); + contentY += 38; } } - // 二维码 + 链接 + 站点名。 + // 底部带边框盒。 + ctx.strokeStyle = 'rgba(122,122,122,0.2)'; + ctx.lineWidth = 2; + ctx.strokeRect(30, height - 230, width - 60, 200); + // 站点名(logo 位,左侧)。 + ctx.fillStyle = '#424242'; + ctx.font = 'bold 30px Arial, sans-serif'; + ctx.fillText(String(siteName || ''), 80, height - 175); + ctx.fillStyle = '#94a3b8'; + ctx.font = '20px Arial, sans-serif'; + ctx.fillText('长按识别二维码访问', 80, height - 130); + // 二维码(右下)。 var qr = window.qrcode(0, 'M'); qr.addData(url); qr.make(); var count = qr.getModuleCount(); - var cell = 6, margin = 4; - var qrSize = (count + margin * 2) * cell; - var qrY = height - qrSize - 130; - ctx.fillStyle = '#ffffff'; - ctx.fillRect(40 - 8, qrY - 8, qrSize + 16, qrSize + 16); - ctx.strokeStyle = '#e2e8f0'; - ctx.strokeRect(40 - 8, qrY - 8, qrSize + 16, qrSize + 16); + var qrBox = 120; + var cell = qrBox / count; + var qrX = width - 200, qrY = height - 200; ctx.fillStyle = '#0f172a'; for (var r = 0; r < count; r++) { for (var c = 0; c < count; c++) { if (qr.isDark(r, c)) { - ctx.fillRect(40 + (c + margin) * cell, qrY + (r + margin) * cell, cell, cell); + ctx.fillRect(qrX + Math.floor(c * cell), qrY + Math.floor(r * cell), Math.ceil(cell), Math.ceil(cell)); } } } - ctx.fillStyle = '#475569'; - ctx.font = '24px sans-serif'; - ctx.fillText('扫描二维码访问', 40 + qrSize + 40, qrY + 40); - ctx.fillStyle = '#0f172a'; - ctx.font = 'bold 28px sans-serif'; - ctx.fillText(String(siteName || ''), 40 + qrSize + 40, qrY + 90); - ctx.fillStyle = '#94a3b8'; - ctx.font = '20px sans-serif'; - ctx.fillText(url.slice(0, 60), 40, height - 40); + // 底部链接(居中)。 + ctx.textAlign = 'center'; + ctx.fillStyle = '#b4b4b4'; + ctx.font = '22px Arial, sans-serif'; + ctx.fillText(String(url).slice(0, 46), width / 2, height - 46); + ctx.textAlign = 'left'; return canvas; }); } + // cover 裁切:把源图按短边铺满目标框并居中裁切。 + function coverRect(boxW, boxH, srcW, srcH) { + var sx = 0, sy = 0, sWidth = srcW, sHeight = srcH; + if (srcW / srcH > boxW / boxH) { + sWidth = (boxW * srcH) / boxH; + sx = (srcW - sWidth) / 2; + } else { + sHeight = (boxH * srcW) / boxW; + sy = (srcH - sHeight) / 2; + } + return { sx: sx, sy: sy, sWidth: sWidth, sHeight: sHeight }; + } + function wrapText(ctx, text, maxWidth) { var lines = []; var current = ''; diff --git a/inc/helpers/user-center-renderers/profile-revenue.php b/inc/helpers/user-center-renderers/profile-revenue.php index 23bc2ad5..bc39d3f1 100644 --- a/inc/helpers/user-center-renderers/profile-revenue.php +++ b/inc/helpers/user-center-renderers/profile-revenue.php @@ -175,11 +175,10 @@ $pane_is_active = static function (string $tab) use ($active_reward_tab): bool { -
%
-
+
diff --git a/tests/widget-system/referral-qr-contract.test.cjs b/tests/widget-system/referral-qr-contract.test.cjs index 8e9bc461..9b21404b 100644 --- a/tests/widget-system/referral-qr-contract.test.cjs +++ b/tests/widget-system/referral-qr-contract.test.cjs @@ -33,7 +33,8 @@ test('revenue detail card is a four-row info list with no standalone QR block', assert.match(template, /data-affiliate-link/) assert.match(template, /data-affiliate-copy/) assert.match(template, /data-affiliate-poster/) - assert.match(template, /data-affiliate-feedback/) + // 复制反馈走 Toast(对齐参考主题),不再有内联反馈元素。 + assert.doesNotMatch(template, /data-affiliate-feedback/) // 四行固定标签:推广链接 / 佣金比例 / 累计佣金 / 返佣详解。 for (const label of ['推广链接', '佣金比例', '累计佣金']) { assert.match(template, new RegExp('<\\?php [a-z_]+\\([^)]*' + label), 'detail row label ' + label) @@ -55,6 +56,9 @@ test('revenue detail card is a four-row info list with no standalone QR block', assert.match(script, /var url = input \? input\.value : '';/) assert.match(script, /if \(copy\) copy\.disabled = !url;/) assert.doesNotMatch(script, /input\.value = j\.url/) + // 复制反馈走 Toast(notyf),不再写内联文本。 + assert.match(script, /notyf\('推广链接已复制'/) + assert.doesNotMatch(script, /data-affiliate-feedback/) }) test('promotion link is server-rendered so it needs no round trip', () => { diff --git a/tools/admin-contract-report.json b/tools/admin-contract-report.json index 0c39a15a..4bb92897 100644 --- a/tools/admin-contract-report.json +++ b/tools/admin-contract-report.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-09-28T13:49:53+00:00", + "generated_at": "2026-09-28T13:58:32+00:00", "errors": [], "warnings": [ "customPage tools-update section 'tools/update' not in PHP schema (ok if pure operational page)",