From 3c25a1bf4d9459ab58db4dc2b22b74ebd39bbeb5 Mon Sep 17 00:00:00 2001 From: houhuan Date: Mon, 4 May 2026 20:17:22 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96GUI=E5=B8=83=E5=B1=80?= =?UTF-8?q?=20=E2=80=94=20=E6=9C=80=E8=BF=91=E6=96=87=E4=BB=B6=E9=AB=98?= =?UTF-8?q?=E5=BA=A6=E5=A2=9E=E5=8A=A0=E3=80=81=E8=AE=BE=E7=BD=AE=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E6=A1=86=E6=94=B9=E4=B8=BA=E5=8F=8C=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 最近文件列表框高度 12→20(增加 2/3) - 系统设置对话框从 560x680 单列改为 700x460 双列布局 - 设置项分为 3 个区块:基本设置、API设置、云端同步 - 保存按钮始终可见,无需滚动 Co-Authored-By: Claude Opus 4.7 --- app/ui/config_dialog.py | 170 ++++++++++++++++++++++------------------ app/ui/main_window.py | 2 +- 2 files changed, 93 insertions(+), 79 deletions(-) diff --git a/app/ui/config_dialog.py b/app/ui/config_dialog.py index b89399e..9d1db39 100644 --- a/app/ui/config_dialog.py +++ b/app/ui/config_dialog.py @@ -22,56 +22,24 @@ def show_config_dialog(root, cfg: ConfigManager): settings = load_user_settings() dlg = tk.Toplevel(root) dlg.title("系统设置") - dlg.geometry("560x680") + dlg.geometry("700x460") center_window(dlg) content = ttk.Frame(dlg) - content.pack(fill=tk.BOTH, expand=True, padx=16, pady=16) - for i in range(2): - content.columnconfigure(i, weight=1) + content.pack(fill=tk.BOTH, expand=True, padx=12, pady=12) + content.columnconfigure(0, weight=1) - # 当前值 - log_level_val = tk.StringVar(value=settings.get('log_level', 'INFO')) - max_workers_val = tk.StringVar(value=str(settings.get('concurrency_max_workers', cfg.getint('Performance', 'max_workers', 4)))) - batch_size_val = tk.StringVar(value=str(settings.get('concurrency_batch_size', cfg.getint('Performance', 'batch_size', 5)))) - template_path_val = tk.StringVar(value=settings.get('template_path', os.path.join(cfg.get('Paths', 'template_folder', 'templates'), cfg.get('Templates', 'purchase_order', '银豹-采购单模板.xls')))) - input_dir_val = tk.StringVar(value=settings.get('input_folder', cfg.get('Paths', 'input_folder', 'data/input'))) - output_dir_val = tk.StringVar(value=settings.get('output_folder', cfg.get('Paths', 'output_folder', 'data/output'))) - result_dir_val = tk.StringVar(value=settings.get('result_folder', 'data/result')) + # ── 辅助函数 ── + def _add_pair(parent, row, col, label_text, widget, label_width=None): + """在 parent 的 (row, col*2) 放 label, (row, col*2+1) 放 widget""" + lbl = ttk.Label(parent, text=label_text) + if label_width: + lbl.configure(width=label_width) + lbl.grid(row=row, column=col * 2, sticky='w', padx=(6, 2), pady=3) + widget.grid(row=row, column=col * 2 + 1, sticky='ew', padx=(2, 6), pady=3) - def add_row(row, label_text, widget): - ttk.Label(content, text=label_text).grid(row=row, column=0, sticky='w', padx=4, pady=6) - widget.grid(row=row, column=1, sticky='ew', padx=4, pady=6) - - # 日志级别 - lvl = ttk.Combobox(content, textvariable=log_level_val, values=['DEBUG', 'INFO', 'WARNING', 'ERROR'], state='readonly') - add_row(0, "日志级别", lvl) - - # 并发参数 - maxw_entry = ttk.Entry(content, textvariable=max_workers_val) - add_row(1, "最大并发(max_workers)", maxw_entry) - batch_entry = ttk.Entry(content, textvariable=batch_size_val) - add_row(2, "批次大小(batch_size)", batch_entry) - - # 模板路径 - tpl_frame = ttk.Frame(content) - tpl_entry = ttk.Entry(tpl_frame, textvariable=template_path_val) - tpl_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) - - def _select_template(): - p = filedialog.askopenfilename(title="选择模板文件", filetypes=[("Excel模板", "*.xls *.xlsx"), ("所有文件", "*.*")]) - if p: - try: - template_path_val.set(os.path.relpath(p, os.getcwd())) - except Exception: - template_path_val.set(p) - - ttk.Button(tpl_frame, text="选择", command=_select_template).pack(side=tk.LEFT, padx=6) - add_row(3, "采购单模板文件", tpl_frame) - - # 目录 - def dir_row(row_idx, label, var): - f = ttk.Frame(content) + def _make_dir_widget(parent, var, label): + f = ttk.Frame(parent) e = ttk.Entry(f, textvariable=var) e.pack(side=tk.LEFT, fill=tk.X, expand=True) @@ -83,49 +51,95 @@ def show_config_dialog(root, cfg: ConfigManager): except Exception: var.set(d) - ttk.Button(f, text="选择", command=_select_dir).pack(side=tk.LEFT, padx=6) - add_row(row_idx, label, f) - - dir_row(4, "输入目录", input_dir_val) - dir_row(5, "输出目录", output_dir_val) - dir_row(6, "结果目录", result_dir_val) + ttk.Button(f, text="选择", command=_select_dir).pack(side=tk.LEFT, padx=4) + return f + # ── 当前值 ── + log_level_val = tk.StringVar(value=settings.get('log_level', 'INFO')) + max_workers_val = tk.StringVar(value=str(settings.get('concurrency_max_workers', cfg.getint('Performance', 'max_workers', 4)))) + batch_size_val = tk.StringVar(value=str(settings.get('concurrency_batch_size', cfg.getint('Performance', 'batch_size', 5)))) + template_path_val = tk.StringVar(value=settings.get('template_path', os.path.join(cfg.get('Paths', 'template_folder', 'templates'), cfg.get('Templates', 'purchase_order', '银豹-采购单模板.xls')))) + input_dir_val = tk.StringVar(value=settings.get('input_folder', cfg.get('Paths', 'input_folder', 'data/input'))) + output_dir_val = tk.StringVar(value=settings.get('output_folder', cfg.get('Paths', 'output_folder', 'data/output'))) + result_dir_val = tk.StringVar(value=settings.get('result_folder', 'data/result')) api_key_val = tk.StringVar(value=settings.get('api_key', cfg.get('API', 'api_key', ''))) secret_key_val = tk.StringVar(value=settings.get('secret_key', cfg.get('API', 'secret_key', ''))) timeout_val = tk.StringVar(value=str(settings.get('timeout', cfg.getint('API', 'timeout', 30)))) max_retries_val = tk.StringVar(value=str(settings.get('max_retries', cfg.getint('API', 'max_retries', 3)))) retry_delay_val = tk.StringVar(value=str(settings.get('retry_delay', cfg.getint('API', 'retry_delay', 2)))) api_url_val = tk.StringVar(value=settings.get('api_url', cfg.get('API', 'api_url', ''))) - - api_key_entry = ttk.Entry(content, textvariable=api_key_val) - add_row(7, "API Key", api_key_entry) - secret_key_entry = ttk.Entry(content, textvariable=secret_key_val) - secret_key_entry.configure(show='*') - add_row(8, "Secret Key", secret_key_entry) - add_row(9, "Timeout", ttk.Entry(content, textvariable=timeout_val)) - add_row(10, "Max Retries", ttk.Entry(content, textvariable=max_retries_val)) - add_row(11, "Retry Delay", ttk.Entry(content, textvariable=retry_delay_val)) - add_row(12, "API URL", ttk.Entry(content, textvariable=api_url_val)) - - # ---- Gitea 云端同步配置 ---- - ttk.Separator(content).grid(row=13, column=0, columnspan=2, sticky='ew', pady=8) - ttk.Label(content, text="云端同步 (Gitea)", font=("Arial", 10, "bold")).grid(row=14, column=0, sticky='w', padx=4, pady=4) - gitea_url_val = tk.StringVar(value=cfg.get('Gitea', 'base_url', fallback='https://gitea.94kan.cn')) gitea_owner_val = tk.StringVar(value=cfg.get('Gitea', 'owner', fallback='houhuan')) gitea_repo_val = tk.StringVar(value=cfg.get('Gitea', 'repo', fallback='yixuan-sync-data')) gitea_token_val = tk.StringVar(value=cfg.get('Gitea', 'token', fallback='')) - add_row(15, "Gitea 地址", ttk.Entry(content, textvariable=gitea_url_val)) - add_row(16, "仓库所有者", ttk.Entry(content, textvariable=gitea_owner_val)) - add_row(17, "仓库名称", ttk.Entry(content, textvariable=gitea_repo_val)) - gitea_token_entry = ttk.Entry(content, textvariable=gitea_token_val, show='*') - add_row(18, "Access Token", gitea_token_entry) + # ═══════════════════════════════════════════════════ + # 区块 1: 基本设置 + # ═══════════════════════════════════════════════════ + f1 = ttk.LabelFrame(content, text=" 基本设置 ", padding=(8, 4)) + f1.pack(fill=tk.X, pady=(0, 6)) + for c in range(4): + f1.columnconfigure(c, weight=1 if c % 2 == 1 else 0) - # 操作按钮 + lvl = ttk.Combobox(f1, textvariable=log_level_val, values=['DEBUG', 'INFO', 'WARNING', 'ERROR'], state='readonly', width=12) + _add_pair(f1, 0, 0, "日志级别", lvl) + _add_pair(f1, 0, 1, "最大并发", ttk.Entry(f1, textvariable=max_workers_val, width=6)) + _add_pair(f1, 1, 0, "批次大小", ttk.Entry(f1, textvariable=batch_size_val, width=6)) + + # 模板路径(带选择按钮,占右列) + tpl_frame = ttk.Frame(f1) + tpl_entry = ttk.Entry(tpl_frame, textvariable=template_path_val) + tpl_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) + + def _select_template(): + p = filedialog.askopenfilename(title="选择模板文件", filetypes=[("Excel模板", "*.xls *.xlsx"), ("所有文件", "*.*")]) + if p: + try: + template_path_val.set(os.path.relpath(p, os.getcwd())) + except Exception: + template_path_val.set(p) + + ttk.Button(tpl_frame, text="选择", command=_select_template).pack(side=tk.LEFT, padx=4) + _add_pair(f1, 1, 1, "采购模板", tpl_frame) + + _add_pair(f1, 2, 0, "输入目录", _make_dir_widget(f1, input_dir_val, "输入目录")) + _add_pair(f1, 2, 1, "输出目录", _make_dir_widget(f1, output_dir_val, "输出目录")) + _add_pair(f1, 3, 0, "结果目录", _make_dir_widget(f1, result_dir_val, "结果目录")) + + # ═══════════════════════════════════════════════════ + # 区块 2: API 设置 + # ═══════════════════════════════════════════════════ + f2 = ttk.LabelFrame(content, text=" API 设置 ", padding=(8, 4)) + f2.pack(fill=tk.X, pady=(0, 6)) + for c in range(4): + f2.columnconfigure(c, weight=1 if c % 2 == 1 else 0) + + _add_pair(f2, 0, 0, "API Key", ttk.Entry(f2, textvariable=api_key_val)) + secret_entry = ttk.Entry(f2, textvariable=secret_key_val, show='*') + _add_pair(f2, 0, 1, "Secret Key", secret_entry) + _add_pair(f2, 1, 0, "Timeout", ttk.Entry(f2, textvariable=timeout_val, width=6)) + _add_pair(f2, 1, 1, "Max Retries", ttk.Entry(f2, textvariable=max_retries_val, width=6)) + _add_pair(f2, 2, 0, "Retry Delay", ttk.Entry(f2, textvariable=retry_delay_val, width=6)) + _add_pair(f2, 2, 1, "API URL", ttk.Entry(f2, textvariable=api_url_val)) + + # ═══════════════════════════════════════════════════ + # 区块 3: 云端同步 (Gitea) + # ═══════════════════════════════════════════════════ + f3 = ttk.LabelFrame(content, text=" 云端同步 (Gitea) ", padding=(8, 4)) + f3.pack(fill=tk.X, pady=(0, 8)) + for c in range(4): + f3.columnconfigure(c, weight=1 if c % 2 == 1 else 0) + + _add_pair(f3, 0, 0, "Gitea 地址", ttk.Entry(f3, textvariable=gitea_url_val)) + _add_pair(f3, 0, 1, "仓库所有者", ttk.Entry(f3, textvariable=gitea_owner_val)) + _add_pair(f3, 1, 0, "仓库名称", ttk.Entry(f3, textvariable=gitea_repo_val)) + _add_pair(f3, 1, 1, "Access Token", ttk.Entry(f3, textvariable=gitea_token_val, show='*')) + + # ═══════════════════════════════════════════════════ + # 按钮区 + # ═══════════════════════════════════════════════════ btns = ttk.Frame(content) - btns.grid(row=19, column=0, columnspan=2, sticky='ew', pady=10) - btns.columnconfigure(0, weight=1) + btns.pack(fill=tk.X, pady=(4, 0)) def save_settings(): try: @@ -201,7 +215,7 @@ def show_config_dialog(root, cfg: ConfigManager): except Exception as e: messagebox.showerror("重新加载失败", str(e)) - ttk.Button(btns, text="重新加载供应商配置", command=reload_suppliers).grid(row=0, column=0, sticky='w') - ttk.Button(btns, text="云端同步", command=lambda: show_cloud_sync_dialog(dlg)).grid(row=0, column=1, sticky='w', padx=6) - ttk.Button(btns, text="取消", command=dlg.destroy).grid(row=0, column=2, sticky='e') - ttk.Button(btns, text="保存", command=save_settings).grid(row=0, column=3, sticky='e', padx=6) + ttk.Button(btns, text="重新加载供应商配置", command=reload_suppliers).pack(side=tk.LEFT) + ttk.Button(btns, text="云端同步", command=lambda: show_cloud_sync_dialog(dlg)).pack(side=tk.LEFT, padx=6) + ttk.Button(btns, text="取消", command=dlg.destroy).pack(side=tk.RIGHT) + ttk.Button(btns, text="保存", command=save_settings).pack(side=tk.RIGHT, padx=6) diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 1be1238..3ed3fbd 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -158,7 +158,7 @@ def _create_recent_files_section(parent, theme, log_text): recent_rect = tk.Frame(recent_top, bg=theme["card_bg"], highlightbackground=theme["border"], highlightthickness=1) recent_rect.pack(fill=tk.BOTH, expand=True) - recent_list = tk.Listbox(recent_rect, height=12) + recent_list = tk.Listbox(recent_rect, height=20) recent_scrollbar = tk.Scrollbar(recent_rect) recent_list.configure(yscrollcommand=recent_scrollbar.set) recent_scrollbar.configure(command=recent_list.yview)