From 426afb8dbe51d49086f86ada8569cd544feaa7f7 Mon Sep 17 00:00:00 2001 From: dekun Date: Sun, 19 Jul 2026 16:11:45 +0800 Subject: [PATCH] Make options review detail a modal and fix screenshot display. Co-authored-by: Cursor --- docs/更新文档.md | 20 +++ lib/common/static/options_review.js | 150 +++++++++++++----- lib/options/options_review_register.py | 2 +- .../templates/options_review_panel.html | 56 +++++-- 4 files changed, 173 insertions(+), 55 deletions(-) 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 + + "" + + '' +
+          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 + - "" + - '' +
-              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 页 -