diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css index 9479747..e4c165e 100644 --- a/lib/common/static/instance_theme.css +++ b/lib/common/static/instance_theme.css @@ -4577,6 +4577,29 @@ html[data-theme="light"] .opt-order-chip { gap: 16px; align-items: stretch; } +.options-order-col { + display: flex; + flex-direction: column; + gap: 12px; + min-width: 0; +} +.options-funds-card { + flex: 0 0 auto; +} +.options-funds-card h2 { + margin: 0 0 8px; + font-size: 0.9rem; + font-weight: 600; +} +.options-funds-card .options-settings-section + .options-settings-section { + margin-top: 12px; + padding-top: 10px; + border-top: 1px solid var(--border-soft, #2a3150); +} +.options-funds-card .options-settings-hint { + margin: 0 0 6px; + font-size: 0.72rem; +} .options-order-card, .options-pos-card-wrap { display: flex; diff --git a/lib/common/static/options_settings.js b/lib/common/static/options_settings.js index 4648b5b..41a3957 100644 --- a/lib/common/static/options_settings.js +++ b/lib/common/static/options_settings.js @@ -1,11 +1,9 @@ (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"]; + const hasSettings = !!document.getElementById("options-settings-root"); + const hasPageFunds = !!document.getElementById("options-funds-card"); + if (!hasSettings && !hasPageFunds) return; async function apiJson(url, opts) { const r = await fetch(url, Object.assign({ credentials: "same-origin" }, opts || {})); @@ -44,36 +42,6 @@ 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; @@ -101,7 +69,6 @@ async function resolveSwapMaxAmount(dir) { const bal = await loadBalances(true, "main"); const ccy = dir === "usdc_to_usdt" ? "USDC" : "USDT"; - // 现货 USDC-USDT 走交易账户(cash); 交易户不足时再看资金户 let amount = pickBalance(bal, "trading", ccy); let source = "trading"; if (!amount) { @@ -119,153 +86,24 @@ 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(); + function setButtonsBusy(btnIds, amountId, 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 { - 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; + btn.disabled = false; + if (btn.dataset.origText) { + btn.textContent = btn.dataset.origText; + delete btn.dataset.origText; } - 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); } }); + const input = document.getElementById(amountId); + if (input) input.disabled = busy; } function hardenAmountAutofill(ids) { @@ -290,23 +128,223 @@ }); } - // 全部划转/兑换前去掉 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 - ); - }); + function bindFundsUi(ids) { + const swapBtn = document.getElementById(ids.swapBtn); + if (!swapBtn) return; - hardenAmountAutofill(["opt-set-swap-amount", "opt-set-int-amount"]); + const swapBtns = [ids.swapBtn, ids.swapAllBtn]; + const intBtns = [ids.intBtn, ids.intAllBtn]; + + async function submitSwap(amount) { + setButtonsBusy(swapBtns, ids.swapAmount, true, "兑换中…"); + setMsg(ids.swapMsg, "兑换中,市价成交可能有延时…", false); + try { + const d = await apiJson("/api/options/spot/swap", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + direction: document.getElementById(ids.swapDir).value, + amount: amount, + }), + }); + if (d.ok) { + setMsg(ids.swapMsg, "兑换成功", false); + refreshFundsAfterMutation(); + } else { + setMsg(ids.swapMsg, "兑换失败:" + (d.msg || "未知错误"), true); + } + return d; + } catch (e) { + setMsg(ids.swapMsg, "兑换失败:" + (e.message || "网络错误"), true); + return { ok: false }; + } finally { + setButtonsBusy(swapBtns, ids.swapAmount, false); + } + } + + async function submitInternalTransfer(amount) { + setButtonsBusy(intBtns, ids.intAmount, true, "划转中…"); + setMsg(ids.intMsg, "划转中…", false); + try { + const d = await apiJson("/api/options/transfer", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + ccy: document.getElementById(ids.intCcy).value, + from: document.getElementById(ids.intFrom).value, + to: document.getElementById(ids.intTo).value, + amount: amount, + }), + }); + if (d.ok) { + setMsg(ids.intMsg, "划转成功", false); + refreshFundsAfterMutation(); + } else { + setMsg(ids.intMsg, "划转失败:" + (d.msg || "未知错误"), true); + } + return d; + } catch (e) { + setMsg(ids.intMsg, "划转失败:" + (e.message || "网络错误"), true); + return { ok: false }; + } finally { + setButtonsBusy(intBtns, ids.intAmount, false); + } + } + + swapBtn.addEventListener("click", async function () { + const amount = parseFloat(document.getElementById(ids.swapAmount).value); + if (!amount || amount <= 0) { + setMsg(ids.swapMsg, "请输入有效数量", true); + return; + } + await submitSwap(amount); + }); + + const swapAllBtn = document.getElementById(ids.swapAllBtn); + if (swapAllBtn) { + swapAllBtn.addEventListener("click", async function () { + try { + const dir = document.getElementById(ids.swapDir).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( + ids.swapMsg, + "资金账户可用 " + + 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(ids.swapAmount).value = String(amount); + await submitSwap(amount); + } catch (e) { + setMsg(ids.swapMsg, "兑换失败:" + (e.message || "余额拉取失败"), true); + } + }); + } + + const intBtn = document.getElementById(ids.intBtn); + if (intBtn) { + intBtn.addEventListener("click", async function () { + const amount = parseFloat(document.getElementById(ids.intAmount).value); + if (!amount || amount <= 0) { + setMsg(ids.intMsg, "请输入有效数量", true); + return; + } + await submitInternalTransfer(amount); + }); + } + + const intAllBtn = document.getElementById(ids.intAllBtn); + if (intAllBtn) { + intAllBtn.addEventListener("click", async function () { + try { + const ccy = document.getElementById(ids.intCcy).value; + const from = document.getElementById(ids.intFrom).value; + const to = document.getElementById(ids.intTo).value; + const amount = await resolveMaxAmount(from, ccy, "main"); + if (!amount) { + setMsg(ids.intMsg, "划出账户可用余额不足", 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(ids.intAmount).value = String(amount); + await submitInternalTransfer(amount); + } catch (e) { + setMsg(ids.intMsg, "划转失败:" + (e.message || "余额拉取失败"), true); + } + }); + } + + [ids.swapAllBtn, ids.intAllBtn].forEach(function (btnId) { + const btn = document.getElementById(btnId); + if (!btn) return; + btn.addEventListener( + "click", + function () { + const map = {}; + map[ids.swapAllBtn] = ids.swapAmount; + map[ids.intAllBtn] = ids.intAmount; + const input = document.getElementById(map[btnId]); + if (input) input.removeAttribute("readonly"); + }, + true + ); + }); + + hardenAmountAutofill([ids.swapAmount, ids.intAmount]); + } + + if (hasSettings) { + bindFundsUi({ + swapDir: "opt-set-swap-dir", + swapAmount: "opt-set-swap-amount", + swapBtn: "opt-set-swap-btn", + swapAllBtn: "opt-set-swap-all-btn", + swapMsg: "opt-set-swap-msg", + intCcy: "opt-set-int-ccy", + intFrom: "opt-set-int-from", + intTo: "opt-set-int-to", + intAmount: "opt-set-int-amount", + intBtn: "opt-set-int-btn", + intAllBtn: "opt-set-int-all-btn", + intMsg: "opt-set-int-msg", + }); + } + + if (hasPageFunds) { + bindFundsUi({ + swapDir: "opt-page-swap-dir", + swapAmount: "opt-page-swap-amount", + swapBtn: "opt-page-swap-btn", + swapAllBtn: "opt-page-swap-all-btn", + swapMsg: "opt-page-swap-msg", + intCcy: "opt-page-int-ccy", + intFrom: "opt-page-int-from", + intTo: "opt-page-int-to", + intAmount: "opt-page-int-amount", + intBtn: "opt-page-int-btn", + intAllBtn: "opt-page-int-all-btn", + intMsg: "opt-page-int-msg", + }); + } })(); diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html index 47932d9..3afa06e 100644 --- a/lib/instance/templates/embed_shell.html +++ b/lib/instance/templates/embed_shell.html @@ -8,7 +8,7 @@ - + diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index b485fcd..99b7b35 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -19,7 +19,7 @@