From 3b491d7b3693622c5351adf06f0bcf62f5bbdcb1 Mon Sep 17 00:00:00 2001 From: Misaka Date: Wed, 11 Feb 2026 22:40:07 +0800 Subject: [PATCH] refactor: optimize settings and validation tabs for User users Material Validation Tab: - Remove file selection area (file_group and related frames) - Move output file path configuration to settings - For User users: hide data source selection, only show shared production IDs Settings Tab: - Add "validation output file" configuration to path settings group - Show "save settings" button for User users - Add User-only mode to only show path settings group Changes: - material_validation_tab.py: Restructure control panel, update export_paths - settings_tab.py: Add validation output filename, add save button for users Co-Authored-By: Claude Sonnet 4.5 --- gui/material_validation_tab.py | 129 +++++++++++---------------------- gui/settings_tab.py | 68 ++++++++++++++--- 2 files changed, 99 insertions(+), 98 deletions(-) diff --git a/gui/material_validation_tab.py b/gui/material_validation_tab.py index c92acfc..b7dd954 100644 --- a/gui/material_validation_tab.py +++ b/gui/material_validation_tab.py @@ -233,53 +233,13 @@ class MaterialValidationTab(ttk.Frame): # 普通用户:默认使用 database_filtered 模式 self.source_mode = tk.StringVar(value="database_filtered") - # 文件选择 - file_group = ttk.LabelFrame(parent, text="文件选择", padding=10) - file_group.pack(fill=tk.X, pady=5) - - # 数据库全表模式 - 无需输入文件 - self.db_full_frame = ttk.Frame(file_group) - ttk.Label( - self.db_full_frame, - text="数据库全表模式:将查询 DiscreteMaterialPlanData 表中的所有材料", - foreground="gray" - ).pack(anchor="w") - - # 数据库过滤模式 - 需要 ProductionID 文件 - self.db_filtered_frame = ttk.Frame(file_group) - - # 判断用户类型 - is_user_only = not self.session_manager.is_admin() - - if is_user_only: - # ========== User 模式:直接使用共享的 Production ID ========== - # 自动设置为使用共享数据源 - self.production_id_source_var = tk.StringVar(value="shared") - - # 创建共享 Production ID 显示标签(但不显示任何内容,只是用于更新) - self.shared_ids_info_label = ttk.Label( - self.db_filtered_frame, - text="", - foreground="gray" - ) - # 不 pack,保持隐藏状态 - - else: - # ========== Admin 模式:可选择文件或共享数据 ========== - # 共享 Production ID 提示标签 - self.shared_ids_label = ttk.Label( - self.db_filtered_frame, - text="💡 数据提取页面输入了 Production ID,可直接使用", - foreground="green", - font=("", 9) - ) - # 初始时不显示,等待 Production ID 更新 - - # Production ID 数据源选择 - self.production_id_source_var = tk.StringVar(value="file") - source_frame = ttk.Frame(self.db_filtered_frame) + # Production ID 数据源(仅 Admin) + if self.session_manager.is_admin(): + source_frame = ttk.Frame(parent) source_frame.pack(fill=tk.X, pady=5) + # Production ID 数据源选择 + self.production_id_source_var = tk.StringVar(value="shared") ttk.Radiobutton( source_frame, text="使用文件:", @@ -298,7 +258,7 @@ class MaterialValidationTab(ttk.Frame): # 文件选择器 self.db_filtered_production_id_selector = FileSelector( - self.db_filtered_frame, + parent, label_text="", file_type="file", file_types=[("文本文件", "*.txt"), ("所有文件", "*.*")], @@ -306,31 +266,14 @@ class MaterialValidationTab(ttk.Frame): ) self.db_filtered_production_id_selector.pack(fill=tk.X) - # 共享 Production ID 显示标签 + # 共享 Production ID 提示标签 self.shared_ids_info_label = ttk.Label( - self.db_filtered_frame, + parent, text="", foreground="blue" ) # 初始时不显示 - # 输出文件(对于所有用户都可见) - self.output_file_selector = FileSelector( - file_group, - label_text="输出文件:", - file_type="file", - file_types=[("Excel 文件", "*.xlsx"), ("所有文件", "*.*")], - initial_dir=self.config.get("paths.data_dir", "data/"), - ) - self.output_file_selector.pack(fill=tk.X, pady=(10, 0)) - - # 设置默认输出 - default_output = os.path.join( - self.config.get("paths.data_dir", "data/"), - self.config.get("paths.validation_output", "物料状态校验结果.xlsx"), - ) - self.output_file_selector.set(default_output) - # 控制按钮 button_frame = ttk.Frame(parent) button_frame.pack(fill=tk.X, pady=10) @@ -438,24 +381,21 @@ class MaterialValidationTab(ttk.Frame): def _on_source_mode_change(self): """数据源模式切换""" - # PERMISSION CHECK: 普通用户固定为 database_filtered 模式 + # PERMISSION CHECK: 仅管理员用户可以选择数据源模式 if not self.session_manager.is_admin(): - # 对于普通用户,pack 数据库过滤模式框架 - self.db_full_frame.pack_forget() - self.db_filtered_frame.pack(fill=tk.X, pady=(0, 5)) return mode = self.source_mode.get() - # 隐藏所有文件选择框架 - self.db_full_frame.pack_forget() - self.db_filtered_frame.pack_forget() - - # 根据模式显示对应的文件选择器 + # 根据模式显示 Production ID 数据源选择(仅 Admin) if mode == "database_full": - self.db_full_frame.pack(fill=tk.X, pady=(0, 5)) + # 全表模式:隐藏 Production ID 选择区域 + if hasattr(self, 'source_frame'): + self.source_frame.pack_forget() elif mode == "database_filtered": - self.db_filtered_frame.pack(fill=tk.X, pady=(0, 5)) + # 过滤模式:显示 Production ID 选择区域 + if hasattr(self, 'source_frame'): + self.source_frame.pack(fill=tk.X, pady=5) def _on_production_id_source_changed(self): """Production ID 数据源切换回调(仅 Admin 模式)""" @@ -818,10 +758,13 @@ class MaterialValidationTab(ttk.Frame): def start_validation(self): """开始校验""" - # 验证输入 - output_file = self.output_file_selector.get() + # 从配置获取输出文件路径 + output_file = os.path.join( + self.config.get("paths.data_dir", "data/"), + self.config.get("paths.validation_output", "物料状态校验结果.xlsx"), + ) if not output_file: - messagebox.showerror("错误", "请指定输出文件路径") + messagebox.showerror("错误", "请先在设置页面配置输出文件路径") return mode = self.source_mode.get() @@ -1232,17 +1175,29 @@ class MaterialValidationTab(ttk.Frame): def export_results(self): """导出结果到 Excel""" - output_file = self.output_file_selector.get() + # 从配置获取输出文件路径 + data_dir = self.config.get("paths.data_dir", "data/") + validation_filename = self.config.get("paths.validation_output", "物料状态校验结果.xlsx") + output_file = os.path.join(data_dir, validation_filename) - if not output_file: - output_file = filedialog.asksaveasfilename( - title="保存结果", - defaultextension=".xlsx", - filetypes=[("Excel 文件", "*.xlsx"), ("所有文件", "*.*")], + # 如果配置的文件不存在,提示用户选择位置 + if not os.path.exists(data_dir): + output_dir = filedialog.askdirectory( + title="选择输出目录", + initialdir=data_dir ) + if output_dir: + self.config.set("paths.data_dir", output_dir) + # 重新读取配置并构建输出文件路径 + data_dir = output_dir + output_file = os.path.join(data_dir, validation_filename) + else: + return - if not output_file: - return + # 如果文件已存在,提示用户覆盖 + if os.path.exists(output_file): + if not messagebox.askyesno("确认覆盖", f"文件已存在:{output_file}\n是否覆盖?"): + return try: # 收集表格数据 diff --git a/gui/settings_tab.py b/gui/settings_tab.py index 5531232..637a7b2 100644 --- a/gui/settings_tab.py +++ b/gui/settings_tab.py @@ -28,6 +28,10 @@ class SettingsTab(ttk.Frame): super().__init__(parent) self.config = config self.session_manager = session_manager + + # 初始化物料校验输出文件名变量 + self.validation_output_filename_var = tk.StringVar() + self.create_widgets() self.load_settings() @@ -58,19 +62,25 @@ class SettingsTab(ttk.Frame): self._create_extraction_group(scrollable_frame) self._create_validation_group(scrollable_frame) self._create_ui_group(scrollable_frame) + else: + # User 用户:只显示路径配置 + self._create_paths_group(scrollable_frame) # 按钮区域 - 根据用户类型显示不同按钮 button_frame = ttk.Frame(scrollable_frame) button_frame.grid(row=7, column=0, columnspan=2, pady=20, sticky="ew") if is_user_only: - # 普通用户只显示测试按钮 + # User 用户:显示测试按钮和保存设置按钮 ttk.Button( button_frame, text="测试 ERP 连接", command=self.test_erp_connection ).pack(side="left", padx=5) ttk.Button( button_frame, text="测试数据库连接", command=self.test_db_connection ).pack(side="left", padx=5) + ttk.Button(button_frame, text="保存设置", command=self.save_settings).pack( + side="left", padx=5 + ) else: # 管理员显示所有按钮 ttk.Button( @@ -234,7 +244,13 @@ class SettingsTab(ttk.Frame): ttk.Label(group, text="默认输出文件:").grid(row=2, column=0, sticky="w", pady=5) self.default_output_var = tk.StringVar() ttk.Entry(group, textvariable=self.default_output_var, width=40).grid( - row=3, column=0, columnspan=2, sticky="ew", pady=5 + row=2, column=1, columnspan=2, sticky="ew", pady=5 + ) + + # 校验输出文件 + ttk.Label(group, text="校验输出文件:").grid(row=3, column=0, sticky="w", pady=5) + ttk.Entry(group, textvariable=self.validation_output_filename_var, width=40).grid( + row=3, column=1, columnspan=2, sticky="ew", pady=5 ) group.columnconfigure(0, weight=1) @@ -298,15 +314,21 @@ class SettingsTab(ttk.Frame): group, text="使用数据库作为数据源", variable=self.validation_use_database_var ).grid(row=1, column=0, columnspan=2, sticky="w", pady=5) + # 输出文件名 + ttk.Label(group, text="输出文件名:").grid(row=2, column=0, sticky="w", pady=5) + ttk.Entry(group, textvariable=self.validation_output_filename_var, width=30).grid( + row=2, column=1, sticky="w", pady=5 + ) + # 批次大小 - ttk.Label(group, text="数据库批次大小:").grid(row=2, column=0, sticky="w", pady=5) + ttk.Label(group, text="数据库批次大小:").grid(row=3, column=0, sticky="w", pady=5) self.validation_batch_size_var = tk.IntVar(value=2000) ttk.Spinbox( group, from_=100, to=2000, textvariable=self.validation_batch_size_var, width=10 - ).grid(row=2, column=1, sticky="w", pady=5) + ).grid(row=3, column=1, sticky="w", pady=5) # 匹配模式 - ttk.Label(group, text="匹配模式:").grid(row=3, column=0, sticky="w", pady=5) + ttk.Label(group, text="匹配模式:").grid(row=4, column=0, sticky="w", pady=5) self.validation_match_mode_var = tk.StringVar() match_mode_combo = ttk.Combobox( group, @@ -315,19 +337,19 @@ class SettingsTab(ttk.Frame): state="readonly", width=30, ) - match_mode_combo.grid(row=3, column=1, sticky="w", pady=5) + match_mode_combo.grid(row=4, column=1, sticky="w", pady=5) # CRUD 操作 self.validation_enable_crud_var = tk.BooleanVar() ttk.Checkbutton( group, text="启用 CRUD 操作(管理待删除物料)", variable=self.validation_enable_crud_var - ).grid(row=4, column=0, columnspan=2, sticky="w", pady=5) + ).grid(row=5, column=0, columnspan=2, sticky="w", pady=5) # 默认负责人 - ttk.Label(group, text="默认负责人:").grid(row=5, column=0, sticky="w", pady=5) + ttk.Label(group, text="默认负责人:").grid(row=6, column=0, sticky="w", pady=5) self.validation_default_manager_var = tk.StringVar() ttk.Entry(group, textvariable=self.validation_default_manager_var, width=30).grid( - row=5, column=1, sticky="w", pady=5 + row=6, column=1, sticky="w", pady=5 ) group.columnconfigure(1, weight=1) @@ -371,8 +393,11 @@ class SettingsTab(ttk.Frame): is_user_only = self.session_manager and self.session_manager.get_user_type() == 'User' if is_user_only: - # 普通用户模式 - 只需要加载测试连接所需的配置 - # 不需要加载设置到界面,因为界面没有配置输入框 + # User 用户模式 - 只需要加载路径设置到界面 + # 路径设置 + self.data_dir_selector.set(self.config.get("paths.data_dir", "")) + self.default_output_var.set(self.config.get("paths.default_output", "")) + self.validation_output_filename_var.set(self.config.get("paths.validation_output", "")) return # 管理员模式 - 加载所有配置 @@ -420,6 +445,7 @@ class SettingsTab(ttk.Frame): # 校验设置 self.validation_data_source_var.set(self.config.get("validation.data_source", "database_full")) self.validation_use_database_var.set(self.config.get("validation.use_database", True)) + self.validation_output_filename_var.set(self.config.get("paths.validation_output", "物料状态校验结果.xlsx")) self.validation_batch_size_var.set(self.config.get("validation.batch_size", 2000)) self.validation_match_mode_var.set(self.config.get("validation.match_mode", "substring")) self.validation_enable_crud_var.set(self.config.get("validation.enable_crud_operations", False)) @@ -432,6 +458,25 @@ class SettingsTab(ttk.Frame): def save_settings(self): """保存界面设置到配置""" + # 判断是否为仅测试用户模式 + is_user_only = self.session_manager and self.session_manager.get_user_type() == 'User' + + if is_user_only: + # User 用户模式 - 只保存路径设置 + self.config.set("paths.data_dir", self.data_dir_selector.get()) + self.config.set("paths.default_output", self.default_output_var.get()) + self.config.set("paths.validation_output", self.validation_output_filename_var.get()) + + # 保存到文件 + if self.config.save(): + messagebox.showinfo("成功", "设置已保存") + # 通知其他标签页重新加载配置 + self._notify_config_reload() + else: + messagebox.showerror("错误", "保存设置失败") + return + + # 管理员模式 - 加载所有配置 # ERP 设置 self.config.set("erp.url", self.erp_url_var.get()) self.config.set("erp.username", self.erp_username_var.get()) @@ -473,6 +518,7 @@ class SettingsTab(ttk.Frame): # 校验设置 self.config.set("validation.data_source", self.validation_data_source_var.get()) self.config.set("validation.use_database", self.validation_use_database_var.get()) + self.config.set("paths.validation_output", self.validation_output_filename_var.get()) self.config.set("validation.batch_size", self.validation_batch_size_var.get()) self.config.set("validation.match_mode", self.validation_match_mode_var.get()) self.config.set("validation.enable_crud_operations", self.validation_enable_crud_var.get())