Spaces:
Sleeping
Sleeping
<html lang="zh-CN"> | |
<head> | |
<meta charset="UTF-8"> | |
<link rel="icon" href="/static/favicon.ico" type="image/x-icon"> | |
<link rel="shortcut icon" href="/static/favicon.ico" type="image/x-icon"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>{% block title %}智能分析系统{% endblock %}</title> | |
<!-- Bootstrap CSS --> | |
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"> | |
<!-- Font Awesome --> | |
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> | |
<link rel="stylesheet" href="{{ url_for('static', filename='css/theme.css') }}"> | |
<!-- ApexCharts --> | |
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/apexcharts.min.css" rel="stylesheet"> | |
<!-- Download PDF --> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script> | |
<!-- Custom CSS --> | |
<style> | |
body { | |
font-family: 'Helvetica Neue', Arial, sans-serif; | |
background-color: var(--bg-color); | |
color: var(--text-color); | |
} | |
.navbar-brand { | |
font-weight: bold; | |
} | |
.nav-item { | |
margin-left: 10px; | |
} | |
.sidebar { | |
background-color: var(--sidebar-bg-color); | |
color: white; | |
min-height: calc(100vh - 56px); | |
} | |
.sidebar .nav-link { | |
color: var(--sidebar-text-color); | |
padding: 0.75rem 1rem; | |
} | |
.sidebar .nav-link:hover { | |
color: #fff; | |
background-color: var(--sidebar-hover-bg-color); | |
} | |
.sidebar .nav-link.active { | |
color: #fff; | |
background-color: var(--sidebar-active-bg-color); | |
} | |
.sidebar .nav-link i { | |
margin-right: 10px; | |
width: 20px; | |
text-align: center; | |
} | |
.main-content { | |
padding: 20px; | |
} | |
.card { | |
margin-bottom: 20px; | |
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); | |
overflow: hidden; /* Prevent content from stretching container */ | |
} | |
.card-header { | |
padding: 0.5rem 1rem; | |
height: auto ; | |
max-height: 50px; | |
} | |
.form-control, .form-select, .input-group-text { | |
font-size: 0.875rem; | |
} | |
.input-group-sm .input-group-text { | |
padding: 0.25rem 0.5rem; | |
} | |
.card-body.py-2 { | |
padding-top: 0.5rem; | |
padding-bottom: 0.5rem; | |
} | |
.card-body { | |
padding: 1.25rem; | |
overflow: hidden; /* Prevent content from stretching container */ | |
} | |
.loading { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
height: 200px; | |
} | |
.spinner-border { | |
width: 3rem; | |
height: 3rem; | |
} | |
.badge-success { | |
background-color: #28a745; | |
} | |
.badge-danger { | |
background-color: #dc3545; | |
} | |
.badge-warning { | |
background-color: #ffc107; | |
color: #212529; | |
} | |
.score-pill { | |
font-size: 1.2rem; | |
padding: 0.5rem 1rem; | |
} | |
#loading-overlay { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(255, 255, 255, 0.8); | |
display: none; | |
justify-content: center; | |
align-items: center; | |
z-index: 9999; | |
} | |
.text-strong { | |
font-weight: bold; | |
} | |
.text-larger { | |
font-size: 1.1em; | |
} | |
.trend-up { | |
color: #28a745; | |
} | |
.trend-down { | |
color: #dc3545; | |
} | |
.analysis-section { | |
margin-bottom: 1.5rem; | |
} | |
/* Fix for chart container heights */ | |
#price-chart { | |
height: 400px ; | |
max-height: 400px; | |
} | |
/* Fix for indicators chart container */ | |
#indicators-chart { | |
height: 350px ; | |
max-height: 350px; | |
} | |
/* Fix chart containers */ | |
.apexcharts-canvas { | |
overflow: visible ; | |
} | |
/* Fix for radar chart */ | |
#radar-chart { | |
height: 200px ; | |
max-height: 200px; | |
} | |
/* Fix for score chart */ | |
#score-chart { | |
height: 200px ; | |
max-height: 200px; | |
} | |
/* Fix header alignment */ | |
.card-header h5 { | |
margin-bottom: 0; | |
display: flex; | |
align-items: center; | |
} | |
.apexcharts-tooltip { | |
background: #fff ; | |
border: 1px solid #e3e3e3 ; | |
box-shadow: 0 2px 5px rgba(0,0,0,0.1) ; | |
border-radius: 4px ; | |
padding: 8px ; | |
font-size: 13px ; | |
} | |
.apexcharts-tooltip-title { | |
background: #f8f9fa ; | |
border-bottom: 1px solid #e3e3e3 ; | |
padding: 6px 8px ; | |
margin-bottom: 4px ; | |
font-weight: 600 ; | |
} | |
.apexcharts-tooltip-y-group { | |
padding: 3px 0 ; | |
} | |
.apexcharts-tooltip-candlestick { | |
padding: 5px 8px ; | |
} | |
.apexcharts-tooltip-candlestick div { | |
margin: 3px 0 ; | |
} | |
.apexcharts-tooltip-candlestick span { | |
font-weight: 600 ; | |
} | |
.apexcharts-crosshairs { | |
stroke-width: 1px ; | |
stroke: #90A4AE ; | |
stroke-dasharray: 0 ; | |
opacity: 0.8 ; | |
} | |
.apexcharts-tooltip-marker { | |
width: 10px ; | |
height: 10px ; | |
display: inline-block ; | |
margin-right: 5px ; | |
border-radius: 50% ; | |
} | |
.apexcharts-tooltip-series-group { | |
padding: 4px 8px ; | |
border-bottom: 1px solid #eee ; | |
} | |
.apexcharts-tooltip-series-group:last-child { | |
border-bottom: none ; | |
} | |
.apexcharts-tooltip-text-y-value { | |
font-weight: 600 ; | |
} | |
.apexcharts-xaxistooltip { | |
background: #fff ; | |
border: 1px solid #e3e3e3 ; | |
border-radius: 2px ; | |
box-shadow: 0 2px 5px rgba(0,0,0,0.1) ; | |
padding: 4px 8px ; | |
font-size: 12px ; | |
color: #333 ; | |
} | |
.apexcharts-yaxistooltip { | |
background: #fff ; | |
border: 1px solid #e3e3e3 ; | |
border-radius: 2px ; | |
box-shadow: 0 2px 5px rgba(0,0,0,0.1) ; | |
padding: 4px 8px ; | |
font-size: 12px ; | |
color: #333 ; | |
} | |
/* AI分析样式 */ | |
.analysis-para { | |
line-height: 1.8; | |
margin-bottom: 1.2rem; | |
color: #333; | |
} | |
.keyword { | |
color: #2c7be5; | |
font-weight: 600; | |
} | |
.term { | |
color: #d6336c; | |
font-weight: 500; | |
padding: 0 2px; | |
} | |
.price { | |
color: #00a47c; | |
font-family: 'Roboto Mono', monospace; | |
background: #f3faf8; | |
padding: 2px 4px; | |
border-radius: 3px; | |
} | |
.date { | |
color: #6c757d; | |
font-family: 'Roboto Mono', monospace; | |
} | |
strong.keyword { | |
border-bottom: 2px solid #2c7be5; | |
} | |
.table-info { | |
position: relative; | |
} | |
.table-info:after { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
background: rgba(0, 123, 255, 0.1); | |
animation: pulse 1.5s infinite; | |
} | |
@keyframes pulse { | |
0% { opacity: 0.5; } | |
50% { opacity: 0.3; } | |
100% { opacity: 0.5; } | |
} | |
/* 财经门户样式 */ | |
.finance-portal-container { | |
display: grid; | |
grid-template-columns: 250px 1fr 300px; | |
grid-template-rows: 1fr 80px; | |
grid-template-areas: | |
"sidebar news hotspot" | |
"footer footer footer"; | |
height: calc(100vh - 56px); | |
overflow: hidden; | |
gap: 15px; | |
padding: 15px; | |
background-color: #f5f7fa; | |
} | |
/* 左侧栏样式 */ | |
.portal-sidebar { | |
grid-area: sidebar; | |
display: flex; | |
flex-direction: column; | |
background-color: #fff; | |
border-radius: 8px; | |
box-shadow: 0 2px 5px rgba(0,0,0,0.05); | |
overflow-y: auto; | |
padding: 15px; | |
} | |
.sidebar-header { | |
padding: 10px 0; | |
border-bottom: 1px solid #eee; | |
} | |
.sidebar-header h5 { | |
margin: 0; | |
color: #333; | |
font-size: 16px; | |
} | |
.sidebar-nav { | |
list-style: none; | |
padding: 0; | |
margin: 10px 0; | |
} | |
.sidebar-nav li { | |
margin-bottom: 5px; | |
} | |
.sidebar-nav a { | |
display: block; | |
padding: 10px 15px; | |
color: #444; | |
text-decoration: none; | |
border-radius: 5px; | |
transition: all 0.2s; | |
} | |
.sidebar-nav a:hover { | |
background-color: #f0f5ff; | |
color: #1a73e8; | |
} | |
.sidebar-nav i { | |
width: 20px; | |
margin-right: 8px; | |
text-align: center; | |
} | |
/* 中间新闻区域样式 */ | |
.portal-news { | |
grid-area: news; | |
display: flex; | |
flex-direction: column; | |
background-color: #fff; | |
border-radius: 8px; | |
box-shadow: 0 2px 5px rgba(0,0,0,0.05); | |
overflow: hidden; | |
} | |
.news-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
padding: 12px 15px; | |
border-bottom: 1px solid #eee; | |
background-color: #fff; | |
} | |
.news-header h5 { | |
margin: 0; | |
color: #333; | |
font-size: 16px; | |
} | |
.news-tools { | |
display: flex; | |
align-items: center; | |
} | |
.news-content { | |
flex: 1; | |
overflow-y: auto; | |
padding: 0; | |
} | |
/* 新闻时间线改进样式 */ | |
.news-timeline-container { | |
padding: 15px; | |
} | |
.time-point { | |
position: relative; | |
padding: 0 0 15px 65px; | |
min-height: 50px; | |
} | |
.time-point:before { | |
content: ''; | |
position: absolute; | |
left: 40px; | |
top: 8px; | |
width: 12px; | |
height: 12px; | |
border-radius: 50%; | |
background-color: #1a73e8; | |
z-index: 1; | |
} | |
.time-point:after { | |
content: ''; | |
position: absolute; | |
left: 45px; | |
top: 15px; | |
width: 2px; | |
height: calc(100% - 8px); | |
background-color: #e3e6ea; | |
} | |
.time-point:last-child:after { | |
display: none; | |
} | |
.time-label { | |
position: absolute; | |
left: 0; | |
top: 5px; | |
width: 35px; | |
text-align: right; | |
font-weight: bold; | |
font-size: 13px; | |
color: #444; | |
} | |
.news-items { | |
background-color: #f9f9f9; | |
border-radius: 8px; | |
box-shadow: 0 1px 3px rgba(0,0,0,0.03); | |
} | |
.news-item { | |
padding: 10px 15px; | |
border-bottom: 1px solid #eee; | |
} | |
.news-item:last-child { | |
border-bottom: none; | |
} | |
.news-content { | |
font-size: 14px; | |
line-height: 1.6; | |
color: #333; | |
} | |
/* 右侧热点区域样式 */ | |
.portal-hotspot { | |
grid-area: hotspot; | |
display: flex; | |
flex-direction: column; | |
background-color: #fff; | |
border-radius: 8px; | |
box-shadow: 0 2px 5px rgba(0,0,0,0.05); | |
overflow: hidden; | |
} | |
.hotspot-header { | |
padding: 12px 15px; | |
border-bottom: 1px solid #eee; | |
background-color: #fff; | |
} | |
.hotspot-header h5 { | |
margin: 0; | |
color: #333; | |
font-size: 16px; | |
} | |
.hotspot-content { | |
overflow-y: auto; | |
padding: 10px 15px; | |
flex: 1; | |
} | |
.hotspot-list { | |
display: flex; | |
flex-direction: column; | |
gap: 10px; | |
} | |
.hotspot-item { | |
display: flex; | |
align-items: flex-start; | |
gap: 10px; | |
padding: 8px 0; | |
border-bottom: 1px solid #f0f0f0; | |
} | |
.hotspot-item:last-child { | |
border-bottom: none; | |
} | |
.hotspot-rank { | |
display: inline-flex; | |
align-items: center; | |
justify-content: center; | |
width: 20px; | |
height: 20px; | |
border-radius: 4px; | |
background-color: #e9ecef; | |
color: #666; | |
font-size: 12px; | |
font-weight: bold; | |
} | |
.hotspot-rank.rank-top { | |
background-color: #fb6340; | |
color: #fff; | |
} | |
.hotspot-title { | |
flex: 1; | |
font-size: 14px; | |
line-height: 1.4; | |
color: #333; | |
} | |
/* 页脚区域样式 */ | |
.portal-footer { | |
grid-area: footer; | |
display: flex; | |
flex-direction: column; | |
background-color: #fff; | |
border-radius: 8px; | |
box-shadow: 0 2px 5px rgba(0,0,0,0.05); | |
} | |
/* 修改后的市场状态样式 */ | |
.market-status { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
height: 40px; | |
border-bottom: 1px solid #eee; | |
padding: 0 10px; | |
} | |
.market-group { | |
display: flex; | |
align-items: center; | |
gap: 10px; | |
} | |
.group-title { | |
font-size: 12px; | |
font-weight: bold; | |
color: #666; | |
white-space: nowrap; | |
} | |
.status-group { | |
display: flex; | |
gap: 15px; | |
} | |
.status-item { | |
display: flex; | |
align-items: center; | |
gap: 4px; | |
font-size: 12px; | |
white-space: nowrap; | |
} | |
.status-item i { | |
font-size: 10px; | |
} | |
.current-time { | |
display: flex; | |
align-items: center; | |
gap: 15px; | |
color: #666; | |
font-size: 12px; | |
} | |
.refresh-time { | |
color: #888; | |
} | |
i.status-open { | |
color: #2dce89; | |
} | |
i.status-closed { | |
color: #8898aa; | |
} | |
.ticker-news { | |
height: 40px; | |
overflow: hidden; | |
position: relative; | |
background-color: #f8f9fa; | |
} | |
.ticker-wrapper { | |
display: flex; | |
position: absolute; | |
white-space: nowrap; | |
} | |
.ticker-item { | |
padding: 0 30px; | |
line-height: 40px; | |
color: #333; | |
} | |
@keyframes ticker { | |
0% { | |
transform: translate3d(0, 0, 0); | |
} | |
100% { | |
transform: translate3d(-50%, 0, 0); | |
} | |
} | |
/* 响应式调整 */ | |
@media (max-width: 1200px) { | |
.finance-portal-container { | |
grid-template-columns: 200px 1fr 250px; | |
} | |
} | |
@media (max-width: 992px) { | |
.finance-portal-container { | |
grid-template-columns: 1fr; | |
grid-template-rows: auto 1fr auto auto; | |
grid-template-areas: | |
"sidebar" | |
"news" | |
"hotspot" | |
"footer"; | |
height: auto; | |
overflow: auto; | |
} | |
.portal-news, .portal-hotspot { | |
height: 500px; | |
} | |
.portal-footer { | |
position: fixed; | |
bottom: 0; | |
left: 0; | |
right: 0; | |
z-index: 1000; | |
border-radius: 0; | |
} | |
} | |
.time-date { | |
position: absolute; | |
left: 0; | |
top: 25px; | |
width: 35px; | |
text-align: right; | |
font-size: 11px; | |
color: #666; | |
font-weight: normal; | |
} | |
/* Global Task Monitor Styles */ | |
#task-monitor { | |
position: fixed; | |
bottom: 20px; | |
right: 20px; | |
width: 350px; | |
max-height: 400px; | |
z-index: 1050; | |
display: none; /* Initially hidden */ | |
} | |
#task-monitor .card-header { | |
cursor: pointer; | |
} | |
#task-monitor .task-item { | |
padding: 8px 12px; | |
border-bottom: 1px solid #eee; | |
} | |
#task-monitor .task-item:last-child { | |
border-bottom: none; | |
} | |
#task-monitor .task-item a { | |
text-decoration: none; | |
color: inherit; | |
} | |
#task-monitor .task-item small { | |
display: block; | |
color: #6c757d; | |
} | |
#task-monitor .progress { | |
height: 8px; | |
margin-top: 5px; | |
} | |
/* 调整时间点样式,为日期留出空间 */ | |
.time-point { | |
position: relative; | |
padding: 0 0 15px 65px; | |
min-height: 60px; /* 增加高度 */ | |
} | |
</style> | |
<script> | |
// Immediately apply the theme on page load to prevent FOUC | |
(function() { | |
const theme = localStorage.getItem('theme') || 'light'; | |
if (theme === 'dark') { | |
document.documentElement.setAttribute('data-theme', 'dark'); | |
} | |
})(); | |
</script> | |
{% block head %}{% endblock %} | |
</head> | |
<body> | |
<!-- Loading Overlay --> | |
<div id="loading-overlay"> | |
<div class="spinner-border text-primary" role="status"> | |
<span class="visually-hidden">Loading...</span> | |
</div> | |
</div> | |
<!-- Top Navigation --> | |
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"> | |
<div class="container-fluid"> | |
<a class="navbar-brand" href="/">智能分析系统</a> | |
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> | |
<span class="navbar-toggler-icon"></span> | |
</button> | |
<!-- 在layout.html的导航栏部分修改 --> | |
<div class="collapse navbar-collapse" id="navbarNav"> | |
<ul class="navbar-nav me-auto"> | |
<li class="nav-item"> | |
<a class="nav-link {% if request.path == '/' %}active{% endif %}" href="/"><i class="fas fa-home"></i> 主页</a> | |
</li> | |
<li class="nav-item"> | |
<a class="nav-link {% if request.path == '/dashboard' %}active{% endif %}" href="/dashboard"><i class="fas fa-chart-line"></i> 智能仪表盘</a> | |
</li> | |
<!-- 新增菜单项 - 基本面分析 --> | |
<li class="nav-item"> | |
<a class="nav-link {% if request.path.startswith('/fundamental') %}active{% endif %}" href="/fundamental"><i class="fas fa-file-invoice-dollar"></i> 基本面分析</a> | |
</li> | |
<!-- 新增菜单项 - 资金流向 --> | |
<li class="nav-item"> | |
<a class="nav-link {% if request.path.startswith('/capital_flow') %}active{% endif %}" href="/capital_flow"><i class="fas fa-money-bill-wave"></i> 资金流向</a> | |
</li> | |
<!-- 新增菜单项 - 情景预测 --> | |
<li class="nav-item"> | |
<a class="nav-link {% if request.path.startswith('/scenario') %}active{% endif %}" href="/scenario_predict"><i class="fas fa-lightbulb"></i> 情景预测</a> | |
</li> | |
<li class="nav-item"> | |
<a class="nav-link {% if request.path == '/market_scan' %}active{% endif %}" href="/market_scan"><i class="fas fa-search"></i> 市场扫描</a> | |
</li> | |
<li class="nav-item"> | |
<a class="nav-link {% if request.path == '/portfolio' %}active{% endif %}" href="/portfolio"><i class="fas fa-briefcase"></i> 投资组合</a> | |
</li> | |
<!-- 新增菜单项 - 风险监控 --> | |
<li class="nav-item"> | |
<a class="nav-link {% if request.path.startswith('/risk') %}active{% endif %}" href="/risk_monitor"><i class="fas fa-exclamation-triangle"></i> 风险监控</a> | |
</li> | |
<!-- 新增菜单项 - 智能问答 --> | |
<li class="nav-item"> | |
<a class="nav-link {% if request.path == '/qa' %}active{% endif %}" href="/qa"><i class="fas fa-question-circle"></i> 智能问答</a> | |
</li> | |
<li class="nav-item"> | |
<a class="nav-link {% if request.path == '/agent_analysis' %}active{% endif %}" href="/agent_analysis"><i class="fas fa-robot"></i> 智能体分析</a> | |
</li> | |
<li class="nav-item"> | |
<a class="nav-link {% if request.path == '/etf_analysis' %}active{% endif %}" href="/etf_analysis"><i class="fas fa-chart-pie"></i> ETF分析</a> | |
</li> | |
</ul> | |
<div class="d-flex align-items-center"> | |
<button class="btn text-white me-2" id="theme-toggle" title="切换主题"> | |
<i class="fas fa-moon"></i> | |
</button> | |
<div class="input-group"> | |
<input type="text" id="search-stock" class="form-control" placeholder="搜索股票代码/名称" aria-label="搜索股票"> | |
<button class="btn btn-light" type="button" id="search-button"> | |
<i class="fas fa-search"></i> | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</nav> | |
<div class="container-fluid"> | |
<div class="row"> | |
{% block sidebar %}{% endblock %} | |
<main class="{% if self.sidebar()|trim %}col-md-9 ms-sm-auto col-lg-10 px-md-4{% else %}col-12{% endif %} main-content"> | |
<div id="alerts-container"></div> | |
{% block content %}{% endblock %} | |
</main> | |
</div> | |
</div> | |
<!-- Global Task Monitor --> | |
<div id="task-monitor" class="card shadow-lg"> | |
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center py-2" data-bs-toggle="collapse" href="#task-monitor-body"> | |
<h6 class="mb-0"><i class="fas fa-tasks me-2"></i>后台分析任务</h6> | |
<span class="badge bg-light text-primary" id="task-count">0</span> | |
</div> | |
<div class="collapse show" id="task-monitor-body"> | |
<div class="card-body p-0" id="task-list" style="max-height: 300px; overflow-y: auto;"> | |
<!-- Active tasks will be injected here --> | |
</div> | |
</div> | |
</div> | |
<!-- Bootstrap JS with Popper --> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script> | |
<!-- jQuery --> | |
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> | |
<!-- ApexCharts --> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/apexcharts.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | |
<!-- Common JS --> | |
<script> | |
// 显示加载中覆盖层 | |
function showLoading() { | |
$('#loading-overlay').css('display', 'flex'); | |
} | |
// 隐藏加载中覆盖层 | |
function hideLoading() { | |
$('#loading-overlay').css('display', 'none'); | |
} | |
// 显示错误提示 | |
function showError(message) { | |
const alertHtml = ` | |
<div class="alert alert-danger alert-dismissible fade show" role="alert"> | |
${message} | |
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | |
</div> | |
`; | |
$('#alerts-container').html(alertHtml); | |
} | |
// 显示信息提示 | |
function showInfo(message) { | |
const alertHtml = ` | |
<div class="alert alert-info alert-dismissible fade show" role="alert"> | |
<i class="fas fa-info-circle me-2"></i>${message} | |
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | |
</div> | |
`; | |
$('#alerts-container').html(alertHtml); | |
} | |
// 显示成功提示 | |
function showSuccess(message) { | |
const alertHtml = ` | |
<div class="alert alert-success alert-dismissible fade show" role="alert"> | |
${message} | |
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | |
</div> | |
`; | |
$('#alerts-container').html(alertHtml); | |
} | |
// 搜索股票并跳转到详情页 | |
$('#search-button').click(function() { | |
const stockCode = $('#search-stock').val().trim(); | |
if (stockCode) { | |
window.location.href = `/stock_detail/${stockCode}`; | |
} | |
}); | |
// 回车键搜索 | |
$('#search-stock').keypress(function(e) { | |
if (e.which === 13) { | |
$('#search-button').click(); | |
} | |
}); | |
// Theme Toggle Logic | |
(function() { | |
const themeToggle = document.getElementById('theme-toggle'); | |
if (!themeToggle) return; | |
const themeIcon = themeToggle.querySelector('i'); | |
const htmlElement = document.documentElement; | |
// Set initial icon based on theme | |
if (htmlElement.getAttribute('data-theme') === 'dark') { | |
themeIcon.classList.remove('fa-moon'); | |
themeIcon.classList.add('fa-sun'); | |
} | |
themeToggle.addEventListener('click', function() { | |
const isDark = htmlElement.hasAttribute('data-theme'); | |
if (isDark) { | |
htmlElement.removeAttribute('data-theme'); | |
localStorage.setItem('theme', 'light'); | |
themeIcon.classList.remove('fa-sun'); | |
themeIcon.classList.add('fa-moon'); | |
} else { | |
htmlElement.setAttribute('data-theme', 'dark'); | |
localStorage.setItem('theme', 'dark'); | |
themeIcon.classList.remove('fa-moon'); | |
themeIcon.classList.add('fa-sun'); | |
} | |
}); | |
})(); | |
// 格式化数字 - 增强版 | |
function formatNumber(num, digits = 2) { | |
console.log('formatNumber called with:', num); | |
if (num === null || num === undefined) return '-'; | |
return parseFloat(num).toFixed(digits); | |
} | |
// 格式化技术指标 - 新增函数 | |
function formatIndicator(value, indicatorType) { | |
if (value === null || value === undefined) return '-'; | |
// 根据指标类型使用不同的小数位数 | |
if (indicatorType === 'MACD' || indicatorType === 'Signal' || indicatorType === 'Histogram') { | |
return parseFloat(value).toFixed(3); // MACD相关指标使用3位小数 | |
} else if (indicatorType === 'RSI') { | |
return parseFloat(value).toFixed(2); // RSI使用2位小数 | |
} else { | |
return parseFloat(value).toFixed(2); // 默认使用2位小数 | |
} | |
} | |
// 格式化百分比 | |
function formatPercent(num, digits = 2) { | |
console.log('formatPercent called with:', num); | |
if (num === null || num === undefined) return '-'; | |
return parseFloat(num).toFixed(digits) + '%'; | |
} | |
// 根据评分获取颜色类 | |
function getScoreColorClass(score) { | |
if (score >= 80) return 'bg-success'; | |
if (score >= 60) return 'bg-primary'; | |
if (score >= 40) return 'bg-warning'; | |
return 'bg-danger'; | |
} | |
// 根据趋势获取颜色类 | |
function getTrendColorClass(trend) { | |
return trend === 'UP' ? 'trend-up' : 'trend-down'; | |
} | |
// 根据趋势获取图标 | |
function getTrendIcon(trend) { | |
return trend === 'UP' ? '<i class="fas fa-arrow-up"></i>' : '<i class="fas fa-arrow-down"></i>'; | |
} | |
// Global Task Monitor Logic | |
$(document).ready(function() { | |
const monitor = $('#task-monitor'); | |
const taskList = $('#task-list'); | |
const taskCount = $('#task-count'); | |
function checkActiveTasks() { | |
$.ajax({ | |
url: '/api/active_tasks', | |
type: 'GET', | |
success: function(response) { | |
const tasks = response.active_tasks; | |
if (tasks && tasks.length > 0) { | |
taskList.empty(); | |
taskCount.text(tasks.length); | |
tasks.forEach(task => { | |
const taskHtml = ` | |
<div class="task-item d-flex justify-content-between align-items-center"> | |
<a href="/agent_analysis?task_id=${task.task_id}" class="text-decoration-none text-reset flex-grow-1"> | |
<strong>股票: ${task.stock_code}</strong> | |
<small class="d-block">${task.current_step}</small> | |
<div class="progress"> | |
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: ${task.progress}%;" aria-valuenow="${task.progress}" aria-valuemin="0" aria-valuemax="100"></div> | |
</div> | |
</a> | |
<button class="btn btn-sm btn-outline-danger delete-task-btn ms-2 flex-shrink-0" data-task-id="${task.task_id}" title="删除任务" style="border: none;"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
`; | |
taskList.append(taskHtml); | |
}); | |
monitor.fadeIn(); | |
} else { | |
monitor.fadeOut(); | |
} | |
}, | |
error: function(xhr) { | |
// Hide monitor on error to avoid confusion | |
monitor.fadeOut(); | |
console.error("Failed to fetch active tasks:", xhr.responseText); | |
} | |
}); | |
} | |
// Check for active tasks every 15 seconds | |
setInterval(checkActiveTasks, 15000); | |
// Initial check on page load | |
checkActiveTasks(); | |
// Handle task deletion | |
$(document).on('click', '#task-list .delete-task-btn', function(e) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
const button = $(this); | |
const taskId = button.data('task-id'); | |
const monitor = $('#task-monitor'); | |
const taskCount = $('#task-count'); | |
if (!taskId) { | |
console.error("Delete button clicked, but no task-id found."); | |
return; | |
} | |
if (confirm(`确定要删除任务 ${taskId} 吗?`)) { | |
$.ajax({ | |
url: '/api/delete_agent_analysis', | |
type: 'POST', | |
contentType: 'application/json', | |
data: JSON.stringify({ task_ids: [taskId] }), | |
success: function(response) { | |
if (response.success) { | |
button.closest('.task-item').fadeOut(300, function() { | |
$(this).remove(); | |
const currentCount = $('#task-list .task-item').length; | |
taskCount.text(currentCount); | |
if (currentCount === 0) { | |
monitor.fadeOut(); | |
} | |
}); | |
showSuccess(`任务 ${taskId} 已成功删除。`); | |
} else { | |
showError(response.error || '删除任务失败。'); | |
} | |
}, | |
error: function(xhr) { | |
showError('删除任务时连接服务器失败。'); | |
console.error("Failed to delete task:", xhr.responseText); | |
} | |
}); | |
} | |
}); | |
}); | |
</script> | |
{% block scripts %}{% endblock %} | |
</body> | |
</html> | |