@@ -0,0 +1,322 @@
|
||||
(() => {
|
||||
const root = document.querySelector('[data-recharge]');
|
||||
if (!root) return;
|
||||
|
||||
const packages = Array.from(root.querySelectorAll('[data-recharge-package]'));
|
||||
const methodsRoot = root.querySelector('[data-recharge-methods]');
|
||||
const methodEmpty = root.querySelector('[data-recharge-method-empty]');
|
||||
const payButton = root.querySelector('[data-recharge-pay]');
|
||||
const price = root.querySelector('[data-recharge-price]');
|
||||
const coins = root.querySelector('[data-recharge-coins]');
|
||||
const status = root.querySelector('[data-recharge-status]');
|
||||
const rowsRoot = root.querySelector('[data-recharge-rows]');
|
||||
const filters = Array.from(root.querySelectorAll('[data-recharge-filter]'));
|
||||
const pagination = root.querySelector('[data-recharge-pagination]');
|
||||
const pageButtons = root.querySelector('[data-recharge-pages]');
|
||||
const previous = root.querySelector('[data-recharge-prev]');
|
||||
const next = root.querySelector('[data-recharge-next]');
|
||||
const total = root.querySelector('[data-recharge-total]');
|
||||
const empty = root.querySelector('[data-recharge-history-empty]');
|
||||
const more = root.querySelector('[data-recharge-more]');
|
||||
const exportBtn = root.querySelector('[data-recharge-export]');
|
||||
const mobile = window.matchMedia('(max-width: 768px)');
|
||||
let selectedPackage = null;
|
||||
let selectedMethod = '';
|
||||
let activeType = 0;
|
||||
let currentPage = Number(pagination.dataset.page) || 1;
|
||||
let pageSize = Number(pagination.dataset.pageSize) || 5;
|
||||
let totalCount = Number(pagination.dataset.total) || 0;
|
||||
let expanded = false;
|
||||
|
||||
function showStatus(message, success) {
|
||||
status.textContent = message;
|
||||
status.hidden = !message;
|
||||
status.classList.toggle('is-success', Boolean(success));
|
||||
}
|
||||
|
||||
function choose(button, choices) {
|
||||
choices.forEach(choice => {
|
||||
const isSelected = choice === button;
|
||||
choice.classList.toggle('is-selected', isSelected);
|
||||
choice.setAttribute('aria-checked', String(isSelected));
|
||||
});
|
||||
}
|
||||
|
||||
function parseMethods(button) {
|
||||
try {
|
||||
const parsed = JSON.parse(button.dataset.payMethods || '[]');
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function renderMethods(items) {
|
||||
methodsRoot.replaceChildren();
|
||||
selectedMethod = '';
|
||||
items.forEach((item, index) => {
|
||||
const type = String(item.type || '').trim();
|
||||
if (!type) return;
|
||||
const button = document.createElement('button');
|
||||
button.className = 'recharge-method';
|
||||
button.type = 'button';
|
||||
button.setAttribute('role', 'radio');
|
||||
button.dataset.rechargeMethod = type;
|
||||
button.innerHTML = '<span class="recharge-method__radio"><img src="/static/images/placeholder.png?v=2" data-src="/static/images/recharge/check.svg" alt="" loading="lazy" decoding="async"></span><span><strong></strong><small>支付成功后到账</small></span>';
|
||||
button.querySelector('strong').textContent = item.typeName || type;
|
||||
button.addEventListener('click', () => {
|
||||
const choices = Array.from(methodsRoot.querySelectorAll('[data-recharge-method]'));
|
||||
choose(button, choices);
|
||||
selectedMethod = type;
|
||||
showStatus('');
|
||||
});
|
||||
methodsRoot.appendChild(button);
|
||||
if (index === 0) {
|
||||
button.classList.add('is-selected');
|
||||
button.setAttribute('aria-checked', 'true');
|
||||
selectedMethod = type;
|
||||
} else {
|
||||
button.setAttribute('aria-checked', 'false');
|
||||
}
|
||||
});
|
||||
const hasMethod = Boolean(selectedMethod);
|
||||
methodEmpty.hidden = hasMethod;
|
||||
payButton.disabled = !selectedPackage || !hasMethod;
|
||||
if (typeof window.initLazyImages === 'function') {
|
||||
window.initLazyImages(methodsRoot);
|
||||
}
|
||||
}
|
||||
|
||||
function selectPackage(button) {
|
||||
selectedPackage = button;
|
||||
choose(button, packages);
|
||||
price.textContent = button.dataset.price || '—';
|
||||
coins.textContent = button.dataset.coins || '—';
|
||||
renderMethods(parseMethods(button));
|
||||
showStatus('');
|
||||
}
|
||||
|
||||
packages.forEach(button => button.addEventListener('click', () => selectPackage(button)));
|
||||
const initialPackage = packages.find(button => button.classList.contains('is-selected')) || packages[0];
|
||||
if (initialPackage) selectPackage(initialPackage);
|
||||
else payButton.disabled = true;
|
||||
|
||||
payButton.addEventListener('click', async () => {
|
||||
if (!selectedPackage || !selectedMethod) {
|
||||
showStatus('请选择套餐和支付方式');
|
||||
return;
|
||||
}
|
||||
payButton.disabled = true;
|
||||
showStatus('正在创建支付订单…');
|
||||
const payload = {
|
||||
packageId: Number(selectedPackage.dataset.packageId),
|
||||
payMode: selectedMethod,
|
||||
};
|
||||
try {
|
||||
let result;
|
||||
if (window.HttpRequest && typeof window.HttpRequest.post === 'function') {
|
||||
result = await window.HttpRequest.post('/api/coin/buy', payload);
|
||||
} else {
|
||||
const response = await fetch('/api/coin/buy', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
result = await response.json();
|
||||
}
|
||||
if (!result || Number(result.code) !== 200 || !result.data || !result.data.payUrl) {
|
||||
if (result && (Number(result.code) === 401 || Number(result.code) === 1000)) {
|
||||
const login = document.querySelector('[data-login-open]');
|
||||
if (login) login.click();
|
||||
else window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
throw new Error((result && (result.tip || result.msg)) || '创建支付订单失败');
|
||||
}
|
||||
showStatus('订单创建成功,正在前往支付…', true);
|
||||
openPayInNewWindow(result.data.payUrl);
|
||||
payButton.disabled = false;
|
||||
} catch (error) {
|
||||
showStatus(error.message || '创建支付订单失败,请稍后重试');
|
||||
payButton.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
function openPayInNewWindow(url) {
|
||||
url = String(url || '').trim();
|
||||
if (!url) return;
|
||||
let opened = null;
|
||||
try {
|
||||
opened = window.open(url, '_blank', 'noopener,noreferrer');
|
||||
} catch (e) {
|
||||
opened = null;
|
||||
}
|
||||
if (opened) {
|
||||
try {
|
||||
opened.opener = null;
|
||||
} catch (e2) {}
|
||||
showStatus('已在新窗口打开支付页面,请完成支付后返回本页', true);
|
||||
return;
|
||||
}
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener noreferrer';
|
||||
link.style.display = 'none';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
showStatus('若未打开支付页,请允许本站弹窗后重试', true);
|
||||
}
|
||||
|
||||
function appendText(parent, tag, className, value) {
|
||||
const node = document.createElement(tag);
|
||||
node.className = className;
|
||||
node.textContent = value || '';
|
||||
parent.appendChild(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
function renderRows(items) {
|
||||
rowsRoot.replaceChildren();
|
||||
items.forEach(item => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'recharge-history__row';
|
||||
row.dataset.rechargeRow = '';
|
||||
row.dataset.category = item.category || '';
|
||||
appendText(row, 'span', 'recharge-history__kind', item.kind);
|
||||
const detail = document.createElement('span');
|
||||
detail.className = 'recharge-history__detail-line';
|
||||
appendText(detail, 'span', 'recharge-history__description', item.description);
|
||||
const time = document.createElement('time');
|
||||
time.className = 'recharge-history__time';
|
||||
appendText(time, 'span', 'only-desktop', item.time);
|
||||
appendText(time, 'span', 'only-mobile', item.mobileTime);
|
||||
detail.appendChild(time);
|
||||
row.appendChild(detail);
|
||||
const amount = appendText(row, 'strong', 'recharge-history__amount', item.amount);
|
||||
amount.classList.toggle('is-credit', Boolean(item.credit));
|
||||
rowsRoot.appendChild(row);
|
||||
});
|
||||
empty.hidden = items.length > 0;
|
||||
empty.textContent = '暂无记录';
|
||||
}
|
||||
|
||||
function renderPagination() {
|
||||
const pageCount = Math.ceil(totalCount / pageSize);
|
||||
currentPage = Math.min(currentPage, Math.max(1, pageCount));
|
||||
pagination.hidden = totalCount === 0;
|
||||
more.hidden = totalCount <= 5 && !expanded;
|
||||
previous.disabled = currentPage <= 1;
|
||||
next.disabled = currentPage >= pageCount;
|
||||
total.textContent = '共 ' + totalCount + ' 条';
|
||||
pageButtons.replaceChildren();
|
||||
const firstPage = Math.max(1, Math.min(currentPage - 1, Math.max(1, pageCount - 3)));
|
||||
for (let page = firstPage; page <= Math.min(pageCount, firstPage + 3); page += 1) {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.textContent = String(page);
|
||||
button.classList.toggle('is-active', page === currentPage);
|
||||
if (page === currentPage) button.setAttribute('aria-current', 'page');
|
||||
button.addEventListener('click', () => loadHistory(page));
|
||||
pageButtons.appendChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadHistory(page) {
|
||||
empty.hidden = false;
|
||||
empty.textContent = '加载中…';
|
||||
try {
|
||||
const query = new URLSearchParams({
|
||||
page: String(page),
|
||||
pageSize: String(pageSize),
|
||||
type: String(activeType),
|
||||
});
|
||||
const url = '/api/coin/trans/list?' + query.toString();
|
||||
let result;
|
||||
if (window.HttpRequest && typeof window.HttpRequest.get === 'function') {
|
||||
result = await window.HttpRequest.get(url);
|
||||
} else {
|
||||
const response = await fetch(url, {credentials: 'same-origin'});
|
||||
result = await response.json();
|
||||
}
|
||||
if (!result || Number(result.code) !== 200 || !result.data) {
|
||||
throw new Error((result && (result.tip || result.msg)) || '交易明细加载失败');
|
||||
}
|
||||
currentPage = Number(result.data.pageNum) || page;
|
||||
totalCount = Number(result.data.totalCount) || 0;
|
||||
renderRows(Array.isArray(result.data.list) ? result.data.list : []);
|
||||
renderPagination();
|
||||
} catch (error) {
|
||||
rowsRoot.replaceChildren();
|
||||
empty.hidden = false;
|
||||
empty.textContent = error.message || '交易明细加载失败';
|
||||
pagination.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
async function exportHistory() {
|
||||
if (!exportBtn) return;
|
||||
exportBtn.disabled = true;
|
||||
exportBtn.classList.add('is-loading');
|
||||
try {
|
||||
const query = new URLSearchParams({type: String(activeType)});
|
||||
const url = '/api/coin/trans/export?' + query.toString();
|
||||
const response = await fetch(url, {credentials: 'same-origin'});
|
||||
const contentType = (response.headers.get('Content-Type') || '').toLowerCase();
|
||||
if (contentType.includes('application/json')) {
|
||||
const result = await response.json();
|
||||
throw new Error((result && (result.tip || result.msg)) || '导出失败');
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error('导出失败');
|
||||
}
|
||||
const blob = await response.blob();
|
||||
const disposition = response.headers.get('Content-Disposition') || '';
|
||||
let filename = 'coin-trans.csv';
|
||||
const match = /filename\*?=(?:UTF-8''|")?([^\";]+)/i.exec(disposition);
|
||||
if (match && match[1]) {
|
||||
filename = decodeURIComponent(match[1].replace(/"/g, '').trim()) || filename;
|
||||
}
|
||||
const objectURL = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = objectURL;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
URL.revokeObjectURL(objectURL);
|
||||
showStatus('导出成功', true);
|
||||
} catch (error) {
|
||||
showStatus(error.message || '导出失败', false);
|
||||
} finally {
|
||||
exportBtn.disabled = false;
|
||||
exportBtn.classList.remove('is-loading');
|
||||
}
|
||||
}
|
||||
|
||||
filters.forEach(button => button.addEventListener('click', () => {
|
||||
activeType = Number(button.dataset.rechargeFilter) || 0;
|
||||
filters.forEach(option => {
|
||||
const selected = option === button;
|
||||
option.classList.toggle('is-active', selected);
|
||||
option.setAttribute('aria-pressed', String(selected));
|
||||
});
|
||||
loadHistory(1);
|
||||
}));
|
||||
previous.addEventListener('click', () => { if (currentPage > 1) loadHistory(currentPage - 1); });
|
||||
next.addEventListener('click', () => loadHistory(currentPage + 1));
|
||||
if (exportBtn) {
|
||||
exportBtn.addEventListener('click', () => exportHistory());
|
||||
}
|
||||
more.addEventListener('click', () => {
|
||||
expanded = !expanded;
|
||||
pageSize = expanded ? 100 : 5;
|
||||
more.firstChild.textContent = expanded ? '收起 ' : '全部 ';
|
||||
more.setAttribute('aria-expanded', String(expanded));
|
||||
loadHistory(1);
|
||||
});
|
||||
|
||||
renderPagination();
|
||||
})();
|
||||
Reference in New Issue
Block a user