From 40f4ed22cf88afd28facbed6d2a8c7097629905f Mon Sep 17 00:00:00 2001 From: leimingsheng Date: Mon, 25 Aug 2025 14:44:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=97=A5=E5=BF=97=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E6=97=B6=E5=A2=9E=E5=8A=A0=E8=BF=9B=E5=BA=A6=E6=9D=A1=E5=B1=95?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/MainWindow.ui | 6 +- src/MainWindow_ui.py | 4 +- src/background_task.py | 40 ++++++++++++ src/file_processor.py | 138 ++++++++++++++++++++++++++-------------- src/main.py | 2 +- src/main_window.py | 1 + src/progress_diaglog.py | 46 ++++++++++++++ src/service.py | 54 ++++++++++++---- src/ui_initializer.py | 15 +++++ 9 files changed, 239 insertions(+), 67 deletions(-) create mode 100644 src/background_task.py create mode 100644 src/progress_diaglog.py diff --git a/resource/MainWindow.ui b/resource/MainWindow.ui index 601363d..b85551d 100644 --- a/resource/MainWindow.ui +++ b/resource/MainWindow.ui @@ -33,13 +33,15 @@ + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:18pt; font-weight:600;">通过文件上传一键日志压缩包来开始</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:18pt; font-weight:600;">或者将日志压缩包拖入程序</span></p></body></html> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> diff --git a/src/MainWindow_ui.py b/src/MainWindow_ui.py index 2226d22..e994bc2 100644 --- a/src/MainWindow_ui.py +++ b/src/MainWindow_ui.py @@ -28,6 +28,7 @@ class Ui_MainWindow(object): self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.tab_console) self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.textBrowser_console = QtWidgets.QTextBrowser(self.tab_console) + self.textBrowser_console.setMarkdown("") self.textBrowser_console.setObjectName("textBrowser_console") self.horizontalLayout_4.addWidget(self.textBrowser_console) self.tabWidget.addTab(self.tab_console, "") @@ -102,8 +103,7 @@ class Ui_MainWindow(object): "\n" -"

通过文件上传一键日志压缩包来开始

\n" -"

或者将日志压缩包拖入程序

")) +"


