From 1767566951c5085f9d04ea92f5e633072981bc28 Mon Sep 17 00:00:00 2001 From: dekun Date: Thu, 25 Jun 2026 23:12:08 +0800 Subject: [PATCH] Add hub order popup modal with compact instance trade embed (plan A). Co-authored-by: Cursor --- embed_templates/embed_page_fragment.html | 6 +- embed_templates/embed_shell.html | 20 ++-- instance_embed_lib.py | 10 ++ manual_trading_hub/static/app.css | 63 +++++++++++ manual_trading_hub/static/app.js | 131 ++++++++++++++++++++++- manual_trading_hub/static/index.html | 21 ++++ static/embed_order_popup.css | 43 ++++++++ static/instance_embed.js | 45 +++++++- tests/test_instance_embed_lib.py | 7 ++ 9 files changed, 334 insertions(+), 12 deletions(-) create mode 100644 static/embed_order_popup.css diff --git a/embed_templates/embed_page_fragment.html b/embed_templates/embed_page_fragment.html index 6f030dd..685a5f6 100644 --- a/embed_templates/embed_page_fragment.html +++ b/embed_templates/embed_page_fragment.html @@ -22,8 +22,8 @@ {% if page == 'key_monitor' %} {% include 'key_monitor_panel.html' %} {% elif page == 'trade' %} -
-
+
+

实盘下单监控

{% if focus_order_id %} @@ -77,6 +77,7 @@ {% include 'order_plan_preview_bar.html' %}
+ {% if not order_popup %}

实时持仓

@@ -191,6 +192,7 @@
+ {% endif %}
{% elif page in ('strategy', 'strategy_trend', 'strategy_roll') %} {% include 'strategy_trading_page.html' %} diff --git a/embed_templates/embed_shell.html b/embed_templates/embed_shell.html index 218a073..14b19a9 100644 --- a/embed_templates/embed_shell.html +++ b/embed_templates/embed_shell.html @@ -14,14 +14,18 @@ -
-
-

加密货币|交易监控 + AI复盘一体化

+{% if order_popup %} + +{% endif %} +
+
+

{% if order_popup %}{{ exchange_display }} · 实盘下单{% else %}加密货币|交易监控 + AI复盘一体化{% endif %}

