diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css index b9af670..2f1bd6a 100644 --- a/lib/common/static/instance_theme.css +++ b/lib/common/static/instance_theme.css @@ -2372,9 +2372,40 @@ html[data-theme="light"] .settings-export-link { .options-chain-toolbar .btn-secondary.active, .opt-uly-btn.active, .opt-type-btn.active { - border-color: #4a7cff; + border-color: #5b8cff; + color: #cfe0ff; + background: rgba(74, 124, 255, 0.28); + box-shadow: inset 0 0 0 1px rgba(120, 160, 255, 0.45); +} +.opt-pick-btn.active { + border-color: #5b8cff; + color: #fff; + background: rgba(74, 124, 255, 0.45); + box-shadow: inset 0 0 0 1px rgba(140, 175, 255, 0.6); +} +.opt-strike-row.opt-row-selected td { + background: rgba(74, 124, 255, 0.1); +} +.opt-strike-row.opt-row-selected td:first-child { + box-shadow: inset 3px 0 0 #5b8cff; +} +.opt-order-inline-row td { + padding: 14px 16px !important; + background: rgba(74, 124, 255, 0.07); + border-top: 1px solid rgba(74, 124, 255, 0.25); + border-bottom: 1px solid rgba(74, 124, 255, 0.25); +} +.opt-order-panel-inner { + border-radius: 8px; +} +.opt-order-panel-inner .opt-order-title { + margin: 0 0 10px; + font-size: 1rem; color: #9ec0ff; } +.opt-order-panel-host { + display: none; +} .options-order-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); diff --git a/lib/common/static/options_panel.js b/lib/common/static/options_panel.js index 31d76ee..0b04a1b 100644 --- a/lib/common/static/options_panel.js +++ b/lib/common/static/options_panel.js @@ -22,11 +22,63 @@ return r.json(); } - function scrollToOrderPanel() { - const panel = document.getElementById("opt-order-panel"); - if (panel && panel.style.display !== "none") { - panel.scrollIntoView({ behavior: "smooth", block: "nearest" }); + function orderPanel() { + return document.getElementById("opt-order-panel"); + } + + function orderPanelHost() { + return document.getElementById("opt-order-panel-host"); + } + + function parkOrderPanel() { + const panel = orderPanel(); + const host = orderPanelHost(); + const inline = document.querySelector(".opt-order-inline-row"); + if (inline) inline.remove(); + if (panel && host && panel.parentElement !== host) { + host.appendChild(panel); + host.hidden = true; } + if (panel) panel.style.display = "none"; + document.querySelectorAll(".opt-strike-row").forEach(function (r) { + r.classList.remove("opt-row-selected"); + }); + document.querySelectorAll(".opt-pick-btn").forEach(function (b) { + b.classList.remove("active"); + b.disabled = false; + if (b.dataset.origText) b.textContent = b.dataset.origText; + }); + } + + function placeOrderPanelAfter(instId) { + const panel = orderPanel(); + if (!panel || !instId) return; + const row = document.querySelector('#opt-strike-tbody tr.opt-strike-row[data-inst="' + CSS.escape(instId) + '"]'); + if (!row) return; + + document.querySelectorAll(".opt-strike-row").forEach(function (r) { + r.classList.toggle("opt-row-selected", r === row); + }); + document.querySelectorAll(".opt-pick-btn").forEach(function (b) { + const on = b.getAttribute("data-inst") === instId; + b.classList.toggle("active", on); + if (!b.dataset.origText) b.dataset.origText = b.textContent; + if (on) b.textContent = "已选"; + }); + + const oldInline = document.querySelector(".opt-order-inline-row"); + if (oldInline) oldInline.remove(); + + const tr = document.createElement("tr"); + tr.className = "opt-order-inline-row"; + const td = document.createElement("td"); + td.colSpan = 6; + td.appendChild(panel); + tr.appendChild(td); + row.after(tr); + panel.style.display = ""; + orderPanelHost().hidden = true; + tr.scrollIntoView({ behavior: "smooth", block: "nearest" }); } function currentSizeMode() { @@ -107,9 +159,12 @@ function renderStrikes() { const tbody = document.getElementById("opt-strike-tbody"); const expMs = document.getElementById("opt-exp-select").value; + const prevSelected = state.selectedInst; + parkOrderPanel(); tbody.innerHTML = ""; if (!expMs || !state.chain) { tbody.innerHTML = '请选择到期日'; + state.selectedInst = null; return; } const exp = (state.chain.expiries || []).find(function (e) { @@ -121,10 +176,14 @@ }); if (!list.length) { tbody.innerHTML = '该到期日暂无报价'; + state.selectedInst = null; return; } + let stillVisible = false; list.forEach(function (c) { const tr = document.createElement("tr"); + tr.className = "opt-strike-row"; + tr.setAttribute("data-inst", c.inst_id); if (c.moneyness) tr.classList.add("opt-row-" + c.moneyness); tr.innerHTML = "" + c.strike + "" + @@ -137,28 +196,30 @@ '' + ""; tbody.appendChild(tr); + if (c.inst_id === prevSelected) stillVisible = true; }); tbody.querySelectorAll(".opt-pick-btn").forEach(function (btn) { btn.addEventListener("click", function () { - selectContract(btn.getAttribute("data-inst")); + selectContract(btn.getAttribute("data-inst"), btn); }); }); tbody.querySelectorAll(".opt-buy-btn").forEach(function (btn) { btn.addEventListener("click", async function () { - await selectContract(btn.getAttribute("data-inst")); + await selectContract(btn.getAttribute("data-inst"), null); await openPosition(); }); }); + if (stillVisible && prevSelected) { + selectContract(prevSelected, null, true); + } else { + state.selectedInst = null; + } } - async function selectContract(instId) { - state.selectedInst = instId; - const d = await apiJson(quoteUrl(instId)); - const panel = document.getElementById("opt-order-panel"); - panel.style.display = ""; - document.getElementById("opt-order-inst").textContent = instId; - document.getElementById("opt-order-ask").textContent = fmt(d.ask, 4); + function fillOrderPanel(d) { const sz = d.sizing || {}; + document.getElementById("opt-order-inst").textContent = d.inst_id || state.selectedInst || ""; + document.getElementById("opt-order-ask").textContent = fmt(d.ask, 4); document.getElementById("opt-order-sheets").textContent = sz.sheets != null ? sz.sheets : "—"; document.getElementById("opt-order-eth").textContent = sz.eth_amount != null ? sz.eth_amount : "—"; document.getElementById("opt-order-premium").textContent = sz.total_premium != null ? fmt(sz.total_premium, 4) + " USDC" : "—"; @@ -173,8 +234,28 @@ msgEl.textContent = ""; msgEl.classList.remove("opt-error"); } - scrollToOrderPanel(); - return d; + } + + async function selectContract(instId, pickBtn, silent) { + state.selectedInst = instId; + placeOrderPanelAfter(instId); + if (pickBtn) { + pickBtn.disabled = true; + if (!pickBtn.dataset.origText) pickBtn.dataset.origText = "选择"; + pickBtn.textContent = "加载…"; + } + try { + const d = await apiJson(quoteUrl(instId)); + fillOrderPanel(d); + return d; + } finally { + if (pickBtn) { + pickBtn.disabled = false; + pickBtn.textContent = "已选"; + pickBtn.classList.add("active"); + } + if (!silent) placeOrderPanelAfter(instId); + } } async function loadChain() { @@ -184,6 +265,8 @@ return; } state.chain = d; + state.selectedInst = null; + parkOrderPanel(); renderExpiries(); renderStrikes(); } @@ -313,7 +396,7 @@ document.querySelectorAll('input[name="opt-size-mode"]').forEach(function (r) { r.addEventListener("change", function () { updateSizeInputs(); - if (state.selectedInst) selectContract(state.selectedInst); + if (state.selectedInst) selectContract(state.selectedInst, null, true); }); }); @@ -321,10 +404,7 @@ const el = document.getElementById(id); if (!el) return; el.addEventListener("change", function () { - if (state.selectedInst) selectContract(state.selectedInst); - }); - el.addEventListener("input", function () { - if (state.selectedInst) selectContract(state.selectedInst); + if (state.selectedInst) selectContract(state.selectedInst, null, true); }); }); diff --git a/lib/options/templates/options_panel.html b/lib/options/templates/options_panel.html index fccf3e5..d429ea1 100644 --- a/lib/options/templates/options_panel.html +++ b/lib/options/templates/options_panel.html @@ -84,27 +84,28 @@ - - - - +