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 @@
@@ -179,6 +179,7 @@
{# 3. 已复盘记录 + 详情 #}
复盘记录
+
已保存的复盘(每页5条).点一行查看详情.
保存后出现在此.点一行查看详情(含截图与完整复盘内容).
+
-
+