Python 编程实战:打造高效便捷的目录结构生成器
相关资源文件已经打包成EXE文件,可双击直接运行程序,且文章末尾已附上相关源码,以供大家学习交流,博主主页还有更多Python相关程序案例,秉着开源精神的想法,望大家喜欢,点个关注不迷路!!!
1.概述
随着文件和目录管理的复杂性不断增加,尤其是在处理大量项目文件和文件夹时,手动管理和查看目录结构变得越来越繁琐。在这个背景下,我们开发了一款 目录结构生成器,它不仅能够帮助用户高效地生成文件夹目录结构,还具备了很多智能化的功能,如文件大小格式化、Excel 导出、拖拽操作以及动态进度条等,极大提升了文件管理的效率。
本文将深入介绍该工具的功能,帮助大家更好地理解并利用它来优化自己的工作流程。
2.功能亮点
1. 目录结构生成
该工具能够遍历指定的文件夹,自动生成该文件夹下所有子文件夹和文件的目录结构。对于每个文件和文件夹,系统会自动获取其名称、大小以及其他必要的属性,并以表格的形式进行展示。
2. 文件大小格式化
通过对文件大小的自动转换,工具能够将字节数(Bytes)转换为更直观的单位,如 KB、MB、GB 等,极大提升了可读性。
3. 支持 Excel 导出
生成的目录结构可以轻松导出为 Excel 文件,支持动态设置 Excel 文件中工作表的名称,并且可以一键点击打开文件所在目录,便于快速访问。
4. 动态进度条与反馈提示
在生成目录结构的过程中,工具会提供进度条,显示当前操作的完成情况。用户能够清晰地看到操作进度,而在操作完成时,工具还会弹出“导出成功”提示,提升了用户体验。
5. 拖拽操作与美化效果
新增的拖拽区域高亮效果,让用户在拖拽目录时能够更加直观地看到拖拽的目标区域,从而提升操作的准确性和流畅性。
6. 文件图标预览
工具还增加了对 Windows 系统的支持,能够展示文件的图标预览。通过 PyQt6 中的 QIconProvider
,让目录中的每个文件都能展现出图标样式,进一步优化了可视化效果。
3. 功能使用
1. 界面设计与操作
该工具的核心界面设计非常简洁,所有操作都集成在一个窗口中,用户可以非常轻松地完成以下任务:
- 选择目标文件夹:点击“选择文件夹”按钮,选择需要生成目录结构的文件夹路径。
- 拖拽操作:在界面的拖拽区域内,用户可以通过拖拽文件夹来快速选择文件夹并自动加载目录结构。
- 查看目录结构:生成的目录结构会以树形结构展示,左侧为文件夹树状图,右侧则展示文件的详细信息(如文件大小、文件类型等)。
- 文件大小显示:文件大小将自动格式化,用户能够清晰地看到文件的大小(如:1.5MB、2GB等)。
- 导出 Excel 文件:点击“导出 Excel”按钮,工具会将当前目录结构生成 Excel 文件。文件生成后,用户可以选择打开文件所在的文件夹。
- 进度反馈:每当操作开始时,工具会显示进度条,完成时会弹出“导出成功”的提示。
2. 高级功能操作
拖拽目录与文件结构生成
为了让用户操作更加便捷,我们特别加入了拖拽操作功能。用户只需要将文件夹拖拽到工具界面的指定区域,工具就会自动加载该文件夹内的目录结构,并以树形方式展示出来。这种方式对于需要快速查看文件夹内容的用户来说,无疑是极大的提升。
导出 Excel 文件
生成目录结构后,用户可以选择将当前目录结构导出为 Excel 文件。导出的文件将包含所有文件夹和文件的相关信息,如文件名、文件路径、文件大小等,并且工具会自动将工作表命名为“目录结构”,方便用户区分。
文件图标预览
对于 Windows 系统用户,工具可以根据每个文件的类型,显示文件的图标。比如,文本文件会显示一个 TXT 图标,图片文件会显示图片缩略图。通过 QIconProvider
,文件图标得以自动提取,并显示在界面上,帮助用户快速识别文件类型。
动态进度条与操作提示
在整个操作过程中,用户可以实时查看进度条,工具会根据文件夹大小和文件数量动态更新进度。当生成目录结构或导出 Excel 完成时,用户会收到一个“导出成功”的提示框,极大地增强了用户体验。
3.运行效果:
4. 总结
1. 整体优化
目录结构生成器 v3.0 在原有版本的基础上,进行了多项优化和功能扩展。尤其是在界面美化和操作便捷性方面,加入了拖拽操作、文件图标预览等细节,使得工具的使用体验更加流畅和直观。
2. 性能提升
除了界面和功能的提升,工具在处理大量文件和目录时的性能也得到了优化。通过合理的进度反馈和动态颜色变化的进度条,用户能够清晰地感受到操作的进展,不会在等待过程中产生焦虑。
3. 实际应用
该工具非常适合需要管理大量文件或处理复杂目录结构的用户,尤其对于开发人员、IT运维人员、文档管理员等群体而言,它可以显著提高工作效率。通过自动生成目录结构和文件大小显示,再结合 Excel 导出功能,用户能够轻松导出目录数据并进行后续处理或备份。
4. 相关源码:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-import sys
import os
import pandas as pd
import time
import logging
from datetime import datetime
from PyQt6.QtWidgets import (QApplication, QWidget, QPushButton, QVBoxLayout, QLabel, QFileDialog, QLineEdit, QTextEdit, QProgressBar, QMessageBox,QCheckBox, QToolTip, QMenu, QHBoxLayout
)
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QPropertyAnimation, QEasingCurve
from PyQt6.QtGui import QIcon, QPixmaplogging.basicConfig(filename='scanner.log', level=logging.INFO)class PathTextEdit(QTextEdit):"""支持拖放文件夹并显示纯路径的自定义文本框"""def __init__(self, parent=None):super().__init__(parent)self.setAcceptDrops(True)self.setPlaceholderText("拖拽文件夹到此处或点击浏览按钮")self.setMinimumHeight(80)def dragEnterEvent(self, event):if any(url.isLocalFile() and os.path.isdir(url.toLocalFile())for url in event.mimeData().urls()):self.setStyleSheet("border: 2px dashed #0078D7; background-color: #F0F7FF;")event.acceptProposedAction()else:event.ignore()def dragLeaveEvent(self, event):self.setStyleSheet("border: 2px dashed #9E9E9E; background-color: #FAFAFA;")event.accept()def dropEvent(self, event):self.setStyleSheet("border: 1px solid #CCCCCC; background-color: white;")valid_paths = []for url in event.mimeData().urls():if url.isLocalFile():path = url.toLocalFile()if os.path.isdir(path):valid_paths.append(path)if valid_paths:self.setText("\n".join(valid_paths))event.accept()else:QToolTip.showText(event.pos(), "请拖入有效的文件夹", self)event.ignore()def contextMenuEvent(self, event):menu = QMenu(self)clear_action = menu.addAction(QIcon("icons/clear.png"), "清空") if os.path.exists("icons/clear.png") else menu.addAction("清空")paste_action = menu.addAction(QIcon("icons/paste.png"), "粘贴路径") if os.path.exists("icons/paste.png") else menu.addAction("粘贴路径")action = menu.exec(event.globalPos())if action == clear_action:self.clear()elif action == paste_action:self.paste()class DirectoryScanner(QThread):progress = pyqtSignal(int, int, float)completed = pyqtSignal(list)error = pyqtSignal(str)def __init__(self, directory, file_types):super().__init__()self.directory = directoryself.file_types = file_typesself._is_running = Truedef stop(self):self._is_running = Falsedef run(self):try:file_data = []total_files = sum(len(files) for _, _, files in os.walk(self.directory))processed_files = 0start_time = time.time()for root, _, files in os.walk(self.directory):if not self._is_running:breakfor file in files:if self.file_types and not any(file.lower().endswith(ft.lower()) for ft in self.file_types if ft):continuetry:file_path = os.path.join(root, file)file_stat = os.stat(file_path)file_data.append([root, file, os.path.splitext(file)[1], os.path.getsize(file_path),time.ctime(file_stat.st_mtime), time.ctime(file_stat.st_ctime), file_path])except Exception as e:logging.error(f"Error processing {file_path}: {str(e)}")continueprocessed_files += 1elapsed_time = time.time() - start_timeestimated_total_time = (elapsed_time / processed_files) * total_files if processed_files else 0remaining_time = max(0, estimated_total_time - elapsed_time)self.progress.emit(processed_files, total_files, remaining_time)if self._is_running:self.completed.emit(file_data)except Exception as e:self.error.emit(f"扫描过程中出错: {str(e)}")class DirectoryScannerApp(QWidget):def __init__(self):super().__init__()self.initUI()self.setup_animations()def initUI(self):self.setWindowTitle("目录结构生成器")self.setGeometry(100, 100, 650, 550)# 主布局self.main_layout = QVBoxLayout()self.main_layout.setSpacing(15)self.main_layout.setContentsMargins(20, 20, 20, 20)# 标题区域title_layout = QHBoxLayout()self.title_icon = QLabel()if os.path.exists("icons/folder.png"):self.title_icon.setPixmap(QPixmap("icons/folder.png").scaled(32, 32))title_layout.addWidget(self.title_icon)self.title_label = QLabel("目录结构生成器")self.title_label.setStyleSheet("font-size: 18pt; font-weight: bold; color: #0078D7;")title_layout.addWidget(self.title_label)title_layout.addStretch()self.main_layout.addLayout(title_layout)# 目录选择部分self.dir_group = QWidget()dir_layout = QVBoxLayout()self.dir_label = QLabel("选择扫描目录:")dir_layout.addWidget(self.dir_label)self.directory_input = PathTextEdit()dir_layout.addWidget(self.directory_input)self.browse_button = QPushButton(" 浏览目录")if os.path.exists("icons/folder_open.png"):self.browse_button.setIcon(QIcon("icons/folder_open.png"))self.browse_button.clicked.connect(self.browse_directory)dir_layout.addWidget(self.browse_button)self.dir_group.setLayout(dir_layout)self.main_layout.addWidget(self.dir_group)# 过滤选项部分self.filter_group = QWidget()filter_layout = QVBoxLayout()self.filter_label = QLabel("文件类型过滤:")filter_layout.addWidget(self.filter_label)self.filter_input = QLineEdit()self.filter_input.setPlaceholderText("例如: .txt,.docx,.xlsx")filter_layout.addWidget(self.filter_input)self.filter_group.setLayout(filter_layout)self.main_layout.addWidget(self.filter_group)# 选项设置self.options_group = QWidget()options_layout = QVBoxLayout()self.checkbox_size = QCheckBox("显示友好文件大小(KB/MB)")self.checkbox_open = QCheckBox("导出后自动打开文件")options_layout.addWidget(self.checkbox_size)options_layout.addWidget(self.checkbox_open)self.options_group.setLayout(options_layout)self.main_layout.addWidget(self.options_group)# 操作按钮self.generate_button = QPushButton(" 生成Excel文件")if os.path.exists("icons/excel.png"):self.generate_button.setIcon(QIcon("icons/excel.png"))self.generate_button.clicked.connect(self.generate_excel)self.main_layout.addWidget(self.generate_button)# 进度显示self.progress_bar = QProgressBar()self.progress_bar.setTextVisible(True)self.main_layout.addWidget(self.progress_bar)# 状态栏self.status_bar = QHBoxLayout()self.status_icon = QLabel()if os.path.exists("icons/info.png"):self.status_icon.setPixmap(QPixmap("icons/info.png").scaled(16, 16))self.status_bar.addWidget(self.status_icon)self.status_label = QLabel("就绪")self.status_bar.addWidget(self.status_label)self.status_bar.addStretch()self.version_label = QLabel(f"探客白泽 © {datetime.now().year}")self.status_bar.addWidget(self.version_label)self.main_layout.addLayout(self.status_bar)self.setLayout(self.main_layout)self.apply_stylesheet()def setup_animations(self):self.animations = {}for btn in [self.browse_button, self.generate_button]:animation = QPropertyAnimation(btn, b"geometry")animation.setDuration(200)animation.setEasingCurve(QEasingCurve.Type.OutQuad)self.animations[btn] = animationdef make_animator(button):return lambda: self.animate_button(button)btn.clicked.connect(make_animator(btn))def animate_button(self, button):animation = self.animations[button]orig_rect = button.geometry()animation.setStartValue(orig_rect.adjusted(0, 5, 0, 5))animation.setEndValue(orig_rect)animation.start()def apply_stylesheet(self):self.setStyleSheet("""/* 主窗口样式 */QWidget {font-family: 'Microsoft YaHei', 'Segoe UI';font-size: 10pt;background-color: #F5F5F5;}/* 分组框样式 */QWidget#dir_group, QWidget#filter_group, QWidget#options_group {background-color: white;border-radius: 6px;padding: 12px;}/* 标签样式 */QLabel {color: #333333;font-weight: 500;}/* 输入框样式 */QTextEdit, QLineEdit {background-color: white;border: 1px solid #CCCCCC;border-radius: 4px;padding: 8px;selection-background-color: #0078D7;}QTextEdit:hover, QLineEdit:hover {border: 1px solid #0078D7;}/* 按钮基础样式 */QPushButton {color: white;border: none;border-radius: 6px;padding: 10px 18px;min-width: 100px;font-weight: 500;}/* 浏览按钮样式 */QPushButton#browse_button {background-color: #4CAF50;}QPushButton#browse_button:hover {background-color: #3E8E41;}QPushButton#browse_button:pressed {background-color: #2E7D32;}/* 生成按钮样式 */QPushButton#generate_button {background-color: #FF5722;font-weight: bold;}QPushButton#generate_button:hover {background-color: #E64A19;}QPushButton#generate_button:pressed {background-color: #BF360C;}/* 进度条样式 */QProgressBar {border: 1px solid #CCCCCC;border-radius: 4px;text-align: center;height: 24px;}QProgressBar::chunk {background-color: #4CAF50;border-radius: 3px;}/* 复选框样式 */QCheckBox {spacing: 8px;color: #333333;}QCheckBox::indicator {width: 18px;height: 18px;}QCheckBox::indicator:unchecked {border: 1px solid #CCCCCC;background: white;border-radius: 3px;}QCheckBox::indicator:checked {border: 1px solid #0078D7;background: #0078D7;border-radius: 3px;}/* 状态栏样式 */QLabel#version_label {color: #666666;font-size: 9pt;}""")# 设置对象名用于样式选择self.browse_button.setObjectName("browse_button")self.generate_button.setObjectName("generate_button")self.version_label.setObjectName("version_label")self.dir_group.setObjectName("dir_group")self.filter_group.setObjectName("filter_group")self.options_group.setObjectName("options_group")def browse_directory(self):directory = QFileDialog.getExistingDirectory(self, "选择目录")if directory:self.directory_input.setText(directory)self.status_label.setText(f"已选择目录: {directory[:50]}..." if len(directory) > 50 else directory)def generate_excel(self):directory = self.directory_input.toPlainText().strip()file_types = [ft.strip() for ft in self.filter_input.text().split(',') if ft.strip()]if not directory or not os.path.isdir(directory):QMessageBox.warning(self, "错误", "请选择有效的目录!")returndefault_name = f"目录结构_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"save_path, _ = QFileDialog.getSaveFileName(self, "保存 Excel 文件", default_name, "Excel Files (*.xlsx)")if not save_path:returnself.scanner = DirectoryScanner(directory, file_types)self.scanner.progress.connect(self.update_progress)self.scanner.completed.connect(lambda data: self.save_to_excel(data, save_path))self.scanner.error.connect(self.show_error)self.generate_button.setEnabled(False)self.status_label.setText("正在扫描目录...")self.status_icon.setPixmap(QPixmap("icons/loading.png").scaled(16, 16)) if os.path.exists("icons/loading.png") else Noneself.scanner.start()def update_progress(self, processed, total, remaining_time):self.progress_bar.setMaximum(total)self.progress_bar.setValue(processed)progress_percent = processed / total * 100status_text = (f"扫描进度: {progress_percent:.1f}% | "f"已处理: {processed}/{total} | "f"剩余时间: {remaining_time:.0f}秒")self.status_label.setText(status_text)def save_to_excel(self, file_data, save_path):try:df = pd.DataFrame(file_data, columns=["目录路径", "文件名", "类型", "大小(B)", "修改日期", "创建时间", "文件位置"])# 添加人性化大小显示if self.checkbox_size.isChecked():df["大小"] = df["大小(B)"].apply(lambda x: f"{x/1024:.2f}KB" if x < 1024*1024 else f"{x/1024/1024:.2f}MB")# 添加超链接df["文件位置"] = df["文件位置"].apply(lambda x: f'=HYPERLINK("{x}", "📂 打开")')with pd.ExcelWriter(save_path, engine='xlsxwriter') as writer:df.to_excel(writer, index=False, sheet_name='目录结构')workbook = writer.bookworksheet = writer.sheets['目录结构']# 设置列宽col_widths = {"目录路径": 40, "文件名": 30, "类型": 10, "大小(B)": 15, "大小": 15, "修改日期": 20,"创建时间": 20, "文件位置": 15}for idx, col in enumerate(df.columns):worksheet.set_column(idx, idx, col_widths.get(col, 15))# 添加冻结窗格和自动筛选worksheet.autofilter(0, 0, 0, len(df.columns)-1)worksheet.freeze_panes(1, 0)self.status_label.setText(f"导出成功: {os.path.basename(save_path)}")self.status_icon.setPixmap(QPixmap("icons/success.png").scaled(16, 16)) if os.path.exists("icons/success.png") else Noneif self.checkbox_open.isChecked():try:if sys.platform == "win32":os.startfile(save_path)elif sys.platform == "darwin":os.system(f'open "{save_path}"')else:os.system(f'xdg-open "{save_path}"')except Exception as e:logging.error(f"无法打开文件: {str(e)}")except Exception as e:self.show_error(f"生成Excel时出错:\n{str(e)}")logging.error(f"Excel导出错误: {str(e)}")finally:self.generate_button.setEnabled(True)self.progress_bar.reset()def show_error(self, message):QMessageBox.critical(self, "错误", message)self.status_label.setText("操作失败")self.status_icon.setPixmap(QPixmap("icons/error.png").scaled(16, 16)) if os.path.exists("icons/error.png") else Noneself.generate_button.setEnabled(True)def closeEvent(self, event):if hasattr(self, 'scanner') and self.scanner.isRunning():self.scanner.stop()self.scanner.wait()event.accept()if __name__ == "__main__":app = QApplication(sys.argv)app.setStyle('Fusion')# 设置应用程序图标if os.path.exists("icons/app_icon.png"):app.setWindowIcon(QIcon("icons/app_icon.png"))window = DirectoryScannerApp()window.show()sys.exit(app.exec())
结语
通过本篇文章,您已经对 目录结构生成器 v3.0 的功能和使用方法有了全面了解。它不仅提升了目录管理效率,还优化了用户体验,使得复杂的文件夹和文件操作变得轻松愉快。希望这款工具能为您的工作带来便利,并期待您的宝贵反馈!