feat(dashboard): dark terminal design system overhaul
Complete visual redesign replacing Apple glassmorphism with Bloomberg-style dark trading terminal aesthetic: - Dark palette: #0d0d0f base, cyan accent (#00d4ff), green/red/amber signals - Font pair: DM Sans (UI) + JetBrains Mono (data/numbers) - Solid sidebar (no backdrop-filter blur) - Compact stat strip in BatchManager (replaces 4-card hero row) - Color system: semantic buy/sell/hold/running with CSS variables - All inline rgba(0,0,0,...) → dark theme tokens - All var(--font-*) → font-ui / font-data - Focus-visible outlines on all interactive elements - prefers-reduced-motion support - Emoji status indicators → CSS status-dot Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
045b61fd7e
commit
f196962112
File diff suppressed because it is too large
Load Diff
|
|
@ -94,7 +94,7 @@ export default function AnalysisMonitor() {
|
|||
case 'failed':
|
||||
return <CloseCircleOutlined style={{ color: 'var(--color-sell)', fontSize: 16 }} />
|
||||
default:
|
||||
return <span style={{ width: 16, height: 16, borderRadius: '50%', border: '2px solid rgba(0,0,0,0.12)', display: 'inline-block' }} />
|
||||
return <span style={{ width: 16, height: 16, borderRadius: '50%', border: '2px solid var(--border-strong)', display: 'inline-block' }} />
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,13 +136,13 @@ export default function AnalysisMonitor() {
|
|||
style={{ marginBottom: 'var(--space-6)' }}
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<span style={{ fontFamily: 'var(--font-display)', fontSize: 17, fontWeight: 600 }}>
|
||||
<span style={{ fontFamily: 'var(--font-ui)', fontSize: 17, fontWeight: 600 }}>
|
||||
当前分析任务
|
||||
</span>
|
||||
<Badge
|
||||
status={error ? 'error' : wsConnected ? 'success' : 'default'}
|
||||
text={
|
||||
<span style={{ fontSize: 12, color: error ? 'var(--color-sell)' : wsConnected ? 'var(--color-buy)' : 'rgba(0,0,0,0.48)' }}>
|
||||
<span style={{ fontSize: 12, color: error ? 'var(--sell)' : wsConnected ? 'var(--buy)' : 'var(--text-muted)' }}>
|
||||
{error ? '错误' : wsConnected ? '实时连接' : '连接中'}
|
||||
</span>
|
||||
}
|
||||
|
|
@ -176,7 +176,7 @@ export default function AnalysisMonitor() {
|
|||
{/* Task Header */}
|
||||
<div style={{ marginBottom: 'var(--space-6)' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16, marginBottom: 16 }}>
|
||||
<span style={{ fontFamily: 'var(--font-display)', fontSize: 28, fontWeight: 600, letterSpacing: 0.196, lineHeight: 1.14 }}>
|
||||
<span style={{ fontFamily: 'var(--font-ui)', fontSize: 28, fontWeight: 600, letterSpacing: 0.196, lineHeight: 1.14 }}>
|
||||
{task.ticker}
|
||||
</span>
|
||||
{getDecisionBadge(task.decision)}
|
||||
|
|
@ -216,7 +216,7 @@ export default function AnalysisMonitor() {
|
|||
style={{
|
||||
fontFamily: 'var(--font-data)',
|
||||
fontSize: 12,
|
||||
background: 'rgba(0,0,0,0.03)',
|
||||
background: 'var(--bg-elevated)',
|
||||
padding: 'var(--space-4)',
|
||||
borderRadius: 'var(--radius-standard)',
|
||||
maxHeight: 280,
|
||||
|
|
@ -226,13 +226,13 @@ export default function AnalysisMonitor() {
|
|||
{task.logs?.length > 0 ? (
|
||||
task.logs.map((log, i) => (
|
||||
<div key={i} style={{ marginBottom: 8, lineHeight: 1.4 }}>
|
||||
<span style={{ color: 'rgba(0,0,0,0.48)' }}>[{log.time}]</span>{' '}
|
||||
<span style={{ color: 'var(--text-muted)' }}>[{log.time}]</span>{' '}
|
||||
<span style={{ fontWeight: 500 }}>{log.stage}:</span>{' '}
|
||||
<span>{log.message}</span>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div style={{ color: 'rgba(0,0,0,0.48)', textAlign: 'center', padding: 'var(--space-4)' }}>
|
||||
<div style={{ color: 'var(--text-muted)', textAlign: 'center', padding: 'var(--space-4)' }}>
|
||||
等待日志输出...
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -70,22 +70,22 @@ export default function BatchManager() {
|
|||
const getStatusIcon = (status) => {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return <CheckCircleOutlined style={{ color: 'var(--color-buy)', fontSize: 16 }} />
|
||||
return <CheckCircleOutlined style={{ color: 'var(--buy)', fontSize: 16 }} />
|
||||
case 'failed':
|
||||
return <CloseCircleOutlined style={{ color: 'var(--color-sell)', fontSize: 16 }} />
|
||||
return <CloseCircleOutlined style={{ color: 'var(--sell)', fontSize: 16 }} />
|
||||
case 'running':
|
||||
return <SyncOutlined spin style={{ color: 'var(--color-running)', fontSize: 16 }} />
|
||||
return <SyncOutlined spin style={{ color: 'var(--running)', fontSize: 16 }} />
|
||||
default:
|
||||
return <span style={{ width: 16, height: 16, borderRadius: '50%', border: '2px solid rgba(0,0,0,0.2)', display: 'inline-block' }} />
|
||||
return <span style={{ width: 16, height: 16, borderRadius: '50%', border: '2px solid var(--border-strong)', display: 'inline-block' }} />
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusTag = (status) => {
|
||||
const map = {
|
||||
pending: { text: '等待', bg: 'rgba(0,0,0,0.06)', color: 'rgba(0,0,0,0.48)' },
|
||||
running: { text: '分析中', bg: 'rgba(168,85,247,0.12)', color: 'var(--color-running)' },
|
||||
completed: { text: '完成', bg: 'rgba(34,197,94,0.12)', color: 'var(--color-buy)' },
|
||||
failed: { text: '失败', bg: 'rgba(220,38,38,0.12)', color: 'var(--color-sell)' },
|
||||
pending: { text: '等待', bg: 'var(--bg-elevated)', color: 'var(--text-muted)' },
|
||||
running: { text: '分析中', bg: 'var(--running-dim)', color: 'var(--running)' },
|
||||
completed: { text: '完成', bg: 'var(--buy-dim)', color: 'var(--buy)' },
|
||||
failed: { text: '失败', bg: 'var(--sell-dim)', color: 'var(--sell)' },
|
||||
}
|
||||
const s = map[status] || map.pending
|
||||
return (
|
||||
|
|
@ -118,7 +118,7 @@ export default function BatchManager() {
|
|||
dataIndex: 'ticker',
|
||||
key: 'ticker',
|
||||
render: (text) => (
|
||||
<span style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 15 }}>{text}</span>
|
||||
<span style={{ fontFamily: 'var(--font-ui)', fontWeight: 600, fontSize: 15 }}>{text}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
@ -131,8 +131,8 @@ export default function BatchManager() {
|
|||
<Progress
|
||||
percent={val || 0}
|
||||
size="small"
|
||||
strokeColor="var(--color-apple-blue)"
|
||||
trailColor="rgba(0,0,0,0.08)"
|
||||
strokeColor="var(--accent)"
|
||||
trailColor="rgba(255,255,255,0.06)"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-data">{val || 0}%</span>
|
||||
|
|
@ -152,12 +152,12 @@ export default function BatchManager() {
|
|||
width: 220,
|
||||
render: (text) => (
|
||||
<Tooltip title={text} placement="topLeft">
|
||||
<span className="text-data" style={{ fontSize: 11, color: 'rgba(0,0,0,0.48)', cursor: 'default' }}>
|
||||
<span className="text-data" style={{ fontSize: 11, color: 'var(--text-muted)', cursor: 'default' }}>
|
||||
{text.slice(0, 18)}...
|
||||
</span>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); handleCopyTaskId(text) }}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px 4px', color: 'rgba(0,0,0,0.48)', display: 'inline-flex', alignItems: 'center' }}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px 4px', color: 'var(--text-muted)', display: 'inline-flex', alignItems: 'center' }}
|
||||
title="复制任务ID"
|
||||
>
|
||||
<CopyOutlined style={{ fontSize: 12 }} />
|
||||
|
|
@ -213,24 +213,37 @@ export default function BatchManager() {
|
|||
|
||||
return (
|
||||
<div>
|
||||
{/* Stats */}
|
||||
<div style={{ display: 'flex', gap: 'var(--space-4)', marginBottom: 'var(--space-6)' }}>
|
||||
<Card size="small" className="card" style={{ flex: 1 }}>
|
||||
<div className="text-data" style={{ fontSize: 32, fontWeight: 600 }}>{pendingCount}</div>
|
||||
<div className="text-caption">等待中</div>
|
||||
</Card>
|
||||
<Card size="small" className="card" style={{ flex: 1 }}>
|
||||
<div className="text-data" style={{ fontSize: 32, fontWeight: 600, color: 'var(--color-running)' }}>{runningCount}</div>
|
||||
<div className="text-caption">分析中</div>
|
||||
</Card>
|
||||
<Card size="small" className="card" style={{ flex: 1 }}>
|
||||
<div className="text-data" style={{ fontSize: 32, fontWeight: 600, color: 'var(--color-buy)' }}>{completedCount}</div>
|
||||
<div className="text-caption">已完成</div>
|
||||
</Card>
|
||||
<Card size="small" className="card" style={{ flex: 1 }}>
|
||||
<div className="text-data" style={{ fontSize: 32, fontWeight: 600, color: 'var(--color-sell)' }}>{failedCount}</div>
|
||||
<div className="text-caption">失败</div>
|
||||
</Card>
|
||||
{/* Compact stat strip — no card nesting, left-aligned with colored accents */}
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(4, 1fr)',
|
||||
gap: 'var(--space-1)',
|
||||
marginBottom: 'var(--space-6)',
|
||||
background: 'var(--border)',
|
||||
borderRadius: 'var(--radius-lg)',
|
||||
padding: 'var(--space-1)',
|
||||
}}>
|
||||
{[
|
||||
{ label: '等待中', value: pendingCount, color: 'var(--text-muted)', border: 'var(--text-muted)' },
|
||||
{ label: '分析中', value: runningCount, color: 'var(--running)', border: 'var(--running)' },
|
||||
{ label: '已完成', value: completedCount, color: 'var(--buy)', border: 'var(--buy)' },
|
||||
{ label: '失败', value: failedCount, color: 'var(--sell)', border: 'var(--sell)' },
|
||||
].map(({ label, value, color, border }) => (
|
||||
<div key={label} style={{
|
||||
background: 'var(--bg-surface)',
|
||||
borderRadius: 'var(--radius-md)',
|
||||
padding: 'var(--space-3) var(--space-4)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 'var(--space-3)',
|
||||
}}>
|
||||
<div style={{ width: 3, height: 32, background: border, borderRadius: 2, flexShrink: 0 }} />
|
||||
<div>
|
||||
<div className="text-data" style={{ fontSize: 22, fontWeight: 600, color, lineHeight: 1 }}>{value}</div>
|
||||
<div style={{ fontSize: 'var(--text-xs)', color: 'var(--text-muted)', marginTop: 2 }}>{label}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Tasks Table */}
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@ function PositionsTab() {
|
|||
<Table columns={columns} dataSource={data} rowKey="position_id" loading={loading} pagination={false} size="middle" scroll={{ x: 700 }} />
|
||||
{data.length === 0 && !loading && (
|
||||
<div className="empty-state">
|
||||
<AccountBookOutlined style={{ fontSize: 40, color: 'rgba(0,0,0,0.2)' }} />
|
||||
<AccountBookOutlined style={{ fontSize: 40, color: 'var(--text-muted)' }} />
|
||||
<div className="empty-state-title">暂无持仓</div>
|
||||
<div className="empty-state-description">点击"添加持仓"录入您的股票仓位</div>
|
||||
</div>
|
||||
|
|
@ -391,8 +391,8 @@ function RecommendationsTab() {
|
|||
<div className="card-title">今日建议</div>
|
||||
<Space>
|
||||
{analyzing && progress && (
|
||||
<span className="text-caption">
|
||||
{wsConnected ? '🟢' : '🔴'}
|
||||
<span className="text-caption" style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
|
||||
<span className={`status-dot ${wsConnected ? 'connected' : 'error'}`} />
|
||||
{progress.completed || 0} / {progress.total || 0}
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -411,7 +411,7 @@ function RecommendationsTab() {
|
|||
<Progress
|
||||
percent={Math.round(((progress.completed || 0) / (progress.total || 1)) * 100)}
|
||||
status="active"
|
||||
strokeColor="var(--color-apple-blue)"
|
||||
strokeColor="var(--accent)"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ export default function ReportsViewer() {
|
|||
key: 'ticker',
|
||||
width: 120,
|
||||
render: (text) => (
|
||||
<span style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 15 }}>{text}</span>
|
||||
<span style={{ fontFamily: 'var(--font-ui)', fontWeight: 600, fontSize: 15 }}>{text}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
@ -126,7 +126,7 @@ export default function ReportsViewer() {
|
|||
allowClear
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
prefix={<SearchOutlined style={{ color: 'rgba(0,0,0,0.48)' }} />}
|
||||
prefix={<SearchOutlined style={{ color: 'var(--text-muted)' }} />}
|
||||
size="large"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
|
|
@ -169,10 +169,10 @@ export default function ReportsViewer() {
|
|||
title={
|
||||
selectedReport ? (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<span style={{ fontFamily: 'var(--font-display)', fontSize: 17, fontWeight: 600 }}>
|
||||
<span style={{ fontFamily: 'var(--font-ui)', fontSize: 17, fontWeight: 600 }}>
|
||||
{selectedReport.ticker}
|
||||
</span>
|
||||
<span style={{ color: 'rgba(0,0,0,0.48)', fontSize: 14 }}>{selectedReport.date}</span>
|
||||
<span style={{ color: 'var(--text-muted)', fontSize: 14 }}>{selectedReport.date}</span>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
|
|
@ -201,7 +201,7 @@ export default function ReportsViewer() {
|
|||
styles={{
|
||||
wrapper: { maxWidth: '95vw' },
|
||||
body: { maxHeight: '70vh', overflow: 'auto', padding: 'var(--space-6)' },
|
||||
header: { padding: 'var(--space-4) var(--space-6)', borderBottom: '1px solid rgba(0,0,0,0.08)' },
|
||||
header: { padding: 'var(--space-4) var(--space-6)', borderBottom: '1px solid var(--border)' },
|
||||
}}
|
||||
>
|
||||
{loadingContent ? (
|
||||
|
|
@ -211,7 +211,7 @@ export default function ReportsViewer() {
|
|||
) : reportContent ? (
|
||||
<div
|
||||
style={{
|
||||
fontFamily: 'var(--font-text)',
|
||||
fontFamily: 'var(--font-ui)',
|
||||
lineHeight: 1.8,
|
||||
fontSize: 15,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ export default function ScreeningPanel() {
|
|||
title: (
|
||||
<Tooltip title="营业收入同比增长率">
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
|
||||
营收增速 <QuestionCircleOutlined style={{ fontSize: 10, color: 'rgba(0,0,0,0.48)' }} />
|
||||
营收增速 <QuestionCircleOutlined style={{ fontSize: 10, color: 'var(--text-muted)' }} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
),
|
||||
|
|
@ -97,7 +97,7 @@ export default function ScreeningPanel() {
|
|||
title: (
|
||||
<Tooltip title="净利润同比增长率">
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
|
||||
利润增速 <QuestionCircleOutlined style={{ fontSize: 10, color: 'rgba(0,0,0,0.48)' }} />
|
||||
利润增速 <QuestionCircleOutlined style={{ fontSize: 10, color: 'var(--text-muted)' }} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
),
|
||||
|
|
@ -115,7 +115,7 @@ export default function ScreeningPanel() {
|
|||
title: (
|
||||
<Tooltip title="净资产收益率 = 净利润/净资产">
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
|
||||
ROE <QuestionCircleOutlined style={{ fontSize: 10, color: 'rgba(0,0,0,0.48)' }} />
|
||||
ROE <QuestionCircleOutlined style={{ fontSize: 10, color: 'var(--text-muted)' }} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
),
|
||||
|
|
@ -141,7 +141,7 @@ export default function ScreeningPanel() {
|
|||
title: (
|
||||
<Tooltip title="当前成交量/过去20日平均成交量">
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
|
||||
Vol比 <QuestionCircleOutlined style={{ fontSize: 10, color: 'rgba(0,0,0,0.48)' }} />
|
||||
Vol比 <QuestionCircleOutlined style={{ fontSize: 10, color: 'var(--text-muted)' }} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
),
|
||||
|
|
@ -184,7 +184,7 @@ export default function ScreeningPanel() {
|
|||
<Col xs={24} sm={8}>
|
||||
<div className="card">
|
||||
<div className="text-caption" style={{ marginBottom: 4 }}>筛选模式</div>
|
||||
<div style={{ fontFamily: 'var(--font-text)', fontSize: 15, fontWeight: 500 }}>
|
||||
<div style={{ fontFamily: 'var(--font-ui)', fontSize: 15, fontWeight: 500 }}>
|
||||
{SCREEN_MODES.find(m => m.value === mode)?.label}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -198,7 +198,7 @@ export default function ScreeningPanel() {
|
|||
<Col xs={24} sm={8}>
|
||||
<div className="card">
|
||||
<div className="text-caption" style={{ marginBottom: 4 }}>通过数量</div>
|
||||
<div className="text-data" style={{ fontSize: 28, fontWeight: 600, color: 'var(--color-buy)' }}>{stats.passed}</div>
|
||||
<div className="text-data" style={{ fontSize: 28, fontWeight: 600, color: 'var(--buy)' }}>{stats.passed}</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
|||
Loading…
Reference in New Issue