# -*- 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 管理系统", "
版本: 1.0.0
" "一个用于管理台球场门店数据 ETL 的图形化工具。
" "功能包括:
" "