refactor: 实现第二批 Manager(LogManager/ConfigManager/DialogService/TrainingModeManager)

- log_manager.py: 日志区+进度条+清空按钮封装,内部订阅 LogMessage/ProgressUpdate

- config_manager.py: 配置读写(new/load/save/get_current_config),懒加载安全(未加载面板返回 {})

- dialog_service.py: 纯展示弹窗封装(Pipeline状态/关于/AI设置)

- training_mode_manager.py: 训练模式切换,发布 TrainingModeChanged 事件

- water_quality_gui_v2.py: 725→605 行,菜单回调全部委托给 Manager,移除 _create_log_panel/_create_progress_panel/_on_log_message/_on_progress_update
This commit is contained in:
DXC
2026-06-17 17:35:27 +08:00
parent 19c86e6e44
commit 39e8c29913
5 changed files with 517 additions and 153 deletions

187
src/gui/core/log_manager.py Normal file
View File

@ -0,0 +1,187 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
日志与进度管理器
将主窗口中的日志区(QTextEdit)、进度条(QProgressBar)和"清空日志"按钮
的 UI 创建与控制逻辑完全封装。
职责:
- create_log_panel() → 返回组装好的 QWidget(日志 + 进度条)
- 内部订阅 LogMessage / ProgressUpdate 事件,自动更新 UI
- 主窗口无需再关心日志/进度的状态同步
订阅的事件:
LogMessage → {message, level} 写入日志区
ProgressUpdate → {percentage, message} 更新进度条
"""
from datetime import datetime
from PyQt5.QtCore import QObject
from PyQt5.QtGui import QTextCursor
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout,
QGroupBox, QTextEdit, QProgressBar, QPushButton,
)
from src.gui.core.event_bus import global_event_bus
class LogManager(QObject):
"""日志与进度管理器。
用法::
log_mgr = LogManager(parent=self)
log_panel = log_mgr.create_log_panel()
layout.addWidget(log_panel)
# 之后所有日志/进度更新由 EventBus 自动驱动,无需手动操作
"""
def __init__(self, parent=None):
super().__init__(parent)
self._log_text: QTextEdit = None
self._progress_bar: QProgressBar = None
# 订阅事件
global_event_bus.subscribe('LogMessage', self._on_log_message)
global_event_bus.subscribe('ProgressUpdate', self._on_progress_update)
# ═══════════════════════════════════════════════════════════
# 公开 API
# ═══════════════════════════════════════════════════════════
def create_log_panel(self) -> QWidget:
"""创建并返回日志+进度面板的组装 Widget。
Returns:
QWidget: 包含日志区(QGroupBox)和进度条(QGroupBox)的垂直布局容器
"""
from src.gui.styles import ModernStylesheet
container = QWidget()
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(10)
# ── 日志区 ──
log_group = QGroupBox("执行日志")
log_group.setStyleSheet(f"""
QGroupBox {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
border: 1px solid {ModernStylesheet.COLORS['border_light']};
border-radius: 5px; margin-top: 8px; padding-top: 15px;
padding-left: 9px; padding-right: 9px; padding-bottom: 9px;
}}
QGroupBox::title {{
subcontrol-origin: margin; subcontrol-position: top left;
padding: 0 5px; font-weight: bold;
color: {ModernStylesheet.COLORS['text_primary']};
}}
""")
log_layout = QVBoxLayout()
log_layout.setContentsMargins(5, 5, 5, 5)
self._log_text = QTextEdit()
self._log_text.setReadOnly(True)
self._log_text.setMaximumHeight(200)
self._log_text.setStyleSheet(f"""
QTextEdit {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
color: {ModernStylesheet.COLORS['text_primary']};
border: 1px solid {ModernStylesheet.COLORS['border']};
border-radius: 4px; padding: 5px;
font-family: 'Courier New', monospace; font-size: 10px;
}}
""")
log_layout.addWidget(self._log_text)
clear_btn = QPushButton("清空日志")
clear_btn.setMaximumWidth(100)
clear_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('normal'))
clear_btn.clicked.connect(self.clear_log)
btn_row = QHBoxLayout()
btn_row.addWidget(clear_btn)
btn_row.addStretch()
log_layout.addLayout(btn_row)
log_group.setLayout(log_layout)
layout.addWidget(log_group, 1)
# ── 进度条 ──
progress_group = QGroupBox("执行进度")
progress_group.setStyleSheet(f"""
QGroupBox {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
border: 1px solid {ModernStylesheet.COLORS['border_light']};
border-radius: 5px; margin-top: 8px; padding-top: 10px;
padding-left: 9px; padding-right: 9px; padding-bottom: 9px;
}}
QGroupBox::title {{
subcontrol-origin: margin; subcontrol-position: top left;
padding: 0 5px; font-weight: bold;
color: {ModernStylesheet.COLORS['text_primary']};
}}
""")
progress_layout = QVBoxLayout()
progress_layout.setContentsMargins(5, 5, 5, 5)
self._progress_bar = QProgressBar()
self._progress_bar.setValue(0)
self._progress_bar.setStyleSheet(f"""
QProgressBar {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
border: 1px solid {ModernStylesheet.COLORS['border']};
border-radius: 4px; padding: 2px; text-align: center; height: 20px;
}}
QProgressBar::chunk {{
background-color: {ModernStylesheet.COLORS['success']}; border-radius: 3px;
}}
""")
progress_layout.addWidget(self._progress_bar)
progress_group.setLayout(progress_layout)
layout.addWidget(progress_group, 0)
container.setLayout(layout)
return container
def clear_log(self):
"""清空日志区。"""
if self._log_text is not None:
self._log_text.clear()
@property
def progress_bar(self) -> QProgressBar:
return self._progress_bar
@property
def log_text(self) -> QTextEdit:
return self._log_text
# ═══════════════════════════════════════════════════════════
# EventBus 订阅回调
# ═══════════════════════════════════════════════════════════
def _on_log_message(self, data: dict):
"""LogMessage 事件回调:写入日志区。"""
if self._log_text is None:
return
message = data.get('message', '')
level = data.get('level', 'info')
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
color_map = {'error': 'red', 'warning': 'orange'}
color = color_map.get(level, 'black')
formatted = f'<span style="color: {color};">[{timestamp}] {message}</span>'
self._log_text.append(formatted)
cursor = self._log_text.textCursor()
cursor.movePosition(QTextCursor.End)
self._log_text.setTextCursor(cursor)
def _on_progress_update(self, data: dict):
"""ProgressUpdate 事件回调:更新进度条。"""
if self._progress_bar is None:
return
percentage = data.get('percentage', 0)
self._progress_bar.setValue(percentage)