feat(hub): mobile AI one-screen and dashboard monitor counts

Fix mobile AI to scroll only in the chat area. Dashboard cards show monitor item counts with expand-to-fullscreen and color-coded position floating P&L.
This commit is contained in:
dekun
2026-06-11 11:27:28 +08:00
parent 1042fdeef3
commit 08ae171e48
7 changed files with 237 additions and 57 deletions
+67 -27
View File
@@ -90,33 +90,72 @@
</div>`;
}
function renderRemarkLines(ac) {
const lines = Array.isArray(ac && ac.remark_lines) ? ac.remark_lines : [];
if (!lines.length) {
const fallback = esc((ac && ac.remark) || "—");
return `<div class="dash-ac-remark"><div class="dash-ac-remark-line">${fallback}</div></div>`;
function renderMonitorCountChips(counts) {
const mc = counts || {};
const chips = [];
const keys = Number(mc.keys) || 0;
const orders = Number(mc.orders) || 0;
const trends = Number(mc.trends) || 0;
const rolls = Number(mc.rolls) || 0;
if (keys > 0) chips.push(`<span class="dash-monitor-chip dash-monitor-key">关键位 ${keys}</span>`);
if (orders > 0) {
chips.push(`<span class="dash-monitor-chip dash-monitor-order">下单监控 ${orders}</span>`);
}
return `<div class="dash-ac-remark">${lines
.map((ln) => {
const kind = ln && ln.kind;
const text = esc((ln && ln.text) || "");
if (kind === "position" && ln.pnl != null && Number.isFinite(Number(ln.pnl))) {
const pnl = Number(ln.pnl);
return (
`<div class="dash-ac-remark-line dash-ac-remark-pos">` +
`${text} 浮<span class="${pnlClass(pnl)}">${pnlSigned(pnl, 2)}</span>` +
`</div>`
);
}
const cls =
kind === "monitor"
? "dash-ac-remark-line dash-ac-remark-mon"
: kind === "issue"
? "dash-ac-remark-line dash-ac-remark-issue"
: "dash-ac-remark-line";
return `<div class="${cls}">${text}</div>`;
})
.join("")}</div>`;
if (trends > 0) chips.push(`<span class="dash-monitor-chip dash-monitor-trend">趋势回调 ${trends}</span>`);
if (rolls > 0) chips.push(`<span class="dash-monitor-chip dash-monitor-roll">顺势加仓 ${rolls}</span>`);
return chips;
}
function renderAccountDetail(ac) {
const counts = (ac && ac.monitor_counts) || {};
const positions = Array.isArray(ac && ac.position_lines) ? ac.position_lines : [];
const issues = Array.isArray(ac && ac.issues) ? ac.issues : [];
const exId = ac && ac.id != null ? String(ac.id) : "";
const chips = renderMonitorCountChips(counts);
const expandBtn = exId
? `<button type="button" class="dash-ac-expand-btn" data-dash-ex-id="${esc(exId)}" title="放大查看监控详情" aria-label="放大查看监控详情">` +
`<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path fill="currentColor" d="M15 3h6v6h-2V6.41l-7.29 7.3-1.42-1.42 7.3-7.29H15V3zM3 9h2v10h10v2H3V9z"/></svg>` +
`</button>`
: "";
const monitorRow =
chips.length || expandBtn
? `<div class="dash-ac-monitor-row">${chips.join("")}${expandBtn}</div>`
: "";
let posHtml = "";
if (positions.length) {
posHtml = positions
.map((ln) => {
const text = esc((ln && ln.text) || "");
if (ln.pnl != null && Number.isFinite(Number(ln.pnl))) {
const pnl = Number(ln.pnl);
return (
`<div class="dash-ac-remark-line dash-ac-remark-pos">` +
`${text} 浮<span class="${pnlClass(pnl)}">${pnlSigned(pnl, 2)}</span>` +
`</div>`
);
}
return `<div class="dash-ac-remark-line dash-ac-remark-pos">${text}</div>`;
})
.join("");
} else if (!chips.length && !issues.length) {
posHtml = `<div class="dash-ac-remark-line dash-ac-remark-empty">无持仓</div>`;
}
const issueHtml = issues
.map((text) => `<div class="dash-ac-remark-line dash-ac-remark-issue">${esc(text)}</div>`)
.join("");
return `<div class="dash-ac-remark">${monitorRow}<div class="dash-ac-positions">${posHtml}</div>${issueHtml}</div>`;
}
function bindDashboardExpand() {
if (!elAccounts) return;
elAccounts.querySelectorAll(".dash-ac-expand-btn").forEach((btn) => {
btn.addEventListener("click", (ev) => {
ev.preventDefault();
ev.stopPropagation();
const id = btn.getAttribute("data-dash-ex-id");
if (id && window.hubOpenMonitorExpand) window.hubOpenMonitorExpand(id);
});
});
}
function renderAccounts(accounts, threshold) {
@@ -158,10 +197,11 @@
<div class="dash-ac-metric"><span>浮盈亏</span><strong class="${pnlClass(floatPnl)}">${pnlSigned(floatPnl, 2)}</strong></div>
</div>
${lossBar}
${renderRemarkLines(ac)}
${renderAccountDetail(ac)}
</article>`;
})
.join("");
bindDashboardExpand();
}
function renderTrades(trades, accounts) {