diff --git a/docs/更新文档.md b/docs/更新文档.md
index 1bbefaf..3707b12 100644
--- a/docs/更新文档.md
+++ b/docs/更新文档.md
@@ -4,6 +4,26 @@
---
+## 2026-07-19 · 期权复盘详情改为对话框 + 截图显示修复
+
+### 修改原因
+
+复盘详情嵌在列表下方不便查看;截图缩略图易裁切/偶发加载失败。
+
+### 修改的地方
+
+| 文件 | 改动摘要 |
+|------|----------|
+| `options_review_panel.html` | 详情改为居中对话框;2×2 截图网格 |
+| `options_review.js` | 点复盘记录打开弹窗;截图 basename + onerror |
+| `options_review_register.py` | 截图静态路由不强制登录(防 iframe 401) |
+
+### 交付之后的验收
+
+点「复盘记录」行弹出对话框;5m/15m/1h/4h 截图完整可见(缺失显示提示);关闭/Esc/点遮罩可关。
+
+---
+
## 2026-07-19 · 期期情景测算:盈亏配色 + 盈亏比(亏=全额保费)
### 修改原因
diff --git a/lib/common/static/options_review.js b/lib/common/static/options_review.js
index 328bb3f..43518fd 100644
--- a/lib/common/static/options_review.js
+++ b/lib/common/static/options_review.js
@@ -464,19 +464,18 @@
}
function hideDetail() {
- var panel = $("or-detail-panel");
- if (panel) panel.classList.add("hidden");
+ var backdrop = $("or-detail-backdrop");
+ if (backdrop) backdrop.hidden = true;
}
function openDetail(tradeId) {
- var panel = $("or-detail-panel");
- if (!panel) return;
- panel.classList.remove("hidden");
+ var backdrop = $("or-detail-backdrop");
+ if (!backdrop) return;
+ backdrop.hidden = false;
($("or-detail-title") || {}).textContent = "加载中…";
($("or-detail-meta") || {}).innerHTML = "";
($("or-detail-text") || {}).innerHTML = "";
($("or-detail-images") || {}).innerHTML = "";
- panel.scrollIntoView({ behavior: "smooth", block: "nearest" });
fetch("/api/options/review/trades/" + tradeId, { credentials: "same-origin" })
.then(function (r) {
@@ -494,6 +493,92 @@
});
}
+ function optionsJournalImgSrc(file) {
+ var name = String(file || "").trim().replace(/\\/g, "/");
+ var slash = name.lastIndexOf("/");
+ if (slash >= 0) name = name.slice(slash + 1);
+ if (!name) return "";
+ return "/static/images/options_journal/" + encodeURIComponent(name);
+ }
+
+ function renderDetailImages(images) {
+ var imagesHost = $("or-detail-images");
+ if (!imagesHost) return;
+ var byTf = {};
+ (images || []).forEach(function (img) {
+ var tf = String((img && img.tf) || "").trim();
+ var file = String((img && img.file) || "").trim();
+ if (!file) return;
+ var key = tf || "_";
+ byTf[key] = file;
+ });
+ var order = ["5m", "15m", "1h", "4h"];
+ var keys = order.slice();
+ Object.keys(byTf).forEach(function (k) {
+ if (keys.indexOf(k) < 0) keys.push(k);
+ });
+ var cells = keys
+ .map(function (tf) {
+ var file = byTf[tf];
+ if (!file) {
+ if (order.indexOf(tf) < 0) return "";
+ return (
+ '
' +
+ '
' +
+ escapeHtml(tf) +
+ "" +
+ '
未上传
' +
+ "
"
+ );
+ }
+ var src = optionsJournalImgSrc(file);
+ var label = escapeHtml(tf === "_" ? "截图" : tf);
+ return (
+ '' +
+ '
' +
+ label +
+ "" +
+ '

' +
+ "
"
+ );
+ })
+ .filter(Boolean);
+ if (!cells.length) {
+ imagesHost.innerHTML = '无截图
';
+ return;
+ }
+ imagesHost.innerHTML = cells.join("");
+ imagesHost.querySelectorAll("img").forEach(function (img) {
+ img.addEventListener("error", function () {
+ var cell = img.closest(".or-detail-img-cell");
+ if (!cell) return;
+ var label = cell.querySelector(".or-detail-img-label");
+ var tf = label ? label.textContent : "截图";
+ cell.innerHTML =
+ '' +
+ escapeHtml(tf) +
+ "" +
+ '文件缺失或无法加载
';
+ });
+ img.addEventListener("click", function () {
+ var src = img.getAttribute("data-src") || img.src;
+ if (typeof global.showImage === "function") {
+ global.showImage(src);
+ } else if (typeof window.showImage === "function") {
+ window.showImage(src);
+ } else {
+ global.open(src, "_blank");
+ }
+ });
+ });
+ }
+
function renderDetail(t) {
var e = t.entry || {};
reviewedCache[t.id] = t;
@@ -566,42 +651,7 @@
var imagesHost = $("or-detail-images");
if (imagesHost) {
- var images = e.images || [];
- if (!images.length) {
- imagesHost.innerHTML = '无截图
';
- } else {
- imagesHost.innerHTML = images
- .map(function (img) {
- var file = String(img.file || "").trim();
- if (!file) return "";
- var src = "/static/images/options_journal/" + encodeURIComponent(file).replace(/%2F/g, "/");
- var label = escapeHtml(img.tf || "截图");
- return (
- '' +
- '
' +
- label +
- "" +
- '

' +
- "
"
- );
- })
- .join("");
- imagesHost.querySelectorAll("img").forEach(function (img) {
- img.addEventListener("click", function () {
- if (typeof global.showImage === "function") {
- global.showImage(img.getAttribute("data-src"));
- } else {
- global.open(img.getAttribute("data-src"), "_blank");
- }
- });
- });
- }
+ renderDetailImages(e.images || []);
}
}
@@ -1075,9 +1125,23 @@
if (detailEdit) {
detailEdit.addEventListener("click", function () {
var id = Number(detailEdit.getAttribute("data-id") || 0);
- if (id) openJournalForm(id);
+ if (id) {
+ hideDetail();
+ openJournalForm(id);
+ }
});
}
+ var detailBackdrop = $("or-detail-backdrop");
+ if (detailBackdrop) {
+ detailBackdrop.addEventListener("click", function (ev) {
+ if (ev.target === detailBackdrop) hideDetail();
+ });
+ }
+ document.addEventListener("keydown", function (ev) {
+ if (ev.key !== "Escape") return;
+ var bd = $("or-detail-backdrop");
+ if (bd && !bd.hidden) hideDetail();
+ });
["or-filter-uly", "or-filter-opt", "or-include-hedge-legs"].forEach(function (id) {
var el = $(id);
if (el) {
diff --git a/lib/options/options_review_register.py b/lib/options/options_review_register.py
index da2fc57..64683cb 100644
--- a/lib/options/options_review_register.py
+++ b/lib/options/options_review_register.py
@@ -94,8 +94,8 @@ def register_options_review_routes(app: Flask, cfg: dict[str, Any], repo_root: s
return send_file(path, mimetype="application/javascript; charset=utf-8")
@app.route("/static/images/options_journal/")
- @lr
def static_options_review_image(filename: str):
+ """截图文件名含 32 位 draft id,按静态资源提供(不强制登录,避免 iframe img 偶发 401)."""
folder = options_review_upload_dir(cfg["upload_folder"])
safe = os.path.basename(filename or "")
path = os.path.join(folder, safe)
diff --git a/lib/options/templates/options_review_panel.html b/lib/options/templates/options_review_panel.html
index 63ea0a0..1d5e36e 100644
--- a/lib/options/templates/options_review_panel.html
+++ b/lib/options/templates/options_review_panel.html
@@ -28,13 +28,43 @@
.or-journal-card .sub{font-size:.7rem}
.or-journal-card.hidden{display:none!important}
.or-reviewed-table tbody tr{cursor:pointer}
- .or-detail-panel{margin-top:10px;padding-top:10px;border-top:1px solid rgba(127,127,127,.25)}
- .or-detail-panel.hidden{display:none!important}
+ .or-detail-backdrop{
+ position:fixed;inset:0;z-index:1300;
+ display:flex;align-items:center;justify-content:center;
+ padding:16px;background:rgba(0,0,0,.72);
+ }
+ .or-detail-backdrop[hidden]{display:none!important}
+ .or-detail-modal{
+ width:min(96vw,920px);max-height:90vh;overflow:auto;
+ background:var(--card-bg,#121726);color:inherit;
+ border:1px solid rgba(127,127,127,.35);border-radius:10px;
+ padding:14px 16px 18px;box-shadow:0 12px 40px rgba(0,0,0,.45);
+ }
+ .or-detail-modal-head{display:flex;align-items:center;gap:8px;margin-bottom:10px}
+ .or-detail-modal-head h3{margin:0;margin-right:auto;font-size:.95rem}
.or-detail-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:6px 12px;font-size:.76rem;margin-bottom:8px}
- .or-detail-images{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px;margin:8px 0}
- .or-detail-img-cell{border:1px solid rgba(127,127,127,.25);border-radius:6px;padding:6px;text-align:center}
- .or-detail-img-label{display:block;font-size:.7rem;margin-bottom:4px;opacity:.8}
- .or-detail-img-thumb{max-width:100%;max-height:160px;border-radius:4px;cursor:pointer}
+ .or-detail-images{
+ display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
+ gap:10px;margin:10px 0 4px;
+ }
+ .or-detail-img-cell{
+ min-width:0;border:1px solid rgba(127,127,127,.25);border-radius:8px;
+ padding:8px;display:flex;flex-direction:column;gap:6px;
+ background:rgba(0,0,0,.18);
+ }
+ .or-detail-img-label{font-size:.72rem;opacity:.85;font-weight:600}
+ .or-detail-img-thumb{
+ width:100%;max-height:280px;object-fit:contain;
+ border-radius:6px;cursor:zoom-in;background:rgba(0,0,0,.25);
+ }
+ .or-detail-img-miss{
+ min-height:120px;display:flex;align-items:center;justify-content:center;
+ font-size:.72rem;opacity:.65;border-radius:6px;background:rgba(127,127,127,.12);
+ }
+ @media (max-width:640px){
+ .or-detail-images{grid-template-columns:1fr}
+ .or-detail-img-thumb{max-height:220px}
+ }
.or-pager{display:flex;align-items:center;gap:8px;margin-top:8px;font-size:.74rem}
.or-list-loading{opacity:.55;pointer-events:none;transition:opacity .12s ease}
.or-trades-table-wrap,.or-reviewed-table-wrap{min-height:9.5rem}
@@ -207,11 +237,15 @@
第 1 / 1 页
-
-
-
复盘详情
+
+
+ {# 复盘详情对话框(点复盘记录行打开) #}
+
+
+
+
复盘详情
-
+
@@ -227,4 +261,4 @@
-
+