diff --git a/lib/common/static/options_review.js b/lib/common/static/options_review.js index a3a50c4..4db3b53 100644 --- a/lib/common/static/options_review.js +++ b/lib/common/static/options_review.js @@ -32,11 +32,68 @@ var tradesPages = 1; var reviewedPage = 0; var reviewedPages = 1; + var tradesListFp = ""; + var reviewedListFp = ""; + var statsFp = ""; function $(id) { return document.getElementById(id); } + function listFingerprint(kind, data, rows) { + var parts = [ + kind, + activeSource, + String((data && data.total) || 0), + String((data && data.page) || 0), + String((data && data.pages) || 0), + ]; + (rows || []).forEach(function (t) { + var e = t.entry || {}; + parts.push( + [ + t.id, + t.realized_pnl_total, + t.profit_rate, + t.initial_premium_usdt, + t.option_fee_usdt, + t.status_raw, + t.reviewed ? 1 : 0, + t.opened_at, + t.closed_at, + t.hold_seconds, + t.reviewed_at, + t.strategy_tag || e.strategy_tag, + t.result_tag || e.result_tag, + t.direction_view || e.direction_view, + t.entry_logic || e.entry_logic, + t.source_label || t.source_type, + t.inst_id, + t.underlying, + t.direction, + t.plan_close_reason, + ].join("\u001f") + ); + }); + return parts.join("\u001e"); + } + + function statsFingerprint(data) { + try { + return JSON.stringify({ + kpi: data.kpi || {}, + by_source_type: data.by_source_type || [], + by_underlying: data.by_underlying || [], + by_strategy: data.by_strategy || [], + by_close_reason: data.by_close_reason || [], + by_hold_bucket: data.by_hold_bucket || [], + by_opt_type: data.by_opt_type || [], + }); + } catch (_) { + return String(Date.now()); + } + } + function escapeHtml(s) { return String(s == null ? "" : s) .replace(/&/g, "&") @@ -203,7 +260,7 @@ setSyncStatus("读取本地记录…"); loadTrades({ sync: false }); loadReviewed({ sync: false }); - loadStats(); + loadStats({ soft: false }); backgroundSync(); } @@ -221,7 +278,7 @@ setSyncStatus("本地记录已加载"); loadTrades({ sync: false, soft: true }); loadReviewed({ sync: false, soft: true }); - loadStats(); + loadStats({ soft: true }); }) .catch(function () { setSyncStatus("同步跳过,已显示本地缓存"); @@ -379,12 +436,8 @@ function beginListLoad(wrapId, soft) { var wrap = $(wrapId); if (!wrap) return null; - if (soft) { - if (!wrap.style.minHeight) { - wrap.style.minHeight = Math.max(wrap.offsetHeight, 1) + "px"; - } - wrap.classList.add("or-list-loading"); - } else { + // soft 刷新不打半透明:无变化时也不应闪;有变化时静默换表 + if (!soft) { wrap.classList.remove("or-list-loading"); wrap.style.minHeight = ""; } @@ -405,6 +458,7 @@ if (!tbody) return; var wrap = beginListLoad("or-trades-wrap", soft); if (!soft) { + tradesListFp = ""; tbody.innerHTML = '加载中…'; } var p = baseQs(); @@ -419,6 +473,7 @@ .then(function (data) { if (doSync) setSyncStatus("本地记录已加载"); if (!data.ok) { + tradesListFp = ""; tbody.innerHTML = '加载失败'; endListLoad(wrap); return; @@ -428,6 +483,12 @@ return; } var rows = data.trades || []; + var fp = listFingerprint("trades", data, rows); + if (soft && fp === tradesListFp) { + endListLoad(wrap); + return; + } + tradesListFp = fp; tradesCache = {}; if (!rows.length) { tbody.innerHTML = @@ -546,6 +607,7 @@ if (!tbody) return; var wrap = beginListLoad("or-reviewed-wrap", soft); if (!soft) { + reviewedListFp = ""; tbody.innerHTML = '加载中…'; } var p = baseQs(); @@ -559,6 +621,7 @@ }) .then(function (data) { if (!data.ok) { + reviewedListFp = ""; tbody.innerHTML = '加载失败'; endListLoad(wrap); return; @@ -568,6 +631,12 @@ return; } var rows = data.trades || []; + var fp = listFingerprint("reviewed", data, rows); + if (soft && fp === reviewedListFp) { + endListLoad(wrap); + return; + } + reviewedListFp = fp; reviewedCache = {}; if (!rows.length) { tbody.innerHTML = '暂无复盘记录'; @@ -1024,10 +1093,13 @@ ); } - function loadStats() { + function loadStats(opts) { + opts = opts || {}; + var soft = !!opts.soft; var kpi = $("or-kpi"); var groups = $("or-stats-groups"); if (!kpi || !groups) return; + if (!soft) statsFp = ""; // 与 trades?sync=1 并行时不再二次同步,避免 SQLite database is locked var qs = baseQs(); qs.set("sync", "0"); @@ -1037,6 +1109,9 @@ }) .then(function (data) { if (!data.ok) return; + var fp = statsFingerprint(data); + if (soft && fp === statsFp) return; + statsFp = fp; var k = data.kpi || {}; kpi.innerHTML = [ ["笔数", k.total], diff --git a/lib/options/templates/options_review_panel.html b/lib/options/templates/options_review_panel.html index 59b086e..f599bcf 100644 --- a/lib/options/templates/options_review_panel.html +++ b/lib/options/templates/options_review_panel.html @@ -465,4 +465,4 @@ - +