Implement a comprehensive GUI application with the following features: - Data Extraction tab: Extract material plan data from ERP system - Material Validation tab: Validate material status and match deletions - Data Query tab: Query database for production order information - Settings tab: Manage ERP, database, browser, and path configurations Key components: - MainWindow: Tabbed interface with status bar - ConfigManager: JSON-based configuration management - LogText: Custom read-only text widget with colored logging - FileSelector: Reusable file/directory selection component - ProgressDialog: Modal progress dialog for long operations Technical details: - Thread-safe UI updates using root.after() - Stdout capture for legacy script integration - Event-based readonly mode allowing copy/select operations - Custom widget composition to avoid Tkinter ScrolledText issues Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
280 lines
12 KiB
Python
280 lines
12 KiB
Python
#!/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
|
||
|
||
|
||
class SettingsTab(ttk.Frame):
|
||
"""设置标签页"""
|
||
|
||
def __init__(self, parent, config: ConfigManager):
|
||
"""
|
||
初始化设置标签页
|
||
|
||
Args:
|
||
parent: 父容器
|
||
config: 配置管理器
|
||
"""
|
||
super().__init__(parent)
|
||
self.config = config
|
||
self.create_widgets()
|
||
self.load_settings()
|
||
|
||
def create_widgets(self):
|
||
"""创建界面组件"""
|
||
# 创建主容器,带滚动条
|
||
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)
|
||
|
||
# ERP 配置组
|
||
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)
|
||
|
||
# 按钮区域
|
||
button_frame = ttk.Frame(scrollable_frame)
|
||
button_frame.grid(row=5, column=0, columnspan=2, pady=20, sticky="ew")
|
||
|
||
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_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_server_var = tk.StringVar()
|
||
ttk.Entry(group, textvariable=self.db_server_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.db_name_var = tk.StringVar()
|
||
ttk.Entry(group, textvariable=self.db_name_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.db_username_var = tk.StringVar()
|
||
ttk.Entry(group, textvariable=self.db_username_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_password_var = tk.StringVar()
|
||
entry = ttk.Entry(group, textvariable=self.db_password_var, width=50, show="*")
|
||
entry.grid(row=3, column=1, pady=5, sticky="ew")
|
||
|
||
group.columnconfigure(1, weight=1)
|
||
|
||
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):
|
||
"""创建路径配置组"""
|
||
group = ttk.LabelFrame(parent, text="路径设置", padding=10)
|
||
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=3, column=0, columnspan=2, sticky="ew", pady=5)
|
||
|
||
group.columnconfigure(0, weight=1)
|
||
|
||
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)
|
||
|
||
def load_settings(self):
|
||
"""从配置加载设置到界面"""
|
||
# 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', ''))
|
||
|
||
# 数据库设置
|
||
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.browser_headless_var.set(self.config.get('browser.headless', True))
|
||
self.browser_ignore_https_var.set(self.config.get('browser.ignore_https_errors', True))
|
||
self.browser_auto_close_var.set(self.config.get('browser.auto_close', 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))
|
||
|
||
def save_settings(self):
|
||
"""保存界面设置到配置"""
|
||
# 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())
|
||
|
||
# 数据库设置
|
||
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())
|
||
|
||
# 浏览器设置
|
||
self.config.set('browser.headless', self.browser_headless_var.get())
|
||
self.config.set('browser.ignore_https_errors', self.browser_ignore_https_var.get())
|
||
self.config.set('browser.auto_close', 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())
|
||
|
||
# 保存到文件
|
||
if self.config.save():
|
||
messagebox.showinfo("成功", "设置已保存")
|
||
else:
|
||
messagebox.showerror("错误", "保存设置失败")
|
||
|
||
def test_db_connection(self):
|
||
"""测试数据库连接"""
|
||
try:
|
||
conn_str = (
|
||
f"DRIVER={{ODBC Driver 18 for SQL Server}};"
|
||
f"SERVER={self.db_server_var.get()};"
|
||
f"DATABASE={self.db_name_var.get()};"
|
||
f"UID={self.db_username_var.get()};"
|
||
f"PWD={self.db_password_var.get()};"
|
||
f"TrustServerCertificate=yes;"
|
||
)
|
||
|
||
conn = pyodbc.connect(conn_str, timeout=5)
|
||
conn.close()
|
||
messagebox.showinfo("成功", "数据库连接测试成功!")
|
||
|
||
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("确认", "确定要恢复默认设置吗?"):
|
||
self.config.reset_to_defaults()
|
||
self.load_settings()
|
||
messagebox.showinfo("成功", "已恢复默认设置")
|