Files
2026-07-30 09:49:01 +08:00

612 lines
20 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 id="page-title">神彩算 - 充值</title>
<script src="../public/css/layui/layui.js"></script>
<script src="../config.js"></script>
<script src="../utils/ApiClient.js"></script>
<script src="../utils/DateUtil.js"></script>
<script src="../utils/CommonUtil.js"></script>
<link rel="stylesheet" href="../public/css/layui/css/layui.css" />
<link rel="stylesheet" href="../public/css/public.css" />
<link rel="stylesheet" href="../public/css/layout-fix.css" />
<link rel="stylesheet" href="../public/css/publish.css" />
<link rel="stylesheet" href="../public/css/finance.css" />
<link rel="stylesheet" href="../public/css/chongzhi.css" />
</head>
<body>
<!-- Header -->
<nav class="topbar" id="navbar">
<div class="topbar__inner">
<a href="index.html" class="topbar__brand">
<img
src="../images/home-logo.png"
alt="神彩算"
class="topbar__logo"
/>
</a>
<div class="topbar__nav" id="navLotteryItems"></div>
<div class="topbar__user">
<div id="notLoggedInBox" class="topbar__guest">
<a href="login.html" class="topbar__btn topbar__btn--ghost">登录</a>
<a href="reg.html" class="topbar__btn topbar__btn--primary"
>免费注册</a
>
</div>
<div id="loggedInBox" class="topbar__logged cz-hidden">
<a href="usercenter.html" class="topbar__profile">
<img
id="userAvatar"
src="../public/img/userimg.png"
alt=""
class="topbar__avatar"
/>
<div class="topbar__meta">
<span id="userName" class="topbar__uname">用户</span>
<span id="userLevel" class="topbar__ubadge">会员</span>
</div>
</a>
<button class="topbar__btn topbar__btn--logout" onclick="logout()">
退出登录
</button>
</div>
</div>
</div>
</nav>
<!-- Main -->
<main class="cxz-main">
<div class="cxz-breadcrumb">
<a href="index.html">首页</a><span class="sep">></span>
<a href="usercenter.html">个人中心</a><span class="sep">></span>
<span>充值</span>
</div>
<!-- Balance Overview -->
<div class="cz-balance-bar">
<div class="cz-balance-bar__item">
<div
class="cz-balance-bar__icon account"
>
<i class="layui-icon layui-icon-rmb"></i>
</div>
<div class="cz-balance-bar__info">
<div class="cz-balance-bar__label">账户余额</div>
<div class="cz-balance-bar__value" id="currentBalance">
0.00<span class="unit"></span>
</div>
</div>
</div>
</div>
<!-- Package Selection -->
<div class="cxz-form-card">
<div class="cxz-form-title">选择充值套餐</div>
<div class="cz-package-grid" id="packageGrid">
<div class="cz-package-loading">
<i
class="layui-icon layui-icon-loading-1 layui-anim layui-anim-rotate layui-anim-loop"
></i>
<span>加载套餐中...</span>
</div>
</div>
<button
class="cxz-submit-btn"
id="rechargeBtn"
onclick="handleRecharge()"
>
立即充值
</button>
</div>
<!-- QR Code Payment Section -->
<div class="cxz-form-card cz-hidden" id="qrcodeCard">
<div class="cz-qr-section" id="qrSection">
<div class="cz-qr-header">
<div class="cz-qr-icon">
<i class="layui-icon layui-icon-cellphone"></i>
</div>
<div class="cz-qr-title">微信扫码支付</div>
<div class="cz-qr-subtitle">请使用微信扫描下方二维码完成支付</div>
</div>
<div class="cz-qr-amount" id="payAmountDisplay">0<span></span></div>
<div class="cz-qr-wrap">
<div id="qrcodeContainer"></div>
</div>
<div class="cz-qr-hint">
二维码有效期 <strong>5分钟</strong>,请尽快完成支付
</div>
<div class="cz-qr-timer">
剩余时间:<span id="countdownTimer">05:00</span>
</div>
<button class="cz-qr-cancel" onclick="cancelPayment()">
取消支付
</button>
</div>
<div class="cz-pay-result cz-hidden" id="paymentSuccess">
<div class="cz-pay-result__icon success">
<i class="layui-icon layui-icon-ok-circle"></i>
</div>
<div class="cz-pay-result__title">支付成功</div>
<div class="cz-pay-result__desc">充值金额已到账,请查看余额</div>
<a href="usercenter.html" class="cz-pay-result__btn">返回个人中心</a>
</div>
<div class="cz-pay-result cz-hidden" id="paymentTimeout">
<div class="cz-pay-result__icon timeout">
<i class="layui-icon layui-icon-close-fill"></i>
</div>
<div class="cz-pay-result__title">支付超时</div>
<div class="cz-pay-result__desc">二维码已过期,请重新发起充值</div>
<button class="cz-pay-result__btn" onclick="resetPayment()">
重新充值
</button>
</div>
</div>
<!-- Tips -->
<div class="cz-tips-card">
<div class="cz-tips-title">
<i class="layui-icon layui-icon-about"></i> 充值说明
</div>
<ul class="cz-tips-list">
<li>充值后余额将实时到账,可用于购买付费方案</li>
<li>微信扫码支付请在5分钟内完成,超时需重新发起</li>
<li>如遇支付问题,请联系客服处理</li>
<li>充值金额不支持退款,请确认后再支付</li>
</ul>
</div>
</main>
<!-- Footer -->
<footer class="site-footer">
<div class="site-footer__inner">
<img
src="../images/home-logo.png"
alt="神彩算"
class="site-footer__logo"
/>
<p id="copyright-info">神彩算以开奖数据、免费文章、资讯推荐为核心</p>
<div class="site-footer__meta">
<a
id="beian"
href="https://beian.miit.gov.cn/"
target="_blank"
class="cz-footer-link"
>工信部备案号</a
>
<span id="additional-info">本站仅供数据分析参考</span>
</div>
</div>
</footer>
<button
class="gotop"
id="backTop"
type="button"
onclick="window.scrollTo({ top: 0, behavior: 'smooth' })"
>
</button>
<script src="../public/js/qrcode.min.js"></script>
<script>
const $ = layui.$;
var configList = [];
var selectedConfigId = "";
var selectedConfigAmount = 0;
var pollingTimer = null;
var countdownTimer = null;
var countdownSeconds = 300;
var pollingStartTime = 0;
var MAX_POLLING_DURATION = 300000;
var POLLING_INTERVAL = 3000;
var currentRechargeOutTradeNo = "";
var currentRechargeExpireTime = 0;
function escapeHtml(value) {
return String(value == null ? "" : value)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function logout() {
localStorage.removeItem("token");
window.location.reload();
}
function checkLogin() {
var token = localStorage.getItem("token");
if (token) {
$("#notLoggedInBox").hide();
$("#loggedInBox").css("display", "flex");
CommonUtil.getCurrentUserDetail(
function (data) {
$("#userName").text(data.nickName || "用户");
$("#userLevel").text(data.isExpert === "1" ? "专家" : "会员");
if (data.avatar) $("#userAvatar").attr("src", data.avatar);
var accountBalance = Number(
data.accountBalance || data.balance || data.amount || 0,
);
$("#currentBalance").html(
(Number.isFinite(accountBalance) ? accountBalance : 0).toFixed(2) +
'<span class="unit">\u5143</span>',
);
},
function () {
$("#notLoggedInBox").show();
$("#loggedInBox").hide();
},
);
} else {
NavigationUtil.open("login.html");
}
}
function renderNav() {
ApiClient.get(ApiClient.API.homeNav).then(function (res) {
if (!ApiClient.isSuccess(res) || !res.data) return;
var $nav = $("#navLotteryItems");
$nav.find(".topbar__nav-link").not(":first").remove();
$.each(ApiClient.asArray(res.data), function (i, item) {
if (item.type && item.type !== "lottery") return;
var code = item.code || item.suoxie || "";
if (!code) return;
$nav.append(
'<a href="cai.html?id=' +
encodeURIComponent(code) +
'" class="topbar__nav-link">' +
escapeHtml(item.name || item.lotteryName || code) +
"</a>",
);
});
});
}
$(window).on("scroll", function () {
$(window).scrollTop() > 320
? $("#backTop").addClass("is-visible")
: $("#backTop").removeClass("is-visible");
});
// Load packages from API
function loadConfigList() {
ApiClient.get(ApiClient.API.walletRechargeConfigList)
.then(function (res) {
var data = ApiClient.pickData ? ApiClient.pickData(res) : res.data;
var list = ApiClient.asArray(data);
if (ApiClient.isSuccess(res) && list.length > 0) {
configList = list;
// Sort by sort field
configList.sort(function (a, b) {
return (a.sort || 0) - (b.sort || 0);
});
renderPackages(configList);
} else {
renderEmptyPackages();
}
})
.catch(function () {
renderEmptyPackages("加载失败,请刷新重试");
});
}
function formatMoney(value) {
var number = Number(value || 0);
return Number.isFinite(number) ? number.toFixed(2) : "0.00";
}
function renderPackages(list) {
var $grid = $("#packageGrid");
$grid.empty();
$.each(list, function (i, item) {
var paidAmount =
parseFloat(item.paidAmount || item.amount || item.price) || 0;
var totalCredited =
parseFloat(
item.totalCredited ||
item.arrivalAmount ||
item.creditedAmount ||
item.receiveAmount,
) || paidAmount;
var bonus =
parseFloat(item.giftAmount || item.bonusAmount || item.giveAmount) ||
totalCredited - paidAmount;
var bonusRate =
paidAmount > 0 ? Math.round((bonus / paidAmount) * 100) : 0;
var tagHtml = "";
if (bonusRate >= 80) {
tagHtml = '<div class="cz-pkg__tag hot">超值</div>';
} else if (bonusRate >= 30) {
tagHtml = '<div class="cz-pkg__tag rec">推荐</div>';
}
var creditHtml =
'<div class="cz-pkg__credit">\u5230\u8d26 ' +
formatMoney(totalCredited) +
" \u5143</div>";
var bonusHtml =
bonus > 0
? '<div class="cz-pkg__bonus">\u8d60 ' +
formatMoney(bonus) +
" \u5143</div>"
: "";
var selectedClass = i === 0 ? " selected" : "";
if (i === 0) {
selectedConfigId = item.id;
selectedConfigAmount = paidAmount;
}
$grid.append(
'<div class="cz-pkg' +
selectedClass +
'" data-id="' +
item.id +
'" data-amount="' +
paidAmount +
'" onclick="selectPackage(this)">' +
tagHtml +
'<div class="cz-pkg__price">' +
formatMoney(paidAmount) +
"<span>\u5143</span></div>" +
creditHtml +
bonusHtml +
"</div>",
);
});
}
function renderEmptyPackages(msg) {
$("#packageGrid").html(
'<div class="cz-package-loading">' +
'<i class="layui-icon layui-icon-face-surprised"></i>' +
"<span>" +
(msg || "暂无充值套餐") +
"</span>" +
"</div>",
);
}
function selectPackage(el) {
$(".cz-pkg").removeClass("selected");
$(el).addClass("selected");
selectedConfigId = $(el).data("id");
selectedConfigAmount = parseFloat($(el).data("amount")) || 0;
}
// Handle recharge
function handleRecharge() {
if (!selectedConfigId) {
layui.layer.msg("请选择充值套餐", { icon: 2 });
return;
}
var $btn = $("#rechargeBtn");
$btn.text("正在生成订单...").prop("disabled", true);
ApiClient.post(ApiClient.API.walletRechargeOrder, {
rechargeId: selectedConfigId,
payType: "NATIVE",
returnUrl: window.location.href,
})
.then(function (res) {
$btn.text("\u7acb\u5373\u5145\u503c").prop("disabled", false);
var data = ApiClient.pickData ? ApiClient.pickData(res) : res.data;
if (ApiClient.isSuccess(res) && data) {
var payUrl = getRechargePaymentUrl(data);
var outTradeNo = getRechargeOutTradeNo(data);
var expireTime = getRechargeExpireTime(data);
var amount = data.amount || data.paidAmount || selectedConfigAmount;
if (!payUrl) {
layui.layer.msg("\u8ba2\u5355\u672a\u8fd4\u56de\u652f\u4ed8\u4e8c\u7ef4\u7801\u5185\u5bb9", { icon: 2 });
return;
}
if (!outTradeNo) {
layui.layer.msg("充值订单未返回订单号,无法查询支付状态", { icon: 2 });
return;
}
showQRCode(payUrl, amount, outTradeNo, expireTime);
} else {
layui.layer.msg(res.msg || "创建订单失败", { icon: 2 });
}
})
.catch(function () {
$btn.text("立即充值").prop("disabled", false);
layui.layer.msg("网络错误,请稍后重试", { icon: 2 });
});
}
function getRechargePaymentUrl(data) {
if (typeof data === "string") return data;
if (!data || typeof data !== "object") return "";
return (
data.qrCode ||
data.qrcode ||
data.payUrl ||
data.paymentUrl ||
data.codeUrl ||
data.code_url ||
data.qrCodeUrl ||
data.nativeUrl ||
data.url ||
""
);
}
function getRechargeOutTradeNo(data) {
if (!data || typeof data !== "object") return "";
return data.outTradeNo || data.out_trade_no || data.orderNo || data.order_no || data.tradeNo || data.trade_no || "";
}
function getRechargeExpireTime(data) {
if (!data || typeof data !== "object") return 0;
var value = data.expireTime || data.expire_time || data.expiredAt || data.expired_at || data.expiresAt || data.expires_at;
if (!value) return Date.now() + MAX_POLLING_DURATION;
var parsed = typeof value === "number" ? value : Date.parse(value);
if (!parsed || Number.isNaN(parsed)) return Date.now() + MAX_POLLING_DURATION;
return parsed < 10000000000 ? parsed * 1000 : parsed;
}
// Show QR code
function showQRCode(url, amount, outTradeNo, expireTime) {
$("#qrcodeCard").show();
$("#qrSection").show();
$("#paymentSuccess").hide();
$("#paymentTimeout").hide();
$("#qrcodeContainer").empty();
$("#payAmountDisplay").html(amount + "<span>元</span>");
new QRCode(document.getElementById("qrcodeContainer"), {
text: url,
width: 200,
height: 200,
colorDark: "#102b6a",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.H,
});
countdownSeconds = 300;
pollingStartTime = Date.now();
currentRechargeOutTradeNo = outTradeNo || "";
currentRechargeExpireTime = expireTime || Date.now() + MAX_POLLING_DURATION;
startCountdown();
startPolling();
$("html,body").animate(
{ scrollTop: $("#qrcodeCard").offset().top - 20 },
400,
);
}
// Countdown
function startCountdown() {
if (countdownTimer) clearInterval(countdownTimer);
countdownTimer = setInterval(function () {
countdownSeconds--;
if (countdownSeconds <= 0) {
clearInterval(countdownTimer);
stopPolling();
$("#qrSection").hide();
$("#paymentTimeout").show();
return;
}
var min = Math.floor(countdownSeconds / 60);
var sec = countdownSeconds % 60;
$("#countdownTimer").text(
(min < 10 ? "0" + min : min) + ":" + (sec < 10 ? "0" + sec : sec),
);
}, 1000);
}
// Polling for payment confirmation
function startPolling() {
stopPolling();
doPoll();
}
function doPoll() {
var elapsed = Date.now() - pollingStartTime;
if (elapsed >= MAX_POLLING_DURATION || Date.now() >= currentRechargeExpireTime) {
stopPolling();
clearInterval(countdownTimer);
$("#qrSection").hide();
$("#paymentTimeout").show();
return;
}
if (!currentRechargeOutTradeNo) {
scheduleNextPoll();
return;
}
queryRechargeOrderStatus(currentRechargeOutTradeNo)
.then(function (paid) {
if (paid) {
stopPolling();
if (countdownTimer) clearInterval(countdownTimer);
$("#qrSection").hide();
$("#paymentTimeout").hide();
$("#paymentSuccess").show();
layui.layer.msg("充值成功", { icon: 1 });
return;
}
scheduleNextPoll();
})
.catch(function () {
scheduleNextPoll();
});
}
function scheduleNextPoll() {
var delay = POLLING_INTERVAL + Math.floor(Math.random() * 1200);
pollingTimer = setTimeout(doPoll, delay);
}
function queryRechargeOrderStatus(outTradeNo) {
return ApiClient.get(ApiClient.API.walletRechargeOrderStatus(outTradeNo))
.then(function (res) {
if (!ApiClient.isSuccess(res)) return false;
var data = ApiClient.pickData ? ApiClient.pickData(res, {}) : res.data || {};
var status = String(data.status || data.payStatus || data.walletStatus || data.orderStatus || data.tradeStatus || "").toLowerCase();
return (
data.paid === true ||
data.success === true ||
data.paySuccess === true ||
status === "success" ||
status === "paid" ||
status === "2" ||
Number(data.status) === 2 ||
Number(data.payStatus) === 2
);
});
}
function stopPolling() {
if (pollingTimer) {
clearTimeout(pollingTimer);
pollingTimer = null;
}
}
function cancelPayment() {
stopPolling();
if (countdownTimer) clearInterval(countdownTimer);
$("#qrcodeCard").hide();
}
function resetPayment() {
$("#paymentTimeout").hide();
$("#qrSection").hide();
$("#qrcodeCard").hide();
$("html,body").animate({ scrollTop: 0 }, 300);
}
// Init
$(function () {
checkLogin();
renderNav();
CommonUtil.loadPageConfig({ ads: false, links: false });
CommonUtil.requireAuth(function () {
loadConfigList();
});
});
$(window).on("beforeunload", function () {
stopPolling();
if (countdownTimer) clearInterval(countdownTimer);
});
</script>
</body>
</html>