Add tabbed layout for options positions card.

Split current positions, stats, and history into tabs to save vertical space and improve readability.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-10 11:19:33 +08:00
parent b6d116ec7a
commit b70ce510ef
3 changed files with 82 additions and 34 deletions
+43 -26
View File
@@ -3033,7 +3033,8 @@ html[data-theme="light"] .opt-be-dist-down {
}
.options-chain-toolbar .btn-secondary.active,
.opt-uly-btn.active,
.opt-type-btn.active {
.opt-type-btn.active,
.opt-pos-tab.active {
border-color: #5b8cff;
color: #cfe0ff;
background: rgba(74, 124, 255, 0.28);
@@ -3191,13 +3192,49 @@ html[data-theme="light"] .options-estimate-row {
flex-direction: column;
min-height: 0;
}
.options-pos-stack {
.options-pos-stack,
.options-pos-tab-body {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
min-height: 0;
}
.options-pos-tabs {
display: flex;
gap: 6px;
margin-bottom: 8px;
}
.opt-pos-tab {
flex: 1;
font-size: 0.78rem;
padding: 6px 8px;
min-height: 32px;
white-space: nowrap;
}
.opt-pos-tab.active {
border-color: #5b8cff;
color: #cfe0ff;
background: rgba(74, 124, 255, 0.28);
}
.options-pos-pane {
display: none;
flex: 1;
flex-direction: column;
min-height: 0;
}
.options-pos-pane.is-active {
display: flex;
}
.options-pos-live-pane {
flex: 1;
min-height: 180px;
max-height: 420px;
overflow-y: auto;
}
.options-pos-live-pane.options-pos-live-pane--accordion {
max-height: 480px;
}
.options-pos-subcard {
display: flex;
flex-direction: column;
@@ -3213,22 +3250,14 @@ html[data-theme="light"] .options-estimate-row {
font-size: 0.78rem;
color: #b8c0dc;
}
.options-pos-live-pane {
flex: 1;
min-height: 100px;
max-height: 220px;
overflow-y: auto;
}
.options-pos-live-pane.options-pos-live-pane--accordion {
max-height: 360px;
}
.options-pos-stats-card {
flex-shrink: 0;
}
.options-stats-grid {
display: flex;
flex-wrap: wrap;
gap: 8px 14px;
gap: 10px 16px;
padding: 4px 2px 8px;
}
.options-stat-item {
display: flex;
@@ -3399,7 +3428,8 @@ html[data-theme="light"] .options-estimate-row {
flex: 1;
overflow-y: auto;
overflow-x: auto;
min-height: 120px;
min-height: 180px;
max-height: 420px;
}
.opt-history-table {
table-layout: fixed;
@@ -3485,19 +3515,6 @@ html[data-theme="light"] .options-estimate-row {
.options-pos-head h2 {
margin: 0;
}
.options-pos-tabs {
display: flex;
gap: 8px;
margin-bottom: 10px;
}
.opt-pos-tab.active {
border-color: #5b8cff;
color: #cfe0ff;
background: rgba(74, 124, 255, 0.28);
}
.options-pos-pane {
min-height: 200px;
}
.opt-pos-card {
margin-bottom: 10px;
}
+29
View File
@@ -15,6 +15,7 @@
selectedInst: null,
orderQuote: null,
expandedPosInst: null,
posTab: "live",
};
let lastGoodPositions = null;
@@ -576,6 +577,33 @@
}
}
function setOptionsPosTab(tabId) {
const tab = tabId || "live";
state.posTab = tab;
document.querySelectorAll(".opt-pos-tab").forEach(function (btn) {
const on = btn.getAttribute("data-opt-pos-tab") === tab;
btn.classList.toggle("active", on);
btn.setAttribute("aria-selected", on ? "true" : "false");
});
document.querySelectorAll("[data-opt-pos-pane]").forEach(function (pane) {
const on = pane.getAttribute("data-opt-pos-pane") === tab;
pane.classList.toggle("is-active", on);
pane.hidden = !on;
});
if (tab === "live" && window.OptionsExpiryCountdown && OptionsExpiryCountdown.ensureTimer) {
OptionsExpiryCountdown.ensureTimer();
}
}
function bindOptionsPosTabs() {
document.querySelectorAll(".opt-pos-tab").forEach(function (btn) {
btn.addEventListener("click", function () {
setOptionsPosTab(btn.getAttribute("data-opt-pos-tab"));
});
});
setOptionsPosTab(state.posTab);
}
function resolvePositionsList(d) {
const now = Date.now();
const list = (d && d.ok && d.positions) ? d.positions : [];
@@ -785,6 +813,7 @@
document.getElementById("opt-load-chain").addEventListener("click", loadChain);
document.getElementById("opt-refresh-positions").addEventListener("click", refreshAllPositions);
document.getElementById("opt-open-btn").addEventListener("click", openPosition);
bindOptionsPosTabs();
document.querySelectorAll('input[name="opt-size-mode"]').forEach(function (r) {
r.addEventListener("change", function () {
+10 -8
View File
@@ -78,16 +78,19 @@
<h2>持仓</h2>
<button type="button" class="btn-secondary" id="opt-refresh-positions">刷新</button>
</div>
<div class="options-pos-stack">
<div class="card-nested options-pos-subcard">
<h3>当前持仓</h3>
<div class="options-pos-tabs" role="tablist" aria-label="持仓面板">
<button type="button" class="btn-secondary opt-pos-tab active" data-opt-pos-tab="live" role="tab" aria-selected="true" id="opt-pos-tab-live">当前持仓</button>
<button type="button" class="btn-secondary opt-pos-tab" data-opt-pos-tab="stats" role="tab" aria-selected="false" id="opt-pos-tab-stats">数据统计</button>
<button type="button" class="btn-secondary opt-pos-tab" data-opt-pos-tab="history" role="tab" aria-selected="false" id="opt-pos-tab-history">期权历史</button>
</div>
<div class="options-pos-tab-body">
<div class="options-pos-pane is-active" data-opt-pos-pane="live" role="tabpanel" aria-labelledby="opt-pos-tab-live">
<div id="opt-pos-live" class="panel-scroll pos-list options-pos-live-pane">
<div class="pos-empty" id="opt-pos-empty">暂无持仓</div>
<div id="opt-pos-cards"></div>
</div>
</div>
<div class="card-nested options-pos-subcard options-pos-stats-card">
<h3>数据统计</h3>
<div class="options-pos-pane" data-opt-pos-pane="stats" role="tabpanel" aria-labelledby="opt-pos-tab-stats" hidden>
<div class="options-stats-grid">
<div class="options-stat-item">
<span class="k">胜率</span>
@@ -111,8 +114,7 @@
</div>
</div>
</div>
<div class="card-nested options-pos-subcard options-pos-history-card">
<h3>期权历史</h3>
<div class="options-pos-pane" data-opt-pos-pane="history" role="tabpanel" aria-labelledby="opt-pos-tab-history" hidden>
<div class="options-history-table-wrap">
<table class="options-strike-table opt-history-table" id="opt-history-table">
<thead>
@@ -137,4 +139,4 @@
</div>
</div>
<script src="/static/options_expiry_countdown.js?v=1"></script>
<script src="/static/options_panel.js?v=17"></script>
<script src="/static/options_panel.js?v=18"></script>