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, QPixmap
logging.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 = directory
self.file_types = file_types
self._is_running = True
def stop(self):
self._is_running = False
def run(self):
try:
file_data = []
total_files = sum(len(files) for _, _, files in os.walk(self.directory))
processed_files = 0
start_time = time.time()
for root, _, files in os.walk(self.directory):
if not self._is_running:
break
for file in files:
if self.file_types and not any(file.lower().endswith(ft.lower()) for ft in self.file_types if ft):
continue
try:
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)}")
continue
processed_files += 1
elapsed_time = time.time() - start_time
estimated_total_time = (elapsed_time / processed_files) * total_files if processed_files else 0
remaining_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] = animation
def 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, "错误", "请选择有效的目录!")
return
default_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:
return
self.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 None
self.scanner.start()
def update_progress(self, processed, total, remaining_time):
self.progress_bar.setMaximum(total)
self.progress_bar.setValue(processed)
progress_percent = processed / total * 100
status_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.book
worksheet = 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 None
if 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 None
self.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 的功能和使用方法有了全面了解。它不仅提升了目录管理效率,还优化了用户体验,使得复杂的文件夹和文件操作变得轻松愉快。希望这款工具能为您的工作带来便利,并期待您的宝贵反馈!