config_editor/dialogs.py
2026-01-19 08:01:13 +00:00

1036 lines
43 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

'''对话交互模块'''
import os
import json
import datetime
from pathlib import Path
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QLineEdit, QCheckBox, QPushButton, QTextEdit,
QTableWidget, QTableWidgetItem, QHeaderView,
QDialogButtonBox, QGroupBox, QSplitter, QListWidget,
QListWidgetItem, QComboBox, QInputDialog, QFileDialog,
QMessageBox, QSpinBox, QDoubleSpinBox, QFrame,QWidget)
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QBrush, QColor, QFont
from utils import get_validation_tooltip, ensure_rule_structure
class ConfigSettingsDialog(QDialog):
"""配置文件设置对话框"""
def __init__(self, current_path="", parent=None):
super().__init__(parent)
self.setWindowTitle("配置文件设置")
self.setModal(True)
self.setMinimumSize(500, 200)
self.current_path = current_path
self.init_ui()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(12)
# 文件路径设置
file_group = QGroupBox("配置文件路径")
file_layout = QVBoxLayout(file_group)
path_layout = QHBoxLayout()
path_layout.addWidget(QLabel("配置文件:"))
self.path_edit = QLineEdit()
self.path_edit.setText(self.current_path)
path_layout.addWidget(self.path_edit)
self.browse_btn = QPushButton("浏览...")
self.browse_btn.clicked.connect(self.browse_file)
path_layout.addWidget(self.browse_btn)
file_layout.addLayout(path_layout)
# 提示信息
tip_label = QLabel("提示:请选择或输入要编辑的配置文件路径")
tip_label.setStyleSheet("color: #666666; font-size: 11px;")
file_layout.addWidget(tip_label)
layout.addWidget(file_group)
# 使用相对路径选项
self.use_relative_checkbox = QCheckBox("使用相对路径(相对于程序所在目录)")
self.use_relative_checkbox.setChecked(True)
layout.addWidget(self.use_relative_checkbox)
layout.addStretch()
# 按钮
button_box = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok |
QDialogButtonBox.StandardButton.Cancel
)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
layout.addWidget(button_box)
def browse_file(self):
"""浏览文件"""
file_path, _ = QFileDialog.getOpenFileName(
self, "选择配置文件",
self.path_edit.text() or str(Path.home()),
"Python Files (*.py);;All Files (*)"
)
if file_path:
self.path_edit.setText(file_path)
def get_settings(self):
"""获取设置"""
config_path = self.path_edit.text().strip()
# 如果使用相对路径,则转换为相对于程序目录的路径
if self.use_relative_checkbox.isChecked() and config_path:
if not os.path.isabs(config_path):
# 如果已经是相对路径,相对于程序所在目录
program_dir = Path(__file__).parent.absolute()
config_path = str(program_dir / config_path)
return {
"config_file_path": config_path,
"use_relative_path": self.use_relative_checkbox.isChecked()
}
class ConfigManagementWindow(QDialog):
def __init__(self, rules, config_fields, parent=None):
super().__init__(parent)
self.rules = rules
self.config_fields = config_fields
self.all_fields = list(config_fields.keys()) # 保存所有字段名用于搜索
self.setWindowTitle("规则管理")
self.setModal(True)
self.setMinimumSize(700, 600)
self.resize(700, 600)
self.init_ui()
self.load_data()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(8, 8, 8, 8)
layout.setSpacing(8)
# 分割窗口
splitter = QSplitter(Qt.Orientation.Horizontal)
layout.addWidget(splitter)
# 左侧:配置项列表
left_widget = QWidget()
left_layout = QVBoxLayout(left_widget)
left_layout.setContentsMargins(4, 4, 4, 4)
# 搜索框 - 添加搜索类型选择
search_layout = QHBoxLayout()
search_layout.addWidget(QLabel("搜索:"))
# 搜索类型选择
self.search_type_combo = QComboBox()
self.search_type_combo.addItems(["变量名", "显示名称"])
self.search_type_combo.setMaximumWidth(80)
search_layout.addWidget(self.search_type_combo)
self.search_edit = QLineEdit()
self.search_edit.setPlaceholderText("输入关键词进行搜索...")
self.search_edit.textChanged.connect(self.filter_fields)
search_layout.addWidget(self.search_edit)
# 清除搜索按钮
self.clear_search_btn = QPushButton("清除")
self.clear_search_btn.clicked.connect(self.clear_search)
self.clear_search_btn.setMaximumWidth(60)
search_layout.addWidget(self.clear_search_btn)
left_layout.addLayout(search_layout)
# 修改:添加分组显示说明
list_label = QLabel("配置项列表(上方为未隐藏项,下方为隐藏项):")
list_label.setStyleSheet("color: #333333; font-weight: bold;")
left_layout.addWidget(list_label)
self.fields_list = QListWidget()
self.fields_list.currentItemChanged.connect(self.on_field_selected)
left_layout.addWidget(self.fields_list)
# 添加统计信息
self.fields_count_label = QLabel("")
left_layout.addWidget(self.fields_count_label)
splitter.addWidget(left_widget)
# 右侧:编辑区域
right_widget = QWidget()
right_layout = QVBoxLayout(right_widget)
right_layout.setContentsMargins(4, 4, 4, 4)
# 基本信息
info_group = QGroupBox("字段属性")
form_layout = QVBoxLayout(info_group)
form_layout.setContentsMargins(8, 8, 8, 8)
# 变量名
self.name_label = QLabel()
self.create_form_row(form_layout, "变量名:", self.name_label)
# 显示名称
self.display_name_edit = QLineEdit()
self.create_form_row(form_layout, "显示名称:", self.display_name_edit)
# 分组和类型
category_layout = QHBoxLayout()
category_layout.addWidget(QLabel("分组:"))
self.category_combo = QComboBox()
self.category_combo.setEditable(True)
category_layout.addWidget(self.category_combo)
category_layout.addStretch()
category_layout.addWidget(QLabel("类型:"))
self.type_combo = QComboBox()
self.type_combo.addItems(["auto", "str", "int", "float", "bool", "json"])
self.type_combo.currentTextChanged.connect(self.on_type_changed)
category_layout.addWidget(self.type_combo)
form_layout.addLayout(category_layout)
# 小数位数设置仅当类型为float或int时显示
self.decimal_row = QHBoxLayout()
self.decimal_label = QLabel("小数位数:")
self.decimal_spinbox = QSpinBox()
self.decimal_spinbox.setRange(0, 10)
self.decimal_spinbox.setValue(2) # 默认值
self.decimal_spinbox.setEnabled(False) # 默认禁用只有float类型才启用
self.decimal_row.addWidget(self.decimal_label)
self.decimal_row.addWidget(self.decimal_spinbox)
self.decimal_row.addStretch()
form_layout.addLayout(self.decimal_row)
# 隐藏状态
self.hidden_checkbox = QCheckBox("隐藏此配置项(不在主界面显示)")
self.hidden_checkbox.stateChanged.connect(self.on_hidden_changed)
form_layout.addWidget(self.hidden_checkbox)
# 加密配置项选项
self.encrypted_checkbox = QCheckBox("加密配置项(修改时需要验证二级密码)")
self.encrypted_checkbox.stateChanged.connect(self.on_encrypted_changed)
form_layout.addWidget(self.encrypted_checkbox)
# 提示信息
form_layout.addWidget(QLabel("提示信息:"))
self.tooltip_edit = QTextEdit()
self.tooltip_edit.setMaximumHeight(80)
form_layout.addWidget(self.tooltip_edit)
right_layout.addWidget(info_group)
# 校验规则
validation_group = QGroupBox("校验规则")
validation_layout = QVBoxLayout(validation_group)
validation_layout.setContentsMargins(8, 8, 8, 8)
# 最小值
min_layout = QHBoxLayout()
min_layout.addWidget(QLabel("最小值:"))
self.min_edit = QLineEdit()
self.min_edit.setPlaceholderText("对于数字类型有效")
min_layout.addWidget(self.min_edit)
min_layout.addStretch()
validation_layout.addLayout(min_layout)
# 最大值
max_layout = QHBoxLayout()
max_layout.addWidget(QLabel("最大值:"))
self.max_edit = QLineEdit()
self.max_edit.setPlaceholderText("对于数字类型有效")
max_layout.addWidget(self.max_edit)
max_layout.addStretch()
validation_layout.addLayout(max_layout)
# 正则表达式
validation_layout.addWidget(QLabel("正则表达式:"))
self.regex_edit = QLineEdit()
self.regex_edit.setPlaceholderText("对于字符串类型有效,如: ^[A-Za-z0-9_]+$")
validation_layout.addWidget(self.regex_edit)
# 必填项
self.required_checkbox = QCheckBox("必填项")
validation_layout.addWidget(self.required_checkbox)
right_layout.addWidget(validation_group)
# 分组管理
group_group = QGroupBox("分组管理")
group_layout = QVBoxLayout(group_group)
group_btn_layout = QHBoxLayout()
self.add_group_btn = QPushButton("添加分组")
self.remove_group_btn = QPushButton("删除分组")
self.add_group_btn.clicked.connect(self.add_group)
self.remove_group_btn.clicked.connect(self.remove_group)
group_btn_layout.addWidget(self.add_group_btn)
group_btn_layout.addWidget(self.remove_group_btn)
group_btn_layout.addStretch()
group_layout.addLayout(group_btn_layout)
right_layout.addWidget(group_group)
# 批量操作
batch_group = QGroupBox("批量操作")
batch_layout = QVBoxLayout(batch_group)
batch_layout.setContentsMargins(8, 8, 8, 8)
batch_btn_layout = QHBoxLayout()
self.show_all_btn = QPushButton("全部显示")
self.show_all_btn.clicked.connect(self.show_all_fields)
self.hide_all_btn = QPushButton("全部隐藏")
self.hide_all_btn.clicked.connect(self.hide_all_fields)
batch_btn_layout.addWidget(self.show_all_btn)
batch_btn_layout.addWidget(self.hide_all_btn)
batch_btn_layout.addStretch()
batch_layout.addLayout(batch_btn_layout)
# 导入导出功能
import_export_group = QGroupBox("导入导出规则文件")
import_export_layout = QVBoxLayout(import_export_group)
import_export_layout.setContentsMargins(8, 8, 8, 8)
import_export_btn_layout = QHBoxLayout()
self.import_rules_btn = QPushButton("导入规则文件")
self.import_rules_btn.clicked.connect(self.import_rules_file)
self.export_rules_btn = QPushButton("导出规则文件")
self.export_rules_btn.clicked.connect(self.export_rules_file)
import_export_btn_layout.addWidget(self.import_rules_btn)
import_export_btn_layout.addWidget(self.export_rules_btn)
import_export_btn_layout.addStretch()
import_export_layout.addLayout(import_export_btn_layout)
right_layout.addWidget(import_export_group)
right_layout.addStretch()
splitter.addWidget(right_widget)
# 设置分割比例
splitter.setSizes([250, 420])
# 按钮
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok |
QDialogButtonBox.StandardButton.Cancel)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
layout.addWidget(button_box)
def create_form_row(self, layout, label_text, widget):
"""创建舒适的表单行"""
row_layout = QHBoxLayout()
label = QLabel(label_text)
label.setMinimumWidth(80)
row_layout.addWidget(label)
row_layout.addWidget(widget)
layout.addLayout(row_layout)
def load_data(self):
"""加载数据到界面(按隐藏状态分组显示)"""
self.fields_list.clear()
# 获取隐藏字段列表
hidden_fields = self.rules.get("hidden", [])
# 获取加密字段列表
encrypted_fields = self.rules.get("encrypted_fields", [])
# 分离未隐藏和已隐藏的字段
visible_fields = []
hidden_fields_list = []
for field_name in sorted(self.config_fields.keys()):
if field_name in hidden_fields:
hidden_fields_list.append(field_name)
else:
visible_fields.append(field_name)
# 添加分隔项
if visible_fields and hidden_fields_list:
# 添加分隔符(未隐藏部分)
self.create_separator_item("未隐藏的配置项")
# 添加未隐藏的字段(高亮显示)
for field_name in visible_fields:
self.create_field_item(field_name, is_hidden=False, is_encrypted=(field_name in encrypted_fields))
# 添加分隔项
if visible_fields and hidden_fields_list:
# 添加分隔符(隐藏部分)
self.create_separator_item("已隐藏的配置项")
# 添加已隐藏的字段(灰色显示)
for field_name in hidden_fields_list:
self.create_field_item(field_name, is_hidden=True, is_encrypted=(field_name in encrypted_fields))
# 如果没有配置项,显示提示
if not visible_fields and not hidden_fields_list:
item = QListWidgetItem("没有配置项")
self.fields_list.addItem(item)
item.setFlags(Qt.ItemFlag.NoItemFlags) # 不可选
item.setForeground(QBrush(QColor("#999999")))
self.update_fields_count()
# 加载分组
self.category_combo.clear()
categories = list(self.rules.get("categories", {}).keys())
# 确保"未分类"分组始终存在
if "未分类" not in categories:
categories.append("未分类")
if categories:
for category in sorted(categories):
self.category_combo.addItem(category)
def create_separator_item(self, text):
"""创建分隔项"""
item = QListWidgetItem(f"--- {text} ---")
self.fields_list.addItem(item)
item.setFlags(Qt.ItemFlag.NoItemFlags) # 不可选
item.setForeground(QBrush(QColor("#666666")))
font = item.font()
font.setItalic(True)
font.setBold(True)
item.setFont(font)
# 设置背景色
item.setBackground(QBrush(QColor("#f0f0f0")))
return item
def create_field_item(self, field_name, is_hidden=False, is_encrypted=False):
"""创建字段项"""
item = QListWidgetItem(field_name)
self.fields_list.addItem(item)
# 获取显示名称用于搜索
display_name = self.rules.get("display_names", {}).get(field_name, field_name)
# 设置自定义数据:变量名和显示名称
item.setData(Qt.ItemDataRole.UserRole, field_name) # 变量名
item.setData(Qt.ItemDataRole.UserRole + 1, display_name) # 显示名称
# 设置显示文本(包含加密标识)
display_text = field_name
if is_encrypted:
display_text += " 🔒"
item.setText(display_text)
if is_hidden:
# 隐藏字段:灰色,斜体
item.setForeground(QBrush(QColor("#999999")))
font = item.font()
font.setItalic(True)
item.setFont(font)
item.setToolTip(f"已隐藏: {field_name} ({display_name})" + (" [加密]" if is_encrypted else ""))
else:
# 未隐藏字段:高亮显示
if is_encrypted:
# 加密字段:橙色高亮
item.setForeground(QBrush(QColor("#ff5722"))) # 橙色
font = item.font()
font.setBold(True)
item.setFont(font)
else:
# 普通字段:默认颜色
item.setBackground(QBrush(QColor("#e8f5e9"))) # 浅绿色背景
font = item.font()
font.setBold(True)
item.setFont(font)
item.setToolTip(f"未隐藏: {field_name} ({display_name})" + (" [加密]" if is_encrypted else ""))
return item
def filter_fields(self, search_text):
"""根据搜索文本过滤配置项列表(保持分组),支持变量名和显示名称搜索"""
search_text = search_text.strip().lower()
search_type = self.search_type_combo.currentText() # 获取搜索类型
if not search_text:
# 如果搜索文本为空,显示所有项
for i in range(self.fields_list.count()):
item = self.fields_list.item(i)
# 跳过分隔符项
if not self.is_separator_item(item):
item.setHidden(False)
else:
# 模糊匹配:根据搜索类型进行匹配
for i in range(self.fields_list.count()):
item = self.fields_list.item(i)
# 跳过分隔符项
if not self.is_separator_item(item):
# 根据搜索类型获取要匹配的文本
if search_type == "变量名":
# 搜索变量名
field_name = item.data(Qt.ItemDataRole.UserRole)
match_text = field_name.lower() if field_name else ""
else: # 显示名称
# 搜索显示名称
display_name = item.data(Qt.ItemDataRole.UserRole + 1)
match_text = display_name.lower() if display_name else ""
# 模糊匹配:检查搜索文本是否在匹配文本中
if search_text in match_text:
item.setHidden(False)
else:
item.setHidden(True)
self.update_fields_count()
def is_separator_item(self, item):
"""检查是否是分隔符项"""
text = item.text()
return text.startswith("---") and text.endswith("---")
def clear_search(self):
"""清除搜索框内容"""
self.search_edit.clear()
def update_fields_count(self):
"""更新配置项计数显示"""
total_count = 0
visible_count = 0
hidden_count = 0
encrypted_count = 0
for i in range(self.fields_list.count()):
item = self.fields_list.item(i)
# 跳过分隔符项
if not self.is_separator_item(item):
total_count += 1
if not item.isHidden():
visible_count += 1
# 统计隐藏状态
if "已隐藏" in item.toolTip():
hidden_count += 1
# 统计加密状态
if "加密" in item.toolTip():
encrypted_count += 1
visible_unhidden = visible_count - hidden_count
visible_hidden = hidden_count
# 显示搜索类型信息
search_type = self.search_type_combo.currentText()
self.fields_count_label.setText(f"总计: {total_count} 个配置项 (显示: {visible_unhidden} 未隐藏, {visible_hidden} 隐藏, {encrypted_count} 加密) - 搜索类型: {search_type}")
def on_type_changed(self, field_type):
"""当类型改变时,控制小数位数设置的显示"""
# 只有float类型才显示小数位数设置
if field_type == "float":
self.decimal_label.setVisible(True)
self.decimal_spinbox.setVisible(True)
self.decimal_spinbox.setEnabled(True)
else:
self.decimal_label.setVisible(False)
self.decimal_spinbox.setVisible(False)
self.decimal_spinbox.setEnabled(False)
def on_hidden_changed(self, state):
"""当隐藏状态改变时,更新左侧列表项的外观和位置"""
current_item = self.fields_list.currentItem()
if not current_item or self.is_separator_item(current_item):
return
field_name = current_item.data(Qt.ItemDataRole.UserRole)
display_name = current_item.data(Qt.ItemDataRole.UserRole + 1)
is_hidden = (state == Qt.CheckState.Checked.value)
# 更新工具提示
if is_hidden:
current_item.setToolTip(f"已隐藏: {field_name} ({display_name})")
else:
current_item.setToolTip(f"未隐藏: {field_name} ({display_name})")
# 更新外观
if is_hidden:
# 改为隐藏样式
current_item.setForeground(QBrush(QColor("#999999")))
font = current_item.font()
font.setItalic(True)
font.setBold(False)
current_item.setFont(font)
current_item.setBackground(QBrush()) # 清除背景色
else:
# 改为未隐藏样式
current_item.setForeground(QBrush()) # 恢复默认前景色
font = current_item.font()
font.setItalic(False)
font.setBold(True)
current_item.setFont(font)
current_item.setBackground(QBrush(QColor("#e8f5e9"))) # 浅绿色背景
def on_encrypted_changed(self, state):
"""当加密状态改变时,更新左侧列表项的外观"""
current_item = self.fields_list.currentItem()
if not current_item or self.is_separator_item(current_item):
return
field_name = current_item.data(Qt.ItemDataRole.UserRole)
display_name = current_item.data(Qt.ItemDataRole.UserRole + 1)
is_encrypted = (state == Qt.CheckState.Checked.value)
# 更新工具提示
current_tooltip = current_item.toolTip()
if "加密" not in current_tooltip and is_encrypted:
current_item.setToolTip(current_tooltip + " [加密]")
elif "加密" in current_tooltip and not is_encrypted:
current_item.setToolTip(current_tooltip.replace(" [加密]", ""))
# 更新显示文本
current_text = current_item.text()
if "🔒" not in current_text and is_encrypted:
current_item.setText(current_text + " 🔒")
elif "🔒" in current_text and not is_encrypted:
current_item.setText(current_text.replace(" 🔒", ""))
# 更新外观
if is_encrypted:
# 加密字段:橙色高亮
current_item.setForeground(QBrush(QColor("#ff5722"))) # 橙色
font = current_item.font()
font.setBold(True)
current_item.setFont(font)
else:
# 恢复默认外观
current_item.setForeground(QBrush()) # 恢复默认前景色
font = current_item.font()
font.setBold(True)
current_item.setFont(font)
def on_field_selected(self, current, previous):
# 保存前一个字段的更改
if previous is not None and not self.is_separator_item(previous):
previous_field_name = previous.data(Qt.ItemDataRole.UserRole)
self.save_current_field_changes(previous_field_name)
if not current or self.is_separator_item(current):
# 清空右侧面板
self.name_label.setText("")
self.display_name_edit.clear()
self.category_combo.setCurrentText("")
self.type_combo.setCurrentText("auto")
self.decimal_spinbox.setValue(2)
self.hidden_checkbox.setChecked(False)
self.encrypted_checkbox.setChecked(False)
self.tooltip_edit.clear()
self.min_edit.clear()
self.max_edit.clear()
self.regex_edit.clear()
self.required_checkbox.setChecked(False)
return
field_name = current.data(Qt.ItemDataRole.UserRole)
display_name = current.data(Qt.ItemDataRole.UserRole + 1)
self.name_label.setText(field_name)
# 从规则中获取显示名称,如果规则中没有则使用当前存储的显示名称
rule_display_name = self.rules.get("display_names", {}).get(field_name, display_name)
self.display_name_edit.setText(rule_display_name)
# 查找配置项所属的分组
category = "未分类"
for cat, fields in self.rules.get("categories", {}).items():
if field_name in fields:
category = cat
break
# 设置分组
index = self.category_combo.findText(category)
if index >= 0:
self.category_combo.setCurrentIndex(index)
else:
self.category_combo.setCurrentText(category)
field_type = self.rules.get("field_types", {}).get(field_name, "auto")
index = self.type_combo.findText(field_type)
if index >= 0:
self.type_combo.setCurrentIndex(index)
# 设置小数位数
decimals = self.rules.get("field_decimals", {}).get(field_name, 6) # 默认6位小数
self.decimal_spinbox.setValue(decimals)
# 根据类型显示/隐藏小数位数设置
self.on_type_changed(field_type)
# 隐藏状态
hidden = field_name in self.rules.get("hidden", [])
self.hidden_checkbox.setChecked(hidden)
# 加密状态
encrypted = field_name in self.rules.get("encrypted_fields", [])
self.encrypted_checkbox.setChecked(encrypted)
tooltip = self.rules.get("tooltips", {}).get(field_name, "")
self.tooltip_edit.setPlainText(tooltip)
# 校验规则
validation = self.rules.get("validations", {}).get(field_name, {})
self.min_edit.setText(validation.get("min", ""))
self.max_edit.setText(validation.get("max", ""))
self.regex_edit.setText(validation.get("regex", ""))
self.required_checkbox.setChecked(validation.get("required", False))
def save_current_field_changes(self, field_name):
"""保存当前字段的更改到规则字典"""
if not field_name:
return
# 更新显示名称
display_name = self.display_name_edit.text().strip()
if display_name:
self.rules.setdefault("display_names", {})[field_name] = display_name
elif field_name in self.rules.get("display_names", {}):
del self.rules["display_names"][field_name]
# 更新分组
category = self.category_combo.currentText().strip()
if category == "未分类":
# 从所有分组中移除该字段
for cat, fields in self.rules.setdefault("categories", {}).items():
if cat != "未分类" and field_name in fields:
fields.remove(field_name)
# 确保在"未分类"分组中
if "未分类" not in self.rules["categories"]:
self.rules["categories"]["未分类"] = []
if field_name not in self.rules["categories"]["未分类"]:
self.rules["categories"]["未分类"].append(field_name)
elif category: # 其他分组
# 从所有分组中移除该字段
for cat, fields in self.rules.setdefault("categories", {}).items():
if field_name in fields:
fields.remove(field_name)
# 确保"未分类"分组存在
if "未分类" not in self.rules["categories"]:
self.rules["categories"]["未分类"] = []
# 添加到新分组
if category not in self.rules["categories"]:
self.rules["categories"][category] = []
if field_name not in self.rules["categories"][category]:
self.rules["categories"][category].append(field_name)
# 更新字段类型
field_type = self.type_combo.currentText()
if field_type != "auto":
self.rules.setdefault("field_types", {})[field_name] = field_type
# 更新小数位数仅当类型为float时保存
if field_type == "float":
decimals = self.decimal_spinbox.value()
self.rules.setdefault("field_decimals", {})[field_name] = decimals
elif field_name in self.rules.get("field_decimals", {}):
# 如果类型不是float但存在小数位数设置删除它
del self.rules["field_decimals"][field_name]
elif field_name in self.rules.get("field_types", {}):
del self.rules["field_types"][field_name]
# 如果字段类型被删除,也删除小数位数设置
if field_name in self.rules.get("field_decimals", {}):
del self.rules["field_decimals"][field_name]
# 更新提示信息
tooltip = self.tooltip_edit.toPlainText().strip()
if tooltip:
self.rules.setdefault("tooltips", {})[field_name] = tooltip
elif field_name in self.rules.get("tooltips", {}):
del self.rules["tooltips"][field_name]
# 更新隐藏状态
hidden = self.hidden_checkbox.isChecked()
hidden_list = self.rules.setdefault("hidden", [])
if hidden and field_name not in hidden_list:
hidden_list.append(field_name)
elif not hidden and field_name in hidden_list:
hidden_list.remove(field_name)
# 更新加密状态
encrypted = self.encrypted_checkbox.isChecked()
encrypted_list = self.rules.setdefault("encrypted_fields", [])
if encrypted and field_name not in encrypted_list:
encrypted_list.append(field_name)
elif not encrypted and field_name in encrypted_list:
encrypted_list.remove(field_name)
# 更新校验规则
validation = {}
min_val = self.min_edit.text().strip()
if min_val:
validation["min"] = min_val
max_val = self.max_edit.text().strip()
if max_val:
validation["max"] = max_val
regex_val = self.regex_edit.text().strip()
if regex_val:
validation["regex"] = regex_val
validation["required"] = self.required_checkbox.isChecked()
if validation:
self.rules.setdefault("validations", {})[field_name] = validation
elif field_name in self.rules.get("validations", {}):
del self.rules["validations"][field_name]
def accept(self):
"""重写accept方法确保保存当前字段的更改"""
current_item = self.fields_list.currentItem()
if current_item and not self.is_separator_item(current_item):
field_name = current_item.data(Qt.ItemDataRole.UserRole)
self.save_current_field_changes(field_name)
super().accept()
def add_group(self):
new_group, ok = QInputDialog.getText(self, "添加分组", "请输入新分组名称:")
if ok and new_group.strip():
if new_group not in self.rules.setdefault("categories", {}):
self.rules["categories"][new_group] = []
self.category_combo.addItem(new_group)
def remove_group(self):
current_group = self.category_combo.currentText()
if current_group and current_group != "未分类":
reply = QMessageBox.question(self, "确认删除",
f"确定要删除分组 '{current_group}' 吗?\n该分组中的所有配置项将被移到'未分类'分组。")
if reply == QMessageBox.StandardButton.Yes:
if current_group in self.rules.get("categories", {}):
# 将该分组中的配置项移到"未分类"分组
fields_to_move = self.rules["categories"][current_group]
if "未分类" not in self.rules["categories"]:
self.rules["categories"]["未分类"] = []
for field_name in fields_to_move:
if field_name not in self.rules["categories"]["未分类"]:
self.rules["categories"]["未分类"].append(field_name)
# 删除分组
del self.rules["categories"][current_group]
# 从下拉框中移除
index = self.category_combo.findText(current_group)
if index >= 0:
self.category_combo.removeItem(index)
# 设置当前选中的分组为"未分类"
self.category_combo.setCurrentText("未分类")
def show_all_fields(self):
"""批量显示所有配置项(取消所有隐藏)"""
reply = QMessageBox.question(self, "确认",
"确定要取消所有配置项的隐藏状态吗?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
if reply == QMessageBox.StandardButton.Yes:
# 清空隐藏列表
self.rules["hidden"] = []
# 重新加载数据以更新显示
self.load_data()
QMessageBox.information(self, "完成", "已取消所有配置项的隐藏状态")
def hide_all_fields(self):
"""批量隐藏所有配置项"""
reply = QMessageBox.question(self, "确认",
"确定要隐藏所有配置项吗?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
if reply == QMessageBox.StandardButton.Yes:
# 获取所有配置项的名称
all_fields = list(self.config_fields.keys())
# 将所有配置项添加到隐藏列表
self.rules["hidden"] = all_fields
# 重新加载数据以更新显示
self.load_data()
QMessageBox.information(self, "完成", "已隐藏所有配置项")
# 导入规则文件功能
def import_rules_file(self):
"""导入规则文件"""
# 确认保存当前更改
current_item = self.fields_list.currentItem()
if current_item and not self.is_separator_item(current_item):
field_name = current_item.data(Qt.ItemDataRole.UserRole)
self.save_current_field_changes(field_name)
# 选择要导入的规则文件
file_path, _ = QFileDialog.getOpenFileName(
self, "选择规则文件",
str(Path.home()),
"JSON规则文件 (*.json);;所有文件 (*)"
)
if not file_path:
return
try:
# 读取规则文件
with open(file_path, 'r', encoding='utf-8') as f:
imported_rules = json.load(f)
# 验证规则文件格式
if not isinstance(imported_rules, dict):
QMessageBox.warning(self, "导入失败", "规则文件格式不正确")
return
# 显示导入确认对话框
reply = QMessageBox.question(
self, "确认导入",
f"确定要导入规则文件吗?\n\n文件: {os.path.basename(file_path)}\n\n"
f"这将覆盖当前的规则设置。",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
if reply != QMessageBox.StandardButton.Yes:
return
# 完全替换现有规则
self.rules.clear()
self.rules.update(imported_rules)
# 确保规则结构完整
ensure_rule_structure(self.rules)
# 重新加载数据
self.load_data()
QMessageBox.information(
self, "导入成功",
f"规则文件导入成功!\n\n"
f"已导入 {len(self.rules.get('display_names', {}))} 个显示名称设置\n"
f"已导入 {len(self.rules.get('categories', {}))} 个分组设置\n"
f"已导入 {len(self.rules.get('hidden', []))} 个隐藏设置"
)
except json.JSONDecodeError:
QMessageBox.warning(self, "导入失败", "规则文件格式不正确不是有效的JSON文件")
except Exception as e:
QMessageBox.critical(self, "导入失败", f"导入规则文件时发生错误:{str(e)}")
# 导出规则文件功能
def export_rules_file(self):
"""导出规则文件"""
# 确认保存当前更改
current_item = self.fields_list.currentItem()
if current_item and not self.is_separator_item(current_item):
field_name = current_item.data(Qt.ItemDataRole.UserRole)
self.save_current_field_changes(field_name)
# 选择导出位置和文件名
default_name = f"config_editor_rules_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
file_path, _ = QFileDialog.getSaveFileName(
self, "导出规则文件",
default_name,
"JSON规则文件 (*.json);;所有文件 (*)"
)
if not file_path:
return
# 确保文件扩展名是.json
if not file_path.lower().endswith('.json'):
file_path += '.json'
try:
# 准备导出的规则数据
export_data = self.rules.copy()
# 添加导出信息
export_data["_export_info"] = {
"export_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"export_version": "1.0",
"note": "配置文件编辑器规则文件"
}
# 添加统计信息
export_data["_statistics"] = {
"total_fields": len(self.all_fields),
"display_names_count": len(self.rules.get("display_names", {})),
"categories_count": len(self.rules.get("categories", {})),
"hidden_fields_count": len(self.rules.get("hidden", [])),
"encrypted_fields_count": len(self.rules.get("encrypted_fields", [])),
"field_types_count": len(self.rules.get("field_types", {})),
"validations_count": len(self.rules.get("validations", {}))
}
# 写入文件
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(export_data, f, indent=2, ensure_ascii=False)
QMessageBox.information(
self, "导出成功",
f"规则文件已成功导出到:\n{file_path}\n\n"
f"导出时间:{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
f"规则数量:{len(export_data) - 2}" # 减去_export_info和_statistics
)
except Exception as e:
QMessageBox.critical(self, "导出失败", f"导出规则文件时发生错误:{str(e)}")
def get_updated_rules(self):
"""获取更新后的规则"""
return self.rules
class ChangeConfirmDialog(QDialog):
"""变更确认对话框"""
def __init__(self, changes, parent=None):
super().__init__(parent)
self.setWindowTitle("确认配置变更")
self.setModal(True)
self.setMinimumSize(600, 400)
self.resize(600, 400)
# 设置窗口标志,避免重影问题
self.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.WindowTitleHint |
Qt.WindowType.WindowCloseButtonHint | Qt.WindowType.WindowStaysOnTopHint)
self.init_ui(changes)
def init_ui(self, changes):
layout = QVBoxLayout(self)
# 标题
title_label = QLabel(f"以下 {len(changes)} 个配置项将被修改:")
title_label.setStyleSheet("font-weight: bold; font-size: 14px;")
layout.addWidget(title_label)
# 变更列表
table = QTableWidget()
table.setColumnCount(3)
table.setHorizontalHeaderLabels(["配置项", "原值", "新值"])
table.setRowCount(len(changes))
for row, (field_name, (old_value, new_value)) in enumerate(changes.items()):
table.setItem(row, 0, QTableWidgetItem(field_name))
table.setItem(row, 1, QTableWidgetItem(str(old_value)))
table.setItem(row, 2, QTableWidgetItem(str(new_value)))
table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
layout.addWidget(table)
# 按钮
button_box = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok |
QDialogButtonBox.StandardButton.Cancel
)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
layout.addWidget(button_box)