Files
feiqiu-ETL/etl_billiards/gui/main_window.py
2026-01-27 22:45:50 +08:00

398 lines
14 KiB
Python

# -*- coding: utf-8 -*-
"""主窗口"""
from PySide6.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QStackedWidget, QListWidget, QListWidgetItem,
QStatusBar, QLabel, QMessageBox, QSplitter
)
from PySide6.QtCore import Qt, QSize, Signal
from PySide6.QtGui import QIcon, QAction
from .widgets.task_panel import TaskPanel
from .widgets.task_manager import TaskManager
from .widgets.env_editor import EnvEditor
from .widgets.log_viewer import LogViewer
from .widgets.db_viewer import DBViewer
from .widgets.status_panel import StatusPanel
from .resources import load_stylesheet
class MainWindow(QMainWindow):
"""ETL GUI 主窗口"""
# 信号
status_message = Signal(str, int) # message, timeout_ms
def __init__(self):
super().__init__()
self.setWindowTitle("飞球 ETL 管理系统")
self.setMinimumSize(1200, 800)
self.resize(1400, 900)
# 应用样式
self.setStyleSheet(load_stylesheet())
# 初始化 UI
self._init_ui()
self._init_menu()
self._init_status_bar()
self._connect_signals()
# 默认选中第一项
self.nav_list.setCurrentRow(0)
# 首次显示标记
self._first_show = True
def showEvent(self, event):
"""窗口显示事件"""
super().showEvent(event)
if self._first_show:
self._first_show = False
# 延迟检查配置,让窗口先显示
from PySide6.QtCore import QTimer
QTimer.singleShot(100, self._check_config_on_startup)
def _init_ui(self):
"""初始化界面"""
# 中央部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 主布局
main_layout = QHBoxLayout(central_widget)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# 创建分割器
splitter = QSplitter(Qt.Horizontal)
main_layout.addWidget(splitter)
# 左侧导航栏
nav_widget = self._create_nav_widget()
splitter.addWidget(nav_widget)
# 右侧内容区
self.content_stack = QStackedWidget()
splitter.addWidget(self.content_stack)
# 设置分割比例
splitter.setSizes([200, 1200])
splitter.setStretchFactor(0, 0)
splitter.setStretchFactor(1, 1)
# 创建各个面板
self._create_panels()
def _create_nav_widget(self) -> QWidget:
"""创建导航侧边栏"""
nav_widget = QWidget()
nav_widget.setMaximumWidth(220)
nav_widget.setMinimumWidth(180)
layout = QVBoxLayout(nav_widget)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# 标题
title_label = QLabel(" ETL 控制台")
title_label.setProperty("heading", True)
title_label.setFixedHeight(60)
title_label.setAlignment(Qt.AlignVCenter)
layout.addWidget(title_label)
# 导航列表
self.nav_list = QListWidget()
self.nav_list.setObjectName("navList")
layout.addWidget(self.nav_list)
# 添加导航项
nav_items = [
("任务配置", "配置并执行 ETL 任务"),
("任务管理", "管理任务队列和历史记录"),
("环境配置", "编辑 .env 配置文件"),
("数据库", "查看数据库和执行查询"),
("ETL 状态", "查看 ETL 运行状态"),
("日志", "查看执行日志"),
]
for name, tooltip in nav_items:
item = QListWidgetItem(name)
item.setToolTip(tooltip)
item.setSizeHint(QSize(0, 44))
self.nav_list.addItem(item)
return nav_widget
def _create_panels(self):
"""创建各个功能面板"""
# 任务配置面板
self.task_panel = TaskPanel()
self.content_stack.addWidget(self.task_panel)
# 任务管理面板
self.task_manager = TaskManager()
self.content_stack.addWidget(self.task_manager)
# 环境配置面板
self.env_editor = EnvEditor()
self.content_stack.addWidget(self.env_editor)
# 数据库查看器
self.db_viewer = DBViewer()
self.content_stack.addWidget(self.db_viewer)
# ETL 状态面板
self.status_panel = StatusPanel()
self.content_stack.addWidget(self.status_panel)
# 日志面板
self.log_viewer = LogViewer()
self.content_stack.addWidget(self.log_viewer)
def _init_menu(self):
"""初始化菜单栏"""
menubar = self.menuBar()
# 文件菜单
file_menu = menubar.addMenu("文件(&F)")
refresh_action = QAction("刷新配置(&R)", self)
refresh_action.setShortcut("Ctrl+R")
refresh_action.triggered.connect(self._refresh_config)
file_menu.addAction(refresh_action)
settings_action = QAction("设置(&S)...", self)
settings_action.setShortcut("Ctrl+,")
settings_action.triggered.connect(self._show_settings)
file_menu.addAction(settings_action)
file_menu.addSeparator()
exit_action = QAction("退出(&X)", self)
exit_action.setShortcut("Ctrl+Q")
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# 视图菜单
view_menu = menubar.addMenu("视图(&V)")
task_config_action = QAction("任务配置(&T)", self)
task_config_action.setShortcut("Ctrl+1")
task_config_action.triggered.connect(lambda: self._switch_panel(0))
view_menu.addAction(task_config_action)
task_manager_action = QAction("任务管理(&M)", self)
task_manager_action.setShortcut("Ctrl+2")
task_manager_action.triggered.connect(lambda: self._switch_panel(1))
view_menu.addAction(task_manager_action)
env_action = QAction("环境配置(&E)", self)
env_action.setShortcut("Ctrl+3")
env_action.triggered.connect(lambda: self._switch_panel(2))
view_menu.addAction(env_action)
db_action = QAction("数据库(&D)", self)
db_action.setShortcut("Ctrl+4")
db_action.triggered.connect(lambda: self._switch_panel(3))
view_menu.addAction(db_action)
status_action = QAction("ETL 状态(&S)", self)
status_action.setShortcut("Ctrl+5")
status_action.triggered.connect(lambda: self._switch_panel(4))
view_menu.addAction(status_action)
log_action = QAction("日志(&L)", self)
log_action.setShortcut("Ctrl+6")
log_action.triggered.connect(lambda: self._switch_panel(5))
view_menu.addAction(log_action)
# 帮助菜单
help_menu = menubar.addMenu("帮助(&H)")
about_action = QAction("关于(&A)", self)
about_action.triggered.connect(self._show_about)
help_menu.addAction(about_action)
def _init_status_bar(self):
"""初始化状态栏"""
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
# 连接状态
self.conn_status_label = QLabel("数据库: 未连接")
self.conn_status_label.setProperty("status", "warning")
self.status_bar.addPermanentWidget(self.conn_status_label)
# 任务状态
self.task_status_label = QLabel("任务: 空闲")
self.status_bar.addPermanentWidget(self.task_status_label)
# 默认消息
self.status_bar.showMessage("就绪", 3000)
def _connect_signals(self):
"""连接信号"""
# 导航切换
self.nav_list.currentRowChanged.connect(self._on_nav_changed)
# 任务面板信号
self.task_panel.task_started.connect(self._on_task_started)
self.task_panel.task_finished.connect(self._on_task_finished)
self.task_panel.log_message.connect(self.log_viewer.append_log)
self.task_panel.add_to_queue.connect(self._on_add_to_queue)
self.task_panel.create_schedule.connect(self._on_create_schedule)
# 任务管理器信号
self.task_manager.task_started.connect(self._on_task_started)
self.task_manager.task_finished.connect(self._on_task_finished)
self.task_manager.log_message.connect(self.log_viewer.append_log)
# 数据库连接状态
self.db_viewer.connection_changed.connect(self._on_db_connection_changed)
# 状态消息
self.status_message.connect(self._show_status_message)
def _on_nav_changed(self, index: int):
"""导航项切换"""
self.content_stack.setCurrentIndex(index)
def _switch_panel(self, index: int):
"""切换到指定面板"""
self.nav_list.setCurrentRow(index)
def _refresh_config(self):
"""刷新配置"""
self.env_editor.load_config()
self.task_panel.refresh_tasks()
self.status_bar.showMessage("配置已刷新", 3000)
def _on_task_started(self, task_info: str):
"""任务开始时"""
self.task_status_label.setText(f"任务: 执行中 - {task_info}")
self.task_status_label.setProperty("status", "info")
self.task_status_label.style().unpolish(self.task_status_label)
self.task_status_label.style().polish(self.task_status_label)
def _on_task_finished(self, success: bool, message: str):
"""任务完成时"""
if success:
self.task_status_label.setText("任务: 完成")
self.task_status_label.setProperty("status", "success")
else:
self.task_status_label.setText("任务: 失败")
self.task_status_label.setProperty("status", "error")
self.task_status_label.style().unpolish(self.task_status_label)
self.task_status_label.style().polish(self.task_status_label)
self.status_bar.showMessage(message, 5000)
def _on_db_connection_changed(self, connected: bool, message: str):
"""数据库连接状态变化"""
if connected:
self.conn_status_label.setText("数据库: 已连接")
self.conn_status_label.setProperty("status", "success")
else:
self.conn_status_label.setText("数据库: 未连接")
self.conn_status_label.setProperty("status", "warning")
self.conn_status_label.style().unpolish(self.conn_status_label)
self.conn_status_label.style().polish(self.conn_status_label)
if message:
self.status_bar.showMessage(message, 3000)
def _show_status_message(self, message: str, timeout: int):
"""显示状态栏消息"""
self.status_bar.showMessage(message, timeout)
def _on_add_to_queue(self, config):
"""添加任务到队列"""
task_id = self.task_manager.add_task(config)
self.status_bar.showMessage(f"任务已添加到队列 (ID: {task_id})", 3000)
def _on_create_schedule(self, name: str, task_codes: list, task_config: dict):
"""创建调度任务"""
# 打开调度编辑对话框
from .widgets.task_manager import ScheduleEditDialog
from .models.schedule_model import ScheduledTask, ScheduleConfig
import uuid
# 创建一个预填充的调度任务
task = ScheduledTask(
id=str(uuid.uuid4())[:8],
name=name,
task_codes=task_codes,
schedule=ScheduleConfig(),
task_config=task_config,
)
# 打开编辑对话框
dialog = ScheduleEditDialog(task=task, parent=self)
if dialog.exec():
updated_task = dialog.get_task()
if updated_task:
self.task_manager.schedule_store.add_task(updated_task)
self.task_manager._refresh_schedule_table()
self.status_bar.showMessage(f"调度任务已创建: {updated_task.name}", 3000)
# 切换到任务管理面板的调度选项卡
self._switch_panel(1)
def _show_settings(self):
"""显示设置对话框"""
from .widgets.settings_dialog import SettingsDialog
dialog = SettingsDialog(self)
if dialog.exec():
# 重新加载配置
self._refresh_config()
self.status_bar.showMessage("设置已保存", 3000)
def _check_config_on_startup(self):
"""启动时检查配置"""
from .utils.app_settings import app_settings
if not app_settings.is_configured():
QMessageBox.information(
self,
"首次配置",
"欢迎使用 ETL 管理系统!\n\n"
"请先配置 ETL 项目路径,否则无法执行任务。\n\n"
"点击 文件 → 设置 进行配置。"
)
def _show_about(self):
"""显示关于对话框"""
QMessageBox.about(
self,
"关于 飞球 ETL 管理系统",
"<h3>飞球 ETL 管理系统</h3>"
"<p>版本: 1.0.0</p>"
"<p>一个用于管理台球场门店数据 ETL 的图形化工具。</p>"
"<p>功能包括:</p>"
"<ul>"
"<li>任务配置与执行</li>"
"<li>环境变量管理</li>"
"<li>数据库查询</li>"
"<li>ETL 状态监控</li>"
"</ul>"
)
def closeEvent(self, event):
"""关闭事件"""
# 检查是否有正在运行的任务
if hasattr(self, 'task_panel') and self.task_panel.is_running():
reply = QMessageBox.question(
self,
"确认退出",
"当前有任务正在执行,确定要退出吗?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
if reply == QMessageBox.No:
event.ignore()
return
# 关闭数据库连接
if hasattr(self, 'db_viewer'):
self.db_viewer.close_connection()
event.accept()