fix(revenue): replicate the reference rebate poster canvas layers
The poster modal shell already matched the reference theme, but the canvas drawing diverged from zibll's poster-share.js rebate branch in four places: - No 推荐人 tags layer. The reference paints a red #fc4e1f 24px line at img_height+160; AKSRT dropped the layer entirely, so the poster lost the referrer attribution that identifies whose link it is. - The logo slot painted text. The reference contains the real logo image into (80, height-190, 300, 100); AKSRT drew the site name as a 30px string plus a hardcoded 长按识别二维码访问 caption in that slot. - Bottom copy was hardcoded. The reference reads share_desc; now sourced from the new pay_rebate_poster_desc_bottom field with share_desc fallback. - No banner fallback. The reference walks banner → banner_default → banner_spare; a single broken URL left the top half of the poster black. Also aligns the week label to the reference's 周 prefix (was 星期日) and ports drawTextOverflow for wrapping/truncation, which retires the local wrapText. Adds pay_rebate_poster_banner and pay_rebate_poster_desc_bottom to both the Vue and PHP schemas; rest_me now returns logo, compatible and referrer_name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ef15416ef7
commit
81b60eb427
7 files changed
+212
-95
No files matched your search
@@ -35,6 +35,8 @@ export const affiliateSchema = {
|
||||
pay_rebate_poster_img: '',
|
||||
pay_rebate_poster_title: '发现一个精品网站,快来看一看',
|
||||
pay_rebate_poster_desc: '网站有超多精品资源和优惠福利,扫码进入网站还可享受诸多优惠哦',
|
||||
pay_rebate_poster_banner: '',
|
||||
pay_rebate_poster_desc_bottom: '',
|
||||
pay_rebate_text_discount: '推荐购买 下单再减%discount%',
|
||||
},
|
||||
groups: [
|
||||
@@ -173,6 +175,8 @@ export const affiliateSchema = {
|
||||
{ key: 'pay_rebate_poster_img', label: '推广海报顶部图片', type: 'media', default: '', dependsOn: { key: 'pay_rebate_poster_s', value: true } },
|
||||
{ key: 'pay_rebate_poster_title', label: '推广海报标题(20-30个字)', type: 'input', default: '发现一个精品网站,快来看一看', dependsOn: { key: 'pay_rebate_poster_s', value: true } },
|
||||
{ key: 'pay_rebate_poster_desc', label: '推广海报简介(40-60个字)', type: 'input', default: '网站有超多精品资源和优惠福利,扫码进入网站还可享受诸多优惠哦', dependsOn: { key: 'pay_rebate_poster_s', value: true } },
|
||||
{ key: 'pay_rebate_poster_banner', label: '海报顶部备用图片', type: 'media', default: '', dependsOn: { key: 'pay_rebate_poster_s', value: true }, help: '顶部主图加载失败时依次回退到这里,再回退到主题内置图,避免海报顶部出现空白。' },
|
||||
{ key: 'pay_rebate_poster_desc_bottom', label: '海报底部文案', type: 'input', default: '扫描二维码阅读全文', dependsOn: { key: 'pay_rebate_poster_s', value: true }, help: '画在二维码下方居中位置,留空则使用默认文案。' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
+124
-86
@@ -139,7 +139,7 @@
|
||||
if (!r.ok) return;
|
||||
var j = r.json || {};
|
||||
var ratio = root.querySelector('[data-affiliate-ratio]'); if (ratio) ratio.textContent = String(j.current_ratio || 0) + '%';
|
||||
initAffiliatePoster(root, url || j.url || '', j.poster || {}, j.site_name || document.title);
|
||||
initAffiliatePoster(root, url || j.url || '', j.poster || {}, j.site_name || document.title, j.referrer_name || '');
|
||||
}).catch(function () { /* 海报与比例为增强项,静默失败即可。 */ });
|
||||
if (copy) copy.addEventListener('click', function () {
|
||||
if (!input || !input.value) return;
|
||||
@@ -151,10 +151,11 @@
|
||||
}
|
||||
|
||||
// ===== Referral poster =====
|
||||
// 弹窗形态 1:1 复刻参考主题 poster-share.js:Bootstrap 语义(.in 显隐 +
|
||||
// .modal-backdrop + body.modal-open + ESC),与下方充值弹窗同构,不复依赖
|
||||
// jQuery/Bootstrap 运行时。壳上带 .flex.jc,故打开态用 flex 而非 block。
|
||||
function initAffiliatePoster(root, url, poster, siteName) {
|
||||
/* 布局 1:1 复刻参考主题 poster-share.js 的 rebate 分支(非 product):
|
||||
* 768×1168,顶部主图占上半屏、左上角日期、标题竖线 + 标题、红色推荐人标签层、
|
||||
* 简介,底部带边框盒内左侧 contain 居中 logo 图 + 右下二维码,最底部居中文案。
|
||||
* 顶部主图按参考主题的三级回退加载(banner → banner_default → banner_spare)。 */
|
||||
function initAffiliatePoster(root, url, poster, siteName, referrerName) {
|
||||
var button = root.querySelector('[data-affiliate-poster]');
|
||||
var modal = document.querySelector('[data-aksrt-affiliate-poster-modal]');
|
||||
var stage = modal ? modal.querySelector('[data-affiliate-poster-stage]') : null;
|
||||
@@ -233,7 +234,7 @@
|
||||
if (download) download.classList.add('hide');
|
||||
openPosterModal();
|
||||
button.disabled = true;
|
||||
buildPosterCanvas(url, poster, siteName).then(function (built) {
|
||||
buildPosterCanvas(url, poster, siteName, referrerName).then(function (built) {
|
||||
canvas = built;
|
||||
if (!stage) return;
|
||||
// 参考主题:img.loaded-img 直接进 .poster-imgbox,遮罩淡出,
|
||||
@@ -270,89 +271,141 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 参考主题 poster-share.js 布局 1:1:768×1168,顶部主图占上半屏,
|
||||
// 左上角日期,标题竖蓝线 + 标题,正文,底部带边框盒(logo/站名 + 二维码 + 链接)。
|
||||
function buildPosterCanvas(url, poster, siteName) {
|
||||
// 三级回退加载图片:任一源成功即返回,全部失败才 reject(参考主题 loadCount 0→1→2)。
|
||||
function loadPosterImage(sources) {
|
||||
var list = (sources || []).filter(function (src) { return !!src; });
|
||||
return new Promise(function (resolve, reject) {
|
||||
var index = 0;
|
||||
function next() {
|
||||
if (index >= list.length) { reject(new Error('poster image unavailable')); return; }
|
||||
var image = new Image();
|
||||
image.crossOrigin = 'anonymous';
|
||||
image.onload = function () { resolve(image); };
|
||||
image.onerror = function () { index += 1; next(); };
|
||||
image.src = list[index];
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
// contain 裁切:参考主题 containImg(),等比缩放塞进盒子并居中,不裁切。
|
||||
function containRect(sx, sy, boxW, boxH, srcW, srcH) {
|
||||
var dx = sx, dy = sy, dWidth = boxW, dHeight = boxH;
|
||||
if (srcW > srcH || (srcW === srcH && boxW < boxH)) {
|
||||
dHeight = (srcH * dWidth) / srcW;
|
||||
dy = sy + (boxH - dHeight) / 2;
|
||||
} else if (srcW < srcH || (srcW === srcH && boxW > boxH)) {
|
||||
dWidth = (srcW * dHeight) / srcH;
|
||||
dx = sx + (boxW - dWidth) / 2;
|
||||
}
|
||||
return { x: dx, y: dy, width: dWidth, height: dHeight };
|
||||
}
|
||||
|
||||
// 参考主题 drawTextOverflow():按 maxWidth-120 换行、x 偏移 38,最多 maxRow 行,溢出补 ...。
|
||||
// 返回实际测量宽度,供价格/标签层做横向排版。
|
||||
function drawTextOverflow(ctx, text, maxWidth, maxRow, font, color, lineHeight, x, y) {
|
||||
var rows = [];
|
||||
var temp = '';
|
||||
var limit = maxWidth - 120;
|
||||
x = x + 38;
|
||||
text = String(text || '').replace(/[\r\n]/g, '');
|
||||
ctx.fillStyle = color;
|
||||
ctx.font = font;
|
||||
var chars = text.split('');
|
||||
var measured = 0;
|
||||
if (ctx.measureText(text).width <= limit) {
|
||||
rows.push(text);
|
||||
} else {
|
||||
for (var i = 0; i < chars.length; i++) {
|
||||
if (rows.length === maxRow && i < chars.length - 1) {
|
||||
rows[rows.length - 1] += '...';
|
||||
break;
|
||||
}
|
||||
if (ctx.measureText(temp).width < limit) {
|
||||
temp += chars[i];
|
||||
if (i === chars.length - 1) rows.push(temp);
|
||||
} else {
|
||||
i--; // 参考主题:回退一格防止字符丢失
|
||||
rows.push(temp);
|
||||
temp = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var r = 0; r < rows.length; r++) {
|
||||
measured = Math.max(measured, ctx.measureText(rows[r]).width);
|
||||
ctx.fillText(rows[r], x, y + r * lineHeight, limit);
|
||||
}
|
||||
return { width: measured, height: rows.length * lineHeight };
|
||||
}
|
||||
|
||||
// 参考主题 buildPosterCanvas rebate 分支:768×1168,顶部主图占上半屏,左上角日期,
|
||||
// 标题竖蓝线 + 标题,推荐人标签(红),简介,底部盒(logo + 二维码 + 底部文案)。
|
||||
function buildPosterCanvas(url, poster, siteName, referrerName) {
|
||||
var width = 768, height = 1168;
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
var loadImage = null;
|
||||
if (poster.image) {
|
||||
loadImage = new Promise(function (resolve) {
|
||||
var image = new Image();
|
||||
image.crossOrigin = 'anonymous';
|
||||
image.onload = function () { resolve(image); };
|
||||
image.onerror = function () { resolve(null); };
|
||||
image.src = poster.image;
|
||||
});
|
||||
} else {
|
||||
loadImage = Promise.resolve(null);
|
||||
}
|
||||
return loadImage.then(function (image) {
|
||||
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, imgHeight);
|
||||
}
|
||||
var imgHeight = height / 2; // rebate 分支:顶部主图占上半屏
|
||||
// 站点名走「周」前缀,与参考主题 zib__('week_prefix') 一致。
|
||||
var weekLabel = '周' + '日一二三四五六'.charAt(new Date().getDay());
|
||||
// 推荐人标签:参考主题 tags = `推荐人: {昵称}`,缺昵称时整层不画。
|
||||
var tags = referrerName ? '推荐人: ' + String(referrerName) : '';
|
||||
// 底部文案:优先推广海报配置,回退到分享设置 share_desc,再回退默认文案。
|
||||
var bottom = String(poster.bottom || '扫描二维码阅读全文');
|
||||
var title = String(poster.title || siteName || '');
|
||||
var description = String(poster.description || '');
|
||||
|
||||
return Promise.all([
|
||||
loadPosterImage([poster.image, poster.banner_default, poster.banner_spare]),
|
||||
poster.logo ? loadPosterImage([poster.logo]).catch(function () { return null; }) : Promise.resolve(null)
|
||||
]).then(function (assets) {
|
||||
var banner = assets[0], logo = assets[1];
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
// 顶部主图(cover 裁切)。
|
||||
var rect = coverRect(width, imgHeight, banner.width, banner.height);
|
||||
ctx.drawImage(banner, rect.sx, rect.sy, rect.sWidth, rect.sHeight, 0, 0, width, imgHeight);
|
||||
// 左上角日期(白字,覆盖在主图上)。
|
||||
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.fillStyle = 'rgba(255,255,255,1)';
|
||||
ctx.font = '35px Arial';
|
||||
ctx.fillText(weekLabel, 30, 37);
|
||||
ctx.font = '28px Arial';
|
||||
ctx.fillText((new Date().getMonth() + 1) + ' / ' + new Date().getDate(), 30, 87);
|
||||
// 标题竖蓝线。
|
||||
ctx.strokeStyle = '#00a1ff';
|
||||
ctx.lineWidth = 7;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.beginPath();
|
||||
ctx.lineCap = 'round';
|
||||
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], 60, cursorY);
|
||||
cursorY += 52;
|
||||
// 标题层(粗体)。参考 rebate 分支把 $title 子画布置于 (20, img_height+40),
|
||||
// 子画布内左对齐 x=40、文字基线偏移 2,故绝对坐标 (40, img_height+42);
|
||||
// drawTextOverflow 内部会 +38,所以这里传 2。
|
||||
drawTextOverflow(ctx, title, width, 2, 'bold 35px Arial', '#424242', 52, 2, imgHeight + 42);
|
||||
// 推荐人标签层(红,最多 1 行):$tags 置于 (0, img_height+160)。
|
||||
if (tags) {
|
||||
drawTextOverflow(ctx, tags, width, 1, '24px Arial', '#fc4e1f', 36, 2, imgHeight + 162);
|
||||
}
|
||||
// 正文(简介,最多 3 行)。
|
||||
var description = String(poster.description || '');
|
||||
// 简介层(灰):$content 置于 (0, img_height+220)。
|
||||
if (description) {
|
||||
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, contentY);
|
||||
contentY += 38;
|
||||
}
|
||||
drawTextOverflow(ctx, description, width, 4, '25px Arial', 'rgba(88,88,88,1)', 39, 2, imgHeight + 222);
|
||||
}
|
||||
// 底部带边框盒。
|
||||
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);
|
||||
// 二维码(右下)。
|
||||
ctx.beginPath();
|
||||
ctx.rect(30, height - 230, width - 60, 200);
|
||||
ctx.stroke();
|
||||
// logo 图:contain 居中,缺图时整块不画(参考主题 logo.naturalWidth 守卫)。
|
||||
if (logo && logo.width && logo.height) {
|
||||
var logoRect = containRect(80, height - 190, 300, 100, logo.width, logo.height);
|
||||
ctx.drawImage(logo, logoRect.x, logoRect.y, logoRect.width, logoRect.height);
|
||||
}
|
||||
// 二维码(右下角)。
|
||||
var qr = window.qrcode(0, 'M');
|
||||
qr.addData(url);
|
||||
qr.make();
|
||||
@@ -368,11 +421,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
// 底部链接(居中)。
|
||||
// 底部文案(居中,盒下方)。
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillStyle = '#b4b4b4';
|
||||
ctx.font = '22px Arial, sans-serif';
|
||||
ctx.fillText(String(url).slice(0, 46), width / 2, height - 46);
|
||||
ctx.font = '22px Arial';
|
||||
ctx.fillText(String(bottom).slice(0, 46), width / 2, height - 58);
|
||||
ctx.textAlign = 'left';
|
||||
return canvas;
|
||||
});
|
||||
@@ -391,21 +444,6 @@
|
||||
return { sx: sx, sy: sy, sWidth: sWidth, sHeight: sHeight };
|
||||
}
|
||||
|
||||
function wrapText(ctx, text, maxWidth) {
|
||||
var lines = [];
|
||||
var current = '';
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
if (ctx.measureText(current + text[i]).width > maxWidth && current) {
|
||||
lines.push(current);
|
||||
current = text[i];
|
||||
} else {
|
||||
current += text[i];
|
||||
}
|
||||
}
|
||||
if (current) lines.push(current);
|
||||
return lines;
|
||||
}
|
||||
|
||||
window.AKSRTAffiliateRevenueInit = initAffiliateRevenue;
|
||||
|
||||
// ===== VIP 开通 =====
|
||||
|
||||
@@ -421,6 +421,8 @@ class SchemaPayment {
|
||||
['key' => 'pay_rebate_poster_img', 'label' => '推广海报顶部图片', 'type' => 'media', 'default' => '', 'dependsOn' => ['key' => 'pay_rebate_poster_s', 'value' => true]],
|
||||
['key' => 'pay_rebate_poster_title', 'label' => '推广海报标题(20-30个字)', 'type' => 'input', 'default' => '发现一个精品网站,快来看一看', 'dependsOn' => ['key' => 'pay_rebate_poster_s', 'value' => true]],
|
||||
['key' => 'pay_rebate_poster_desc', 'label' => '推广海报简介(40-60个字)', 'type' => 'input', 'default' => '网站有超多精品资源和优惠福利,扫码进入网站还可享受诸多优惠哦', 'dependsOn' => ['key' => 'pay_rebate_poster_s', 'value' => true]],
|
||||
['key' => 'pay_rebate_poster_banner', 'label' => '海报顶部备用图片', 'type' => 'media', 'default' => '', 'dependsOn' => ['key' => 'pay_rebate_poster_s', 'value' => true], 'help' => '顶部主图加载失败时依次回退到这里,再回退到主题内置图,避免海报顶部出现空白。'],
|
||||
['key' => 'pay_rebate_poster_desc_bottom', 'label' => '海报底部文案', 'type' => 'input', 'default' => '扫描二维码阅读全文', 'dependsOn' => ['key' => 'pay_rebate_poster_s', 'value' => true], 'help' => '画在二维码下方居中位置,留空则使用默认文案。'],
|
||||
]],
|
||||
['key' => 'discount', 'title' => '推广让利标签', 'fields' => [
|
||||
['key' => 'discount_notice', 'label' => '', 'type' => 'content', 'ui_only' => true, 'content' => '<p><b>推广让利标签配置详解:</b></p><ul style="margin:6px 0 0 18px;line-height:1.9"><li>可使用的变量:折扣 <code>%discount%</code>、推荐人姓名 <code>%referrer_name%</code></li><li>使用变量示例:<code>%referrer_name%推荐购买 下单再减%discount%元</code></li></ul>', 'dependsOn' => ['key' => 'pay_rebate_s', 'value' => true]],
|
||||
|
||||
@@ -348,12 +348,25 @@ final class ReferralAttribution {
|
||||
$affiliate = \AKSRT\Settings\Settings::get_affiliate_settings();
|
||||
$registration = $this->registration_settings();
|
||||
$reward = $this->reward_settings();
|
||||
$poster = (array) ($affiliate['poster'] ?? ['enabled' => false]);
|
||||
// 参考主题 rebate 海报的 logo 与底部文案取自分享设置,不在推广海报字段里重复配置。
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$share = \AKSRT\Settings\Settings::get_share_settings();
|
||||
$poster['logo'] = esc_url_raw((string) ($share['poster_logo'] ?? ''));
|
||||
$poster['compatible'] = !empty($share['compatible_s']);
|
||||
if (($poster['bottom'] ?? '') === '') {
|
||||
$poster['bottom'] = sanitize_text_field((string) ($share['share_desc'] ?? ''));
|
||||
}
|
||||
}
|
||||
$user = get_userdata($user_id);
|
||||
return new \WP_REST_Response([
|
||||
'ref' => $link['ref'],
|
||||
'url' => $link['url'],
|
||||
'status' => $link['status'],
|
||||
'site_name' => get_bloginfo('name'),
|
||||
'poster' => (array) ($affiliate['poster'] ?? ['enabled' => false]),
|
||||
// 参考主题 tags:`推荐人: {昵称}`,画在海报主图下方的独立红色标签层。
|
||||
'referrer_name' => $user instanceof WP_User ? $user->display_name : '',
|
||||
'poster' => $poster,
|
||||
'features' => [
|
||||
'registration' => !empty($registration['enabled']),
|
||||
'points_reward' => !empty($reward['enabled']),
|
||||
|
||||
@@ -328,6 +328,9 @@ class PaymentRuntime {
|
||||
'image' => esc_url_raw((string) ($settings['pay_rebate_poster_img'] ?? '')),
|
||||
'title' => sanitize_text_field((string) ($settings['pay_rebate_poster_title'] ?? '')),
|
||||
'description' => sanitize_text_field((string) ($settings['pay_rebate_poster_desc'] ?? '')),
|
||||
'banner_default' => esc_url_raw((string) ($settings['pay_rebate_poster_banner'] ?? '')),
|
||||
'banner_spare' => get_template_directory_uri() . '/assets/img/share_img.jpg',
|
||||
'bottom' => sanitize_text_field((string) ($settings['pay_rebate_poster_desc_bottom'] ?? '')),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -100,16 +100,73 @@ test('poster canvas is built from admin-configured copy and gated on the poster
|
||||
assert.match(runtime, /pay_rebate_poster_desc/)
|
||||
|
||||
const attribution = read('src', 'Payment', 'ReferralAttribution.php')
|
||||
assert.match(attribution, /'poster' => \(array\) \(\$affiliate\['poster'\] \?\? \['enabled' => false\]\)/)
|
||||
// 推广海报 payload:开关 / 主图 / 标题 / 简介 / 两级备用横幅 / 底部文案。
|
||||
assert.match(attribution, /\$poster = \(array\) \(\$affiliate\['poster'\] \?\? \['enabled' => false\]\)/)
|
||||
assert.match(attribution, /'poster' => \$poster/)
|
||||
// 参考主题 rebate 的 logo 与底部文案取自分享设置,不在推广字段里重复配置。
|
||||
assert.match(attribution, /\$poster\['logo'\] = esc_url_raw/)
|
||||
assert.match(attribution, /\$poster\['compatible'\] = !empty\(\$share\['compatible_s'\]\)/)
|
||||
assert.match(attribution, /\$poster\['bottom'\] = sanitize_text_field/)
|
||||
// 参考主题 tags 层:`推荐人: {昵称}`。
|
||||
assert.match(attribution, /'referrer_name' => \$user instanceof WP_User \? \$user->display_name/)
|
||||
assert.match(attribution, /'site_name' => get_bloginfo\('name'\)/)
|
||||
|
||||
const script = read('assets', 'js', 'credits.js')
|
||||
// 海报仅在开关开启时暴露按钮;绘制含顶部图、文案换行、二维码与站点名。
|
||||
assert.match(script, /initAffiliatePoster\(root, url \|\| j\.url \|\| '', j\.poster \|\| \{\}, j\.site_name \|\| document\.title\)/)
|
||||
// 海报仅在开关开启时暴露按钮;推荐人昵称也由 REST 下发。
|
||||
assert.match(script, /initAffiliatePoster\(root, url \|\| j\.url \|\| '', j\.poster \|\| \{\}, j\.site_name \|\| document\.title, j\.referrer_name \|\| ''\)/)
|
||||
assert.match(script, /poster\.enabled !== true\) return/)
|
||||
assert.match(script, /function buildPosterCanvas/)
|
||||
assert.match(script, /function wrapText\(ctx, text, maxWidth\)/)
|
||||
assert.match(script, /image\.crossOrigin = 'anonymous'/)
|
||||
|
||||
// 顶部主图三级回退,对齐参考主题 banner → banner_default → banner_spare。
|
||||
const posterRuntime = read('src', 'Settings', 'PaymentRuntime.php')
|
||||
assert.match(posterRuntime, /'banner_default' => esc_url_raw\(\(string\) \(\$settings\['pay_rebate_poster_banner'\] \?\? ''\)\)/)
|
||||
assert.match(posterRuntime, /'banner_spare' => get_template_directory_uri\(\) \. '\/assets\/img\/share_img\.jpg'/)
|
||||
assert.match(posterRuntime, /'bottom' => sanitize_text_field\(\(string\) \(\$settings\['pay_rebate_poster_desc_bottom'\] \?\? ''\)\)/)
|
||||
// 备用图必须是真实存在的资源,否则回退到 404 等于没回退。
|
||||
assert.ok(fs.existsSync(path.join(root, 'assets', 'img', 'share_img.jpg')), 'assets/img/share_img.jpg must be committed')
|
||||
|
||||
// 新增的后台字段必须在 SPA schema 与 PHP schema 双侧注册,否则 contract audit 会红。
|
||||
const vue = read('assets', 'admin', 'src', 'schema', 'sections', 'payment', 'affiliate.js')
|
||||
const php = read('src', 'Admin', 'Schema', 'SchemaPayment.php')
|
||||
for (const key of ['pay_rebate_poster_banner', 'pay_rebate_poster_desc_bottom']) {
|
||||
assert.match(vue, new RegExp("key: '" + key + "'"), 'vue schema ' + key)
|
||||
assert.match(php, new RegExp("'key' => '" + key + "'"), 'php schema ' + key)
|
||||
}
|
||||
})
|
||||
|
||||
test('poster canvas layers match the reference rebate branch 1:1', () => {
|
||||
const script = read('assets', 'js', 'credits.js')
|
||||
const body = script.slice(script.indexOf('function buildPosterCanvas'), script.indexOf('// cover 裁切'))
|
||||
|
||||
// 参考主题 rebate 分支用 drawTextOverflow 的换行/截断,不是自研 wrapText。
|
||||
assert.match(script, /function drawTextOverflow\(ctx, text, maxWidth, maxRow, font, color, lineHeight, x, y\)/)
|
||||
assert.doesNotMatch(script, /function wrapText\(/, 'wrapText is dead once drawTextOverflow lands')
|
||||
assert.doesNotMatch(script, /wrapText\(/)
|
||||
|
||||
// logo 用 contain 裁切画真实图片,不再把站名当文本画在 logo 位。
|
||||
assert.match(script, /function containRect\(sx, sy, boxW, boxH, srcW, srcH\)/)
|
||||
assert.match(body, /containRect\(80, height - 190, 300, 100, logo\.width, logo\.height\)/)
|
||||
assert.doesNotMatch(body, /长按识别二维码访问/, 'hardcoded bottom copy must come from config')
|
||||
assert.doesNotMatch(body, /ctx\.fillText\(String\(siteName/)
|
||||
assert.match(body, /poster\.bottom \|\| '扫描二维码阅读全文'/)
|
||||
|
||||
// 推荐人标签层:参考主题 img_height+172 的红色 24px 层。
|
||||
assert.match(body, /'推荐人: ' \+ String\(referrerName\)/)
|
||||
assert.match(body, /drawTextOverflow\(ctx, tags, width, 1, '24px Arial', '#fc4e1f', 36, 2, imgHeight \+ 162\)/)
|
||||
|
||||
// 站点名走「周」前缀(参考主题 zib__('week_prefix'))。
|
||||
assert.match(body, /'周' \+ '日一二三四五六'\.charAt\(new Date\(\)\.getDay\(\)\)/)
|
||||
assert.doesNotMatch(body, /\['星期日', '星期一'/)
|
||||
|
||||
// 层叠坐标必须与参考主题 rebate 分支一致:$title 于 (20, img+40)、
|
||||
// $tags 于 (0, img+160)、$content 于 (0, img+220);子画布左对齐 x=40、基线偏移 2,
|
||||
// drawTextOverflow 内部 +38,故 x 参数传 2。
|
||||
assert.match(body, /drawTextOverflow\(ctx, title, width, 2, 'bold 35px Arial', '#424242', 52, 2, imgHeight \+ 42\)/)
|
||||
assert.match(body, /drawTextOverflow\(ctx, tags, width, 1, '24px Arial', '#fc4e1f', 36, 2, imgHeight \+ 162\)/)
|
||||
assert.match(body, /drawTextOverflow\(ctx, description, width, 4, '25px Arial', 'rgba\(88,88,88,1\)', 39, 2, imgHeight \+ 222\)/)
|
||||
// 底部文案基线:参考主题 $description 子画布置于 height-60,内部基线偏移 2。
|
||||
assert.match(body, /height - 58/)
|
||||
})
|
||||
|
||||
test('poster modal markup is a 1:1 replica of the reference poster-share dialog', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated_at": "2026-10-13T12:53:18+00:00",
|
||||
"generated_at": "2026-10-21T13:04:45+00:00",
|
||||
"errors": [],
|
||||
"warnings": [
|
||||
"customPage tools-update section 'tools/update' not in PHP schema (ok if pure operational page)",
|
||||
@@ -252,7 +252,7 @@
|
||||
"entry": true,
|
||||
"missing_chunks": [],
|
||||
"chunks": [
|
||||
"SchemaPage.BrFz57tJ.js",
|
||||
"SchemaPage.CD_W16f3.js",
|
||||
"vendor-vue.BqIbVHit.js",
|
||||
"FaIcon.BMA0TNWc.js",
|
||||
"AksMediaField.C9RRDn2V.js",
|
||||
@@ -268,8 +268,8 @@
|
||||
"FloatingButtonsSettings.DM1NXEcr.js",
|
||||
"PageHeader.UhySXCpq.js",
|
||||
"FooterTabbarSettings.8D3P8zNv.js",
|
||||
"PostPageSettings.B5Sm-lcm.js",
|
||||
"CommentSettings.DiQrTo4p.js",
|
||||
"PostPageSettings.BjFJtDVl.js",
|
||||
"CommentSettings.DxYhkUD2.js",
|
||||
"PermissionsCapabilities.CnQfjtxC.js",
|
||||
"CmsCredits.Dh2b9uHb.js",
|
||||
"ThemeInfoPage.zYktV7qz.js",
|
||||
|
||||
Reference in new issue
Block a user