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 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-02-11 22:40:07 +08:00
parent 28d7db26db
commit 3b491d7b36
2 changed files with 99 additions and 98 deletions

View File

@@ -233,53 +233,13 @@ class MaterialValidationTab(ttk.Frame):
# 普通用户:默认使用 database_filtered 模式 # 普通用户:默认使用 database_filtered 模式
self.source_mode = tk.StringVar(value="database_filtered") self.source_mode = tk.StringVar(value="database_filtered")
# 文件选择 # Production ID 数据源(仅 Admin
file_group = ttk.LabelFrame(parent, text="文件选择", padding=10) if self.session_manager.is_admin():
file_group.pack(fill=tk.X, pady=5) source_frame = ttk.Frame(parent)
# 数据库全表模式 - 无需输入文件
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)
source_frame.pack(fill=tk.X, pady=5) source_frame.pack(fill=tk.X, pady=5)
# Production ID 数据源选择
self.production_id_source_var = tk.StringVar(value="shared")
ttk.Radiobutton( ttk.Radiobutton(
source_frame, source_frame,
text="使用文件:", text="使用文件:",
@@ -298,7 +258,7 @@ class MaterialValidationTab(ttk.Frame):
# 文件选择器 # 文件选择器
self.db_filtered_production_id_selector = FileSelector( self.db_filtered_production_id_selector = FileSelector(
self.db_filtered_frame, parent,
label_text="", label_text="",
file_type="file", file_type="file",
file_types=[("文本文件", "*.txt"), ("所有文件", "*.*")], file_types=[("文本文件", "*.txt"), ("所有文件", "*.*")],
@@ -306,31 +266,14 @@ class MaterialValidationTab(ttk.Frame):
) )
self.db_filtered_production_id_selector.pack(fill=tk.X) self.db_filtered_production_id_selector.pack(fill=tk.X)
# 共享 Production ID 示标签 # 共享 Production ID 示标签
self.shared_ids_info_label = ttk.Label( self.shared_ids_info_label = ttk.Label(
self.db_filtered_frame, parent,
text="", text="",
foreground="blue" 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 = ttk.Frame(parent)
button_frame.pack(fill=tk.X, pady=10) button_frame.pack(fill=tk.X, pady=10)
@@ -438,24 +381,21 @@ class MaterialValidationTab(ttk.Frame):
def _on_source_mode_change(self): def _on_source_mode_change(self):
"""数据源模式切换""" """数据源模式切换"""
# PERMISSION CHECK: 普通用户固定为 database_filtered 模式 # PERMISSION CHECK: 仅管理员用户可以选择数据源模式
if not self.session_manager.is_admin(): 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 return
mode = self.source_mode.get() mode = self.source_mode.get()
# 隐藏所有文件选择框架 # 根据模式显示 Production ID 数据源选择(仅 Admin
self.db_full_frame.pack_forget()
self.db_filtered_frame.pack_forget()
# 根据模式显示对应的文件选择器
if mode == "database_full": 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": 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): def _on_production_id_source_changed(self):
"""Production ID 数据源切换回调(仅 Admin 模式)""" """Production ID 数据源切换回调(仅 Admin 模式)"""
@@ -818,10 +758,13 @@ class MaterialValidationTab(ttk.Frame):
def start_validation(self): 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: if not output_file:
messagebox.showerror("错误", "指定输出文件路径") messagebox.showerror("错误", "先在设置页面配置输出文件路径")
return return
mode = self.source_mode.get() mode = self.source_mode.get()
@@ -1232,17 +1175,29 @@ class MaterialValidationTab(ttk.Frame):
def export_results(self): def export_results(self):
"""导出结果到 Excel""" """导出结果到 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( if not os.path.exists(data_dir):
title="保存结果", output_dir = filedialog.askdirectory(
defaultextension=".xlsx", title="选择输出目录",
filetypes=[("Excel 文件", "*.xlsx"), ("所有文件", "*.*")], 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: try:
# 收集表格数据 # 收集表格数据

View File

@@ -28,6 +28,10 @@ class SettingsTab(ttk.Frame):
super().__init__(parent) super().__init__(parent)
self.config = config self.config = config
self.session_manager = session_manager self.session_manager = session_manager
# 初始化物料校验输出文件名变量
self.validation_output_filename_var = tk.StringVar()
self.create_widgets() self.create_widgets()
self.load_settings() self.load_settings()
@@ -58,19 +62,25 @@ class SettingsTab(ttk.Frame):
self._create_extraction_group(scrollable_frame) self._create_extraction_group(scrollable_frame)
self._create_validation_group(scrollable_frame) self._create_validation_group(scrollable_frame)
self._create_ui_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 = ttk.Frame(scrollable_frame)
button_frame.grid(row=7, column=0, columnspan=2, pady=20, sticky="ew") button_frame.grid(row=7, column=0, columnspan=2, pady=20, sticky="ew")
if is_user_only: if is_user_only:
# 普通用户显示测试按钮 # User 用户显示测试按钮和保存设置按钮
ttk.Button( ttk.Button(
button_frame, text="测试 ERP 连接", command=self.test_erp_connection button_frame, text="测试 ERP 连接", command=self.test_erp_connection
).pack(side="left", padx=5) ).pack(side="left", padx=5)
ttk.Button( ttk.Button(
button_frame, text="测试数据库连接", command=self.test_db_connection button_frame, text="测试数据库连接", command=self.test_db_connection
).pack(side="left", padx=5) ).pack(side="left", padx=5)
ttk.Button(button_frame, text="保存设置", command=self.save_settings).pack(
side="left", padx=5
)
else: else:
# 管理员显示所有按钮 # 管理员显示所有按钮
ttk.Button( ttk.Button(
@@ -234,7 +244,13 @@ class SettingsTab(ttk.Frame):
ttk.Label(group, text="默认输出文件:").grid(row=2, column=0, sticky="w", pady=5) ttk.Label(group, text="默认输出文件:").grid(row=2, column=0, sticky="w", pady=5)
self.default_output_var = tk.StringVar() self.default_output_var = tk.StringVar()
ttk.Entry(group, textvariable=self.default_output_var, width=40).grid( 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) group.columnconfigure(0, weight=1)
@@ -298,15 +314,21 @@ class SettingsTab(ttk.Frame):
group, text="使用数据库作为数据源", variable=self.validation_use_database_var group, text="使用数据库作为数据源", variable=self.validation_use_database_var
).grid(row=1, column=0, columnspan=2, sticky="w", pady=5) ).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) self.validation_batch_size_var = tk.IntVar(value=2000)
ttk.Spinbox( ttk.Spinbox(
group, from_=100, to=2000, textvariable=self.validation_batch_size_var, width=10 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() self.validation_match_mode_var = tk.StringVar()
match_mode_combo = ttk.Combobox( match_mode_combo = ttk.Combobox(
group, group,
@@ -315,19 +337,19 @@ class SettingsTab(ttk.Frame):
state="readonly", state="readonly",
width=30, 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 操作 # CRUD 操作
self.validation_enable_crud_var = tk.BooleanVar() self.validation_enable_crud_var = tk.BooleanVar()
ttk.Checkbutton( ttk.Checkbutton(
group, text="启用 CRUD 操作(管理待删除物料)", variable=self.validation_enable_crud_var 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() self.validation_default_manager_var = tk.StringVar()
ttk.Entry(group, textvariable=self.validation_default_manager_var, width=30).grid( 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) 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' is_user_only = self.session_manager and self.session_manager.get_user_type() == 'User'
if is_user_only: 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 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_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_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_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_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)) 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): 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 设置 # ERP 设置
self.config.set("erp.url", self.erp_url_var.get()) self.config.set("erp.url", self.erp_url_var.get())
self.config.set("erp.username", self.erp_username_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.data_source", self.validation_data_source_var.get())
self.config.set("validation.use_database", self.validation_use_database_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.batch_size", self.validation_batch_size_var.get())
self.config.set("validation.match_mode", self.validation_match_mode_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()) self.config.set("validation.enable_crud_operations", self.validation_enable_crud_var.get())