当前位置:首页>python>利用Python快速整理删除工程文件

利用Python快速整理删除工程文件

  • 2026-10-11 06:36:25
利用Python快速整理删除工程文件
日常工作里对接资料是常事,整理打包资料时总会遇到一个普遍痛点:整个文件夹里文档繁杂,对方往往只需要其中一部分文件,其余附件、草稿、过程版、作废图纸、多余台账全都属于冗余内容。手动逐个挑选、删除费时费力,删错还难以恢复;大批量文件修改时间戳统一规整也只能挨个右键设置,效率极低。用Python 来弄就简单多了。
# -*- coding: utf-8 -*-"""文件筛选工具 v3.0功能:  1. 按关键字筛选文件(删除包含/不包含关键字的文件)-> 回收站  2. 将文件夹中所有文件的修改日期调整为今天  3. 支持递归/非递归模式无需额外依赖,删除直接走Windows原生回收站API"""import osimport sysimport ctypesimport tracebackfrom datetime import datetimeimport tkinter as tkfrom tkinter import ttk, messagebox, filedialog# ========== Windows原生回收站API(无需send2trash)==========def send_to_recycle_bin(path):    """使用Windows shell32 API将文件/文件夹移至回收站"""    abs_path = os.path.abspath(path)    if len(abs_path) > 240:        if not abs_path.startswith('\\\\?\\'):            abs_path = '\\\\?\\' + abs_path    class SHFILEOPSTRUCTW(ctypes.Structure):        _fields_ = [            ("hwnd", ctypes.c_void_p),            ("wFunc", ctypes.c_uint),            ("pFrom", ctypes.c_wchar_p),            ("pTo", ctypes.c_wchar_p),            ("fFlags", ctypes.c_ushort),            ("fAnyOperationsAborted", ctypes.c_bool),            ("hNameMappings", ctypes.c_void_p),            ("lpszProgressTitle", ctypes.c_wchar_p),        ]    FO_DELETE = 0x0003    FOF_ALLOWUNDO = 0x0040    FOF_NOCONFIRMATION = 0x0010    FOF_SILENT = 0x0004    op = SHFILEOPSTRUCTW()    op.hwnd = None    op.wFunc = FO_DELETE    op.pFrom = abs_path + '\0'    op.pTo = None    op.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT    op.fAnyOperationsAborted = False    op.hNameMappings = None    op.lpszProgressTitle = None    result = ctypes.windll.shell32.SHFileOperationW(ctypes.byref(op))    if result != 0:        raise OSError(f"SHFileOperationW returned {result}")    if op.fAnyOperationsAborted:        raise OSError("Operation was aborted")def set_file_mtime(path, new_time=None):    if new_time is None:        new_time = datetime.now()    timestamp = new_time.timestamp()    os.utime(path, (timestamp, timestamp))def show_fatal_error(msg):    try:        ctypes.windll.user32.MessageBoxW(0, msg, "启动错误", 0x10)    except:        print(msg, file=sys.stderr)# ========== 配色主题 ==========COLORS = {    'bg_main': '#F8F9FA',    'bg_card': '#FFFFFF',    'primary': '#2563EB',    'primary_hover': '#1D4ED8',    'danger': '#DC2626',    'danger_hover': '#B91C1C',    'success': '#059669',    'success_hover': '#047857',    'text': '#1F2937',    'text_light': '#6B7280',    'border': '#E5E7EB',    'header': '#1E3A5F',}class FileFilterApp:    def __init__(self, root):        self.root = root        self.root.title("文件筛选工具 v3.0")        self.root.geometry("880x720")        self.root.configure(bg=COLORS['bg_main'])        self.root.resizable(False, False)        self.current_dir = os.getcwd()        self._create_widgets()        self.refresh_list()    def _create_widgets(self):        # ----- 标题栏 -----        header = tk.Frame(self.root, bg=COLORS['header'], height=55)        header.pack(fill='x')        header.pack_propagate(False)        tk.Label(header, text="文件筛选工具", font=("微软雅黑", 18, "bold"),                fg='white', bg=COLORS['header']).pack(side='left', padx=20, pady=8)        tk.Label(header, text="智能文件管理", font=("微软雅黑", 11),                fg='#B0C4DE', bg=COLORS['header']).pack(side='left', pady=14)        # ----- 主内容区 -----        main = tk.Frame(self.root, bg=COLORS['bg_main'])        main.pack(fill='both', expand=True, padx=16, pady=10)        # 左侧面板        left = tk.Frame(main, bg=COLORS['bg_main'], width=260)        left.pack(side='left', fill='y', padx=(0, 10))        left.pack_propagate(False)        self._create_info_panel(left)        self._create_action_panel(left)        # 右侧面板:文件列表        right = tk.Frame(main, bg=COLORS['bg_card'], bd=1, relief='solid')        right.pack(side='right', fill='both', expand=True)        tk.Label(right, text="文件列表", font=("微软雅黑", 12, "bold"),                bg=COLORS['bg_card'], fg=COLORS['text']).pack(anchor='w', padx=12, pady=8)        self.stats_label = tk.Label(right, text="共 0 个文件", fg=COLORS['text_light'],                                   bg=COLORS['bg_card'], font=("微软雅黑", 9))        self.stats_label.pack(anchor='w', padx=12)        list_frame = tk.Frame(right, bg=COLORS['bg_card'])        list_frame.pack(fill='both', expand=True, padx=12, pady=5)        scrollbar = tk.Scrollbar(list_frame, bg=COLORS['bg_card'])        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)        self.listbox = tk.Listbox(list_frame, selectmode=tk.EXTENDED,                                  yscrollcommand=scrollbar.set, font=("Consolas", 10),                                  bg='#FAFBFC', fg=COLORS['text'],                                  selectbackground=COLORS['primary'],                                  selectforeground='white',                                  relief='flat', highlightthickness=1,                                  highlightcolor=COLORS['border'])        self.listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)        scrollbar.config(command=self.listbox.yview)        # 底部日志        log_frame = tk.LabelFrame(self.root, text="操作日志", font=("微软雅黑", 9),                                  bg=COLORS['bg_main'], fg=COLORS['text_light'])        log_frame.pack(fill='x', padx=16, pady=(0, 10))        self.log_text = tk.Text(log_frame, height=4, font=("Consolas", 9),                                wrap="word", bg='#FAFBFC', fg=COLORS['text'],                                relief='flat', highlightthickness=1,                                highlightcolor=COLORS['border'])        self.log_text.pack(fill='x', padx=6, pady=6)        self.log_text.config(state="disabled")    def _create_info_panel(self, parent):        # 目录信息卡        card = tk.Frame(parent, bg=COLORS['bg_card'], bd=1, relief='solid')        card.pack(fill='x', pady=5, padx=2)        tk.Label(card, text="当前目录", font=("微软雅黑", 10, "bold"),                bg=COLORS['bg_card'], fg=COLORS['text']).pack(anchor='w', padx=10, pady=(8, 2))        self.dir_label = tk.Label(card, text=self.current_dir,                                  bg=COLORS['bg_card'], fg=COLORS['text_light'],                                  font=("Consolas", 8), wraplength=230, justify='left')        self.dir_label.pack(anchor='w', padx=10, pady=(0, 8))        btn_frame = tk.Frame(card, bg=COLORS['bg_card'])        btn_frame.pack(fill='x', padx=10, pady=(0, 10))        tk.Button(btn_frame, text="选择文件夹", command=self._select_dir,                 bg=COLORS['primary'], fg='white', font=("微软雅黑", 9),                 relief='flat', cursor='hand2').pack(side='left', padx=(0, 5))        tk.Button(btn_frame, text="刷新", command=self.refresh_list,                 bg='#E5E7EB', fg=COLORS['text'], font=("微软雅黑", 9),                 relief='flat', cursor='hand2').pack(side='left')        # 选项卡        opt_card = tk.Frame(parent, bg=COLORS['bg_card'], bd=1, relief='solid')        opt_card.pack(fill='x', pady=5, padx=2)        tk.Label(opt_card, text="选项", font=("微软雅黑", 10, "bold"),                bg=COLORS['bg_card'], fg=COLORS['text']).pack(anchor='w', padx=10, pady=8)        self.recursive_var = tk.BooleanVar(value=True)        tk.Checkbutton(opt_card, text="包含子文件夹(递归)",                       variable=self.recursive_var, command=self.refresh_list,                       font=("微软雅黑", 9), bg=COLORS['bg_card'],                       activebackground=COLORS['bg_card']).pack(anchor='w', padx=10, pady=2)        self.show_detail_var = tk.BooleanVar(value=False)        tk.Checkbutton(opt_card, text="显示完整路径",                       variable=self.show_detail_var, command=self.refresh_list,                       font=("微软雅黑", 9), bg=COLORS['bg_card'],                       activebackground=COLORS['bg_card']).pack(anchor='w', padx=10, pady=2)    def _create_action_panel(self, parent):        # Tab切换        self.notebook = ttk.Notebook(parent)        self.notebook.pack(fill='x', pady=5, padx=2)        style = ttk.Style()        style.configure('TNotebook.Tab', font=("微软雅黑", 9))        # Tab 1: 筛选删除        tab1 = tk.Frame(self.notebook, bg=COLORS['bg_main'])        self.notebook.add(tab1, text="筛选删除")        card1 = tk.Frame(tab1, bg=COLORS['bg_card'], bd=1, relief='solid')        card1.pack(fill='x', pady=5, padx=2)        tk.Label(card1, text="关键字", font=("微软雅黑", 10, "bold"),                bg=COLORS['bg_card'], fg=COLORS['text']).pack(anchor='w', padx=10, pady=(10, 4))        self.keyword_entry = tk.Entry(card1, width=24, font=("微软雅黑", 10),                                     relief='solid', highlightthickness=1,                                     highlightcolor=COLORS['primary'])        self.keyword_entry.pack(anchor='w', padx=10, pady=(0, 8))        self.mode_var = tk.StringVar(value="delete_contain")        tk.Radiobutton(card1, text="删除包含关键字的文件",                       variable=self.mode_var, value="delete_contain",                       font=("微软雅黑", 9), bg=COLORS['bg_card'],                       activebackground=COLORS['bg_card']).pack(anchor='w', padx=10, pady=2)        tk.Radiobutton(card1, text="删除不包含关键字的文件",                       variable=self.mode_var, value="delete_not_contain",                       font=("微软雅黑", 9), bg=COLORS['bg_card'],                       activebackground=COLORS['bg_card']).pack(anchor='w', padx=10, pady=2)        tk.Button(card1, text="执行删除 → 回收站", command=self._execute_delete,                 bg=COLORS['danger'], fg='white', font=("微软雅黑", 10, "bold"),                 relief='flat', cursor='hand2', width=20).pack(pady=12)        # Tab 2: 修改日期        tab2 = tk.Frame(self.notebook, bg=COLORS['bg_main'])        self.notebook.add(tab2, text="修改日期")        card2 = tk.Frame(tab2, bg=COLORS['bg_card'], bd=1, relief='solid')        card2.pack(fill='x', pady=5, padx=2)        tk.Label(card2, text='修改日期',                font=("微软雅黑", 10, "bold"),                bg=COLORS['bg_card'], fg=COLORS['text']).pack(anchor='w', padx=10, pady=(10, 4))        tk.Label(card2, text='将文件"修改日期"调整为:',                font=("微软雅黑", 9), bg=COLORS['bg_card'],                fg=COLORS['text_light']).pack(anchor='w', padx=10)        self.date_var = tk.StringVar(value=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))        tk.Entry(card2, textvariable=self.date_var, width=24,                font=("Consolas", 10), relief='solid',                highlightthickness=1, highlightcolor=COLORS['primary']).pack(anchor='w', padx=10, pady=5)        tk.Label(card2, text="格式: YYYY-MM-DD HH:MM:SS",                fg=COLORS['text_light'], font=("微软雅黑", 8),                bg=COLORS['bg_card']).pack(anchor='w', padx=10)        tk.Button(card2, text="一键修改为今天", command=self._execute_set_date,                 bg=COLORS['success'], fg='white', font=("微软雅黑", 10, "bold"),                 relief='flat', cursor='hand2', width=20).pack(pady=12)    def log(self, message):        self.log_text.config(state="normal")        self.log_text.delete("1.0", "end")        self.log_text.insert("1.0", message)        self.log_text.config(state="disabled")        self.root.update()    def _select_dir(self):        new_dir = filedialog.askdirectory(initialdir=self.current_dir)        if new_dir:            self.current_dir = new_dir            self.dir_label.config(text=self.current_dir)            self.refresh_list()    def refresh_list(self):        self.listbox.delete(0, tk.END)        recursive = self.recursive_var.get()        show_full = self.show_detail_var.get()        count = 0        try:            if recursive:                for root_dir, dirs, files in os.walk(self.current_dir):                    rel_root = os.path.relpath(root_dir, self.current_dir)                    if rel_root == ".":                        rel_root = ""                    for file in files:                        count += 1                        if show_full:                            display = os.path.join(root_dir, file)                        else:                            display = os.path.join(rel_root, file) if rel_root else file                        self.listbox.insert(tk.END, display)            else:                for item in sorted(os.listdir(self.current_dir)):                    full_path = os.path.join(self.current_dir, item)                    if os.path.isfile(full_path):                        count += 1                        self.listbox.insert(tk.END, item)            self.stats_label.config(text=f"共 {count} 个文件")        except Exception as e:            messagebox.showerror("错误", f"读取目录失败:{e}")    def _execute_delete(self):        keyword = self.keyword_entry.get().strip()        if not keyword:            messagebox.showwarning("警告", "请输入关键字")            return        file_list = list(self.listbox.get(0, tk.END))        if not file_list:            messagebox.showinfo("提示", "没有文件可操作")            return        mode = self.mode_var.get()        to_delete = []        for display_path in file_list:            if os.path.isabs(display_path):                full_path = display_path            else:                full_path = os.path.join(self.current_dir, display_path)            basename = os.path.basename(full_path)            if mode == "delete_contain":                if keyword in basename:                    to_delete.append(full_path)            else:                if keyword not in basename:                    to_delete.append(full_path)        if not to_delete:            messagebox.showinfo("提示", "没有符合条件的文件")            return        msg = f"将删除 {len(to_delete)} 个文件(移至回收站)。\n\n确定继续吗?"        if not messagebox.askyesno("确认操作", msg):            return        deleted = 0        errors = []        for full_path in to_delete:            try:                send_to_recycle_bin(full_path)                deleted += 1            except Exception as e:                errors.append(f"{os.path.basename(full_path)}: {e}")        result = f"成功将 {deleted} 个文件移至回收站"        if errors:            result += f"\n失败 {len(errors)} 个:\n" + "\n".join(errors[:5])        self.log(result)        messagebox.showinfo("完成", result)        self.refresh_list()    def _execute_set_date(self):        file_list = list(self.listbox.get(0, tk.END))        if not file_list:            messagebox.showinfo("提示", "没有文件可操作")            return        date_str = self.date_var.get().strip()        try:            if date_str:                target_time = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")            else:                target_time = datetime.now()        except ValueError:            messagebox.showerror("错误", "日期格式错误,请使用 YYYY-MM-DD HH:MM:SS")            return        msg = (f"将把 {len(file_list)} 个文件的修改日期\n"               f"调整为: {target_time.strftime('%Y-%m-%d %H:%M:%S')}\n\n"               f"确定继续吗?")        if not messagebox.askyesno("确认操作", msg):            return        success = 0        errors = []        for display_path in file_list:            if os.path.isabs(display_path):                full_path = display_path            else:                full_path = os.path.join(self.current_dir, display_path)            try:                set_file_mtime(full_path, target_time)                success += 1            except Exception as e:                errors.append(f"{os.path.basename(full_path)}: {e}")        result = f"成功修改 {success} 个文件的修改日期"        if errors:            result += f"\n失败 {len(errors)} 个:\n" + "\n".join(errors[:5])        self.log(result)        messagebox.showinfo("完成", result)        self.refresh_list()if __name__ == "__main__":    try:        root = tk.Tk()        app = FileFilterApp(root)        root.mainloop()    except Exception as e:        show_fatal_error(f"程序启动失败:\n\n{str(e)}\n\n详细错误:\n{traceback.format_exc()}")        raise

最新文章

随机文章