fix(revenue): align detail card, poster modal and copy feedback with reference

- 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
This commit is contained in:
2026-09-28 21:58:43 +08:00
parent 0374de3fec
commit fa53a0f4f9
4 changed files with 87 additions and 48 deletions
+80 -44
View File
@@ -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 = '';
@@ -175,11 +175,10 @@ $pane_is_active = static function (string $tab) use ($active_reward_tab): bool {
<input type="text" readonly data-affiliate-link value="<?php echo esc_attr($affiliate_link_url); ?>" aria-label="<?php esc_attr_e('推广链接', 'aksrt'); ?>">
<button type="button" class="btn-primary btn-sm" data-affiliate-copy><?php esc_html_e('复制链接', 'aksrt'); ?></button>
<?php if (!empty($affiliate['poster']['enabled'])) : ?><button type="button" class="btn-outline btn-sm" data-affiliate-poster hidden><?php esc_html_e('推广海报', 'aksrt'); ?></button><?php endif; ?>
<small data-affiliate-feedback role="status" aria-live="polite"></small>
</div>
<?php if ($affiliate_on) : ?>
<div class="uc-revenue-summary-row"><span><?php esc_html_e('佣金比例', 'aksrt'); ?></span><strong data-affiliate-ratio><?php echo esc_html((string) $commission_ratio); ?>%</strong></div>
<div class="uc-revenue-summary-row uc-revenue-summary-row--total"><span><?php esc_html_e('累计佣金', 'aksrt'); ?></span><div><strong><?php printf(esc_html__('累计 %s 元', 'aksrt'), esc_html($total_amount)); ?></strong><strong><?php printf(esc_html__('可提现 %s 元', 'aksrt'), esc_html($pool_amount)); ?></strong><strong><?php printf(esc_html__('已提现 %s 元', 'aksrt'), esc_html($withdrawn_amount)); ?></strong><?php if (($commission_stats['debt'] ?? '0.00') !== '0.00') : ?><strong><?php printf(esc_html__('待偿欠款 %s 元', 'aksrt'), esc_html($debt_amount)); ?></strong><?php endif; ?></div></div>
<div class="uc-revenue-summary-row uc-revenue-summary-row--total"><span><?php esc_html_e('累计佣金', 'aksrt'); ?></span><div><strong><?php printf(esc_html__('累计 %s 元', 'aksrt'), esc_html($total_amount)); ?></strong><strong><?php printf(esc_html__('已提现 %s 元', 'aksrt'), esc_html($withdrawn_amount)); ?></strong><?php if (($commission_stats['debt'] ?? '0.00') !== '0.00') : ?><strong><?php printf(esc_html__('待偿欠款 %s 元', 'aksrt'), esc_html($debt_amount)); ?></strong><?php endif; ?></div></div>
<div class="uc-revenue-summary-row uc-revenue-summary-row--explain"><span><?php echo esc_html($details_title); ?></span><div class="uc-reward-richtext"><?php echo wp_kses_post(wpautop($details_text)); ?></div></div>
<?php endif; ?>
</div>
@@ -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('<span><\\?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', () => {
+1 -1
View File
@@ -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)",