期权页增加重试卖回ETH按钮,平仓后可手动全额卖回交易户标的币

币本位模式下持仓区显示重试按钮与可用余额提示,调用已有 spot-bridge 接口按交易账户全部可用量市价卖回 USDT。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-23 08:01:19 +08:00
parent 893a2cc115
commit fe5bb923d7
4 changed files with 161 additions and 9 deletions
+18
View File
@@ -5279,6 +5279,24 @@ html[data-theme="light"] .opt-source-badge--oo {
.options-pos-head h2 {
margin: 0;
}
.options-pos-head-actions {
display: flex;
align-items: center;
justify-content: flex-end;
flex-wrap: wrap;
gap: 8px;
margin-left: auto;
}
.opt-bridge-sell-hint {
font-size: 0.78rem;
line-height: 1.35;
max-width: 280px;
text-align: right;
}
.opt-retry-sell-coin-btn--pending {
border-color: #ffb347;
color: #ffb347;
}
.opt-pos-card {
margin-bottom: 10px;
}
+124 -7
View File
@@ -268,6 +268,7 @@
await refreshPendingOrders();
refreshAllPositions();
if (typeof refreshAccountSnapshot === "function") refreshAccountSnapshot();
void refreshBridgeSellUi(true);
} finally {
if (btn) btn.disabled = false;
}
@@ -301,6 +302,116 @@
}
}
let lastBalancesPayload = null;
function tradingCoinAvailFromPayload(d, uly) {
if (!d) return null;
const u = String(uly || state.underlying || "ETH").toUpperCase();
const key = u === "BTC" ? "trading_btc_avail" : "trading_eth_avail";
const v = d[key];
if (v == null) return d[u === "BTC" ? "trading_btc" : "trading_eth"];
return v;
}
function coinMarginModeFromPayload(d) {
if (d && d.options_margin_mode === "coin") return true;
if (d && d.options_margin_mode === "usdc") return false;
return isCoinMarginMode();
}
function updateRetrySellCoinUi(d) {
const btn = document.getElementById("opt-retry-sell-coin");
const hint = document.getElementById("opt-bridge-sell-hint");
if (!btn) return;
const payload = d || lastBalancesPayload;
const coinOn = coinMarginModeFromPayload(payload);
const uly = String(state.underlying || "ETH").toUpperCase();
btn.hidden = !coinOn;
if (!coinOn) {
if (hint) hint.hidden = true;
btn.classList.remove("opt-retry-sell-coin-btn--pending");
return;
}
btn.textContent = "重试卖回 " + uly;
const avail = tradingCoinAvailFromPayload(payload, uly);
const pending = payload && payload.bridge_status === "pending_sell_spot";
btn.classList.toggle("opt-retry-sell-coin-btn--pending", !!pending);
if (hint) {
const parts = [];
if (pending) parts.push("平仓后卖币未成功,可点此重试");
if (avail != null && Number(avail) > 0) {
parts.push("交易户可用 " + fmt(Number(avail), 6) + " " + uly);
}
if (parts.length) {
hint.textContent = parts.join(" · ");
hint.hidden = false;
} else {
hint.hidden = true;
}
}
}
async function refreshBridgeSellUi(force) {
try {
const qs = force ? "?force=1" : "";
const d = await apiJson("/api/options/balances" + qs);
lastBalancesPayload = d;
syncCompoundFlagsFromPayload(d);
if (d && d.options_margin_mode === "coin") {
if (!state.chain) state.chain = {};
state.chain.options_margin_mode = "coin";
state.chain.margin_mode = "coin";
}
if (d && d.trade_budget != null && root) {
root.dataset.tradeBudget = String(d.trade_budget);
}
updateRetrySellCoinUi(d);
return d;
} catch (_) {
updateRetrySellCoinUi(null);
return null;
}
}
async function retrySellBackCoin() {
const uly = String(state.underlying || "ETH").toUpperCase();
const d = await refreshBridgeSellUi(true);
const avail = tradingCoinAvailFromPayload(d, uly);
const availTxt =
avail != null && Number(avail) > 0
? ("\n交易账户可用: " + fmt(Number(avail), 6) + " " + uly)
: "";
const msg =
"确认市价卖出交易账户中的全部可用 " + uly + " 换回 USDT?" +
availTxt +
"\n\n将按可用余额全额卖出(非 bridge 记账量)。";
if (!confirm(msg)) return;
const btn = document.getElementById("opt-retry-sell-coin");
if (btn) btn.disabled = true;
try {
const r = await apiJson("/api/options/spot-bridge/retry-sell", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ underlying: uly }),
});
if (r.ok) {
if (r.skipped) {
alert(r.msg || "无残留币需卖回");
} else {
const sold = r.sell && r.sell.coin_sold;
alert("卖回已提交" + (sold != null ? (" " + fmt(Number(sold), 6) + " " + uly) : ""));
}
} else {
alert(r.msg || "卖回失败");
}
refreshAllPositions();
if (typeof refreshAccountSnapshot === "function") refreshAccountSnapshot();
await refreshBridgeSellUi(true);
} finally {
if (btn) btn.disabled = false;
}
}
function updateSizeInputs() {
const mode = currentSizeMode();
const sheetsEl = document.getElementById("opt-sheets-amount");
@@ -1508,6 +1619,7 @@
parkOrderPanel();
}
updateUnderlyingLabel();
updateRetrySellCoinUi(lastBalancesPayload);
renderExpiries();
if (soft && keepExp) {
const sel = document.getElementById("opt-exp-select");
@@ -2195,12 +2307,20 @@
}
if (r.remaining_sheets > 0) okMsg += "\n剩余: " + r.remaining_sheets + " 张(下次再平)";
if (r.stopped_reason) okMsg += "\n状态: " + r.stopped_reason;
const spotSell = r.spot_sell;
if (spotSell && spotSell.ok === false) {
okMsg += "\n卖回 " + premCcy + " 失败: " + (spotSell.msg || "未知错误");
okMsg += "\n可点「重试卖回 " + premCcy + "」手动卖出交易户全部可用币。";
} else if (spotSell && spotSell.ok && spotSell.sell && spotSell.sell.coin_sold != null) {
okMsg += "\n已卖回: " + fmt(Number(spotSell.sell.coin_sold), 6) + " " + premCcy;
}
alert(okMsg);
} else {
alert(r.msg || "平仓失败");
}
refreshAllPositions();
if (typeof refreshAccountSnapshot === "function") refreshAccountSnapshot();
void refreshBridgeSellUi(true);
} finally {
if (btn) btn.disabled = false;
}
@@ -2644,13 +2764,7 @@
applyBudgetBuffer(state.budgetBuffer);
updateSizeInputs();
void (async function syncLiveCompoundFlag() {
try {
const d = await apiJson("/api/options/balances");
syncCompoundFlagsFromPayload(d);
if (d && d.trade_budget != null && root) {
root.dataset.tradeBudget = String(d.trade_budget);
}
} catch (_) {}
await refreshBridgeSellUi(false);
})();
syncMoneyFilterButtons();
syncChainViewUI();
@@ -2681,6 +2795,7 @@
document.querySelectorAll(".opt-uly-btn").forEach(function (b) { b.classList.remove("active"); });
btn.classList.add("active");
state.underlying = btn.getAttribute("data-uly");
updateRetrySellCoinUi(lastBalancesPayload);
loadChain();
});
});
@@ -2735,6 +2850,8 @@
document.getElementById("opt-exp-select").addEventListener("change", onExpiryChange);
document.getElementById("opt-load-chain").addEventListener("click", loadChain);
document.getElementById("opt-refresh-positions").addEventListener("click", refreshAllPositions);
const retrySellBtn = document.getElementById("opt-retry-sell-coin");
if (retrySellBtn) retrySellBtn.addEventListener("click", retrySellBackCoin);
document.getElementById("opt-open-btn").addEventListener("click", openPosition);
const pendingRefreshBtn = document.getElementById("opt-pending-refresh");
if (pendingRefreshBtn) {