refactor: CSS残留hex清理+内联样式提取+console.log降级+错误toast补全

- components.css: 10处hex替换为CSS变量(stoploss/watch/signal/alert-change/toast)
- pages.css: 20+处hex替换(ss-dot信号强度/step操作色/summary-tag/trade-type/reason-item)
- auth.css: 2处hex替换(login-error/login-success)
- base.css: 新增30+公共CSS class(cursor-pointer/flex-center/input-mini/icon-sm等)
- index.html: 41处内联style提取为CSS class(93→52)
- app.js: 14处console.log降级为console.debug(减少生产日志噪音)
- app.js: 5处用户可感知catch块补充showToast错误反馈
This commit is contained in:
selfrelease
2026-07-18 09:28:37 +08:00
parent b3c21c6f91
commit 03e095ded0
6 changed files with 140 additions and 93 deletions
+2 -2
View File
@@ -120,14 +120,14 @@
}
.login-error {
color: #ff4444;
color: var(--danger);
font-size: 13px;
margin-bottom: 12px;
text-align: center;
}
.login-success {
color: #00ff88;
color: var(--success);
font-size: 13px;
margin-bottom: 12px;
text-align: center;
+43 -1
View File
@@ -383,7 +383,7 @@
}
.scan-source-select option {
background: #1a1a2e;
background: var(--bg-dark);
color: var(--text-primary);
padding: 10px;
}
@@ -773,4 +773,46 @@
color: var(--text-muted);
line-height: 1.5;
}
/* ===== 内联样式提取的公共 class ===== */
.cursor-pointer { cursor: pointer; }
.margin-top-sm { margin-top: 8px; }
.margin-top-md { margin-top: 15px; }
.margin-bottom-sm { margin-bottom: 8px; }
.margin-bottom-md { margin-bottom: 12px; }
.margin-left-auto { margin-left: auto; }
.margin-right-sm { margin-right: 6px; }
.text-xs { font-size: 11px; }
.text-sm { font-size: 12px; }
.text-md { font-size: 13px; }
.text-muted-2 { color: var(--text-muted); }
.text-secondary-2 { color: var(--text-secondary); }
.text-danger { color: var(--danger); }
.text-success { color: var(--success); }
.text-warning { color: var(--warning); }
.text-info { color: var(--info); }
.font-weight-600 { font-weight: 600; }
.flex-center { display: flex; align-items: center; gap: 4px; }
.flex-wrap-gap { display: flex; flex-wrap: wrap; gap: 6px; }
.grid-2col { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.hidden { display: none; }
.opacity-70 { opacity: 0.7; }
.padding-y-sm { padding: 8px 0; }
.max-height-scroll { max-height: 300px; overflow-y: auto; }
.icon-sm { width: 16px; height: 16px; vertical-align: -2px; margin-right: 4px; }
.icon-xs { width: 14px; height: 14px; vertical-align: -2px; margin-right: 3px; }
.input-mini {
width: 60px; background: var(--bg-dark); border: 1px solid var(--border-color);
color: var(--text-primary); border-radius: 4px; padding: 2px 6px;
}
.input-mini-wide {
width: 80px; background: var(--bg-dark); border: 1px solid var(--border-color);
color: var(--text-primary); border-radius: 4px; padding: 2px 6px;
}
.badge-mini {
background: var(--bg-dark); padding: 1px 6px; border-radius: 8px; font-size: 10px;
}
.border-top-divider {
margin-top: 10px; border-top: 1px solid var(--border-color); padding-top: 10px;
}
+10 -10
View File
@@ -438,7 +438,7 @@
}
.stoploss-sell-btn:hover {
background: #ff6666;
background: var(--danger);
}
/* 基本面弹窗样式 */
@@ -502,7 +502,7 @@
}
.watch-btn.add:hover {
background: #00cc6a;
background: var(--success);
}
.watch-btn.remove {
@@ -592,7 +592,7 @@
}
.signal-chip.triggered {
background: rgba(239,68,68,0.12);
color: #ef4444;
color: var(--danger);
border: 1px solid rgba(239,68,68,0.25);
}
.signal-chip.inactive {
@@ -606,7 +606,7 @@
border-radius: 50%;
display: inline-block;
}
.signal-dot.on { background: #ef4444; }
.signal-dot.on { background: var(--danger); }
.signal-dot.off { background: var(--text-muted); opacity: 0.4; }
.signal-summary {
font-size: 12px;
@@ -614,7 +614,7 @@
border-radius: 6px;
background: var(--bg-secondary);
}
.signal-strong { color: #ef4444; font-weight: 600; }
.signal-strong { color: var(--danger); font-weight: 600; }
.signal-normal { color: var(--text-secondary); }
.signal-none { color: var(--text-muted); }
@@ -1826,7 +1826,7 @@
}
.alert-price {
font-size: 12px;
color: #ffd700;
color: var(--warning);
margin-left: auto;
font-weight: 500;
}
@@ -1839,14 +1839,14 @@
font-weight: 500;
margin-right: 4px;
}
.alert-change.up { color: #ff4444; }
.alert-change.down { color: #00ce9e; }
.alert-change.up { color: var(--up); }
.alert-change.down { color: var(--down); }
.alert-sig-tag {
font-size: 10px;
padding: 1px 6px;
border-radius: 3px;
background: rgba(0, 206, 158, 0.15);
color: #00ce9e;
color: var(--success);
}
.refresh-btn {
@@ -1932,7 +1932,7 @@
.toast-container.info .toast-icon {
background: rgba(100, 150, 255, 0.2);
color: #6496ff;
color: var(--info);
}
.toast-container.success .toast-icon {
+22 -22
View File
@@ -89,8 +89,8 @@
.tech-recommend-reason { font-size: 12px; color: var(--text-secondary); margin-top: 6px; }
.tech-holding-note {
margin-top: 8px; padding: 6px 10px;
background: rgba(255,152,0,0.08); border-radius: 6px;
font-size: 12px; color: #e67e00; line-height: 1.5;
background: rgba(255,170,0,0.08); border-radius: 6px;
font-size: 12px; color: var(--warning); line-height: 1.5;
}
.tech-indicators {
@@ -843,9 +843,9 @@
color: var(--text-secondary);
}
.step-watch .step-action { color: #748ffc; }
.step-buy .step-action { color: #ff6b6b; }
.step-add .step-action { color: #00ce9e; }
.step-hold .step-action { color: #ffd93d; }
.step-buy .step-action { color: var(--danger); }
.step-add .step-action { color: var(--success); }
.step-hold .step-action { color: var(--warning); }
/* 核心信号一句话总结 */
.signal-summary-list {
@@ -869,11 +869,11 @@
}
.summary-tag.core {
background: rgba(255, 107, 107, 0.15);
color: #ff6b6b;
color: var(--danger);
}
.summary-tag.normal {
background: rgba(0, 206, 158, 0.15);
color: #00ce9e;
color: var(--success);
}
.summary-tag.aux {
background: rgba(255,255,255,0.08);
@@ -1287,13 +1287,13 @@
flex-shrink: 0;
}
.ss-dot.s85 { background: #ff4444; }
.ss-dot.s80 { background: #ff6b35; }
.ss-dot.s75 { background: #4CAF50; }
.ss-dot.s70 { background: #2196F3; }
.ss-dot.s65 { background: #9C27B0; }
.ss-dot.s60 { background: #FF9800; }
.ss-dot.s55 { background: #607D8B; }
.ss-dot.s85 { background: var(--sig-85); }
.ss-dot.s80 { background: var(--sig-80); }
.ss-dot.s75 { background: var(--sig-75); }
.ss-dot.s70 { background: var(--sig-70); }
.ss-dot.s65 { background: var(--sig-65); }
.ss-dot.s60 { background: var(--sig-60); }
.ss-dot.s55 { background: var(--sig-55); }
.ss-name {
font-weight: 600;
@@ -1315,7 +1315,7 @@
.ss-badge.active {
background: rgba(0, 200, 83, 0.2);
color: #00c853;
color: var(--down);
border: 1px solid rgba(0, 200, 83, 0.4);
}
@@ -1431,12 +1431,12 @@
.trade-type-tag.buy {
background: rgba(76, 175, 80, 0.2);
color: #4caf50;
color: var(--success);
}
.trade-type-tag.sell, .trade-type-tag.partial_sell {
background: rgba(244, 67, 54, 0.2);
color: #f44336;
color: var(--danger);
}
.trade-stock {
@@ -1451,7 +1451,7 @@
}
.trade-fee {
color: #ff9800;
color: var(--warning);
font-size: 11px;
}
@@ -1474,22 +1474,22 @@
.result-reason-item.reason-limit {
background: rgba(255, 152, 0, 0.1);
border-left: 3px solid #ff9800;
border-left: 3px solid var(--warning);
}
.result-reason-item.reason-cash {
background: rgba(33, 150, 243, 0.1);
border-left: 3px solid #2196f3;
border-left: 3px solid var(--info);
}
.result-reason-item.reason-sell {
background: rgba(244, 67, 54, 0.08);
border-left: 3px solid #f44336;
border-left: 3px solid var(--danger);
}
.result-reason-item.reason-buy {
background: rgba(76, 175, 80, 0.08);
border-left: 3px solid #4caf50;
border-left: 3px solid var(--success);
}
.result-reason-item.reason-info {
+18 -13
View File
@@ -541,10 +541,10 @@
const response = await axios.get('/api/me');
if (response.data.success && response.data.user) {
this.currentUser = response.data.user;
console.log('已登录:', this.currentUser.username);
console.debug('已登录:', this.currentUser.username);
}
} catch (e) {
console.log('未登录');
console.debug('未登录');
} finally {
this.checkingAuth = false;
}
@@ -791,7 +791,7 @@
return response.data.data.stock_name;
}
} catch (e) {
console.log('获取股票名称失败');
console.debug('获取股票名称失败');
}
return null;
},
@@ -1235,19 +1235,19 @@
// 版本不匹配则强制刷新
if (cacheVer < ALERT_CACHE_VERSION) {
console.log(`缓存版本过旧(v${cacheVer} < v${ALERT_CACHE_VERSION}),强制刷新`);
console.debug(`缓存版本过旧(v${cacheVer} < v${ALERT_CACHE_VERSION}),强制刷新`);
} else {
this.stockAlerts = cachedAlerts;
console.log(`加载缓存数据: ${cachedAlerts.length}条, 更新时间: ${lastUpdate}, v${cacheVer}`);
console.debug(`加载缓存数据: ${cachedAlerts.length}条, 更新时间: ${lastUpdate}, v${cacheVer}`);
if (cacheDate === today) {
console.log('缓存是今天的,无需更新');
console.debug('缓存是今天的,无需更新');
this.alertsLoading = false;
this.updateHoldingPricesFromAlerts();
const cachedCodes = new Set(cachedAlerts.map(a => a.code));
const newStocks = allStocks.filter(s => !cachedCodes.has(s.code));
if (newStocks.length > 0) {
console.log(`发现${newStocks.length}只新股票,增量更新`);
console.debug(`发现${newStocks.length}只新股票,增量更新`);
await this.analyzeNewStocks(newStocks, today);
}
return;
@@ -1255,7 +1255,7 @@
}
}
} catch (err) {
console.log('加载缓存失败,将重新分析:', err);
console.debug('加载缓存失败,将重新分析:', err);
}
}
@@ -1264,7 +1264,7 @@
this.alertsProgress = { current: 0, total: allStocks.length };
try {
console.log(`开始信号分析 ${allStocks.length} 只股票...`);
console.debug(`开始信号分析 ${allStocks.length} 只股票...`);
const startTime = Date.now();
const response = await axios.post('/api/signal_alerts', {
@@ -1273,14 +1273,14 @@
});
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
console.log(`信号分析完成,耗时 ${elapsed}s`);
console.debug(`信号分析完成,耗时 ${elapsed}s`);
if (response.data.success) {
const results = response.data.results || [];
this.alertsProgress.current = allStocks.length;
this.stockAlerts = [...results];
console.log(`成功: ${response.data.success_count}`);
console.debug(`成功: ${response.data.success_count}`);
// 3. 保存到缓存
this.saveAlertsCache(this.stockAlerts);
@@ -1296,7 +1296,7 @@
} catch (err) {
console.error('批量分析请求失败:', err);
// 回退到逐个分析
console.log('回退到逐个分析模式...');
console.debug('回退到逐个分析模式...');
await this.analyzeStocksOneByOne(allStocks, today, forceRefresh);
}
@@ -1445,7 +1445,7 @@
lastUpdate: new Date().toISOString().replace('T', ' ').split('.')[0],
version: ALERT_CACHE_VERSION
});
console.log('分析结果已保存到缓存');
console.debug('分析结果已保存到缓存');
} catch (err) {
console.error('保存缓存失败:', err);
}
@@ -1562,6 +1562,7 @@
await this.loadAvailableCash();
} catch (err) {
console.error('加载交易记录失败:', err);
this.showToast('加载交易记录失败', 'error');
}
},
@@ -1752,6 +1753,7 @@
}
} catch (error) {
console.error('检查止损失败:', error);
this.showToast('检查止损失败,请稍后重试', 'error');
}
},
@@ -2161,6 +2163,7 @@
}
} catch (err) {
console.error('技术信号检测失败:', err);
this.showToast('技术信号检测失败,请稍后重试', 'error');
} finally {
this.techLoading = false;
}
@@ -2187,6 +2190,7 @@
}
} catch (err) {
console.error('批量技术信号检测失败:', err);
this.showToast('批量检测失败,请稍后重试', 'error');
} finally {
this.techLoading = false;
}
@@ -2241,6 +2245,7 @@
}
} catch (err) {
console.error('获取扫描结果失败:', err);
this.showToast('获取扫描结果失败,请稍后重试', 'error');
} finally {
this.fullScanLoading = false;
}
+45 -45
View File
@@ -21,10 +21,10 @@
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📈</text></svg>">
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="/static/css/base.css?v=20260718v1">
<link rel="stylesheet" href="/static/css/components.css?v=20260318v2">
<link rel="stylesheet" href="/static/css/auth.css?v=20260318v1">
<link rel="stylesheet" href="/static/css/pages.css?v=20260718v1">
<link rel="stylesheet" href="/static/css/base.css?v=20260718v2">
<link rel="stylesheet" href="/static/css/components.css?v=20260718v1">
<link rel="stylesheet" href="/static/css/auth.css?v=20260718v1">
<link rel="stylesheet" href="/static/css/pages.css?v=20260718v2">
<link rel="stylesheet" href="/static/css/scan.css?v=20260718v1">
<link rel="stylesheet" href="/static/css/responsive.css?v=20260718v1">
</head>
@@ -193,7 +193,7 @@
<div class="current-model">策略: 交易信号实战体系</div>
</div>
<div class="user-info">
<span class="username" @click="showPasswordModal = true" style="cursor: pointer;">{{ currentUser?.username }}</span>
<span class="username" @click="showPasswordModal = true" class="cursor-pointer">{{ currentUser?.username }}</span>
<button class="logout-btn" @click="handleLogout">退出</button>
</div>
</div>
@@ -1412,7 +1412,7 @@
</div><!-- 买入分析子页面结束 -->
<!-- (模拟交易已移至模型tab) -->
<div style="display:none">
<div class="hidden">
<!-- 模拟交易统计卡片 -->
<div class="sim-stats-card">
<div class="sim-stats-header">
@@ -1463,9 +1463,9 @@
</div>
<!-- 模拟 vs 实盘 对比看板 -->
<div class="sim-compare-panel" v-if="simStats.total_fees > 0 || simStats.total_trades > 0">
<div class="sim-compare-header" @click="compareExpanded = !compareExpanded" style="cursor:pointer;">
<div class="sim-compare-header" @click="compareExpanded = !compareExpanded" class="cursor-pointer">
<span style="font-size:12px;font-weight:600;color:#aaa;">
<svg style="width:14px;height:14px;vertical-align:-2px;margin-right:3px;" viewBox="0 0 24 24" fill="none" stroke="#ff9f43" stroke-width="2"><path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/></svg>
<svg class="icon-xs" viewBox="0 0 24 24" fill="none" stroke="#ff9f43" stroke-width="2"><path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/></svg>
模拟 vs 实盘对比
</span>
<span :class="['toggle-icon', { expanded: compareExpanded }]" style="color:#888;font-size:10px;"></span>
@@ -1511,12 +1511,12 @@
<h4>⚙️ 算法配置 <span v-if="smartAlgoIsDefault" style="color:#ffd93d;font-size:12px;">(默认)</span></h4>
<span :class="['toggle-icon', { expanded: smartConfigExpanded }]"></span>
</div>
<div v-show="smartConfigExpanded" style="padding:8px 0;">
<div v-show="smartConfigExpanded" class="padding-y-sm">
<!-- 模板选择 -->
<div v-if="smartTemplates.length > 0" style="margin-bottom:12px;">
<div style="font-size:13px;font-weight:600;margin-bottom:6px;color:#ccc;">快速选择算法模板:</div>
<div style="display:flex;flex-wrap:wrap;gap:6px;">
<div class="flex-wrap-gap">
<button v-for="tpl in smartTemplates" :key="tpl.name"
class="sim-btn"
:class="{'auto': smartAlgoConfig?.algo_name === tpl.display_name}"
@@ -1546,39 +1546,39 @@
</div>
<!-- 编辑模式 -->
<div v-if="smartConfigEditing" style="margin-top:10px;border-top:1px solid #333;padding-top:10px;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;">
<label style="display:flex;align-items:center;gap:4px;">
<div v-if="smartConfigEditing" class="border-top-divider">
<div class="grid-2col">
<label class="flex-center">
总本金: <input type="number" v-model.number="smartAlgoConfig.total_capital"
style="width:80px;background:#1a1a2e;border:1px solid #444;color:#fff;border-radius:4px;padding:2px 6px;">
class="input-mini-wide">
</label>
<label style="display:flex;align-items:center;gap:4px;">
<label class="flex-center">
止盈%: <input type="number" v-model.number="smartAlgoConfig.take_profit_pct" step="1"
style="width:60px;background:#1a1a2e;border:1px solid #444;color:#fff;border-radius:4px;padding:2px 6px;">
class="input-mini">
</label>
<label style="display:flex;align-items:center;gap:4px;">
<label class="flex-center">
止损%: <input type="number" v-model.number="smartAlgoConfig.stop_loss_pct" step="1"
style="width:60px;background:#1a1a2e;border:1px solid #444;color:#fff;border-radius:4px;padding:2px 6px;">
class="input-mini">
</label>
<label style="display:flex;align-items:center;gap:4px;">
<label class="flex-center">
仓位%: <input type="number" v-model.number="smartAlgoConfig.position_pct" step="1"
style="width:60px;background:#1a1a2e;border:1px solid #444;color:#fff;border-radius:4px;padding:2px 6px;">
class="input-mini">
</label>
<label style="display:flex;align-items:center;gap:4px;">
<label class="flex-center">
最长天数: <input type="number" v-model.number="smartAlgoConfig.max_hold_days"
style="width:60px;background:#1a1a2e;border:1px solid #444;color:#fff;border-radius:4px;padding:2px 6px;">
class="input-mini">
</label>
<label style="display:flex;align-items:center;gap:4px;">
<label class="flex-center">
部分止盈%: <input type="number" v-model.number="smartAlgoConfig.partial_exit_pct"
style="width:60px;background:#1a1a2e;border:1px solid #444;color:#fff;border-radius:4px;padding:2px 6px;">
class="input-mini">
</label>
<label style="display:flex;align-items:center;gap:4px;">
<label class="flex-center">
保本触发%: <input type="number" v-model.number="smartAlgoConfig.breakeven_at"
style="width:60px;background:#1a1a2e;border:1px solid #444;color:#fff;border-radius:4px;padding:2px 6px;">
class="input-mini">
</label>
<label style="display:flex;align-items:center;gap:4px;">
<label class="flex-center">
确认天数: <input type="number" v-model.number="smartAlgoConfig.sell_confirm_days"
style="width:60px;background:#1a1a2e;border:1px solid #444;color:#fff;border-radius:4px;padding:2px 6px;">
class="input-mini">
</label>
</div>
<div style="margin-top:8px;display:flex;gap:8px;">
@@ -1602,13 +1602,13 @@
<!-- 今日交易 -->
<div class="sim-section collapsible-section">
<div class="sim-section-header" @click="todayTradesExpanded = !todayTradesExpanded" style="cursor:pointer;">
<h4 class="section-title"><svg style="width:16px;height:16px;vertical-align:-2px;margin-right:4px;" viewBox="0 0 24 24" fill="none" stroke="#4ecdc4" stroke-width="2"><path d="M12 20V10"/><path d="M18 20V4"/><path d="M6 20v-4"/></svg>今日交易 <span class="badge">{{ todayTrades.length }}</span></h4>
<span :class="['toggle-icon', { expanded: todayTradesExpanded }]" style="color:#888;"></span>
<div class="sim-section-header" @click="todayTradesExpanded = !todayTradesExpanded" class="cursor-pointer">
<h4 class="section-title"><svg class="icon-sm" viewBox="0 0 24 24" fill="none" stroke="#4ecdc4" stroke-width="2"><path d="M12 20V10"/><path d="M18 20V4"/><path d="M6 20v-4"/></svg>今日交易 <span class="badge">{{ todayTrades.length }}</span></h4>
<span :class="['toggle-icon', { expanded: todayTradesExpanded }]" class="text-muted-2"></span>
</div>
<div v-show="todayTradesExpanded && todayTrades.length === 0" class="empty-tip">今日暂无交易</div>
<div v-show="todayTradesExpanded" class="sim-trades-list">
<div v-for="trade in todayTrades" :key="trade.id" :class="['sim-trade-item', trade.trade_type]" @click="loadFundamental(trade.stock_code, trade.stock_name)" style="cursor: pointer;">
<div v-for="trade in todayTrades" :key="trade.id" :class="['sim-trade-item', trade.trade_type]" @click="loadFundamental(trade.stock_code, trade.stock_name)" class="cursor-pointer">
<div class="sim-trade-type" :class="trade.trade_type">
{{ trade.trade_type === 'buy' ? '买' : '卖' }}
</div>
@@ -1633,9 +1633,9 @@
<!-- 模拟持仓列表(增强版:显示算法状态) -->
<div class="sim-section collapsible-section">
<div class="sim-section-header" @click="simPositionsExpanded = !simPositionsExpanded" style="cursor:pointer;">
<h4 class="section-title"><svg style="width:16px;height:16px;vertical-align:-2px;margin-right:4px;" viewBox="0 0 24 24" fill="none" stroke="#00ce9e" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>当前持仓 <span class="badge">{{ simPositions.length }}</span></h4>
<span :class="['toggle-icon', { expanded: simPositionsExpanded }]" style="color:#888;"></span>
<div class="sim-section-header" @click="simPositionsExpanded = !simPositionsExpanded" class="cursor-pointer">
<h4 class="section-title"><svg class="icon-sm" viewBox="0 0 24 24" fill="none" stroke="#00ce9e" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>当前持仓 <span class="badge">{{ simPositions.length }}</span></h4>
<span :class="['toggle-icon', { expanded: simPositionsExpanded }]" class="text-muted-2"></span>
</div>
<div v-show="simPositionsExpanded && simPositions.length === 0" class="empty-tip">暂无持仓</div>
<div v-show="simPositionsExpanded" class="sim-positions-list">
@@ -1674,13 +1674,13 @@
<!-- 信号日志(可折叠) -->
<div class="sim-section collapsible-section">
<div class="sim-section-header" @click="smartSignalExpanded = !smartSignalExpanded" style="cursor:pointer;">
<h4 class="section-title"><svg style="width:16px;height:16px;vertical-align:-2px;margin-right:4px;" viewBox="0 0 24 24" fill="none" stroke="#ffd93d" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>信号日志 <span class="badge">{{ smartSignals.length }}</span></h4>
<span :class="['toggle-icon', { expanded: smartSignalExpanded }]" style="color:#888;"></span>
<div class="sim-section-header" @click="smartSignalExpanded = !smartSignalExpanded" class="cursor-pointer">
<h4 class="section-title"><svg class="icon-sm" viewBox="0 0 24 24" fill="none" stroke="#ffd93d" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>信号日志 <span class="badge">{{ smartSignals.length }}</span></h4>
<span :class="['toggle-icon', { expanded: smartSignalExpanded }]" class="text-muted-2"></span>
</div>
<div v-show="smartSignalExpanded">
<div v-if="smartSignals.length === 0" class="empty-tip">暂无信号记录</div>
<div v-else style="max-height:300px;overflow-y:auto;">
<div v-else class="max-height-scroll">
<div v-for="sig in smartSignals" :key="sig.id"
style="padding:6px 0;border-bottom:1px solid #1a1a2e;font-size:12px;display:flex;gap:8px;align-items:flex-start;">
<span :style="{
@@ -1708,13 +1708,13 @@
<!-- 模拟交易记录 -->
<div class="sim-section collapsible-section">
<div class="sim-section-header" @click="simTradesExpanded = !simTradesExpanded" style="cursor:pointer;">
<h4 class="section-title"><svg style="width:16px;height:16px;vertical-align:-2px;margin-right:4px;" viewBox="0 0 24 24" fill="none" stroke="#a78bfa" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>交易记录 <span class="badge">{{ simTrades.length }}</span></h4>
<span :class="['toggle-icon', { expanded: simTradesExpanded }]" style="color:#888;"></span>
<div class="sim-section-header" @click="simTradesExpanded = !simTradesExpanded" class="cursor-pointer">
<h4 class="section-title"><svg class="icon-sm" viewBox="0 0 24 24" fill="none" stroke="#a78bfa" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>交易记录 <span class="badge">{{ simTrades.length }}</span></h4>
<span :class="['toggle-icon', { expanded: simTradesExpanded }]" class="text-muted-2"></span>
</div>
<div v-show="simTradesExpanded && simTrades.length === 0" class="empty-tip">暂无交易记录</div>
<div v-show="simTradesExpanded" class="sim-trades-list">
<div v-for="trade in simTrades" :key="trade.id" :class="['sim-trade-item', trade.trade_type]" @click="loadFundamental(trade.stock_code, trade.stock_name)" style="cursor: pointer;">
<div v-for="trade in simTrades" :key="trade.id" :class="['sim-trade-item', trade.trade_type]" @click="loadFundamental(trade.stock_code, trade.stock_name)" class="cursor-pointer">
<div class="sim-trade-type" :class="trade.trade_type">
{{ trade.trade_type === 'buy' ? '买' : '卖' }}
</div>
@@ -1924,12 +1924,12 @@
<!-- 模拟持仓和交易历史直接复用现有逻辑 -->
<div class="sim-section collapsible-section" v-if="simPositions.length > 0">
<div class="sim-section-header" style="cursor:pointer;" @click="simPositionsExpanded = !simPositionsExpanded">
<div class="sim-section-header" class="cursor-pointer" @click="simPositionsExpanded = !simPositionsExpanded">
<h4 class="section-title">持仓 <span class="badge">{{ simPositions.length }}</span></h4>
<span class="expand-arrow" :class="{ expanded: simPositionsExpanded }"></span>
</div>
<div v-show="simPositionsExpanded" class="sim-positions-list">
<div v-for="pos in simPositions" :key="pos.stock_code" class="sim-position-item" @click="loadFundamental(pos.stock_code, pos.stock_name)" style="cursor:pointer;">
<div v-for="pos in simPositions" :key="pos.stock_code" class="sim-position-item" @click="loadFundamental(pos.stock_code, pos.stock_name)" class="cursor-pointer">
<div class="sim-pos-header">
<span class="sim-pos-stock">{{ pos.stock_code }} {{ pos.stock_name }}</span>
<span class="sim-pos-profit" :class="(pos.unrealized_profit || 0) >= 0 ? 'profit' : 'loss'">