增加密钥
This commit is contained in:
parent
9339b7a558
commit
6ec1feb2a7
@ -14,6 +14,7 @@ services:
|
||||
- FLASK_ENV=production
|
||||
- PYTHONPATH=/app
|
||||
- TZ=Asia/Shanghai
|
||||
- OPERATION_KEY=${OPERATION_KEY:-9257}
|
||||
# MySQL连接配置 (可通过环境变量覆盖)
|
||||
- MYSQL_HOST=${MYSQL_HOST:-cd-cynosdbmysql-grp-7kdd8qe4.sql.tencentcdb.com}
|
||||
- MYSQL_PORT=${MYSQL_PORT:-26558}
|
||||
@ -39,6 +40,7 @@ services:
|
||||
environment:
|
||||
- PYTHONPATH=/app
|
||||
- TZ=Asia/Shanghai
|
||||
- OPERATION_KEY=${OPERATION_KEY:-9257}
|
||||
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||
- MARKET_SCAN_STOCKS=${MARKET_SCAN_STOCKS:-200}
|
||||
# MySQL连接配置
|
||||
|
||||
@ -5,6 +5,7 @@ AI 智能选股大师 MySQL版本 Web 展示界面
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
from flask import Flask, render_template, jsonify, request
|
||||
from datetime import datetime, date, timedelta
|
||||
@ -22,6 +23,9 @@ from loguru import logger
|
||||
app = Flask(__name__)
|
||||
app.secret_key = 'trading_ai_mysql_secret_key_2023'
|
||||
|
||||
# 操作验证密钥(放在导入后立即定义)
|
||||
OPERATION_KEY = os.environ.get('OPERATION_KEY', '9257')
|
||||
|
||||
# 初始化组件
|
||||
db_manager = MySQLDatabaseManager()
|
||||
config_loader = ConfigLoader()
|
||||
@ -172,6 +176,17 @@ def api_stats():
|
||||
def api_clear_signals():
|
||||
"""API接口 - 清空信号数据"""
|
||||
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))
|
||||
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
|
||||
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']:
|
||||
return jsonify({
|
||||
|
||||
@ -1110,4 +1110,367 @@ footer .text-muted {
|
||||
font-size: 0.65rem;
|
||||
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 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 %}
|
||||
|
||||
{% block extra_js %}
|
||||
@ -298,41 +419,403 @@
|
||||
window.location.search = urlParams.toString();
|
||||
}
|
||||
|
||||
// 清空信号按钮
|
||||
$('#clearSignalsBtn').on('click', function() {
|
||||
if (confirm('确认要清空最近7天的信号数据吗?此操作不可恢复!')) {
|
||||
const btn = $(this);
|
||||
const originalText = btn.html();
|
||||
// 全局变量用于存储操作回调
|
||||
let currentOperationCallback = null;
|
||||
let currentInputCallback = null;
|
||||
let pendingOperationKey = null; // 存储密钥
|
||||
|
||||
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({
|
||||
days: 7,
|
||||
strategy_name: ''
|
||||
}),
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
alert(`清空成功!删除了 ${response.deleted_count} 条信号记录`);
|
||||
location.reload();
|
||||
} else {
|
||||
alert('清空失败:' + response.error);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
alert('清空失败:网络错误或服务器异常');
|
||||
console.error('清空信号失败:', error);
|
||||
},
|
||||
complete: function() {
|
||||
btn.prop('disabled', false).html(originalText);
|
||||
}
|
||||
});
|
||||
// 显示验证密钥模态框
|
||||
function showKeyModal(title, message, callback) {
|
||||
$('#keyModal .modal-title').text(title);
|
||||
$('#keyModal p').text(message);
|
||||
$('#operationKey').val('').removeClass('is-invalid');
|
||||
$('#keyError').text('');
|
||||
currentOperationCallback = callback;
|
||||
new bootstrap.Modal($('#keyModal')).show();
|
||||
}
|
||||
|
||||
// 显示确认操作模态框
|
||||
function showConfirmModal(title, message, type = 'warning', callback) {
|
||||
const $header = $('#confirmHeader');
|
||||
const $icon = $('#confirmIcon');
|
||||
const $action = $('#confirmAction');
|
||||
|
||||
// 设置标题
|
||||
$('#confirmTitle').html(`<i class="fas fa-question-circle me-2"></i>${title}`);
|
||||
$('#confirmMessage').text(message);
|
||||
|
||||
// 根据类型设置样式
|
||||
$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;
|
||||
|
||||
@ -408,56 +891,43 @@
|
||||
</div>
|
||||
`);
|
||||
|
||||
// 显示成功完成模态框
|
||||
showAlertModal(
|
||||
'分析完成',
|
||||
'股票扫描分析已完成,页面即将刷新显示最新信号',
|
||||
'success'
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 1500);
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 立即分析按钮
|
||||
$('#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;
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
new bootstrap.Modal($('#keyModal')).show();
|
||||
});
|
||||
|
||||
// 页面加载时检查是否有正在运行的分析
|
||||
checkAnalysisStatus();
|
||||
|
||||
// 页面可见性改变时重新检查分析状态(用户切换回标签页时)
|
||||
document.addEventListener('visibilitychange', function() {
|
||||
if (!document.hidden) {
|
||||
checkAnalysisStatus();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Loading…
Reference in New Issue
Block a user