Files
playwrite/gui/settings_tab.py
Misaka_Company 2d485be0ac feat: add ERP credential configuration for User mode in settings
Allow User type users to configure ERP username and password through
the settings interface. The credentials are stored as shared config
in .env file, and the URL remains admin-configured.

Co-Authored-By: Claude (glm-5) <noreply@anthropic.com>
2026-02-27 15:21:46 +08:00

770 lines
31 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
设置标签页
管理系统配置ERP、数据库、浏览器、路径等
"""
import tkinter as tk
from tkinter import ttk, messagebox
import pyodbc
from gui.config_manager import ConfigManager
from config.schema import DatabaseType
class SettingsTab(ttk.Frame):
"""设置标签页"""
def __init__(self, parent, config: ConfigManager, session_manager=None):
"""
初始化设置标签页
Args:
parent: 父容器
config: 配置管理器
session_manager: 会话管理器,用于获取用户类型
"""
super().__init__(parent)
self.config = config
self.session_manager = session_manager
# 初始化物料校验输出文件名变量
self.validation_output_filename_var = tk.StringVar()
self.create_widgets()
self.load_settings()
def create_widgets(self):
"""创建界面组件"""
# 判断用户类型
is_user_only = (
self.session_manager and self.session_manager.get_user_type() == "User"
)
# 创建主容器,带滚动条
canvas = tk.Canvas(self)
scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
"<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
# 根据用户类型创建配置组
if not is_user_only:
# 管理员显示所有配置组
self._create_erp_group(scrollable_frame)
self._create_database_group(scrollable_frame)
self._create_browser_group(scrollable_frame)
self._create_paths_group(scrollable_frame)
self._create_extraction_group(scrollable_frame)
self._create_validation_group(scrollable_frame)
self._create_ui_group(scrollable_frame)
else:
# User 用户:显示 ERP 凭据、路径配置和执行设置
self._create_user_erp_group(scrollable_frame)
self._create_paths_group(scrollable_frame)
self._create_user_execution_group(scrollable_frame)
# 按钮区域 - 根据用户类型显示不同按钮
button_frame = ttk.Frame(scrollable_frame)
if is_user_only:
# User 用户显示测试按钮和保存设置按钮row=3 因为有 ERP 凭据组、路径设置组、执行设置组)
button_frame.grid(row=3, column=0, columnspan=2, pady=20, sticky="ew")
else:
# 管理员显示所有按钮
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(
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
)
ttk.Button(button_frame, text="恢复默认", command=self.reset_defaults).pack(
side="left", padx=5
)
# 布局
canvas.grid(row=0, column=0, sticky="nsew")
scrollbar.grid(row=0, column=1, sticky="ns")
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
def _create_erp_group(self, parent):
"""创建 ERP 配置组"""
group = ttk.LabelFrame(parent, text="ERP 系统配置", padding=10)
group.grid(row=0, column=0, columnspan=2, pady=10, padx=10, sticky="ew")
# URL
ttk.Label(group, text="ERP URL:").grid(row=0, column=0, sticky="w", pady=5)
self.erp_url_var = tk.StringVar()
ttk.Entry(group, textvariable=self.erp_url_var, width=50).grid(
row=0, column=1, pady=5, sticky="ew"
)
# 用户名
ttk.Label(group, text="用户名:").grid(row=1, column=0, sticky="w", pady=5)
self.erp_username_var = tk.StringVar()
ttk.Entry(group, textvariable=self.erp_username_var, width=50).grid(
row=1, column=1, pady=5, sticky="ew"
)
# 密码
ttk.Label(group, text="密码:").grid(row=2, column=0, sticky="w", pady=5)
self.erp_password_var = tk.StringVar()
entry = ttk.Entry(group, textvariable=self.erp_password_var, width=50, show="*")
entry.grid(row=2, column=1, pady=5, sticky="ew")
group.columnconfigure(1, weight=1)
def _create_user_erp_group(self, parent):
"""创建 User 用户的 ERP 凭据配置组"""
group = ttk.LabelFrame(parent, text="ERP 登录凭据", padding=10)
group.grid(row=0, column=0, columnspan=2, pady=10, padx=10, sticky="ew")
# 用户名
ttk.Label(group, text="ERP 用户名:").grid(row=0, column=0, sticky="w", pady=5)
self.user_erp_username_var = tk.StringVar()
ttk.Entry(group, textvariable=self.user_erp_username_var, width=40).grid(
row=0, column=1, pady=5, sticky="ew"
)
# 密码(带显示/隐藏切换)
ttk.Label(group, text="ERP 密码:").grid(row=1, column=0, sticky="w", pady=5)
self.user_erp_password_var = tk.StringVar()
password_entry = ttk.Entry(group, textvariable=self.user_erp_password_var, width=40, show="*")
password_entry.grid(row=1, column=1, pady=5, sticky="ew")
# 显示/隐藏密码按钮
self.password_visible = tk.BooleanVar(value=False)
ttk.Checkbutton(
group, text="显示密码", variable=self.password_visible,
command=lambda: password_entry.configure(show="" if self.password_visible.get() else "*")
).grid(row=2, column=1, sticky="w", pady=2)
# 提示文字
hint_label = ttk.Label(
group,
text="提示:此凭据为共享配置,修改后所有用户将使用新的 ERP 账号。",
foreground="gray",
)
hint_label.grid(row=3, column=0, columnspan=2, sticky="w", pady=(5, 0))
group.columnconfigure(1, weight=1)
def _create_database_group(self, parent):
"""创建数据库配置组"""
group = ttk.LabelFrame(parent, text="数据库配置", padding=10)
group.grid(row=1, column=0, columnspan=2, pady=10, padx=10, sticky="ew")
# 数据库类型选择
ttk.Label(group, text="数据库类型:").grid(row=0, column=0, sticky="w", pady=5)
self.db_type_var = tk.StringVar()
db_type_combo = ttk.Combobox(
group,
textvariable=self.db_type_var,
values=["sqlserver", "mysql"],
state="readonly",
width=30,
)
db_type_combo.grid(row=0, column=1, sticky="w", pady=5)
db_type_combo.bind("<<ComboboxSelected>>", self._on_db_type_changed)
# SQL Server 配置
self.sqlserver_frame = ttk.Frame(group)
self.sqlserver_frame.grid(row=1, column=0, columnspan=2, sticky="ew", pady=5)
ttk.Label(self.sqlserver_frame, text="服务器:").grid(
row=0, column=0, sticky="w", pady=5
)
self.db_server_var = tk.StringVar()
ttk.Entry(self.sqlserver_frame, textvariable=self.db_server_var, width=50).grid(
row=0, column=1, pady=5, sticky="ew"
)
# MySQL 配置
self.mysql_frame = ttk.Frame(group)
ttk.Label(self.mysql_frame, text="主机:").grid(
row=0, column=0, sticky="w", pady=5
)
self.mysql_host_var = tk.StringVar()
ttk.Entry(self.mysql_frame, textvariable=self.mysql_host_var, width=50).grid(
row=0, column=1, pady=5, sticky="ew"
)
ttk.Label(self.mysql_frame, text="端口:").grid(
row=1, column=0, sticky="w", pady=5
)
self.mysql_port_var = tk.IntVar(value=3306)
ttk.Spinbox(
self.mysql_frame,
from_=1,
to=65535,
textvariable=self.mysql_port_var,
width=10,
).grid(row=1, column=1, sticky="w", pady=5)
# 通用配置(两种数据库都需要)
ttk.Label(group, text="数据库:").grid(row=2, column=0, sticky="w", pady=5)
self.db_name_var = tk.StringVar()
ttk.Entry(group, textvariable=self.db_name_var, width=50).grid(
row=2, column=1, pady=5, sticky="ew"
)
ttk.Label(group, text="用户名:").grid(row=3, column=0, sticky="w", pady=5)
self.db_username_var = tk.StringVar()
ttk.Entry(group, textvariable=self.db_username_var, width=50).grid(
row=3, column=1, pady=5, sticky="ew"
)
ttk.Label(group, text="密码:").grid(row=4, column=0, sticky="w", pady=5)
self.db_password_var = tk.StringVar()
entry = ttk.Entry(group, textvariable=self.db_password_var, width=50, show="*")
entry.grid(row=4, column=1, pady=5, sticky="ew")
group.columnconfigure(1, weight=1)
def _on_db_type_changed(self, event=None):
"""数据库类型改变时的回调"""
db_type = self.db_type_var.get()
if db_type == "mysql":
self.sqlserver_frame.grid_forget()
self.mysql_frame.grid(row=1, column=0, columnspan=2, sticky="ew", pady=5)
else:
self.mysql_frame.grid_forget()
self.sqlserver_frame.grid(
row=1, column=0, columnspan=2, sticky="ew", pady=5
)
def _create_browser_group(self, parent):
"""创建浏览器配置组"""
group = ttk.LabelFrame(parent, text="浏览器设置", padding=10)
group.grid(row=2, column=0, pady=10, padx=10, sticky="ew")
self.browser_headless_var = tk.BooleanVar()
ttk.Checkbutton(
group, text="无头模式 (不显示浏览器)", variable=self.browser_headless_var
).grid(row=0, column=0, sticky="w", pady=5)
self.browser_ignore_https_var = tk.BooleanVar()
ttk.Checkbutton(
group, text="忽略 HTTPS 错误", variable=self.browser_ignore_https_var
).grid(row=1, column=0, sticky="w", pady=5)
self.browser_auto_close_var = tk.BooleanVar()
ttk.Checkbutton(
group, text="操作完成后自动关闭浏览器", variable=self.browser_auto_close_var
).grid(row=2, column=0, sticky="w", pady=5)
def _create_paths_group(self, parent):
"""创建路径配置组"""
# 根据用户类型调整 grid 位置
is_user_only = (
self.session_manager and self.session_manager.get_user_type() == "User"
)
group = ttk.LabelFrame(parent, text="路径设置", padding=10)
if is_user_only:
# User 用户ERP 凭据在 row=0路径设置在 row=1
group.grid(row=1, column=0, columnspan=2, pady=10, padx=10, sticky="ew")
else:
group.grid(row=2, column=1, pady=10, padx=10, sticky="nsew")
from gui.widgets import FileSelector
# 数据目录
ttk.Label(group, text="数据目录:").grid(row=0, column=0, sticky="w", pady=5)
self.data_dir_selector = FileSelector(
group,
label_text="",
file_type="directory",
initial_dir="D:/python/playwrite/data/",
)
self.data_dir_selector.grid(row=1, column=0, columnspan=2, sticky="ew", pady=5)
# 默认输出文件
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=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)
def _create_user_execution_group(self, parent):
"""创建 User 用户的执行设置组"""
group = ttk.LabelFrame(parent, text="执行设置", padding=10)
# User 用户ERP 凭据在 row=0路径设置在 row=1执行设置在 row=2
group.grid(row=2, column=0, columnspan=2, pady=10, padx=10, sticky="ew")
# dryrun 模式设置
self.user_dryrun_var = tk.BooleanVar(value=False)
ttk.Checkbutton(
group, text="预览模式 (执行删除时不保存更改)", variable=self.user_dryrun_var
).grid(row=0, column=0, sticky="w", pady=5)
# 说明文字
hint_label = ttk.Label(
group,
text="提示:勾选后,执行删除操作时将只预览不实际保存,用于测试流程。",
foreground="gray",
)
hint_label.grid(row=1, column=0, sticky="w", pady=(0, 5))
def _create_extraction_group(self, parent):
"""创建处理配置组"""
group = ttk.LabelFrame(parent, text="数据提取设置", padding=10)
group.grid(row=3, column=0, columnspan=2, pady=10, padx=10, sticky="ew")
# 批次大小
ttk.Label(group, text="批次大小:").grid(row=0, column=0, sticky="w", pady=5)
self.batch_size_var = tk.IntVar(value=100)
ttk.Spinbox(
group, from_=10, to=500, textvariable=self.batch_size_var, width=10
).grid(row=0, column=1, sticky="w", pady=5)
# 详细日志
self.verbose_var = tk.BooleanVar()
ttk.Checkbutton(group, text="启用详细日志", variable=self.verbose_var).grid(
row=1, column=0, columnspan=2, sticky="w", pady=5
)
# 自动转换
self.auto_convert_var = tk.BooleanVar()
ttk.Checkbutton(
group, text="自动转换 Excel 格式", variable=self.auto_convert_var
).grid(row=2, column=0, columnspan=2, sticky="w", pady=5)
# 合并批次
self.merge_batches_var = tk.BooleanVar()
ttk.Checkbutton(
group, text="自动合并批次数据", variable=self.merge_batches_var
).grid(row=3, column=0, columnspan=2, sticky="w", pady=5)
# 数据库持久化
self.enable_db_persistence_var = tk.BooleanVar()
ttk.Checkbutton(
group, text="保存到数据库", variable=self.enable_db_persistence_var
).grid(row=4, column=0, columnspan=2, sticky="w", pady=5)
def _create_validation_group(self, parent):
"""创建物料校验配置组"""
group = ttk.LabelFrame(parent, text="物料校验设置", padding=10)
group.grid(row=4, column=0, columnspan=2, pady=10, padx=10, sticky="ew")
# 数据源选择
ttk.Label(group, text="默认数据源:").grid(row=0, column=0, sticky="w", pady=5)
self.validation_data_source_var = tk.StringVar()
data_source_combo = ttk.Combobox(
group,
textvariable=self.validation_data_source_var,
values=[
"database_full",
"database_filtered",
"excel_existing",
"excel_full",
],
state="readonly",
width=30,
)
data_source_combo.grid(row=0, column=1, sticky="w", pady=5)
# 使用数据库
self.validation_use_database_var = tk.BooleanVar()
ttk.Checkbutton(
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=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=3, column=1, 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,
textvariable=self.validation_match_mode_var,
values=["substring", "exact"],
state="readonly",
width=30,
)
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=5, column=0, columnspan=2, 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=6, column=1, sticky="w", pady=5)
group.columnconfigure(1, weight=1)
def _create_ui_group(self, parent):
"""创建 UI 配置组"""
group = ttk.LabelFrame(parent, text="界面设置", padding=10)
group.grid(row=5, column=0, columnspan=2, pady=10, padx=10, sticky="ew")
# 字体选择
ttk.Label(group, text="字体:").grid(row=0, column=0, sticky="w", pady=5)
self.ui_font_family_var = tk.StringVar()
font_combo = ttk.Combobox(
group,
textvariable=self.ui_font_family_var,
values=[
"Microsoft YaHei UI",
"SimSun",
"KaiTi",
"FangSong",
"Arial",
"Segoe UI",
],
state="readonly",
width=30,
)
font_combo.grid(row=0, column=1, sticky="w", pady=5)
# 字号选择
ttk.Label(group, text="字号:").grid(row=1, column=0, sticky="w", pady=5)
self.ui_font_size_var = tk.IntVar(value=10)
ttk.Spinbox(
group, from_=8, to=24, textvariable=self.ui_font_size_var, width=10
).grid(row=1, column=1, sticky="w", pady=5)
# Production ID 输入框宽度
ttk.Label(group, text="输入框宽度(字符):").grid(
row=2, column=0, sticky="w", pady=5
)
self.ui_input_width_var = tk.IntVar(value=20)
ttk.Spinbox(
group, from_=10, to=100, textvariable=self.ui_input_width_var, width=10
).grid(row=2, column=1, sticky="w", pady=5)
group.columnconfigure(1, weight=1)
def load_settings(self):
"""从配置加载设置到界面"""
# 判断是否为仅测试用户模式
is_user_only = (
self.session_manager and self.session_manager.get_user_type() == "User"
)
if is_user_only:
# User 用户模式 - 只需要加载 ERP 凭据、路径设置到界面
# ERP 凭据
self.user_erp_username_var.set(self.config.get("erp.username", ""))
self.user_erp_password_var.set(self.config.get("erp.password", ""))
# 路径设置
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", "")
)
# 执行设置
self.user_dryrun_var.set(self.config.get("execution.dryrun", False))
return
# 管理员模式 - 加载所有配置
# ERP 设置
self.erp_url_var.set(self.config.get("erp.url", ""))
self.erp_username_var.set(self.config.get("erp.username", ""))
self.erp_password_var.set(self.config.get("erp.password", ""))
# 数据库设置
db_type = self.config.get("database.db_type", "sqlserver")
self.db_type_var.set(db_type)
if db_type == "mysql":
self.db_server_var.set(self.config.get("database.server", ""))
self.mysql_host_var.set(self.config.get("database.mysql.host", ""))
self.mysql_port_var.set(self.config.get("database.mysql.port", 3306))
else:
self.db_server_var.set(self.config.get("database.server", ""))
self.db_name_var.set(self.config.get("database.database", ""))
self.db_username_var.set(self.config.get("database.username", ""))
self.db_password_var.set(self.config.get("database.password", ""))
# 更新界面显示
self._on_db_type_changed()
# 浏览器设置(已合并到 ERP 配置中)
self.browser_headless_var.set(self.config.get("erp.headless", True))
self.browser_ignore_https_var.set(
self.config.get("erp.ignore_https_errors", True)
)
self.browser_auto_close_var.set(self.config.get("erp.auto_close_browser", True))
# 路径设置
self.data_dir_selector.set(self.config.get("paths.data_dir", ""))
self.default_output_var.set(self.config.get("paths.default_output", ""))
# 处理设置
self.batch_size_var.set(self.config.get("extraction.batch_size", 100))
self.verbose_var.set(self.config.get("extraction.verbose", True))
self.auto_convert_var.set(self.config.get("extraction.auto_convert", True))
self.merge_batches_var.set(self.config.get("extraction.merge_batches", True))
self.enable_db_persistence_var.set(
self.config.get("extraction.enable_db_persistence", False)
)
# 校验设置
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)
)
self.validation_default_manager_var.set(
self.config.get("validation.default_manager", "")
)
# UI 设置
self.ui_font_family_var.set(
self.config.get("ui.font_family", "Microsoft YaHei UI")
)
self.ui_font_size_var.set(self.config.get("ui.font_size", 10))
self.ui_input_width_var.set(self.config.get("ui.production_id_input_width", 20))
def save_settings(self):
"""保存界面设置到配置"""
# 判断是否为仅测试用户模式
is_user_only = (
self.session_manager and self.session_manager.get_user_type() == "User"
)
if is_user_only:
# User 用户模式 - 只保存 ERP 凭据、路径设置和执行设置
# ERP 凭据
self.config.set("erp.username", self.user_erp_username_var.get())
self.config.set("erp.password", self.user_erp_password_var.get())
# 路径设置
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()
)
# 保存执行设置
self.config.set("execution.dryrun", self.user_dryrun_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())
self.config.set("erp.password", self.erp_password_var.get())
# 数据库设置
db_type = self.db_type_var.get()
self.config.set("database.db_type", db_type)
if db_type == "mysql":
# MySQL: 使用 host 字段
self.config.set("database.server", self.mysql_host_var.get())
self.config.set("database.mysql.host", self.mysql_host_var.get())
self.config.set("database.mysql.port", self.mysql_port_var.get())
else:
# SQL Server: 使用 server 字段
self.config.set("database.server", self.db_server_var.get())
self.config.set("database.database", self.db_name_var.get())
self.config.set("database.username", self.db_username_var.get())
self.config.set("database.password", self.db_password_var.get())
# 浏览器设置(已合并到 ERP 配置中)
self.config.set("erp.headless", self.browser_headless_var.get())
self.config.set("erp.ignore_https_errors", self.browser_ignore_https_var.get())
self.config.set("erp.auto_close_browser", self.browser_auto_close_var.get())
# 路径设置
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("extraction.batch_size", self.batch_size_var.get())
self.config.set("extraction.verbose", self.verbose_var.get())
self.config.set("extraction.auto_convert", self.auto_convert_var.get())
self.config.set("extraction.merge_batches", self.merge_batches_var.get())
self.config.set(
"extraction.enable_db_persistence", self.enable_db_persistence_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(
"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()
)
self.config.set(
"validation.default_manager", self.validation_default_manager_var.get()
)
# UI 设置
self.config.set("ui.font_family", self.ui_font_family_var.get())
self.config.set("ui.font_size", self.ui_font_size_var.get())
self.config.set("ui.production_id_input_width", self.ui_input_width_var.get())
# 保存到文件
if self.config.save():
messagebox.showinfo("成功", "设置已保存")
# 通知其他标签页重新加载配置
self._notify_config_reload()
else:
messagebox.showerror("错误", "保存设置失败")
def _notify_config_reload(self):
"""通知其他标签页配置已更新"""
# 尝试通知主窗口重新加载配置
try:
# 获取主窗口
main_window = self.winfo_toplevel()
# 调用主窗口的 reload_config 方法(如果存在)
if hasattr(main_window, "reload_config"):
main_window.reload_config()
except Exception:
pass
def test_db_connection(self):
"""测试数据库连接"""
# 从配置读取而不是从 UI 变量(支持 User 类型用户)
db_type = self.config.get("database.db_type", "sqlserver")
try:
if db_type == "mysql":
import mysql.connector
from mysql.connector import Error
conn = mysql.connector.connect(
host=self.config.get("database.mysql.host", ""),
port=self.config.get("database.mysql.port", 3306),
database=self.config.get("database.database", ""),
user=self.config.get("database.username", ""),
password=self.config.get("database.password", ""),
connection_timeout=5,
)
conn.close()
messagebox.showinfo("成功", "MySQL 数据库连接测试成功!")
else:
conn_str = (
f"DRIVER={{ODBC Driver 18 for SQL Server}};"
f"SERVER={self.config.get('database.server', '')};"
f"DATABASE={self.config.get('database.database', '')};"
f"UID={self.config.get('database.username', '')};"
f"PWD={self.config.get('database.password', '')};"
f"TrustServerCertificate=yes;"
)
conn = pyodbc.connect(conn_str, timeout=5)
conn.close()
messagebox.showinfo("成功", "SQL Server 数据库连接测试成功!")
except ImportError:
if db_type == "mysql":
messagebox.showerror(
"错误",
"未安装 mysql-connector-python请运行\npip install mysql-connector-python",
)
else:
messagebox.showerror(
"错误", "未安装 pyodbc请运行\npip install pyodbc"
)
except Exception as e:
messagebox.showerror("错误", f"数据库连接失败:\n{str(e)}")
def test_erp_connection(self):
"""测试 ERP 连接"""
# ERP 连接测试需要实际启动浏览器
messagebox.showinfo("提示", "ERP 连接测试将在数据提取功能中自动验证")
def reset_defaults(self):
"""恢复默认设置"""
if messagebox.askyesno(
"确认", "确定要恢复默认设置吗?这将覆盖 .env 文件中的所有配置。"
):
from config.schema import AppConfig
self.config.config = AppConfig.from_env() # 重新加载默认配置
self.config.save()
self.load_settings()
messagebox.showinfo("成功", "已恢复默认设置")