2025-12-03 07:42:43 +00:00
|
|
|
|
import sys
|
|
|
|
|
|
import os
|
|
|
|
|
|
import ast
|
|
|
|
|
|
import json
|
|
|
|
|
|
import re
|
|
|
|
|
|
import traceback
|
2025-12-31 09:23:12 +00:00
|
|
|
|
import datetime
|
|
|
|
|
|
import shutil
|
2025-12-03 07:42:43 +00:00
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
from PyQt6.QtWidgets import (QApplication, QMainWindow, QTabWidget, QWidget, QVBoxLayout,
|
|
|
|
|
|
QHBoxLayout, QLabel, QLineEdit, QCheckBox, QPushButton,
|
|
|
|
|
|
QTextEdit, QScrollArea, QMessageBox, QFrame, QSpinBox,
|
|
|
|
|
|
QDoubleSpinBox, QTableWidget, QTableWidgetItem, QHeaderView,
|
|
|
|
|
|
QToolBar, QStatusBar, QDialog, QDialogButtonBox,
|
2025-12-31 09:23:12 +00:00
|
|
|
|
QGroupBox, QSplitter, QListWidget, QListWidgetItem, QComboBox,
|
|
|
|
|
|
QInputDialog, QFileDialog, QMenuBar, QMenu)
|
|
|
|
|
|
from PyQt6.QtCore import Qt, QSize, QTimer, QEvent, QMimeData
|
|
|
|
|
|
from PyQt6.QtGui import QAction, QDrag, QColor, QBrush, QFont
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 搜索框 - 添加搜索类型选择
|
2025-12-03 07:42:43 +00:00
|
|
|
|
search_layout = QHBoxLayout()
|
|
|
|
|
|
search_layout.addWidget(QLabel("搜索:"))
|
2025-12-31 09:23:12 +00:00
|
|
|
|
|
|
|
|
|
|
# 搜索类型选择
|
|
|
|
|
|
self.search_type_combo = QComboBox()
|
|
|
|
|
|
self.search_type_combo.addItems(["变量名", "显示名称"])
|
|
|
|
|
|
self.search_type_combo.setMaximumWidth(80)
|
|
|
|
|
|
search_layout.addWidget(self.search_type_combo)
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
self.search_edit = QLineEdit()
|
2025-12-31 09:23:12 +00:00
|
|
|
|
self.search_edit.setPlaceholderText("输入关键词进行搜索...")
|
2025-12-03 07:42:43 +00:00
|
|
|
|
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)
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 修改:添加分组显示说明
|
|
|
|
|
|
list_label = QLabel("配置项列表(上方为未隐藏项,下方为隐藏项):")
|
|
|
|
|
|
list_label.setStyleSheet("color: #333333; font-weight: bold;")
|
|
|
|
|
|
left_layout.addWidget(list_label)
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
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("隐藏此配置项(不在主界面显示)")
|
2025-12-31 09:23:12 +00:00
|
|
|
|
self.hidden_checkbox.stateChanged.connect(self.on_hidden_changed)
|
2025-12-03 07:42:43 +00:00
|
|
|
|
form_layout.addWidget(self.hidden_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)
|
|
|
|
|
|
|
2025-12-12 09:49:12 +00:00
|
|
|
|
# 批量操作
|
|
|
|
|
|
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)
|
2025-12-31 09:23:12 +00:00
|
|
|
|
|
|
|
|
|
|
# === 新增:导入导出功能 ===
|
|
|
|
|
|
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)
|
2025-12-12 09:49:12 +00:00
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
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):
|
2025-12-31 09:23:12 +00:00
|
|
|
|
"""加载数据到界面(按隐藏状态分组显示)"""
|
2025-12-03 07:42:43 +00:00
|
|
|
|
self.fields_list.clear()
|
2025-12-31 09:23:12 +00:00
|
|
|
|
|
|
|
|
|
|
# 获取隐藏字段列表
|
|
|
|
|
|
hidden_fields = self.rules.get("hidden", [])
|
|
|
|
|
|
|
|
|
|
|
|
# 分离未隐藏和已隐藏的字段
|
|
|
|
|
|
visible_fields = []
|
|
|
|
|
|
hidden_fields_list = []
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
for field_name in sorted(self.config_fields.keys()):
|
2025-12-31 09:23:12 +00:00
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加分隔项
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
# 如果没有配置项,显示提示
|
|
|
|
|
|
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")))
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
self.update_fields_count()
|
|
|
|
|
|
|
2025-12-12 09:49:12 +00:00
|
|
|
|
# 加载分组
|
2025-12-03 07:42:43 +00:00
|
|
|
|
self.category_combo.clear()
|
|
|
|
|
|
categories = list(self.rules.get("categories", {}).keys())
|
2025-12-12 09:49:12 +00:00
|
|
|
|
# 确保"未分类"分组始终存在
|
|
|
|
|
|
if "未分类" not in categories:
|
|
|
|
|
|
categories.append("未分类")
|
|
|
|
|
|
if categories:
|
|
|
|
|
|
for category in sorted(categories):
|
|
|
|
|
|
self.category_combo.addItem(category)
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
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):
|
|
|
|
|
|
"""创建字段项"""
|
|
|
|
|
|
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) # 显示名称
|
|
|
|
|
|
|
|
|
|
|
|
if is_hidden:
|
|
|
|
|
|
# 隐藏字段:灰色,斜体
|
|
|
|
|
|
item.setForeground(QBrush(QColor("#999999")))
|
|
|
|
|
|
font = item.font()
|
|
|
|
|
|
font.setItalic(True)
|
|
|
|
|
|
item.setFont(font)
|
|
|
|
|
|
item.setToolTip(f"已隐藏: {field_name} ({display_name})")
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 未隐藏字段:高亮显示(浅绿色背景)
|
|
|
|
|
|
item.setBackground(QBrush(QColor("#e8f5e9"))) # 浅绿色背景
|
|
|
|
|
|
font = item.font()
|
|
|
|
|
|
font.setBold(True)
|
|
|
|
|
|
item.setFont(font)
|
|
|
|
|
|
item.setToolTip(f"未隐藏: {field_name} ({display_name})")
|
|
|
|
|
|
|
|
|
|
|
|
return item
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
def filter_fields(self, search_text):
|
2025-12-31 09:23:12 +00:00
|
|
|
|
"""根据搜索文本过滤配置项列表(保持分组),支持变量名和显示名称搜索"""
|
2025-12-03 07:42:43 +00:00
|
|
|
|
search_text = search_text.strip().lower()
|
2025-12-31 09:23:12 +00:00
|
|
|
|
search_type = self.search_type_combo.currentText() # 获取搜索类型
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
if not search_text:
|
|
|
|
|
|
# 如果搜索文本为空,显示所有项
|
|
|
|
|
|
for i in range(self.fields_list.count()):
|
|
|
|
|
|
item = self.fields_list.item(i)
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 跳过分隔符项
|
|
|
|
|
|
if not self.is_separator_item(item):
|
|
|
|
|
|
item.setHidden(False)
|
2025-12-03 07:42:43 +00:00
|
|
|
|
else:
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 模糊匹配:根据搜索类型进行匹配
|
2025-12-03 07:42:43 +00:00
|
|
|
|
for i in range(self.fields_list.count()):
|
|
|
|
|
|
item = self.fields_list.item(i)
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 跳过分隔符项
|
|
|
|
|
|
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)
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
self.update_fields_count()
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
def is_separator_item(self, item):
|
|
|
|
|
|
"""检查是否是分隔符项"""
|
|
|
|
|
|
text = item.text()
|
|
|
|
|
|
return text.startswith("---") and text.endswith("---")
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
def clear_search(self):
|
|
|
|
|
|
"""清除搜索框内容"""
|
|
|
|
|
|
self.search_edit.clear()
|
|
|
|
|
|
|
|
|
|
|
|
def update_fields_count(self):
|
|
|
|
|
|
"""更新配置项计数显示"""
|
2025-12-31 09:23:12 +00:00
|
|
|
|
total_count = 0
|
2025-12-03 07:42:43 +00:00
|
|
|
|
visible_count = 0
|
2025-12-31 09:23:12 +00:00
|
|
|
|
hidden_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
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
visible_unhidden = visible_count - hidden_count
|
|
|
|
|
|
visible_hidden = hidden_count
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 显示搜索类型信息
|
|
|
|
|
|
search_type = self.search_type_combo.currentText()
|
|
|
|
|
|
self.fields_count_label.setText(f"总计: {total_count} 个配置项 (显示: {visible_unhidden} 未隐藏, {visible_hidden} 隐藏) - 搜索类型: {search_type}")
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
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"))) # 浅绿色背景
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
def on_field_selected(self, current, previous):
|
|
|
|
|
|
# 保存前一个字段的更改
|
2025-12-31 09:23:12 +00:00
|
|
|
|
if previous is not None and not self.is_separator_item(previous):
|
|
|
|
|
|
previous_field_name = previous.data(Qt.ItemDataRole.UserRole)
|
2025-12-03 07:42:43 +00:00
|
|
|
|
self.save_current_field_changes(previous_field_name)
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
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.tooltip_edit.clear()
|
|
|
|
|
|
self.min_edit.clear()
|
|
|
|
|
|
self.max_edit.clear()
|
|
|
|
|
|
self.regex_edit.clear()
|
|
|
|
|
|
self.required_checkbox.setChecked(False)
|
2025-12-03 07:42:43 +00:00
|
|
|
|
return
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
field_name = current.data(Qt.ItemDataRole.UserRole)
|
|
|
|
|
|
display_name = current.data(Qt.ItemDataRole.UserRole + 1)
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
self.name_label.setText(field_name)
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 从规则中获取显示名称,如果规则中没有则使用当前存储的显示名称
|
|
|
|
|
|
rule_display_name = self.rules.get("display_names", {}).get(field_name, display_name)
|
|
|
|
|
|
self.display_name_edit.setText(rule_display_name)
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
2025-12-12 09:49:12 +00:00
|
|
|
|
# 查找配置项所属的分组
|
2025-12-03 07:42:43 +00:00
|
|
|
|
category = "未分类"
|
|
|
|
|
|
for cat, fields in self.rules.get("categories", {}).items():
|
|
|
|
|
|
if field_name in fields:
|
|
|
|
|
|
category = cat
|
|
|
|
|
|
break
|
|
|
|
|
|
|
2025-12-12 09:49:12 +00:00
|
|
|
|
# 设置分组
|
2025-12-03 07:42:43 +00:00
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
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()
|
2025-12-12 09:49:12 +00:00
|
|
|
|
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():
|
2025-12-03 07:42:43 +00:00
|
|
|
|
if field_name in fields:
|
|
|
|
|
|
fields.remove(field_name)
|
2025-12-12 09:49:12 +00:00
|
|
|
|
|
|
|
|
|
|
# 确保"未分类"分组存在
|
|
|
|
|
|
if "未分类" not in self.rules["categories"]:
|
|
|
|
|
|
self.rules["categories"]["未分类"] = []
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
# 添加到新分组
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
# 更新校验规则
|
|
|
|
|
|
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()
|
2025-12-31 09:23:12 +00:00
|
|
|
|
if current_item and not self.is_separator_item(current_item):
|
|
|
|
|
|
field_name = current_item.data(Qt.ItemDataRole.UserRole)
|
2025-12-03 07:42:43 +00:00
|
|
|
|
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, "确认删除",
|
2025-12-12 09:49:12 +00:00
|
|
|
|
f"确定要删除分组 '{current_group}' 吗?\n该分组中的所有配置项将被移到'未分类'分组。")
|
2025-12-03 07:42:43 +00:00
|
|
|
|
if reply == QMessageBox.StandardButton.Yes:
|
|
|
|
|
|
if current_group in self.rules.get("categories", {}):
|
2025-12-12 09:49:12 +00:00
|
|
|
|
# 将该分组中的配置项移到"未分类"分组
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
# 删除分组
|
2025-12-03 07:42:43 +00:00
|
|
|
|
del self.rules["categories"][current_group]
|
2025-12-12 09:49:12 +00:00
|
|
|
|
|
|
|
|
|
|
# 从下拉框中移除
|
|
|
|
|
|
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"] = []
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 重新加载数据以更新显示
|
|
|
|
|
|
self.load_data()
|
2025-12-12 09:49:12 +00:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 重新加载数据以更新显示
|
|
|
|
|
|
self.load_data()
|
2025-12-12 09:49:12 +00:00
|
|
|
|
|
|
|
|
|
|
QMessageBox.information(self, "完成", "已隐藏所有配置项")
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# === 新增:导入规则文件功能 ===
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
# 确保规则结构完整
|
|
|
|
|
|
self.ensure_rule_structure()
|
|
|
|
|
|
|
|
|
|
|
|
# 重新加载数据
|
|
|
|
|
|
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", [])),
|
|
|
|
|
|
"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 ensure_rule_structure(self):
|
|
|
|
|
|
"""确保规则结构完整"""
|
|
|
|
|
|
# 确保规则中有必要的字段
|
|
|
|
|
|
if "categories" not in self.rules:
|
|
|
|
|
|
self.rules["categories"] = {"未分类": []}
|
|
|
|
|
|
if "未分类" not in self.rules["categories"]:
|
|
|
|
|
|
self.rules["categories"]["未分类"] = []
|
|
|
|
|
|
if "display_names" not in self.rules:
|
|
|
|
|
|
self.rules["display_names"] = {}
|
|
|
|
|
|
if "field_types" not in self.rules:
|
|
|
|
|
|
self.rules["field_types"] = {}
|
|
|
|
|
|
if "field_decimals" not in self.rules:
|
|
|
|
|
|
self.rules["field_decimals"] = {}
|
|
|
|
|
|
if "tooltips" not in self.rules:
|
|
|
|
|
|
self.rules["tooltips"] = {}
|
|
|
|
|
|
if "hidden" not in self.rules:
|
|
|
|
|
|
self.rules["hidden"] = []
|
|
|
|
|
|
if "validations" not in self.rules:
|
|
|
|
|
|
self.rules["validations"] = {}
|
|
|
|
|
|
if "field_order" not in self.rules:
|
|
|
|
|
|
self.rules["field_order"] = {}
|
|
|
|
|
|
if "last_saved_values" not in self.rules:
|
|
|
|
|
|
self.rules["last_saved_values"] = {}
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
def get_updated_rules(self):
|
|
|
|
|
|
"""获取更新后的规则"""
|
|
|
|
|
|
return self.rules
|
|
|
|
|
|
|
|
|
|
|
|
class NoWheelSpinBox(QSpinBox):
|
|
|
|
|
|
"""禁用鼠标滚轮的SpinBox"""
|
|
|
|
|
|
def wheelEvent(self, event):
|
|
|
|
|
|
event.ignore()
|
|
|
|
|
|
|
|
|
|
|
|
class NoWheelDoubleSpinBox(QDoubleSpinBox):
|
|
|
|
|
|
"""禁用鼠标滚轮的DoubleSpinBox"""
|
|
|
|
|
|
def wheelEvent(self, event):
|
|
|
|
|
|
event.ignore()
|
|
|
|
|
|
|
|
|
|
|
|
def setDecimalsFromRules(self, decimals):
|
|
|
|
|
|
"""根据规则设置小数位数"""
|
|
|
|
|
|
self.setDecimals(decimals)
|
|
|
|
|
|
|
|
|
|
|
|
class ConfigField:
|
|
|
|
|
|
"""配置项元数据类"""
|
|
|
|
|
|
def __init__(self, name, value, category="未分类", display_name=None,
|
|
|
|
|
|
field_type="auto", decimals=None, tooltip="", hidden=False,
|
2025-12-31 09:23:12 +00:00
|
|
|
|
line_number=None, original_lines=None, validation=None,
|
|
|
|
|
|
last_saved_value=None): # 新增:上次保存的值
|
2025-12-03 07:42:43 +00:00
|
|
|
|
self.name = name
|
|
|
|
|
|
self.value = value
|
|
|
|
|
|
self.category = category
|
|
|
|
|
|
self.display_name = display_name or name
|
|
|
|
|
|
self.field_type = field_type
|
|
|
|
|
|
self.decimals = decimals # 小数位数(仅对float类型有效)
|
|
|
|
|
|
self.tooltip = tooltip
|
|
|
|
|
|
self.hidden = hidden
|
|
|
|
|
|
self.line_number = line_number # 记录配置项在文件中的行号
|
|
|
|
|
|
self.original_lines = original_lines or [] # 保存原始行内容(包括注释)
|
|
|
|
|
|
self.validation = validation or {} # 校验规则
|
2025-12-31 09:23:12 +00:00
|
|
|
|
self.last_saved_value = last_saved_value # 新增:上次保存的值
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
def get_actual_field_type(self):
|
|
|
|
|
|
"""获取实际的字段类型"""
|
|
|
|
|
|
if self.field_type != "auto":
|
|
|
|
|
|
return self.field_type
|
|
|
|
|
|
|
|
|
|
|
|
if isinstance(self.value, bool):
|
|
|
|
|
|
return "bool"
|
|
|
|
|
|
elif isinstance(self.value, int):
|
|
|
|
|
|
return "int"
|
|
|
|
|
|
elif isinstance(self.value, float):
|
|
|
|
|
|
return "float"
|
|
|
|
|
|
elif isinstance(self.value, (list, dict)):
|
|
|
|
|
|
return "json"
|
|
|
|
|
|
else:
|
|
|
|
|
|
return "str"
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
class ConfigEditor(QMainWindow):
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
|
|
|
|
|
|
# 获取程序所在目录
|
|
|
|
|
|
if getattr(sys, 'frozen', False):
|
|
|
|
|
|
# 打包后的可执行文件
|
|
|
|
|
|
self.program_dir = Path(sys.executable).parent
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 直接运行脚本
|
|
|
|
|
|
self.program_dir = Path(__file__).parent
|
|
|
|
|
|
|
|
|
|
|
|
# 规则文件固定位置:与主程序在同一目录
|
|
|
|
|
|
self.rules_file = str(self.program_dir / "config_editor_rules.json")
|
|
|
|
|
|
|
|
|
|
|
|
# 用户设置文件
|
|
|
|
|
|
self.settings_file = str(self.program_dir / "config_editor_settings.json")
|
|
|
|
|
|
|
|
|
|
|
|
# 加载用户设置
|
|
|
|
|
|
self.user_settings = self.load_user_settings()
|
|
|
|
|
|
|
|
|
|
|
|
# 获取配置文件路径(如果有)
|
|
|
|
|
|
self.config_file_path = self.user_settings.get("config_file_path", "")
|
|
|
|
|
|
|
|
|
|
|
|
self.config_data = {}
|
|
|
|
|
|
self.original_config_data = {} # 保存原始配置数据用于比较
|
|
|
|
|
|
self.config_fields = {} # 过滤后的配置项(不包含隐藏的)
|
|
|
|
|
|
self.all_config_fields = {} # 所有配置项(包含隐藏的)
|
|
|
|
|
|
self.dynamic_widgets = {}
|
|
|
|
|
|
self.category_widgets = {}
|
|
|
|
|
|
self.original_content = ""
|
|
|
|
|
|
self.rules = {}
|
|
|
|
|
|
self.config_lines = [] # 存储每行内容和对应的配置项
|
|
|
|
|
|
self.modified_fields = set() # 记录被修改的字段
|
|
|
|
|
|
self.field_containers = {} # 存储字段的容器
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# === 新增:拖拽相关属性 ===
|
|
|
|
|
|
self.dragging_field = None # 当前正在拖拽的字段名
|
|
|
|
|
|
self.drag_start_pos = None # 拖拽起始位置
|
|
|
|
|
|
self.drop_target_field = None # 拖拽目标字段名
|
|
|
|
|
|
self.field_order = {} # 存储每个分组中字段的顺序,格式:{category: [field1, field2, ...]}
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
self.init_ui()
|
|
|
|
|
|
self.load_rules()
|
|
|
|
|
|
|
|
|
|
|
|
# 检查是否第一次启动(没有配置文件地址)
|
|
|
|
|
|
if not self.config_file_path or not os.path.exists(self.config_file_path):
|
|
|
|
|
|
# 首次启动,显示设置对话框
|
|
|
|
|
|
self.show_settings_dialog(initial_setup=True)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 非首次启动,加载配置文件
|
|
|
|
|
|
self.load_config()
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
def load_icon(self):
|
|
|
|
|
|
"""加载程序图标 - 创建一个简单的CE色块图标"""
|
|
|
|
|
|
from PyQt6.QtGui import QIcon, QPixmap, QPainter, QFont
|
|
|
|
|
|
from PyQt6.QtCore import Qt
|
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
|
|
|
|
# 方法1: 尝试加载本地图标文件
|
|
|
|
|
|
icon_paths = [
|
|
|
|
|
|
os.path.join(self.program_dir, "logo.ico"),
|
|
|
|
|
|
os.path.join(self.program_dir, "logo.png"),
|
|
|
|
|
|
os.path.join(self.program_dir, "resources", "logo.ico"),
|
|
|
|
|
|
os.path.join(self.program_dir, "resources", "logo.png"),
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for path in icon_paths:
|
|
|
|
|
|
if os.path.exists(path):
|
|
|
|
|
|
try:
|
|
|
|
|
|
icon = QIcon(path)
|
|
|
|
|
|
if not icon.isNull():
|
|
|
|
|
|
print(f"加载图标: {path}")
|
|
|
|
|
|
return icon
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"加载图标失败 {path}: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
# 方法2: 创建简单的CE色块图标(无需外部文件)
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 创建不同尺寸的图标以确保在各种场景下都能清晰显示
|
|
|
|
|
|
sizes = [16, 32, 48, 64, 128]
|
|
|
|
|
|
icon = QIcon()
|
|
|
|
|
|
|
|
|
|
|
|
for size in sizes:
|
|
|
|
|
|
pixmap = QPixmap(size, size)
|
|
|
|
|
|
pixmap.fill(Qt.GlobalColor.transparent)
|
|
|
|
|
|
|
|
|
|
|
|
# 在pixmap上绘制
|
|
|
|
|
|
from PyQt6.QtGui import QPen, QBrush, QColor
|
|
|
|
|
|
painter = QPainter(pixmap)
|
|
|
|
|
|
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
|
|
|
|
|
|
|
|
|
|
# 绘制蓝色圆角矩形背景
|
|
|
|
|
|
bg_color = QColor(33, 150, 243) # 蓝色
|
|
|
|
|
|
painter.setBrush(QBrush(bg_color))
|
|
|
|
|
|
painter.setPen(Qt.PenStyle.NoPen)
|
|
|
|
|
|
|
|
|
|
|
|
# 根据尺寸调整圆角
|
|
|
|
|
|
corner_radius = size // 8
|
|
|
|
|
|
painter.drawRoundedRect(0, 0, size, size, corner_radius, corner_radius)
|
|
|
|
|
|
|
|
|
|
|
|
# 绘制白色CE文字
|
|
|
|
|
|
painter.setPen(QPen(Qt.GlobalColor.white, 1))
|
|
|
|
|
|
font_size = max(size // 3, 8) # 确保字体不会太小
|
|
|
|
|
|
painter.setFont(QFont("Arial", font_size, QFont.Weight.Bold))
|
|
|
|
|
|
painter.drawText(pixmap.rect(), Qt.AlignmentFlag.AlignCenter, "CE")
|
|
|
|
|
|
|
|
|
|
|
|
painter.end()
|
|
|
|
|
|
|
|
|
|
|
|
# 将pixmap添加到图标
|
|
|
|
|
|
icon.addPixmap(pixmap)
|
|
|
|
|
|
|
|
|
|
|
|
return icon
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"创建图标失败: {e}")
|
|
|
|
|
|
return QIcon() # 返回空图标
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
def load_user_settings(self):
|
|
|
|
|
|
"""加载用户设置"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
if os.path.exists(self.settings_file):
|
|
|
|
|
|
with open(self.settings_file, 'r', encoding='utf-8') as f:
|
|
|
|
|
|
return json.load(f)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"加载用户设置失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
def save_user_settings(self):
|
|
|
|
|
|
"""保存用户设置"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.user_settings.update({
|
|
|
|
|
|
"config_file_path": self.config_file_path,
|
|
|
|
|
|
"last_used": str(Path(self.config_file_path).absolute()) if self.config_file_path and os.path.exists(self.config_file_path) else ""
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
with open(self.settings_file, 'w', encoding='utf-8') as f:
|
|
|
|
|
|
json.dump(self.user_settings, f, indent=2, ensure_ascii=False)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"保存用户设置失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
|
self.setWindowTitle("配置管理页")
|
|
|
|
|
|
self.setGeometry(100, 100, 1000, 700)
|
|
|
|
|
|
self.resize(1000, 700)
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 设置窗口图标(显示在标题栏和任务栏)
|
|
|
|
|
|
self.setWindowIcon(self.load_icon())
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
# 创建菜单栏
|
|
|
|
|
|
menubar = self.menuBar()
|
|
|
|
|
|
|
|
|
|
|
|
# 文件菜单
|
|
|
|
|
|
file_menu = menubar.addMenu("文件")
|
|
|
|
|
|
|
|
|
|
|
|
self.open_action = QAction("打开配置文件", self)
|
|
|
|
|
|
self.open_action.triggered.connect(self.open_config_file)
|
|
|
|
|
|
file_menu.addAction(self.open_action)
|
|
|
|
|
|
|
|
|
|
|
|
self.settings_action = QAction("配置文件设置", self)
|
|
|
|
|
|
self.settings_action.triggered.connect(lambda: self.show_settings_dialog(initial_setup=False))
|
|
|
|
|
|
file_menu.addAction(self.settings_action)
|
|
|
|
|
|
|
|
|
|
|
|
file_menu.addSeparator()
|
|
|
|
|
|
|
|
|
|
|
|
self.exit_action = QAction("退出", self)
|
|
|
|
|
|
self.exit_action.triggered.connect(self.close)
|
|
|
|
|
|
file_menu.addAction(self.exit_action)
|
|
|
|
|
|
|
|
|
|
|
|
# 编辑菜单
|
|
|
|
|
|
edit_menu = menubar.addMenu("编辑")
|
|
|
|
|
|
|
|
|
|
|
|
self.reload_action = QAction("重新加载", self)
|
|
|
|
|
|
self.reload_action.triggered.connect(self.load_config)
|
|
|
|
|
|
edit_menu.addAction(self.reload_action)
|
|
|
|
|
|
|
|
|
|
|
|
self.save_action = QAction("保存配置", self)
|
|
|
|
|
|
self.save_action.triggered.connect(self.save_config)
|
|
|
|
|
|
edit_menu.addAction(self.save_action)
|
|
|
|
|
|
|
|
|
|
|
|
# 工具菜单
|
|
|
|
|
|
tools_menu = menubar.addMenu("工具")
|
|
|
|
|
|
|
|
|
|
|
|
self.rules_action = QAction("管理规则", self)
|
|
|
|
|
|
self.rules_action.triggered.connect(self.manage_rules)
|
|
|
|
|
|
tools_menu.addAction(self.rules_action)
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# === 新增:在工具菜单中添加导入导出规则文件 ===
|
|
|
|
|
|
tools_menu.addSeparator()
|
|
|
|
|
|
self.import_rules_action = QAction("导入规则文件", self)
|
|
|
|
|
|
self.import_rules_action.triggered.connect(self.import_rules_file)
|
|
|
|
|
|
tools_menu.addAction(self.import_rules_action)
|
|
|
|
|
|
|
|
|
|
|
|
self.export_rules_action = QAction("导出规则文件", self)
|
|
|
|
|
|
self.export_rules_action.triggered.connect(self.export_rules_file)
|
|
|
|
|
|
tools_menu.addAction(self.export_rules_action)
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
# 状态栏
|
|
|
|
|
|
self.statusBar().showMessage("就绪")
|
|
|
|
|
|
|
|
|
|
|
|
central_widget = QWidget()
|
|
|
|
|
|
self.setCentralWidget(central_widget)
|
|
|
|
|
|
|
|
|
|
|
|
main_layout = QVBoxLayout(central_widget)
|
|
|
|
|
|
main_layout.setContentsMargins(8, 8, 8, 8)
|
|
|
|
|
|
main_layout.setSpacing(8)
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 顶部信息栏(路径显示)
|
2025-12-03 07:42:43 +00:00
|
|
|
|
info_frame = QFrame()
|
|
|
|
|
|
info_frame.setFrameShape(QFrame.Shape.StyledPanel)
|
|
|
|
|
|
info_frame.setStyleSheet("""
|
|
|
|
|
|
QFrame {
|
2025-12-31 09:23:12 +00:00
|
|
|
|
background-color: #f8f9fa;
|
|
|
|
|
|
border: 1px solid #dee2e6;
|
2025-12-03 07:42:43 +00:00
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
""")
|
|
|
|
|
|
info_layout = QHBoxLayout(info_frame)
|
2025-12-31 09:23:12 +00:00
|
|
|
|
info_layout.setContentsMargins(12, 8, 12, 8)
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
self.path_label = QLabel()
|
2025-12-31 09:23:12 +00:00
|
|
|
|
self.path_label.setStyleSheet("""
|
|
|
|
|
|
QLabel {
|
|
|
|
|
|
color: #495057;
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
font-family: 'Consolas', 'Monaco', monospace;
|
|
|
|
|
|
}
|
|
|
|
|
|
""")
|
2025-12-03 07:42:43 +00:00
|
|
|
|
info_layout.addWidget(self.path_label)
|
|
|
|
|
|
info_layout.addStretch()
|
2025-12-31 09:23:12 +00:00
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
main_layout.addWidget(info_frame)
|
|
|
|
|
|
|
|
|
|
|
|
self.tab_widget = QTabWidget()
|
|
|
|
|
|
main_layout.addWidget(self.tab_widget)
|
|
|
|
|
|
|
|
|
|
|
|
self.init_category_tabs()
|
|
|
|
|
|
|
|
|
|
|
|
# 按钮布局
|
|
|
|
|
|
button_layout = QHBoxLayout()
|
|
|
|
|
|
button_layout.setSpacing(8)
|
|
|
|
|
|
|
|
|
|
|
|
self.open_btn = QPushButton("打开文件")
|
|
|
|
|
|
self.open_btn.clicked.connect(self.open_config_file)
|
|
|
|
|
|
button_layout.addWidget(self.open_btn)
|
|
|
|
|
|
|
|
|
|
|
|
self.settings_btn = QPushButton("配置文件设置")
|
|
|
|
|
|
self.settings_btn.clicked.connect(lambda: self.show_settings_dialog(initial_setup=False))
|
|
|
|
|
|
button_layout.addWidget(self.settings_btn)
|
|
|
|
|
|
|
|
|
|
|
|
button_layout.addStretch()
|
|
|
|
|
|
|
|
|
|
|
|
self.save_btn = QPushButton("保存配置")
|
|
|
|
|
|
self.save_btn.clicked.connect(self.save_config)
|
|
|
|
|
|
self.save_btn.setStyleSheet("QPushButton { background-color: #4CAF50; color: white; }")
|
|
|
|
|
|
button_layout.addWidget(self.save_btn)
|
|
|
|
|
|
|
|
|
|
|
|
self.reload_btn = QPushButton("重新加载")
|
|
|
|
|
|
self.reload_btn.clicked.connect(self.load_config)
|
|
|
|
|
|
button_layout.addWidget(self.reload_btn)
|
|
|
|
|
|
|
|
|
|
|
|
self.rules_btn = QPushButton("管理规则")
|
|
|
|
|
|
self.rules_btn.clicked.connect(self.manage_rules)
|
|
|
|
|
|
button_layout.addWidget(self.rules_btn)
|
|
|
|
|
|
|
|
|
|
|
|
# 显示/隐藏配置项按钮
|
|
|
|
|
|
self.toggle_hidden_btn = QPushButton("显示隐藏项")
|
|
|
|
|
|
self.toggle_hidden_btn.setCheckable(True)
|
|
|
|
|
|
self.toggle_hidden_btn.toggled.connect(self.toggle_hidden_fields)
|
|
|
|
|
|
button_layout.addWidget(self.toggle_hidden_btn)
|
|
|
|
|
|
|
2025-12-12 09:49:12 +00:00
|
|
|
|
# 新增:一键隐藏所有配置项按钮
|
|
|
|
|
|
self.hide_all_btn = QPushButton("一键隐藏所有项")
|
|
|
|
|
|
self.hide_all_btn.clicked.connect(self.hide_all_fields)
|
|
|
|
|
|
self.hide_all_btn.setStyleSheet("QPushButton { background-color: #ff9800; color: white; }")
|
|
|
|
|
|
button_layout.addWidget(self.hide_all_btn)
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
main_layout.addLayout(button_layout)
|
|
|
|
|
|
|
|
|
|
|
|
# 更新路径显示
|
|
|
|
|
|
self.update_path_display()
|
|
|
|
|
|
|
|
|
|
|
|
def update_path_display(self):
|
|
|
|
|
|
"""更新路径显示"""
|
|
|
|
|
|
if self.config_file_path and os.path.exists(self.config_file_path):
|
|
|
|
|
|
abs_path = os.path.abspath(self.config_file_path)
|
|
|
|
|
|
self.path_label.setText(f"配置文件: {abs_path}")
|
|
|
|
|
|
self.path_label.setStyleSheet("color: #666666; font-size: 11px;")
|
|
|
|
|
|
elif self.config_file_path:
|
|
|
|
|
|
self.path_label.setText(f"配置文件不存在: {self.config_file_path} (点击'配置文件设置'调整)")
|
|
|
|
|
|
self.path_label.setStyleSheet("color: #ff0000; font-size: 11px;")
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.path_label.setText("未设置配置文件路径 (点击'配置文件设置'配置)")
|
|
|
|
|
|
self.path_label.setStyleSheet("color: #ff9900; font-size: 11px;")
|
|
|
|
|
|
|
|
|
|
|
|
def check_config_file(self):
|
|
|
|
|
|
"""检查配置文件是否存在"""
|
|
|
|
|
|
if not self.config_file_path or not os.path.exists(self.config_file_path):
|
|
|
|
|
|
return False
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def show_settings_dialog(self, initial_setup=False):
|
|
|
|
|
|
"""显示设置对话框
|
|
|
|
|
|
initial_setup: 是否是首次启动的设置
|
|
|
|
|
|
"""
|
|
|
|
|
|
dialog = ConfigSettingsDialog(self.config_file_path, self)
|
|
|
|
|
|
|
|
|
|
|
|
if initial_setup:
|
|
|
|
|
|
# 首次启动,设置对话框模态且必须设置
|
|
|
|
|
|
dialog.setWindowTitle("首次启动 - 配置文件设置")
|
|
|
|
|
|
dialog.setWindowModality(Qt.WindowModality.ApplicationModal)
|
|
|
|
|
|
|
|
|
|
|
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
|
|
|
|
settings = dialog.get_settings()
|
|
|
|
|
|
|
|
|
|
|
|
# 更新设置
|
|
|
|
|
|
self.config_file_path = settings["config_file_path"]
|
|
|
|
|
|
|
|
|
|
|
|
# 保存用户设置
|
|
|
|
|
|
self.save_user_settings()
|
|
|
|
|
|
|
|
|
|
|
|
# 更新路径显示
|
|
|
|
|
|
self.update_path_display()
|
|
|
|
|
|
|
|
|
|
|
|
# 重新加载配置
|
|
|
|
|
|
if self.check_config_file():
|
|
|
|
|
|
self.load_config()
|
|
|
|
|
|
if not initial_setup:
|
|
|
|
|
|
QMessageBox.information(self, "成功", "配置文件路径已更新并重新加载配置")
|
|
|
|
|
|
else:
|
|
|
|
|
|
QMessageBox.warning(self, "警告", "配置文件不存在,请检查路径")
|
|
|
|
|
|
elif initial_setup:
|
|
|
|
|
|
# 首次启动用户取消了设置,退出程序
|
|
|
|
|
|
QMessageBox.warning(self, "提示", "首次启动需要设置配置文件路径,程序将退出。")
|
|
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
|
|
|
|
def open_config_file(self):
|
|
|
|
|
|
"""打开配置文件"""
|
|
|
|
|
|
if self.modified_fields:
|
|
|
|
|
|
reply = QMessageBox.question(
|
|
|
|
|
|
self, "确认",
|
|
|
|
|
|
"当前有未保存的修改,是否保存?",
|
|
|
|
|
|
QMessageBox.StandardButton.Yes |
|
|
|
|
|
|
QMessageBox.StandardButton.No |
|
|
|
|
|
|
QMessageBox.StandardButton.Cancel
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if reply == QMessageBox.StandardButton.Cancel:
|
|
|
|
|
|
return
|
|
|
|
|
|
elif reply == QMessageBox.StandardButton.Yes:
|
|
|
|
|
|
self.save_config()
|
|
|
|
|
|
|
|
|
|
|
|
# 使用文件对话框选择文件
|
|
|
|
|
|
file_path, _ = QFileDialog.getOpenFileName(
|
|
|
|
|
|
self, "选择配置文件",
|
|
|
|
|
|
self.config_file_path or str(Path.home()),
|
|
|
|
|
|
"Python Files (*.py);;All Files (*)"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if file_path:
|
|
|
|
|
|
self.config_file_path = file_path
|
|
|
|
|
|
self.save_user_settings()
|
|
|
|
|
|
self.update_path_display()
|
|
|
|
|
|
self.load_config()
|
|
|
|
|
|
|
|
|
|
|
|
def init_category_tabs(self):
|
2025-12-12 09:49:12 +00:00
|
|
|
|
"""初始化分类标签页"""
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 清空现有的标签页
|
|
|
|
|
|
self.tab_widget.clear()
|
2025-12-03 07:42:43 +00:00
|
|
|
|
self.category_widgets.clear()
|
2025-12-31 09:23:12 +00:00
|
|
|
|
|
|
|
|
|
|
# 获取所有分组
|
2025-12-03 07:42:43 +00:00
|
|
|
|
categories = list(self.rules.get("categories", {}).keys())
|
|
|
|
|
|
if not categories:
|
|
|
|
|
|
categories = ["未分类"]
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 统计每个分组中未隐藏的配置项数量
|
|
|
|
|
|
category_counts = {}
|
|
|
|
|
|
for category in categories:
|
|
|
|
|
|
# 获取该分组中所有配置项
|
|
|
|
|
|
category_fields = self.rules.get("categories", {}).get(category, [])
|
|
|
|
|
|
# 统计未隐藏的配置项数量
|
|
|
|
|
|
visible_count = 0
|
|
|
|
|
|
for field_name in category_fields:
|
|
|
|
|
|
if field_name in self.config_fields: # config_fields只包含未隐藏的配置项
|
|
|
|
|
|
visible_count += 1
|
|
|
|
|
|
category_counts[category] = visible_count
|
|
|
|
|
|
|
|
|
|
|
|
# 检查是否需要隐藏"未分类"分组
|
|
|
|
|
|
hide_uncategorized = False
|
|
|
|
|
|
if "未分类" in categories:
|
|
|
|
|
|
unclassified_count = category_counts.get("未分类", 0)
|
|
|
|
|
|
# 如果有其他分组存在
|
|
|
|
|
|
other_categories = [c for c in categories if c != "未分类"]
|
|
|
|
|
|
has_other_categories = len(other_categories) > 0
|
|
|
|
|
|
|
|
|
|
|
|
# 如果"未分类"分组没有未隐藏的配置项,且存在其他分组,则隐藏
|
|
|
|
|
|
if unclassified_count == 0 and has_other_categories:
|
|
|
|
|
|
hide_uncategorized = True
|
|
|
|
|
|
|
|
|
|
|
|
# 创建标签页
|
2025-12-03 07:42:43 +00:00
|
|
|
|
for category in categories:
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 如果是"未分类"分组且需要隐藏,则跳过
|
|
|
|
|
|
if category == "未分类" and hide_uncategorized:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
self.create_category_tab(category)
|
|
|
|
|
|
|
|
|
|
|
|
def create_category_tab(self, category):
|
2025-12-12 09:49:12 +00:00
|
|
|
|
"""创建分类标签页"""
|
2025-12-03 07:42:43 +00:00
|
|
|
|
tab = QWidget()
|
|
|
|
|
|
layout = QVBoxLayout(tab)
|
|
|
|
|
|
layout.setContentsMargins(4, 4, 4, 4)
|
|
|
|
|
|
|
|
|
|
|
|
scroll = QScrollArea()
|
|
|
|
|
|
scroll_widget = QWidget()
|
|
|
|
|
|
scroll_layout = QVBoxLayout(scroll_widget)
|
|
|
|
|
|
scroll_layout.setContentsMargins(8, 8, 8, 8)
|
|
|
|
|
|
scroll_layout.setSpacing(6)
|
|
|
|
|
|
|
|
|
|
|
|
self.category_widgets[category] = scroll_layout
|
|
|
|
|
|
|
|
|
|
|
|
scroll.setWidget(scroll_widget)
|
|
|
|
|
|
scroll.setWidgetResizable(True)
|
|
|
|
|
|
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
|
|
|
|
|
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
|
|
|
|
|
layout.addWidget(scroll)
|
|
|
|
|
|
|
|
|
|
|
|
self.tab_widget.addTab(tab, category)
|
|
|
|
|
|
|
|
|
|
|
|
def load_rules(self):
|
2025-12-31 09:23:12 +00:00
|
|
|
|
"""加载规则文件,如果已有规则文件则保留,否则创建默认"""
|
2025-12-03 07:42:43 +00:00
|
|
|
|
try:
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 先备份现有规则文件
|
|
|
|
|
|
self.backup_existing_rules()
|
|
|
|
|
|
|
|
|
|
|
|
# 检查是否已有规则文件
|
2025-12-03 07:42:43 +00:00
|
|
|
|
if os.path.exists(self.rules_file):
|
|
|
|
|
|
with open(self.rules_file, 'r', encoding='utf-8') as f:
|
2025-12-31 09:23:12 +00:00
|
|
|
|
existing_rules = json.load(f)
|
|
|
|
|
|
|
|
|
|
|
|
# 检查规则文件是否为空或格式错误
|
|
|
|
|
|
if not existing_rules or not isinstance(existing_rules, dict):
|
|
|
|
|
|
raise ValueError("规则文件为空或格式错误")
|
|
|
|
|
|
|
|
|
|
|
|
# 从打包资源加载默认规则(用于合并新字段)
|
|
|
|
|
|
default_rules = self.get_default_rules()
|
|
|
|
|
|
|
|
|
|
|
|
# 智能合并:保留现有规则,只添加新字段
|
|
|
|
|
|
self.rules = self.merge_rules(existing_rules, default_rules)
|
|
|
|
|
|
|
|
|
|
|
|
# 保存合并后的规则(包含新字段)
|
|
|
|
|
|
self.save_rules()
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
else:
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 如果没有规则文件,使用默认规则
|
|
|
|
|
|
self.rules = self.get_default_rules()
|
|
|
|
|
|
# 首次运行,创建规则文件
|
2025-12-03 07:42:43 +00:00
|
|
|
|
try:
|
|
|
|
|
|
self.save_rules()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"创建规则文件失败: {e}")
|
2025-12-31 09:23:12 +00:00
|
|
|
|
|
|
|
|
|
|
except (json.JSONDecodeError, ValueError, KeyError) as e:
|
|
|
|
|
|
# 如果规则文件损坏,备份后使用默认规则
|
|
|
|
|
|
print(f"加载规则文件失败: {e},将使用默认规则")
|
|
|
|
|
|
self.backup_corrupted_rules()
|
|
|
|
|
|
self.rules = self.get_default_rules()
|
|
|
|
|
|
self.save_rules()
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"加载规则文件失败: {e}")
|
2025-12-31 09:23:12 +00:00
|
|
|
|
self.rules = self.get_default_rules()
|
|
|
|
|
|
|
|
|
|
|
|
# 确保规则中有必要的字段
|
|
|
|
|
|
self.ensure_rule_structure()
|
|
|
|
|
|
|
|
|
|
|
|
def get_default_rules(self):
|
|
|
|
|
|
"""获取默认规则"""
|
|
|
|
|
|
return {
|
|
|
|
|
|
"categories": {"未分类": []},
|
|
|
|
|
|
"display_names": {},
|
|
|
|
|
|
"tooltips": {},
|
|
|
|
|
|
"field_types": {},
|
|
|
|
|
|
"field_decimals": {},
|
|
|
|
|
|
"hidden": [],
|
|
|
|
|
|
"validations": {},
|
|
|
|
|
|
"field_order": {},
|
|
|
|
|
|
"last_saved_values": {} # 新增:上次保存的值
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def merge_rules(self, existing_rules, default_rules):
|
|
|
|
|
|
"""智能合并规则:保留现有规则,只添加默认规则中的新字段"""
|
|
|
|
|
|
merged_rules = existing_rules.copy()
|
|
|
|
|
|
|
|
|
|
|
|
# 确保所有必需的字段都存在
|
|
|
|
|
|
for key in default_rules:
|
|
|
|
|
|
if key not in merged_rules:
|
|
|
|
|
|
merged_rules[key] = default_rules[key]
|
|
|
|
|
|
|
|
|
|
|
|
# 确保"未分类"分组始终存在
|
|
|
|
|
|
if "categories" not in merged_rules:
|
|
|
|
|
|
merged_rules["categories"] = {}
|
|
|
|
|
|
if "未分类" not in merged_rules["categories"]:
|
|
|
|
|
|
merged_rules["categories"]["未分类"] = []
|
|
|
|
|
|
|
|
|
|
|
|
# 确保其他必要字段存在
|
|
|
|
|
|
for field in ["display_names", "tooltips", "field_types", "field_decimals",
|
|
|
|
|
|
"hidden", "validations", "field_order", "last_saved_values"]: # 新增last_saved_values
|
|
|
|
|
|
if field not in merged_rules:
|
|
|
|
|
|
if field == "hidden":
|
|
|
|
|
|
merged_rules[field] = []
|
|
|
|
|
|
else:
|
|
|
|
|
|
merged_rules[field] = {}
|
|
|
|
|
|
|
|
|
|
|
|
return merged_rules
|
|
|
|
|
|
|
|
|
|
|
|
def ensure_rule_structure(self):
|
|
|
|
|
|
"""确保规则结构完整"""
|
|
|
|
|
|
# 确保规则中有"未分类"分组
|
|
|
|
|
|
if "categories" not in self.rules:
|
|
|
|
|
|
self.rules["categories"] = {}
|
|
|
|
|
|
if "未分类" not in self.rules["categories"]:
|
|
|
|
|
|
self.rules["categories"]["未分类"] = []
|
|
|
|
|
|
|
|
|
|
|
|
# 确保规则中有field_decimals字段
|
|
|
|
|
|
if "field_decimals" not in self.rules:
|
|
|
|
|
|
self.rules["field_decimals"] = {}
|
|
|
|
|
|
|
|
|
|
|
|
# 确保规则中有field_order字段
|
|
|
|
|
|
if "field_order" not in self.rules:
|
|
|
|
|
|
self.rules["field_order"] = {}
|
|
|
|
|
|
|
|
|
|
|
|
# 确保规则中有last_saved_values字段
|
|
|
|
|
|
if "last_saved_values" not in self.rules:
|
|
|
|
|
|
self.rules["last_saved_values"] = {}
|
|
|
|
|
|
|
|
|
|
|
|
def backup_existing_rules(self):
|
|
|
|
|
|
"""备份现有规则"""
|
|
|
|
|
|
if os.path.exists(self.rules_file):
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 创建备份目录
|
|
|
|
|
|
backup_dir = os.path.join(self.program_dir, "backups")
|
|
|
|
|
|
os.makedirs(backup_dir, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
|
|
# 生成带时间戳的备份文件名
|
|
|
|
|
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
|
|
|
backup_file = os.path.join(backup_dir, f"config_editor_rules_{timestamp}.json")
|
|
|
|
|
|
|
|
|
|
|
|
# 备份文件
|
|
|
|
|
|
shutil.copy2(self.rules_file, backup_file)
|
|
|
|
|
|
print(f"已备份规则文件到: {backup_file}")
|
|
|
|
|
|
|
|
|
|
|
|
# 清理旧的备份文件(保留最近5个)
|
|
|
|
|
|
self.cleanup_old_backups(backup_dir)
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"备份规则文件失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
def cleanup_old_backups(self, backup_dir):
|
|
|
|
|
|
"""清理旧的备份文件(保留最近5个)"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 获取所有备份文件
|
|
|
|
|
|
backup_files = []
|
|
|
|
|
|
for filename in os.listdir(backup_dir):
|
|
|
|
|
|
if filename.startswith("config_editor_rules_") and filename.endswith(".json"):
|
|
|
|
|
|
filepath = os.path.join(backup_dir, filename)
|
|
|
|
|
|
backup_files.append((filepath, os.path.getmtime(filepath)))
|
|
|
|
|
|
|
|
|
|
|
|
# 按修改时间排序(从旧到新)
|
|
|
|
|
|
backup_files.sort(key=lambda x: x[1])
|
|
|
|
|
|
|
|
|
|
|
|
# 删除旧的备份文件,保留最近5个
|
|
|
|
|
|
if len(backup_files) > 5:
|
|
|
|
|
|
files_to_delete = backup_files[:-5] # 删除除最近5个外的所有文件
|
|
|
|
|
|
for filepath, _ in files_to_delete:
|
|
|
|
|
|
os.remove(filepath)
|
|
|
|
|
|
print(f"已删除旧备份文件: {filepath}")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"清理旧备份文件失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
def backup_corrupted_rules(self):
|
|
|
|
|
|
"""备份损坏的规则文件"""
|
|
|
|
|
|
if os.path.exists(self.rules_file):
|
|
|
|
|
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
|
|
|
backup_file = f"{self.rules_file}.corrupted_{timestamp}.bak"
|
|
|
|
|
|
try:
|
|
|
|
|
|
shutil.copy2(self.rules_file, backup_file)
|
|
|
|
|
|
print(f"已备份损坏的规则文件到: {backup_file}")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"备份损坏规则文件失败: {e}")
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
def save_rules(self):
|
2025-12-12 09:49:12 +00:00
|
|
|
|
"""保存规则文件"""
|
2025-12-03 07:42:43 +00:00
|
|
|
|
try:
|
|
|
|
|
|
with open(self.rules_file, 'w', encoding='utf-8') as f:
|
|
|
|
|
|
json.dump(self.rules, f, indent=2, ensure_ascii=False)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"保存规则文件失败: {e}")
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# === 新增:在主界面直接导入导出规则文件 ===
|
|
|
|
|
|
def import_rules_file(self):
|
|
|
|
|
|
"""在主界面直接导入规则文件"""
|
|
|
|
|
|
# 检查当前是否有未保存的规则更改
|
|
|
|
|
|
if hasattr(self, 'rules_modified') and self.rules_modified:
|
|
|
|
|
|
reply = QMessageBox.question(
|
|
|
|
|
|
self, "确认导入",
|
|
|
|
|
|
"当前有未保存的规则更改,导入将覆盖这些更改。\n是否继续?",
|
|
|
|
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
|
|
|
|
QMessageBox.StandardButton.No
|
|
|
|
|
|
)
|
|
|
|
|
|
if reply != QMessageBox.StandardButton.Yes:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 选择要导入的规则文件
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
# 确保规则结构完整
|
|
|
|
|
|
self.ensure_rule_structure()
|
|
|
|
|
|
|
|
|
|
|
|
# 保存到规则文件
|
|
|
|
|
|
self.save_rules()
|
|
|
|
|
|
|
|
|
|
|
|
# 重新加载配置(这会应用新规则)
|
|
|
|
|
|
self.load_config()
|
|
|
|
|
|
|
|
|
|
|
|
QMessageBox.information(
|
|
|
|
|
|
self, "导入成功",
|
|
|
|
|
|
f"规则文件导入成功!\n\n"
|
|
|
|
|
|
f"已导入 {len(self.rules.get('display_names', {}))} 个显示名称设置\n"
|
|
|
|
|
|
f"已导入 {len(self.rules.get('categories', {}))} 个分组设置\n"
|
|
|
|
|
|
f"配置文件已重新加载以应用新规则。"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
|
QMessageBox.warning(self, "导入失败", "规则文件格式不正确,不是有效的JSON文件")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
QMessageBox.critical(self, "导入失败", f"导入规则文件时发生错误:{str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
def export_rules_file(self):
|
|
|
|
|
|
"""在主界面直接导出规则文件"""
|
|
|
|
|
|
# 首先保存当前规则到文件(确保最新)
|
|
|
|
|
|
self.save_rules()
|
|
|
|
|
|
|
|
|
|
|
|
# 选择导出位置和文件名
|
|
|
|
|
|
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:
|
|
|
|
|
|
# 复制规则文件到指定位置
|
|
|
|
|
|
shutil.copy2(self.rules_file, file_path)
|
|
|
|
|
|
|
|
|
|
|
|
QMessageBox.information(
|
|
|
|
|
|
self, "导出成功",
|
|
|
|
|
|
f"规则文件已成功导出到:\n{file_path}\n\n"
|
|
|
|
|
|
f"导出时间:{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
|
|
|
|
|
|
f"规则文件:{os.path.basename(self.rules_file)}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
QMessageBox.critical(self, "导出失败", f"导出规则文件时发生错误:{str(e)}")
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
def categorize_field(self, field_name):
|
2025-12-12 09:49:12 +00:00
|
|
|
|
"""为字段分类,如果未分配分组则归到'未分类'"""
|
2025-12-03 07:42:43 +00:00
|
|
|
|
for category, fields in self.rules.get("categories", {}).items():
|
|
|
|
|
|
if field_name in fields:
|
|
|
|
|
|
return category
|
2025-12-12 09:49:12 +00:00
|
|
|
|
return "未分类" # 默认归到"未分类"
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
def get_display_name(self, field_name):
|
2025-12-12 09:49:12 +00:00
|
|
|
|
"""获取字段的显示名称"""
|
2025-12-03 07:42:43 +00:00
|
|
|
|
return self.rules.get("display_names", {}).get(field_name, field_name)
|
|
|
|
|
|
|
|
|
|
|
|
def get_tooltip(self, field_name):
|
2025-12-12 09:49:12 +00:00
|
|
|
|
"""获取字段的提示信息"""
|
2025-12-03 07:42:43 +00:00
|
|
|
|
return self.rules.get("tooltips", {}).get(field_name, f"配置项: {field_name}")
|
|
|
|
|
|
|
|
|
|
|
|
def get_field_type(self, field_name, value):
|
2025-12-12 09:49:12 +00:00
|
|
|
|
"""获取字段类型"""
|
2025-12-03 07:42:43 +00:00
|
|
|
|
if field_name in self.rules.get("field_types", {}):
|
|
|
|
|
|
return self.rules["field_types"][field_name]
|
|
|
|
|
|
|
|
|
|
|
|
if isinstance(value, bool):
|
|
|
|
|
|
return "bool"
|
|
|
|
|
|
elif isinstance(value, int):
|
|
|
|
|
|
return "int"
|
|
|
|
|
|
elif isinstance(value, float):
|
|
|
|
|
|
return "float"
|
|
|
|
|
|
elif isinstance(value, (list, dict)):
|
|
|
|
|
|
return "json"
|
|
|
|
|
|
else:
|
|
|
|
|
|
return "str"
|
|
|
|
|
|
|
|
|
|
|
|
def get_field_decimals(self, field_name):
|
|
|
|
|
|
"""获取字段的小数位数"""
|
|
|
|
|
|
return self.rules.get("field_decimals", {}).get(field_name, 6) # 默认6位
|
|
|
|
|
|
|
|
|
|
|
|
def get_validation(self, field_name):
|
|
|
|
|
|
"""获取配置项的校验规则"""
|
|
|
|
|
|
return self.rules.get("validations", {}).get(field_name, {})
|
|
|
|
|
|
|
|
|
|
|
|
def is_hidden(self, field_name):
|
|
|
|
|
|
"""检查配置项是否被标记为隐藏"""
|
|
|
|
|
|
return field_name in self.rules.get("hidden", [])
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
def get_last_saved_value(self, field_name, current_value):
|
|
|
|
|
|
"""获取配置项的上次保存值,如果没有则使用当前值"""
|
|
|
|
|
|
last_saved_values = self.rules.get("last_saved_values", {})
|
|
|
|
|
|
if field_name in last_saved_values:
|
|
|
|
|
|
return last_saved_values[field_name]
|
|
|
|
|
|
return None # 第一次没有上次保存值,返回None
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
def parse_config_file(self):
|
|
|
|
|
|
"""解析配置文件,精确匹配每个配置项的位置和注释"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(self.config_file_path, 'r', encoding='utf-8') as f:
|
|
|
|
|
|
content = f.read()
|
|
|
|
|
|
lines = content.split('\n')
|
|
|
|
|
|
|
|
|
|
|
|
tree = ast.parse(content)
|
|
|
|
|
|
config_data = {}
|
|
|
|
|
|
all_config_fields = {}
|
|
|
|
|
|
|
|
|
|
|
|
# 第一遍:识别所有配置项及其位置
|
|
|
|
|
|
for node in ast.walk(tree):
|
|
|
|
|
|
if isinstance(node, ast.Assign):
|
|
|
|
|
|
for target in node.targets:
|
|
|
|
|
|
if isinstance(target, ast.Name) and target.id.isupper():
|
|
|
|
|
|
var_name = target.id
|
|
|
|
|
|
|
|
|
|
|
|
# 获取配置项在文件中的位置
|
|
|
|
|
|
assignment_line = node.lineno - 1 # ast行号从1开始,我们使用0基索引
|
|
|
|
|
|
|
|
|
|
|
|
# 向上查找配置项上面的注释行
|
|
|
|
|
|
comment_lines = []
|
|
|
|
|
|
current_line = assignment_line - 1
|
|
|
|
|
|
while current_line >= 0:
|
|
|
|
|
|
line = lines[current_line].strip()
|
|
|
|
|
|
# 如果是注释行或空行,则包含在注释中
|
|
|
|
|
|
if line.startswith('#') or line == '':
|
|
|
|
|
|
comment_lines.insert(0, lines[current_line])
|
|
|
|
|
|
current_line -= 1
|
|
|
|
|
|
# 如果是多行注释的开始
|
|
|
|
|
|
elif line.startswith("'''") or line.startswith('"""'):
|
|
|
|
|
|
# 找到多行注释的起始位置
|
|
|
|
|
|
comment_start = current_line
|
|
|
|
|
|
while comment_start >= 0 and not (lines[comment_start].strip().endswith("'''") or lines[comment_start].strip().endswith('"""')):
|
|
|
|
|
|
comment_start -= 1
|
|
|
|
|
|
if comment_start >= 0:
|
|
|
|
|
|
for i in range(comment_start, current_line + 1):
|
|
|
|
|
|
comment_lines.insert(0, lines[i])
|
|
|
|
|
|
current_line = comment_start - 1
|
|
|
|
|
|
else:
|
|
|
|
|
|
break
|
|
|
|
|
|
else:
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
# 完整的配置项块(包括注释和赋值行)
|
|
|
|
|
|
full_block = comment_lines + [lines[assignment_line]]
|
|
|
|
|
|
|
|
|
|
|
|
# 如果赋值行有后续行(如多行字符串),添加它们
|
|
|
|
|
|
end_line = getattr(node, 'end_lineno', node.lineno) - 1
|
|
|
|
|
|
if end_line > assignment_line:
|
|
|
|
|
|
for i in range(assignment_line + 1, end_line + 1):
|
|
|
|
|
|
full_block.append(lines[i])
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 获取配置项的值
|
|
|
|
|
|
var_value = eval(compile(ast.Expression(node.value), '<string>', 'eval'))
|
|
|
|
|
|
config_data[var_name] = var_value
|
|
|
|
|
|
|
|
|
|
|
|
# 获取字段类型和小数位数
|
|
|
|
|
|
field_type = self.get_field_type(var_name, var_value)
|
|
|
|
|
|
decimals = self.get_field_decimals(var_name) if field_type == "float" else None
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 获取上次保存的值
|
|
|
|
|
|
last_saved_value = self.get_last_saved_value(var_name, var_value)
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
all_config_fields[var_name] = ConfigField(
|
|
|
|
|
|
name=var_name,
|
|
|
|
|
|
value=var_value,
|
2025-12-12 09:49:12 +00:00
|
|
|
|
category=self.categorize_field(var_name), # 使用分类方法
|
2025-12-03 07:42:43 +00:00
|
|
|
|
display_name=self.get_display_name(var_name),
|
|
|
|
|
|
field_type=field_type,
|
|
|
|
|
|
decimals=decimals,
|
|
|
|
|
|
tooltip=self.get_tooltip(var_name),
|
|
|
|
|
|
hidden=self.is_hidden(var_name),
|
|
|
|
|
|
line_number=assignment_line - len(comment_lines),
|
|
|
|
|
|
original_lines=full_block,
|
2025-12-31 09:23:12 +00:00
|
|
|
|
validation=self.get_validation(var_name),
|
|
|
|
|
|
last_saved_value=last_saved_value # 新增:设置上次保存的值
|
2025-12-03 07:42:43 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
except:
|
|
|
|
|
|
# 如果无法解析值,使用字符串表示
|
|
|
|
|
|
try:
|
|
|
|
|
|
value_str = ast.get_source_segment(content, node.value)
|
|
|
|
|
|
config_data[var_name] = value_str
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 获取上次保存的值
|
|
|
|
|
|
last_saved_value = self.get_last_saved_value(var_name, value_str)
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
all_config_fields[var_name] = ConfigField(
|
|
|
|
|
|
name=var_name,
|
|
|
|
|
|
value=value_str,
|
2025-12-12 09:49:12 +00:00
|
|
|
|
category=self.categorize_field(var_name), # 使用分类方法
|
2025-12-03 07:42:43 +00:00
|
|
|
|
display_name=self.get_display_name(var_name),
|
|
|
|
|
|
field_type="str",
|
|
|
|
|
|
decimals=None,
|
|
|
|
|
|
tooltip=self.get_tooltip(var_name),
|
|
|
|
|
|
hidden=self.is_hidden(var_name),
|
|
|
|
|
|
line_number=assignment_line - len(comment_lines),
|
|
|
|
|
|
original_lines=full_block,
|
2025-12-31 09:23:12 +00:00
|
|
|
|
validation=self.get_validation(var_name),
|
|
|
|
|
|
last_saved_value=last_saved_value # 新增:设置上次保存的值
|
2025-12-03 07:42:43 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
except:
|
|
|
|
|
|
config_data[var_name] = "无法解析的值"
|
2025-12-31 09:23:12 +00:00
|
|
|
|
|
|
|
|
|
|
# 获取上次保存的值
|
|
|
|
|
|
last_saved_value = self.get_last_saved_value(var_name, "无法解析的值")
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
all_config_fields[var_name] = ConfigField(
|
|
|
|
|
|
name=var_name,
|
|
|
|
|
|
value="无法解析的值",
|
2025-12-12 09:49:12 +00:00
|
|
|
|
category=self.categorize_field(var_name), # 使用分类方法
|
2025-12-03 07:42:43 +00:00
|
|
|
|
display_name=self.get_display_name(var_name),
|
|
|
|
|
|
field_type="str",
|
|
|
|
|
|
decimals=None,
|
|
|
|
|
|
tooltip=self.get_tooltip(var_name),
|
|
|
|
|
|
hidden=self.is_hidden(var_name),
|
|
|
|
|
|
line_number=assignment_line - len(comment_lines),
|
|
|
|
|
|
original_lines=full_block,
|
2025-12-31 09:23:12 +00:00
|
|
|
|
validation=self.get_validation(var_name),
|
|
|
|
|
|
last_saved_value=last_saved_value # 新增:设置上次保存的值
|
2025-12-03 07:42:43 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return config_data, all_config_fields, content
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
QMessageBox.critical(self, "错误", f"解析配置文件失败:{str(e)}")
|
|
|
|
|
|
return {}, {}, ""
|
|
|
|
|
|
|
|
|
|
|
|
def create_field_widget(self, config_field):
|
2025-12-12 09:49:12 +00:00
|
|
|
|
"""创建字段编辑控件"""
|
2025-12-03 07:42:43 +00:00
|
|
|
|
field_type = config_field.get_actual_field_type()
|
|
|
|
|
|
|
|
|
|
|
|
if field_type == "bool":
|
|
|
|
|
|
widget = QCheckBox()
|
|
|
|
|
|
widget.setChecked(bool(config_field.value))
|
|
|
|
|
|
# 连接状态改变信号
|
|
|
|
|
|
widget.stateChanged.connect(lambda state, fn=config_field.name: self.on_field_modified(fn))
|
|
|
|
|
|
elif field_type == "int":
|
|
|
|
|
|
widget = NoWheelSpinBox() # 使用禁用了滚轮的SpinBox
|
|
|
|
|
|
widget.setRange(-1000000, 1000000)
|
|
|
|
|
|
if config_field.value is not None:
|
|
|
|
|
|
widget.setValue(int(config_field.value))
|
|
|
|
|
|
# 连接值改变信号
|
|
|
|
|
|
widget.valueChanged.connect(lambda value, fn=config_field.name: self.on_field_modified(fn))
|
|
|
|
|
|
elif field_type == "float":
|
|
|
|
|
|
widget = NoWheelDoubleSpinBox() # 使用禁用了滚轮的DoubleSpinBox
|
|
|
|
|
|
widget.setRange(-1000000.0, 1000000.0)
|
|
|
|
|
|
|
|
|
|
|
|
# 根据规则设置小数位数
|
|
|
|
|
|
decimals = config_field.decimals or 6 # 默认6位
|
|
|
|
|
|
widget.setDecimals(decimals)
|
|
|
|
|
|
|
|
|
|
|
|
if config_field.value is not None:
|
|
|
|
|
|
widget.setValue(float(config_field.value))
|
|
|
|
|
|
# 连接值改变信号
|
|
|
|
|
|
widget.valueChanged.connect(lambda value, fn=config_field.name: self.on_field_modified(fn))
|
|
|
|
|
|
elif field_type == "json":
|
|
|
|
|
|
widget = QTextEdit()
|
|
|
|
|
|
widget.setMaximumHeight(120)
|
|
|
|
|
|
if config_field.value is not None:
|
|
|
|
|
|
# 对于字典,使用漂亮的格式化显示
|
|
|
|
|
|
formatted_json = json.dumps(config_field.value, indent=4, ensure_ascii=False)
|
|
|
|
|
|
widget.setPlainText(formatted_json)
|
|
|
|
|
|
# 连接文本改变信号
|
|
|
|
|
|
widget.textChanged.connect(lambda fn=config_field.name: self.on_field_modified(fn))
|
|
|
|
|
|
else:
|
|
|
|
|
|
widget = QLineEdit()
|
|
|
|
|
|
if config_field.value is not None:
|
|
|
|
|
|
widget.setText(str(config_field.value))
|
|
|
|
|
|
# 连接文本改变信号
|
|
|
|
|
|
widget.textChanged.connect(lambda text, fn=config_field.name: self.on_field_modified(fn))
|
|
|
|
|
|
|
|
|
|
|
|
# 添加校验提示到tooltip
|
|
|
|
|
|
validation_tooltip = self.get_validation_tooltip(config_field)
|
|
|
|
|
|
full_tooltip = config_field.tooltip
|
|
|
|
|
|
if validation_tooltip:
|
|
|
|
|
|
full_tooltip += f"\n\n校验规则:\n{validation_tooltip}"
|
|
|
|
|
|
|
|
|
|
|
|
widget.setToolTip(full_tooltip)
|
|
|
|
|
|
widget.setMinimumWidth(300)
|
|
|
|
|
|
return widget
|
|
|
|
|
|
|
|
|
|
|
|
def get_validation_tooltip(self, config_field):
|
|
|
|
|
|
"""生成校验规则的提示信息"""
|
|
|
|
|
|
validation = config_field.validation
|
|
|
|
|
|
if not validation:
|
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
rules = []
|
|
|
|
|
|
if validation.get("required"):
|
|
|
|
|
|
rules.append("• 必填项")
|
|
|
|
|
|
if "min" in validation:
|
|
|
|
|
|
rules.append(f"• 最小值: {validation['min']}")
|
|
|
|
|
|
if "max" in validation:
|
|
|
|
|
|
rules.append(f"• 最大值: {validation['max']}")
|
|
|
|
|
|
if "regex" in validation:
|
|
|
|
|
|
rules.append(f"• 正则表达式: {validation['regex']}")
|
|
|
|
|
|
|
|
|
|
|
|
return "\n".join(rules)
|
|
|
|
|
|
|
|
|
|
|
|
def on_field_modified(self, field_name):
|
|
|
|
|
|
"""当字段值被修改时的处理"""
|
|
|
|
|
|
if field_name not in self.modified_fields:
|
|
|
|
|
|
self.modified_fields.add(field_name)
|
|
|
|
|
|
# 高亮显示修改的字段
|
|
|
|
|
|
self.highlight_modified_field(field_name)
|
|
|
|
|
|
|
|
|
|
|
|
# 更新状态栏显示修改数量
|
|
|
|
|
|
self.update_status_bar()
|
|
|
|
|
|
|
|
|
|
|
|
def highlight_modified_field(self, field_name):
|
|
|
|
|
|
"""高亮显示被修改的字段,但不覆盖子控件样式"""
|
|
|
|
|
|
if field_name in self.field_containers:
|
|
|
|
|
|
container = self.field_containers[field_name]
|
|
|
|
|
|
# 只修改背景色,使用更精确的选择器确保不影响子控件
|
|
|
|
|
|
container.setStyleSheet("""
|
2025-12-31 09:23:12 +00:00
|
|
|
|
QFrame#field_container_%s {
|
2025-12-03 07:42:43 +00:00
|
|
|
|
background-color: #fff9c4;
|
|
|
|
|
|
}
|
2025-12-31 09:23:12 +00:00
|
|
|
|
""" % field_name)
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
def clear_field_highlight(self, field_name):
|
|
|
|
|
|
"""清除字段的高亮显示,恢复原有样式"""
|
|
|
|
|
|
if field_name in self.field_containers:
|
|
|
|
|
|
container = self.field_containers[field_name]
|
|
|
|
|
|
# 恢复原始样式,不影响子控件
|
|
|
|
|
|
container.setStyleSheet("""
|
2025-12-31 09:23:12 +00:00
|
|
|
|
QFrame#field_container_%s {
|
|
|
|
|
|
border: 1px solid #e0e0e0;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
padding: 4px;
|
2025-12-03 07:42:43 +00:00
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
}
|
2025-12-31 09:23:12 +00:00
|
|
|
|
QFrame#field_container_%s:hover {
|
|
|
|
|
|
border: 1px solid #2196F3;
|
|
|
|
|
|
background-color: #f8fdff;
|
|
|
|
|
|
}
|
|
|
|
|
|
""" % (field_name, field_name))
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
def update_status_bar(self):
|
|
|
|
|
|
"""更新状态栏显示修改数量"""
|
|
|
|
|
|
if self.modified_fields:
|
|
|
|
|
|
self.statusBar().showMessage(f"已修改 {len(self.modified_fields)} 个配置项")
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.statusBar().showMessage("就绪")
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# === 新增:鼠标按下事件处理器 ===
|
|
|
|
|
|
def create_mouse_press_handler(self, field_name, label_widget):
|
|
|
|
|
|
"""创建鼠标按下事件处理器"""
|
|
|
|
|
|
def mouse_press_handler(event):
|
|
|
|
|
|
if event.button() == Qt.MouseButton.LeftButton:
|
|
|
|
|
|
self.dragging_field = field_name
|
|
|
|
|
|
self.drag_start_pos = event.pos()
|
|
|
|
|
|
|
|
|
|
|
|
# 更改光标为抓取手型
|
|
|
|
|
|
label_widget.setCursor(Qt.CursorShape.ClosedHandCursor)
|
|
|
|
|
|
|
|
|
|
|
|
# 高亮当前拖拽的字段
|
|
|
|
|
|
self.highlight_dragging_field(field_name, True)
|
|
|
|
|
|
|
|
|
|
|
|
# 开始拖拽操作
|
|
|
|
|
|
drag = QDrag(label_widget)
|
|
|
|
|
|
mime_data = QMimeData()
|
|
|
|
|
|
mime_data.setText(field_name)
|
|
|
|
|
|
drag.setMimeData(mime_data)
|
|
|
|
|
|
|
|
|
|
|
|
# 设置拖拽时的缩略图
|
|
|
|
|
|
pixmap = label_widget.grab()
|
|
|
|
|
|
drag.setPixmap(pixmap)
|
|
|
|
|
|
drag.setHotSpot(event.pos())
|
|
|
|
|
|
|
|
|
|
|
|
drag.exec(Qt.DropAction.MoveAction)
|
|
|
|
|
|
|
|
|
|
|
|
# 拖拽结束后恢复
|
|
|
|
|
|
label_widget.setCursor(Qt.CursorShape.OpenHandCursor)
|
|
|
|
|
|
self.highlight_dragging_field(field_name, False)
|
|
|
|
|
|
self.dragging_field = None
|
|
|
|
|
|
return mouse_press_handler
|
|
|
|
|
|
|
|
|
|
|
|
# === 新增:事件过滤器,用于处理拖拽进入、离开和放置 ===
|
|
|
|
|
|
def eventFilter(self, obj, event):
|
|
|
|
|
|
"""事件过滤器,用于处理拖拽相关事件"""
|
|
|
|
|
|
# 检查是否是字段容器
|
|
|
|
|
|
if isinstance(obj, QFrame) and obj.objectName().startswith("field_container_"):
|
|
|
|
|
|
if event.type() == QEvent.Type.DragEnter:
|
|
|
|
|
|
# 拖拽进入容器
|
|
|
|
|
|
self.handle_drag_enter_event(obj, event)
|
|
|
|
|
|
return True
|
|
|
|
|
|
elif event.type() == QEvent.Type.DragLeave:
|
|
|
|
|
|
# 拖拽离开容器
|
|
|
|
|
|
self.handle_drag_leave_event(obj)
|
|
|
|
|
|
return True
|
|
|
|
|
|
elif event.type() == QEvent.Type.Drop:
|
|
|
|
|
|
# 放置到容器
|
|
|
|
|
|
self.handle_drop_event(obj, event)
|
|
|
|
|
|
return True
|
|
|
|
|
|
elif event.type() == QEvent.Type.DragMove:
|
|
|
|
|
|
# 拖拽在容器上移动
|
|
|
|
|
|
self.handle_drag_move_event(obj, event)
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
return super().eventFilter(obj, event)
|
|
|
|
|
|
|
|
|
|
|
|
# === 新增:处理拖拽进入事件 ===
|
|
|
|
|
|
def handle_drag_enter_event(self, container, event):
|
|
|
|
|
|
"""处理拖拽进入事件"""
|
|
|
|
|
|
# 获取容器对应的字段名
|
|
|
|
|
|
field_name = container.objectName().replace("field_container_", "")
|
|
|
|
|
|
|
|
|
|
|
|
# 如果不是拖拽自身,则接受拖拽
|
|
|
|
|
|
if field_name != self.dragging_field:
|
|
|
|
|
|
event.acceptProposedAction()
|
|
|
|
|
|
self.drop_target_field = field_name
|
|
|
|
|
|
self.highlight_drop_target(field_name, True)
|
|
|
|
|
|
|
|
|
|
|
|
# === 新增:处理拖拽离开事件 ===
|
|
|
|
|
|
def handle_drag_leave_event(self, container):
|
|
|
|
|
|
"""处理拖拽离开事件"""
|
|
|
|
|
|
# 获取容器对应的字段名
|
|
|
|
|
|
field_name = container.objectName().replace("field_container_", "")
|
|
|
|
|
|
|
|
|
|
|
|
# 清除目标高亮
|
|
|
|
|
|
if field_name == self.drop_target_field:
|
|
|
|
|
|
self.highlight_drop_target(field_name, False)
|
|
|
|
|
|
self.drop_target_field = None
|
|
|
|
|
|
|
|
|
|
|
|
# === 新增:处理放置事件 ===
|
|
|
|
|
|
def handle_drop_event(self, container, event):
|
|
|
|
|
|
"""处理放置事件"""
|
|
|
|
|
|
# 获取源字段和目标字段
|
|
|
|
|
|
source_field = self.dragging_field
|
|
|
|
|
|
target_field = container.objectName().replace("field_container_", "")
|
|
|
|
|
|
|
|
|
|
|
|
if source_field and target_field and source_field != target_field:
|
|
|
|
|
|
# 获取字段的分组
|
|
|
|
|
|
source_config = self.all_config_fields.get(source_field)
|
|
|
|
|
|
target_config = self.all_config_fields.get(target_field)
|
|
|
|
|
|
|
|
|
|
|
|
if source_config and target_config and source_config.category == target_config.category:
|
|
|
|
|
|
# 在同一分组内移动字段
|
|
|
|
|
|
self.reorder_fields_in_category(source_field, target_field, source_config.category)
|
|
|
|
|
|
|
|
|
|
|
|
# 重新生成UI
|
|
|
|
|
|
show_hidden = getattr(self, 'show_hidden', False)
|
|
|
|
|
|
self.generate_dynamic_ui(show_hidden)
|
|
|
|
|
|
|
|
|
|
|
|
# 保存顺序到规则文件
|
|
|
|
|
|
self.save_field_order_to_rules()
|
|
|
|
|
|
|
|
|
|
|
|
# === 修复:不显示弹窗,只更新状态栏 ===
|
|
|
|
|
|
self.statusBar().showMessage(f"已将 '{source_config.display_name}' 移动到 '{target_config.display_name}' 的位置", 3000)
|
|
|
|
|
|
|
|
|
|
|
|
# 清除目标高亮
|
|
|
|
|
|
self.highlight_drop_target(target_field, False)
|
|
|
|
|
|
self.drop_target_field = None
|
|
|
|
|
|
event.acceptProposedAction()
|
|
|
|
|
|
|
|
|
|
|
|
# === 新增:处理拖拽移动事件 ===
|
|
|
|
|
|
def handle_drag_move_event(self, container, event):
|
|
|
|
|
|
"""处理拖拽移动事件"""
|
|
|
|
|
|
# 获取容器对应的字段名
|
|
|
|
|
|
field_name = container.objectName().replace("field_container_", "")
|
|
|
|
|
|
|
|
|
|
|
|
# 如果不是拖拽自身,则接受拖拽移动
|
|
|
|
|
|
if field_name != self.dragging_field:
|
|
|
|
|
|
event.acceptProposedAction()
|
|
|
|
|
|
|
|
|
|
|
|
# === 新增:高亮显示拖拽中的字段 ===
|
|
|
|
|
|
def highlight_dragging_field(self, field_name, is_dragging):
|
|
|
|
|
|
"""高亮显示拖拽中的字段"""
|
|
|
|
|
|
if field_name in self.field_containers:
|
|
|
|
|
|
container = self.field_containers[field_name]
|
|
|
|
|
|
if is_dragging:
|
|
|
|
|
|
container.setStyleSheet(f"""
|
|
|
|
|
|
QFrame#field_container_{field_name} {{
|
|
|
|
|
|
border: 2px dashed #2196F3;
|
|
|
|
|
|
background-color: #e3f2fd;
|
|
|
|
|
|
opacity: 0.7;
|
|
|
|
|
|
}}
|
|
|
|
|
|
""")
|
|
|
|
|
|
else:
|
|
|
|
|
|
container.setStyleSheet(f"""
|
|
|
|
|
|
QFrame#field_container_{field_name} {{
|
|
|
|
|
|
border: 1px solid #e0e0e0;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
padding: 4px;
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
}}
|
|
|
|
|
|
QFrame#field_container_{field_name}:hover {{
|
|
|
|
|
|
border: 1px solid #2196F3;
|
|
|
|
|
|
background-color: #f8fdff;
|
|
|
|
|
|
}}
|
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
|
|
# === 新增:高亮显示放置目标 ===
|
|
|
|
|
|
def highlight_drop_target(self, field_name, is_target):
|
|
|
|
|
|
"""高亮显示放置目标"""
|
|
|
|
|
|
if field_name in self.field_containers:
|
|
|
|
|
|
container = self.field_containers[field_name]
|
|
|
|
|
|
if is_target:
|
|
|
|
|
|
container.setStyleSheet(f"""
|
|
|
|
|
|
QFrame#field_container_{field_name} {{
|
|
|
|
|
|
border: 2px solid #4CAF50;
|
|
|
|
|
|
background-color: #e8f5e9;
|
|
|
|
|
|
}}
|
|
|
|
|
|
""")
|
|
|
|
|
|
else:
|
|
|
|
|
|
container.setStyleSheet(f"""
|
|
|
|
|
|
QFrame#field_container_{field_name} {{
|
|
|
|
|
|
border: 1px solid #e0e0e0;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
padding: 4px;
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
}}
|
|
|
|
|
|
QFrame#field_container_{field_name}:hover {{
|
|
|
|
|
|
border: 1px solid #2196F3;
|
|
|
|
|
|
background-color: #f8fdff;
|
|
|
|
|
|
}}
|
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
|
|
# === 新增:重新排序分组内的字段 ===
|
|
|
|
|
|
def reorder_fields_in_category(self, source_field, target_field, category):
|
|
|
|
|
|
"""重新排序分组内的字段"""
|
|
|
|
|
|
# 获取当前分组内的字段顺序
|
|
|
|
|
|
if category not in self.field_order:
|
|
|
|
|
|
# 如果还没有该分组的顺序记录,从配置字段中获取
|
|
|
|
|
|
fields_in_category = []
|
|
|
|
|
|
for field_name, config_field in self.all_config_fields.items():
|
|
|
|
|
|
if config_field.category == category:
|
|
|
|
|
|
fields_in_category.append(field_name)
|
|
|
|
|
|
self.field_order[category] = fields_in_category
|
|
|
|
|
|
|
|
|
|
|
|
# 重新排序
|
|
|
|
|
|
order_list = self.field_order[category]
|
|
|
|
|
|
|
|
|
|
|
|
# 移除源字段
|
|
|
|
|
|
if source_field in order_list:
|
|
|
|
|
|
order_list.remove(source_field)
|
|
|
|
|
|
|
|
|
|
|
|
# 找到目标字段的位置,在目标位置插入源字段
|
|
|
|
|
|
if target_field in order_list:
|
|
|
|
|
|
target_index = order_list.index(target_field)
|
|
|
|
|
|
order_list.insert(target_index, source_field)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 如果目标字段不在列表中(不应该发生),添加到末尾
|
|
|
|
|
|
order_list.append(source_field)
|
|
|
|
|
|
|
|
|
|
|
|
# === 新增:保存字段顺序到规则文件 ===
|
|
|
|
|
|
def save_field_order_to_rules(self):
|
|
|
|
|
|
"""保存字段顺序到规则文件"""
|
|
|
|
|
|
# 将字段顺序转换为规则格式
|
|
|
|
|
|
self.rules["field_order"] = self.field_order
|
|
|
|
|
|
|
|
|
|
|
|
# 保存规则文件
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(self.rules_file, 'w', encoding='utf-8') as f:
|
|
|
|
|
|
json.dump(self.rules, f, indent=2, ensure_ascii=False)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"保存字段顺序失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
# === 新增:初始化字段顺序 ===
|
|
|
|
|
|
def initialize_field_order(self):
|
|
|
|
|
|
"""初始化字段顺序"""
|
|
|
|
|
|
# 从规则中加载字段顺序
|
|
|
|
|
|
if "field_order" in self.rules:
|
|
|
|
|
|
self.field_order = self.rules["field_order"]
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.field_order = {}
|
|
|
|
|
|
|
|
|
|
|
|
# 确保所有字段都在顺序列表中
|
|
|
|
|
|
for field_name, config_field in self.all_config_fields.items():
|
|
|
|
|
|
category = config_field.category
|
|
|
|
|
|
if category not in self.field_order:
|
|
|
|
|
|
self.field_order[category] = []
|
|
|
|
|
|
|
|
|
|
|
|
if field_name not in self.field_order[category]:
|
|
|
|
|
|
self.field_order[category].append(field_name)
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
def create_field_editor(self, config_field):
|
2025-12-31 09:23:12 +00:00
|
|
|
|
"""创建字段编辑器,返回容器和控件(新增拖拽支持)"""
|
2025-12-03 07:42:43 +00:00
|
|
|
|
# 创建容器框架
|
|
|
|
|
|
container = QFrame()
|
2025-12-31 09:23:12 +00:00
|
|
|
|
container.setObjectName(f"field_container_{config_field.name}") # 设置唯一ID
|
2025-12-03 07:42:43 +00:00
|
|
|
|
container.setFrameShape(QFrame.Shape.StyledPanel)
|
2025-12-31 09:23:12 +00:00
|
|
|
|
|
|
|
|
|
|
# === 新增:设置容器可接受拖拽事件 ===
|
|
|
|
|
|
container.setAcceptDrops(True)
|
|
|
|
|
|
container.installEventFilter(self)
|
|
|
|
|
|
|
|
|
|
|
|
# 设置基础样式
|
2025-12-03 07:42:43 +00:00
|
|
|
|
container.setStyleSheet("""
|
2025-12-31 09:23:12 +00:00
|
|
|
|
QFrame#field_container_%s {
|
2025-12-03 07:42:43 +00:00
|
|
|
|
border: 1px solid #e0e0e0;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
padding: 4px;
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
}
|
2025-12-31 09:23:12 +00:00
|
|
|
|
QFrame#field_container_%s:hover {
|
|
|
|
|
|
border: 1px solid #2196F3;
|
|
|
|
|
|
background-color: #f8fdff;
|
|
|
|
|
|
}
|
|
|
|
|
|
""" % (config_field.name, config_field.name))
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
layout = QHBoxLayout(container)
|
|
|
|
|
|
layout.setContentsMargins(8, 4, 8, 4)
|
|
|
|
|
|
layout.setSpacing(8)
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 标签 - 设置为可拖拽的区域
|
2025-12-03 07:42:43 +00:00
|
|
|
|
label = QLabel(config_field.display_name)
|
|
|
|
|
|
label.setMinimumWidth(180)
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# === 新增:为标签添加拖拽支持 ===
|
|
|
|
|
|
label.setProperty("field_name", config_field.name) # 存储字段名
|
|
|
|
|
|
label.setProperty("category", config_field.category) # 存储分组名
|
|
|
|
|
|
label.mousePressEvent = self.create_mouse_press_handler(config_field.name, label)
|
|
|
|
|
|
label.setCursor(Qt.CursorShape.OpenHandCursor) # 设置手型光标
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
# 添加校验指示器(如果配置项有校验规则)
|
|
|
|
|
|
validation_indicator = ""
|
|
|
|
|
|
if config_field.validation:
|
|
|
|
|
|
if config_field.validation.get("required"):
|
|
|
|
|
|
validation_indicator = " *"
|
|
|
|
|
|
label.setStyleSheet("color: #d32f2f; font-weight: bold;")
|
|
|
|
|
|
else:
|
|
|
|
|
|
label.setStyleSheet("color: #1976d2;")
|
|
|
|
|
|
|
|
|
|
|
|
label.setText(f"{config_field.display_name}{validation_indicator}")
|
|
|
|
|
|
label.setToolTip(self.get_validation_tooltip(config_field))
|
|
|
|
|
|
|
|
|
|
|
|
# 如果配置项是隐藏的,添加视觉提示
|
|
|
|
|
|
if config_field.hidden:
|
|
|
|
|
|
label.setStyleSheet("color: #999999; font-style: italic;")
|
|
|
|
|
|
label.setText(f"{config_field.display_name} [隐藏]")
|
|
|
|
|
|
|
|
|
|
|
|
layout.addWidget(label)
|
|
|
|
|
|
|
|
|
|
|
|
# 控件
|
|
|
|
|
|
widget = self.create_field_widget(config_field)
|
|
|
|
|
|
|
|
|
|
|
|
# 如果配置项是隐藏的,禁用控件
|
|
|
|
|
|
if config_field.hidden:
|
|
|
|
|
|
widget.setEnabled(False)
|
|
|
|
|
|
widget.setStyleSheet("background-color: #f0f0f0; color: #999999;")
|
|
|
|
|
|
|
|
|
|
|
|
layout.addWidget(widget)
|
|
|
|
|
|
|
|
|
|
|
|
# 变量名显示
|
|
|
|
|
|
name_label = QLabel(f"({config_field.name})")
|
|
|
|
|
|
name_label.setStyleSheet("color: #666666; font-size: 10px; font-style: italic;")
|
|
|
|
|
|
name_label.setMinimumWidth(120)
|
|
|
|
|
|
name_label.setToolTip(f"配置变量名: {config_field.name}")
|
|
|
|
|
|
layout.addWidget(name_label)
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# === 新增:上次保存的值显示 ===
|
|
|
|
|
|
last_saved_label = QLabel()
|
|
|
|
|
|
last_saved_label.setMinimumWidth(120)
|
|
|
|
|
|
last_saved_label.setToolTip("上次保存的值")
|
|
|
|
|
|
|
|
|
|
|
|
# 格式化显示上次保存的值
|
|
|
|
|
|
if config_field.last_saved_value is not None:
|
|
|
|
|
|
# 检查是否是复杂类型
|
|
|
|
|
|
if isinstance(config_field.last_saved_value, (dict, list)):
|
|
|
|
|
|
# 对于复杂类型,使用JSON格式化
|
|
|
|
|
|
try:
|
|
|
|
|
|
formatted = json.dumps(config_field.last_saved_value, ensure_ascii=False)
|
|
|
|
|
|
# 如果太长,截断显示
|
|
|
|
|
|
if len(formatted) > 20:
|
|
|
|
|
|
formatted = formatted[:17] + "..."
|
|
|
|
|
|
last_saved_label.setText(f"上次: {formatted}")
|
|
|
|
|
|
except:
|
|
|
|
|
|
value_type = type(config_field.last_saved_value).__name__
|
|
|
|
|
|
last_saved_label.setText(f"上次: [{value_type}]")
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 对于简单类型,显示完整值
|
|
|
|
|
|
value_str = str(config_field.last_saved_value)
|
|
|
|
|
|
# 如果太长,截断显示
|
|
|
|
|
|
if len(value_str) > 20:
|
|
|
|
|
|
value_str = value_str[:17] + "..."
|
|
|
|
|
|
last_saved_label.setText(f"上次: {value_str}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
last_saved_label.setText("上次: 无")
|
|
|
|
|
|
|
|
|
|
|
|
last_saved_label.setStyleSheet("color: #888888; font-size: 10px; font-style: italic; background-color: #f8f8f8; padding: 2px; border-radius: 2px;")
|
|
|
|
|
|
layout.addWidget(last_saved_label)
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
layout.addStretch()
|
|
|
|
|
|
|
|
|
|
|
|
# 存储容器引用
|
|
|
|
|
|
self.field_containers[config_field.name] = container
|
|
|
|
|
|
|
|
|
|
|
|
return container, widget
|
|
|
|
|
|
|
|
|
|
|
|
def generate_dynamic_ui(self, show_hidden=False):
|
2025-12-31 09:23:12 +00:00
|
|
|
|
"""生成动态UI,可选择是否显示隐藏的配置项,支持自定义顺序"""
|
2025-12-03 07:42:43 +00:00
|
|
|
|
self.dynamic_widgets.clear()
|
2025-12-31 09:23:12 +00:00
|
|
|
|
self.modified_fields.clear()
|
|
|
|
|
|
self.field_containers.clear()
|
|
|
|
|
|
|
|
|
|
|
|
# 重新初始化标签页(考虑是否隐藏"未分类"分组)
|
|
|
|
|
|
self.init_category_tabs()
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
for category, layout in self.category_widgets.items():
|
|
|
|
|
|
while layout.count():
|
|
|
|
|
|
child = layout.takeAt(0)
|
|
|
|
|
|
if child.widget():
|
|
|
|
|
|
child.widget().deleteLater()
|
|
|
|
|
|
|
|
|
|
|
|
# 根据是否显示隐藏项,选择使用哪个配置项集合
|
|
|
|
|
|
config_fields_to_use = self.all_config_fields if show_hidden else self.config_fields
|
|
|
|
|
|
|
|
|
|
|
|
categorized_fields = {}
|
|
|
|
|
|
for field_name, config_field in config_fields_to_use.items():
|
|
|
|
|
|
category = config_field.category
|
|
|
|
|
|
if category not in categorized_fields:
|
|
|
|
|
|
categorized_fields[category] = []
|
|
|
|
|
|
categorized_fields[category].append(config_field)
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 初始化字段顺序
|
|
|
|
|
|
self.initialize_field_order()
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
for category, fields in categorized_fields.items():
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 跳过不在当前标签页中的分组(如被隐藏的"未分类"分组)
|
2025-12-03 07:42:43 +00:00
|
|
|
|
if category not in self.category_widgets:
|
2025-12-31 09:23:12 +00:00
|
|
|
|
continue
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
layout = self.category_widgets[category]
|
2025-12-31 09:23:12 +00:00
|
|
|
|
|
|
|
|
|
|
# === 修改:按照保存的顺序排序 ===
|
|
|
|
|
|
if category in self.field_order and self.field_order[category]:
|
|
|
|
|
|
# 创建从字段名到配置项的映射
|
|
|
|
|
|
field_dict = {f.name: f for f in fields}
|
|
|
|
|
|
|
|
|
|
|
|
# 按照保存的顺序排序
|
|
|
|
|
|
ordered_fields = []
|
|
|
|
|
|
for field_name in self.field_order[category]:
|
|
|
|
|
|
if field_name in field_dict:
|
|
|
|
|
|
ordered_fields.append(field_dict[field_name])
|
|
|
|
|
|
del field_dict[field_name]
|
|
|
|
|
|
|
|
|
|
|
|
# 添加不在顺序列表中的字段(新字段)
|
|
|
|
|
|
remaining_fields = list(field_dict.values())
|
|
|
|
|
|
remaining_fields.sort(key=lambda x: x.name) # 按字母顺序排序剩余字段
|
|
|
|
|
|
ordered_fields.extend(remaining_fields)
|
|
|
|
|
|
|
|
|
|
|
|
fields = ordered_fields
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 如果没有保存的顺序,按字母顺序排序
|
|
|
|
|
|
fields.sort(key=lambda x: x.name)
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
# 使用GroupBox,更清晰的分组
|
2025-12-12 09:49:12 +00:00
|
|
|
|
group_box = QGroupBox(f"{category}")
|
2025-12-03 07:42:43 +00:00
|
|
|
|
group_layout = QVBoxLayout(group_box)
|
|
|
|
|
|
group_layout.setSpacing(6)
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# === 修改:去掉拖拽提示标签 ===
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
for config_field in fields:
|
|
|
|
|
|
container, widget = self.create_field_editor(config_field)
|
|
|
|
|
|
group_layout.addWidget(container)
|
|
|
|
|
|
self.dynamic_widgets[config_field.name] = (widget, config_field.get_actual_field_type())
|
|
|
|
|
|
|
|
|
|
|
|
group_layout.addStretch()
|
|
|
|
|
|
layout.addWidget(group_box)
|
|
|
|
|
|
|
|
|
|
|
|
def load_config(self):
|
2025-12-12 09:49:12 +00:00
|
|
|
|
"""加载配置文件"""
|
2025-12-03 07:42:43 +00:00
|
|
|
|
try:
|
|
|
|
|
|
# 检查配置文件是否存在
|
|
|
|
|
|
if not self.check_config_file():
|
|
|
|
|
|
QMessageBox.warning(self, "警告", f"配置文件不存在: {self.config_file_path}")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self.config_data, self.all_config_fields, self.original_content = self.parse_config_file()
|
|
|
|
|
|
|
|
|
|
|
|
if not self.config_data:
|
|
|
|
|
|
QMessageBox.warning(self, "警告", "未找到任何配置项")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 保存原始配置数据用于比较
|
|
|
|
|
|
self.original_config_data = self.config_data.copy()
|
|
|
|
|
|
|
|
|
|
|
|
# 过滤掉隐藏的配置项
|
|
|
|
|
|
self.config_fields = {k: v for k, v in self.all_config_fields.items() if not v.hidden}
|
|
|
|
|
|
|
|
|
|
|
|
# 根据当前显示状态生成UI
|
|
|
|
|
|
show_hidden = getattr(self, 'show_hidden', False)
|
|
|
|
|
|
self.generate_dynamic_ui(show_hidden)
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 统计显示的标签页数量
|
|
|
|
|
|
visible_tabs = self.tab_widget.count()
|
2025-12-03 07:42:43 +00:00
|
|
|
|
hidden_count = len(self.all_config_fields) - len(self.config_fields)
|
2025-12-31 09:23:12 +00:00
|
|
|
|
|
|
|
|
|
|
self.statusBar().showMessage(f"成功加载 {len(self.config_data)} 个配置项 ({len(self.config_fields)} 个显示, {hidden_count} 个隐藏, {visible_tabs} 个分组)")
|
2025-12-03 07:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
QMessageBox.critical(self, "错误", f"加载配置文件失败:{str(e)}")
|
|
|
|
|
|
self.statusBar().showMessage("加载配置文件失败")
|
|
|
|
|
|
|
|
|
|
|
|
def toggle_hidden_fields(self, checked):
|
|
|
|
|
|
"""切换显示/隐藏配置项"""
|
|
|
|
|
|
self.show_hidden = checked
|
|
|
|
|
|
self.generate_dynamic_ui(checked)
|
|
|
|
|
|
|
|
|
|
|
|
# 更新按钮文本
|
|
|
|
|
|
if checked:
|
|
|
|
|
|
self.toggle_hidden_btn.setText("隐藏隐藏项")
|
|
|
|
|
|
self.statusBar().showMessage("已显示所有配置项(包括隐藏的)")
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.toggle_hidden_btn.setText("显示隐藏项")
|
|
|
|
|
|
self.statusBar().showMessage("已隐藏标记为隐藏的配置项")
|
|
|
|
|
|
|
2025-12-12 09:49:12 +00:00
|
|
|
|
def hide_all_fields(self):
|
|
|
|
|
|
"""一键隐藏所有配置项"""
|
|
|
|
|
|
reply = QMessageBox.question(
|
|
|
|
|
|
self, "确认一键隐藏",
|
|
|
|
|
|
"确定要隐藏所有配置项吗?\n隐藏后,页面上将不显示任何配置项。\n如需显示,需要在规则管理中取消隐藏。",
|
|
|
|
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if reply == QMessageBox.StandardButton.Yes:
|
|
|
|
|
|
# 获取所有配置项的名称
|
|
|
|
|
|
all_field_names = list(self.all_config_fields.keys())
|
|
|
|
|
|
|
|
|
|
|
|
# 将所有配置项添加到隐藏列表
|
|
|
|
|
|
self.rules["hidden"] = all_field_names
|
|
|
|
|
|
|
|
|
|
|
|
# 保存规则
|
|
|
|
|
|
self.save_rules()
|
|
|
|
|
|
|
|
|
|
|
|
# 重新加载配置
|
|
|
|
|
|
self.load_config()
|
|
|
|
|
|
|
|
|
|
|
|
# 更新状态栏
|
|
|
|
|
|
self.statusBar().showMessage("已隐藏所有配置项,页面已清空")
|
|
|
|
|
|
|
|
|
|
|
|
QMessageBox.information(self, "完成", "已成功隐藏所有配置项。\n如需显示配置项,请在规则管理中取消隐藏。")
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
def manage_rules(self):
|
2025-12-12 09:49:12 +00:00
|
|
|
|
"""管理规则"""
|
2025-12-03 07:42:43 +00:00
|
|
|
|
dialog = ConfigManagementWindow(self.rules, self.all_config_fields, self)
|
|
|
|
|
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
|
|
|
|
updated_rules = dialog.get_updated_rules()
|
|
|
|
|
|
self.rules.update(updated_rules)
|
|
|
|
|
|
self.save_rules()
|
|
|
|
|
|
self.load_config()
|
|
|
|
|
|
QMessageBox.information(self, "成功", "规则已更新并应用!")
|
|
|
|
|
|
|
|
|
|
|
|
def collect_ui_data(self):
|
|
|
|
|
|
"""从UI收集数据"""
|
|
|
|
|
|
ui_data = {}
|
|
|
|
|
|
for field_name, (widget, field_type) in self.dynamic_widgets.items():
|
|
|
|
|
|
# 跳过隐藏且禁用的配置项
|
|
|
|
|
|
if not widget.isEnabled():
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
if field_type == "bool":
|
|
|
|
|
|
ui_data[field_name] = widget.isChecked()
|
|
|
|
|
|
elif field_type == "int":
|
|
|
|
|
|
ui_data[field_name] = widget.value()
|
|
|
|
|
|
elif field_type == "float":
|
|
|
|
|
|
ui_data[field_name] = widget.value()
|
|
|
|
|
|
elif field_type == "json":
|
|
|
|
|
|
try:
|
|
|
|
|
|
text = widget.toPlainText().strip()
|
|
|
|
|
|
if text:
|
|
|
|
|
|
ui_data[field_name] = json.loads(text)
|
|
|
|
|
|
else:
|
|
|
|
|
|
ui_data[field_name] = {}
|
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
|
QMessageBox.warning(self, "JSON格式错误",
|
|
|
|
|
|
f"配置项 {field_name} 的JSON格式不正确")
|
|
|
|
|
|
return None
|
|
|
|
|
|
else:
|
|
|
|
|
|
text = widget.text().strip()
|
|
|
|
|
|
ui_data[field_name] = text
|
|
|
|
|
|
|
|
|
|
|
|
return ui_data
|
|
|
|
|
|
|
|
|
|
|
|
def validate_config_data(self, config_data):
|
|
|
|
|
|
"""校验配置数据"""
|
|
|
|
|
|
errors = []
|
|
|
|
|
|
|
|
|
|
|
|
for field_name, value in config_data.items():
|
|
|
|
|
|
config_field = self.all_config_fields.get(field_name)
|
|
|
|
|
|
if not config_field:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
validation = config_field.validation
|
|
|
|
|
|
if not validation:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
# 必填校验
|
|
|
|
|
|
if validation.get("required") and (value is None or value == ""):
|
|
|
|
|
|
errors.append(f"配置项 '{config_field.display_name}' 是必填项")
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
# 数字范围校验
|
|
|
|
|
|
if isinstance(value, (int, float)):
|
|
|
|
|
|
if "min" in validation:
|
|
|
|
|
|
try:
|
|
|
|
|
|
min_val = float(validation["min"])
|
|
|
|
|
|
if value < min_val:
|
|
|
|
|
|
errors.append(f"配置项 '{config_field.display_name}' 的值不能小于 {min_val}")
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
if "max" in validation:
|
|
|
|
|
|
try:
|
|
|
|
|
|
max_val = float(validation["max"])
|
|
|
|
|
|
if value > max_val:
|
|
|
|
|
|
errors.append(f"配置项 '{config_field.display_name}' 的值不能大于 {max_val}")
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
# 字符串正则校验
|
|
|
|
|
|
elif isinstance(value, str) and "regex" in validation:
|
|
|
|
|
|
try:
|
|
|
|
|
|
if not re.match(validation["regex"], value):
|
|
|
|
|
|
errors.append(f"配置项 '{config_field.display_name}' 的值不符合格式要求")
|
|
|
|
|
|
except re.error:
|
|
|
|
|
|
errors.append(f"配置项 '{config_field.display_name}' 的正则表达式格式错误")
|
|
|
|
|
|
|
|
|
|
|
|
return errors
|
|
|
|
|
|
|
|
|
|
|
|
def get_changes(self, new_data):
|
|
|
|
|
|
"""获取配置变更列表"""
|
|
|
|
|
|
changes = {}
|
|
|
|
|
|
for field_name, new_value in new_data.items():
|
|
|
|
|
|
old_value = self.original_config_data.get(field_name)
|
|
|
|
|
|
if old_value != new_value:
|
|
|
|
|
|
changes[field_name] = (old_value, new_value)
|
|
|
|
|
|
return changes
|
|
|
|
|
|
|
|
|
|
|
|
def format_dict_value(self, value, original_lines=None):
|
|
|
|
|
|
"""专门格式化字典值,保持多行格式"""
|
|
|
|
|
|
if not isinstance(value, dict):
|
|
|
|
|
|
return json.dumps(value, ensure_ascii=False)
|
|
|
|
|
|
|
|
|
|
|
|
# 如果有原始行信息,尝试保持原始格式
|
|
|
|
|
|
if original_lines and len(original_lines) > 1:
|
|
|
|
|
|
# 检查原始格式是否是漂亮的多行格式
|
|
|
|
|
|
first_line = original_lines[0].strip()
|
|
|
|
|
|
if first_line.endswith('{') and len(original_lines) > 2:
|
|
|
|
|
|
# 原始是多行格式,我们也使用多行格式
|
|
|
|
|
|
result = "{\n"
|
|
|
|
|
|
for i, (key, val) in enumerate(value.items()):
|
|
|
|
|
|
# 使用4个空格缩进
|
|
|
|
|
|
result += f' "{key}": {json.dumps(val, ensure_ascii=False)}'
|
|
|
|
|
|
if i < len(value) - 1:
|
|
|
|
|
|
result += ",\n"
|
|
|
|
|
|
else:
|
|
|
|
|
|
result += "\n"
|
|
|
|
|
|
result += "}"
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
# 如果没有原始格式信息或不是多行格式,使用漂亮的JSON格式
|
|
|
|
|
|
return json.dumps(value, indent=4, ensure_ascii=False)
|
|
|
|
|
|
|
|
|
|
|
|
def format_value(self, value, config_field=None):
|
|
|
|
|
|
"""格式化值,保持与Python兼容的格式"""
|
|
|
|
|
|
if value is None:
|
|
|
|
|
|
return "None"
|
|
|
|
|
|
elif isinstance(value, bool):
|
|
|
|
|
|
return "True" if value else "False"
|
|
|
|
|
|
elif isinstance(value, (int, float)):
|
|
|
|
|
|
# 根据小数位数格式化浮点数
|
|
|
|
|
|
if isinstance(value, float) and config_field and config_field.decimals is not None:
|
|
|
|
|
|
# 格式化浮点数,保留指定小数位数
|
|
|
|
|
|
format_str = f"{{:.{config_field.decimals}f}}"
|
|
|
|
|
|
return format_str.format(value)
|
|
|
|
|
|
else:
|
|
|
|
|
|
return str(value)
|
|
|
|
|
|
elif isinstance(value, str):
|
|
|
|
|
|
# 检查字符串是否已经用引号包围
|
|
|
|
|
|
if (value.startswith('"') and value.endswith('"')) or (value.startswith("'") and value.endswith("'")):
|
|
|
|
|
|
return value
|
|
|
|
|
|
# 检查字符串是否包含特殊字符
|
|
|
|
|
|
elif any(char in value for char in [' ', '\t', '\n', '#', '=']):
|
|
|
|
|
|
return f'"{value}"'
|
|
|
|
|
|
else:
|
|
|
|
|
|
return f'"{value}"'
|
|
|
|
|
|
elif isinstance(value, dict):
|
|
|
|
|
|
# 对于字典,使用专门的格式化函数
|
|
|
|
|
|
return self.format_dict_value(value, config_field.original_lines if config_field else None)
|
|
|
|
|
|
elif isinstance(value, list):
|
|
|
|
|
|
return json.dumps(value, ensure_ascii=False, indent=4)
|
|
|
|
|
|
else:
|
|
|
|
|
|
return repr(value)
|
|
|
|
|
|
|
|
|
|
|
|
def save_config(self):
|
|
|
|
|
|
"""保存配置,保留所有注释、空格和格式"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 检查配置文件是否存在
|
|
|
|
|
|
if not self.check_config_file():
|
|
|
|
|
|
QMessageBox.warning(self, "警告", f"配置文件不存在: {self.config_file_path}")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 收集UI数据
|
|
|
|
|
|
new_data = self.collect_ui_data()
|
|
|
|
|
|
if new_data is None: # JSON解析错误
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 校验配置数据
|
|
|
|
|
|
validation_errors = self.validate_config_data(new_data)
|
|
|
|
|
|
if validation_errors:
|
|
|
|
|
|
error_msg = "配置校验失败:\n\n" + "\n".join(validation_errors)
|
|
|
|
|
|
QMessageBox.warning(self, "配置校验失败", error_msg)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 获取变更列表
|
|
|
|
|
|
changes = self.get_changes(new_data)
|
|
|
|
|
|
|
|
|
|
|
|
if not changes:
|
|
|
|
|
|
QMessageBox.information(self, "提示", "没有检测到任何配置变更")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 显示变更确认对话框
|
|
|
|
|
|
dialog = ChangeConfirmDialog(changes, self)
|
|
|
|
|
|
result = dialog.exec()
|
|
|
|
|
|
|
|
|
|
|
|
if result != QDialog.DialogCode.Accepted:
|
|
|
|
|
|
self.statusBar().showMessage("用户取消了保存操作")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# === 新增:在保存前更新上次保存的值到规则文件 ===
|
|
|
|
|
|
for field_name, (old_value, new_value) in changes.items():
|
|
|
|
|
|
# 将修改前的值(旧值)保存为上次保存的值
|
|
|
|
|
|
self.rules.setdefault("last_saved_values", {})[field_name] = old_value
|
|
|
|
|
|
|
|
|
|
|
|
# 立即保存规则文件,确保上次保存的值被持久化
|
|
|
|
|
|
self.save_rules()
|
|
|
|
|
|
|
2025-12-03 07:42:43 +00:00
|
|
|
|
# 读取原始文件内容
|
|
|
|
|
|
with open(self.config_file_path, 'r', encoding='utf-8') as f:
|
|
|
|
|
|
original_content = f.read()
|
|
|
|
|
|
original_lines = original_content.split('\n')
|
|
|
|
|
|
|
|
|
|
|
|
# 构建新文件内容 - 只修改值部分,保留所有其他内容
|
|
|
|
|
|
new_lines = original_lines.copy()
|
|
|
|
|
|
|
|
|
|
|
|
# 对每个变更的配置项,找到其位置并修改值,保留空格和格式
|
|
|
|
|
|
for field_name, (old_value, new_value) in changes.items():
|
|
|
|
|
|
config_field = self.all_config_fields.get(field_name)
|
|
|
|
|
|
if not config_field or config_field.line_number is None:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
# 获取配置项的原始块
|
|
|
|
|
|
original_block = config_field.original_lines
|
|
|
|
|
|
|
|
|
|
|
|
# 查找赋值行在原始块中的位置
|
|
|
|
|
|
assignment_line_index = -1
|
|
|
|
|
|
for i, line in enumerate(original_block):
|
|
|
|
|
|
stripped = line.strip()
|
|
|
|
|
|
if stripped.startswith(field_name) and '=' in stripped:
|
|
|
|
|
|
assignment_line_index = i
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
if assignment_line_index >= 0:
|
|
|
|
|
|
# 获取原始赋值行的完整内容(包括注释等)
|
|
|
|
|
|
original_assignment_line = original_block[assignment_line_index]
|
|
|
|
|
|
|
|
|
|
|
|
# 使用改进的正则表达式匹配赋值语句
|
|
|
|
|
|
# 这个正则表达式会捕获:
|
|
|
|
|
|
# 1. 变量名前的所有空格
|
|
|
|
|
|
# 2. 变量名
|
|
|
|
|
|
# 3. 等号前的空格
|
|
|
|
|
|
# 4. 等号
|
|
|
|
|
|
# 5. 等号后的空格
|
|
|
|
|
|
# 6. 原始值(直到注释或行尾)
|
|
|
|
|
|
# 7. 行尾注释(如果有)
|
|
|
|
|
|
pattern = rf'^(\s*)({re.escape(field_name)})(\s*)(=)(\s*)(.*?)(\s*(#.*)?)$'
|
|
|
|
|
|
match = re.match(pattern, original_assignment_line)
|
|
|
|
|
|
|
|
|
|
|
|
if match:
|
|
|
|
|
|
# 获取匹配的各个部分
|
|
|
|
|
|
leading_spaces = match.group(1) # 变量名前的空格
|
|
|
|
|
|
var_name_part = match.group(2) # 变量名
|
|
|
|
|
|
spaces_before_eq = match.group(3) # 等号前的空格
|
|
|
|
|
|
eq_sign = match.group(4) # 等号
|
|
|
|
|
|
spaces_after_eq = match.group(5) # 等号后的空格
|
|
|
|
|
|
old_value_part = match.group(6) # 原始值
|
|
|
|
|
|
trailing_comment = match.group(7) if match.group(7) else '' # 行尾注释
|
|
|
|
|
|
|
|
|
|
|
|
# 格式化新值
|
|
|
|
|
|
formatted_value = self.format_value(new_value, config_field)
|
|
|
|
|
|
|
|
|
|
|
|
# 构建新行,保持原始的空格格式
|
|
|
|
|
|
new_line = (f"{leading_spaces}{var_name_part}{spaces_before_eq}"
|
|
|
|
|
|
f"{eq_sign}{spaces_after_eq}{formatted_value}"
|
|
|
|
|
|
f"{trailing_comment}")
|
|
|
|
|
|
|
|
|
|
|
|
# 找到赋值行在原始文件中的实际位置
|
|
|
|
|
|
# assignment_line_in_file 是赋值行在原始文件中的行号(从0开始)
|
|
|
|
|
|
assignment_line_in_file = config_field.line_number + assignment_line_index
|
|
|
|
|
|
|
|
|
|
|
|
if assignment_line_in_file < len(new_lines):
|
|
|
|
|
|
new_lines[assignment_line_in_file] = new_line
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 如果正则匹配失败,使用简单的替换方法
|
|
|
|
|
|
# 查找赋值行在原始文件中的位置
|
|
|
|
|
|
for i in range(len(new_lines)):
|
|
|
|
|
|
line = new_lines[i]
|
|
|
|
|
|
stripped = line.strip()
|
|
|
|
|
|
if stripped.startswith(field_name) and '=' in stripped:
|
|
|
|
|
|
# 使用更简单的正则表达式
|
|
|
|
|
|
pattern = rf'^(\s*{re.escape(field_name)}\s*=\s*).*$'
|
|
|
|
|
|
match = re.match(pattern, line.rstrip())
|
|
|
|
|
|
if match:
|
|
|
|
|
|
# 格式化新值
|
|
|
|
|
|
formatted_value = self.format_value(new_value, config_field)
|
|
|
|
|
|
|
|
|
|
|
|
# 检查是否有行尾注释
|
|
|
|
|
|
comment_pos = line.find('#')
|
|
|
|
|
|
if comment_pos > 0:
|
|
|
|
|
|
# 有行尾注释,保留注释
|
|
|
|
|
|
line_comment = line[comment_pos:]
|
|
|
|
|
|
prefix = match.group(1)
|
|
|
|
|
|
new_line = prefix + formatted_value + line_comment
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 没有行尾注释
|
|
|
|
|
|
prefix = match.group(1)
|
|
|
|
|
|
new_line = prefix + formatted_value
|
|
|
|
|
|
|
|
|
|
|
|
new_lines[i] = new_line
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
# 写入文件,保持原有的换行符格式
|
|
|
|
|
|
with open(self.config_file_path, 'w', encoding='utf-8', newline='') as f:
|
|
|
|
|
|
f.write('\n'.join(new_lines))
|
|
|
|
|
|
|
|
|
|
|
|
# 清除所有高亮显示
|
|
|
|
|
|
for field_name in self.modified_fields.copy():
|
|
|
|
|
|
self.clear_field_highlight(field_name)
|
|
|
|
|
|
self.modified_fields.remove(field_name)
|
|
|
|
|
|
|
2025-12-31 09:23:12 +00:00
|
|
|
|
# 重新加载配置(这会重新读取规则文件中的上次保存值)
|
2025-12-03 07:42:43 +00:00
|
|
|
|
self.load_config()
|
|
|
|
|
|
|
|
|
|
|
|
self.statusBar().showMessage(f"配置文件保存成功,修改了 {len(changes)} 个配置项")
|
|
|
|
|
|
|
|
|
|
|
|
# 使用定时器延迟显示成功消息
|
|
|
|
|
|
QTimer.singleShot(100, lambda: QMessageBox.information(
|
|
|
|
|
|
self, "成功", f"配置文件保存成功!\n修改了 {len(changes)} 个配置项"
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.statusBar().showMessage("保存配置文件失败")
|
|
|
|
|
|
QMessageBox.critical(self, "错误", f"保存配置文件失败:{str(e)}\n\n错误详情:{traceback.format_exc()}")
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
app = QApplication(sys.argv)
|
|
|
|
|
|
app.setStyle('Fusion')
|
|
|
|
|
|
|
|
|
|
|
|
# 创建编辑器
|
|
|
|
|
|
editor = ConfigEditor()
|
|
|
|
|
|
editor.show()
|
|
|
|
|
|
sys.exit(app.exec())
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2025-12-31 09:23:12 +00:00
|
|
|
|
main()
|