From d5f3f315dcf58447af1e197514a0c4282180f268 Mon Sep 17 00:00:00 2001 From: dekun Date: Sun, 19 Jul 2026 08:28:54 +0800 Subject: [PATCH] Show hedge-plan scenario preview in a modal with start/cancel. Co-authored-by: Cursor --- lib/common/static/hedge_plan.js | 73 ++++++++++++++---- lib/common/static/instance_theme.css | 17 +++- .../templates/hedge_plan_panel.html | 77 ++++++++----------- 3 files changed, 101 insertions(+), 66 deletions(-) diff --git a/lib/common/static/hedge_plan.js b/lib/common/static/hedge_plan.js index 785ade2..60100f4 100644 --- a/lib/common/static/hedge_plan.js +++ b/lib/common/static/hedge_plan.js @@ -35,6 +35,9 @@ fundingUsdc: null, tradeBudgetUsdc: null, budgetBuffer: 0.95, + previewOk: false, + canStart: false, + previewPlanType: null, }; function $(id) { @@ -169,13 +172,24 @@ ]; if (gates.reasons && gates.reasons.length) parts.push(gates.reasons.join("; ")); el.textContent = parts.join(" · "); - const start = $("hp-start-btn"); - const startOo = $("hp-start-btn-oo"); - if ((gates.plan_type || state.mode) === "options_options") { - if (startOo) startOo.disabled = !gates.can_start; - } else { - if (start) start.disabled = !gates.can_start; - } + state.canStart = !!gates.can_start; + syncPreviewStartBtn(); + } + + function syncPreviewStartBtn() { + const start = $("hp-preview-start"); + if (!start) return; + start.disabled = !(state.previewOk && state.canStart); + } + + function openPreviewModal() { + const modal = $("hp-preview-modal"); + if (modal) modal.hidden = false; + } + + function closePreviewModal() { + const modal = $("hp-preview-modal"); + if (modal) modal.hidden = true; } function setOptionsBalance(chain) { @@ -851,8 +865,18 @@ async function runPreview() { const isOo = state.mode === "options_options"; - const tbody = $(isOo ? "hp-result-tbody-oo" : "hp-result-tbody"); - const summary = $(isOo ? "hp-summary-oo" : "hp-summary"); + const tbody = $("hp-result-tbody"); + const summary = $("hp-preview-summary"); + const midTh = $("hp-preview-mid-th"); + const title = $("hp-preview-title"); + state.previewOk = false; + state.previewPlanType = isOo ? "options_options" : "perp_options"; + syncPreviewStartBtn(); + if (midTh) midTh.textContent = isOo ? "腿盈亏" : "永续/腿盈亏"; + if (title) title.textContent = isOo ? "情景测算 · 期期" : "情景测算 · 永期"; + if (summary) summary.textContent = ""; + if (tbody) tbody.innerHTML = '计算中…'; + openPreviewModal(); try { let body; if (isOo) { @@ -949,7 +973,11 @@ ""; tbody.appendChild(tr); }); + state.previewOk = true; + syncPreviewStartBtn(); } catch (e) { + state.previewOk = false; + syncPreviewStartBtn(); if (tbody) tbody.innerHTML = '' + (e.message || e) + ""; if (summary) summary.textContent = ""; } @@ -1075,14 +1103,19 @@ state.mode = "options_options"; void runPreview(); }); - if ($("hp-start-btn")) - $("hp-start-btn").addEventListener("click", function () { - void startPlan("perp_options"); + if ($("hp-preview-cancel")) $("hp-preview-cancel").addEventListener("click", closePreviewModal); + if ($("hp-preview-cancel-x")) $("hp-preview-cancel-x").addEventListener("click", closePreviewModal); + if ($("hp-preview-start")) + $("hp-preview-start").addEventListener("click", function () { + const planType = state.previewPlanType || state.mode; + void startPlan(planType, true); }); - if ($("hp-start-btn-oo")) - $("hp-start-btn-oo").addEventListener("click", function () { - void startPlan("options_options"); + const previewModal = $("hp-preview-modal"); + if (previewModal) { + previewModal.addEventListener("click", function (ev) { + if (ev.target === previewModal) closePreviewModal(); }); + } if ($("hp-detail-close")) $("hp-detail-close").addEventListener("click", closeModal); const modal = $("hp-detail-modal"); if (modal) { @@ -1442,8 +1475,9 @@ } } - async function startPlan(planType) { + async function startPlan(planType, fromPreviewModal) { const isOo = planType === "options_options"; + const startBtn = $("hp-preview-start"); try { let body; if (isOo) { @@ -1487,17 +1521,22 @@ margin: state.market && state.market.full_margin_sizing && state.market.full_margin_sizing.margin_capital, }; } - if (!window.confirm("确认启动对冲计划并真实下单?\n(将按期权账户/合约账户分别下单)")) return; + if (!fromPreviewModal) { + if (!window.confirm("确认启动对冲计划并真实下单?\n(将按期权账户/合约账户分别下单)")) return; + } + if (startBtn) startBtn.disabled = true; const d = await apiJson("/api/hedge-plan/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); setGateLine(d.gates); + closePreviewModal(); alert("计划已启动 #" + (d.plan_id || "") + (d.dry_run ? " (dry_run)" : "")); void loadGates(); } catch (e) { alert(e.message || String(e)); + syncPreviewStartBtn(); } } diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css index b62e248..8d4fd2c 100644 --- a/lib/common/static/instance_theme.css +++ b/lib/common/static/instance_theme.css @@ -3536,15 +3536,24 @@ html[data-theme="light"] .opt-be-dist-down { display: flex; flex-direction: column; } -.hedge-plan-page-wrap .hp-preview-card { - margin-top: 0; - clear: both; -} .hedge-plan-page-wrap .hp-action-row { margin-top: 10px; gap: 8px; justify-content: flex-end; } +.hedge-plan-page-wrap .hp-preview-modal { + width: min(96vw, 920px); +} +.hedge-plan-page-wrap .hp-preview-summary { + margin: 0 0 10px; + font-size: 0.84rem; + line-height: 1.45; +} +.hedge-plan-page-wrap .hp-preview-actions { + margin-top: 14px; + justify-content: flex-end; + gap: 10px; +} .hedge-plan-page-wrap .hp-pick.active, .hedge-plan-page-wrap .opt-row-selected td { background: rgba(90, 140, 255, 0.18); diff --git a/lib/hedge_plan/templates/hedge_plan_panel.html b/lib/hedge_plan/templates/hedge_plan_panel.html index e165f0c..2251c23 100644 --- a/lib/hedge_plan/templates/hedge_plan_panel.html +++ b/lib/hedge_plan/templates/hedge_plan_panel.html @@ -97,31 +97,9 @@
-
-
-

情景测算

-
-
- - - - - - - - - - - - - - -
情景现货价永续/腿盈亏期权盈亏合计≈U说明
填写参数后点计算
-
-
-
-
-

情景测算

-
-
- - - - - - - - - - - - - - -
情景现货价腿盈亏期权合计≈U说明
选用两腿并填上破/下破目标后点计算
-
-
+ + - +