Add hub order popup modal with compact instance trade embed (plan A).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -22,8 +22,8 @@
|
|||||||
{% if page == 'key_monitor' %}
|
{% if page == 'key_monitor' %}
|
||||||
{% include 'key_monitor_panel.html' %}
|
{% include 'key_monitor_panel.html' %}
|
||||||
{% elif page == 'trade' %}
|
{% elif page == 'trade' %}
|
||||||
<div class="dual-panel-grid" style="grid-column:1/-1">
|
<div class="dual-panel-grid{% if order_popup %} order-popup-trade-grid{% endif %}" style="grid-column:1/-1">
|
||||||
<div class="card">
|
<div class="card order-popup-form-card">
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;margin-bottom:8px">
|
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;margin-bottom:8px">
|
||||||
<h2 style="margin-bottom:0">实盘下单监控</h2>
|
<h2 style="margin-bottom:0">实盘下单监控</h2>
|
||||||
{% if focus_order_id %}
|
{% if focus_order_id %}
|
||||||
@@ -77,6 +77,7 @@
|
|||||||
</form>
|
</form>
|
||||||
{% include 'order_plan_preview_bar.html' %}
|
{% include 'order_plan_preview_bar.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if not order_popup %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 style="margin-bottom:8px">实时持仓</h2>
|
<h2 style="margin-bottom:8px">实时持仓</h2>
|
||||||
<div class="panel-scroll pos-list pos-list-live">
|
<div class="panel-scroll pos-list pos-list-live">
|
||||||
@@ -191,6 +192,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% elif page in ('strategy', 'strategy_trend', 'strategy_roll') %}
|
{% elif page in ('strategy', 'strategy_trend', 'strategy_roll') %}
|
||||||
{% include 'strategy_trading_page.html' %}
|
{% include 'strategy_trading_page.html' %}
|
||||||
|
|||||||
@@ -14,14 +14,18 @@
|
|||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
data-embed-shell="1"
|
data-embed-shell="1"
|
||||||
|
{% if order_popup %}data-order-popup="1"{% endif %}
|
||||||
data-risk-percent="{{ risk_percent }}"
|
data-risk-percent="{{ risk_percent }}"
|
||||||
data-page="{{ initial_tab }}"
|
data-page="{{ initial_tab }}"
|
||||||
data-balance-refresh-ms="{{ balance_refresh_seconds * 1000 }}"
|
data-balance-refresh-ms="{{ balance_refresh_seconds * 1000 }}"
|
||||||
data-price-refresh-ms="{{ price_refresh_seconds * 1000 }}"
|
data-price-refresh-ms="{{ price_refresh_seconds * 1000 }}"
|
||||||
>
|
>
|
||||||
<div class="container">
|
{% if order_popup %}
|
||||||
<div class="header">
|
<link rel="stylesheet" href="/static/embed_order_popup.css?v=1">
|
||||||
<h1>加密货币|交易监控 + AI复盘一体化</h1>
|
{% endif %}
|
||||||
|
<div class="container{% if order_popup %} embed-order-popup-shell{% endif %}">
|
||||||
|
<div class="header{% if order_popup %} embed-order-popup-head{% endif %}">
|
||||||
|
<h1>{% if order_popup %}{{ exchange_display }} · 实盘下单{% else %}加密货币|交易监控 + AI复盘一体化{% endif %}</h1>
|
||||||
<div class="header-row">
|
<div class="header-row">
|
||||||
<div class="exchange-tag">{{ exchange_display }}</div>
|
<div class="exchange-tag">{{ exchange_display }}</div>
|
||||||
<span class="risk-status-badge risk-status-{{ risk_status.status|default('normal') }}" id="account-risk-badge" role="status" title="{{ risk_status.reason|default('', true) }}" data-status-label="{{ risk_status.status_label|default('正常') }}"{% if risk_status.freeze_until_ms %} data-freeze-until-ms="{{ risk_status.freeze_until_ms }}"{% endif %}>{{ risk_status.status_label|default('正常') }}</span>
|
<span class="risk-status-badge risk-status-{{ risk_status.status|default('normal') }}" id="account-risk-badge" role="status" title="{{ risk_status.reason|default('', true) }}" data-status-label="{{ risk_status.status_label|default('正常') }}"{% if risk_status.freeze_until_ms %} data-freeze-until-ms="{{ risk_status.freeze_until_ms }}"{% endif %}>{{ risk_status.status_label|default('正常') }}</span>
|
||||||
@@ -39,6 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if not order_popup %}
|
||||||
<nav class="top-nav embed-top-nav" aria-label="实例导航">
|
<nav class="top-nav embed-top-nav" aria-label="实例导航">
|
||||||
<a href="/key_monitor" data-embed-tab="key_monitor" class="{% if initial_tab == 'key_monitor' %}active{% endif %}">关键位监控</a>
|
<a href="/key_monitor" data-embed-tab="key_monitor" class="{% if initial_tab == 'key_monitor' %}active{% endif %}">关键位监控</a>
|
||||||
<a href="/trade" data-embed-tab="trade" class="{% if initial_tab == 'trade' %}active{% endif %}">实盘下单</a>
|
<a href="/trade" data-embed-tab="trade" class="{% if initial_tab == 'trade' %}active{% endif %}">实盘下单</a>
|
||||||
@@ -47,8 +52,10 @@
|
|||||||
<a href="/records" data-embed-tab="records" class="{% if initial_tab == 'records' %}active{% endif %}">交易记录与复盘</a>
|
<a href="/records" data-embed-tab="records" class="{% if initial_tab == 'records' %}active{% endif %}">交易记录与复盘</a>
|
||||||
<a href="/stats" data-embed-tab="stats" class="{% if initial_tab == 'stats' %}active{% endif %}">统计分析</a>
|
<a href="/stats" data-embed-tab="stats" class="{% if initial_tab == 'stats' %}active{% endif %}">统计分析</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
{% endif %}
|
||||||
<div id="embed-flash" class="flash" style="display:none" role="status"></div>
|
<div id="embed-flash" class="flash" style="display:none" role="status"></div>
|
||||||
|
|
||||||
|
{% if not order_popup %}
|
||||||
<div class="list-window-bar">
|
<div class="list-window-bar">
|
||||||
<span style="color:#cfd3ef">列表筛选(<strong>UTC</strong>,默认当日):{{ list_window.label }}</span>
|
<span style="color:#cfd3ef">列表筛选(<strong>UTC</strong>,默认当日):{{ list_window.label }}</span>
|
||||||
<label>预设
|
<label>预设
|
||||||
@@ -66,14 +73,15 @@
|
|||||||
<button type="button" style="padding:6px 12px" onclick="applyListWindow()">应用</button>
|
<button type="button" style="padding:6px 12px" onclick="applyListWindow()">应用</button>
|
||||||
<span style="color:#8892b0;font-size:.75rem">统计页仍按北京时间 {{ stats_bundle.stats_reset_hour|default(reset_hour) }}:00 切日</span>
|
<span style="color:#8892b0;font-size:.75rem">统计页仍按北京时间 {{ stats_bundle.stats_reset_hour|default(reset_hour) }}:00 切日</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="export-bar instance-desktop-only">
|
{% endif %}
|
||||||
|
<div class="export-bar instance-desktop-only{% if order_popup %} embed-order-popup-hide{% endif %}">
|
||||||
<span style="color:#9aa">数据导出(v{{ data_export_version }} CSV,UTF-8;交易记录含开仓类型列,复盘单独导出):</span>
|
<span style="color:#9aa">数据导出(v{{ data_export_version }} CSV,UTF-8;交易记录含开仓类型列,复盘单独导出):</span>
|
||||||
<a href="/export/trade_records">交易记录</a>
|
<a href="/export/trade_records">交易记录</a>
|
||||||
<a href="/export/journal_entries">复盘记录</a>
|
<a href="/export/journal_entries">复盘记录</a>
|
||||||
<a href="/export/key_monitors">关键位(当前)</a>
|
<a href="/export/key_monitors">关键位(当前)</a>
|
||||||
<a href="/export/key_monitor_history">关键位历史</a>
|
<a href="/export/key_monitor_history">关键位历史</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-box instance-desktop-only">
|
<div class="stat-box instance-desktop-only{% if order_popup %} embed-order-popup-hide{% endif %}">
|
||||||
<div class="stat-item"><div class="label">交易所</div><div class="value">{{ exchange_display }}</div></div>
|
<div class="stat-item"><div class="label">交易所</div><div class="value">{{ exchange_display }}</div></div>
|
||||||
<div class="stat-item"><div class="label">总交易</div><div class="value" id="stat-total">{{ total }}</div></div>
|
<div class="stat-item"><div class="label">总交易</div><div class="value" id="stat-total">{{ total }}</div></div>
|
||||||
<div class="stat-item"><div class="label">错过次数</div><div class="value" id="stat-miss">{{ miss_count }}</div></div>
|
<div class="stat-item"><div class="label">错过次数</div><div class="value" id="stat-miss">{{ miss_count }}</div></div>
|
||||||
@@ -116,6 +124,6 @@
|
|||||||
<script src="/static/manual_order_rr_preview.js?v=4"></script>
|
<script src="/static/manual_order_rr_preview.js?v=4"></script>
|
||||||
<script src="/static/key_monitor_form.js?v=1"></script>
|
<script src="/static/key_monitor_form.js?v=1"></script>
|
||||||
{% include 'embed_boot_scripts.html' %}
|
{% include 'embed_boot_scripts.html' %}
|
||||||
<script src="/static/instance_embed.js?v=4"></script>
|
<script src="/static/instance_embed.js?v=5"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -140,8 +140,18 @@ def register_embed_routes(
|
|||||||
return jsonify({"ok": True, "page": tab, "html": html})
|
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:
|
def embed_context_extras(exchange_key: str) -> dict:
|
||||||
return {
|
return {
|
||||||
"order_rule_tips_tpl": order_rule_tips_template(exchange_key),
|
"order_rule_tips_tpl": order_rule_tips_template(exchange_key),
|
||||||
"include_transfer_block": include_transfer_block(exchange_key),
|
"include_transfer_block": include_transfer_block(exchange_key),
|
||||||
|
"order_popup": request_order_popup(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2526,6 +2526,69 @@ button.btn-sm {
|
|||||||
gap: 8px;
|
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 {
|
.table-scroll {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
|
|||||||
@@ -394,6 +394,9 @@
|
|||||||
let instanceFrameUrl = "";
|
let instanceFrameUrl = "";
|
||||||
/** @type {{ exchangeId: string, nextPath: string, title: string } | null} */
|
/** @type {{ exchangeId: string, nextPath: string, title: string } | null} */
|
||||||
let instanceFrameCtx = null;
|
let instanceFrameCtx = null;
|
||||||
|
let orderPopupUrl = "";
|
||||||
|
/** @type {{ exchangeId: string, title: string, symbol: string } | null} */
|
||||||
|
let orderPopupCtx = null;
|
||||||
|
|
||||||
function isHubEmbedded() {
|
function isHubEmbedded() {
|
||||||
try {
|
try {
|
||||||
@@ -449,6 +452,120 @@
|
|||||||
shell.classList.remove("is-instance-nav-loading");
|
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) {
|
async function openInstance(exchangeId, nextPath, opts) {
|
||||||
const options = opts || {};
|
const options = opts || {};
|
||||||
const newTab = !!options.newTab;
|
const newTab = !!options.newTab;
|
||||||
@@ -2385,6 +2502,15 @@
|
|||||||
openMarketForPosition(btn.dataset.exId, btn.dataset.symbol, btn.dataset.exKey, btn.dataset.posCtx);
|
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) => {
|
box.querySelectorAll(".btn-open-instance").forEach((btn) => {
|
||||||
btn.onclick = (ev) => {
|
btn.onclick = (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
@@ -3214,7 +3340,7 @@
|
|||||||
<div class="fs-head-actions">
|
<div class="fs-head-actions">
|
||||||
<button type="button" class="ghost btn-expand-back">返回监控</button>
|
<button type="button" class="ghost btn-expand-back">返回监控</button>
|
||||||
${flaskOpen ? `<a class="btn-link btn-open-instance btn-open-trade" href="#" data-ex-id="${esc(row.id)}" data-next="/trade" data-new-tab="1">打开实例</a>` : ""}
|
${flaskOpen ? `<a class="btn-link btn-open-instance btn-open-trade" href="#" data-ex-id="${esc(row.id)}" data-next="/trade" data-new-tab="1">打开实例</a>` : ""}
|
||||||
${flaskOpen ? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/trade">下单</a>` : ""}
|
${flaskOpen ? `<a class="btn-link btn-open-order-popup" href="#" data-ex-id="${esc(row.id)}">下单</a>` : ""}
|
||||||
${flaskOpen ? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/key_monitor">监控位</a>` : ""}
|
${flaskOpen ? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/key_monitor">监控位</a>` : ""}
|
||||||
${flaskOpen ? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/records">复盘</a>` : ""}
|
${flaskOpen ? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/records">复盘</a>` : ""}
|
||||||
<button type="button" class="danger btn-close-ex" data-id="${esc(row.id)}">全平</button>
|
<button type="button" class="danger btn-close-ex" data-id="${esc(row.id)}">全平</button>
|
||||||
@@ -3542,7 +3668,7 @@
|
|||||||
? `<a class="btn-link btn-open-instance btn-open-trade" href="#" data-ex-id="${esc(row.id)}" data-next="/trade" data-new-tab="1">打开实例</a>`
|
? `<a class="btn-link btn-open-instance btn-open-trade" href="#" data-ex-id="${esc(row.id)}" data-next="/trade" data-new-tab="1">打开实例</a>`
|
||||||
: "";
|
: "";
|
||||||
const openTrade = flaskOpen
|
const openTrade = flaskOpen
|
||||||
? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/trade">下单</a>`
|
? `<a class="btn-link btn-open-order-popup" href="#" data-ex-id="${esc(row.id)}">下单</a>`
|
||||||
: "";
|
: "";
|
||||||
const openKey = flaskOpen
|
const openKey = flaskOpen
|
||||||
? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/key_monitor">监控位</a>`
|
? `<a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/key_monitor">监控位</a>`
|
||||||
@@ -4870,6 +4996,7 @@
|
|||||||
|
|
||||||
initTpslModal();
|
initTpslModal();
|
||||||
initInstanceFrame();
|
initInstanceFrame();
|
||||||
|
initOrderPopupModal();
|
||||||
initFullscreen();
|
initFullscreen();
|
||||||
initMobileLayout();
|
initMobileLayout();
|
||||||
if (globalThis.HubTheme && typeof HubTheme.initToggleUI === "function") {
|
if (globalThis.HubTheme && typeof HubTheme.initToggleUI === "function") {
|
||||||
|
|||||||
@@ -1000,6 +1000,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="order-popup-modal" class="modal order-popup-modal hidden" aria-hidden="true">
|
||||||
|
<div class="modal-backdrop" id="order-popup-backdrop"></div>
|
||||||
|
<div class="modal-card order-popup-card" role="dialog" aria-labelledby="order-popup-title">
|
||||||
|
<div class="modal-head order-popup-head">
|
||||||
|
<h3 id="order-popup-title">实盘下单</h3>
|
||||||
|
<div class="order-popup-head-actions">
|
||||||
|
<button type="button" id="order-popup-refresh" class="ghost">刷新</button>
|
||||||
|
<button type="button" id="order-popup-newtab" class="ghost">新标签</button>
|
||||||
|
<button type="button" class="ghost order-popup-close" id="order-popup-close" aria-label="关闭">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="order-popup-frame-wrap">
|
||||||
|
<div id="order-popup-loading" class="order-popup-loading" aria-hidden="true">
|
||||||
|
<span class="instance-frame-spinner" aria-hidden="true"></span>
|
||||||
|
<span>加载实例下单页…</span>
|
||||||
|
</div>
|
||||||
|
<iframe id="order-popup-frame" class="order-popup-frame" title="实盘下单"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="tpsl-modal" class="modal hidden" aria-hidden="true">
|
<div id="tpsl-modal" class="modal hidden" aria-hidden="true">
|
||||||
<div class="modal-backdrop" id="tpsl-modal-backdrop"></div>
|
<div class="modal-backdrop" id="tpsl-modal-backdrop"></div>
|
||||||
<div class="modal-panel" role="dialog" aria-labelledby="tpsl-modal-title">
|
<div class="modal-panel" role="dialog" aria-labelledby="tpsl-modal-title">
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/* 中控弹窗 iframe:仅保留实盘下单表单区 */
|
||||||
|
body[data-order-popup="1"] {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-order-popup="1"] .container.embed-order-popup-shell {
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 10px 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-order-popup="1"] .embed-order-popup-head {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-order-popup="1"] .embed-order-popup-head h1 {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-order-popup="1"] .header-row {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-order-popup="1"] .embed-order-popup-hide {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-order-popup="1"] .order-popup-trade-grid {
|
||||||
|
display: block;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-order-popup="1"] .order-popup-form-card {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-order-popup="1"] .order-popup-form-card h2 {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-order-popup="1"] .order-popup-form-card > div:first-child span.btn-del {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
@@ -17,6 +17,35 @@
|
|||||||
/** 自带校验后 form.submit() 的表单,勿在捕获阶段再 fetch 一份(会双发 POST) */
|
/** 自带校验后 form.submit() 的表单,勿在捕获阶段再 fetch 一份(会双发 POST) */
|
||||||
const CUSTOM_SUBMIT_FORM_IDS = new Set(["add-order-form", "key-form"]);
|
const CUSTOM_SUBMIT_FORM_IDS = new Set(["add-order-form", "key-form"]);
|
||||||
|
|
||||||
|
function isOrderPopup() {
|
||||||
|
return document.body && document.body.getAttribute("data-order-popup") === "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyOrderPopupParent(ok, message) {
|
||||||
|
if (!isOrderPopup()) return;
|
||||||
|
try {
|
||||||
|
window.parent.postMessage(
|
||||||
|
{
|
||||||
|
type: "hub-order-popup-done",
|
||||||
|
ok: !!ok,
|
||||||
|
message: message || "",
|
||||||
|
},
|
||||||
|
"*"
|
||||||
|
);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function prefillOrderSymbolFromQuery() {
|
||||||
|
if (!isOrderPopup()) return;
|
||||||
|
let sym = "";
|
||||||
|
try {
|
||||||
|
sym = (new URLSearchParams(location.search).get("symbol") || "").trim();
|
||||||
|
} catch (_) {}
|
||||||
|
if (!sym) return;
|
||||||
|
const el = document.getElementById("order-symbol");
|
||||||
|
if (el && !String(el.value || "").trim()) el.value = sym;
|
||||||
|
}
|
||||||
|
|
||||||
function isEmbedShell() {
|
function isEmbedShell() {
|
||||||
return document.body && document.body.getAttribute("data-embed-shell") === "1";
|
return document.body && document.body.getAttribute("data-embed-shell") === "1";
|
||||||
}
|
}
|
||||||
@@ -67,6 +96,7 @@
|
|||||||
if (global.ManualOrderRrPreview && typeof global.ManualOrderRrPreview.wire === "function") {
|
if (global.ManualOrderRrPreview && typeof global.ManualOrderRrPreview.wire === "function") {
|
||||||
global.ManualOrderRrPreview.wire();
|
global.ManualOrderRrPreview.wire();
|
||||||
}
|
}
|
||||||
|
prefillOrderSymbolFromQuery();
|
||||||
}
|
}
|
||||||
if (tab === "key_monitor" && global.KeyMonitorForm && typeof global.KeyMonitorForm.init === "function") {
|
if (tab === "key_monitor" && global.KeyMonitorForm && typeof global.KeyMonitorForm.init === "function") {
|
||||||
global.KeyMonitorForm.init();
|
global.KeyMonitorForm.init();
|
||||||
@@ -140,14 +170,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const fd = new FormData(form);
|
const fd = new FormData(form);
|
||||||
|
const isOrderForm = form.id === "add-order-form";
|
||||||
return fetch(form.action, {
|
return fetch(form.action, {
|
||||||
method: form.method || "POST",
|
method: form.method || "POST",
|
||||||
body: fd,
|
body: fd,
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
redirect: "manual",
|
redirect: "manual",
|
||||||
})
|
})
|
||||||
.then(() => reloadCurrentTab())
|
.then((resp) => {
|
||||||
.catch(() => reloadCurrentTab());
|
if (isOrderForm && isOrderPopup()) {
|
||||||
|
notifyOrderPopupParent(resp.ok || resp.type === "opaqueredirect", "");
|
||||||
|
}
|
||||||
|
return reloadCurrentTab();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (isOrderForm && isOrderPopup()) {
|
||||||
|
notifyOrderPopupParent(false, String(err && err.message ? err.message : err));
|
||||||
|
}
|
||||||
|
return reloadCurrentTab();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchApplyListWindow() {
|
function patchApplyListWindow() {
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ def test_rewrite_embed_dest():
|
|||||||
assert "hub_theme=dark" in url
|
assert "hub_theme=dark" in url
|
||||||
|
|
||||||
|
|
||||||
|
def test_rewrite_embed_dest_order_popup_query():
|
||||||
|
url = rewrite_embed_dest("/trade?order_popup=1&symbol=BTC%2FUSDT")
|
||||||
|
assert "tab=trade" in url
|
||||||
|
assert "order_popup=1" in url
|
||||||
|
assert "symbol=BTC" in url
|
||||||
|
|
||||||
|
|
||||||
def test_embed_tabs_cover_main_nav():
|
def test_embed_tabs_cover_main_nav():
|
||||||
assert "trade" in EMBED_TABS
|
assert "trade" in EMBED_TABS
|
||||||
assert "key_monitor" in EMBED_TABS
|
assert "key_monitor" in EMBED_TABS
|
||||||
|
|||||||
Reference in New Issue
Block a user