refactor : 代码整体框架重构
This commit is contained in:
parent
dcd3c54eb0
commit
c8794b8d47
@ -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(" 无法解析此行的格式")
|
||||
|
||||
|
||||
88
src/event_handler.py
Normal file
88
src/event_handler.py
Normal file
@ -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
|
||||
74
src/file_processor.py
Normal file
74
src/file_processor.py
Normal file
@ -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)
|
||||
320
src/main.py
320
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()
|
||||
# 进入应用主循环
|
||||
|
||||
48
src/main_window.py
Normal file
48
src/main_window.py
Normal file
@ -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"
|
||||
}
|
||||
67
src/ui_initializer.py
Normal file
67
src/ui_initializer.py
Normal file
@ -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
|
||||
)
|
||||
35
src/utils.py
35
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__)
|
||||
|
||||
124
src/view_renderer.py
Normal file
124
src/view_renderer.py
Normal file
@ -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])
|
||||
Loading…
Reference in New Issue
Block a user