fix: inline options order panel below selected row with clear active states
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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));
|
||||
|
||||
@@ -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 = '<tr><td colspan="6" class="muted">请选择到期日</td></tr>';
|
||||
state.selectedInst = null;
|
||||
return;
|
||||
}
|
||||
const exp = (state.chain.expiries || []).find(function (e) {
|
||||
@@ -121,10 +176,14 @@
|
||||
});
|
||||
if (!list.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="muted">该到期日暂无报价</td></tr>';
|
||||
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 =
|
||||
"<td>" + c.strike + "</td>" +
|
||||
@@ -137,28 +196,30 @@
|
||||
'<button type="button" class="btn-primary opt-buy-btn" data-inst="' + c.inst_id + '">买入</button>' +
|
||||
"</td>";
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -84,27 +84,28 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="options-section card-nested" id="opt-order-panel" style="display:none">
|
||||
<h3>下单</h3>
|
||||
<div id="opt-order-inst" class="options-order-inst"></div>
|
||||
<div class="options-order-grid">
|
||||
<div><span class="k">卖一(每1币)</span><span id="opt-order-ask" class="v">—</span></div>
|
||||
<div><span class="k">张数</span><span id="opt-order-sheets" class="v">—</span></div>
|
||||
<div><span class="k">ETH/BTC 数量</span><span id="opt-order-eth" class="v">—</span></div>
|
||||
<div><span class="k">预估权利金</span><span id="opt-order-premium" class="v">—</span></div>
|
||||
<div id="opt-order-panel-host" class="opt-order-panel-host" hidden aria-hidden="true">
|
||||
<div id="opt-order-panel" class="opt-order-panel-inner" style="display:none">
|
||||
<h3 class="opt-order-title">下单</h3>
|
||||
<div id="opt-order-inst" class="options-order-inst"></div>
|
||||
<div class="options-order-grid">
|
||||
<div><span class="k">卖一(每1币)</span><span id="opt-order-ask" class="v">—</span></div>
|
||||
<div><span class="k">张数</span><span id="opt-order-sheets" class="v">—</span></div>
|
||||
<div><span class="k">ETH/BTC 数量</span><span id="opt-order-eth" class="v">—</span></div>
|
||||
<div><span class="k">预估权利金</span><span id="opt-order-premium" class="v">—</span></div>
|
||||
</div>
|
||||
<div class="form-row options-order-mode-row">
|
||||
<label><input type="radio" name="opt-size-mode" value="sheets" checked> 指定张数</label>
|
||||
<input type="number" id="opt-sheets-amount" min="1" step="1" value="1" placeholder="张数">
|
||||
<label><input type="radio" name="opt-size-mode" value="budget_full"> 按单笔上限打满</label>
|
||||
<label><input type="radio" name="opt-size-mode" value="eth_amount"> 指定币数量</label>
|
||||
<input type="number" id="opt-eth-amount" min="0.01" step="0.01" placeholder="如 0.5" style="display:none">
|
||||
<input type="text" id="opt-signal-note" placeholder="备注(关键位说明)">
|
||||
<button type="button" class="btn-primary" id="opt-open-btn">限价买入 @ 卖一</button>
|
||||
</div>
|
||||
<div id="opt-order-msg" class="muted"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row options-order-mode-row">
|
||||
<label><input type="radio" name="opt-size-mode" value="sheets" checked> 指定张数</label>
|
||||
<input type="number" id="opt-sheets-amount" min="1" step="1" value="1" placeholder="张数">
|
||||
<label><input type="radio" name="opt-size-mode" value="budget_full"> 按单笔上限打满</label>
|
||||
<label><input type="radio" name="opt-size-mode" value="eth_amount"> 指定币数量</label>
|
||||
<input type="number" id="opt-eth-amount" min="0.01" step="0.01" placeholder="如 0.5" style="display:none">
|
||||
<input type="text" id="opt-signal-note" placeholder="备注(关键位说明)">
|
||||
<button type="button" class="btn-primary" id="opt-open-btn">限价买入 @ 卖一</button>
|
||||
</div>
|
||||
<div id="opt-order-msg" class="muted"></div>
|
||||
</div>
|
||||
|
||||
<div class="options-section">
|
||||
@@ -131,4 +132,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/options_panel.js?v=3"></script>
|
||||
<script src="/static/options_panel.js?v=4"></script>
|
||||
|
||||
Reference in New Issue
Block a user