1
This commit is contained in:
parent
69f81e6357
commit
8abdd23987
@ -313,6 +313,13 @@
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.panel-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.panel-title {
|
.panel-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
@ -653,6 +660,26 @@
|
|||||||
background: rgba(126, 200, 255, 0.12);
|
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 {
|
.tab-pane {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -667,7 +694,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.workspace-stream {
|
.workspace-stream {
|
||||||
min-height: 240px;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-list {
|
.event-list {
|
||||||
@ -702,6 +729,27 @@
|
|||||||
background: rgba(126, 200, 255, 0.10);
|
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 {
|
.event-item {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 120px 110px 1fr;
|
grid-template-columns: 120px 110px 1fr;
|
||||||
@ -1163,6 +1211,25 @@
|
|||||||
border-color: rgba(255, 111, 97, 0.2);
|
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) {
|
@media (max-width: 1240px) {
|
||||||
.hero,
|
.hero,
|
||||||
.layout,
|
.layout,
|
||||||
@ -1291,6 +1358,9 @@
|
|||||||
<h2 class="panel-title" style="margin-top: 12px;">平台执行概览</h2>
|
<h2 class="panel-title" style="margin-top: 12px;">平台执行概览</h2>
|
||||||
<div class="panel-sub">资金、杠杆、持仓、挂单、回撤阈值</div>
|
<div class="panel-sub">资金、杠杆、持仓、挂单、回撤阈值</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="panel-actions">
|
||||||
|
<button class="ghost-btn" id="toggleSensitiveBtn">敏感数据: 隐藏</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="platform-grid" id="platformGrid">
|
<div class="platform-grid" id="platformGrid">
|
||||||
<div class="loading">正在加载平台状态...</div>
|
<div class="loading">正在加载平台状态...</div>
|
||||||
@ -1451,6 +1521,9 @@
|
|||||||
let timer = null;
|
let timer = null;
|
||||||
let currentEventFilter = 'all';
|
let currentEventFilter = 'all';
|
||||||
let cachedExecutionEvents = [];
|
let cachedExecutionEvents = [];
|
||||||
|
let cachedConsoleData = null;
|
||||||
|
let revealSensitiveData = false;
|
||||||
|
const SENSITIVE_VISIBILITY_KEY = 'console_sensitive_visible_v2';
|
||||||
|
|
||||||
function formatNumber(value, digits = 2) {
|
function formatNumber(value, digits = 2) {
|
||||||
const num = Number(value || 0);
|
const num = Number(value || 0);
|
||||||
@ -1521,6 +1594,48 @@
|
|||||||
el.innerHTML = `<div class="${isError ? 'error-box' : 'empty-box'}" style="margin-bottom: 18px;">${message}</div>`;
|
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() {
|
function initTabs() {
|
||||||
document.querySelectorAll('[data-tab-group]').forEach((groupEl) => {
|
document.querySelectorAll('[data-tab-group]').forEach((groupEl) => {
|
||||||
const group = groupEl.getAttribute('data-tab-group');
|
const group = groupEl.getAttribute('data-tab-group');
|
||||||
@ -1528,17 +1643,68 @@
|
|||||||
buttons.forEach((button) => {
|
buttons.forEach((button) => {
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
const target = button.getAttribute('data-target');
|
const target = button.getAttribute('data-target');
|
||||||
buttons.forEach((item) => {
|
setActiveTab(group, target);
|
||||||
item.classList.toggle('active', item === button);
|
|
||||||
});
|
|
||||||
document.querySelectorAll(`[data-tab-pane="${group}"]`).forEach((pane) => {
|
|
||||||
pane.classList.toggle('active', pane.id === 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) {
|
function toneClassForHealth(status) {
|
||||||
if (['error', 'failed', 'stopped'].includes(String(status || '').toLowerCase())) return 'danger';
|
if (['error', 'failed', 'stopped'].includes(String(status || '').toLowerCase())) return 'danger';
|
||||||
if (['warning', 'halted', 'idle'].includes(String(status || '').toLowerCase())) return 'warn';
|
if (['warning', 'halted', 'idle'].includes(String(status || '').toLowerCase())) return 'warn';
|
||||||
@ -1677,11 +1843,11 @@
|
|||||||
<div class="platform-stats">
|
<div class="platform-stats">
|
||||||
<div class="platform-stat">
|
<div class="platform-stat">
|
||||||
<span class="label">权益</span>
|
<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>
|
||||||
<div class="platform-stat">
|
<div class="platform-stat">
|
||||||
<span class="label">可用</span>
|
<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>
|
||||||
<div class="platform-stat">
|
<div class="platform-stat">
|
||||||
<span class="label">持仓</span>
|
<span class="label">持仓</span>
|
||||||
@ -1714,7 +1880,7 @@
|
|||||||
function renderSignalStream(signals) {
|
function renderSignalStream(signals) {
|
||||||
const container = document.getElementById('signalStream');
|
const container = document.getElementById('signalStream');
|
||||||
if (!signals || signals.length === 0) {
|
if (!signals || signals.length === 0) {
|
||||||
container.innerHTML = '<div class="empty-box">最近没有可展示信号</div>';
|
container.innerHTML = compactEmpty('最近没有可展示信号', '等待新的分析结果写入,运行状态可继续看上方心跳。');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1755,7 +1921,7 @@
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
if (entries.length === 0) {
|
if (entries.length === 0) {
|
||||||
cards.push('<div class="empty-box">Crypto Agent 暂无最近信号缓存</div>');
|
cards.push(compactEmpty('Crypto Agent 暂无最近信号缓存', '当前没有缓存到最近信号,可切到信号流或执行流继续查看。'));
|
||||||
} else {
|
} else {
|
||||||
entries.slice(0, 5).forEach(([symbol, sig]) => {
|
entries.slice(0, 5).forEach(([symbol, sig]) => {
|
||||||
cards.push(`
|
cards.push(`
|
||||||
@ -1809,7 +1975,7 @@
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
if (!analysisEvents || analysisEvents.length === 0) {
|
if (!analysisEvents || analysisEvents.length === 0) {
|
||||||
logList.innerHTML = '<div class="empty-box">最近还没有分析日志</div>';
|
logList.innerHTML = compactEmpty('最近还没有分析日志', '等待下一轮分析或新的运行事件写入。');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1847,7 +2013,7 @@
|
|||||||
const entries = Object.entries(previewMap || {});
|
const entries = Object.entries(previewMap || {});
|
||||||
|
|
||||||
if (entries.length === 0) {
|
if (entries.length === 0) {
|
||||||
container.innerHTML = '<div class="empty-box">暂无最近决策预览</div>';
|
container.innerHTML = compactEmpty('暂无最近决策预览', '还没有形成最近一轮执行预览,通常意味着还没出现可执行信号。');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1927,7 +2093,7 @@
|
|||||||
cachedExecutionEvents = Array.isArray(events) ? events : [];
|
cachedExecutionEvents = Array.isArray(events) ? events : [];
|
||||||
const filtered = cachedExecutionEvents.filter((event) => currentEventFilter === 'all' || event.status === currentEventFilter);
|
const filtered = cachedExecutionEvents.filter((event) => currentEventFilter === 'all' || event.status === currentEventFilter);
|
||||||
if (!filtered || filtered.length === 0) {
|
if (!filtered || filtered.length === 0) {
|
||||||
container.innerHTML = '<div class="empty-box">最近还没有执行事件</div>';
|
container.innerHTML = compactEmpty('最近没有匹配的执行事件', currentEventFilter === 'all' ? '当前没有新的执行结果或异常事件。' : `筛选条件为 ${currentEventFilter.toUpperCase()},当前暂无匹配记录。`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1956,7 +2122,7 @@
|
|||||||
function renderAttentionItems(items) {
|
function renderAttentionItems(items) {
|
||||||
const container = document.getElementById('attentionList');
|
const container = document.getElementById('attentionList');
|
||||||
if (!items || items.length === 0) {
|
if (!items || items.length === 0) {
|
||||||
container.innerHTML = '<div class="empty-box">当前没有需要人工处理的事项</div>';
|
container.innerHTML = compactEmpty('当前没有需要人工处理的事项', '系统暂无明显风险、停机或待干预问题。');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1982,7 +2148,7 @@
|
|||||||
toolbar.innerHTML = `${platformCounts}<span class="toolbar-chip">total: ${total.length}</span>`;
|
toolbar.innerHTML = `${platformCounts}<span class="toolbar-chip">total: ${total.length}</span>`;
|
||||||
|
|
||||||
if (!total.length) {
|
if (!total.length) {
|
||||||
container.innerHTML = '<div class="empty-box">当前没有跨平台持仓</div>';
|
container.innerHTML = compactEmpty('当前没有跨平台持仓', '没有活动持仓时,这里会保持紧凑,不再占用大块留白。');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2033,7 +2199,7 @@
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
if (!total.length) {
|
if (!total.length) {
|
||||||
container.innerHTML = '<div class="empty-box">当前没有跨平台挂单</div>';
|
container.innerHTML = compactEmpty('当前没有跨平台挂单', '没有入场单或保护单时,这里会保持紧凑展示。');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2113,6 +2279,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = result.data || {};
|
const data = result.data || {};
|
||||||
|
cachedConsoleData = data;
|
||||||
|
syncTabState(data);
|
||||||
renderHero(data);
|
renderHero(data);
|
||||||
renderHealthRibbon(data);
|
renderHealthRibbon(data);
|
||||||
renderPlatforms(data.platforms, data.crypto_agent?.platform_halts);
|
renderPlatforms(data.platforms, data.crypto_agent?.platform_halts);
|
||||||
@ -2153,6 +2321,7 @@
|
|||||||
autoRefresh = !autoRefresh;
|
autoRefresh = !autoRefresh;
|
||||||
applyAutoRefreshState();
|
applyAutoRefreshState();
|
||||||
});
|
});
|
||||||
|
document.getElementById('toggleSensitiveBtn').addEventListener('click', toggleSensitiveData);
|
||||||
document.getElementById('eventFilters').querySelectorAll('[data-filter]').forEach((button) => {
|
document.getElementById('eventFilters').querySelectorAll('[data-filter]').forEach((button) => {
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
currentEventFilter = button.getAttribute('data-filter');
|
currentEventFilter = button.getAttribute('data-filter');
|
||||||
@ -2163,6 +2332,7 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
loadSensitivePreference();
|
||||||
initTabs();
|
initTabs();
|
||||||
applyAutoRefreshState();
|
applyAutoRefreshState();
|
||||||
loadConsole();
|
loadConsole();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user