from pathlib import Path import os,time,json,sys from PySide6.QtGui import QPixmap from PySide6.QtWidgets import (QDialog, QVBoxLayout, QListWidget, QPushButton, QHBoxLayout, QMessageBox, QFileDialog, ) from PySide6 import QtCore, QtWidgets from PySide6.QtCore import QDir, QSettings from info_core.get_pictures import get_picture_nums, collect_defect_data from info_core.MyQtClass import DLabel, AddProjectDialog, SmartDropdown from ui.外部报告生成_初始化_ui import * from ui.外部报告生成_内容填写_ui import * class MainWindow(QtWidgets.QMainWindow): project_info_received = QtCore.Signal(object) # 定义一个信号 def __init__(self): super().__init__() self.ui = Ui_Form() # 创建UI实例 self.ui.setupUi(self) # 设置UI # 存储结果的变量 self.project_info = None # 连接信号 self.ui.file_dir_button.clicked.connect(self.get_file_dir) self.ui.chooseproject.clicked.connect(self.get_project_info) self.project_info_received.connect(self.handle_project_info) # 接收信号 self.ui.chooseproject_2.clicked.connect(self.open_saved_project) self.ui.add_project.clicked.connect(self.add_project_info) self.ui.edit_project.clicked.connect(self.edit_project_info) # 获取项目目录 if getattr(sys, 'frozen', False): self.project_dir = os.path.dirname(sys.executable) else: self.project_dir = os.path.dirname(os.path.abspath(__file__)) self.ui.file_dir.setText(self.project_dir) self.ui.file_dir.setPlaceholderText('选择图片目录...') self.ui.lineEdit.setText(self.project_dir) self.ui.file_dir.setPlaceholderText('选择保存的项目...') self.show() self.init_project_info() self.open_init() print(f'初始化窗口初始化完毕,当前项目目录:{self.project_dir}') def init_project_info(self): """从默认文件夹初始化项目信息""" self.project_folder = os.path.join(self.project_dir, 'project') project_folder = os.path.join(self.project_dir, 'project') if os.path.exists(project_folder) and os.path.isdir(project_folder): json_files = [f for f in os.listdir(project_folder) if f.endswith('.json')] if json_files: self.ui.projects.addItems([f.split('.json')[0] for f in json_files]) else: QMessageBox.warning(self, "警告", "project文件夹中没有找到.json文件,请先手动添加一个项目信息") else: QMessageBox.warning(self, "警告", "project文件夹不在当前程序根目录下,请确认后选择项目基本信息文件夹") self.choose_project_folder() def choose_project_folder(self): """让用户选择保存了基本项目信息的文件夹""" folder_path = QFileDialog.getExistingDirectory(None, "选择包含基本项目信息的文件夹") if folder_path: json_files = [f for f in os.listdir(folder_path) if f.endswith('.json')] if json_files: self.ui.projects.addItems(json_files) self.project_folder = folder_path else: print("所选文件夹中没有找到.json文件") else: print("未选择文件夹") def edit_project_info(self): """更改项目信息""" dialog = AddProjectDialog(self.project_folder, self, self.get_current_project_path()) dialog.exec() def add_project_info(self): """添加新的项目信息""" dialog = AddProjectDialog(self.project_folder, self) dialog.exec() def get_current_project_path(self): """获取当前选中的项目文件路径""" if not hasattr(self, 'project_folder'): return None # 或者 raise 一个异常 current_project = self.ui.projects.currentText() if not current_project: return None if self.project_folder.endswith('.json'): return self.project_folder # 如果是文件路径,直接返回 # 拼接完整文件路径 file_path = os.path.join(self.project_folder, f"{current_project}.json") return file_path def open_saved_project(self): # 确定保存目录路径 dir = self.ui.lineEdit.text() # 创建对话框 dialog = QDialog(self) dialog.setWindowTitle("打开保存的项目") dialog.resize(600, 300) # 主布局 layout = QVBoxLayout() # 文件列表 self.file_list = QListWidget() # 按钮布局 button_layout = QHBoxLayout() # 浏览按钮 browse_btn = QPushButton("浏览") browse_btn.clicked.connect(self.browse_directory) # 打开按钮 open_btn = QPushButton("打开") open_btn.clicked.connect(lambda: self.on_open_project(dialog)) # 刷新按钮 refresh_btn = QPushButton("刷新") refresh_btn.clicked.connect(lambda: self.refresh_file_list(dir)) # 取消按钮 cancel_btn = QPushButton("取消") cancel_btn.clicked.connect(dialog.reject) button_layout.addWidget(browse_btn) button_layout.addWidget(open_btn) button_layout.addWidget(refresh_btn) button_layout.addWidget(cancel_btn) # 添加部件到主布局 layout.addWidget(self.file_list) layout.addLayout(button_layout) dialog.setLayout(layout) # 初始加载文件列表 self.refresh_file_list(dir) # 双击打开 self.file_list.itemDoubleClicked.connect(lambda: self.on_open_project(dialog)) return dialog.exec() def browse_directory(self): # 使用QFileDialog来选择目录 options = QFileDialog.Options() options |= QFileDialog.ShowDirsOnly dir = QFileDialog.getExistingDirectory(self, "选择目录", "", options=options) if dir: self.ui.lineEdit.setText(dir) # 更新保存目录路径 self.refresh_file_list(dir) # 刷新文件列表 def refresh_file_list(self, path): """刷新文件列表""" self.file_list.clear() # 检查目录是否存在 if not os.path.exists(path): QMessageBox.warning(self, "警告", "保存目录不存在!") return # 获取目录下所有文件 dir = QDir(path) files = dir.entryList(QDir.Files) # 添加到列表 for file in files: self.file_list.addItem(file) def on_open_project(self, dialog): """打开选中的项目""" selected_items = self.file_list.selectedItems() if not selected_items: QMessageBox.warning(self, "警告", "请先选择一个项目文件!") return filename = selected_items[0].text() filepath = os.path.join(self.ui.lineEdit.text(), filename) # 这里调用你的加载项目函数,传递文件路径 # 例如: self.load_project(filepath) dialog.accept() # 关闭对话框 self.load_project(filepath) # 返回文件路径,可以在外部调用load_project return filepath def load_project(self, filepath): self.close() with open(filepath, 'r') as f: data = json.load(f) dir = data['picture_dir'] print('获取保存记录信息中...') if 'waibu_jiancha' in data.keys(): print('当前报告包含外部') self.ui.ifwaibu.setChecked(data['waibu_jiancha']) if 'neibu_jiancha' in data.keys(): print('当前报告包含内部') self.ui.ifneibu.setChecked(data['neibu_jiancha']) if 'fanglei_jiancha' in data.keys(): print('当前报告包含防雷') self.ui.iffanglei.setChecked(data['fanglei_jiancha']) self.ui.file_dir.setText(dir) global Picture_dir Picture_dir = dir self.ui.comboBox.setCurrentIndex(data['get_info_mode'] if 'get_info_mode' in data.keys() else 0) info = get_xiangmu_base_info(dir, None, None) info.project_dir = self.project_dir self.contentfill = contentfill_window(info, self) self.contentfill.show() self.contentfill.load_project(filepath) def get_file_dir(self): default_dir = self.ui.file_dir.text() if not os.path.exists(default_dir): # 检查当前目录是否存在 default_dir = os.path.abspath(os.sep) # 如果不存在,则使用根目录 file_path = QtWidgets.QFileDialog.getExistingDirectory( self, "选择文件夹", default_dir, # 使用检查后的默认目录 QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontResolveSymlinks ) if file_path: self.ui.file_dir.setText(file_path) def get_project_info(self): if self.ui.file_dir.text(): project = self.ui.projects.currentText() self.project_info = get_xiangmu_base_info(self.ui.file_dir.text(), project, self.project_folder) self.project_info_received.emit(self.project_info) # 发送信号 return self.project_info else: QtWidgets.QMessageBox.warning(self, "警告", "请先选择图片目录") return None def handle_project_info(self, info): if info: self.close() global Picture_dir Picture_dir = self.ui.file_dir.text() info.project_dir = self.project_dir self.contentfill = contentfill_window(info, self) self.contentfill.show() def open_init(self): settings = QSettings('baogao', 'baogao') self.ui.ifwaibu.setChecked(settings.value("ifwaibu", False, type=bool)) self.ui.ifneibu.setChecked(settings.value("ifneibu", False, type=bool)) self.ui.iffanglei.setChecked(settings.value("iffanglei", False, type=bool)) self.ui.comboBox.setCurrentIndex(settings.value("generate_mode", 0, type=int)) self.ui.lineEdit.setText(settings.value("project_save_dir", "", type=str)) self.ui.file_dir.setText(settings.value("picture_dir", "", type=str)) if self.ui.lineEdit.text() == "": self.ui.lineEdit.setText(self.project_dir) if self.ui.file_dir.text() == "": self.ui.file_dir.setText(self.project_dir) def closeEvent(self, event): settings = QSettings('baogao', 'baogao') settings.setValue("ifwaibu", self.ui.ifwaibu.isChecked()) settings.setValue("ifneibu", self.ui.ifneibu.isChecked()) settings.setValue("iffanglei", self.ui.iffanglei.isChecked()) settings.setValue("generate_mode", self.ui.comboBox.currentIndex()) settings.setValue("project_save_dir", self.ui.lineEdit.text()) settings.setValue("picture_dir", os.path.dirname(self.ui.file_dir.text())) #获取file_dir的上一级路径 return super().closeEvent(event) class contentfill_window(QtWidgets.QMainWindow): global jituan_name, fengchang_name, jizu_type, company_name_yi, company_name_jia, project_location, fuzeren, phone_fuzeren, Y1, Y2, Y3, xiangmuguige def __init__(self, info, mainwindow : MainWindow): super().__init__() self.Picture_dir = info.Picture_dir self.project_dir = info.project_dir self.Y1 = info.Y1 self.Y2 = info.Y2 self.Y3 = info.Y3 self.get_info_mode = mainwindow.ui.comboBox.currentIndex() self.has_load_huizong = False self.has_load_quexian = False self.ifwaibu = mainwindow.ui.ifwaibu.isChecked() self.ifneibu = mainwindow.ui.ifneibu.isChecked() self.iffanglei = mainwindow.ui.iffanglei.isChecked() self.ui = Ui_MainWindow() self.ui.setupUi(self) self._init_base_info(info) self._init_cover_page() self._init_project_overview() self.start_init_check_method() self._init_inspection_info() self._init_inspection_content() self.ui.conclusion.setPlaceholderText("""例: 1、因海上风电叶片运行环境恶劣、空气盐碱度高,叶片前缘合模缝区域及PS面(迎风面)涂层易受腐蚀,建议定期观察维护。 2、经无人机近距离外观检查发现H3-08#机位Y200220AF叶片PS面距叶根20m处发现一处裂纹,损伤长度轴向3m,该缺陷经我方判定为严重缺陷,建议尽快结安排对该机组停机并结合其他检查手段(如人工打磨)进一步勘查并决定维修处置方案,防止风险进一步升级。 3、经无人机近距离外观检查未发现H3-08#机位Y200249AF、Y200250AF叶片有明显影响机组正常运行的外部缺陷。""") self.ui.conclusion_company.setText(company_name_yi) self.ui.conclusion_date.setText(time.strftime("%Y年%m月%d日", time.localtime())) self.ui.save_dir.setText(os.path.join(self.project_dir, '报告数据保存')) self.ui.output_dir.setText(os.path.join(self.project_dir, '报告输出')) self.ui.muban_dir.setText(os.path.join(self.project_dir,'报告模板')) self.ui.bianzhishijian.setText(self.ui.calendarWidget.selectedDate().toString('yyyy/MM/dd')) self.ui.conclusion_date.setText(self.ui.calendarWidget.selectedDate().toString('yyyy/MM/dd')) self.total_picture_dir = None self.ui.geregate_button.clicked.connect(self.start_generate_report) self.ui.tabWidget.setCurrentIndex(0) self.ui.label_45.setText(f"图片路径:{self.Picture_dir}") self.has_init_inspection = False self._init_connect() def connect_calendar_to_lineedit_via_button(self, button: QPushButton, lineedit: QLineEdit): """ 通过按钮触发日历选择,确认后更新LineEdit的值 参数: button: QPushButton 触发按钮 lineedit: QLineEdit 文本输入框组件 """ # 设置LineEdit为禁用状态 lineedit.setDisabled(True) def show_calendar_dialog(): from PySide6.QtWidgets import QDialogButtonBox # 创建对话框 dialog = QDialog() dialog.setWindowTitle("选择日期") layout = QVBoxLayout() # 创建日历组件 calendar = QCalendarWidget() layout.addWidget(calendar) # 创建确认按钮 button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) layout.addWidget(button_box) dialog.setLayout(layout) # 如果用户确认选择 if dialog.exec() == QDialog.Accepted: selected_date = calendar.selectedDate() date_str = selected_date.toString("yyyy/MM/dd") lineedit.setText(date_str) # 连接按钮点击事件到显示日历对话框 button.clicked.connect(show_calendar_dialog) def closeEvent(self, event): # 创建一个QMessageBox实例 msg_box = QMessageBox(self) msg_box.setWindowTitle('保存进度') msg_box.setText('是否保存当前报告修改进度?') # 添加自定义按钮 save_button = msg_box.addButton("保存", QMessageBox.YesRole) no_save_button = msg_box.addButton("不保存", QMessageBox.NoRole) cancel_button = msg_box.addButton("取消", QMessageBox.RejectRole) msg_box.setDefaultButton(save_button) msg_box.exec() # 执行对话框 clicked_button = msg_box.clickedButton() # 获取被点击的按钮 if clicked_button == save_button: self.ui.messagebrowser.append("保存数据") result = self.save_form_data() if result == False: event.ignore() # 阻止关闭 return event.accept() # 允许关闭 elif clicked_button == no_save_button: event.accept() # 允许关闭 else: # 取消或其他情况 event.ignore() # 阻止关闭 def save_textbrowser_content(self ,text_browser, file_path: str): """ 保存QTextBrowser内容到文件 :param text_browser: QTextBrowser实例 :param file_path: 要保存的文件路径 """ # 获取全部文本 full_text = text_browser.toPlainText() temp = '' lines = '' for line in full_text.split('\n'): if temp == line: continue temp = line lines += line + '\n' # 写入文件 with open(file_path, 'w', encoding='utf-8') as f: f.write(full_text) def start_init_check_method(self): # 获取scrollArea的内容区域 area = self.ui.scrollAreaWidgetContents_4 area.setLayout(QVBoxLayout()) # 创建标签和对应的TextEdit框 labels_and_textedits = { "工作内容": QTextEdit(), "人员配置": QTextEdit(), "设备配置": QTextEdit(), "施工方案": QTextEdit(), "备注": QTextEdit() } # 设置每个TextEdit的一些基本属性 for label, textedit in labels_and_textedits.items(): textedit.setPlaceholderText(f"请输入{label}...") textedit.setMinimumHeight(100) # 设置最小高度 textedit.setStyleSheet(""" QTextEdit { border: 1px solid #ccc; border-radius: 4px; padding: 5px; font-size: 14px; } QTextEdit:focus { border: 1px solid #0078d7; } """) # 添加到布局中 area.layout().addWidget(textedit) # 设置布局的间距和边距 area.layout().setSpacing(10) # 控件之间的间距 area.layout().setContentsMargins(10, 10, 10, 10) # 布局的边距 # 保存这些TextEdit以便后续访问 self.check_method_textedits = labels_and_textedits text_to_add = { "工作内容": '', "人员配置": '', "设备配置": '', "施工方案": '', "备注": '', } if self.ifwaibu: text_to_add['工作内容'] += '无人机叶片外观巡检\n' text_to_add['人员配置'] += '1人;主检飞手1人\n' text_to_add['设备配置'] += '大疆无人机1台(M350rtk,M300rtk,M30T,M30,精灵4PRO)||大疆精灵4PRO+索尼A7R2机身+索尼200-600mm镜头/适马150-600mm镜头\n' if self.ifneibu: text_to_add['工作内容'] += '叶片内部检查\n' text_to_add['人员配置'] += '2人;轮毂作业检查2人\n' text_to_add['设备配置'] += '人工检查:照明设备2套,视频记录手机2台,含氧量监测仪1台,电动扳手2套,卷尺1个。||爬壁机器人检查:无人作业校车+视频图传1套,照明设备2套,含氧量监测仪1台,电动扳手2套,卷尺1个。\n' if self.iffanglei: text_to_add['工作内容'] += '无人机叶片防雷导通测\n无人吊篮叶片导通测试(含机舱设备、)' text_to_add['人员配置'] += '2人;主检飞手1人,副检抄表1人\n3人,轮毂机舱作业1人,揽风绳作业1人,无人设备操作员及抄表1人' text_to_add['设备配置'] += '1四轴电阻无人机1套,电子微欧计1台,视频记录手机1台\n无人吊篮系统1套(爬绳器+接触平台)、电子微欧计1套,视频记录手机1台,对讲机2台' for name, textedit in labels_and_textedits.items(): if text_to_add[name]: textedit.setPlainText(text_to_add[name]) def start_init_huizong(self): if not self.get_info_mode: self._init_inspection_content_label() else: self._init_inspection_content_from_file() self.has_load_huizong = True self.ui.label_53.setText(f"初始化汇总完毕,如需要重新初始化,请新建项目") self.ui.start_init_huizong.setEnabled(False) def _init_inspection_content_from_file(self): #主要部位图片展示表/检查内容表 try: search_file_list = [] if self.ifwaibu: search_file_list.append("外汇总") if self.ifneibu: search_file_list.append("内汇总") if self.iffanglei: search_file_list.append("防汇总") picture_Y1_num, Y1_dict = collect_defect_data(Y1, Picture_dir, search_file_list) picture_Y2_num, Y2_dict = collect_defect_data(Y2, Picture_dir, search_file_list) picture_Y3_num, Y3_dict = collect_defect_data(Y3, Picture_dir, search_file_list) for name, path in Y1_dict.items(): self.add_dlabel(0, self.ui.scrollAreaWidgetContents, name, path, True) for name, path in Y2_dict.items(): self.add_dlabel(1, self.ui.scrollAreaWidgetContents_2, name, path, True) for name, path in Y3_dict.items(): self.add_dlabel(2, self.ui.scrollAreaWidgetContents_3, name, path, True) self.ui.messagebrowser.append(f"自动获取汇总图成功,共{picture_Y1_num+picture_Y2_num+picture_Y3_num}张图片") except Exception as e: self.ui.messagebrowser.append(f"自动获取汇总图失败:{e}") # def toggle_terminal_window(self): # """切换终端窗口的显示状态""" # if self.terminal_window is None or not self.terminal_window.isVisible(): # # 创建或显示终端窗口 # self.terminal_window = TerminalWindow(self) # self.terminal_window.show() # print("终端输出窗口已打开") # else: # # 隐藏终端窗口 # self.terminal_window.hide() # print("终端输出窗口已隐藏") def save_form_data(self): """保存表单数据到JSON文件""" #保存静态内容: try: self.ui.messagebrowser.append('保存静态数据中...') self.data = { 'has_load_huizong': self.has_load_huizong, 'has_load_quexian': self.has_load_quexian, 'key_words': '缺,损,裂,脱,污', #关键字,用于汇总图的名字包含缺陷时标红,匹配逻辑为正则匹配单个字则为红 后续可优化 #当前检查报告基本内容 'jizu_type': self.ui.jizu_type.text(), #检查的机组编号 'jiancha_date': self.ui.jiancha_date.text(), #检查叶片日期 注意空格分开 小时分钟间隔为. 'baogao_date': self.ui.date.text(), #生成报告时间 注意空格分开 'baogao_type': self.ui.baogao_name.text(), #报告类型 'jiancha_shijian': self.ui.jiancha_shijian.text(), 'chuli_shijian': self.ui.chulishijian.text(), 'bianzhi_shijian': self.ui.bianzhishijian.text(), 'weak_Y1': self.ui.weak_num_Y1.text(), 'weak_Y2': self.ui.weak_num_Y2.text(), 'weak_Y3': self.ui.weak_num_Y3.text(), 'quexian_Y1': self.ui.textEdit.toPlainText(), 'quexian_Y2': self.ui.textEdit_2.toPlainText(), 'quexian_Y3': self.ui.textEdit_3.toPlainText(), #检查方案 #待完善 'beizhu': '无', 'renyuan_peizhi': '''2人;主检飞手1人,副检抄表1人 3人,轮毂机舱作业1人,揽风绳作业1人,无人设备操作员及抄表1人 1人;抄表人员1人,检测人员1人,监护1人。 1人;主检飞手1人 2人;轮毂作业检查2人''', 'gongzuo_neirong': '''无人机叶片防雷导通测 无人吊篮叶片导通测试(含机舱设备、) 风机基础、办公楼、变电站防雷接地检测及浪涌保护器测试 无人机叶片外观巡检 叶片内部检查''', 'shigong_fangan': '无', 'shebei_peizhi': '''1四轴电阻无人机1套,电子微欧计1台,视频记录手机1台 无人吊篮系统1套(爬绳器+接触平台)、电子微欧计1套,视频记录手机1台,对讲机2台 接地电阻测试仪1套、SPD测试仪1套、对讲机2个、 1、大疆无人机1台(M350rtk,M300rtk,M30T,M30,精灵4PRO)2、大疆精灵4PRO+索尼A7R2机身+索尼200-600mm镜头/适马150-600mm镜头 1、人工检查:照明设备2套,视频记录手机2台,含氧量监测仪1台,电动扳手2套,卷尺1个。2、爬壁机器人检查:无人作业校车+视频图传1套,照明设备2套,含氧量监测仪1台,电动扳手2套,卷尺1个。''', 'jiancha_renyuan': '张三', #检查信息 'waibu_jiancha': self.ifwaibu, #是否包含外部检查 'neibu_jiancha': self.ifneibu, #是否包含内部检查 'fanglei_jiancha': self.iffanglei, #是否包含防雷检查 #注:防雷检测占不存放缺陷图 'jiancha_location': '、'.join(self.checkpos), #检查内容文字 'jiancha_fangshi': self.ui.jiancha_fangshi.text(), #检查方式文字 'yepian_changjia': self.ui.changjia.text(), #叶片厂家信息 'jiancha_renyuan': self.ui.jiancha_renyuan.text(), #检查人员 'Tatong_pic_dir': self.ui.Tatong_label.current_path if self.ui.Tatong_label.current_path else '', #报告处理信息 'yezhu_renyuan': self.ui.yezhu_renyuan.text(), #业主(人员) 'changjia_renyuan': self.ui.changjia_renyuan.text(), #厂家(人员) 'date_process': self.ui.shujuchuli.text(), #数据处理人员 吴总希望获取前端用户执行生成人员为这个人 'baogao_bianzhi': self.ui.baogao_bianzhi.text(), #报告编制人员 吴总希望获取前端用户执行生成人员为这个人 'baogao_shenghe': self.ui.baogao_shenghe.text(), #报告审核人员 'shenghe_date': self.ui.shenghe_shijian.text(), #报告审核日期 #检查情况汇总表(文字信息) 前端根据是否包含对应部分检查自行确定检查内容,这里默认全部包含 'Y1_jiancha_neirong': self.ui.textEdit_4.toPlainText(), 'Y2_jiancha_neirong': self.ui.textEdit_5.toPlainText(), 'Y3_jiancha_neirong': self.ui.textEdit_6.toPlainText(), #报告总结 'baogao_zongjie': self.ui.conclusion.toPlainText(), #报告总结文字 } self.data.update( { #目录 'picture_dir': self.Picture_dir, #图片存放地址 为了报告美观,希望总的汇总图片数量为3的倍数 'shengcheng_dir': self.ui.output_dir.text(), #工作路径(报告生成的路径、报告模板存放的路径) 'muban_dir': self.ui.muban_dir.text(), #文档模板存放路径 'save_dir': self.ui.save_dir.text(), #项目保存路径 #项目概况 'jituan_jianxie': self.ui.jituan_jianxie.text(), 'jia_Company': self.ui.Jia_Company.text(), 'jizu_num': self.ui.project_num.text(), 'jizu_bianhao': self.ui.jizu_bianhao.text(), 'fengchang_name': self.ui.fengchang_name.text(), 'fengchang_location': self.ui.fengchang_location.text(), #乙方信息 'yi_Company': self.ui.Yi_Company.text(), 'fuzeren': self.ui.fuzeren.text(), 'phone_fuzeren': self.ui.phone_fuzeren.text(), 'get_info_mode': self.get_info_mode, 'if_xiangmugaikuang': self.ui.checkBox.isChecked(), 'if_jianchaxinxi': self.ui.checkBox_8.isChecked(), 'if_chengguodijiao': self.ui.checkBox_9.isChecked(), 'if_jianchahuizong': self.ui.checkBox_10.isChecked(), 'if_jiancha_yilan': self.ui.checkBox_11.isChecked(), 'if_quexian_xiangqing': self.ui.checkBox_13.isChecked(), } ) except Exception as e: self.ui.messagebrowser.append(f"保存静态内容失败:{e}") try: if self.total_picture_dir: self.data['total_picture_dir'] = self.total_picture_dir else: self.data['total_picture_dir'] = None except Exception as e: self.ui.messagebrowser.append(f'保存略缩图信息失败:{e}') #保存动态内容: try: if self.has_load_huizong: self.ui.messagebrowser.append('保存动态数据中...') #汇总部分 self.data.update({ 'labels':{ 'init':[[],[],[]], 'add': [[],[],[]], }, 'lines':{ 'init':[[],[],[]], 'add': [[],[],[]] } }) self.ui.messagebrowser.append('保存汇总图片与描述') for i in range(3): if self.get_info_mode == 0: if self.label_init_list is not None: for j in range(len(self.label_init_list[i])): self.data['labels']['init'][i].append(self.label_init_list[i][j].current_path) if self.line_init_list is not None: for j in range(len(self.line_init_list[i])): self.data['lines']['init'][i].append(self.line_init_list[i][j].text()) if self.new_add_dlabel is not None: for j in range(len(self.new_add_dlabel[i])): self.data['labels']['add'][i].append(self.new_add_dlabel[i][j].current_path) if self.new_add_lines is not None: for j in range(len(self.new_add_lines[i])): self.data['lines']['add'][i].append(self.new_add_lines[i][j].text()) except Exception as e: self.ui.messagebrowser.append(f"保存动态组件失败:{e}") return try: if self.has_load_quexian: #缺陷部分 self.ui.messagebrowser.append('保存缺陷图片与描述') self.data.update({ 'quexian_image_infos': [[], [], []] }) if self.table_infos_modules is not None: for i in range(len(self.data['quexian_image_infos'])): for image in self.table_infos_modules[i]: self.data['quexian_image_infos'][i].append({ 'image_path': image['image_path'], 'visibility': image['visibility_combo'].currentIndex(), 'severity': image['severity_combo'].currentIndex(), 'urgency': image['urgency_combo'].currentIndex(), 'defect_type': image['defect_type_combo'].current_index, 'defect_location': image['defect_location_edit'].text(), 'defect_size': image['defect_size_edit'].text(), 'repair_suggestion': image['repair_suggestion_edit'].toPlainText(), }) except Exception as e: self.ui.messagebrowser.append(f"保存缺陷图片与描述失败:{e}") return try: self.data.update({ "工作内容": '', "人员配置": '', "设备配置": '', "施工方案": '', "备注": '', }) for name, textedit in self.check_method_textedits.items(): self.data[name] = self.check_method_textedits[name].toPlainText() except Exception as e: self.ui.messagebrowser.append(f'保存施工方案失败') # data['quexian_info'] = self.table_info_to_save #保存 # 默认保存路径 default_savepath = self.ui.save_dir.text() + rf'\{self.ui.jituan_jianxie.text()}{self.ui.fengchang_name_2.text()}项目{self.ui.baogao_name.text()}{self.ui.jizu_type_2.text()}{time.strftime("%Y%m%d_%H%M%S", time.localtime())}.json' # 弹出文件保存对话框 file_path, _ = QFileDialog.getSaveFileName( self, # 父窗口 "保存文件", # 对话框标题 default_savepath, # 默认路径和文件名 "JSON文件 (*.json);;所有文件 (*)" # 文件过滤器 ) # 如果用户取消了对话框,file_path会是空字符串 if not file_path: return False try: with open(file_path, 'w') as f: json.dump(self.data, f) self.ui.messagebrowser.append(f"保存成功,保存路径:{file_path}") except Exception as e: self.ui.messagebrowser.append(f"保存失败: {str(e)}") QMessageBox.information(self, "保存成功", f"保存成功,保存路径:{file_path}") return True def apply_date_to_calendar(self, date_str: str, calendar: QCalendarWidget): """ 将 "年/月/日" 格式的日期字符串应用到日历组件 参数: date_str: str, 格式如 "2023/12/31" calendar: QCalendarWidget 日历组件 """ # 使用 QDate.fromString 解析日期字符串 date = QDate.fromString(date_str, "yyyy/MM/dd") # 检查日期是否有效(避免无效日期,如 "2023/02/30") if date.isValid(): calendar.setSelectedDate(date) else: print(f"警告:无效的日期格式 {date_str},无法应用到日历") def load_project(self, path): """从JSON文件加载表单数据""" self.ui.messagebrowser.append(f"加载项目数据: {path}") self.ui.messagebrowser.append('加载静态数据中...') try: with open(path, 'r') as f: self.data = json.load(f) except FileNotFoundError: print(f"错误:文件 {path} 不存在") return None except json.JSONDecodeError: print(f"错误:文件 {path} 不是有效的JSON格式") return None #加载静态内容 try: # 当前检查报告基本内容 self.ui.jizu_type_2.setText(self.data['jizu_bianhao']) # 检查的机组编号 self.ui.jizu_type.setText(self.data['jizu_type']) self.ui.jiancha_date.setText(self.data['jiancha_date']) # 检查叶片日期 self.ui.date.setText(self.data['baogao_date']) # 生成报告时间 self.ui.baogao_name.setText(self.data['baogao_type']) # 报告类型 # 检查信息 self.ui.changjia.setText(self.data['yepian_changjia']) # 叶片厂家信息 self.ui.jiancha_fangshi.setText(self.data['jiancha_fangshi']) self.ui.jiancha_renyuan.setText(self.data['jiancha_renyuan']) self.ui.jiancha_renyuan2.setText(self.data['jiancha_renyuan']) self.ui.jizu_bianhao.setText(self.data['jizu_bianhao']) self.ui.jizu_bianhao2.setText(self.data['jizu_bianhao']) try: if self.data['Tatong_pic_dir'] != '': self.ui.Tatong_label.load_image(self.data['Tatong_pic_dir']) except Exception as e: self.ui.messagebrowser.append(f"加载塔筒图片失败,请再次导入:{e}") self.ui.jiancha_shijian.setText(self.data['jiancha_shijian']) self.ui.chulishijian.setText(self.data['chuli_shijian']) self.ui.bianzhishijian.setText(self.data['bianzhi_shijian']) self.apply_date_to_calendar(self.data['bianzhi_shijian'], self.ui.calendarWidget) # 检查方案 try: for name, textedit in self.check_method_textedits.items(): self.check_method_textedits[name].setPlainText(self.data[name]) except Exception as e: self.ui.messagebrowser.append(f'加载施工方案失败') # 报告处理信息 self.ui.yezhu_renyuan.setText(self.data['yezhu_renyuan']) # 业主(人员) self.ui.changjia_renyuan.setText(self.data['changjia_renyuan']) # 厂家(人员) self.ui.shujuchuli.setText(self.data['date_process']) # 数据处理人员 self.ui.baogao_bianzhi.setText(self.data['baogao_bianzhi']) # 报告编制人员 self.ui.docx_bianzhi.setText(self.data['baogao_bianzhi']) self.ui.docx_check.setText(self.data['baogao_shenghe']) self.ui.baogao_shenghe.setText(self.data['baogao_shenghe']) # 报告审核人员 self.ui.shenghe_shijian.setText(self.data['shenghe_date']) # 报告审核日期 # 检查情况汇总表(文字信息) self.ui.weak_num_Y1.setText(self.data['weak_Y1']) self.ui.weak_num_Y2.setText(self.data['weak_Y2']) self.ui.weak_num_Y3.setText(self.data['weak_Y3']) self.ui.textEdit_4.setPlainText(self.data['Y1_jiancha_neirong']) self.ui.textEdit_5.setPlainText(self.data['Y2_jiancha_neirong']) self.ui.textEdit_6.setPlainText(self.data['Y3_jiancha_neirong']) self.ui.textEdit.setPlainText(self.data['quexian_Y1']) self.ui.textEdit_2.setPlainText(self.data['quexian_Y2']) self.ui.textEdit_3.setPlainText(self.data['quexian_Y3']) # 报告总结 self.ui.conclusion.setPlainText(self.data['baogao_zongjie']) # 报告总结文字 # 项目概况 self.ui.jituan_jianxie.setText(self.data['jituan_jianxie']) self.ui.Jia_Company.setText(self.data['jia_Company']) self.ui.project_num.setText(self.data['jizu_num']) self.ui.fengchang_name.setText(self.data['fengchang_name']) self.ui.fengchang_location.setText(self.data['fengchang_location']) self.ui.fengchang_name_2.setText(self.data['fengchang_name']) # 乙方信息 self.ui.Yi_Company.setText(self.data['yi_Company']) self.ui.Jiancha_company.setText(self.data['yi_Company']) self.ui.conclusion_company.setText(self.data['yi_Company']) self.ui.fuzeren.setText(self.data['fuzeren']) self.ui.phone_fuzeren.setText(self.data['phone_fuzeren']) self.has_load_huizong = self.data['has_load_huizong'] self.has_load_quexian = self.data['has_load_quexian'] self.ui.muban_dir.setText(self.data['muban_dir']) self.ui.save_dir.setText(self.data['save_dir'] if self.data.get('save_dir') else '') self.ui.output_dir.setText(self.data['shengcheng_dir'] if self.data.get('shengcheng_dir') else '') try: if self.data['total_picture_dir']: total_picture_dir = self.data['total_picture_dir'] self.total_picture_dir = total_picture_dir self.total_picture_count = 0 self.recursive_count_images(total_picture_dir) self.ui.label_18.setText('路径:' + str(total_picture_dir)) self.ui.messagebrowser.append(f"图片总数: {self.total_picture_count}、文件夹路径:{total_picture_dir}") self.ui.total_picture_num.setText(str(self.total_picture_count)) self.ui.messagebrowser.append(f"已选择图片目录项,程序生成时会自动读取这些目录进行略缩图创建") output_dir = self.Picture_dir if os.path.exists(output_dir + '/merged_thumbnail.jpg'): self.ui.messagebrowser.append(f"!!!报告生成图片路径已有略缩图,如需使用本功能生成,请先前往目录:{output_dir} 删除略缩图 merged_thumbnail.jpg") except Exception as e: self.ui.messagebrowser.append(f"加载图片总数失败:{e}") except Exception as e: self.ui.messagebrowser.append(f"加载静态数据失败:{e}") return print('开始加载数据') try: # 加载动态内容 self.ui.messagebrowser.append('加载动态数据中...') scrollarea = [self.ui.scrollAreaWidgetContents, self.ui.scrollAreaWidgetContents_2, self.ui.scrollAreaWidgetContents_3] if self.data['has_load_huizong']: self.ui.messagebrowser.append('上次保存时已初始化汇总数据,开始加载初始化汇总数据') for i in range(3): for label, line in zip(self.data['labels']['init'][i], self.data['lines']['init'][i]): self.ui.messagebrowser.append(f"加载初始化汇总数据: {label}, {line}") self.add_dlabel(i, scrollarea[i], line, label) for i in range(3): for label, line in zip(self.data['labels']['add'][i], self.data['lines']['add'][i]): self.add_dlabel(i, scrollarea[i], line, label) self.ui.messagebrowser.append('加载初始化汇总数据成功') self.ui.label_53.setText(f"初始化汇总完毕,如需要重新初始化,请保存项目信息,重新加载") self.ui.start_init_huizong.setEnabled(False) if self.data['has_load_quexian']: self.ui.messagebrowser.append('上次保存时已初始化缺陷数据,开始加载初始化缺陷数据') self.load_tabs_from_saved_data(self.ui.tab_8, self.data['quexian_image_infos']) except Exception as e: self.ui.messagebrowser.append(f"加载动态数据失败:{e}") return self.ui.messagebrowser.append('加载数据成功') def set_tab_fill_disable(self, box : QtWidgets.QCheckBox, index : int): parent = box.parent() while parent is not None: if isinstance(parent, QTabWidget): break parent = parent.parent() for child in parent.widget(index).findChildren(QWidget): if child.objectName() == box.objectName(): continue if isinstance(child, QLineEdit) or isinstance(child, QTextEdit): if box.isChecked(): child.setReadOnly(True) else: child.setReadOnly(False) class ReportGeneratorThread(QtCore.QThread): class MessageStream(QtCore.QObject): """自定义流,用于实时捕获 print 输出并发送信号""" from io import StringIO message_received = QtCore.Signal(str) # 定义信号,用于传递消息 def write(self, text): self.message_received.emit(text) # 发送消息到主线程 # 可以在这里添加额外的处理(如过滤空行) if text.strip(): # 如果非空才发送 self.message_received.emit(text) def flush(self): pass # 必须实现 flush,但可以留空 report_generated = QtCore.Signal(str) # 报告生成完成信号 error_occurred = QtCore.Signal(str) # 错误信号 def __init__(self, ui, parent): super().__init__() self.ui = ui self.parent = parent self.message_stream = self.MessageStream() # 自定义流 def run(self): try: from main import generate_report import asyncio # 重定向标准输出到自定义流(实时捕获 print) sys.stdout = self.message_stream # 调用异步函数生成报告 file_path = asyncio.run(generate_report(self.ui, self.parent)) # 恢复标准输出 sys.stdout = sys.__stdout__ # 发送完成信号 self.report_generated.emit(file_path) except Exception as e: # 恢复标准输出 sys.stdout = sys.__stdout__ self.error_occurred.emit(str(e)) def start_generate_report(self): """启动报告生成线程""" dirs = [self.ui.save_dir.text(), self.ui.output_dir.text(), self.ui.muban_dir.text()] if not self.check_dir(dirs): return if not self.validate_form_auto(): return # 创建并启动线程 self.report_thread = self.ReportGeneratorThread(self.ui, self) self.report_thread.report_generated.connect(self.on_report_generated) self.report_thread.error_occurred.connect(self.on_report_error) self.report_thread.message_stream.message_received.connect(self.append_message) # 连接实时输出信号 self.report_thread.start() # 显示提示 QMessageBox.information(self, "提示", "报告正在生成中,请稍候...", QMessageBox.Ok) def append_message(self, message): """实时追加消息到 messagebrowser""" self.ui.messagebrowser.append(message) # 去除多余空白 def on_report_generated(self, file_path): """报告生成成功回调""" self.ui.messagebrowser.append("------------\n报告生成成功\n------------") self.save_textbrowser_content(self.ui.messagebrowser, self.ui.output_dir.text() + r'\log.txt') self.ui.messagebrowser.append(f"------------\n生成日志已保存在输出路径{self.ui.output_dir.text()}/log.txt\n------------") reply = QMessageBox.question( self, "报告生成成功", f"报告已生成在: {file_path}\n\n是否要现在打开?", QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes ) if reply == QMessageBox.Yes: import os os.startfile(file_path) # Windows def on_report_error(self, error_msg): """错误回调""" QMessageBox.critical(self, "错误", f"生成报告时出错:\n{error_msg}") def validate_form_auto(self): line_edits = self.findChildren(QLineEdit) text_edits = self.findChildren(QTextEdit) self.ui.messagebrowser.append(f"找到的 QLineEdit 数量: {len(line_edits)}") self.ui.messagebrowser.append(f"找到的 QTextEdit 数量: {len(text_edits)}") missing = False missing_tabs = set() try: for le in line_edits: if le.text().strip() == '' and le.isReadOnly() == False: #为空且不只读 self.ui.messagebrowser.append(f"发现未填写的 QLineEdit: {le.objectName()}") # 调试点1 stylesheet = le.styleSheet() + "\nbackground-color: yellow;" le.setStyleSheet(stylesheet) le.textChanged.connect(lambda text, widget=le: self.reset_lineedit_style(widget)) missing = True tab_widget, tab_name = self.find_tab_widget_and_name(le) self.ui.messagebrowser.append(f"所属标签页: {tab_name}") # 调试点2 if tab_name: missing_tabs.add(tab_name) for te in text_edits: if te.toPlainText().strip() == '' and te.isReadOnly() == False: self.ui.messagebrowser.append(f"发现未填写的 QTextEdit: {te.objectName()}") # 调试点1 stylesheet = te.styleSheet() + "\nbackground-color: yellow;" te.setStyleSheet(stylesheet) te.textChanged.connect(lambda widget=te: self.reset_lineedit_style(widget)) missing = True tab_widget, tab_name = self.find_tab_widget_and_name(te) self.ui.messagebrowser.append(f"所属标签页: {tab_name}") # 调试点2 if tab_name: missing_tabs.add(tab_name) except Exception as e: self.ui.messagebrowser.append(f"验证表单错误: {e}") if missing: self.ui.messagebrowser.append(f"所有缺失的标签页: {missing_tabs}") # 调试点3 message = "有未填写部分,已标黄" if missing_tabs: message += f"\n未填写部分位于以下标签页:" for tab in missing_tabs: message += f"\n{tab}" if self.ui.Tatong_label.text() == "塔筒号图片选择" and not self.ui.checkBox_8.isChecked(): message += "\n塔筒号图片未选择" message += '\n是否继续生成?' msg_box = QMessageBox(self) msg_box.setIcon(QMessageBox.Warning) msg_box.setWindowTitle("错误") msg_box.setText(message) msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg_box.setWindowModality(Qt.NonModal) # 关键:设为非模态 response = msg_box.exec() if response == QMessageBox.Yes: self.empty_line_set() return True else: return False return True def find_tab_widget_and_name(self, widget): try: parent = widget.parent() while parent is not None: self.ui.messagebrowser.append(f"当前父组件: {parent}, 类型: {type(parent)}") # 调试点4 # 检查当前父级是否是 QTabWidget if isinstance(parent, QTabWidget): # 获取当前控件所在的标签页索引 tab_index = parent.indexOf(widget) if tab_index == -1: # 如果控件不在 QTabWidget 的直接子级,尝试查找其所在的 QWidget 子级 for i in range(parent.count()): tab_content = parent.widget(i) if tab_content and widget in tab_content.findChildren(QWidget, options=Qt.FindChildrenRecursively): tab_name = parent.tabText(i) self.ui.messagebrowser.append(f"找到标签页: {tab_name} (索引: {i})") # 调试点5 return parent, tab_name else: tab_name = parent.tabText(tab_index) self.ui.messagebrowser.append(f"找到标签页: {tab_name} (索引: {tab_index})") # 调试点5 return parent, tab_name parent = parent.parent() self.ui.messagebrowser.append("未找到 QTabWidget 父组件") # 调试点6 return None, None except Exception as e: self.ui.messagebrowser.append(f"查找标签页错误: {e}") return None, None def reset_lineedit_style(self, line_edit : QLineEdit): """重置 QLineEdit 的样式表为空""" self.remove_last_style_sheet_rule(line_edit) try: line_edit.textChanged.disconnect() except: pass def remove_last_style_sheet_rule(self, widget : QWidget): stylesheet = widget.styleSheet() if stylesheet: lines = stylesheet.splitlines() # 分割为行列表 if lines: # 如果非空 lines.pop() # 移除最后一行 new_stylesheet = "\n".join(lines) # 重新合并 widget.setStyleSheet(new_stylesheet) def _init_cover_page(self): # 初始化日期显示 self._init_date_display() # 设置封面内容 self.ui.jituan_jianxie.setText(jituan_name) self.ui.fengchang_name_2.setText(fengchang_name) self.ui.jizu_type.setText(jizu_type) self.ui.Yi_Company.setText(company_name_yi) def _init_date_display(self): """初始化日期显示,包括中文转换""" unit_map = {'1' : '一', '2' : '二', '3' : '三', '4' : '四', '5' : '五', '6' : '六', '7' : '七', '8' : '八', '9' : '九', '0' : '〇'} unit_map_month = {1 : '一', 2 : '二', 3 : '三', 4 : '四', 5 : '五', 6 : '六', 7 : '七', 8 : '八', 9 : '九', 10 : '十', 11 : '十一', 12 : '十二'} self.dateyear = self.ui.calendarWidget.selectedDate().toString('yyyy年') self.dateyear = self.dateyear.translate(str.maketrans(unit_map)) self.datemonth = self.ui.calendarWidget.selectedDate().toString('MM') self.datemonth = (unit_map_month[int(self.datemonth)] + '月') self.ui.date.setText(self.dateyear + self.datemonth) def _init_project_overview(self): """初始化项目概况部分""" self.ui.fengchang_name.setText(fengchang_name) self.ui.fengchang_location.setText(project_location) self.ui.Jia_Company.setText(company_name_jia) self.ui.jizu_type.setText(jizu_type) self.ui.Jiancha_company.setText(company_name_yi) self.ui.fuzeren.setText(fuzeren) self.ui.phone_fuzeren.setText(phone_fuzeren) self.ui.project_num.setText(xiangmuguige) def _init_inspection_info(self): """初始化检查信息部分""" try: jiancha = [] self.neirong = [] xiangmu = [] self.checkpos = [] if self.ifwaibu: self.checkpos.append('叶片外部外观') jiancha.append("无人机外部高精度飞行") self.neirong.append(f"{Y1}、{Y2}、{Y3}三支叶片的前缘、后缘、迎风面、背风面。") xiangmu.append("外部") if self.ifneibu: self.checkpos.append('叶片内部') jiancha.append("人工内部拍摄") self.neirong.append(f"{Y1}、{Y2}、{Y3}三支叶片的内部导雷卡、腹板、透光、人孔盖版、叶根盖板...") xiangmu.append("内部") if self.iffanglei: self.checkpos.append('机组防雷') jiancha.append("人工防雷") self.neirong.append(f"轮毂至塔基导通、内部导线线阻、外部导线线阻...") xiangmu.append("防雷") self.ui.label_49.setText(f"当前报告项目为:" + '、'.join(_ for _ in xiangmu)) normal_picture_num = get_picture_nums(self.Picture_dir) #使用根目录文件夹名作为机组编号 self.ui.jizu_bianhao.setText(self.Picture_dir.split('/')[-1]) self.ui.jizu_bianhao2.setText(self.Picture_dir.split('/')[-1]) self.ui.jizu_type_2.setText(self.Picture_dir.split('/')[-1]) self.ui.Y1.setText(f'叶片 1:{Y1}') self.ui.Y2.setText(f'叶片 2:{Y2}') self.ui.Y3.setText(f'叶片 3:{Y3}') self.ui.label_52.setText("本次" + "、".join(_ for _ in jiancha) + "检查,采集叶片图片") self.ui.jiancha_xiangqing.setText("内容覆盖" + ";".join(_ for _ in self.neirong)) self.ui.Tatong_label.messagebrowser = self.ui.messagebrowser self.ui.Tatong_label.setGeometry(QtCore.QRect(10, 10, 151, 151)) except Exception as e: self.ui.messagebrowser.append(f"初始化检查信息失败:{e}") print(f"初始化检查信息失败:{e}") def _init_inspection_summary(self): """ 初始化检查情况汇总。 具体问题描述初始化。如果实现了图片缺陷命名到图片上即可实现。 """ try: self.Y1_quexian_num, self.Y2_quexian_num, self.Y3_quexian_num, self.quexian_num = 0, 0, 0, 0 self.Y1_quexian_dict, self.Y2_quexian_dict, self.Y3_quexian_dict = {}, {}, {} search_file_list = [] if self.ifwaibu: search_file_list.append("外缺陷图") if self.ifneibu: search_file_list.append("内缺陷图") self.Y1_quexian_num, self.Y1_quexian_dict = collect_defect_data(Y1, Picture_dir, search_file_list) self.Y2_quexian_num, self.Y2_quexian_dict = collect_defect_data(Y2, Picture_dir, search_file_list) self.Y3_quexian_num, self.Y3_quexian_dict = collect_defect_data(Y3, Picture_dir, search_file_list) self.ui.weak_num_Y1.setText(f"{Y1}共发现缺陷{self.Y1_quexian_num}处") self.ui.weak_num_Y2.setText(f"{Y2}共发现缺陷{self.Y2_quexian_num}处") self.ui.weak_num_Y3.setText(f"{Y3}共发现缺陷{self.Y3_quexian_num}处") self.ui.textEdit_4.setPlainText("\n".join(f"{i+1}.{neirong}" for i, neirong in enumerate(self.neirong) if neirong)) self.ui.textEdit_5.setPlainText("\n".join(f"{i+1}.{neirong}" for i, neirong in enumerate(self.neirong) if neirong)) self.ui.textEdit_6.setPlainText("\n".join(f"{i+1}.{neirong}" for i, neirong in enumerate(self.neirong) if neirong)) if self.get_info_mode == 1: self.ui.textEdit.setPlainText("\n".join([f"{i+1}.{name}" for i, (name, path) in enumerate(self.Y1_quexian_dict.items())]) if self.Y1_quexian_num else '未发现明显影响风力发电机组正常运行的缺陷') self.ui.textEdit_2.setPlainText("\n".join([f"{i+1}.{name}" for i, (name, path) in enumerate(self.Y2_quexian_dict.items())]) if self.Y2_quexian_num else '未发现明显影响风力发电机组正常运行的缺陷') self.ui.textEdit_3.setPlainText("\n".join([f"{i+1}.{name}" for i, (name, path) in enumerate(self.Y3_quexian_dict.items())]) if self.Y2_quexian_num else '未发现明显影响风力发电机组正常运行的缺陷') self.ui.messagebrowser.append(f"读取路径:{Picture_dir},读取到{Y1}缺陷数:{self.Y1_quexian_num}") self.ui.messagebrowser.append(f"读取路径:{Picture_dir},读取到{Y2}缺陷数:{self.Y2_quexian_num}") self.ui.messagebrowser.append(f"读取路径:{Picture_dir},读取到{Y3}缺陷数:{self.Y3_quexian_num}") # 保存缺陷总数供其他方法使用 self.quexian_num = self.Y1_quexian_num + self.Y2_quexian_num + self.Y3_quexian_num except Exception as e: self.ui.messagebrowser.append(f"获取缺陷表信息失败:{e}") def _init_inspection_content(self): """初始化检查内容一览""" # 设置页标题 self.ui.tabWidget_2.setTabText(0, f"叶片1:{Y1}检查内容") self.ui.tabWidget_2.setTabText(1, f"叶片2:{Y2}检查内容") self.ui.tabWidget_2.setTabText(2, f"叶片3:{Y3}检查内容") self.picture_line = [[],[],[]] self.picture_labels = [[],[],[]] # 初始化布局和计数器 self.current_row = [0, 0, 0] self.current_col = [0, 0, 0] # 创建三个网格布局 self.gridlayout = [ QtWidgets.QGridLayout(self.ui.scrollAreaWidgetContents), QtWidgets.QGridLayout(self.ui.scrollAreaWidgetContents_2), QtWidgets.QGridLayout(self.ui.scrollAreaWidgetContents_3) ] # 设置布局对齐方式 for layout in self.gridlayout: layout.setAlignment(Qt.AlignLeft | Qt.AlignTop) # 连接按钮信号 self.ui.add_dlabel.clicked.connect(lambda: self.add_dlabel(0, self.ui.scrollAreaWidgetContents)) self.ui.add_dlabel_2.clicked.connect(lambda: self.add_dlabel(1, self.ui.scrollAreaWidgetContents_2)) self.ui.add_dlabel_3.clicked.connect(lambda: self.add_dlabel(2, self.ui.scrollAreaWidgetContents_3)) self.label_init_list = [[],[],[]] self.line_init_list = [[],[],[]] self.new_add_dlabel = [[],[],[]] self.new_add_lines = [[],[],[]] def _init_inspection_content_label(self): self.photo_pos = ["PS面(最大弦长)", "SS面(最大弦长)", "PS面(叶尖)", "SS面(叶尖)", "前缘局部", "后缘局部"] self.init_dlabel(self.gridlayout) self.ui.tabWidget_2.setCurrentIndex(0) def start_init_quexian(self): ''' 开始初始化缺陷信息 ''' self.has_load_quexian = True self._init_inspection_summary() self.setup_tabs(self.ui.tab_8, self.quexian_num, self.Picture_dir) def add_waibu_text(self): self.ui.messagebrowser.append("自动在三个叶片中添加关于外部检查的检查内容信息...") self.ui.textEdit_4.append('外部检查:叶片前缘、后缘、PS面、SS面。') self.ui.textEdit_5.append('外部检查:叶片前缘、后缘、PS面、SS面。') self.ui.textEdit_6.append('外部检查:叶片前缘、后缘、PS面、SS面。') def add_neibu_text(self): self.ui.messagebrowser.append("自动在三个叶片中添加关于内部检查的检查内容信息...") self.ui.textEdit_4.append('内部检查:叶片内部导雷卡、腹板、透光、人孔盖版、叶根盖板...') self.ui.textEdit_5.append('内部检查:叶片内部导雷卡、腹板、透光、人孔盖版、叶根盖板...') self.ui.textEdit_6.append('内部检查:叶片内部导雷卡、腹板、透光、人孔盖版、叶根盖板...') def add_fanglei_text(self): self.ui.messagebrowser.append("自动在三个叶片中添加关于防雷检查的检查内容信息...") self.ui.textEdit_4.append('防雷检查:轮毂至塔基导通、内部导线线阻、外部导线线阻...') self.ui.textEdit_5.append('防雷检查:轮毂至塔基导通、内部导线线阻、外部导线线阻...') self.ui.textEdit_6.append('防雷检查:轮毂至塔基导通、内部导线线阻、外部导线线阻...') def _init_connect(self): #相同表单同步 self.ui.saveproject.clicked.connect(lambda: self.save_form_data()) def create_sync_handler(source, target, message): def handler(): if source.text() != target.text(): target.setText(source.text()) # 只在真正有变化时才显示消息 if message: self.ui.messagebrowser.append(message) return handler # 检查人员 self.ui.jiancha_renyuan.editingFinished.connect( create_sync_handler(self.ui.jiancha_renyuan, self.ui.jiancha_renyuan2, "检测到检查人员更改,自动同步所有检查人员栏...")) self.ui.jiancha_renyuan2.editingFinished.connect( create_sync_handler(self.ui.jiancha_renyuan2, self.ui.jiancha_renyuan, "检测到检查人员更改,自动同步所有检查人员栏...")) # 风场名 self.ui.fengchang_name.editingFinished.connect( create_sync_handler(self.ui.fengchang_name, self.ui.fengchang_name_2, "检测到风场名更改,自动同步风场名栏...")) self.ui.fengchang_name_2.editingFinished.connect( create_sync_handler(self.ui.fengchang_name_2, self.ui.fengchang_name, "检测到风场名更改,自动同步风场名栏...")) # 检查日期 self.ui.jiancha_date.textChanged.connect(lambda: (self.ui.messagebrowser.append("检测到检查日期更改,自动同步检查日期栏..."), self.ui.jiancha_riqi.setText(self.ui.jiancha_date.text()))) self.ui.jiancha_riqi.textChanged.connect(lambda: (self.ui.messagebrowser.append("检测到检查日期更改,自动同步检查日期栏..."), self.ui.jiancha_date.setText(self.ui.jiancha_riqi.text()))) # 报告编制人员 self.ui.baogao_bianzhi.editingFinished.connect( create_sync_handler(self.ui.baogao_bianzhi, self.ui.docx_bianzhi, "检测到报告编制人员更改,自动同步编制人员栏...")) self.ui.docx_bianzhi.editingFinished.connect( create_sync_handler(self.ui.docx_bianzhi, self.ui.baogao_bianzhi, "检测到报告编制人员更改,自动同步编制人员栏...")) # 报告审核人员 self.ui.baogao_shenghe.editingFinished.connect( create_sync_handler(self.ui.baogao_shenghe, self.ui.docx_check, "检测到报告审核人员更改,自动同步审核人员栏...")) self.ui.docx_check.editingFinished.connect( create_sync_handler(self.ui.docx_check, self.ui.baogao_shenghe, "检测到报告审核人员更改,自动同步审核人员栏...")) # 乙方公司/检查公司/结论公司 def sync_bianhao(source): if (source.text() != self.ui.jizu_bianhao.text() or source.text() != self.ui.jizu_bianhao2.text() or source.text() != self.ui.jizu_type_2.text()): self.ui.jizu_bianhao.setText(source.text()) self.ui.jizu_bianhao2.setText(source.text()) self.ui.jizu_type_2.setText(source.text()) self.ui.messagebrowser.append("检测到机组编号更改,自动同步所有机组编号栏...") self.ui.jizu_bianhao.editingFinished.connect(lambda: sync_bianhao(self.ui.jizu_bianhao)) self.ui.jizu_bianhao2.editingFinished.connect(lambda: sync_bianhao(self.ui.jizu_bianhao2)) self.ui.jizu_type_2.editingFinished.connect(lambda: sync_bianhao(self.ui.jizu_type_2)) # 乙方公司/检查公司/结论公司 def sync_companies(source): if (source.text() != self.ui.Yi_Company.text() or source.text() != self.ui.Jiancha_company.text() or source.text() != self.ui.conclusion_company.text()): self.ui.Yi_Company.setText(source.text()) self.ui.Jiancha_company.setText(source.text()) self.ui.conclusion_company.setText(source.text()) self.ui.messagebrowser.append("检测到公司名称更改,自动同步所有公司名称栏...") self.ui.Yi_Company.editingFinished.connect(lambda: sync_companies(self.ui.Yi_Company)) self.ui.Jiancha_company.editingFinished.connect(lambda: sync_companies(self.ui.Jiancha_company)) self.ui.conclusion_company.editingFinished.connect(lambda: sync_companies(self.ui.conclusion_company)) self.ui.calendarWidget.selectionChanged.connect(lambda: self.tongbu_time()) self.ui.waibu_add_text.clicked.connect(lambda: self.add_waibu_text()) self.ui.neibu_add_text.clicked.connect(lambda: self.add_neibu_text()) self.ui.fanglei_add_text.clicked.connect(lambda: self.add_fanglei_text()) self.ui.start_init_quexian.clicked.connect(lambda: self.start_init_quexian()) self.ui.start_init_huizong.clicked.connect(lambda: self.start_init_huizong()) self.bind_directory_browser(self.ui.muban_dir, self.ui.get_muban) self.bind_directory_browser(self.ui.output_dir, self.ui.get_output) self.bind_directory_browser(self.ui.save_dir, self.ui.get_save) self.ui.checkBox.checkStateChanged.connect(lambda: self.set_tab_fill_disable(self.ui.checkBox, 1)) self.ui.checkBox_8.checkStateChanged.connect(lambda: self.set_tab_fill_disable(self.ui.checkBox_8, 3)) self.ui.checkBox_9.checkStateChanged.connect(lambda: self.set_tab_fill_disable(self.ui.checkBox_9, 4)) self.ui.checkBox_10.checkStateChanged.connect(lambda: self.set_tab_fill_disable(self.ui.checkBox_10, 5)) self.ui.checkBox_11.checkStateChanged.connect(lambda: self.set_tab_fill_disable(self.ui.checkBox_11, 6)) self.ui.checkBox_13.checkStateChanged.connect(lambda: self.set_tab_fill_disable(self.ui.checkBox_13, 7)) #日期/日历绑定 self.connect_calendar_to_lineedit_via_button(self.ui.jiancha_calander, self.ui.jiancha_riqi) self.connect_calendar_to_lineedit_via_button(self.ui.chuli_calader, self.ui.chulishijian) self.connect_calendar_to_lineedit_via_button(self.ui.shenghe_calendar, self.ui.shenghe_shijian) self.connect_timeedit_to_lineedit(self.ui.timeEdit, self.ui.jiancha_shijian) self.ui.choose_picture_file.clicked.connect(lambda: self.choose_file_to_get_picture_num()) def choose_file_to_get_picture_num(self): """选择文件夹,计算文件夹内图片数量并生成略缩图,并记录文件夹路径。 """ # 打开文件夹选择对话框,允许多选 folder_path = QFileDialog.getExistingDirectory(self, "选择文件夹") if folder_path: self.total_picture_dir = folder_path self.total_picture_count = 0 self.recursive_count_images(folder_path) self.ui.label_18.setText('路径:' + str(folder_path)) self.ui.messagebrowser.append(f"图片总数: {self.total_picture_count}、文件夹路径:{folder_path}") self.ui.total_picture_num.setText(str(self.total_picture_count)) self.ui.messagebrowser.append(f"已选择图片目录项,程序生成时会自动读取这些目录进行略缩图创建") output_dir = self.Picture_dir if os.path.exists(output_dir + '/merged_thumbnail.jpg'): self.ui.messagebrowser.append(f"!!!报告生成图片路径已有略缩图,如需使用本功能生成,请先前往目录:{output_dir} 删除略缩图 merged_thumbnail.jpg") def recursive_count_images(self, path): """ 递归遍历文件夹,统计图片数量 """ for root, dirs, files in os.walk(path): for file in files: # 检查文件扩展名是否为图片格式 if file.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff')): self.total_picture_count += 1 def connect_timeedit_to_lineedit(self, timeedit: 'QTimeEdit', lineedit: 'QLineEdit'): """ 将时间编辑组件的时间连接到LineEdit,格式化为 时:分 格式 参数: timeedit: QTimeEdit 时间编辑组件 lineedit: QLineEdit 文本输入框组件 """ # 定义时间转换函数 def update_lineedit_time(time: QTime): # 将时间转换为 时:分 格式 (HH:mm) time_str = time.toString("HH:mm") lineedit.setText(time_str) # 连接时间编辑的时间变化信号到更新函数 timeedit.timeChanged.connect(update_lineedit_time) # 初始设置当前时间 update_lineedit_time(timeedit.time()) def _init_base_info(self, info): if not info: return global jituan_name, fengchang_name, jizu_type, company_name_yi, company_name_jia, project_location, fuzeren, phone_fuzeren, Y1, Y2, Y3, xiangmuguige #获取项目基本信息。 self.ui.output_dir.setText('\\'.join(self.Picture_dir.split('\\')[:-1])) #插入图片存放路径 xiangmubaseinfo = info jituan_name, fengchang_name, jizu_type, company_name_yi, company_name_jia, project_location, fuzeren, phone_fuzeren, Y1, Y2, Y3, xiangmuguige = xiangmubaseinfo.get_base_info() def clear_tab_contents(self, tab : QtWidgets.QTabWidget): """清除tab中的所有子控件和布局""" if not tab.layout(): return # 保存布局的间距等属性 old_spacing = tab.layout().spacing() old_contents_margins = tab.layout().contentsMargins() # 清除所有子控件 self.clear_layout(tab.layout()) # 重新设置布局属性 tab.layout().setSpacing(old_spacing) tab.layout().setContentsMargins(old_contents_margins) def clear_layout(self, layout): """递归清除布局中的所有子控件""" while layout.count(): item = layout.takeAt(0) if item.widget(): item.widget().deleteLater() elif item.layout(): self.clear_layout(item.layout()) def load_tabs_from_saved_data(self, tab, saved_data): """ 从保存的数据加载标签页内容到给定的tab对象中 参数: tab: 你在Qt Designer中创建的容器对象 saved_data: 包含保存的缺陷信息的字典 """ try: # 清除tab中现有的内容 self.clear_tab_contents(tab) # 确保 tab 有一个新的布局 if tab.layout() is None: tab.setLayout(QVBoxLayout()) else: # 如果已有布局,确保它是空的 self.clear_layout(tab.layout()) # 获取或创建主布局 layout = tab.layout() if tab.layout() else QVBoxLayout(tab) layout.setSpacing(10) # 重置存储结构 self.table_infos_modules = [[], [], []] self.table_info_to_save = [] # 计算总缺陷数 total_defects = sum(len(images) for images in saved_data) # 添加缺陷数量标签 num_label = QLabel(f"文件夹内总缺陷数: {total_defects}") num_label.setStyleSheet("font-size: 16px; font-weight: bold;") layout.addWidget(num_label) # 创建QTabWidget(确保只创建一个) if not hasattr(self, 'tab_widget') or not isinstance(getattr(self, 'tab_widget', None), QTabWidget): self.tab_widget = QTabWidget() else: # 如果已存在,清除所有页签 while self.tab_widget.count(): self.tab_widget.removeTab(0) layout.addWidget(self.tab_widget) yepians = [Y1, Y2, Y3] self.quexian_images = [[], [], []] # 为每个叶片的每张图片创建标签页 image_num = 0 except Exception as e: self.ui.messagebrowser.append(f"加载数据错误: {str(e)}") return for yepian_idx, Y in enumerate(yepians): for img_idx, image_info in enumerate(saved_data[yepian_idx]): image_path = image_info['image_path'] # 更新主进度 current_progress = sum(len(saved_data[i]) for i in range(yepian_idx)) + img_idx + 1 self.ui.messagebrowser.append(f"加载图片 {current_progress}/{total_defects}") # 创建单个标签页 page = QWidget() main_layout = QVBoxLayout(page) # 创建水平布局容器(图片+信息 与 组合框+输入框) container = QWidget() h_layout = QHBoxLayout(container) h_layout.setSpacing(1) # 左侧:图片和信息区域 left_widget = QWidget() left_layout = QVBoxLayout(left_widget) left_layout.setSpacing(0) # 添加图片路径信息 path_label = QLabel(f"图片地址(双击打开图片): {image_path}") path_label.setWordWrap(True) path_label.setAlignment(Qt.AlignmentFlag.AlignCenter) path_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse | Qt.TextInteractionFlag.TextSelectableByKeyboard) left_layout.addWidget(path_label) # 添加图片 image_label = DLabel(getevent=False, messagebrowser=self.ui.messagebrowser) image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) image_label.current_path = image_path try: # 加载图片 MAX_SIZE = 5 * 1024 * 1024 # 5MB try: file_size = os.path.getsize(image_path) if file_size <= MAX_SIZE: pixmap = QPixmap(image_path) else: self.progress_dialog.setLabelText(f"压缩图片{image_path}中...") # 图片过大,进行压缩处理 from PIL import Image import io img = Image.open(image_path) quality = 95 # 初始质量 while quality > 10: # 设置最低质量限制 buffer = io.BytesIO() img.save(buffer, format="JPEG", quality=quality) if buffer.tell() <= MAX_SIZE: break quality -= 5 buffer.seek(0) pixmap = QPixmap() pixmap.loadFromData(buffer.getvalue()) if not pixmap.isNull(): scaled_pixmap = pixmap.scaled(300, 200, aspectMode=Qt.AspectRatioMode.IgnoreAspectRatio) image_label.setPixmap(scaled_pixmap) else: image_label.setText(f"无法加载图片: {image_path}") except Exception as e: image_label.setText(f"图片加载错误: {str(e)}") except Exception as e: image_label.setText(f"图片加载错误: {str(e)}") left_layout.addWidget(image_label) h_layout.addWidget(left_widget) # 右侧:组合框和输入框区域 right_widget = QWidget() right_layout = QVBoxLayout(right_widget) # 添加水平布局的ComboBox组 combo_layout = QHBoxLayout() combo_layout.setSpacing(0) # 可见程度ComboBox visibility_widget = QWidget() visibility_layout = QHBoxLayout(visibility_widget) visibility_layout.setSpacing(0) visibility_combo = QComboBox() visibility_combo.addItems(["轻微", "一般", "重要", "严重"]) visibility_combo.setCurrentIndex(image_info['visibility']) # 从保存的数据设置值 visibility_label = QLabel("可见程度:") visibility_layout.addWidget(visibility_label) visibility_layout.addWidget(visibility_combo) combo_layout.addWidget(visibility_widget) # 严重程度ComboBox severity_widget = QWidget() severity_layout = QHBoxLayout(severity_widget) severity_layout.setSpacing(0) severity_combo = QComboBox() severity_combo.addItems(["轻微", "一般", "重要", "严重"]) severity_combo.setCurrentIndex(image_info['severity']) # 从保存的数据设置值 severity_label = QLabel("严重程度:") severity_layout.addWidget(severity_label) severity_layout.addWidget(severity_combo) combo_layout.addWidget(severity_widget) # 紧急程度ComboBox urgency_widget = QWidget() urgency_layout = QHBoxLayout(urgency_widget) urgency_layout.setSpacing(0) urgency_combo = QComboBox() urgency_combo.addItems(["不紧急", "一般", "紧急", "非常紧急"]) urgency_combo.setCurrentIndex(image_info['urgency']) # 从保存的数据设置值 urgency_combo.setMinimumWidth(74) urgency_label = QLabel("紧急程度:") urgency_layout.addWidget(urgency_label) urgency_layout.addWidget(urgency_combo) combo_layout.addWidget(urgency_widget) input_layout = QVBoxLayout() # 缺陷类型输入框 defect_type_widget = QWidget() defect_type_layout = QVBoxLayout(defect_type_widget) defect_type_combo = SmartDropdown('缺陷类型', self.ui.messagebrowser, '缺陷类型:') defect_type_combo.setStyleSheet("font-size: 18px; font-weight: bold;") defect_type_combo.current_index = image_info['defect_type'] # 从保存的数据设置值 defect_type_layout.addWidget(defect_type_combo) input_layout.addWidget(defect_type_widget) # 缺陷位置输入框 defect_location_widget = QWidget() defect_location_layout = QVBoxLayout(defect_location_widget) defect_location_label = QLabel("缺陷位置:") defect_location_label.setStyleSheet("font-size: 18px; font-weight: bold;") defect_location_edit = QLineEdit() defect_location_edit.setText(image_info['defect_location']) # 从保存的数据设置值 defect_location_edit.setPlaceholderText("如:叶片ps面距叶根3m处") defect_location_edit.setStyleSheet("font-size: 18px; font-weight: bold;") defect_location_layout.addWidget(defect_location_label) defect_location_layout.addWidget(defect_location_edit) input_layout.addWidget(defect_location_widget) # 缺陷尺寸输入框 defect_size_widget = QWidget() defect_size_layout = QVBoxLayout(defect_size_widget) defect_size_label = QLabel("缺陷尺寸(mm):") defect_size_label.setStyleSheet("font-size: 18px; font-weight: bold;") defect_size_edit = QLineEdit() defect_size_edit.setText(image_info['defect_size']) # 从保存的数据设置值 defect_size_edit.setPlaceholderText("如:弦向100mm,轴向800mm") defect_size_edit.setStyleSheet("font-size: 18px; font-weight: bold;") defect_size_layout.addWidget(defect_size_label) defect_size_layout.addWidget(defect_size_edit) input_layout.addWidget(defect_size_widget) right_layout.setSpacing(0) right_layout.addLayout(combo_layout) right_layout.addLayout(input_layout) h_layout.addWidget(right_widget) main_layout.addWidget(container) main_layout.setSpacing(0) # 添加维修建议 repair_suggestion_label = QLabel("维修建议:") repair_suggestion_edit = QTextEdit() repair_suggestion_edit.setPlainText(image_info['repair_suggestion']) # 从保存的数据设置值 repair_suggestion_edit.setPlaceholderText("暂不处理/观察运行/建议(尽快)打磨维修/(雷雨季前)修复") repair_suggestion_edit.setMaximumHeight(100) repair_layout = QVBoxLayout() repair_layout.addWidget(repair_suggestion_label) repair_layout.addWidget(repair_suggestion_edit) main_layout.addLayout(repair_layout) self.tab_widget.addTab(page, f"叶片:{Y} 缺陷 {img_idx+1}") # 为ComboBox设置objectName visibility_combo.setObjectName(f"visibility_combo_{image_num}") severity_combo.setObjectName(f"severity_combo_{image_num}") urgency_combo.setObjectName(f"urgency_combo_{image_num}") # 为LineEdit设置objectName defect_type_combo.setObjectName(f"defect_type_edit_{image_num}") defect_location_edit.setObjectName(f"defect_location_edit_{image_num}") defect_size_edit.setObjectName(f"defect_size_edit_{image_num}") # 为TextEdit设置objectName repair_suggestion_edit.setObjectName(f"repair_suggestion_edit_{image_num}") image_num += 1 self.ui.messagebrowser.append(f"""设置组件对象名称:{visibility_combo.objectName()}, {severity_combo.objectName()}, {urgency_combo.objectName()}, {defect_type_combo.objectName()}, {defect_location_edit.objectName()}, {defect_size_edit.objectName()}, {repair_suggestion_edit.objectName()}""") # 将当前图片的所有组件对象存储到对应的叶片列表中 loaded_image_info = { 'image_path': image_path, 'path_label': path_label, 'image_label': image_label, 'visibility_combo': visibility_combo, 'severity_combo': severity_combo, 'urgency_combo': urgency_combo, 'defect_type_combo': defect_type_combo, 'defect_location_edit': defect_location_edit, 'defect_size_edit': defect_size_edit, 'repair_suggestion_edit': repair_suggestion_edit, } self.table_infos_modules[yepian_idx].append(loaded_image_info) self.ui.checkBox_13.raise_() #置顶 self.has_init_inspection = True self.ui.start_init_quexian.setDisabled(True) self.ui.messagebrowser.append("从保存的数据加载完成") def setup_tabs(self, tab, num, Picture_dir): """ 在给定的tab对象中设置带有num个页面的QTabWidget 参数: tab: 你在Qt Designer中创建的容器对象 num: 要创建的标签页数量 Picture_dir: 图片目录路径 """ # 清除tab中现有的内容 self.clear_tab_contents(tab) # 确保 tab 有一个新的布局(解决第二个问题) if tab.layout() is None: tab.setLayout(QVBoxLayout()) else: # 如果已有布局,确保它是空的 self.clear_layout(tab.layout()) # 获取或创建主布局 layout = tab.layout() if tab.layout() else QVBoxLayout(tab) layout.setSpacing(10) # 重置存储结构 self.table_infos_modules = [[], [], []] self.table_info_to_save = [] # 添加缺陷数量标签 num_label = QLabel(f"文件夹内总缺陷数: {num}") num_label.setStyleSheet("font-size: 16px; font-weight: bold;") layout.addWidget(num_label) # 创建QTabWidget(确保只创建一个) if not hasattr(self, 'tab_widget') or not isinstance(getattr(self, 'tab_widget', None), QTabWidget): self.tab_widget = QTabWidget() else: # 如果已存在,清除所有页签 while self.tab_widget.count(): self.tab_widget.removeTab(0) layout.addWidget(self.tab_widget) yepians = [Y1, Y2, Y3] self.quexian_images = [[], [], []] try: # 收集所有缺陷图片路径,按叶片分类 for i, Y in enumerate(yepians): # 获取图片路径 image_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp'} current_images = [] if self.ifwaibu: for root, _, files in os.walk(os.path.join(Picture_dir, Y, "外缺陷图")): for file in files: if os.path.splitext(file)[1].lower() in image_extensions: current_images.append(os.path.join(root, file)) if self.ifneibu: for root, _, files in os.walk(os.path.join(Picture_dir, Y, "内缺陷图")): for file in files: if os.path.splitext(file)[1].lower() in image_extensions: current_images.append(os.path.join(root, file)) self.quexian_images[i] = current_images except Exception as e: self.ui.messagebrowser.append(f"缺陷图获取失败: {e}") # 设置主进度条 total_images = sum(len(images) for images in self.quexian_images) # 为每个叶片的每张图片创建标签页 image_num = 0 for yepian_idx, Y in enumerate(yepians): for img_idx, image_path in enumerate(self.quexian_images[yepian_idx]): # 更新主进度 current_progress = sum(len(self.quexian_images[i]) for i in range(yepian_idx)) + img_idx + 1 self.ui.messagebrowser.append(f"处理图片 {current_progress}/{total_images}") # 创建单个标签页 page = QWidget() main_layout = QVBoxLayout(page) # 创建水平布局容器(图片+信息 与 组合框+输入框) container = QWidget() h_layout = QHBoxLayout(container) h_layout.setSpacing(1) # 左侧:图片和信息区域 left_widget = QWidget() left_layout = QVBoxLayout(left_widget) left_layout.setSpacing(0) # 添加图片路径信息 path_label = QLabel(f"图片地址(双击打开图片): {image_path}") path_label.setWordWrap(True) path_label.setAlignment(Qt.AlignmentFlag.AlignCenter) path_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse | Qt.TextInteractionFlag.TextSelectableByKeyboard) left_layout.addWidget(path_label) # 添加图片 image_label = DLabel(getevent=False, messagebrowser=self.ui.messagebrowser) image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) image_label.current_path = image_path # image_button = QPushButton("编辑图片") # left_layout.addWidget(image_button) #self.bind_edit_function(image_label, image_button) try: # 加载图片 MAX_SIZE = 5 * 1024 * 1024 # 5MB try: file_size = os.path.getsize(image_path) if file_size <= MAX_SIZE: pixmap = QPixmap(image_path) else: self.progress_dialog.setLabelText(f"压缩图片{image_path}中...") # 图片过大,进行压缩处理 from PIL import Image import io img = Image.open(image_path) quality = 95 # 初始质量 while quality > 10: # 设置最低质量限制 buffer = io.BytesIO() img.save(buffer, format="JPEG", quality=quality) if buffer.tell() <= MAX_SIZE: break quality -= 5 buffer.seek(0) pixmap = QPixmap() pixmap.loadFromData(buffer.getvalue()) if not pixmap.isNull(): scaled_pixmap = pixmap.scaled(300, 200, aspectMode=Qt.AspectRatioMode.IgnoreAspectRatio) image_label.setPixmap(scaled_pixmap) else: image_label.setText(f"无法加载图片: {image_path}") except Exception as e: image_label.setText(f"图片加载错误: {str(e)}") except Exception as e: image_label.setText(f"图片加载错误: {str(e)}") left_layout.addWidget(image_label) h_layout.addWidget(left_widget) # 右侧:组合框和输入框区域 right_widget = QWidget() right_layout = QVBoxLayout(right_widget) # 添加水平布局的ComboBox组 combo_layout = QHBoxLayout() combo_layout.setSpacing(0) # 可见程度ComboBox visibility_widget = QWidget() visibility_layout = QHBoxLayout(visibility_widget) visibility_layout.setSpacing(0) visibility_combo = QComboBox() visibility_combo.addItems(["轻微", "一般", "重要", "严重"]) visibility_combo.setCurrentIndex(1) # 默认选择"一般" visibility_label = QLabel("可见程度:") visibility_layout.addWidget(visibility_label) visibility_layout.addWidget(visibility_combo) combo_layout.addWidget(visibility_widget) # 严重程度ComboBox severity_widget = QWidget() severity_layout = QHBoxLayout(severity_widget) severity_layout.setSpacing(0) severity_combo = QComboBox() severity_combo.addItems(["轻微", "一般", "重要", "严重"]) severity_combo.setCurrentIndex(1) # 默认选择"一般" severity_label = QLabel("严重程度:") severity_layout.addWidget(severity_label) severity_layout.addWidget(severity_combo) combo_layout.addWidget(severity_widget) # 紧急程度ComboBox urgency_widget = QWidget() urgency_layout = QHBoxLayout(urgency_widget) urgency_layout.setSpacing(0) urgency_combo = QComboBox() urgency_combo.addItems(["不紧急", "一般", "紧急", "非常紧急"]) urgency_combo.setCurrentIndex(1) # 默认选择"一般" urgency_combo.setMinimumWidth(74) urgency_label = QLabel("紧急程度:") urgency_layout.addWidget(urgency_label) urgency_layout.addWidget(urgency_combo) combo_layout.addWidget(urgency_widget) input_layout = QVBoxLayout() # 缺陷类型输入框 defect_type_widget = QWidget() defect_type_layout = QVBoxLayout(defect_type_widget) defect_type_combo = SmartDropdown('缺陷类型', self.ui.messagebrowser, '缺陷类型') defect_type_combo.setStyleSheet("font-size: 18px; font-weight: bold;") defect_type_layout.addWidget(defect_type_combo) input_layout.addWidget(defect_type_widget) # 缺陷位置输入框 defect_location_widget = QWidget() defect_location_layout = QVBoxLayout(defect_location_widget) defect_location_label = QLabel("缺陷位置:") defect_location_label.setStyleSheet("font-size: 18px; font-weight: bold;") defect_location_edit = QLineEdit() defect_location_edit.setPlaceholderText("如:叶片ps面距叶根3m处") defect_location_edit.setStyleSheet("font-size: 18px; font-weight: bold;") defect_location_layout.addWidget(defect_location_label) defect_location_layout.addWidget(defect_location_edit) input_layout.addWidget(defect_location_widget) # 缺陷尺寸输入框 defect_size_widget = QWidget() defect_size_layout = QVBoxLayout(defect_size_widget) defect_size_label = QLabel("缺陷尺寸(mm):") defect_size_label.setStyleSheet("font-size: 18px; font-weight: bold;") defect_size_edit = QLineEdit() defect_size_edit.setPlaceholderText("如:弦向100mm,轴向800mm") defect_size_edit.setStyleSheet("font-size: 18px; font-weight: bold;") defect_size_layout.addWidget(defect_size_label) defect_size_layout.addWidget(defect_size_edit) input_layout.addWidget(defect_size_widget) right_layout.setSpacing(0) right_layout.addLayout(combo_layout) right_layout.addLayout(input_layout) h_layout.addWidget(right_widget) main_layout.addWidget(container) main_layout.setSpacing(0) # 添加维修建议 repair_suggestion_label = QLabel("维修建议:") repair_suggestion_edit = QTextEdit() repair_suggestion_edit.setPlaceholderText("暂不处理/观察运行/建议(尽快)打磨维修/(雷雨季前)修复") repair_suggestion_edit.setMaximumHeight(100) repair_layout = QVBoxLayout() repair_layout.addWidget(repair_suggestion_label) repair_layout.addWidget(repair_suggestion_edit) main_layout.addLayout(repair_layout) self.tab_widget.addTab(page, f"叶片:{Y} 缺陷 {img_idx+1}") # 为ComboBox设置objectName visibility_combo.setObjectName(f"visibility_combo_{image_num}") severity_combo.setObjectName(f"severity_combo_{image_num}") urgency_combo.setObjectName(f"urgency_combo_{image_num}") # 为LineEdit设置objectName defect_type_combo.setObjectName(f"defect_type_edit_{image_num}") defect_location_edit.setObjectName(f"defect_location_edit_{image_num}") defect_size_edit.setObjectName(f"defect_size_edit_{image_num}") # 为TextEdit设置objectName repair_suggestion_edit.setObjectName(f"repair_suggestion_edit_{image_num}") image_num += 1 self.ui.messagebrowser.append(f"""设置组件对象名称:{visibility_combo.objectName()}, {severity_combo.objectName()}, {urgency_combo.objectName()}, {defect_type_combo.objectName()}, {defect_location_edit.objectName()}, {defect_size_edit.objectName()}, {repair_suggestion_edit.objectName()}""") # 将当前图片的所有组件对象存储到对应的叶片列表中 image_info = { 'image_path': image_path, 'path_label': path_label, 'image_label': image_label, 'visibility_combo': visibility_combo, 'severity_combo': severity_combo, 'urgency_combo': urgency_combo, 'defect_type_combo': defect_type_combo, 'defect_location_edit': defect_location_edit, 'defect_size_edit': defect_size_edit, 'repair_suggestion_edit': repair_suggestion_edit, } self.table_infos_modules[yepian_idx].append(image_info) self.ui.checkBox_13.raise_() #置顶 self.has_init_inspection = True self.ui.start_init_quexian.setDisabled(True) self.ui.messagebrowser.append("初次初始化完成,如需要重新初始化,请保存项目信息,重新加载") # def bind_edit_function(self, dlabel, button): # """绑定按钮和DLabel类,点击按钮触发编辑功能 # Args: # dlabel (DLabel): DLabel实例 # button (QPushButton): 触发编辑功能的按钮 # """ # button.clicked.connect(lambda: self._start_edit(dlabel)) # def _start_edit(self, dlabel): # """开始编辑图片 # Args: # dlabel (DLabel): DLabel实例 # """ # if not dlabel.current_path: # QMessageBox.warning(dlabel, "警告", "请先加载图片") # return # # 创建编辑对话框 # dialog = QDialog(dlabel) # dialog.setWindowTitle("图片编辑") # dialog.setMinimumSize(800, 600) # # 主布局 # main_layout = QVBoxLayout(dialog) # # 创建图形视图和场景 # class ZoomableGraphicsView(QGraphicsView): # def wheelEvent(self, event): # zoom_factor = 1.15 # if event.angleDelta().y() > 0: # self.scale(zoom_factor, zoom_factor) # else: # self.scale(1/zoom_factor, 1/zoom_factor) # view = ZoomableGraphicsView() # scene = QGraphicsScene() # view.setScene(scene) # view.setRenderHint(QPainter.Antialiasing) # view.setRenderHint(QPainter.SmoothPixmapTransform) # view.setDragMode(QGraphicsView.ScrollHandDrag) # # 使用dlabel的load_image方法加载图片 # if not hasattr(dlabel, 'load_image'): # QMessageBox.warning(dlabel, "错误", "DLabel缺少load_image方法") # return # # 获取当前图片路径 # original_path = dlabel.current_path # # 加载图片 # pixmap = QPixmap(original_path) # if pixmap.isNull(): # QMessageBox.warning(dlabel, "错误", "无法加载图片") # return # pixmap_item = QGraphicsPixmapItem(pixmap) # scene.addItem(pixmap_item) # scene.setSceneRect(QRectF(pixmap.rect())) # # 创建选择标记(更粗的红色边框,透明填充) # selection_marker = QGraphicsRectItem(0, 0, 0, 0) # selection_marker.setPen(QPen(QColor(255, 0, 0), 10)) # 加粗到5像素 # selection_marker.setBrush(Qt.NoBrush) # selection_marker.setVisible(False) # scene.addItem(selection_marker) # # 添加一个状态变量来跟踪是否有有效选择 # has_valid_selection = False # # 按钮布局 # btn_layout = QHBoxLayout() # btn_layout.setContentsMargins(5, 5, 5, 5) # btn_layout.setSpacing(10) # # 选择模式按钮 - 改为文字按钮 # btn_select = QPushButton("选择区域") # btn_select.setToolTip("选择要裁剪的区域") # btn_select.setCheckable(True) # btn_select.setCursor(QCursor(Qt.PointingHandCursor)) # btn_select.setFixedSize(100, 30) # btn_select.setStyleSheet(""" # QPushButton { # background-color: rgba(200, 200, 200, 150); # border-radius: 4px; # border: 1px solid #888; # } # QPushButton:checked { # background-color: rgba(100, 150, 255, 150); # } # """) # # 适应窗口按钮 - 改为文字按钮 # btn_fit = QPushButton("适应窗口") # btn_fit.setToolTip("将图片适应窗口大小") # btn_fit.setCursor(QCursor(Qt.PointingHandCursor)) # btn_fit.setFixedSize(100, 30) # btn_fit.setStyleSheet(""" # QPushButton { # background-color: rgba(200, 200, 200, 150); # border-radius: 4px; # border: 1px solid #888; # } # """) # # 保存按钮 # btn_save = QPushButton("保存修改") # btn_save.setToolTip("保存修改并覆盖原图") # btn_save.setCursor(QCursor(Qt.PointingHandCursor)) # btn_save.setFixedSize(100, 30) # btn_save.setStyleSheet(""" # QPushButton { # background-color: rgba(200, 200, 200, 150); # border-radius: 4px; # border: 1px solid #888; # } # QPushButton:hover { # background-color: rgba(100, 255, 100, 150); # } # """) # # 添加按钮到布局 # btn_layout.addWidget(btn_select) # btn_layout.addWidget(btn_fit) # btn_layout.addWidget(btn_save) # btn_layout.addStretch() # # 添加到主布局 # main_layout.addWidget(view) # main_layout.addLayout(btn_layout) # # 状态变量 # is_selecting = False # start_pos = QPointF() # current_rect = QRectF() # # 初始适应窗口 # def fit_to_view(): # view.fitInView(scene.sceneRect(), Qt.KeepAspectRatio) # fit_to_view() # # 切换选择模式 # def toggle_selection_mode(checked): # nonlocal is_selecting # is_selecting = checked # if checked: # view.setDragMode(QGraphicsView.NoDrag) # selection_marker.setVisible(False) # view.setCursor(QCursor(Qt.CrossCursor)) # else: # view.setDragMode(QGraphicsView.ScrollHandDrag) # selection_marker.setVisible(False) # view.setCursor(QCursor(Qt.ArrowCursor)) # # 鼠标按下事件 # def mouse_press(event): # nonlocal start_pos, current_rect, has_valid_selection # if is_selecting and event.button() == Qt.LeftButton: # # 使用 position() 替代 pos() # pos = event.position() if hasattr(event, 'position') else event.posF() if hasattr(event, 'posF') else event.pos() # start_pos = view.mapToScene(pos.toPoint()) # current_rect = QRectF(start_pos, QSizeF(0, 0)) # selection_marker.setRect(current_rect) # selection_marker.setVisible(True) # has_valid_selection = False # else: # QGraphicsView.mousePressEvent(view, event) # # 鼠标移动事件 # def mouse_move(event): # nonlocal has_valid_selection # if is_selecting and selection_marker.isVisible() and (event.buttons() & Qt.LeftButton): # # 使用 position() 替代 pos() # pos = event.position() if hasattr(event, 'position') else event.posF() if hasattr(event, 'posF') else event.pos() # current_pos = view.mapToScene(pos.toPoint()) # current_rect = QRectF( # min(start_pos.x(), current_pos.x()), # min(start_pos.y(), current_pos.y()), # abs(current_pos.x() - start_pos.x()), # abs(current_pos.y() - start_pos.y()) # ) # selection_marker.setRect(current_rect) # has_valid_selection = current_rect.width() > 5 and current_rect.height() > 5 # self.ui.messagebrowser.append(f"当前选择区域:{current_rect}") # else: # QGraphicsView.mouseMoveEvent(view, event) # # 鼠标释放事件 # def mouse_release(event): # if is_selecting and event.button() == Qt.LeftButton: # # 不需要额外处理,状态已在mouse_move中更新 # pass # else: # QGraphicsView.mouseReleaseEvent(view, event) # # 保存裁剪并覆盖原图 # def save_cropped_image(): # nonlocal has_valid_selection, current_rect, original_path # if not has_valid_selection: # QMessageBox.warning(dialog, "警告", "请先选择有效的区域") # return # try: # # 获取原始图片 # original_pixmap = QPixmap(original_path) # if original_pixmap.isNull(): # QMessageBox.warning(dialog, "错误", "无法加载原始图片") # return # # 创建一个新的QPixmap作为画布,大小与原始图片相同 # result_pixmap = QPixmap(original_pixmap.size()) # result_pixmap.fill(Qt.transparent) # 透明背景 # # 创建QPainter来绘制 # painter = QPainter(result_pixmap) # # 首先绘制原始图片 # painter.drawPixmap(0, 0, original_pixmap) # # 设置画笔属性(红色,10像素宽) # pen = QPen(QColor(255, 0, 0), 10) # pen.setStyle(Qt.SolidLine) # painter.setPen(pen) # painter.setBrush(Qt.NoBrush) # 无填充 # # 将场景坐标转换为图片坐标 # pixmap_rect = pixmap_item.pixmap().rect() # scene_rect = pixmap_item.sceneBoundingRect() # # 计算缩放比例 # scale_x = pixmap_rect.width() / scene_rect.width() # scale_y = pixmap_rect.height() / scene_rect.height() # # 将选择区域转换为原始图片坐标 # draw_rect = QRect( # int((current_rect.x() - scene_rect.x()) * scale_x), # int((current_rect.y() - scene_rect.y()) * scale_y), # int(current_rect.width() * scale_x), # int(current_rect.height() * scale_y) # ) # # 确保绘制区域在图片范围内 # draw_rect = draw_rect.intersected(pixmap_rect) # if draw_rect.isEmpty(): # QMessageBox.warning(dialog, "错误", "绘制区域无效") # return # # 绘制红色矩形框 # painter.drawRect(draw_rect) # # 结束绘制 # painter.end() # # 保存图片,覆盖原图 # if not result_pixmap.save(original_path): # QMessageBox.warning(dialog, "错误", "保存图片失败") # return # # 重新加载图片 # dlabel.load_image(original_path) # QMessageBox.information(dialog, "成功", "图片已保存并重新加载") # dialog.accept() # except Exception as e: # QMessageBox.critical(dialog, "错误", f"保存过程中发生错误: {str(e)}") # # 添加快捷键支持 # def key_press_event(event): # if event.key() == Qt.Key_F: # fit_to_view() # elif event.key() == Qt.Key_C: # btn_select.setChecked(not btn_select.isChecked()) # elif event.key() == Qt.Key_S: # save_cropped_image() # else: # QGraphicsView.keyPressEvent(view, event) # # 连接信号 # btn_select.toggled.connect(toggle_selection_mode) # btn_fit.clicked.connect(fit_to_view) # btn_save.clicked.connect(save_cropped_image) # # 重写事件 # view.mousePressEvent = mouse_press # view.mouseMoveEvent = mouse_move # view.mouseReleaseEvent = mouse_release # view.keyPressEvent = key_press_event # # 窗口大小变化时重新适应 # def resize_event(e): # fit_to_view() # QDialog.resizeEvent(dialog, e) # dialog.resizeEvent = resize_event # # 设置对话框焦点,以便接收键盘事件 # dialog.setFocus() # dialog.exec() # def setup_tabs(self, tab, num, Picture_dir): # """ # 在给定的tab对象中设置带有num个页面的QTabWidget # 参数: # tab: 你在Qt Designer中创建的容器对象 # num: 要创建的标签页数量 # Picture_dir: 图片目录路径 # """ # self.table_infos_modules = [{},{},{}] # self._clear_existing_layout(tab) # layout = self._create_main_layout(tab) # self._add_count_label(layout, num) # tab_widget = self._create_tab_widget(layout) # self._populate_tabs(tab_widget, Picture_dir) # self.ui.checkBox_13.raise_() #置顶 # def _clear_existing_layout(self, tab): # """清除tab中现有的布局和控件""" # if tab.layout(): # while tab.layout().count(): # item = tab.layout().takeAt(0) # if item.widget(): # item.widget().deleteLater() # def _create_main_layout(self, tab): # """创建并返回主布局""" # layout = QVBoxLayout(tab) # layout.setSpacing(10) # return layout # def _add_count_label(self, layout, num): # """添加缺陷数量标签""" # num_label = QLabel(f"文件夹内总缺陷数: {num}") # num_label.setStyleSheet("font-size: 16px; font-weight: bold;") # layout.addWidget(num_label) # def _create_tab_widget(self, layout): # """创建并返回QTabWidget""" # tab_widget = QTabWidget() # layout.addWidget(tab_widget) # return tab_widget # def _populate_tabs(self, tab_widget, Picture_dir): # """填充标签页内容""" # yepians = [Y1, Y2, Y3] # quexian_images = [] # self.quexian_images = [[],[],[]] # # 收集所有缺陷图片路径 # for i, Y in enumerate(yepians): # quexian_images.extend(self.get_image_paths( # os.path.join(Picture_dir, Y, "缺陷图"), Y)) # self.quexian_images[i] = quexian_images # # 设置主进度条 # self.progress_dialog.setRange(0, len(quexian_images)) # # 为每个图片创建标签页 # for i, (image_path, yepian) in enumerate(quexian_images, 1): # # 更新主进度 # self.progress_dialog.setValue(i) # self.progress_dialog.setLabelText(f"处理图片 {i+1}/{len(quexian_images)}") # page = self._create_tab_page(image_path) # tab_widget.addTab(page, f"叶片:{yepian} 缺陷 {i}") # self.progress_dialog.close() # def _create_tab_page(self, image_path): # """创建单个标签页""" # page = QWidget() # main_layout = QVBoxLayout(page) # # 创建水平布局容器 # horizontal_container = self._create_horizontal_container(image_path) # main_layout.addWidget(horizontal_container) # main_layout.setSpacing(0) # # 添加维修建议 # self._add_repair_suggestion(main_layout) # return page # def _create_horizontal_container(self, image_path): # """创建水平布局容器(图片+信息 与 组合框+输入框)""" # container = QWidget() # layout = QHBoxLayout(container) # layout.setSpacing(1) # # 左侧:图片和信息区域 # left_widget = self._create_image_widget(image_path) # # 右侧:组合框和输入框区域 # right_widget = QWidget() # right_layout = QVBoxLayout(right_widget) # self.add_quexian_combo(right_layout) # layout.addWidget(left_widget) # layout.addWidget(right_widget) # return container # def _create_image_widget(self, image_path): # """创建图片显示部件""" # widget = QWidget() # layout = QVBoxLayout(widget) # layout.setSpacing(0) # # 添加图片路径信息 # self._add_image_path_label(layout, image_path) # # 添加图片 # image_label = self._create_image_label(image_path) # layout.addWidget(image_label) # return widget # def _add_image_path_label(self, layout, image_path): # """添加图片路径标签""" # path_label = QLabel(f"图片地址(双击打开图片): {image_path}") # path_label.setWordWrap(True) # path_label.setAlignment(Qt.AlignmentFlag.AlignCenter) # layout.addWidget(path_label) # def _create_image_label(self, image_path): # """创建并返回图片标签""" # image_label = DLabel(getevent = False) # image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) # image_label.current_path = image_path # try: # pixmap = self._load_pixmap(image_path) # if not pixmap.isNull(): # scaled_pixmap = pixmap.scaled(300, 200, aspectMode = Qt.AspectRatioMode.IgnoreAspectRatio) # image_label.setPixmap(scaled_pixmap) # else: # image_label.setText(f"无法加载图片: {image_path}") # except Exception as e: # image_label.setText(f"图片加载错误: {str(e)}") # return image_label # def _load_pixmap(self, image_path): # """加载并返回QPixmap,处理大图片压缩""" # MAX_SIZE = 5 * 1024 * 1024 # 5MB # try: # file_size = os.path.getsize(image_path) # if file_size <= MAX_SIZE: # return QPixmap(image_path) # self.progress_dialog.setLabelText(f"压缩图片{image_path}中...") # # 图片过大,进行压缩处理 # from PIL import Image # import io # img = Image.open(image_path) # quality = 95 # 初始质量 # while quality > 10: # 设置最低质量限制 # buffer = io.BytesIO() # img.save(buffer, format="JPEG", quality=quality) # if buffer.tell() <= MAX_SIZE: # break # quality -= 5 # buffer.seek(0) # pixmap = QPixmap() # pixmap.loadFromData(buffer.getvalue()) # return pixmap # except Exception as e: # print(f"图片加载/压缩失败: {e}") # return QPixmap() # def _add_repair_suggestion(self, layout): # """添加维修建议部件""" # repair_suggestion_label = QLabel("维修建议:") # self.repair_suggestion_edit = QTextEdit() # self.repair_suggestion_edit.setPlaceholderText("暂不处理/观察运行/建议(尽快)打磨维修/(雷雨季前)修复") # self.repair_suggestion_edit.setMaximumHeight(100) # repair_layout = QVBoxLayout() # repair_layout.addWidget(repair_suggestion_label) # repair_layout.addWidget(self.repair_suggestion_edit) # layout.addLayout(repair_layout) # def add_quexian_combo(self, page_layout): # """添加缺陷类型填写框 # Args: # page_layout (QtWidgets.QVBoxLayout): 页面布局对象 # """ # # 添加水平布局的ComboBox组 # layout = QHBoxLayout() # layout.setSpacing(0) # # 可见程度ComboBox # visibility_widget = QWidget() # visibility_layout = QHBoxLayout(visibility_widget) # visibility_layout.setSpacing(0) # visibility_combo = QComboBox() # visibility_combo.addItems(["轻微", "一般", "重要", "严重"]) # visibility_combo.setCurrentIndex(1) # 默认选择"一般" # visibility_label = QLabel("可见程度:") # visibility_layout.addWidget(visibility_label) # visibility_layout.addWidget(visibility_combo) # layout.addWidget(visibility_widget) # # 严重程度ComboBox # severity_widget = QWidget() # severity_layout = QHBoxLayout(severity_widget) # severity_layout.setSpacing(0) # severity_combo = QComboBox() # severity_combo.addItems(["轻微", "一般", "重要", "严重"]) # severity_combo.setCurrentIndex(1) # 默认选择"一般" # severity_label = QLabel("严重程度:") # severity_layout.addWidget(severity_label) # severity_layout.addWidget(severity_combo) # layout.addWidget(severity_widget) # # 紧急程度ComboBox # urgency_widget = QWidget() # urgency_layout = QHBoxLayout(urgency_widget) # urgency_layout.setSpacing(0) # urgency_combo = QComboBox() # urgency_combo.addItems(["不紧急", "一般", "紧急", "非常紧急"]) # urgency_combo.setCurrentIndex(1) # 默认选择"一般" # urgency_combo.setMinimumWidth(74) # urgency_label = QLabel("紧急程度:") # urgency_layout.addWidget(urgency_label) # urgency_layout.addWidget(urgency_combo) # layout.addWidget(urgency_widget) # vlayout = QVBoxLayout() # # 缺陷类型输入框 # defect_type_widget = QWidget() # defect_type_layout = QVBoxLayout(defect_type_widget) # defect_type_label = QLabel("缺陷类型:") # defect_type_label.setStyleSheet("font-size: 18px; font-weight: bold;") # self.defect_type_edit = QLineEdit() # self.defect_type_edit.setPlaceholderText("如:涂层损伤") # self.defect_type_edit.setStyleSheet("font-size: 18px; font-weight: bold;") # defect_type_layout.addWidget(defect_type_label) # defect_type_layout.addWidget(self.defect_type_edit) # vlayout.addWidget(defect_type_widget) # # 缺陷位置输入框 # defect_location_widget = QWidget() # defect_location_layout = QVBoxLayout(defect_location_widget) # defect_location_label = QLabel("缺陷位置:") # defect_location_label.setStyleSheet("font-size: 18px; font-weight: bold;") # self.defect_location_edit = QLineEdit() # self.defect_location_edit.setPlaceholderText("如:叶片ps面距叶根3m处") # self.defect_location_edit.setStyleSheet("font-size: 18px; font-weight: bold;") # defect_location_layout.addWidget(defect_location_label) # defect_location_layout.addWidget(self.defect_location_edit) # vlayout.addWidget(defect_location_widget) # # 缺陷尺寸输入框 # defect_size_widget = QWidget() # defect_size_layout = QVBoxLayout(defect_size_widget) # defect_size_label = QLabel("缺陷尺寸(mm):") # defect_size_label.setStyleSheet("font-size: 18px; font-weight: bold;") # self.defect_size_edit = QLineEdit() # self.defect_size_edit.setPlaceholderText("如:弦向100mm,轴向800mm") # self.defect_size_edit.setStyleSheet("font-size: 18px; font-weight: bold;") # defect_size_layout.addWidget(defect_size_label) # defect_size_layout.addWidget(self.defect_size_edit) # vlayout.addWidget(defect_size_widget) # page_layout.setSpacing(0) # page_layout.addLayout(layout) # page_layout.addLayout(vlayout) # def get_image_paths(self, directory, Y): # image_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp'} # image_paths = [] # # 使用 os.walk # for root, _, files in os.walk(directory): # for file in files: # if os.path.splitext(file)[1].lower() in image_extensions: # image_paths.append((os.path.join(root, file), Y)) # return image_paths def init_dlabel(self,scrollArea): """初始化图片控件 汇总部分 Args: scrollArea (QtWidgets.QScrollArea): 页面控件对象 """ area = scrollArea for i in range(3): for j in range(6): container = QWidget() container.setFixedSize(150,150) v_layout = QVBoxLayout(container) label = DLabel(messagebrowser=self.ui.messagebrowser) label.setGeometry(10, 10, 150, 150) v_layout.addWidget(label) line_edit = QLineEdit() line_edit.setPlaceholderText("输入图片拍摄点位") line_edit.setText(self.photo_pos[j]) v_layout.addWidget(line_edit) self.gridlayout[i].addWidget(container, self.current_row[i], self.current_col[i]) self.current_col[i] += 1 if self.current_col[i] >= 5: self.current_col[i] = 0 self.current_row[i] += 1 self.picture_line[i].append(line_edit) self.picture_labels[i].append(label) self.label_init_list[i].append(label) self.line_init_list[i].append(line_edit) def add_dlabel(self, i, scrollArea, text = None, picture_path = None, ifauto = False): """增加图片控件 Args: i (int): 第几个页面 scrollArea (QtWidgets.QScrollArea): 页面控件对象 """ area = scrollArea container = QWidget() container.setFixedSize(150,150) v_layout = QVBoxLayout(container) label = DLabel(messagebrowser=self.ui.messagebrowser) label.setGeometry(10, 10, 150, 150) if picture_path: label.load_image(picture_path) v_layout.addWidget(label) line_edit = QLineEdit() line_edit.setPlaceholderText("输入图片拍摄点位") if text: line_edit.setText(text) v_layout.addWidget(line_edit) self.gridlayout[i].addWidget(container, self.current_row[i], self.current_col[i]) self.current_col[i] += 1 if self.current_col[i] >= 5: self.current_col[i] = 0 self.current_row[i] += 1 self.picture_line[i].append(line_edit) self.picture_labels[i].append(label) self.new_add_dlabel[i].append(label) self.new_add_lines[i].append(line_edit) return label, line_edit def empty_line_set(self): """遍历整个页面,如果有输入框为空,则置为‘未输入’""" widget = self.ui.tabWidget # 最根组件 # 遍历所有的标签页 for i in range(widget.count()): tab = widget.widget(i) # 假设输入框是通过布局(例如 QVBoxLayout 或 QHBoxLayout)来组织的 # 如果输入框是直接作为子部件添加的,可以使用 tab.children() 来获取所有子部件 for child in tab.findChildren(QtWidgets.QLineEdit): if not child.text().strip(): # 如果输入框为空(包括空白字符) child.setText('未输入') # 设置为“未输入” def check_dir(self, dirs: list[str]): """检查目录是否存在,不存在则提示不可达""" missing_dirs = [] ifmisiing = False for directory in dirs: if not os.path.isdir(directory): missing_dirs.append(directory) ifmisiing = True if ifmisiing: QtWidgets.QMessageBox.warning(None, "提示", f"以下目录不存在:\n{missing_dirs}\n请检查后重试") return False return True def bind_directory_browser(self, line_edit: QLineEdit, button: QPushButton): """绑定目录浏览功能到QLineEdit和QPushButton Args: line_edit: 显示和存储目录路径的QLineEdit组件 button: 触发目录浏览的QPushButton组件 """ def browse_directory(): # 获取当前line_edit中的路径 current_dir = line_edit.text().strip() # 检查路径是否存在,不存在则使用根目录 if not current_dir or not QDir(current_dir).exists(): current_dir = QDir.rootPath() # 打开目录选择对话框 selected_dir = QFileDialog.getExistingDirectory( button, # 父组件 "选择目录", # 标题 current_dir, # 初始目录 QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks # 选项 ) # 如果用户选择了目录(未点击取消),则更新line_edit if selected_dir: line_edit.setText(QDir.toNativeSeparators(selected_dir)) # 连接按钮的点击信号到浏览函数 button.clicked.connect(browse_directory) def tongbu_time(self): self.dateyear = self.ui.calendarWidget.selectedDate().toString('yyyy年') unit_map = {'1' : '一', '2' : '二', '3' : '三', '4' : '四', '5' : '五', '6' : '六', '7' : '七', '8' : '八', '9' : '九', '0' : '〇'} unit_map_month = {1 : '一', 2 : '二', 3 : '三', 4 : '四', 5 : '五', 6 : '六', 7 : '七', 8 : '八', 9 : '九', 10 : '十', 11 : '十一', 12 : '十二'} self.dateyear = self.dateyear.translate(str.maketrans(unit_map)) self.datemonth = self.ui.calendarWidget.selectedDate().toString('MM') self.datemonth = (unit_map_month[int(self.datemonth)] + '月') self.ui.date.setText(self.dateyear + self.datemonth) self.ui.bianzhishijian.setText(self.ui.calendarWidget.selectedDate().toString('yyyy/MM/dd')) self.ui.conclusion_date.setText(self.ui.calendarWidget.selectedDate().toString('yyyy/MM/dd')) self.ui.messagebrowser.append("检测到手动更改报告时间,自动同步编制时间、总结时间") class XiangmuBaseInfo: """项目基本信息类""" def __init__(self, tupian_dir, project_info_data): self.tupian_dir = tupian_dir if len(project_info_data) > 0: self.jituan_name = project_info_data["甲方集团"] self.fengchang_name = project_info_data["风场名"] self.jizu_type = project_info_data["机组型号"] self.company_name_yi = project_info_data["乙方公司"] self.company_name_jia = project_info_data["甲方公司"] self.project_location = project_info_data["风场地址"] self.fuzeren = project_info_data["负责人"] self.phone_fuzeren = project_info_data["负责人电话"] self.xiangmuguige = project_info_data["项目规格"] else: self.jituan_name = '' self.fengchang_name = '' self.jizu_type = '' self.company_name_yi = '' self.company_name_jia = '' self.project_location = '' self.fuzeren = '' self.phone_fuzeren = '' self.xiangmuguige = '' self.Picture_dir = tupian_dir self.project_dir = '' tupian_dir = Path(tupian_dir) folder_names = [f.name for f in tupian_dir.iterdir() if f.is_dir()] #通过图片路径获取各叶片编号 self.Y1 = folder_names[0] self.Y2 = folder_names[1] self.Y3 = folder_names[2] def get_base_info(self): '''返回项目基本信息 Returns: tuple: jituan_name, fengchang_name, jizu_type, company_name_yi, company_name_jia, project_location, fuzeren, phone_fuzeren, Y1, Y2, Y3, xiangmuguige ''' return self.jituan_name, self.fengchang_name, self.jizu_type, self.company_name_yi, self.company_name_jia, self.project_location, self.fuzeren, self.phone_fuzeren, self.Y1, self.Y2, self.Y3, self.xiangmuguige def get_xiangmu_base_info(tupian_dir, project = None, exe_dir = None, ifdatabase = False): """获取项目基本信息 Args: tupian_dir (str): 图片文件夹路径 project: 项目名称(通过此项目名称查找本地/数据库对应项目信息) 未实现 Returns: XiangmuBaseInfo: 项目基本信息对象 """ if not project: return XiangmuBaseInfo(tupian_dir, {}) if not ifdatabase and project and exe_dir: # 构建项目信息JSON文件的路径 project_info_path = exe_dir + rf"\{project}.json" # 检查文件是否存在 if os.path.exists(project_info_path): # 读取并解析JSON文件中的数据 with open(project_info_path, 'r', encoding='utf-8') as file: project_info_data = json.load(file) else: # 如果文件不存在,可以抛出异常或返回空信息 raise FileNotFoundError(f"项目信息文件未找到: {project_info_path}") return XiangmuBaseInfo(tupian_dir, project_info_data) if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) # 创建QApplication实例 QCoreApplication.setOrganizationName('DTAI') QCoreApplication.setApplicationName('baogao_shengcheng') # 创建并显示主窗口 window = MainWindow() window.show() # 运行应用程序 sys.exit(app.exec())