(function () { "use strict"; const root = document.getElementById("options-settings-root"); if (!root) return; const SWAP_BTNS = ["opt-set-swap-btn", "opt-set-swap-all-btn"]; const INT_BTNS = ["opt-set-int-btn", "opt-set-int-all-btn"]; async function apiJson(url, opts) { const r = await fetch(url, Object.assign({ credentials: "same-origin" }, opts || {})); return r.json(); } function refreshFundsAfterMutation() { if (typeof refreshAccountSnapshot !== "function") return; refreshAccountSnapshot({ force: true }); setTimeout(function () { refreshAccountSnapshot({ force: true, silent: true }); }, 1500); } function setMsg(id, text, isErr) { const el = document.getElementById(id); if (!el) return; el.textContent = text || ""; el.classList.toggle("opt-error", !!isErr); el.classList.toggle("opt-success", !!text && !isErr); } function fmtAmt(amount, ccy) { return `${Number(amount).toFixed(2)} ${ccy}`; } function accountLabel(acct) { return acct === "trading" ? "交易账户" : "资金账户"; } function swapDirLabel(dir) { return dir === "usdc_to_usdt" ? "USDC → USDT" : "USDT → USDC"; } function confirmOk(message) { return window.confirm(message); } function setButtonsBusy(btnIds, busy, busyText) { btnIds.forEach(function (id) { const btn = document.getElementById(id); if (!btn) return; if (busy) { if (!btn.dataset.origText) btn.dataset.origText = btn.textContent; btn.disabled = true; if (busyText) btn.textContent = busyText; } else { btn.disabled = false; if (btn.dataset.origText) { btn.textContent = btn.dataset.origText; delete btn.dataset.origText; } } }); const amountIds = { "opt-set-swap-btn": "opt-set-swap-amount", "opt-set-swap-all-btn": "opt-set-swap-amount", "opt-set-int-btn": "opt-set-int-amount", "opt-set-int-all-btn": "opt-set-int-amount", "opt-set-cross-btn": "opt-set-cross-amount", "opt-set-cross-all-btn": "opt-set-cross-amount", }; btnIds.forEach(function (id) { const input = document.getElementById(amountIds[id]); if (input) input.disabled = busy; }); } function roundAvail(v) { const n = Number(v); if (!Number.isFinite(n) || n <= 0) return null; return Math.round(n * 100) / 100; } async function loadBalances(force, scope) { const parts = []; if (force) parts.push("force=1"); if (scope && scope !== "main") parts.push("scope=" + encodeURIComponent(scope)); const q = parts.length ? "?" + parts.join("&") : ""; const d = await apiJson("/api/options/balances" + q); if (!d.ok) throw new Error(d.msg || "余额拉取失败"); return d; } function pickBalance(bal, account, ccy) { const acct = account === "trading" ? "trading" : "funding"; const c = String(ccy || "").toLowerCase(); const availKey = acct + "_" + c + "_avail"; const totalKey = acct + "_" + c; return roundAvail(bal[availKey] != null ? bal[availKey] : bal[totalKey]); } async function resolveSwapMaxAmount(dir) { const bal = await loadBalances(true, "main"); const ccy = dir === "usdc_to_usdt" ? "USDC" : "USDT"; // 币种兑换走资金账户现货;统一账户下 USDT 有时在交易户,市价单仍可能成交 let amount = pickBalance(bal, "funding", ccy); let source = "funding"; if (!amount && ccy === "USDT") { const tradingAmt = pickBalance(bal, "trading", ccy); if (tradingAmt) { amount = tradingAmt; source = "trading"; } } return { amount, bal, ccy, source }; } async function resolveMaxAmount(account, ccy, scope) { const bal = await loadBalances(true, scope || "main"); return pickBalance(bal, account, ccy); } async function submitSwap(amount) { setButtonsBusy(SWAP_BTNS, true, "兑换中…"); setMsg("opt-set-swap-msg", "兑换中,市价成交可能有延时…", false); try { const d = await apiJson("/api/options/spot/swap", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ direction: document.getElementById("opt-set-swap-dir").value, amount: amount, }), }); if (d.ok) { setMsg("opt-set-swap-msg", "兑换成功", false); refreshFundsAfterMutation(); } else { setMsg("opt-set-swap-msg", "兑换失败:" + (d.msg || "未知错误"), true); } return d; } catch (e) { setMsg("opt-set-swap-msg", "兑换失败:" + (e.message || "网络错误"), true); return { ok: false }; } finally { setButtonsBusy(SWAP_BTNS, false); } } const swapBtn = document.getElementById("opt-set-swap-btn"); if (swapBtn) { swapBtn.addEventListener("click", async function () { const amount = parseFloat(document.getElementById("opt-set-swap-amount").value); if (!amount || amount <= 0) { setMsg("opt-set-swap-msg", "请输入有效数量", true); return; } await submitSwap(amount); }); } const swapAllBtn = document.getElementById("opt-set-swap-all-btn"); if (swapAllBtn) { swapAllBtn.addEventListener("click", async function () { try { const dir = document.getElementById("opt-set-swap-dir").value; const { amount, bal, ccy, source } = await resolveSwapMaxAmount(dir); if (!amount) { const fu = bal.funding_usdt_avail != null ? bal.funding_usdt_avail : bal.funding_usdt; const tu = bal.trading_usdt_avail != null ? bal.trading_usdt_avail : bal.trading_usdt; const fc = bal.funding_usdc_avail != null ? bal.funding_usdc_avail : bal.funding_usdc; setMsg( "opt-set-swap-msg", "资金账户可用 " + ccy + " 不足(资金户 USDT:" + (fu != null ? fu : "—") + " USDC:" + (fc != null ? fc : "—") + "; 交易户 USDT:" + (tu != null ? tu : "—") + ")", true ); return; } const srcLabel = source === "trading" ? "交易账户" : "资金账户"; const msg = "确认全部兑换?\n\n" + "方向:" + swapDirLabel(dir) + "\n" + "金额:" + fmtAmt(amount, ccy) + "\n" + "来源:" + srcLabel + "\n\n" + "将按该账户可用余额发起市价兑换(可能有延时)。请确认。"; if (!confirmOk(msg)) return; document.getElementById("opt-set-swap-amount").value = String(amount); await submitSwap(amount); } catch (e) { setMsg("opt-set-swap-msg", "兑换失败:" + (e.message || "余额拉取失败"), true); } }); } async function submitInternalTransfer(amount) { setButtonsBusy(INT_BTNS, true, "划转中…"); setMsg("opt-set-int-msg", "划转中…", false); try { const d = await apiJson("/api/options/transfer", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ccy: document.getElementById("opt-set-int-ccy").value, from: document.getElementById("opt-set-int-from").value, to: document.getElementById("opt-set-int-to").value, amount: amount, }), }); if (d.ok) { setMsg("opt-set-int-msg", "划转成功", false); refreshFundsAfterMutation(); } else { setMsg("opt-set-int-msg", "划转失败:" + (d.msg || "未知错误"), true); } return d; } catch (e) { setMsg("opt-set-int-msg", "划转失败:" + (e.message || "网络错误"), true); return { ok: false }; } finally { setButtonsBusy(INT_BTNS, false); } } const intBtn = document.getElementById("opt-set-int-btn"); if (intBtn) { intBtn.addEventListener("click", async function () { const amount = parseFloat(document.getElementById("opt-set-int-amount").value); if (!amount || amount <= 0) { setMsg("opt-set-int-msg", "请输入有效数量", true); return; } await submitInternalTransfer(amount); }); } const intAllBtn = document.getElementById("opt-set-int-all-btn"); if (intAllBtn) { intAllBtn.addEventListener("click", async function () { try { const ccy = document.getElementById("opt-set-int-ccy").value; const from = document.getElementById("opt-set-int-from").value; const to = document.getElementById("opt-set-int-to").value; const amount = await resolveMaxAmount(from, ccy, "main"); if (!amount) { setMsg("opt-set-int-msg", "划出账户可用余额不足", true); return; } const msg = "确认全部划转?\n\n" + "币种:" + ccy + "\n" + "划出:" + accountLabel(from) + "\n" + "划入:" + accountLabel(to) + "\n" + "金额:" + fmtAmt(amount, ccy) + "\n\n" + "将划转该账户全部可用余额。"; if (!confirmOk(msg)) return; document.getElementById("opt-set-int-amount").value = String(amount); await submitInternalTransfer(amount); } catch (e) { setMsg("opt-set-int-msg", "划转失败:" + (e.message || "余额拉取失败"), true); } }); } function hardenAmountAutofill(ids) { ids.forEach(function (id) { const el = document.getElementById(id); if (!el) return; function wipe() { const v = String(el.value || "").trim(); if (/^[a-z][a-z0-9._-]{1,31}$/i.test(v)) el.value = ""; } wipe(); el.setAttribute("readonly", "readonly"); el.addEventListener("focus", function () { el.removeAttribute("readonly"); }); el.addEventListener("blur", function () { if (!el.value) el.setAttribute("readonly", "readonly"); }); setTimeout(wipe, 200); setTimeout(wipe, 800); setTimeout(wipe, 2000); }); } // 全部划转/兑换前去掉 readonly,避免写不进数量 ["opt-set-swap-all-btn", "opt-set-int-all-btn"].forEach(function (btnId) { const btn = document.getElementById(btnId); if (!btn) return; btn.addEventListener( "click", function () { const map = { "opt-set-swap-all-btn": "opt-set-swap-amount", "opt-set-int-all-btn": "opt-set-int-amount", }; const input = document.getElementById(map[btnId]); if (input) input.removeAttribute("readonly"); }, true ); }); hardenAmountAutofill(["opt-set-swap-amount", "opt-set-int-amount"]); })();