/** * OKX 期权复盘:待复盘交易(5行) → 点复盘出表单 → 复盘记录详情 → 统计. */ (function (global) { "use strict"; var PAGE_SIZE = 5; var TAB_LABELS = { option_spot: "期权交易记录", options_options: "期期对冲记录", perp_options: "永期对冲记录", }; var FORM_PRESETS = { option_spot: { strategy: ["顺势", "反转"], direction: ["多", "空"], entry: ["假突破", "结构突破"], }, hedge: { strategy: ["横盘", "趋势"], direction: ["多", "空"], entry: ["横盘博弈方向", "趋势对冲止损"], }, }; var RESULT_OPTIONS = ["盈利", "亏损", "持平"]; var activeSource = "option_spot"; var currentTradeId = null; var draftId = ""; var tradesCache = {}; var reviewedCache = {}; var tradesPage = 0; var tradesPages = 1; var reviewedPage = 0; var reviewedPages = 1; function $(id) { return document.getElementById(id); } function escapeHtml(s) { return String(s == null ? "" : s) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function fmtPnl(v) { if (v == null || v === "") return "—"; var n = Number(v); if (Number.isNaN(n)) return "—"; return (n >= 0 ? "+" : "") + n.toFixed(2); } function fmtHold(sec) { if (sec == null) return "—"; var s = Math.max(0, Number(sec) || 0); if (s < 3600) return Math.round(s / 60) + "m"; if (s < 86400) return (s / 3600).toFixed(1) + "h"; return (s / 86400).toFixed(1) + "d"; } function toLocalInput(ts) { if (!ts) return ""; var s = String(ts).trim().replace(" ", "T"); if (s.length >= 16) return s.slice(0, 16); return s; } function tradeTitle(t) { if (!t) return "—"; if (t.source_type === "option_spot") return t.inst_id || "—"; return ( (t.underlying || "") + (t.direction ? " " + t.direction : "") + (t.plan_close_reason ? " · " + t.plan_close_reason : "") ); } function pnlStyle(v) { var n = Number(v); if (n > 0) return "color:#3dd68c"; if (n < 0) return "color:#f07178"; return ""; } function newDraftId() { if (global.crypto && typeof global.crypto.randomUUID === "function") { return global.crypto.randomUUID().replace(/-/g, ""); } var s = ""; for (var i = 0; i < 32; i++) s += Math.floor(Math.random() * 16).toString(16); return s; } function baseQs() { var p = new URLSearchParams(); p.set("source_type", activeSource); var uly = ($("or-filter-uly") || {}).value || ""; var opt = ($("or-filter-opt") || {}).value || ""; var strategy = (($("or-filter-strategy") || {}).value || "").trim(); var from = ($("or-filter-from") || {}).value || ""; var to = ($("or-filter-to") || {}).value || ""; if (uly) p.set("underlying", uly); if (opt) p.set("opt_type", opt); if (strategy) p.set("strategy_tag", strategy); if (from) p.set("closed_from", from.replace("T", " ") + ":00"); if (to) p.set("closed_to", to.replace("T", " ") + ":00"); if (($("or-include-hedge-legs") || {}).checked) p.set("include_hedge_legs", "1"); return p; } function setSyncStatus(text) { var el = $("or-sync-status"); if (el) el.textContent = text || ""; } function reloadAll() { setSyncStatus("读取本地记录…"); loadTrades({ sync: true }); loadReviewed({ sync: false }); loadStats(); } function isHedgeSource(sourceType) { return sourceType === "options_options" || sourceType === "perp_options"; } function fillSelect(el, options, placeholder) { if (!el) return; var keep = el.value; el.innerHTML = ""; var first = document.createElement("option"); first.value = ""; first.textContent = placeholder || ""; el.appendChild(first); (options || []).forEach(function (v) { var opt = document.createElement("option"); opt.value = v; opt.textContent = v; el.appendChild(opt); }); if (keep) setSelectValue(el, keep); } function setSelectValue(el, value) { if (!el) return; var v = value == null ? "" : String(value); if (!v) { el.value = ""; return; } var found = false; for (var i = 0; i < el.options.length; i++) { if (el.options[i].value === v) { found = true; break; } } if (!found) { var opt = document.createElement("option"); opt.value = v; opt.textContent = v; el.appendChild(opt); } el.value = v; } function applyFormPresets(sourceType) { var preset = isHedgeSource(sourceType) ? FORM_PRESETS.hedge : FORM_PRESETS.option_spot; fillSelect($("or-f-strategy"), preset.strategy, "策略标签"); fillSelect($("or-f-direction"), preset.direction, "方向判断"); fillSelect($("or-f-entry"), preset.entry, "入场逻辑"); fillSelect($("or-f-result"), RESULT_OPTIONS, "结果标签"); } function autoDirection(t) { if (!t) return ""; if (isHedgeSource(t.source_type)) { var d = String(t.direction || "").trim().toLowerCase(); if (d === "long" || d === "buy" || d === "多") return "多"; if (d === "short" || d === "sell" || d === "空") return "空"; return ""; } var ot = String(t.opt_type || "").trim().toUpperCase(); if (ot === "C" || ot === "CALL") return "多"; if (ot === "P" || ot === "PUT") return "空"; return ""; } function autoResultTag(pnl) { if (pnl == null || pnl === "") return ""; var n = Number(pnl); if (Number.isNaN(n)) return ""; if (n > 0) return "盈利"; if (n < 0) return "亏损"; return "持平"; } 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); }); var title = $("or-list-title"); if (title) title.textContent = TAB_LABELS[activeSource] || "记录"; applyFormPresets(activeSource); hideJournalForm(); hideDetail(); reloadAll(); } function updateTradesPager() { var label = $("or-trades-page-label"); var prev = $("or-trades-prev"); var next = $("or-trades-next"); if (label) { label.textContent = "第 " + (tradesPage + 1) + " / " + tradesPages + " 页"; } if (prev) prev.disabled = tradesPage <= 0; if (next) next.disabled = tradesPage + 1 >= tradesPages; } 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 beginListLoad(wrapId, soft) { var wrap = $(wrapId); if (!wrap) return null; if (soft) { if (!wrap.style.minHeight) { wrap.style.minHeight = Math.max(wrap.offsetHeight, 1) + "px"; } wrap.classList.add("or-list-loading"); } else { wrap.classList.remove("or-list-loading"); wrap.style.minHeight = ""; } return wrap; } function endListLoad(wrap) { if (!wrap) return; wrap.classList.remove("or-list-loading"); wrap.style.minHeight = ""; } function loadTrades(opts) { opts = opts || {}; var doSync = opts.sync !== false; var soft = !!opts.soft; var tbody = $("or-trades-tbody"); if (!tbody) return; var wrap = beginListLoad("or-trades-wrap", soft); if (!soft) { tbody.innerHTML = '加载中…'; } var p = baseQs(); 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) { if (doSync) setSyncStatus("本地记录已加载"); if (!data.ok) { tbody.innerHTML = '加载失败'; endListLoad(wrap); return; } if (applyPagerMeta(data, "trades") && Number(data.total || 0) > 0) { loadTrades(opts); return; } var rows = data.trades || []; tradesCache = {}; if (!rows.length) { tbody.innerHTML = '暂无待复盘记录'; endListLoad(wrap); return; } tbody.innerHTML = rows .map(function (t) { tradesCache[t.id] = t; var active = currentTradeId === t.id ? " or-row-active" : ""; return ( '' + "" + escapeHtml(t.source_label || t.source_type) + "" + "" + escapeHtml(tradeTitle(t)) + "" + '' + fmtPnl(t.realized_pnl_total) + "" + '' + escapeHtml(t.opened_at || "—") + "
" + escapeHtml(t.closed_at || "—") + "" + "" + fmtHold(t.hold_seconds) + "" + ' ' + '' + "" ); }) .join(""); tbody.querySelectorAll(".or-review-btn").forEach(function (btn) { btn.addEventListener("click", function (ev) { ev.preventDefault(); ev.stopPropagation(); openJournalForm(Number(btn.getAttribute("data-id"))); }); }); tbody.querySelectorAll(".or-hide-btn").forEach(function (btn) { btn.addEventListener("click", function (ev) { ev.preventDefault(); ev.stopPropagation(); hideTrade(Number(btn.getAttribute("data-id"))); }); }); endListLoad(wrap); }) .catch(function () { tbody.innerHTML = '加载失败'; endListLoad(wrap); }); } function loadReviewed(opts) { opts = opts || {}; var doSync = opts.sync === true; var soft = !!opts.soft; var tbody = $("or-reviewed-tbody"); if (!tbody) return; var wrap = beginListLoad("or-reviewed-wrap", soft); if (!soft) { tbody.innerHTML = '加载中…'; } var p = baseQs(); p.set("reviewed", "1"); 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(); }) .then(function (data) { if (!data.ok) { tbody.innerHTML = '加载失败'; endListLoad(wrap); return; } if (applyPagerMeta(data, "reviewed") && Number(data.total || 0) > 0) { loadReviewed(opts); return; } var rows = data.trades || []; reviewedCache = {}; if (!rows.length) { tbody.innerHTML = '暂无复盘记录'; endListLoad(wrap); return; } tbody.innerHTML = rows .map(function (t) { reviewedCache[t.id] = t; return ( '' + "" + escapeHtml(t.source_label || t.source_type) + "" + "" + escapeHtml(tradeTitle(t)) + "" + '' + fmtPnl(t.realized_pnl_total) + "" + "" + escapeHtml(t.strategy_tag || "—") + "" + "" + escapeHtml(t.result_tag || "—") + "" + '' + escapeHtml(t.reviewed_at || "—") + "" + "" ); }) .join(""); tbody.querySelectorAll(".or-reviewed-row").forEach(function (tr) { tr.addEventListener("click", function () { openDetail(Number(tr.getAttribute("data-id"))); }); }); endListLoad(wrap); }) .catch(function () { tbody.innerHTML = '加载失败'; endListLoad(wrap); }); } function hideDetail() { var panel = $("or-detail-panel"); if (panel) panel.classList.add("hidden"); } function openDetail(tradeId) { var panel = $("or-detail-panel"); if (!panel) return; panel.classList.remove("hidden"); ($("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) { return r.json(); }) .then(function (data) { if (!data.ok || !data.trade) { ($("or-detail-title") || {}).textContent = "加载失败"; return; } renderDetail(data.trade); }) .catch(function () { ($("or-detail-title") || {}).textContent = "加载失败"; }); } function renderDetail(t) { var e = t.entry || {}; reviewedCache[t.id] = t; ($("or-detail-title") || {}).textContent = "复盘详情 · " + (t.source_label || "") + " · " + tradeTitle(t); var editBtn = $("or-detail-edit-btn"); if (editBtn) editBtn.setAttribute("data-id", String(t.id)); var meta = $("or-detail-meta"); if (meta) { var cells = [ ["标的", t.underlying || "—"], ["合约/计划", tradeTitle(t)], ["盈亏", fmtPnl(t.realized_pnl_total)], ["持有", fmtHold(t.hold_seconds)], ["开仓", t.opened_at || "—"], ["平仓", t.closed_at || "—"], ["策略", e.strategy_tag || "—"], ["方向", e.direction_view || "—"], ["结果", e.result_tag || "—"], ["离场", e.exit_reason || "—"], ["按计划", e.followed_plan || "—"], ["入场逻辑", e.entry_logic || "—"], ]; if (t.is_hedge) { cells.push(["永续盈亏", fmtPnl(t.realized_pnl_perp)]); cells.push(["期权盈亏", fmtPnl(t.realized_pnl_options)]); } meta.innerHTML = cells .map(function (pair) { return ( "
" + escapeHtml(pair[0]) + "
" + escapeHtml(pair[1]) + "
" ); }) .join(""); } var text = $("or-detail-text"); if (text) { var lines = []; if (e.mistake_tags) lines.push("
心理标签:" + escapeHtml(e.mistake_tags) + "
"); if (e.note) lines.push("
备注:" + escapeHtml(e.note).replace(/\n/g, "
") + "
"); if (t.legs && t.legs.length) { lines.push( "
计划腿
" + t.legs .map(function (leg) { return ( "" ); }) .join("") + "
合约盈亏原因
" + escapeHtml(leg.leg_role || "") + "" + escapeHtml(leg.inst_id || leg.symbol || "") + "" + fmtPnl(leg.realized_pnl) + "" + escapeHtml(leg.close_reason || "") + "
" ); } text.innerHTML = lines.join("") || '
无额外备注
'; } 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"); } }); }); } } } function renderGroup(title, items) { if (!items || !items.length) { return ( '
' + title + '
无数据
' ); } var lines = items .slice(0, 8) .map(function (g) { return ( '
' + "" + escapeHtml(g.key) + " · " + g.count + "笔" + "" + fmtPnl(g.pnl_sum) + " / 胜" + (g.win_rate || 0) + "%" + "
" ); }) .join(""); return ( '
' + title + "
" + lines + "
" ); } function loadStats() { var kpi = $("or-kpi"); var groups = $("or-stats-groups"); if (!kpi || !groups) return; fetch("/api/options/review/stats?" + baseQs().toString(), { credentials: "same-origin" }) .then(function (r) { return r.json(); }) .then(function (data) { if (!data.ok) return; var k = data.kpi || {}; kpi.innerHTML = [ ["笔数", k.total], ["已复盘率", (k.review_rate || 0) + "%"], ["胜率", (k.win_rate || 0) + "%"], ["累计盈亏", fmtPnl(k.pnl_sum)], ["平均盈亏", fmtPnl(k.avg_pnl)], ["平均持有", fmtHold(k.avg_hold_sec)], ] .map(function (pair) { return ( '
' + pair[0] + '
' + pair[1] + "
" ); }) .join(""); groups.innerHTML = [ renderGroup("按类型", data.by_source_type), renderGroup("按标的", data.by_underlying), renderGroup("按策略", data.by_strategy), renderGroup("对冲结束原因", data.by_close_reason), renderGroup("持有周期", data.by_hold_bucket), renderGroup("Call/Put", data.by_opt_type), ].join(""); }) .catch(function () {}); } function resetUploadSlots() { draftId = newDraftId(); var draftEl = $("or-draft-id"); if (draftEl) draftEl.value = draftId; document.querySelectorAll("#or-upload-slots .or-upload-hidden").forEach(function (el) { el.value = ""; }); document.querySelectorAll("#or-upload-slots .or-upload-input").forEach(function (el) { el.value = ""; }); document.querySelectorAll("#or-upload-slots .or-upload-status").forEach(function (el) { el.textContent = ""; }); } function bindUploadSlots() { document.querySelectorAll("#or-upload-slots .or-upload-input").forEach(function (input) { if (input.dataset.orBound === "1") return; input.dataset.orBound = "1"; input.addEventListener("change", function () { var file = input.files && input.files[0]; var row = input.closest(".journal-upload-row"); var status = row && row.querySelector(".or-upload-status"); var hidden = row && row.querySelector(".or-upload-hidden"); if (!file) { if (hidden) hidden.value = ""; if (status) { status.textContent = ""; status.className = "journal-upload-status or-upload-status"; } return; } if (!draftId) draftId = newDraftId(); if (status) { status.textContent = "上传中…"; status.className = "journal-upload-status or-upload-status journal-upload-status--pending"; } var fd = new FormData(); fd.append("draft_id", draftId); fd.append("tf", input.getAttribute("data-tf") || ""); fd.append("file", file); fetch("/api/options/review/upload_slot", { method: "POST", body: fd, credentials: "same-origin", }) .then(function (r) { return r.json(); }) .then(function (data) { if (!data.ok) throw new Error(data.error || "fail"); if (hidden) hidden.value = data.file; if (status) { status.textContent = "上传成功 " + data.file; status.className = "journal-upload-status or-upload-status journal-upload-status--ok"; } input.value = ""; }) .catch(function () { if (hidden) hidden.value = ""; if (status) { status.textContent = "上传失败"; status.className = "journal-upload-status or-upload-status journal-upload-status--err"; } }); }); }); } function setMoodTags(raw) { var set = {}; String(raw || "") .split(/[,,]/) .map(function (x) { return x.trim(); }) .filter(Boolean) .forEach(function (x) { set[x] = true; }); document.querySelectorAll(".or-mood").forEach(function (cb) { cb.checked = !!set[cb.value]; }); } function collectMoodTags() { var out = []; document.querySelectorAll(".or-mood:checked").forEach(function (cb) { out.push(cb.value); }); return out.join(","); } function collectImages() { var out = []; document.querySelectorAll("#or-upload-slots .or-upload-hidden").forEach(function (el) { var file = (el.value || "").trim(); if (file) out.push({ tf: el.getAttribute("data-tf") || "", file: file }); }); return out; } function hideJournalForm() { currentTradeId = null; var card = $("or-journal-card"); if (card) card.classList.add("hidden"); document.querySelectorAll(".or-trade-row").forEach(function (tr) { tr.classList.remove("or-row-active"); }); ($("or-trade-id") || {}).value = ""; ($("or-f-open") || {}).value = ""; ($("or-f-close") || {}).value = ""; ($("or-f-coin") || {}).value = ""; ($("or-f-inst") || {}).value = ""; ($("or-f-pnl") || {}).value = ""; ($("or-f-hold") || {}).value = ""; ($("or-f-strategy") || {}).value = ""; ($("or-f-direction") || {}).value = ""; ($("or-f-exit") || {}).value = ""; ($("or-f-followed") || {}).value = ""; ($("or-f-result") || {}).value = ""; ($("or-f-entry") || {}).value = ""; ($("or-f-note") || {}).value = ""; setMoodTags(""); resetUploadSlots(); var summary = $("or-journal-summary"); if (summary) { summary.textContent = "截图槽位与合约复盘相同(5m / 15m / 1h / 4h)."; } var legsHost = $("or-legs-host"); if (legsHost) legsHost.innerHTML = ""; ($("or-save-status") || {}).textContent = ""; } function openJournalForm(tradeId) { currentTradeId = tradeId; var card = $("or-journal-card"); if (!card) return; card.classList.remove("hidden"); card.scrollIntoView({ behavior: "smooth", block: "start" }); document.querySelectorAll(".or-trade-row").forEach(function (tr) { tr.classList.toggle("or-row-active", Number(tr.getAttribute("data-id")) === tradeId); }); resetUploadSlots(); bindUploadSlots(); ($("or-save-status") || {}).textContent = "加载中…"; fetch("/api/options/review/trades/" + tradeId, { credentials: "same-origin" }) .then(function (r) { return r.json(); }) .then(function (data) { if (!data.ok || !data.trade) { ($("or-save-status") || {}).textContent = "加载失败"; return; } fillForm(data.trade); ($("or-save-status") || {}).textContent = "已选中 #" + tradeId; }) .catch(function () { ($("or-save-status") || {}).textContent = "加载失败"; }); } function fillForm(t) { var e = t.entry || {}; applyFormPresets(t.source_type || activeSource); ($("or-trade-id") || {}).value = String(t.id || ""); ($("or-f-open") || {}).value = toLocalInput(t.opened_at); ($("or-f-close") || {}).value = toLocalInput(t.closed_at); ($("or-f-coin") || {}).value = t.underlying || ""; ($("or-f-inst") || {}).value = t.source_type === "option_spot" ? t.inst_id || "" : (t.source_label || "") + (t.plan_close_reason ? " · " + t.plan_close_reason : ""); ($("or-f-pnl") || {}).value = fmtPnl(t.realized_pnl_total); ($("or-f-hold") || {}).value = fmtHold(t.hold_seconds); setSelectValue($("or-f-strategy"), e.strategy_tag || ""); setSelectValue($("or-f-direction"), e.direction_view || autoDirection(t)); ($("or-f-exit") || {}).value = e.exit_reason || t.plan_close_reason || ""; ($("or-f-followed") || {}).value = e.followed_plan || ""; setSelectValue($("or-f-result"), e.result_tag || autoResultTag(t.realized_pnl_total)); setSelectValue($("or-f-entry"), e.entry_logic || ""); ($("or-f-note") || {}).value = e.note || ""; setMoodTags(e.mistake_tags); var summary = $("or-journal-summary"); if (summary) { summary.textContent = (t.source_label || "") + " · " + (t.inst_id || t.underlying || "#" + t.id) + " · 盈亏 " + fmtPnl(t.realized_pnl_total) + (t.is_hedge ? " (永续 " + fmtPnl(t.realized_pnl_perp) + " / 期权 " + fmtPnl(t.realized_pnl_options) + ")" : ""); } (e.images || []).forEach(function (img) { var hidden = document.querySelector( '#or-upload-slots .or-upload-hidden[data-tf="' + img.tf + '"]' ); var status = document.querySelector( '#or-upload-slots .or-upload-status[data-tf="' + img.tf + '"]' ); if (hidden && img.file) { hidden.value = img.file; if (status) status.textContent = "已有 " + img.file; } }); var legsHost = $("or-legs-host"); if (legsHost) { if (t.legs && t.legs.length) { legsHost.innerHTML = "

计划腿

" + t.legs .map(function (leg) { return ( "" ); }) .join("") + "
合约盈亏原因
" + escapeHtml(leg.leg_role || "") + "" + escapeHtml(leg.inst_id || leg.symbol || "") + "" + fmtPnl(leg.realized_pnl) + "" + escapeHtml(leg.close_reason || "") + "
"; } else { legsHost.innerHTML = ""; } } } function hideTrade(tradeId) { if (!tradeId) return; if (!confirm("从待复盘列表删除并隐藏?刷新后也不会再出现.")) return; fetch("/api/options/review/trades/" + tradeId, { method: "DELETE", credentials: "same-origin", }) .then(function (r) { return r.json(); }) .then(function (data) { if (!data.ok) { alert(data.msg || "删除失败"); return; } if (currentTradeId === tradeId) hideJournalForm(); reloadAll(); }) .catch(function () { alert("删除失败"); }); } function saveEntry() { var tradeId = Number(($("or-trade-id") || {}).value || 0); if (!tradeId) { alert("请先点击交易记录中的「复盘」"); return; } var strategy = (($("or-f-strategy") || {}).value || "").trim(); if (!strategy) { alert("请选择策略标签"); return; } var payload = { trade_id: tradeId, strategy_tag: strategy, direction_view: ($("or-f-direction") || {}).value || "", exit_reason: ($("or-f-exit") || {}).value || "", followed_plan: ($("or-f-followed") || {}).value || "", result_tag: ($("or-f-result") || {}).value || "", mistake_tags: collectMoodTags(), entry_logic: ($("or-f-entry") || {}).value || "", note: ($("or-f-note") || {}).value || "", images: collectImages(), }; ($("or-save-status") || {}).textContent = "保存中…"; fetch("/api/options/review/entry", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }) .then(function (r) { return r.json(); }) .then(function (data) { if (!data.ok) { ($("or-save-status") || {}).textContent = data.msg || "保存失败"; return; } ($("or-save-status") || {}).textContent = "已保存"; hideJournalForm(); reloadAll(); openDetail(tradeId); }) .catch(function () { ($("or-save-status") || {}).textContent = "保存失败"; }); } function deleteEntry() { var tradeId = Number(($("or-trade-id") || {}).value || 0); if (!tradeId) return; if (!confirm("删除该条复盘内容与图片?交易记录会回到待复盘列表.")) return; fetch("/api/options/review/entry/" + tradeId, { method: "DELETE", credentials: "same-origin", }) .then(function (r) { return r.json(); }) .then(function () { hideJournalForm(); hideDetail(); reloadAll(); }); } function init() { if (!$("options-review-root")) return; document.querySelectorAll(".or-tab").forEach(function (btn) { btn.addEventListener("click", function () { setActiveTab(btn.getAttribute("data-source")); }); }); var reloadBtn = $("or-reload-btn"); var saveBtn = $("or-save-btn"); var clearBtn = $("or-clear-btn"); 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); if (saveBtn) saveBtn.addEventListener("click", saveEntry); if (clearBtn) clearBtn.addEventListener("click", hideJournalForm); if (delBtn) delBtn.addEventListener("click", deleteEntry); if (prevBtn) { prevBtn.addEventListener("click", function (ev) { ev.preventDefault(); ev.stopPropagation(); if (tradesPage <= 0) return; tradesPage -= 1; updateTradesPager(); loadTrades({ sync: false, soft: true }); }); } if (nextBtn) { nextBtn.addEventListener("click", function (ev) { ev.preventDefault(); ev.stopPropagation(); if (tradesPage + 1 >= tradesPages) return; tradesPage += 1; updateTradesPager(); loadTrades({ sync: false, soft: true }); }); } if (reviewedPrev) { reviewedPrev.addEventListener("click", function (ev) { ev.preventDefault(); ev.stopPropagation(); if (reviewedPage <= 0) return; reviewedPage -= 1; updateReviewedPager(); loadReviewed({ sync: false, soft: true }); }); } if (reviewedNext) { reviewedNext.addEventListener("click", function (ev) { ev.preventDefault(); ev.stopPropagation(); if (reviewedPage + 1 >= reviewedPages) return; reviewedPage += 1; updateReviewedPager(); loadReviewed({ sync: false, soft: true }); }); } if (detailClose) detailClose.addEventListener("click", hideDetail); if (detailEdit) { detailEdit.addEventListener("click", function () { var id = Number(detailEdit.getAttribute("data-id") || 0); if (id) openJournalForm(id); }); } ["or-filter-uly", "or-filter-opt", "or-include-hedge-legs"].forEach(function (id) { var el = $(id); if (el) { el.addEventListener("change", function () { tradesPage = 0; reviewedPage = 0; reloadAll(); }); } }); ["or-filter-strategy", "or-filter-from", "or-filter-to"].forEach(function (id) { var el = $(id); if (el) { el.addEventListener("change", function () { tradesPage = 0; reviewedPage = 0; reloadAll(); }); } }); bindUploadSlots(); hideJournalForm(); hideDetail(); setActiveTab("option_spot"); } global.OptionsReview = { init: init, openJournalForm: openJournalForm, hideJournalForm: hideJournalForm, }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })(typeof window !== "undefined" ? window : globalThis);