Add mobile compact trade and journal lists with tap-to-expand detail.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 手机端:交易记录 / 复盘记录紧凑列表(币种 · 方向 · 盈亏),点击展开详情。
|
||||
*/
|
||||
(function (global) {
|
||||
"use strict";
|
||||
|
||||
var resizeTimer = null;
|
||||
|
||||
function refreshTradeRecords() {
|
||||
var UI = global.InstanceUI;
|
||||
if (!UI) return;
|
||||
var card = document.querySelector(".records-card");
|
||||
if (!card) return;
|
||||
var tableWrap = card.querySelector(".table-wrap");
|
||||
var table = tableWrap && tableWrap.querySelector("table");
|
||||
if (!table) return;
|
||||
|
||||
var listEl = card.querySelector(".mobile-record-list");
|
||||
var mobile = UI.isMobileCompactRecords();
|
||||
|
||||
if (!mobile) {
|
||||
if (listEl) listEl.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!listEl) {
|
||||
listEl = document.createElement("div");
|
||||
listEl.className = "mobile-record-list";
|
||||
tableWrap.parentNode.insertBefore(listEl, tableWrap);
|
||||
}
|
||||
|
||||
var rows = table.querySelectorAll('tr[id^="trade-row-"]');
|
||||
listEl.innerHTML = Array.prototype.map
|
||||
.call(rows, function (tr) {
|
||||
return UI.renderMobileTradeRow(tr);
|
||||
})
|
||||
.join("");
|
||||
|
||||
listEl.querySelectorAll(".mobile-record-row").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
var rowId = btn.getAttribute("data-row-id");
|
||||
var tr = rowId && document.getElementById(rowId);
|
||||
if (tr) UI.openTradeRecordDetailModal(tr);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function onResize() {
|
||||
if (resizeTimer) clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(function () {
|
||||
refreshTradeRecords();
|
||||
if (typeof global.loadJournals === "function" && document.getElementById("journal-list")) {
|
||||
global.loadJournals();
|
||||
}
|
||||
}, 180);
|
||||
}
|
||||
|
||||
function init() {
|
||||
refreshTradeRecords();
|
||||
global.addEventListener("resize", onResize);
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
|
||||
global.InstanceRecordsMobile = {
|
||||
refresh: refreshTradeRecords,
|
||||
};
|
||||
})(typeof window !== "undefined" ? window : globalThis);
|
||||
@@ -98,6 +98,120 @@
|
||||
.grid {
|
||||
grid-template-columns: minmax(0, 1fr) !important;
|
||||
}
|
||||
|
||||
.records-card .table-wrap {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-record-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.mobile-record-row-wrap {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.mobile-record-row {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.2fr) auto minmax(0, 0.9fr);
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid rgba(120, 140, 200, 0.28);
|
||||
border-radius: 8px;
|
||||
background: rgba(18, 24, 42, 0.65);
|
||||
color: #e8ecff;
|
||||
font-size: 0.82rem;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.mobile-record-row:active {
|
||||
background: rgba(30, 42, 72, 0.85);
|
||||
}
|
||||
|
||||
.mrr-symbol {
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mrr-dir {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.mrr-dir .badge {
|
||||
font-size: 0.72rem;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
.mrr-pnl {
|
||||
justify-self: end;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mrr-muted {
|
||||
color: #8892b0;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.mobile-record-del {
|
||||
flex: 0 0 36px;
|
||||
width: 36px;
|
||||
border: 1px solid rgba(200, 80, 80, 0.35);
|
||||
border-radius: 8px;
|
||||
background: rgba(80, 24, 24, 0.35);
|
||||
color: #ff9a9a;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#journal-list .entry {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#journal-list .journal-empty-msg {
|
||||
color: #8892b0;
|
||||
font-size: 0.82rem;
|
||||
padding: 8px 4px;
|
||||
}
|
||||
|
||||
#detailActions.detail-actions,
|
||||
.detail-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
padding: 10px 14px 14px;
|
||||
border-top: 1px solid rgba(120, 140, 200, 0.2);
|
||||
}
|
||||
|
||||
.detail-actions-inner {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.detail-actions .table-del,
|
||||
.detail-actions button {
|
||||
font-size: 0.78rem !important;
|
||||
padding: 6px 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-record-list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 手机竖屏(含大屏手机) */
|
||||
@@ -1090,3 +1204,27 @@ html[data-theme="light"] .key-row-collapse.key-history-failed .key-history-outco
|
||||
border-color: rgba(192, 48, 48, 0.22) !important;
|
||||
}
|
||||
|
||||
html[data-theme="light"] .mobile-record-row {
|
||||
background: #fff !important;
|
||||
border-color: #b8c8d8 !important;
|
||||
color: #142232 !important;
|
||||
}
|
||||
|
||||
html[data-theme="light"] .mobile-record-row:active {
|
||||
background: #eef3f8 !important;
|
||||
}
|
||||
|
||||
html[data-theme="light"] .mrr-muted {
|
||||
color: #6a7588 !important;
|
||||
}
|
||||
|
||||
html[data-theme="light"] .mobile-record-del {
|
||||
background: rgba(192, 48, 48, 0.08) !important;
|
||||
border-color: rgba(192, 48, 48, 0.28) !important;
|
||||
color: #b04040 !important;
|
||||
}
|
||||
|
||||
html[data-theme="light"] .detail-actions {
|
||||
border-top-color: #d0dae4 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
titleEl.innerText = `交易复盘详情|${o.coin || "-"} ${o.tf || "-"}`;
|
||||
}
|
||||
setJournalDetailBody(o, formatExitLine);
|
||||
clearDetailActions();
|
||||
const imgEl = document.getElementById("detailImage");
|
||||
if (imgEl) {
|
||||
if (o.image) {
|
||||
@@ -83,6 +84,168 @@
|
||||
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
|
||||
? `<span class="badge ${dir.cls}">${escapeHtml(dir.text)}</span>`
|
||||
: `<span class="mrr-muted">-</span>`;
|
||||
const id = escapeHtml(o.id);
|
||||
return `<div class="mobile-record-row-wrap">
|
||||
<button type="button" class="mobile-record-row" onclick="openJournalDetail('${id}')">
|
||||
<span class="mrr-symbol">${escapeHtml(o.coin || "-")} ${escapeHtml(o.tf || "")}</span>
|
||||
<span class="mrr-dir">${dirHtml}</span>
|
||||
<span class="mrr-pnl ${pnlCls}">${escapeHtml(o.pnl == null || o.pnl === "" ? "-" : o.pnl)}U</span>
|
||||
</button>
|
||||
<button type="button" class="mobile-record-del" title="删除" onclick="deleteJournal('${id}')">×</button>
|
||||
</div>`;
|
||||
}
|
||||
const moodTags = (o.mood_issues || []).join(",") || "无";
|
||||
const id = escapeHtml(o.id);
|
||||
return `<div class="entry">
|
||||
<div><strong>${escapeHtml(o.coin || "-")} ${escapeHtml(o.tf || "-")}</strong> | 盈亏:${escapeHtml(o.pnl == null || o.pnl === "" ? "-" : o.pnl)}U</div>
|
||||
<div>开:${escapeHtml(o.open_datetime || "-")} 平:${escapeHtml(o.close_datetime || "-")} 持仓:${escapeHtml(o.hold_duration || "-")}</div>
|
||||
<div>心态标签:${escapeHtml(moodTags)}</div>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:6px">
|
||||
<button type="button" class="btn-del" style="border:none;cursor:pointer;background:#1f3a5a;color:#8fc8ff" onclick="openJournalDetail('${id}')">查看详情</button>
|
||||
<button type="button" class="btn-del" onclick="deleteJournal('${id}')">删除</button>
|
||||
</div>
|
||||
</div>`;
|
||||
})
|
||||
.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 `<button type="button" class="mobile-record-row" data-row-id="${escapeHtml(row.rowId)}">
|
||||
<span class="mrr-symbol">${escapeHtml(row.symbol)}</span>
|
||||
<span class="mrr-dir">${row.directionHtml}</span>
|
||||
<span class="mrr-pnl ${pnlCls}">${escapeHtml(row.pnlText || "-")}</span>
|
||||
</button>`;
|
||||
}
|
||||
|
||||
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("<br>");
|
||||
}
|
||||
|
||||
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(
|
||||
`<div class="detail-actions-inner">${row.actionsHtml}</div>`
|
||||
);
|
||||
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,
|
||||
@@ -90,5 +253,13 @@
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user