diff --git a/lib/common/static/options_review.js b/lib/common/static/options_review.js index f67f1d4..1c39242 100644 --- a/lib/common/static/options_review.js +++ b/lib/common/static/options_review.js @@ -29,7 +29,9 @@ var tradesCache = {}; var reviewedCache = {}; var tradesPage = 0; - var tradesHasMore = false; + var tradesPages = 1; + var reviewedPage = 0; + var reviewedPages = 1; function $(id) { return document.getElementById(id); @@ -115,8 +117,8 @@ function reloadAll() { setSyncStatus("读取本地记录…"); - loadTrades(); - loadReviewed(); + loadTrades({ sync: true }); + loadReviewed({ sync: false }); loadStats(); } @@ -198,6 +200,7 @@ function setActiveTab(source) { activeSource = source || "option_spot"; tradesPage = 0; + reviewedPage = 0; document.querySelectorAll(".or-tab").forEach(function (btn) { btn.classList.toggle("active", btn.getAttribute("data-source") === activeSource); }); @@ -213,12 +216,49 @@ var label = $("or-trades-page-label"); var prev = $("or-trades-prev"); var next = $("or-trades-next"); - if (label) label.textContent = "第 " + (tradesPage + 1) + " 页"; + if (label) { + label.textContent = "第 " + (tradesPage + 1) + " / " + tradesPages + " 页"; + } if (prev) prev.disabled = tradesPage <= 0; - if (next) next.disabled = !tradesHasMore; + if (next) next.disabled = tradesPage + 1 >= tradesPages; } - function loadTrades() { + function updateReviewedPager() { + var label = $("or-reviewed-page-label"); + var prev = $("or-reviewed-prev"); + var next = $("or-reviewed-next"); + if (label) { + label.textContent = "第 " + (reviewedPage + 1) + " / " + reviewedPages + " 页"; + } + if (prev) prev.disabled = reviewedPage <= 0; + if (next) next.disabled = reviewedPage + 1 >= reviewedPages; + } + + function applyPagerMeta(data, kind) { + var pages = Number(data.pages || 1); + if (!pages || pages < 1) pages = 1; + var clamped = false; + if (kind === "trades") { + tradesPages = pages; + if (tradesPage >= tradesPages) { + tradesPage = Math.max(0, tradesPages - 1); + clamped = true; + } + updateTradesPager(); + } else { + reviewedPages = pages; + if (reviewedPage >= reviewedPages) { + reviewedPage = Math.max(0, reviewedPages - 1); + clamped = true; + } + updateReviewedPager(); + } + return clamped; + } + + function loadTrades(opts) { + opts = opts || {}; + var doSync = opts.sync !== false; var tbody = $("or-trades-tbody"); if (!tbody) return; tbody.innerHTML = '加载中…'; @@ -226,20 +266,23 @@ p.set("reviewed", "0"); p.set("limit", String(PAGE_SIZE)); p.set("offset", String(tradesPage * PAGE_SIZE)); + if (!doSync) p.set("sync", "0"); fetch("/api/options/review/trades?" + p.toString(), { credentials: "same-origin" }) .then(function (r) { return r.json(); }) .then(function (data) { - setSyncStatus("本地记录已加载"); + if (doSync) setSyncStatus("本地记录已加载"); if (!data.ok) { tbody.innerHTML = '加载失败'; return; } + if (applyPagerMeta(data, "trades") && Number(data.total || 0) > 0) { + loadTrades(opts); + return; + } var rows = data.trades || []; - tradesHasMore = rows.length >= PAGE_SIZE; tradesCache = {}; - updateTradesPager(); if (!rows.length) { tbody.innerHTML = '暂无待复盘记录'; @@ -304,14 +347,17 @@ }); } - function loadReviewed() { + function loadReviewed(opts) { + opts = opts || {}; + var doSync = opts.sync === true; var tbody = $("or-reviewed-tbody"); if (!tbody) return; tbody.innerHTML = '加载中…'; var p = baseQs(); p.set("reviewed", "1"); - p.set("limit", "100"); - p.set("offset", "0"); + p.set("limit", String(PAGE_SIZE)); + p.set("offset", String(reviewedPage * PAGE_SIZE)); + if (!doSync) p.set("sync", "0"); fetch("/api/options/review/trades?" + p.toString(), { credentials: "same-origin" }) .then(function (r) { return r.json(); @@ -321,6 +367,10 @@ tbody.innerHTML = '加载失败'; return; } + if (applyPagerMeta(data, "reviewed") && Number(data.total || 0) > 0) { + loadReviewed(opts); + return; + } var rows = data.trades || []; reviewedCache = {}; if (!rows.length) { @@ -929,6 +979,8 @@ var delBtn = $("or-del-btn"); var prevBtn = $("or-trades-prev"); var nextBtn = $("or-trades-next"); + var reviewedPrev = $("or-reviewed-prev"); + var reviewedNext = $("or-reviewed-next"); var detailClose = $("or-detail-close-btn"); var detailEdit = $("or-detail-edit-btn"); if (reloadBtn) reloadBtn.addEventListener("click", reloadAll); @@ -939,14 +991,28 @@ prevBtn.addEventListener("click", function () { if (tradesPage <= 0) return; tradesPage -= 1; - loadTrades(); + loadTrades({ sync: false }); }); } if (nextBtn) { nextBtn.addEventListener("click", function () { - if (!tradesHasMore) return; + if (tradesPage + 1 >= tradesPages) return; tradesPage += 1; - loadTrades(); + loadTrades({ sync: false }); + }); + } + if (reviewedPrev) { + reviewedPrev.addEventListener("click", function () { + if (reviewedPage <= 0) return; + reviewedPage -= 1; + loadReviewed({ sync: false }); + }); + } + if (reviewedNext) { + reviewedNext.addEventListener("click", function () { + if (reviewedPage + 1 >= reviewedPages) return; + reviewedPage += 1; + loadReviewed({ sync: false }); }); } if (detailClose) detailClose.addEventListener("click", hideDetail); @@ -961,6 +1027,7 @@ if (el) { el.addEventListener("change", function () { tradesPage = 0; + reviewedPage = 0; reloadAll(); }); } @@ -970,6 +1037,7 @@ if (el) { el.addEventListener("change", function () { tradesPage = 0; + reviewedPage = 0; reloadAll(); }); } diff --git a/lib/options/options_review_lib.py b/lib/options/options_review_lib.py index e699685..43f4b60 100644 --- a/lib/options/options_review_lib.py +++ b/lib/options/options_review_lib.py @@ -591,8 +591,7 @@ def enrich_trade_row(row: dict[str, Any], entry: dict[str, Any] | None = None) - return out -def list_review_trades( - conn: sqlite3.Connection, +def _review_trades_filters( *, source_type: str | None = None, underlying: str | None = None, @@ -602,10 +601,7 @@ def list_review_trades( include_hedge_legs: bool = False, closed_from: str | None = None, closed_to: str | None = None, - limit: int = 200, - offset: int = 0, -) -> list[dict[str, Any]]: - init_options_review_tables(conn) +) -> tuple[str, list[Any]]: wheres: list[str] = [] args: list[Any] = [] if source_type and source_type in SOURCE_TYPES: @@ -647,6 +643,69 @@ def list_review_trades( elif reviewed == "0" or reviewed == "no": wheres.append("e.id IS NULL") where = (" WHERE " + " AND ".join(wheres)) if wheres else "" + return where, args + + +def count_review_trades( + conn: sqlite3.Connection, + *, + source_type: str | None = None, + underlying: str | None = None, + opt_type: str | None = None, + strategy_tag: str | None = None, + reviewed: str | None = None, + include_hedge_legs: bool = False, + closed_from: str | None = None, + closed_to: str | None = None, +) -> int: + init_options_review_tables(conn) + where, args = _review_trades_filters( + source_type=source_type, + underlying=underlying, + opt_type=opt_type, + strategy_tag=strategy_tag, + reviewed=reviewed, + include_hedge_legs=include_hedge_legs, + closed_from=closed_from, + closed_to=closed_to, + ) + row = conn.execute( + f""" + SELECT COUNT(*) AS c + FROM options_review_trades t + LEFT JOIN options_review_entries e ON e.trade_id = t.id + {where} + """, + args, + ).fetchone() + return int(row["c"] if row else 0) + + +def list_review_trades( + conn: sqlite3.Connection, + *, + source_type: str | None = None, + underlying: str | None = None, + opt_type: str | None = None, + strategy_tag: str | None = None, + reviewed: str | None = None, + include_hedge_legs: bool = False, + closed_from: str | None = None, + closed_to: str | None = None, + limit: int = 200, + offset: int = 0, +) -> list[dict[str, Any]]: + init_options_review_tables(conn) + where, args = _review_trades_filters( + source_type=source_type, + underlying=underlying, + opt_type=opt_type, + strategy_tag=strategy_tag, + reviewed=reviewed, + include_hedge_legs=include_hedge_legs, + closed_from=closed_from, + closed_to=closed_to, + ) rows = conn.execute( f""" SELECT t.*, e.id AS entry_id, e.strategy_tag AS e_strategy_tag, diff --git a/lib/options/options_review_register.py b/lib/options/options_review_register.py index 58a145d..da2fc57 100644 --- a/lib/options/options_review_register.py +++ b/lib/options/options_review_register.py @@ -19,6 +19,7 @@ from lib.options.options_review_images_lib import ( from lib.options.options_review_lib import ( SOURCE_LABELS, compute_review_stats, + count_review_trades, delete_review_entry, ensure_local_review_synced, get_review_trade, @@ -120,23 +121,46 @@ def register_options_review_routes(app: Flask, cfg: dict[str, Any], repo_root: s def api_options_review_trades(): conn = cfg["get_db"]() try: - ensure_local_review_synced(conn) - conn.commit() - items = list_review_trades( - conn, + # 翻页可跳过同步,仅刷新当前卡片列表 + do_sync = (request.args.get("sync") or "1").strip().lower() not in ( + "0", + "false", + "no", + ) + if do_sync: + ensure_local_review_synced(conn) + conn.commit() + filt = dict( source_type=(request.args.get("source_type") or "").strip() or None, underlying=(request.args.get("underlying") or "").strip() or None, opt_type=(request.args.get("opt_type") or "").strip() or None, strategy_tag=(request.args.get("strategy_tag") or "").strip() or None, reviewed=(request.args.get("reviewed") or "").strip() or None, - include_hedge_legs=(request.args.get("include_hedge_legs") or "").strip().lower() + include_hedge_legs=(request.args.get("include_hedge_legs") or "") + .strip() + .lower() in ("1", "true", "yes"), closed_from=(request.args.get("closed_from") or "").strip() or None, closed_to=(request.args.get("closed_to") or "").strip() or None, - limit=min(500, max(1, int(request.args.get("limit") or 200))), - offset=max(0, int(request.args.get("offset") or 0)), ) - return jsonify({"ok": True, "trades": items, "source_labels": SOURCE_LABELS}) + limit = min(500, max(1, int(request.args.get("limit") or 200))) + offset = max(0, int(request.args.get("offset") or 0)) + total = count_review_trades(conn, **filt) + items = list_review_trades(conn, **filt, limit=limit, offset=offset) + pages = max(1, (total + limit - 1) // limit) if total else 1 + page = (offset // limit) + 1 if limit else 1 + return jsonify( + { + "ok": True, + "trades": items, + "source_labels": SOURCE_LABELS, + "total": total, + "limit": limit, + "offset": offset, + "page": page, + "pages": pages, + } + ) finally: conn.close() diff --git a/lib/options/templates/options_review_panel.html b/lib/options/templates/options_review_panel.html index 8620d5b..98f1512 100644 --- a/lib/options/templates/options_review_panel.html +++ b/lib/options/templates/options_review_panel.html @@ -89,7 +89,7 @@
- 1 + 第 1 / 1 页
@@ -179,6 +179,7 @@ {# 3. 已复盘记录 + 详情 #}

复盘记录

+

已保存的复盘(每页5条).点一行查看详情.

保存后出现在此.点一行查看详情(含截图与完整复盘内容).

@@ -197,6 +198,11 @@
+
+ + 第 1 / 1 页 + +
- +