feat: add Production ID input widget and UI configuration

- Add ProductionIdInput widget with placeholder and multi-line support
- Add UIConfig class for font family, font size, and input width settings
- Refactor data extraction tab to use horizontal PanedWindow layout
  - Left panel: Production ID text input (draggable width)
  - Right panel: control panel and log output
- Share Production IDs between data extraction and material validation tabs
- Add UI settings group in settings page (font selection, size, input width)
- For User users: automatically use shared Production IDs, simplified UI
- Apply font settings to input and log widgets
- Use sashpos() to set initial pane width correctly

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-02-11 21:47:17 +08:00
parent 62f323d420
commit 6f8df2f2e6
8 changed files with 561 additions and 96 deletions

View File

@@ -131,7 +131,7 @@ class CheckboxTreeview(ttk.Treeview):
class MaterialValidationTab(ttk.Frame):
"""物料校验标签页"""
def __init__(self, parent, config: ConfigManager, session_manager):
def __init__(self, parent, config: ConfigManager, session_manager, main_window=None):
"""
初始化物料校验标签页
@@ -139,13 +139,16 @@ class MaterialValidationTab(ttk.Frame):
parent: 父容器
config: 配置管理器
session_manager: SessionManager 实例,用于权限控制
main_window: 主窗口引用,用于获取共享的 Production ID
"""
super().__init__(parent)
self.config = config
self.session_manager = session_manager
self.main_window = main_window
self.validating = False
self.validation_results = None
self.material_records_cache = None # 缓存完整的物料记录
self.shared_production_ids = [] # 共享的 Production ID 列表
# 负责人筛选相关
self.managers: List[str] = [] # 可用负责人列表
@@ -244,14 +247,72 @@ class MaterialValidationTab(ttk.Frame):
# 数据库过滤模式 - 需要 ProductionID 文件
self.db_filtered_frame = ttk.Frame(file_group)
self.db_filtered_production_id_selector = FileSelector(
self.db_filtered_frame,
label_text="ProductionID 文件:",
file_type="file",
file_types=[("文本文件", "*.txt"), ("所有文件", "*.*")],
initial_dir="D:/python/playwrite/",
)
self.db_filtered_production_id_selector.pack(fill=tk.X)
# 判断用户类型
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)
ttk.Radiobutton(
source_frame,
text="使用文件:",
variable=self.production_id_source_var,
value="file",
command=self._on_production_id_source_changed
).pack(side=tk.LEFT)
ttk.Radiobutton(
source_frame,
text="使用数据提取页面的 Production ID:",
variable=self.production_id_source_var,
value="shared",
command=self._on_production_id_source_changed
).pack(side=tk.LEFT, padx=10)
# 文件选择器
self.db_filtered_production_id_selector = FileSelector(
self.db_filtered_frame,
label_text="",
file_type="file",
file_types=[("文本文件", "*.txt"), ("所有文件", "*.*")],
initial_dir="D:/python/playwrite/",
)
self.db_filtered_production_id_selector.pack(fill=tk.X)
# 共享 Production ID 显示标签
self.shared_ids_info_label = ttk.Label(
self.db_filtered_frame,
text="",
foreground="blue"
)
# 初始时不显示
# 输出文件(对于所有用户都可见)
self.output_file_selector = FileSelector(
@@ -396,6 +457,56 @@ class MaterialValidationTab(ttk.Frame):
elif mode == "database_filtered":
self.db_filtered_frame.pack(fill=tk.X, pady=(0, 5))
def _on_production_id_source_changed(self):
"""Production ID 数据源切换回调(仅 Admin 模式)"""
# User 模式下没有文件选择器,直接返回
if not self.session_manager.is_admin():
return
source = self.production_id_source_var.get()
if source == "shared":
# 使用共享 Production ID
self.db_filtered_production_id_selector.pack_forget()
self.shared_ids_info_label.pack(anchor="w", pady=(0, 5))
else:
# 使用文件
self.shared_ids_info_label.pack_forget()
self.db_filtered_production_id_selector.pack(fill=tk.X)
def on_production_ids_updated(self, production_ids: list):
"""当数据提取页面的 Production ID 更新时调用"""
self.shared_production_ids = production_ids
is_user_only = not self.session_manager.is_admin()
# User 模式:静默更新,不显示任何提示
if is_user_only:
return
# Admin 模式:显示提示信息
if production_ids:
count = len(production_ids)
preview = ", ".join(production_ids[:3])
if count > 3:
preview += f" ... (共 {count} 个)"
self.shared_ids_info_label.config(text=f"📋 {preview}")
# 显示提示标签
if hasattr(self, 'shared_ids_label') and not self.shared_ids_label.winfo_ismapped():
self.shared_ids_label.pack(anchor="w", pady=(0, 5))
else:
self.shared_ids_info_label.config(text="")
# 隐藏提示标签
if hasattr(self, 'shared_ids_label'):
if self.shared_ids_label.winfo_ismapped():
self.shared_ids_label.pack_forget()
def reload_config(self):
"""配置更新后重新加载"""
# 从主窗口获取最新的 Production ID
if self.main_window:
production_ids = self.main_window.get_shared_production_ids()
self.on_production_ids_updated(production_ids)
def _create_manager_filter_area(self, parent):
"""创建负责人筛选区域"""
# PERMISSION CHECK: 非管理员用户不显示筛选区域
@@ -716,19 +827,41 @@ class MaterialValidationTab(ttk.Frame):
mode = self.source_mode.get()
input_file = None
production_id_file = None
production_ids_list = None # 新增:用于传递共享的 Production ID 列表
is_user_only = not self.session_manager.is_admin()
# 根据模式验证输入文件
if mode == "database_full":
# 无需输入文件
pass
elif mode == "database_filtered":
production_id_file = self.db_filtered_production_id_selector.get()
if not production_id_file:
messagebox.showerror("错误", "请选择 ProductionID 文件")
return
if not os.path.exists(production_id_file):
messagebox.showerror("错误", f"文件不存在:{production_id_file}")
return
if is_user_only:
# User 模式:自动使用共享的 Production ID
production_ids_list = self.shared_production_ids
if not production_ids_list:
messagebox.showerror("错误", "没有可用的共享 Production ID\n请在数据提取页面输入 Production ID 后再试")
return
self.log_text.info(f"使用数据提取页面的 Production ID{len(production_ids_list)} 个)")
else:
# Admin 模式:检查数据源选择
source = self.production_id_source_var.get()
if source == "shared":
# 使用共享的 Production ID
production_ids_list = self.shared_production_ids
if not production_ids_list:
messagebox.showerror("错误", "没有可用的共享 Production ID\n请在数据提取页面输入 Production ID 后再试")
return
self.log_text.info(f"使用共享的 Production ID{len(production_ids_list)} 个)")
else:
# 使用文件
production_id_file = self.db_filtered_production_id_selector.get()
if not production_id_file:
messagebox.showerror("错误", "请选择 ProductionID 文件")
return
if not os.path.exists(production_id_file):
messagebox.showerror("错误", f"文件不存在:{production_id_file}")
return
else:
messagebox.showerror("错误", f"未知的校验模式: {mode}")
return
@@ -758,16 +891,27 @@ class MaterialValidationTab(ttk.Frame):
# 在后台线程中执行校验
validation_thread = threading.Thread(
target=self._validation_worker_enhanced,
args=(mode, input_file, production_id_file, output_file),
args=(mode, input_file, production_id_file, output_file, production_ids_list),
daemon=True,
)
validation_thread.start()
def _validation_worker_enhanced(
self, mode: str, input_file: str, production_id_file: str, output_file: str
self, mode: str, input_file: str, production_id_file: str, output_file: str, production_ids_list: list = None
):
"""增强的校验工作线程(使用完整记录模式)"""
import tempfile
temp_production_id_file = None
try:
# 如果提供了共享的 Production ID 列表,创建临时文件
if production_ids_list:
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8') as f:
temp_production_id_file = f.name
f.write('\n'.join(production_ids_list))
production_id_file = temp_production_id_file
self._update_log(f"使用共享的 Production ID 列表({len(production_ids_list)} 个)", "INFO")
# 导入校验器
from utils.material_status_validator import MaterialStatusValidator
@@ -820,6 +964,12 @@ class MaterialValidationTab(ttk.Frame):
import traceback
self._update_log(traceback.format_exc(), "ERROR")
finally:
# 清理临时文件
if temp_production_id_file and os.path.exists(temp_production_id_file):
try:
os.unlink(temp_production_id_file)
except:
pass
# 更新 UI 状态
self.after(0, self._validation_complete)