1036 lines
43 KiB
Python
1036 lines
43 KiB
Python
'''对话交互模块'''
|
||
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) |