feat: 新增股票数据波形图和截图功能

refactor: 重构数据库和LLM分析器逻辑

fix: 修复爬虫解析和UI显示问题

docs: 更新配置文件和注释

style: 优化代码格式和日志输出
This commit is contained in:
2026-01-12 09:19:38 +08:00
parent 5b8b9ec35a
commit 96f206ea78
18 changed files with 1358 additions and 93 deletions

View File

@@ -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):
"""自定义复选框"""