feat: 新增股票数据波形图和截图功能
refactor: 重构数据库和LLM分析器逻辑 fix: 修复爬虫解析和UI显示问题 docs: 更新配置文件和注释 style: 优化代码格式和日志输出
This commit is contained in:
117
main_window.py
117
main_window.py
@@ -4,10 +4,13 @@ PySide6 GUI界面模块
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QPushButton, QSlider, QDialog, QFormLayout,
|
||||
QLineEdit, QSpinBox, QMessageBox, QSystemTrayIcon,
|
||||
QMenu, QTextEdit, QGroupBox, QDialogButtonBox)
|
||||
QMenu, QTextEdit, QGroupBox, QDialogButtonBox, QCheckBox)
|
||||
from PySide6.QtCore import Qt, QTimer, Signal, QPoint
|
||||
from PySide6.QtGui import QFont, QColor, QPainter, QBrush, QPen, QIcon, QAction
|
||||
from typing import Callable, Optional
|
||||
from loguru import logger
|
||||
|
||||
from waveform_widget import WaveformWidget
|
||||
|
||||
|
||||
class SentimentIndicator(QWidget):
|
||||
@@ -57,22 +60,38 @@ class SentimentIndicator(QWidget):
|
||||
|
||||
def _get_color(self, score: int) -> QColor:
|
||||
"""根据分数获取颜色"""
|
||||
if score < 30:
|
||||
# 冷色系 - 蓝色/青色
|
||||
ratio = score / 30
|
||||
return QColor(int(0 + 100 * ratio), int(150 + 50 * ratio), 255)
|
||||
elif score < 70:
|
||||
# 中性 - 灰色/绿色
|
||||
if score < 50:
|
||||
ratio = (score - 30) / 20
|
||||
return QColor(int(100 + 50 * ratio), int(200 + 20 * ratio), int(200 - 50 * ratio))
|
||||
else:
|
||||
ratio = (score - 50) / 20
|
||||
return QColor(int(150 + 50 * ratio), int(220 - 20 * ratio), int(150 - 50 * ratio))
|
||||
# 45-55分区间:每1分一个颜色(从绿色到黄色渐变)
|
||||
if 45 <= score <= 55:
|
||||
# 在45-55分之间,每1分一个颜色
|
||||
ratio = (score - 45) / 10 # 0到1之间的比例
|
||||
# 从绿色(0, 200, 0)渐变到黄色(255, 255, 0)
|
||||
r = int(0 + 255 * ratio)
|
||||
g = 200 if ratio < 0.5 else int(200 + 55 * (ratio - 0.5) * 2)
|
||||
b = int(0 + 0 * ratio)
|
||||
return QColor(r, g, b)
|
||||
|
||||
# 45分以下:每5分一个颜色(从深蓝到绿色渐变)
|
||||
elif score < 45:
|
||||
# 将分数映射到0-8的区间(0-40分,每5分一个级别)
|
||||
level = score // 5
|
||||
# 从深蓝色(0, 100, 255)渐变到绿色(0, 200, 0)
|
||||
ratio = level / 8 # 0-8对应0-40分
|
||||
r = int(0 + 0 * ratio)
|
||||
g = int(100 + 100 * ratio)
|
||||
b = int(255 - 255 * ratio)
|
||||
return QColor(r, g, b)
|
||||
|
||||
# 55分以上:每5分一个颜色(从黄色到红色渐变)
|
||||
else:
|
||||
# 暖色系 - 橙色/红色
|
||||
ratio = (score - 70) / 30
|
||||
return QColor(255, int(200 - 100 * ratio), int(50 + 50 * ratio))
|
||||
# 将分数映射到0-8的区间(60-100分,每5分一个级别)
|
||||
level = (score - 60) // 5
|
||||
level = max(0, min(8, level)) # 限制在0-8范围内
|
||||
# 从黄色(255, 255, 0)渐变到红色(255, 0, 0)
|
||||
ratio = level / 8
|
||||
r = 255
|
||||
g = int(255 - 255 * ratio)
|
||||
b = 0
|
||||
return QColor(r, g, b)
|
||||
|
||||
def get_description(self, score: int) -> str:
|
||||
"""获取描述文本"""
|
||||
@@ -125,7 +144,7 @@ class ConfigDialog(QDialog):
|
||||
self.user_agent_edit = QLineEdit(spider_config.get('user_agent', ''))
|
||||
self.interval_spin = QSpinBox()
|
||||
self.interval_spin.setRange(10, 3600)
|
||||
self.interval_spin.setValue(spider_config.get('fetch_interval', 60))
|
||||
self.interval_spin.setValue(spider_config.get('fetch_interval', 15))
|
||||
|
||||
layout.addRow("目标URL:", self.url_edit)
|
||||
layout.addRow("XPath:", self.xpath_edit)
|
||||
@@ -197,10 +216,14 @@ class ConfigDialog(QDialog):
|
||||
class MainWindow(QWidget):
|
||||
"""主窗口"""
|
||||
|
||||
def __init__(self, config_manager, parent=None):
|
||||
def __init__(self, config_manager, spider_manager=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.config_manager = config_manager
|
||||
self.setWindowTitle("股吧人气指示器")
|
||||
self.spider_manager = spider_manager
|
||||
|
||||
# 获取页面标题并设置窗口标题
|
||||
self._set_window_title()
|
||||
|
||||
self._init_ui()
|
||||
self._apply_config()
|
||||
|
||||
@@ -217,7 +240,7 @@ class MainWindow(QWidget):
|
||||
layout.setContentsMargins(10, 10, 10, 10)
|
||||
|
||||
# 标题
|
||||
self.title_label = QLabel("股吧人气")
|
||||
self.title_label = QLabel("上证指数sh000001")
|
||||
self.title_label.setAlignment(Qt.AlignCenter)
|
||||
title_font = QFont()
|
||||
title_font.setPointSize(14)
|
||||
@@ -236,26 +259,64 @@ class MainWindow(QWidget):
|
||||
status_font.setPointSize(10)
|
||||
self.status_label.setFont(status_font)
|
||||
|
||||
# 波形图组件
|
||||
self.waveform_widget = WaveformWidget()
|
||||
self.waveform_widget.setMinimumHeight(200)
|
||||
|
||||
# 按钮
|
||||
btn_layout = QHBoxLayout()
|
||||
self.refresh_btn = QPushButton("刷新")
|
||||
self.config_btn = QPushButton("配置")
|
||||
self.quit_btn = QPushButton("退出")
|
||||
self.quit_btn.clicked.connect(self.quit_app)
|
||||
btn_layout.addWidget(self.refresh_btn)
|
||||
btn_layout.addWidget(self.config_btn)
|
||||
btn_layout.addWidget(self.quit_btn)
|
||||
|
||||
# 添加到主布局
|
||||
layout.addWidget(self.title_label)
|
||||
layout.addWidget(self.indicator)
|
||||
layout.addWidget(self.score_label)
|
||||
layout.addWidget(self.status_label)
|
||||
layout.addWidget(self.waveform_widget)
|
||||
layout.addLayout(btn_layout)
|
||||
|
||||
# 设置窗口标志(无边框、可拖拽)
|
||||
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
|
||||
self.setAttribute(Qt.WA_TranslucentBackground)
|
||||
|
||||
def _set_window_title(self):
|
||||
"""设置窗口标题"""
|
||||
logger.debug("设置窗口标题")
|
||||
|
||||
# 尝试从爬虫获取页面标题
|
||||
if hasattr(self, 'spider_manager') and self.spider_manager:
|
||||
try:
|
||||
page_title = self.spider_manager.get_page_title()
|
||||
if page_title:
|
||||
# 从页面标题中提取股票名称
|
||||
import re
|
||||
match = re.search(r'(上证指数sh\d+)', page_title)
|
||||
if match:
|
||||
stock_name = match.group(1)
|
||||
window_title = f"冷暖值 - {stock_name}"
|
||||
self.setWindowTitle(window_title)
|
||||
|
||||
# 同时更新主标题标签(如果已初始化)
|
||||
if hasattr(self, 'title_label'):
|
||||
self.title_label.setText("上证指数sh000001")
|
||||
logger.info(f"设置窗口标题: {window_title}")
|
||||
return
|
||||
except Exception as e:
|
||||
logger.error(f"获取页面标题失败: {e}")
|
||||
|
||||
# 如果获取失败,使用默认标题
|
||||
self.setWindowTitle("冷暖值 - 股吧人气")
|
||||
logger.info("使用默认窗口标题")
|
||||
|
||||
def _init_tray_icon(self):
|
||||
"""初始化系统托盘"""
|
||||
logger.debug("初始化系统托盘")
|
||||
self.tray_icon = QSystemTrayIcon(self)
|
||||
self.tray_icon.setToolTip("股吧人气指示器")
|
||||
|
||||
@@ -275,9 +336,11 @@ class MainWindow(QWidget):
|
||||
|
||||
self.tray_icon.setContextMenu(tray_menu)
|
||||
self.tray_icon.show()
|
||||
logger.info("系统托盘初始化完成")
|
||||
|
||||
def quit_app(self):
|
||||
"""退出应用"""
|
||||
logger.info("退出应用")
|
||||
self.close()
|
||||
import sys
|
||||
sys.exit(0)
|
||||
@@ -322,7 +385,7 @@ class MainWindow(QWidget):
|
||||
|
||||
context_menu.addAction(config_action)
|
||||
context_menu.addAction(quit_action)
|
||||
context_menu.exec(event.globalPosition().toPoint())
|
||||
context_menu.exec(event.globalPos())
|
||||
|
||||
def show_config(self):
|
||||
"""显示配置对话框"""
|
||||
@@ -336,23 +399,33 @@ class MainWindow(QWidget):
|
||||
label = self.indicator.get_description(score)
|
||||
self.indicator.set_value(score, label)
|
||||
self.score_label.setText(f"{score} - {label}")
|
||||
logger.debug(f"更新指示灯: {score}分 - {label}")
|
||||
|
||||
def update_status(self, text: str):
|
||||
"""更新状态"""
|
||||
self.status_label.setText(text)
|
||||
logger.debug(f"更新状态: {text}")
|
||||
|
||||
def set_refresh_callback(self, callback: Callable):
|
||||
"""设置刷新按钮回调"""
|
||||
self.refresh_btn.clicked.connect(callback)
|
||||
logger.debug("设置刷新按钮回调")
|
||||
|
||||
def set_config_callback(self, callback: Callable):
|
||||
"""设置配置按钮回调"""
|
||||
self.config_btn.clicked.connect(callback)
|
||||
logger.debug("设置配置按钮回调")
|
||||
|
||||
def show_message(self, title: str, message: str, icon=QMessageBox.Information):
|
||||
def show_message(self, title: str, message: str):
|
||||
"""显示消息"""
|
||||
logger.info(f"显示消息: {title} - {message}")
|
||||
QMessageBox.information(self, title, message)
|
||||
|
||||
def add_waveform_data(self, time_str: str, value: float):
|
||||
"""添加波形图数据点"""
|
||||
self.waveform_widget.add_data_point(time_str, value)
|
||||
logger.info(f"添加波形图数据点: 时间={time_str}, 值={value}")
|
||||
|
||||
|
||||
class QCheckBox(QPushButton):
|
||||
"""自定义复选框"""
|
||||
|
||||
Reference in New Issue
Block a user