")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_console), _translate("MainWindow", "控制台")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_baseinfo), _translate("MainWindow", "基本信息")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_alert), _translate("MainWindow", "关键告警")) diff --git a/src/background_task.py b/src/background_task.py new file mode 100644 index 0000000..d7887e1 --- /dev/null +++ b/src/background_task.py @@ -0,0 +1,40 @@ +from PyQt5.QtCore import QThread, pyqtSignal +import service + +class DiagnoseThread(QThread): + """后台线程,用于执行耗时的诊断任务""" + # 定义信号:更新进度(值, 消息)、任务完成(是否成功, 消息) + progress_updated = pyqtSignal(int, str) + task_completed = pyqtSignal(bool, str) + + def __init__(self, parse_status, file_path): + super().__init__() + self.parse_status = parse_status + self.file_path = file_path + self.is_running = True + + def run(self): + """线程执行函数:执行耗时任务""" + try: + self.progress_updated.emit(1, "正在初始化解析...") + + # 执行耗时任务 + service.start_diagnose(self.parse_status, self.file_path, progress_callback=self.update_progress) + + # 确保最终进度为100% + self.progress_updated.emit(100, "处理完成") + self.task_completed.emit(True, "日志解析任务已完成") + except Exception as e: + # 出错时也允许关闭对话框 + self.progress_updated.emit(100, f"处理失败:{str(e)}") + self.task_completed.emit(False, f"处理失败:{str(e)}") + + def update_progress(self, value, message): + """接收service层的进度更新""" + if 0 <= value <= 100 and self.is_running: + self.progress_updated.emit(value, message) + + def stop(self): + """停止线程""" + self.is_running = False + self.wait() \ No newline at end of file diff --git a/src/file_processor.py b/src/file_processor.py index abe4f86..31d9a96 100644 --- a/src/file_processor.py +++ b/src/file_processor.py @@ -1,77 +1,119 @@ +import time import os import service -from utils import show_error_message, show_critical_message +from utils import show_error_message, show_critical_message, show_info_message +from progress_diaglog import ProgressDialog +from background_task import DiagnoseThread 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 # 关联视图渲染模块 + self.main_window = main_window + self.parse_status = main_window.parse_status + self.view_renderer = main_window.view_renderer + self.last_processed_path = None + self.last_processed_time = 0 + self.diagnose_thread = None # 保存后台线程引用 + def process_uploaded_file(self, file_path): + # 防重复处理 + current_time = time.time() + if (file_path == self.last_processed_path and + current_time - self.last_processed_time < 1): + print(f"跳过重复处理:{file_path}") + return + + self.last_processed_path = file_path + self.last_processed_time = current_time + + # 格式校验 + if not self.is_valid_file_format(file_path): + show_error_message(self.main_window, file_path) + return + + try: + # 显示文件信息 + file_name = os.path.basename(file_path) + file_size = os.path.getsize(file_path) / 1024 + 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) + + # 初始化进度对话框 + self.progress_dialog = ProgressDialog( + parent=self.main_window, + title="正在处理文件", + label_text="准备开始处理..." + ) + + # 创建并配置后台线程 + self.diagnose_thread = DiagnoseThread(self.parse_status, file_path) + self.diagnose_thread.progress_updated.connect(self.progress_dialog.update_progress) + self.diagnose_thread.task_completed.connect(self.on_diagnose_completed) + + # 显示进度条并启动线程 + self.progress_dialog.show() + self.diagnose_thread.start() + + except Exception as e: + show_critical_message(self.main_window, "处理失败", f"文件处理出错:{str(e)}") + + def on_diagnose_completed(self, success, message): + """处理诊断任务完成后的逻辑""" + if hasattr(self, 'progress_dialog') and self.progress_dialog: + # 明确允许对话框关闭 + self.progress_dialog.set_allow_close(True) + # 关闭对话框 + self.progress_dialog.close() + # 释放引用 + self.progress_dialog = None + + # 显示结果信息 + self.main_window.textBrowser_console.insertPlainText(f"{message}\n") + + if success: + # 处理成功,更新视图 + self._update_view_after_parse() + show_info_message(self.main_window, "成功", "文件解析完成") + else: + # 处理失败,显示错误 + show_critical_message(self.main_window, "失败", message) + + # 清理线程引用 + self.diagnose_thread = None + + # 其他原有方法保持不变... 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"), + self.get_sensorhistory_path("all"), self.parse_status.diag_complete_status ) - # 更新告警表格 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 + self.main_window.timeline_content.load_events_from_json(event_json) + + def get_sensorhistory_path(self, key): + return service.get_sensorhistory_path(key) + \ No newline at end of file diff --git a/src/main.py b/src/main.py index 7011bc6..3ad0604 100644 --- a/src/main.py +++ b/src/main.py @@ -15,7 +15,7 @@ if __name__ == "__main__": pass # 非Windows系统忽略 # 初始化服务缓存 - service.app_cache_init() + # service.app_cache_init() # 创建PyQt应用实例 app = QApplication(sys.argv) # 获取文件系统中存放的icon diff --git a/src/main_window.py b/src/main_window.py index 3efeb8a..7ccec28 100644 --- a/src/main_window.py +++ b/src/main_window.py @@ -25,6 +25,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): """统一执行所有模块的初始化""" # UI初始化(样式、图形视图、表格模型、时间线) self.ui_init.init_ui() + self.ui_init.init_textbrower_console_style() self.ui_init.init_graphic_views() self.ui_init.init_textbrowser_style() self.ui_init.init_tableView_alert_model() diff --git a/src/progress_diaglog.py b/src/progress_diaglog.py new file mode 100644 index 0000000..4e77a4c --- /dev/null +++ b/src/progress_diaglog.py @@ -0,0 +1,46 @@ +from PyQt5.QtWidgets import QDialog, QVBoxLayout, QProgressBar, QLabel +from PyQt5.QtCore import Qt, pyqtSignal + +class ProgressDialog(QDialog): + """进度条对话框,支持程序控制自动关闭""" + def __init__(self, parent=None, title="处理中", label_text="正在处理,请稍候..."): + super().__init__(parent) + self.setWindowTitle(title) + self.setModal(True) # 模态对话框 + self.setFixedSize(400, 120) + self.allow_close = False # 新增:允许关闭标记 + + # 布局 + layout = QVBoxLayout() + + # 提示文本 + self.label = QLabel(label_text) + layout.addWidget(self.label) + + # 进度条 + self.progress_bar = QProgressBar() + self.progress_bar.setRange(0, 100) + self.progress_bar.setValue(0) + layout.addWidget(self.progress_bar) + + self.setLayout(layout) + + def update_progress(self, value, message=None): + """更新进度值和提示信息""" + self.progress_bar.setValue(value) + if message: + self.label.setText(message) + # 当进度完成时,允许关闭 + if value >= 100: + self.allow_close = True + + def set_allow_close(self, allow): + """设置是否允许关闭对话框""" + self.allow_close = allow + + def closeEvent(self, event): + """根据允许关闭标记决定是否关闭""" + if self.allow_close: + event.accept() # 允许关闭 + else: + event.ignore() # 禁止关闭 \ No newline at end of file diff --git a/src/service.py b/src/service.py index 44317f5..6ae4717 100644 --- a/src/service.py +++ b/src/service.py @@ -76,18 +76,44 @@ def is_idl_alert_empty(): def get_timeline_event_json(): return zijin_event.get_event_json() -def start_diagnose(parseStatus): - result_sensorhistory = sensorparse.program_main() - parseStatus.set_sensorhistory_status(result_sensorhistory) - - result_baseinfo = baseinfo.program_main() - parseStatus.set_baseinfo_status(result_baseinfo) - - result_parseidl = parseidl.program_main() - parseStatus.set_parseidl_status(result_parseidl) - - result_eventline = zijin_event.program_main() - parseStatus.set_eventline_status(result_eventline) +def start_diagnose(parseStatus, filepath, progress_callback=None): + """ + 执行诊断任务,添加进度回调 + :param progress_callback: 进度回调函数,接收(value, message)参数 + """ + try: + if progress_callback: + progress_callback(10, "正在初始化应用cache...") + app_cache_init() + + if progress_callback: + progress_callback(20, "正在解压日志...") + send_log_to_cache(filepath) + + if progress_callback: + progress_callback(30, "正在解析Sensor历史数据信息...") + result_sensorhistory = sensorparse.program_main() + parseStatus.set_sensorhistory_status(result_sensorhistory) + + if progress_callback: + progress_callback(50, "正在解析基础信息...") + result_baseinfo = baseinfo.program_main() + parseStatus.set_baseinfo_status(result_baseinfo) + + if progress_callback: + progress_callback(80, "正在解析告警信息...") + result_parseidl = parseidl.program_main() + parseStatus.set_parseidl_status(result_parseidl) + + if progress_callback: + progress_callback(95, "正在生成时间线...") + result_eventline = zijin_event.program_main() + parseStatus.set_eventline_status(result_eventline) - # 完成文件解析后将 diag_complete_status 置位 - parseStatus.diag_complete_status = True \ No newline at end of file + # 完成文件解析后将 diag_complete_status 置位 + parseStatus.diag_complete_status = True + + except Exception as e: + if progress_callback: + progress_callback(-1, f"处理出错:{str(e)}") + raise e \ No newline at end of file diff --git a/src/ui_initializer.py b/src/ui_initializer.py index fbd98d8..161bd6d 100644 --- a/src/ui_initializer.py +++ b/src/ui_initializer.py @@ -41,6 +41,21 @@ class UIInitializer: padding: 8px; } """) + + def init_textbrower_console_style(self): + self.main_window.textBrowser_console.setStyleSheet(""" + QTextBrowser { + font-family: 'SimHei'; + font-size: 20px; + color: #2c3e50; + background-color: #f8f9fa; + border: 1px solid #ddd; + border-radius: 4px; + padding: 8px; + } + """) + message = "从文件打开日志压缩包或将压缩包拖入该窗口\n" + self.main_window.textBrowser_console.insertPlainText(f"{message}\n") def init_graphic_views(self): """初始化图形视图(抗锯齿、场景绑定)"""