{{ exchange_display }}
{{ risk_status.status_label|default('正常') }} @@ -39,6 +43,7 @@
+ {% if not order_popup %} + {% endif %} + {% if not order_popup %}
列表筛选(UTC,默认当日):{{ list_window.label }}
-
+ {% endif %} +
数据导出(v{{ data_export_version }} CSV,UTF-8;交易记录含开仓类型列,复盘单独导出): 交易记录 复盘记录 关键位(当前) 关键位历史
-
+
交易所
{{ exchange_display }}
总交易
{{ total }}
错过次数
{{ miss_count }}
@@ -116,6 +124,6 @@ {% include 'embed_boot_scripts.html' %} - + diff --git a/instance_embed_lib.py b/instance_embed_lib.py index f8ebf72..305569f 100644 --- a/instance_embed_lib.py +++ b/instance_embed_lib.py @@ -140,8 +140,18 @@ def register_embed_routes( return jsonify({"ok": True, "page": tab, "html": html}) +def request_order_popup() -> bool: + return (request.args.get("order_popup") or "").strip().lower() in ( + "1", + "true", + "yes", + "on", + ) + + def embed_context_extras(exchange_key: str) -> dict: return { "order_rule_tips_tpl": order_rule_tips_template(exchange_key), "include_transfer_block": include_transfer_block(exchange_key), + "order_popup": request_order_popup(), } diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index 6cd7f47..8688dba 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -2526,6 +2526,69 @@ button.btn-sm { gap: 8px; } +.order-popup-modal .order-popup-card { + width: min(920px, calc(100vw - 24px)); + max-height: calc(100vh - 32px); + display: flex; + flex-direction: column; + padding: 0; + overflow: hidden; +} + +.order-popup-head { + padding: 12px 14px; + border-bottom: 1px solid var(--border-soft); + flex-shrink: 0; +} + +.order-popup-head-actions { + display: flex; + align-items: center; + gap: 6px; +} + +.order-popup-frame-wrap { + position: relative; + flex: 1; + min-height: 420px; + max-height: calc(100vh - 120px); + background: #0b0d14; +} + +.order-popup-frame { + width: 100%; + height: 100%; + min-height: 420px; + border: 0; + display: block; + background: #0b0d14; +} + +.order-popup-loading { + position: absolute; + inset: 0; + display: none; + align-items: center; + justify-content: center; + gap: 10px; + color: var(--muted); + font-size: 13px; + background: rgba(8, 10, 18, 0.82); + z-index: 2; +} + +.order-popup-modal.is-loading .order-popup-loading { + display: flex; +} + +.order-popup-modal.is-loading .order-popup-frame { + opacity: 0.35; +} + +body.hub-order-popup-open { + overflow: hidden; +} + .table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 9153b1c..ec29333 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -394,6 +394,9 @@ let instanceFrameUrl = ""; /** @type {{ exchangeId: string, nextPath: string, title: string } | null} */ let instanceFrameCtx = null; + let orderPopupUrl = ""; + /** @type {{ exchangeId: string, title: string, symbol: string } | null} */ + let orderPopupCtx = null; function isHubEmbedded() { try { @@ -449,6 +452,120 @@ shell.classList.remove("is-instance-nav-loading"); } + function setOrderPopupLoading(loading) { + const modal = document.getElementById("order-popup-modal"); + if (!modal) return; + modal.classList.toggle("is-loading", !!loading); + } + + function closeOrderPopup() { + const modal = document.getElementById("order-popup-modal"); + const frame = document.getElementById("order-popup-frame"); + orderPopupUrl = ""; + orderPopupCtx = null; + if (frame) frame.src = "about:blank"; + if (modal) { + modal.classList.add("hidden"); + modal.setAttribute("aria-hidden", "true"); + modal.classList.remove("is-loading"); + } + document.body.classList.remove("hub-order-popup-open"); + } + + async function openOrderPopup(exchangeId, opts) { + const options = opts || {}; + const symbol = (options.symbol || "").trim(); + let next = "/trade?order_popup=1"; + if (symbol) next += "&symbol=" + encodeURIComponent(symbol); + try { + const url = await fetchInstanceOpenUrl(exchangeId, next, { embed: true }); + const row = lastMonitorRows.find((x) => String(x.id) === String(exchangeId)); + const title = row ? row.name : exchangeId; + orderPopupCtx = { exchangeId: String(exchangeId), title, symbol }; + orderPopupUrl = url; + const modal = document.getElementById("order-popup-modal"); + const frame = document.getElementById("order-popup-frame"); + const titleEl = document.getElementById("order-popup-title"); + if (!modal || !frame) { + window.open(url, "_blank", "noopener"); + return; + } + if (titleEl) titleEl.textContent = title + " · 实盘下单"; + setOrderPopupLoading(true); + frame.src = url; + modal.classList.remove("hidden"); + modal.setAttribute("aria-hidden", "false"); + document.body.classList.add("hub-order-popup-open"); + if (frame.dataset.orderPopupBound !== "1") { + frame.dataset.orderPopupBound = "1"; + frame.addEventListener("load", () => { + setOrderPopupLoading(false); + try { + if (globalThis.HubTheme && typeof HubTheme.get === "function" && frame.contentWindow) { + frame.contentWindow.postMessage( + { type: "hub-theme-sync", theme: HubTheme.get() }, + "*" + ); + } + } catch (_) {} + }); + } + } catch (e) { + setOrderPopupLoading(false); + showToast(String(e), true); + } + } + + async function refreshOrderPopup() { + if (!orderPopupCtx) return; + const frame = document.getElementById("order-popup-frame"); + if (!frame) return; + try { + let next = "/trade?order_popup=1"; + if (orderPopupCtx.symbol) { + next += "&symbol=" + encodeURIComponent(orderPopupCtx.symbol); + } + const url = await fetchInstanceOpenUrl(orderPopupCtx.exchangeId, next, { embed: true }); + orderPopupUrl = url; + setOrderPopupLoading(true); + frame.src = url; + } catch (e) { + setOrderPopupLoading(false); + showToast(String(e), true); + } + } + + function initOrderPopupModal() { + const modal = document.getElementById("order-popup-modal"); + if (!modal || modal.dataset.bound === "1") return; + modal.dataset.bound = "1"; + const backdrop = document.getElementById("order-popup-backdrop"); + const closeBtn = document.getElementById("order-popup-close"); + const refreshBtn = document.getElementById("order-popup-refresh"); + const newTabBtn = document.getElementById("order-popup-newtab"); + if (backdrop) backdrop.onclick = () => closeOrderPopup(); + if (closeBtn) closeBtn.onclick = () => closeOrderPopup(); + if (refreshBtn) refreshBtn.onclick = () => refreshOrderPopup(); + if (newTabBtn) { + newTabBtn.onclick = () => { + if (!orderPopupCtx) return; + openInstance(orderPopupCtx.exchangeId, "/trade", { newTab: true }); + }; + } + if (!window.__hubOrderPopupMsgBound) { + window.__hubOrderPopupMsgBound = true; + window.addEventListener("message", (ev) => { + const d = ev.data; + if (!d || d.type !== "hub-order-popup-done") return; + refreshMonitorBoardNow(); + showToast( + d.message || (d.ok ? "开仓请求已提交,请查看监控区" : "开仓提交可能失败,请查看表单提示"), + !d.ok + ); + }); + } + } + async function openInstance(exchangeId, nextPath, opts) { const options = opts || {}; const newTab = !!options.newTab; @@ -2385,6 +2502,15 @@ openMarketForPosition(btn.dataset.exId, btn.dataset.symbol, btn.dataset.exKey, btn.dataset.posCtx); }; }); + box.querySelectorAll(".btn-open-order-popup").forEach((btn) => { + btn.onclick = (ev) => { + ev.preventDefault(); + ev.stopPropagation(); + openOrderPopup(btn.dataset.exId, { + symbol: (btn.dataset.symbol || "").trim(), + }); + }; + }); box.querySelectorAll(".btn-open-instance").forEach((btn) => { btn.onclick = (ev) => { ev.preventDefault(); @@ -3214,7 +3340,7 @@
${flaskOpen ? `打开实例` : ""} - ${flaskOpen ? `下单` : ""} + ${flaskOpen ? `下单` : ""} ${flaskOpen ? `监控位` : ""} ${flaskOpen ? `复盘` : ""} @@ -3542,7 +3668,7 @@ ? `打开实例` : ""; const openTrade = flaskOpen - ? `下单` + ? `下单` : ""; const openKey = flaskOpen ? `监控位` @@ -4870,6 +4996,7 @@ initTpslModal(); initInstanceFrame(); + initOrderPopupModal(); initFullscreen(); initMobileLayout(); if (globalThis.HubTheme && typeof HubTheme.initToggleUI === "function") { diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 0b9c653..2cde47c 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -1000,6 +1000,27 @@
+ +