上传文件至 /
This commit is contained in:
parent
93309fb762
commit
fb5f83ff7c
39
main.py
Normal file
39
main.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
'''项目入口启动程序'''
|
||||||
|
import sys
|
||||||
|
from PyQt6.QtWidgets import QApplication
|
||||||
|
from login.login_dialog import LoginDialog
|
||||||
|
from main_window import ConfigEditor
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
app.setStyle('Fusion')
|
||||||
|
|
||||||
|
# 创建登录对话框
|
||||||
|
login_dialog = LoginDialog()
|
||||||
|
|
||||||
|
def on_login_success(username, user_config_path):
|
||||||
|
"""登录成功回调"""
|
||||||
|
# 注意:这里传递的是 user_config_path,这是账密文件路径
|
||||||
|
# 而 ConfigEditor 需要的是要编辑的配置文件路径
|
||||||
|
print(f"[调试] 登录成功: 用户名={username}, 账密文件={user_config_path}")
|
||||||
|
|
||||||
|
# 创建主窗口,传递账密文件路径和用户名
|
||||||
|
# 主窗口会自己加载要编辑的配置文件
|
||||||
|
editor = ConfigEditor(user_config_path, username)
|
||||||
|
editor.show()
|
||||||
|
|
||||||
|
# 连接信号
|
||||||
|
login_dialog.login_success.connect(on_login_success)
|
||||||
|
|
||||||
|
# 显示登录对话框
|
||||||
|
result = login_dialog.exec()
|
||||||
|
|
||||||
|
# 如果登录对话框被取消(用户点击退出),则退出程序
|
||||||
|
if result == 0:
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# 登录成功后,启动应用程序的主事件循环
|
||||||
|
sys.exit(app.exec())
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
1555
main_window.py
Normal file
1555
main_window.py
Normal file
File diff suppressed because it is too large
Load Diff
143
models.py
Normal file
143
models.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
'''数据模型定义'''
|
||||||
|
import json
|
||||||
|
import datetime
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ConfigField:
|
||||||
|
"""配置项元数据类"""
|
||||||
|
name: str
|
||||||
|
value: Any
|
||||||
|
category: str = "未分类"
|
||||||
|
display_name: str = ""
|
||||||
|
field_type: str = "auto"
|
||||||
|
decimals: Optional[int] = None
|
||||||
|
tooltip: str = ""
|
||||||
|
hidden: bool = False
|
||||||
|
encrypted: bool = False # 是否加密
|
||||||
|
line_number: Optional[int] = None
|
||||||
|
original_lines: List[str] = field(default_factory=list)
|
||||||
|
validation: Dict = field(default_factory=dict)
|
||||||
|
last_saved_value: Any = None
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
if not self.display_name:
|
||||||
|
self.display_name = self.name
|
||||||
|
|
||||||
|
def get_actual_field_type(self) -> str:
|
||||||
|
"""获取实际的字段类型"""
|
||||||
|
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"
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EditorRules:
|
||||||
|
"""编辑器规则数据类"""
|
||||||
|
categories: Dict[str, List[str]] = field(default_factory=lambda: {"未分类": []})
|
||||||
|
display_names: Dict[str, str] = field(default_factory=dict)
|
||||||
|
tooltips: Dict[str, str] = field(default_factory=dict)
|
||||||
|
field_types: Dict[str, str] = field(default_factory=dict)
|
||||||
|
field_decimals: Dict[str, int] = field(default_factory=dict)
|
||||||
|
hidden: List[str] = field(default_factory=list)
|
||||||
|
encrypted_fields: List[str] = field(default_factory=list) # 加密字段列表
|
||||||
|
validations: Dict[str, Dict] = field(default_factory=dict)
|
||||||
|
field_order: Dict[str, List[str]] = field(default_factory=dict)
|
||||||
|
last_saved_values: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict:
|
||||||
|
"""转换为字典"""
|
||||||
|
return {
|
||||||
|
"categories": self.categories,
|
||||||
|
"display_names": self.display_names,
|
||||||
|
"tooltips": self.tooltips,
|
||||||
|
"field_types": self.field_types,
|
||||||
|
"field_decimals": self.field_decimals,
|
||||||
|
"hidden": self.hidden,
|
||||||
|
"encrypted_fields": self.encrypted_fields,
|
||||||
|
"validations": self.validations,
|
||||||
|
"field_order": self.field_order,
|
||||||
|
"last_saved_values": self.last_saved_values
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: Dict) -> 'EditorRules':
|
||||||
|
"""从字典创建实例"""
|
||||||
|
return cls(
|
||||||
|
categories=data.get("categories", {"未分类": []}),
|
||||||
|
display_names=data.get("display_names", {}),
|
||||||
|
tooltips=data.get("tooltips", {}),
|
||||||
|
field_types=data.get("field_types", {}),
|
||||||
|
field_decimals=data.get("field_decimals", {}),
|
||||||
|
hidden=data.get("hidden", []),
|
||||||
|
encrypted_fields=data.get("encrypted_fields", []),
|
||||||
|
validations=data.get("validations", {}),
|
||||||
|
field_order=data.get("field_order", {}),
|
||||||
|
last_saved_values=data.get("last_saved_values", {})
|
||||||
|
)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class UserSettings:
|
||||||
|
"""用户设置数据类"""
|
||||||
|
config_file_path: str = ""
|
||||||
|
last_used: str = ""
|
||||||
|
use_relative_path: bool = True
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict:
|
||||||
|
return {
|
||||||
|
"config_file_path": self.config_file_path,
|
||||||
|
"last_used": self.last_used
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: Dict) -> 'UserSettings':
|
||||||
|
return cls(
|
||||||
|
config_file_path=data.get("config_file_path", ""),
|
||||||
|
last_used=data.get("last_used", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExportInfo:
|
||||||
|
"""导出信息数据类"""
|
||||||
|
export_time: str = ""
|
||||||
|
export_version: str = "1.0"
|
||||||
|
note: str = "配置文件编辑器规则文件"
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict:
|
||||||
|
return {
|
||||||
|
"export_time": self.export_time or datetime.datetime.now().strftime("%Y-%m-d %H:%M:%S"),
|
||||||
|
"export_version": self.export_version,
|
||||||
|
"note": self.note
|
||||||
|
}
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Statistics:
|
||||||
|
"""统计信息数据类"""
|
||||||
|
total_fields: int = 0
|
||||||
|
display_names_count: int = 0
|
||||||
|
categories_count: int = 0
|
||||||
|
hidden_fields_count: int = 0
|
||||||
|
encrypted_fields_count: int = 0 # 加密字段计数
|
||||||
|
field_types_count: int = 0
|
||||||
|
validations_count: int = 0
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict:
|
||||||
|
return {
|
||||||
|
"total_fields": self.total_fields,
|
||||||
|
"display_names_count": self.display_names_count,
|
||||||
|
"categories_count": self.categories_count,
|
||||||
|
"hidden_fields_count": self.hidden_fields_count,
|
||||||
|
"encrypted_fields_count": self.encrypted_fields_count,
|
||||||
|
"field_types_count": self.field_types_count,
|
||||||
|
"validations_count": self.validations_count
|
||||||
|
}
|
||||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
PyQt6>=6.5.0
|
||||||
263
utils.py
Normal file
263
utils.py
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
'''工具函数集合'''
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import datetime
|
||||||
|
import re
|
||||||
|
import ast
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, Any, List, Tuple, Optional
|
||||||
|
|
||||||
|
def merge_rules(existing_rules: Dict, default_rules: Dict) -> Dict:
|
||||||
|
"""智能合并规则:保留现有规则,只添加默认规则中的新字段"""
|
||||||
|
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", "encrypted_fields"]:
|
||||||
|
if field not in merged_rules:
|
||||||
|
if field == "hidden" or field == "encrypted_fields":
|
||||||
|
merged_rules[field] = []
|
||||||
|
else:
|
||||||
|
merged_rules[field] = {}
|
||||||
|
|
||||||
|
return merged_rules
|
||||||
|
|
||||||
|
def backup_existing_rules(rules_file: str, program_dir: str) -> str:
|
||||||
|
"""备份现有规则文件"""
|
||||||
|
if os.path.exists(rules_file):
|
||||||
|
try:
|
||||||
|
# 创建备份目录
|
||||||
|
backup_dir = os.path.join(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(rules_file, backup_file)
|
||||||
|
print(f"已备份规则文件到: {backup_file}")
|
||||||
|
|
||||||
|
# 清理旧的备份文件
|
||||||
|
cleanup_old_backups(backup_dir)
|
||||||
|
|
||||||
|
return backup_file
|
||||||
|
except Exception as e:
|
||||||
|
print(f"备份规则文件失败: {e}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def cleanup_old_backups(backup_dir: str, keep_count: int = 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])
|
||||||
|
|
||||||
|
# 删除旧的备份文件,保留最近几个
|
||||||
|
if len(backup_files) > keep_count:
|
||||||
|
files_to_delete = backup_files[:-keep_count]
|
||||||
|
for filepath, _ in files_to_delete:
|
||||||
|
os.remove(filepath)
|
||||||
|
print(f"已删除旧备份文件: {filepath}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"清理旧备份文件失败: {e}")
|
||||||
|
|
||||||
|
def get_validation_tooltip(validation: Dict) -> str:
|
||||||
|
"""生成校验规则的提示信息"""
|
||||||
|
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 format_dict_value(value: Dict, original_lines: List[str] = None) -> str:
|
||||||
|
"""专门格式化字典值,保持多行格式"""
|
||||||
|
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(value: Any, config_field=None) -> str:
|
||||||
|
"""格式化值,保持与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):
|
||||||
|
# 对于字典,使用专门的格式化函数
|
||||||
|
original_lines = config_field.original_lines if config_field else None
|
||||||
|
return format_dict_value(value, original_lines)
|
||||||
|
elif isinstance(value, list):
|
||||||
|
return json.dumps(value, ensure_ascii=False, indent=4)
|
||||||
|
else:
|
||||||
|
return repr(value)
|
||||||
|
|
||||||
|
def get_default_rules() -> Dict:
|
||||||
|
"""获取默认规则"""
|
||||||
|
return {
|
||||||
|
"categories": {"未分类": []},
|
||||||
|
"display_names": {},
|
||||||
|
"tooltips": {},
|
||||||
|
"field_types": {},
|
||||||
|
"field_decimals": {},
|
||||||
|
"hidden": [],
|
||||||
|
"encrypted_fields": [], #加密字段列表
|
||||||
|
"validations": {},
|
||||||
|
"field_order": {},
|
||||||
|
"last_saved_values": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
def validate_config_data(config_data: Dict[str, Any], all_config_fields: Dict[str, Any]) -> List[str]:
|
||||||
|
"""校验配置数据"""
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for field_name, value in config_data.items():
|
||||||
|
config_field = 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_field_type_from_value(value: Any) -> str:
|
||||||
|
"""根据值推断字段类型"""
|
||||||
|
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 ensure_rule_structure(rules: Dict) -> Dict:
|
||||||
|
"""确保规则结构完整"""
|
||||||
|
# 确保规则中有"未分类"分组
|
||||||
|
if "categories" not in rules:
|
||||||
|
rules["categories"] = {}
|
||||||
|
if "未分类" not in rules["categories"]:
|
||||||
|
rules["categories"]["未分类"] = []
|
||||||
|
|
||||||
|
# 确保规则中有field_decimals字段
|
||||||
|
if "field_decimals" not in rules:
|
||||||
|
rules["field_decimals"] = {}
|
||||||
|
|
||||||
|
# 确保规则中有field_order字段
|
||||||
|
if "field_order" not in rules:
|
||||||
|
rules["field_order"] = {}
|
||||||
|
|
||||||
|
# 确保规则中有last_saved_values字段
|
||||||
|
if "last_saved_values" not in rules:
|
||||||
|
rules["last_saved_values"] = {}
|
||||||
|
|
||||||
|
# 确保规则中有encrypted_fields字段
|
||||||
|
if "encrypted_fields" not in rules:
|
||||||
|
rules["encrypted_fields"] = []
|
||||||
|
|
||||||
|
# 确保其他必要字段存在
|
||||||
|
for field in ["display_names", "tooltips", "field_types", "validations"]:
|
||||||
|
if field not in rules:
|
||||||
|
rules[field] = {}
|
||||||
|
|
||||||
|
if "hidden" not in rules:
|
||||||
|
rules["hidden"] = []
|
||||||
|
|
||||||
|
return rules
|
||||||
Loading…
x
Reference in New Issue
Block a user