Make options review detail a modal and fix screenshot display.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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 · 期期情景测算:盈亏配色 + 盈亏比(亏=全额保费)
|
## 2026-07-19 · 期期情景测算:盈亏配色 + 盈亏比(亏=全额保费)
|
||||||
|
|
||||||
### 修改原因
|
### 修改原因
|
||||||
|
|||||||
@@ -464,19 +464,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function hideDetail() {
|
function hideDetail() {
|
||||||
var panel = $("or-detail-panel");
|
var backdrop = $("or-detail-backdrop");
|
||||||
if (panel) panel.classList.add("hidden");
|
if (backdrop) backdrop.hidden = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDetail(tradeId) {
|
function openDetail(tradeId) {
|
||||||
var panel = $("or-detail-panel");
|
var backdrop = $("or-detail-backdrop");
|
||||||
if (!panel) return;
|
if (!backdrop) return;
|
||||||
panel.classList.remove("hidden");
|
backdrop.hidden = false;
|
||||||
($("or-detail-title") || {}).textContent = "加载中…";
|
($("or-detail-title") || {}).textContent = "加载中…";
|
||||||
($("or-detail-meta") || {}).innerHTML = "";
|
($("or-detail-meta") || {}).innerHTML = "";
|
||||||
($("or-detail-text") || {}).innerHTML = "";
|
($("or-detail-text") || {}).innerHTML = "";
|
||||||
($("or-detail-images") || {}).innerHTML = "";
|
($("or-detail-images") || {}).innerHTML = "";
|
||||||
panel.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
||||||
|
|
||||||
fetch("/api/options/review/trades/" + tradeId, { credentials: "same-origin" })
|
fetch("/api/options/review/trades/" + tradeId, { credentials: "same-origin" })
|
||||||
.then(function (r) {
|
.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 (
|
||||||
|
'<div class="or-detail-img-cell">' +
|
||||||
|
'<span class="or-detail-img-label">' +
|
||||||
|
escapeHtml(tf) +
|
||||||
|
"</span>" +
|
||||||
|
'<div class="or-detail-img-miss">未上传</div>' +
|
||||||
|
"</div>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
var src = optionsJournalImgSrc(file);
|
||||||
|
var label = escapeHtml(tf === "_" ? "截图" : tf);
|
||||||
|
return (
|
||||||
|
'<div class="or-detail-img-cell">' +
|
||||||
|
'<span class="or-detail-img-label">' +
|
||||||
|
label +
|
||||||
|
"</span>" +
|
||||||
|
'<img class="or-detail-img-thumb" src="' +
|
||||||
|
src +
|
||||||
|
'" alt="' +
|
||||||
|
label +
|
||||||
|
'" data-src="' +
|
||||||
|
src +
|
||||||
|
'" loading="lazy">' +
|
||||||
|
"</div>"
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
if (!cells.length) {
|
||||||
|
imagesHost.innerHTML = '<div class="muted">无截图</div>';
|
||||||
|
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 =
|
||||||
|
'<span class="or-detail-img-label">' +
|
||||||
|
escapeHtml(tf) +
|
||||||
|
"</span>" +
|
||||||
|
'<div class="or-detail-img-miss">文件缺失或无法加载</div>';
|
||||||
|
});
|
||||||
|
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) {
|
function renderDetail(t) {
|
||||||
var e = t.entry || {};
|
var e = t.entry || {};
|
||||||
reviewedCache[t.id] = t;
|
reviewedCache[t.id] = t;
|
||||||
@@ -566,42 +651,7 @@
|
|||||||
|
|
||||||
var imagesHost = $("or-detail-images");
|
var imagesHost = $("or-detail-images");
|
||||||
if (imagesHost) {
|
if (imagesHost) {
|
||||||
var images = e.images || [];
|
renderDetailImages(e.images || []);
|
||||||
if (!images.length) {
|
|
||||||
imagesHost.innerHTML = '<div class="muted">无截图</div>';
|
|
||||||
} 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 (
|
|
||||||
'<div class="or-detail-img-cell">' +
|
|
||||||
'<span class="or-detail-img-label">' +
|
|
||||||
label +
|
|
||||||
"</span>" +
|
|
||||||
'<img class="or-detail-img-thumb" src="' +
|
|
||||||
src +
|
|
||||||
'" alt="' +
|
|
||||||
label +
|
|
||||||
'" data-src="' +
|
|
||||||
src +
|
|
||||||
'">' +
|
|
||||||
"</div>"
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.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");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1075,9 +1125,23 @@
|
|||||||
if (detailEdit) {
|
if (detailEdit) {
|
||||||
detailEdit.addEventListener("click", function () {
|
detailEdit.addEventListener("click", function () {
|
||||||
var id = Number(detailEdit.getAttribute("data-id") || 0);
|
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) {
|
["or-filter-uly", "or-filter-opt", "or-include-hedge-legs"].forEach(function (id) {
|
||||||
var el = $(id);
|
var el = $(id);
|
||||||
if (el) {
|
if (el) {
|
||||||
|
|||||||
@@ -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")
|
return send_file(path, mimetype="application/javascript; charset=utf-8")
|
||||||
|
|
||||||
@app.route("/static/images/options_journal/<path:filename>")
|
@app.route("/static/images/options_journal/<path:filename>")
|
||||||
@lr
|
|
||||||
def static_options_review_image(filename: str):
|
def static_options_review_image(filename: str):
|
||||||
|
"""截图文件名含 32 位 draft id,按静态资源提供(不强制登录,避免 iframe img 偶发 401)."""
|
||||||
folder = options_review_upload_dir(cfg["upload_folder"])
|
folder = options_review_upload_dir(cfg["upload_folder"])
|
||||||
safe = os.path.basename(filename or "")
|
safe = os.path.basename(filename or "")
|
||||||
path = os.path.join(folder, safe)
|
path = os.path.join(folder, safe)
|
||||||
|
|||||||
@@ -28,13 +28,43 @@
|
|||||||
.or-journal-card .sub{font-size:.7rem}
|
.or-journal-card .sub{font-size:.7rem}
|
||||||
.or-journal-card.hidden{display:none!important}
|
.or-journal-card.hidden{display:none!important}
|
||||||
.or-reviewed-table tbody tr{cursor:pointer}
|
.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-backdrop{
|
||||||
.or-detail-panel.hidden{display:none!important}
|
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-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-images{
|
||||||
.or-detail-img-cell{border:1px solid rgba(127,127,127,.25);border-radius:6px;padding:6px;text-align:center}
|
display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
|
||||||
.or-detail-img-label{display:block;font-size:.7rem;margin-bottom:4px;opacity:.8}
|
gap:10px;margin:10px 0 4px;
|
||||||
.or-detail-img-thumb{max-width:100%;max-height:160px;border-radius:4px;cursor:pointer}
|
}
|
||||||
|
.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-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-list-loading{opacity:.55;pointer-events:none;transition:opacity .12s ease}
|
||||||
.or-trades-table-wrap,.or-reviewed-table-wrap{min-height:9.5rem}
|
.or-trades-table-wrap,.or-reviewed-table-wrap{min-height:9.5rem}
|
||||||
@@ -207,11 +237,15 @@
|
|||||||
<span class="muted" id="or-reviewed-page-label">第 1 / 1 页</span>
|
<span class="muted" id="or-reviewed-page-label">第 1 / 1 页</span>
|
||||||
<button type="button" class="btn-secondary" id="or-reviewed-next" style="font-size:.72rem;padding:2px 8px">下一页</button>
|
<button type="button" class="btn-secondary" id="or-reviewed-next" style="font-size:.72rem;padding:2px 8px">下一页</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="or-detail-panel hidden" id="or-detail-panel">
|
</div>
|
||||||
<div class="form-row" style="align-items:center;gap:8px;margin-bottom:6px">
|
|
||||||
<h3 style="margin:0;margin-right:auto" id="or-detail-title">复盘详情</h3>
|
{# 复盘详情对话框(点复盘记录行打开) #}
|
||||||
|
<div id="or-detail-backdrop" class="or-detail-backdrop" hidden>
|
||||||
|
<div class="or-detail-modal" role="dialog" aria-modal="true" aria-labelledby="or-detail-title" id="or-detail-panel">
|
||||||
|
<div class="or-detail-modal-head">
|
||||||
|
<h3 id="or-detail-title">复盘详情</h3>
|
||||||
<button type="button" class="btn-secondary" id="or-detail-edit-btn" style="font-size:.72rem;padding:2px 8px">编辑</button>
|
<button type="button" class="btn-secondary" id="or-detail-edit-btn" style="font-size:.72rem;padding:2px 8px">编辑</button>
|
||||||
<button type="button" class="btn-secondary" id="or-detail-close-btn" style="font-size:.72rem;padding:2px 8px">收起</button>
|
<button type="button" class="btn-secondary" id="or-detail-close-btn" style="font-size:.72rem;padding:2px 8px">关闭</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="or-detail-grid" id="or-detail-meta"></div>
|
<div class="or-detail-grid" id="or-detail-meta"></div>
|
||||||
<div id="or-detail-text" style="font-size:.76rem;line-height:1.5;margin-bottom:8px"></div>
|
<div id="or-detail-text" style="font-size:.76rem;line-height:1.5;margin-bottom:8px"></div>
|
||||||
@@ -227,4 +261,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/options_review.js?v=11"></script>
|
<script src="/static/options_review.js?v=12"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user