/** * 四所实例共用 UI:复盘详情、盈亏着色等。 */ (function (global) { "use strict"; function escapeHtml(s) { return String(s == null ? "" : s) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function pnlClassFromValue(val) { const n = Number(String(val == null ? "" : val).replace(/[^\d.-]/g, "")); if (!Number.isFinite(n) || n === 0) return ""; return n > 0 ? "pnl-profit" : "pnl-loss"; } function formatPnlSpan(val, suffix) { const sfx = suffix == null ? "U" : suffix; const cls = pnlClassFromValue(val); const text = escapeHtml(val == null || val === "" ? "-" : val) + sfx; return cls ? `${text}` : text; } function buildJournalDetailHtml(o, formatExitLine) { const moodTags = Array.isArray(o.mood_issues) && o.mood_issues.length ? o.mood_issues.join(",") : o.mood_issues || "无"; const exitText = typeof formatExitLine === "function" ? formatExitLine(o) : o.exit_reason || "无"; const lines = [ `币种/周期:${escapeHtml(o.coin || "-")} ${escapeHtml(o.tf || "-")}`, `开仓时间:${escapeHtml(o.open_datetime || "-")}`, `平仓时间:${escapeHtml(o.close_datetime || "-")}`, `持仓时长:${escapeHtml(o.hold_duration || "-")}`, `盈亏:${formatPnlSpan(o.pnl)}`, `开仓类型:${escapeHtml(o.entry_reason || "无")}`, `平仓/离场:${escapeHtml(exitText)}`, `预期RR:${escapeHtml(o.expect_rr || "-")}`, `实际RR:${escapeHtml(o.real_rr || "-")}`, `保本后盯盘:${escapeHtml(o.post_breakeven_stare || "-")}`, `占用时新开仓:${escapeHtml(o.new_trade_while_occupied || "-")}`, `心态标签:${escapeHtml(moodTags)}`, `备注:${escapeHtml(o.note || "无")}`, ]; return lines.join("
"); } function setJournalDetailBody(o, formatExitLine) { const body = document.getElementById("detailBody"); if (!body) return; body.classList.remove("md-review"); body.classList.add("journal-detail-meta"); body.innerHTML = buildJournalDetailHtml(o, formatExitLine); } function openJournalDetailModal(id, journalCache, formatExitLine) { const o = journalCache && journalCache[id]; if (!o) return; const titleEl = document.getElementById("detailTitle"); if (titleEl) { titleEl.innerText = `交易复盘详情|${o.coin || "-"} ${o.tf || "-"}`; } setJournalDetailBody(o, formatExitLine); clearDetailActions(); const imgEl = document.getElementById("detailImage"); if (imgEl) { if (o.image) { imgEl.src = `/static/images/${o.image}`; imgEl.style.display = "block"; } else { imgEl.src = ""; imgEl.style.display = "none"; } } if (typeof setDetailModalFullscreen === "function") { setDetailModalFullscreen(false); } const modal = document.getElementById("detailModal"); if (modal) modal.style.display = "flex"; } function isMobileCompactRecords() { if (typeof window === "undefined" || !window.matchMedia) return false; return window.matchMedia("(max-width: 720px)").matches; } function inferJournalDirection(o) { const text = String((o && o.entry_reason) || ""); if (/做空|空头|short/i.test(text)) { return { text: "做空", cls: "direction-short" }; } if (/做多|多头|long/i.test(text)) { return { text: "做多", cls: "direction-long" }; } return null; } function renderJournalListHtml(data) { if (!data || !data.length) return ""; const mobile = isMobileCompactRecords(); return data .map(function (o) { if (mobile) { const dir = inferJournalDirection(o); const pnlCls = pnlClassFromValue(o.pnl); const dirHtml = dir ? `${escapeHtml(dir.text)}` : `-`; const id = escapeHtml(o.id); return `
`; } const moodTags = (o.mood_issues || []).join(",") || "无"; const id = escapeHtml(o.id); return `
${escapeHtml(o.coin || "-")} ${escapeHtml(o.tf || "-")} | 盈亏:${escapeHtml(o.pnl == null || o.pnl === "" ? "-" : o.pnl)}U
开:${escapeHtml(o.open_datetime || "-")} 平:${escapeHtml(o.close_datetime || "-")} 持仓:${escapeHtml(o.hold_duration || "-")}
心态标签:${escapeHtml(moodTags)}
`; }) .join(""); } function parseTradeRecordRow(tr) { const cells = tr.querySelectorAll("td"); if (cells.length < 14) return null; const dirBadge = cells[2].querySelector(".badge"); return { rowId: tr.id, symbol: cells[0].textContent.trim(), type: cells[1].textContent.trim(), directionHtml: dirBadge ? dirBadge.outerHTML : cells[2].innerHTML, directionText: cells[2].textContent.trim(), trigger: cells[3].textContent.trim(), stopLoss: cells[4].textContent.trim(), takeProfit: cells[5].textContent.trim(), margin: cells[6].textContent.trim(), leverage: cells[7].textContent.trim(), holdMinutes: cells[8].textContent.trim(), openedAt: cells[9].textContent.trim(), closedAt: cells[10].textContent.trim(), pnlHtml: cells[11].innerHTML, pnlText: cells[11].textContent.trim(), resultHtml: cells[12].innerHTML, resultText: cells[12].textContent.trim(), actionsHtml: cells[13].innerHTML, }; } function renderMobileTradeRow(tr) { const row = parseTradeRecordRow(tr); if (!row) return ""; const pnlCls = pnlClassFromValue(row.pnlText); return ``; } function buildTradeRecordDetailHtml(row) { const lines = [ `品种:${escapeHtml(row.symbol)}`, `类型:${escapeHtml(row.type)}`, `方向:${row.directionHtml}`, `成交价:${escapeHtml(row.trigger)}`, `止损(开仓):${escapeHtml(row.stopLoss)}`, `止盈:${escapeHtml(row.takeProfit)}`, `基数:${escapeHtml(row.margin)}`, `杠杆:${escapeHtml(row.leverage)}`, `持仓分钟:${escapeHtml(row.holdMinutes)}`, `开仓时间:${escapeHtml(row.openedAt)}`, `平仓时间:${escapeHtml(row.closedAt)}`, `盈亏U:${row.pnlHtml}`, `结果:${row.resultHtml}`, ]; return lines.join("
"); } function clearDetailActions() { const el = document.getElementById("detailActions"); if (el) { el.innerHTML = ""; el.style.display = "none"; } } function setDetailActionsHtml(html) { let el = document.getElementById("detailActions"); if (!el) { const panel = document.querySelector("#detailModal .panel"); if (!panel) return; el = document.createElement("div"); el.id = "detailActions"; el.className = "detail-actions"; const body = document.getElementById("detailBody"); if (body && body.parentNode === panel) { panel.insertBefore(el, body.nextSibling); } else { panel.appendChild(el); } } el.innerHTML = html || ""; el.style.display = html ? "flex" : "none"; } function openTradeRecordDetailModal(tr) { const row = parseTradeRecordRow(tr); if (!row) return; const titleEl = document.getElementById("detailTitle"); if (titleEl) { titleEl.innerText = `交易记录|${row.symbol}`; } const body = document.getElementById("detailBody"); if (body) { body.classList.remove("md-review", "journal-detail-meta"); body.innerHTML = buildTradeRecordDetailHtml(row); } setDetailActionsHtml( `
${row.actionsHtml}
` ); const imgEl = document.getElementById("detailImage"); if (imgEl) { imgEl.src = ""; imgEl.style.display = "none"; } if (typeof setDetailModalFullscreen === "function") { setDetailModalFullscreen(false); } const modal = document.getElementById("detailModal"); if (modal) modal.style.display = "flex"; } global.InstanceUI = { escapeHtml: escapeHtml, pnlClassFromValue: pnlClassFromValue, formatPnlSpan: formatPnlSpan, buildJournalDetailHtml: buildJournalDetailHtml, setJournalDetailBody: setJournalDetailBody, openJournalDetailModal: openJournalDetailModal, isMobileCompactRecords: isMobileCompactRecords, inferJournalDirection: inferJournalDirection, renderJournalListHtml: renderJournalListHtml, parseTradeRecordRow: parseTradeRecordRow, renderMobileTradeRow: renderMobileTradeRow, buildTradeRecordDetailHtml: buildTradeRecordDetailHtml, openTradeRecordDetailModal: openTradeRecordDetailModal, clearDetailActions: clearDetailActions, }; })(typeof window !== "undefined" ? window : globalThis);