Show cancellable pending option orders beside the order form.
Fetch live OKX option hangs on the right after submit, with refresh and one-click cancel. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3639,6 +3639,101 @@ html[data-theme="light"] .options-strike-table--t .opt-strike-row-atm td {
|
||||
.opt-order-panel-inner {
|
||||
border-radius: 8px;
|
||||
}
|
||||
.opt-order-layout {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 14px;
|
||||
}
|
||||
.opt-order-main {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
.opt-order-pending {
|
||||
flex: 0 0 280px;
|
||||
max-width: 320px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(158, 192, 255, 0.2);
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
.opt-order-pending-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.opt-order-pending-title {
|
||||
margin: 0;
|
||||
font-size: 0.82rem;
|
||||
color: #9ec0ff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.opt-order-pending-head .btn-secondary {
|
||||
font-size: 0.68rem;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
.opt-pending-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
max-height: 220px;
|
||||
overflow: auto;
|
||||
}
|
||||
.opt-pending-empty {
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
.opt-pending-item {
|
||||
padding: 8px 9px;
|
||||
border-radius: 7px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
.opt-pending-item-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.opt-pending-side {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.opt-pending-side.is-buy { color: #3dd68c; }
|
||||
.opt-pending-side.is-sell { color: #ff6b7a; }
|
||||
.opt-pending-inst {
|
||||
font-size: 0.68rem;
|
||||
color: #c5d0ee;
|
||||
word-break: break-all;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.opt-pending-meta {
|
||||
font-size: 0.68rem;
|
||||
color: #8892b0;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.opt-pending-item .opt-pending-cancel {
|
||||
font-size: 0.68rem;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
html[data-theme="light"] .opt-order-pending {
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
border-color: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
html[data-theme="light"] .opt-pending-item {
|
||||
background: #fff;
|
||||
border-color: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.opt-order-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
.opt-order-pending {
|
||||
flex: 1 1 auto;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
.opt-order-panel-inner .opt-order-title {
|
||||
margin: 0 0 8px;
|
||||
font-size: 0.85rem;
|
||||
|
||||
@@ -27,7 +27,9 @@
|
||||
let lastGoodPositionsAt = 0;
|
||||
let positionsRefreshSeq = 0;
|
||||
let refreshAllTimer = null;
|
||||
let pendingRefreshTimer = null;
|
||||
const POSITIONS_STALE_MS = 45000;
|
||||
const PENDING_POLL_MS = 8000;
|
||||
|
||||
function fmt(v, d) {
|
||||
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
|
||||
@@ -68,6 +70,7 @@
|
||||
}
|
||||
|
||||
function parkOrderPanel() {
|
||||
stopPendingOrdersPoll();
|
||||
const panel = orderPanel();
|
||||
const host = orderPanelHost();
|
||||
const inline = document.querySelector(".opt-order-inline-row");
|
||||
@@ -116,6 +119,99 @@
|
||||
panel.style.display = "";
|
||||
orderPanelHost().hidden = true;
|
||||
tr.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
||||
refreshPendingOrders();
|
||||
startPendingOrdersPoll();
|
||||
}
|
||||
|
||||
function paintPendingOrders(orders) {
|
||||
const host = document.getElementById("opt-pending-list");
|
||||
if (!host) return;
|
||||
const rows = Array.isArray(orders) ? orders : [];
|
||||
if (!rows.length) {
|
||||
host.innerHTML = '<div class="muted opt-pending-empty">暂无未成交委托</div>';
|
||||
return;
|
||||
}
|
||||
host.innerHTML = rows.map(function (o) {
|
||||
const side = String(o.side || "").toLowerCase();
|
||||
const sideCls = side === "buy" ? "is-buy" : side === "sell" ? "is-sell" : "";
|
||||
const remain = (o.sz != null && o.fill_sz != null) ? Math.max(0, Number(o.sz) - Number(o.fill_sz)) : o.sz;
|
||||
const pxTxt = o.px != null ? fmtOptionPx(o.px, null) : "—";
|
||||
return (
|
||||
'<div class="opt-pending-item" data-ord="' + (o.ord_id || "") + '" data-inst="' + (o.inst_id || "") + '">' +
|
||||
'<div class="opt-pending-item-top">' +
|
||||
'<span class="opt-pending-side ' + sideCls + '">' + (o.side_label || side || "—") + "</span>" +
|
||||
'<button type="button" class="btn-secondary opt-pending-cancel" data-ord="' + (o.ord_id || "") +
|
||||
'" data-inst="' + (o.inst_id || "") + '">撤销</button>' +
|
||||
"</div>" +
|
||||
'<div class="opt-pending-inst" title="' + (o.inst_id || "") + '">' + (o.inst_id || "—") + "</div>" +
|
||||
'<div class="opt-pending-meta">价 ' + pxTxt +
|
||||
" · 张数 " + (o.sz != null ? o.sz : "—") +
|
||||
(o.fill_sz != null && Number(o.fill_sz) > 0 ? " · 已成 " + o.fill_sz : "") +
|
||||
(remain != null && o.fill_sz != null && Number(o.fill_sz) > 0 ? " · 剩余 " + remain : "") +
|
||||
"</div></div>"
|
||||
);
|
||||
}).join("");
|
||||
host.querySelectorAll(".opt-pending-cancel").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
cancelPendingOrder(btn.getAttribute("data-inst"), btn.getAttribute("data-ord"), btn);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function refreshPendingOrders() {
|
||||
const host = document.getElementById("opt-pending-list");
|
||||
if (!host) return;
|
||||
try {
|
||||
const d = await apiJson("/api/options/orders/pending");
|
||||
if (!d.ok) {
|
||||
host.innerHTML = '<div class="muted opt-pending-empty">' + (d.msg || "获取委托失败") + "</div>";
|
||||
return;
|
||||
}
|
||||
paintPendingOrders(d.orders || []);
|
||||
} catch (e) {
|
||||
host.innerHTML = '<div class="muted opt-pending-empty">获取委托失败</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function startPendingOrdersPoll() {
|
||||
stopPendingOrdersPoll();
|
||||
pendingRefreshTimer = setInterval(function () {
|
||||
const panel = orderPanel();
|
||||
if (!panel || panel.style.display === "none") {
|
||||
stopPendingOrdersPoll();
|
||||
return;
|
||||
}
|
||||
refreshPendingOrders();
|
||||
}, PENDING_POLL_MS);
|
||||
}
|
||||
|
||||
function stopPendingOrdersPoll() {
|
||||
if (pendingRefreshTimer) {
|
||||
clearInterval(pendingRefreshTimer);
|
||||
pendingRefreshTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelPendingOrder(inst, ordId, btn) {
|
||||
if (!inst || !ordId) return;
|
||||
if (!confirm("撤销该委托?\n合约: " + inst + "\n订单: " + ordId)) return;
|
||||
if (btn) btn.disabled = true;
|
||||
try {
|
||||
const d = await apiJson("/api/options/orders/cancel", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ inst_id: inst, ord_id: ordId }),
|
||||
});
|
||||
if (!d.ok) {
|
||||
alert(d.msg || "撤销失败");
|
||||
return;
|
||||
}
|
||||
await refreshPendingOrders();
|
||||
refreshAllPositions();
|
||||
if (typeof refreshAccountSnapshot === "function") refreshAccountSnapshot();
|
||||
} finally {
|
||||
if (btn) btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function currentSizeMode() {
|
||||
@@ -852,9 +948,11 @@
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const msgEl = document.getElementById("opt-order-msg");
|
||||
msgEl.textContent = d.ok ? "下单已提交,可在 OKX 委托中查看" : (d.msg || "失败");
|
||||
msgEl.textContent = d.ok ? "下单已提交,右侧可查看/撤销未成交委托" : (d.msg || "失败");
|
||||
msgEl.classList.toggle("opt-error", !d.ok);
|
||||
if (d.ok) {
|
||||
refreshPendingOrders();
|
||||
startPendingOrdersPoll();
|
||||
refreshAllPositions();
|
||||
if (typeof refreshAccountSnapshot === "function") refreshAccountSnapshot();
|
||||
} else {
|
||||
@@ -1667,6 +1765,12 @@
|
||||
document.getElementById("opt-load-chain").addEventListener("click", loadChain);
|
||||
document.getElementById("opt-refresh-positions").addEventListener("click", refreshAllPositions);
|
||||
document.getElementById("opt-open-btn").addEventListener("click", openPosition);
|
||||
const pendingRefreshBtn = document.getElementById("opt-pending-refresh");
|
||||
if (pendingRefreshBtn) {
|
||||
pendingRefreshBtn.addEventListener("click", function () {
|
||||
refreshPendingOrders();
|
||||
});
|
||||
}
|
||||
bindOptionsPosTabs();
|
||||
|
||||
document.querySelectorAll('input[name="opt-size-mode"]').forEach(function (r) {
|
||||
|
||||
Reference in New Issue
Block a user