fix:优化当前的项目
This commit is contained in:
292
app/static/upgrade.js
Normal file
292
app/static/upgrade.js
Normal file
@@ -0,0 +1,292 @@
|
||||
const $ = (id) => document.getElementById(id);
|
||||
let pendingOrderNo = "";
|
||||
let pendingPollTimer = null;
|
||||
let pendingPollCount = 0;
|
||||
|
||||
function setStatus(msg, danger = false) {
|
||||
const el = $("status");
|
||||
if (!el) return;
|
||||
el.style.color = danger ? "#b42318" : "#0f5f3d";
|
||||
el.textContent = msg;
|
||||
}
|
||||
|
||||
function setLoading(button, loading, idleText, loadingText) {
|
||||
if (!button) return;
|
||||
button.disabled = loading;
|
||||
button.textContent = loading ? loadingText : idleText;
|
||||
}
|
||||
|
||||
function importantNotice(message, title = "提示") {
|
||||
if (typeof window.uiAlert === "function") {
|
||||
void window.uiAlert(message, title);
|
||||
return;
|
||||
}
|
||||
setStatus(message);
|
||||
}
|
||||
|
||||
function openPayLink(url) {
|
||||
const payUrl = String(url || "").trim();
|
||||
if (!payUrl) return false;
|
||||
const tab = window.open(payUrl, "_blank", "noopener");
|
||||
if (tab) return true;
|
||||
window.location.href = payUrl;
|
||||
return true;
|
||||
}
|
||||
|
||||
async function postJSON(url, body) {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.detail || "请求失败");
|
||||
return data;
|
||||
}
|
||||
|
||||
async function fetchMe() {
|
||||
const res = await fetch("/api/auth/me");
|
||||
const data = await res.json();
|
||||
if (!data.logged_in) {
|
||||
window.location.href = "/auth?next=/upgrade";
|
||||
return null;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function formatDateTime(tsSec) {
|
||||
const n = Number(tsSec || 0);
|
||||
if (!n) return "-";
|
||||
return new Date(n * 1000).toLocaleString();
|
||||
}
|
||||
|
||||
function formatDate(tsSec) {
|
||||
const n = Number(tsSec || 0);
|
||||
if (!n) return "-";
|
||||
const d = new Date(n * 1000);
|
||||
const y = d.getFullYear();
|
||||
const m = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
return `${y}-${m}-${day}`;
|
||||
}
|
||||
|
||||
function refreshPurchaseCycleText() {
|
||||
const el = $("purchaseCycleText");
|
||||
if (!el) return;
|
||||
const start = Math.floor(Date.now() / 1000);
|
||||
const end = start + 30 * 24 * 3600;
|
||||
el.textContent = `时长:1个月(到期:${formatDate(end)})`;
|
||||
}
|
||||
|
||||
function renderVip(vip) {
|
||||
const shared = Number(vip.shared_credits || vip.token_balance || 0);
|
||||
const seatRemaining = Number(vip.seat_remaining_credits || 0);
|
||||
const totalAvailable = Number(vip.total_available_credits || seatRemaining + shared);
|
||||
const enabled = Boolean(vip.vip_enabled);
|
||||
const cycleStart = Number(vip.cycle_started_at || 0);
|
||||
const cycleEnd = Number(vip.cycle_expires_at || 0);
|
||||
if ($("upgradeTokenBalance")) $("upgradeTokenBalance").textContent = String(totalAvailable);
|
||||
if ($("upgradeTokenBalanceHero")) $("upgradeTokenBalanceHero").textContent = String(totalAvailable);
|
||||
if ($("vipTokenBalance")) $("vipTokenBalance").textContent = String(shared);
|
||||
if ($("vipSeatRemaining")) $("vipSeatRemaining").textContent = String(seatRemaining);
|
||||
if ($("vipTotalConsumed")) $("vipTotalConsumed").textContent = String(Number(vip.total_consumed_tokens || 0));
|
||||
if ($("vipEnabledSelect")) $("vipEnabledSelect").value = enabled ? "1" : "0";
|
||||
if ($("vipStateText")) {
|
||||
$("vipStateText").textContent = totalAvailable <= 0 ? "额度已用完" : enabled ? "平台模型已开启" : "平台模型已关闭";
|
||||
}
|
||||
if ($("vipCycleHint")) {
|
||||
if (cycleStart > 0 && cycleEnd > 0) {
|
||||
const startText = formatDateTime(cycleStart);
|
||||
const endText = formatDateTime(cycleEnd);
|
||||
$("vipCycleHint").textContent = `当前周期:${startText} - ${endText}(到期自动清零)`;
|
||||
} else {
|
||||
$("vipCycleHint").textContent = "当前未开始月周期,首次支付成功后开始计时。";
|
||||
}
|
||||
}
|
||||
if (totalAvailable <= 0) {
|
||||
setStatus("Credits 额度已用完,请充值共享加油包或等待下个计费周期。", true);
|
||||
}
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const me = await fetchMe();
|
||||
if (me && me.vip) renderVip(me.vip);
|
||||
}
|
||||
|
||||
async function fetchBillingOverview() {
|
||||
const res = await fetch("/api/billing/overview");
|
||||
const data = await res.json();
|
||||
if (!res.ok || !data.ok) throw new Error(data.detail || "账单读取失败");
|
||||
return data;
|
||||
}
|
||||
|
||||
function stopPendingPoll() {
|
||||
if (pendingPollTimer) {
|
||||
window.clearInterval(pendingPollTimer);
|
||||
pendingPollTimer = null;
|
||||
}
|
||||
pendingPollCount = 0;
|
||||
}
|
||||
|
||||
function startPendingPoll(orderNo, showCreatedNotice = true) {
|
||||
if (!orderNo) return;
|
||||
pendingOrderNo = orderNo;
|
||||
stopPendingPoll();
|
||||
const msg = `订单 ${orderNo} 已创建,请完成支付,系统将自动刷新余额。`;
|
||||
setStatus(msg);
|
||||
if (showCreatedNotice) {
|
||||
importantNotice(msg, "订单已创建");
|
||||
}
|
||||
pendingPollTimer = window.setInterval(async () => {
|
||||
pendingPollCount += 1;
|
||||
try {
|
||||
const data = await fetchBillingOverview();
|
||||
const records = Array.isArray(data.recharge_records) ? data.recharge_records : [];
|
||||
const current = records.find((r) => (r.order_no || "") === pendingOrderNo);
|
||||
if (current && (current.status || "") === "paid") {
|
||||
stopPendingPoll();
|
||||
pendingOrderNo = "";
|
||||
setStatus("支付成功,余额已自动刷新。");
|
||||
await refresh();
|
||||
return;
|
||||
}
|
||||
if (current && ["cancelled", "closed"].includes(String(current.status || "").toLowerCase())) {
|
||||
stopPendingPoll();
|
||||
pendingOrderNo = "";
|
||||
setStatus("订单超过15分钟未支付,已自动取消。", true);
|
||||
await refresh();
|
||||
return;
|
||||
}
|
||||
if (pendingPollCount >= 120) {
|
||||
stopPendingPoll();
|
||||
setStatus("订单仍未支付,可支付后点击刷新查看余额。", true);
|
||||
}
|
||||
} catch {
|
||||
if (pendingPollCount >= 120) {
|
||||
stopPendingPoll();
|
||||
}
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
const saveVipBtn = $("saveVipBtn");
|
||||
const vipRechargeBtn = $("vipRechargeBtn");
|
||||
const vipRechargeTokensInput = $("vipRechargeTokens");
|
||||
const vipRechargeAmountInput = $("vipRechargeAmount");
|
||||
const payChannelOptions = Array.from(document.querySelectorAll(".pay-channel-option"));
|
||||
let selectedPayChannel = "wechat";
|
||||
|
||||
function bindPayChannelOptions() {
|
||||
if (!payChannelOptions.length) return;
|
||||
payChannelOptions.forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
selectedPayChannel = (btn.dataset.channel || "wechat").trim() || "wechat";
|
||||
payChannelOptions.forEach((item) => {
|
||||
const active = item === btn;
|
||||
item.classList.toggle("is-active", active);
|
||||
item.setAttribute("aria-pressed", active ? "true" : "false");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function packageRate() {
|
||||
const credits = Number((vipRechargeTokensInput && vipRechargeTokensInput.dataset.packageCredits) || "1500");
|
||||
const amount = Number((vipRechargeAmountInput && vipRechargeAmountInput.dataset.packageAmount) || "19.9");
|
||||
return {
|
||||
packageCredits: Number.isFinite(credits) && credits > 0 ? credits : 1500,
|
||||
packageAmount: Number.isFinite(amount) && amount > 0 ? amount : 19.9,
|
||||
};
|
||||
}
|
||||
|
||||
function syncRechargeAmount() {
|
||||
const { packageCredits, packageAmount } = packageRate();
|
||||
const credits = packageCredits;
|
||||
const amount = packageAmount;
|
||||
if (vipRechargeTokensInput) vipRechargeTokensInput.value = String(credits);
|
||||
vipRechargeAmountInput.value = amount.toFixed(2);
|
||||
if ($("purchaseCredits")) $("purchaseCredits").textContent = String(credits);
|
||||
if ($("purchaseAmount")) $("purchaseAmount").textContent = `¥${amount.toFixed(2)}`;
|
||||
}
|
||||
|
||||
if (vipRechargeAmountInput) vipRechargeAmountInput.readOnly = true;
|
||||
syncRechargeAmount();
|
||||
refreshPurchaseCycleText();
|
||||
bindPayChannelOptions();
|
||||
|
||||
if (saveVipBtn) {
|
||||
saveVipBtn.addEventListener("click", async () => {
|
||||
setLoading(saveVipBtn, true, "保存升级设置", "保存中...");
|
||||
try {
|
||||
const enabled = (($("vipEnabledSelect") && $("vipEnabledSelect").value) || "0") === "1";
|
||||
const out = await postJSON("/api/auth/vip/toggle", { enabled });
|
||||
if (!out.ok) {
|
||||
setStatus(out.detail || "VIP 设置保存失败", true);
|
||||
return;
|
||||
}
|
||||
setStatus("升级设置已保存。");
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
setStatus(e.message || "VIP 设置保存失败", true);
|
||||
} finally {
|
||||
setLoading(saveVipBtn, false, "保存升级设置", "保存中...");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (vipRechargeBtn) {
|
||||
vipRechargeBtn.addEventListener("click", async () => {
|
||||
setLoading(vipRechargeBtn, true, "订阅", "创建订单中...");
|
||||
try {
|
||||
const tokens = Number((($("vipRechargeTokens") && $("vipRechargeTokens").value) || "0").trim());
|
||||
const amount = Number((($("vipRechargeAmount") && $("vipRechargeAmount").value) || "0").trim());
|
||||
if (!Number.isFinite(tokens) || tokens <= 0) {
|
||||
setStatus("请输入正确的 Credits 数量", true);
|
||||
return;
|
||||
}
|
||||
if (!Number.isFinite(amount) || amount <= 0) {
|
||||
setStatus("请输入正确的支付金额", true);
|
||||
return;
|
||||
}
|
||||
const out = await postJSON("/api/pay/wechat/", {
|
||||
tokens: Math.round(tokens),
|
||||
amount_cny: Number(amount.toFixed(2)),
|
||||
channel: selectedPayChannel || "wechat",
|
||||
subscriber_name: "",
|
||||
subscriber_phone: "",
|
||||
shipping_address: "",
|
||||
});
|
||||
if (!out.ok) {
|
||||
setStatus(out.detail || "充值失败", true);
|
||||
return;
|
||||
}
|
||||
const orderNo = out.order && out.order.order_no ? out.order.order_no : "";
|
||||
if (orderNo) startPendingPoll(orderNo, true);
|
||||
if (out.pay_url) {
|
||||
openPayLink(out.pay_url);
|
||||
return;
|
||||
}
|
||||
const tip = "订单已创建,但未获取到支付链接,请检查支付网关配置。";
|
||||
setStatus(tip, true);
|
||||
importantNotice(tip, "支付链接缺失");
|
||||
} catch (e) {
|
||||
setStatus(e.message || "充值失败", true);
|
||||
} finally {
|
||||
setLoading(vipRechargeBtn, false, "订阅", "创建订单中...");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
refresh();
|
||||
(async () => {
|
||||
await fetchMe();
|
||||
try {
|
||||
const data = await fetchBillingOverview();
|
||||
const records = Array.isArray(data.recharge_records) ? data.recharge_records : [];
|
||||
const pending = records.find((r) => (r.status || "") === "pending");
|
||||
if (pending && pending.order_no) startPendingPoll(pending.order_no, false);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
})();
|
||||
window.setInterval(refreshPurchaseCycleText, 60000);
|
||||
Reference in New Issue
Block a user