Files
SMANAGE/TempLate/pages/private/CreateE/Recharge.html
T

636 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script src="/assets/gerp/GERPThemeSync.js"></script>
<link rel="stylesheet" href="/assets/css/GERP.css">
<style>
.rc-container {
padding: 24px;
max-width: 1100px;
margin: 0 auto;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: var(--TextPrimary, #1e293b);
}
.rc-section {
background: var(--CardBackground, #fff);
border: 1px solid var(--BorderColor, #e2e8f0);
border-radius: 12px;
overflow: hidden;
margin-bottom: 24px;
}
.rc-section-header {
background: var(--PrimaryColor, #2563eb);
color: #fff;
padding: 14px 20px;
display: flex;
align-items: center;
justify-content: space-between;
}
.rc-section-header h2 {
margin: 0;
font-size: 17px;
font-weight: 600;
color: #fff;
}
.rc-section-body { padding: 20px; }
.rc-hint {
font-size: 13px;
color: var(--TextSecondary, #64748b);
margin-bottom: 16px;
line-height: 1.6;
}
.rc-hint .warn { color: var(--WarningColor, #f59e0b); }
.rc-rules-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 12px;
}
.rc-plan-card {
cursor: pointer;
padding: 14px;
border-radius: 10px;
border: 2px solid var(--BorderColor, #e2e8f0);
background: var(--BackgroundColor, #f5f5f5);
transition: all 0.18s;
}
.rc-plan-card:hover {
border-color: var(--PrimaryColor, #2563eb);
transform: translateY(-1px);
}
.rc-plan-card.selected {
border-color: var(--PrimaryColor, #2563eb);
background: color-mix(in srgb, var(--PrimaryColor, #2563eb) 8%, transparent);
}
.rc-plan-title-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 8px;
}
.rc-plan-name {
font-size: 15px;
font-weight: 600;
}
.rc-rate-badge {
font-size: 11px;
padding: 2px 8px;
border-radius: 10px;
background: rgba(16, 185, 129, 0.18);
color: #10b981;
}
.rc-plan-meta {
font-size: 12px;
color: var(--TextSecondary, #64748b);
line-height: 1.8;
}
.rc-plan-meta .label { color: var(--TextSecondary, #64748b); }
.rc-plan-meta .value { color: var(--TextPrimary, #1e293b); font-weight: 500; }
.rc-plan-meta .withdraw-ok { color: #10b981; font-weight: 500; }
.rc-plan-meta .withdraw-lock { color: var(--WarningColor, #f59e0b); font-weight: 500; }
/* 收益预览 */
.rc-profit-preview {
margin-top: 14px;
padding: 14px;
border-radius: 10px;
background: linear-gradient(135deg, color-mix(in srgb, var(--PrimaryColor, #2563eb) 6%, transparent), color-mix(in srgb, var(--SecondaryColor, #8b5cf6) 6%, transparent));
border: 1px solid var(--BorderColor, #e2e8f0);
}
.rc-profit-preview.hidden { display: none; }
.rc-profit-preview h3 {
margin: 0 0 10px;
font-size: 14px;
color: var(--PrimaryColor, #2563eb);
display: flex;
align-items: center;
gap: 6px;
}
.rc-profit-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 10px;
}
.rc-profit-item {
background: var(--CardBackground, #fff);
border: 1px solid var(--BorderColor, #e2e8f0);
border-radius: 8px;
padding: 10px 12px;
}
.rc-profit-label {
font-size: 11px;
color: var(--TextSecondary, #64748b);
margin-bottom: 4px;
}
.rc-profit-value {
font-size: 16px;
font-weight: 600;
color: var(--PrimaryColor, #2563eb);
}
.rc-profit-value .unit { font-size: 11px; color: var(--TextSecondary, #64748b); font-weight: 400; margin-left: 3px; }
.rc-profit-value.mature { color: #10b981; }
.rc-profit-value.warn { color: var(--WarningColor, #f59e0b); font-size: 14px; }
.rc-summary {
margin-top: 18px;
padding: 14px;
border-radius: 10px;
background: var(--BackgroundColor, #f5f5f5);
border: 1px solid var(--BorderColor, #e2e8f0);
}
.rc-summary.hidden { display: none; }
.rc-summary h3 {
margin: 0 0 10px;
font-size: 14px;
color: var(--PrimaryColor, #2563eb);
}
.rc-plan-line {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 10px;
background: var(--CardBackground, #fff);
border: 1px solid var(--BorderColor, #e2e8f0);
border-radius: 6px;
margin-bottom: 6px;
font-size: 13px;
}
.rc-plan-remove {
background: none;
border: none;
color: var(--DangerColor, #ef4444);
cursor: pointer;
font-size: 12px;
padding: 2px 6px;
}
.rc-plan-remove:hover { text-decoration: underline; }
.rc-action-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: flex-end;
margin-top: 14px;
}
.rc-input-group { flex: 1; min-width: 160px; }
.rc-input-group label {
display: block;
font-size: 12px;
color: var(--TextSecondary, #64748b);
margin-bottom: 4px;
}
.rc-input {
width: 100%;
box-sizing: border-box;
padding: 8px 10px;
border: 1px solid var(--BorderColor, #e2e8f0);
border-radius: 6px;
font-size: 14px;
background: var(--CardBackground, #fff);
color: var(--TextPrimary, #1e293b);
}
.rc-input:focus {
outline: none;
border-color: var(--PrimaryColor, #2563eb);
}
.rc-btn {
padding: 9px 16px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
font-weight: 500;
transition: opacity 0.18s;
}
.rc-btn:hover { opacity: 0.9; }
.rc-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.rc-btn-primary { background: var(--PrimaryColor, #2563eb); color: #fff; }
.rc-btn-secondary { background: var(--SecondaryColor, #8b5cf6); color: #fff; }
.rc-btn-ghost {
background: var(--BackgroundColor, #f5f5f5);
color: var(--TextSecondary, #64748b);
border: 1px solid var(--BorderColor, #e2e8f0);
}
.rc-total {
margin-top: 10px;
font-size: 13px;
color: var(--TextSecondary, #64748b);
}
.rc-total b {
color: var(--PrimaryColor, #2563eb);
font-size: 15px;
}
.rc-empty {
text-align: center;
padding: 28px;
color: var(--TextSecondary, #64748b);
}
.rc-empty i {
font-size: 28px;
display: block;
margin-bottom: 8px;
opacity: 0.4;
}
.rc-loading {
display: inline-block;
animation: rc-spin 1s linear infinite;
}
@keyframes rc-spin { to { transform: rotate(360deg); } }
/* Toast */
.rc-toast-wrap {
position: fixed;
bottom: 16px;
right: 16px;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 8px;
}
.rc-toast {
padding: 10px 14px;
border-radius: 6px;
color: #fff;
font-size: 13px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
animation: rc-toast-in 0.25s ease-out;
display: flex;
align-items: center;
gap: 8px;
}
.rc-toast.out { animation: rc-toast-out 0.25s ease-in forwards; }
@keyframes rc-toast-in {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes rc-toast-out {
from { opacity: 1; }
to { opacity: 0; transform: translateY(-8px); }
}
</style>
<div class="rc-container">
<div class="rc-section">
<div class="rc-section-header">
<h2>储值充值</h2>
<span id="rules-spinner" class="rc-loading" style="color:#fff;display:none;">⟳</span>
</div>
<div class="rc-section-body">
<div class="rc-hint">
选择储值方案并输入金额,提交后将从您的 GC 钱包扣除相应金额并存入定期,按日计息。
<span class="warn">1 游戏日 = 20 现实分钟;活期随时可取,定期需满存期方可取款。</span>
</div>
<div id="rules-grid" class="rc-rules-grid">
<div class="rc-empty"><i class="fas fa-spinner rc-loading"></i><p>加载储值方案中...</p></div>
</div>
<div id="purchase-summary" class="rc-summary hidden">
<h3>已选方案</h3>
<div id="selected-plans-list"></div>
<div class="rc-action-row">
<div class="rc-input-group">
<label>新增方案金额(GC)</label>
<input type="number" id="new-plan-amount" class="rc-input" min="1" step="1" placeholder="输入金额" />
</div>
<button id="add-plan-btn" class="rc-btn rc-btn-secondary">添加到方案</button>
<button id="submit-plans-btn" class="rc-btn rc-btn-primary">提交充值</button>
<button id="clear-plans-btn" class="rc-btn rc-btn-ghost">清空</button>
</div>
<div class="rc-total">合计扣除:<b id="total-amount">0</b> GC</div>
<div id="profit-preview" class="rc-profit-preview hidden">
<h3><i class="fas fa-chart-line"></i> 收益预览(<span id="pp-plan-name">-</span>)</h3>
<div class="rc-profit-grid">
<div class="rc-profit-item">
<div class="rc-profit-label">每游戏日收益</div>
<div class="rc-profit-value"><span id="pp-per-game-day">0</span><span class="unit">GC/游戏日</span></div>
</div>
<div class="rc-profit-item">
<div class="rc-profit-label">每现实日收益</div>
<div class="rc-profit-value"><span id="pp-per-real-day">0</span><span class="unit">GC/现实日</span></div>
</div>
<div class="rc-profit-item">
<div class="rc-profit-label">到期总收益</div>
<div class="rc-profit-value warn"><span id="pp-exp-total">0</span><span class="unit">GC</span></div>
</div>
<div class="rc-profit-item">
<div class="rc-profit-label">到期本息合计</div>
<div class="rc-profit-value mature"><span id="pp-mature">0</span><span class="unit">GC</span></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="rc-toast-wrap" id="toast-wrap"></div>
<script>
(function () {
'use strict';
let StoreValueRules = [];
let SelectedPlanId = null;
let SelectedPlans = [];
function showToast(message, type) {
const wrap = document.getElementById('toast-wrap');
const t = document.createElement('div');
t.className = 'rc-toast';
const colors = {
success: '#10b981', danger: '#ef4444',
warning: '#f59e0b', info: '#3b82f6'
};
t.style.background = colors[type || 'info'] || colors.info;
const icons = {
success: 'fa-check-circle', danger: 'fa-exclamation-circle',
warning: 'fa-exclamation-triangle', info: 'fa-info-circle'
};
t.innerHTML = '<i class="fas ' + (icons[type || 'info'] || icons.info) + '"></i><span>' + message + '</span>';
wrap.appendChild(t);
setTimeout(function () {
t.classList.add('out');
setTimeout(function () { t.remove(); }, 260);
}, 3000);
}
function formatNum(n) {
if (n === null || n === undefined || isNaN(n)) return '0';
return Number(n).toLocaleString();
}
function fetchRules() {
const spinner = document.getElementById('rules-spinner');
spinner.style.display = 'inline-block';
fetch('/API/StoredValue/GetRules', { credentials: 'include' })
.then(function (r) {
if (!r.ok) throw new Error('Network error');
return r.json();
})
.then(function (data) {
if (data.status === 0 && Array.isArray(data.data)) {
StoreValueRules = data.data;
renderRules();
} else {
document.getElementById('rules-grid').innerHTML =
'<div class="rc-empty"><i class="fas fa-exclamation-circle"></i><p>获取储值方案失败</p></div>';
}
})
.catch(function (err) {
console.error('fetchRules error:', err);
document.getElementById('rules-grid').innerHTML =
'<div class="rc-empty"><i class="fas fa-exclamation-circle"></i><p>获取储值方案失败</p></div>';
})
.finally(function () {
spinner.style.display = 'none';
});
}
function renderRules() {
const grid = document.getElementById('rules-grid');
if (!StoreValueRules.length) {
grid.innerHTML = '<div class="rc-empty"><i class="fas fa-inbox"></i><p>暂无储值方案</p></div>';
return;
}
grid.innerHTML = StoreValueRules.map(function (rule) {
const minGC = rule.MinGC === -1 ? '不限' : formatNum(rule.MinGC);
const maxGC = rule.MaxGC === -1 ? '不限' : formatNum(rule.MaxGC);
const minDay = rule.MinDay === -1 ? '活期' : rule.MinDay + ' 游戏日';
// 取款限制:活期随时可取;定期需满 MinDay 游戏日(换算现实时间)
let withdrawInfo;
if (rule.MinDay === -1) {
withdrawInfo = '<span class="withdraw-ok">随时可取</span>';
} else {
// 1 游戏日 = 1200 现实秒 = 20 现实分钟
const realMinutes = Math.round(rule.MinDay * 20);
const realHours = (realMinutes / 60).toFixed(1);
withdrawInfo = '<span class="withdraw-lock">' + rule.MinDay + ' 游戏日(约 ' + realHours + ' 现实时)可取</span>';
}
// 到期时间换算
const expRealHours = (rule.ExpirationGameDate * 20 / 60).toFixed(1);
const isSelected = SelectedPlanId === rule.Id;
// 利率显示:百万分之 X → 百分比
const ratePercent = (rule.InterestRate / 1000).toFixed(3);
return '<div class="rc-plan-card ' + (isSelected ? 'selected' : '') + '" data-id="' + rule.Id + '">' +
'<div class="rc-plan-title-row">' +
'<div class="rc-plan-name">' + rule.Name + '</div>' +
'<span class="rc-rate-badge">日利率 ' + ratePercent + '%</span>' +
'</div>' +
'<div class="rc-plan-meta">' +
'<div><span class="label">取款限制:</span>' + withdrawInfo + '</div>' +
'<div><span class="label">金额范围:</span><span class="value">' + minGC + ' ~ ' + maxGC + ' GC</span></div>' +
'<div><span class="label">到期游戏日:</span><span class="value">' + rule.ExpirationGameDate + ' 日(约 ' + expRealHours + ' 现实时)</span></div>' +
'</div>' +
'</div>';
}).join('');
document.querySelectorAll('.rc-plan-card').forEach(function (card) {
card.addEventListener('click', function () {
SelectedPlanId = parseInt(card.getAttribute('data-id'));
renderRules();
document.getElementById('purchase-summary').classList.remove('hidden');
updateProfitPreview();
});
});
}
// 收益计算工具
// 换算:1 游戏日 = 1200 现实秒 = 20 现实分钟;1 现实日 = 72 游戏日
function calcProfit(amount, rule) {
if (!amount || amount <= 0 || !rule) return null;
const perGameDay = amount * rule.InterestRate / 100000; // 每游戏日收益
const perRealDay = perGameDay * 72; // 每现实日收益(72游戏日)
const expTotal = perGameDay * rule.ExpirationGameDate; // 到期总收益
const mature = amount + expTotal; // 到期本息
return { perGameDay: perGameDay, perRealDay: perRealDay, expTotal: expTotal, mature: mature };
}
function updateProfitPreview() {
const preview = document.getElementById('profit-preview');
const amountInput = document.getElementById('new-plan-amount');
const amount = parseInt(amountInput.value) || 0;
if (!SelectedPlanId || amount <= 0) {
preview.classList.add('hidden');
return;
}
const rule = StoreValueRules.find(function (r) { return r.Id === SelectedPlanId; });
if (!rule) { preview.classList.add('hidden'); return; }
const profit = calcProfit(amount, rule);
if (!profit) { preview.classList.add('hidden'); return; }
preview.classList.remove('hidden');
document.getElementById('pp-per-game-day').textContent = formatNum(profit.perGameDay.toFixed(2));
document.getElementById('pp-per-real-day').textContent = formatNum(profit.perRealDay.toFixed(2));
document.getElementById('pp-exp-total').textContent = formatNum(profit.expTotal.toFixed(2));
document.getElementById('pp-mature').textContent = formatNum(profit.mature.toFixed(2));
document.getElementById('pp-plan-name').textContent = rule.Name;
}
function addPlan() {
if (SelectedPlanId === null) {
showToast('请先选择一个储值方案', 'warning');
return;
}
const amountInput = document.getElementById('new-plan-amount');
const amount = parseInt(amountInput.value);
if (!amount || amount <= 0) {
showToast('请输入有效金额', 'warning');
return;
}
const rule = StoreValueRules.find(function (r) { return r.Id === SelectedPlanId; });
if (!rule) return;
if (rule.MinGC !== -1 && amount < rule.MinGC) {
showToast('金额不能少于 ' + formatNum(rule.MinGC) + ' GC', 'warning');
return;
}
if (rule.MaxGC !== -1 && amount > rule.MaxGC) {
showToast('金额不能超过 ' + formatNum(rule.MaxGC) + ' GC', 'warning');
return;
}
SelectedPlans.push({ Id: rule.Id, Name: rule.Name, Amount: amount });
amountInput.value = '';
renderSelectedPlans();
}
function renderSelectedPlans() {
const list = document.getElementById('selected-plans-list');
const total = document.getElementById('total-amount');
if (!SelectedPlans.length) {
list.innerHTML = '<div style="font-size:13px;color:var(--TextSecondary,#64748b);padding:6px;">暂无已选方案</div>';
total.textContent = '0';
return;
}
list.innerHTML = SelectedPlans.map(function (p, idx) {
return '<div class="rc-plan-line">' +
'<span>' + p.Name + ' × ' + formatNum(p.Amount) + ' GC</span>' +
'<button class="rc-plan-remove" data-idx="' + idx + '">移除</button>' +
'</div>';
}).join('');
document.querySelectorAll('.rc-plan-remove').forEach(function (btn) {
btn.addEventListener('click', function () {
const idx = parseInt(btn.getAttribute('data-idx'));
SelectedPlans.splice(idx, 1);
renderSelectedPlans();
});
});
const totalAmount = SelectedPlans.reduce(function (s, p) { return s + p.Amount; }, 0);
total.textContent = formatNum(totalAmount);
}
function clearPlans() {
SelectedPlans = [];
SelectedPlanId = null;
renderRules();
renderSelectedPlans();
document.getElementById('purchase-summary').classList.add('hidden');
}
function submitPlans() {
if (!SelectedPlans.length) {
showToast('请先添加储值方案', 'warning');
return;
}
const btn = document.getElementById('submit-plans-btn');
btn.disabled = true;
btn.textContent = '处理中...';
const plans = SelectedPlans.map(function (p) { return { id: p.Id, amount: p.Amount }; });
fetch('/API/StoredValue/StoreValue', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ plans: plans })
})
.then(function (r) { return r.json(); })
.then(function (data) {
if (data.status === 0) {
showToast('储值充值成功', 'success');
clearPlans();
} else {
showToast(data.msg || '储值充值失败', 'danger');
}
})
.catch(function (err) {
console.error('submitPlans error:', err);
showToast('网络错误,储值充值失败', 'danger');
})
.finally(function () {
btn.disabled = false;
btn.textContent = '提交充值';
});
}
document.getElementById('add-plan-btn').addEventListener('click', addPlan);
document.getElementById('submit-plans-btn').addEventListener('click', submitPlans);
document.getElementById('clear-plans-btn').addEventListener('click', clearPlans);
document.getElementById('new-plan-amount').addEventListener('input', updateProfitPreview);
fetchRules();
})();
// ==================== GERP 主题同步 ====================
window.addEventListener('load', function () {
const CheckAndInit = () => {
if (window.GERPThemeSync) {
window.GERPThemeSync.init();
return;
}
setTimeout(CheckAndInit, 100);
};
CheckAndInit();
if (window.parent !== window) {
window.parent.postMessage({ type: 'GET_THEME' }, '*');
}
});
window.addEventListener('message', function (e) {
if (e.data && e.data.type === 'THEME_CHANGE') {
const colors = e.data.colors || (e.data.data && e.data.data.colors);
if (colors) {
const root = document.documentElement;
Object.entries(colors).forEach(([key, value]) => {
root.style.setProperty('--' + key, value);
});
}
}
});
</script>