This commit is contained in:
aaron 2026-04-22 11:51:00 +08:00
parent 69f81e6357
commit 8abdd23987

View File

@ -313,6 +313,13 @@
margin-bottom: 16px;
}
.panel-actions {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.panel-title {
margin: 0;
font-size: 18px;
@ -653,6 +660,26 @@
background: rgba(126, 200, 255, 0.12);
}
.workspace-tab.muted {
color: rgba(142, 166, 188, 0.72);
border-color: rgba(255,255,255,0.05);
}
.tab-count {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 18px;
height: 18px;
margin-left: 6px;
padding: 0 6px;
border-radius: 999px;
background: rgba(255,255,255,0.08);
color: inherit;
font-size: 10px;
line-height: 1;
}
.tab-pane {
display: none;
}
@ -667,7 +694,7 @@
}
.workspace-stream {
min-height: 240px;
min-height: 0;
}
.event-list {
@ -702,6 +729,27 @@
background: rgba(126, 200, 255, 0.10);
}
.ghost-btn {
appearance: none;
border: 1px solid rgba(255,255,255,0.08);
background: rgba(255,255,255,0.03);
color: var(--muted);
cursor: pointer;
border-radius: 999px;
padding: 8px 12px;
font-size: 11px;
font-family: "IBM Plex Mono", monospace;
text-transform: uppercase;
letter-spacing: 0.05em;
transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}
.ghost-btn.active {
color: var(--cold);
border-color: rgba(126, 200, 255, 0.24);
background: rgba(126, 200, 255, 0.1);
}
.event-item {
display: grid;
grid-template-columns: 120px 110px 1fr;
@ -1163,6 +1211,25 @@
border-color: rgba(255, 111, 97, 0.2);
}
.empty-box.compact {
padding: 14px 16px;
text-align: left;
border-radius: 14px;
}
.empty-box.compact strong {
display: block;
color: var(--text);
font-size: 13px;
margin-bottom: 4px;
}
.empty-detail {
color: var(--muted);
font-size: 12px;
line-height: 1.55;
}
@media (max-width: 1240px) {
.hero,
.layout,
@ -1291,6 +1358,9 @@
<h2 class="panel-title" style="margin-top: 12px;">平台执行概览</h2>
<div class="panel-sub">资金、杠杆、持仓、挂单、回撤阈值</div>
</div>
<div class="panel-actions">
<button class="ghost-btn" id="toggleSensitiveBtn">敏感数据: 隐藏</button>
</div>
</div>
<div class="platform-grid" id="platformGrid">
<div class="loading">正在加载平台状态...</div>
@ -1451,6 +1521,9 @@
let timer = null;
let currentEventFilter = 'all';
let cachedExecutionEvents = [];
let cachedConsoleData = null;
let revealSensitiveData = false;
const SENSITIVE_VISIBILITY_KEY = 'console_sensitive_visible_v2';
function formatNumber(value, digits = 2) {
const num = Number(value || 0);
@ -1521,6 +1594,48 @@
el.innerHTML = `<div class="${isError ? 'error-box' : 'empty-box'}" style="margin-bottom: 18px;">${message}</div>`;
}
function compactEmpty(title, detail = '') {
return `
<div class="empty-box compact">
<strong>${title}</strong>
${detail ? `<div class="empty-detail">${detail}</div>` : ''}
</div>
`;
}
function loadSensitivePreference() {
try {
revealSensitiveData = window.localStorage.getItem(SENSITIVE_VISIBILITY_KEY) === '1';
} catch {
revealSensitiveData = false;
}
renderSensitiveToggle();
}
function renderSensitiveToggle() {
const button = document.getElementById('toggleSensitiveBtn');
if (!button) return;
button.textContent = `敏感数据: ${revealSensitiveData ? '显示' : '隐藏'}`;
button.classList.toggle('active', revealSensitiveData);
}
function toggleSensitiveData() {
revealSensitiveData = !revealSensitiveData;
try {
window.localStorage.setItem(SENSITIVE_VISIBILITY_KEY, revealSensitiveData ? '1' : '0');
} catch {
// ignore storage errors
}
renderSensitiveToggle();
if (cachedConsoleData) {
renderPlatforms(cachedConsoleData.platforms, cachedConsoleData.crypto_agent?.platform_halts);
}
}
function formatSensitiveMoney(value) {
return revealSensitiveData ? formatMoney(value) : '$••••';
}
function initTabs() {
document.querySelectorAll('[data-tab-group]').forEach((groupEl) => {
const group = groupEl.getAttribute('data-tab-group');
@ -1528,17 +1643,68 @@
buttons.forEach((button) => {
button.addEventListener('click', () => {
const target = button.getAttribute('data-target');
buttons.forEach((item) => {
item.classList.toggle('active', item === button);
});
document.querySelectorAll(`[data-tab-pane="${group}"]`).forEach((pane) => {
pane.classList.toggle('active', pane.id === target);
});
setActiveTab(group, target);
});
});
});
}
function setActiveTab(group, target) {
document.querySelectorAll(`[data-tab="${group}"]`).forEach((button) => {
button.classList.toggle('active', button.getAttribute('data-target') === target);
});
document.querySelectorAll(`[data-tab-pane="${group}"]`).forEach((pane) => {
pane.classList.toggle('active', pane.id === target);
});
}
function getActiveTabTarget(group) {
return document.querySelector(`[data-tab="${group}"].active`)?.getAttribute('data-target') || null;
}
function updateTabButton(group, target, label, count, hasData) {
const button = document.querySelector(`[data-tab="${group}"][data-target="${target}"]`);
if (!button) return;
button.innerHTML = `${label}<span class="tab-count">${count}</span>`;
button.classList.toggle('muted', !hasData);
}
function syncTabState(data) {
const recentSignals = data.signals?.latest || [];
const executionEvents = data.execution_events || [];
const positions = data.management?.positions || [];
const orders = data.management?.orders || [];
const lastSignals = Object.keys(data.crypto_agent?.last_signals || {}).length;
const previews = Object.keys(data.crypto_agent?.last_execution_preview || {}).length;
const statsTotal = Number(data.signals?.stats_7d?.total || 0);
const coordinationCount = lastSignals + previews + (statsTotal > 0 ? 1 : 0);
updateTabButton('workspace', 'workspaceCoordination', '协同', coordinationCount, coordinationCount > 0);
updateTabButton('workspace', 'workspaceSignals', '信号流', recentSignals.length, recentSignals.length > 0);
updateTabButton('workspace', 'workspaceExecution', '执行流', executionEvents.length, executionEvents.length > 0);
updateTabButton('asset', 'assetPositions', '持仓', positions.length, positions.length > 0);
updateTabButton('asset', 'assetOrders', '挂单', orders.length, orders.length > 0);
const workspaceCurrent = getActiveTabTarget('workspace');
const workspaceChoices = [
{ target: 'workspaceCoordination', hasData: coordinationCount > 0 },
{ target: 'workspaceExecution', hasData: executionEvents.length > 0 },
{ target: 'workspaceSignals', hasData: recentSignals.length > 0 },
];
if (!workspaceChoices.find((item) => item.target === workspaceCurrent && item.hasData)) {
setActiveTab('workspace', workspaceChoices.find((item) => item.hasData)?.target || 'workspaceCoordination');
}
const assetCurrent = getActiveTabTarget('asset');
const assetChoices = [
{ target: 'assetPositions', hasData: positions.length > 0 },
{ target: 'assetOrders', hasData: orders.length > 0 },
];
if (!assetChoices.find((item) => item.target === assetCurrent && item.hasData)) {
setActiveTab('asset', assetChoices.find((item) => item.hasData)?.target || 'assetPositions');
}
}
function toneClassForHealth(status) {
if (['error', 'failed', 'stopped'].includes(String(status || '').toLowerCase())) return 'danger';
if (['warning', 'halted', 'idle'].includes(String(status || '').toLowerCase())) return 'warn';
@ -1677,11 +1843,11 @@
<div class="platform-stats">
<div class="platform-stat">
<span class="label">权益</span>
<span class="value">${formatMoney(account.current_balance || account.account_value)}</span>
<span class="value">${formatSensitiveMoney(account.current_balance || account.account_value)}</span>
</div>
<div class="platform-stat">
<span class="label">可用</span>
<span class="value">${formatMoney(account.available || account.available_balance)}</span>
<span class="value">${formatSensitiveMoney(account.available || account.available_balance)}</span>
</div>
<div class="platform-stat">
<span class="label">持仓</span>
@ -1714,7 +1880,7 @@
function renderSignalStream(signals) {
const container = document.getElementById('signalStream');
if (!signals || signals.length === 0) {
container.innerHTML = '<div class="empty-box">最近没有可展示信号</div>';
container.innerHTML = compactEmpty('最近没有可展示信号', '等待新的分析结果写入,运行状态可继续看上方心跳。');
return;
}
@ -1755,7 +1921,7 @@
`);
if (entries.length === 0) {
cards.push('<div class="empty-box">Crypto Agent 暂无最近信号缓存</div>');
cards.push(compactEmpty('Crypto Agent 暂无最近信号缓存', '当前没有缓存到最近信号,可切到信号流或执行流继续查看。'));
} else {
entries.slice(0, 5).forEach(([symbol, sig]) => {
cards.push(`
@ -1809,7 +1975,7 @@
`;
if (!analysisEvents || analysisEvents.length === 0) {
logList.innerHTML = '<div class="empty-box">最近还没有分析日志</div>';
logList.innerHTML = compactEmpty('最近还没有分析日志', '等待下一轮分析或新的运行事件写入。');
return;
}
@ -1847,7 +2013,7 @@
const entries = Object.entries(previewMap || {});
if (entries.length === 0) {
container.innerHTML = '<div class="empty-box">暂无最近决策预览</div>';
container.innerHTML = compactEmpty('暂无最近决策预览', '还没有形成最近一轮执行预览,通常意味着还没出现可执行信号。');
return;
}
@ -1927,7 +2093,7 @@
cachedExecutionEvents = Array.isArray(events) ? events : [];
const filtered = cachedExecutionEvents.filter((event) => currentEventFilter === 'all' || event.status === currentEventFilter);
if (!filtered || filtered.length === 0) {
container.innerHTML = '<div class="empty-box">最近还没有执行事件</div>';
container.innerHTML = compactEmpty('最近没有匹配的执行事件', currentEventFilter === 'all' ? '当前没有新的执行结果或异常事件。' : `筛选条件为 ${currentEventFilter.toUpperCase()},当前暂无匹配记录。`);
return;
}
@ -1956,7 +2122,7 @@
function renderAttentionItems(items) {
const container = document.getElementById('attentionList');
if (!items || items.length === 0) {
container.innerHTML = '<div class="empty-box">当前没有需要人工处理的事项</div>';
container.innerHTML = compactEmpty('当前没有需要人工处理的事项', '系统暂无明显风险、停机或待干预问题。');
return;
}
@ -1982,7 +2148,7 @@
toolbar.innerHTML = `${platformCounts}<span class="toolbar-chip">total: ${total.length}</span>`;
if (!total.length) {
container.innerHTML = '<div class="empty-box">当前没有跨平台持仓</div>';
container.innerHTML = compactEmpty('当前没有跨平台持仓', '没有活动持仓时,这里会保持紧凑,不再占用大块留白。');
return;
}
@ -2033,7 +2199,7 @@
`;
if (!total.length) {
container.innerHTML = '<div class="empty-box">当前没有跨平台挂单</div>';
container.innerHTML = compactEmpty('当前没有跨平台挂单', '没有入场单或保护单时,这里会保持紧凑展示。');
return;
}
@ -2113,6 +2279,8 @@
}
const data = result.data || {};
cachedConsoleData = data;
syncTabState(data);
renderHero(data);
renderHealthRibbon(data);
renderPlatforms(data.platforms, data.crypto_agent?.platform_halts);
@ -2153,6 +2321,7 @@
autoRefresh = !autoRefresh;
applyAutoRefreshState();
});
document.getElementById('toggleSensitiveBtn').addEventListener('click', toggleSensitiveData);
document.getElementById('eventFilters').querySelectorAll('[data-filter]').forEach((button) => {
button.addEventListener('click', () => {
currentEventFilter = button.getAttribute('data-filter');
@ -2163,6 +2332,7 @@
});
});
loadSensitivePreference();
initTabs();
applyAutoRefreshState();
loadConsole();