增加密钥
This commit is contained in:
parent
9339b7a558
commit
6ec1feb2a7
@ -14,6 +14,7 @@ services:
|
|||||||
- FLASK_ENV=production
|
- FLASK_ENV=production
|
||||||
- PYTHONPATH=/app
|
- PYTHONPATH=/app
|
||||||
- TZ=Asia/Shanghai
|
- TZ=Asia/Shanghai
|
||||||
|
- OPERATION_KEY=${OPERATION_KEY:-9257}
|
||||||
# MySQL连接配置 (可通过环境变量覆盖)
|
# MySQL连接配置 (可通过环境变量覆盖)
|
||||||
- MYSQL_HOST=${MYSQL_HOST:-cd-cynosdbmysql-grp-7kdd8qe4.sql.tencentcdb.com}
|
- MYSQL_HOST=${MYSQL_HOST:-cd-cynosdbmysql-grp-7kdd8qe4.sql.tencentcdb.com}
|
||||||
- MYSQL_PORT=${MYSQL_PORT:-26558}
|
- MYSQL_PORT=${MYSQL_PORT:-26558}
|
||||||
@ -39,6 +40,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- PYTHONPATH=/app
|
- PYTHONPATH=/app
|
||||||
- TZ=Asia/Shanghai
|
- TZ=Asia/Shanghai
|
||||||
|
- OPERATION_KEY=${OPERATION_KEY:-9257}
|
||||||
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||||
- MARKET_SCAN_STOCKS=${MARKET_SCAN_STOCKS:-200}
|
- MARKET_SCAN_STOCKS=${MARKET_SCAN_STOCKS:-200}
|
||||||
# MySQL连接配置
|
# MySQL连接配置
|
||||||
|
|||||||
@ -5,6 +5,7 @@ AI 智能选股大师 MySQL版本 Web 展示界面
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from flask import Flask, render_template, jsonify, request
|
from flask import Flask, render_template, jsonify, request
|
||||||
from datetime import datetime, date, timedelta
|
from datetime import datetime, date, timedelta
|
||||||
@ -22,6 +23,9 @@ from loguru import logger
|
|||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = 'trading_ai_mysql_secret_key_2023'
|
app.secret_key = 'trading_ai_mysql_secret_key_2023'
|
||||||
|
|
||||||
|
# 操作验证密钥(放在导入后立即定义)
|
||||||
|
OPERATION_KEY = os.environ.get('OPERATION_KEY', '9257')
|
||||||
|
|
||||||
# 初始化组件
|
# 初始化组件
|
||||||
db_manager = MySQLDatabaseManager()
|
db_manager = MySQLDatabaseManager()
|
||||||
config_loader = ConfigLoader()
|
config_loader = ConfigLoader()
|
||||||
@ -172,6 +176,17 @@ def api_stats():
|
|||||||
def api_clear_signals():
|
def api_clear_signals():
|
||||||
"""API接口 - 清空信号数据"""
|
"""API接口 - 清空信号数据"""
|
||||||
try:
|
try:
|
||||||
|
# 获取密钥验证
|
||||||
|
operation_key = request.json.get('operation_key', '') if request.is_json else request.form.get('operation_key', '')
|
||||||
|
|
||||||
|
# 验证密钥
|
||||||
|
if operation_key != OPERATION_KEY:
|
||||||
|
logger.warning(f"清空信号操作密钥验证失败: {operation_key}")
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': '操作密钥验证失败,请输入正确的验证密钥'
|
||||||
|
})
|
||||||
|
|
||||||
# 获取清空范围参数
|
# 获取清空范围参数
|
||||||
days = request.json.get('days', 7) if request.is_json else int(request.form.get('days', 7))
|
days = request.json.get('days', 7) if request.is_json else int(request.form.get('days', 7))
|
||||||
strategy_name = request.json.get('strategy_name', '') if request.is_json else request.form.get('strategy_name', '')
|
strategy_name = request.json.get('strategy_name', '') if request.is_json else request.form.get('strategy_name', '')
|
||||||
@ -204,6 +219,17 @@ def api_run_analysis():
|
|||||||
import threading
|
import threading
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
# 获取密钥验证
|
||||||
|
operation_key = request.json.get('operation_key', '') if request.is_json else request.form.get('operation_key', '')
|
||||||
|
|
||||||
|
# 验证密钥
|
||||||
|
if operation_key != OPERATION_KEY:
|
||||||
|
logger.warning(f"立即分析操作密钥验证失败: {operation_key}")
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': '操作密钥验证失败,请输入正确的验证密钥'
|
||||||
|
})
|
||||||
|
|
||||||
# 检查是否已有分析在运行
|
# 检查是否已有分析在运行
|
||||||
if analysis_status['is_running']:
|
if analysis_status['is_running']:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
|
|||||||
@ -1110,4 +1110,367 @@ footer .text-muted {
|
|||||||
font-size: 0.65rem;
|
font-size: 0.65rem;
|
||||||
padding: 0.2rem 0.4rem;
|
padding: 0.2rem 0.4rem;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 自定义模态框样式 ========== */
|
||||||
|
|
||||||
|
/* 模态框整体美化 */
|
||||||
|
.modal-content {
|
||||||
|
border: none !important;
|
||||||
|
border-radius: var(--radius-lg) !important;
|
||||||
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25) !important;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
animation: modalFadeIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes modalFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9) translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 模态框背景遮罩美化 */
|
||||||
|
.modal-backdrop {
|
||||||
|
background: linear-gradient(135deg, rgba(0, 0, 0, 0.4), rgba(37, 99, 235, 0.1)) !important;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 密钥输入模态框 */
|
||||||
|
#keyModal .modal-header {
|
||||||
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
|
||||||
|
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyModal .modal-title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyModal .modal-body {
|
||||||
|
background: linear-gradient(135deg, var(--bg-accent) 0%, var(--bg-secondary) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyModal #operationKey {
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--bg-accent);
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyModal #operationKey:focus {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 4px var(--primary-lighter), inset 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#keyModal #operationKey.is-invalid {
|
||||||
|
border-color: var(--danger-color);
|
||||||
|
box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.1);
|
||||||
|
animation: shake 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
0%, 100% { transform: translateX(0); }
|
||||||
|
25% { transform: translateX(-5px); }
|
||||||
|
75% { transform: translateX(5px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确认操作模态框 */
|
||||||
|
#confirmModal .modal-header.bg-danger {
|
||||||
|
background: linear-gradient(135deg, var(--danger-color) 0%, #dc2626 100%) !important;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#confirmModal .modal-header.bg-warning {
|
||||||
|
background: linear-gradient(135deg, var(--warning-color) 0%, #d97706 100%) !important;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#confirmModal .modal-header.bg-info {
|
||||||
|
background: linear-gradient(135deg, var(--info-color) 0%, #0891b2 100%) !important;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#confirmModal .modal-body {
|
||||||
|
background: linear-gradient(135deg, var(--bg-accent) 0%, var(--bg-secondary) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#confirmModal #confirmIcon.text-danger {
|
||||||
|
color: var(--danger-color) !important;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
#confirmModal #confirmIcon.text-warning {
|
||||||
|
color: var(--warning-color) !important;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
#confirmModal #confirmIcon.text-info {
|
||||||
|
color: var(--info-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { transform: scale(1); opacity: 1; }
|
||||||
|
50% { transform: scale(1.05); opacity: 0.8; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 输入参数模态框 */
|
||||||
|
#inputModal .modal-header {
|
||||||
|
background: linear-gradient(135deg, var(--info-color) 0%, #0891b2 100%);
|
||||||
|
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#inputModal .modal-body {
|
||||||
|
background: linear-gradient(135deg, var(--bg-accent) 0%, var(--bg-secondary) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#inputModal #inputValue {
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--bg-accent);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
#inputModal #inputValue:focus {
|
||||||
|
border-color: var(--info-color);
|
||||||
|
box-shadow: 0 0 0 4px rgba(6, 182, 212, 0.1), inset 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#inputModal #inputValue.is-invalid {
|
||||||
|
border-color: var(--danger-color);
|
||||||
|
box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.1);
|
||||||
|
animation: shake 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通知提示模态框 */
|
||||||
|
#alertModal .modal-header.bg-success {
|
||||||
|
background: linear-gradient(135deg, var(--success-color) 0%, #059669 100%) !important;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#alertModal .modal-header.bg-error {
|
||||||
|
background: linear-gradient(135deg, var(--danger-color) 0%, #dc2626 100%) !important;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#alertModal .modal-header.bg-warning {
|
||||||
|
background: linear-gradient(135deg, var(--warning-color) 0%, #d97706 100%) !important;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#alertModal .modal-header.bg-info {
|
||||||
|
background: linear-gradient(135deg, var(--info-color) 0%, #0891b2 100%) !important;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#alertModal .modal-body {
|
||||||
|
background: linear-gradient(135deg, var(--bg-accent) 0%, var(--bg-secondary) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#alertModal #alertIcon.text-success {
|
||||||
|
color: var(--success-color) !important;
|
||||||
|
animation: bounce 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
#alertModal #alertIcon.text-error {
|
||||||
|
color: var(--danger-color) !important;
|
||||||
|
animation: shake 1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#alertModal #alertIcon.text-warning {
|
||||||
|
color: var(--warning-color) !important;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
#alertModal #alertIcon.text-info {
|
||||||
|
color: var(--info-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
0%, 20%, 50%, 80%, 100% { transform: translateY(0); }
|
||||||
|
40% { transform: translateY(-8px); }
|
||||||
|
60% { transform: translateY(-4px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 模态框按钮美化 */
|
||||||
|
.modal .btn {
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .btn::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transition: width 0.6s, height 0.6s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .btn:hover::before {
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .btn-primary {
|
||||||
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .btn-primary:hover {
|
||||||
|
background: linear-gradient(135deg, #1d4ed8 0%, var(--primary-color) 100%);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 20px rgba(37, 99, 235, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .btn-danger {
|
||||||
|
background: linear-gradient(135deg, var(--danger-color) 0%, #dc2626 100%);
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .btn-danger:hover {
|
||||||
|
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 20px rgba(239, 68, 68, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .btn-info {
|
||||||
|
background: linear-gradient(135deg, var(--info-color) 0%, #0891b2 100%);
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 4px 12px rgba(6, 182, 212, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .btn-info:hover {
|
||||||
|
background: linear-gradient(135deg, #0891b2 0%, #0e7490 100%);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 20px rgba(6, 182, 212, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .btn-secondary {
|
||||||
|
background: linear-gradient(135deg, var(--secondary-color) 0%, #475569 100%);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 12px rgba(100, 116, 139, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .btn-secondary:hover {
|
||||||
|
background: linear-gradient(135deg, #475569 0%, #334155 100%);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 20px rgba(100, 116, 139, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 模态框输入框验证状态 */
|
||||||
|
.modal .form-control.is-invalid {
|
||||||
|
border-color: var(--danger-color);
|
||||||
|
box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .form-control.is-valid {
|
||||||
|
border-color: var(--success-color);
|
||||||
|
box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .invalid-feedback {
|
||||||
|
display: block;
|
||||||
|
color: var(--danger-color);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
animation: slideIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 模态框图标美化 */
|
||||||
|
.modal i[style*="font-size: 3rem"] {
|
||||||
|
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.1));
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal i[style*="font-size: 3rem"]:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 模态框关闭按钮美化 */
|
||||||
|
.modal .btn-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .btn-close:hover {
|
||||||
|
opacity: 1;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 模态框表单标签美化 */
|
||||||
|
.modal .form-label {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .form-text {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式模态框 */
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.modal-dialog {
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .btn {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal i[style*="font-size: 3rem"] {
|
||||||
|
font-size: 2rem !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -278,6 +278,127 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 自定义模态框 -->
|
||||||
|
<!-- 密钥输入模态框 -->
|
||||||
|
<div class="modal fade" id="keyModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content border-0 shadow">
|
||||||
|
<div class="modal-header bg-primary text-white">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
<i class="fas fa-key me-2"></i>操作验证
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body p-4">
|
||||||
|
<div class="text-center mb-3">
|
||||||
|
<i class="fas fa-shield-alt text-primary" style="font-size: 3rem;"></i>
|
||||||
|
</div>
|
||||||
|
<p class="text-center text-muted mb-4">请输入操作验证密钥以继续</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="operationKey" class="form-label fw-bold">验证密钥</label>
|
||||||
|
<input type="password" class="form-control form-control-lg text-center"
|
||||||
|
id="operationKey" placeholder="请输入验证密钥">
|
||||||
|
<div class="invalid-feedback" id="keyError"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0 pt-0">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times me-1"></i>取消
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="confirmKey">
|
||||||
|
<i class="fas fa-check me-1"></i>确认
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 确认操作模态框 -->
|
||||||
|
<div class="modal fade" id="confirmModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content border-0 shadow">
|
||||||
|
<div class="modal-header" id="confirmHeader">
|
||||||
|
<h5 class="modal-title" id="confirmTitle">
|
||||||
|
<i class="fas fa-question-circle me-2"></i>确认操作
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body p-4">
|
||||||
|
<div class="text-center mb-3">
|
||||||
|
<i id="confirmIcon" class="fas fa-exclamation-triangle text-warning" style="font-size: 3rem;"></i>
|
||||||
|
</div>
|
||||||
|
<p class="text-center" id="confirmMessage">确认要执行此操作吗?</p>
|
||||||
|
<div id="confirmDetails" class="mt-3" style="display: none;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0 pt-0">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times me-1"></i>取消
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn" id="confirmAction">
|
||||||
|
<i class="fas fa-check me-1"></i>确认
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 输入参数模态框 -->
|
||||||
|
<div class="modal fade" id="inputModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content border-0 shadow">
|
||||||
|
<div class="modal-header bg-info text-white">
|
||||||
|
<h5 class="modal-title" id="inputTitle">
|
||||||
|
<i class="fas fa-edit me-2"></i>参数设置
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body p-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputValue" class="form-label fw-bold" id="inputLabel">请输入参数</label>
|
||||||
|
<input type="number" class="form-control form-control-lg text-center"
|
||||||
|
id="inputValue" placeholder="">
|
||||||
|
<div class="form-text text-muted" id="inputHelp"></div>
|
||||||
|
<div class="invalid-feedback" id="inputError"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0 pt-0">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times me-1"></i>取消
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-info" id="confirmInput">
|
||||||
|
<i class="fas fa-check me-1"></i>确认
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 通知提示模态框 -->
|
||||||
|
<div class="modal fade" id="alertModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content border-0 shadow">
|
||||||
|
<div class="modal-header" id="alertHeader">
|
||||||
|
<h5 class="modal-title" id="alertTitle">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>提示
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body p-4 text-center">
|
||||||
|
<div class="mb-3">
|
||||||
|
<i id="alertIcon" class="fas fa-info-circle text-info" style="font-size: 3rem;"></i>
|
||||||
|
</div>
|
||||||
|
<p id="alertMessage" class="mb-0">操作完成</p>
|
||||||
|
<div id="alertDetails" class="mt-3 text-start" style="display: none;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0 pt-0 justify-content-center">
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" id="alertConfirm">
|
||||||
|
<i class="fas fa-check me-1"></i>确定
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
@ -298,41 +419,403 @@
|
|||||||
window.location.search = urlParams.toString();
|
window.location.search = urlParams.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空信号按钮
|
// 全局变量用于存储操作回调
|
||||||
$('#clearSignalsBtn').on('click', function() {
|
let currentOperationCallback = null;
|
||||||
if (confirm('确认要清空最近7天的信号数据吗?此操作不可恢复!')) {
|
let currentInputCallback = null;
|
||||||
const btn = $(this);
|
let pendingOperationKey = null; // 存储密钥
|
||||||
const originalText = btn.html();
|
|
||||||
|
|
||||||
btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin me-1"></i>清空中...');
|
// ========== 自定义模态框函数 ==========
|
||||||
|
|
||||||
$.ajax({
|
// 显示验证密钥模态框
|
||||||
url: '/api/clear_signals',
|
function showKeyModal(title, message, callback) {
|
||||||
method: 'POST',
|
$('#keyModal .modal-title').text(title);
|
||||||
contentType: 'application/json',
|
$('#keyModal p').text(message);
|
||||||
data: JSON.stringify({
|
$('#operationKey').val('').removeClass('is-invalid');
|
||||||
days: 7,
|
$('#keyError').text('');
|
||||||
strategy_name: ''
|
currentOperationCallback = callback;
|
||||||
}),
|
new bootstrap.Modal($('#keyModal')).show();
|
||||||
success: function(response) {
|
}
|
||||||
if (response.success) {
|
|
||||||
alert(`清空成功!删除了 ${response.deleted_count} 条信号记录`);
|
// 显示确认操作模态框
|
||||||
location.reload();
|
function showConfirmModal(title, message, type = 'warning', callback) {
|
||||||
} else {
|
const $header = $('#confirmHeader');
|
||||||
alert('清空失败:' + response.error);
|
const $icon = $('#confirmIcon');
|
||||||
}
|
const $action = $('#confirmAction');
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
// 设置标题
|
||||||
alert('清空失败:网络错误或服务器异常');
|
$('#confirmTitle').html(`<i class="fas fa-question-circle me-2"></i>${title}`);
|
||||||
console.error('清空信号失败:', error);
|
$('#confirmMessage').text(message);
|
||||||
},
|
|
||||||
complete: function() {
|
// 根据类型设置样式
|
||||||
btn.prop('disabled', false).html(originalText);
|
$header.removeClass('bg-danger bg-warning bg-info').addClass(`bg-${type}`);
|
||||||
}
|
$icon.removeClass('text-danger text-warning text-info').addClass(`text-${type}`);
|
||||||
});
|
$action.removeClass('btn-danger btn-warning btn-info').addClass(`btn-${type}`);
|
||||||
|
|
||||||
|
// 设置图标
|
||||||
|
const icons = {
|
||||||
|
'danger': 'fas fa-exclamation-triangle',
|
||||||
|
'warning': 'fas fa-exclamation-triangle',
|
||||||
|
'info': 'fas fa-info-circle'
|
||||||
|
};
|
||||||
|
$icon.attr('class', `${icons[type] || icons.warning} text-${type}`).css('font-size', '3rem');
|
||||||
|
|
||||||
|
currentOperationCallback = callback;
|
||||||
|
new bootstrap.Modal($('#confirmModal')).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示输入参数模态框
|
||||||
|
function showInputModal(title, label, placeholder, helpText, callback) {
|
||||||
|
$('#inputTitle').html(`<i class="fas fa-edit me-2"></i>${title}`);
|
||||||
|
$('#inputLabel').text(label);
|
||||||
|
$('#inputValue').attr('placeholder', placeholder).val(placeholder).removeClass('is-invalid');
|
||||||
|
$('#inputHelp').text(helpText);
|
||||||
|
$('#inputError').text('');
|
||||||
|
currentInputCallback = callback;
|
||||||
|
new bootstrap.Modal($('#inputModal')).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示通知提示模态框
|
||||||
|
function showAlertModal(title, message, type = 'info', details = null) {
|
||||||
|
const $header = $('#alertHeader');
|
||||||
|
const $icon = $('#alertIcon');
|
||||||
|
const $confirm = $('#alertConfirm');
|
||||||
|
|
||||||
|
// 设置标题
|
||||||
|
$('#alertTitle').html(`<i class="fas fa-info-circle me-2"></i>${title}`);
|
||||||
|
$('#alertMessage').text(message);
|
||||||
|
|
||||||
|
// 根据类型设置样式
|
||||||
|
const typeClass = type === 'error' ? 'error' : type;
|
||||||
|
$header.removeClass('bg-success bg-error bg-warning bg-info').addClass(`bg-${typeClass}`);
|
||||||
|
$icon.removeClass('text-success text-error text-warning text-info').addClass(`text-${typeClass}`);
|
||||||
|
|
||||||
|
// 设置图标
|
||||||
|
const icons = {
|
||||||
|
'success': 'fas fa-check-circle',
|
||||||
|
'error': 'fas fa-exclamation-circle',
|
||||||
|
'warning': 'fas fa-exclamation-triangle',
|
||||||
|
'info': 'fas fa-info-circle'
|
||||||
|
};
|
||||||
|
$icon.attr('class', `${icons[type] || icons.info} text-${typeClass}`).css('font-size', '3rem');
|
||||||
|
|
||||||
|
// 显示详情
|
||||||
|
if (details) {
|
||||||
|
$('#alertDetails').html(details).show();
|
||||||
|
} else {
|
||||||
|
$('#alertDetails').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
new bootstrap.Modal($('#alertModal')).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 模态框事件处理 ==========
|
||||||
|
|
||||||
|
// 验证密钥确认
|
||||||
|
$(document).on('click', '#confirmKey', function() {
|
||||||
|
const key = $('#operationKey').val().trim();
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
$('#operationKey').addClass('is-invalid');
|
||||||
|
$('#keyError').text('请输入验证密钥');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const modalInstance = bootstrap.Modal.getInstance($('#keyModal')[0]);
|
||||||
|
if (modalInstance) {
|
||||||
|
modalInstance.hide();
|
||||||
|
} else {
|
||||||
|
$('#keyModal').modal('hide');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$('#keyModal').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.handleKeyConfirm) {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.handleKeyConfirm(key);
|
||||||
|
}, 300);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 确认操作
|
||||||
|
$(document).on('click', '#confirmAction', function() {
|
||||||
|
try {
|
||||||
|
const modalInstance = bootstrap.Modal.getInstance($('#confirmModal')[0]);
|
||||||
|
if (modalInstance) {
|
||||||
|
modalInstance.hide();
|
||||||
|
} else {
|
||||||
|
$('#confirmModal').modal('hide');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$('#confirmModal').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.handleConfirmAction) {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.handleConfirmAction();
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 输入参数确认
|
||||||
|
$(document).on('click', '#confirmInput', function() {
|
||||||
|
const value = $('#inputValue').val().trim();
|
||||||
|
// 如果输入为空,使用placeholder作为默认值
|
||||||
|
const finalValue = value || $('#inputValue').attr('placeholder');
|
||||||
|
|
||||||
|
if (!finalValue) {
|
||||||
|
$('#inputValue').addClass('is-invalid');
|
||||||
|
$('#inputError').text('请输入有效的参数值');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const modalInstance = bootstrap.Modal.getInstance($('#inputModal')[0]);
|
||||||
|
if (modalInstance) {
|
||||||
|
modalInstance.hide();
|
||||||
|
} else {
|
||||||
|
$('#inputModal').modal('hide');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$('#inputModal').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.handleInputConfirm) {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.handleInputConfirm(finalValue);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 回车键支持
|
||||||
|
$('#operationKey').on('keypress', function(e) {
|
||||||
|
if (e.which === 13) $('#confirmKey').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#inputValue').on('keypress', function(e) {
|
||||||
|
if (e.which === 13) $('#confirmInput').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清空验证错误
|
||||||
|
$('#operationKey').on('input', function() {
|
||||||
|
$(this).removeClass('is-invalid');
|
||||||
|
$('#keyError').text('');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#inputValue').on('input', function() {
|
||||||
|
$(this).removeClass('is-invalid');
|
||||||
|
$('#inputError').text('');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清空信号按钮
|
||||||
|
$('#clearSignalsBtn').on('click', function() {
|
||||||
|
// 显示密钥输入模态框
|
||||||
|
$('#keyModal .modal-title').text('操作验证');
|
||||||
|
$('#keyModal p').text('请输入操作验证密钥以清空信号数据');
|
||||||
|
$('#operationKey').val('').removeClass('is-invalid');
|
||||||
|
$('#keyError').text('');
|
||||||
|
|
||||||
|
// 设置全局标识
|
||||||
|
window.currentOperation = 'clear_signals';
|
||||||
|
|
||||||
|
new bootstrap.Modal($('#keyModal')).show();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 全局处理密钥确认
|
||||||
|
window.handleKeyConfirm = function(operationKey) {
|
||||||
|
if (window.currentOperation === 'clear_signals') {
|
||||||
|
// 显示确认模态框
|
||||||
|
$('#confirmModal .modal-title').html('<i class="fas fa-question-circle me-2"></i>确认清空信号');
|
||||||
|
$('#confirmMessage').text('确认要清空最近7天的信号数据吗?此操作不可恢复!');
|
||||||
|
$('#confirmHeader').removeClass('bg-danger bg-warning bg-info').addClass('bg-danger');
|
||||||
|
$('#confirmIcon').removeClass('text-danger text-warning text-info').addClass('text-danger');
|
||||||
|
$('#confirmAction').removeClass('btn-danger btn-warning btn-info').addClass('btn-danger');
|
||||||
|
$('#confirmIcon').attr('class', 'fas fa-exclamation-triangle text-danger').css('font-size', '3rem');
|
||||||
|
|
||||||
|
// 存储密钥
|
||||||
|
window.pendingOperationKey = operationKey;
|
||||||
|
window.currentOperation = 'confirm_clear_signals';
|
||||||
|
|
||||||
|
new bootstrap.Modal($('#confirmModal')).show();
|
||||||
|
} else if (window.currentOperation === 'run_analysis') {
|
||||||
|
// 显示参数输入模态框
|
||||||
|
$('#inputModal .modal-title').html('<i class="fas fa-edit me-2"></i>设置分析参数');
|
||||||
|
$('#inputLabel').text('扫描股票数量');
|
||||||
|
$('#inputValue').attr('placeholder', '200').val('200').removeClass('is-invalid');
|
||||||
|
$('#inputHelp').text('请输入要扫描的股票数量 (范围: 1-1000)');
|
||||||
|
$('#inputError').text('');
|
||||||
|
|
||||||
|
// 存储密钥
|
||||||
|
window.pendingOperationKey = operationKey;
|
||||||
|
window.currentOperation = 'input_analysis_params';
|
||||||
|
|
||||||
|
new bootstrap.Modal($('#inputModal')).show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 全局处理确认操作
|
||||||
|
window.handleConfirmAction = function() {
|
||||||
|
if (window.currentOperation === 'confirm_clear_signals' && window.pendingOperationKey) {
|
||||||
|
executeClearSignals(window.pendingOperationKey);
|
||||||
|
window.currentOperation = null;
|
||||||
|
window.pendingOperationKey = null;
|
||||||
|
} else if (window.currentOperation === 'confirm_run_analysis' && window.pendingOperationKey && window.pendingStockCount) {
|
||||||
|
executeRunAnalysis(window.pendingOperationKey, window.pendingStockCount);
|
||||||
|
window.currentOperation = null;
|
||||||
|
window.pendingOperationKey = null;
|
||||||
|
window.pendingStockCount = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 全局处理输入参数确认
|
||||||
|
window.handleInputConfirm = function(inputValue) {
|
||||||
|
if (window.currentOperation === 'input_analysis_params' && window.pendingOperationKey) {
|
||||||
|
const count = parseInt(inputValue) || 200;
|
||||||
|
|
||||||
|
if (count <= 0 || count > 1000) {
|
||||||
|
showAlertModal('参数错误', '股票数量必须在 1-1000 之间', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示确认模态框
|
||||||
|
$('#confirmModal .modal-title').html('<i class="fas fa-question-circle me-2"></i>确认分析');
|
||||||
|
$('#confirmMessage').text(`确认要立即分析 ${count} 只热门股票吗?分析可能需要几分钟时间`);
|
||||||
|
$('#confirmHeader').removeClass('bg-danger bg-warning bg-info').addClass('bg-info');
|
||||||
|
$('#confirmIcon').removeClass('text-danger text-warning text-info').addClass('text-info');
|
||||||
|
$('#confirmAction').removeClass('btn-danger btn-warning btn-info').addClass('btn-info');
|
||||||
|
$('#confirmIcon').attr('class', 'fas fa-info-circle text-info').css('font-size', '3rem');
|
||||||
|
|
||||||
|
// 存储参数
|
||||||
|
window.pendingStockCount = count;
|
||||||
|
window.currentOperation = 'confirm_run_analysis';
|
||||||
|
|
||||||
|
new bootstrap.Modal($('#confirmModal')).show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 执行清空信号的实际操作
|
||||||
|
function executeClearSignals(operationKey) {
|
||||||
|
const btn = $('#clearSignalsBtn');
|
||||||
|
const originalText = btn.html();
|
||||||
|
|
||||||
|
btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin me-1"></i>清空中...');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/clear_signals',
|
||||||
|
method: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify({
|
||||||
|
operation_key: operationKey,
|
||||||
|
days: 7,
|
||||||
|
strategy_name: ''
|
||||||
|
}),
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
showAlertModal(
|
||||||
|
'清空成功',
|
||||||
|
`成功删除了 ${response.deleted_count} 条信号记录`,
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
setTimeout(() => location.reload(), 2000);
|
||||||
|
} else {
|
||||||
|
// 检查是否是密钥验证失败
|
||||||
|
if (response.error && response.error.includes('密钥验证失败')) {
|
||||||
|
showAlertModal('验证失败', '操作密钥验证失败,请检查密钥是否正确', 'warning');
|
||||||
|
} else {
|
||||||
|
showAlertModal('清空失败', response.error || '未知错误', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
let errorMessage = '网络错误或服务器异常';
|
||||||
|
|
||||||
|
// 检查HTTP状态码
|
||||||
|
if (xhr.status === 401 || xhr.status === 403) {
|
||||||
|
errorMessage = '操作密钥验证失败,请检查密钥是否正确';
|
||||||
|
} else if (xhr.responseJSON && xhr.responseJSON.error) {
|
||||||
|
errorMessage = xhr.responseJSON.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
showAlertModal('操作失败', errorMessage, 'error');
|
||||||
|
console.error('清空信号失败:', error, xhr);
|
||||||
|
},
|
||||||
|
complete: function() {
|
||||||
|
btn.prop('disabled', false).html(originalText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行立即分析的实际操作
|
||||||
|
function executeRunAnalysis(operationKey, stockCount) {
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/run_analysis',
|
||||||
|
method: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify({
|
||||||
|
operation_key: operationKey,
|
||||||
|
stock_count: stockCount
|
||||||
|
}),
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
// 重置刷新标志
|
||||||
|
hasRefreshedAfterCompletion = false;
|
||||||
|
|
||||||
|
// 立即开始状态轮询
|
||||||
|
checkAnalysisStatus();
|
||||||
|
|
||||||
|
// 设置备用刷新机制(10分钟后强制刷新,以防状态检查失败)
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!hasRefreshedAfterCompletion) {
|
||||||
|
console.log('备用刷新机制触发:分析可能已完成但状态检查失败');
|
||||||
|
showAlertModal(
|
||||||
|
'刷新页面',
|
||||||
|
'分析可能已完成,正在刷新页面显示最新结果',
|
||||||
|
'info'
|
||||||
|
);
|
||||||
|
setTimeout(() => location.reload(), 1500);
|
||||||
|
}
|
||||||
|
}, 600000); // 10分钟
|
||||||
|
|
||||||
|
const details = `
|
||||||
|
<div class="text-start">
|
||||||
|
<p><strong>扫描股票:</strong> ${response.stock_count} 只</p>
|
||||||
|
<p><strong>启动时间:</strong> ${response.start_time}</p>
|
||||||
|
<p><strong>状态:</strong> 页面将自动显示分析进度</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
showAlertModal(
|
||||||
|
'分析任务已启动',
|
||||||
|
'正在后台扫描股票,请稍候...',
|
||||||
|
'success',
|
||||||
|
details
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 检查是否是密钥验证失败
|
||||||
|
if (response.error && response.error.includes('密钥验证失败')) {
|
||||||
|
showAlertModal('验证失败', '操作密钥验证失败,请检查密钥是否正确', 'warning');
|
||||||
|
} else {
|
||||||
|
showAlertModal('启动失败', response.error || '未知错误', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
let errorMessage = '网络错误或服务器异常';
|
||||||
|
|
||||||
|
// 检查HTTP状态码
|
||||||
|
if (xhr.status === 401 || xhr.status === 403) {
|
||||||
|
errorMessage = '操作密钥验证失败,请检查密钥是否正确';
|
||||||
|
} else if (xhr.responseJSON && xhr.responseJSON.error) {
|
||||||
|
errorMessage = xhr.responseJSON.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
showAlertModal('操作失败', errorMessage, 'error');
|
||||||
|
console.error('启动分析失败:', error, xhr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 全局变量
|
// 全局变量
|
||||||
let analysisStatusInterval = null;
|
let analysisStatusInterval = null;
|
||||||
|
|
||||||
@ -408,56 +891,43 @@
|
|||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
// 显示成功完成模态框
|
||||||
|
showAlertModal(
|
||||||
|
'分析完成',
|
||||||
|
'股票扫描分析已完成,页面即将刷新显示最新信号',
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
location.reload();
|
location.reload();
|
||||||
}, 1500);
|
}, 2000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 立即分析按钮
|
// 立即分析按钮
|
||||||
$('#runAnalysisBtn').on('click', function() {
|
$('#runAnalysisBtn').on('click', function() {
|
||||||
const stockCount = prompt('请输入要扫描的股票数量 (默认200):', '200');
|
// 显示密钥输入模态框
|
||||||
|
$('#keyModal .modal-title').text('操作验证');
|
||||||
|
$('#keyModal p').text('请输入操作验证密钥以启动市场分析');
|
||||||
|
$('#operationKey').val('').removeClass('is-invalid');
|
||||||
|
$('#keyError').text('');
|
||||||
|
|
||||||
if (stockCount === null) return; // 用户取消
|
// 设置全局标识
|
||||||
|
window.currentOperation = 'run_analysis';
|
||||||
|
|
||||||
const count = parseInt(stockCount) || 200;
|
new bootstrap.Modal($('#keyModal')).show();
|
||||||
|
|
||||||
if (count <= 0 || count > 1000) {
|
|
||||||
alert('股票数量必须在 1-1000 之间');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (confirm(`确认要立即分析 ${count} 只热门股票吗?分析可能需要几分钟时间`)) {
|
|
||||||
$.ajax({
|
|
||||||
url: '/api/run_analysis',
|
|
||||||
method: 'POST',
|
|
||||||
contentType: 'application/json',
|
|
||||||
data: JSON.stringify({
|
|
||||||
stock_count: count
|
|
||||||
}),
|
|
||||||
success: function(response) {
|
|
||||||
if (response.success) {
|
|
||||||
// 重置刷新标志
|
|
||||||
hasRefreshedAfterCompletion = false;
|
|
||||||
|
|
||||||
// 立即开始状态轮询
|
|
||||||
checkAnalysisStatus();
|
|
||||||
alert(`分析任务已启动!正在后台扫描 ${response.stock_count} 只股票\n\n启动时间: ${response.start_time}\n\n页面将自动显示分析进度`);
|
|
||||||
} else {
|
|
||||||
alert('启动分析失败:' + response.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
alert('启动分析失败:网络错误或服务器异常');
|
|
||||||
console.error('启动分析失败:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 页面加载时检查是否有正在运行的分析
|
// 页面加载时检查是否有正在运行的分析
|
||||||
checkAnalysisStatus();
|
checkAnalysisStatus();
|
||||||
|
|
||||||
|
// 页面可见性改变时重新检查分析状态(用户切换回标签页时)
|
||||||
|
document.addEventListener('visibilitychange', function() {
|
||||||
|
if (!document.hidden) {
|
||||||
|
checkAnalysisStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Loading…
Reference in New Issue
Block a user