6fd14b4e49
- EXE版(sys.frozen)不显示安装按钮,提示用源码版安装后重新打包 - tkinterdnd2 加入 hidden_imports,打包时自动带上 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
499 lines
22 KiB
Python
499 lines
22 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
"""主窗口模块"""
|
|
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import tkinter as tk
|
|
from tkinter import messagebox, filedialog, scrolledtext
|
|
|
|
from app.config.settings import ConfigManager
|
|
from app.core.utils.log_utils import set_log_level
|
|
|
|
from .theme import THEMES, get_theme_mode, set_theme_mode, create_modern_button, create_card_frame
|
|
from .logging_ui import add_to_log, poll_log_queue
|
|
from .ui_widgets import StatusBar
|
|
from .user_settings import (
|
|
load_user_settings, save_user_settings, refresh_recent_list_widget,
|
|
_extract_path_from_recent_item, clear_recent_files, RECENT_LIST_WIDGET,
|
|
)
|
|
from .file_operations import (
|
|
ensure_directories, open_result_directory, clean_cache,
|
|
clean_data_files, clean_result_files,
|
|
)
|
|
from .action_handlers import (
|
|
process_single_image_with_status, run_pipeline_directly,
|
|
batch_ocr_with_status, batch_process_orders_with_status,
|
|
merge_orders_with_status, process_excel_file_with_status,
|
|
process_dropped_file,
|
|
)
|
|
from .config_dialog import show_config_dialog
|
|
from .barcode_editor import edit_barcode_mappings
|
|
from .shortcuts import bind_keyboard_shortcuts
|
|
from app.core.utils.dialog_utils import show_cloud_sync_dialog
|
|
|
|
|
|
def _init_window():
|
|
"""初始化窗口、主题和设置,返回 (root, theme, settings, dnd_supported)"""
|
|
ensure_directories()
|
|
|
|
dnd_supported = False
|
|
try:
|
|
from tkinterdnd2 import TkinterDnD, DND_FILES
|
|
root = TkinterDnD.Tk()
|
|
dnd_supported = True
|
|
except Exception:
|
|
root = tk.Tk()
|
|
|
|
settings = load_user_settings()
|
|
theme_mode = settings.get('theme_mode', get_theme_mode())
|
|
set_theme_mode(theme_mode)
|
|
|
|
try:
|
|
cfg_for_title = ConfigManager()
|
|
ver = cfg_for_title.get('App', 'version', fallback='dev')
|
|
root.title(f"益选-OCR订单处理系统 v{ver} by 欢欢欢")
|
|
except Exception:
|
|
root.title("益选-OCR订单处理系统 by 欢欢欢")
|
|
|
|
root.geometry("900x600")
|
|
settings['window_size'] = "900x600"
|
|
theme = THEMES[get_theme_mode()]
|
|
root.configure(bg=theme["bg"])
|
|
|
|
try:
|
|
log_level = settings.get('log_level')
|
|
if log_level:
|
|
set_log_level(log_level)
|
|
concurrency = settings.get('concurrency_max_workers')
|
|
if concurrency:
|
|
cfg = ConfigManager()
|
|
cfg.update('Performance', 'max_workers', str(concurrency))
|
|
cfg.save_config()
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
root.iconbitmap(default="")
|
|
except Exception:
|
|
pass
|
|
|
|
return root, theme, settings, dnd_supported
|
|
|
|
|
|
def _create_left_panel(content_frame, theme, log_text, status_bar):
|
|
"""创建左侧面板:完整流程、OCR处理、Excel处理、最近文件"""
|
|
left_panel = create_card_frame(content_frame)
|
|
left_panel.pack(side=tk.LEFT, fill=tk.BOTH, expand=False, padx=(0, 5), pady=5)
|
|
left_panel.configure(width=160)
|
|
|
|
panel_content = tk.Frame(left_panel, bg=theme["card_bg"])
|
|
panel_content.pack(fill=tk.BOTH, expand=True, padx=10, pady=(5, 10))
|
|
|
|
# 完整流程区
|
|
pipeline_section = tk.LabelFrame(
|
|
panel_content, text="完整流程", bg=theme["card_bg"], fg=theme["fg"],
|
|
font=("Microsoft YaHei UI", 10, "bold"), relief="flat", borderwidth=0
|
|
)
|
|
pipeline_section.pack(fill=tk.X, pady=(0, 8))
|
|
pipeline_frame = tk.Frame(pipeline_section, bg=theme["card_bg"])
|
|
pipeline_frame.pack(fill=tk.X, padx=8, pady=6)
|
|
create_modern_button(pipeline_frame, "一键处理", lambda: run_pipeline_directly(log_text, status_bar), "primary", px_width=150, px_height=32).pack(anchor='w', pady=3)
|
|
|
|
# OCR处理区
|
|
core_section = tk.LabelFrame(
|
|
panel_content, text="OCR处理", bg=theme["card_bg"], fg=theme["fg"],
|
|
font=("Microsoft YaHei UI", 10, "bold"), relief="flat", borderwidth=0
|
|
)
|
|
core_section.pack(fill=tk.X, pady=(0, 8))
|
|
core_buttons_frame = tk.Frame(core_section, bg=theme["card_bg"])
|
|
core_buttons_frame.pack(fill=tk.X, padx=8, pady=6)
|
|
core_row1 = tk.Frame(core_buttons_frame, bg=theme["card_bg"])
|
|
core_row1.pack(fill=tk.X, pady=3)
|
|
create_modern_button(core_row1, "批量识别", lambda: batch_ocr_with_status(log_text, status_bar), "primary", px_width=72, px_height=32).pack(side=tk.LEFT, padx=(0, 3))
|
|
create_modern_button(core_row1, "单个识别", lambda: process_single_image_with_status(log_text, status_bar), "primary", px_width=72, px_height=32).pack(side=tk.LEFT, padx=(3, 0))
|
|
|
|
# Excel处理区
|
|
ocr_section = tk.LabelFrame(
|
|
panel_content, text="Excel处理", bg=theme["card_bg"], fg=theme["fg"],
|
|
font=("Microsoft YaHei UI", 10, "bold"), relief="flat", borderwidth=0
|
|
)
|
|
ocr_section.pack(fill=tk.X, pady=(0, 8))
|
|
ocr_buttons_frame = tk.Frame(ocr_section, bg=theme["card_bg"])
|
|
ocr_buttons_frame.pack(fill=tk.X, padx=8, pady=6)
|
|
ocr_row1 = tk.Frame(ocr_buttons_frame, bg=theme["card_bg"])
|
|
ocr_row1.pack(fill=tk.X, pady=3)
|
|
create_modern_button(ocr_row1, "批量处理", lambda: batch_process_orders_with_status(log_text, status_bar), "primary", px_width=72, px_height=32).pack(side=tk.LEFT, padx=(0, 3))
|
|
create_modern_button(ocr_row1, "单个处理", lambda: process_excel_file_with_status(log_text, status_bar), "primary", px_width=72, px_height=32).pack(side=tk.LEFT, padx=(3, 0))
|
|
|
|
# 最近文件区
|
|
_create_recent_files_section(panel_content, theme, log_text)
|
|
|
|
|
|
def _create_recent_files_section(parent, theme, log_text):
|
|
"""创建最近文件列表区域"""
|
|
recent_section = tk.LabelFrame(
|
|
parent, text="最近文件", bg=theme["card_bg"], fg=theme["fg"],
|
|
font=("Microsoft YaHei UI", 10, "bold"), relief="flat", borderwidth=0
|
|
)
|
|
recent_section.pack(fill=tk.BOTH, pady=(0, 12))
|
|
recent_frame = tk.Frame(recent_section, bg=theme["card_bg"])
|
|
recent_frame.pack(fill=tk.BOTH, padx=8, pady=6)
|
|
recent_top = tk.Frame(recent_frame, bg=theme["card_bg"])
|
|
recent_top.pack(fill=tk.X)
|
|
|
|
def _resize_recent_top(e):
|
|
try:
|
|
h = max(int(e.height * 0.85), 180)
|
|
recent_top.configure(height=h)
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
recent_top.pack_propagate(False)
|
|
except Exception:
|
|
pass
|
|
recent_frame.bind('<Configure>', _resize_recent_top)
|
|
|
|
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=20)
|
|
recent_scrollbar = tk.Scrollbar(recent_rect)
|
|
recent_list.configure(yscrollcommand=recent_scrollbar.set)
|
|
recent_scrollbar.configure(command=recent_list.yview)
|
|
recent_list.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
|
recent_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
|
|
|
import app.ui.user_settings as _us_mod
|
|
_us_mod.RECENT_LIST_WIDGET = recent_list
|
|
|
|
def _open_selected_event(evt=None):
|
|
try:
|
|
idxs = recent_list.curselection()
|
|
if not idxs:
|
|
return
|
|
p = _extract_path_from_recent_item(recent_list.get(idxs[0]))
|
|
if os.path.exists(p):
|
|
os.startfile(p)
|
|
else:
|
|
messagebox.showwarning("文件不存在", p)
|
|
except Exception as e:
|
|
messagebox.showerror("打开失败", str(e))
|
|
|
|
recent_list.bind('<Double-Button-1>', _open_selected_event)
|
|
refresh_recent_list_widget()
|
|
rf_btns = tk.Frame(recent_frame, bg=theme["card_bg"])
|
|
rf_btns.pack(fill=tk.X, pady=6)
|
|
|
|
def clear_list():
|
|
clear_recent_files()
|
|
recent_list.delete(0, tk.END)
|
|
|
|
create_modern_button(rf_btns, "清空列表", clear_list, "primary", px_width=72, px_height=32).pack(side=tk.LEFT, padx=(3, 0))
|
|
|
|
def purge_invalid():
|
|
try:
|
|
kept = []
|
|
for i in range(recent_list.size()):
|
|
item = recent_list.get(i)
|
|
p = _extract_path_from_recent_item(item)
|
|
if os.path.exists(p):
|
|
kept.append(p)
|
|
try:
|
|
kept_sorted = sorted(kept, key=lambda p: os.path.getmtime(p), reverse=True)
|
|
except Exception:
|
|
kept_sorted = kept
|
|
s = load_user_settings()
|
|
s['recent_files'] = kept_sorted
|
|
save_user_settings(s)
|
|
recent_list.delete(0, tk.END)
|
|
for i, p in enumerate(s['recent_files'][:recent_list.size() or len(s['recent_files'])], start=1):
|
|
recent_list.insert(tk.END, f"{i}. {p}")
|
|
refresh_recent_list_widget()
|
|
add_to_log(log_text, "已清理无效的最近文件条目\n", "success")
|
|
except Exception as e:
|
|
messagebox.showerror("清理失败", str(e))
|
|
|
|
create_modern_button(rf_btns, "清理无效", purge_invalid, "primary", px_width=72, px_height=32).pack(side=tk.LEFT, padx=(3, 0))
|
|
|
|
|
|
def _create_right_panel(content_frame, theme, log_text, root):
|
|
"""创建右侧面板:快捷操作、系统设置"""
|
|
right_panel = create_card_frame(content_frame)
|
|
right_panel.pack(side=tk.RIGHT, fill=tk.BOTH, expand=False, padx=(5, 0), pady=5)
|
|
right_panel.configure(width=380)
|
|
|
|
right_panel_content = tk.Frame(right_panel, bg=theme["card_bg"])
|
|
right_panel_content.pack(fill=tk.BOTH, expand=True, padx=10, pady=(5, 10))
|
|
|
|
# 工具功能区
|
|
tools_section = tk.LabelFrame(
|
|
right_panel_content, text="快捷操作", bg=theme["card_bg"], fg=theme["fg"],
|
|
font=("Microsoft YaHei UI", 10, "bold"), relief="flat", borderwidth=0
|
|
)
|
|
tools_section.pack(fill=tk.X, pady=(0, 8))
|
|
tools_buttons_frame = tk.Frame(tools_section, bg=theme["card_bg"])
|
|
tools_buttons_frame.pack(fill=tk.X, padx=8, pady=6)
|
|
tk.Frame(tools_buttons_frame, bg=theme["card_bg"]).pack(fill=tk.X, pady=3)
|
|
|
|
create_modern_button(tools_buttons_frame, "打开结果目录", lambda: open_result_directory(), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
|
|
create_modern_button(tools_buttons_frame, "打开输出目录", lambda: os.startfile(os.path.abspath("data/output")), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
|
|
create_modern_button(tools_buttons_frame, "打开输入目录", lambda: os.startfile(os.path.abspath("data/input")), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
|
|
create_modern_button(tools_buttons_frame, "合并订单", lambda: merge_orders_with_status(log_text, StatusBar(root)), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
|
|
create_modern_button(tools_buttons_frame, "清除缓存", lambda: clean_cache(log_text), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
|
|
create_modern_button(tools_buttons_frame, "清理input/out文件", lambda: clean_data_files(log_text), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
|
|
create_modern_button(tools_buttons_frame, "清理result文件", lambda: clean_result_files(log_text), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
|
|
|
|
# 系统设置区
|
|
settings_section = tk.LabelFrame(
|
|
right_panel_content, text="系统设置", bg=theme["card_bg"], fg=theme["fg"],
|
|
font=("Microsoft YaHei UI", 10, "bold"), relief="flat", borderwidth=0
|
|
)
|
|
settings_section.pack(fill=tk.X, pady=(0, 8))
|
|
settings_buttons_frame = tk.Frame(settings_section, bg=theme["card_bg"])
|
|
settings_buttons_frame.pack(fill=tk.X, padx=8, pady=6)
|
|
create_modern_button(settings_buttons_frame, "系统设置", lambda: show_config_dialog(root, ConfigManager()), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
|
|
create_modern_button(settings_buttons_frame, "条码映射", lambda: edit_barcode_mappings(log_text), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
|
|
create_modern_button(settings_buttons_frame, "云端同步", lambda: show_cloud_sync_dialog(root), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
|
|
|
|
|
|
def _setup_drag_area(mid_container, theme, dnd_supported, log_text, status_bar):
|
|
"""创建拖拽/点击选择文件区域"""
|
|
drag_panel = create_card_frame(mid_container)
|
|
drag_panel.pack(side=tk.TOP, fill=tk.X, padx=(5, 5), pady=(0, 5))
|
|
drag_panel_content = tk.Frame(drag_panel, bg=theme["card_bg"])
|
|
drag_panel_content.pack(fill=tk.X, padx=10, pady=6)
|
|
|
|
dnd_section = tk.LabelFrame(
|
|
drag_panel_content, bg=theme["card_bg"], fg=theme["fg"],
|
|
font=("Microsoft YaHei UI", 10, "bold"), relief="flat", borderwidth=0
|
|
)
|
|
dnd_section.pack(fill=tk.X, pady=(0, 0))
|
|
dnd_frame = tk.Frame(dnd_section, bg=theme["card_bg"], highlightthickness=1, highlightbackground=theme["border"])
|
|
dnd_frame.configure(height=60)
|
|
dnd_frame.pack(fill=tk.X, padx=8, pady=6)
|
|
try:
|
|
dnd_frame.pack_propagate(False)
|
|
except Exception:
|
|
pass
|
|
|
|
def _set_highlight(active: bool):
|
|
try:
|
|
dnd_frame.configure(highlightbackground=theme["info"] if active else theme["border"])
|
|
except Exception:
|
|
pass
|
|
|
|
dnd_frame.bind('<Enter>', lambda e: _set_highlight(True))
|
|
dnd_frame.bind('<Leave>', lambda e: _set_highlight(False))
|
|
|
|
msg_row = tk.Frame(dnd_frame, bg=theme["card_bg"])
|
|
msg_row.pack(fill=tk.X)
|
|
if dnd_supported:
|
|
tk.Label(
|
|
msg_row, text="拖拽已启用:拖拽或点击此区域选择文件",
|
|
bg=theme["card_bg"], fg="#999999", justify="center"
|
|
).pack(fill=tk.X)
|
|
else:
|
|
tk.Label(
|
|
msg_row, text="点击此区域选择文件;可安装拖拽支持",
|
|
bg=theme["card_bg"], fg="#999999", justify="center"
|
|
).pack(fill=tk.X)
|
|
|
|
if not dnd_supported:
|
|
btn_row = tk.Frame(dnd_frame, bg=theme["card_bg"])
|
|
btn_row.pack(fill=tk.X)
|
|
|
|
is_frozen = getattr(sys, 'frozen', False)
|
|
|
|
def copy_install():
|
|
try:
|
|
mid_container.winfo_toplevel().clipboard_clear()
|
|
mid_container.winfo_toplevel().clipboard_append("pip install tkinterdnd2")
|
|
messagebox.showinfo("已复制", "已复制安装命令:pip install tkinterdnd2")
|
|
except Exception as e:
|
|
messagebox.showwarning("复制失败", str(e))
|
|
|
|
if is_frozen:
|
|
tk.Label(
|
|
btn_row, text="EXE版不支持运行时安装,请用源码版安装后重新打包",
|
|
bg=theme["card_bg"], fg="#999999", font=("Microsoft YaHei UI", 8)
|
|
).pack(side=tk.RIGHT, padx=4)
|
|
else:
|
|
def install_and_restart():
|
|
try:
|
|
add_to_log(log_text, "开始安装拖拽支持库 tkinterdnd2...\n", "info")
|
|
cmd = [sys.executable, "-m", "pip", "install", "tkinterdnd2"]
|
|
result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
add_to_log(log_text, result.stdout + "\n", "info")
|
|
add_to_log(log_text, "安装成功,准备重启程序以启用拖拽...\n", "success")
|
|
if messagebox.askyesno("安装完成", "已安装拖拽支持,是否立即重启应用?"):
|
|
os.execl(sys.executable, sys.executable, *sys.argv)
|
|
except subprocess.CalledProcessError as e:
|
|
add_to_log(log_text, f"安装失败: {e.stderr}\n", "error")
|
|
messagebox.showerror("安装失败", f"安装输出:\n{e.stderr}")
|
|
except Exception as e:
|
|
add_to_log(log_text, f"安装失败: {str(e)}\n", "error")
|
|
messagebox.showerror("安装失败", str(e))
|
|
|
|
create_modern_button(btn_row, "一键安装拖拽", install_and_restart, "primary", px_width=132, px_height=28).pack(side=tk.RIGHT, padx=(3, 0))
|
|
|
|
create_modern_button(btn_row, "复制安装命令", copy_install, "primary", px_width=132, px_height=28).pack(side=tk.RIGHT)
|
|
|
|
# 点击拖拽框选择文件
|
|
def _click_select(evt=None):
|
|
try:
|
|
files = filedialog.askopenfilenames(
|
|
title="选择图片或Excel文件",
|
|
filetypes=[
|
|
("支持文件", "*.xlsx *.xls *.jpg *.jpeg *.png *.bmp"),
|
|
("Excel", "*.xlsx *.xls"),
|
|
("图片", "*.jpg *.jpeg *.png *.bmp"),
|
|
("所有文件", "*.*"),
|
|
]
|
|
)
|
|
if not files:
|
|
return
|
|
for p in files:
|
|
process_dropped_file(log_text, status_bar, p)
|
|
except Exception as e:
|
|
messagebox.showerror("选择失败", str(e))
|
|
|
|
dnd_frame.bind('<Button-1>', _click_select)
|
|
msg_row.bind('<Button-1>', _click_select)
|
|
|
|
if dnd_supported:
|
|
def _on_drop(event):
|
|
try:
|
|
data = event.data
|
|
paths = []
|
|
buf = ""
|
|
in_brace = False
|
|
for ch in data:
|
|
if ch == '{':
|
|
in_brace = True
|
|
buf = ""
|
|
elif ch == '}':
|
|
in_brace = False
|
|
paths.append(buf)
|
|
buf = ""
|
|
elif ch == ' ' and not in_brace:
|
|
if buf:
|
|
paths.append(buf)
|
|
buf = ""
|
|
else:
|
|
buf += ch
|
|
if buf:
|
|
paths.append(buf)
|
|
for p in paths:
|
|
process_dropped_file(log_text, status_bar, p)
|
|
except Exception as e:
|
|
add_to_log(log_text, f"拖拽处理失败: {str(e)}\n", "error")
|
|
|
|
try:
|
|
from tkinterdnd2 import DND_FILES
|
|
dnd_frame.drop_target_register(DND_FILES)
|
|
dnd_frame.dnd_bind('<<Drop>>', _on_drop)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def _create_log_panel(mid_container, theme):
|
|
"""创建中间日志面板,返回 log_text widget"""
|
|
log_panel = create_card_frame(mid_container, "处理日志")
|
|
log_panel.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=(5, 5), pady=5)
|
|
|
|
log_text = scrolledtext.ScrolledText(
|
|
log_panel, wrap=tk.WORD, width=68, height=26,
|
|
bg=theme["log_bg"], fg=theme["log_fg"],
|
|
font=("Consolas", 9), state=tk.DISABLED,
|
|
relief="flat", borderwidth=0
|
|
)
|
|
log_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=(5, 10))
|
|
|
|
log_text.tag_configure("command", foreground=theme["info"], font=("Consolas", 9, "bold"))
|
|
log_text.tag_configure("time", foreground=theme["secondary_bg"], font=("Consolas", 8))
|
|
log_text.tag_configure("separator", foreground=theme["border"])
|
|
log_text.tag_configure("success", foreground=theme["success"], font=("Consolas", 9, "bold"))
|
|
log_text.tag_configure("error", foreground=theme["error"], font=("Consolas", 9, "bold"))
|
|
log_text.tag_configure("warning", foreground=theme["warning"], font=("Consolas", 9, "bold"))
|
|
log_text.tag_configure("info", foreground=theme["info"], font=("Consolas", 9))
|
|
|
|
poll_log_queue(log_text)
|
|
|
|
try:
|
|
_ver = ConfigManager().get('App', 'version', fallback='')
|
|
_ver_str = f" v{_ver}" if _ver else ""
|
|
except Exception:
|
|
_ver_str = ""
|
|
add_to_log(log_text, f"欢迎使用 益选-OCR订单处理系统{_ver_str}\n", "success")
|
|
add_to_log(log_text, "系统已就绪,请选择相应功能进行操作。\n\n", "info")
|
|
add_to_log(log_text, "功能说明:\n", "command")
|
|
add_to_log(log_text, "• 完整处理流程:一键完成OCR识别和Excel处理\n", "info")
|
|
add_to_log(log_text, "• 批量处理订单:批量处理多个订单文件\n", "info")
|
|
add_to_log(log_text, "• 处理烟草订单:专门处理烟草类订单\n", "info")
|
|
add_to_log(log_text, "• 合并订单:将多个订单合并为一个文件\n\n", "info")
|
|
add_to_log(log_text, "请将需要处理的图片文件放入 data/input 目录中。\n", "warning")
|
|
add_to_log(log_text, "OCR识别结果保存在 data/output 目录,处理完成的订单保存在 result 目录中。\n\n", "warning")
|
|
add_to_log(log_text, "=" * 50 + "\n\n", "separator")
|
|
|
|
return log_text
|
|
|
|
|
|
def main():
|
|
"""主函数"""
|
|
try:
|
|
root, theme, settings, dnd_supported = _init_window()
|
|
|
|
# 主容器
|
|
main_container = tk.Frame(root, bg=theme["bg"])
|
|
main_container.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
content_frame = tk.Frame(main_container, bg=theme["bg"])
|
|
content_frame.pack(fill=tk.BOTH, expand=True)
|
|
|
|
# 中间容器(拖拽区 + 日志区)
|
|
mid_container = tk.Frame(content_frame, bg=theme["bg"])
|
|
mid_container.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(5, 5), pady=5)
|
|
|
|
log_text = _create_log_panel(mid_container, theme)
|
|
|
|
# 状态栏
|
|
status_bar = StatusBar(root)
|
|
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
|
|
|
|
# 左侧面板
|
|
_create_left_panel(content_frame, theme, log_text, status_bar)
|
|
|
|
# 右侧面板
|
|
_create_right_panel(content_frame, theme, log_text, root)
|
|
|
|
# 拖拽区域
|
|
_setup_drag_area(mid_container, theme, dnd_supported, log_text, status_bar)
|
|
|
|
# 快捷键 + 关闭事件
|
|
def on_close():
|
|
try:
|
|
w = root.winfo_width()
|
|
h = root.winfo_height()
|
|
settings['window_size'] = f"{w}x{h}"
|
|
settings['theme_mode'] = get_theme_mode()
|
|
save_user_settings(settings)
|
|
except Exception:
|
|
pass
|
|
root.destroy()
|
|
|
|
root.protocol("WM_DELETE_WINDOW", on_close)
|
|
bind_keyboard_shortcuts(root, log_text, status_bar)
|
|
|
|
root.mainloop()
|
|
|
|
except Exception as e:
|
|
import traceback
|
|
error_msg = f"程序启动失败: {str(e)}\n详细错误信息:\n{traceback.format_exc()}"
|
|
print(error_msg)
|
|
try:
|
|
import tkinter.messagebox as mb
|
|
mb.showerror("启动错误", f"程序启动失败:\n{str(e)}")
|
|
except Exception:
|
|
pass
|