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:
@@ -3821,6 +3821,8 @@ html[data-theme="light"] button.danger {
|
||||
/* --- Hub AI 教练(整页一屏,内容区内滚动)--- */
|
||||
body.hub-page-ai {
|
||||
overflow: hidden;
|
||||
height: 100dvh;
|
||||
max-height: 100dvh;
|
||||
}
|
||||
body.hub-page-ai .app-shell {
|
||||
padding-bottom: 12px;
|
||||
@@ -3831,6 +3833,13 @@ body.hub-page-ai .app-shell {
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body.hub-page-ai .app-shell > #page-ai {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
body.hub-page-ai .app-header {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 4px;
|
||||
@@ -3876,6 +3885,11 @@ body.hub-page-ai #page-ai {
|
||||
|
||||
/* 手机 AI:须在 .ai-layout 双列定义之后,避免被覆盖成半屏 */
|
||||
@media (max-width: 720px) {
|
||||
html:has(body.hub-page-ai) {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body.hub-page-ai .app-shell {
|
||||
padding-bottom: max(8px, env(safe-area-inset-bottom));
|
||||
height: var(--hub-vvh, 100dvh);
|
||||
@@ -3888,8 +3902,24 @@ body.hub-page-ai #page-ai {
|
||||
}
|
||||
|
||||
body.hub-page-ai {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background: var(--bg);
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
body.hub-page-ai .app-header {
|
||||
padding: 6px 0;
|
||||
margin-bottom: 2px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
body.hub-page-ai .top-nav a {
|
||||
min-height: 34px;
|
||||
padding: 6px 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
body.hub-page-ai.hub-ai-keyboard-open .app-header,
|
||||
@@ -4062,8 +4092,19 @@ body.hub-page-ai #page-ai {
|
||||
body.hub-page-ai .ai-chat-messages {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
max-height: none;
|
||||
padding: 4px 2px 8px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
body.hub-page-ai .ai-layout[data-ai-mobile-tab="history"] .ai-chat-history-list {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
|
||||
@@ -3618,6 +3618,22 @@
|
||||
window.addEventListener("popstate", setActiveNav);
|
||||
}
|
||||
|
||||
window.hubOpenMonitorExpand = function hubOpenMonitorExpand(exId) {
|
||||
const id = String(exId || "").trim();
|
||||
if (!id) return;
|
||||
expandedExchangeId = id;
|
||||
sessionStorage.setItem("hub_expanded_ex", id);
|
||||
if (currentPage() !== "monitor") {
|
||||
history.pushState({}, "", "/monitor");
|
||||
setActiveNav();
|
||||
}
|
||||
if (lastMonitorRows.length) {
|
||||
openExchangeFullscreen(id);
|
||||
} else {
|
||||
void fetchMonitorBoardSnapshot({ showLoading: true });
|
||||
}
|
||||
};
|
||||
|
||||
initAuth().then((ok) => {
|
||||
if (!ok) return;
|
||||
initShellNav();
|
||||
|
||||
@@ -317,6 +317,75 @@ body.hub-page-dashboard .page#page-dashboard {
|
||||
color: var(--dash-muted);
|
||||
line-height: 1.4;
|
||||
word-break: break-word;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.dash-ac-monitor-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.dash-monitor-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 3px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
line-height: 1.3;
|
||||
border: 1px solid transparent;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dash-monitor-chip.dash-monitor-key {
|
||||
color: #b8a0ff;
|
||||
background: rgba(123, 97, 255, 0.18);
|
||||
border-color: rgba(123, 97, 255, 0.42);
|
||||
}
|
||||
|
||||
.dash-monitor-chip.dash-monitor-order {
|
||||
color: var(--dash-accent);
|
||||
background: rgba(0, 212, 255, 0.14);
|
||||
border-color: rgba(0, 212, 255, 0.38);
|
||||
}
|
||||
|
||||
.dash-monitor-chip.dash-monitor-trend {
|
||||
color: var(--dash-ok);
|
||||
background: rgba(0, 255, 157, 0.1);
|
||||
border-color: rgba(0, 255, 157, 0.38);
|
||||
}
|
||||
|
||||
.dash-monitor-chip.dash-monitor-roll {
|
||||
color: #ffb020;
|
||||
background: rgba(255, 176, 32, 0.14);
|
||||
border-color: rgba(255, 176, 32, 0.42);
|
||||
}
|
||||
|
||||
.dash-ac-expand-btn {
|
||||
margin-left: auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--dash-card-border);
|
||||
background: color-mix(in srgb, var(--dash-accent) 8%, var(--dash-card-bg));
|
||||
color: var(--dash-accent);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dash-ac-expand-btn:hover {
|
||||
border-color: var(--dash-accent);
|
||||
background: color-mix(in srgb, var(--dash-accent) 14%, var(--dash-card-bg));
|
||||
}
|
||||
|
||||
.dash-ac-positions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
@@ -346,10 +415,33 @@ body.hub-page-dashboard .page#page-dashboard {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dash-ac-remark-pos .pos {
|
||||
color: var(--dash-ok);
|
||||
}
|
||||
|
||||
.dash-ac-remark-pos .neg {
|
||||
color: var(--dash-warn);
|
||||
}
|
||||
|
||||
.dash-ac-remark-empty {
|
||||
color: var(--dash-muted);
|
||||
}
|
||||
|
||||
.dash-ac-remark-issue {
|
||||
color: var(--dash-warn);
|
||||
}
|
||||
|
||||
html[data-theme="light"] .dash-monitor-chip.dash-monitor-key {
|
||||
color: #5b4fc7;
|
||||
background: rgba(91, 79, 199, 0.1);
|
||||
border-color: rgba(91, 79, 199, 0.28);
|
||||
}
|
||||
|
||||
html[data-theme="light"] .dash-monitor-chip.dash-monitor-trend {
|
||||
background: rgba(10, 143, 92, 0.1);
|
||||
border-color: rgba(10, 143, 92, 0.28);
|
||||
}
|
||||
|
||||
.dash-table-wrap {
|
||||
overflow: auto;
|
||||
max-height: min(52vh, 480px);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
||||
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
|
||||
<link rel="stylesheet" href="/assets/app.css?v=20260611-hub-ai-tabs" />
|
||||
<link rel="stylesheet" href="/assets/dashboard.css?v=20260611-dash-remark-lines" />
|
||||
<link rel="stylesheet" href="/assets/app.css?v=20260612-hub-ai-one-screen" />
|
||||
<link rel="stylesheet" href="/assets/dashboard.css?v=20260612-dash-monitor-count" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-bg" aria-hidden="true"></div>
|
||||
@@ -555,8 +555,8 @@
|
||||
<script src="/assets/chart.js?v=20260609-market-day-split"></script>
|
||||
<script src="/assets/archive.js?v=20260608-hub-archive-history"></script>
|
||||
<script src="/assets/funds.js?v=20260609-hub-funds-fold"></script>
|
||||
<script src="/assets/dashboard.js?v=20260611-dash-remark-lines"></script>
|
||||
<script src="/assets/dashboard.js?v=20260612-dash-monitor-count"></script>
|
||||
<script src="/assets/ai_review_render.js?v=2"></script>
|
||||
<script src="/assets/app.js?v=20260611-hub-ai-tabs"></script>
|
||||
<script src="/assets/app.js?v=20260612-hub-monitor-expand"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user