From c302c3e4eae8f13fe970424e68c7bac01df4bded Mon Sep 17 00:00:00 2001 From: dekun Date: Tue, 23 Jun 2026 16:53:56 +0800 Subject: [PATCH] Add mobile compact trade and journal lists with tap-to-expand detail. Co-authored-by: Cursor --- crypto_monitor_binance/templates/index.html | 26 +-- crypto_monitor_gate/templates/index.html | 26 +-- crypto_monitor_gate_bot/templates/index.html | 26 +-- crypto_monitor_okx/templates/index.html | 26 +-- hub_bridge.py | 1 + static/instance_records_mobile.js | 72 ++++++++ static/instance_theme.css | 138 +++++++++++++++ static/instance_ui.js | 171 +++++++++++++++++++ 8 files changed, 418 insertions(+), 68 deletions(-) create mode 100644 static/instance_records_mobile.js diff --git a/crypto_monitor_binance/templates/index.html b/crypto_monitor_binance/templates/index.html index 65fd313..0c9a505 100644 --- a/crypto_monitor_binance/templates/index.html +++ b/crypto_monitor_binance/templates/index.html @@ -237,7 +237,7 @@ .stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px} .stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4} - + @@ -822,7 +822,8 @@ - + + @@ -907,6 +908,7 @@ function setDetailBodyPlain(text){ body.innerText = text || ""; } function setDetailBodyMarkdown(text){ + if(window.InstanceUI && InstanceUI.clearDetailActions) InstanceUI.clearDetailActions(); const body = document.getElementById("detailBody"); if(!body) return; if(window.AiReviewRender && AiReviewRender.setElementMarkdown){ @@ -1110,22 +1112,12 @@ function loadJournals(){ const qs = listWindowQueryString(); fetch("/api/journals" + (qs ? "?" + qs : "")).then(r=>r.json()).then(data=>{ Object.keys(journalCache).forEach(k=>delete journalCache[k]); - let html=""; - data.forEach(o=>{ - journalCache[o.id] = o; - const moodTags = (o.mood_issues || []).join(",") || "无"; - html += `
-
${o.coin||"-"} ${o.tf||"-"} | 盈亏:${o.pnl||"-"}U
-
开:${o.open_datetime||"-"} 平:${o.close_datetime||"-"} 持仓:${o.hold_duration||"-"}
-
心态标签:${moodTags}
-
- - -
-
`; - }); + data.forEach(o=>{ journalCache[o.id] = o; }); const box = document.getElementById("journal-list"); - if(box){ box.innerHTML = html || "
暂无数据
"; } + if(box){ + const html = InstanceUI.renderJournalListHtml(data); + box.innerHTML = html || "
暂无数据
"; + } }); } diff --git a/crypto_monitor_gate/templates/index.html b/crypto_monitor_gate/templates/index.html index 61e595e..7e23ceb 100644 --- a/crypto_monitor_gate/templates/index.html +++ b/crypto_monitor_gate/templates/index.html @@ -237,7 +237,7 @@ .stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px} .stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4} - + @@ -789,7 +789,8 @@ - + + @@ -874,6 +875,7 @@ function setDetailBodyPlain(text){ body.innerText = text || ""; } function setDetailBodyMarkdown(text){ + if(window.InstanceUI && InstanceUI.clearDetailActions) InstanceUI.clearDetailActions(); const body = document.getElementById("detailBody"); if(!body) return; if(window.AiReviewRender && AiReviewRender.setElementMarkdown){ @@ -1077,22 +1079,12 @@ function loadJournals(){ const qs = listWindowQueryString(); fetch("/api/journals" + (qs ? "?" + qs : "")).then(r=>r.json()).then(data=>{ Object.keys(journalCache).forEach(k=>delete journalCache[k]); - let html=""; - data.forEach(o=>{ - journalCache[o.id] = o; - const moodTags = (o.mood_issues || []).join(",") || "无"; - html += `
-
${o.coin||"-"} ${o.tf||"-"} | 盈亏:${o.pnl||"-"}U
-
开:${o.open_datetime||"-"} 平:${o.close_datetime||"-"} 持仓:${o.hold_duration||"-"}
-
心态标签:${moodTags}
-
- - -
-
`; - }); + data.forEach(o=>{ journalCache[o.id] = o; }); const box = document.getElementById("journal-list"); - if(box){ box.innerHTML = html || "
暂无数据
"; } + if(box){ + const html = InstanceUI.renderJournalListHtml(data); + box.innerHTML = html || "
暂无数据
"; + } }); } diff --git a/crypto_monitor_gate_bot/templates/index.html b/crypto_monitor_gate_bot/templates/index.html index 61e595e..7e23ceb 100644 --- a/crypto_monitor_gate_bot/templates/index.html +++ b/crypto_monitor_gate_bot/templates/index.html @@ -237,7 +237,7 @@ .stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px} .stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4} - + @@ -789,7 +789,8 @@ - + + @@ -874,6 +875,7 @@ function setDetailBodyPlain(text){ body.innerText = text || ""; } function setDetailBodyMarkdown(text){ + if(window.InstanceUI && InstanceUI.clearDetailActions) InstanceUI.clearDetailActions(); const body = document.getElementById("detailBody"); if(!body) return; if(window.AiReviewRender && AiReviewRender.setElementMarkdown){ @@ -1077,22 +1079,12 @@ function loadJournals(){ const qs = listWindowQueryString(); fetch("/api/journals" + (qs ? "?" + qs : "")).then(r=>r.json()).then(data=>{ Object.keys(journalCache).forEach(k=>delete journalCache[k]); - let html=""; - data.forEach(o=>{ - journalCache[o.id] = o; - const moodTags = (o.mood_issues || []).join(",") || "无"; - html += `
-
${o.coin||"-"} ${o.tf||"-"} | 盈亏:${o.pnl||"-"}U
-
开:${o.open_datetime||"-"} 平:${o.close_datetime||"-"} 持仓:${o.hold_duration||"-"}
-
心态标签:${moodTags}
-
- - -
-
`; - }); + data.forEach(o=>{ journalCache[o.id] = o; }); const box = document.getElementById("journal-list"); - if(box){ box.innerHTML = html || "
暂无数据
"; } + if(box){ + const html = InstanceUI.renderJournalListHtml(data); + box.innerHTML = html || "
暂无数据
"; + } }); } diff --git a/crypto_monitor_okx/templates/index.html b/crypto_monitor_okx/templates/index.html index a57e577..9de18c0 100644 --- a/crypto_monitor_okx/templates/index.html +++ b/crypto_monitor_okx/templates/index.html @@ -237,7 +237,7 @@ .stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px} .stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4} - + @@ -818,7 +818,8 @@ - + + @@ -903,6 +904,7 @@ function setDetailBodyPlain(text){ body.innerText = text || ""; } function setDetailBodyMarkdown(text){ + if(window.InstanceUI && InstanceUI.clearDetailActions) InstanceUI.clearDetailActions(); const body = document.getElementById("detailBody"); if(!body) return; if(window.AiReviewRender && AiReviewRender.setElementMarkdown){ @@ -1106,22 +1108,12 @@ function loadJournals(){ const qs = listWindowQueryString(); fetch("/api/journals" + (qs ? "?" + qs : "")).then(r=>r.json()).then(data=>{ Object.keys(journalCache).forEach(k=>delete journalCache[k]); - let html=""; - data.forEach(o=>{ - journalCache[o.id] = o; - const moodTags = (o.mood_issues || []).join(",") || "无"; - html += `
-
${o.coin||"-"} ${o.tf||"-"} | 盈亏:${o.pnl||"-"}U
-
开:${o.open_datetime||"-"} 平:${o.close_datetime||"-"} 持仓:${o.hold_duration||"-"}
-
心态标签:${moodTags}
-
- - -
-
`; - }); + data.forEach(o=>{ journalCache[o.id] = o; }); const box = document.getElementById("journal-list"); - if(box){ box.innerHTML = html || "
暂无数据
"; } + if(box){ + const html = InstanceUI.renderJournalListHtml(data); + box.innerHTML = html || "
暂无数据
"; + } }); } diff --git a/hub_bridge.py b/hub_bridge.py index a3cee03..6c7b395 100644 --- a/hub_bridge.py +++ b/hub_bridge.py @@ -54,6 +54,7 @@ def install_instance_theme_static(app) -> None: "instance_theme.css": "text/css; charset=utf-8", "account_risk_badge.css": "text/css; charset=utf-8", "instance_ui.js": "application/javascript; charset=utf-8", + "instance_records_mobile.js": "application/javascript; charset=utf-8", "ai_review_render.js": "application/javascript; charset=utf-8", "form_submit_guard.js": "application/javascript; charset=utf-8", "key_monitor_form.js": "application/javascript; charset=utf-8", diff --git a/static/instance_records_mobile.js b/static/instance_records_mobile.js new file mode 100644 index 0000000..e7f35c2 --- /dev/null +++ b/static/instance_records_mobile.js @@ -0,0 +1,72 @@ +/** + * 手机端:交易记录 / 复盘记录紧凑列表(币种 · 方向 · 盈亏),点击展开详情。 + */ +(function (global) { + "use strict"; + + var resizeTimer = null; + + function refreshTradeRecords() { + var UI = global.InstanceUI; + if (!UI) return; + var card = document.querySelector(".records-card"); + if (!card) return; + var tableWrap = card.querySelector(".table-wrap"); + var table = tableWrap && tableWrap.querySelector("table"); + if (!table) return; + + var listEl = card.querySelector(".mobile-record-list"); + var mobile = UI.isMobileCompactRecords(); + + if (!mobile) { + if (listEl) listEl.remove(); + return; + } + + if (!listEl) { + listEl = document.createElement("div"); + listEl.className = "mobile-record-list"; + tableWrap.parentNode.insertBefore(listEl, tableWrap); + } + + var rows = table.querySelectorAll('tr[id^="trade-row-"]'); + listEl.innerHTML = Array.prototype.map + .call(rows, function (tr) { + return UI.renderMobileTradeRow(tr); + }) + .join(""); + + listEl.querySelectorAll(".mobile-record-row").forEach(function (btn) { + btn.addEventListener("click", function () { + var rowId = btn.getAttribute("data-row-id"); + var tr = rowId && document.getElementById(rowId); + if (tr) UI.openTradeRecordDetailModal(tr); + }); + }); + } + + function onResize() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(function () { + refreshTradeRecords(); + if (typeof global.loadJournals === "function" && document.getElementById("journal-list")) { + global.loadJournals(); + } + }, 180); + } + + function init() { + refreshTradeRecords(); + global.addEventListener("resize", onResize); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } + + global.InstanceRecordsMobile = { + refresh: refreshTradeRecords, + }; +})(typeof window !== "undefined" ? window : globalThis); diff --git a/static/instance_theme.css b/static/instance_theme.css index d1c77f9..446fe17 100644 --- a/static/instance_theme.css +++ b/static/instance_theme.css @@ -98,6 +98,120 @@ .grid { grid-template-columns: minmax(0, 1fr) !important; } + + .records-card .table-wrap { + display: none !important; + } + + .mobile-record-list { + display: flex; + flex-direction: column; + gap: 6px; + } + + .mobile-record-row-wrap { + display: flex; + align-items: stretch; + gap: 6px; + } + + .mobile-record-row { + flex: 1; + display: grid; + grid-template-columns: minmax(0, 1.2fr) auto minmax(0, 0.9fr); + align-items: center; + gap: 8px; + width: 100%; + margin: 0; + padding: 10px 12px; + border: 1px solid rgba(120, 140, 200, 0.28); + border-radius: 8px; + background: rgba(18, 24, 42, 0.65); + color: #e8ecff; + font-size: 0.82rem; + text-align: left; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + } + + .mobile-record-row:active { + background: rgba(30, 42, 72, 0.85); + } + + .mrr-symbol { + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .mrr-dir { + justify-self: center; + } + + .mrr-dir .badge { + font-size: 0.72rem; + padding: 2px 8px; + } + + .mrr-pnl { + justify-self: end; + font-weight: 600; + white-space: nowrap; + } + + .mrr-muted { + color: #8892b0; + font-size: 0.78rem; + } + + .mobile-record-del { + flex: 0 0 36px; + width: 36px; + border: 1px solid rgba(200, 80, 80, 0.35); + border-radius: 8px; + background: rgba(80, 24, 24, 0.35); + color: #ff9a9a; + font-size: 1.1rem; + line-height: 1; + cursor: pointer; + } + + #journal-list .entry { + display: none; + } + + #journal-list .journal-empty-msg { + color: #8892b0; + font-size: 0.82rem; + padding: 8px 4px; + } + + #detailActions.detail-actions, + .detail-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; + padding: 10px 14px 14px; + border-top: 1px solid rgba(120, 140, 200, 0.2); + } + + .detail-actions-inner { + display: flex; + flex-wrap: wrap; + gap: 8px; + width: 100%; + } + + .detail-actions .table-del, + .detail-actions button { + font-size: 0.78rem !important; + padding: 6px 10px !important; + } +} + +.mobile-record-list { + display: none; } /* 手机竖屏(含大屏手机) */ @@ -1090,3 +1204,27 @@ html[data-theme="light"] .key-row-collapse.key-history-failed .key-history-outco border-color: rgba(192, 48, 48, 0.22) !important; } +html[data-theme="light"] .mobile-record-row { + background: #fff !important; + border-color: #b8c8d8 !important; + color: #142232 !important; +} + +html[data-theme="light"] .mobile-record-row:active { + background: #eef3f8 !important; +} + +html[data-theme="light"] .mrr-muted { + color: #6a7588 !important; +} + +html[data-theme="light"] .mobile-record-del { + background: rgba(192, 48, 48, 0.08) !important; + border-color: rgba(192, 48, 48, 0.28) !important; + color: #b04040 !important; +} + +html[data-theme="light"] .detail-actions { + border-top-color: #d0dae4 !important; +} + diff --git a/static/instance_ui.js b/static/instance_ui.js index 01ca56a..f8df97d 100644 --- a/static/instance_ui.js +++ b/static/instance_ui.js @@ -66,6 +66,7 @@ titleEl.innerText = `交易复盘详情|${o.coin || "-"} ${o.tf || "-"}`; } setJournalDetailBody(o, formatExitLine); + clearDetailActions(); const imgEl = document.getElementById("detailImage"); if (imgEl) { if (o.image) { @@ -83,6 +84,168 @@ if (modal) modal.style.display = "flex"; } + function isMobileCompactRecords() { + if (typeof window === "undefined" || !window.matchMedia) return false; + return window.matchMedia("(max-width: 720px)").matches; + } + + function inferJournalDirection(o) { + const text = String((o && o.entry_reason) || ""); + if (/做空|空头|short/i.test(text)) { + return { text: "做空", cls: "direction-short" }; + } + if (/做多|多头|long/i.test(text)) { + return { text: "做多", cls: "direction-long" }; + } + return null; + } + + function renderJournalListHtml(data) { + if (!data || !data.length) return ""; + const mobile = isMobileCompactRecords(); + return data + .map(function (o) { + if (mobile) { + const dir = inferJournalDirection(o); + const pnlCls = pnlClassFromValue(o.pnl); + const dirHtml = dir + ? `${escapeHtml(dir.text)}` + : `-`; + const id = escapeHtml(o.id); + return `
+ + +
`; + } + const moodTags = (o.mood_issues || []).join(",") || "无"; + const id = escapeHtml(o.id); + return `
+
${escapeHtml(o.coin || "-")} ${escapeHtml(o.tf || "-")} | 盈亏:${escapeHtml(o.pnl == null || o.pnl === "" ? "-" : o.pnl)}U
+
开:${escapeHtml(o.open_datetime || "-")} 平:${escapeHtml(o.close_datetime || "-")} 持仓:${escapeHtml(o.hold_duration || "-")}
+
心态标签:${escapeHtml(moodTags)}
+
+ + +
+
`; + }) + .join(""); + } + + function parseTradeRecordRow(tr) { + const cells = tr.querySelectorAll("td"); + if (cells.length < 14) return null; + const dirBadge = cells[2].querySelector(".badge"); + return { + rowId: tr.id, + symbol: cells[0].textContent.trim(), + type: cells[1].textContent.trim(), + directionHtml: dirBadge ? dirBadge.outerHTML : cells[2].innerHTML, + directionText: cells[2].textContent.trim(), + trigger: cells[3].textContent.trim(), + stopLoss: cells[4].textContent.trim(), + takeProfit: cells[5].textContent.trim(), + margin: cells[6].textContent.trim(), + leverage: cells[7].textContent.trim(), + holdMinutes: cells[8].textContent.trim(), + openedAt: cells[9].textContent.trim(), + closedAt: cells[10].textContent.trim(), + pnlHtml: cells[11].innerHTML, + pnlText: cells[11].textContent.trim(), + resultHtml: cells[12].innerHTML, + resultText: cells[12].textContent.trim(), + actionsHtml: cells[13].innerHTML, + }; + } + + function renderMobileTradeRow(tr) { + const row = parseTradeRecordRow(tr); + if (!row) return ""; + const pnlCls = pnlClassFromValue(row.pnlText); + return ``; + } + + function buildTradeRecordDetailHtml(row) { + const lines = [ + `品种:${escapeHtml(row.symbol)}`, + `类型:${escapeHtml(row.type)}`, + `方向:${row.directionHtml}`, + `成交价:${escapeHtml(row.trigger)}`, + `止损(开仓):${escapeHtml(row.stopLoss)}`, + `止盈:${escapeHtml(row.takeProfit)}`, + `基数:${escapeHtml(row.margin)}`, + `杠杆:${escapeHtml(row.leverage)}`, + `持仓分钟:${escapeHtml(row.holdMinutes)}`, + `开仓时间:${escapeHtml(row.openedAt)}`, + `平仓时间:${escapeHtml(row.closedAt)}`, + `盈亏U:${row.pnlHtml}`, + `结果:${row.resultHtml}`, + ]; + return lines.join("
"); + } + + function clearDetailActions() { + const el = document.getElementById("detailActions"); + if (el) { + el.innerHTML = ""; + el.style.display = "none"; + } + } + + function setDetailActionsHtml(html) { + let el = document.getElementById("detailActions"); + if (!el) { + const panel = document.querySelector("#detailModal .panel"); + if (!panel) return; + el = document.createElement("div"); + el.id = "detailActions"; + el.className = "detail-actions"; + const body = document.getElementById("detailBody"); + if (body && body.parentNode === panel) { + panel.insertBefore(el, body.nextSibling); + } else { + panel.appendChild(el); + } + } + el.innerHTML = html || ""; + el.style.display = html ? "flex" : "none"; + } + + function openTradeRecordDetailModal(tr) { + const row = parseTradeRecordRow(tr); + if (!row) return; + const titleEl = document.getElementById("detailTitle"); + if (titleEl) { + titleEl.innerText = `交易记录|${row.symbol}`; + } + const body = document.getElementById("detailBody"); + if (body) { + body.classList.remove("md-review", "journal-detail-meta"); + body.innerHTML = buildTradeRecordDetailHtml(row); + } + setDetailActionsHtml( + `
${row.actionsHtml}
` + ); + const imgEl = document.getElementById("detailImage"); + if (imgEl) { + imgEl.src = ""; + imgEl.style.display = "none"; + } + if (typeof setDetailModalFullscreen === "function") { + setDetailModalFullscreen(false); + } + const modal = document.getElementById("detailModal"); + if (modal) modal.style.display = "flex"; + } + global.InstanceUI = { escapeHtml: escapeHtml, pnlClassFromValue: pnlClassFromValue, @@ -90,5 +253,13 @@ buildJournalDetailHtml: buildJournalDetailHtml, setJournalDetailBody: setJournalDetailBody, openJournalDetailModal: openJournalDetailModal, + isMobileCompactRecords: isMobileCompactRecords, + inferJournalDirection: inferJournalDirection, + renderJournalListHtml: renderJournalListHtml, + parseTradeRecordRow: parseTradeRecordRow, + renderMobileTradeRow: renderMobileTradeRow, + buildTradeRecordDetailHtml: buildTradeRecordDetailHtml, + openTradeRecordDetailModal: openTradeRecordDetailModal, + clearDetailActions: clearDetailActions, }; })(typeof window !== "undefined" ? window : globalThis);