feat: UI/UX全面优化 — CSS变量体系/a11y无障碍/Admin角色化/骨架屏/打印样式
This commit is contained in:
@@ -21,19 +21,19 @@
|
||||
<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=20260318v2">
|
||||
<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=20260511v3">
|
||||
<link rel="stylesheet" href="/static/css/scan.css?v=20260318v2">
|
||||
<link rel="stylesheet" href="/static/css/responsive.css?v=20260318v2">
|
||||
<link rel="stylesheet" href="/static/css/pages.css?v=20260718v1">
|
||||
<link rel="stylesheet" href="/static/css/scan.css?v=20260718v1">
|
||||
<link rel="stylesheet" href="/static/css/responsive.css?v=20260718v1">
|
||||
</head>
|
||||
<body>
|
||||
{% raw %}
|
||||
<div id="app" v-cloak>
|
||||
<!-- 全局 Toast 提示 -->
|
||||
<transition name="toast-fade">
|
||||
<div v-if="toastVisible" :class="['toast-container', toastType]">
|
||||
<div v-if="toastVisible" :class="['toast-container', toastType]" role="alert" aria-live="assertive">
|
||||
<span class="toast-icon">
|
||||
{{ toastType === 'success' ? '✓' : toastType === 'error' ? '✕' : toastType === 'warning' ? '⚠' : 'ℹ' }}
|
||||
</span>
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
<!-- 确认对话框 -->
|
||||
<transition name="modal-fade">
|
||||
<div v-if="confirmVisible" class="confirm-overlay" @click.self="handleConfirmCancel">
|
||||
<div v-if="confirmVisible" class="confirm-overlay" @click.self="handleConfirmCancel" role="dialog" aria-modal="true" aria-label="确认操作">
|
||||
<div class="confirm-dialog">
|
||||
<div class="confirm-message">{{ confirmMessage }}</div>
|
||||
<div class="confirm-buttons">
|
||||
@@ -113,7 +113,7 @@
|
||||
|
||||
<!-- 登录弹窗 -->
|
||||
<transition name="modal-fade">
|
||||
<div v-if="showLoginModal" class="modal-overlay" @click.self="showLoginModal = false">
|
||||
<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>
|
||||
@@ -199,11 +199,11 @@
|
||||
</div>
|
||||
|
||||
<!-- 顶部导航 -->
|
||||
<div class="nav-tabs nav-tabs-compact">
|
||||
<button :class="['nav-tab', { active: activeTab === 'alerts' }]" @click="activeTab = 'alerts'">提醒</button>
|
||||
<button :class="['nav-tab', { active: activeTab === 'trades' }]" @click="activeTab = 'trades'; loadTrades()">交易</button>
|
||||
<button :class="['nav-tab', { active: activeTab === 'analysis' }]" @click="activeTab = 'analysis'; if (analysisSubTab === 'scan') fetchFullScanResults(1)">分析</button>
|
||||
<button :class="['nav-tab', { active: activeTab === 'model' }]" @click="activeTab = 'model'">模型</button>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- 提醒页面 -->
|
||||
@@ -262,7 +262,7 @@
|
||||
<!-- 卖出信号(持有的股票出现卖出信号需要优先关注) -->
|
||||
<div v-if="holdingAlerts.filter(a => a.signalType === 'sell').length > 0" class="alert-section">
|
||||
<h4 class="section-title sell">卖出信号</h4>
|
||||
<div v-for="alert in holdingAlerts.filter(a => a.signalType === 'sell')" :key="alert.code" class="alert-card sell" @click="loadFundamental(alert.code, alert.name)">
|
||||
<div v-for="alert in holdingAlerts.filter(a => a.signalType === 'sell')" :key="alert.code" class="alert-card sell" role="button" tabindex="0" @click="loadFundamental(alert.code, alert.name)" @keyup.enter="loadFundamental(alert.code, alert.name)">
|
||||
<div class="alert-header">
|
||||
<span class="alert-code">{{ alert.code }}</span>
|
||||
<span class="alert-name">{{ alert.name }}</span>
|
||||
@@ -294,7 +294,7 @@
|
||||
<div v-if="holdingAlerts.filter(a => a.signalType === 'buy').length > 0" class="alert-section">
|
||||
<h4 class="section-title buy">正向信号</h4>
|
||||
<div v-for="alert in holdingAlerts.filter(a => a.signalType === 'buy')" :key="alert.code"
|
||||
class="alert-card" :class="alert.recommendText === '加仓' ? 'buy' : 'hold'" @click="loadFundamental(alert.code, alert.name)">
|
||||
class="alert-card" :class="alert.recommendText === '加仓' ? 'buy' : 'hold'" role="button" tabindex="0" @click="loadFundamental(alert.code, alert.name)" @keyup.enter="loadFundamental(alert.code, alert.name)">
|
||||
<div class="alert-header">
|
||||
<span class="alert-code">{{ alert.code }}</span>
|
||||
<span class="alert-name">{{ alert.name }}</span>
|
||||
@@ -326,7 +326,7 @@
|
||||
<!-- 观望(持有的股票无明确信号) -->
|
||||
<div v-if="holdingAlerts.filter(a => a.signalType === 'watch').length > 0" class="alert-section">
|
||||
<h4 class="section-title watch">持有观望</h4>
|
||||
<div v-for="alert in holdingAlerts.filter(a => a.signalType === 'watch')" :key="alert.code" class="alert-card watch" @click="loadFundamental(alert.code, alert.name)">
|
||||
<div v-for="alert in holdingAlerts.filter(a => a.signalType === 'watch')" :key="alert.code" class="alert-card watch" role="button" tabindex="0" @click="loadFundamental(alert.code, alert.name)" @keyup.enter="loadFundamental(alert.code, alert.name)">
|
||||
<div class="alert-header">
|
||||
<span class="alert-code">{{ alert.code }}</span>
|
||||
<span class="alert-name">{{ alert.name }}</span>
|
||||
@@ -362,7 +362,7 @@
|
||||
<!-- 买入提醒列表 -->
|
||||
<div v-if="buyAlerts.length > 0" class="alert-section">
|
||||
<h4 class="section-title buy">买入信号</h4>
|
||||
<div v-for="alert in buyAlerts" :key="alert.code" class="alert-card buy" @click="loadFundamental(alert.code, alert.name)">
|
||||
<div v-for="alert in buyAlerts" :key="alert.code" class="alert-card buy" role="button" tabindex="0" @click="loadFundamental(alert.code, alert.name)" @keyup.enter="loadFundamental(alert.code, alert.name)">
|
||||
<div class="alert-header">
|
||||
<span class="alert-code">{{ alert.code }}</span>
|
||||
<span class="alert-name">{{ alert.name }}</span>
|
||||
@@ -395,7 +395,7 @@
|
||||
<!-- 卖出提醒列表 -->
|
||||
<div v-if="sellAlerts.length > 0" class="alert-section">
|
||||
<h4 class="section-title sell">卖出信号</h4>
|
||||
<div v-for="alert in sellAlerts" :key="alert.code" class="alert-card sell" @click="loadFundamental(alert.code, alert.name)">
|
||||
<div v-for="alert in sellAlerts" :key="alert.code" class="alert-card sell" role="button" tabindex="0" @click="loadFundamental(alert.code, alert.name)" @keyup.enter="loadFundamental(alert.code, alert.name)">
|
||||
<div class="alert-header">
|
||||
<span class="alert-code">{{ alert.code }}</span>
|
||||
<span class="alert-name">{{ alert.name }}</span>
|
||||
@@ -427,7 +427,7 @@
|
||||
<!-- 观望列表 -->
|
||||
<div v-if="watchAlerts.length > 0" class="alert-section">
|
||||
<h4 class="section-title watch">观望</h4>
|
||||
<div v-for="alert in watchAlerts" :key="alert.code" class="alert-card watch" @click="loadFundamental(alert.code, alert.name)">
|
||||
<div v-for="alert in watchAlerts" :key="alert.code" class="alert-card watch" role="button" tabindex="0" @click="loadFundamental(alert.code, alert.name)" @keyup.enter="loadFundamental(alert.code, alert.name)">
|
||||
<div class="alert-header">
|
||||
<span class="alert-code">{{ alert.code }}</span>
|
||||
<span class="alert-name">{{ alert.name }}</span>
|
||||
@@ -847,7 +847,7 @@
|
||||
<!-- 全量扫描结果 -->
|
||||
<template v-if="fullScanStatus && (fullScanResults.length > 0 || fullScanFilterRecommend !== 'all')">
|
||||
<div class="scan-summary-section">
|
||||
<div class="scan-summary-header" @click="scanSummaryCollapsed = !scanSummaryCollapsed">
|
||||
<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>
|
||||
@@ -899,6 +899,11 @@
|
||||
<span class="scan-change" v-if="item.change_pct != null"
|
||||
:class="item.change_pct >= 0 ? 'up' : 'down'">{{ item.change_pct >= 0 ? '+' : '' }}{{ item.change_pct.toFixed(2) }}%</span>
|
||||
</span>
|
||||
<span class="scan-score-pill" v-if="item.final_score != null"
|
||||
:class="'score-' + (item.final_score >= 65 ? 'high' : item.final_score >= 50 ? 'mid' : 'low')"
|
||||
:title="'技术' + item.technical_score + ' / 外部' + (item.external_score >= 0 ? '+' : '') + item.external_score">
|
||||
{{ item.final_score }}分
|
||||
</span>
|
||||
<span class="scan-triggered" v-if="item.triggered_count > 0">🔔 {{ item.triggered_count }}个信号</span>
|
||||
<span class="scan-no-signal" v-else>无信号</span>
|
||||
</div>
|
||||
@@ -1115,8 +1120,16 @@
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="deepLoading" class="bull-loading">
|
||||
<span class="loading-dot"></span> 正在进行深度分析...
|
||||
<div v-if="deepLoading" class="skeleton-card">
|
||||
<div class="skeleton-line medium"></div>
|
||||
<div class="skeleton-line long"></div>
|
||||
<div class="skeleton-grid">
|
||||
<div class="skeleton-line short"></div>
|
||||
<div class="skeleton-line short"></div>
|
||||
</div>
|
||||
<div class="skeleton-line long"></div>
|
||||
<div class="skeleton-line medium"></div>
|
||||
<div class="skeleton-line long"></div>
|
||||
</div>
|
||||
|
||||
<!-- 分析报告 -->
|
||||
@@ -1142,6 +1155,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 三项得分明细 -->
|
||||
<div v-if="deepReport.comprehensive" class="deep-score-breakdown">
|
||||
<div class="score-breakdown-item">
|
||||
<span class="breakdown-label">技术得分</span>
|
||||
<span class="breakdown-value">{{ deepReport.comprehensive.technical_score }}</span>
|
||||
</div>
|
||||
<div class="score-breakdown-item">
|
||||
<span class="breakdown-label">外部得分</span>
|
||||
<span class="breakdown-value" :class="deepReport.comprehensive.external_score >= 0 ? 'pos' : 'neg'">
|
||||
{{ deepReport.comprehensive.external_score >= 0 ? '+' : '' }}{{ deepReport.comprehensive.external_score }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="score-breakdown-item highlight">
|
||||
<span class="breakdown-label">综合得分</span>
|
||||
<span class="breakdown-value">{{ deepReport.comprehensive.final_score }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI通俗解说 -->
|
||||
<div v-if="deepReport.ai_summary" class="deep-ai-summary">
|
||||
<div class="deep-ai-title">💡 AI 解读</div>
|
||||
@@ -1266,10 +1297,10 @@
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="!deepReport && !deepLoading" class="bull-empty-tip">
|
||||
<div class="empty-icon" style="font-size:28px">个股深析</div>
|
||||
<p>输入股票代码,获取深度技术分析报告</p>
|
||||
<p class="sub-tip">包含价格位置、压力支撑、量价分析、空间估算、综合评分</p>
|
||||
<div v-if="!deepReport && !deepLoading" class="empty-state">
|
||||
<div class="empty-state-icon">📊</div>
|
||||
<div class="empty-state-title">个股深析</div>
|
||||
<div class="empty-state-desc">输入股票代码,获取深度技术分析报告<br>包含价格位置、压力支撑、量价分析、空间估算、综合评分</div>
|
||||
</div>
|
||||
</div><!-- 个股深析子页面结束 -->
|
||||
|
||||
@@ -1288,10 +1319,10 @@
|
||||
<span class="loading-dot"></span> 正在批量分析买入推荐股...({{ buyAnalysisProgress }}/{{ buyAnalysisTotal }})
|
||||
</div>
|
||||
|
||||
<div v-if="!buyAnalysisLoading && buyAnalysisList.length === 0" class="bull-empty-tip">
|
||||
<div class="empty-icon" style="font-size:28px">📊</div>
|
||||
<p>暂无买入推荐股票</p>
|
||||
<p class="sub-tip">当全景扫描中有买入推荐时,会自动进行深度分析</p>
|
||||
<div v-if="!buyAnalysisLoading && buyAnalysisList.length === 0" class="empty-state">
|
||||
<div class="empty-state-icon">📋</div>
|
||||
<div class="empty-state-title">暂无买入推荐股票</div>
|
||||
<div class="empty-state-desc">当全景扫描中有买入推荐时,可在此批量执行深度分析</div>
|
||||
</div>
|
||||
|
||||
<div v-if="buyAnalysisList.length > 0" class="buy-analysis-list">
|
||||
@@ -1316,6 +1347,22 @@
|
||||
<span class="buy-card-arrow" :class="{ expanded: item._expanded }">▸</span>
|
||||
</div>
|
||||
<div v-show="item._expanded" class="buy-card-detail">
|
||||
<div v-if="item.comprehensive" class="deep-score-breakdown" style="margin-bottom:8px;">
|
||||
<div class="score-breakdown-item">
|
||||
<span class="breakdown-label">技术得分</span>
|
||||
<span class="breakdown-value">{{ item.comprehensive.technical_score }}</span>
|
||||
</div>
|
||||
<div class="score-breakdown-item">
|
||||
<span class="breakdown-label">外部得分</span>
|
||||
<span class="breakdown-value" :class="item.comprehensive.external_score >= 0 ? 'pos' : 'neg'">
|
||||
{{ item.comprehensive.external_score >= 0 ? '+' : '' }}{{ item.comprehensive.external_score }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="score-breakdown-item highlight">
|
||||
<span class="breakdown-label">综合得分</span>
|
||||
<span class="breakdown-value">{{ item.comprehensive.final_score }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.ai_summary" class="deep-ai-summary" style="margin-bottom:8px;">
|
||||
<div class="deep-ai-title">💡 AI 解读</div>
|
||||
<div class="deep-ai-text"><p>{{ item.ai_summary.text }}</p></div>
|
||||
@@ -1953,7 +2000,7 @@
|
||||
|
||||
<!-- 止损预警区(可折叠) -->
|
||||
<div v-if="stopLossAlerts && stopLossAlerts.length > 0" class="stoploss-alerts-card stoploss-collapsible">
|
||||
<div class="stoploss-alerts-header" @click="stopLossAlertsExpanded = !stopLossAlertsExpanded">
|
||||
<div class="stoploss-alerts-header" role="button" tabindex="0" :aria-expanded="stopLossAlertsExpanded" @click="stopLossAlertsExpanded = !stopLossAlertsExpanded" @keyup.enter="stopLossAlertsExpanded = !stopLossAlertsExpanded">
|
||||
<h4>⚠️ 止损预警 <span class="stoploss-badge">{{ stopLossAlerts.length }}</span></h4>
|
||||
<span :class="['toggle-icon', { expanded: stopLossAlertsExpanded }]">▼</span>
|
||||
</div>
|
||||
@@ -1985,7 +2032,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 交易记录表单弹窗 -->
|
||||
<div v-if="showTradeForm" class="modal-overlay" @click.self="showTradeForm = false">
|
||||
<div v-if="showTradeForm" class="modal-overlay" @click.self="showTradeForm = false" role="dialog" aria-modal="true" aria-label="记录交易">
|
||||
<div class="modal-content">
|
||||
<h3>记录交易</h3>
|
||||
<form @submit.prevent="submitTrade">
|
||||
@@ -2074,7 +2121,7 @@
|
||||
<button class="toggle-btn" @click.stop="toggleTradeGroup(group.code)">
|
||||
{{ expandedTradeGroups[group.code] ? '▼' : '▶' }}
|
||||
</button>
|
||||
<div class="trade-group-content" @click="loadFundamental(group.code, group.name)">
|
||||
<div class="trade-group-content" role="button" tabindex="0" @click="loadFundamental(group.code, group.name)" @keyup.enter="loadFundamental(group.code, group.name)">
|
||||
<!-- 第一行:股票信息 -->
|
||||
<div class="trade-group-info">
|
||||
<span class="trade-group-code">{{ group.code }}</span>
|
||||
|
||||
Reference in New Issue
Block a user