/** * Jio affiliate revenue center + referral poster (split from credits.js). * Exposes window.JioAffiliateRevenueInit for the user-center router. * * @package Jio */ (function () { "use strict"; function api(path, options) { return window.JioRequest.request(path, options || {}).then(function (response) { return { ok: response.ok, status: response.status, json: response.json || {}, data: response.data }; }).catch(function (error) { var response = error && error.response ? error.response : {}; return { ok: false, status: (error && error.status) || response.status || 0, json: (error && error.json) || response.json || {} }; }); } // ===== Forward-only affiliate revenue center ===== /* 推广链接由 PHP 直出,首屏即可见、可复制;这里只补异步才拿得到的 * 海报配置与佣金比例,取失败也不影响复制。 */ function initAffiliateRevenue() { var root = document.querySelector('[data-jio-revenue-page]'); if (!root || root.dataset.affiliateBound) return; root.dataset.affiliateBound = '1'; var input = root.querySelector('[data-affiliate-link]'); var copy = root.querySelector('[data-affiliate-copy]'); var url = input ? input.value : ''; if (copy) copy.disabled = !url; api('/revenue/me').then(function (r) { 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, j.referrer_name || ''); }).catch(function () { /* 海报与比例为增强项,静默失败即可。 */ }); if (copy) copy.addEventListener('click', function () { if (!input || !input.value) return; var done = function () { notyf('推广链接已复制', 'success', 3, 'jio-affiliate-copy-toast'); }; var fail = function () { input.focus(); input.select(); notyf('复制失败,请手动复制', 'danger', 3, 'jio-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(); } } }); } // ===== Referral poster ===== /* 布局 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-jio-affiliate-poster-modal]'); var stage = modal ? modal.querySelector('[data-affiliate-poster-stage]') : null; var loading = modal ? modal.querySelector('[data-affiliate-poster-loading]') : null; var content = modal ? modal.querySelector('.modal-content') : null; var download = modal ? modal.querySelector('[data-affiliate-poster-download]') : null; if (!button || !modal) return; if (!url || !poster || poster.enabled !== true) return; button.hidden = false; var backdrop = null; var canvas = null; function openPosterModal() { if (modal.classList.contains('in')) return; modal.style.display = 'flex'; void modal.offsetWidth; // 强制 reflow,让 .fade 过渡从 scale(0.9) 起手 modal.classList.add('in'); modal.setAttribute('aria-hidden', 'false'); if (!backdrop) { backdrop = document.createElement('div'); backdrop.className = 'modal-backdrop fade in'; backdrop.setAttribute('data-affiliate-poster-backdrop', '1'); document.body.appendChild(backdrop); } document.body.classList.add('modal-open'); } function closePosterModal() { if (!modal.classList.contains('in')) return; modal.classList.remove('in'); modal.setAttribute('aria-hidden', 'true'); document.body.classList.remove('modal-open'); if (backdrop && backdrop.parentNode) backdrop.parentNode.removeChild(backdrop); backdrop = null; // 等过渡结束再撤 display,复刻 Bootstrap 的 fade-out 收尾 window.setTimeout(function () { if (!modal.classList.contains('in')) modal.style.display = ''; }, 300); } modal.addEventListener('click', function (event) { // 点遮罩层(.modal 自身)或关闭按钮(data-dismiss="modal") if (event.target === modal || (event.target.closest && event.target.closest('[data-dismiss="modal"]'))) { closePosterModal(); } }); document.addEventListener('keydown', function (event) { if (event.key === 'Escape' && modal.classList.contains('in')) closePosterModal(); }); if (download) { download.addEventListener('click', function () { if (!canvas) return; try { var link = document.createElement('a'); link.download = 'referral-poster.png'; link.href = canvas.toDataURL('image/png'); document.body.appendChild(link); link.click(); link.remove(); } catch (err) { /* Canvas is same-origin generated; download can only fail if the browser blocks it. */ } }); } button.addEventListener('click', function () { if (typeof window.qrcode !== 'function') { if (stage) { stage.innerHTML = '
' + '二维码组件未加载。' + '
'; } openPosterModal(); return; } if (stage) { stage.innerHTML = ''; stage.classList.remove('loaded'); } if (loading) loading.style.display = ''; if (download) download.classList.add('hide'); openPosterModal(); button.disabled = true; buildPosterCanvas(url, poster, siteName, referrerName).then(function (built) { canvas = built; if (!stage) return; // 参考主题:img.loaded-img 直接进 .poster-imgbox,遮罩淡出, // 内容块高度从 220px 过渡到海报实际高度后清掉内联样式。 var img = document.createElement('img'); img.src = canvas.toDataURL('image/png'); img.alt = '推广海报'; img.className = 'loaded-img'; // 参考主题按 0.7 比例预设尺寸;.poster-imgbox > img 的 max-width:360px // 会在窄屏把它压回弹窗宽度,height:100% 让 offsetHeight 反映真实渲染高度。 img.width = 768 * 0.7; img.height = 1168 * 0.7; stage.appendChild(img); stage.classList.add('loaded'); if (download) { download.href = img.src; download.classList.remove('hide'); } if (loading) loading.style.display = 'none'; if (content) { content.style.height = stage.offsetHeight + 'px'; content.style.transition = '.2s'; window.setTimeout(function () { content.style.height = ''; content.style.overflow = ''; content.style.transition = ''; }, 200); } }).catch(function () { if (stage) { stage.innerHTML = '' + '海报生成失败,请重试。' + '
'; } }).finally(function () { button.disabled = false; }); }); } // 三级回退加载图片:任一源成功即返回,全部失败才 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'); var imgHeight = height / 2; // rebate 分支:顶部主图占上半屏 // 站点名走「周」前缀。 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); // 左上角日期(白字,覆盖在主图上)。 ctx.textBaseline = 'top'; ctx.textAlign = 'left'; 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.beginPath(); ctx.lineCap = 'round'; ctx.moveTo(40, imgHeight + 38); ctx.lineTo(40, imgHeight + 74); ctx.stroke(); // 标题层(粗体)。参考 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); } // 简介层(灰):$content 置于 (0, img_height+220)。 if (description) { 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.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(); var count = qr.getModuleCount(); 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(qrX + Math.floor(c * cell), qrY + Math.floor(r * cell), Math.ceil(cell), Math.ceil(cell)); } } } // 底部文案(居中,盒下方)。 ctx.textAlign = 'center'; ctx.fillStyle = '#b4b4b4'; ctx.font = '22px Arial'; ctx.fillText(String(bottom).slice(0, 46), width / 2, height - 58); 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 }; } window.JioAffiliateRevenueInit = initAffiliateRevenue; jQuery(document).off("auto_fun.jio-credits-affiliate").on("auto_fun.jio-credits-affiliate", initAffiliateRevenue); })();