#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ BallController配置管理工具 - 简化版 图形界面版本,不依赖pandas,支持浏览文件并选择Excel/CSV表格进行配置导入 功能: 1. 图形界面浏览项目文件 2. 多选Excel/CSV配置表格 3. 预览配置内容 4. 一键导入配置到JSON 5. 配置备份和恢复 作者: AI Assistant 日期: 2024 """ import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext import json import os import csv from datetime import datetime from pathlib import Path import threading # 尝试导入pandas,如果失败则使用纯CSV模式 try: import pandas as pd PANDAS_AVAILABLE = True except ImportError: PANDAS_AVAILABLE = False print("警告: pandas未安装,将使用纯CSV模式(不支持Excel文件)") # 导入武器配置管理器 try: from weapon_config_manager import WeaponConfigManager WEAPON_MANAGER_AVAILABLE = True except ImportError: WEAPON_MANAGER_AVAILABLE = False print("警告: weapon_config_manager.py 未找到,武器配置功能将不可用") try: from enemy_config_manager import EnemyConfigManager ENEMY_MANAGER_AVAILABLE = True except ImportError: ENEMY_MANAGER_AVAILABLE = False print("警告: enemy_config_manager.py 未找到,敌人配置功能将不可用") try: from level_config_manager import LevelConfigManager LEVEL_MANAGER_AVAILABLE = True except ImportError: LEVEL_MANAGER_AVAILABLE = False print("警告: level_config_manager.py 未找到,关卡配置功能将不可用") # 导入新的配置管理器模块 try: from ball_controller_config_manager import BallControllerConfigManager BALL_CONTROLLER_MANAGER_AVAILABLE = True except ImportError: BALL_CONTROLLER_MANAGER_AVAILABLE = False print("警告: ball_controller_config_manager.py 未找到,球控制器配置功能将不可用") try: from skill_config_manager import SkillConfigManager SKILL_CONFIG_MANAGER_AVAILABLE = True except ImportError: SKILL_CONFIG_MANAGER_AVAILABLE = False print("警告: skill_config_manager.py 未找到,局外技能配置功能将不可用") try: from game_skill_config_manager import GameSkillConfigManager GAME_SKILL_MANAGER_AVAILABLE = True except ImportError: GAME_SKILL_MANAGER_AVAILABLE = False print("警告: game_skill_config_manager.py 未找到,游戏内技能配置功能将不可用") try: from shop_config_manager import ShopConfigManager SHOP_MANAGER_AVAILABLE = True except ImportError: SHOP_MANAGER_AVAILABLE = False print("警告: shop_config_manager.py 未找到,商店配置功能将不可用") try: from ball_price_config_manager import BallPriceConfigManager BALL_PRICE_MANAGER_AVAILABLE = True except ImportError: BALL_PRICE_MANAGER_AVAILABLE = False print("警告: ball_price_config_manager.py 未找到,小球价格配置功能将不可用") def parse_skill_types(self, df): """解析技能类型配置""" skill_types = [] # 读取所有数据行(pandas已经自动处理了标题行) for index, row in df.iterrows(): # 检查是否为空行 if pd.isna(row.iloc[0]): continue try: skill_type = { "id": int(row.iloc[0]) if not pd.isna(row.iloc[0]) else 0, "name": str(row.iloc[1]) if not pd.isna(row.iloc[1]) else "", "displayName": str(row.iloc[2]) if not pd.isna(row.iloc[2]) else "", "nameTemplate": str(row.iloc[3]) if not pd.isna(row.iloc[3]) else "", "type": str(row.iloc[4]) if not pd.isna(row.iloc[4]) else "" } skill_types.append(skill_type) except Exception as e: print(f"解析技能类型第 {index+1} 行失败: {e}") continue return skill_types def parse_skill_groups(self, df): """解析技能组配置""" skill_groups = [] # 读取所有数据行(pandas已经自动处理了标题行) for index, row in df.iterrows(): # 检查是否为空行 if pd.isna(row.iloc[0]): continue try: skill_group = { "group": int(row.iloc[0]) if not pd.isna(row.iloc[0]) else 1, "effectPercent": int(row.iloc[1]) if not pd.isna(row.iloc[1]) else 0, "diamondCost": int(row.iloc[2]) if not pd.isna(row.iloc[2]) else 0 } skill_groups.append(skill_group) except Exception as e: print(f"解析技能组第 {index+1} 行失败: {e}") continue return skill_groups def validate_config(self, config): """验证配置数据""" errors = [] # 验证技能类型 if not config.get("skillTypes"): errors.append("技能类型配置为空") else: for i, skill_type in enumerate(config["skillTypes"]): if not skill_type.get("name"): errors.append(f"技能类型 {i+1} 缺少名称") if not skill_type.get("type"): errors.append(f"技能类型 {i+1} 缺少类型标识") # 验证技能组 if not config.get("skillGroups"): errors.append("技能组配置为空") else: groups = [sg["group"] for sg in config["skillGroups"]] if len(groups) != len(set(groups)): errors.append("技能组编号存在重复") # 验证基本配置 if config.get("totalGroups", 0) <= 0: errors.append("总组数必须大于0") if config.get("skillsPerGroup", 0) <= 0: errors.append("每组技能数必须大于0") return errors def backup_json(self): """备份当前JSON文件(已禁用)""" # 不再创建备份文件,直接返回成功 return True def save_json_config(self, config): """保存配置到JSON文件""" try: with open(self.json_file, 'w', encoding='utf-8') as f: json.dump(config, f, ensure_ascii=False, indent=2) print(f"配置已保存到: {self.json_file}") return True except Exception as e: print(f"保存JSON文件失败: {e}") return False class ConfigManagerGUI: def __init__(self): self.root = tk.Tk() self.root.title("游戏配置管理工具") self.root.geometry("1000x700") self.root.resizable(True, True) # 配置文件路径 - 动态获取项目根目录 # 从当前脚本位置向上查找项目根目录 current_dir = Path(__file__).parent self.project_root = current_dir.parent.parent.parent.parent # 从excel目录向上4级到项目根目录 self.excel_dir = current_dir # 手动配置变量 self.selected_json_path = None self.format_type = 'horizontal' # 默认为横向表格 self.multi_sheet = False self.sheet_names = [] self.param_types = {} # 当前选择的配置映射 self.current_mapping = None # 移除json_config_path,现在使用selected_json_path self.param_types = {} # 默认配置值 self.default_config = { 'baseSpeed': 60, 'maxReflectionRandomness': 0.2, 'antiTrapTimeWindow': 5.0, 'antiTrapHitThreshold': 5, 'deflectionAttemptThreshold': 3, 'antiTrapDeflectionMultiplier': 3.0, 'FIRE_COOLDOWN': 0.05, 'ballRadius': 10, 'gravityScale': 0, 'linearDamping': 0, 'angularDamping': 0, 'colliderGroup': 2, 'colliderTag': 1, 'friction': 0, 'restitution': 1, 'safeDistance': 50, 'edgeOffset': 20, 'sensor': False } self.selected_files = [] self.config_data = {} # 初始化配置管理器类引用 self.weapon_config_manager_class = WeaponConfigManager if WEAPON_MANAGER_AVAILABLE else None self.enemy_config_manager_class = EnemyConfigManager if ENEMY_MANAGER_AVAILABLE else None self.level_config_manager_class = LevelConfigManager if LEVEL_MANAGER_AVAILABLE else None self.ball_controller_config_manager_class = BallControllerConfigManager if BALL_CONTROLLER_MANAGER_AVAILABLE else None self.skill_config_manager_class = SkillConfigManager if SKILL_CONFIG_MANAGER_AVAILABLE else None self.game_skill_config_manager_class = GameSkillConfigManager if GAME_SKILL_MANAGER_AVAILABLE else None self.shop_config_manager_class = ShopConfigManager if SHOP_MANAGER_AVAILABLE else None self.ball_price_config_manager_class = BallPriceConfigManager if BALL_PRICE_MANAGER_AVAILABLE else None # 初始化新的配置管理器模块 self.init_config_managers() self.setup_ui() self.load_current_config() def setup_ui(self): """设置用户界面""" # 主框架 main_frame = ttk.Frame(self.root, padding="10") main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # 配置根窗口的网格权重 self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=1) main_frame.columnconfigure(1, weight=1) main_frame.rowconfigure(2, weight=1) # 标题 title_label = ttk.Label(main_frame, text="游戏配置管理工具", font=("Arial", 16, "bold")) title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20)) # 左侧面板 - 文件选择 file_types_text = "Excel/CSV配置文件选择" if PANDAS_AVAILABLE else "CSV配置文件选择" left_frame = ttk.LabelFrame(main_frame, text=file_types_text, padding="10") left_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 10)) left_frame.columnconfigure(0, weight=1) left_frame.rowconfigure(3, weight=1) # 文件浏览按钮 browse_frame = ttk.Frame(left_frame) browse_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10)) browse_frame.columnconfigure(0, weight=1) browse_text = "浏览Excel/CSV文件" if PANDAS_AVAILABLE else "浏览CSV文件" ttk.Button(browse_frame, text=browse_text, command=self.browse_files).grid(row=0, column=0, sticky=(tk.W, tk.E)) ttk.Button(browse_frame, text="扫描项目目录", command=self.scan_project_files).grid(row=0, column=1, padx=(10, 0)) # JSON文件选择区域 json_frame = ttk.LabelFrame(left_frame, text="JSON输出路径选择", padding="5") json_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(10, 0)) json_frame.columnconfigure(1, weight=1) ttk.Label(json_frame, text="输出路径:").grid(row=0, column=0, sticky=tk.W) self.json_path_var = tk.StringVar() self.json_path_entry = ttk.Entry(json_frame, textvariable=self.json_path_var, state='readonly') self.json_path_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0)) ttk.Button(json_frame, text="浏览", command=self.browse_json_path).grid(row=0, column=2, padx=(5, 0)) # 配置选项 config_frame = ttk.LabelFrame(left_frame, text="配置选项", padding="5") config_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=(10, 0)) # 表格格式选择 ttk.Label(config_frame, text="表格格式:").grid(row=0, column=0, sticky=tk.W) self.format_var = tk.StringVar(value="horizontal") format_frame = ttk.Frame(config_frame) format_frame.grid(row=0, column=1, sticky=tk.W, padx=(5, 0)) ttk.Radiobutton(format_frame, text="横向", variable=self.format_var, value="horizontal").pack(side=tk.LEFT) ttk.Radiobutton(format_frame, text="纵向", variable=self.format_var, value="vertical").pack(side=tk.LEFT, padx=(10, 0)) # 多工作表选项 self.multi_sheet_var = tk.BooleanVar() ttk.Checkbutton(config_frame, text="多工作表文件", variable=self.multi_sheet_var, command=self.on_multi_sheet_change).grid(row=1, column=0, columnspan=2, sticky=tk.W, pady=(5, 0)) # 单个JSON选项 self.single_json_var = tk.BooleanVar(value=True) ttk.Checkbutton(config_frame, text="单个json", variable=self.single_json_var, command=self.on_single_json_change).grid(row=2, column=0, columnspan=2, sticky=tk.W, pady=(5, 0)) # 文件列表 list_frame = ttk.Frame(left_frame) list_frame.grid(row=3, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) list_frame.columnconfigure(0, weight=1) list_frame.rowconfigure(0, weight=1) self.file_listbox = tk.Listbox(list_frame, selectmode=tk.MULTIPLE, height=15) scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.file_listbox.yview) self.file_listbox.configure(yscrollcommand=scrollbar.set) self.file_listbox.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S)) # 文件操作按钮 file_btn_frame = ttk.Frame(left_frame) file_btn_frame.grid(row=4, column=0, sticky=(tk.W, tk.E), pady=(10, 0)) ttk.Button(file_btn_frame, text="预览选中文件", command=self.preview_selected_files).pack(side=tk.LEFT) ttk.Button(file_btn_frame, text="清空选择", command=self.clear_selection).pack(side=tk.LEFT, padx=(10, 0)) # 右侧面板 - 配置预览和操作 right_frame = ttk.LabelFrame(main_frame, text="配置预览与操作", padding="10") right_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S)) right_frame.columnconfigure(0, weight=1) right_frame.rowconfigure(0, weight=1) # 配置预览文本框 self.preview_text = scrolledtext.ScrolledText(right_frame, height=20, width=50) self.preview_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10)) # 操作按钮 - 移除了导入配置、备份当前配置、恢复默认配置按钮 btn_frame = ttk.Frame(right_frame) btn_frame.grid(row=1, column=0, sticky=(tk.W, tk.E)) # 球控制器配置按钮 ball_controller_btn_frame = ttk.Frame(right_frame) ball_controller_btn_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=(10, 0)) if BALL_CONTROLLER_MANAGER_AVAILABLE: ttk.Button(ball_controller_btn_frame, text="导入球控制器配置", command=self.import_ball_controller_config).pack(side=tk.LEFT) else: ttk.Label(ball_controller_btn_frame, text="球控制器配置管理器不可用", foreground="red").pack(side=tk.LEFT) # 局外技能配置按钮 skill_config_btn_frame = ttk.Frame(right_frame) skill_config_btn_frame.grid(row=3, column=0, sticky=(tk.W, tk.E), pady=(10, 0)) if SKILL_CONFIG_MANAGER_AVAILABLE: ttk.Button(skill_config_btn_frame, text="导入局外技能配置", command=self.import_skill_config_new).pack(side=tk.LEFT) else: ttk.Label(skill_config_btn_frame, text="局外技能配置管理器不可用", foreground="red").pack(side=tk.LEFT) # 游戏内技能配置按钮 game_skill_btn_frame = ttk.Frame(right_frame) game_skill_btn_frame.grid(row=4, column=0, sticky=(tk.W, tk.E), pady=(10, 0)) if GAME_SKILL_MANAGER_AVAILABLE: ttk.Button(game_skill_btn_frame, text="导入游戏内技能配置", command=self.import_game_skill_config).pack(side=tk.LEFT) else: ttk.Label(game_skill_btn_frame, text="游戏内技能配置管理器不可用", foreground="red").pack(side=tk.LEFT) # 武器配置专用按钮 weapon_btn_frame = ttk.Frame(right_frame) weapon_btn_frame.grid(row=5, column=0, sticky=(tk.W, tk.E), pady=(10, 0)) if WEAPON_MANAGER_AVAILABLE: ttk.Button(weapon_btn_frame, text="导入武器配置", command=self.import_weapon_config).pack(side=tk.LEFT) else: ttk.Label(weapon_btn_frame, text="武器配置管理器不可用", foreground="red").pack(side=tk.LEFT) # 敌人配置专用按钮 enemy_btn_frame = ttk.Frame(right_frame) enemy_btn_frame.grid(row=6, column=0, sticky=(tk.W, tk.E), pady=(10, 0)) if ENEMY_MANAGER_AVAILABLE: ttk.Button(enemy_btn_frame, text="导入敌人配置", command=self.import_enemy_config).pack(side=tk.LEFT) else: ttk.Label(enemy_btn_frame, text="敌人配置管理器不可用", foreground="red").pack(side=tk.LEFT) # 关卡配置专用按钮 level_btn_frame = ttk.Frame(right_frame) level_btn_frame.grid(row=7, column=0, sticky=(tk.W, tk.E), pady=(10, 0)) if LEVEL_MANAGER_AVAILABLE: ttk.Button(level_btn_frame, text="导入关卡配置", command=self.import_level_config).pack(side=tk.LEFT) else: ttk.Label(level_btn_frame, text="关卡配置管理器不可用", foreground="red").pack(side=tk.LEFT) # 商店配置专用按钮 shop_btn_frame = ttk.Frame(right_frame) shop_btn_frame.grid(row=8, column=0, sticky=(tk.W, tk.E), pady=(10, 0)) if SHOP_MANAGER_AVAILABLE: ttk.Button(shop_btn_frame, text="导入商店配置", command=self.import_shop_config).pack(side=tk.LEFT) else: ttk.Label(shop_btn_frame, text="商店配置管理器不可用", foreground="red").pack(side=tk.LEFT) # 小球价格配置专用按钮 ball_price_btn_frame = ttk.Frame(right_frame) ball_price_btn_frame.grid(row=9, column=0, sticky=(tk.W, tk.E), pady=(10, 0)) if BALL_PRICE_MANAGER_AVAILABLE: ttk.Button(ball_price_btn_frame, text="导入小球价格配置", command=self.import_ball_price_config).pack(side=tk.LEFT) else: ttk.Label(ball_price_btn_frame, text="小球价格配置管理器不可用", foreground="red").pack(side=tk.LEFT) # 底部状态栏 self.status_var = tk.StringVar() self.status_var.set("就绪") status_bar = ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.grid(row=5, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(10, 0)) # 绑定事件 self.file_listbox.bind('<>', self.on_file_select) def browse_files(self): """浏览Excel/CSV文件""" if PANDAS_AVAILABLE: title = "选择Excel/CSV配置文件" filetypes = [("Excel文件", "*.xlsx *.xls"), ("CSV文件", "*.csv"), ("文本文件", "*.txt"), ("所有文件", "*.*")] else: title = "选择CSV配置文件" filetypes = [("CSV文件", "*.csv"), ("文本文件", "*.txt"), ("所有文件", "*.*")] files = filedialog.askopenfilenames( title=title, filetypes=filetypes, initialdir=str(self.excel_dir) ) if files: self.file_listbox.delete(0, tk.END) for file in files: self.file_listbox.insert(tk.END, file) self.status_var.set(f"已选择 {len(files)} 个文件") def scan_project_files(self): """扫描项目目录中的Excel/CSV文件""" self.status_var.set("正在扫描项目目录...") def scan_thread(): config_files = [] # 扫描常见的配置目录 scan_dirs = [ self.project_root / "assets/data", self.project_root / "assets/data/excel", self.project_root / "assets/excel", self.project_root / "config", self.project_root / "data", self.project_root ] # 根据pandas可用性选择扫描的文件类型 patterns = ['*.xlsx', '*.xls', '*.csv', '*.txt'] if PANDAS_AVAILABLE else ['*.csv', '*.txt'] for scan_dir in scan_dirs: if scan_dir.exists(): for pattern in patterns: config_files.extend(scan_dir.rglob(pattern)) # 更新UI self.root.after(0, self.update_file_list, config_files) threading.Thread(target=scan_thread, daemon=True).start() def browse_json_path(self): """浏览JSON输出路径""" # 根据用户选择的选项组合决定选择文件还是目录 multi_sheet = self.multi_sheet_var.get() single_json = self.single_json_var.get() if multi_sheet and not single_json: # 多工作表文件且不是单个JSON:输出到目录(每个sheet一个JSON文件) path = filedialog.askdirectory( title="选择JSON输出目录", initialdir=str(self.project_root / "assets/data") ) else: # 其他情况:输出单个JSON文件 path = filedialog.askopenfilename( title="选择JSON输出文件", filetypes=[("JSON文件", "*.json"), ("所有文件", "*.*")], initialdir=str(self.project_root / "assets/data") ) if path: self.json_path_var.set(path) self.selected_json_path = Path(path) self.status_var.set(f"已选择输出路径: {Path(path).name}") def on_multi_sheet_change(self): """多工作表选项变化处理""" # 清空当前选择的JSON路径,让用户重新选择 self.json_path_var.set("") def on_single_json_change(self): """单个JSON选项变化处理""" # 清空当前选择的JSON路径,让用户重新选择 self.json_path_var.set("") self.selected_json_path = None if self.multi_sheet_var.get(): self.status_var.set("多工作表模式:请选择输出目录") else: self.status_var.set("单工作表模式:请选择输出JSON文件") def update_file_list(self, files): """更新文件列表""" self.file_listbox.delete(0, tk.END) for file in files: self.file_listbox.insert(tk.END, str(file)) file_type_text = "Excel/CSV文件" if PANDAS_AVAILABLE else "CSV文件" self.status_var.set(f"找到 {len(files)} 个{file_type_text}") def on_file_select(self, event): """文件选择事件处理""" selection = self.file_listbox.curselection() if selection: self.selected_files = [self.file_listbox.get(i) for i in selection] # 根据选择的文件设置配置映射 self._set_config_mapping_for_files() self.preview_selected_files() self.status_var.set(f"已选择 {len(selection)} 个文件") def preview_selected_files(self): """预览选择的文件内容""" if not self.selected_files: self.preview_text.delete(1.0, tk.END) return preview_content = "" for file_path in self.selected_files: try: file_path_obj = Path(file_path) preview_content += f"文件: {file_path_obj.name}\n" preview_content += f"路径: {file_path}\n" if PANDAS_AVAILABLE and file_path.endswith(('.xlsx', '.xls')): # Excel文件预览 try: xl_file = pd.ExcelFile(file_path) preview_content += f"工作表: {', '.join(xl_file.sheet_names)}\n" # 预览第一个工作表的前几行 first_sheet = xl_file.sheet_names[0] df = pd.read_excel(file_path, sheet_name=first_sheet, nrows=5) preview_content += f"\n{first_sheet} 预览 (前5行):\n" preview_content += df.to_string(index=False) + "\n" except Exception as e: preview_content += f"Excel预览失败: {e}\n" elif file_path.endswith(('.csv', '.txt')): # CSV/TXT文件预览 try: with open(file_path, 'r', encoding='utf-8') as f: lines = [f.readline().strip() for _ in range(5)] preview_content += "文件预览 (前5行):\n" preview_content += "\n".join(lines) + "\n" except Exception as e: preview_content += f"文件预览失败: {e}\n" preview_content += "-" * 50 + "\n\n" except Exception as e: preview_content += f"处理文件 {file_path} 时出错: {e}\n\n" # 更新预览文本框 self.preview_text.delete(1.0, tk.END) self.preview_text.insert(1.0, preview_content) def _set_config_mapping_for_files(self): """根据选择的文件设置配置映射""" if not self.selected_files: return # 获取第一个文件名来判断配置类型 first_file = Path(self.selected_files[0]) filename = first_file.name.lower() # 根据文件名设置配置映射 if '武器' in filename or 'weapon' in filename: self.current_mapping = { 'format_type': 'horizontal', 'param_types': { 'ID': str, '名称': str, '伤害': int, '射程': int, '射速': float, '弹药容量': int, '重装时间': float, '解锁等级': int, '价格': int, '描述': str, '最大等级': int, # 英文字段支持 'weaponId': str, 'name': str, 'damage': int, 'range': int, 'fireRate': float, 'ammoCapacity': int, 'reloadTime': float, 'unlockLevel': int, 'price': int, 'description': str, 'maxLevel': int } } elif '敌人' in filename or 'enemy' in filename: self.current_mapping = { 'format_type': 'multi_sheet', 'multi_sheet': True, 'param_types': { # 基础配置字段 '敌人ID': ('id', str), '敌人名称': ('name', str), '敌人类型': ('type', str), '生命值': ('stats.health', int), '最大生命值': ('stats.maxHealth', int), '防御力': ('stats.defense', int), '移动速度': ('stats.speed', float), # 战斗配置字段 '攻击伤害': ('combat.attackDamage', int), '攻击范围': ('combat.attackRange', int), '攻击速度': ('combat.attackSpeed', float), '能否格挡': ('combat.canBlock', bool), '格挡几率': ('combat.blockChance', float), '格挡伤害减免': ('combat.blockDamageReduction', float), '攻击冷却': ('combat.attackCooldown', float), '攻击类型': ('combat.attackType', str), '攻击延迟': ('combat.attackDelay', float), '武器类型': ('combat.weaponType', str), '投射物类型': ('combat.projectileType', str), '投射物速度': ('combat.projectileSpeed', float), # 移动配置字段 '移动模式': ('movement.pattern', str), '巡逻范围': ('movement.patrolRange', int), '旋转速度': ('movement.rotationSpeed', float), '移动类型': ('movement.moveType', str), '摆动幅度': ('movement.swingAmplitude', float), '摆动频率': ('movement.swingFrequency', float), '速度变化': ('movement.speedVariation', float), # 视觉配置字段 '精灵路径': ('visualConfig.spritePath', str), '缩放比例': ('visualConfig.scale', float), '动画速度': ('visualConfig.animationSpeed', float), '水平翻转': ('visualConfig.flipX', bool), '待机动画': ('visualConfig.animations.idle', str), '行走动画': ('visualConfig.animations.walk', str), '攻击动画': ('visualConfig.animations.attack', str), '死亡动画': ('visualConfig.animations.death', str), '武器道具': ('visualConfig.weaponProp', str), # 音频配置字段 '攻击音效': ('audioConfig.attackSound', str), '死亡音效': ('audioConfig.deathSound', str), '受击音效': ('audioConfig.hitSound', str), '行走音效': ('audioConfig.walkSound', str), '格挡音效': ('audioConfig.blockSound', str), '隐身音效': ('audioConfig.stealthSound', str), '护甲破碎音效': ('audioConfig.armorBreakSound', str), '引信音效': ('audioConfig.fuseSound', str), # 特殊能力配置字段 '能力类型': ('specialAbilities.type', str), '伤害': ('specialAbilities.damage', int), '范围': ('specialAbilities.range', float), '冷却时间': ('specialAbilities.cooldown', float), # BOSS配置字段 '是否BOSS': ('bossConfig.isBoss', bool), '阶段数': ('bossConfig.phases', int), '狂暴阈值': ('bossConfig.enrageThreshold', float), '狂暴伤害倍数': ('bossConfig.enrageDamageMultiplier', float), '狂暴速度倍数': ('bossConfig.enrageSpeedMultiplier', float), # 英文字段支持 'id': ('id', str), 'name': ('name', str), 'type': ('type', str), 'health': ('stats.health', int), 'maxHealth': ('stats.maxHealth', int), 'defense': ('stats.defense', int), 'speed': ('stats.speed', float), 'attackDamage': ('combat.attackDamage', int), 'attackRange': ('combat.attackRange', int), 'attackSpeed': ('combat.attackSpeed', float) } } elif '技能' in filename or 'skill' in filename: self.current_mapping = { 'format_type': 'horizontal', 'param_types': { 'ID': str, '名称': str, '伤害': int, '冷却时间': float, '消耗': int, '描述': str, # 英文字段支持 'skillId': str, 'skillName': str, 'damage': int, 'cooldown': float, 'cost': int, 'description': str } } elif '关卡' in filename or 'level' in filename: self.current_mapping = { 'format_type': 'horizontal', 'param_types': { 'ID': str, '名称': str, '敌人列表': str, '波数': int, '难度': int, # 英文字段支持 'levelId': str, 'name': str, 'enemies': str, 'waves': int, 'difficulty': int } } elif 'ballcontroller' in filename or '球控制' in filename or '标准配置表' in filename: # BallController配置映射(纵向格式:参数名-数值) self.current_mapping = { 'format_type': 'vertical', 'param_types': { 'baseSpeed': float, 'maxReflectionRandomness': float, 'antiTrapTimeWindow': float, 'antiTrapHitThreshold': int, 'deflectionAttemptThreshold': int, 'antiTrapDeflectionMultiplier': float, 'FIRE_COOLDOWN': float, 'ballRadius': float, 'gravityScale': float, 'linearDamping': float, 'angularDamping': float, 'colliderGroup': int, 'colliderTag': int, 'friction': float, 'restitution': float, 'safeDistance': float, 'edgeOffset': float, 'sensor': bool, 'maxAttempts': int } } else: # 默认配置映射 self.current_mapping = { 'format_type': 'horizontal', 'param_types': {} } print(f"为文件 {filename} 设置配置映射: {self.current_mapping['format_type']} 格式") print(f"支持的参数类型: {list(self.current_mapping['param_types'].keys())}") def clear_selection(self): """清空选择""" self.file_listbox.selection_clear(0, tk.END) self.preview_text.delete(1.0, tk.END) self.config_data = {} self.status_var.set("已清空选择") self.load_current_config() def load_current_config(self): """加载当前配置""" try: if not self.selected_json_path: self.preview_text.insert(tk.END, "请先选择JSON输出路径\n") return json_path = Path(self.selected_json_path) if json_path.exists(): with open(json_path, 'r', encoding='utf-8') as f: current_config = json.load(f) self.preview_text.insert(tk.END, f"=== 当前配置 ({json_path.name}) ===\n") # 根据配置类型显示不同的预览格式 if self.format_var.get() == 'horizontal': # 横向表格配置(如敌人、武器) if 'enemies' in current_config: self.preview_text.insert(tk.END, f"敌人配置 ({len(current_config['enemies'])} 个):\n") for i, enemy in enumerate(current_config['enemies'][:5]): # 只显示前5个 self.preview_text.insert(tk.END, f" {i+1}. {enemy.get('name', enemy.get('id', 'Unknown'))}\n") if len(current_config['enemies']) > 5: self.preview_text.insert(tk.END, f" ... 还有 {len(current_config['enemies']) - 5} 个\n") if 'weapons' in current_config: self.preview_text.insert(tk.END, f"\n武器配置 ({len(current_config['weapons'])} 个):\n") for i, weapon in enumerate(current_config['weapons'][:5]): # 只显示前5个 self.preview_text.insert(tk.END, f" {i+1}. {weapon.get('name', weapon.get('id', 'Unknown'))}\n") if len(current_config['weapons']) > 5: self.preview_text.insert(tk.END, f" ... 还有 {len(current_config['weapons']) - 5} 个\n") else: # 纵向表格配置(如BallController) self.preview_text.insert(tk.END, json.dumps(current_config, indent=2, ensure_ascii=False)) file_hint = "Excel/CSV文件" if PANDAS_AVAILABLE else "CSV文件" self.preview_text.insert(tk.END, f"\n\n请选择{file_hint}进行配置导入...\n") else: self.preview_text.insert(tk.END, "配置文件不存在\n") except Exception as e: self.preview_text.insert(tk.END, f"加载当前配置失败: {e}\n") def import_weapon_config(self): """导入武器配置""" try: if not WEAPON_MANAGER_AVAILABLE: messagebox.showerror("错误", "武器配置管理器不可用,请检查weapon_config_manager.py文件是否存在") return # 创建武器配置管理器 excel_dir = Path(self.excel_dir) weapon_excel_file = excel_dir / "方块武器配置" / "方块武器配置表.xlsx" weapon_json_file = excel_dir.parent / "weapons.json" if not weapon_excel_file.exists(): messagebox.showwarning("警告", f"未找到武器配置文件: {weapon_excel_file}") return # 使用WeaponConfigManager导入配置 weapon_manager = WeaponConfigManager( excel_file_path=str(weapon_excel_file), json_file_path=str(weapon_json_file) ) # 在后台线程中执行导入 def import_thread(): try: success = weapon_manager.import_weapon_config() # 在主线程中更新UI def update_ui(): if success: messagebox.showinfo("成功", "武器配置导入成功!") self.status_var.set("武器配置导入成功") # 更新预览文本 self.preview_text.delete(1.0, tk.END) self.preview_text.insert(tk.END, "武器配置导入成功\n\n配置已从Excel文件合并到JSON文件中") else: messagebox.showerror("错误", "武器配置导入失败,请查看控制台输出") self.status_var.set("武器配置导入失败") self.root.after(0, update_ui) except Exception as e: def show_error(): import traceback error_details = traceback.format_exc() print(f"导入武器配置失败,详细错误信息:") print(error_details) messagebox.showerror("错误", f"导入武器配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。") self.status_var.set("武器配置导入失败") self.root.after(0, show_error) # 启动导入线程 self.status_var.set("正在导入武器配置...") thread = threading.Thread(target=import_thread) thread.daemon = True thread.start() except Exception as e: import traceback error_details = traceback.format_exc() print(f"启动武器配置导入失败,详细错误信息:") print(error_details) messagebox.showerror("错误", f"启动武器配置导入失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。") def import_enemy_config(self): """导入敌人配置""" try: if not ENEMY_MANAGER_AVAILABLE: messagebox.showerror("错误", "敌人配置管理器不可用,请检查enemy_config_manager.py文件是否存在") return # 创建敌人配置管理器 excel_dir = Path(self.excel_dir) enemy_excel_file = excel_dir / "敌人配置表.xlsx" enemy_json_file = excel_dir.parent / "enemies.json" if not enemy_excel_file.exists(): messagebox.showwarning("警告", f"未找到敌人配置文件: {enemy_excel_file}") return # 使用EnemyConfigManager导入配置 enemy_manager = EnemyConfigManager( excel_path=str(enemy_excel_file), json_path=str(enemy_json_file) ) # 在后台线程中执行导入 def import_thread(): try: success = enemy_manager.import_config() # 在主线程中更新UI def update_ui(): if success: messagebox.showinfo("成功", "敌人配置导入成功!") self.status_var.set("敌人配置导入成功") # 更新预览文本 self.preview_text.delete(1.0, tk.END) self.preview_text.insert(tk.END, "敌人配置导入成功\n\n配置已从Excel文件合并到JSON文件中") else: messagebox.showerror("错误", "敌人配置导入失败,请查看控制台输出") self.status_var.set("敌人配置导入失败") self.root.after(0, update_ui) except Exception as e: def show_error(): import traceback error_details = traceback.format_exc() print(f"导入敌人配置失败,详细错误信息:") print(error_details) messagebox.showerror("错误", f"导入敌人配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。") self.status_var.set("敌人配置导入失败") self.root.after(0, show_error) # 启动导入线程 self.status_var.set("正在导入敌人配置...") thread = threading.Thread(target=import_thread) thread.daemon = True thread.start() except Exception as e: import traceback error_details = traceback.format_exc() print(f"启动敌人配置导入失败,详细错误信息:") print(error_details) messagebox.showerror("错误", f"启动敌人配置导入失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。") def import_level_config(self): """导入关卡配置""" try: if not LEVEL_MANAGER_AVAILABLE: messagebox.showerror("错误", "关卡配置管理器不可用,请检查level_config_manager.py文件是否存在") return # 创建关卡配置管理器 excel_dir = Path(self.excel_dir) level_excel_file = excel_dir / "关卡配置" / "关卡配置表.xlsx" levels_dir = excel_dir.parent / "levels" if not level_excel_file.exists(): messagebox.showwarning("警告", f"未找到关卡配置文件: {level_excel_file}") return if not levels_dir.exists(): messagebox.showwarning("警告", f"未找到关卡配置目录: {levels_dir}") return # 使用LevelConfigManager导入配置 level_manager = LevelConfigManager( excel_path=str(level_excel_file), levels_dir=str(levels_dir) ) # 在后台线程中执行导入 def import_thread(): try: success = level_manager.import_from_excel() # 在主线程中更新UI def update_ui(): if success: messagebox.showinfo("成功", "关卡配置导入成功!") self.status_var.set("关卡配置导入成功") # 更新预览文本 self.preview_text.delete(1.0, tk.END) self.preview_text.insert(tk.END, "关卡配置导入成功\n\n配置已从Excel文件合并到各个关卡JSON文件中") else: messagebox.showerror("错误", "关卡配置导入失败,请查看控制台输出") self.status_var.set("关卡配置导入失败") self.root.after(0, update_ui) except Exception as e: def show_error(): import traceback error_details = traceback.format_exc() print(f"导入关卡配置失败,详细错误信息:") print(error_details) messagebox.showerror("错误", f"导入关卡配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。") self.status_var.set("关卡配置导入失败") self.root.after(0, show_error) # 启动导入线程 self.status_var.set("正在导入关卡配置...") thread = threading.Thread(target=import_thread) thread.daemon = True thread.start() except Exception as e: import traceback error_details = traceback.format_exc() print(f"启动关卡配置导入失败,详细错误信息:") print(error_details) messagebox.showerror("错误", f"启动关卡配置导入失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。") def import_ball_controller_config(self): """导入球控制器配置""" try: if not BALL_CONTROLLER_MANAGER_AVAILABLE: messagebox.showerror("错误", "球控制器配置管理器不可用,请检查ball_controller_config_manager.py文件是否存在") return # 创建球控制器配置管理器 excel_dir = Path(self.excel_dir) ball_controller_excel_file = excel_dir / "BallController标准配置表.xlsx" ball_controller_json_file = excel_dir.parent / "ballController.json" if not ball_controller_excel_file.exists(): messagebox.showwarning("警告", f"未找到球控制器配置文件: {ball_controller_excel_file}") return # 使用BallControllerConfigManager导入配置 ball_controller_manager = BallControllerConfigManager( excel_file_path=str(ball_controller_excel_file), json_file_path=str(ball_controller_json_file) ) # 在后台线程中执行导入 def import_thread(): try: success = ball_controller_manager.import_ball_controller_config() # 在主线程中更新UI def update_ui(): if success: messagebox.showinfo("成功", "球控制器配置导入成功!") self.status_var.set("球控制器配置导入成功") # 更新预览文本 self.preview_text.delete(1.0, tk.END) self.preview_text.insert(tk.END, "球控制器配置导入成功\n\n配置已从Excel文件合并到JSON文件中") else: messagebox.showerror("错误", "球控制器配置导入失败,请查看控制台输出") self.status_var.set("球控制器配置导入失败") self.root.after(0, update_ui) except Exception as e: def show_error(): import traceback error_details = traceback.format_exc() print(f"导入球控制器配置失败,详细错误信息:") print(error_details) messagebox.showerror("错误", f"导入球控制器配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。") self.status_var.set("球控制器配置导入失败") self.root.after(0, show_error) # 启动导入线程 self.status_var.set("正在导入球控制器配置...") thread = threading.Thread(target=import_thread) thread.daemon = True thread.start() except Exception as e: import traceback error_details = traceback.format_exc() print(f"启动球控制器配置导入失败,详细错误信息:") print(error_details) messagebox.showerror("错误", f"启动球控制器配置导入失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。") def import_skill_config_new(self): """导入局外技能配置""" try: if not SKILL_CONFIG_MANAGER_AVAILABLE: messagebox.showerror("错误", "局外技能配置管理器不可用,请检查skill_config_manager.py文件是否存在") return # 创建局外技能配置管理器 excel_dir = Path(self.excel_dir) skill_excel_file = excel_dir / "局外技能配置表.xlsx" skill_json_file = excel_dir.parent / "skill_config.json" if not skill_excel_file.exists(): messagebox.showwarning("警告", f"未找到局外技能配置文件: {skill_excel_file}") return # 使用SkillConfigManager导入配置 skill_manager = SkillConfigManager( excel_file_path=str(skill_excel_file), json_file_path=str(skill_json_file) ) # 在后台线程中执行导入 def import_thread(): try: success = skill_manager.import_skill_config() # 在主线程中更新UI def update_ui(): if success: messagebox.showinfo("成功", "局外技能配置导入成功!") self.status_var.set("局外技能配置导入成功") # 更新预览文本 self.preview_text.delete(1.0, tk.END) self.preview_text.insert(tk.END, "局外技能配置导入成功\n\n配置已从Excel文件合并到JSON文件中") else: messagebox.showerror("错误", "局外技能配置导入失败,请查看控制台输出") self.status_var.set("局外技能配置导入失败") self.root.after(0, update_ui) except Exception as e: def show_error(): import traceback error_details = traceback.format_exc() print(f"导入局外技能配置失败,详细错误信息:") print(error_details) messagebox.showerror("错误", f"导入局外技能配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。") self.status_var.set("局外技能配置导入失败") self.root.after(0, show_error) # 启动导入线程 self.status_var.set("正在导入局外技能配置...") thread = threading.Thread(target=import_thread) thread.daemon = True thread.start() except Exception as e: import traceback error_details = traceback.format_exc() print(f"启动局外技能配置导入失败,详细错误信息:") print(error_details) messagebox.showerror("错误", f"启动局外技能配置导入失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。") def import_game_skill_config(self): """导入游戏内技能配置""" try: if not GAME_SKILL_MANAGER_AVAILABLE: messagebox.showerror("错误", "游戏内技能配置管理器不可用,请检查game_skill_config_manager.py文件是否存在") return # 创建游戏内技能配置管理器 excel_dir = Path(self.excel_dir) game_skill_excel_file = excel_dir / "技能配置表.xlsx" game_skill_json_file = excel_dir.parent / "skill.json" if not game_skill_excel_file.exists(): messagebox.showwarning("警告", f"未找到游戏内技能配置文件: {game_skill_excel_file}") return # 使用GameSkillConfigManager导入配置 game_skill_manager = GameSkillConfigManager( excel_file_path=str(game_skill_excel_file), json_file_path=str(game_skill_json_file) ) # 在后台线程中执行导入 def import_thread(): try: success = game_skill_manager.import_game_skill_config() # 在主线程中更新UI def update_ui(): if success: messagebox.showinfo("成功", "游戏内技能配置导入成功!") self.status_var.set("游戏内技能配置导入成功") # 更新预览文本 self.preview_text.delete(1.0, tk.END) self.preview_text.insert(tk.END, "游戏内技能配置导入成功\n\n配置已从Excel文件合并到JSON文件中") else: messagebox.showerror("错误", "游戏内技能配置导入失败,请查看控制台输出") self.status_var.set("游戏内技能配置导入失败") self.root.after(0, update_ui) except Exception as e: def show_error(): import traceback error_details = traceback.format_exc() print(f"导入游戏内技能配置失败,详细错误信息:") print(error_details) messagebox.showerror("错误", f"导入游戏内技能配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。") self.status_var.set("游戏内技能配置导入失败") self.root.after(0, show_error) # 启动导入线程 self.status_var.set("正在导入游戏内技能配置...") thread = threading.Thread(target=import_thread) thread.daemon = True thread.start() except Exception as e: import traceback error_details = traceback.format_exc() print(f"启动游戏内技能配置导入失败,详细错误信息:") print(error_details) messagebox.showerror("错误", f"启动游戏内技能配置导入失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。") def import_shop_config(self): """导入商店配置""" try: if not SHOP_MANAGER_AVAILABLE: messagebox.showerror("错误", "商店配置管理器不可用,请检查shop_config_manager.py文件是否存在") return # 创建商店配置管理器 excel_dir = Path(self.excel_dir) shop_excel_file = excel_dir / "商店配置" / "商店配置表.xlsx" shop_json_file = excel_dir.parent / "shop.json" if not shop_excel_file.exists(): messagebox.showwarning("警告", f"未找到商店配置文件: {shop_excel_file}") return # 使用ShopConfigManager导入配置 shop_manager = ShopConfigManager( excel_file_path=str(shop_excel_file), json_file_path=str(shop_json_file) ) # 在后台线程中执行导入 def import_thread(): try: success = shop_manager.import_config() # 在主线程中更新UI def update_ui(): if success: messagebox.showinfo("成功", "商店配置导入成功!") self.status_var.set("商店配置导入成功") # 更新预览文本 self.preview_text.delete(1.0, tk.END) self.preview_text.insert(tk.END, "商店配置导入成功\n\n配置已从Excel文件合并到JSON文件中") else: messagebox.showerror("错误", "商店配置导入失败,请查看控制台输出") self.status_var.set("商店配置导入失败") self.root.after(0, update_ui) except Exception as e: def show_error(): import traceback error_details = traceback.format_exc() print(f"导入商店配置失败,详细错误信息:") print(error_details) messagebox.showerror("错误", f"导入商店配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。") self.status_var.set("商店配置导入失败") self.root.after(0, show_error) # 启动导入线程 self.status_var.set("正在导入商店配置...") thread = threading.Thread(target=import_thread) thread.daemon = True thread.start() except Exception as e: import traceback error_details = traceback.format_exc() print(f"启动商店配置导入失败,详细错误信息:") print(error_details) messagebox.showerror("错误", f"启动商店配置导入失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。") def import_ball_price_config(self): """导入小球价格配置""" try: if not BALL_PRICE_MANAGER_AVAILABLE: messagebox.showerror("错误", "小球价格配置管理器不可用,请检查ball_price_config_manager.py文件是否存在") return # 创建小球价格配置管理器 excel_dir = Path(self.excel_dir) ball_price_excel_file = excel_dir / "小球价格配置" / "小球价格配置表.xlsx" ball_price_json_file = excel_dir.parent / "ball_price_config.json" if not ball_price_excel_file.exists(): messagebox.showwarning("警告", f"未找到小球价格配置文件: {ball_price_excel_file}") return # 使用BallPriceConfigManager导入配置 ball_price_manager = BallPriceConfigManager( excel_file_path=str(ball_price_excel_file), json_file_path=str(ball_price_json_file) ) # 在后台线程中执行导入 def import_thread(): try: success = ball_price_manager.import_config() # 在主线程中更新UI def update_ui(): if success: messagebox.showinfo("成功", "小球价格配置导入成功!") self.status_var.set("小球价格配置导入成功") # 更新预览文本 self.preview_text.delete(1.0, tk.END) self.preview_text.insert(tk.END, "小球价格配置导入成功\n\n配置已从Excel文件合并到JSON文件中") else: messagebox.showerror("错误", "小球价格配置导入失败,请查看控制台输出") self.status_var.set("小球价格配置导入失败") self.root.after(0, update_ui) except Exception as e: def show_error(): import traceback error_details = traceback.format_exc() print(f"导入小球价格配置失败,详细错误信息:") print(error_details) messagebox.showerror("错误", f"导入小球价格配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。") self.status_var.set("小球价格配置导入失败") self.root.after(0, show_error) # 启动导入线程 self.status_var.set("正在导入小球价格配置...") thread = threading.Thread(target=import_thread) thread.daemon = True thread.start() except Exception as e: import traceback error_details = traceback.format_exc() print(f"启动小球价格配置导入失败,详细错误信息:") print(error_details) messagebox.showerror("错误", f"启动小球价格配置导入失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。") def init_config_managers(self): """初始化新的配置管理器模块""" try: # 初始化武器配置管理器 if self.weapon_config_manager_class: self.weapon_config_manager = self.weapon_config_manager_class() print("武器配置管理器初始化成功") else: self.weapon_config_manager = None print("武器配置管理器不可用") # 初始化敌人配置管理器 if self.enemy_config_manager_class: self.enemy_config_manager = self.enemy_config_manager_class() print("敌人配置管理器初始化成功") else: self.enemy_config_manager = None print("敌人配置管理器不可用") # 初始化关卡配置管理器 if self.level_config_manager_class: self.level_config_manager = self.level_config_manager_class() print("关卡配置管理器初始化成功") else: self.level_config_manager = None print("关卡配置管理器不可用") # 初始化球控制器配置管理器 if self.ball_controller_config_manager_class: self.ball_controller_config_manager = self.ball_controller_config_manager_class() print("球控制器配置管理器初始化成功") else: self.ball_controller_config_manager = None print("球控制器配置管理器不可用") # 初始化技能配置管理器 if self.skill_config_manager_class: self.skill_config_manager = self.skill_config_manager_class() print("技能配置管理器初始化成功") else: self.skill_config_manager = None print("技能配置管理器不可用") # 初始化游戏内技能配置管理器 if self.game_skill_config_manager_class: self.game_skill_config_manager = self.game_skill_config_manager_class() print("游戏内技能配置管理器初始化成功") else: self.game_skill_config_manager = None print("游戏内技能配置管理器不可用") except Exception as e: print(f"配置管理器初始化失败: {e}") def run(self): """运行应用""" self.root.mainloop() def main(): """主函数""" try: app = ConfigManagerGUI() app.run() except Exception as e: print(f"启动应用失败: {e}") input("按回车键退出...") if __name__ == "__main__": main()