add more components
This commit is contained in:
+106
@@ -0,0 +1,106 @@
|
|||||||
|
# 更新日志 (CHANGELOG)
|
||||||
|
|
||||||
|
## [1.1.0] - 2024-12-23
|
||||||
|
|
||||||
|
### 新增功能 ✨
|
||||||
|
|
||||||
|
#### 自动刷新功能
|
||||||
|
- **自动刷新控制**: 添加了勾选框来启用/禁用自动刷新功能
|
||||||
|
- **刷新间隔设置**: 支持5秒、10秒、15秒、30秒、1分钟、5分钟的刷新间隔选择
|
||||||
|
- **默认配置**: 默认刷新间隔设置为15秒
|
||||||
|
- **智能状态管理**: 启用自动刷新时激活相关控件,禁用时自动禁用下拉菜单
|
||||||
|
- **实时状态显示**: 显示当前自动刷新状态和下次刷新时间
|
||||||
|
- **参数保持**: 自动刷新时保持所有其他分析参数不变
|
||||||
|
|
||||||
|
#### 用户界面改进
|
||||||
|
- **新增控制面板行**: 在原有控制面板下方添加自动刷新控制区域
|
||||||
|
- **状态指示器**: 添加刷新状态和下次刷新时间的实时显示
|
||||||
|
- **更新信息**: 在统计信息中显示最后更新时间和更新方式(手动/自动)
|
||||||
|
|
||||||
|
#### 技术架构优化
|
||||||
|
- **回调函数重构**: 重新设计数据分析回调函数支持自动刷新触发
|
||||||
|
- **状态存储**: 使用dcc.Store组件管理自动刷新设置
|
||||||
|
- **定时器集成**: 集成dcc.Interval组件实现定时刷新
|
||||||
|
- **错误处理**: 改进自动刷新的错误处理和状态管理
|
||||||
|
|
||||||
|
### 文件变更 📝
|
||||||
|
|
||||||
|
#### 修改文件
|
||||||
|
- `web/app.py`: 添加自动刷新控制组件和相关回调函数
|
||||||
|
|
||||||
|
#### 新增文件
|
||||||
|
- `auto_refresh_feature.md`: 自动刷新功能详细说明文档
|
||||||
|
- `demo_auto_refresh.py`: 功能演示脚本
|
||||||
|
- `test_auto_refresh.py`: 测试脚本(需要selenium)
|
||||||
|
- `CHANGELOG.md`: 更新日志文档
|
||||||
|
|
||||||
|
### 使用方法 🚀
|
||||||
|
|
||||||
|
1. **启动应用**:
|
||||||
|
```bash
|
||||||
|
python demo_auto_refresh.py
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **手动启动**:
|
||||||
|
```bash
|
||||||
|
python -c "from web.app import run_app; run_app(debug=False, port=8051)"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **访问界面**: 打开浏览器访问 http://localhost:8051
|
||||||
|
|
||||||
|
4. **使用自动刷新**:
|
||||||
|
- 勾选"启用自动刷新"
|
||||||
|
- 选择所需的刷新间隔
|
||||||
|
- 点击"获取数据并分析"获取初始数据
|
||||||
|
- 系统将自动按设定间隔刷新数据
|
||||||
|
|
||||||
|
### 技术细节 🔧
|
||||||
|
|
||||||
|
#### 新增组件
|
||||||
|
- `dcc.Checklist`: 自动刷新开关控制
|
||||||
|
- `dcc.Dropdown`: 刷新间隔选择器
|
||||||
|
- `dcc.Interval`: 定时器组件
|
||||||
|
- `dcc.Store`: 自动刷新设置存储
|
||||||
|
- `html.Div`: 状态显示组件
|
||||||
|
|
||||||
|
#### 回调函数
|
||||||
|
- `control_auto_refresh`: 控制自动刷新功能的启用/禁用
|
||||||
|
- `update_next_refresh_time`: 更新下次刷新时间显示
|
||||||
|
- `analyze_data_with_auto_refresh`: 处理自动和手动触发的数据分析
|
||||||
|
|
||||||
|
#### 状态管理
|
||||||
|
- 自动刷新设置存储在客户端Store中
|
||||||
|
- 定时器状态根据用户设置动态调整
|
||||||
|
- 支持手动刷新和自动刷新的区分标记
|
||||||
|
|
||||||
|
### 兼容性 ⚠️
|
||||||
|
|
||||||
|
- **向后兼容**: 完全兼容现有功能,不影响原有操作流程
|
||||||
|
- **浏览器要求**: 需要支持JavaScript的现代浏览器
|
||||||
|
- **网络依赖**: 自动刷新功能依赖稳定的网络连接
|
||||||
|
|
||||||
|
### 注意事项 ⚠️
|
||||||
|
|
||||||
|
1. **首次使用**: 启用自动刷新后需要先手动获取一次数据
|
||||||
|
2. **性能考虑**: 短间隔刷新可能增加服务器负载,建议根据需要选择合适间隔
|
||||||
|
3. **网络异常**: 网络连接异常时自动刷新可能失败,可通过手动刷新恢复
|
||||||
|
4. **浏览器标签**: 切换到其他标签页时,自动刷新可能受到浏览器节能机制影响
|
||||||
|
|
||||||
|
### 未来计划 📋
|
||||||
|
|
||||||
|
- [ ] 添加数据变化通知
|
||||||
|
- [ ] 支持自定义刷新间隔
|
||||||
|
- [ ] 添加网络状态监控
|
||||||
|
- [ ] 实现失败重试机制
|
||||||
|
- [ ] 添加数据历史对比功能
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.0.0] - 2024-12-XX
|
||||||
|
|
||||||
|
### 初始版本
|
||||||
|
- 基础缠论分析功能
|
||||||
|
- 交互式图表展示
|
||||||
|
- 多种时间周期支持
|
||||||
|
- 分型强度分析
|
||||||
|
- 买卖点识别
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
# 自动刷新功能说明
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
|
||||||
|
在缠论分析系统中新增了自动刷新功能,允许用户设置定时自动刷新数据,实时监控市场变化。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
### 1. 自动刷新控制
|
||||||
|
- **启用/禁用开关**: 通过勾选框控制自动刷新的开启和关闭
|
||||||
|
- **智能状态管理**: 启用时激活相关控件,禁用时自动禁用下拉菜单
|
||||||
|
|
||||||
|
### 2. 刷新间隔设置
|
||||||
|
支持以下刷新间隔选项:
|
||||||
|
- 5秒
|
||||||
|
- 10秒
|
||||||
|
- 15秒 (默认)
|
||||||
|
- 30秒
|
||||||
|
- 1分钟
|
||||||
|
- 5分钟
|
||||||
|
|
||||||
|
### 3. 实时状态显示
|
||||||
|
- **刷新状态**: 显示当前自动刷新的启用状态和间隔设置
|
||||||
|
- **下次刷新时间**: 显示预计下次刷新的时间
|
||||||
|
- **更新时间**: 在统计信息中显示最后更新的时间
|
||||||
|
- **更新方式**: 区分手动刷新和自动刷新
|
||||||
|
|
||||||
|
## 界面布局
|
||||||
|
|
||||||
|
新增的自动刷新控制面板位于原有控制面板下方,包含:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─ 自动刷新控制行 ─────────────────────────────────────┐
|
||||||
|
│ [自动刷新:] [刷新间隔:] [刷新状态:] [下次刷新:] │
|
||||||
|
│ ☐ 启用自动刷新 [15秒 ▼] 未启用 -- │
|
||||||
|
└──────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
启用自动刷新后:
|
||||||
|
```
|
||||||
|
┌─ 自动刷新控制行 ─────────────────────────────────────┐
|
||||||
|
│ [自动刷新:] [刷新间隔:] [刷新状态:] [下次刷新:] │
|
||||||
|
│ ☑ 启用自动刷新 [15秒 ▼] 已启用-每15秒刷新 14:30:45 │
|
||||||
|
└──────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 1. 启用自动刷新
|
||||||
|
1. 勾选"启用自动刷新"复选框
|
||||||
|
2. 刷新状态会显示为"已启用 - 每X秒刷新"
|
||||||
|
3. 刷新间隔下拉菜单变为可用状态
|
||||||
|
|
||||||
|
### 2. 设置刷新间隔
|
||||||
|
1. 确保自动刷新已启用
|
||||||
|
2. 在"刷新间隔"下拉菜单中选择所需的时间间隔
|
||||||
|
3. 状态文字会立即更新显示新的间隔
|
||||||
|
|
||||||
|
### 3. 开始自动刷新
|
||||||
|
1. 首次需要点击"获取数据并分析"按钮获取初始数据
|
||||||
|
2. 之后系统会根据设定的间隔自动刷新数据
|
||||||
|
3. 自动刷新时会保持所有其他设置参数不变
|
||||||
|
|
||||||
|
### 4. 监控刷新状态
|
||||||
|
- 查看"下次刷新时间"了解预计刷新时间
|
||||||
|
- 在统计信息中查看"最后更新"时间
|
||||||
|
- "更新方式"会显示是"自动刷新"还是"手动刷新"
|
||||||
|
|
||||||
|
## 技术实现
|
||||||
|
|
||||||
|
### 1. 组件结构
|
||||||
|
- `dcc.Checklist`: 自动刷新开关
|
||||||
|
- `dcc.Dropdown`: 刷新间隔选择
|
||||||
|
- `dcc.Interval`: 定时器组件
|
||||||
|
- `dcc.Store`: 存储自动刷新设置
|
||||||
|
- `html.Div`: 状态显示组件
|
||||||
|
|
||||||
|
### 2. 回调函数
|
||||||
|
- `control_auto_refresh`: 控制自动刷新功能的启用/禁用
|
||||||
|
- `update_next_refresh_time`: 更新下次刷新时间显示
|
||||||
|
- `analyze_data_with_auto_refresh`: 处理自动刷新触发的数据分析
|
||||||
|
|
||||||
|
### 3. 状态管理
|
||||||
|
- 自动刷新设置存储在 `auto-refresh-settings` Store中
|
||||||
|
- 定时器组件的enabled状态根据设置动态切换
|
||||||
|
- 刷新间隔实时更新定时器的interval属性
|
||||||
|
|
||||||
|
## 测试验证
|
||||||
|
|
||||||
|
### 手动测试步骤
|
||||||
|
1. 访问 http://localhost:8051
|
||||||
|
2. 查看自动刷新控制面板是否正常显示
|
||||||
|
3. 测试启用/禁用自动刷新功能
|
||||||
|
4. 测试不同刷新间隔的设置
|
||||||
|
5. 验证自动刷新是否按设定间隔工作
|
||||||
|
6. 检查状态显示是否准确更新
|
||||||
|
|
||||||
|
### 预期行为
|
||||||
|
- 初始状态:未启用,下拉菜单禁用
|
||||||
|
- 启用后:状态更新,下拉菜单可用
|
||||||
|
- 更改间隔:状态文字立即更新
|
||||||
|
- 自动刷新:数据按间隔自动更新
|
||||||
|
- 保持设置:其他参数在自动刷新时不变
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **首次使用**: 启用自动刷新后需要先手动获取一次数据
|
||||||
|
2. **网络连接**: 自动刷新依赖网络连接,连接异常时可能失败
|
||||||
|
3. **性能影响**: 短间隔刷新可能增加服务器负载
|
||||||
|
4. **浏览器要求**: 需要JavaScript支持,建议使用现代浏览器
|
||||||
|
5. **数据一致性**: 自动刷新会保持所有分析参数设置不变
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 自动刷新不工作
|
||||||
|
- 检查是否已勾选"启用自动刷新"
|
||||||
|
- 确认已先进行过一次手动数据获取
|
||||||
|
- 查看浏览器控制台是否有错误信息
|
||||||
|
|
||||||
|
### 状态显示异常
|
||||||
|
- 刷新页面重新加载组件
|
||||||
|
- 检查网络连接是否正常
|
||||||
|
- 确认后端服务是否正常运行
|
||||||
|
|
||||||
|
### 刷新间隔不准确
|
||||||
|
- 网络延迟可能影响实际刷新间隔
|
||||||
|
- 数据处理时间会额外增加总耗时
|
||||||
|
- 建议设置不小于5秒的刷新间隔
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
自动刷新功能演示脚本
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
import webbrowser
|
||||||
|
from web.app import run_app
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("🚀 缠论分析系统 - 自动刷新功能演示")
|
||||||
|
print("="*50)
|
||||||
|
|
||||||
|
print("\n📋 新增功能说明:")
|
||||||
|
print("✅ 自动刷新勾选框 - 控制自动刷新开关")
|
||||||
|
print("✅ 刷新间隔下拉菜单 - 选择5秒/10秒/15秒/30秒/1分钟/5分钟")
|
||||||
|
print("✅ 默认15秒刷新间隔")
|
||||||
|
print("✅ 刷新时保持其他选项不变")
|
||||||
|
print("✅ 实时状态显示")
|
||||||
|
print("✅ 更新时间和方式标记")
|
||||||
|
|
||||||
|
print("\n🎯 演示步骤:")
|
||||||
|
print("1. 启动Web应用服务器")
|
||||||
|
print("2. 自动打开浏览器")
|
||||||
|
print("3. 手动测试自动刷新功能")
|
||||||
|
|
||||||
|
print("\n🔧 手动测试指南:")
|
||||||
|
print("─" * 30)
|
||||||
|
print("① 查看页面上方是否有\"自动刷新控制行\"")
|
||||||
|
print("② 初始状态应显示:")
|
||||||
|
print(" - ☐ 启用自动刷新 (未勾选)")
|
||||||
|
print(" - 刷新间隔: 15秒 (禁用状态)")
|
||||||
|
print(" - 刷新状态: 未启用")
|
||||||
|
print(" - 下次刷新: --")
|
||||||
|
|
||||||
|
print("\n③ 勾选\"启用自动刷新\"后应显示:")
|
||||||
|
print(" - ☑ 启用自动刷新 (已勾选)")
|
||||||
|
print(" - 刷新间隔: 15秒 (可选择)")
|
||||||
|
print(" - 刷新状态: 已启用 - 每15秒刷新")
|
||||||
|
print(" - 下次刷新: HH:MM:SS")
|
||||||
|
|
||||||
|
print("\n④ 更改刷新间隔测试:")
|
||||||
|
print(" - 选择不同间隔(5秒、30秒等)")
|
||||||
|
print(" - 状态文字应相应更新")
|
||||||
|
|
||||||
|
print("\n⑤ 数据自动刷新测试:")
|
||||||
|
print(" - 点击\"获取数据并分析\"获取初始数据")
|
||||||
|
print(" - 观察页面是否按设定间隔自动刷新")
|
||||||
|
print(" - 检查统计信息中的更新时间和方式")
|
||||||
|
|
||||||
|
print("\n🌐 准备启动Web服务器...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 给用户一些时间阅读说明
|
||||||
|
for i in range(3, 0, -1):
|
||||||
|
print(f"⏳ {i}秒后启动服务器...")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
print("\n🔗 Web服务器启动中...")
|
||||||
|
print("📱 浏览器将自动打开: http://localhost:8051")
|
||||||
|
print("⚠️ 如果浏览器未自动打开,请手动访问上述地址")
|
||||||
|
|
||||||
|
# 延迟打开浏览器
|
||||||
|
def open_browser():
|
||||||
|
time.sleep(2)
|
||||||
|
try:
|
||||||
|
webbrowser.open('http://localhost:8051')
|
||||||
|
print("✅ 浏览器已打开")
|
||||||
|
except:
|
||||||
|
print("❌ 无法自动打开浏览器,请手动访问 http://localhost:8051")
|
||||||
|
|
||||||
|
import threading
|
||||||
|
browser_thread = threading.Thread(target=open_browser)
|
||||||
|
browser_thread.daemon = True
|
||||||
|
browser_thread.start()
|
||||||
|
|
||||||
|
# 启动应用
|
||||||
|
print("\n🎮 服务器正在运行...")
|
||||||
|
print("💡 按 Ctrl+C 停止服务器")
|
||||||
|
print("─" * 50)
|
||||||
|
|
||||||
|
run_app(debug=False, port=8051)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\n🛑 服务器已停止")
|
||||||
|
print("👋 感谢使用缠论分析系统!")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ 启动失败: {e}")
|
||||||
|
print("🔧 请检查端口8051是否被占用")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
"""
|
||||||
|
测试自动刷新功能
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
|
from selenium.webdriver.chrome.options import Options
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestAutoRefresh(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""设置测试环境"""
|
||||||
|
# 配置Chrome选项
|
||||||
|
chrome_options = Options()
|
||||||
|
chrome_options.add_argument('--headless') # 无头模式
|
||||||
|
chrome_options.add_argument('--no-sandbox')
|
||||||
|
chrome_options.add_argument('--disable-dev-shm-usage')
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.driver = webdriver.Chrome(options=chrome_options)
|
||||||
|
self.driver.get('http://localhost:8051')
|
||||||
|
self.wait = WebDriverWait(self.driver, 10)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"无法启动Chrome浏览器: {e}")
|
||||||
|
self.skipTest("Chrome浏览器不可用")
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""清理测试环境"""
|
||||||
|
if hasattr(self, 'driver'):
|
||||||
|
self.driver.quit()
|
||||||
|
|
||||||
|
def test_auto_refresh_components_exist(self):
|
||||||
|
"""测试自动刷新组件是否存在"""
|
||||||
|
# 检查自动刷新勾选框
|
||||||
|
auto_refresh_toggle = self.wait.until(
|
||||||
|
EC.presence_of_element_located((By.ID, 'auto-refresh-toggle'))
|
||||||
|
)
|
||||||
|
self.assertIsNotNone(auto_refresh_toggle)
|
||||||
|
|
||||||
|
# 检查刷新间隔下拉菜单
|
||||||
|
refresh_interval_dropdown = self.driver.find_element(By.ID, 'refresh-interval-dropdown')
|
||||||
|
self.assertIsNotNone(refresh_interval_dropdown)
|
||||||
|
|
||||||
|
# 检查状态显示
|
||||||
|
refresh_status = self.driver.find_element(By.ID, 'refresh-status')
|
||||||
|
self.assertIsNotNone(refresh_status)
|
||||||
|
|
||||||
|
# 检查下次刷新时间显示
|
||||||
|
next_refresh_time = self.driver.find_element(By.ID, 'next-refresh-time')
|
||||||
|
self.assertIsNotNone(next_refresh_time)
|
||||||
|
|
||||||
|
def test_initial_state(self):
|
||||||
|
"""测试初始状态"""
|
||||||
|
# 检查初始状态:自动刷新未启用
|
||||||
|
refresh_status = self.driver.find_element(By.ID, 'refresh-status')
|
||||||
|
self.assertEqual(refresh_status.text, "未启用")
|
||||||
|
|
||||||
|
# 检查下次刷新时间显示为 "--"
|
||||||
|
next_refresh_time = self.driver.find_element(By.ID, 'next-refresh-time')
|
||||||
|
self.assertEqual(next_refresh_time.text, "--")
|
||||||
|
|
||||||
|
# 检查刷新间隔下拉菜单是否禁用
|
||||||
|
refresh_interval_dropdown = self.driver.find_element(By.ID, 'refresh-interval-dropdown')
|
||||||
|
# Dash组件的disabled状态需要通过其他方式检查
|
||||||
|
|
||||||
|
def test_enable_auto_refresh(self):
|
||||||
|
"""测试启用自动刷新"""
|
||||||
|
# 勾选自动刷新
|
||||||
|
auto_refresh_checkbox = self.wait.until(
|
||||||
|
EC.element_to_be_clickable((By.XPATH, "//input[@value='enabled']"))
|
||||||
|
)
|
||||||
|
auto_refresh_checkbox.click()
|
||||||
|
|
||||||
|
# 等待状态更新
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# 检查状态是否更新
|
||||||
|
refresh_status = self.driver.find_element(By.ID, 'refresh-status')
|
||||||
|
self.assertIn("已启用", refresh_status.text)
|
||||||
|
self.assertIn("15秒", refresh_status.text) # 默认15秒
|
||||||
|
|
||||||
|
def test_change_refresh_interval(self):
|
||||||
|
"""测试更改刷新间隔"""
|
||||||
|
# 先启用自动刷新
|
||||||
|
auto_refresh_checkbox = self.wait.until(
|
||||||
|
EC.element_to_be_clickable((By.XPATH, "//input[@value='enabled']"))
|
||||||
|
)
|
||||||
|
auto_refresh_checkbox.click()
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# 更改刷新间隔(这部分可能需要根据Dash的实际实现调整)
|
||||||
|
# 由于Dash组件的交互比较复杂,这里主要验证组件存在
|
||||||
|
refresh_interval_dropdown = self.driver.find_element(By.ID, 'refresh-interval-dropdown')
|
||||||
|
self.assertIsNotNone(refresh_interval_dropdown)
|
||||||
|
|
||||||
|
def test_web_server_running():
|
||||||
|
"""测试Web服务器是否运行"""
|
||||||
|
try:
|
||||||
|
response = requests.get('http://localhost:8051', timeout=5)
|
||||||
|
return response.status_code == 200
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("测试自动刷新功能...")
|
||||||
|
|
||||||
|
# 首先检查web服务器是否运行
|
||||||
|
if not test_web_server_running():
|
||||||
|
print("❌ Web服务器未运行在 http://localhost:8051")
|
||||||
|
print("请先运行: python -c \"from web.app import run_app; run_app(debug=False, port=8051)\"")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
print("✅ Web服务器正在运行")
|
||||||
|
|
||||||
|
# 运行测试
|
||||||
|
try:
|
||||||
|
unittest.main(argv=[''], exit=False, verbosity=2)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"测试运行出错: {e}")
|
||||||
|
print("可能是因为没有安装selenium或Chrome驱动")
|
||||||
|
print("手动测试建议:")
|
||||||
|
print("1. 打开浏览器访问 http://localhost:8051")
|
||||||
|
print("2. 查看是否有'自动刷新'勾选框和'刷新间隔'下拉菜单")
|
||||||
|
print("3. 勾选自动刷新,查看状态是否变为'已启用'")
|
||||||
|
print("4. 修改刷新间隔,查看状态是否相应更新")
|
||||||
|
print("5. 点击'获取数据并分析'按钮,然后观察是否会自动刷新")
|
||||||
+312
-93
@@ -20,7 +20,66 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
"""创建Dash应用"""
|
"""创建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()
|
data_fetcher = DataFetcher()
|
||||||
@@ -30,12 +89,12 @@ def create_app():
|
|||||||
# 应用布局
|
# 应用布局
|
||||||
app.layout = html.Div([
|
app.layout = html.Div([
|
||||||
# 标题
|
# 标题
|
||||||
html.H1("缠论分析系统", className="text-center mb-4"),
|
html.H1("缠论分析系统", className="text-center mb-2"),
|
||||||
|
|
||||||
# 控制面板
|
# 控制面板
|
||||||
html.Div([
|
html.Div([
|
||||||
html.Div([
|
html.Div([
|
||||||
html.Label("交易对:"),
|
html.Label("交易对:", className="small"),
|
||||||
dcc.Dropdown(
|
dcc.Dropdown(
|
||||||
id='symbol-dropdown',
|
id='symbol-dropdown',
|
||||||
options=[
|
options=[
|
||||||
@@ -46,12 +105,12 @@ def create_app():
|
|||||||
{'label': 'SOL/USDT', 'value': 'SOL/USDT'}
|
{'label': 'SOL/USDT', 'value': 'SOL/USDT'}
|
||||||
],
|
],
|
||||||
value='BTC/USDT',
|
value='BTC/USDT',
|
||||||
className="mb-3"
|
className="mb-1"
|
||||||
)
|
)
|
||||||
], className="col-md-2"),
|
], className="col-md-2"),
|
||||||
|
|
||||||
html.Div([
|
html.Div([
|
||||||
html.Label("时间周期:"),
|
html.Label("时间周期:", className="small"),
|
||||||
dcc.Dropdown(
|
dcc.Dropdown(
|
||||||
id='timeframe-dropdown',
|
id='timeframe-dropdown',
|
||||||
options=[
|
options=[
|
||||||
@@ -63,52 +122,52 @@ def create_app():
|
|||||||
{'label': '4小时', 'value': '4h'},
|
{'label': '4小时', 'value': '4h'},
|
||||||
{'label': '1天', 'value': '1d'}
|
{'label': '1天', 'value': '1d'}
|
||||||
],
|
],
|
||||||
value='1h',
|
value='5m',
|
||||||
className="form-select"
|
className="form-select mb-1"
|
||||||
)
|
)
|
||||||
], className="col-md-2"),
|
], className="col-md-2"),
|
||||||
|
|
||||||
html.Div([
|
html.Div([
|
||||||
html.Label("数据数量:"),
|
html.Label("数据数量:", className="small"),
|
||||||
dcc.Slider(
|
dcc.Slider(
|
||||||
id='limit-slider',
|
id='limit-slider',
|
||||||
min=100,
|
min=100,
|
||||||
max=1000,
|
max=1000,
|
||||||
step=50,
|
step=50,
|
||||||
value=500,
|
value=100,
|
||||||
marks={i: str(i) for i in range(100, 1001, 200)},
|
marks={i: str(i) for i in range(100, 1001, 300)},
|
||||||
className="mb-3"
|
className="mb-1"
|
||||||
)
|
)
|
||||||
], className="col-md-2"),
|
], className="col-md-2"),
|
||||||
|
|
||||||
html.Div([
|
html.Div([
|
||||||
html.Label("基础分型强度:"),
|
html.Label("基础分型强度:", className="small"),
|
||||||
dcc.Slider(
|
dcc.Slider(
|
||||||
id='fractal-strength-slider',
|
id='fractal-strength-slider',
|
||||||
min=1,
|
min=1,
|
||||||
max=5,
|
max=5,
|
||||||
step=1,
|
step=1,
|
||||||
value=1,
|
value=5,
|
||||||
marks={i: str(i) for i in range(1, 6)},
|
marks={i: str(i) for i in range(1, 6)},
|
||||||
className="mb-3"
|
className="mb-1"
|
||||||
)
|
)
|
||||||
], className="col-md-2"),
|
], className="col-md-2"),
|
||||||
|
|
||||||
html.Div([
|
html.Div([
|
||||||
html.Label("增强强度过滤:"),
|
html.Label("增强强度过滤:", className="small"),
|
||||||
dcc.Slider(
|
dcc.Slider(
|
||||||
id='enhanced-strength-filter',
|
id='enhanced-strength-filter',
|
||||||
min=0,
|
min=0,
|
||||||
max=100,
|
max=100,
|
||||||
step=10,
|
step=10,
|
||||||
value=0,
|
value=0,
|
||||||
marks={i: str(i) for i in range(0, 101, 20)},
|
marks={i: str(i) for i in range(0, 101, 50)},
|
||||||
className="mb-3"
|
className="mb-1"
|
||||||
)
|
)
|
||||||
], className="col-md-2"),
|
], className="col-md-2"),
|
||||||
|
|
||||||
html.Div([
|
html.Div([
|
||||||
html.Label("显示级别:"),
|
html.Label("显示级别:", className="small"),
|
||||||
dcc.Dropdown(
|
dcc.Dropdown(
|
||||||
id='display-level-dropdown',
|
id='display-level-dropdown',
|
||||||
options=[
|
options=[
|
||||||
@@ -118,20 +177,61 @@ def create_app():
|
|||||||
{'label': '自定义过滤', 'value': 'custom'}
|
{'label': '自定义过滤', 'value': 'custom'}
|
||||||
],
|
],
|
||||||
value='all',
|
value='all',
|
||||||
className="form-select"
|
className="form-select mb-1"
|
||||||
)
|
)
|
||||||
], className="col-md-2")
|
], 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.Div([
|
||||||
html.Button("获取数据并分析", id="analyze-btn",
|
html.Button("获取数据并分析", id="analyze-btn",
|
||||||
className="btn btn-primary me-2"),
|
className="btn btn-primary btn-sm me-2"),
|
||||||
html.Button("刷新数据", id="refresh-btn",
|
html.Button("刷新数据", id="refresh-btn",
|
||||||
className="btn btn-secondary me-2"),
|
className="btn btn-secondary btn-sm me-2"),
|
||||||
html.Button("导出结果", id="export-btn",
|
html.Button("导出结果", id="export-btn",
|
||||||
className="btn btn-success"),
|
className="btn btn-success btn-sm"),
|
||||||
], className="text-center mb-4"),
|
], className="text-center mb-2"),
|
||||||
|
|
||||||
# 加载状态
|
# 加载状态
|
||||||
dcc.Loading(
|
dcc.Loading(
|
||||||
@@ -139,49 +239,84 @@ def create_app():
|
|||||||
children=[
|
children=[
|
||||||
# 主图表
|
# 主图表
|
||||||
html.Div([
|
html.Div([
|
||||||
dcc.Graph(id='main-chart', style={'height': '800px'})
|
dcc.Graph(id='main-chart', style={'height': '600px'})
|
||||||
], className="mb-4"),
|
], className="mb-2"),
|
||||||
|
|
||||||
# 统计信息
|
# 信息面板 - 使用三栏布局
|
||||||
html.Div([
|
html.Div([
|
||||||
html.H3("分析统计", className="mb-3"),
|
# 统计信息
|
||||||
html.Div(id='statistics-content')
|
html.Div([
|
||||||
], className="mb-4"),
|
html.H5("分析统计", className="mb-1"),
|
||||||
|
html.Div(id='statistics-content')
|
||||||
|
], className="col-md-4 mb-2"),
|
||||||
|
|
||||||
# 市场结构
|
# 市场结构
|
||||||
html.Div([
|
html.Div([
|
||||||
html.H3("当前市场结构", className="mb-3"),
|
html.H5("市场结构", className="mb-1"),
|
||||||
html.Div(id='market-structure-content')
|
html.Div(id='market-structure-content')
|
||||||
], className="mb-4"),
|
], className="col-md-4 mb-2"),
|
||||||
|
|
||||||
# 最新信号
|
# 最新信号
|
||||||
html.Div([
|
html.Div([
|
||||||
html.H3("最新买卖点信号", className="mb-3"),
|
html.H5("最新信号", className="mb-1"),
|
||||||
html.Div(id='latest-signals-content')
|
html.Div(id='latest-signals-content')
|
||||||
])
|
], className="col-md-4 mb-2")
|
||||||
|
], className="row")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
||||||
# 存储数据
|
# 存储数据和定时器组件
|
||||||
dcc.Store(id='analysis-data'),
|
dcc.Store(id='analysis-data'),
|
||||||
dcc.Store(id='market-structure-data')
|
dcc.Store(id='market-structure-data'),
|
||||||
], className="container-fluid p-4")
|
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(
|
@app.callback(
|
||||||
[Output('analysis-data', 'data'),
|
[Output('analysis-data', 'data'),
|
||||||
Output('market-structure-data', 'data')],
|
Output('market-structure-data', 'data')],
|
||||||
[Input('analyze-btn', 'n_clicks'),
|
[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('symbol-dropdown', 'value'),
|
||||||
State('timeframe-dropdown', 'value'),
|
State('timeframe-dropdown', 'value'),
|
||||||
State('limit-slider', 'value'),
|
State('limit-slider', 'value'),
|
||||||
State('fractal-strength-slider', 'value'),
|
State('fractal-strength-slider', 'value'),
|
||||||
State('enhanced-strength-filter', '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):
|
def analyze_data_with_auto_refresh(analyze_clicks, refresh_clicks, auto_refresh_intervals,
|
||||||
if not analyze_clicks and not refresh_clicks:
|
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
|
return None, None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -264,7 +399,9 @@ def create_app():
|
|||||||
'central_banks_count': result['central_bank_info']['total'],
|
'central_banks_count': result['central_bank_info']['total'],
|
||||||
'trading_points_count': result['trading_signal_info']['total'],
|
'trading_points_count': result['trading_signal_info']['total'],
|
||||||
'symbol': symbol,
|
'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']} 缠论分析图",
|
title=f"{analysis_data['symbol']} {analysis_data['timeframe']} 缠论分析图",
|
||||||
xaxis_title="时间",
|
xaxis_title="时间",
|
||||||
yaxis_title="价格",
|
yaxis_title="价格",
|
||||||
height=700,
|
height=550,
|
||||||
xaxis_rangeslider_visible=False,
|
xaxis_rangeslider_visible=False,
|
||||||
hovermode='x unified'
|
hovermode='x unified',
|
||||||
|
margin=dict(l=50, r=50, t=50, b=50)
|
||||||
)
|
)
|
||||||
|
|
||||||
return fig
|
return fig
|
||||||
@@ -505,14 +643,15 @@ def create_app():
|
|||||||
|
|
||||||
# 基础统计
|
# 基础统计
|
||||||
basic_stats = html.Div([
|
basic_stats = html.Div([
|
||||||
html.H5("📊 基础统计"),
|
html.P(f"交易对: {analysis_data.get('symbol', 'N/A')}", className="mb-1 small"),
|
||||||
html.P(f"交易对: {analysis_data.get('symbol', 'N/A')}"),
|
html.P(f"时间周期: {analysis_data.get('timeframe', 'N/A')}", className="mb-1 small"),
|
||||||
html.P(f"时间周期: {analysis_data.get('timeframe', 'N/A')}"),
|
html.P(f"K线数量: {analysis_data.get('processed_klines_count', 0)}", className="mb-1 small"),
|
||||||
html.P(f"处理后K线数量: {analysis_data.get('processed_klines_count', 0)}"),
|
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('strokes_count', 0)}"),
|
html.P(f"中枢: {analysis_data.get('central_banks_count', 0)} | 买卖点: {analysis_data.get('trading_points_count', 0)}", className="mb-1 small"),
|
||||||
html.P(f"线段数量: {analysis_data.get('segments_count', 0)}"),
|
html.Hr(className="my-1"),
|
||||||
html.P(f"中枢数量: {analysis_data.get('central_banks_count', 0)}"),
|
html.P(f"更新: {analysis_data.get('last_update', 'N/A')}", className="text-info small mb-1"),
|
||||||
html.P(f"买卖点数量: {analysis_data.get('trading_points_count', 0)}")
|
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_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([
|
fractal_stats = html.Div([
|
||||||
html.H5("🔥 分型强度分析"),
|
html.Hr(className="my-1"),
|
||||||
html.P(f"总分型数: {len(fractals)} 个"),
|
html.P(f"🔥 分型分析", className="fw-bold small mb-1"),
|
||||||
html.P(f"强势分型(≥70分): {len(strong_fractals)} 个"),
|
html.P(f"总数: {len(fractals)} | 强势: {len(strong_fractals)} | 中等: {len(medium_fractals)}", className="mb-1 small"),
|
||||||
html.P(f"中等分型(40-70分): {len(medium_fractals)} 个"),
|
html.P(f"当前显示: {len(filtered_fractals)} 个", className="mb-1 small"),
|
||||||
html.P(f"弱势分型(<40分): {len(weak_fractals)} 个"),
|
html.P(f"平均强度: {avg_enhanced:.1f}分", className="mb-0 small text-muted")
|
||||||
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 ""
|
|
||||||
])
|
])
|
||||||
|
|
||||||
return html.Div([basic_stats, html.Hr(), fractal_stats])
|
return html.Div([basic_stats, fractal_stats])
|
||||||
else:
|
else:
|
||||||
return basic_stats
|
return basic_stats
|
||||||
|
|
||||||
@@ -568,19 +695,21 @@ def create_app():
|
|||||||
)
|
)
|
||||||
def update_market_structure(market_data):
|
def update_market_structure(market_data):
|
||||||
if not market_data:
|
if not market_data:
|
||||||
return "暂无市场数据"
|
return "暂无数据"
|
||||||
|
|
||||||
latest_price = market_data.get('latest_price', 0)
|
latest_price = market_data.get('latest_price', 0)
|
||||||
price_change = market_data.get('price_change', 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
|
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([
|
return html.Div([
|
||||||
html.H4("市场结构"),
|
html.P(f"当前价格: ${latest_price:.2f}", className="mb-1 small"),
|
||||||
html.P(f"当前价格: ${latest_price:.2f}"),
|
html.P(f"变化: ${price_change:.2f} ({change_percent:+.2f}%)",
|
||||||
html.P(f"价格变化: ${price_change:.2f} ({change_percent:+.2f}%)"),
|
className=f"mb-1 small {color}"),
|
||||||
html.P(f"24小时最高: ${market_data.get('high_24h', 0):.2f}"),
|
html.P(f"24h最高: ${market_data.get('high_24h', 0):.2f}", className="mb-1 small"),
|
||||||
html.P(f"24小时最低: ${market_data.get('low_24h', 0):.2f}"),
|
html.P(f"24h最低: ${market_data.get('low_24h', 0):.2f}", className="mb-1 small"),
|
||||||
html.P(f"平均成交量: {market_data.get('volume_avg', 0):.2f}")
|
html.P(f"平均量: {market_data.get('volume_avg', 0):.0f}", className="mb-0 small text-muted")
|
||||||
])
|
])
|
||||||
|
|
||||||
@app.callback(
|
@app.callback(
|
||||||
@@ -589,25 +718,115 @@ def create_app():
|
|||||||
)
|
)
|
||||||
def update_latest_signals(analysis_data):
|
def update_latest_signals(analysis_data):
|
||||||
if not analysis_data:
|
if not analysis_data:
|
||||||
return "暂无信号数据"
|
return "暂无数据"
|
||||||
|
|
||||||
trading_points_count = analysis_data.get('trading_points_count', 0)
|
trading_points_count = analysis_data.get('trading_points_count', 0)
|
||||||
|
fractals_count = analysis_data.get('fractals_count', 0)
|
||||||
|
|
||||||
return html.Div([
|
return html.Div([
|
||||||
html.H4("最新信号"),
|
html.P(f"买卖点: {trading_points_count} 个", className="mb-1 small"),
|
||||||
html.P(f"检测到 {trading_points_count} 个买卖点信号"),
|
html.P(f"分型: {fractals_count} 个", className="mb-1 small"),
|
||||||
html.P("详细信号分析请查看主图表标记")
|
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
|
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):
|
def run_app(debug=True, port=8050):
|
||||||
"""运行Web应用"""
|
"""运行Web应用"""
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
|||||||
+205
@@ -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. **日志输出**: 查看服务器端日志信息
|
||||||
|
|
||||||
|
## 未来改进
|
||||||
|
|
||||||
|
### 增强功能
|
||||||
|
- [ ] 添加视觉进度条显示
|
||||||
|
- [ ] 支持暂停/恢复倒计时
|
||||||
|
- [ ] 添加声音提醒功能
|
||||||
|
- [ ] 支持自定义倒计时样式
|
||||||
|
|
||||||
|
### 性能优化
|
||||||
|
- [ ] 优化定时器精度
|
||||||
|
- [ ] 减少不必要的重新渲染
|
||||||
|
- [ ] 添加防抖机制
|
||||||
|
- [ ] 支持离线模式下的倒计时
|
||||||
|
|
||||||
|
### 用户体验
|
||||||
|
- [ ] 添加动画效果
|
||||||
|
- [ ] 支持倒计时颜色变化
|
||||||
|
- [ ] 添加键盘快捷键
|
||||||
|
- [ ] 支持移动端手势操作
|
||||||
@@ -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. 浏览器兼容性
|
||||||
|
|
||||||
|
## 未来优化方向
|
||||||
|
|
||||||
|
- [ ] 添加暗色主题支持
|
||||||
|
- [ ] 进一步优化移动端显示
|
||||||
|
- [ ] 添加可自定义的界面密度设置
|
||||||
|
- [ ] 优化图表的交互体验
|
||||||
|
- [ ] 添加快捷键支持
|
||||||
Reference in New Issue
Block a user