ReportGeneratorLocal/info_core/MyQtClass.py

1586 lines
62 KiB
Python
Raw Permalink Normal View History

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,
QTreeView, QSplitter, QFileSystemModel)
2025-08-05 17:51:11 +08:00
from PySide6.QtCore import (QDateTime,QTimer,QDateTime,Signal,QSettings,
2025-08-07 18:16:09 +08:00
QSortFilterProxyModel, QSize, QDir)
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,
QAction)
2025-08-04 18:08:53 +08:00
import json, sys, os
from info_core.defines import *
class ConfigComboBoxGroup(QGroupBox):
"""可复用的配置下拉框组件(带完整样式)"""
def __init__(self, title, allow_add=True, parent=None, is_project = True):
super().__init__(title, parent)
self.allow_add = allow_add
self.is_project = is_project
self.config_dir = os.path.join(os.getcwd(), "config", title)
# 确保配置目录存在
os.makedirs(self.config_dir, exist_ok=True)
# 设置组框样式
self.setStyleSheet(GROUP_BOX_STYLE)
self.setMinimumSize(GROUP_BOX_MIN_WIDTH, GROUP_BOX_MIN_HEIGHT)
# 创建UI
self.init_ui()
self.load_config_files()
def init_ui(self):
"""初始化UI组件"""
layout = QVBoxLayout()
layout.setSpacing(GROUP_BOX_SPACING)
layout.setContentsMargins(*GROUP_BOX_MARGINS)
self.combo_box_init()
# 按钮布局
btn_layout = QHBoxLayout()
btn_layout.addStretch() # 将按钮推到右侧
self.modify_btn = self.create_button("修改")
self.modify_btn.clicked.connect(self.on_modify_clicked)
btn_layout.addWidget(self.modify_btn)
if self.allow_add:
self.add_btn = self.create_button("添加")
self.add_btn.clicked.connect(self.on_add_clicked)
btn_layout.addWidget(self.add_btn)
self.delete_btn = self.create_button("删除")
self.delete_btn.clicked.connect(self.on_delete_clicked)
btn_layout.addWidget(self.delete_btn)
layout.addWidget(self.combo_box)
layout.addLayout(btn_layout)
self.setLayout(layout)
def combo_box_init(self):
# 下拉框
self.combo_box = QFontComboBox()
self.combo_box.setEditable(True)
# 代理模型(支持中文模糊匹配)
self.proxy_model = QSortFilterProxyModel()
self.proxy_model.setSourceModel(self.combo_box.model())
self.proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive)
# 补全器
self.completer = QCompleter(self.proxy_model, self.combo_box)
self.completer.setFilterMode(Qt.MatchContains)
self.combo_box.setCompleter(self.completer)
def create_button(self, text):
"""创建统一风格的按钮"""
btn = QPushButton(text)
btn.setStyleSheet(BUTTON_STYLE)
btn.setFixedSize(BUTTON_WIDTH, BUTTON_HEIGHT)
return btn
def load_config_files(self):
"""加载config文件夹中的JSON文件"""
self.combo_box.clear()
# 获取所有.json文件
config_files = [f for f in os.listdir(self.config_dir)
if f.endswith('.json')]
# 添加到下拉框(不带.json后缀
for file in config_files:
self.combo_box.addItem(file[:-5])
def on_modify_clicked(self):
"""修改按钮点击事件"""
if self.is_project:
project_widget = AddProjectDialog(self.config_dir, self, os.path.join(self.config_dir, self.combo_box.currentText() + ".json"))
project_widget.exec()
else:
person_widget = AddPersonnelDialog(self.config_dir, self, os.path.join(self.config_dir, self.combo_box.currentText() + ".json"))
person_widget.exec()
self.load_config_files()
def on_add_clicked(self):
"""添加按钮点击,创建默认ConfigEditDialog"""
if self.is_project:
project_widget = AddProjectDialog(self.config_dir, self)
project_widget.exec()
else:
person_widget = AddPersonnelDialog(self.config_dir, self)
person_widget.exec()
self.load_config_files()
def on_delete_clicked(self):
"""删除选中的项目"""
# 获取当前选中的项目名称
selected_project_name = self.combo_box.currentText()
if selected_project_name:
# 确认删除操作
confirm_message = f"你确定要删除项目 '{selected_project_name}' 吗?此操作不可恢复!"
reply = QMessageBox.question(self, '警告', confirm_message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
# 构建项目文件的路径
project_file_path = os.path.join(self.config_dir, selected_project_name + ".json")
# 检查文件是否存在并删除
if os.path.exists(project_file_path):
os.remove(project_file_path)
else:
QMessageBox.warning(self, '警告', f"项目文件 '{selected_project_name}.json' 不存在。")
# 从UI中移除该项目
self.combo_box.removeItem(self.combo_box.currentIndex())
# 重新加载配置文件列表
self.load_config_files()
else:
QMessageBox.information(self, '信息', '删除操作已取消。')
else:
QMessageBox.warning(self, '警告', '没有选中任何项目。')
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()
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
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
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)
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()
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()
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
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)
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)
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)
self.back_btn.setVisible(False)
2025-08-05 17:51:11 +08:00
self.clear_btn.setVisible(True)
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()
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:
"""管理树形模型的数据操作"""
@staticmethod
def create_left_tree_model(folder_list):
"""创建左侧文件夹树模型"""
model = QStandardItemModel()
model.setHorizontalHeaderLabels(["Folder Structure"])
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:
for child in os.listdir(folder_path):
child_path = os.path.join(folder_path, child)
if os.path.isdir(child_path):
child_item = QStandardItem(child)
parent_item.appendRow(child_item)
except Exception as e:
print(f"Error reading folder {folder_path}: {e}")
return model, folder_map
@staticmethod
def create_right_tree_model(folder_names):
"""创建右侧可编辑树模型"""
model = QStandardItemModel()
model.setHorizontalHeaderLabels(["Name", "Value"])
json_data = {}
for folder_name in folder_names:
parent_item = QStandardItem(folder_name)
parent_item.setEditable(False)
# 添加机组编号
turbine_item = QStandardItem("turbinecode")
turbine_value = QStandardItem(folder_name.replace("机组", "").strip())
turbine_value.setEditable(True)
parent_item.appendRow([turbine_item, turbine_value])
# 添加默认的三个子部件
for i in range(1, 4):
part_item = QStandardItem(f"part{i}")
part_item.setEditable(False)
code_item = QStandardItem("001")
code_item.setEditable(True)
part_item.appendRow([QStandardItem("code"), code_item])
parent_item.appendRow(part_item)
model.appendRow(parent_item)
# 初始化JSON数据
json_data[folder_name] = {
"turbinecode": folder_name.replace("机组", "").strip(),
"part1": {"code": "001"},
"part2": {"code": "001"},
"part3": {"code": "001"}
}
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:
part_name = part_item.text()
if part_name in data:
code_item = part_item.child(0, 1)
if code_item:
code_item.setText(data[part_name].get("code", "001"))
@staticmethod
def update_json_from_model(model, json_data):
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:
json_data[folder_name] = {"turbinecode": "", "part1": {}, "part2": {}, "part3": {}}
# 获取 turbinecode 值(从第二列获取)
turbine_index = model.index(0, 1, folder_index) # 第一行第二列
if turbine_index.isValid():
new_value = model.data(turbine_index)
print(f"更新 turbinecode: {json_data[folder_name]['turbinecode']} -> {new_value}")
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():
code_index = model.index(0, 1, part_index)
if code_index.isValid():
json_data[folder_name][f"part{part_num}"]["code"] = model.data(code_index, Qt.DisplayRole)
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) # 数据变更信号
def __init__(self, parent=None):
super().__init__(parent)
self.output_path = QDir.currentPath() # 默认输出路径
self.json_data = {} # 存储所有JSON数据
self.folder_map = {} # 文件夹路径映射
self.init_ui()
self.setup_connections()
def init_ui(self):
"""初始化用户界面"""
main_layout = QHBoxLayout(self)
splitter = QSplitter(Qt.Horizontal)
# 左侧文件夹树视图
self.left_model = QStandardItemModel()
self.left_model.setHorizontalHeaderLabels(["Folder Structure"])
self.left_tree = QTreeView()
self.left_tree.setModel(self.left_model)
self.left_tree.setEditTriggers(QTreeView.NoEditTriggers)
# 添加上下文菜单(复制功能)
self.left_tree.setContextMenuPolicy(Qt.ActionsContextMenu)
copy_action = QAction("Copy", self.left_tree)
copy_action.triggered.connect(self.copy_left_tree_text)
self.left_tree.addAction(copy_action)
# 右侧可编辑树视图
self.right_model = QStandardItemModel()
self.right_model.setHorizontalHeaderLabels(["Name", "Value"])
self.right_tree = QTreeView()
self.right_tree.setModel(self.right_model)
self.right_tree.setItemDelegate(
DirectSaveDelegate(self.handle_immediate_save, self.right_tree)
)
# 添加到分割器
splitter.addWidget(self.left_tree)
splitter.addWidget(self.right_tree)
splitter.setSizes([300, 500])
main_layout.addWidget(splitter)
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 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)
self.right_tree.expanded.connect(self.sync_left_tree_expand)
self.right_tree.collapsed.connect(self.sync_left_tree_collapse)
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()
self.left_model.setHorizontalHeaderLabels(["Folder Structure"])
self.right_model.setHorizontalHeaderLabels(["Name", "Value"])
self.json_data = {}
self.folder_map = {}
if not folder_list: # 添加空列表检查
print("Warning: folder_list is empty")
return
self.folder_map = {} # 新增: {文件夹名: 初始文件名}
for folder_path in folder_list:
folder_name = os.path.basename(folder_path)
# 使用初始 turbinecode 作为固定文件名
initial_code = folder_name.replace("机组", "").strip()
self.folder_map[folder_name] = f"{initial_code}.json" # 存储固定文件名
# 创建左侧树模型
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):
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):
"""获取当前所有数据"""
return self.json_data