/**
* AKSRT CMS Download
*
* 功能:
* - 监听 [data-action="aksrt-download"] 按钮点击
* - 调 POST /aksrt/v1/download/request
* - 弹窗显示下载链接(含提取码)
*
* 依赖:
* - window.aksrtData.{ ajaxUrl, nonce, siteUrl }
* - 主题已加载 fetch (现代浏览器)
*/
(function () {
'use strict';
if (!window.aksrtData) return;
const REST_BASE = window.aksrtData.siteUrl ? window.aksrtData.siteUrl.replace(/\/$/, '') + '/wp-json/aksrt/v1' : '/wp-json/aksrt/v1';
function tt(key, zh) { return zh; }
function escapeHtml(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function escapeAttr(value) {
return escapeHtml(value);
}
// ===== Focus trap state =====
let lastFocusedElement = null;
function getFocusableElements(container) {
if (!container) return [];
return Array.prototype.slice.call(container.querySelectorAll(
'a[href], area[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]),' +
' select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
)).filter(function (el) {
return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
});
}
function trapFocus(e, modal) {
if (e.key !== 'Tab') return;
const focusables = getFocusableElements(modal);
if (!focusables.length) {
e.preventDefault();
return;
}
const first = focusables[0];
const last = focusables[focusables.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
// ===== Modal helper =====
function buildModal() {
let modal = document.getElementById('aksrt-dl-modal');
if (modal) return modal;
modal = document.createElement('div');
modal.id = 'aksrt-dl-modal';
modal.className = 'aksrt-dl-modal';
modal.setAttribute('role', 'dialog');
modal.setAttribute('aria-modal', 'true');
modal.setAttribute('aria-hidden', 'true');
modal.setAttribute('aria-labelledby', 'aksrt-dl-modal-title');
modal.innerHTML =
'
' +
'';
document.body.appendChild(modal);
modal.addEventListener('click', function (e) {
if (e.target.matches('[data-close]')) close();
});
modal.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && modal.classList.contains('is-open')) {
e.preventDefault();
close();
} else if (e.key === 'Tab' && modal.classList.contains('is-open')) {
trapFocus(e, modal);
}
});
return modal;
}
function open(html, returnFocusEl) {
// 记录调用方按钮以便关闭后回焦
lastFocusedElement = returnFocusEl || document.activeElement;
const modal = buildModal();
const body = modal.querySelector('[data-body]');
// 给标题加 id,与 aria-labelledby 联动
body.innerHTML = html.replace(/aksrt-dl-modal__title/, 'aksrt-dl-modal__title" id="aksrt-dl-modal-title');
modal.classList.add('is-open');
modal.setAttribute('aria-hidden', 'false');
document.body.classList.add('aksrt-dl-modal-open');
// 进入后将焦点放在第一个可聚焦元素,否则放 panel
setTimeout(function () {
const focusables = getFocusableElements(modal);
if (focusables.length) {
focusables[0].focus();
} else {
modal.querySelector('.aksrt-dl-modal__panel').focus();
}
}, 0);
}
function close() {
const modal = document.getElementById('aksrt-dl-modal');
if (!modal) return;
modal.classList.remove('is-open');
modal.setAttribute('aria-hidden', 'true');
document.body.classList.remove('aksrt-dl-modal-open');
// 焦点回到触发按钮
if (lastFocusedElement && typeof lastFocusedElement.focus === 'function') {
try { lastFocusedElement.focus(); } catch (_) { /* ignore */ }
}
lastFocusedElement = null;
}
// ===== 错误提示渲染 =====
function renderError(msg, data, returnFocusEl, code) {
let title = tt('failed', '下载失败');
let extra = '';
if (code === 'aksrt_need_login' && data && data.login_url) {
title = tt('need_login', '请先登录');
msg = tt('login_hint', '登录后即可继续下载,访客不可使用此资源。');
extra = '' + tt('go_login', '前往登录') + '';
} else if (code === 'aksrt_need_purchase') {
title = tt('need_purchase', '请先购买');
msg = msg || tt('purchase_hint', '请先完成购买后再下载该资源。');
extra = '';
} else if (code === 'aksrt_need_vip') {
title = tt('need_vip', '会员专属');
msg = msg || tt('vip_hint', '该资源仅限会员购买/下载。');
if (data && data.vip_url) {
extra = '' + tt('go_vip', '开通会员') + '';
}
} else if (code === 'aksrt_credits_insufficient' || code === 'aksrt_balance_insufficient') {
title = tt('credit_low', '积分不够');
const balance = typeof (data && data.balance) === 'number' ? data.balance : 0;
const cost = typeof (data && data.cost) === 'number' ? data.cost : 0;
msg = tt('credit_hint',
'当前余额 ' + balance + ' 积分,需要 ' + cost + ' 积分。可通过签到、阅读、邀请好友等方式获取积分。'
);
if (data && data.credits_url) {
extra = '' + tt('my_points', '我的积分') + '';
}
} else if (code === 'aksrt_download_limit') {
title = tt('quota_over', '今日免费额度已用完');
msg = msg || tt('quota_hint', '您今日下载免费资源个数已超限,请明日再下载。');
if (data && data.allow_limit_over && data.post_id) {
extra = '';
}
} else if (code === 'aksrt_captcha_failed') {
title = tt('captcha_fail', '验证失败');
msg = msg || tt('captcha_retry', '人机验证失败,请重试。');
} else if (data && data.login_url) {
extra = '' + tt('go_login', '前往登录') + '';
}
open(
'' + title + '
' +
'' + (msg || '') + '
' +
(extra ? '' + extra + '
' : ''),
returnFocusEl
);
const openCashierBtn = document.querySelector('[data-aksrt-open-cashier]');
if (openCashierBtn) {
openCashierBtn.addEventListener('click', function () {
const pid = parseInt(openCashierBtn.getAttribute('data-aksrt-open-cashier'), 10) || 0;
const pt = openCashierBtn.getAttribute('data-price-type') || '';
close();
if (pid) openCashier(pid, returnFocusEl, pt);
});
}
}
// ===== 成功渲染 =====
function renderSuccess(payload, returnFocusEl) {
const code = String(payload.extract_code || '').trim();
const has = !!payload.has_extract || code !== '';
const safeCode = escapeHtml(code);
const safeCodeAttr = escapeAttr(code);
const dlUrl = escapeAttr(payload.dl_url || '#');
const extractHtml = code !== ''
? '' + tt('extract', '提取码') + '' + safeCode + '
'
: (has ? '' + tt('extract_missing', '下载源标记了提取码,但接口未返回具体内容,请检查资源下载源的 extract_code 字段。') + '
' : '');
open(
'' + tt('ready', '下载已就绪') + '
' +
(extractHtml || '' + tt('hint', '点击下方按钮开始下载,链接 10 分钟内有效。') + '
') +
'' +
'
' + tt('go', '立即下载') + '' +
(typeof payload.balance === 'number' ? '
' + tt('balance', '余额:') + payload.balance + '' : '') +
'
',
returnFocusEl
);
// 复制按钮
document.querySelectorAll('[data-copy]').forEach(function (btn) {
btn.addEventListener('click', function () {
const text = btn.getAttribute('data-copy') || '';
const label = btn.querySelector('.aksrt-dl-modal__copy-text') || btn;
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(function () {
label.textContent = tt('copied', '已复制');
setTimeout(function () { label.textContent = tt('copy', '复制'); }, 2000);
});
}
});
});
}
// ===== 主流程 =====
function collectCaptchaPayload(sourceEl) {
// 优先从触发按钮所属的下载卡片中查找验证码容器(支持双下载框场景:侧栏+移动端)
var container = null;
if (sourceEl) {
var box = sourceEl.closest('.resource-download-box');
if (box) container = box.querySelector('[data-aksrt-download-captcha]');
}
// 回退到全局查找
if (!container) container = document.querySelector('[data-aksrt-download-captcha]');
if (!container) return {};
const out = {};
const keys = [
'captcha_verified',
'cf_turnstile_response',
'tencent_ticket', 'tencent_randstr', 'tencent_sid',
'geetest_lot_number', 'geetest_captcha_output', 'geetest_pass_token', 'geetest_gen_time',
'aliyun_captcha_verify_param',
];
keys.forEach(function (k) {
const el = container.querySelector('input[name="' + k + '"]');
if (el && el.value !== '') out[k] = el.value;
});
return out;
}
function isCaptchaActive() {
const card = document.querySelector('.resource-download-box');
if (!card) return false;
return card.getAttribute('data-captcha') === '1';
}
function ensureCaptchaThen(callback) {
if (!isCaptchaActive()) { callback(); return; }
const state = window.AKSRTCaptchaState;
const scene = state && state.scenes && state.scenes.download;
if (!scene || typeof scene.verify !== 'function') {
// captcha 组件未就绪:不直发,避免绕过服务端门槛
if (window.console && console.warn) {
console.warn('[AKSRT] download captcha not ready');
}
alert(tt('captcha_loading', '验证码组件加载中,请稍候再试'));
return;
}
scene.verify(function () { callback(); });
}
function performDownloadRequest(button) {
const card = button.closest('.resource-download-box');
if (!card) return;
const postId = parseInt(card.getAttribute('data-post-id'), 10);
const sourceIndex = parseInt(button.getAttribute('data-source-index'), 10);
if (!postId || isNaN(sourceIndex)) return;
button.disabled = true;
const original = button.innerHTML;
button.innerHTML = ' ' + tt('processing', '处理中…');
const restNonce = window.aksrtData.restNonce || '';
const captchaPayload = collectCaptchaPayload(button);
fetch(REST_BASE + '/download/request', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': restNonce,
},
body: JSON.stringify({
post_id: postId,
source_index: sourceIndex,
captcha: captchaPayload,
}),
})
.then(function (resp) { return resp.json().then(function (j) { return { ok: resp.ok, json: j }; }); })
.then(function (r) {
button.disabled = false;
button.innerHTML = original;
if (!r.ok || (r.json && r.json.code)) {
renderError(r.json.message || tt('default_err', '请稍后重试'), r.json.data || {}, button, r.json.code);
// 验证码已被消费,重置以便用户重试
try {
const scene = window.AKSRTCaptchaState && window.AKSRTCaptchaState.scenes && window.AKSRTCaptchaState.scenes.download;
if (scene && typeof scene.reset === 'function') scene.reset();
} catch (_) { /* ignore */ }
return;
}
renderSuccess(r.json, button);
})
.catch(function () {
button.disabled = false;
button.innerHTML = original;
renderError(tt('network_err', '网络错误,请稍后重试'), {}, button);
});
}
function requestDownload(button) {
ensureCaptchaThen(function () {
performDownloadRequest(button);
});
}
function pollOrderPaid(orderNumber, returnFocusEl) {
if (!orderNumber) return;
let tries = 0;
const maxTries = 40;
const timer = setInterval(function () {
tries += 1;
fetch(REST_BASE + '/pay/content/order/' + encodeURIComponent(orderNumber), {
method: 'GET',
credentials: 'same-origin',
headers: restHeaders(),
})
.then(function (resp) { return resp.json().then(function (j) { return { ok: resp.ok, json: j }; }); })
.then(function (r) {
if (r.ok && r.json && r.json.paid) {
clearInterval(timer);
window.location.reload();
}
})
.catch(function () { /* ignore */ });
if (tries >= maxTries) clearInterval(timer);
}, 3000);
}
// ===== 积分不足按钮(前置拦截,不发请求) =====
function showLowCreditModal(button) {
const balance = parseInt(button.getAttribute('data-balance'), 10) || 0;
const cost = parseInt(button.getAttribute('data-cost'), 10) || 0;
renderError('', { balance: balance, cost: cost }, button, 'aksrt_credits_insufficient');
}
// ===== 收银台(内容购买) =====
function restHeaders() {
return {
'Content-Type': 'application/json',
'X-WP-Nonce': window.aksrtData.restNonce || '',
};
}
function payApiBase(apiKind) {
if (apiKind === 'topic') return '/pay/topic/';
if (apiKind === 'plate_follow') return '/pay/plate-follow/';
return '/pay/content/';
}
function buildPreviewUrl(postId, priceType, couponCode, apiKind) {
apiKind = apiKind || 'content';
var previewUrl = REST_BASE + payApiBase(apiKind) + postId;
var qs = [];
if (priceType) qs.push('price_type=' + encodeURIComponent(priceType));
if (couponCode) qs.push('coupon_code=' + encodeURIComponent(couponCode));
if (qs.length) previewUrl += '?' + qs.join('&');
return previewUrl;
}
function fetchPreview(postId, priceType, couponCode, apiKind) {
return fetch(buildPreviewUrl(postId, priceType, couponCode, apiKind), {
method: 'GET',
credentials: 'same-origin',
headers: restHeaders(),
}).then(function (resp) {
return resp.json().then(function (j) { return { ok: resp.ok, status: resp.status, json: j }; });
});
}
function openCashier(postId, returnFocusEl, priceType, couponCode, apiKind) {
if (!postId) return;
priceType = priceType || '';
couponCode = couponCode || '';
apiKind = apiKind || 'content';
open(
'' + tt('cashier_loading', '确认购买') + '
' +
'' + tt('loading', '加载中…') + '
',
returnFocusEl
);
fetchPreview(postId, priceType, couponCode, apiKind)
.then(function (r) {
if (!r.ok || (r.json && r.json.code)) {
const code = r.json && r.json.code ? r.json.code : '';
const data = (r.json && r.json.data) || {};
if (code === 'aksrt_need_login' || r.status === 401) {
renderError(r.json.message || '', data, returnFocusEl, 'aksrt_need_login');
return;
}
if (code === 'aksrt_need_vip') {
renderError(r.json.message || '', data, returnFocusEl, 'aksrt_need_vip');
return;
}
if (code === 'aksrt_already_paid' || code === 'aksrt_already_followed') {
window.location.reload();
return;
}
renderError((r.json && r.json.message) || tt('default_err', '请稍后重试'), data, returnFocusEl, code);
return;
}
var prev = r.json || {};
if (priceType) prev._price_type = priceType;
if (couponCode) prev._coupon_code = couponCode;
prev._api_kind = apiKind;
renderCashier(prev, returnFocusEl);
})
.catch(function () {
renderError(tt('network_err', '网络错误,请稍后重试'), {}, returnFocusEl);
});
}
function renderCashier(preview, returnFocusEl) {
if (!preview) return;
if (preview.need_login && !preview.allow_guest) {
renderError('', { login_url: preview.login_url }, returnFocusEl, 'aksrt_need_login');
return;
}
if (preview.need_vip) {
renderError('', { vip_url: preview.vip_url }, returnFocusEl, 'aksrt_need_vip');
return;
}
if (preview.has_paid_access) {
window.location.reload();
return;
}
// Content free: reload. Topic/plate free: auto free-order POST (VIP free / free-after-coupon).
if (preview.is_free && preview.pay_type !== 'topic' && preview.pay_type !== 'plate_follow') {
window.location.reload();
return;
}
const isPoints = preview.pay_modo === 'points';
const priceType = (preview._price_type || preview.price_type || '');
const apiKind = preview._api_kind
|| (preview.pay_type === 'topic' ? 'topic' : (preview.pay_type === 'plate_follow' ? 'plate_follow' : 'content'));
// Auto-submit free topic/plate orders so user doesn't click through empty cash UI.
if (preview.is_free && (apiKind === 'topic' || apiKind === 'plate_follow') && !isPoints) {
open(
'' + tt('confirm_buy', '确认购买') + '
' +
'' + tt('processing', '处理中…') + '
',
returnFocusEl
);
const freeBtn = document.createElement('button');
freeBtn.type = 'button';
freeBtn.style.display = 'none';
freeBtn.setAttribute('data-price-type', priceType || '');
document.body.appendChild(freeBtn);
const freeCode = String(preview.coupon_code || preview._coupon_code || '');
submitPurchase(preview.post_id, 'cash', 'balance', freeBtn, returnFocusEl, freeCode, apiKind);
try { freeBtn.remove(); } catch (e0) { /* ignore */ }
return;
}
let amountLabel = escapeHtml(preview.amount_label || '');
let baseLabel = escapeHtml(preview.base_label || '');
const listLabel = escapeHtml(preview.list_amount_label || preview.amount_label || amountLabel);
if (priceType === 'download_limit_over' && preview.download_limit_over_label && !preview.discounts) {
amountLabel = escapeHtml(preview.download_limit_over_label);
baseLabel = amountLabel;
preview.amount = preview.download_limit_over_price;
preview.base = preview.download_limit_over_price;
}
const title = escapeHtml(preview.title || '');
const typeLabel = escapeHtml(preview.pay_type_label || '');
const balance = typeof preview.balance === 'number' ? preview.balance : 0;
const methods = Array.isArray(preview.payment_methods) ? preview.payment_methods : [];
const couponAllowed = !isPoints && !!preview.coupon_allowed;
const couponCodeVal = escapeAttr(preview.coupon_code || preview._coupon_code || '');
let priceRows = '';
if (!preview.pay_limit) {
priceRows += '' + tt('price', '价格') + '' + baseLabel + '
';
}
// VIP list price row when VIP reduced list amount.
if (typeof preview.list_amount === 'number' && typeof preview.base === 'number' && preview.list_amount !== preview.base) {
priceRows += '' + tt('vip_price', '会员价') + '' + listLabel + '
';
} else if (preview.amount !== preview.base && !preview.rebate_discount && !preview.coupon_discount) {
priceRows += '' + tt('vip_price', '会员价') + '' + amountLabel + '
';
}
if (preview.rebate_discount > 0) {
priceRows += '' + tt('rebate_discount', '推广折扣') + '' + escapeHtml(preview.rebate_discount_label || ('-' + preview.rebate_discount)) + '
';
}
if (preview.coupon_discount > 0) {
priceRows += '' + tt('coupon_discount', '优惠码') + '' + escapeHtml(preview.coupon_discount_label || ('-' + preview.coupon_discount)) + '
';
}
priceRows += '' + tt('pay_amount', '支付金额') + '' + amountLabel + '
';
let body = '';
body += '' + typeLabel + '' + title + '
';
body += '' + priceRows + '
';
if (isPoints) {
body += '' + tt('my_points', '我的积分') + '' + balance + '
';
if (!preview.balance_enough) {
body += '' + tt('points_short', '抱歉,您的积分不足,暂时无法支付') + '
';
body += '';
open('' + tt('confirm_buy', '确认购买') + '
' + body, returnFocusEl);
return;
}
body += '';
body += '';
body += '
';
} else {
if (couponAllowed) {
body += '';
body += '
' + tt('use_coupon', '使用优惠码') + '
';
body += '
';
body += '';
body += '';
body += '
';
if (preview.coupon_desc) {
body += '
' + escapeHtml(preview.coupon_desc) + '
';
}
if (preview.coupon_error) {
body += '
' + escapeHtml(preview.coupon_error) + '
';
} else if (preview.coupon_discount > 0) {
body += '
' + tt('coupon_ok', '优惠码可用') + '
';
}
body += '
';
}
let methodHtml = '';
methods.forEach(function (m, idx) {
const id = escapeAttr(m.id || '');
const name = escapeHtml(m.name || id);
methodHtml += '';
});
if (methodHtml) {
body += '' + tt('pay_method', '支付方式') + '
' + methodHtml + '
';
}
body += '';
body += '';
body += '
';
}
open('' + tt('confirm_buy', '确认购买') + '
' + body, returnFocusEl);
const checkBtn = document.querySelector('[data-aksrt-check-coupon]');
if (checkBtn) {
checkBtn.addEventListener('click', function () {
const input = document.querySelector('input[name="aksrt_coupon_code"]');
const code = input ? String(input.value || '').trim() : '';
if (!code) {
if (input) input.focus();
return;
}
checkBtn.disabled = true;
const old = checkBtn.innerHTML;
checkBtn.innerHTML = tt('checking', '检查中…');
fetchPreview(preview.post_id, priceType, code, apiKind)
.then(function (r) {
checkBtn.disabled = false;
checkBtn.innerHTML = old;
if (!r.ok || (r.json && r.json.code && !r.json.amount && r.json.amount !== 0)) {
renderError((r.json && r.json.message) || tt('default_err', '请稍后重试'), (r.json && r.json.data) || {}, returnFocusEl, (r.json && r.json.code) || '');
return;
}
var prev = r.json || {};
if (priceType) prev._price_type = priceType;
prev._coupon_code = code;
prev._api_kind = apiKind;
renderCashier(prev, returnFocusEl);
})
.catch(function () {
checkBtn.disabled = false;
checkBtn.innerHTML = old;
renderError(tt('network_err', '网络错误,请稍后重试'), {}, returnFocusEl);
});
});
}
const submitBtn = document.querySelector('[data-aksrt-pay-submit]');
if (submitBtn) {
submitBtn.addEventListener('click', function () {
const mode = submitBtn.getAttribute('data-mode') || (isPoints ? 'points' : 'cash');
let paymentMethod = 'balance';
const checked = document.querySelector('input[name="aksrt_pay_method"]:checked');
if (checked) paymentMethod = checked.value;
const input = document.querySelector('input[name="aksrt_coupon_code"]');
const code = input ? String(input.value || '').trim() : (preview.coupon_code || preview._coupon_code || '');
submitPurchase(preview.post_id, mode, paymentMethod, submitBtn, returnFocusEl, code, apiKind);
});
}
}
function submitPurchase(postId, mode, paymentMethod, button, returnFocusEl, couponCode, apiKind) {
if (!postId) return;
apiKind = apiKind || 'content';
const original = button.innerHTML;
button.disabled = true;
button.innerHTML = tt('processing', '处理中…');
const payload = { mode: mode };
if (mode === 'cash') payload.payment_method = paymentMethod || 'balance';
const pt = button.getAttribute('data-price-type') || '';
if (pt) payload.price_type = pt;
if (mode === 'cash' && couponCode) payload.coupon_code = couponCode;
fetch(REST_BASE + payApiBase(apiKind) + postId, {
method: 'POST',
credentials: 'same-origin',
headers: restHeaders(),
body: JSON.stringify(payload),
})
.then(function (resp) { return resp.json().then(function (j) { return { ok: resp.ok, status: resp.status, json: j }; }); })
.then(function (r) {
button.disabled = false;
button.innerHTML = original;
if (!r.ok || (r.json && r.json.code && !r.json.success)) {
const code = r.json && r.json.code ? r.json.code : '';
const data = (r.json && r.json.data) || {};
if (code === 'aksrt_already_paid' || code === 'aksrt_already_followed') {
window.location.reload();
return;
}
renderError((r.json && r.json.message) || tt('default_err', '请稍后重试'), data, returnFocusEl, code);
return;
}
const data = (r.json && r.json.data) ? r.json.data : r.json;
if (data && data.redirect && (data.payment_url || data.qr_content)) {
if (data.payment_url) {
window.open(data.payment_url, '_blank');
}
open(
'' + tt('scan_pay', '扫码支付') + '
' +
'' + escapeHtml(data.message || tt('scan_hint', '请使用手机扫码完成支付,支付后刷新页面。')) + '
' +
(data.qr_content ? ('' + escapeHtml(data.qr_content || '') + '
') : '') +
'',
returnFocusEl
);
const reloadBtn = document.querySelector('[data-aksrt-reload]');
if (reloadBtn) reloadBtn.addEventListener('click', function () { window.location.reload(); });
if (data.order_number) pollOrderPaid(data.order_number, returnFocusEl);
return;
}
window.location.reload();
})
.catch(function () {
button.disabled = false;
button.innerHTML = original;
renderError(tt('network_err', '网络错误,请稍后重试'), {}, returnFocusEl);
});
}
// ===== 事件绑定 =====
document.addEventListener('click', function (e) {
const lowBtn = e.target.closest('[data-action="aksrt-need-credits"]');
if (lowBtn) {
e.preventDefault();
showLowCreditModal(lowBtn);
return;
}
const topicBuyBtn = e.target.closest('[data-aksrt-pay-topic]');
if (topicBuyBtn) {
e.preventDefault();
const topicId = parseInt(topicBuyBtn.getAttribute('data-aksrt-pay-topic') || '0', 10);
if (topicId) openCashier(topicId, topicBuyBtn, '', '', 'topic');
return;
}
const plateFollowBuyBtn = e.target.closest('[data-aksrt-pay-plate-follow]');
if (plateFollowBuyBtn) {
e.preventDefault();
const plateId = parseInt(plateFollowBuyBtn.getAttribute('data-aksrt-pay-plate-follow') || '0', 10);
if (plateId) openCashier(plateId, plateFollowBuyBtn, '', '', 'plate_follow');
return;
}
const buyBtn = e.target.closest('[data-action="aksrt-need-purchase"], [data-aksrt-pay-content]');
if (buyBtn) {
e.preventDefault();
const postId = parseInt(
buyBtn.getAttribute('data-aksrt-pay-content') ||
buyBtn.getAttribute('data-post-id') ||
(buyBtn.closest('[data-post-id]') && buyBtn.closest('[data-post-id]').getAttribute('data-post-id')) ||
'0',
10
);
const priceType = buyBtn.getAttribute('data-price-type') || '';
if (postId) openCashier(postId, buyBtn, priceType, '', 'content');
return;
}
const cashierLink = e.target.closest('.cashier-link');
if (cashierLink && cashierLink.hasAttribute('data-aksrt-pay-content')) {
e.preventDefault();
const postId = parseInt(cashierLink.getAttribute('data-aksrt-pay-content') || '0', 10);
if (postId) openCashier(postId, cashierLink, '', '', 'content');
return;
}
const btn = e.target.closest('[data-action="aksrt-download"]');
if (!btn) return;
if (btn.disabled || btn.getAttribute('aria-disabled') === 'true') return;
e.preventDefault();
requestDownload(btn);
});
// Bridge for forum-governance plate money cashier.
window.aksrtOpenCashier = function (postId, returnFocusEl, priceType, couponCode, apiKind) {
openCashier(postId, returnFocusEl || null, priceType || '', couponCode || '', apiKind || 'content');
};
// ===== 注入 modal 基础样式(避免必须额外加载 CSS)=====
const style = document.createElement('style');
style.textContent =
'.aksrt-dl-modal{position:fixed;inset:0;z-index:10000;display:none;}' +
'.aksrt-dl-modal.is-open{display:flex;align-items:center;justify-content:center;}' +
'.aksrt-dl-modal__backdrop{position:absolute;inset:0;background:rgba(0,0,0,.55);}' +
'.aksrt-dl-modal__panel{position:relative;background:#fff;border-radius:14px;padding:26px 28px 24px;width:min(420px,calc(100vw - 32px));box-shadow:0 24px 56px rgba(15,23,42,.24);color:#111827;}' +
'.aksrt-dl-modal__close{position:absolute;top:10px;right:12px;background:transparent;border:0;font-size:24px;line-height:1;cursor:pointer;color:#6b7280;}' +
'.aksrt-dl-modal__title{font:700 20px/1.3 inherit;margin:0 28px 18px 0;color:#111827;}' +
'.aksrt-dl-modal__msg{margin:0 0 18px;color:#4b5563;font-size:14px;line-height:1.65;}' +
'.aksrt-dl-modal__msg--warn{color:#92400e;background:transparent;border:0;border-top:1px solid #fde68a;border-bottom:1px solid #fde68a;padding:12px 0;}' +
'.aksrt-dl-modal__row{display:grid;grid-template-columns:64px minmax(0,1fr) 72px;align-items:center;gap:12px;margin:0 0 18px;padding:12px 0;border-top:1px solid #e5e7eb;border-bottom:1px solid #e5e7eb;}' +
'.aksrt-dl-modal__label{font-size:13px;font-weight:600;color:#6b7280;white-space:nowrap;}' +
'.aksrt-dl-modal__code{display:block;min-width:0;color:#111827;font:800 18px/1.25 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;letter-spacing:.06em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}' +
'.aksrt-dl-modal__copy{appearance:none!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;width:72px!important;height:32px!important;background:#eff6ff!important;border:1px solid #bfdbfe!important;border-radius:8px!important;padding:0!important;cursor:pointer!important;color:#2563eb!important;-webkit-text-fill-color:#2563eb!important;font-size:0!important;font-weight:700!important;line-height:1!important;font-family:inherit!important;text-indent:0!important;opacity:1!important;visibility:visible!important;white-space:nowrap!important;overflow:visible!important;}' +
'.aksrt-dl-modal__copy::before,.aksrt-dl-modal__copy::after{content:none!important;display:none!important;}' +
'.aksrt-dl-modal__copy:hover{background:#dbeafe!important;border-color:#93c5fd!important;color:#1d4ed8!important;-webkit-text-fill-color:#1d4ed8!important;}' +
'.aksrt-dl-modal__copy-text{display:inline-flex!important;align-items:center!important;justify-content:center!important;position:relative!important;z-index:2!important;color:#2563eb!important;-webkit-text-fill-color:#2563eb!important;font-size:13px!important;font-weight:700!important;line-height:1!important;font-family:inherit!important;text-indent:0!important;opacity:1!important;visibility:visible!important;white-space:nowrap!important;}' +
'.aksrt-dl-modal__copy:hover .aksrt-dl-modal__copy-text{color:#1d4ed8!important;-webkit-text-fill-color:#1d4ed8!important;}' +
'.aksrt-dl-modal__actions{display:flex;align-items:center;gap:12px;padding-top:4px;flex-wrap:wrap;}' +
'.aksrt-dl-modal__download{min-height:40px;padding:0 18px;border-radius:9px;display:inline-flex;align-items:center;justify-content:center;text-decoration:none;border:0;cursor:pointer;}' +
'.aksrt-dl-modal__balance{font-size:13px;color:#6b7280;margin-left:auto;white-space:nowrap;}' +
'.aksrt-cashier__product{margin:0 0 12px;font-size:14px;line-height:1.5;}' +
'.aksrt-cashier__tag{display:inline-block;margin-right:8px;padding:2px 8px;border-radius:6px;background:#eff6ff;color:#1d4ed8;font-size:12px;font-weight:700;}' +
'.aksrt-cashier__box{background:#f8fafc;border:1px solid #e5e7eb;border-radius:10px;padding:10px 12px;margin:0 0 12px;}' +
'.aksrt-cashier__row{display:flex;justify-content:space-between;align-items:center;gap:12px;font-size:13px;color:#6b7280;padding:4px 0;}' +
'.aksrt-cashier__row b{color:#111827;font-size:15px;}' +
'.aksrt-cashier__row b.is-sale{color:#dc2626;}' +
'.aksrt-cashier__row b.is-ok{color:#059669;}' +
'.aksrt-cashier__warn{background:#fef2f2;color:#b91c1c;border-radius:8px;padding:10px 12px;margin:0 0 12px;font-size:13px;}' +
'.aksrt-cashier__methods{margin:0 0 14px;}' +
'.aksrt-cashier__methods-title{font-size:13px;font-weight:600;color:#374151;margin:0 0 8px;}' +
'.aksrt-cashier__method{display:flex;align-items:center;gap:8px;padding:8px 10px;border:1px solid #e5e7eb;border-radius:8px;margin:0 0 6px;cursor:pointer;font-size:13px;}' +
'.aksrt-cashier__coupon{margin:0 0 14px;}' +
'.aksrt-cashier__coupon-row{display:flex;gap:8px;align-items:center;}' +
'.aksrt-cashier__coupon-input{flex:1;min-width:0;padding:8px 10px;border:1px solid #e5e7eb;border-radius:8px;font-size:13px;}' +
'.aksrt-cashier__coupon-desc{margin-top:6px;font-size:12px;color:#9ca3af;line-height:1.5;}' +
'.aksrt-cashier__coupon-ok{margin-top:6px;font-size:12px;color:#059669;}' +
'.resource-download-box__attrs{list-style:none;margin:10px 0 0;padding:0;display:grid;gap:6px;}' +
'.resource-download-box__attrs-item{display:flex;justify-content:space-between;gap:12px;font-size:12px;color:#6b7280;}' +
'.resource-download-box__attrs-name{flex:0 0 auto;}' +
'.resource-download-box__attrs-value{color:#111827;text-align:right;}' +
'.resource-download-box__paid-badge{background:#ecfdf5!important;color:#047857!important;}.resource-download-box__paid-meta{display:grid;gap:4px;margin:8px 0 12px;padding:10px 12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:10px;font-size:12px;color:#166534;}' +
'@media(max-width:480px){.aksrt-dl-modal__panel{padding:24px 20px 22px;}.aksrt-dl-modal__row{grid-template-columns:1fr 72px;gap:8px 12px;}.aksrt-dl-modal__label{grid-column:1 / -1;}.aksrt-dl-modal__code{font-size:17px;}.aksrt-dl-modal__actions{align-items:flex-start;flex-direction:column;}.aksrt-dl-modal__download{width:100%;}.aksrt-dl-modal__balance{margin-left:0;}}' +
'body.aksrt-dl-modal-open{overflow:hidden;}';
document.head.appendChild(style);
})();