Files
SMANAGE/backup/pages/private/CreateE/wallet.html
T

692 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>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
box-sizing: border-box;
transition: background-color 0.5s, color 0.5s;
}
.wallet-container {
text-align: center;
padding: 32px;
border-radius: 15px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
max-width: 560px;
width: 100%;
transition: all 0.3s ease;
}
.wallet-container:hover {
box-shadow: 0 15px 50px rgba(0, 0, 0, 0.3);
}
h1 {
margin: 0 0 8px;
font-size: 24px;
font-weight: 600;
color: inherit;
}
.wallet-type {
font-size: 14px;
opacity: 0.7;
margin-bottom: 24px;
}
.balances-section {
text-align: left;
margin: 20px 0;
}
.balances-section-title {
font-size: 13px;
opacity: 0.7;
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.balance-card {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
border-radius: 10px;
background-color: rgba(255, 255, 255, 0.08);
margin-bottom: 8px;
transition: background-color 0.2s;
}
.balance-card:hover {
background-color: rgba(255, 255, 255, 0.14);
}
.balance-info {
display: flex;
flex-direction: column;
gap: 2px;
}
.balance-type {
font-size: 15px;
font-weight: 600;
display: flex;
align-items: center;
gap: 6px;
}
.balance-symbol {
font-size: 12px;
opacity: 0.6;
}
.balance-amount {
font-size: 20px;
font-weight: bold;
font-family: 'Courier New', monospace;
letter-spacing: 1px;
}
.balance-meta {
display: flex;
align-items: center;
gap: 10px;
}
.tradable-badge {
font-size: 10px;
padding: 2px 8px;
border-radius: 10px;
background-color: rgba(34, 197, 94, 0.2);
color: #22c55e;
border: 1px solid rgba(34, 197, 94, 0.4);
}
.untradable-badge {
font-size: 10px;
padding: 2px 8px;
border-radius: 10px;
background-color: rgba(148, 163, 184, 0.2);
color: #94a3b8;
border: 1px solid rgba(148, 163, 184, 0.4);
}
.exchange-btn {
background: var(--PrimaryColor, #2563eb);
color: #fff;
border: none;
padding: 6px 14px;
border-radius: 6px;
cursor: pointer;
font-size: 12px;
font-weight: 500;
transition: opacity 0.2s, transform 0.1s;
}
.exchange-btn:hover { opacity: 0.9; }
.exchange-btn:active { transform: scale(0.96); }
.exchange-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.address-container {
background-color: rgba(255, 255, 255, 0.06);
padding: 14px;
border-radius: 8px;
margin: 24px 0;
word-break: break-all;
}
.address-label {
font-size: 12px;
margin-bottom: 4px;
opacity: 0.7;
}
.address {
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.5;
margin-bottom: 10px;
}
.address:last-child { margin-bottom: 0; }
.qr-code-container {
margin: 24px auto;
width: 160px;
height: 160px;
padding: 8px;
background-color: white;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
}
.qr-code { width: 100%; height: 100%; }
.share-text {
margin-top: 12px;
font-size: 12px;
opacity: 0.7;
}
#unbound-message {
display: none;
padding: 10px;
border-radius: 5px;
margin: 20px 0;
font-weight: bold;
}
/* 兑换对话框 */
.exchange-overlay {
display: none;
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
align-items: center;
justify-content: center;
}
.exchange-overlay.open { display: flex; }
.exchange-dialog {
background: var(--CardBackground, #fff);
color: var(--TextPrimary, #1e293b);
border-radius: 12px;
padding: 24px;
width: 90%;
max-width: 380px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.exchange-dialog h3 {
margin: 0 0 18px;
font-size: 17px;
}
.exchange-field { margin-bottom: 14px; }
.exchange-field label {
display: block;
font-size: 12px;
opacity: 0.7;
margin-bottom: 4px;
}
.exchange-field input,
.exchange-field select {
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);
}
.exchange-field input:focus,
.exchange-field select:focus {
outline: none;
border-color: var(--PrimaryColor, #2563eb);
}
.exchange-info {
background: var(--BackgroundColor, #f5f5f5);
padding: 10px 12px;
border-radius: 6px;
font-size: 13px;
line-height: 1.8;
margin: 14px 0;
}
.exchange-info-row {
display: flex;
justify-content: space-between;
}
.exchange-actions {
display: flex;
gap: 10px;
margin-top: 18px;
}
.exchange-actions button {
flex: 1;
padding: 10px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: opacity 0.2s;
}
.btn-cancel {
background: var(--BackgroundColor, #f5f5f5);
color: var(--TextSecondary, #64748b);
}
.btn-confirm {
background: var(--PrimaryColor, #2563eb);
color: #fff;
}
.exchange-actions button:hover { opacity: 0.9; }
.exchange-actions button:disabled { opacity: 0.4; cursor: not-allowed; }
/* 颜色主题:基于 GERP 变量,不同 GC 等级使用不同强调色 */
.default-theme { background-color: var(--BackgroundColor, #f5f5f5); color: var(--TextPrimary, #333); }
.default-theme .wallet-container { background-color: var(--CardBackground, #ffffff); }
.green-theme { background-color: var(--BackgroundColor, #e8f5e9); color: var(--SuccessColor, #2e7d32); }
.green-theme .wallet-container { background-color: var(--CardBackground, #ffffff); }
.blue-theme { background-color: var(--BackgroundColor, #e3f2fd); color: var(--PrimaryColor, #1565c0); }
.blue-theme .wallet-container { background-color: var(--CardBackground, #ffffff); }
.gold-theme { background-color: var(--BackgroundColor, #212121); color: var(--WarningColor, #ffd700); }
.gold-theme .wallet-container { background-color: var(--CardBackground, #333333); }
.corporate-theme { background-color: var(--BackgroundColor, #0d47a1); color: var(--PrimaryColor, #ffffff); }
.corporate-theme .wallet-container { background-color: var(--CardBackground, #1565c0); }
.default-theme #unbound-message,
.green-theme #unbound-message,
.blue-theme #unbound-message {
background-color: var(--DangerAlpha10, #ffebee);
color: var(--DangerColor, #b71c1c);
}
.gold-theme #unbound-message,
.corporate-theme #unbound-message {
background-color: var(--DangerAlpha10, rgba(255, 255, 255, 0.1));
color: var(--DangerColor, #ff8a80);
}
</style>
<div class="wallet-container">
<h1>枢机信用储备银行 钱包</h1>
<div class="wallet-type">中心化数字资产钱包</div>
<div id="unbound-message" style="display: none;">
账户未绑定玩家
</div>
<div class="balances-section">
<div class="balances-section-title">资产列表</div>
<div id="balances-list">数据加载中...</div>
</div>
<div class="address-container">
<div class="address-label">玩家地址</div>
<div class="address" id="Player-uuid">等待请求...</div>
<div class="address-label">账户地址</div>
<div class="address" id="User-uuid">等待请求...</div>
</div>
<div class="qr-code-container">
<img id="share-qr" class="qr-code" alt="分享二维码" style="display:none;" />
<div id="qr-loading" style="color:#999;font-size:12px;">生成分享码中...</div>
</div>
<div class="share-text">
扫描二维码查看余额<br>
<span id="qr-expire" style="font-size:11px;opacity:0.6;"></span>
<button id="refresh-qr-btn" class="exchange-btn" style="margin-left:8px;padding:2px 10px;font-size:11px;">刷新</button>
</div>
</div>
<!-- 兑换对话框 -->
<div class="exchange-overlay" id="exchange-overlay">
<div class="exchange-dialog">
<h3>币种兑换</h3>
<div class="exchange-field">
<label>源币种</label>
<input type="text" id="from-currency" readonly />
</div>
<div class="exchange-field">
<label>目标币种</label>
<select id="to-currency"></select>
</div>
<div class="exchange-field">
<label>兑换金额 <span id="max-hint" style="opacity:0.6;"></span></label>
<input type="number" id="exchange-amount" step="0.0001" min="0" placeholder="输入金额" />
</div>
<div class="exchange-info">
<div class="exchange-info-row">
<span>汇率</span>
<span id="exchange-rate">—</span>
</div>
<div class="exchange-info-row">
<span>预计到账</span>
<span id="exchange-received">—</span>
</div>
</div>
<div class="exchange-actions">
<button class="btn-cancel" id="exchange-cancel">取消</button>
<button class="btn-confirm" id="exchange-confirm" disabled>确认兑换</button>
</div>
</div>
</div>
<script>
// 当前钱包数据(API 返回的 Balances)
let WalletData = null;
// 根据 GC 数量设置主题(保留旧逻辑,GC 为基准展示色阶)
function SetThemeByGC(amount) {
const body = document.body;
body.className = '';
if (amount === 0) {
body.classList.add('default-theme');
} else if (amount > 0 && amount <= 9999) {
body.classList.add('green-theme');
} else if (amount > 9999 && amount <= 999999) {
body.classList.add('blue-theme');
} else if (amount > 999999 && amount <= 9999999) {
body.classList.add('gold-theme');
} else if (amount > 9999999) {
body.classList.add('corporate-theme');
} else {
body.classList.add('default-theme');
}
}
// 格式化数字显示(保留三位小数,添加千分位)
function FormatAmount(n) {
if (n === null || n === undefined || isNaN(n)) return '0.000';
return Number(n).toLocaleString(undefined, {
minimumFractionDigits: 3,
maximumFractionDigits: 3
});
}
// 渲染币种列表
function RenderBalances(balances) {
const list = document.getElementById('balances-list');
if (!balances || balances.length === 0) {
list.innerHTML = '<div style="text-align:center;opacity:0.6;padding:20px;">暂无资产</div>';
return;
}
list.innerHTML = balances.map(b => {
const tradable = b.Tradable === 1 && b.Rate !== null && b.Rate !== undefined;
const badge = tradable
? '<span class="tradable-badge">可兑换</span>'
: '<span class="untradable-badge">不可兑换</span>';
const btn = tradable
? `<button class="exchange-btn" onclick="OpenExchange('${b.Type}')">兑换</button>`
: '';
return `
<div class="balance-card">
<div class="balance-info">
<div class="balance-type">${b.Type} ${badge}</div>
<div class="balance-symbol">${b.Symbol || ''}</div>
</div>
<div class="balance-meta">
<div class="balance-amount">${FormatAmount(b.Balance)}</div>
${btn}
</div>
</div>
`;
}).join('');
}
// 拉取钱包数据
function RefreshWallet() {
return fetch('/API/wallet', { credentials: 'include' })
.then(r => {
if (!r.ok) throw new Error('网络响应不正常');
return r.json();
})
.then(data => {
WalletData = data;
const balances = data.Balances || [];
RenderBalances(balances);
// GC 余额决定主题与二维码
const gc = balances.find(x => x.Type === 'GC');
const gcAmount = gc ? gc.Balance : -1;
if (gcAmount === -1 && balances.length === 0) {
document.getElementById('unbound-message').style.display = 'block';
} else {
document.getElementById('unbound-message').style.display = 'none';
}
const qrVal = gcAmount === -1 ? 0 : gcAmount;
// 生成24小时临时分享 token,二维码内容由后端构建
RefreshShareToken();
document.getElementById('Player-uuid').textContent = data.PlayerUUID || '无数据';
document.getElementById('User-uuid').textContent = data.UserUUID || '无数据';
SetThemeByGC(gcAmount === -1 ? -1 : gcAmount);
})
.catch(err => {
console.error('请求出错:', err);
document.getElementById('balances-list').innerHTML = '';
document.getElementById('Player-uuid').textContent = '请求失败';
document.getElementById('User-uuid').textContent = '请求失败';
document.getElementById('unbound-message').style.display = 'block';
document.getElementById('unbound-message').textContent = '数据加载失败,请刷新页面';
SetThemeByGC(-1);
});
}
// ===== 24小时临时分享 token =====
let ShareTokenExpireAt = 0;
let ShareTokenTimer = null;
function RefreshShareToken() {
const qrImg = document.getElementById('share-qr');
const loading = document.getElementById('qr-loading');
const expireEl = document.getElementById('qr-expire');
fetch('/API/Wallet/GenerateShareToken', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' }
})
.then(r => r.json())
.then(data => {
if (data.status !== 0) {
loading.textContent = data.msg || '生成分享码失败';
return;
}
const token = data.token;
ShareTokenExpireAt = data.expire_at;
// 二维码图片直接从后端获取
qrImg.src = `/API/Wallet/ShareQRCode?token=${encodeURIComponent(token)}`;
qrImg.style.display = 'block';
loading.style.display = 'none';
// 启动倒计时
if (ShareTokenTimer) clearInterval(ShareTokenTimer);
UpdateShareTokenExpire();
ShareTokenTimer = setInterval(UpdateShareTokenExpire, 1000);
})
.catch(err => {
console.error('生成分享码失败:', err);
loading.textContent = '生成分享码失败';
});
}
function UpdateShareTokenExpire() {
const expireEl = document.getElementById('qr-expire');
const now = Math.floor(Date.now() / 1000);
const remain = Math.max(0, Math.floor(ShareTokenExpireAt) - now);
if (remain <= 0) {
expireEl.textContent = '已过期,请刷新';
if (ShareTokenTimer) { clearInterval(ShareTokenTimer); ShareTokenTimer = null; }
return;
}
const h = Math.floor(remain / 3600);
const m = Math.floor((remain % 3600) / 60);
const s = remain % 60;
expireEl.textContent = `有效期剩余 ${h}时${m}分${s}秒`;
}
document.getElementById('refresh-qr-btn').addEventListener('click', RefreshShareToken);
// ===== 兑换对话框 =====
let ExchangeFromType = null;
function OpenExchange(fromType) {
ExchangeFromType = fromType;
const balances = (WalletData && WalletData.Balances) || [];
const fromItem = balances.find(b => b.Type === fromType);
if (!fromItem) return;
document.getElementById('from-currency').value = fromType;
document.getElementById('max-hint').textContent =
`(可用 ${FormatAmount(fromItem.Balance)})`;
// 填充目标币种下拉(可兑换且非源币种)
const toSelect = document.getElementById('to-currency');
toSelect.innerHTML = '';
balances.forEach(b => {
if (b.Type === fromType) return;
if (b.Tradable !== 1 || b.Rate === null || b.Rate === undefined) return;
const opt = document.createElement('option');
opt.value = b.Type;
opt.textContent = `${b.Type} (${b.Symbol || ''})`;
toSelect.appendChild(opt);
});
if (toSelect.options.length === 0) {
toSelect.innerHTML = '<option disabled>无可兑换目标币种</option>';
}
document.getElementById('exchange-amount').value = '';
document.getElementById('exchange-rate').textContent = '—';
document.getElementById('exchange-received').textContent = '—';
document.getElementById('exchange-confirm').disabled = true;
document.getElementById('exchange-overlay').classList.add('open');
// 触发一次计算
CalcExchange();
}
function CloseExchange() {
document.getElementById('exchange-overlay').classList.remove('open');
ExchangeFromType = null;
}
function CalcExchange() {
const balances = (WalletData && WalletData.Balances) || [];
const fromItem = balances.find(b => b.Type === ExchangeFromType);
const toType = document.getElementById('to-currency').value;
const toItem = balances.find(b => b.Type === toType);
const amountStr = document.getElementById('exchange-amount').value;
const amount = parseFloat(amountStr);
const rateEl = document.getElementById('exchange-rate');
const recvEl = document.getElementById('exchange-received');
const confirmBtn = document.getElementById('exchange-confirm');
if (!fromItem || !toItem || !fromItem.Rate || !toItem.Rate || isNaN(amount) || amount <= 0) {
rateEl.textContent = '—';
recvEl.textContent = '—';
confirmBtn.disabled = true;
return;
}
const rate = fromItem.Rate / toItem.Rate;
const received = amount * rate;
rateEl.textContent = `1 ${ExchangeFromType} = ${rate.toFixed(6)} ${toType}`;
recvEl.textContent = `${received.toFixed(4)} ${toType}`;
// 余额校验
const ok = amount <= fromItem.Balance;
confirmBtn.disabled = !ok;
if (!ok) {
recvEl.textContent = '余额不足';
}
}
function SubmitExchange() {
const toType = document.getElementById('to-currency').value;
const amount = parseFloat(document.getElementById('exchange-amount').value);
if (!ExchangeFromType || !toType || isNaN(amount) || amount <= 0) return;
const confirmBtn = document.getElementById('exchange-confirm');
confirmBtn.disabled = true;
confirmBtn.textContent = '兑换中...';
fetch('/API/Wallet/Exchange', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
FromCurrency: ExchangeFromType,
ToCurrency: toType,
Amount: amount
})
})
.then(r => r.json())
.then(data => {
if (data.status === 0) {
CloseExchange();
RefreshWallet();
} else {
alert(data.msg || '兑换失败');
confirmBtn.disabled = false;
confirmBtn.textContent = '确认兑换';
}
})
.catch(err => {
console.error('兑换出错:', err);
alert('网络错误,兑换失败');
confirmBtn.disabled = false;
confirmBtn.textContent = '确认兑换';
});
}
// 绑定事件
document.getElementById('to-currency').addEventListener('change', CalcExchange);
document.getElementById('exchange-amount').addEventListener('input', CalcExchange);
document.getElementById('exchange-cancel').addEventListener('click', CloseExchange);
document.getElementById('exchange-confirm').addEventListener('click', SubmitExchange);
document.getElementById('exchange-overlay').addEventListener('click', (e) => {
if (e.target.id === 'exchange-overlay') CloseExchange();
});
// 初始化
RefreshWallet();
// ==================== 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>