323 lines
12 KiB
JavaScript
323 lines
12 KiB
JavaScript
(() => {
|
|
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();
|
|
})();
|