681 lines
25 KiB
HTML
681 lines
25 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>虾盘云 — 充值</title>
|
||
<style>
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
:root{
|
||
--bg:#f5f5f7;
|
||
--surface:#ffffff;
|
||
--border:#e5e5e5;
|
||
--border-hover:#d0d0d0;
|
||
--text:#1a1a1a;
|
||
--text-muted:#6b7280;
|
||
--text-dim:#9ca3af;
|
||
--blue:#2563eb;
|
||
--blue-light:#eff6ff;
|
||
--blue-border:#bfdbfe;
|
||
--green:#16a34a;
|
||
--green-light:#f0fdf4;
|
||
--green-border:#bbf7d0;
|
||
--orange:#ea580c;
|
||
--orange-light:#fff7ed;
|
||
--orange-border:#fed7aa;
|
||
--red:#dc2626;
|
||
--yellow:#d97706;
|
||
--radius:14px;
|
||
--shadow:0 1px 3px rgba(0,0,0,0.08),0 1px 2px rgba(0,0,0,0.04);
|
||
--shadow-hover:0 4px 12px rgba(0,0,0,0.1),0 2px 4px rgba(0,0,0,0.06);
|
||
}
|
||
body{font-family:-apple-system,"Microsoft YaHei","PingFang SC",sans-serif;background:var(--bg);color:var(--text);min-height:100vh}
|
||
|
||
/* Header */
|
||
.header{
|
||
background:var(--surface);
|
||
border-bottom:1px solid var(--border);
|
||
padding:0 28px;height:56px;
|
||
display:flex;align-items:center;justify-content:space-between;
|
||
position:sticky;top:0;z-index:10;
|
||
}
|
||
.header-left{display:flex;align-items:center;gap:10px}
|
||
.logo{width:28px;height:28px;border-radius:7px;border:1px solid var(--border)}
|
||
.header-title{font-size:.95em;font-weight:700;color:var(--text)}
|
||
.header-badge{
|
||
font-size:.68em;padding:2px 8px;border-radius:20px;font-weight:600;
|
||
background:var(--orange-light);color:var(--orange);border:1px solid var(--orange-border);
|
||
}
|
||
|
||
/* Layout */
|
||
.container{max-width:520px;margin:0 auto;padding:28px 20px 80px}
|
||
|
||
/* Section toggle */
|
||
.section{display:none}
|
||
.section.active{display:block}
|
||
|
||
/* Key 手动输入 */
|
||
.key-input-card{
|
||
background:var(--surface);border:1.5px solid var(--border);
|
||
border-radius:var(--radius);padding:20px;margin-bottom:16px;
|
||
box-shadow:var(--shadow);
|
||
}
|
||
.key-input-title{font-size:.88em;font-weight:600;color:var(--text-muted);margin-bottom:10px}
|
||
.key-input-wrap{display:flex;gap:8px}
|
||
.key-input-wrap input{
|
||
flex:1;padding:9px 12px;
|
||
background:var(--bg);border:1.5px solid var(--border);
|
||
border-radius:9px;color:var(--text);font-size:.86em;
|
||
outline:none;transition:border-color .15s;font-family:inherit;
|
||
}
|
||
.key-input-wrap input:focus{border-color:var(--orange)}
|
||
.key-confirm-btn{
|
||
padding:9px 16px;border:none;border-radius:9px;
|
||
background:var(--blue);color:#fff;font-size:.84em;font-weight:600;
|
||
cursor:pointer;white-space:nowrap;font-family:inherit;transition:background .15s;
|
||
}
|
||
.key-confirm-btn:hover{background:#1d4ed8}
|
||
|
||
/* 账户面板 */
|
||
.xp-panel{
|
||
background:var(--surface);
|
||
border:1.5px solid var(--orange-border);
|
||
border-radius:var(--radius);
|
||
overflow:hidden;
|
||
box-shadow:var(--shadow);
|
||
margin-bottom:16px;
|
||
}
|
||
.xp-panel-head{
|
||
background:var(--orange-light);
|
||
padding:14px 18px;
|
||
border-bottom:1px solid var(--orange-border);
|
||
display:flex;align-items:center;justify-content:space-between;gap:8px;
|
||
}
|
||
.xp-panel-head-left{display:flex;align-items:center;gap:8px}
|
||
.xp-panel-title{font-size:.9em;font-weight:700;color:var(--orange)}
|
||
.xp-panel-body{padding:16px 18px;display:flex;flex-direction:column;gap:10px}
|
||
|
||
/* Key 行 */
|
||
.xp-key-box{
|
||
background:var(--bg);border:1px solid var(--border);
|
||
border-radius:9px;padding:10px 12px;
|
||
}
|
||
.xp-key-label{font-size:.68em;color:var(--text-dim);margin-bottom:4px;font-weight:600;letter-spacing:.04em}
|
||
.xp-key-inner{display:flex;align-items:center;gap:6px}
|
||
.xp-key-val{
|
||
flex:1;font-size:.72em;color:var(--text-muted);
|
||
font-family:"Consolas","Courier New",monospace;
|
||
word-break:break-all;line-height:1.4;
|
||
}
|
||
.xp-copy-btn{
|
||
background:var(--surface);border:1px solid var(--border);
|
||
color:var(--text-muted);border-radius:6px;
|
||
padding:4px 10px;font-size:.7em;font-weight:600;
|
||
cursor:pointer;white-space:nowrap;font-family:inherit;
|
||
transition:all .15s;flex-shrink:0;
|
||
}
|
||
.xp-copy-btn:hover{border-color:var(--blue);color:var(--blue)}
|
||
|
||
/* 余额行 */
|
||
.xp-bal-box{
|
||
background:var(--bg);border:1px solid var(--border);
|
||
border-radius:9px;padding:10px 12px;
|
||
display:flex;align-items:center;gap:8px;
|
||
}
|
||
.xp-bal-label{font-size:.72em;color:var(--text-dim);flex-shrink:0}
|
||
.xp-bal-val{flex:1;font-size:.95em;font-weight:700;color:var(--green)}
|
||
.xp-bal-val.warn{color:var(--yellow)}
|
||
.xp-bal-val.low{color:var(--red)}
|
||
.xp-bal-val.dim{color:var(--text-dim);font-weight:400;font-size:.82em}
|
||
.xp-refresh-btn{
|
||
background:none;border:1px solid var(--border);
|
||
color:var(--text-dim);border-radius:6px;
|
||
padding:3px 9px;font-size:.68em;cursor:pointer;
|
||
font-family:inherit;transition:all .15s;white-space:nowrap;flex-shrink:0;
|
||
}
|
||
.xp-refresh-btn:hover{border-color:var(--border-hover);color:var(--text)}
|
||
.xp-refresh-btn:disabled{opacity:.45;cursor:default}
|
||
|
||
/* 充值表单卡片 */
|
||
.form-card{
|
||
background:var(--surface);border:1.5px solid var(--border);
|
||
border-radius:var(--radius);padding:20px;margin-bottom:16px;
|
||
box-shadow:var(--shadow);
|
||
}
|
||
.form-card-title{
|
||
font-size:.9em;font-weight:700;color:var(--text);
|
||
margin-bottom:16px;display:flex;align-items:center;gap:8px;
|
||
}
|
||
|
||
/* 金额网格 */
|
||
.amt-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:6px;margin-bottom:12px}
|
||
.amt-btn{
|
||
background:var(--bg);border:1.5px solid var(--border);
|
||
border-radius:10px;padding:12px 6px;text-align:center;
|
||
cursor:pointer;transition:all .15s;
|
||
}
|
||
.amt-btn:hover{border-color:var(--border-hover);box-shadow:var(--shadow-hover)}
|
||
.amt-btn.sel{border-color:var(--orange);background:var(--orange-light);box-shadow:0 0 0 1px var(--orange)}
|
||
.amt-cny{font-size:1em;font-weight:700;color:var(--text)}
|
||
.amt-quota{font-size:.62em;color:var(--text-dim);margin-top:2px}
|
||
.amt-btn.sel .amt-cny{color:var(--orange)}
|
||
.amt-btn.sel .amt-quota{color:rgba(234,88,12,.6)}
|
||
|
||
.custom-wrap{display:none;margin-bottom:8px}
|
||
.custom-inner{display:flex;align-items:center;gap:6px}
|
||
.custom-prefix{font-size:.9em;color:var(--text-muted)}
|
||
input[type="number"],input[type="text"]{
|
||
flex:1;padding:9px 12px;
|
||
background:var(--bg);border:1.5px solid var(--border);
|
||
border-radius:9px;color:var(--text);font-size:.86em;
|
||
outline:none;transition:border-color .15s;font-family:inherit;
|
||
-moz-appearance:textfield;
|
||
}
|
||
input[type="number"]::-webkit-outer-spin-button,
|
||
input[type="number"]::-webkit-inner-spin-button{-webkit-appearance:none}
|
||
input:focus{border-color:var(--orange)}
|
||
.input-hint{font-size:.74em;color:var(--text-dim);margin-top:5px}
|
||
|
||
/* 分割线 */
|
||
.divider{height:1px;background:var(--border);margin:16px 0}
|
||
|
||
/* QR */
|
||
.qr-tabs{display:flex;gap:5px;margin-bottom:10px}
|
||
.qr-tab{
|
||
flex:1;padding:7px;border-radius:8px;
|
||
font-size:.78em;font-weight:600;
|
||
cursor:pointer;border:1.5px solid var(--border);
|
||
background:transparent;color:var(--text-muted);
|
||
font-family:inherit;transition:all .15s;text-align:center;
|
||
}
|
||
.qr-tab.active{background:var(--green-light);border-color:var(--green-border);color:var(--green)}
|
||
.qr-box{display:none;text-align:center}
|
||
.qr-box.active{display:block}
|
||
.qr-img{
|
||
width:168px;height:168px;object-fit:contain;
|
||
border-radius:10px;border:1px solid var(--border);
|
||
background:var(--bg);margin:0 auto 8px;display:block;
|
||
}
|
||
.qr-placeholder{
|
||
width:168px;height:168px;border-radius:10px;
|
||
border:1.5px dashed var(--border);background:var(--bg);
|
||
margin:0 auto 8px;display:flex;align-items:center;justify-content:center;
|
||
flex-direction:column;gap:6px;color:var(--text-dim);font-size:.78em;
|
||
}
|
||
.qr-name{font-size:.82em;color:var(--text-muted);font-weight:600}
|
||
.qr-sub{font-size:.7em;color:var(--text-dim);margin-top:2px;line-height:1.5}
|
||
|
||
/* 截图上传 */
|
||
.upload-area{
|
||
border:1.5px dashed var(--border);border-radius:10px;
|
||
padding:22px 16px;text-align:center;cursor:pointer;
|
||
transition:all .15s;position:relative;overflow:hidden;
|
||
background:var(--bg);
|
||
}
|
||
.upload-area:hover{border-color:var(--border-hover);background:var(--surface)}
|
||
.upload-area.has{border-color:var(--green);border-style:solid;background:var(--green-light)}
|
||
.upload-area input{position:absolute;inset:0;opacity:0;cursor:pointer;width:100%;height:100%}
|
||
.upload-icon{font-size:1.8em;margin-bottom:6px;color:var(--text-dim)}
|
||
.upload-text{font-size:.82em;color:var(--text-muted)}
|
||
.upload-sub{font-size:.72em;color:var(--text-dim);margin-top:3px}
|
||
.upload-preview{max-width:100%;max-height:160px;border-radius:8px;object-fit:contain;display:none;margin:0 auto}
|
||
|
||
label{display:block;font-size:.8em;color:var(--text-muted);font-weight:600;margin-bottom:8px}
|
||
|
||
/* 提交按钮 */
|
||
.submit-btn{
|
||
width:100%;padding:13px;border:none;border-radius:10px;
|
||
font-size:.94em;font-weight:700;cursor:pointer;
|
||
font-family:inherit;transition:all .15s;
|
||
background:var(--orange);color:#fff;
|
||
box-shadow:0 2px 8px rgba(234,88,12,0.25);
|
||
}
|
||
.submit-btn:hover{background:#c2410c;box-shadow:0 4px 14px rgba(234,88,12,0.3);transform:translateY(-1px)}
|
||
.submit-btn:active{transform:none}
|
||
.submit-btn:disabled{background:var(--text-dim);cursor:not-allowed;box-shadow:none;transform:none}
|
||
|
||
/* 成功页 */
|
||
.success-view{
|
||
background:var(--surface);border:1.5px solid var(--green-border);
|
||
border-radius:var(--radius);padding:40px 24px;
|
||
text-align:center;box-shadow:var(--shadow);
|
||
}
|
||
.success-icon{font-size:3em;margin-bottom:16px}
|
||
.success-title{font-size:1.2em;font-weight:700;color:var(--green);margin-bottom:8px}
|
||
.success-desc{color:var(--text-muted);font-size:.86em;line-height:1.8;margin-bottom:20px}
|
||
.success-tip{
|
||
background:var(--green-light);border:1px solid var(--green-border);
|
||
border-radius:10px;padding:14px 16px;
|
||
font-size:.8em;color:var(--text-muted);line-height:1.8;text-align:left;
|
||
}
|
||
.back-btn{
|
||
display:inline-flex;align-items:center;gap:5px;
|
||
margin-top:20px;padding:9px 20px;
|
||
background:var(--bg);border:1.5px solid var(--border);
|
||
border-radius:8px;color:var(--text-muted);font-size:.84em;
|
||
cursor:pointer;font-family:inherit;transition:all .15s;
|
||
}
|
||
.back-btn:hover{border-color:var(--border-hover);color:var(--text)}
|
||
|
||
/* Toast */
|
||
.toast{
|
||
position:fixed;top:20px;right:20px;
|
||
background:var(--green);color:#fff;
|
||
padding:10px 18px;border-radius:8px;
|
||
font-size:.82em;font-weight:500;
|
||
opacity:0;transform:translateY(-8px);
|
||
transition:all .25s;z-index:999;pointer-events:none;
|
||
box-shadow:0 4px 12px rgba(0,0,0,0.15);
|
||
}
|
||
.toast.show{opacity:1;transform:translateY(0)}
|
||
.toast.error{background:var(--red)}
|
||
.toast.info{background:var(--blue)}
|
||
|
||
@media(max-width:480px){
|
||
.amt-grid{grid-template-columns:repeat(2,1fr)}
|
||
.header{padding:0 16px}
|
||
.container{padding:20px 14px 80px}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="header">
|
||
<div class="header-left">
|
||
<img class="logo" src="assets/logo.png" alt="" onerror="this.style.display='none'">
|
||
<span class="header-title">虾盘云 — 充值</span>
|
||
</div>
|
||
<span class="header-badge">虾盘出品</span>
|
||
</div>
|
||
|
||
<div class="container">
|
||
|
||
<!-- Key 手动输入(未检测到时显示) -->
|
||
<div class="key-input-card" id="keyInputCard" style="display:none">
|
||
<div class="key-input-title">未检测到 API Key,请手动粘贴</div>
|
||
<div class="key-input-wrap">
|
||
<input type="text" id="manualKeyInput" placeholder="sk-xp-... 或 sk-...">
|
||
<button class="key-confirm-btn" onclick="confirmManualKey()">确认</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 主表单 -->
|
||
<div class="section active" id="mainSection">
|
||
|
||
<!-- 账户面板 -->
|
||
<div class="xp-panel">
|
||
<div class="xp-panel-head">
|
||
<div class="xp-panel-head-left">
|
||
<span style="font-size:1.1em">🦐</span>
|
||
<span class="xp-panel-title">虾盘云 · 账户</span>
|
||
</div>
|
||
<button class="xp-refresh-btn" id="refreshBtn" onclick="loadBalance()">刷新</button>
|
||
</div>
|
||
<div class="xp-panel-body">
|
||
<div class="xp-key-box">
|
||
<div class="xp-key-label">我的专属 KEY</div>
|
||
<div class="xp-key-inner">
|
||
<div class="xp-key-val" id="keyDisplay">检测中...</div>
|
||
<button class="xp-copy-btn" onclick="copyKey()">复制</button>
|
||
</div>
|
||
</div>
|
||
<div class="xp-bal-box">
|
||
<span class="xp-bal-label">当前余额</span>
|
||
<span class="xp-bal-val dim" id="balanceVal">查询中...</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 充值表单 -->
|
||
<div class="form-card">
|
||
<div class="form-card-title">选择充值金额</div>
|
||
|
||
<div class="amt-grid">
|
||
<div class="amt-btn" onclick="selectAmt(20)" data-amt="20">
|
||
<div class="amt-cny">¥20</div>
|
||
<div class="amt-quota">+1000万</div>
|
||
</div>
|
||
<div class="amt-btn" onclick="selectAmt(50)" data-amt="50">
|
||
<div class="amt-cny">¥50</div>
|
||
<div class="amt-quota">+2500万</div>
|
||
</div>
|
||
<div class="amt-btn" onclick="selectAmt(100)" data-amt="100">
|
||
<div class="amt-cny">¥100</div>
|
||
<div class="amt-quota">+5000万</div>
|
||
</div>
|
||
<div class="amt-btn" onclick="selectAmt(0)" data-amt="0">
|
||
<div class="amt-cny">自定</div>
|
||
<div class="amt-quota">输入金额</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="custom-wrap" id="customWrap">
|
||
<div class="custom-inner">
|
||
<span class="custom-prefix">¥</span>
|
||
<input type="number" id="customAmt" placeholder="最低 ¥10" min="10" max="9999" oninput="onCustomInput()">
|
||
</div>
|
||
<div class="input-hint">¥1 = 50万 quota,¥10 起充</div>
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<label>扫码付款</label>
|
||
<div class="qr-tabs">
|
||
<button class="qr-tab active" onclick="switchQr('alipay',this)">支付宝</button>
|
||
<button class="qr-tab" onclick="switchQr('wechat',this)">微信支付</button>
|
||
</div>
|
||
<div class="qr-box active" id="qr-alipay">
|
||
<div class="qr-placeholder" id="qr-alipay-ph">
|
||
<div style="font-size:1.8em">📱</div>
|
||
<div>支付宝收款码</div>
|
||
<div style="font-size:.68em;margin-top:2px;opacity:.6">(管理员配置后显示)</div>
|
||
</div>
|
||
<img class="qr-img" id="qr-alipay-img" src="" alt="支付宝" style="display:none"
|
||
onerror="this.style.display='none';document.getElementById('qr-alipay-ph').style.display='flex'">
|
||
<div class="qr-name">支付宝</div>
|
||
<div class="qr-sub">扫码付款 · 备注填写您的 Key 后8位</div>
|
||
</div>
|
||
<div class="qr-box" id="qr-wechat">
|
||
<div class="qr-placeholder" id="qr-wechat-ph">
|
||
<div style="font-size:1.8em">💚</div>
|
||
<div>微信收款码</div>
|
||
<div style="font-size:.68em;margin-top:2px;opacity:.6">(管理员配置后显示)</div>
|
||
</div>
|
||
<img class="qr-img" id="qr-wechat-img" src="" alt="微信" style="display:none"
|
||
onerror="this.style.display='none';document.getElementById('qr-wechat-ph').style.display='flex'">
|
||
<div class="qr-name">微信支付</div>
|
||
<div class="qr-sub">扫码付款 · 备注填写您的 Key 后8位</div>
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<label>上传付款截图 <span style="color:var(--text-dim);font-weight:400">(可选,有图更快审核)</span></label>
|
||
<div class="upload-area" id="uploadArea" onclick="document.getElementById('screenshotInput').click()">
|
||
<input type="file" id="screenshotInput" accept="image/*" style="display:none" onchange="onFileSelect(event)">
|
||
<div id="uploadPh">
|
||
<div class="upload-icon">📸</div>
|
||
<div class="upload-text">点击上传付款截图</div>
|
||
<div class="upload-sub">支持 JPG / PNG,不超过 5MB</div>
|
||
</div>
|
||
<img id="uploadPreview" class="upload-preview">
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<label style="margin-top:4px">备注 <span style="color:var(--text-dim);font-weight:400">(可选)</span></label>
|
||
<input type="text" id="noteInput" placeholder="如有问题可备注,管理员会看到">
|
||
</div>
|
||
|
||
<button class="submit-btn" id="submitBtn" onclick="submitRecharge()">提交充值申请 →</button>
|
||
|
||
</div><!-- /mainSection -->
|
||
|
||
<!-- 成功页 -->
|
||
<div class="section" id="successSection">
|
||
<div class="success-view">
|
||
<div class="success-icon">✅</div>
|
||
<div class="success-title">申请已提交!</div>
|
||
<div class="success-desc">
|
||
您的充值申请已成功提交。<br>
|
||
管理员会在微信收到通知,一般 <strong style="color:var(--orange)">10分钟内</strong> 完成审核。
|
||
</div>
|
||
<div class="success-tip">
|
||
<strong style="color:var(--text)">审核完成后:</strong><br>
|
||
· 余额自动到账,无需任何操作<br>
|
||
· 点"刷新"即可看到最新余额<br>
|
||
· 如超过1小时未到账,请联系管理员
|
||
</div>
|
||
<button class="back-btn" onclick="goBack()">← 返回</button>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /container -->
|
||
|
||
<div class="toast" id="toast"></div>
|
||
|
||
<script>
|
||
const API_BASE = 'https://api.u-claw.org';
|
||
const RECHARGE_API = API_BASE + '/recharge';
|
||
|
||
let currentKey = '';
|
||
let selectedAmt = -1;
|
||
let screenshotB64 = '';
|
||
let screenshotMime = 'image/jpeg';
|
||
|
||
window.addEventListener('DOMContentLoaded', () => {
|
||
initKey();
|
||
loadQrCodes();
|
||
});
|
||
|
||
// ── Key 检测 ─────────────────────────────────────────────
|
||
function initKey() {
|
||
const params = new URLSearchParams(window.location.search);
|
||
let key = params.get('key') || params.get('api_key') || '';
|
||
if (!key) key = localStorage.getItem('xiapan_key') || '';
|
||
if (key) { setKey(key); return; }
|
||
fetchKeyFromConfigServer().then(k => {
|
||
if (k) setKey(k);
|
||
else showKeyInput();
|
||
});
|
||
}
|
||
|
||
async function fetchKeyFromConfigServer() {
|
||
const ports = [18799, 18798, 18797, 18796, 18789];
|
||
for (const port of ports) {
|
||
try {
|
||
const r = await fetch(`http://localhost:${port}/config`, {signal: AbortSignal.timeout(800)});
|
||
if (r.ok) {
|
||
const cfg = await r.json();
|
||
const k = cfg?.providers?.xiapanyun?.api_key || '';
|
||
if (k) return k;
|
||
}
|
||
} catch(e) {}
|
||
}
|
||
return '';
|
||
}
|
||
|
||
function setKey(key) {
|
||
currentKey = key;
|
||
localStorage.setItem('xiapan_key', key);
|
||
const masked = key.length > 14 ? key.slice(0, 10) + '...' + key.slice(-6) : key;
|
||
document.getElementById('keyDisplay').textContent = masked;
|
||
document.getElementById('keyInputCard').style.display = 'none';
|
||
loadBalance();
|
||
}
|
||
|
||
function showKeyInput() {
|
||
document.getElementById('keyInputCard').style.display = 'block';
|
||
document.getElementById('keyDisplay').textContent = '未检测到';
|
||
document.getElementById('balanceVal').textContent = '—';
|
||
document.getElementById('balanceVal').className = 'xp-bal-val dim';
|
||
}
|
||
|
||
function confirmManualKey() {
|
||
const val = document.getElementById('manualKeyInput').value.trim();
|
||
if (val.length < 8) { showToast('Key 格式不对,请重新确认', 'error'); return; }
|
||
setKey(val);
|
||
}
|
||
|
||
// ── 余额查询 ─────────────────────────────────────────────
|
||
async function loadBalance() {
|
||
if (!currentKey) return;
|
||
const btn = document.getElementById('refreshBtn');
|
||
const valEl = document.getElementById('balanceVal');
|
||
btn.disabled = true;
|
||
valEl.textContent = '查询中...';
|
||
valEl.className = 'xp-bal-val dim';
|
||
|
||
try {
|
||
const d = new Date();
|
||
const monthStart = d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-01';
|
||
const hdrs = {'Authorization': 'Bearer ' + currentKey};
|
||
const [subRes, usageRes] = await Promise.all([
|
||
fetch(API_BASE + '/v1/dashboard/billing/subscription', {headers: hdrs}),
|
||
fetch(API_BASE + '/v1/dashboard/billing/usage?start_date=' + monthStart, {headers: hdrs})
|
||
]);
|
||
if (!subRes.ok) throw new Error('Key 无效');
|
||
const sub = await subRes.json();
|
||
const usage = usageRes.ok ? await usageRes.json() : {total_usage: 0};
|
||
|
||
const total = sub.hard_limit_usd || 0;
|
||
const used = (usage.total_usage || 0) / 100;
|
||
const remaining = Math.max(0, total - used);
|
||
const remCny = remaining * 7.2;
|
||
|
||
let cls = '';
|
||
if (remCny < 5) cls = 'low';
|
||
else if (remCny < 20) cls = 'warn';
|
||
|
||
valEl.textContent = '¥' + remCny.toFixed(2);
|
||
valEl.className = 'xp-bal-val' + (cls ? ' ' + cls : '');
|
||
} catch(e) {
|
||
valEl.textContent = '查询失败';
|
||
valEl.className = 'xp-bal-val dim';
|
||
} finally {
|
||
btn.disabled = false;
|
||
}
|
||
}
|
||
|
||
// ── 二维码 ─────────────────────────────────────────────
|
||
async function loadQrCodes() {
|
||
try {
|
||
const r = await fetch(RECHARGE_API + '/config', {signal: AbortSignal.timeout(3000)});
|
||
if (r.ok) {
|
||
const cfg = await r.json();
|
||
if (cfg.qr_alipay) showQrImg('alipay', cfg.qr_alipay);
|
||
if (cfg.qr_wechat) showQrImg('wechat', cfg.qr_wechat);
|
||
}
|
||
} catch(e) {}
|
||
}
|
||
|
||
function showQrImg(type, src) {
|
||
const img = document.getElementById('qr-' + type + '-img');
|
||
const ph = document.getElementById('qr-' + type + '-ph');
|
||
img.src = src;
|
||
img.style.display = 'block';
|
||
ph.style.display = 'none';
|
||
}
|
||
|
||
function switchQr(type, btn) {
|
||
document.querySelectorAll('.qr-tab').forEach(t => t.classList.remove('active'));
|
||
document.querySelectorAll('.qr-box').forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
document.getElementById('qr-' + type).classList.add('active');
|
||
}
|
||
|
||
// ── 金额选择 ─────────────────────────────────────────────
|
||
function selectAmt(amt) {
|
||
document.querySelectorAll('.amt-btn').forEach(b => b.classList.remove('sel'));
|
||
document.querySelector(`.amt-btn[data-amt="${amt}"]`).classList.add('sel');
|
||
selectedAmt = amt;
|
||
document.getElementById('customWrap').style.display = amt === 0 ? 'block' : 'none';
|
||
if (amt !== 0) document.getElementById('customAmt').value = '';
|
||
}
|
||
|
||
function onCustomInput() {
|
||
selectedAmt = parseFloat(document.getElementById('customAmt').value) || 0;
|
||
}
|
||
|
||
function getEffectiveAmt() {
|
||
if (selectedAmt === 0) return parseFloat(document.getElementById('customAmt').value) || 0;
|
||
return selectedAmt;
|
||
}
|
||
|
||
// ── 截图上传 ─────────────────────────────────────────────
|
||
function onFileSelect(e) {
|
||
const file = e.target.files[0];
|
||
if (!file) return;
|
||
if (file.size > 5 * 1024 * 1024) { showToast('图片不超过5MB', 'error'); return; }
|
||
screenshotMime = file.type || 'image/jpeg';
|
||
const reader = new FileReader();
|
||
reader.onload = ev => {
|
||
const dataUrl = ev.target.result;
|
||
screenshotB64 = dataUrl.split(',')[1];
|
||
const preview = document.getElementById('uploadPreview');
|
||
preview.src = dataUrl;
|
||
preview.style.display = 'block';
|
||
document.getElementById('uploadPh').style.display = 'none';
|
||
document.getElementById('uploadArea').classList.add('has');
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
|
||
// ── 提交 ─────────────────────────────────────────────
|
||
async function submitRecharge() {
|
||
if (!currentKey) { showToast('请先输入您的 API Key', 'error'); return; }
|
||
const amount = getEffectiveAmt();
|
||
if (amount < 10) { showToast('充值金额最低 ¥10', 'error'); return; }
|
||
|
||
const btn = document.getElementById('submitBtn');
|
||
btn.disabled = true;
|
||
btn.textContent = '提交中...';
|
||
|
||
try {
|
||
const res = await fetch(RECHARGE_API + '/submit', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({
|
||
api_key: currentKey,
|
||
amount_cny: amount,
|
||
screenshot_b64: screenshotB64,
|
||
screenshot_mime: screenshotMime,
|
||
note: document.getElementById('noteInput').value.trim()
|
||
})
|
||
});
|
||
const data = await res.json();
|
||
if (!res.ok) throw new Error(data.error || '服务器错误');
|
||
|
||
document.getElementById('mainSection').classList.remove('active');
|
||
document.getElementById('successSection').classList.add('active');
|
||
} catch(e) {
|
||
showToast('提交失败:' + e.message, 'error');
|
||
btn.disabled = false;
|
||
btn.textContent = '提交充值申请 →';
|
||
}
|
||
}
|
||
|
||
function goBack() {
|
||
document.getElementById('successSection').classList.remove('active');
|
||
document.getElementById('mainSection').classList.add('active');
|
||
screenshotB64 = '';
|
||
document.getElementById('uploadPreview').style.display = 'none';
|
||
document.getElementById('uploadPh').style.display = 'block';
|
||
document.getElementById('uploadArea').classList.remove('has');
|
||
document.getElementById('screenshotInput').value = '';
|
||
document.getElementById('noteInput').value = '';
|
||
document.querySelectorAll('.amt-btn').forEach(b => b.classList.remove('sel'));
|
||
document.getElementById('customWrap').style.display = 'none';
|
||
document.getElementById('submitBtn').disabled = false;
|
||
document.getElementById('submitBtn').textContent = '提交充值申请 →';
|
||
selectedAmt = -1;
|
||
loadBalance();
|
||
}
|
||
|
||
// ── 工具 ─────────────────────────────────────────────
|
||
function copyKey() {
|
||
if (!currentKey) return;
|
||
navigator.clipboard.writeText(currentKey)
|
||
.then(() => showToast('Key 已复制'))
|
||
.catch(() => {
|
||
const el = document.createElement('textarea');
|
||
el.value = currentKey;
|
||
document.body.appendChild(el);
|
||
el.select();
|
||
document.execCommand('copy');
|
||
document.body.removeChild(el);
|
||
showToast('Key 已复制');
|
||
});
|
||
}
|
||
|
||
let toastTimer;
|
||
function showToast(msg, type='') {
|
||
const t = document.getElementById('toast');
|
||
t.textContent = msg;
|
||
t.className = 'toast show' + (type ? ' ' + type : '');
|
||
clearTimeout(toastTimer);
|
||
toastTimer = setTimeout(() => t.classList.remove('show'), 3000);
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|