ReportGeneratorLocal/info_core/MyQtClass.py

1085 lines
43 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from PySide6.QtWidgets import (QGroupBox, QVBoxLayout, QHBoxLayout, QCheckBox,
QPushButton, QDialog, QLineEdit, QFontComboBox,
QFileDialog, QMessageBox, QLabel, QWidget,
QTextBrowser, QApplication, QCompleter, QFrame)
from PySide6.QtCore import QDateTime,QTimer,QDateTime,Signal,QSettings, QSortFilterProxyModel
from PySide6.QtGui import QPixmap, QDragEnterEvent, QDropEvent, Qt, QIcon
import json, sys, os
from info_core.defines import *
class ConfigComboBoxGroup(QGroupBox):
"""可复用的配置下拉框组件(带完整样式)"""
def __init__(self, title, allow_add=True, parent=None, is_project = True):
super().__init__(title, parent)
self.allow_add = allow_add
self.is_project = is_project
self.config_dir = os.path.join(os.getcwd(), "config", title)
# 确保配置目录存在
os.makedirs(self.config_dir, exist_ok=True)
# 设置组框样式
self.setStyleSheet(GROUP_BOX_STYLE)
self.setMinimumSize(GROUP_BOX_MIN_WIDTH, GROUP_BOX_MIN_HEIGHT)
# 创建UI
self.init_ui()
self.load_config_files()
def init_ui(self):
"""初始化UI组件"""
layout = QVBoxLayout()
layout.setSpacing(GROUP_BOX_SPACING)
layout.setContentsMargins(*GROUP_BOX_MARGINS)
self.combo_box_init()
# 按钮布局
btn_layout = QHBoxLayout()
btn_layout.addStretch() # 将按钮推到右侧
self.modify_btn = self.create_button("修改")
self.modify_btn.clicked.connect(self.on_modify_clicked)
btn_layout.addWidget(self.modify_btn)
if self.allow_add:
self.add_btn = self.create_button("添加")
self.add_btn.clicked.connect(self.on_add_clicked)
btn_layout.addWidget(self.add_btn)
self.delete_btn = self.create_button("删除")
self.delete_btn.clicked.connect(self.on_delete_clicked)
btn_layout.addWidget(self.delete_btn)
layout.addWidget(self.combo_box)
layout.addLayout(btn_layout)
self.setLayout(layout)
def combo_box_init(self):
# 下拉框
self.combo_box = QFontComboBox()
self.combo_box.setEditable(True)
# 代理模型(支持中文模糊匹配)
self.proxy_model = QSortFilterProxyModel()
self.proxy_model.setSourceModel(self.combo_box.model())
self.proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive)
# 补全器
self.completer = QCompleter(self.proxy_model, self.combo_box)
self.completer.setFilterMode(Qt.MatchContains)
self.combo_box.setCompleter(self.completer)
def create_button(self, text):
"""创建统一风格的按钮"""
btn = QPushButton(text)
btn.setStyleSheet(BUTTON_STYLE)
btn.setFixedSize(BUTTON_WIDTH, BUTTON_HEIGHT)
return btn
def load_config_files(self):
"""加载config文件夹中的JSON文件"""
self.combo_box.clear()
# 获取所有.json文件
config_files = [f for f in os.listdir(self.config_dir)
if f.endswith('.json')]
# 添加到下拉框(不带.json后缀
for file in config_files:
self.combo_box.addItem(file[:-5])
def on_modify_clicked(self):
"""修改按钮点击事件"""
if self.is_project:
project_widget = AddProjectDialog(self.config_dir, self, os.path.join(self.config_dir, self.combo_box.currentText() + ".json"))
project_widget.exec()
else:
person_widget = AddPersonnelDialog(self.config_dir, self, os.path.join(self.config_dir, self.combo_box.currentText() + ".json"))
person_widget.exec()
self.load_config_files()
def on_add_clicked(self):
"""添加按钮点击,创建默认ConfigEditDialog"""
if self.is_project:
project_widget = AddProjectDialog(self.config_dir, self)
project_widget.exec()
else:
person_widget = AddPersonnelDialog(self.config_dir, self)
person_widget.exec()
self.load_config_files()
def on_delete_clicked(self):
"""删除选中的项目"""
# 获取当前选中的项目名称
selected_project_name = self.combo_box.currentText()
if selected_project_name:
# 确认删除操作
confirm_message = f"你确定要删除项目 '{selected_project_name}' 吗?此操作不可恢复!"
reply = QMessageBox.question(self, '警告', confirm_message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
# 构建项目文件的路径
project_file_path = os.path.join(self.config_dir, selected_project_name + ".json")
# 检查文件是否存在并删除
if os.path.exists(project_file_path):
os.remove(project_file_path)
else:
QMessageBox.warning(self, '警告', f"项目文件 '{selected_project_name}.json' 不存在。")
# 从UI中移除该项目
self.combo_box.removeItem(self.combo_box.currentIndex())
# 重新加载配置文件列表
self.load_config_files()
else:
QMessageBox.information(self, '信息', '删除操作已取消。')
else:
QMessageBox.warning(self, '警告', '没有选中任何项目。')
class AddProjectDialog(QDialog):
def __init__(self, dir, parent=None, path=None, combobox=None):
super(AddProjectDialog, self).__init__(parent)
self.setWindowTitle("添加/修改项目信息")
self.resize(800, 400) # 增加宽度以适应多列布局
self.dir = dir
self.path = path
self.combobox = combobox
# 定义所有表单项的配置
self.form_fields = [
{"key": "项目名称", "label": "项目名称:", "default": ""},
{"key": "风场名", "label": "风场名:", "default": ""},
{"key": "风场地址", "label": "风场地址:", "default": ""},
{"key": "甲方公司", "label": "甲方公司:", "default": ""},
{"key": "甲方负责人", "label": "甲方负责人:", "default": ""},
{"key": "甲方负责人电话", "label": "甲方负责人电话:", "default": ""},
{"key": "乙方公司", "label": "乙方公司:", "default": "武汉迪特聚能科技有限公司"},
{"key": "乙方负责人", "label": "乙方负责人:", "default": ""},
{"key": "乙方负责人电话", "label": "乙方负责人电话:", "default": ""},
{"key": "项目规格", "label": "项目规格:", "default": ""},
{"key": "项目工期", "label": "项目工期:", "default": ""},
]
# 加载现有数据(如果有)
self.data = {}
if self.path:
self.load_existing_data()
self.init_ui()
def load_existing_data(self):
"""加载现有项目数据"""
try:
with open(self.path, 'r', encoding='utf-8') as f:
self.data = json.load(f)
except json.JSONDecodeError:
QMessageBox.critical(self, "错误", "JSON 文件格式错误!")
except Exception as e:
QMessageBox.critical(self, "错误", f"加载文件失败: {str(e)}")
def init_ui(self):
"""初始化用户界面"""
main_layout = QVBoxLayout()
# 创建表单布局
form_widget = QWidget()
form_layout = QHBoxLayout(form_widget)
# 创建两列布局
column1 = QVBoxLayout()
column2 = QVBoxLayout()
# 将表单字段添加到列中
self.fields = {}
for i, field in enumerate(self.form_fields):
# 创建标签和输入框
label = QLabel(field["label"])
edit = QLineEdit()
# 设置默认值或从现有数据加载
if self.path and field["key"] in self.data:
edit.setText(self.data[field["key"]])
else:
edit.setText(field["default"])
# 存储输入框引用
self.fields[field["key"]] = edit
# 根据索引决定添加到哪一列
if i < len(self.form_fields) / 2:
column1.addWidget(label)
column1.addWidget(edit)
else:
column2.addWidget(label)
column2.addWidget(edit)
# 添加间距和弹性空间
column1.addStretch()
column2.addStretch()
# 将列添加到表单布局
form_layout.addLayout(column1)
form_layout.addSpacing(20) # 列间距
form_layout.addLayout(column2)
# 创建按钮
self.save_button = QPushButton("保存")
self.save_button.clicked.connect(self.on_save_button_clicked)
# 添加到主布局
main_layout.addWidget(form_widget)
main_layout.addWidget(self.save_button)
self.setLayout(main_layout)
def on_save_button_clicked(self):
"""保存按钮点击事件"""
# 收集所有字段数据
project_info = {
field["key"]: self.fields[field["key"]].text()
for field in self.form_fields
}
project_info["json路径"] = self.dir
# 验证保存目录
if not os.path.exists(self.dir) or not os.path.isdir(self.dir):
QMessageBox.warning(self, "警告", "请选择一个有效的保存项目信息的文件夹路径")
return
# 构建默认文件名
default_filename = f"{project_info['项目名称']}.json"
# 让用户选择文件名
file_path, _ = QFileDialog.getSaveFileName(
self,
"保存项目信息",
os.path.join(self.dir, default_filename),
"JSON Files (*.json)"
)
if not file_path:
QMessageBox.warning(self, "警告", "未选择文件名")
return
# 保存项目信息到文件
try:
with open(file_path, 'w', encoding='utf-8') as file:
json.dump(project_info, file, ensure_ascii=False, indent=4)
except Exception as e:
QMessageBox.critical(self, "错误", f"保存文件失败: {str(e)}")
return
self.parent().load_config_files()
# 提示用户保存成功
QMessageBox.information(self, "信息", f"项目信息已成功保存到 {file_path}")
# 关闭对话框
self.close()
class AddPersonnelDialog(QDialog):
def __init__(self, dir, parent=None, path=None, combobox=None):
super(AddPersonnelDialog, self).__init__(parent)
self.setWindowTitle("添加/修改单次检查信息")
self.resize(800, 400) # 增加宽度以适应多列布局
self.dir = dir
self.path = path
self.combobox = combobox
# 定义所有表单项的配置
self.form_fields = [
{"key": "检查人员", "label": "检查人员:", "default": ""},
{"key": "厂家人员", "label": "厂家人员:", "default": ""},
{"key": "业主人员", "label": "业主人员:", "default": ""},
{"key": "数据处理人员", "label": "数据处理人员:", "default": ""},
{"key": "报告编制人员", "label": "报告编制人员:", "default": ""},
{"key": "机组型号", "label": "机组型号:", "default": ""},
{"key": "机组厂家", "label": "机组厂家:", "default": ""},
{"key": "施工日期", "label": "施工日期", "default": "开始-结束"},
{"key": "", "label": "", "default": ""},
#{"key": "", "label": "", "default": ""},
# 可以轻松添加更多人员字段
]
# 添加复选框字段
self.checkbox_fields = [
{"key": "外部检查", "label": "外部检查", "default": False},
{"key": "内部检查", "label": "内部检查", "default": False},
{"key": "防雷检查", "label": "防雷检查", "default": False}
]
# 加载现有数据(如果有)
self.data = {}
if self.path:
self.load_existing_data()
self.init_ui()
def load_existing_data(self):
"""加载现有人员数据"""
try:
with open(self.path, 'r', encoding='utf-8') as f:
self.data = json.load(f)
except json.JSONDecodeError:
QMessageBox.critical(self, "错误", "JSON 文件格式错误!")
except Exception as e:
QMessageBox.critical(self, "错误", f"加载文件失败: {str(e)}")
def init_ui(self):
"""初始化用户界面"""
main_layout = QVBoxLayout()
# 创建表单布局
form_widget = QWidget()
form_layout = QHBoxLayout(form_widget)
# 创建两列布局
column1 = QVBoxLayout()
column2 = QVBoxLayout()
# 将表单字段添加到列中
self.fields = {}
for i, field in enumerate(self.form_fields):
# 创建标签和输入框
label = QLabel(field["label"])
edit = QLineEdit()
# 设置默认值或从现有数据加载
if self.path and field["key"] in self.data:
edit.setText(self.data[field["key"]])
else:
edit.setText(field["default"])
# 存储输入框引用
self.fields[field["key"]] = edit
# 根据索引决定添加到哪一列
if i < len(self.form_fields) / 2:
column1.addWidget(label)
column1.addWidget(edit)
else:
column2.addWidget(label)
column2.addWidget(edit)
# 添加间距和弹性空间
column1.addStretch()
column2.addStretch()
# 将列添加到表单布局
form_layout.addLayout(column1)
form_layout.addSpacing(20) # 列间距
form_layout.addLayout(column2)
# 创建按钮
self.save_button = QPushButton("保存")
self.save_button.clicked.connect(self.on_save_button_clicked)
# 添加到主布局
main_layout.addWidget(form_widget)
main_layout.addWidget(self.save_button)
self.setLayout(main_layout)
def on_save_button_clicked(self):
"""保存按钮点击事件"""
# 收集所有字段数据
personnel_info = {
field["key"]: self.fields[field["key"]].text()
for field in self.form_fields
}
personnel_info["json路径"] = self.dir
# 验证保存目录
if not os.path.exists(self.dir) or not os.path.isdir(self.dir):
QMessageBox.warning(self, "警告", "请选择一个有效的保存人员信息的文件夹路径")
return
# 构建默认文件名
default_filename = "人员信息.json"
# 让用户选择文件名
file_path, _ = QFileDialog.getSaveFileName(
self,
"保存人员信息",
os.path.join(self.dir, default_filename),
"JSON Files (*.json)"
)
if not file_path:
QMessageBox.warning(self, "警告", "未选择文件名")
return
# 保存人员信息到文件
try:
with open(file_path, 'w', encoding='utf-8') as file:
json.dump(personnel_info, file, ensure_ascii=False, indent=4)
except Exception as e:
QMessageBox.critical(self, "错误", f"保存文件失败: {str(e)}")
return
# 更新QComboBox
if self.combobox is None:
self.parent().ui.personnel.addItem(os.path.basename(file_path))
else:
self.combobox.load_config_files(os.path.basename(self.dir))
# 提示用户保存成功
QMessageBox.information(self, "信息", f"人员信息已成功保存到 {file_path}")
# 关闭对话框
self.close()
class SmartDropdown(QWidget):
"""
支持动态同步的智能下拉框
-全局可保存共享选择池
-可编辑共享池
存储形式:
"""
shared_pool = {} # 类变量存储共享选项
class MyFontComboBox(QFontComboBox):
def __init__(self, parent=None):
super().__init__(parent)
# 获取内部的QLineEdit部件
self.line_edit = self.lineEdit()
# 当获得焦点时全选文本
self.line_edit.selectionChanged.connect(self.select_all_text)
def select_all_text(self):
if self.line_edit.hasFocus():
self.line_edit.selectAll()
@classmethod
def update_shared_pool(cls, dropdown_type, options):
"""更新指定类别的共享选项"""
if not options: # 如果选项为空,添加默认选项
options = ["未选中"]
cls.shared_pool[dropdown_type] = list(options)
cls.save_shared_pool()
# 自动刷新所有该类型的下拉框
for widget in QApplication.allWidgets():
if isinstance(widget, SmartDropdown) and widget.dropdown_type == dropdown_type:
widget._load_options()
@classmethod
def save_shared_pool(cls):
"""保存共享池到QSettings"""
settings = QSettings()
settings.setValue("SmartDropdown/shared_pool", cls.shared_pool)
@classmethod
def load_shared_pool(cls):
"""从QSettings加载共享池"""
settings = QSettings()
pool = settings.value("SmartDropdown/shared_pool", {})
cls.shared_pool = {k: v for k, v in pool.items()} if pool else {}
# 确保每个类别至少有一个默认选项
for dropdown_type in cls.shared_pool:
if not cls.shared_pool[dropdown_type]:
cls.shared_pool[dropdown_type] = ["未选中"]
def __init__(self, dropdown_type, messagebrowser, label_text=None, parent=None):
super().__init__(parent)
self.dropdown_type = dropdown_type
self.label_text = label_text
self.messagebrowser = messagebrowser
# 初始化共享池
if not self.shared_pool:
self.load_shared_pool()
# 确保当前类型在共享池中存在
if self.dropdown_type not in self.shared_pool:
self.shared_pool[self.dropdown_type] = ["未选中"]
self.save_shared_pool()
self._setup_ui()
self._load_options()
self._initialize_defect_type()
self.messagebrowser.append(f"初始化下拉框: {dropdown_type}")
@property
def current_index(self):
"""当前选中索引0表示未选中"""
return self.combo_box.currentIndex()
@current_index.setter
def current_index(self, index):
if self.combo_box.count() > 0:
clamped_index = max(0, min(index, self.combo_box.count()-1))
self.combo_box.setCurrentIndex(clamped_index)
def _initialize_defect_type(self):
"""初始化缺陷类型选项,如果选项不足则自动重置"""
if self.dropdown_type == "缺陷类型":
# 检查当前选项数量是否不足
if len(self.shared_pool.get(self.dropdown_type, [])) <= 1:
default_options = [
"未选中...", "前缘涂层损伤(风损)", "表面裂纹", "腻子脱落", "漆面脱落",
"油污","布层破损(深层、不规则面状)", "裂纹(浅层线状)", "开裂(深层线带窄面状)",
"贯穿损伤", "鼓包", "凹陷", "雷击45d", "雷击zzw", "雷击ck",
"合模缝开裂", "透光", "雷击碳化", "褶皱", "脱胶", "缺胶",
"防雷线断裂", "开裂"
]
self.update_shared_pool(self.dropdown_type, default_options)
self.messagebrowser.append(
f"初始化提示: 检测到{self.dropdown_type}选项不足,已自动重置为默认选项集"
f"(原因: 第一次初始化或选项被清空)"
)
def _setup_ui(self):
"""初始化UI组件"""
self.combo_box = QFontComboBox()
self.combo_box.setEditable(True)
# 操作按钮
self.add_btn = QPushButton("+添加输入的类别")
self.del_btn = QPushButton("-删除选中类别")
self.refresh_btn = QPushButton()
self.refresh_btn.setIcon(QIcon.fromTheme("view-refresh")) # 使用系统刷新图标
self.reset_btn = QPushButton("重置")
# 设置按钮大小
for btn in [self.add_btn, self.del_btn, self.reset_btn]:
btn.setFixedWidth(150)
self.refresh_btn.setFixedWidth(30)
# 设置工具提示
self.add_btn.setToolTip("添加选项到共享池")
self.del_btn.setToolTip("从共享池删除当前选项")
self.refresh_btn.setToolTip("同步共享池选项")
self.reset_btn.setToolTip("重置为默认选项")
# 按钮布局
btn_layout = QHBoxLayout()
btn_layout.addWidget(self.add_btn)
btn_layout.addWidget(self.del_btn)
btn_layout.addWidget(self.reset_btn)
btn_layout.addWidget(self.refresh_btn)
btn_layout.setSpacing(2)
btn_layout.setContentsMargins(0, 0, 0, 0)
# 主布局
main_layout = QVBoxLayout()
main_layout.setSpacing(5)
if self.label_text:
main_layout.addWidget(QLabel(self.label_text))
main_layout.addWidget(self.combo_box)
main_layout.addLayout(btn_layout)
self.setLayout(main_layout)
# 信号连接
self.add_btn.clicked.connect(self._add_option)
self.del_btn.clicked.connect(self._del_option)
self.refresh_btn.clicked.connect(self._refresh_options)
self.reset_btn.clicked.connect(self._reset_options)
self.combo_box.currentIndexChanged.connect(self._update_buttons)
def _reset_options(self):
"""重置选项为默认值"""
if self.dropdown_type == "缺陷类型":
default_options = [
"未选中...", "前缘涂层损伤(风损)", "表面裂纹", "腻子脱落", "漆面脱落",
"油污","布层破损(深层、不规则面状)", "裂纹(浅层线状)", "开裂(深层线带窄面状)",
"贯穿损伤", "鼓包", "凹陷", "雷击45d", "雷击zzw", "雷击ck",
"合模缝开裂", "透光", "雷击碳化", "褶皱", "脱胶", "缺胶",
"防雷线断裂", "开裂"
]
reply = QMessageBox.question(
self, '确认重置',
'确定要重置缺陷类型为默认选项吗?此操作不可撤销!',
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No)
if reply == QMessageBox.StandardButton.Yes:
self.update_shared_pool(self.dropdown_type, default_options)
self.messagebrowser.append(f"已重置 {self.dropdown_type} 为默认选项")
else:
QMessageBox.information(
self, '提示',
f'当前类型 {self.dropdown_type} 没有预定义的重置选项')
def _load_options(self):
"""加载选项(确保只有一个未选中选项)"""
current_text = self.combo_box.currentText() if self.combo_box.count() > 0 else ""
self.combo_box.blockSignals(True) # 防止触发信号
self.combo_box.clear()
# 确保共享池中有当前类型的选项
if (self.dropdown_type not in self.shared_pool or
not self.shared_pool[self.dropdown_type]):
self.shared_pool[self.dropdown_type] = ["未选中"]
self.save_shared_pool()
# 直接从共享池加载选项
self.combo_box.addItems(self.shared_pool[self.dropdown_type])
# 恢复之前的选中项(如果有)
if current_text:
idx = self.combo_box.findText(current_text)
if idx >= 0:
self.combo_box.setCurrentIndex(idx)
self.combo_box.blockSignals(False)
self._update_buttons()
self.messagebrowser.append(f"加载选项: {self.dropdown_type} - {self.shared_pool[self.dropdown_type]}")
def _refresh_options(self):
"""手动刷新选项"""
self.messagebrowser.append(f"手动刷新选项: {self.dropdown_type}")
self._load_options()
def _update_buttons(self):
"""根据当前状态更新按钮可用性"""
has_options = len(self.shared_pool.get(self.dropdown_type, [])) > 0
has_selection = self.current_index >= 0
self.del_btn.setEnabled(has_options and has_selection)
self.add_btn.setEnabled(True)
self.refresh_btn.setEnabled(True)
self.reset_btn.setEnabled(True)
def _add_option(self):
"""添加新选项到共享池"""
new_option = self.combo_box.currentText().strip()
if not new_option:
return
if new_option not in self.shared_pool[self.dropdown_type]:
if QMessageBox.Yes == QMessageBox.question(
self, "确认添加",
f"添加 '{new_option}''{self.dropdown_type}' 选项池?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No):
if self.dropdown_type not in self.shared_pool:
self.shared_pool[self.dropdown_type] = ["未选中"]
self.shared_pool[self.dropdown_type].append(new_option)
self.messagebrowser.append(f"添加选项: {new_option}{self.dropdown_type}")
self.update_shared_pool(self.dropdown_type, self.shared_pool[self.dropdown_type])
self.combo_box.setCurrentIndex(self.combo_box.findText(new_option))
else:
self.messagebrowser.append(f"添加失败: 选项 {new_option} 已存在于 {self.dropdown_type} 选项池")
def _del_option(self):
"""从共享池删除当前选项"""
if self.current_index < 0: # 无效选择
return
current_text = self.combo_box.currentText()
if current_text == "未选中":
self.messagebrowser.append("不能删除默认的'未选中'选项")
return
if QMessageBox.Yes == QMessageBox.question(
self, "确认删除",
f"'{self.dropdown_type}' 删除 '{current_text}'",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No):
try:
self.shared_pool[self.dropdown_type].remove(current_text)
# 如果删除后选项为空,恢复默认选项
if not self.shared_pool[self.dropdown_type]:
self.shared_pool[self.dropdown_type] = ["未选中"]
self.messagebrowser.append(f"删除选项: {current_text}{self.dropdown_type}")
self.update_shared_pool(self.dropdown_type, self.shared_pool[self.dropdown_type])
except ValueError:
self.messagebrowser.append(f"删除失败: 选项 {current_text} 不存在")
class FolderImportWidget(QGroupBox):
"""支持双路径独立导入的组件"""
main_folder_selected = Signal(str) # 仅总图片路径变化时发射
selected_folder_selected = Signal(str) # 选中路径变化时发射
get_baogao_choose_info = Signal(str) # 获取报告选择
def __init__(self, title: str = "图片文件夹导入", parent=None):
super().__init__(title, parent)
self.main_folder = None
self.selected_folder = None
self.setup_ui()
self.apply_styles()
def setup_ui(self):
"""设置UI布局"""
self.setMinimumSize(GROUP_BOX_MIN_WIDTH, GROUP_BOX_MIN_HEIGHT)
main_layout = QVBoxLayout()
main_layout.setSpacing(GROUP_BOX_SPACING)
main_layout.setContentsMargins(*GROUP_BOX_MARGINS)
# ================= 总图片路径区域 =================
self.main_group = DropGroupBox("总图片路径(自动解析数量,生成略缩图)")
main_group_layout = QVBoxLayout()
# 拖拽区域
self.main_drag_label = QLabel("拖拽文件夹到此处或点击按钮选择")
self.main_drag_label.setProperty("class", "waiting-label")
# 路径显示
self.main_path_label = QLabel("未选择")
self.main_path_label.setProperty("class", "info-display")
# 选择按钮
self.main_select_btn = QPushButton("选择总图片文件夹")
self.main_select_btn.setProperty("class", "normal-button")
main_group_layout.addWidget(self.main_drag_label)
main_group_layout.addWidget(self.main_path_label)
main_group_layout.addWidget(self.main_select_btn, 0, Qt.AlignmentFlag.AlignHCenter)
self.main_group.setLayout(main_group_layout)
self.main_group.setAcceptDrops(True)
# ================= 选中图片路径区域 =================
self.selected_group = DropGroupBox("选中图片路径(获取典型部位图片,自动读取图片名做描述)")
selected_group_layout = QVBoxLayout()
# 拖拽区域
self.selected_drag_label = QLabel("拖拽文件夹到此处或点击按钮选择")
self.selected_drag_label.setProperty("class", "waiting-label")
# 路径显示
self.selected_path_label = QLabel("未选择")
self.selected_path_label.setProperty("class", "info-display")
# 选择按钮
self.selected_select_btn = QPushButton("选择图片文件夹")
self.selected_select_btn.setProperty("class", "normal-button")
self.modify_layout = QHBoxLayout()
self.modify_picture_quexian_btn = QPushButton("修改图片缺陷信息")
self.modify_picture_quexian_btn.setProperty("class", "normal-button")
self.modify_picture_quexian_btn.setEnabled(False)
self.modify_picture_quexian_btn.clicked.connect(self.on_modify_picture_quexian_btn_clicked)
self.modify_picture_huizong_btn = QPushButton("修改图片汇总信息")
self.modify_picture_huizong_btn.setProperty("class", "normal-button")
self.modify_picture_huizong_btn.setEnabled(False)
self.modify_picture_huizong_btn.clicked.connect(self.on_modify_picture_huizong_btn_clicked)
self.modify_group = QGroupBox("修改图片信息")
self.modify_layout.addWidget(self.modify_picture_quexian_btn, 0, Qt.AlignmentFlag.AlignHCenter)
self.modify_layout.addWidget(self.modify_picture_huizong_btn, 0, Qt.AlignmentFlag.AlignHCenter)
self.modify_layout.addStretch(1)
self.modify_group.setLayout(self.modify_layout)
selected_group_layout.addWidget(self.selected_drag_label)
selected_group_layout.addWidget(self.selected_path_label)
selected_group_layout.addWidget(self.selected_select_btn, 0, Qt.AlignmentFlag.AlignHCenter)
selected_group_layout.addWidget(self.modify_group)
self.selected_group.setLayout(selected_group_layout)
self.selected_group.setAcceptDrops(True)
# ================= 添加到主布局 =================
main_layout.addWidget(self.main_group)
main_layout.addWidget(self.selected_group)
self.setLayout(main_layout)
# 连接信号
self.main_select_btn.clicked.connect(lambda: self.select_folder("main"))
self.selected_select_btn.clicked.connect(lambda: self.select_folder("selected"))
self.search_file_list = []
def apply_styles(self):
"""应用样式"""
self.setStyleSheet(f"""
{GROUP_BOX_STYLE}
/* 内部GroupBox样式 */
QGroupBox QGroupBox {{
font-size: {TITLE_FONT_SIZE - 1}pt;
border: 1px solid #ced4da;
margin-top: 16px;
}}
QLabel[class="waiting-label"] {{
{WAITING_LABEL_STYLE}
font-size: {CONTENT_FONT_SIZE}pt;
}}
QLabel[class="info-display"] {{
{INFO_DISPLAY_STYLE}
min-height: 60px;
}}
QPushButton[class="normal-button"] {{
{BUTTON_STYLE}
min-width: 160px;
}}
""")
def select_folder(self, folder_type: str):
"""选择文件夹"""
dialog_title = "选择总图片文件夹" if folder_type == "main" else "选择图片文件夹"
folder = QFileDialog.getExistingDirectory(
self,
dialog_title,
os.path.expanduser("~"),
QFileDialog.Option.ShowDirsOnly | QFileDialog.Option.DontResolveSymlinks
)
if folder:
self.set_folder_path(folder, folder_type)
def set_folder_path(self, folder_path: str, folder_type: str):
"""设置文件夹路径并更新UI"""
if folder_type == "main":
self.main_folder = folder_path
self.main_path_label.setText(folder_path)
self.main_drag_label.hide()
self.main_folder_selected.emit(folder_path) # 触发解析
else:
self.selected_folder = folder_path
self.selected_path_label.setText(folder_path)
self.selected_drag_label.hide()
self.modify_picture_quexian_btn.setEnabled(True)
self.modify_picture_huizong_btn.setEnabled(True)
self.selected_folder_selected.emit(folder_path)
def dragEnterEvent(self, event: QDragEnterEvent):
"""总区域的拖拽事件(转发到对应子区域)"""
source = event.source()
if isinstance(source, QGroupBox):
# 如果是来自子区域的拖拽,交给子区域处理
return
# 否则检查是否是有效的文件夹拖拽
if event.mimeData().hasUrls():
urls = event.mimeData().urls()
if len(urls) == 1 and urls[0].isLocalFile():
file_info = urls[0].toLocalFile()
if os.path.isdir(file_info):
event.acceptProposedAction()
def dropEvent(self, event: QDropEvent):
"""总区域的放下事件(自动分配到第一个区域)"""
urls = event.mimeData().urls()
if urls and urls[0].isLocalFile():
folder_path = urls[0].toLocalFile()
if os.path.isdir(folder_path):
self.set_folder_path(folder_path, "main")
def get_paths(self) -> tuple:
"""获取两个路径(总路径,选中路径)"""
return self.main_folder, self.selected_folder
def get_baogao_choose(self):
self.get_baogao_choose_info.emit()
def update_baogao_choose_info(self, info: list[str]):
self.search_file_list = info
def on_modify_picture_quexian_btn_clicked(self):
"""修改图片缺陷类型"""
# self.get_baogao_choose()
# if self.search_file_list:
# Y1_num, Y1 =
def on_modify_picture_huizong_btn_clicked(self):
"""修改图片汇总信息"""
self.get_baogao_choose()
# 子区域GroupBox需要单独处理拖拽事件
class DropGroupBox(QGroupBox):
"""支持拖拽的子区域GroupBox"""
def __init__(self, parent=None):
super().__init__(parent)
self.setAcceptDrops(True)
def dragEnterEvent(self, event: QDragEnterEvent):
if event.mimeData().hasUrls():
urls = event.mimeData().urls()
if len(urls) == 1 and urls[0].isLocalFile():
file_info = urls[0].toLocalFile()
if os.path.isdir(file_info):
event.acceptProposedAction()
return
event.ignore()
def dropEvent(self, event: QDropEvent):
urls = event.mimeData().urls()
if urls and urls[0].isLocalFile():
folder_path = urls[0].toLocalFile()
if os.path.isdir(folder_path):
# 通过parent()调用主组件的方法
parent = self.parent()
while parent and not isinstance(parent, FolderImportWidget):
parent = parent.parent()
if parent:
folder_type = "main" if self == parent.main_group else "selected"
parent.set_folder_path(folder_path, folder_type)
class ImageAnalysisWidget(QGroupBox):
"""完全使用宏定义样式的图片解析组件"""
generate_path_selected = Signal(str)
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("图片解析")
self.image_folder_path = None
self.generate_path = None
self.setup_ui()
self.apply_styles()
def setup_ui(self):
"""设置UI布局"""
self.setMinimumSize(GROUP_BOX_MIN_WIDTH, GROUP_BOX_MIN_HEIGHT)
main_layout = QVBoxLayout()
main_layout.setSpacing(GROUP_BOX_SPACING)
main_layout.setContentsMargins(*GROUP_BOX_MARGINS)
# 初始状态 - 等待导入
self.waiting_label = QLabel("请先导入图片文件夹以开始解析")
self.waiting_label.setProperty("class", "waiting-label")
# 解析结果区域
self.result_frame = QFrame()
result_layout = QVBoxLayout()
result_layout.setSpacing(15)
# 图片文件夹信息
folder_info_layout = QHBoxLayout()
folder_label = QLabel("图片文件夹:")
folder_label.setProperty("class", "info-label")
self.folder_path_label = QLabel("未选择")
self.folder_path_label.setProperty("class", "info-display")
folder_info_layout.addWidget(folder_label)
folder_info_layout.addWidget(self.folder_path_label)
# 图片数量信息
count_info_layout = QHBoxLayout()
count_label = QLabel("图片数量:")
count_label.setProperty("class", "info-label")
self.image_count_label = QLabel("-")
self.image_count_label.setProperty("class", "info-display")
count_info_layout.addWidget(count_label)
count_info_layout.addWidget(self.image_count_label)
# 分隔线
separator = QFrame()
separator.setFrameShape(QFrame.Shape.HLine)
separator.setProperty("class", "separator")
# 生成路径区域
path_info_layout = QHBoxLayout()
path_label = QLabel("生成路径:")
path_label.setProperty("class", "info-label")
self.generate_path_label = QLabel("未选择")
self.generate_path_label.setProperty("class", "info-display")
path_info_layout.addWidget(path_label)
path_info_layout.addWidget(self.generate_path_label)
# 选择按钮
self.select_path_button = QPushButton("选择生成路径")
self.select_path_button.setProperty("class", "normal-button")
self.select_path_button.clicked.connect(self.select_generate_path)
self.select_path_button.setEnabled(False)
# 生成报告选项
check_box_layout = QHBoxLayout()
self.check_is_waibu = QCheckBox("外部")
self.check_is_neibu = QCheckBox("内部")
self.check_is_fanglei = QCheckBox("防雷")
self.check_is_neibu.setStyleSheet(CHECKBOX_STYLE)
self.check_is_waibu.setStyleSheet(CHECKBOX_STYLE)
self.check_is_fanglei.setStyleSheet(CHECKBOX_STYLE)
check_box_layout.addWidget(self.check_is_waibu)
check_box_layout.addWidget(self.check_is_neibu)
check_box_layout.addWidget(self.check_is_fanglei)
# 添加到结果布局
result_layout.addLayout(folder_info_layout)
result_layout.addLayout(count_info_layout)
result_layout.addWidget(separator)
result_layout.addLayout(path_info_layout)
result_layout.addWidget(self.select_path_button, 0, Qt.AlignmentFlag.AlignHCenter)
result_layout.addLayout(check_box_layout)
result_layout.addStretch()
self.result_frame.setLayout(result_layout)
self.result_frame.hide()
# 添加到主布局
main_layout.addWidget(self.waiting_label)
main_layout.addWidget(self.result_frame)
self.setLayout(main_layout)
def apply_styles(self):
"""完全使用宏定义应用样式"""
self.setStyleSheet(GROUP_BOX_STYLE)
# 通过QSS类选择器应用样式
self.setStyleSheet(f"""
{self.styleSheet()}
/* 等待提示 */
QLabel[class="waiting-label"] {{
{WAITING_LABEL_STYLE}
}}
/* 信息标签 */
QLabel[class="info-label"] {{
{INFO_LABEL_STYLE}
}}
/* 信息显示 */
QLabel[class="info-display"] {{
{INFO_DISPLAY_STYLE}
}}
/* 分隔线 */
QFrame[class="separator"] {{
{SEPARATOR_STYLE}
}}
/* 普通按钮 */
QPushButton[class="normal-button"] {{
{BUTTON_STYLE}
}}
""")
def set_image_folder(self, folder_path: str):
"""设置图片文件夹路径并开始解析"""
self.image_folder_path = folder_path
self.folder_path_label.setText(folder_path)
self.start_analysis()
def start_analysis(self):
"""开始解析"""
self.total_picture_count = 0
for root, dirs, files in os.walk(self.image_folder_path):
for file in files:
# 检查文件扩展名是否为图片格式
if file.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff')):
self.total_picture_count += 1
self.waiting_label.hide()
self.image_count_label.setText(str(self.total_picture_count)) # 模拟解析结果
self.select_path_button.setEnabled(True)
self.result_frame.show()
def select_generate_path(self):
"""选择生成路径"""
path = QFileDialog.getExistingDirectory(
self,
"选择生成路径",
os.path.expanduser("~") or self.image_folder_path,
QFileDialog.Option.ShowDirsOnly | QFileDialog.Option.DontResolveSymlinks
)
if path:
self.set_generate_path(path)
def set_generate_path(self, path: str):
"""设置并显示生成路径"""
self.generate_path = path
self.generate_path_label.setText(path)
self.generate_path_selected.emit(path)