import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import os
import shutil
from pathlib import Path
import json
from datetime import datetime
import threading
File type categories
FILE_CATEGORIES = {
"图片": [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg", ".webp", ".ico", ".tiff"],
"文档": [".doc", ".docx", ".pdf", ".txt", ".md", ".rtf", ".odt", ".pages", ".xls", ".xlsx", ".ppt", ".pptx"],
"视频": [".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".m4v"],
"音频": [".mp3", ".wav", ".flac", ".aac", ".ogg", ".wma", ".m4a"],
"压缩包": [".zip", ".rar", ".7z", ".tar", ".gz", ".bz2", ".xz"],
"代码": [".py", ".js", ".html", ".css", ".json", ".xml", ".yaml", ".yml", ".sh", ".bat", ".ps1"],
"可执行文件": [".exe", ".msi", ".dmg", ".app", ".apk", ".deb", ".rpm"],
"其他": []
}
class FileOrganizerApp:
def init(self, root):
self.root = root
self.root.title("桌面文件智能分区收纳工具")
self.root.geometry("800x600")
self.source_dir = tk.StringVar()
self.target_dir = tk.StringVar()
self.use_source_as_target = tk.BooleanVar(value=True)
self.preview_mode = tk.BooleanVar(value=True)
self.create_folders = tk.BooleanVar(value=True)
self.setup_ui()
def setup_ui(self):
# Source directory
frame_source = ttk.LabelFrame(self.root, text="源目录 (待整理)", padding=10)
frame_source.pack(fill="x", padx=10, pady=5)
ttk.Entry(frame_source, textvariable=self.source_dir, width=60).pack(side="left", padx=5)
ttk.Button(frame_source, text="选择目录", command=self.select_source).pack(side="left", padx=5)
ttk.Button(frame_source, text="桌面", command=self.use_desktop).pack(side="left", padx=5)
# Target directory
frame_target = ttk.LabelFrame(self.root, text="目标目录 (分类后存放)", padding=10)
frame_target.pack(fill="x", padx=10, pady=5)
ttk.Checkbutton(frame_target, text="与源目录相同", variable=self.use_source_as_target,
command=self.toggle_target).pack(anchor="w", padx=5, pady=2)
self.target_entry = ttk.Entry(frame_target, textvariable=self.target_dir, width=60, state="disabled")
self.target_entry.pack(side="left", padx=5, pady=5)
self.target_button = ttk.Button(frame_target, text="选择目录", command=self.select_target, state="disabled")
self.target_button.pack(side="left", padx=5, pady=5)
# Options
frame_options = ttk.LabelFrame(self.root, text="选项", padding=10)
frame_options.pack(fill="x", padx=10, pady=5)
ttk.Checkbutton(frame_options, text="预览模式 (不实际移动,仅查看)", variable=self.preview_mode).pack(anchor="w", padx=5, pady=2)
ttk.Checkbutton(frame_options, text="自动创建分类文件夹", variable=self.create_folders).pack(anchor="w", padx=5, pady=2)
# Action buttons
frame_actions = ttk.Frame(self.root, padding=10)
frame_actions.pack(fill="x", padx=10, pady=5)
ttk.Button(frame_actions, text="扫描文件", command=self.scan_files).pack(side="left", padx=5)
ttk.Button(frame_actions, text="开始整理", command=self.start_organize).pack(side="left", padx=5)
ttk.Button(frame_actions, text="清空日志", command=self.clear_log).pack(side="left", padx=5)
# Log area
frame_log = ttk.LabelFrame(self.root, text="执行日志", padding=10)
frame_log.pack(fill="both", expand=True, padx=10, pady=5)
self.log_text = scrolledtext.ScrolledText(frame_log, height=15, wrap=tk.WORD)
self.log_text.pack(fill="both", expand=True)
# Progress bar
self.progress = ttk.Progressbar(self.root, mode="indeterminate")
self.progress.pack(fill="x", padx=10, pady=5)
def select_source(self):
dir_path = filedialog.askdirectory()
if dir_path:
self.source_dir.set(dir_path)
def use_desktop(self):
desktop = os.path.join(os.path.expanduser("~"), "Desktop")
self.source_dir.set(desktop)
def toggle_target(self):
if self.use_source_as_target.get():
self.target_dir.set("")
self.target_entry.config(state="disabled")
self.target_button.config(state="disabled")
else:
self.target_entry.config(state="normal")
self.target_button.config(state="normal")
def select_target(self):
dir_path = filedialog.askdirectory()
if dir_path:
self.target_dir.set(dir_path)
def log(self, message):
self.log_text.insert(tk.END, f"[{datetime.now().strftime('%H:%M:%S')}] {message}\n")
self.log_text.see(tk.END)
def clear_log(self):
self.log_text.delete(1.0, tk.END)
def scan_files(self):
source = self.source_dir.get()
if not source or not os.path.exists(source):
messagebox.showerror("错误", "请先选择有效的源目录")
return
self.log("开始扫描文件...")
# Count files by category
category_count = {cat: 0 for cat in FILE_CATEGORIES.keys()}
for root, dirs, files in os.walk(source):
for file in files:
ext = os.path.splitext(file)[1].lower()
categorized = False
for cat, exts in FILE_CATEGORIES.items():
if ext in exts:
category_count[cat] += 1
categorized = True
break
if not categorized:
category_count["其他"] += 1
self.log(f"扫描完成!文件统计:")
for cat, count in category_count.items():
if count > 0:
self.log(f" {cat}: {count} 个文件")
def start_organize(self):
source = self.source_dir.get()
if not source or not os.path.exists(source):
messagebox.showerror("错误", "请先选择有效的源目录")
return
target = self.target_dir.get() if not self.use_source_as_target.get() else source
if not target:
target = source
if not os.path.exists(target):
os.makedirs(target)
# Confirm
if not self.preview_mode.get():
if not messagebox.askyesno("确认", "确定要开始移动文件吗?"):
return
self.log(f"{'预览模式:' if self.preview_mode.get() else '开始整理:'} {source} -> {target}")
# Start organizing in a separate thread
threading.Thread(target=self.organize_files, args=(source, target), daemon=True).start()
def organize_files(self, source, target):
self.progress.start()
move_log = []
for root, dirs, files in os.walk(source):
for file in files:
src_path = os.path.join(root, file)
# Skip if file is in target subdirectory (already organized)
if os.path.commonpath([src_path, target]) == target and src_path != source:
continue
ext = os.path.splitext(file)[1].lower()
# Determine category
category = "其他"
for cat, exts in FILE_CATEGORIES.items():
if ext in exts:
category = cat
break
# Create target directory
cat_dir = os.path.join(target, category)
if self.create_folders.get() and not os.path.exists(cat_dir):
if not self.preview_mode.get():
os.makedirs(cat_dir, exist_ok=True)
self.log(f"创建文件夹: {cat_dir}")
else:
self.log(f"[预览] 将创建文件夹: {cat_dir}")
dst_path = os.path.join(cat_dir, file)
# Handle duplicates
if os.path.exists(dst_path):
base, ext_part = os.path.splitext(file)
counter = 1
while os.path.exists(dst_path):
new_name = f"{base}_{counter}{ext_part}"
dst_path = os.path.join(cat_dir, new_name)
counter += 1
if not self.preview_mode.get():
try:
shutil.move(src_path, dst_path)
self.log(f"移动: {file} -> {category}/")
move_log.append({"src": src_path, "dst": dst_path})
except Exception as e:
self.log(f"错误: 无法移动 {file}: {e}")
else:
self.log(f"[预览] 将移动: {file} -> {category}/{os.path.basename(dst_path)}")
# Save move log for undo
if not self.preview_mode.get() and move_log:
log_path = os.path.join(target, "file_organizer_log.json")
with open(log_path, "w", encoding="utf-8") as f:
json.dump(move_log, f, ensure_ascii=False, indent=2)
self.log(f"移动日志已保存: {log_path}")
self.progress.stop()
self.log("完成!")
messagebox.showinfo("完成", "文件整理完成!")
if name == "main":
root = tk.Tk()
app = FileOrganizerApp(root)
root.mainloop()