2025-08-04 18:08:53 +08:00
|
|
|
|
from PySide6.QtWidgets import (QGroupBox, QVBoxLayout, QHBoxLayout, QCheckBox,
|
|
|
|
|
QPushButton, QDialog, QLineEdit, QFontComboBox,
|
|
|
|
|
QFileDialog, QMessageBox, QLabel, QWidget,
|
2025-08-05 17:51:11 +08:00
|
|
|
|
QTextBrowser, QApplication, QCompleter, QFrame,
|
|
|
|
|
QListWidgetItem, QListWidget, QAbstractItemView,
|
2025-08-07 18:16:09 +08:00
|
|
|
|
QStackedWidget, QStackedLayout, QStyledItemDelegate,
|
2025-08-08 18:00:56 +08:00
|
|
|
|
QTreeView, QSplitter, QFileSystemModel, QScrollArea,
|
|
|
|
|
QToolTip, QGridLayout, QSizePolicy, QProgressDialog,
|
2025-08-11 17:58:06 +08:00
|
|
|
|
QButtonGroup, QInputDialog, QMenu, QComboBox, QDialogButtonBox,
|
|
|
|
|
QTextEdit, QStyle)
|
2025-08-08 18:00:56 +08:00
|
|
|
|
from PySide6.QtCore import (QDateTime,QTimer,QDateTime,Signal,QSettings, QPoint, QEvent,
|
|
|
|
|
QSortFilterProxyModel, QSize, QDir, QMimeData, QRunnable, QObject,
|
|
|
|
|
QThreadPool, QPoint, QRect, QUrl)
|
2025-08-05 17:51:11 +08:00
|
|
|
|
from PySide6.QtGui import (QPixmap, QDragEnterEvent, QDropEvent, Qt, QIcon,
|
2025-08-07 18:16:09 +08:00
|
|
|
|
QFontMetrics, QStandardItem, QStandardItemModel,
|
2025-08-08 18:00:56 +08:00
|
|
|
|
QAction, QColor, QImageReader, QDrag, QCursor,
|
|
|
|
|
QPainter)
|
2025-08-11 17:58:06 +08:00
|
|
|
|
import json, sys, os, re, time
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
2025-08-11 17:58:06 +08:00
|
|
|
|
from info_core.Img_edit import DefectMarkEditor
|
2025-08-04 18:08:53 +08:00
|
|
|
|
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, '警告', '没有选中任何项目。')
|
2025-08-05 17:51:11 +08:00
|
|
|
|
def get_current_config_file(self):
|
|
|
|
|
"""获取当前选中的配置文件"""
|
2025-08-07 18:16:09 +08:00
|
|
|
|
if self.combo_box.currentText():
|
|
|
|
|
return os.path.join(self.config_dir, self.combo_box.currentText() + ".json")
|
2025-08-05 17:51:11 +08:00
|
|
|
|
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
|
|
|
|
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": ""},
|
2025-08-05 17:51:11 +08:00
|
|
|
|
{"key": "叶片型号", "label": "叶片型号", "default": ""},
|
|
|
|
|
{"key": "叶片厂家", "label": "叶片厂家:", "default": ""},
|
2025-08-04 18:08:53 +08:00
|
|
|
|
{"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)
|
|
|
|
|
|
2025-08-05 17:51:11 +08:00
|
|
|
|
# 创建复选框布局
|
|
|
|
|
checkbox_group = QGroupBox("检查类型")
|
|
|
|
|
checkbox_layout = QHBoxLayout()
|
|
|
|
|
|
|
|
|
|
# 添加复选框
|
|
|
|
|
self.checkboxes = {}
|
|
|
|
|
for field in self.checkbox_fields:
|
|
|
|
|
checkbox = QCheckBox(field["label"])
|
|
|
|
|
if self.path and field["key"] in self.data:
|
|
|
|
|
checkbox.setChecked(bool(self.data[field["key"]]))
|
|
|
|
|
else:
|
|
|
|
|
checkbox.setChecked(field["default"])
|
|
|
|
|
self.checkboxes[field["key"]] = checkbox
|
|
|
|
|
checkbox_layout.addWidget(checkbox)
|
|
|
|
|
|
|
|
|
|
checkbox_group.setLayout(checkbox_layout)
|
|
|
|
|
|
2025-08-04 18:08:53 +08:00
|
|
|
|
# 添加到主布局
|
2025-08-05 17:51:11 +08:00
|
|
|
|
main_layout.addWidget(checkbox_group)
|
2025-08-04 18:08:53 +08:00
|
|
|
|
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
|
|
|
|
|
}
|
2025-08-05 17:51:11 +08:00
|
|
|
|
# 添加复选框数据
|
|
|
|
|
for key, checkbox in self.checkboxes.items():
|
|
|
|
|
personnel_info[key] = checkbox.isChecked()
|
|
|
|
|
|
2025-08-04 18:08:53 +08:00
|
|
|
|
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
|
|
|
|
|
|
2025-08-05 17:51:11 +08:00
|
|
|
|
self.parent().load_config_files()
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
|
|
|
|
# 提示用户保存成功
|
|
|
|
|
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} 不存在")
|
|
|
|
|
|
2025-08-05 17:51:11 +08:00
|
|
|
|
class FolderDropWidget(QWidget):
|
|
|
|
|
selection_changed = Signal(list)
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
2025-08-05 17:51:11 +08:00
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self.setAcceptDrops(True)
|
|
|
|
|
self.current_dir = None
|
|
|
|
|
self.selected_folders = set()
|
|
|
|
|
self.init_ui()
|
2025-08-04 18:08:53 +08:00
|
|
|
|
self.apply_styles()
|
2025-08-05 17:51:11 +08:00
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
main_layout = QVBoxLayout()
|
2025-08-06 17:56:29 +08:00
|
|
|
|
main_layout.setSpacing(5)
|
|
|
|
|
main_layout.setContentsMargins(0, 0, 0, 0)
|
2025-08-05 17:51:11 +08:00
|
|
|
|
|
|
|
|
|
# 顶部按钮区域
|
|
|
|
|
btn_layout = QHBoxLayout()
|
|
|
|
|
self.reset_btn = QPushButton("重新选择项目文件夹")
|
|
|
|
|
self.reset_btn.clicked.connect(self.reset_selection)
|
|
|
|
|
self.reset_btn.setFixedSize(120, 40)
|
|
|
|
|
self.reset_btn.setVisible(False)
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
2025-08-06 17:56:29 +08:00
|
|
|
|
self.clear_btn = QPushButton("清空")
|
2025-08-05 17:51:11 +08:00
|
|
|
|
self.clear_btn.clicked.connect(self.clear_selected)
|
|
|
|
|
self.clear_btn.setFixedSize(120, 40)
|
|
|
|
|
self.clear_btn.setVisible(False)
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
2025-08-06 17:56:29 +08:00
|
|
|
|
self.full_select_btn = QPushButton("全选")
|
|
|
|
|
self.full_select_btn.clicked.connect(self.select_all_folders)
|
|
|
|
|
self.full_select_btn.setFixedSize(120, 40)
|
|
|
|
|
self.full_select_btn.setVisible(False)
|
|
|
|
|
|
|
|
|
|
# 添加返回按钮
|
|
|
|
|
self.back_btn = QPushButton("返回项目选择")
|
|
|
|
|
self.back_btn.clicked.connect(lambda: (self.stacked_layout.setCurrentIndex(1) or self.clear_selected() or self.reset_btn.setVisible(True) or self.back_btn.setVisible(False)))
|
|
|
|
|
self.back_btn.setVisible(False)
|
|
|
|
|
|
2025-08-05 17:51:11 +08:00
|
|
|
|
btn_layout.addWidget(self.reset_btn)
|
2025-08-06 17:56:29 +08:00
|
|
|
|
btn_layout.addWidget(self.back_btn)
|
|
|
|
|
btn_layout.addWidget(self.full_select_btn)
|
2025-08-05 17:51:11 +08:00
|
|
|
|
btn_layout.addWidget(self.clear_btn)
|
|
|
|
|
btn_layout.addStretch()
|
|
|
|
|
main_layout.addLayout(btn_layout)
|
|
|
|
|
|
|
|
|
|
# 主内容区域 - 使用堆叠布局管理不同状态
|
|
|
|
|
self.stacked_layout = QStackedLayout()
|
|
|
|
|
|
|
|
|
|
# 状态1: 未选择文件夹时的提示
|
|
|
|
|
self.prompt_container = QWidget()
|
|
|
|
|
prompt_layout = QVBoxLayout()
|
|
|
|
|
self.prompt_label = QLabel("拖放文件夹到此处或点击选择文件夹")
|
|
|
|
|
self.prompt_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
|
self.prompt_label.mousePressEvent = self.select_folder
|
|
|
|
|
prompt_layout.addWidget(self.prompt_label)
|
|
|
|
|
self.prompt_container.setLayout(prompt_layout)
|
|
|
|
|
self.stacked_layout.addWidget(self.prompt_container)
|
|
|
|
|
|
|
|
|
|
# 状态2: 显示第一层文件夹 (项目选择)
|
|
|
|
|
self.level1_container = QWidget()
|
|
|
|
|
level1_main_layout = QVBoxLayout()
|
2025-08-06 17:56:29 +08:00
|
|
|
|
level1_main_layout.setSpacing(0)
|
|
|
|
|
level1_main_layout.setContentsMargins(0, 0, 0, 0)
|
2025-08-05 17:51:11 +08:00
|
|
|
|
|
|
|
|
|
# 添加状态标签
|
|
|
|
|
self.level1_title = QLabel("项目选择")
|
|
|
|
|
self.level1_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
|
level1_main_layout.addWidget(self.level1_title)
|
|
|
|
|
|
|
|
|
|
self.level1_list = QListWidget()
|
|
|
|
|
self.level1_list.setIconSize(QSize(48, 48))
|
|
|
|
|
self.level1_list.setViewMode(QListWidget.ViewMode.IconMode)
|
|
|
|
|
self.level1_list.setResizeMode(QListWidget.ResizeMode.Adjust)
|
|
|
|
|
self.level1_list.setMovement(QListWidget.Movement.Static)
|
|
|
|
|
self.level1_list.setSpacing(10)
|
|
|
|
|
self.level1_list.itemDoubleClicked.connect(self.show_level2_folders)
|
|
|
|
|
level1_main_layout.addWidget(self.level1_list)
|
|
|
|
|
|
|
|
|
|
self.level1_container.setLayout(level1_main_layout)
|
|
|
|
|
self.stacked_layout.addWidget(self.level1_container)
|
|
|
|
|
|
|
|
|
|
# 状态3: 显示第二层文件夹 (机组选择)
|
|
|
|
|
self.level2_container = QWidget()
|
|
|
|
|
level2_main_layout = QVBoxLayout()
|
2025-08-06 17:56:29 +08:00
|
|
|
|
level2_main_layout.setSpacing(0)
|
|
|
|
|
level2_main_layout.setContentsMargins(0, 0, 0, 0)
|
2025-08-05 17:51:11 +08:00
|
|
|
|
|
|
|
|
|
# 添加状态标签
|
|
|
|
|
self.level2_title = QLabel("机组选择")
|
|
|
|
|
self.level2_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
|
level2_main_layout.addWidget(self.level2_title)
|
|
|
|
|
|
|
|
|
|
self.level2_list = QListWidget()
|
|
|
|
|
self.level2_list.setIconSize(QSize(48, 48))
|
|
|
|
|
self.level2_list.setViewMode(QListWidget.ViewMode.IconMode)
|
|
|
|
|
self.level2_list.setResizeMode(QListWidget.ResizeMode.Adjust)
|
|
|
|
|
self.level2_list.setMovement(QListWidget.Movement.Static)
|
|
|
|
|
self.level2_list.setSpacing(10)
|
|
|
|
|
level2_main_layout.addWidget(self.level2_list)
|
|
|
|
|
|
|
|
|
|
self.level2_container.setLayout(level2_main_layout)
|
|
|
|
|
self.stacked_layout.addWidget(self.level2_container)
|
|
|
|
|
|
|
|
|
|
# 将堆叠布局添加到主布局
|
|
|
|
|
stack_container = QWidget()
|
|
|
|
|
stack_container.setLayout(self.stacked_layout)
|
|
|
|
|
main_layout.addWidget(stack_container)
|
|
|
|
|
|
|
|
|
|
# 已选目录列表 (始终显示在底部)
|
|
|
|
|
self.selected_group = QGroupBox("已选机组")
|
|
|
|
|
self.selected_group.setVisible(False)
|
|
|
|
|
selected_layout = QVBoxLayout()
|
|
|
|
|
self.selected_list = QListWidget()
|
|
|
|
|
self.selected_list.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
|
|
|
|
selected_layout.addWidget(self.selected_list)
|
|
|
|
|
self.selected_group.setLayout(selected_layout)
|
2025-08-04 18:08:53 +08:00
|
|
|
|
main_layout.addWidget(self.selected_group)
|
2025-08-05 17:51:11 +08:00
|
|
|
|
|
2025-08-04 18:08:53 +08:00
|
|
|
|
self.setLayout(main_layout)
|
2025-08-05 17:51:11 +08:00
|
|
|
|
|
2025-08-06 17:56:29 +08:00
|
|
|
|
def select_all_folders(self):
|
|
|
|
|
self.selected_folders.update(
|
|
|
|
|
set(item.data(Qt.ItemDataRole.UserRole) for item in self.level2_list.findItems("", Qt.MatchFlag.MatchContains))
|
|
|
|
|
)
|
|
|
|
|
self.update_checkbox_states()
|
|
|
|
|
self.selection_changed.emit(self.get_selected_folders())
|
|
|
|
|
|
2025-08-04 18:08:53 +08:00
|
|
|
|
def apply_styles(self):
|
2025-08-05 17:51:11 +08:00
|
|
|
|
self.prompt_label.setStyleSheet(f"""
|
|
|
|
|
{PATH_DISPLAY_STYLE}
|
|
|
|
|
border: 2px dashed {SECONDARY_COLOR};
|
|
|
|
|
min-height: 150px;
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
self.level1_list.setStyleSheet(f"""
|
|
|
|
|
QListWidget {{
|
|
|
|
|
background-color: #f8f9fa;
|
|
|
|
|
min-height: 150px;
|
2025-08-04 18:08:53 +08:00
|
|
|
|
}}
|
|
|
|
|
""")
|
|
|
|
|
|
2025-08-05 17:51:11 +08:00
|
|
|
|
self.level2_list.setStyleSheet(f"""
|
|
|
|
|
QListWidget {{
|
|
|
|
|
background-color: #f8f9fa;
|
|
|
|
|
min-height: 150px;
|
|
|
|
|
}}
|
|
|
|
|
""")
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
2025-08-05 17:51:11 +08:00
|
|
|
|
self.level1_title.setStyleSheet(f"""
|
|
|
|
|
{LABEL_STYLE}
|
|
|
|
|
font-size: {TITLE_FONT_SIZE}pt;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
padding: 10px;
|
|
|
|
|
""")
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
2025-08-05 17:51:11 +08:00
|
|
|
|
self.level2_title.setStyleSheet(f"""
|
|
|
|
|
{LABEL_STYLE}
|
|
|
|
|
font-size: {TITLE_FONT_SIZE}pt;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
padding: 10px;
|
|
|
|
|
""")
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
2025-08-05 17:51:11 +08:00
|
|
|
|
self.selected_group.setStyleSheet(GROUP_BOX_STYLE)
|
2025-08-06 17:56:29 +08:00
|
|
|
|
self.full_select_btn.setStyleSheet(BUTTON_STYLE)
|
2025-08-05 17:51:11 +08:00
|
|
|
|
self.reset_btn.setStyleSheet(BUTTON_STYLE)
|
|
|
|
|
self.clear_btn.setStyleSheet(BUTTON_STYLE)
|
|
|
|
|
self.back_btn.setStyleSheet(BUTTON_STYLE)
|
|
|
|
|
|
|
|
|
|
def select_folder(self, event=None):
|
|
|
|
|
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
|
|
|
|
|
if folder:
|
|
|
|
|
self.load_folder(folder)
|
|
|
|
|
|
|
|
|
|
def reset_selection(self):
|
|
|
|
|
self.current_dir = None
|
|
|
|
|
self.selected_folders.clear()
|
|
|
|
|
self.level1_list.clear()
|
|
|
|
|
self.level2_list.clear()
|
|
|
|
|
self.selected_list.clear()
|
|
|
|
|
self.stacked_layout.setCurrentIndex(0) # 显示提示
|
|
|
|
|
self.reset_btn.setVisible(False)
|
|
|
|
|
self.clear_btn.setVisible(False)
|
2025-08-06 17:56:29 +08:00
|
|
|
|
self.back_btn.setVisible(False)
|
2025-08-05 17:51:11 +08:00
|
|
|
|
self.selected_group.setVisible(False)
|
|
|
|
|
self.selection_changed.emit([])
|
|
|
|
|
|
|
|
|
|
def clear_selected(self):
|
|
|
|
|
self.selected_folders.clear()
|
|
|
|
|
self.selected_list.clear()
|
|
|
|
|
self.update_checkbox_states()
|
|
|
|
|
self.selection_changed.emit([])
|
|
|
|
|
|
|
|
|
|
def load_folder(self, path):
|
|
|
|
|
self.current_dir = path
|
|
|
|
|
self.load_level1_folders(path)
|
|
|
|
|
self.stacked_layout.setCurrentIndex(1) # 显示第一层
|
|
|
|
|
self.reset_btn.setVisible(True)
|
2025-08-06 17:56:29 +08:00
|
|
|
|
self.back_btn.setVisible(False)
|
2025-08-05 17:51:11 +08:00
|
|
|
|
self.clear_btn.setVisible(True)
|
2025-08-06 17:56:29 +08:00
|
|
|
|
self.full_select_btn.setVisible(True)
|
2025-08-05 17:51:11 +08:00
|
|
|
|
self.selected_group.setVisible(True)
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
|
|
|
|
def dragEnterEvent(self, event: QDragEnterEvent):
|
|
|
|
|
if event.mimeData().hasUrls():
|
2025-08-05 17:51:11 +08:00
|
|
|
|
event.acceptProposedAction()
|
|
|
|
|
|
2025-08-04 18:08:53 +08:00
|
|
|
|
def dropEvent(self, event: QDropEvent):
|
2025-08-05 17:51:11 +08:00
|
|
|
|
for url in event.mimeData().urls():
|
|
|
|
|
path = url.toLocalFile()
|
|
|
|
|
if os.path.isdir(path):
|
|
|
|
|
self.load_folder(path)
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
def load_level1_folders(self, root_path):
|
|
|
|
|
self.level1_list.clear()
|
|
|
|
|
self.level2_list.clear()
|
|
|
|
|
|
|
|
|
|
for item in os.listdir(root_path):
|
|
|
|
|
item_path = os.path.join(root_path, item)
|
|
|
|
|
if os.path.isdir(item_path):
|
|
|
|
|
item_widget = QListWidgetItem(QIcon.fromTheme("folder"), item)
|
|
|
|
|
item_widget.setData(Qt.ItemDataRole.UserRole, item_path)
|
|
|
|
|
item_widget.setSizeHint(QSize(80, 80))
|
|
|
|
|
item_widget.setTextAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignBottom)
|
|
|
|
|
self.level1_list.addItem(item_widget)
|
|
|
|
|
|
|
|
|
|
def show_level2_folders(self, item):
|
|
|
|
|
self.level2_list.clear()
|
2025-08-06 17:56:29 +08:00
|
|
|
|
self.reset_btn.setVisible(False)
|
|
|
|
|
self.back_btn.setVisible(True)
|
2025-08-05 17:51:11 +08:00
|
|
|
|
folder_path = item.data(Qt.ItemDataRole.UserRole)
|
|
|
|
|
|
|
|
|
|
for sub_item in os.listdir(folder_path):
|
|
|
|
|
sub_item_path = os.path.join(folder_path, sub_item)
|
|
|
|
|
if os.path.isdir(sub_item_path):
|
|
|
|
|
list_item = QListWidgetItem()
|
|
|
|
|
list_item.setSizeHint(QSize(100, 80))
|
|
|
|
|
list_item.setData(Qt.ItemDataRole.UserRole, sub_item_path)
|
|
|
|
|
|
|
|
|
|
widget = QWidget()
|
|
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
|
|
|
|
|
|
icon_label = QLabel()
|
|
|
|
|
icon_label.setPixmap(QIcon.fromTheme("folder").pixmap(48, 48))
|
|
|
|
|
icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
|
layout.addWidget(icon_label)
|
|
|
|
|
|
|
|
|
|
name_check_layout = QHBoxLayout()
|
|
|
|
|
checkbox = QCheckBox(sub_item)
|
|
|
|
|
checkbox.setStyleSheet(f"""
|
|
|
|
|
{PRIMARY_CHECKBOX_STYLE}
|
|
|
|
|
QCheckBox::indicator:checked {{
|
|
|
|
|
background-color: {PRIMARY_COLOR};
|
|
|
|
|
border: 1px solid {PRIMARY_COLOR};
|
|
|
|
|
}}
|
|
|
|
|
""")
|
|
|
|
|
checkbox.stateChanged.connect(lambda state, path=sub_item_path: self.toggle_folder_selection(path, state))
|
|
|
|
|
checkbox.setChecked(sub_item_path in self.selected_folders)
|
|
|
|
|
name_check_layout.addWidget(checkbox)
|
|
|
|
|
layout.addLayout(name_check_layout)
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
2025-08-05 17:51:11 +08:00
|
|
|
|
widget.setLayout(layout)
|
|
|
|
|
|
|
|
|
|
list_item.setSizeHint(widget.sizeHint())
|
|
|
|
|
self.level2_list.addItem(list_item)
|
|
|
|
|
self.level2_list.setItemWidget(list_item, widget)
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
2025-08-05 17:51:11 +08:00
|
|
|
|
self.stacked_layout.setCurrentIndex(2) # 显示第二层
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
2025-08-05 17:51:11 +08:00
|
|
|
|
def toggle_folder_selection(self, folder_path, state):
|
|
|
|
|
if state == Qt.CheckState.Checked.value:
|
|
|
|
|
self.selected_folders.add(folder_path)
|
|
|
|
|
item = QListWidgetItem(folder_path)
|
|
|
|
|
self.selected_list.addItem(item)
|
|
|
|
|
else:
|
|
|
|
|
self.selected_folders.discard(folder_path)
|
|
|
|
|
items = self.selected_list.findItems(folder_path, Qt.MatchFlag.MatchExactly)
|
|
|
|
|
for item in items:
|
|
|
|
|
self.selected_list.takeItem(self.selected_list.row(item))
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
2025-08-05 17:51:11 +08:00
|
|
|
|
self.selection_changed.emit(self.get_selected_folders())
|
|
|
|
|
|
|
|
|
|
def update_checkbox_states(self):
|
|
|
|
|
for i in range(self.level2_list.count()):
|
|
|
|
|
item = self.level2_list.item(i)
|
|
|
|
|
widget = self.level2_list.itemWidget(item)
|
|
|
|
|
if widget:
|
|
|
|
|
checkbox = widget.findChild(QCheckBox)
|
|
|
|
|
if checkbox:
|
|
|
|
|
path = item.data(Qt.ItemDataRole.UserRole)
|
|
|
|
|
checkbox.setChecked(path in self.selected_folders)
|
|
|
|
|
|
|
|
|
|
def get_selected_folders(self):
|
|
|
|
|
return list(self.selected_folders)
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
2025-08-05 17:51:11 +08:00
|
|
|
|
class DraggableLine(QFrame):
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self.setFrameShape(QFrame.HLine)
|
|
|
|
|
self.setFrameShadow(QFrame.Sunken)
|
|
|
|
|
self.setLineWidth(2)
|
|
|
|
|
self.setFixedHeight(10)
|
|
|
|
|
self.dragging = False
|
|
|
|
|
self.setCursor(Qt.SizeVerCursor)
|
|
|
|
|
|
|
|
|
|
def mousePressEvent(self, event):
|
|
|
|
|
if event.button() == Qt.LeftButton:
|
|
|
|
|
self.dragging = True
|
|
|
|
|
self.start_pos = event.globalPos()
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
2025-08-05 17:51:11 +08:00
|
|
|
|
def mouseMoveEvent(self, event):
|
|
|
|
|
if self.dragging:
|
|
|
|
|
delta = event.globalPos() - self.start_pos
|
|
|
|
|
self.start_pos = event.globalPos()
|
|
|
|
|
# 通知父窗口调整布局
|
|
|
|
|
self.parent().adjust_row_height(delta.y())
|
2025-08-04 18:08:53 +08:00
|
|
|
|
|
2025-08-05 17:51:11 +08:00
|
|
|
|
def mouseReleaseEvent(self, event):
|
|
|
|
|
if event.button() == Qt.LeftButton:
|
2025-08-07 18:16:09 +08:00
|
|
|
|
self.dragging = False
|
|
|
|
|
|
|
|
|
|
class OutputDirSelector(QWidget):
|
|
|
|
|
# 定义一个信号,当路径改变时发出
|
|
|
|
|
path_changed = Signal(str)
|
|
|
|
|
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self.output_dir = ""
|
|
|
|
|
self.init_ui()
|
|
|
|
|
self.set_style()
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
# 主布局
|
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
|
layout.setSpacing(10)
|
|
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
|
|
|
|
|
# 组框
|
|
|
|
|
self.group_box = QFrame()
|
|
|
|
|
self.group_box.setFrameShape(QFrame.StyledPanel)
|
|
|
|
|
group_layout = QVBoxLayout(self.group_box)
|
|
|
|
|
group_layout.setSpacing(GROUP_BOX_SPACING)
|
|
|
|
|
group_layout.setContentsMargins(*GROUP_BOX_MARGINS)
|
|
|
|
|
|
|
|
|
|
# 标题标签
|
|
|
|
|
self.setWindowTitle("选择输出目录")
|
|
|
|
|
|
|
|
|
|
# 路径显示标签
|
|
|
|
|
self.path_label = QLabel("未选择文件夹")
|
|
|
|
|
self.path_label.setWordWrap(True)
|
|
|
|
|
self.path_label.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
|
|
|
|
|
|
|
|
|
# 选择按钮
|
|
|
|
|
self.select_button = QPushButton("选择输出文件夹")
|
|
|
|
|
self.select_button.clicked.connect(self.select_output_dir)
|
|
|
|
|
|
|
|
|
|
# 添加部件到布局
|
|
|
|
|
group_layout.addWidget(self.path_label)
|
|
|
|
|
group_layout.addWidget(self.select_button)
|
|
|
|
|
|
|
|
|
|
# 添加组框到主布局
|
|
|
|
|
layout.addWidget(self.group_box)
|
|
|
|
|
|
|
|
|
|
def set_style(self):
|
|
|
|
|
# 设置组框样式
|
|
|
|
|
self.group_box.setStyleSheet(GROUP_BOX_STYLE)
|
|
|
|
|
|
|
|
|
|
# 设置路径显示样式
|
|
|
|
|
self.path_label.setStyleSheet(PATH_DISPLAY_STYLE)
|
|
|
|
|
|
|
|
|
|
# 设置按钮样式
|
|
|
|
|
self.select_button.setStyleSheet(PRIMARY_BUTTON_STYLE)
|
|
|
|
|
|
|
|
|
|
# 设置最小尺寸
|
|
|
|
|
self.group_box.setMinimumSize(GROUP_BOX_MIN_WIDTH, GROUP_BOX_MIN_HEIGHT)
|
|
|
|
|
|
|
|
|
|
def select_output_dir(self):
|
|
|
|
|
"""打开文件夹选择对话框"""
|
|
|
|
|
dir_path = QFileDialog.getExistingDirectory(
|
|
|
|
|
self,
|
|
|
|
|
"选择输出文件夹",
|
|
|
|
|
"", # 默认路径为空,使用系统默认
|
|
|
|
|
QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if dir_path: # 如果用户选择了文件夹
|
|
|
|
|
self.output_dir = dir_path
|
|
|
|
|
self.path_label.setText(dir_path)
|
|
|
|
|
self.path_changed.emit(dir_path) # 发出路径改变信号
|
|
|
|
|
|
|
|
|
|
def get_output_dir(self):
|
|
|
|
|
"""获取输出路径"""
|
|
|
|
|
return self.output_dir
|
|
|
|
|
|
|
|
|
|
def set_output_dir(self, path):
|
|
|
|
|
"""设置输出路径"""
|
|
|
|
|
if path:
|
|
|
|
|
self.output_dir = path
|
|
|
|
|
self.path_label.setText(path)
|
|
|
|
|
self.path_changed.emit(path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class JsonFileHandler:
|
|
|
|
|
"""处理JSON文件的读写操作"""
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def read_json(file_path):
|
|
|
|
|
"""读取JSON文件"""
|
|
|
|
|
try:
|
|
|
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
|
|
|
return json.load(f)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error reading JSON file {file_path}: {e}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def write_json(file_path, data):
|
|
|
|
|
try:
|
|
|
|
|
# 检查目录是否可写
|
|
|
|
|
dir_path = os.path.dirname(file_path)
|
|
|
|
|
if not os.access(dir_path, os.W_OK):
|
|
|
|
|
print(f"错误:目录不可写 {dir_path}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
|
|
|
json.dump(data, f, ensure_ascii=False, indent=4)
|
|
|
|
|
print(f"成功写入文件:{file_path}")
|
|
|
|
|
return True
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"写入文件错误:{str(e)}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
class TreeModelManager:
|
|
|
|
|
"""管理树形模型的数据操作"""
|
|
|
|
|
|
2025-08-08 18:00:56 +08:00
|
|
|
|
# 中英文映射字典
|
|
|
|
|
TRANSLATION_MAP = {
|
|
|
|
|
# 左侧树
|
|
|
|
|
"Directory Structure": "目录结构",
|
|
|
|
|
|
|
|
|
|
# 右侧树
|
|
|
|
|
"Information": "信息",
|
|
|
|
|
"Value": "值",
|
|
|
|
|
"turbinecode": "机组编号",
|
|
|
|
|
"part1": "叶片1",
|
|
|
|
|
"part2": "叶片2",
|
|
|
|
|
"part3": "叶片3",
|
|
|
|
|
"code": "部件代码",
|
|
|
|
|
"defect_picture_dict": "缺陷图字典",
|
|
|
|
|
"typical_picture_dict": "典型图字典"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def translate_text(key):
|
|
|
|
|
"""根据映射表翻译文本"""
|
|
|
|
|
return TreeModelManager.TRANSLATION_MAP.get(key, key)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def reverse_translate_text(chinese_text):
|
|
|
|
|
"""根据中文反向查找英文键"""
|
|
|
|
|
reverse_map = {v: k for k, v in TreeModelManager.TRANSLATION_MAP.items()}
|
|
|
|
|
return reverse_map.get(chinese_text, chinese_text)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def set_item_color(item, color=QColor(255, 0, 0)):
|
|
|
|
|
"""设置树节点的文本颜色"""
|
|
|
|
|
if item is not None:
|
|
|
|
|
item.setForeground(color)
|
|
|
|
|
|
2025-08-07 18:16:09 +08:00
|
|
|
|
@staticmethod
|
2025-08-08 18:00:56 +08:00
|
|
|
|
def create_left_tree_model(folder_list, required_child_count=3):
|
|
|
|
|
"""创建左侧文件夹树模型
|
|
|
|
|
Args:
|
|
|
|
|
folder_list: 文件夹路径列表
|
|
|
|
|
required_child_count: 要求的子目录数量,默认为3
|
|
|
|
|
"""
|
2025-08-07 18:16:09 +08:00
|
|
|
|
model = QStandardItemModel()
|
2025-08-08 18:00:56 +08:00
|
|
|
|
model.setHorizontalHeaderLabels(["目录结构"])
|
2025-08-07 18:16:09 +08:00
|
|
|
|
folder_map = {}
|
|
|
|
|
|
|
|
|
|
for folder_path in folder_list:
|
|
|
|
|
folder_name = os.path.basename(folder_path)
|
|
|
|
|
folder_map[folder_name] = folder_path
|
|
|
|
|
|
|
|
|
|
parent_item = QStandardItem(folder_name)
|
|
|
|
|
model.appendRow(parent_item)
|
|
|
|
|
|
|
|
|
|
try:
|
2025-08-08 18:00:56 +08:00
|
|
|
|
child_dirs = []
|
2025-08-07 18:16:09 +08:00
|
|
|
|
for child in os.listdir(folder_path):
|
|
|
|
|
child_path = os.path.join(folder_path, child)
|
2025-08-08 18:00:56 +08:00
|
|
|
|
folder_map[child] = child_path
|
|
|
|
|
|
2025-08-07 18:16:09 +08:00
|
|
|
|
if os.path.isdir(child_path):
|
2025-08-08 18:00:56 +08:00
|
|
|
|
child_dirs.append(child)
|
2025-08-07 18:16:09 +08:00
|
|
|
|
child_item = QStandardItem(child)
|
|
|
|
|
parent_item.appendRow(child_item)
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
|
|
|
|
# 检查子目录数量是否满足要求
|
|
|
|
|
if len(child_dirs) < required_child_count:
|
|
|
|
|
TreeModelManager.set_item_color(parent_item) # 不满足则标红
|
|
|
|
|
|
2025-08-07 18:16:09 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error reading folder {folder_path}: {e}")
|
2025-08-08 18:00:56 +08:00
|
|
|
|
TreeModelManager.set_item_color(parent_item) # 读取错误也标红
|
2025-08-07 18:16:09 +08:00
|
|
|
|
|
|
|
|
|
return model, folder_map
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def create_right_tree_model(folder_names):
|
|
|
|
|
"""创建右侧可编辑树模型"""
|
|
|
|
|
model = QStandardItemModel()
|
2025-08-08 18:00:56 +08:00
|
|
|
|
model.setHorizontalHeaderLabels([
|
|
|
|
|
TreeModelManager.translate_text("Information"),
|
|
|
|
|
TreeModelManager.translate_text("Value")
|
|
|
|
|
])
|
2025-08-07 18:16:09 +08:00
|
|
|
|
|
|
|
|
|
json_data = {}
|
|
|
|
|
for folder_name in folder_names:
|
|
|
|
|
parent_item = QStandardItem(folder_name)
|
|
|
|
|
parent_item.setEditable(False)
|
|
|
|
|
|
|
|
|
|
# 添加机组编号
|
2025-08-08 18:00:56 +08:00
|
|
|
|
turbine_item = QStandardItem(TreeModelManager.translate_text("turbinecode"))
|
2025-08-07 18:16:09 +08:00
|
|
|
|
turbine_value = QStandardItem(folder_name.replace("机组", "").strip())
|
|
|
|
|
turbine_value.setEditable(True)
|
|
|
|
|
parent_item.appendRow([turbine_item, turbine_value])
|
|
|
|
|
|
|
|
|
|
# 添加默认的三个子部件
|
|
|
|
|
for i in range(1, 4):
|
2025-08-08 18:00:56 +08:00
|
|
|
|
part_item = QStandardItem(TreeModelManager.translate_text(f"part{i}"))
|
2025-08-07 18:16:09 +08:00
|
|
|
|
part_item.setEditable(False)
|
|
|
|
|
|
2025-08-08 18:00:56 +08:00
|
|
|
|
# 部件代码
|
|
|
|
|
code_item = QStandardItem(TreeModelManager.translate_text("code"))
|
|
|
|
|
code_value = QStandardItem("001")
|
|
|
|
|
code_value.setEditable(True)
|
|
|
|
|
part_item.appendRow([code_item, code_value])
|
|
|
|
|
|
|
|
|
|
# 缺陷图字典
|
|
|
|
|
defect_pic_item = QStandardItem(TreeModelManager.translate_text("defect_picture_dict"))
|
|
|
|
|
defect_pic_item.setEditable(False)
|
|
|
|
|
part_item.appendRow([defect_pic_item, QStandardItem("{}")])
|
|
|
|
|
|
|
|
|
|
# 典型图字典
|
|
|
|
|
typical_pic_item = QStandardItem(TreeModelManager.translate_text("typical_picture_dict"))
|
|
|
|
|
typical_pic_item.setEditable(False)
|
|
|
|
|
part_item.appendRow([typical_pic_item, QStandardItem("{}")])
|
2025-08-07 18:16:09 +08:00
|
|
|
|
|
|
|
|
|
parent_item.appendRow(part_item)
|
|
|
|
|
|
|
|
|
|
model.appendRow(parent_item)
|
|
|
|
|
|
2025-08-08 18:00:56 +08:00
|
|
|
|
# 初始化JSON数据(保持英文键)
|
2025-08-07 18:16:09 +08:00
|
|
|
|
json_data[folder_name] = {
|
|
|
|
|
"turbinecode": folder_name.replace("机组", "").strip(),
|
2025-08-08 18:00:56 +08:00
|
|
|
|
"part1": {
|
|
|
|
|
"code": "001",
|
|
|
|
|
"defect_picture_dict": {},
|
|
|
|
|
"typical_picture_dict": {}
|
|
|
|
|
},
|
|
|
|
|
"part2": {
|
|
|
|
|
"code": "001",
|
|
|
|
|
"defect_picture_dict": {},
|
|
|
|
|
"typical_picture_dict": {}
|
|
|
|
|
},
|
|
|
|
|
"part3": {
|
|
|
|
|
"code": "001",
|
|
|
|
|
"defect_picture_dict": {},
|
|
|
|
|
"typical_picture_dict": {}
|
|
|
|
|
}
|
2025-08-07 18:16:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return model, json_data
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def update_model_from_json(model, folder_name, json_data):
|
|
|
|
|
"""根据JSON数据更新模型"""
|
|
|
|
|
parent_item = None
|
|
|
|
|
for i in range(model.rowCount()):
|
|
|
|
|
item = model.item(i)
|
|
|
|
|
if item and item.text() == folder_name:
|
|
|
|
|
parent_item = item
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if parent_item and folder_name in json_data:
|
|
|
|
|
data = json_data[folder_name]
|
|
|
|
|
|
|
|
|
|
# 更新机组编号
|
|
|
|
|
turbine_item = parent_item.child(0)
|
|
|
|
|
if turbine_item and turbine_item.child(0, 1):
|
|
|
|
|
turbine_item.child(0, 1).setText(data.get("turbinecode", ""))
|
|
|
|
|
|
|
|
|
|
# 更新部件信息
|
|
|
|
|
for j in range(1, 4):
|
|
|
|
|
part_item = parent_item.child(j)
|
|
|
|
|
if part_item:
|
2025-08-08 18:00:56 +08:00
|
|
|
|
part_name = TreeModelManager.reverse_translate_text(part_item.text()) # 反向查找英文键
|
2025-08-07 18:16:09 +08:00
|
|
|
|
if part_name in data:
|
2025-08-08 18:00:56 +08:00
|
|
|
|
# 更新部件代码
|
2025-08-07 18:16:09 +08:00
|
|
|
|
code_item = part_item.child(0, 1)
|
|
|
|
|
if code_item:
|
|
|
|
|
code_item.setText(data[part_name].get("code", "001"))
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
|
|
|
|
# 更新缺陷图字典
|
|
|
|
|
defect_pic_item = part_item.child(1, 1)
|
|
|
|
|
if defect_pic_item:
|
|
|
|
|
defect_pic_dict = data[part_name].get("defect_picture_dict", {})
|
|
|
|
|
defect_pic_item.setText(str(defect_pic_dict))
|
|
|
|
|
|
|
|
|
|
# 更新典型图字典
|
|
|
|
|
typical_pic_item = part_item.child(2, 1)
|
|
|
|
|
if typical_pic_item:
|
|
|
|
|
typical_pic_dict = data[part_name].get("typical_picture_dict", {})
|
|
|
|
|
typical_pic_item.setText(str(typical_pic_dict))
|
2025-08-07 18:16:09 +08:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def update_json_from_model(model, json_data):
|
2025-08-08 18:00:56 +08:00
|
|
|
|
"""从模型更新JSON数据"""
|
2025-08-07 18:16:09 +08:00
|
|
|
|
for i in range(model.rowCount()):
|
|
|
|
|
folder_index = model.index(i, 0)
|
|
|
|
|
folder_name = model.data(folder_index)
|
|
|
|
|
|
|
|
|
|
if not folder_name:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 确保数据结构存在
|
|
|
|
|
if folder_name not in json_data:
|
2025-08-08 18:00:56 +08:00
|
|
|
|
json_data[folder_name] = {
|
|
|
|
|
"turbinecode": "",
|
|
|
|
|
"part1": {"code": "001", "defect_picture_dict": {}, "typical_picture_dict": {}},
|
|
|
|
|
"part2": {"code": "001", "defect_picture_dict": {}, "typical_picture_dict": {}},
|
|
|
|
|
"part3": {"code": "001", "defect_picture_dict": {}, "typical_picture_dict": {}}
|
|
|
|
|
}
|
2025-08-07 18:16:09 +08:00
|
|
|
|
|
2025-08-08 18:00:56 +08:00
|
|
|
|
# 获取 turbinecode 值
|
|
|
|
|
turbine_index = model.index(0, 1, folder_index)
|
2025-08-07 18:16:09 +08:00
|
|
|
|
if turbine_index.isValid():
|
|
|
|
|
new_value = model.data(turbine_index)
|
|
|
|
|
json_data[folder_name]["turbinecode"] = new_value
|
|
|
|
|
|
|
|
|
|
# 更新parts
|
|
|
|
|
for part_num in range(1, 4):
|
|
|
|
|
part_index = model.index(part_num, 0, folder_index)
|
|
|
|
|
if part_index.isValid():
|
2025-08-08 18:00:56 +08:00
|
|
|
|
part_name = TreeModelManager.reverse_translate_text(model.data(part_index)) # 反向查找英文键
|
|
|
|
|
|
|
|
|
|
# 更新部件代码
|
2025-08-07 18:16:09 +08:00
|
|
|
|
code_index = model.index(0, 1, part_index)
|
|
|
|
|
if code_index.isValid():
|
2025-08-08 18:00:56 +08:00
|
|
|
|
json_data[folder_name][part_name]["code"] = model.data(code_index, Qt.DisplayRole)
|
|
|
|
|
|
|
|
|
|
# 更新缺陷图字典
|
|
|
|
|
defect_pic_index = model.index(1, 1, part_index)
|
|
|
|
|
if defect_pic_index.isValid():
|
|
|
|
|
defect_pic_str = model.data(defect_pic_index, Qt.DisplayRole)
|
|
|
|
|
try:
|
|
|
|
|
json_data[folder_name][part_name]["defect_picture_dict"] = eval(defect_pic_str)
|
|
|
|
|
except:
|
|
|
|
|
json_data[folder_name][part_name]["defect_picture_dict"] = {}
|
|
|
|
|
|
|
|
|
|
# 更新典型图字典
|
|
|
|
|
typical_pic_index = model.index(2, 1, part_index)
|
|
|
|
|
if typical_pic_index.isValid():
|
|
|
|
|
typical_pic_str = model.data(typical_pic_index, Qt.DisplayRole)
|
|
|
|
|
try:
|
|
|
|
|
json_data[folder_name][part_name]["typical_picture_dict"] = eval(typical_pic_str)
|
|
|
|
|
except:
|
|
|
|
|
json_data[folder_name][part_name]["typical_picture_dict"] = {}
|
2025-08-07 18:16:09 +08:00
|
|
|
|
|
|
|
|
|
class DirectSaveDelegate(QStyledItemDelegate):
|
|
|
|
|
"""直接保存的委托实现"""
|
|
|
|
|
def __init__(self, save_callback, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self.save_callback = save_callback
|
|
|
|
|
|
|
|
|
|
def setModelData(self, editor, model, index):
|
|
|
|
|
try:
|
|
|
|
|
# 提交数据前验证索引
|
|
|
|
|
if not index.isValid():
|
|
|
|
|
print("⚠️ 无效的模型索引")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
old_value = index.data(Qt.DisplayRole)
|
|
|
|
|
super().setModelData(editor, model, index)
|
|
|
|
|
new_value = index.data(Qt.DisplayRole)
|
|
|
|
|
|
|
|
|
|
print(f"📝 数据变更: {old_value} → {new_value}")
|
|
|
|
|
|
|
|
|
|
# 获取顶层文件夹索引
|
|
|
|
|
top_index = index
|
|
|
|
|
while top_index.parent().isValid():
|
|
|
|
|
top_index = top_index.parent()
|
|
|
|
|
|
|
|
|
|
if model.hasIndex(top_index.row(), top_index.column(), top_index.parent()):
|
|
|
|
|
folder_name = model.data(top_index, Qt.DisplayRole)
|
|
|
|
|
self.save_callback(folder_name)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"❌ 委托错误: {str(e)}")
|
|
|
|
|
import traceback
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
|
|
|
|
class FolderBrowser(QWidget):
|
|
|
|
|
"""文件夹浏览编辑主窗口"""
|
|
|
|
|
data_changed = Signal(dict) # 数据变更信号
|
2025-08-08 18:00:56 +08:00
|
|
|
|
folder_selected = Signal(str)
|
2025-08-07 18:16:09 +08:00
|
|
|
|
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self.output_path = QDir.currentPath() # 默认输出路径
|
|
|
|
|
self.json_data = {} # 存储所有JSON数据
|
|
|
|
|
self.folder_map = {} # 文件夹路径映射
|
2025-08-08 18:00:56 +08:00
|
|
|
|
self.current_image_dir = "" # 当前显示的图片目录
|
2025-08-07 18:16:09 +08:00
|
|
|
|
self.init_ui()
|
|
|
|
|
self.setup_connections()
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
"""初始化用户界面"""
|
2025-08-08 18:00:56 +08:00
|
|
|
|
main_layout = QVBoxLayout(self)
|
|
|
|
|
main_layout.setContentsMargins(5, 5, 5, 5)
|
|
|
|
|
|
|
|
|
|
# 上下分割的主区域
|
|
|
|
|
main_splitter = QSplitter(Qt.Vertical)
|
|
|
|
|
|
|
|
|
|
# 上半部分 - 左右分割的树视图
|
|
|
|
|
tree_splitter = QSplitter(Qt.Horizontal)
|
2025-08-07 18:16:09 +08:00
|
|
|
|
|
|
|
|
|
# 左侧文件夹树视图
|
|
|
|
|
self.left_model = QStandardItemModel()
|
2025-08-08 18:00:56 +08:00
|
|
|
|
self.left_model.setHorizontalHeaderLabels(["目录结构"])
|
2025-08-07 18:16:09 +08:00
|
|
|
|
self.left_tree = QTreeView()
|
|
|
|
|
self.left_tree.setModel(self.left_model)
|
|
|
|
|
self.left_tree.setEditTriggers(QTreeView.NoEditTriggers)
|
|
|
|
|
|
|
|
|
|
# 右侧可编辑树视图
|
|
|
|
|
self.right_model = QStandardItemModel()
|
2025-08-08 18:00:56 +08:00
|
|
|
|
self.right_model.setHorizontalHeaderLabels(["信息", "值"])
|
2025-08-07 18:16:09 +08:00
|
|
|
|
self.right_tree = QTreeView()
|
|
|
|
|
self.right_tree.setModel(self.right_model)
|
|
|
|
|
self.right_tree.setItemDelegate(
|
|
|
|
|
DirectSaveDelegate(self.handle_immediate_save, self.right_tree)
|
|
|
|
|
)
|
|
|
|
|
|
2025-08-08 18:00:56 +08:00
|
|
|
|
# 添加到水平分割器
|
|
|
|
|
tree_splitter.addWidget(self.left_tree)
|
|
|
|
|
tree_splitter.addWidget(self.right_tree)
|
|
|
|
|
tree_splitter.setSizes([300, 500])
|
|
|
|
|
|
|
|
|
|
# 下半部分 - 图片浏览器和字典浏览器
|
|
|
|
|
image_splitter = QSplitter(Qt.Horizontal)
|
|
|
|
|
|
|
|
|
|
# 左侧图片浏览器
|
|
|
|
|
self.image_browser = ImageBrowser()
|
|
|
|
|
|
|
|
|
|
# 右侧字典浏览器
|
|
|
|
|
self.dictionary_browser = DictionaryBrowser()
|
|
|
|
|
|
|
|
|
|
# 添加到水平分割器
|
|
|
|
|
image_splitter.addWidget(self.image_browser)
|
|
|
|
|
image_splitter.addWidget(self.dictionary_browser)
|
|
|
|
|
image_splitter.setSizes([400, 400])
|
|
|
|
|
|
|
|
|
|
# 添加到垂直分割器
|
|
|
|
|
main_splitter.addWidget(tree_splitter)
|
|
|
|
|
main_splitter.addWidget(image_splitter)
|
|
|
|
|
main_splitter.setSizes([400, 400])
|
|
|
|
|
|
|
|
|
|
main_layout.addWidget(main_splitter)
|
|
|
|
|
|
|
|
|
|
# 设置拖放接收
|
|
|
|
|
self.dictionary_browser.setAcceptDrops(True)
|
2025-08-07 18:16:09 +08:00
|
|
|
|
|
2025-08-08 18:00:56 +08:00
|
|
|
|
def setup_connections(self):
|
|
|
|
|
"""设置信号和槽的连接"""
|
|
|
|
|
self.right_model.dataChanged.connect(self.on_right_data_changed)
|
|
|
|
|
self.left_tree.expanded.connect(self.sync_right_tree_expand)
|
|
|
|
|
self.left_tree.collapsed.connect(self.sync_right_tree_collapse)
|
2025-08-11 17:58:06 +08:00
|
|
|
|
self.left_tree.clicked.connect(self.on_left_tree_clicked)
|
|
|
|
|
self.folder_selected.connect(self.image_browser.show_images)
|
|
|
|
|
|
2025-08-08 18:00:56 +08:00
|
|
|
|
self.right_tree.expanded.connect(self.sync_left_tree_expand)
|
|
|
|
|
self.right_tree.collapsed.connect(self.sync_left_tree_collapse)
|
|
|
|
|
self.right_tree.clicked.connect(self.on_right_tree_clicked)
|
2025-08-11 17:58:06 +08:00
|
|
|
|
|
|
|
|
|
self.dictionary_browser.dictionary_updated.connect(self.handle_dictionary_update)
|
|
|
|
|
|
|
|
|
|
def handle_dictionary_update(self, folder_name, part_name, dict_type_en, dict_data):
|
|
|
|
|
"""处理字典更新"""
|
|
|
|
|
print(f"🔧 字典更新 {self.json_data}:\n[{folder_name}][{part_name}][{dict_type_en}] -> \n{dict_data}")
|
|
|
|
|
# 1. 更新JSON数据
|
|
|
|
|
if folder_name in self.json_data and part_name in self.json_data[folder_name]:
|
|
|
|
|
self.json_data[folder_name][part_name][dict_type_en] = dict_data
|
|
|
|
|
|
|
|
|
|
# 2. 更新树模型
|
|
|
|
|
self.update_right_tree(folder_name)
|
|
|
|
|
|
|
|
|
|
# 3. 保存到文件
|
|
|
|
|
self.save_to_json(folder_name)
|
|
|
|
|
|
|
|
|
|
def update_right_tree(self, folder_name):
|
|
|
|
|
"""更新右侧树视图"""
|
|
|
|
|
# 找到对应的文件夹项
|
|
|
|
|
for row in range(self.right_model.rowCount()):
|
|
|
|
|
folder_item = self.right_model.item(row, 0)
|
|
|
|
|
if folder_item.text() == folder_name:
|
|
|
|
|
# 更新整个子树
|
|
|
|
|
TreeModelManager.update_model_from_json(
|
|
|
|
|
self.right_model,
|
|
|
|
|
folder_name,
|
|
|
|
|
self.json_data
|
|
|
|
|
)
|
|
|
|
|
# 展开该节点
|
|
|
|
|
self.right_tree.expand(folder_item.index())
|
|
|
|
|
break
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
|
|
|
|
def on_right_tree_clicked(self, index):
|
|
|
|
|
"""处理右侧树视图点击事件"""
|
|
|
|
|
if not index.isValid():
|
|
|
|
|
return
|
2025-08-11 17:58:06 +08:00
|
|
|
|
|
2025-08-08 18:00:56 +08:00
|
|
|
|
item = self.right_model.itemFromIndex(index)
|
2025-08-11 17:58:06 +08:00
|
|
|
|
text = item.text() # 获取显示文本(中文)
|
|
|
|
|
print(f"点击了右侧树节点: {text}")
|
|
|
|
|
# 检查是否点击了字典节点(中文)
|
|
|
|
|
if text in ["缺陷图字典", "典型图字典"]:
|
|
|
|
|
# 获取part名称和原文件夹名
|
|
|
|
|
part_item = item.parent()
|
|
|
|
|
folder_item = part_item.parent() if part_item else None
|
|
|
|
|
|
|
|
|
|
if part_item and folder_item:
|
|
|
|
|
folder_name = folder_item.text()
|
|
|
|
|
part_name = part_item.text()
|
|
|
|
|
# 转换为英文键名获取数据
|
|
|
|
|
dict_type = DictionaryBrowser.DICT_TYPE_MAPPING.get(text, "")
|
|
|
|
|
if dict_type:
|
|
|
|
|
dict_data = self.json_data.get(folder_name, {}).get(DictionaryBrowser.DICT_TYPE_MAPPING.get(part_name, "")).get(dict_type, {})
|
|
|
|
|
|
|
|
|
|
# 传递中文显示名称
|
|
|
|
|
self.dictionary_browser.show_dictionary(folder_name, part_name, text, dict_data)
|
|
|
|
|
|
2025-08-08 18:00:56 +08:00
|
|
|
|
def get_item_path(self, item):
|
|
|
|
|
"""获取树节点路径"""
|
|
|
|
|
path = []
|
|
|
|
|
while item is not None:
|
|
|
|
|
path.append(item.text())
|
|
|
|
|
item = item.parent()
|
|
|
|
|
return "/".join(reversed(path))
|
|
|
|
|
|
|
|
|
|
def update_blade_name(self, folder_path):
|
|
|
|
|
"""从文件夹路径中提取叶片名称并更新字典浏览器"""
|
|
|
|
|
# 假设叶片名称是文件夹路径的最后一部分
|
|
|
|
|
blade_name = os.path.basename(folder_path.rstrip('/\\'))
|
|
|
|
|
self.dictionary_browser.set_current_blade(blade_name)
|
|
|
|
|
|
|
|
|
|
def on_left_tree_clicked(self, index):
|
|
|
|
|
"""处理左侧树视图选择变化事件"""
|
|
|
|
|
if not index.isValid():
|
|
|
|
|
return
|
|
|
|
|
item = self.left_model.itemFromIndex(index)
|
|
|
|
|
relative_path = self.folder_map.get(item.text(), "")
|
|
|
|
|
print(f"🔍 选择文件夹: {relative_path}")
|
|
|
|
|
self.folder_selected.emit(relative_path)
|
|
|
|
|
|
2025-08-07 18:16:09 +08:00
|
|
|
|
def handle_immediate_save(self, folder_name):
|
|
|
|
|
print(f"🔧 保存触发 [{folder_name}]")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# 安全获取文件夹索引
|
|
|
|
|
matches = self.right_model.match(
|
|
|
|
|
self.right_model.index(0, 0),
|
|
|
|
|
Qt.DisplayRole,
|
|
|
|
|
folder_name,
|
|
|
|
|
hits=1,
|
|
|
|
|
flags=Qt.MatchExactly
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not matches:
|
|
|
|
|
print(f"❌ 找不到文件夹: {folder_name}")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
folder_index = matches[0]
|
|
|
|
|
|
|
|
|
|
# 获取 turbinecode 值(使用标准模型访问方式)
|
|
|
|
|
turbine_index = self.right_model.index(0, 1, folder_index)
|
|
|
|
|
turbine_value = self.right_model.data(turbine_index, Qt.DisplayRole)
|
|
|
|
|
|
|
|
|
|
print(f"📊 当前值 - turbinecode: {turbine_value or '<空>'}")
|
|
|
|
|
|
|
|
|
|
# 更新数据
|
|
|
|
|
TreeModelManager.update_json_from_model(self.right_model, self.json_data)
|
|
|
|
|
self.save_to_json(folder_name)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"❌ 保存错误: {str(e)}")
|
|
|
|
|
|
|
|
|
|
def refresh_views(self):
|
|
|
|
|
"""刷新左右视图显示"""
|
|
|
|
|
self.left_tree.setModel(None) # 先重置模型
|
|
|
|
|
self.left_tree.setModel(self.left_model)
|
|
|
|
|
|
|
|
|
|
self.right_tree.setModel(None)
|
|
|
|
|
self.right_tree.setModel(self.right_model)
|
|
|
|
|
|
|
|
|
|
# 展开第一层节点
|
|
|
|
|
for i in range(self.left_model.rowCount()):
|
|
|
|
|
self.left_tree.expand(self.left_model.index(i, 0))
|
|
|
|
|
self.right_tree.expand(self.right_model.index(i, 0))
|
|
|
|
|
|
|
|
|
|
def copy_left_tree_text(self):
|
|
|
|
|
"""复制左侧树选中的文本"""
|
|
|
|
|
index = self.left_tree.currentIndex()
|
|
|
|
|
if index.isValid():
|
|
|
|
|
text = self.left_model.data(index, Qt.DisplayRole)
|
|
|
|
|
QApplication.clipboard().setText(text)
|
|
|
|
|
|
|
|
|
|
def sync_right_tree_expand(self, index):
|
|
|
|
|
"""同步右侧树的展开状态(仅第一层)"""
|
|
|
|
|
if not index.parent().isValid(): # 只处理第一层
|
|
|
|
|
right_index = self.right_model.index(index.row(), 0)
|
|
|
|
|
self.right_tree.expand(right_index)
|
|
|
|
|
|
|
|
|
|
def sync_right_tree_collapse(self, index):
|
|
|
|
|
"""同步右侧树的折叠状态(仅第一层)"""
|
|
|
|
|
if not index.parent().isValid(): # 只处理第一层
|
|
|
|
|
right_index = self.right_model.index(index.row(), 0)
|
|
|
|
|
self.right_tree.collapse(right_index)
|
|
|
|
|
|
|
|
|
|
def sync_left_tree_expand(self, index):
|
|
|
|
|
"""同步左侧树的展开状态(仅第一层)"""
|
|
|
|
|
if not index.parent().isValid(): # 只处理第一层
|
|
|
|
|
left_index = self.left_model.index(index.row(), 0)
|
|
|
|
|
self.left_tree.expand(left_index)
|
|
|
|
|
|
|
|
|
|
def sync_left_tree_collapse(self, index):
|
|
|
|
|
"""同步左侧树的折叠状态(仅第一层)"""
|
|
|
|
|
if not index.parent().isValid(): # 只处理第一层
|
|
|
|
|
left_index = self.left_model.index(index.row(), 0)
|
|
|
|
|
self.left_tree.collapse(left_index)
|
|
|
|
|
|
|
|
|
|
def set_output_path(self, path):
|
|
|
|
|
"""设置JSON文件输出路径"""
|
|
|
|
|
self.output_path = path
|
|
|
|
|
if not os.path.exists(path):
|
|
|
|
|
os.makedirs(path)
|
|
|
|
|
|
|
|
|
|
def set_initial_folders(self, folder_list):
|
|
|
|
|
"""初始化文件夹结构"""
|
|
|
|
|
self.left_model.clear()
|
|
|
|
|
self.right_model.clear()
|
2025-08-08 18:00:56 +08:00
|
|
|
|
self.left_model.setHorizontalHeaderLabels(["目录结构"])
|
|
|
|
|
self.right_model.setHorizontalHeaderLabels(["信息", "值"])
|
2025-08-07 18:16:09 +08:00
|
|
|
|
self.json_data = {}
|
|
|
|
|
self.folder_map = {}
|
|
|
|
|
|
|
|
|
|
if not folder_list: # 添加空列表检查
|
|
|
|
|
print("Warning: folder_list is empty")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 创建左侧树模型
|
|
|
|
|
self.left_model, self.folder_map = TreeModelManager.create_left_tree_model(folder_list)
|
|
|
|
|
|
|
|
|
|
# 创建右侧树模型
|
|
|
|
|
folder_names = [os.path.basename(f) for f in folder_list]
|
|
|
|
|
self.right_model, self.json_data = TreeModelManager.create_right_tree_model(folder_names)
|
|
|
|
|
|
|
|
|
|
# 初始化JSON文件
|
|
|
|
|
for folder_name in folder_names:
|
|
|
|
|
self.initialize_json_file(folder_name)
|
|
|
|
|
|
|
|
|
|
self.refresh_views() # 添加视图刷新
|
|
|
|
|
|
|
|
|
|
def initialize_json_file(self, folder_name):
|
|
|
|
|
"""初始化JSON文件"""
|
|
|
|
|
if not hasattr(self, 'output_path') or not self.output_path:
|
|
|
|
|
QMessageBox.warning(self, "Warning", "Output path not set!")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
json_filename = self.get_json_file_path(folder_name)
|
|
|
|
|
|
|
|
|
|
# 如果文件已存在,则读取现有数据
|
|
|
|
|
if os.path.exists(json_filename):
|
|
|
|
|
existing_data = JsonFileHandler.read_json(json_filename)
|
|
|
|
|
if existing_data:
|
|
|
|
|
self.json_data[folder_name].update(existing_data)
|
|
|
|
|
print(f"加载已有json文件: {json_filename},{existing_data}")
|
|
|
|
|
TreeModelManager.update_model_from_json(
|
|
|
|
|
self.right_model, folder_name, self.json_data
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
# 创建新JSON文件
|
|
|
|
|
self.save_to_json(folder_name)
|
|
|
|
|
|
|
|
|
|
def get_json_file_path(self, folder_name):
|
|
|
|
|
"""
|
|
|
|
|
获取JSON文件路径
|
|
|
|
|
规则: 使用原始文件夹名 + .json后缀,保存到输出目录
|
|
|
|
|
"""
|
|
|
|
|
# 确保文件夹名是有效的文件名
|
|
|
|
|
safe_name = folder_name.strip()
|
|
|
|
|
# 只添加.json后缀,不做其他修改
|
|
|
|
|
if not safe_name.lower().endswith('.json'):
|
|
|
|
|
safe_name += '.json'
|
|
|
|
|
return os.path.join(self.output_path, safe_name)
|
|
|
|
|
|
|
|
|
|
def on_right_data_changed(self, top_left, bottom_right):
|
|
|
|
|
folder_item = self.right_model.itemFromIndex(top_left)
|
|
|
|
|
while folder_item and folder_item.parent() is not None:
|
|
|
|
|
folder_item = folder_item.parent()
|
|
|
|
|
print(f"开始更改")
|
|
|
|
|
if folder_item:
|
|
|
|
|
folder_name = folder_item.text()
|
|
|
|
|
print(f"变更检测到文件夹:{folder_name}")
|
|
|
|
|
|
|
|
|
|
# 调试:打印变更前的数据
|
|
|
|
|
print("变更前数据:", self.json_data.get(folder_name))
|
|
|
|
|
|
|
|
|
|
# 更新数据
|
|
|
|
|
TreeModelManager.update_json_from_model(self.right_model, self.json_data)
|
|
|
|
|
|
|
|
|
|
# 调试:打印变更后的数据
|
|
|
|
|
print("变更后数据:", self.json_data.get(folder_name))
|
|
|
|
|
|
|
|
|
|
self.save_to_json(folder_name)
|
|
|
|
|
self.data_changed.emit(self.json_data)
|
|
|
|
|
|
|
|
|
|
def save_to_json(self, folder_name):
|
2025-08-11 17:58:06 +08:00
|
|
|
|
"""将类中现在的self.json_data保存到JSON文件
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
folder_name (str): 文件夹名称(要保存的json文件名,文件名为机组号)
|
|
|
|
|
"""
|
2025-08-07 18:16:09 +08:00
|
|
|
|
json_path = self.get_json_file_path(folder_name)
|
|
|
|
|
print(f"🛠️ 准备保存到: {json_path}")
|
|
|
|
|
|
|
|
|
|
# 验证路径
|
|
|
|
|
if os.path.isdir(json_path):
|
|
|
|
|
print(f"❌ 错误:路径是目录,自动添加.json后缀")
|
|
|
|
|
json_path += '.json'
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# 确保目录存在
|
|
|
|
|
os.makedirs(os.path.dirname(json_path), exist_ok=True)
|
|
|
|
|
|
|
|
|
|
# 写入文件
|
|
|
|
|
with open(json_path, 'w', encoding='utf-8') as f:
|
|
|
|
|
json.dump(self.json_data[folder_name], f, ensure_ascii=False, indent=4)
|
|
|
|
|
print(f"✅ 成功保存: {json_path}")
|
|
|
|
|
return True
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"❌ 保存失败: {str(e)}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def batch_update_folders(self, folder_mapping):
|
|
|
|
|
"""批量更新文件夹结构"""
|
|
|
|
|
for folder_name, new_data in folder_mapping.items():
|
|
|
|
|
if folder_name in self.json_data:
|
|
|
|
|
self.json_data[folder_name].update(new_data)
|
|
|
|
|
TreeModelManager.update_model_from_json(
|
|
|
|
|
self.right_model, folder_name, self.json_data
|
|
|
|
|
)
|
|
|
|
|
self.save_to_json(folder_name)
|
|
|
|
|
|
|
|
|
|
self.data_changed.emit(self.json_data)
|
|
|
|
|
|
|
|
|
|
def get_output_path(self):
|
|
|
|
|
"""获取当前输出路径"""
|
|
|
|
|
return self.output_path
|
|
|
|
|
|
|
|
|
|
def get_current_data(self):
|
|
|
|
|
"""获取当前所有数据"""
|
2025-08-08 18:00:56 +08:00
|
|
|
|
return self.json_data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ImageLoaderSignals(QObject):
|
|
|
|
|
"""加载信号"""
|
|
|
|
|
image_loaded = Signal(str, QPixmap) # 文件路径, 加载好的图片
|
|
|
|
|
loading_finished = Signal() # 加载完成
|
|
|
|
|
progress_updated = Signal(int) # 进度更新信号
|
|
|
|
|
|
|
|
|
|
class ImageLoaderWorker(QRunnable):
|
|
|
|
|
"""单个图片加载任务"""
|
|
|
|
|
def __init__(self, file_path, signals, thumbnail_size = 300):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.file_path = file_path
|
|
|
|
|
self.signals = signals
|
|
|
|
|
self.thumbnail_size = thumbnail_size
|
|
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
|
"""加载图片"""
|
|
|
|
|
try:
|
|
|
|
|
# 方法1: 直接读取并缩放为缩略图 (最快)
|
|
|
|
|
reader = QImageReader(self.file_path)
|
|
|
|
|
|
|
|
|
|
# 设置缩放尺寸以提高读取速度
|
|
|
|
|
size = reader.size()
|
|
|
|
|
if size.width() > 1000 or size.height() > 1000:
|
|
|
|
|
reader.setScaledSize(QSize(1000, 1000))
|
|
|
|
|
|
|
|
|
|
# 读取图像 (不进行高质量转换)
|
|
|
|
|
image = reader.read()
|
|
|
|
|
if image.isNull():
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 快速生成缩略图 (低质量)
|
|
|
|
|
thumb = image.scaled(
|
|
|
|
|
self.thumbnail_size, self.thumbnail_size,
|
|
|
|
|
Qt.KeepAspectRatio,
|
|
|
|
|
Qt.FastTransformation # 使用快速转换模式
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 转换为QPixmap
|
|
|
|
|
pixmap = QPixmap.fromImage(thumb)
|
|
|
|
|
if not pixmap.isNull():
|
|
|
|
|
self.signals.image_loaded.emit(self.file_path, pixmap)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"加载图片出错: {self.file_path}, 错误: {str(e)}")
|
|
|
|
|
|
|
|
|
|
class ImageLoader(QObject):
|
|
|
|
|
"""图片加载管理器"""
|
|
|
|
|
def __init__(self, image_files, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self.image_files = image_files
|
|
|
|
|
self.signals = ImageLoaderSignals()
|
|
|
|
|
self.thread_pool = QThreadPool.globalInstance()
|
|
|
|
|
self.batch_size = 40 # 每批加载数量
|
|
|
|
|
self.current_index = 0
|
|
|
|
|
self.timer = QTimer()
|
|
|
|
|
self.timer.timeout.connect(self.load_next_batch)
|
|
|
|
|
self.cancel_requested = False
|
|
|
|
|
|
|
|
|
|
# 暴露信号
|
|
|
|
|
self.image_loaded = self.signals.image_loaded
|
|
|
|
|
self.loading_finished = self.signals.loading_finished
|
|
|
|
|
self.progress_updated = self.signals.progress_updated
|
|
|
|
|
|
|
|
|
|
def start_loading(self):
|
|
|
|
|
"""开始加载图片"""
|
|
|
|
|
self.current_index = 0
|
|
|
|
|
self.cancel_requested = False
|
|
|
|
|
self.load_next_batch()
|
|
|
|
|
|
|
|
|
|
def load_next_batch(self):
|
|
|
|
|
"""加载下一批图片"""
|
|
|
|
|
if self.cancel_requested:
|
|
|
|
|
self.signals.loading_finished.emit()
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
end_index = min(self.current_index + self.batch_size, len(self.image_files))
|
|
|
|
|
|
|
|
|
|
# 批量提交任务
|
|
|
|
|
for i in range(self.current_index, end_index):
|
|
|
|
|
file_path = self.image_files[i]
|
|
|
|
|
worker = ImageLoaderWorker(file_path, self.signals)
|
|
|
|
|
self.thread_pool.start(worker)
|
|
|
|
|
|
|
|
|
|
self.current_index = end_index
|
|
|
|
|
|
|
|
|
|
# 计算并发射进度
|
|
|
|
|
progress = int((self.current_index / len(self.image_files)) * 100)
|
|
|
|
|
self.signals.progress_updated.emit(progress)
|
|
|
|
|
|
|
|
|
|
if self.current_index < len(self.image_files):
|
|
|
|
|
# 更短的间隔加载下一批
|
|
|
|
|
self.timer.start(50) # 50ms后加载下一批
|
|
|
|
|
else:
|
|
|
|
|
# 全部加载完成
|
|
|
|
|
self.signals.loading_finished.emit()
|
|
|
|
|
self.signals.progress_updated.emit(100)
|
|
|
|
|
self.timer.stop()
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"加载过程中出错: {str(e)}")
|
|
|
|
|
self.signals.loading_finished.emit()
|
|
|
|
|
self.timer.stop()
|
|
|
|
|
|
|
|
|
|
def cancel(self):
|
|
|
|
|
"""取消加载"""
|
|
|
|
|
self.cancel_requested = True
|
|
|
|
|
self.timer.stop()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ImageBrowser(QWidget):
|
|
|
|
|
"""主显示窗口"""
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self.current_dir = ""
|
|
|
|
|
self.image_labels = {} # key: 文件路径, value: QLabel
|
|
|
|
|
self.image_loader = None # 图片加载器实例
|
|
|
|
|
self.cols_per_row = 4 # 默认每行列数
|
|
|
|
|
self.image_files = [] # 存储当前目录下的图片文件路径
|
|
|
|
|
self.progress_dialog = None # 进度条对话框
|
|
|
|
|
self.thumbnail_size = 100 # 缩略图大小
|
|
|
|
|
self.init_ui()
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
"""初始化界面"""
|
|
|
|
|
self.setMinimumHeight(200)
|
|
|
|
|
|
|
|
|
|
# 主布局
|
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
|
|
|
|
|
# 分割线
|
|
|
|
|
line = QFrame()
|
|
|
|
|
line.setFrameShape(QFrame.HLine)
|
|
|
|
|
line.setFrameShadow(QFrame.Sunken)
|
|
|
|
|
layout.addWidget(line)
|
|
|
|
|
|
|
|
|
|
# 图片浏览区域
|
|
|
|
|
self.scroll_area = QScrollArea()
|
|
|
|
|
self.scroll_area.setWidgetResizable(True)
|
|
|
|
|
|
|
|
|
|
# 使用网格布局
|
|
|
|
|
self.image_container = QWidget()
|
|
|
|
|
self.image_layout = QGridLayout(self.image_container)
|
|
|
|
|
self.image_layout.setContentsMargins(5, 5, 5, 5)
|
|
|
|
|
self.image_layout.setSpacing(10)
|
|
|
|
|
self.image_layout.setAlignment(Qt.AlignTop)
|
|
|
|
|
|
|
|
|
|
self.scroll_area.setWidget(self.image_container)
|
|
|
|
|
layout.addWidget(self.scroll_area)
|
|
|
|
|
|
|
|
|
|
# 初始状态提示
|
|
|
|
|
self.placeholder_label = QLabel("请点击左侧目录查看图片")
|
|
|
|
|
self.placeholder_label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
self.placeholder_label.setStyleSheet("color: gray; font-size: 14px;")
|
|
|
|
|
self.scroll_area.setWidget(self.placeholder_label)
|
|
|
|
|
|
|
|
|
|
# 创建简单的加载中图标
|
|
|
|
|
self.loading_pixmap = self.create_loading_pixmap()
|
|
|
|
|
|
|
|
|
|
def create_loading_pixmap(self):
|
|
|
|
|
"""创建加载中图标"""
|
|
|
|
|
pixmap = QPixmap(50, 50)
|
|
|
|
|
pixmap.fill(Qt.transparent)
|
|
|
|
|
painter = QPainter(pixmap)
|
|
|
|
|
painter.setPen(QColor(200, 200, 200))
|
|
|
|
|
painter.drawEllipse(5, 5, 40, 40)
|
|
|
|
|
painter.drawText(QRect(5, 5, 40, 40), Qt.AlignCenter, "...")
|
|
|
|
|
painter.end()
|
|
|
|
|
return pixmap
|
|
|
|
|
|
|
|
|
|
def show_images(self, dir_path):
|
|
|
|
|
"""显示指定目录下的图片"""
|
|
|
|
|
self.current_dir = dir_path
|
|
|
|
|
self.clear_images()
|
|
|
|
|
|
|
|
|
|
if not dir_path:
|
|
|
|
|
self.scroll_area.setWidget(self.placeholder_label)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 读取目录中的图片文件
|
|
|
|
|
dir = QDir(dir_path)
|
|
|
|
|
self.image_files = [dir.filePath(f) for f in dir.entryList(
|
|
|
|
|
["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.JPG", "*.JPEG", "*.PNG", "*.BMP"],
|
|
|
|
|
QDir.Files, QDir.Name
|
|
|
|
|
)]
|
|
|
|
|
|
|
|
|
|
if not self.image_files:
|
|
|
|
|
no_image_label = QLabel(f"该目录没有图片文件: {dir.path()}")
|
|
|
|
|
no_image_label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
no_image_label.setStyleSheet("color: gray; font-size: 14px;")
|
|
|
|
|
self.scroll_area.setWidget(no_image_label)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 创建图片容器
|
|
|
|
|
self.image_container = QWidget()
|
|
|
|
|
self.image_layout = QGridLayout(self.image_container)
|
|
|
|
|
self.image_layout.setContentsMargins(5, 5, 5, 5)
|
|
|
|
|
self.image_layout.setSpacing(5)
|
|
|
|
|
self.image_layout.setAlignment(Qt.AlignTop)
|
|
|
|
|
|
|
|
|
|
self.scroll_area.setWidget(self.image_container)
|
|
|
|
|
|
|
|
|
|
# 先创建所有占位符
|
|
|
|
|
for i, file_path in enumerate(self.image_files):
|
|
|
|
|
self.add_placeholder(file_path, i)
|
|
|
|
|
|
|
|
|
|
# 创建并启动图片加载器
|
|
|
|
|
if self.image_loader:
|
|
|
|
|
self.image_loader.deleteLater()
|
|
|
|
|
|
|
|
|
|
# 创建进度对话框
|
|
|
|
|
self.progress_dialog = QProgressDialog("正在加载图片...", "取消", 0, 100, self)
|
|
|
|
|
self.progress_dialog.setWindowTitle("图片加载进度")
|
|
|
|
|
self.progress_dialog.setWindowModality(Qt.WindowModal)
|
|
|
|
|
self.progress_dialog.setAutoClose(True)
|
|
|
|
|
self.progress_dialog.setAutoReset(True)
|
|
|
|
|
self.progress_dialog.canceled.connect(self.cancel_loading)
|
|
|
|
|
|
|
|
|
|
self.image_loader = ImageLoader(self.image_files)
|
|
|
|
|
self.image_loader.image_loaded.connect(self.update_image)
|
|
|
|
|
self.image_loader.loading_finished.connect(self.on_loading_finished)
|
|
|
|
|
self.image_loader.progress_updated.connect(self.update_progress)
|
|
|
|
|
self.image_loader.start_loading()
|
|
|
|
|
|
|
|
|
|
def cancel_loading(self):
|
|
|
|
|
"""取消加载"""
|
|
|
|
|
if self.image_loader:
|
|
|
|
|
self.image_loader.cancel()
|
|
|
|
|
self.image_loader.deleteLater()
|
|
|
|
|
self.image_loader = None
|
|
|
|
|
|
|
|
|
|
if self.progress_dialog:
|
|
|
|
|
self.progress_dialog.close()
|
|
|
|
|
self.progress_dialog = None
|
|
|
|
|
|
|
|
|
|
def update_progress(self, value):
|
|
|
|
|
"""更新进度条"""
|
|
|
|
|
if self.progress_dialog:
|
|
|
|
|
self.progress_dialog.setValue(value)
|
|
|
|
|
|
|
|
|
|
def on_loading_finished(self):
|
|
|
|
|
"""图片加载完成后的处理"""
|
|
|
|
|
if self.progress_dialog:
|
|
|
|
|
self.progress_dialog.close()
|
|
|
|
|
self.progress_dialog = None
|
|
|
|
|
|
|
|
|
|
if self.image_loader:
|
|
|
|
|
self.image_loader.deleteLater()
|
|
|
|
|
self.image_loader = None
|
|
|
|
|
|
|
|
|
|
def add_placeholder(self, file_path, index):
|
|
|
|
|
"""添加占位符到指定位置"""
|
|
|
|
|
self.update_columns_count()
|
|
|
|
|
row = index // self.cols_per_row
|
|
|
|
|
col = index % self.cols_per_row
|
|
|
|
|
|
|
|
|
|
placeholder = self.create_placeholder(file_path)
|
|
|
|
|
self.image_layout.addWidget(placeholder, row, col)
|
|
|
|
|
self.image_labels[file_path] = placeholder
|
|
|
|
|
|
|
|
|
|
def create_placeholder(self, file_path):
|
|
|
|
|
"""创建单个占位符 - 添加双击事件和拖动支持"""
|
|
|
|
|
file_name = QDir(file_path).dirName()
|
|
|
|
|
|
|
|
|
|
placeholder = QLabel()
|
|
|
|
|
placeholder.setFixedSize(self.thumbnail_size, self.thumbnail_size)
|
|
|
|
|
placeholder.setAlignment(Qt.AlignCenter)
|
|
|
|
|
placeholder.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
|
|
|
|
placeholder.setStyleSheet("""
|
|
|
|
|
QLabel {
|
|
|
|
|
border: 1px solid #eee;
|
|
|
|
|
background: white;
|
|
|
|
|
qproperty-alignment: 'AlignCenter';
|
|
|
|
|
}
|
|
|
|
|
QLabel:hover {
|
|
|
|
|
border: 1px solid #aaa;
|
|
|
|
|
background: #f5f5f5;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
placeholder.setPixmap(self.loading_pixmap)
|
|
|
|
|
placeholder.setProperty("file_path", file_path)
|
|
|
|
|
placeholder.setToolTip(f"加载中: {file_name}")
|
|
|
|
|
|
|
|
|
|
# 安装事件过滤器处理鼠标事件
|
|
|
|
|
placeholder.installEventFilter(self)
|
|
|
|
|
|
|
|
|
|
# 存储拖动相关状态
|
|
|
|
|
placeholder.setProperty("drag_start_pos", None)
|
|
|
|
|
placeholder.setProperty("drag_threshold", 5) # 拖动触发阈值(像素)
|
|
|
|
|
|
|
|
|
|
return placeholder
|
|
|
|
|
|
|
|
|
|
def update_image(self, file_path, pixmap):
|
|
|
|
|
"""更新图片显示"""
|
|
|
|
|
if file_path in self.image_labels:
|
|
|
|
|
label = self.image_labels[file_path]
|
|
|
|
|
label.setPixmap(pixmap.scaled(
|
|
|
|
|
self.thumbnail_size, self.thumbnail_size,
|
|
|
|
|
Qt.KeepAspectRatio, Qt.SmoothTransformation
|
|
|
|
|
))
|
|
|
|
|
file_name = QDir(file_path).dirName()
|
|
|
|
|
label.setToolTip(f"文件名: {file_name}")
|
|
|
|
|
label.setStyleSheet("""
|
|
|
|
|
QLabel { border: 1px solid #ddd; background: white; }
|
|
|
|
|
QLabel:hover { border: 1px solid #aaa; background: #f0f0f0; }
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
def open_image(self, file_path):
|
|
|
|
|
"""用系统默认程序打开图片"""
|
|
|
|
|
try:
|
|
|
|
|
if os.name == 'nt': # Windows
|
|
|
|
|
os.startfile(file_path)
|
|
|
|
|
elif os.name == 'posix': # macOS/Linux
|
|
|
|
|
opener = "open" if sys.platform == "darwin" else "xdg-open"
|
|
|
|
|
import subprocess
|
|
|
|
|
subprocess.call([opener, file_path])
|
|
|
|
|
except Exception as e:
|
|
|
|
|
QMessageBox.warning(self, "打开失败", f"无法打开图片: {str(e)}")
|
|
|
|
|
|
|
|
|
|
def update_columns_count(self):
|
|
|
|
|
"""根据当前宽度更新每行列数"""
|
|
|
|
|
if not self.image_container or not self.isVisible():
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# 获取容器实际可用宽度(减去左右边距和间距)
|
|
|
|
|
margins = self.image_layout.contentsMargins()
|
|
|
|
|
spacing = self.image_layout.spacing()
|
|
|
|
|
|
|
|
|
|
# 可用宽度 = 容器宽度 - 左边距 - 右边距
|
|
|
|
|
available_width = self.image_container.width() - margins.left() - margins.right()
|
|
|
|
|
|
|
|
|
|
# 每个项目需要的宽度 = 缩略图宽度 + 间距
|
|
|
|
|
item_width = self.thumbnail_size + spacing
|
|
|
|
|
|
|
|
|
|
# 计算最大列数(至少1列)
|
|
|
|
|
self.cols_per_row = max(1, available_width // item_width)
|
|
|
|
|
except RuntimeError:
|
|
|
|
|
# 对象已被删除,忽略错误
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def resizeEvent(self, event):
|
|
|
|
|
"""窗口大小改变时重新计算列数"""
|
|
|
|
|
super().resizeEvent(event)
|
|
|
|
|
if not self.current_dir or not self.image_labels:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
self.update_columns_count()
|
|
|
|
|
QTimer.singleShot(100, self.recalculate_layout)
|
|
|
|
|
except RuntimeError:
|
|
|
|
|
# 对象已被删除,忽略错误
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def recalculate_layout(self):
|
|
|
|
|
"""重新计算布局"""
|
|
|
|
|
try:
|
|
|
|
|
if not hasattr(self, 'image_files') or not self.image_files or not self.image_container:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 保存当前滚动位置
|
|
|
|
|
scroll_bar = self.scroll_area.verticalScrollBar()
|
|
|
|
|
old_scroll = scroll_bar.value()
|
|
|
|
|
|
|
|
|
|
# 清除现有布局
|
|
|
|
|
for i in reversed(range(self.image_layout.count())):
|
|
|
|
|
widget = self.image_layout.itemAt(i).widget()
|
|
|
|
|
if widget:
|
|
|
|
|
self.image_layout.removeWidget(widget)
|
|
|
|
|
|
|
|
|
|
# 更新列数
|
|
|
|
|
self.update_columns_count()
|
|
|
|
|
|
|
|
|
|
# 重新添加已加载的图片
|
|
|
|
|
for i, file_path in enumerate(self.image_files):
|
|
|
|
|
if file_path in self.image_labels:
|
|
|
|
|
row = i // self.cols_per_row
|
|
|
|
|
col = i % self.cols_per_row
|
|
|
|
|
self.image_layout.addWidget(self.image_labels[file_path], row, col)
|
|
|
|
|
|
|
|
|
|
# 设置列拉伸,使最后一列填充剩余空间
|
|
|
|
|
for c in range(self.cols_per_row):
|
|
|
|
|
self.image_layout.setColumnStretch(c, 1)
|
|
|
|
|
|
|
|
|
|
# 恢复滚动位置
|
|
|
|
|
scroll_bar.setValue(old_scroll)
|
|
|
|
|
except RuntimeError:
|
|
|
|
|
# 对象已被删除,忽略错误
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def clear_images(self):
|
|
|
|
|
"""清除所有图片"""
|
|
|
|
|
for label in self.image_labels.values():
|
|
|
|
|
try:
|
|
|
|
|
label.deleteLater()
|
|
|
|
|
except RuntimeError:
|
|
|
|
|
pass
|
|
|
|
|
self.image_labels.clear()
|
|
|
|
|
|
|
|
|
|
if self.image_loader:
|
|
|
|
|
try:
|
|
|
|
|
self.image_loader.deleteLater()
|
|
|
|
|
except RuntimeError:
|
|
|
|
|
pass
|
|
|
|
|
self.image_loader = None
|
|
|
|
|
|
|
|
|
|
def eventFilter(self, obj, event):
|
|
|
|
|
"""处理鼠标事件以实现双击和拖动"""
|
|
|
|
|
try:
|
|
|
|
|
if isinstance(obj, QLabel) and obj in self.image_labels.values():
|
|
|
|
|
file_path = obj.property("file_path")
|
|
|
|
|
|
|
|
|
|
if event.type() == QEvent.MouseButtonPress:
|
|
|
|
|
if event.button() == Qt.LeftButton:
|
|
|
|
|
# 记录鼠标按下位置和时间
|
|
|
|
|
obj.setProperty("drag_start_pos", event.pos())
|
|
|
|
|
obj.setProperty("press_time", QDateTime.currentMSecsSinceEpoch())
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
elif event.type() == QEvent.MouseMove:
|
|
|
|
|
if obj.property("drag_start_pos") is not None:
|
|
|
|
|
# 检查是否达到拖动阈值
|
|
|
|
|
start_pos = obj.property("drag_start_pos")
|
|
|
|
|
if (event.pos() - start_pos).manhattanLength() > obj.property("drag_threshold"):
|
|
|
|
|
# 触发拖动
|
|
|
|
|
self.start_drag(obj, event)
|
|
|
|
|
obj.setProperty("drag_start_pos", None)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
elif event.type() == QEvent.MouseButtonRelease:
|
|
|
|
|
if event.button() == Qt.LeftButton and obj.property("drag_start_pos") is not None:
|
|
|
|
|
# 只清除状态,不触发任何动作
|
|
|
|
|
obj.setProperty("drag_start_pos", None)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
elif event.type() == QEvent.MouseButtonDblClick:
|
|
|
|
|
if event.button() == Qt.LeftButton:
|
|
|
|
|
self.open_image(file_path)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
except RuntimeError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
return super().eventFilter(obj, event)
|
|
|
|
|
|
|
|
|
|
def open_image(self, file_path):
|
|
|
|
|
"""用默认方式打开图片"""
|
|
|
|
|
try:
|
|
|
|
|
import os
|
|
|
|
|
if os.name == 'nt': # Windows
|
|
|
|
|
os.startfile(file_path)
|
|
|
|
|
elif os.name == 'posix': # macOS/Linux
|
|
|
|
|
import subprocess
|
|
|
|
|
opener = "open" if sys.platform == "darwin" else "xdg-open"
|
|
|
|
|
subprocess.call([opener, file_path])
|
|
|
|
|
except Exception as e:
|
|
|
|
|
QMessageBox.warning(self, "打开失败", f"无法打开图片: {str(e)}")
|
|
|
|
|
|
|
|
|
|
def start_drag(self, label, event):
|
|
|
|
|
"""开始拖动图片"""
|
|
|
|
|
try:
|
|
|
|
|
if label.pixmap() and not label.pixmap().isNull():
|
|
|
|
|
file_path = label.property("file_path")
|
|
|
|
|
|
|
|
|
|
drag = QDrag(self)
|
|
|
|
|
mime_data = QMimeData()
|
|
|
|
|
|
|
|
|
|
# 设置多种MIME类型以支持不同场景
|
|
|
|
|
url = QUrl.fromLocalFile(file_path)
|
|
|
|
|
mime_data.setUrls([url])
|
|
|
|
|
mime_data.setText(file_path)
|
|
|
|
|
mime_data.setImageData(label.pixmap().toImage())
|
|
|
|
|
|
|
|
|
|
drag.setMimeData(mime_data)
|
|
|
|
|
|
|
|
|
|
# 设置拖动时的缩略图
|
|
|
|
|
thumb = label.pixmap().scaled(
|
|
|
|
|
200, 200, Qt.KeepAspectRatio, Qt.SmoothTransformation
|
|
|
|
|
)
|
|
|
|
|
drag.setPixmap(thumb)
|
|
|
|
|
|
|
|
|
|
# 设置热点位置
|
|
|
|
|
drag.setHotSpot(QPoint(
|
|
|
|
|
thumb.width() // 2,
|
|
|
|
|
thumb.height() // 2
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
# 执行拖动操作
|
|
|
|
|
drag.exec(Qt.CopyAction | Qt.MoveAction)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"拖动失败: {str(e)}")
|
|
|
|
|
|
2025-08-11 17:58:06 +08:00
|
|
|
|
class OverlayLabel(QLabel):
|
|
|
|
|
"""支持叠加图标的标签控件"""
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self._overlay_icon = None
|
|
|
|
|
|
|
|
|
|
def set_overlay_icon(self, standard_pixmap):
|
|
|
|
|
"""设置叠加图标"""
|
|
|
|
|
self._overlay_icon = standard_pixmap
|
|
|
|
|
self.update()
|
|
|
|
|
|
|
|
|
|
def paintEvent(self, event):
|
|
|
|
|
"""重绘事件,绘制图标叠加"""
|
|
|
|
|
super().paintEvent(event)
|
|
|
|
|
|
|
|
|
|
if self._overlay_icon and self.pixmap():
|
|
|
|
|
# 获取系统标准图标
|
|
|
|
|
icon = self.style().standardIcon(self._overlay_icon)
|
|
|
|
|
pixmap = icon.pixmap(24, 24) # 图标大小
|
|
|
|
|
|
|
|
|
|
# 在右下角绘制图标
|
|
|
|
|
painter = QPainter(self)
|
|
|
|
|
margin = 5
|
|
|
|
|
x = self.width() - pixmap.width() - margin
|
|
|
|
|
y = self.height() - pixmap.height() - margin
|
|
|
|
|
painter.drawPixmap(x, y, pixmap)
|
|
|
|
|
painter.end()
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
|
|
|
|
class DictionaryBrowser(QWidget):
|
2025-08-11 17:58:06 +08:00
|
|
|
|
"""字典浏览编辑窗口"""
|
|
|
|
|
dictionary_updated = Signal(str, str, str, dict) # 原文件夹名, part名称, 字典类型(英文), 字典数据
|
|
|
|
|
|
|
|
|
|
# 中英文映射字典
|
|
|
|
|
DICT_TYPE_MAPPING = {
|
|
|
|
|
"缺陷图字典": "defect_picture_dict",
|
|
|
|
|
"典型图字典": "typical_picture_dict",
|
|
|
|
|
"叶片1" : "part1",
|
|
|
|
|
"叶片2" : "part2",
|
|
|
|
|
"叶片3" : "part3",
|
|
|
|
|
}
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
2025-08-11 17:58:06 +08:00
|
|
|
|
REVERSE_DICT_MAPPING = {v: k for k, v in DICT_TYPE_MAPPING.items()}
|
|
|
|
|
|
2025-08-08 18:00:56 +08:00
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super().__init__(parent)
|
2025-08-11 17:58:06 +08:00
|
|
|
|
self.original_folder_name = "" # 原文件夹名
|
|
|
|
|
self.current_part = "" # 当前part名称(part1/part2/part3)
|
|
|
|
|
self.current_dict_type = "" # 当前字典类型(英文)
|
|
|
|
|
self.current_dict_data = {} # 当前字典数据
|
2025-08-08 18:00:56 +08:00
|
|
|
|
self.init_ui()
|
2025-08-11 17:58:06 +08:00
|
|
|
|
|
2025-08-08 18:00:56 +08:00
|
|
|
|
def init_ui(self):
|
2025-08-11 17:58:06 +08:00
|
|
|
|
"""初始化界面"""
|
2025-08-08 18:00:56 +08:00
|
|
|
|
self.setMinimumWidth(300)
|
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
|
layout.setContentsMargins(5, 5, 5, 5)
|
|
|
|
|
|
|
|
|
|
# 标题标签
|
|
|
|
|
self.title_label = QLabel("请选择字典节点")
|
|
|
|
|
self.title_label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
self.title_label.setStyleSheet("font-weight: bold; font-size: 14px;")
|
|
|
|
|
layout.addWidget(self.title_label)
|
|
|
|
|
|
|
|
|
|
# 图片浏览区域
|
|
|
|
|
self.scroll_area = QScrollArea()
|
|
|
|
|
self.scroll_area.setWidgetResizable(True)
|
|
|
|
|
layout.addWidget(self.scroll_area)
|
|
|
|
|
|
|
|
|
|
# 初始状态提示
|
2025-08-11 17:58:06 +08:00
|
|
|
|
self.set_placeholder("请点击右侧JSON树中的字典节点")
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
|
|
|
|
# 设置拖放接收
|
|
|
|
|
self.setAcceptDrops(True)
|
|
|
|
|
|
2025-08-11 17:58:06 +08:00
|
|
|
|
def set_placeholder(self, text):
|
|
|
|
|
"""设置占位文本"""
|
|
|
|
|
label = QLabel(text)
|
|
|
|
|
label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
label.setStyleSheet("color: gray; font-size: 14px;")
|
|
|
|
|
self.scroll_area.setWidget(label)
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
2025-08-11 17:58:06 +08:00
|
|
|
|
def show_dictionary(self, folder_name, part_name, dict_type_display, dict_data):
|
|
|
|
|
"""显示指定字典内容
|
|
|
|
|
:param dict_type_display: 显示用的字典类型名称(中文)
|
|
|
|
|
"""
|
|
|
|
|
self.original_folder_name = folder_name
|
|
|
|
|
self.current_part = self.DICT_TYPE_MAPPING.get(part_name, "")
|
|
|
|
|
# 转换为英文键名存储
|
|
|
|
|
self.current_dict_type = self.DICT_TYPE_MAPPING.get(dict_type_display, "")
|
|
|
|
|
self.current_dict_data = dict_data or {}
|
|
|
|
|
|
2025-08-08 18:00:56 +08:00
|
|
|
|
self.update_title()
|
2025-08-11 17:58:06 +08:00
|
|
|
|
self.refresh_view()
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
|
|
|
|
def update_title(self):
|
2025-08-11 17:58:06 +08:00
|
|
|
|
"""更新标题显示(使用中文)"""
|
|
|
|
|
if not all([self.original_folder_name, self.current_part, self.current_dict_type]):
|
2025-08-08 18:00:56 +08:00
|
|
|
|
self.title_label.setText("请选择字典节点")
|
|
|
|
|
else:
|
2025-08-11 17:58:06 +08:00
|
|
|
|
# 转换为中文显示
|
|
|
|
|
dict_name = self.REVERSE_DICT_MAPPING.get(self.current_dict_type, self.current_dict_type)
|
|
|
|
|
self.title_label.setText(f"{self.original_folder_name} - {self.current_part} - {dict_name}")
|
|
|
|
|
|
|
|
|
|
def refresh_view(self):
|
|
|
|
|
"""刷新视图"""
|
|
|
|
|
# 安全清除旧内容
|
|
|
|
|
old_widget = self.scroll_area.takeWidget()
|
|
|
|
|
if old_widget:
|
|
|
|
|
old_widget.deleteLater()
|
|
|
|
|
|
|
|
|
|
if not all([self.original_folder_name, self.current_part, self.current_dict_type]):
|
|
|
|
|
self.set_placeholder("请选择有效的字典节点")
|
2025-08-08 18:00:56 +08:00
|
|
|
|
return
|
|
|
|
|
|
2025-08-11 17:58:06 +08:00
|
|
|
|
if not self.current_dict_data:
|
|
|
|
|
self.set_placeholder("该字典没有数据")
|
2025-08-08 18:00:56 +08:00
|
|
|
|
return
|
|
|
|
|
|
2025-08-11 17:58:06 +08:00
|
|
|
|
# 创建新的容器
|
|
|
|
|
container = QWidget()
|
|
|
|
|
layout = QVBoxLayout(container) # 改为垂直布局
|
|
|
|
|
layout.setContentsMargins(5, 5, 5, 5)
|
|
|
|
|
layout.setSpacing(5)
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
2025-08-11 17:58:06 +08:00
|
|
|
|
# 创建水平流式布局的容器
|
|
|
|
|
flow_widget = QWidget()
|
|
|
|
|
flow_layout = QHBoxLayout(flow_widget)
|
|
|
|
|
flow_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
flow_layout.setSpacing(5)
|
|
|
|
|
flow_layout.setAlignment(Qt.AlignLeft | Qt.AlignTop) # 左对齐且顶部对齐
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
|
|
|
|
# 添加图片项
|
2025-08-11 17:58:06 +08:00
|
|
|
|
for key, img_path in self.current_dict_data.items():
|
|
|
|
|
self._add_image_item(flow_layout, img_path, key)
|
|
|
|
|
|
|
|
|
|
# 添加流式布局到主布局
|
|
|
|
|
layout.addWidget(flow_widget)
|
|
|
|
|
layout.addStretch(1) # 添加拉伸因子使内容保持在顶部
|
|
|
|
|
|
|
|
|
|
self.scroll_area.setWidget(container)
|
|
|
|
|
|
|
|
|
|
def _check_description_format(self, key):
|
|
|
|
|
"""检查描述是否符合标准格式"""
|
|
|
|
|
if not key or "_" not in key:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
parts = key.split("_")
|
|
|
|
|
# 检查是否有足够的字段(序号+6个标准字段+建议)
|
|
|
|
|
return len(parts) >= 8
|
|
|
|
|
|
|
|
|
|
def _add_image_item(self, parent_layout, img_path, key):
|
|
|
|
|
"""添加单个图片项 - 只显示图片,符合格式的显示对勾"""
|
|
|
|
|
thumbnail_size = 150
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
|
|
|
|
# 创建容器widget
|
|
|
|
|
item_widget = QWidget()
|
2025-08-11 17:58:06 +08:00
|
|
|
|
|
|
|
|
|
# 设置悬停提示
|
|
|
|
|
hover_text = f"图片路径: {img_path}\n\n描述详情:\n{self.get_description_display(key)}"
|
|
|
|
|
item_widget.setToolTip(hover_text)
|
|
|
|
|
|
2025-08-08 18:00:56 +08:00
|
|
|
|
item_layout = QVBoxLayout(item_widget)
|
|
|
|
|
item_layout.setContentsMargins(5, 5, 5, 5)
|
2025-08-11 17:58:06 +08:00
|
|
|
|
item_layout.setSpacing(0)
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
2025-08-11 17:58:06 +08:00
|
|
|
|
# 图片标签 - 使用自定义的OverlayLabel类
|
|
|
|
|
img_label = OverlayLabel()
|
2025-08-08 18:00:56 +08:00
|
|
|
|
img_label.setFixedSize(thumbnail_size, thumbnail_size)
|
|
|
|
|
img_label.setAlignment(Qt.AlignCenter)
|
2025-08-11 17:58:06 +08:00
|
|
|
|
|
|
|
|
|
# 检查描述是否符合格式
|
|
|
|
|
is_valid_format = self._check_description_format(key)
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
|
|
|
|
# 加载图片
|
|
|
|
|
if os.path.exists(img_path):
|
|
|
|
|
pixmap = QPixmap(img_path)
|
|
|
|
|
if not pixmap.isNull():
|
|
|
|
|
img_label.setPixmap(pixmap.scaled(
|
|
|
|
|
thumbnail_size, thumbnail_size,
|
|
|
|
|
Qt.KeepAspectRatio, Qt.SmoothTransformation
|
|
|
|
|
))
|
2025-08-11 17:58:06 +08:00
|
|
|
|
# 如果格式正确,显示对勾
|
|
|
|
|
if is_valid_format:
|
|
|
|
|
img_label.set_overlay_icon(QStyle.SP_DialogApplyButton)
|
2025-08-08 18:00:56 +08:00
|
|
|
|
else:
|
|
|
|
|
img_label.setText("无效图片")
|
2025-08-11 17:58:06 +08:00
|
|
|
|
img_label.setStyleSheet("color: red;")
|
2025-08-08 18:00:56 +08:00
|
|
|
|
else:
|
|
|
|
|
img_label.setText("图片不存在")
|
2025-08-11 17:58:06 +08:00
|
|
|
|
img_label.setStyleSheet("color: red;")
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
|
|
|
|
item_layout.addWidget(img_label)
|
|
|
|
|
|
|
|
|
|
# 添加上下文菜单
|
|
|
|
|
item_widget.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
|
|
|
item_widget.customContextMenuRequested.connect(
|
2025-08-11 17:58:06 +08:00
|
|
|
|
lambda pos, path=img_path, k=key: self.show_context_menu(pos, path, k))
|
|
|
|
|
|
|
|
|
|
parent_layout.addWidget(item_widget)
|
|
|
|
|
|
|
|
|
|
def get_description_display(self, key):
|
|
|
|
|
"""获取描述信息的显示文本 - 用于悬停提示"""
|
|
|
|
|
if not key or "_" not in key:
|
|
|
|
|
return "⚠ 无描述信息"
|
|
|
|
|
|
|
|
|
|
parts = key.split("_")
|
|
|
|
|
if len(parts) < 8:
|
|
|
|
|
return f"⚠ 描述格式不完整\n当前描述: {key}"
|
|
|
|
|
|
|
|
|
|
# 格式化显示
|
|
|
|
|
description = (
|
|
|
|
|
f"✔ 已编辑描述\n"
|
|
|
|
|
f"缺陷类型: {parts[1]}\n"
|
|
|
|
|
f"位置: {parts[2]}\n"
|
|
|
|
|
f"尺寸: {parts[3]}\n"
|
|
|
|
|
f"可见程度: {parts[4]}\n"
|
|
|
|
|
f"紧急程度: {parts[5]}\n"
|
|
|
|
|
f"危重等级: {parts[6]}\n"
|
|
|
|
|
f"维修建议: {'_'.join(parts[7:])}"
|
2025-08-08 18:00:56 +08:00
|
|
|
|
)
|
|
|
|
|
|
2025-08-11 17:58:06 +08:00
|
|
|
|
return description
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def dragEnterEvent(self, event):
|
|
|
|
|
"""拖拽进入事件"""
|
|
|
|
|
if event.mimeData().hasUrls():
|
|
|
|
|
event.acceptProposedAction()
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
2025-08-11 17:58:06 +08:00
|
|
|
|
def dropEvent(self, event):
|
|
|
|
|
"""处理拖放事件"""
|
|
|
|
|
if not all([self.original_folder_name, self.current_part, self.current_dict_type]):
|
|
|
|
|
QMessageBox.warning(self, "警告", "请先选择有效的字典节点")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
urls = event.mimeData().urls()
|
|
|
|
|
if urls:
|
|
|
|
|
img_path = urls[0].toLocalFile()
|
|
|
|
|
if os.path.isfile(img_path):
|
|
|
|
|
# 生成新键(使用当前最大数字+1)
|
|
|
|
|
keys = [int(k) for k in self.current_dict_data.keys() if k.isdigit()]
|
|
|
|
|
max_key = max(keys) if keys else -1
|
|
|
|
|
new_key = str(max_key + 1)
|
|
|
|
|
|
|
|
|
|
# 更新字典数据
|
|
|
|
|
self.current_dict_data[new_key] = img_path
|
|
|
|
|
|
|
|
|
|
# 刷新显示
|
|
|
|
|
self.refresh_view()
|
|
|
|
|
|
|
|
|
|
# 发射信号通知更新 (包含英文字典类型)
|
|
|
|
|
self.dictionary_updated.emit(
|
|
|
|
|
self.original_folder_name,
|
|
|
|
|
self.current_part,
|
|
|
|
|
self.current_dict_type, # 英文键名
|
|
|
|
|
self.current_dict_data
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def show_context_menu(self, pos, img_path, key):
|
2025-08-08 18:00:56 +08:00
|
|
|
|
"""显示上下文菜单"""
|
|
|
|
|
menu = QMenu(self)
|
|
|
|
|
|
|
|
|
|
# 删除图片动作
|
|
|
|
|
delete_action = QAction("删除图片", self)
|
2025-08-11 17:58:06 +08:00
|
|
|
|
delete_action.triggered.connect(lambda: self.delete_image(key))
|
|
|
|
|
|
|
|
|
|
# 添加编辑描述动作 (仅当是缺陷图字典时显示)
|
|
|
|
|
edit_desc_action = None
|
|
|
|
|
if self.current_dict_type == "defect_picture_dict":
|
|
|
|
|
edit_desc_action = QAction("编辑缺陷描述", self)
|
|
|
|
|
edit_desc_action.triggered.connect(lambda: self.edit_defect_description(key, img_path))
|
|
|
|
|
menu.addAction(edit_desc_action)
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
|
|
|
|
menu.addAction(delete_action)
|
|
|
|
|
menu.exec_(QCursor.pos())
|
2025-08-11 17:58:06 +08:00
|
|
|
|
|
|
|
|
|
def edit_defect_description(self, key, img_path):
|
|
|
|
|
"""编辑缺陷图描述"""
|
|
|
|
|
# 获取当前描述 (如果没有则使用默认格式)
|
|
|
|
|
current_desc = self.current_dict_data.get(key, "") if key in self.current_dict_data else ""
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
2025-08-11 17:58:06 +08:00
|
|
|
|
# 创建并显示编辑对话框
|
|
|
|
|
editor = DefectDescriptionEditor(self, current_description=current_desc)
|
|
|
|
|
if editor.exec_() == QDialog.Accepted:
|
|
|
|
|
# 获取新描述
|
|
|
|
|
new_desc = editor.get_description()
|
|
|
|
|
|
|
|
|
|
# 更新字典数据 - 删除旧键并添加新键
|
|
|
|
|
if key in self.current_dict_data:
|
|
|
|
|
# 保留图片路径,只更新键名
|
|
|
|
|
img_path = self.current_dict_data.pop(key)
|
|
|
|
|
self.current_dict_data[new_desc] = img_path
|
|
|
|
|
|
|
|
|
|
# 刷新显示
|
|
|
|
|
self.refresh_view()
|
|
|
|
|
|
|
|
|
|
# 发射信号通知更新
|
|
|
|
|
self.dictionary_updated.emit(
|
|
|
|
|
self.original_folder_name,
|
|
|
|
|
self.current_part,
|
|
|
|
|
self.current_dict_type,
|
|
|
|
|
self.current_dict_data
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def delete_image(self, key):
|
|
|
|
|
"""删除指定图片"""
|
2025-08-08 18:00:56 +08:00
|
|
|
|
reply = QMessageBox.question(
|
|
|
|
|
self, "确认删除",
|
2025-08-11 17:58:06 +08:00
|
|
|
|
f"确定要从字典中删除Key为 {key} 的图片吗?",
|
2025-08-08 18:00:56 +08:00
|
|
|
|
QMessageBox.Yes | QMessageBox.No
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if reply == QMessageBox.Yes:
|
|
|
|
|
# 从字典中删除
|
2025-08-11 17:58:06 +08:00
|
|
|
|
self.current_dict_data.pop(key, None)
|
|
|
|
|
|
|
|
|
|
# 刷新显示
|
|
|
|
|
self.refresh_view()
|
|
|
|
|
|
|
|
|
|
# 发射信号通知更新
|
2025-08-08 18:00:56 +08:00
|
|
|
|
self.dictionary_updated.emit(
|
2025-08-11 17:58:06 +08:00
|
|
|
|
self.original_folder_name,
|
|
|
|
|
self.current_part,
|
|
|
|
|
self.current_dict_type,
|
|
|
|
|
self.current_dict_data
|
2025-08-08 18:00:56 +08:00
|
|
|
|
)
|
2025-08-11 17:58:06 +08:00
|
|
|
|
|
|
|
|
|
class DefectDescriptionEditor(QDialog):
|
|
|
|
|
def __init__(self, parent=None, current_description="", img_path=""):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self.setWindowTitle("编辑缺陷图描述")
|
|
|
|
|
self.setMinimumSize(1000, 700) # 增大窗口尺寸以适应两个编辑器
|
|
|
|
|
self.setStyleSheet(DEFECT_EDIT_WINDOW_STYLE)
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
2025-08-11 17:58:06 +08:00
|
|
|
|
self.current_description = current_description
|
|
|
|
|
self.img_path = img_path
|
|
|
|
|
self.init_ui()
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
2025-08-11 17:58:06 +08:00
|
|
|
|
def init_ui(self):
|
|
|
|
|
main_layout = QHBoxLayout(self)
|
|
|
|
|
main_layout.setContentsMargins(5, 5, 5, 5)
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
2025-08-11 17:58:06 +08:00
|
|
|
|
# 左侧 - 图片编辑器 (使用DefectMarkEditor)
|
|
|
|
|
self.image_editor = DefectMarkEditor(self, self.img_path)
|
|
|
|
|
self.image_editor.setMinimumSize(600, 600)
|
|
|
|
|
main_layout.addWidget(self.image_editor, stretch=2)
|
|
|
|
|
|
|
|
|
|
# 右侧 - 描述编辑器
|
|
|
|
|
self.init_description_editor()
|
|
|
|
|
main_layout.addWidget(self.desc_editor_widget, stretch=1)
|
|
|
|
|
|
|
|
|
|
def init_description_editor(self):
|
|
|
|
|
self.desc_editor_widget = QWidget()
|
|
|
|
|
layout = QVBoxLayout(self.desc_editor_widget)
|
|
|
|
|
layout.setContentsMargins(5, 0, 0, 0)
|
|
|
|
|
|
|
|
|
|
# 创建一个简单的 QTextEdit 作为消息浏览器
|
|
|
|
|
self.message_browser = QTextEdit()
|
|
|
|
|
self.message_browser.setReadOnly(True)
|
|
|
|
|
layout.addWidget(self.message_browser)
|
|
|
|
|
|
|
|
|
|
# 解析当前描述
|
|
|
|
|
parts = self.parse_current_description()
|
|
|
|
|
|
|
|
|
|
# 1. 缺陷类型
|
|
|
|
|
type_group = QGroupBox("缺陷类型")
|
|
|
|
|
type_group.setStyleSheet(GROUP_BOX_STYLE)
|
|
|
|
|
type_layout = QHBoxLayout(type_group)
|
|
|
|
|
|
|
|
|
|
# 使用 SmartDropdown 作为缺陷类型选择器
|
|
|
|
|
self.defect_type_dropdown = SmartDropdown(
|
|
|
|
|
dropdown_type="缺陷类型",
|
|
|
|
|
messagebrowser=self.message_browser,
|
|
|
|
|
parent=self
|
2025-08-08 18:00:56 +08:00
|
|
|
|
)
|
2025-08-11 17:58:06 +08:00
|
|
|
|
if "type" in parts:
|
|
|
|
|
self.defect_type_dropdown.combo_box.setCurrentText(parts["type"])
|
|
|
|
|
type_layout.addWidget(self.defect_type_dropdown)
|
|
|
|
|
|
|
|
|
|
layout.addWidget(type_group)
|
|
|
|
|
|
|
|
|
|
# 2. 缺陷位置
|
|
|
|
|
location_group = QGroupBox("缺陷位置")
|
|
|
|
|
location_group.setStyleSheet(GROUP_BOX_STYLE)
|
|
|
|
|
location_layout = QVBoxLayout(location_group)
|
|
|
|
|
|
|
|
|
|
self.location_edit = QLineEdit()
|
|
|
|
|
self.location_edit.setStyleSheet(LINE_EDIT_STYLE)
|
|
|
|
|
self.location_edit.setText(parts.get("location", ""))
|
|
|
|
|
location_layout.addWidget(self.location_edit)
|
|
|
|
|
|
|
|
|
|
layout.addWidget(location_group)
|
|
|
|
|
|
|
|
|
|
# 3. 缺陷尺寸
|
|
|
|
|
size_group = QGroupBox("缺陷尺寸")
|
|
|
|
|
size_group.setStyleSheet(GROUP_BOX_STYLE)
|
|
|
|
|
size_layout = QVBoxLayout(size_group)
|
|
|
|
|
|
|
|
|
|
# 轴向尺寸
|
|
|
|
|
axial_container = QWidget()
|
|
|
|
|
axial_container.setStyleSheet(DIMENSION_CONTAINER_STYLE)
|
|
|
|
|
axial_layout = QHBoxLayout(axial_container)
|
|
|
|
|
axial_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
|
|
|
|
|
axial_label = QLabel("轴向尺寸:")
|
|
|
|
|
axial_label.setStyleSheet(DIMENSION_LABEL_STYLE)
|
|
|
|
|
axial_layout.addWidget(axial_label)
|
|
|
|
|
|
|
|
|
|
self.axial_edit = QLineEdit()
|
|
|
|
|
self.axial_edit.setStyleSheet(DIMENSION_EDIT_STYLE)
|
|
|
|
|
self.axial_edit.setText(parts.get("axial_size", ""))
|
|
|
|
|
axial_layout.addWidget(self.axial_edit)
|
|
|
|
|
|
|
|
|
|
# 添加单位标签
|
|
|
|
|
axial_unit = QLabel("mm")
|
|
|
|
|
axial_unit.setStyleSheet(LABEL_STYLE)
|
|
|
|
|
axial_layout.addWidget(axial_unit)
|
|
|
|
|
|
|
|
|
|
axial_layout.addStretch()
|
|
|
|
|
size_layout.addWidget(axial_container)
|
|
|
|
|
|
|
|
|
|
# 弦向尺寸
|
|
|
|
|
chord_container = QWidget()
|
|
|
|
|
chord_container.setStyleSheet(DIMENSION_CONTAINER_STYLE)
|
|
|
|
|
chord_layout = QHBoxLayout(chord_container)
|
|
|
|
|
chord_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
|
|
|
|
|
chord_label = QLabel("弦向尺寸:")
|
|
|
|
|
chord_label.setStyleSheet(DIMENSION_LABEL_STYLE)
|
|
|
|
|
chord_layout.addWidget(chord_label)
|
|
|
|
|
|
|
|
|
|
self.chord_edit = QLineEdit()
|
|
|
|
|
self.chord_edit.setStyleSheet(DIMENSION_EDIT_STYLE)
|
|
|
|
|
self.chord_edit.setText(parts.get("chord_size", ""))
|
|
|
|
|
chord_layout.addWidget(self.chord_edit)
|
|
|
|
|
|
|
|
|
|
# 添加单位标签
|
|
|
|
|
chord_unit = QLabel("mm")
|
|
|
|
|
chord_unit.setStyleSheet(LABEL_STYLE)
|
|
|
|
|
chord_layout.addWidget(chord_unit)
|
|
|
|
|
|
|
|
|
|
chord_layout.addStretch()
|
|
|
|
|
size_layout.addWidget(chord_container)
|
|
|
|
|
|
|
|
|
|
layout.addWidget(size_group)
|
|
|
|
|
|
|
|
|
|
# 4. 其他属性
|
|
|
|
|
attr_group = QGroupBox("其他属性")
|
|
|
|
|
attr_group.setStyleSheet(GROUP_BOX_STYLE)
|
|
|
|
|
attr_layout = QGridLayout(attr_group)
|
|
|
|
|
|
|
|
|
|
# 可见程度
|
|
|
|
|
visibility_label = QLabel("可见程度:")
|
|
|
|
|
visibility_label.setStyleSheet(LABEL_STYLE)
|
|
|
|
|
self.visibility_combo = QComboBox()
|
|
|
|
|
self.visibility_combo.setStyleSheet(COMBO_BOX_STYLE)
|
|
|
|
|
self.visibility_combo.addItems(["轻微", "一般", "重要", "严重"])
|
|
|
|
|
if "visibility" in parts:
|
|
|
|
|
self.visibility_combo.setCurrentText(parts["visibility"])
|
|
|
|
|
attr_layout.addWidget(visibility_label, 0, 0)
|
|
|
|
|
attr_layout.addWidget(self.visibility_combo, 0, 1)
|
|
|
|
|
|
|
|
|
|
# 紧急程度
|
|
|
|
|
urgency_label = QLabel("紧急程度:")
|
|
|
|
|
urgency_label.setStyleSheet(LABEL_STYLE)
|
|
|
|
|
self.urgency_combo = QComboBox()
|
|
|
|
|
self.urgency_combo.setStyleSheet(COMBO_BOX_STYLE)
|
|
|
|
|
self.urgency_combo.addItems(["不紧急", "一般", "紧急", "非常紧急"])
|
|
|
|
|
if "urgency" in parts:
|
|
|
|
|
self.urgency_combo.setCurrentText(parts["urgency"])
|
|
|
|
|
attr_layout.addWidget(urgency_label, 1, 0)
|
|
|
|
|
attr_layout.addWidget(self.urgency_combo, 1, 1)
|
|
|
|
|
|
|
|
|
|
# 危重等级
|
|
|
|
|
severity_label = QLabel("危重等级:")
|
|
|
|
|
severity_label.setStyleSheet(LABEL_STYLE)
|
|
|
|
|
self.severity_combo = QComboBox()
|
|
|
|
|
self.severity_combo.setStyleSheet(COMBO_BOX_STYLE)
|
|
|
|
|
self.severity_combo.addItems(["轻微", "一般", "重要", "严重"])
|
|
|
|
|
if "severity" in parts:
|
|
|
|
|
self.severity_combo.setCurrentText(parts["severity"])
|
|
|
|
|
attr_layout.addWidget(severity_label, 2, 0)
|
|
|
|
|
attr_layout.addWidget(self.severity_combo, 2, 1)
|
|
|
|
|
|
|
|
|
|
layout.addWidget(attr_group)
|
|
|
|
|
|
|
|
|
|
# 5. 维修建议
|
|
|
|
|
suggestion_group = QGroupBox("维修建议")
|
|
|
|
|
suggestion_group.setStyleSheet(GROUP_BOX_STYLE)
|
|
|
|
|
suggestion_layout = QVBoxLayout(suggestion_group)
|
|
|
|
|
|
|
|
|
|
self.suggestion_edit = QLineEdit()
|
|
|
|
|
self.suggestion_edit.setStyleSheet(LINE_EDIT_STYLE)
|
|
|
|
|
self.suggestion_edit.setText(parts.get("suggestion", ""))
|
|
|
|
|
suggestion_layout.addWidget(self.suggestion_edit)
|
|
|
|
|
|
|
|
|
|
layout.addWidget(suggestion_group)
|
|
|
|
|
|
|
|
|
|
# 按钮
|
|
|
|
|
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
|
|
|
button_box.setStyleSheet(PRIMARY_BUTTON_STYLE)
|
|
|
|
|
button_box.accepted.connect(self.accept)
|
|
|
|
|
button_box.rejected.connect(self.reject)
|
|
|
|
|
layout.addWidget(button_box)
|
|
|
|
|
|
|
|
|
|
def parse_current_description(self):
|
|
|
|
|
"""解析当前描述字符串"""
|
|
|
|
|
parts = {
|
|
|
|
|
"type": "",
|
|
|
|
|
"location": "",
|
|
|
|
|
"axial_size": "",
|
|
|
|
|
"chord_size": "",
|
|
|
|
|
"visibility": "一般",
|
|
|
|
|
"urgency": "一般",
|
|
|
|
|
"severity": "一般",
|
|
|
|
|
"suggestion": ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if not self.current_description:
|
|
|
|
|
return parts
|
2025-08-08 18:00:56 +08:00
|
|
|
|
|
2025-08-11 17:58:06 +08:00
|
|
|
|
# 尝试解析格式: 1_缺陷类型_缺陷位置_缺陷尺寸_可见程度_紧急程度_危重等级_维修建议
|
|
|
|
|
desc_parts = self.current_description.split("_")
|
|
|
|
|
if len(desc_parts) >= 8:
|
|
|
|
|
parts["type"] = desc_parts[1]
|
|
|
|
|
parts["location"] = desc_parts[2]
|
|
|
|
|
|
|
|
|
|
# 解析尺寸 (格式: 轴向XXmm弦向XXmm)
|
|
|
|
|
size_str = desc_parts[3]
|
|
|
|
|
axial_match = re.search(r"轴向(\d+)mm", size_str)
|
|
|
|
|
chord_match = re.search(r"弦向(\d+)mm", size_str)
|
|
|
|
|
if axial_match:
|
|
|
|
|
parts["axial_size"] = axial_match.group(1)
|
|
|
|
|
if chord_match:
|
|
|
|
|
parts["chord_size"] = chord_match.group(1)
|
|
|
|
|
|
|
|
|
|
if len(desc_parts) > 4:
|
|
|
|
|
parts["visibility"] = desc_parts[4]
|
|
|
|
|
if len(desc_parts) > 5:
|
|
|
|
|
parts["urgency"] = desc_parts[5]
|
|
|
|
|
if len(desc_parts) > 6:
|
|
|
|
|
parts["severity"] = desc_parts[6]
|
|
|
|
|
if len(desc_parts) > 7:
|
|
|
|
|
parts["suggestion"] = "_".join(desc_parts[7:])
|
|
|
|
|
|
|
|
|
|
return parts
|
|
|
|
|
|
|
|
|
|
def get_description(self):
|
|
|
|
|
"""生成描述字符串"""
|
|
|
|
|
# 生成序号 (使用当前时间戳的最后4位确保唯一性)
|
|
|
|
|
seq = str(int(time.time()) % 10000)
|
|
|
|
|
|
|
|
|
|
# 获取各字段值
|
|
|
|
|
defect_type = self.defect_type_dropdown.combo_box.currentText()
|
|
|
|
|
location = self.location_edit.text().strip()
|
|
|
|
|
axial_size = self.axial_edit.text().strip()
|
|
|
|
|
chord_size = self.chord_edit.text().strip()
|
|
|
|
|
visibility = self.visibility_combo.currentText()
|
|
|
|
|
urgency = self.urgency_combo.currentText()
|
|
|
|
|
severity = self.severity_combo.currentText()
|
|
|
|
|
suggestion = self.suggestion_edit.text().strip()
|
|
|
|
|
|
|
|
|
|
# 构建尺寸字符串
|
|
|
|
|
size_str = ""
|
|
|
|
|
if axial_size or chord_size:
|
|
|
|
|
size_parts = []
|
|
|
|
|
if axial_size:
|
|
|
|
|
size_parts.append(f"轴向{axial_size}mm")
|
|
|
|
|
if chord_size:
|
|
|
|
|
size_parts.append(f"弦向{chord_size}mm")
|
|
|
|
|
size_str = "".join(size_parts)
|
|
|
|
|
|
|
|
|
|
# 构建完整描述
|
|
|
|
|
description = f"{seq}_{defect_type}_{location}_{size_str}_{visibility}_{urgency}_{severity}_{suggestion}"
|
|
|
|
|
return description
|