UI/UX优化: CSS变量化 + 错误反馈增强 + 按钮type属性

- CSS: 全部硬编码hex颜色替换为CSS变量(base/scan/pages/responsive/admin)
- CSS: 硬编码字体提取为--font-sans/--font-mono变量
- CSS: 新增打印色/渐变色/辅助色等20+变量定义
- CSS: 修复scan.css lint警告(line-clamp标准属性+空规则集)
- app.js: 16处用户可感知catch块补充showToast错误反馈
- app.js: 全部console.log降级为console.debug
- index.html: 76个button补充type属性
- index.html: 内联hex颜色/SVG stroke/Vue :style替换为CSS变量
- admin.html: 残留hex替换为CSS变量
- 全部CSS版本号更新用于缓存刷新
This commit is contained in:
selfrelease
2026-07-18 09:53:34 +08:00
parent 03e095ded0
commit bb5a72767f
7 changed files with 267 additions and 209 deletions
+10 -8
View File
@@ -22,6 +22,8 @@
--danger: #ff4444;
--warning: #fbbf24;
--info: #3b82f6;
--info-light: #60a5fa;
--purple: #a855f7;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
@@ -60,11 +62,11 @@
.stat-card { background: var(--bg-glass); border-radius: 14px; padding: 14px; display: flex; align-items: center; gap: 12px; }
.stat-icon { width: 36px; height: 36px; border-radius: 10px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.stat-icon svg { width: 18px; height: 18px; }
.stat-icon.blue { background: rgba(59,130,246,0.15); color: #3b82f6; }
.stat-icon.green { background: rgba(0,255,136,0.15); color: #00ff88; }
.stat-icon.orange { background: rgba(255,170,0,0.15); color: #ffaa00; }
.stat-icon.red { background: rgba(255,68,68,0.15); color: #ff4444; }
.stat-icon.purple { background: rgba(168,85,247,0.15); color: #a855f7; }
.stat-icon.blue { background: rgba(59,130,246,0.15); color: var(--info); }
.stat-icon.green { background: rgba(0,255,136,0.15); color: var(--success); }
.stat-icon.orange { background: rgba(255,170,0,0.15); color: var(--warning); }
.stat-icon.red { background: rgba(255,68,68,0.15); color: var(--danger); }
.stat-icon.purple { background: rgba(168,85,247,0.15); color: var(--purple); }
.stat-info .stat-val { font-size: 22px; font-weight: 700; letter-spacing: -0.5px; }
.stat-info .stat-lbl { font-size: 11px; color: var(--text-muted); margin-top: 1px; }
@@ -338,7 +340,7 @@
</div>
<div class="sys-info-row">
<span class="lbl">🌐 外网IP</span>
<span class="val" style="font-family:monospace;color:#60a5fa;">{{ dash.server_info.external_ip }}</span>
<span class="val" style="font-family:monospace;color:var(--info-light);">{{ dash.server_info.external_ip }}</span>
</div>
<div class="sys-info-row">
<span class="lbl">🏠 内网IP</span>
@@ -346,11 +348,11 @@
</div>
<div class="sys-info-row">
<span class="lbl">🔌 应用端口</span>
<span class="val" style="font-family:monospace;color:#fbbf24;">{{ dash.server_info.app_port }}</span>
<span class="val" style="font-family:monospace;color:var(--accent);">{{ dash.server_info.app_port }}</span>
</div>
<div class="sys-info-row">
<span class="lbl">🌍 访问域名</span>
<span class="val" style="font-family:monospace;color:#34d399;">{{ dash.server_info.scheme }}://{{ dash.server_info.domain }}</span>
<span class="val" style="font-family:monospace;color:var(--success);">{{ dash.server_info.scheme }}://{{ dash.server_info.domain }}</span>
</div>
<div class="sys-info-row">
<span class="lbl">🖥️ 主机名</span>
+118 -118
View File
@@ -21,12 +21,12 @@
<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=20260718v2">
<link rel="stylesheet" href="/static/css/base.css?v=20260719v2">
<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">
<link rel="stylesheet" href="/static/css/pages.css?v=20260719v2">
<link rel="stylesheet" href="/static/css/scan.css?v=20260719v3">
<link rel="stylesheet" href="/static/css/responsive.css?v=20260719v1">
</head>
<body>
{% raw %}
@@ -47,8 +47,8 @@
<div class="confirm-dialog">
<div class="confirm-message">{{ confirmMessage }}</div>
<div class="confirm-buttons">
<button class="confirm-btn cancel" @click="handleConfirmCancel">取消</button>
<button class="confirm-btn ok" @click="handleConfirmOk">确定</button>
<button type="button" class="confirm-btn cancel" @click="handleConfirmCancel">取消</button>
<button type="button" class="confirm-btn ok" @click="handleConfirmOk">确定</button>
</div>
</div>
</div>
@@ -65,7 +65,7 @@
</span>
{{ smartResultData?.title || '执行结果' }}
</div>
<button class="modal-close-btn" @click="smartResultVisible = false">×</button>
<button type="button" class="modal-close-btn" @click="smartResultVisible = false">×</button>
</div>
<div class="smart-result-body" v-if="smartResultData">
<!-- 算法标识 -->
@@ -105,7 +105,7 @@
</div>
</div>
<div class="smart-result-footer">
<button class="confirm-btn ok" @click="smartResultVisible = false">我知道了</button>
<button type="button" class="confirm-btn ok" @click="smartResultVisible = false">我知道了</button>
</div>
</div>
</div>
@@ -116,9 +116,9 @@
<div v-if="showLoginModal" class="modal-overlay" @click.self="showLoginModal = false" role="dialog" aria-modal="true" aria-label="登录">
<div class="login-modal">
<div class="login-tabs">
<button :class="['login-tab', { active: !isRegister }]" @click="isRegister = false; loginError = ''">登录</button>
<button :class="['login-tab', { active: isRegister }]" @click="isRegister = true; loginError = ''">注册</button>
<button class="modal-close-btn" @click="showLoginModal = false">×</button>
<button type="button" :class="['login-tab', { active: !isRegister }]" @click="isRegister = false; loginError = ''">登录</button>
<button type="button" :class="['login-tab', { active: isRegister }]" @click="isRegister = true; loginError = ''">注册</button>
<button type="button" class="modal-close-btn" @click="showLoginModal = false">×</button>
</div>
<div class="login-body">
<div class="login-field">
@@ -130,7 +130,7 @@
<input v-model="loginForm.password" type="password" :placeholder="isRegister ? '设置密码(至少6位)' : '请输入密码'" @keyup.enter="handleLogin">
</div>
<div v-if="loginError" class="login-error">{{ loginError }}</div>
<button class="login-submit" @click="handleLogin" :disabled="loginLoading">
<button type="button" class="login-submit" @click="handleLogin" :disabled="loginLoading">
{{ loginLoading ? '处理中...' : (isRegister ? '注册' : '登录') }}
</button>
</div>
@@ -160,8 +160,8 @@
</div>
<div class="login-modal login-modal-inline">
<div class="login-tabs">
<button :class="['login-tab', { active: !isRegister }]" @click="isRegister = false; loginError = ''">登录</button>
<button :class="['login-tab', { active: isRegister }]" @click="isRegister = true; loginError = ''">注册</button>
<button type="button" :class="['login-tab', { active: !isRegister }]" @click="isRegister = false; loginError = ''">登录</button>
<button type="button" :class="['login-tab', { active: isRegister }]" @click="isRegister = true; loginError = ''">注册</button>
</div>
<div class="login-body">
<div class="login-field">
@@ -173,7 +173,7 @@
<input v-model="loginForm.password" type="password" :placeholder="isRegister ? '设置密码(至少6位)' : '请输入密码'" @keyup.enter="handleLogin">
</div>
<div v-if="loginError" class="login-error">{{ loginError }}</div>
<button class="login-submit" @click="handleLogin" :disabled="loginLoading">
<button type="button" class="login-submit" @click="handleLogin" :disabled="loginLoading">
{{ loginLoading ? '处理中...' : (isRegister ? '注册' : '登录') }}
</button>
</div>
@@ -194,16 +194,16 @@
</div>
<div class="user-info">
<span class="username" @click="showPasswordModal = true" class="cursor-pointer">{{ currentUser?.username }}</span>
<button class="logout-btn" @click="handleLogout">退出</button>
<button type="button" class="logout-btn" @click="handleLogout">退出</button>
</div>
</div>
<!-- 顶部导航 -->
<div class="nav-tabs nav-tabs-compact" role="tablist">
<button :class="['nav-tab', { active: activeTab === 'alerts' }]" role="tab" :aria-selected="activeTab === 'alerts'" @click="activeTab = 'alerts'">提醒</button>
<button :class="['nav-tab', { active: activeTab === 'trades' }]" role="tab" :aria-selected="activeTab === 'trades'" @click="activeTab = 'trades'; loadTrades()">交易</button>
<button :class="['nav-tab', { active: activeTab === 'analysis' }]" role="tab" :aria-selected="activeTab === 'analysis'" @click="activeTab = 'analysis'; if (analysisSubTab === 'scan') fetchFullScanResults(1)">分析</button>
<button :class="['nav-tab', { active: activeTab === 'model' }]" role="tab" :aria-selected="activeTab === 'model'" @click="activeTab = 'model'">模型</button>
<button type="button" :class="['nav-tab', { active: activeTab === 'alerts' }]" role="tab" :aria-selected="activeTab === 'alerts'" @click="activeTab = 'alerts'">提醒</button>
<button type="button" :class="['nav-tab', { active: activeTab === 'trades' }]" role="tab" :aria-selected="activeTab === 'trades'" @click="activeTab = 'trades'; loadTrades()">交易</button>
<button type="button" :class="['nav-tab', { active: activeTab === 'analysis' }]" role="tab" :aria-selected="activeTab === 'analysis'" @click="activeTab = 'analysis'; if (analysisSubTab === 'scan') fetchFullScanResults(1)">分析</button>
<button type="button" :class="['nav-tab', { active: activeTab === 'model' }]" role="tab" :aria-selected="activeTab === 'model'" @click="activeTab = 'model'">模型</button>
</div>
<!-- 提醒页面 -->
@@ -226,26 +226,26 @@
<span class="summary-label">建议卖出</span>
<span class="summary-value">{{ sellAlerts.length + holdingAlerts.filter(a => a.signalType === 'sell').length }}</span>
</div>
<button class="refresh-btn-inline" @click="refreshAlerts(true)" :disabled="alertsLoading">
<button type="button" class="refresh-btn-inline" @click="refreshAlerts(true)" :disabled="alertsLoading">
{{ alertsLoading ? (alertsProgress.total > 0 ? `${alertsProgress.current}/${alertsProgress.total}` : '加载中...') : '刷新' }}
</button>
</div>
<!-- 子导航 -->
<div class="sub-tabs">
<button :class="['sub-tab', { active: alertSubTab === 'holding' }]" @click="alertSubTab = 'holding'">
<button type="button" :class="['sub-tab', { active: alertSubTab === 'holding' }]" @click="alertSubTab = 'holding'">
持有 <span v-if="holdingStocks.length" class="sub-badge">{{ holdingStocks.length }}</span>
</button>
<button :class="['sub-tab', { active: alertSubTab === 'watching' }]" @click="alertSubTab = 'watching'">
<button type="button" :class="['sub-tab', { active: alertSubTab === 'watching' }]" @click="alertSubTab = 'watching'">
关注 <span v-if="watchingStocks.length" class="sub-badge">{{ watchingStocks.length }}</span>
</button>
<button class="add-watch-btn" @click="showAddWatch = !showAddWatch" title="添加关注">+</button>
<button type="button" class="add-watch-btn" @click="showAddWatch = !showAddWatch" title="添加关注">+</button>
</div>
<!-- 添加关注输入框 -->
<div v-if="showAddWatch" class="add-watch-input">
<input v-model="newWatchCode" type="text" placeholder="输入股票代码" maxlength="6" @keyup.enter="addToWatch">
<button @click="addToWatch" :disabled="!newWatchCode || newWatchCode.length < 6">添加</button>
<button type="button" @click="addToWatch" :disabled="!newWatchCode || newWatchCode.length < 6">添加</button>
</div>
<div v-if="alertsLoading" class="loading">
@@ -285,7 +285,7 @@
<div class="alert-bottom">
<div class="alert-desc">{{ alert.reason }}</div>
<span class="recommend-rate sell">{{ alert.recommendRate }}%</span>
<button class="alert-action-btn sell" @click.stop="quickTradeFromAlert(alert, 'sell')">卖出</button>
<button type="button" class="alert-action-btn sell" @click.stop="quickTradeFromAlert(alert, 'sell')">卖出</button>
</div>
</div>
</div>
@@ -318,7 +318,7 @@
<div class="alert-bottom">
<div class="alert-desc">{{ alert.reason }}</div>
<span class="recommend-rate" :class="alert.recommendText === '加仓' ? 'buy' : 'hold'">{{ alert.recommendRate }}%</span>
<button v-if="alert.recommendText === '加仓'" class="alert-action-btn buy" @click.stop="quickTradeFromAlert(alert, 'buy')">加仓</button>
<button type="button"v-if="alert.recommendText === '加仓'" class="alert-action-btn buy" @click.stop="quickTradeFromAlert(alert, 'buy')">加仓</button>
</div>
</div>
</div>
@@ -387,7 +387,7 @@
<div class="alert-desc">{{ alert.reason }}</div>
<div class="alert-desc holding-note" v-if="alert.holdingNote">{{ alert.holdingNote }}</div>
<span class="recommend-rate buy">{{ alert.recommendRate }}%</span>
<button class="alert-action-btn buy" @click.stop="quickTradeFromAlert(alert, 'buy')">买入</button>
<button type="button" class="alert-action-btn buy" @click.stop="quickTradeFromAlert(alert, 'buy')">买入</button>
</div>
</div>
</div>
@@ -419,7 +419,7 @@
<div class="alert-bottom">
<div class="alert-desc">{{ alert.reason }}</div>
<span class="recommend-rate sell">{{ alert.recommendRate }}%</span>
<button class="alert-action-btn sell" @click.stop="quickTradeFromAlert(alert, 'sell')">卖出</button>
<button type="button" class="alert-action-btn sell" @click.stop="quickTradeFromAlert(alert, 'sell')">卖出</button>
</div>
</div>
</div>
@@ -461,20 +461,20 @@
<div class="fundamental-header">
<h3>{{ fundamentalData?.stock_name || '股票' }} 基本面</h3>
<span class="fundamental-code">{{ fundamentalData?.stock_code }}</span>
<button class="modal-close" @click="showFundamentalModal = false">×</button>
<button type="button" class="modal-close" @click="showFundamentalModal = false">×</button>
</div>
<div v-if="fundamentalData" class="fundamental-actions">
<button v-if="!searchHistory.some(h => h.code === fundamentalData.stock_code)"
<button type="button" v-if="!searchHistory.some(h => h.code === fundamentalData.stock_code)"
class="watch-btn add"
@click="addToWatchFromFundamental">
★ 添加关注
</button>
<button v-else
<button type="button" v-else
class="watch-btn remove"
@click="removeFromWatchFromFundamental">
☆ 取消关注
</button>
<button class="ai-analyze-btn"
<button type="button" class="ai-analyze-btn"
@click="aiAnalyze(fundamentalData.stock_code, fundamentalData.stock_name)"
:disabled="aiAnalyzing">
{{ aiAnalyzing ? '分析中...' : 'AI分析' }}
@@ -503,7 +503,7 @@
<span class="loading-dot"></span> AI正在分析中...
</div>
<button v-if="!aiAnalyzing" class="ai-close-btn" @click="aiAnalysisResult = ''; aiReasoningResult = ''">关闭</button>
<button type="button" v-if="!aiAnalyzing" class="ai-close-btn" @click="aiAnalysisResult = ''; aiReasoningResult = ''">关闭</button>
</div>
<div v-if="fundamentalLoading" class="fundamental-loading">
@@ -641,10 +641,10 @@
<div class="kline-header">
<h4>📈 股价走势</h4>
<div class="kline-period-tabs">
<button :class="['kline-tab', { active: klinePeriod === 'weekly' }]" @click="changeKlinePeriod('weekly')"></button>
<button :class="['kline-tab', { active: klinePeriod === 'monthly' }]" @click="changeKlinePeriod('monthly')"></button>
<button :class="['kline-tab', { active: klinePeriod === 'quarterly' }]" @click="changeKlinePeriod('quarterly')"></button>
<button :class="['kline-tab', { active: klinePeriod === 'yearly' }]" @click="changeKlinePeriod('yearly')"></button>
<button type="button" :class="['kline-tab', { active: klinePeriod === 'weekly' }]" @click="changeKlinePeriod('weekly')"></button>
<button type="button" :class="['kline-tab', { active: klinePeriod === 'monthly' }]" @click="changeKlinePeriod('monthly')"></button>
<button type="button" :class="['kline-tab', { active: klinePeriod === 'quarterly' }]" @click="changeKlinePeriod('quarterly')"></button>
<button type="button" :class="['kline-tab', { active: klinePeriod === 'yearly' }]" @click="changeKlinePeriod('yearly')"></button>
</div>
</div>
<div class="kline-chart-container" :class="{ loading: klineLoading }">
@@ -665,13 +665,13 @@
<!-- 分析页子导航 -->
<div class="sub-tabs">
<button :class="['sub-tab', { active: analysisSubTab === 'scan' }]" @click="analysisSubTab = 'scan'; fetchFullScanResults(1)">
<button type="button" :class="['sub-tab', { active: analysisSubTab === 'scan' }]" @click="analysisSubTab = 'scan'; fetchFullScanResults(1)">
全景扫描
</button>
<button :class="['sub-tab', { active: analysisSubTab === 'bull' }]" @click="analysisSubTab = 'bull'">
<button type="button" :class="['sub-tab', { active: analysisSubTab === 'bull' }]" @click="analysisSubTab = 'bull'">
个股深析
</button>
<button :class="['sub-tab', { active: analysisSubTab === 'buyanalysis' }]" @click="analysisSubTab = 'buyanalysis'; fetchBuyAnalysis()">
<button type="button" :class="['sub-tab', { active: analysisSubTab === 'buyanalysis' }]" @click="analysisSubTab = 'buyanalysis'; fetchBuyAnalysis()">
买入分析
</button>
</div>
@@ -819,10 +819,10 @@
<div class="tech-signal-header" style="margin-bottom:8px;">
<div class="tech-input-row">
<input v-model="techSignalCode" placeholder="输入股票代码" class="tech-input" @keyup.enter="fetchTechSignals()">
<button class="tech-btn" @click="fetchTechSignals()" :disabled="techLoading">
<button type="button" class="tech-btn" @click="fetchTechSignals()" :disabled="techLoading">
{{ techLoading ? '检测中...' : '检测信号' }}
</button>
<button class="tech-btn batch" @click="batchTechSignals" :disabled="techLoading">
<button type="button" class="tech-btn batch" @click="batchTechSignals" :disabled="techLoading">
批量扫描关注
</button>
</div>
@@ -850,7 +850,7 @@
<div class="scan-summary-header" role="button" tabindex="0" :aria-expanded="!scanSummaryCollapsed" @click="scanSummaryCollapsed = !scanSummaryCollapsed" @keyup.enter="scanSummaryCollapsed = !scanSummaryCollapsed">
<span class="scan-summary-text">扫描 <b>{{ fullScanStatus.scanned }}</b><small v-if="fullScanStatus.total && fullScanStatus.scanned < fullScanStatus.total">/{{ fullScanStatus.total }}</small> 只,有信号 <b class="highlight">{{ fullScanStatus.triggered }}</b></span>
<span class="scan-summary-actions">
<button class="section-close inline" @click.stop="fullScanStatus = null; fullScanResults = []" title="关闭"></button>
<button type="button" class="section-close inline" @click.stop="fullScanStatus = null; fullScanResults = []" title="关闭"></button>
<span class="scan-summary-toggle">{{ scanSummaryCollapsed ? '▶' : '▼' }}</span>
</span>
</div>
@@ -917,9 +917,9 @@
</div>
</div>
<div v-if="fullScanTotalPages > 1" class="scan-pagination">
<button @click="fetchFullScanResults(fullScanPage - 1)" :disabled="fullScanPage <= 1">上一页</button>
<button type="button" @click="fetchFullScanResults(fullScanPage - 1)" :disabled="fullScanPage <= 1">上一页</button>
<span>{{ fullScanPage }} / {{ fullScanTotalPages }}</span>
<button @click="fetchFullScanResults(fullScanPage + 1)" :disabled="fullScanPage >= fullScanTotalPages">下一页</button>
<button type="button" @click="fetchFullScanResults(fullScanPage + 1)" :disabled="fullScanPage >= fullScanTotalPages">下一页</button>
</div>
</template>
</div>
@@ -932,14 +932,14 @@
🔔 {{ (techSignalResult.signal_status || []).filter(s => s.triggered).length }} 个信号
</span>
<span class="signal-count none" v-else-if="!techSignalResult.has_scan_data">无信号</span>
<button class="section-close" @click="techSignalResult = null" title="关闭"></button>
<button type="button" class="section-close" @click="techSignalResult = null" title="关闭"></button>
</div>
<div class="tech-result-actions">
<button v-if="!searchHistory.some(h => h.code === techSignalResult.stock_code)"
<button type="button" v-if="!searchHistory.some(h => h.code === techSignalResult.stock_code)"
class="watch-btn add" @click="addToWatchFromSignal">
★ 添加关注
</button>
<button v-else class="watch-btn remove" @click="removeFromWatchFromSignal">
<button type="button" v-else class="watch-btn remove" @click="removeFromWatchFromSignal">
☆ 取消关注
</button>
</div>
@@ -1054,7 +1054,7 @@
</div>
</template>
<div v-if="(techSignalResult.signals || []).length > 0" class="tech-signal-list">
<h5 style="color:#aaa;margin:12px 0 8px;font-size:13px;">📋 近期触发记录</h5>
<h5 style="color:var(--text-secondary);margin:12px 0 8px;font-size:13px;">📋 近期触发记录</h5>
<div v-for="sig in techSignalResult.signals" :key="sig.date + sig.type"
:class="['tech-signal-item', 'strength-' + sig.strength]">
<div class="sig-header">
@@ -1072,7 +1072,7 @@
<div v-if="techBatchResults.length > 0" class="tech-batch-results">
<div class="batch-results-header">
<h4 class="section-title">关注股票信号扫描 <span class="badge">{{ techBatchResults.length }}</span></h4>
<button class="section-close" @click="techBatchResults = []" title="关闭"></button>
<button type="button" class="section-close" @click="techBatchResults = []" title="关闭"></button>
</div>
<div class="full-scan-list">
<div v-for="item in techBatchResults" :key="item.code" class="scan-result-card"
@@ -1113,7 +1113,7 @@
<div class="deep-input-row">
<input v-model="deepCode" placeholder="输入股票代码,如 601609"
class="deep-code-input" @keyup.enter="fetchDeepAnalysis()" maxlength="6">
<button class="deep-analyze-btn" @click="fetchDeepAnalysis()" :disabled="deepLoading">
<button type="button" class="deep-analyze-btn" @click="fetchDeepAnalysis()" :disabled="deepLoading">
{{ deepLoading ? '分析中...' : '深度分析' }}
</button>
</div>
@@ -1310,7 +1310,7 @@
<div class="buy-analysis-header">
<div class="buy-analysis-title">买入股深度分析</div>
<div class="buy-analysis-desc">对全景扫描中推荐买入的股票批量执行深度分析</div>
<button class="deep-analyze-btn" @click="fetchBuyAnalysis()" :disabled="buyAnalysisLoading" style="margin-top:8px;">
<button type="button" class="deep-analyze-btn" @click="fetchBuyAnalysis()" :disabled="buyAnalysisLoading" style="margin-top:8px;">
{{ buyAnalysisLoading ? '分析中...' : '刷新分析' }}
</button>
</div>
@@ -1373,7 +1373,7 @@
<div class="buy-detail-grid">
<div class="buy-detail-item">
<span class="buy-detail-label">系统推荐</span>
<span class="buy-detail-value" style="color:#f44336;font-weight:600;">{{ item.recommend.display }} {{ item.recommend.rate }}分</span>
<span class="buy-detail-value" style="color:var(--up);font-weight:600;">{{ item.recommend.display }} {{ item.recommend.rate }}分</span>
</div>
<div class="buy-detail-item">
<span class="buy-detail-label">推荐理由</span>
@@ -1402,7 +1402,7 @@
<span class="buy-detail-value">{{ item.ma_trend_label }}</span>
</div>
</div>
<button class="deep-analyze-btn" style="margin-top:8px;font-size:12px;padding:6px 12px;"
<button type="button" class="deep-analyze-btn" style="margin-top:8px;font-size:12px;padding:6px 12px;"
@click="deepCode = item.stock_code; analysisSubTab = 'bull'; fetchDeepAnalysis();">
查看完整报告 →
</button>
@@ -1416,12 +1416,12 @@
<!-- 模拟交易统计卡片 -->
<div class="sim-stats-card">
<div class="sim-stats-header">
<h4>智能交易 <span v-if="smartAlgoConfig" style="font-size:12px;color:#888;">{{ smartAlgoConfig.algo_name }}</span></h4>
<h4>智能交易 <span v-if="smartAlgoConfig" style="font-size:12px;color:var(--text-muted);">{{ smartAlgoConfig.algo_name }}</span></h4>
<div class="sim-actions">
<button class="sim-btn auto" @click="executeAutoTrade" :disabled="simAutoTrading">
<button type="button" class="sim-btn auto" @click="executeAutoTrade" :disabled="simAutoTrading">
{{ simAutoTrading ? '执行中...' : '执行' }}
</button>
<button class="sim-btn reset" @click="confirmResetSim">重置</button>
<button type="button" class="sim-btn reset" @click="confirmResetSim">重置</button>
</div>
</div>
<div class="sim-stats-grid">
@@ -1457,18 +1457,18 @@
<span :class="(simStats.realized_profit || 0) >= 0 ? 'profit' : 'loss'">
已实现 {{ (simStats.realized_profit || 0) >= 0 ? '+' : '' }}{{ formatMoney(simStats.realized_profit || 0) }}
</span>
<button class="sim-btn refresh" @click="updateSimPrices" :disabled="simPositions.length === 0 || simPriceRefreshing" v-if="simPositions.length > 0" style="margin-left:auto;font-size:11px;padding:2px 8px;">
<button type="button" class="sim-btn refresh" @click="updateSimPrices" :disabled="simPositions.length === 0 || simPriceRefreshing" v-if="simPositions.length > 0" style="margin-left:auto;font-size:11px;padding:2px 8px;">
{{ simPriceRefreshing ? '刷新中...' : '刷新现价' }}
</button>
</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" class="cursor-pointer">
<span style="font-size:12px;font-weight:600;color:#aaa;">
<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>
<span style="font-size:12px;font-weight:600;color:var(--text-secondary);">
<svg class="icon-xs" viewBox="0 0 24 24" fill="none" stroke="var(--warning)" 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>
<span :class="['toggle-icon', { expanded: compareExpanded }]" style="color:var(--text-muted);font-size:10px;"></span>
</div>
<div v-show="compareExpanded" class="sim-compare-body">
<div class="sim-compare-row">
@@ -1489,14 +1489,14 @@
</div>
</div>
<div class="sim-compare-fees">
<span>总手续费: <b style="color:#ff6b6b;">-{{ formatMoney(simStats.total_fees || 0) }}</b></span>
<span style="color:#666;font-size:11px;">
<span>总手续费: <b style="color:var(--up);">-{{ formatMoney(simStats.total_fees || 0) }}</b></span>
<span style="color:var(--text-dim);font-size:11px;">
佣金 ¥{{ (simStats.total_commission || 0).toFixed(2) }} +
印花税 ¥{{ (simStats.total_stamp_tax || 0).toFixed(2) }}
</span>
</div>
<div class="sim-compare-tip" v-if="simStats.total_fees > 0">
<span style="color:#888;font-size:11px;">
<span style="color:var(--text-muted);font-size:11px;">
💡 手续费占毛利 {{ simStats.gross_profit > 0 ? ((simStats.total_fees / simStats.gross_profit) * 100).toFixed(1) : '0.0' }}%
| 每笔均费 ¥{{ simStats.total_trades > 0 ? (simStats.total_fees / simStats.total_trades).toFixed(2) : '0.00' }}
</span>
@@ -1508,16 +1508,16 @@
<!-- 算法配置卡片 -->
<div class="sim-rule-card collapsible">
<div class="sim-rule-header" @click="smartConfigExpanded = !smartConfigExpanded">
<h4>⚙️ 算法配置 <span v-if="smartAlgoIsDefault" style="color:#ffd93d;font-size:12px;">(默认)</span></h4>
<h4>⚙️ 算法配置 <span v-if="smartAlgoIsDefault" style="color:var(--hold);font-size:12px;">(默认)</span></h4>
<span :class="['toggle-icon', { expanded: smartConfigExpanded }]"></span>
</div>
<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="font-size:13px;font-weight:600;margin-bottom:6px;color:var(--text-secondary);">快速选择算法模板:</div>
<div class="flex-wrap-gap">
<button v-for="tpl in smartTemplates" :key="tpl.name"
<button type="button" v-for="tpl in smartTemplates" :key="tpl.name"
class="sim-btn"
:class="{'auto': smartAlgoConfig?.algo_name === tpl.display_name}"
@click="applyAlgoTemplate(tpl.name)"
@@ -1529,18 +1529,18 @@
</div>
<!-- 当前配置详情 -->
<div v-if="smartAlgoConfig" style="font-size:12px;color:#aaa;line-height:1.8;">
<div v-if="smartAlgoConfig" style="font-size:12px;color:var(--text-secondary);line-height:1.8;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px 16px;">
<span>止盈: <strong style="color:#00ce9e;">{{ smartAlgoConfig.take_profit_pct }}%</strong></span>
<span>止损: <strong style="color:#ff6b6b;">{{ smartAlgoConfig.stop_loss_pct }}%</strong></span>
<span>止盈: <strong style="color:var(--teal);">{{ smartAlgoConfig.take_profit_pct }}%</strong></span>
<span>止损: <strong style="color:var(--up);">{{ smartAlgoConfig.stop_loss_pct }}%</strong></span>
<span>仓位: <strong>{{ smartAlgoConfig.position_pct }}%</strong> <span v-if="smartAlgoConfig.signal_weight">+信号加权</span></span>
<span>最长持仓: <strong>{{ smartAlgoConfig.max_hold_days }}</strong></span>
<span>部分止盈: <strong>{{ smartAlgoConfig.partial_exit_pct }}%</strong></span>
<span>跟踪回撤: <strong>{{ smartAlgoConfig.momentum_trail_gap }}%</strong></span>
<span>保本触发: <strong>{{ smartAlgoConfig.breakeven_at > 0 ? smartAlgoConfig.breakeven_at + '%' : '关闭' }}</strong></span>
<span>卖出确认: <strong>{{ smartAlgoConfig.sell_confirm_days }}</strong></span>
<span>买入时点: <strong style="color:#4ecdc4;">{{ smartAlgoConfig.buy_time }}</strong></span>
<span>卖出时点: <strong style="color:#4ecdc4;">{{ smartAlgoConfig.sell_time }}</strong></span>
<span>买入时点: <strong style="color:var(--accent-cyan);">{{ smartAlgoConfig.buy_time }}</strong></span>
<span>卖出时点: <strong style="color:var(--accent-cyan);">{{ smartAlgoConfig.sell_time }}</strong></span>
<span>忽略卖信号: <strong>{{ smartAlgoConfig.ignore_sell_signal ? '是' : '否' }}</strong></span>
<span>连涨不平仓: <strong>{{ smartAlgoConfig.no_timeout_if_rising ? '是' : '否' }}</strong></span>
</div>
@@ -1582,19 +1582,19 @@
</label>
</div>
<div style="margin-top:8px;display:flex;gap:8px;">
<button class="sim-btn auto" @click="saveSmartConfig" style="font-size:12px;">保存配置</button>
<button class="sim-btn" @click="smartConfigEditing = false; loadSmartAlgoConfig();" style="font-size:12px;">取消</button>
<button type="button" class="sim-btn auto" @click="saveSmartConfig" style="font-size:12px;">保存配置</button>
<button type="button" class="sim-btn" @click="smartConfigEditing = false; loadSmartAlgoConfig();" style="font-size:12px;">取消</button>
</div>
</div>
<div v-else style="margin-top:6px;">
<button class="sim-btn" @click="smartConfigEditing = true" style="font-size:11px;padding:2px 8px;">✏️ 自定义参数</button>
<button type="button" class="sim-btn" @click="smartConfigEditing = true" style="font-size:11px;padding:2px 8px;">✏️ 自定义参数</button>
</div>
</div>
<!-- 自动执行时间说明 -->
<div style="margin-top:8px;font-size:11px;color:#666;border-top:1px solid #222;padding-top:6px;">
⏰ 每个交易日 <strong style="color:#4ecdc4;">{{ smartAlgoConfig?.buy_time || '09:35' }}</strong> 买入 /
<strong style="color:#4ecdc4;">{{ smartAlgoConfig?.sell_time || '13:40' }}</strong> 卖出 /
<div style="margin-top:8px;font-size:11px;color:var(--text-dim);border-top:1px solid var(--border-glass);padding-top:6px;">
⏰ 每个交易日 <strong style="color:var(--accent-cyan);">{{ smartAlgoConfig?.buy_time || '09:35' }}</strong> 买入 /
<strong style="color:var(--accent-cyan);">{{ smartAlgoConfig?.sell_time || '13:40' }}</strong> 卖出 /
<strong>15:05</strong> 收盘更新 — 基于v7回测最优时点
</div>
</div>
@@ -1603,7 +1603,7 @@
<!-- 今日交易 -->
<div class="sim-section collapsible-section">
<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>
<h4 class="section-title"><svg class="icon-sm" viewBox="0 0 24 24" fill="none" stroke="var(--accent-cyan)" 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>
@@ -1621,7 +1621,7 @@
<span>¥{{ trade.price?.toFixed(2) }} × {{ trade.quantity }}股</span>
<span class="sim-trade-amount">{{ formatMoney(trade.price * trade.quantity) }}</span>
</div>
<div class="sim-trade-fee" v-if="trade.total_fee > 0" style="font-size:11px;color:#888;">
<div class="sim-trade-fee" v-if="trade.total_fee > 0" style="font-size:11px;color:var(--text-muted);">
手续费 ¥{{ trade.total_fee?.toFixed(2) }}
</div>
<div class="sim-trade-reason" v-if="trade.signal_reason">{{ trade.signal_reason }}</div>
@@ -1634,7 +1634,7 @@
<!-- 模拟持仓列表(增强版:显示算法状态) -->
<div class="sim-section collapsible-section">
<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>
<h4 class="section-title"><svg class="icon-sm" viewBox="0 0 24 24" fill="none" stroke="var(--teal)" 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>
@@ -1655,16 +1655,16 @@
</div>
<!-- 算法状态标签 -->
<div v-if="getActiveRules(pos.stock_code).length > 0 || getPositionMeta(pos.stock_code)"
class="sim-pos-algo-status" style="font-size:11px;color:#888;margin-top:2px;display:flex;gap:6px;flex-wrap:wrap;">
class="sim-pos-algo-status" style="font-size:11px;color:var(--text-muted);margin-top:2px;display:flex;gap:6px;flex-wrap:wrap;">
<span v-for="rule in getActiveRules(pos.stock_code)" :key="rule"
style="background:#1a1a2e;padding:1px 6px;border-radius:8px;font-size:10px;">{{ rule }}</span>
<span v-if="getPositionMeta(pos.stock_code)?.days_held" style="color:#666;">
style="background:var(--grad-strategy-text);padding:1px 6px;border-radius:8px;font-size:10px;">{{ rule }}</span>
<span v-if="getPositionMeta(pos.stock_code)?.days_held" style="color:var(--text-dim);">
持{{ getPositionMeta(pos.stock_code).days_held }}天
</span>
<span v-if="getPositionMeta(pos.stock_code)?.consecutive_up_days > 0" style="color:#00ce9e;">
<span v-if="getPositionMeta(pos.stock_code)?.consecutive_up_days > 0" style="color:var(--teal);">
连涨{{ getPositionMeta(pos.stock_code).consecutive_up_days }}天
</span>
<span v-if="getPositionMeta(pos.stock_code)?.consecutive_sell_signals > 0" style="color:#ffd93d;">
<span v-if="getPositionMeta(pos.stock_code)?.consecutive_sell_signals > 0" style="color:var(--hold);">
卖信号{{ getPositionMeta(pos.stock_code).consecutive_sell_signals }}天
</span>
</div>
@@ -1675,32 +1675,32 @@
<!-- 信号日志(可折叠) -->
<div class="sim-section collapsible-section">
<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>
<h4 class="section-title"><svg class="icon-sm" viewBox="0 0 24 24" fill="none" stroke="var(--hold)" 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 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;">
style="padding:6px 0;border-bottom:1px solid var(--grad-strategy-text);font-size:12px;display:flex;gap:8px;align-items:flex-start;">
<span :style="{
color: sig.action === 'buy' ? '#ff6b6b' :
sig.action === 'sell' ? '#00ce9e' :
sig.action === 'partial_sell' ? '#ffd93d' : '#888',
color: sig.action === 'buy' ? 'var(--up)' :
sig.action === 'sell' ? 'var(--teal)' :
sig.action === 'partial_sell' ? 'var(--hold)' : 'var(--text-muted)',
fontWeight: 600, minWidth: '48px'
}">
{{ sig.action === 'buy' ? '买入' : sig.action === 'sell' ? '卖出' :
sig.action === 'partial_sell' ? '减仓' : sig.action === 'hold' ? '持有' : sig.action }}
</span>
<span style="color:#ccc;flex:1;">
<span style="color:var(--text-secondary);flex:1;">
{{ sig.stock_code }}
<span v-if="sig.algo_rule" style="color:#4ecdc4;font-size:11px;">[{{ sig.algo_rule }}]</span>
<span v-if="sig.algo_rule" style="color:var(--accent-cyan);font-size:11px;">[{{ sig.algo_rule }}]</span>
{{ sig.reason }}
<span v-if="sig.profit_pct" :style="{color: sig.profit_pct >= 0 ? '#00ce9e' : '#ff6b6b'}">
<span v-if="sig.profit_pct" :style="{color: sig.profit_pct >= 0 ? 'var(--teal)' : 'var(--up)'}">
({{ sig.profit_pct >= 0 ? '+' : '' }}{{ sig.profit_pct?.toFixed(1) }}%)
</span>
</span>
<span style="color:#555;font-size:11px;white-space:nowrap;">{{ sig.signal_date }}</span>
<span style="color:var(--text-dim);font-size:11px;white-space:nowrap;">{{ sig.signal_date }}</span>
</div>
</div>
</div>
@@ -1709,7 +1709,7 @@
<!-- 模拟交易记录 -->
<div class="sim-section collapsible-section">
<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>
<h4 class="section-title"><svg class="icon-sm" viewBox="0 0 24 24" fill="none" stroke="var(--purple-light)" 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>
@@ -1727,9 +1727,9 @@
<span>¥{{ trade.price?.toFixed(2) }} × {{ trade.quantity }}股</span>
<span class="sim-trade-amount">{{ formatMoney(trade.price * trade.quantity) }}</span>
</div>
<div class="sim-trade-fee" v-if="trade.total_fee > 0" style="font-size:11px;color:#888;">
<div class="sim-trade-fee" v-if="trade.total_fee > 0" style="font-size:11px;color:var(--text-muted);">
手续费 ¥{{ trade.total_fee?.toFixed(2) }}
<span style="color:#666;">(佣金{{ trade.commission?.toFixed(2) }}{{ trade.stamp_tax > 0 ? ' + 印花税' + trade.stamp_tax?.toFixed(2) : '' }})</span>
<span style="color:var(--text-dim);">(佣金{{ trade.commission?.toFixed(2) }}{{ trade.stamp_tax > 0 ? ' + 印花税' + trade.stamp_tax?.toFixed(2) : '' }})</span>
</div>
<div class="sim-trade-reason" v-if="trade.signal_reason">{{ trade.signal_reason }}</div>
<div class="sim-trade-date">{{ trade.trade_date }} {{ trade.trade_time?.substring(0,5) }}</div>
@@ -1746,10 +1746,10 @@
<div v-show="activeTab === 'model'" class="model-page">
<!-- 模型页子导航 -->
<div class="sub-tabs">
<button :class="['sub-tab', { active: modelSubTab === 'system' }]" @click="modelSubTab = 'system'">
<button type="button" :class="['sub-tab', { active: modelSubTab === 'system' }]" @click="modelSubTab = 'system'">
信号体系
</button>
<button :class="['sub-tab', { active: modelSubTab === 'sim' }]" @click="modelSubTab = 'sim'; loadSimData()">
<button type="button" :class="['sub-tab', { active: modelSubTab === 'sim' }]" @click="modelSubTab = 'sim'; loadSimData()">
模拟交易
</button>
</div>
@@ -1875,12 +1875,12 @@
<!-- 模拟交易统计卡片 -->
<div class="sim-stats-card">
<div class="sim-stats-header">
<h4>智能交易 <span v-if="smartAlgoConfig" style="font-size:12px;color:#888;">{{ smartAlgoConfig.algo_name }}</span></h4>
<h4>智能交易 <span v-if="smartAlgoConfig" style="font-size:12px;color:var(--text-muted);">{{ smartAlgoConfig.algo_name }}</span></h4>
<div class="sim-actions">
<button class="sim-btn auto" @click="executeAutoTrade" :disabled="simAutoTrading">
<button type="button" class="sim-btn auto" @click="executeAutoTrade" :disabled="simAutoTrading">
{{ simAutoTrading ? '执行中...' : '执行' }}
</button>
<button class="sim-btn reset" @click="confirmResetSim">重置</button>
<button type="button" class="sim-btn reset" @click="confirmResetSim">重置</button>
</div>
</div>
<div class="sim-stats-grid">
@@ -1916,7 +1916,7 @@
<span :class="(simStats.realized_profit || 0) >= 0 ? 'profit' : 'loss'">
已实现 {{ (simStats.realized_profit || 0) >= 0 ? '+' : '' }}{{ formatMoney(simStats.realized_profit || 0) }}
</span>
<button class="sim-btn refresh" @click="updateSimPrices" :disabled="simPositions.length === 0 || simPriceRefreshing" v-if="simPositions.length > 0" style="margin-left:auto;font-size:11px;padding:2px 8px;">
<button type="button" class="sim-btn refresh" @click="updateSimPrices" :disabled="simPositions.length === 0 || simPriceRefreshing" v-if="simPositions.length > 0" style="margin-left:auto;font-size:11px;padding:2px 8px;">
{{ simPriceRefreshing ? '刷新中...' : '刷新现价' }}
</button>
</div>
@@ -1982,13 +1982,13 @@
<span class="cash-label">可用资金</span>
<template v-if="!editingCash">
<span class="cash-value">{{ formatMoney(availableCash) }}</span>
<button class="cash-edit-btn" @click="startEditCash" title="编辑可用资金">✏️</button>
<button type="button" class="cash-edit-btn" @click="startEditCash" title="编辑可用资金">✏️</button>
</template>
<template v-else>
<input type="number" ref="cashInput" v-model="cashInputValue" class="cash-input"
@keyup.enter="saveCash" @keyup.esc="cancelEditCash" placeholder="输入金额">
<button class="cash-save-btn" @click="saveCash"></button>
<button class="cash-cancel-btn" @click="cancelEditCash"></button>
<button type="button" class="cash-save-btn" @click="saveCash"></button>
<button type="button" class="cash-cancel-btn" @click="cancelEditCash"></button>
</template>
</div>
<!-- 总资产 = 持仓市值 + 可用资金 -->
@@ -2016,17 +2016,17 @@
<span v-if="alert.stop_loss_price">止损 ¥{{ alert.stop_loss_price }}</span>
</div>
<div class="stoploss-alert-message">{{ alert.message }}</div>
<button class="stoploss-sell-btn" @click="quickSellFromStopLoss(alert)">立即卖出</button>
<button type="button" class="stoploss-sell-btn" @click="quickSellFromStopLoss(alert)">立即卖出</button>
</div>
</div>
</div>
<!-- 操作按钮区 -->
<div class="trade-actions-bar">
<button class="add-trade-btn" @click="openTradeForm" :disabled="allStocksForTrade.length === 0">
<button type="button" class="add-trade-btn" @click="openTradeForm" :disabled="allStocksForTrade.length === 0">
{{ allStocksForTrade.length === 0 ? '请先分析股票' : '记录交易' }}
</button>
<button class="refresh-price-btn" @click="refreshHoldingPrices" :disabled="priceRefreshing">
<button type="button" class="refresh-price-btn" @click="refreshHoldingPrices" :disabled="priceRefreshing">
{{ priceRefreshing ? '刷新中...' : '刷新价格' }}
</button>
</div>
@@ -2100,10 +2100,10 @@
<!-- 持有/清仓 子Tab -->
<div class="trade-sub-tabs">
<button :class="['trade-sub-tab', { active: tradeViewType === 'holding' }]" @click="tradeViewType = 'holding'">
<button type="button" :class="['trade-sub-tab', { active: tradeViewType === 'holding' }]" @click="tradeViewType = 'holding'">
持有 <span class="tab-count">{{ holdingGroupCount }}</span>
</button>
<button :class="['trade-sub-tab', { active: tradeViewType === 'cleared' }]" @click="tradeViewType = 'cleared'">
<button type="button" :class="['trade-sub-tab', { active: tradeViewType === 'cleared' }]" @click="tradeViewType = 'cleared'">
清仓 <span class="tab-count">{{ clearedGroupCount }}</span>
</button>
</div>
@@ -2118,7 +2118,7 @@
<div v-for="group in filteredGroupedTrades" :key="group.code" class="trade-group">
<!-- 股票分组标题 -->
<div class="trade-group-header">
<button class="toggle-btn" @click.stop="toggleTradeGroup(group.code)">
<button type="button" class="toggle-btn" @click.stop="toggleTradeGroup(group.code)">
{{ expandedTradeGroups[group.code] ? '▼' : '▶' }}
</button>
<div class="trade-group-content" role="button" tabindex="0" @click="loadFundamental(group.code, group.name)" @keyup.enter="loadFundamental(group.code, group.name)">
@@ -2194,8 +2194,8 @@
<div class="trade-date">{{ trade.trade_date }}</div>
</div>
<div class="trade-actions-vertical">
<button @click="editTrade(trade)">编辑</button>
<button @click="deleteTrade(trade.id)">删除</button>
<button type="button" @click="editTrade(trade)">编辑</button>
<button type="button" @click="deleteTrade(trade.id)">删除</button>
</div>
</div>
</div>
@@ -2225,8 +2225,8 @@
<div v-if="passwordError" class="login-error">{{ passwordError }}</div>
<div v-if="passwordSuccess" class="login-success">{{ passwordSuccess }}</div>
<div class="password-buttons">
<button class="btn-cancel" @click="showPasswordModal = false">关闭</button>
<button class="login-submit" @click="handleChangePassword" :disabled="passwordLoading">
<button type="button" class="btn-cancel" @click="showPasswordModal = false">关闭</button>
<button type="button" class="login-submit" @click="handleChangePassword" :disabled="passwordLoading">
{{ passwordLoading ? '处理中...' : '确认修改' }}
</button>
</div>