(() => { 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 = '支付成功后到账'; 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(); })();