add more components
This commit is contained in:
+314
-95
@@ -20,7 +20,66 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
def create_app():
|
||||
"""创建Dash应用"""
|
||||
app = dash.Dash(__name__)
|
||||
app = dash.Dash(__name__, external_stylesheets=[
|
||||
'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css'
|
||||
])
|
||||
|
||||
# 添加自定义CSS样式
|
||||
app.index_string = '''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{%metas%}
|
||||
<title>{%title%}</title>
|
||||
{%favicon%}
|
||||
{%css%}
|
||||
<style>
|
||||
.dash-slider .rc-slider-rail {
|
||||
height: 4px;
|
||||
}
|
||||
.dash-slider .rc-slider-track {
|
||||
height: 4px;
|
||||
}
|
||||
.dash-slider .rc-slider-handle {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-top: -4px;
|
||||
}
|
||||
.form-label {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.row {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.small {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.btn-sm {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.dash-graph {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
h5 {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{%app_entry%}
|
||||
<footer>
|
||||
{%config%}
|
||||
{%scripts%}
|
||||
{%renderer%}
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
# 初始化组件
|
||||
data_fetcher = DataFetcher()
|
||||
@@ -30,12 +89,12 @@ def create_app():
|
||||
# 应用布局
|
||||
app.layout = html.Div([
|
||||
# 标题
|
||||
html.H1("缠论分析系统", className="text-center mb-4"),
|
||||
html.H1("缠论分析系统", className="text-center mb-2"),
|
||||
|
||||
# 控制面板
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Label("交易对:"),
|
||||
html.Label("交易对:", className="small"),
|
||||
dcc.Dropdown(
|
||||
id='symbol-dropdown',
|
||||
options=[
|
||||
@@ -46,12 +105,12 @@ def create_app():
|
||||
{'label': 'SOL/USDT', 'value': 'SOL/USDT'}
|
||||
],
|
||||
value='BTC/USDT',
|
||||
className="mb-3"
|
||||
className="mb-1"
|
||||
)
|
||||
], className="col-md-2"),
|
||||
|
||||
html.Div([
|
||||
html.Label("时间周期:"),
|
||||
html.Label("时间周期:", className="small"),
|
||||
dcc.Dropdown(
|
||||
id='timeframe-dropdown',
|
||||
options=[
|
||||
@@ -63,52 +122,52 @@ def create_app():
|
||||
{'label': '4小时', 'value': '4h'},
|
||||
{'label': '1天', 'value': '1d'}
|
||||
],
|
||||
value='1h',
|
||||
className="form-select"
|
||||
value='5m',
|
||||
className="form-select mb-1"
|
||||
)
|
||||
], className="col-md-2"),
|
||||
|
||||
html.Div([
|
||||
html.Label("数据数量:"),
|
||||
html.Label("数据数量:", className="small"),
|
||||
dcc.Slider(
|
||||
id='limit-slider',
|
||||
min=100,
|
||||
max=1000,
|
||||
step=50,
|
||||
value=500,
|
||||
marks={i: str(i) for i in range(100, 1001, 200)},
|
||||
className="mb-3"
|
||||
value=100,
|
||||
marks={i: str(i) for i in range(100, 1001, 300)},
|
||||
className="mb-1"
|
||||
)
|
||||
], className="col-md-2"),
|
||||
|
||||
html.Div([
|
||||
html.Label("基础分型强度:"),
|
||||
html.Label("基础分型强度:", className="small"),
|
||||
dcc.Slider(
|
||||
id='fractal-strength-slider',
|
||||
min=1,
|
||||
max=5,
|
||||
step=1,
|
||||
value=1,
|
||||
value=5,
|
||||
marks={i: str(i) for i in range(1, 6)},
|
||||
className="mb-3"
|
||||
className="mb-1"
|
||||
)
|
||||
], className="col-md-2"),
|
||||
|
||||
html.Div([
|
||||
html.Label("增强强度过滤:"),
|
||||
html.Label("增强强度过滤:", className="small"),
|
||||
dcc.Slider(
|
||||
id='enhanced-strength-filter',
|
||||
min=0,
|
||||
max=100,
|
||||
step=10,
|
||||
value=0,
|
||||
marks={i: str(i) for i in range(0, 101, 20)},
|
||||
className="mb-3"
|
||||
marks={i: str(i) for i in range(0, 101, 50)},
|
||||
className="mb-1"
|
||||
)
|
||||
], className="col-md-2"),
|
||||
|
||||
html.Div([
|
||||
html.Label("显示级别:"),
|
||||
html.Label("显示级别:", className="small"),
|
||||
dcc.Dropdown(
|
||||
id='display-level-dropdown',
|
||||
options=[
|
||||
@@ -118,20 +177,61 @@ def create_app():
|
||||
{'label': '自定义过滤', 'value': 'custom'}
|
||||
],
|
||||
value='all',
|
||||
className="form-select"
|
||||
className="form-select mb-1"
|
||||
)
|
||||
], className="col-md-2")
|
||||
], className="row mb-4"),
|
||||
], className="row mb-2"),
|
||||
|
||||
# 自动刷新控制行
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Label("自动刷新:", className="form-label small"),
|
||||
dcc.Checklist(
|
||||
id='auto-refresh-toggle',
|
||||
options=[{'label': '启用', 'value': 'enabled'}],
|
||||
value=[],
|
||||
className="form-check small"
|
||||
)
|
||||
], className="col-md-2"),
|
||||
|
||||
html.Div([
|
||||
html.Label("刷新间隔:", className="form-label small"),
|
||||
dcc.Dropdown(
|
||||
id='refresh-interval-dropdown',
|
||||
options=[
|
||||
{'label': '5秒', 'value': 5000},
|
||||
{'label': '10秒', 'value': 10000},
|
||||
{'label': '15秒', 'value': 15000},
|
||||
{'label': '30秒', 'value': 30000},
|
||||
{'label': '1分钟', 'value': 60000},
|
||||
{'label': '5分钟', 'value': 300000}
|
||||
],
|
||||
value=15000, # 默认15秒
|
||||
className="form-select small",
|
||||
disabled=True # 初始状态禁用
|
||||
)
|
||||
], className="col-md-2"),
|
||||
|
||||
html.Div([
|
||||
html.Label("状态:", className="form-label small"),
|
||||
html.Div(id='refresh-status', children="未启用", className="text-muted small")
|
||||
], className="col-md-4"),
|
||||
|
||||
html.Div([
|
||||
html.Label("下次刷新:", className="form-label small"),
|
||||
html.Div(id='next-refresh-time', children="--", className="text-muted small")
|
||||
], className="col-md-4")
|
||||
], className="row mb-2"),
|
||||
|
||||
# 按钮组
|
||||
html.Div([
|
||||
html.Button("获取数据并分析", id="analyze-btn",
|
||||
className="btn btn-primary me-2"),
|
||||
className="btn btn-primary btn-sm me-2"),
|
||||
html.Button("刷新数据", id="refresh-btn",
|
||||
className="btn btn-secondary me-2"),
|
||||
className="btn btn-secondary btn-sm me-2"),
|
||||
html.Button("导出结果", id="export-btn",
|
||||
className="btn btn-success"),
|
||||
], className="text-center mb-4"),
|
||||
className="btn btn-success btn-sm"),
|
||||
], className="text-center mb-2"),
|
||||
|
||||
# 加载状态
|
||||
dcc.Loading(
|
||||
@@ -139,49 +239,84 @@ def create_app():
|
||||
children=[
|
||||
# 主图表
|
||||
html.Div([
|
||||
dcc.Graph(id='main-chart', style={'height': '800px'})
|
||||
], className="mb-4"),
|
||||
dcc.Graph(id='main-chart', style={'height': '600px'})
|
||||
], className="mb-2"),
|
||||
|
||||
# 统计信息
|
||||
# 信息面板 - 使用三栏布局
|
||||
html.Div([
|
||||
html.H3("分析统计", className="mb-3"),
|
||||
html.Div(id='statistics-content')
|
||||
], className="mb-4"),
|
||||
|
||||
# 市场结构
|
||||
html.Div([
|
||||
html.H3("当前市场结构", className="mb-3"),
|
||||
html.Div(id='market-structure-content')
|
||||
], className="mb-4"),
|
||||
|
||||
# 最新信号
|
||||
html.Div([
|
||||
html.H3("最新买卖点信号", className="mb-3"),
|
||||
html.Div(id='latest-signals-content')
|
||||
])
|
||||
# 统计信息
|
||||
html.Div([
|
||||
html.H5("分析统计", className="mb-1"),
|
||||
html.Div(id='statistics-content')
|
||||
], className="col-md-4 mb-2"),
|
||||
|
||||
# 市场结构
|
||||
html.Div([
|
||||
html.H5("市场结构", className="mb-1"),
|
||||
html.Div(id='market-structure-content')
|
||||
], className="col-md-4 mb-2"),
|
||||
|
||||
# 最新信号
|
||||
html.Div([
|
||||
html.H5("最新信号", className="mb-1"),
|
||||
html.Div(id='latest-signals-content')
|
||||
], className="col-md-4 mb-2")
|
||||
], className="row")
|
||||
]
|
||||
),
|
||||
|
||||
# 存储数据
|
||||
# 存储数据和定时器组件
|
||||
dcc.Store(id='analysis-data'),
|
||||
dcc.Store(id='market-structure-data')
|
||||
], className="container-fluid p-4")
|
||||
dcc.Store(id='market-structure-data'),
|
||||
dcc.Store(id='auto-refresh-settings'),
|
||||
dcc.Store(id='refresh-start-time'), # 存储刷新开始时间
|
||||
dcc.Interval(
|
||||
id='auto-refresh-interval',
|
||||
interval=15000, # 默认15秒
|
||||
n_intervals=0,
|
||||
disabled=True # 初始状态禁用
|
||||
),
|
||||
dcc.Interval(
|
||||
id='countdown-timer',
|
||||
interval=1000, # 每秒更新一次倒计时
|
||||
n_intervals=0,
|
||||
disabled=False # 始终启用
|
||||
)
|
||||
], className="container-fluid p-2")
|
||||
|
||||
# 回调函数
|
||||
@app.callback(
|
||||
[Output('analysis-data', 'data'),
|
||||
Output('market-structure-data', 'data')],
|
||||
[Input('analyze-btn', 'n_clicks'),
|
||||
Input('refresh-btn', 'n_clicks')],
|
||||
Input('refresh-btn', 'n_clicks'),
|
||||
Input('auto-refresh-interval', 'n_intervals')],
|
||||
[State('symbol-dropdown', 'value'),
|
||||
State('timeframe-dropdown', 'value'),
|
||||
State('limit-slider', 'value'),
|
||||
State('fractal-strength-slider', 'value'),
|
||||
State('enhanced-strength-filter', 'value'),
|
||||
State('display-level-dropdown', 'value')]
|
||||
State('display-level-dropdown', 'value'),
|
||||
State('auto-refresh-settings', 'data')]
|
||||
)
|
||||
def analyze_data(analyze_clicks, refresh_clicks, symbol, timeframe, limit, fractal_strength, enhanced_strength_filter, display_level):
|
||||
if not analyze_clicks and not refresh_clicks:
|
||||
def analyze_data_with_auto_refresh(analyze_clicks, refresh_clicks, auto_refresh_intervals,
|
||||
symbol, timeframe, limit, fractal_strength,
|
||||
enhanced_strength_filter, display_level, auto_refresh_settings):
|
||||
"""带自动刷新功能的数据分析"""
|
||||
ctx = callback_context
|
||||
if not ctx.triggered:
|
||||
return None, None
|
||||
|
||||
trigger_id = ctx.triggered[0]['prop_id'].split('.')[0]
|
||||
|
||||
# 检查是否是自动刷新触发,且自动刷新已启用
|
||||
if trigger_id == 'auto-refresh-interval':
|
||||
if not auto_refresh_settings or not auto_refresh_settings.get('enabled'):
|
||||
return None, None
|
||||
# 只有当auto_refresh_intervals > 0时才进行自动刷新
|
||||
if auto_refresh_intervals == 0:
|
||||
return None, None
|
||||
elif not analyze_clicks and not refresh_clicks:
|
||||
return None, None
|
||||
|
||||
try:
|
||||
@@ -264,7 +399,9 @@ def create_app():
|
||||
'central_banks_count': result['central_bank_info']['total'],
|
||||
'trading_points_count': result['trading_signal_info']['total'],
|
||||
'symbol': symbol,
|
||||
'timeframe': timeframe
|
||||
'timeframe': timeframe,
|
||||
'last_update': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), # 添加更新时间
|
||||
'update_type': 'auto' if trigger_id == 'auto-refresh-interval' else 'manual' # 标记更新类型
|
||||
}
|
||||
|
||||
# 市场结构数据
|
||||
@@ -486,9 +623,10 @@ def create_app():
|
||||
title=f"{analysis_data['symbol']} {analysis_data['timeframe']} 缠论分析图",
|
||||
xaxis_title="时间",
|
||||
yaxis_title="价格",
|
||||
height=700,
|
||||
height=550,
|
||||
xaxis_rangeslider_visible=False,
|
||||
hovermode='x unified'
|
||||
hovermode='x unified',
|
||||
margin=dict(l=50, r=50, t=50, b=50)
|
||||
)
|
||||
|
||||
return fig
|
||||
@@ -505,14 +643,15 @@ def create_app():
|
||||
|
||||
# 基础统计
|
||||
basic_stats = html.Div([
|
||||
html.H5("📊 基础统计"),
|
||||
html.P(f"交易对: {analysis_data.get('symbol', 'N/A')}"),
|
||||
html.P(f"时间周期: {analysis_data.get('timeframe', 'N/A')}"),
|
||||
html.P(f"处理后K线数量: {analysis_data.get('processed_klines_count', 0)}"),
|
||||
html.P(f"笔数量: {analysis_data.get('strokes_count', 0)}"),
|
||||
html.P(f"线段数量: {analysis_data.get('segments_count', 0)}"),
|
||||
html.P(f"中枢数量: {analysis_data.get('central_banks_count', 0)}"),
|
||||
html.P(f"买卖点数量: {analysis_data.get('trading_points_count', 0)}")
|
||||
html.P(f"交易对: {analysis_data.get('symbol', 'N/A')}", className="mb-1 small"),
|
||||
html.P(f"时间周期: {analysis_data.get('timeframe', 'N/A')}", className="mb-1 small"),
|
||||
html.P(f"K线数量: {analysis_data.get('processed_klines_count', 0)}", className="mb-1 small"),
|
||||
html.P(f"笔: {analysis_data.get('strokes_count', 0)} | 线段: {analysis_data.get('segments_count', 0)}", className="mb-1 small"),
|
||||
html.P(f"中枢: {analysis_data.get('central_banks_count', 0)} | 买卖点: {analysis_data.get('trading_points_count', 0)}", className="mb-1 small"),
|
||||
html.Hr(className="my-1"),
|
||||
html.P(f"更新: {analysis_data.get('last_update', 'N/A')}", className="text-info small mb-1"),
|
||||
html.P(f"方式: {'自动' if analysis_data.get('update_type') == 'auto' else '手动'}",
|
||||
className="text-muted small mb-0")
|
||||
])
|
||||
|
||||
# 分型强度统计
|
||||
@@ -537,28 +676,16 @@ def create_app():
|
||||
|
||||
# 平均强度
|
||||
avg_enhanced = sum(f.get('enhanced_strength', 0) for f in fractals) / len(fractals) if fractals else 0
|
||||
avg_price_dom = sum(f.get('price_dominance', 0) for f in fractals) / len(fractals) if fractals else 0
|
||||
avg_volume = sum(f.get('volume_strength', 0) for f in fractals) / len(fractals) if fractals else 0
|
||||
avg_trend = sum(f.get('trend_position', 0) for f in fractals) / len(fractals) if fractals else 0
|
||||
|
||||
fractal_stats = html.Div([
|
||||
html.H5("🔥 分型强度分析"),
|
||||
html.P(f"总分型数: {len(fractals)} 个"),
|
||||
html.P(f"强势分型(≥70分): {len(strong_fractals)} 个"),
|
||||
html.P(f"中等分型(40-70分): {len(medium_fractals)} 个"),
|
||||
html.P(f"弱势分型(<40分): {len(weak_fractals)} 个"),
|
||||
html.Hr(),
|
||||
html.P(f"平均增强强度: {avg_enhanced:.1f}分"),
|
||||
html.P(f"平均价格优势: {avg_price_dom:.1f}分"),
|
||||
html.P(f"平均成交量强度: {avg_volume:.1f}分"),
|
||||
html.P(f"平均趋势位置: {avg_trend:.1f}分"),
|
||||
html.Hr(),
|
||||
html.P(f"🎯 当前显示: {len(filtered_fractals)} 个分型"),
|
||||
html.P(f"过滤级别: {display_level}", className="text-muted"),
|
||||
html.P(f"过滤阈值: {enhanced_strength_filter}分", className="text-muted") if display_level == 'custom' else ""
|
||||
html.Hr(className="my-1"),
|
||||
html.P(f"🔥 分型分析", className="fw-bold small mb-1"),
|
||||
html.P(f"总数: {len(fractals)} | 强势: {len(strong_fractals)} | 中等: {len(medium_fractals)}", className="mb-1 small"),
|
||||
html.P(f"当前显示: {len(filtered_fractals)} 个", className="mb-1 small"),
|
||||
html.P(f"平均强度: {avg_enhanced:.1f}分", className="mb-0 small text-muted")
|
||||
])
|
||||
|
||||
return html.Div([basic_stats, html.Hr(), fractal_stats])
|
||||
return html.Div([basic_stats, fractal_stats])
|
||||
else:
|
||||
return basic_stats
|
||||
|
||||
@@ -568,19 +695,21 @@ def create_app():
|
||||
)
|
||||
def update_market_structure(market_data):
|
||||
if not market_data:
|
||||
return "暂无市场数据"
|
||||
return "暂无数据"
|
||||
|
||||
latest_price = market_data.get('latest_price', 0)
|
||||
price_change = market_data.get('price_change', 0)
|
||||
change_percent = (price_change / (latest_price - price_change)) * 100 if (latest_price - price_change) != 0 else 0
|
||||
|
||||
color = "text-success" if price_change >= 0 else "text-danger"
|
||||
|
||||
return html.Div([
|
||||
html.H4("市场结构"),
|
||||
html.P(f"当前价格: ${latest_price:.2f}"),
|
||||
html.P(f"价格变化: ${price_change:.2f} ({change_percent:+.2f}%)"),
|
||||
html.P(f"24小时最高: ${market_data.get('high_24h', 0):.2f}"),
|
||||
html.P(f"24小时最低: ${market_data.get('low_24h', 0):.2f}"),
|
||||
html.P(f"平均成交量: {market_data.get('volume_avg', 0):.2f}")
|
||||
html.P(f"当前价格: ${latest_price:.2f}", className="mb-1 small"),
|
||||
html.P(f"变化: ${price_change:.2f} ({change_percent:+.2f}%)",
|
||||
className=f"mb-1 small {color}"),
|
||||
html.P(f"24h最高: ${market_data.get('high_24h', 0):.2f}", className="mb-1 small"),
|
||||
html.P(f"24h最低: ${market_data.get('low_24h', 0):.2f}", className="mb-1 small"),
|
||||
html.P(f"平均量: {market_data.get('volume_avg', 0):.0f}", className="mb-0 small text-muted")
|
||||
])
|
||||
|
||||
@app.callback(
|
||||
@@ -589,25 +718,115 @@ def create_app():
|
||||
)
|
||||
def update_latest_signals(analysis_data):
|
||||
if not analysis_data:
|
||||
return "暂无信号数据"
|
||||
return "暂无数据"
|
||||
|
||||
trading_points_count = analysis_data.get('trading_points_count', 0)
|
||||
fractals_count = analysis_data.get('fractals_count', 0)
|
||||
|
||||
return html.Div([
|
||||
html.H4("最新信号"),
|
||||
html.P(f"检测到 {trading_points_count} 个买卖点信号"),
|
||||
html.P("详细信号分析请查看主图表标记")
|
||||
html.P(f"买卖点: {trading_points_count} 个", className="mb-1 small"),
|
||||
html.P(f"分型: {fractals_count} 个", className="mb-1 small"),
|
||||
html.P("详情见图表标记", className="mb-0 small text-muted")
|
||||
])
|
||||
|
||||
# 自动刷新功能回调函数
|
||||
@app.callback(
|
||||
[Output('refresh-interval-dropdown', 'disabled'),
|
||||
Output('auto-refresh-interval', 'disabled'),
|
||||
Output('auto-refresh-interval', 'interval'),
|
||||
Output('refresh-status', 'children'),
|
||||
Output('auto-refresh-settings', 'data'),
|
||||
Output('refresh-start-time', 'data')],
|
||||
[Input('auto-refresh-toggle', 'value'),
|
||||
Input('refresh-interval-dropdown', 'value')],
|
||||
prevent_initial_call=True
|
||||
)
|
||||
def control_auto_refresh(auto_refresh_enabled, refresh_interval):
|
||||
"""控制自动刷新功能"""
|
||||
enabled = 'enabled' in (auto_refresh_enabled or [])
|
||||
|
||||
if enabled:
|
||||
status = f"已启用 - 每{refresh_interval/1000:.0f}秒刷新"
|
||||
dropdown_disabled = False
|
||||
interval_disabled = False
|
||||
else:
|
||||
status = "未启用"
|
||||
dropdown_disabled = True
|
||||
interval_disabled = True
|
||||
refresh_interval = 15000 # 保持默认值
|
||||
|
||||
settings = {
|
||||
'enabled': enabled,
|
||||
'interval': refresh_interval
|
||||
}
|
||||
|
||||
# 记录当前时间作为刷新开始时间
|
||||
from datetime import datetime
|
||||
start_time = datetime.now().timestamp() if enabled else None
|
||||
|
||||
return dropdown_disabled, interval_disabled, refresh_interval, status, settings, start_time
|
||||
|
||||
@app.callback(
|
||||
Output('next-refresh-time', 'children'),
|
||||
[Input('countdown-timer', 'n_intervals'),
|
||||
Input('auto-refresh-settings', 'data'),
|
||||
Input('refresh-start-time', 'data'),
|
||||
Input('auto-refresh-interval', 'n_intervals')],
|
||||
prevent_initial_call=True
|
||||
)
|
||||
def update_countdown(timer_intervals, settings, start_time, auto_refresh_intervals):
|
||||
"""更新倒计时显示"""
|
||||
if not settings or not settings.get('enabled') or start_time is None:
|
||||
return "--"
|
||||
|
||||
from datetime import datetime
|
||||
import math
|
||||
|
||||
# 获取刷新间隔(秒)
|
||||
interval_seconds = settings.get('interval', 15000) / 1000
|
||||
|
||||
# 计算自启用或上次刷新以来的时间
|
||||
current_time = datetime.now().timestamp()
|
||||
|
||||
# 计算已经过去的时间
|
||||
elapsed_time = current_time - start_time
|
||||
|
||||
# 考虑自动刷新的次数,重新计算开始时间
|
||||
if auto_refresh_intervals > 0:
|
||||
# 调整开始时间,考虑已经发生的自动刷新
|
||||
adjusted_start_time = start_time + (auto_refresh_intervals * interval_seconds)
|
||||
elapsed_time = current_time - adjusted_start_time
|
||||
|
||||
# 计算剩余时间
|
||||
remaining_time = interval_seconds - (elapsed_time % interval_seconds)
|
||||
|
||||
if remaining_time <= 0:
|
||||
remaining_time = interval_seconds
|
||||
|
||||
remaining_seconds = math.ceil(remaining_time)
|
||||
|
||||
if remaining_seconds <= 1:
|
||||
return "即将刷新..."
|
||||
else:
|
||||
return f"还有 {remaining_seconds} 秒"
|
||||
|
||||
# 同时需要在自动刷新触发时重置开始时间
|
||||
@app.callback(
|
||||
Output('refresh-start-time', 'data', allow_duplicate=True),
|
||||
[Input('auto-refresh-interval', 'n_intervals')],
|
||||
[State('auto-refresh-settings', 'data')],
|
||||
prevent_initial_call=True
|
||||
)
|
||||
def reset_refresh_start_time(auto_refresh_intervals, settings):
|
||||
"""重置刷新开始时间"""
|
||||
if auto_refresh_intervals > 0 and settings and settings.get('enabled'):
|
||||
from datetime import datetime
|
||||
return datetime.now().timestamp()
|
||||
return dash.no_update
|
||||
|
||||
return app
|
||||
|
||||
|
||||
# 添加CSS样式
|
||||
external_stylesheets = [
|
||||
'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css'
|
||||
]
|
||||
|
||||
|
||||
def run_app(debug=True, port=8050):
|
||||
"""运行Web应用"""
|
||||
app = create_app()
|
||||
|
||||
Reference in New Issue
Block a user