From c8794b8d472a850b2819d5024d604bb4b1371e3f Mon Sep 17 00:00:00 2001 From: "marcinlei@outlook.com" <1750818448@qq.com> Date: Sun, 24 Aug 2025 02:17:32 +0800 Subject: [PATCH] =?UTF-8?q?refactor=20:=20=E4=BB=A3=E7=A0=81=E6=95=B4?= =?UTF-8?q?=E4=BD=93=E6=A1=86=E6=9E=B6=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ZiJin_parse_idl.py | 66 +-------- src/event_handler.py | 88 ++++++++++++ src/file_processor.py | 74 ++++++++++ src/main.py | 320 +---------------------------------------- src/main_window.py | 48 +++++++ src/ui_initializer.py | 67 +++++++++ src/utils.py | 35 +++++ src/view_renderer.py | 124 ++++++++++++++++ 8 files changed, 445 insertions(+), 377 deletions(-) create mode 100644 src/event_handler.py create mode 100644 src/file_processor.py create mode 100644 src/main_window.py create mode 100644 src/ui_initializer.py create mode 100644 src/view_renderer.py diff --git a/src/ZiJin_parse_idl.py b/src/ZiJin_parse_idl.py index 133505b..4590233 100644 --- a/src/ZiJin_parse_idl.py +++ b/src/ZiJin_parse_idl.py @@ -41,64 +41,6 @@ def is_alertjson_file_empty(): except Exception as e: return None -# def parse_log_line(log_line): -# """ -# 解析特定格式的日志行,提取关键信息 - -# 参数: -# log_line (str): 要解析的日志行字符串 - -# 返回: -# dict: 包含提取的信息的字典,若解析失败则返回None -# """ -# try: -# # 处理开头的 <162> 部分,先移除这部分内容 -# # 找到第一个空格,跳过 <162> 部分 -# first_space_index = log_line.find(' ') -# if first_space_index == -1: -# return None -# content_after_prefix = log_line[first_space_index:].strip() -# # 从剩余内容中提取第一个时间戳(到下一个空格) -# timestamp_end = content_after_prefix.find(' ') -# if timestamp_end == -1: -# return None -# timestamp = content_after_prefix[:timestamp_end].strip() - -# # 查找包含|分隔符的部分 -# pipe_start = log_line.find('|') -# if pipe_start == -1: -# return None -# pipe_content = log_line[pipe_start:] - -# # 按|分割内容 -# parts = [part.strip() for part in pipe_content.split('|') if part.strip()] - -# # 检查是否有足够的部分 -# if len(parts) < 5: -# return None - -# # 提取各部分信息 -# component_type = parts[1] # 部件类型 -# event_type = parts[2] # 事件类型 -# event_level = parts[3] # 事件等级 -# event_code = parts[4] # 事件代码 - -# # 事件描述是剩余部分的组合 -# event_description = '|'.join(parts[5:]) if len(parts) > 5 else "" - -# return { -# 'timestamp': timestamp, -# 'component_type': component_type, -# 'event_type': event_type, -# 'event_level': event_level, -# 'event_code': event_code, -# 'description': event_description -# } - -# except Exception as e: -# print(f"解析日志行时出错: {str(e)}") -# return None - def process_log_with_keywords(file_path, keywords, json_output_path=cache_sys_error): """ 结合关键字匹配、日志解析并将结果保存为JSON文件的完整处理函数 @@ -120,7 +62,7 @@ def process_log_with_keywords(file_path, keywords, json_output_path=cache_sys_er for line_number, line in enumerate(file, 1): line_lower = line.lower() if any(keyword in line_lower for keyword in lower_keywords): - print(f"\n第{line_number}行: {line.strip()}") + # print(f"\n第{line_number}行: {line.strip()}") match_count += 1 # 解析日志行 @@ -129,9 +71,9 @@ def process_log_with_keywords(file_path, keywords, json_output_path=cache_sys_er # 添加行号信息,方便追溯 parsed['line_number'] = line_number parsed_results.append(parsed) - print("提取的信息:") - for key, value in parsed.items(): - print(f" {key}: {value}") + # print("提取的信息:") + # for key, value in parsed.items(): + # print(f" {key}: {value}") else: print(" 无法解析此行的格式") diff --git a/src/event_handler.py b/src/event_handler.py new file mode 100644 index 0000000..5e584eb --- /dev/null +++ b/src/event_handler.py @@ -0,0 +1,88 @@ +import os +from PyQt5.QtWidgets import QFileDialog +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QDragEnterEvent, QDropEvent + +class EventHandler: + def __init__(self, main_window): + self.main_window = main_window # 持有主窗口引用 + self.file_processor = main_window.file_processor # 关联文件处理模块 + self.view_renderer = main_window.view_renderer # 关联视图渲染模块 + + def bind_events(self): + """绑定所有界面事件""" + # 菜单项事件 + self.main_window.actionUpload_log.triggered.connect(self.upload_file) + # 下拉框变化事件 + self.main_window.comboBox.currentTextChanged.connect(self.on_combo_changed) + # 图形视图滚轮缩放事件 + self.main_window.graphicsView.wheelEvent = self.zoom_with_mouse_wheel + # 窗口 resize 事件 + self.main_window.resizeEvent = self.resize_event + + def upload_file(self): + """打开文件选择对话框""" + file_path, _ = QFileDialog.getOpenFileName( + self.main_window, + "选择文件", + os.getcwd(), + "所有文件 (*);;日志压缩文件 (*.gz)" + ) + if file_path: + self.file_processor.process_uploaded_file(file_path) + + def on_combo_changed(self, selected_text): + """下拉框选项变化:切换图片""" + # 获取选中项对应的路径关键词 + key = self.main_window.combo_box_text_dict.get(selected_text) + if key: + self.view_renderer.display_pic(self.main_window.file_processor.get_sensorhistory_path(key)) + + def dragEnterEvent(self, event: QDragEnterEvent): + """拖入事件:判断是否为文件""" + if event.mimeData().hasUrls(): + event.acceptProposedAction() + else: + event.ignore() + + def dropEvent(self, event: QDropEvent): + """放下事件:处理拖放的文件""" + file_paths = [url.toLocalFile() for url in event.mimeData().urls()] + for file_path in file_paths: + self.file_processor.process_uploaded_file(file_path) + + def zoom_with_mouse_wheel(self, event): + """鼠标滚轮缩放图片""" + if self.main_window.pixmap_item: + # 缩放中心:鼠标当前位置 + mouse_pos = event.pos() + scene_pos = self.main_window.graphicsView.mapToScene(mouse_pos) + # 缩放因子 + scale_factor = 1.1 if event.angleDelta().y() > 0 else 0.9 + # 执行缩放 + self.main_window.graphicsView.scale(scale_factor, scale_factor) + # 保持缩放后鼠标位置不变 + new_mouse_pos = self.main_window.graphicsView.mapFromScene(scene_pos) + delta = new_mouse_pos - mouse_pos + self.main_window.graphicsView.horizontalScrollBar().setValue( + self.main_window.graphicsView.horizontalScrollBar().value() - delta.x() + ) + self.main_window.graphicsView.verticalScrollBar().setValue( + self.main_window.graphicsView.verticalScrollBar().value() - delta.y() + ) + else: + # 无图片时调用父类默认行为 + super(type(self.main_window.graphicsView), self.main_window.graphicsView).wheelEvent(event) + + def resize_event(self, event): + """窗口 resize 时自适应图片""" + if self.main_window.pixmap_item and not self.main_window.graphicsView.transform().isIdentity(): + self.main_window.graphicsView.fitInView(self.main_window.scene.sceneRect(), Qt.KeepAspectRatio) + # 调用父类默认 resize 行为 + super(type(self.main_window), self.main_window).resizeEvent(event) + + # 绑定拖放事件到主窗口 + def patch_drag_events(self): + """将拖放事件绑定到主窗口(需在主窗口初始化时调用)""" + self.main_window.dragEnterEvent = self.dragEnterEvent + self.main_window.dropEvent = self.dropEvent \ No newline at end of file diff --git a/src/file_processor.py b/src/file_processor.py new file mode 100644 index 0000000..b99eb52 --- /dev/null +++ b/src/file_processor.py @@ -0,0 +1,74 @@ +import os +import service +from utils import show_error_message, show_critical_message + +class FileProcessor: + def __init__(self, main_window): + self.main_window = main_window # 持有主窗口引用 + self.parse_status = main_window.parse_status # 服务状态 + self.view_renderer = main_window.view_renderer # 关联视图渲染模块 + + def is_valid_file_format(self, file_path): + """校验文件是否为 .tar.gz 格式""" + _, ext = os.path.splitext(file_path) + if ext.lower() == '.gz': + base, ext2 = os.path.splitext(os.path.splitext(file_path)[0]) + return ext2.lower() == '.tar' + return False + + def get_sensorhistory_path(self, key): + """调用服务层获取传感器历史图片路径""" + return service.get_sensorhistory_path(key) + + def process_uploaded_file(self, file_path): + """核心:处理上传的文件(校验→解析→传递结果)""" + # 1. 格式校验 + if not self.is_valid_file_format(file_path): + show_error_message(self.main_window, file_path) + return + + try: + # 2. 提取文件信息并显示到控制台 + file_name = os.path.basename(file_path) + file_size = os.path.getsize(file_path) / 1024 # 转换为 KB + info = ( + f"已上传文件:\n" + f"文件名:{file_name}\n" + f"路径:{file_path}\n" + f"大小:{file_size:.2f} KB\n" + f"------------------------------------------------------"+ "\n" + ) + self.main_window.textBrowser_console.insertPlainText(info) + + # 3. 调用服务层解析文件 + service.send_log_to_cache(file_path) + service.start_diagnose(self.parse_status) + + # 4. 根据解析状态更新视图 + self._update_view_after_parse() + + self.main_window.textBrowser_console.insertPlainText("完成文件解析\n") + + except Exception as e: + show_critical_message(self.main_window, "处理失败", f"文件处理出错:{str(e)}") + + def _update_view_after_parse(self): + """解析完成后更新各视图组件""" + # 更新基础信息文本框 + if self.parse_status.baseinfo_status: + baseinfo_str = service.get_baseinfo_str() + self.main_window.textBrowser_info.insertPlainText(baseinfo_str) + + # 更新传感器图片 + if self.parse_status.sensorhistory_status: + self.view_renderer.display_pic(self.get_sensorhistory_path("all")) + + # 更新告警表格 + if self.parse_status.parseidl_status and not service.is_idl_alert_empty(): + alert_json = service.get_idl_alert_json() + self.view_renderer.fill_tableView_alert(alert_json) + + # 更新时间线 + if self.parse_status.eventline_status: + event_json = service.get_timeline_event_json() + self.main_window.timeline_content.load_events_from_json(event_json) \ No newline at end of file diff --git a/src/main.py b/src/main.py index 1765f37..6d8da70 100644 --- a/src/main.py +++ b/src/main.py @@ -1,324 +1,14 @@ import sys -import os -from PyQt5.QtWidgets import (QApplication, QMainWindow, QFileDialog, QTextBrowser, QVBoxLayout, - QMessageBox, QTextEdit, QGraphicsScene, QGraphicsPixmapItem) -from PyQt5.QtCore import Qt, QMimeData -from PyQt5.QtGui import QPixmap -from PyQt5.QtGui import QPainter # 单独导入QPainter用于抗锯齿设置 -from PyQt5.QtGui import QStandardItemModel, QStandardItem, QDragEnterEvent, QDropEvent -from MainWindow_ui import Ui_MainWindow # 导入转换后的UI类 import service -import timelineEvent - -class MainWindow(QMainWindow, Ui_MainWindow): - def __init__(self): - super().__init__() # 初始化父类 - self.setupUi(self) # 调用UI类的方法初始化界面 - self.init_events() # 绑定事件(自定义方法) - self.parseStatus = service.ServiceStatus() - self.init_graphic_views() - self.init_textbrowser_style() - self.init_tableView_alert_model() - self.initUI() - self.reInitTimeLineTab() - - self.comboBoxTextDict = { - "ALL_Temp": "all", - "CPU_Temp": "cpu", - "FPGA_Temp": "fpga", - "DIMM_Temp": "dimm", - "Inlet_CPU_Temp": "inlet_cpu", - "Inlet_FPGA_Temp": "inlet_fpga", - "M2_Temp": "m2", - "Outlet_CPU_Temp": "outlet_cpu", - "Outlet_FPGA_Temp": "outlet_fpga", - "VR_CPU_Temp": "vr_cpu", - "VR_FPGA_Temp": "vr_fpga" - } - - def initUI(self): - # 允许窗口接收拖放事件 - self.setAcceptDrops(True) - - def reInitTimeLineTab(self): - # 1. 清除目标标签页的现有布局 - if hasattr(self.tab_timeline_event, 'layout'): - # 移除现有布局中的所有组件 - layout = self.tab_timeline_event.layout() - if layout: - while layout.count(): - item = layout.takeAt(0) - widget = item.widget() - if widget: - widget.deleteLater() - - # 2. 创建时间线内容组件实例 - self.timeline_content = timelineEvent.TimelineTabContent() - - # 3. 为标签页创建新布局并添加时间线组件 - layout = QVBoxLayout(self.tab_timeline_event) - layout.setContentsMargins(0, 0, 0, 0) # 可选:去除边距 - layout.addWidget(self.timeline_content) - - def dragEnterEvent(self, event: QDragEnterEvent): - """拖入事件:判断拖入的是否是文件""" - if event.mimeData().hasUrls(): - # 只接受文件拖入 - event.acceptProposedAction() - else: - event.ignore() - - def dropEvent(self, event: QDropEvent): - """放下事件:处理拖放的文件""" - # 获取拖放的文件路径列表 - file_paths = [url.toLocalFile() for url in event.mimeData().urls()] - - # 调用你的文件上传函数 - for file_path in file_paths: - self.process_uploaded_file(file_path) # 这是你已经写好的上传函数 - - def init_textbrowser_style(self): - # 设置样式表(全局文本样式) - self.textBrowser_info.setStyleSheet(""" - QTextBrowser { - font-family: 'SimHei'; - font-size: 24px; - color: #2c3e50; - background-color: #f8f9fa; - border: 1px solid #ddd; - border-radius: 4px; - padding: 8px; - } - """) - - def init_events(self): - """绑定界面元素的事件(如按钮点击、输入框变化等)""" - # 示例:给按钮添加点击事件 - # self.pushButton.clicked.connect(self.on_button_click) # pushButton是UI中定义的按钮对象名 - - # 绑定上传菜单项事件 - self.actionUpload_log.triggered.connect(self.upload_file) - self.comboBox.currentTextChanged.connect(self.on_combo_changed) - - def init_graphic_views(self): - # 初始化图形场景和视图 - self.scene = QGraphicsScene(self) # 创建场景 - self.graphicsView.setScene(self.scene) # 将场景绑定到视图 - self.graphicsView.setRenderHint(QPainter.Antialiasing) # 抗锯齿 - self.graphicsView.setRenderHint(QPainter.SmoothPixmapTransform) # 平滑缩放 - - # 存储当前显示的图片项 - self.pixmap_item = None - self.current_image_path = None - - def init_tableView_alert_model(self): - # 创建一个4行3列的模型 - self.model_alert = QStandardItemModel(4, 5) - - # 设置表头 - self.model_alert.setHorizontalHeaderLabels(["时间", "部件", "等级", "方向", "描述"]) - - # 将模型绑定到QTableView - self.tableView_alert.setModel(self.model_alert) - - # 可选:调整列宽自适应内容 - self.tableView_alert.horizontalHeader().setSectionResizeMode( - self.tableView_alert.horizontalHeader().ResizeToContents - ) - - def show_error_message(self, file_path): - """显示格式错误提示弹窗""" - QMessageBox.critical( - self, - "文件格式错误", - f"不支持的文件格式:\n{os.path.basename(file_path)}\n\n请上传.tar.gz格式的压缩包。" - ) - - def is_valid_file_format(self, file_path): - """检查文件是否为.tar.gz格式""" - # 获取文件后缀 - _, ext = os.path.splitext(file_path) - - # 先检查是否有.gz后缀 - if ext.lower() == '.gz': - # 再检查去掉.gz后的后缀是否为.tar - base, ext2 = os.path.splitext(os.path.splitext(file_path)[0]) - return ext2.lower() == '.tar' - - return False - - def upload_file(self): - """打开文件选择对话框并处理选中的文件""" - # 打开文件选择对话框 - # 参数说明: - # - self:父窗口 - # - "选择文件":对话框标题 - # - os.getcwd():默认打开路径(当前工作目录) - # - "所有文件 (*);;文本文件 (*.txt);;图片文件 (*.png *.jpg)":文件筛选器 - file_path, file_type = QFileDialog.getOpenFileName( - self, - "选择文件", - os.getcwd(), - "所有文件 (*);;日志压缩文件 (*.gz)" - ) - - # 判断用户是否选择了文件(取消选择时file_path为空) - if file_path: - self.process_uploaded_file(file_path) - # else: - # self.text_edit.append("已取消文件选择") - - def process_uploaded_file(self, file_path): - """处理上传的文件""" - if not self.is_valid_file_format(file_path): - self.show_error_message(file_path) - return - - try: - # 获取文件信息 - file_name = os.path.basename(file_path) - file_size = os.path.getsize(file_path) - file_size_kb = file_size / 1024 # 转换为KB - - # 显示文件信息 - info = f"已上传文件:\n" - info += f"文件名:{file_name}\n" - info += f"路径:{file_path}\n" - info += f"大小:{file_size_kb:.2f} KB\n" - info += "-" * 50 + "\n" - - self.textBrowser_console.insertPlainText(info) - - # 这里可以添加实际的文件处理逻辑: - service.send_log_to_cache(file_path) - service.start_diagnose(self.parseStatus) - - if self.parseStatus.baseinfo_status: - baseinfo_str = service.get_baseinfo_str() - self.textBrowser_info.insertPlainText(baseinfo_str) - - if self.parseStatus.sensorhistory_status: - self.display_pic(service.get_sensorhistory_path("all")) - - if self.parseStatus.parseidl_status: - if not service.is_idl_alert_empty(): - alert_json = service.get_idl_alert_json() - self.fill_tableView_alert(alert_json) - - if self.parseStatus.eventline_status: - event_json = service.get_timeline_event_json() - self.timeline_content.load_events_from_json(event_json) - - self.textBrowser_console.insertPlainText("完成文件解析\n") - - except Exception as e: - QMessageBox.critical(self, "处理失败", f"文件处理出错:{str(e)}") - - def fill_tableView_alert(self, json_data): - """从JSON数据更新表格内容""" - # 清空现有数据 - self.model_alert.clear() - - # 根据JSON数据结构设置表头和内容 - if isinstance(json_data, list): - # 处理列表类型的JSON(如多条记录) - if json_data and isinstance(json_data[0], dict): - # 使用第一条记录的键作为表头 - headers = json_data[0].keys() - self.model_alert.setHorizontalHeaderLabels(headers) - - # 添加所有行数据 - for item in json_data: - row_items = [] - for key in headers: - # 将值转换为字符串显示 - value = str(item.get(key, "")) - row_items.append(QStandardItem(value)) - self.model_alert.appendRow(row_items) - elif isinstance(json_data, dict): - # 处理字典类型的JSON(键值对) - self.model_alert.setHorizontalHeaderLabels(["键", "值"]) - for key, value in json_data.items(): - key_item = QStandardItem(str(key)) - value_item = QStandardItem(str(value)) - # 设置单元格不可编辑 - key_item.setEditable(False) - value_item.setEditable(False) - self.model_alert.appendRow([key_item, value_item]) - else: - # 处理简单类型的JSON - self.model_alert.setHorizontalHeaderLabels(["数据"]) - self.model_alert.appendRow([QStandardItem(str(json_data))]) - - # 调整列宽以适应内容 - self.tableView_alert.horizontalHeader().setSectionResizeMode( - self.tableView_alert.horizontalHeader().ResizeToContents - ) - - def on_combo_changed(self, selected_text): - """下拉列表选项变化时切换图片""" - self.display_pic(service.get_sensorhistory_path(self.comboBoxTextDict[selected_text])) - - - def display_pic(self, image_path): - """在QGraphicsView中显示图片""" - try: - # 清空场景中已有的内容 - self.scene.clear() - - # 加载图片并创建图形项 - pixmap = QPixmap(image_path) - if pixmap.isNull(): - raise Exception("无法加载图片文件") - - # 创建图片项并添加到场景 - self.pixmap_item = QGraphicsPixmapItem(pixmap) - self.scene.addItem(self.pixmap_item) - - # 初始显示时让图片居中 - self.graphicsView.fitInView(self.scene.sceneRect(), Qt.KeepAspectRatio) - - except Exception as e: - QMessageBox.critical(self, "错误", f"显示图片失败:{str(e)}") - - def zoom_with_mouse_wheel(self, event): - """鼠标滚轮缩放图片""" - if self.pixmap_item: # 只有存在图片时才允许缩放 - # 获取当前鼠标位置作为缩放中心 - mouse_pos = event.pos() - scene_pos = self.graphicsView.mapToScene(mouse_pos) - - # 缩放因子(滚轮向前放大,向后缩小) - scale_factor = 1.1 if event.angleDelta().y() > 0 else 0.9 - - # 缩放视图 - self.graphicsView.scale(scale_factor, scale_factor) - - # 缩放后将鼠标位置保持在原场景位置(避免缩放时画面跳动) - new_mouse_pos = self.graphicsView.mapFromScene(scene_pos) - delta = new_mouse_pos - mouse_pos - self.graphicsView.horizontalScrollBar().setValue( - self.graphicsView.horizontalScrollBar().value() - delta.x() - ) - self.graphicsView.verticalScrollBar().setValue( - self.graphicsView.verticalScrollBar().value() - delta.y() - ) - else: - # 若无图片,忽略滚轮事件 - super(type(self.graphicsView), self.graphicsView).wheelEvent(event) - - def resizeEvent(self, event): - """窗口大小改变时,自适应调整图片显示""" - if self.pixmap_item and not self.graphicsView.transform().isIdentity(): - # 仅在未手动缩放时自动适应窗口 - self.graphicsView.fitInView(self.scene.sceneRect(), Qt.KeepAspectRatio) - super().resizeEvent(event) +from PyQt5.QtWidgets import QApplication +from main_window import MainWindow # 导入重构后的主窗口类 if __name__ == "__main__": + # 初始化服务缓存 service.app_cache_init() - # 创建应用实例 + # 创建PyQt应用实例 app = QApplication(sys.argv) - # 创建主窗口并显示 + # 初始化并显示主窗口 window = MainWindow() window.show() # 进入应用主循环 diff --git a/src/main_window.py b/src/main_window.py new file mode 100644 index 0000000..4de41a8 --- /dev/null +++ b/src/main_window.py @@ -0,0 +1,48 @@ +from PyQt5.QtWidgets import QMainWindow +from MainWindow_ui import Ui_MainWindow # 原UI类 +from ui_initializer import UIInitializer # UI初始化模块 +from event_handler import EventHandler # 事件处理模块 +from file_processor import FileProcessor # 文件处理模块 +from view_renderer import ViewRenderer # 视图渲染模块 +import service + +class MainWindow(QMainWindow, Ui_MainWindow): + def __init__(self): + super().__init__() + # 1. 初始化UI界面 + self.setupUi(self) + # 2. 初始化服务状态 + self.parse_status = service.ServiceStatus() + # 3. 初始化各功能模块(注入主窗口实例) + self.ui_init = UIInitializer(self) + self.view_renderer = ViewRenderer(self) + self.file_processor = FileProcessor(self) + self.event_handler = EventHandler(self) + # 4. 执行各模块初始化逻辑 + self._init_all_modules() + + def _init_all_modules(self): + """统一执行所有模块的初始化""" + # UI初始化(样式、图形视图、表格模型、时间线) + self.ui_init.init_ui() + self.ui_init.init_graphic_views() + self.ui_init.init_textbrowser_style() + self.ui_init.init_tableView_alert_model() + self.ui_init.re_init_time_line_tab() + # 事件绑定(按钮、拖放、下拉框) + self.event_handler.bind_events() + self.event_handler.patch_drag_events() # 将拖放事件绑定到主窗口 + # 初始化下拉框映射字典(跨模块使用,放在主窗口) + self.combo_box_text_dict = { + "ALL_Temp": "all", + "CPU_Temp": "cpu", + "FPGA_Temp": "fpga", + "DIMM_Temp": "dimm", + "Inlet_CPU_Temp": "inlet_cpu", + "Inlet_FPGA_Temp": "inlet_fpga", + "M2_Temp": "m2", + "Outlet_CPU_Temp": "outlet_cpu", + "Outlet_FPGA_Temp": "outlet_fpga", + "VR_CPU_Temp": "vr_cpu", + "VR_FPGA_Temp": "vr_fpga" + } \ No newline at end of file diff --git a/src/ui_initializer.py b/src/ui_initializer.py new file mode 100644 index 0000000..fbd98d8 --- /dev/null +++ b/src/ui_initializer.py @@ -0,0 +1,67 @@ +from PyQt5.QtWidgets import QVBoxLayout, QGraphicsScene +from PyQt5.QtGui import QPainter, QStandardItemModel +from PyQt5.QtCore import Qt +import timelineEvent + +class UIInitializer: + def __init__(self, main_window): + self.main_window = main_window # 持有主窗口引用 + + def init_ui(self): + """初始化窗口基础设置(如拖放允许)""" + self.main_window.setAcceptDrops(True) + + def re_init_time_line_tab(self): + """重新初始化时间线标签页布局""" + # 清除现有布局 + if hasattr(self.main_window.tab_timeline_event, 'layout'): + layout = self.main_window.tab_timeline_event.layout() + if layout: + while layout.count(): + item = layout.takeAt(0) + widget = item.widget() + if widget: + widget.deleteLater() + # 创建新布局和时间线组件 + self.main_window.timeline_content = timelineEvent.TimelineTabContent() + layout = QVBoxLayout(self.main_window.tab_timeline_event) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.main_window.timeline_content) + + def init_textbrowser_style(self): + """设置文本浏览器样式""" + self.main_window.textBrowser_info.setStyleSheet(""" + QTextBrowser { + font-family: 'SimHei'; + font-size: 24px; + color: #2c3e50; + background-color: #f8f9fa; + border: 1px solid #ddd; + border-radius: 4px; + padding: 8px; + } + """) + + def init_graphic_views(self): + """初始化图形视图(抗锯齿、场景绑定)""" + # 创建图形场景 + self.main_window.scene = QGraphicsScene(self.main_window) + self.main_window.graphicsView.setScene(self.main_window.scene) + # 设置渲染优化 + self.main_window.graphicsView.setRenderHint(QPainter.Antialiasing) + self.main_window.graphicsView.setRenderHint(QPainter.SmoothPixmapTransform) + # 初始化图片相关变量 + self.main_window.pixmap_item = None + self.main_window.current_image_path = None + + def init_tableView_alert_model(self): + """初始化告警表格模型""" + # 创建4行5列模型 + self.main_window.model_alert = QStandardItemModel(4, 5) + self.main_window.model_alert.setHorizontalHeaderLabels(["时间", "部件", "等级", "方向", "描述"]) + # 绑定模型到表格 + self.main_window.tableView_alert.setModel(self.main_window.model_alert) + # 列宽自适应 + self.main_window.tableView_alert.horizontalHeader().setSectionResizeMode( + self.main_window.tableView_alert.horizontalHeader().ResizeToContents + ) \ No newline at end of file diff --git a/src/utils.py b/src/utils.py index 8913c59..5971cb7 100644 --- a/src/utils.py +++ b/src/utils.py @@ -4,6 +4,41 @@ import shutil import stat import re from datetime import datetime +from PyQt5.QtWidgets import QMessageBox + +def show_error_message(parent, file_path): + """显示文件格式错误提示弹窗""" + QMessageBox.critical( + parent, + "文件格式错误", + f"不支持的文件格式:\n{file_path}\n\n请上传.tar.gz格式的压缩包。" + ) + +def show_critical_message(parent, title, message): + """显示通用错误提示弹窗""" + QMessageBox.critical( + parent, + title, + message + ) + +def show_info_message(parent, title, message): + """显示信息提示弹窗""" + QMessageBox.information( + parent, + title, + message + ) + +def show_question_message(parent, title, message): + """显示询问提示弹窗,返回用户选择(Yes/No)""" + return QMessageBox.question( + parent, + title, + message, + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) def get_project_root(): current_file = os.path.abspath(__file__) diff --git a/src/view_renderer.py b/src/view_renderer.py new file mode 100644 index 0000000..dc4e37c --- /dev/null +++ b/src/view_renderer.py @@ -0,0 +1,124 @@ +from PyQt5.QtWidgets import QGraphicsPixmapItem +from PyQt5.QtGui import QPixmap, QStandardItem +from PyQt5.QtCore import Qt +from utils import show_critical_message # 导入工具类的错误弹窗函数 + +class ViewRenderer: + """视图渲染模块:负责图片显示、表格数据填充等UI渲染逻辑""" + def __init__(self, main_window): + # 持有主窗口引用,用于访问UI组件(如graphicsView、model_alert) + self.main_window = main_window + + def display_pic(self, image_path): + """ + 在 QGraphicsView 中显示图片(支持抗锯齿、居中适配) + :param image_path: 图片文件的绝对路径 + """ + try: + # 1. 清空场景中已有的图片(避免叠加显示) + self.main_window.scene.clear() + + # 2. 加载图片文件(QPixmap 是PyQt5中处理图片的核心类) + pixmap = QPixmap(image_path) + # 检查图片是否成功加载(路径错误或格式不支持会返回空 pixmap) + if pixmap.isNull(): + raise ValueError(f"图片文件加载失败,路径:{image_path}(可能是路径错误或格式不支持)") + + # 3. 创建图片项并添加到场景 + self.main_window.pixmap_item = QGraphicsPixmapItem(pixmap) + self.main_window.scene.addItem(self.main_window.pixmap_item) + + # 4. 适配视图大小(保持图片宽高比,避免拉伸变形) + self.main_window.graphicsView.fitInView( + self.main_window.scene.sceneRect(), # 场景范围(即图片大小) + Qt.KeepAspectRatio # 保持宽高比 + ) + + except Exception as e: + # 调用工具类弹窗显示错误信息,提升用户体验 + show_critical_message( + parent=self.main_window, + title="图片显示错误", + message=f"无法在图形视图中显示图片:\n{str(e)}" + ) + + def fill_tableView_alert(self, json_data): + """ + 从JSON数据动态填充告警表格(支持列表/字典两种JSON格式) + :param json_data: 告警数据(JSON格式,可能是列表[多条记录]或字典[键值对]) + """ + try: + # 1. 清空表格现有数据(避免新旧数据叠加) + self.main_window.model_alert.clear() + + # 2. 根据JSON数据类型动态处理表格结构 + if isinstance(json_data, list): + # 情况1:JSON是列表(多条告警记录,每条记录是字典) + self._fill_table_from_list(json_data) + elif isinstance(json_data, dict): + # 情况2:JSON是字典(单条记录或键值对数据) + self._fill_table_from_dict(json_data) + else: + # 情况3:JSON是简单类型(如字符串、数字),直接显示 + self._fill_table_from_simple_type(json_data) + + # 3. 调整列宽(自适应内容,避免文字被截断) + self.main_window.tableView_alert.horizontalHeader().setSectionResizeMode( + self.main_window.tableView_alert.horizontalHeader().ResizeToContents + ) + + except Exception as e: + show_critical_message( + parent=self.main_window, + title="表格填充错误", + message=f"无法从JSON数据更新告警表格:\n{str(e)}" + ) + + def _fill_table_from_list(self, json_list): + """私有方法:处理列表类型的JSON数据(多条告警记录)""" + # 检查列表是否为空,且第一条记录是否为字典(确保数据格式正确) + if not json_list or not isinstance(json_list[0], dict): + raise ValueError("列表类型的JSON数据格式错误,需为[{...}, {...}]结构") + + # 以第一条记录的键作为表格表头(如"时间"、"部件"、"等级") + headers = json_list[0].keys() + self.main_window.model_alert.setHorizontalHeaderLabels(headers) + + # 遍历列表,为每条记录创建一行表格数据 + for record in json_list: + row_items = [] + for key in headers: + # 获取当前字段的值(无值时显示空字符串,避免报错) + field_value = str(record.get(key, "")) + # 创建表格项(QStandardItem 是表格模型的基础单元) + item = QStandardItem(field_value) + # 设置单元格不可编辑(防止用户误修改告警数据) + item.setEditable(False) + row_items.append(item) + # 将行数据添加到表格模型 + self.main_window.model_alert.appendRow(row_items) + + def _fill_table_from_dict(self, json_dict): + """私有方法:处理字典类型的JSON数据(键值对)""" + # 设置表头为"键"和"值"(适配键值对结构) + self.main_window.model_alert.setHorizontalHeaderLabels(["字段名", "字段值"]) + + # 遍历字典,为每个键值对创建一行表格数据 + for key, value in json_dict.items(): + # 键和值都转换为字符串(确保显示格式统一) + key_item = QStandardItem(str(key)) + value_item = QStandardItem(str(value)) + # 设置单元格不可编辑 + key_item.setEditable(False) + value_item.setEditable(False) + # 添加到表格模型 + self.main_window.model_alert.appendRow([key_item, value_item]) + + def _fill_table_from_simple_type(self, data): + """私有方法:处理简单类型的JSON数据(如字符串、数字)""" + # 设置表头为"数据内容"(适配单一数据) + self.main_window.model_alert.setHorizontalHeaderLabels(["告警信息"]) + # 创建表格项并添加到模型 + data_item = QStandardItem(str(data)) + data_item.setEditable(False) + self.main_window.model_alert.appendRow([data_item]) \ No newline at end of file