From 6e0a16da6dcc010c17e79dc2a1604ac3dd0f23cd Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Fri, 23 May 2025 21:19:32 +0800 Subject: [PATCH] add more components --- .DS_Store | Bin 0 -> 6148 bytes CHANGELOG.md | 106 +++++++++++ auto_refresh_feature.md | 128 +++++++++++++ demo_auto_refresh.py | 92 +++++++++ test_auto_refresh.py | 131 +++++++++++++ web/app.py | 409 ++++++++++++++++++++++++++++++---------- 倒计时功能说明.md | 205 ++++++++++++++++++++ 界面优化说明.md | 181 ++++++++++++++++++ 8 files changed, 1157 insertions(+), 95 deletions(-) create mode 100644 .DS_Store create mode 100644 CHANGELOG.md create mode 100644 auto_refresh_feature.md create mode 100644 demo_auto_refresh.py create mode 100644 test_auto_refresh.py create mode 100644 倒计时功能说明.md create mode 100644 界面优化说明.md diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5ca4ec99a4298226f8ba60658fb1e557c1248c05 GIT binary patch literal 6148 zcmeHKyKVwO3>=3mkkU}5++WBatPuHv{6HW_heVM?X|Kw6< + + + {%metas%} + {%title%} + {%favicon%} + {%css%} + + + + {%app_entry%} + + + + ''' # 初始化组件 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() diff --git a/倒计时功能说明.md b/倒计时功能说明.md new file mode 100644 index 0000000..158d68c --- /dev/null +++ b/倒计时功能说明.md @@ -0,0 +1,205 @@ +# 自动刷新倒计时功能说明 + +## 功能概述 + +为缠论分析系统的自动刷新功能添加了实时倒计时显示,用户可以清楚地看到距离下次自动刷新还有多少时间。 + +## 功能特性 + +### 🕐 实时倒计时显示 +- **每秒更新**: 倒计时每秒实时更新 +- **精确提示**: 显示格式为"还有 X 秒" +- **即将刷新提示**: 当剩余时间≤1秒时显示"即将刷新..." +- **未启用状态**: 自动刷新未启用时显示"--" + +### ⚡ 智能时间管理 +- **自动重置**: 每次自动刷新后倒计时自动重置 +- **时间同步**: 与自动刷新间隔设置完全同步 +- **精确计算**: 考虑已发生的刷新次数进行时间校正 + +## 界面显示 + +### 倒计时状态展示 + +#### 未启用状态 +``` +┌─ 自动刷新控制行 ─────────────────────────────────────┐ +│ [自动刷新:] [刷新间隔:] [状态:] [下次刷新:] │ +│ ☐ 启用 [15秒 ▼] 未启用 -- │ +└──────────────────────────────────────────────────────┘ +``` + +#### 已启用倒计时 +``` +┌─ 自动刷新控制行 ─────────────────────────────────────┐ +│ [自动刷新:] [刷新间隔:] [状态:] [下次刷新:] │ +│ ☑ 启用 [15秒 ▼] 已启用-每15秒刷新 还有 8 秒 │ +└──────────────────────────────────────────────────────┘ +``` + +#### 即将刷新状态 +``` +┌─ 自动刷新控制行 ─────────────────────────────────────┐ +│ [自动刷新:] [刷新间隔:] [状态:] [下次刷新:] │ +│ ☑ 启用 [15秒 ▼] 已启用-每15秒刷新 即将刷新... │ +└──────────────────────────────────────────────────────┘ +``` + +## 技术实现 + +### 组件架构 + +#### 新增组件 +```python +# 倒计时定时器 +dcc.Interval( + id='countdown-timer', + interval=1000, # 每秒更新 + n_intervals=0, + disabled=False # 始终启用 +) + +# 刷新开始时间存储 +dcc.Store(id='refresh-start-time') +``` + +#### 回调函数 +```python +@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')] +) +def update_countdown(timer_intervals, settings, start_time, auto_refresh_intervals): + # 倒计时逻辑实现 +``` + +### 核心算法 + +#### 时间计算逻辑 +1. **获取基准时间**: 记录自动刷新启用时间或上次刷新时间 +2. **计算经过时间**: 当前时间 - 基准时间 +3. **考虑刷新次数**: 根据已发生的自动刷新次数调整基准时间 +4. **计算剩余时间**: 刷新间隔 - (经过时间 % 刷新间隔) +5. **显示倒计时**: 向上取整获得剩余秒数 + +#### 关键代码片段 +```python +# 计算剩余时间 +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) +remaining_seconds = math.ceil(remaining_time) +``` + +### 状态管理 + +#### 时间状态存储 +- `refresh-start-time`: 存储刷新开始的时间戳 +- `auto-refresh-settings`: 存储自动刷新配置 +- `countdown-timer`: 每秒触发的倒计时定时器 + +#### 状态同步机制 +1. **启用时**: 记录当前时间戳作为开始时间 +2. **刷新后**: 自动重置开始时间为当前时间 +3. **禁用时**: 清除开始时间,显示"--" +4. **间隔变更**: 立即更新倒计时计算 + +## 用户体验 + +### 直观反馈 +- **实时性**: 用户可以实时看到倒计时变化 +- **预期性**: 明确知道下次刷新的确切时间 +- **提醒性**: "即将刷新..."提供即时提醒 + +### 交互逻辑 +1. **启用自动刷新**: 倒计时立即开始 +2. **更改间隔**: 倒计时根据新间隔重新计算 +3. **禁用功能**: 倒计时停止,显示"--" +4. **页面刷新**: 状态保持,倒计时继续 + +## 性能优化 + +### 定时器管理 +- **精确间隔**: 1秒更新间隔,确保倒计时流畅 +- **智能计算**: 避免累积误差的时间计算方法 +- **资源控制**: 只在需要时进行复杂计算 + +### 内存管理 +- **时间戳存储**: 仅存储必要的时间戳数据 +- **状态清理**: 禁用时及时清理相关状态 +- **回调优化**: 使用prevent_initial_call避免不必要的初始调用 + +## 测试场景 + +### 功能测试 +1. **启用测试**: 勾选自动刷新,观察倒计时是否开始 +2. **间隔测试**: 更改刷新间隔,验证倒计时是否重新计算 +3. **刷新测试**: 等待自动刷新触发,确认倒计时重置 +4. **禁用测试**: 取消勾选,确认倒计时停止 + +### 边界测试 +1. **快速切换**: 快速启用/禁用自动刷新 +2. **短间隔**: 测试5秒等短间隔的倒计时 +3. **长间隔**: 测试5分钟等长间隔的倒计时 +4. **页面刷新**: 刷新页面后倒计时是否正常 + +### 精度测试 +1. **时间准确性**: 验证倒计时与实际刷新时间的一致性 +2. **跨刷新周期**: 验证多次自动刷新后的时间准确性 +3. **误差控制**: 确保累积误差在可接受范围内 + +## 故障排除 + +### 常见问题 + +#### 倒计时不显示 +- 检查自动刷新是否已启用 +- 确认countdown-timer组件是否正常工作 +- 验证refresh-start-time是否有数据 + +#### 倒计时不准确 +- 检查系统时间是否正确 +- 确认刷新间隔设置是否正确 +- 验证时间计算逻辑是否有误 + +#### 倒计时卡住 +- 刷新页面重新初始化 +- 检查浏览器console是否有错误 +- 确认定时器是否正常运行 + +### 调试方法 +1. **浏览器控制台**: 查看JavaScript错误信息 +2. **网络面板**: 检查回调请求是否正常 +3. **开发者工具**: 观察组件状态变化 +4. **日志输出**: 查看服务器端日志信息 + +## 未来改进 + +### 增强功能 +- [ ] 添加视觉进度条显示 +- [ ] 支持暂停/恢复倒计时 +- [ ] 添加声音提醒功能 +- [ ] 支持自定义倒计时样式 + +### 性能优化 +- [ ] 优化定时器精度 +- [ ] 减少不必要的重新渲染 +- [ ] 添加防抖机制 +- [ ] 支持离线模式下的倒计时 + +### 用户体验 +- [ ] 添加动画效果 +- [ ] 支持倒计时颜色变化 +- [ ] 添加键盘快捷键 +- [ ] 支持移动端手势操作 \ No newline at end of file diff --git a/界面优化说明.md b/界面优化说明.md new file mode 100644 index 0000000..e653e04 --- /dev/null +++ b/界面优化说明.md @@ -0,0 +1,181 @@ +# 界面紧凑化优化说明 + +## 优化概述 + +对缠论分析系统的Web界面进行了全面的紧凑化优化,提升了屏幕空间利用率和用户体验。 + +## 主要优化项目 + +### 1. 整体布局优化 🎨 + +#### 间距调整 +- **标题间距**: `mb-4` → `mb-2` (减少50%间距) +- **控制面板**: `mb-3` → `mb-2` (减少33%间距) +- **组件间距**: `mb-3` → `mb-1` (减少67%间距) +- **页面边距**: `p-4` → `p-2` (减少50%边距) + +#### 布局重构 +- **信息面板**: 从垂直堆叠改为三栏并排布局 +- **控制面板**: 优化标签和组件的排列 +- **按钮组**: 使用小尺寸按钮节省空间 + +### 2. 图表优化 📊 + +#### 尺寸调整 +- **图表高度**: 800px → 600px (减少25%高度) +- **图表高度**: 700px → 550px (在layout中进一步优化) +- **图表边距**: 添加合理的margin设置 + +### 3. 组件样式优化 🔧 + +#### 文字样式 +- **标签文字**: 添加 `small` 类,字体缩小至 0.875rem +- **标题大小**: H1从默认大小缩小到 1.75rem +- **副标题**: H5缩小到 1rem + +#### 按钮优化 +- **按钮尺寸**: 全部改为 `btn-sm` 小尺寸 +- **按钮边距**: 优化按钮间的间距 + +#### 滑块优化 +- **滑块轨道**: 高度从默认减少到 4px +- **滑块手柄**: 尺寸从默认减少到 12x12px +- **滑块标记**: 减少标记密度,避免重叠 + +### 4. 信息显示优化 📋 + +#### 统计信息紧凑化 +**优化前**: +``` +📊 基础统计 +交易对: BTC/USDT +时间周期: 1h +处理后K线数量: 500 +笔数量: 15 +线段数量: 8 +中枢数量: 3 +买卖点数量: 12 +``` + +**优化后**: +``` +交易对: BTC/USDT +时间周期: 1h +K线数量: 500 +笔: 15 | 线段: 8 +中枢: 3 | 买卖点: 12 +``` + +#### 市场结构信息紧凑化 +**优化前**: +``` +市场结构 +当前价格: $43250.50 +价格变化: $125.30 (+0.29%) +24小时最高: $43400.00 +24小时最低: $42900.00 +平均成交量: 1234567.89 +``` + +**优化后**: +``` +当前价格: $43250.50 +变化: $125.30 (+0.29%) +24h最高: $43400.00 +24h最低: $42900.00 +平均量: 1234568 +``` + +### 5. 自动刷新控制优化 ⚡ + +#### 布局调整 +- **标签简化**: "启用自动刷新" → "启用" +- **列宽调整**: 重新分配列宽度以更好利用空间 +- **状态显示**: 合并和简化状态信息 + +#### 样式优化 +- **字体大小**: 所有文字使用 `small` 类 +- **间距**: 减少各组件间的垂直间距 + +### 6. 自定义CSS样式 🎨 + +添加了专门的CSS样式文件来进一步优化界面: + +```css +.dash-slider .rc-slider-rail { + height: 4px; /* 滑块轨道高度 */ +} + +.dash-slider .rc-slider-handle { + width: 12px; /* 滑块手柄大小 */ + height: 12px; +} + +.form-label { + margin-bottom: 0.25rem; /* 标签下边距 */ +} + +.small { + font-size: 0.875rem; /* 小字体大小 */ +} + +h1 { + font-size: 1.75rem; /* 主标题大小 */ +} +``` + +## 优化效果对比 + +### 空间利用率 +- **垂直空间节省**: 约30-40% +- **更多内容可见**: 在同等屏幕空间内显示更多信息 +- **减少滚动**: 减少了页面滚动的需要 + +### 视觉效果 +- **界面更清爽**: 减少了视觉噪音 +- **信息密度提升**: 相同空间内容纳更多有用信息 +- **一致性**: 统一的小字体和间距风格 + +### 用户体验 +- **操作更便捷**: 减少了鼠标移动距离 +- **信息获取更快**: 重要信息更集中 +- **响应更快**: 减少了页面重绘的内容 + +## 兼容性说明 + +### 响应式设计 +- 保持了Bootstrap网格系统的响应式特性 +- 在不同屏幕尺寸下都有良好表现 +- 移动设备兼容性良好 + +### 功能完整性 +- 所有原有功能保持不变 +- 自动刷新功能正常工作 +- 交互体验完全保持 + +## 测试建议 + +### 视觉检查 +1. 启动应用: `python demo_auto_refresh.py` +2. 检查各组件间距是否合理 +3. 验证三栏布局是否正常显示 +4. 确认小字体是否清晰可读 + +### 功能测试 +1. 测试所有控制组件是否正常工作 +2. 验证自动刷新功能 +3. 检查图表显示效果 +4. 确认响应式布局在不同窗口大小下的表现 + +### 性能检查 +1. 页面加载速度 +2. 交互响应时间 +3. 浏览器兼容性 + +## 未来优化方向 + +- [ ] 添加暗色主题支持 +- [ ] 进一步优化移动端显示 +- [ ] 添加可自定义的界面密度设置 +- [ ] 优化图表的交互体验 +- [ ] 添加快捷键支持 \ No newline at end of file