diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css index 79124bb..1bd99fc 100644 --- a/lib/common/static/instance_theme.css +++ b/lib/common/static/instance_theme.css @@ -3928,22 +3928,11 @@ html[data-theme="light"] .options-strike-table--t .opt-strike-row-atm td { border-radius: 8px; } .opt-order-layout { - display: flex; - align-items: stretch; - gap: 14px; + display: block; } .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; @@ -3973,6 +3962,12 @@ html[data-theme="light"] .options-strike-table--t .opt-strike-row-atm td { max-height: 220px; overflow: auto; } +.opt-pending-list--tab { + max-height: min(52vh, 420px); +} +.opt-pos-pending-pane { + padding: 4px 2px 8px; +} .opt-pending-empty { font-size: 0.72rem; } @@ -4010,21 +4005,61 @@ html[data-theme="light"] .options-strike-table--t .opt-strike-row-atm td { 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); +.opt-eth-order-backdrop { + position: fixed; + inset: 0; + z-index: 2100; + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + background: rgba(0, 0, 0, 0.72); +} +.opt-eth-order-backdrop[hidden] { + display: none !important; +} +.opt-eth-order-dialog { + width: min(92vw, 420px); + background: var(--card-bg, #121726); + color: inherit; + border: 1px solid rgba(127, 127, 127, 0.35); + border-radius: 12px; + padding: 16px 18px 18px; + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45); +} +.opt-eth-order-dialog h3 { + margin: 0 0 8px; + font-size: 0.95rem; +} +.opt-eth-order-label { + display: block; + font-size: 0.76rem; + margin-bottom: 6px; + opacity: 0.85; +} +.opt-eth-order-dialog input[type="number"] { + width: 100%; + box-sizing: border-box; + font-size: 0.9rem; + padding: 8px 10px; + border-radius: 8px; +} +.opt-eth-order-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; +} +.opt-eth-order-actions .btn-primary, +.opt-eth-order-actions .btn-secondary { + flex: 1 1 140px; } 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-pending-list--tab { + max-height: min(46vh, 360px); } } .opt-order-panel-inner .opt-order-title { diff --git a/lib/common/static/options_panel.js b/lib/common/static/options_panel.js index 4b4313e..00cd13b 100644 --- a/lib/common/static/options_panel.js +++ b/lib/common/static/options_panel.js @@ -276,9 +276,144 @@ function updateSizeInputs() { const mode = currentSizeMode(); const sheetsEl = document.getElementById("opt-sheets-amount"); - const ethEl = document.getElementById("opt-eth-amount"); if (sheetsEl) sheetsEl.style.display = mode === "sheets" ? "" : "none"; - if (ethEl) ethEl.style.display = mode === "eth_amount" ? "" : "none"; + } + + function hardenOrderAutofill() { + const note = document.getElementById("opt-signal-note"); + if (!note) return; + function wipeNote() { + // 浏览器常把用户名(dekun)填进备注框 + if (/^[a-z][a-z0-9._-]{1,31}$/i.test(String(note.value || "").trim())) { + note.value = ""; + } + } + wipeNote(); + note.addEventListener("focus", function () { + note.removeAttribute("readonly"); + }); + note.addEventListener("blur", function () { + if (!note.value) note.setAttribute("readonly", "readonly"); + }); + setTimeout(wipeNote, 200); + setTimeout(wipeNote, 800); + } + + function hideEthOrderDialog() { + const bd = document.getElementById("opt-eth-order-backdrop"); + if (bd) bd.hidden = true; + } + + function openEthOrderDialog() { + if (!state.selectedInst) { + alert("请先选择合约"); + const sheetsRadio = document.querySelector('input[name="opt-size-mode"][value="sheets"]'); + if (sheetsRadio) { + sheetsRadio.checked = true; + updateSizeInputs(); + } + return; + } + const bd = document.getElementById("opt-eth-order-backdrop"); + const instEl = document.getElementById("opt-eth-order-inst"); + const amountEl = document.getElementById("opt-eth-order-amount"); + const preview = document.getElementById("opt-eth-order-preview"); + const msg = document.getElementById("opt-eth-order-msg"); + if (instEl) instEl.textContent = state.selectedInst; + if (amountEl) { + amountEl.value = document.getElementById("opt-eth-amount").value || ""; + amountEl.focus(); + } + if (preview) preview.textContent = "输入币数量后显示张数与权利金估算"; + if (msg) { + msg.textContent = ""; + msg.classList.remove("opt-error"); + } + if (bd) bd.hidden = false; + refreshEthOrderPreview(); + } + + async function refreshEthOrderPreview() { + const preview = document.getElementById("opt-eth-order-preview"); + const amountEl = document.getElementById("opt-eth-order-amount"); + if (!preview || !amountEl || !state.selectedInst) return; + const eth = parseFloat(amountEl.value); + if (!Number.isFinite(eth) || eth <= 0) { + preview.textContent = "输入币数量后显示张数与权利金估算"; + return; + } + preview.textContent = "估算中…"; + try { + const url = + "/api/options/quote?inst_id=" + + encodeURIComponent(state.selectedInst) + + "&mode=eth_amountð_amount=" + + encodeURIComponent(String(eth)); + const d = await apiJson(url); + if (!d || !d.ok) { + preview.textContent = (d && d.msg) || "报价失败"; + return; + } + const sz = d.sizing || {}; + if (sz.ok === false) { + preview.textContent = sz.msg || "张数无效"; + return; + } + const sheets = sz.sheets != null ? sz.sheets : "—"; + const prem = sz.total_premium != null ? sz.total_premium : null; + const premTxt = prem != null ? fmt(prem, 2) + " USDC" : "—"; + preview.textContent = "约 " + sheets + " 张 · 预估权利金 " + premTxt; + } catch (e) { + preview.textContent = "报价失败"; + } + } + + async function submitEthOrderDialog() { + const amountEl = document.getElementById("opt-eth-order-amount"); + const msg = document.getElementById("opt-eth-order-msg"); + const submitBtn = document.getElementById("opt-eth-order-submit"); + const eth = amountEl ? parseFloat(amountEl.value) : NaN; + if (!Number.isFinite(eth) || eth <= 0) { + if (msg) { + msg.textContent = "请输入有效币数量"; + msg.classList.add("opt-error"); + } + return; + } + const hidden = document.getElementById("opt-eth-amount"); + if (hidden) hidden.value = String(eth); + const ethRadio = document.querySelector('input[name="opt-size-mode"][value="eth_amount"]'); + if (ethRadio) ethRadio.checked = true; + updateSizeInputs(); + if (submitBtn) submitBtn.disabled = true; + try { + if (state.selectedInst) { + await selectContract(state.selectedInst, null, true); + } + const ok = await openPosition(); + if (ok) { + hideEthOrderDialog(); + setOptionsPosTab("pending"); + } else if (msg) { + const orderMsg = document.getElementById("opt-order-msg"); + msg.textContent = (orderMsg && orderMsg.textContent) || "下单失败"; + msg.classList.add("opt-error"); + } + } finally { + if (submitBtn) submitBtn.disabled = false; + } + } + + function cancelEthOrderDialog() { + hideEthOrderDialog(); + const sheetsRadio = document.querySelector('input[name="opt-size-mode"][value="sheets"]'); + if (sheetsRadio) { + sheetsRadio.checked = true; + updateSizeInputs(); + } + const hidden = document.getElementById("opt-eth-amount"); + if (hidden) hidden.value = ""; + if (state.selectedInst) selectContract(state.selectedInst, null, true); } function quoteUrl(instId) { @@ -1106,16 +1241,16 @@ async function openPosition() { if (!state.selectedInst) { alert("请先选择合约"); - return; + return false; } const q = state.orderQuote; if (!q || !q.ok || !q.can_open) { alert((q && (q.msg || q.open_block_msg)) || "暂无卖一深度,无法按卖一开仓"); - return; + return false; } if (q.sizing && q.sizing.ok === false) { alert(q.sizing.msg || "张数无效"); - return; + return false; } const btn = document.getElementById("opt-open-btn"); btn.disabled = true; @@ -1136,7 +1271,7 @@ const tgt = parseFloat(tgtRaw); if (!Number.isFinite(tgt) || tgt <= 0) { alert("目标位无效"); - return; + return false; } body.target_index = tgt; } @@ -1146,16 +1281,17 @@ body: JSON.stringify(body), }); const msgEl = document.getElementById("opt-order-msg"); - msgEl.textContent = d.ok ? "下单已提交,右侧可查看/撤销未成交委托" : (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 { - alert(d.msg || "下单失败"); + return true; } + alert(d.msg || "下单失败"); + return false; } finally { const latest = state.orderQuote; btn.disabled = !(latest && latest.ok && latest.can_open && !(latest.sizing && latest.sizing.ok === false)); @@ -1587,6 +1723,10 @@ if (tab === "live" && window.OptionsExpiryCountdown && OptionsExpiryCountdown.ensureTimer) { OptionsExpiryCountdown.ensureTimer(); } + if (tab === "pending") { + refreshPendingOrders(); + startPendingOrdersPoll(); + } } function bindOptionsPosTabs() { @@ -2047,15 +2187,48 @@ }); } bindOptionsPosTabs(); + hardenOrderAutofill(); document.querySelectorAll('input[name="opt-size-mode"]').forEach(function (r) { r.addEventListener("change", function () { + const mode = currentSizeMode(); updateSizeInputs(); + if (mode === "eth_amount") { + openEthOrderDialog(); + return; + } if (state.selectedInst) selectContract(state.selectedInst, null, true); }); }); - ["opt-sheets-amount", "opt-eth-amount", "opt-target-idx"].forEach(function (id) { + const ethBackdrop = document.getElementById("opt-eth-order-backdrop"); + const ethCancel = document.getElementById("opt-eth-order-cancel"); + const ethSubmit = document.getElementById("opt-eth-order-submit"); + const ethAmount = document.getElementById("opt-eth-order-amount"); + if (ethCancel) ethCancel.addEventListener("click", cancelEthOrderDialog); + if (ethSubmit) ethSubmit.addEventListener("click", submitEthOrderDialog); + if (ethAmount) { + ethAmount.addEventListener("input", refreshEthOrderPreview); + ethAmount.addEventListener("change", refreshEthOrderPreview); + ethAmount.addEventListener("keydown", function (ev) { + if (ev.key === "Enter") { + ev.preventDefault(); + submitEthOrderDialog(); + } + }); + } + if (ethBackdrop) { + ethBackdrop.addEventListener("click", function (ev) { + if (ev.target === ethBackdrop) cancelEthOrderDialog(); + }); + } + document.addEventListener("keydown", function (ev) { + if (ev.key !== "Escape") return; + const bd = document.getElementById("opt-eth-order-backdrop"); + if (bd && !bd.hidden) cancelEthOrderDialog(); + }); + + ["opt-sheets-amount", "opt-target-idx"].forEach(function (id) { const el = document.getElementById(id); if (!el) return; el.addEventListener("change", function () { diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html index 7249e4a..3884997 100644 --- a/lib/instance/templates/embed_shell.html +++ b/lib/instance/templates/embed_shell.html @@ -7,7 +7,7 @@ - + {{ pwa_app_name }} diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index a8f5a77..26142c9 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -17,7 +17,7 @@ {{ pwa_app_name }} - +
- + - - - + + {# 实际数量在弹窗录入;隐藏字段仅给报价/下单读 #} + +
- + {# 指定币数量下单对话框 #} + +

持仓

@@ -125,6 +136,7 @@
+
@@ -152,6 +164,17 @@
+