From ff83f6eebcbd270c6846a33c467e06bd83438de9 Mon Sep 17 00:00:00 2001 From: dekun Date: Thu, 9 Jul 2026 17:00:06 +0800 Subject: [PATCH] Refine options position accordion: default collapsed, 2dp PnL, card outside scroll. Co-authored-by: Cursor --- lib/common/static/instance_theme.css | 39 ++++++---- lib/common/static/options_panel.js | 91 +++++++++++++++++------- lib/options/templates/options_panel.html | 9 ++- 3 files changed, 96 insertions(+), 43 deletions(-) diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css index 4eeb5be..623623b 100644 --- a/lib/common/static/instance_theme.css +++ b/lib/common/static/instance_theme.css @@ -3213,6 +3213,29 @@ html[data-theme="light"] .options-estimate-row { font-size: 0.78rem; color: #b8c0dc; } +.options-pos-live-layout { + display: flex; + flex-direction: column; + min-height: 0; + flex: 1; +} +.opt-pos-expanded-host { + flex-shrink: 0; + margin-top: 8px; + overflow: visible; +} +.opt-pos-expanded-host .opt-pos-card { + margin-bottom: 0; +} +.options-pos-live-pane--bars-only { + flex: 1; + min-height: 72px; + max-height: 140px; + overflow-y: auto; +} +.options-pos-subcard .options-pos-live-layout { + min-height: 0; +} .options-pos-live-pane { flex: 1; min-height: 100px; @@ -3284,8 +3307,7 @@ html[data-theme="light"] .options-estimate-row { background: #171d2a; } .opt-pos-accordion-item.is-expanded .opt-pos-bar { - border-radius: 8px 8px 0 0; - border-bottom-color: transparent; + border-color: #4a6fd8; background: #171d2a; } .opt-pos-bar-main { @@ -3337,19 +3359,6 @@ html[data-theme="light"] .options-estimate-row { .opt-pos-accordion-item.is-expanded .opt-pos-bar-chevron { transform: rotate(90deg); } -.opt-pos-accordion-body { - border: 1px solid #2a3348; - border-top: none; - border-radius: 0 0 8px 8px; - overflow: hidden; - background: #141923; -} -.opt-pos-card--nested { - margin-bottom: 0 !important; - border: none !important; - border-radius: 0 !important; - background: transparent !important; -} .options-page-wrap .opt-pos-card .pos-card-symbol strong { font-size: 0.78rem; } diff --git a/lib/common/static/options_panel.js b/lib/common/static/options_panel.js index a8cf35b..524af2c 100644 --- a/lib/common/static/options_panel.js +++ b/lib/common/static/options_panel.js @@ -457,7 +457,7 @@ '
平掉回本' + fmt(p.close_be_px, 0) + "
" + '
浮盈亏' + fmt(p.upl, 2) + "
" + '
收益率' + - (p.upl_ratio_pct != null ? p.upl_ratio_pct + "%" : "—") + "
" + + (p.upl_ratio_pct != null ? fmt(p.upl_ratio_pct, 2) + "%" : "—") + "" + "" ); } @@ -470,7 +470,7 @@ ); } - function renderPositionAccordionItem(p, expanded) { + function renderPositionBar(p, expanded) { const upl = p.upl; const uplCls = upl > 0 ? "pos-pnl-profit" : upl < 0 ? "pos-pnl-loss" : ""; const sideCls = (p.opt_type || "").toUpperCase() === "P" ? "pos-side-short" : "pos-side-long"; @@ -492,17 +492,51 @@ : "") + '' + fmt(p.upl, 2) + " USDC" + '' + - (p.upl_ratio_pct != null ? p.upl_ratio_pct + "%" : "—") + "" + + (p.upl_ratio_pct != null ? fmt(p.upl_ratio_pct, 2) + "%" : "—") + "" + "" + - "" + - '
" + - '
' + - renderPositionCardInner(p) + - "
" + "" ); } - function bindPositionActions(container) { + function renderExpandedPositionCard(p) { + return renderPositionCard(p); + } + + function applyAccordionState(list) { + const wrap = document.getElementById("opt-pos-cards"); + const expandedHost = document.getElementById("opt-pos-expanded-host"); + const livePane = document.getElementById("opt-pos-live"); + if (!wrap) return; + const expanded = (list || []).find(function (p) { return p.inst_id === state.expandedPosInst; }) || null; + wrap.querySelectorAll(".opt-pos-accordion-item").forEach(function (el) { + const open = el.getAttribute("data-inst") === state.expandedPosInst; + el.classList.toggle("is-expanded", open); + const btn = el.querySelector(".opt-pos-bar"); + if (btn) btn.setAttribute("aria-expanded", open ? "true" : "false"); + }); + if (expandedHost) { + if (expanded) { + expandedHost.hidden = false; + expandedHost.setAttribute("aria-hidden", "false"); + expandedHost.innerHTML = renderExpandedPositionCard(expanded); + expandedHost.querySelectorAll(".opt-close-btn").forEach(function (btn) { + btn.addEventListener("click", function (e) { + e.stopPropagation(); + closePosition(btn.getAttribute("data-inst"), btn); + }); + }); + } else { + expandedHost.hidden = true; + expandedHost.setAttribute("aria-hidden", "true"); + expandedHost.innerHTML = ""; + } + } + if (livePane) { + livePane.classList.toggle("options-pos-live-pane--bars-only", !!(list && list.length >= 2)); + } + } + + function bindPositionActions(container, list) { if (!container) return; container.querySelectorAll(".opt-close-btn").forEach(function (btn) { btn.addEventListener("click", function (e) { @@ -516,15 +550,10 @@ if (!item) return; const inst = item.getAttribute("data-inst"); state.expandedPosInst = state.expandedPosInst === inst ? null : inst; - const wrap = document.getElementById("opt-pos-cards"); - wrap.querySelectorAll(".opt-pos-accordion-item").forEach(function (el) { - const open = el.getAttribute("data-inst") === state.expandedPosInst; - el.classList.toggle("is-expanded", open); - const body = el.querySelector(".opt-pos-accordion-body"); - const btn = el.querySelector(".opt-pos-bar"); - if (body) body.hidden = !open; - if (btn) btn.setAttribute("aria-expanded", open ? "true" : "false"); - }); + applyAccordionState(list); + if (window.OptionsExpiryCountdown && OptionsExpiryCountdown.ensureTimer) { + OptionsExpiryCountdown.ensureTimer(); + } }); }); } @@ -564,10 +593,19 @@ const d = await apiJson("/api/options/positions"); const wrap = document.getElementById("opt-pos-cards"); const empty = document.getElementById("opt-pos-empty"); + const expandedHost = document.getElementById("opt-pos-expanded-host"); + const livePane = document.getElementById("opt-pos-live"); const list = (d.ok && d.positions) || []; wrap.innerHTML = ""; + if (expandedHost) { + expandedHost.hidden = true; + expandedHost.setAttribute("aria-hidden", "true"); + expandedHost.innerHTML = ""; + } if (!list.length) { empty.style.display = ""; + state.expandedPosInst = null; + if (livePane) livePane.classList.remove("options-pos-live-pane--bars-only"); return; } empty.style.display = "none"; @@ -575,23 +613,26 @@ wrap.classList.toggle("opt-pos-cards--accordion", multi); if (multi) { const ids = list.map(function (p) { return p.inst_id; }); - if (!state.expandedPosInst || ids.indexOf(state.expandedPosInst) < 0) { - state.expandedPosInst = list[0].inst_id || null; + if (state.expandedPosInst && ids.indexOf(state.expandedPosInst) < 0) { + state.expandedPosInst = null; } list.forEach(function (p) { const div = document.createElement("div"); - div.innerHTML = renderPositionAccordionItem(p, p.inst_id === state.expandedPosInst); + div.innerHTML = renderPositionBar(p, p.inst_id === state.expandedPosInst); wrap.appendChild(div.firstChild); }); + applyAccordionState(list); + bindPositionActions(wrap, list); } else { state.expandedPosInst = null; + if (livePane) livePane.classList.remove("options-pos-live-pane--bars-only"); list.forEach(function (p) { const div = document.createElement("div"); div.innerHTML = renderPositionCard(p); wrap.appendChild(div.firstChild); }); + bindPositionActions(wrap, list); } - bindPositionActions(wrap); if (window.OptionsExpiryCountdown && OptionsExpiryCountdown.ensureTimer) { OptionsExpiryCountdown.ensureTimer(); } @@ -617,11 +658,11 @@ if (closedEl) closedEl.textContent = String(d.total_closed || 0); if (profitEl) { profitEl.textContent = d.total_profit != null && d.total_profit > 0 - ? fmt(d.total_profit, 4) + " USDC" : (d.total_closed ? "0 USDC" : "—"); + ? fmt(d.total_profit, 2) + " USDC" : (d.total_closed ? "0 USDC" : "—"); } if (lossEl) { lossEl.textContent = d.total_loss != null && d.total_loss > 0 - ? fmt(d.total_loss, 4) + " USDC" : (d.total_closed ? "0 USDC" : "—"); + ? fmt(d.total_loss, 2) + " USDC" : (d.total_closed ? "0 USDC" : "—"); } } @@ -660,7 +701,7 @@ const tr = document.createElement("tr"); const prem = h.status === "closed" ? h.premium_received : h.premium_paid; const pnl = h.realized_pnl; - const pnlTxt = pnl != null ? fmt(pnl, 4) : "—"; + const pnlTxt = pnl != null ? fmt(pnl, 2) : "—"; const pnlCls = pnl > 0 ? "pos-pnl-profit" : pnl < 0 ? "pos-pnl-loss" : ""; tr.innerHTML = "" + (h.inst_id || "") + "" + diff --git a/lib/options/templates/options_panel.html b/lib/options/templates/options_panel.html index f0bb0c3..b56fb4f 100644 --- a/lib/options/templates/options_panel.html +++ b/lib/options/templates/options_panel.html @@ -81,9 +81,12 @@

当前持仓

-
-
暂无持仓
-
+
+
+
暂无持仓
+
+
+