深度解析如何基于Python实现文件结构管理工具

作者:

文章目录
  • 在软件开发、学术研究或复杂的文档管理中,维持一致的文件组织结构至关重要。常见的痛点包括: 重复劳动:每次新项目都要手动创建 src/, tests/, docs/ 等目录。 割裂感:AI 或文档给出了目录结构,但开发者需要“肉眼阅读”并“手动复现”。 文件同步麻烦:每天产生的新文件(如日志、导出的临时代码)需要快速覆盖到项目对应的占位文件中。 该工具正是在这种“快速落地项目结构”和“日常文件维护”的需求下诞生的。
  • 该程序的核心目标是打造一个 轻量级的桌面效率工具,具体实现以下功能: 文本转目录:解析 ASCII 树状文本,并在本地磁盘一键生成对应的文件夹和文件。 可视化管理:通过 GUI 树状控件直观展示生成的目录。 增量扫描:自动识别计算机中“今天”修改过的非媒体文件,方便快速同步。 文件操作集成:支持文件内容预览、快速覆盖(Copy2)以及备注记录。
  • 为了实现上述目标,开发者采用了以下技术栈和设计模式: GUI 框架:使用 wxPython。它提供了原生的 Windows/macOS/Linux 控件体验,适合开发这类工具类应用。 核心逻辑库: os & pathlib:处理路径拼接、目录创建和文件存在性检查。 shutil:执行高保真的文件复制(保留元数据)。 json:实现配置的持久化存储,记录用户上次选择的路径。 解析算法:采用栈(Stack)数据结构处理树状结构的深度级联。通过计算字符串前缀的空格和符号数量来判断层级关系。
  • 通过运行该源代码,用户可以获得一个功能完备的桌面应用: 高效初始化:输入 my_project/ n ├── main.py n └── config/,点击按钮,磁盘上立即出现对应结构。 配置记忆:程序启动时会自动加载 file_manager_config.json,用户无需反复选择目标文件夹。 闭环操作:用户可以在左侧看到今天写了哪些文件,在右侧树状图中选择目标,一键“覆盖”,极大地简化了代码片段或配置文件的同步流程。 预览与备注:无需打开外部编辑器即可查看文件内容,点击备注即可快速复制到剪贴板。
  • import wx import os import shutil import json from datetime import datetime from pathlib import Path class FileStructureManager(wx.Frame): def __init__(self): super().__init__(parent=None, title=’文件结构管理工具’, size=(1200, 800)) self.target_folder = “” self.tree_root_path = “” self.created_root_folder = “” # 记录创建的根文件夹路径 self.config_file = “file_manager_config.json” self.last_scan_folder = “” # 记录上次扫描的文件夹 self.current_file_path = “” # 当前预览的文件路径 # 加载配置 self.load_config() panel = wx.Panel(self) main_sizer = wx.BoxSizer(wx.VERTICAL) # 目标文件夹选择区域 folder_sizer = wx.BoxSizer(wx.HORIZONTAL) self.folder_label = wx.StaticText(panel, label=”目标文件夹: 未选择”) folder_btn = wx.Button(panel, label=”选择目标文件夹”) folder_btn.Bind(wx.EVT_BUTTON, self.on_select_folder) folder_sizer.Add(self.folder_label, 1, wx.ALL | wx.EXPAND, 5) folder_sizer.Add(folder_btn, 0, wx.ALL, 5) main_sizer.Add(folder_sizer, 0, wx.EXPAND) # 树状结构输入区域 input_sizer = wx.BoxSizer(wx.HORIZONTAL) # 左侧:Memo输入框 left_sizer = wx.BoxSizer(wx.VERTICAL) left_sizer.Add(wx.StaticText(panel, label=”输入树状结构:”), 0, wx.ALL, 5) self.memo = wx.TextCtrl(panel, style=wx.TE_MULTILINE, size=(300, 200)) self.memo.SetValue(“excel-sql-ai/n├── server.jsn├── public/n│ └── index.htmln├── uploads/n└── .env”) left_sizer.Add(self.memo, 1, wx.ALL | wx.EXPAND, 5) create_btn = wx.Button(panel, label=”创建文件结构”) create_btn.Bind(wx.EVT_BUTTON, self.on_create_structure) left_sizer.Add(create_btn, 0, wx.ALL | wx.EXPAND, 5) input_sizer.Add(left_sizer, 1, wx.EXPAND) # 右侧:Tree组件 right_sizer = wx.BoxSizer(wx.VERTICAL) right_sizer.Add(wx.StaticText(panel, label=”文件结构预览:”), 0, wx.ALL, 5) # — 新增:加载树按钮 — load_tree_btn = wx.Button(panel, label=”加载/刷新目录树”) load_tree_btn.Bind(wx.EVT_BUTTON, self.on_load_tree) right_sizer.Add(load_tree_btn, 0, wx.ALL | wx.EXPAND, 5) # ———————- self.tree = wx.TreeCtrl(panel, size=(300, 200), style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT) self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.on_tree_select) right_sizer.Add(self.tree, 1, wx.ALL | wx.EXPAND, 5) open_btn = wx.Button(panel, label=”打开根目录”) open_btn.Bind(wx.EVT_BUTTON, self.on_open_root) right_sizer.Add(open_btn, 0, wx.ALL | wx.EXPAND, 5) input_sizer.Add(right_sizer, 1, wx.EXPAND) main_sizer.Add(input_sizer, 0, wx.EXPAND) # 文件列表区域 list_sizer = wx.BoxSizer(wx.HORIZONTAL) # ListBox1:当天非媒体文件 list1_sizer = wx.BoxSizer(wx.VERTICAL) list1_label_sizer = wx.BoxSizer(wx.HORIZONTAL) list1_label_sizer.Add(wx.StaticText(panel, label=”今日非媒体文件:”), 1, wx.ALL, 5) scan_btn = wx.Button(panel, label=”扫描文件”) scan_btn.Bind(wx.EVT_BUTTON, self.on_scan_files) list1_label_sizer.Add(scan_btn, 0, wx.ALL, 5) refresh_btn = wx.Button(panel, label=”刷新”) refresh_btn.Bind(wx.EVT_BUTTON, self.on_refresh_scan) list1_label_sizer.Add(refresh_btn, 0, wx.ALL, 5) list1_sizer.Add(list1_label_sizer, 0, wx.EXPAND) self.listbox1 = wx.ListBox(panel, size=(250, 150)) list1_sizer.Add(self.listbox1, 1, wx.ALL | wx.EXPAND, 5) copy_btn = wx.Button(panel, label=”覆盖”) copy_btn.Bind(wx.EVT_BUTTON, self.on_copy_file) list1_sizer.Add(copy_btn, 0, wx.ALL | wx.EXPAND, 5) list_sizer.Add(list1_sizer, 1, wx.EXPAND) # 中间:预览区域 preview_sizer = wx.BoxSizer(wx.VERTICAL) preview_label_sizer = wx.BoxSizer(wx.HORIZONTAL) preview_label_sizer.Add(wx.StaticText(panel, label=”文件预览:”), 1, wx.ALL, 5) save_preview_btn = wx.Button(panel, label=”保存修改”) save_preview_btn.Bind(wx.EVT_BUTTON, self.on_save_preview) preview_label_sizer.Add(save_preview_btn, 0, wx.ALL, 5) preview_sizer.Add(preview_label_sizer, 0, wx.EXPAND) self.preview = wx.TextCtrl(panel, style=wx.TE_MULTILINE, size=(300, 150)) preview_sizer.Add(self.preview, 1, wx.ALL | wx.EXPAND, 5) list_sizer.Add(preview_sizer, 1, wx.EXPAND) # ListBox2:备注列表 list2_sizer = wx.BoxSizer(wx.VERTICAL) list2_sizer.Add(wx.StaticText(panel, label=”备注列表:”), 0, wx.ALL, 5) edit_sizer = wx.BoxSizer(wx.HORIZONTAL) self.edit1 = wx.TextCtrl(panel) submit_btn = wx.Button(panel, label=”提交”) submit_btn.Bind(wx.EVT_BUTTON, self.on_submit_note) edit_sizer.Add(self.edit1, 1, wx.ALL, 5) edit_sizer.Add(submit_btn, 0, wx.ALL, 5) list2_sizer.Add(edit_sizer, 0, wx.EXPAND) self.listbox2 = wx.ListBox(panel, size=(250, 100)) self.listbox2.Bind(wx.EVT_LISTBOX, self.on_note_select) list2_sizer.Add(self.listbox2, 1, wx.ALL | wx.EXPAND, 5) list_sizer.Add(list2_sizer, 1, wx.EXPAND) main_sizer.Add(list_sizer, 1, wx.EXPAND) # 状态栏 self.status = wx.StaticText(panel, label=”就绪”) main_sizer.Add(self.status, 0, wx.ALL | wx.EXPAND, 5) panel.SetSizer(main_sizer) self.Centre() self.Show() def on_load_tree(self, event): “””点击加载树按钮的回调””” if not self.target_folder: wx.MessageBox(“请先选择目标文件夹”, “提示”, wx.OK | wx.ICON_WARNING) return if not os.path.exists(self.target_folder): wx.MessageBox(“目标文件夹路径不存在,请重新选择”, “错误”, wx.OK | wx.ICON_ERROR) return # 如果用户点击“加载树”,通常是想看整个目标文件夹的内容 # 我们可以清除掉“记录的已创建根目录”,强制刷新整个目标目录 self.created_root_folder = “” self.status.SetLabel(f”正在加载: {self.target_folder}”) self.refresh_tree() self.status.SetLabel(“目录树加载完成”) def load_config(self): “””加载配置文件””” try: if os.path.exists(self.config_file): with open(self.config_file, ‘r’, encoding=’utf-8′) as f: config = json.load(f) self.target_folder = config.get(‘target_folder’, ”) self.last_scan_folder = config.get(‘last_scan_folder’, ”) self.created_root_folder = config.get(‘created_root_folder’, ”) except Exception as e: print(f”加载配置失败: {e}”) def save_config(self): “””保存配置文件””” try: config = { ‘target_folder’: self.target_folder, ‘last_scan_folder’: self.last_scan_folder, ‘created_root_folder’: self.created_root_folder } with open(self.config_file, ‘w’, encoding=’utf-8′) as f: json.dump(config, f, ensure_ascii=False, indent=2) except Exception as e: print(f”保存配置失败: {e}”) def on_select_folder(self, event): dlg = wx.DirDialog(self, “选择目标文件夹”) if dlg.ShowModal() == wx.ID_OK: self.target_folder = dlg.GetPath() self.folder_label.SetLabel(f”目标文件夹: {self.target_folder}”) self.status.SetLabel(f”已选择: {self.target_folder}”) self.save_config() # 保存配置 dlg.Destroy() def on_create_structure(self, event): if not self.target_folder: wx.MessageBox(“请先选择目标文件夹”, “错误”, wx.OK | wx.ICON_ERROR) return text = self.memo.GetValue() lines = text.split(‘n’) try: # 解析并创建文件结构 stack = [(self.target_folder, -1)] # (路径, 层级) root_created = False self.created_root_folder = “” # 重置创建的根文件夹路径 for line_num, line in enumerate(lines): original_line = line if not line.strip(): continue # 移除树形符号并获取文件/文件夹名 clean_line = line # 移除树形字符: ├── └── │ ─ for symbol in [‘├──’, ‘└──’, ‘│’, ‘─’]: clean_line = clean_line.replace(symbol, ”) clean_line = clean_line.strip() if not clean_line: continue # 移除注释部分(# 后面的内容) if ‘#’ in clean_line: clean_line = clean_line.split(‘#’)[0].strip() if not clean_line: continue # 计算层级 level = 0 for char in original_line: if char in ‘ │’: level += 1 else: break # 如果是第一行且以/结尾,创建根文件夹 if not root_created and clean_line.endswith(‘/’): folder_name = clean_line.rstrip(‘/’) full_path = os.path.join(self.target_folder, folder_name) os.makedirs(full_path, exist_ok=True) stack = [(full_path, 0)] root_created = True self.created_root_folder = full_path # 记录根文件夹 continue # 根据层级找到父目录 while len(stack) > 1 and stack[-1][1] >= level: stack.pop() parent_path = stack[-1][0] if clean_line.endswith(‘/’): # 创建文件夹 folder_name = clean_line.rstrip(‘/’) full_path = os.path.join(parent_path, folder_name) os.makedirs(full_path, exist_ok=True) stack.append((full_path, level)) else: # 创建文件 full_path = os.path.join(parent_path, clean_line) dir_path = os.path.dirname(full_path) if dir_path and not os.path.exists(dir_path): os.makedirs(dir_path, exist_ok=True) if not os.path.exists(full_path): with open(full_path, ‘w’, encoding=’utf-8′) as f: f.write(“”) # 刷新树形显示 self.refresh_tree() self.save_config() # 保存配置 self.status.SetLabel(“文件结构创建成功”) wx.MessageBox(“文件结构创建成功!”, “成功”, wx.OK | wx.ICON_INFORMATION) except Exception as e: import traceback error_msg = f”创建失败: {str(e)}nn详细信息:n{traceback.format_exc()}” wx.MessageBox(error_msg, “错误”, wx.OK | wx.ICON_ERROR) self.status.SetLabel(f”创建失败: {str(e)}”) def refresh_tree(self): self.tree.DeleteAllItems() root = self.tree.AddRoot(“Root”) # 如果有创建的根文件夹,显示它;否则显示目标文件夹 display_path = self.created_root_folder if self.created_root_folder else self.target_folder if display_path and os.path.exists(display_path): self.tree_root_path = display_path try: self.add_tree_nodes(root, display_path, depth=0, max_depth=10) self.tree.ExpandAll() # 展开所有节点以显示创建的结构 except Exception as e: self.status.SetLabel(f”刷新树失败: {str(e)}”) def add_tree_nodes(self, parent, path, depth=0, max_depth=10): # 限制递归深度,避免死循环 if depth >= max_depth: return try: items = sorted(os.listdir(path)) # 限制每层最多显示100个项目 if len(items) > 100: items = items[:100] for item in items: full_path = os.path.join(path, item) # 跳过隐藏文件和系统文件 if item.startswith(‘.’) and item not in [‘.env’, ‘.gitignore’]: continue try: node = self.tree.AppendItem(parent, item) self.tree.SetItemData(node, full_path) if os.path.isdir(full_path): # 递归添加子节点 self.add_tree_nodes(node, full_path, depth + 1, max_depth) except (PermissionError, OSError): # 跳过无权限访问的文件/文件夹 continue except (PermissionError, OSError) as e: # 无法访问该目录,跳过 pass def on_tree_select(self, event): item = event.GetItem() if item: path = self.tree.GetItemData(item) if path and os.path.isfile(path): self.current_file_path = path # 记录当前文件路径 try: with open(path, ‘r’, encoding=’utf-8′, errors=’ignore’) as f: content = f.read() self.preview.SetValue(content) except: self.preview.SetValue(“无法预览此文件”) else: self.current_file_path = “” self.preview.SetValue(“”) def on_scan_files(self, event): dlg = wx.DirDialog(self, “选择要扫描的文件夹”) if dlg.ShowModal() == wx.ID_OK: folder = dlg.GetPath() self.last_scan_folder = folder # 记录最后一次扫描的文件夹 self.save_config() # 保存到配置文件 self.scan_today_files(folder) dlg.Destroy() # 修复缺失的刷新方法 def on_refresh_scan(self, event): if self.last_scan_folder and os.path.exists(self.last_scan_folder): self.scan_today_files(self.last_scan_folder) self.status.SetLabel(f”已刷新扫描: {self.last_scan_folder}”) else: wx.MessageBox(“没有记录上次扫描的文件夹,请先点击’扫描文件'”, “提示”) # 修复缺失的保存预览方法 def on_save_preview(self, event): if not self.current_file_path: wx.MessageBox(“当前没有打开的文件”, “错误”) return content = self.preview.GetValue() try: with open(self.current_file_path, ‘w’, encoding=’utf-8′) as f: f.write(content) self.status.SetLabel(f”已保存修改: {os.path.basename(self.current_file_path)}”) wx.MessageBox(“文件保存成功!”, “成功”) except Exception as e: wx.MessageBox(f”保存失败: {str(e)}”, “错误”) def scan_today_files(self, folder): self.listbox1.Clear() today = datetime.now().date() media_extensions = {‘.jpg’, ‘.jpeg’, ‘.png’, ‘.gif’, ‘.bmp’, ‘.mp4’, ‘.avi’, ‘.mp3’, ‘.wav’, ‘.mov’} try: for root, dirs, files in os.walk(folder): for file in files: full_path = os.path.join(root, file) ext = os.path.splitext(file)[1].lower() if ext not in media_extensions: mod_time = datetime.fromtimestamp(os.path.getmtime(full_path)).date() if mod_time == today: self.listbox1.Append(full_path) self.status.SetLabel(f”找到 {self.listbox1.GetCount()} 个今日文件”) except Exception as e: wx.MessageBox(f”扫描失败: {str(e)}”, “错误”, wx.OK | wx.ICON_ERROR) def on_copy_file(self, event): # 获取选中的tree文件 tree_item = self.tree.GetSelection() if not tree_item or not tree_item.IsOk(): wx.MessageBox(“请先在树中选择目标文件”, “提示”, wx.OK | wx.ICON_WARNING) return target_path = self.tree.GetItemData(tree_item) if not target_path or os.path.isdir(target_path): wx.MessageBox(“请选择一个文件而不是文件夹”, “提示”, wx.OK | wx.ICON_WARNING) return # 获取选中的源文件 selection = self.listbox1.GetSelection() if selection == wx.NOT_FOUND: wx.MessageBox(“请先在列表中选择源文件”, “提示”, wx.OK | wx.ICON_WARNING) return source_path = self.listbox1.GetString(selection) try: shutil.copy2(source_path, target_path) self.status.SetLabel(f”已覆盖: {os.path.basename(target_path)}”) # 刷新预览 with open(target_path, ‘r’, encoding=’utf-8′, errors=’ignore’) as f: content = f.read(1000) self.preview.SetValue(content) except Exception as e: wx.MessageBox(f”复制失败: {str(e)}”, “错误”, wx.OK | wx.ICON_ERROR) def on_open_root(self, event): open_path = self.created_root_folder if self.created_root_folder else self.tree_root_path if open_path and os.path.exists(open_path): if os.name == ‘nt’: # Windows os.startfile(open_path) elif os.name == ‘posix’: # macOS/Linux os.system(f’open “{open_path}”‘ if os.uname().sysname == ‘Darwin’ else f’xdg-open “{open_path}”‘) else: wx.MessageBox(“根目录不存在”, “错误”, wx.OK | wx.ICON_ERROR) def on_submit_note(self, event): note = self.edit1.GetValue() if note: self.listbox2.Append(note) self.edit1.Clear() self.status.SetLabel(“备注已添加”) def on_note_select(self, event): selection = self.listbox2.GetSelection() if selection != wx.NOT_FOUND: text = self.listbox2.GetString(selection) if wx.TheClipboard.Open(): wx.TheClipboard.SetData(wx.TextDataObject(text)) wx.TheClipboard.Close() self.status.SetLabel(“已复制到剪贴板”) if __name__ == ‘__main__’: app = wx.App() frame = FileStructureManager() app.MainLoop() 以上就是深度解析如何基于Python实现文件结构管理工具的详细内容,更多关于Python文件结构管理的资料请关注风君子博客其它相关文章! 您可能感兴趣的文章: 使用python快速生成nodejs项目文件结构 python项目文件结构实例详解 Python项目中的文件夹命名和结构设计建议最佳实践 Python+PyQt5实现文件夹结构映射工具 使用Python实现一个强大的文件系统结构创建器 使用Python解析PPT文件并生成JSON结构详解 使用Python创建一个文件夹结构生成器 使用wxPython创建一个文件夹结构生成器
  • 目录
    • 背景 (Background)
    • 目标 (Goal)
    • 实现方法 (Method)
    • 过程 (Process)
      • 1. 核心解析引擎:从字符串到磁盘
      • 2. 界面布局逻辑
      • 3. 文件扫描与过滤机制
    • 结果 (Results)
      • 运行结果
        • 完整代码 

          在日常开发和项目初始化过程中,我们经常需要按照某种预设的架构创建大量的文件夹和空文件。特别是当我们在使用 AI 生成项目方案时,它通常会给出一个视觉化的树状图(Tree Structure)。手动一个个新建显然太慢。

          今天,我们将深入分析一个基于 PythonwxPython 编写的脚本,它不仅能将“文本树”瞬间转化为“真实目录”,还集成了文件扫描、预览和备注管理功能。

          在软件开发、学术研究或复杂的文档管理中,维持一致的文件组织结构至关重要。常见的痛点包括:

          • 重复劳动:每次新项目都要手动创建 src/, tests/, docs/ 等目录。
          • 割裂感:AI 或文档给出了目录结构,但开发者需要“肉眼阅读”并“手动复现”。
          • 文件同步麻烦:每天产生的新文件(如日志、导出的临时代码)需要快速覆盖到项目对应的占位文件中。

          该工具正是在这种“快速落地项目结构”和“日常文件维护”的需求下诞生的。

          该程序的核心目标是打造一个 轻量级的桌面效率工具,具体实现以下功能:

          • 文本转目录:解析 ASCII 树状文本,并在本地磁盘一键生成对应的文件夹和文件。
          • 可视化管理:通过 GUI 树状控件直观展示生成的目录。
          • 增量扫描:自动识别计算机中“今天”修改过的非媒体文件,方便快速同步。
          • 文件操作集成:支持文件内容预览、快速覆盖(Copy2)以及备注记录。

          为了实现上述目标,开发者采用了以下技术栈和设计模式:

          GUI 框架:使用 wxPython。它提供了原生的 Windows/macOS/Linux 控件体验,适合开发这类工具类应用。

          核心逻辑库

          • os & pathlib:处理路径拼接、目录创建和文件存在性检查。
          • shutil:执行高保真的文件复制(保留元数据)。
          • json:实现配置的持久化存储,记录用户上次选择的路径。

          解析算法:采用栈(Stack)数据结构处理树状结构的深度级联。通过计算字符串前缀的空格和符号数量来判断层级关系。

          这是程序最精彩的部分(on_create_structure 方法)。它通过以下步骤处理输入的文本:

          • 符号清洗:通过 replace 去掉 ├──, └──, 等装饰性符号。
          • 层级推算:利用循环计数每行开头的空格和特殊字符,确定当前文件处于第几层。
          • 栈式追踪:维护一个包含 (当前路径, 层级) 的栈。当新一行的层级减少时,不断弹出栈顶,直到找到其父目录。
          • 智能识别:以 / 结尾的行识别为文件夹,否则识别为文件并创建空文件。

          程序使用了 wx.BoxSizer 进行响应式布局。

          • 顶部:目标路径选择。
          • 中部:左右分栏。左侧输入 ASCII 文本,右侧即时呈现生成的 wx.TreeCtrl 树状视图。
          • 底部:集成文件扫描器、预览框和剪贴板备注工具。

          scan_today_files 函数通过 os.walk 遍历目录:

          • 时间过滤:使用 os.path.getmtime 获取最后修改时间,并与 datetime.now().date() 比对。
          • 类型过滤:定义了 media_extensions 集合,自动排除图片、视频、音频等大文件,聚焦于脚本和文档。

          通过运行该源代码,用户可以获得一个功能完备的桌面应用:

          • 高效初始化:输入 my_project/ n ├── main.py n └── config/,点击按钮,磁盘上立即出现对应结构。
          • 配置记忆:程序启动时会自动加载 file_manager_config.json,用户无需反复选择目标文件夹。
          • 闭环操作:用户可以在左侧看到今天写了哪些文件,在右侧树状图中选择目标,一键“覆盖”,极大地简化了代码片段或配置文件的同步流程。
          • 预览与备注:无需打开外部编辑器即可查看文件内容,点击备注即可快速复制到剪贴板。

          import wx
          import os
          import shutil
          import json
          from datetime import datetime
          from pathlib import Path
          
          class FileStructureManager(wx.Frame):
              def __init__(self):
                  super().__init__(parent=None, title='文件结构管理工具', size=(1200, 800))
                  
                  self.target_folder = ""
                  self.tree_root_path = ""
                  self.created_root_folder = ""  # 记录创建的根文件夹路径
                  self.config_file = "file_manager_config.json"
                  self.last_scan_folder = ""  # 记录上次扫描的文件夹
                  self.current_file_path = ""  # 当前预览的文件路径
                  
                  # 加载配置
                  self.load_config()
                  
                  panel = wx.Panel(self)
                  main_sizer = wx.BoxSizer(wx.VERTICAL)
                  
                  # 目标文件夹选择区域
                  folder_sizer = wx.BoxSizer(wx.HORIZONTAL)
                  self.folder_label = wx.StaticText(panel, label="目标文件夹: 未选择")
                  folder_btn = wx.Button(panel, label="选择目标文件夹")
                  folder_btn.Bind(wx.EVT_BUTTON, self.on_select_folder)
                  folder_sizer.Add(self.folder_label, 1, wx.ALL | wx.EXPAND, 5)
                  folder_sizer.Add(folder_btn, 0, wx.ALL, 5)
                  main_sizer.Add(folder_sizer, 0, wx.EXPAND)
                  
                  # 树状结构输入区域
                  input_sizer = wx.BoxSizer(wx.HORIZONTAL)
                  
                  # 左侧:Memo输入框
                  left_sizer = wx.BoxSizer(wx.VERTICAL)
                  left_sizer.Add(wx.StaticText(panel, label="输入树状结构:"), 0, wx.ALL, 5)
                  self.memo = wx.TextCtrl(panel, style=wx.TE_MULTILINE, size=(300, 200))
                  self.memo.SetValue("excel-sql-ai/n├── server.jsn├── public/n│   └── index.htmln├── uploads/n└── .env")
                  left_sizer.Add(self.memo, 1, wx.ALL | wx.EXPAND, 5)
                  
                  create_btn = wx.Button(panel, label="创建文件结构")
                  create_btn.Bind(wx.EVT_BUTTON, self.on_create_structure)
                  left_sizer.Add(create_btn, 0, wx.ALL | wx.EXPAND, 5)
                  
                  input_sizer.Add(left_sizer, 1, wx.EXPAND)
                  
                 # 右侧:Tree组件
                  right_sizer = wx.BoxSizer(wx.VERTICAL)
                  right_sizer.Add(wx.StaticText(panel, label="文件结构预览:"), 0, wx.ALL, 5)
                  
                  # --- 新增:加载树按钮 ---
                  load_tree_btn = wx.Button(panel, label="加载/刷新目录树")
                  load_tree_btn.Bind(wx.EVT_BUTTON, self.on_load_tree)
                  right_sizer.Add(load_tree_btn, 0, wx.ALL | wx.EXPAND, 5)
                  # ----------------------
          
                  self.tree = wx.TreeCtrl(panel, size=(300, 200), style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT)
                  self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.on_tree_select)
                  right_sizer.Add(self.tree, 1, wx.ALL | wx.EXPAND, 5)
                  
                  open_btn = wx.Button(panel, label="打开根目录")
                  open_btn.Bind(wx.EVT_BUTTON, self.on_open_root)
                  right_sizer.Add(open_btn, 0, wx.ALL | wx.EXPAND, 5)
                  
                  input_sizer.Add(right_sizer, 1, wx.EXPAND)
                  
                  main_sizer.Add(input_sizer, 0, wx.EXPAND)
                  
                  # 文件列表区域
                  list_sizer = wx.BoxSizer(wx.HORIZONTAL)
                  
                  # ListBox1:当天非媒体文件
                  list1_sizer = wx.BoxSizer(wx.VERTICAL)
                  list1_label_sizer = wx.BoxSizer(wx.HORIZONTAL)
                  list1_label_sizer.Add(wx.StaticText(panel, label="今日非媒体文件:"), 1, wx.ALL, 5)
                  scan_btn = wx.Button(panel, label="扫描文件")
                  scan_btn.Bind(wx.EVT_BUTTON, self.on_scan_files)
                  list1_label_sizer.Add(scan_btn, 0, wx.ALL, 5)
                  refresh_btn = wx.Button(panel, label="刷新")
                  refresh_btn.Bind(wx.EVT_BUTTON, self.on_refresh_scan)
                  list1_label_sizer.Add(refresh_btn, 0, wx.ALL, 5)
                  list1_sizer.Add(list1_label_sizer, 0, wx.EXPAND)
                  
                  self.listbox1 = wx.ListBox(panel, size=(250, 150))
                  list1_sizer.Add(self.listbox1, 1, wx.ALL | wx.EXPAND, 5)
                  
                  copy_btn = wx.Button(panel, label="覆盖")
                  copy_btn.Bind(wx.EVT_BUTTON, self.on_copy_file)
                  list1_sizer.Add(copy_btn, 0, wx.ALL | wx.EXPAND, 5)
                  
                  list_sizer.Add(list1_sizer, 1, wx.EXPAND)
                  
                  # 中间:预览区域
                  preview_sizer = wx.BoxSizer(wx.VERTICAL)
                  preview_label_sizer = wx.BoxSizer(wx.HORIZONTAL)
                  preview_label_sizer.Add(wx.StaticText(panel, label="文件预览:"), 1, wx.ALL, 5)
                  save_preview_btn = wx.Button(panel, label="保存修改")
                  save_preview_btn.Bind(wx.EVT_BUTTON, self.on_save_preview)
                  preview_label_sizer.Add(save_preview_btn, 0, wx.ALL, 5)
                  preview_sizer.Add(preview_label_sizer, 0, wx.EXPAND)
                  self.preview = wx.TextCtrl(panel, style=wx.TE_MULTILINE, size=(300, 150))
                  preview_sizer.Add(self.preview, 1, wx.ALL | wx.EXPAND, 5)
                  list_sizer.Add(preview_sizer, 1, wx.EXPAND)
                  
                  # ListBox2:备注列表
                  list2_sizer = wx.BoxSizer(wx.VERTICAL)
                  list2_sizer.Add(wx.StaticText(panel, label="备注列表:"), 0, wx.ALL, 5)
                  
                  edit_sizer = wx.BoxSizer(wx.HORIZONTAL)
                  self.edit1 = wx.TextCtrl(panel)
                  submit_btn = wx.Button(panel, label="提交")
                  submit_btn.Bind(wx.EVT_BUTTON, self.on_submit_note)
                  edit_sizer.Add(self.edit1, 1, wx.ALL, 5)
                  edit_sizer.Add(submit_btn, 0, wx.ALL, 5)
                  list2_sizer.Add(edit_sizer, 0, wx.EXPAND)
                  
                  self.listbox2 = wx.ListBox(panel, size=(250, 100))
                  self.listbox2.Bind(wx.EVT_LISTBOX, self.on_note_select)
                  list2_sizer.Add(self.listbox2, 1, wx.ALL | wx.EXPAND, 5)
                  
                  list_sizer.Add(list2_sizer, 1, wx.EXPAND)
                  
                  main_sizer.Add(list_sizer, 1, wx.EXPAND)
                  
                  # 状态栏
                  self.status = wx.StaticText(panel, label="就绪")
                  main_sizer.Add(self.status, 0, wx.ALL | wx.EXPAND, 5)
                  
                  panel.SetSizer(main_sizer)
                  self.Centre()
                  self.Show()
              
              def on_load_tree(self, event):
                  """点击加载树按钮的回调"""
                  if not self.target_folder:
                      wx.MessageBox("请先选择目标文件夹", "提示", wx.OK | wx.ICON_WARNING)
                      return
                      
                  if not os.path.exists(self.target_folder):
                      wx.MessageBox("目标文件夹路径不存在,请重新选择", "错误", wx.OK | wx.ICON_ERROR)
                      return
          
                  # 如果用户点击“加载树”,通常是想看整个目标文件夹的内容
                  # 我们可以清除掉“记录的已创建根目录”,强制刷新整个目标目录
                  self.created_root_folder = "" 
                  
                  self.status.SetLabel(f"正在加载: {self.target_folder}")
                  self.refresh_tree()
                  self.status.SetLabel("目录树加载完成")
              def load_config(self):
                  """加载配置文件"""
                  try:
                      if os.path.exists(self.config_file):
                          with open(self.config_file, 'r', encoding='utf-8') as f:
                              config = json.load(f)
                              self.target_folder = config.get('target_folder', '')
                              self.last_scan_folder = config.get('last_scan_folder', '')
                              self.created_root_folder = config.get('created_root_folder', '')
                  except Exception as e:
                      print(f"加载配置失败: {e}")
              
              def save_config(self):
                  """保存配置文件"""
                  try:
                      config = {
                          'target_folder': self.target_folder,
                          'last_scan_folder': self.last_scan_folder,
                          'created_root_folder': self.created_root_folder
                      }
                      with open(self.config_file, 'w', encoding='utf-8') as f:
                          json.dump(config, f, ensure_ascii=False, indent=2)
                  except Exception as e:
                      print(f"保存配置失败: {e}")
              
              def on_select_folder(self, event):
                  dlg = wx.DirDialog(self, "选择目标文件夹")
                  if dlg.ShowModal() == wx.ID_OK:
                      self.target_folder = dlg.GetPath()
                      self.folder_label.SetLabel(f"目标文件夹: {self.target_folder}")
                      self.status.SetLabel(f"已选择: {self.target_folder}")
                      self.save_config()  # 保存配置
                  dlg.Destroy()
              
              def on_create_structure(self, event):
                  if not self.target_folder:
                      wx.MessageBox("请先选择目标文件夹", "错误", wx.OK | wx.ICON_ERROR)
                      return
                  
                  text = self.memo.GetValue()
                  lines = text.split('n')
                  
                  try:
                      # 解析并创建文件结构
                      stack = [(self.target_folder, -1)]  # (路径, 层级)
                      root_created = False
                      self.created_root_folder = ""  # 重置创建的根文件夹路径
                      
                      for line_num, line in enumerate(lines):
                          original_line = line
                          if not line.strip():
                              continue
                          
                          # 移除树形符号并获取文件/文件夹名
                          clean_line = line
                          # 移除树形字符: ├── └── │ ─
                          for symbol in ['├──', '└──', '│', '─']:
                              clean_line = clean_line.replace(symbol, '')
                          clean_line = clean_line.strip()
                          
                          if not clean_line:
                              continue
                          
                          # 移除注释部分(# 后面的内容)
                          if '#' in clean_line:
                              clean_line = clean_line.split('#')[0].strip()
                          
                          if not clean_line:
                              continue
                          
                          # 计算层级
                          level = 0
                          for char in original_line:
                              if char in ' │':
                                  level += 1
                              else:
                                  break
                          
                          # 如果是第一行且以/结尾,创建根文件夹
                          if not root_created and clean_line.endswith('/'):
                              folder_name = clean_line.rstrip('/')
                              full_path = os.path.join(self.target_folder, folder_name)
                              os.makedirs(full_path, exist_ok=True)
                              stack = [(full_path, 0)]
                              root_created = True
                              self.created_root_folder = full_path  # 记录根文件夹
                              continue
                          
                          # 根据层级找到父目录
                          while len(stack) > 1 and stack[-1][1] >= level:
                              stack.pop()
                          
                          parent_path = stack[-1][0]
                          
                          if clean_line.endswith('/'):
                              # 创建文件夹
                              folder_name = clean_line.rstrip('/')
                              full_path = os.path.join(parent_path, folder_name)
                              os.makedirs(full_path, exist_ok=True)
                              stack.append((full_path, level))
                          else:
                              # 创建文件
                              full_path = os.path.join(parent_path, clean_line)
                              dir_path = os.path.dirname(full_path)
                              if dir_path and not os.path.exists(dir_path):
                                  os.makedirs(dir_path, exist_ok=True)
                              if not os.path.exists(full_path):
                                  with open(full_path, 'w', encoding='utf-8') as f:
                                      f.write("")
                      
                      # 刷新树形显示
                      self.refresh_tree()
                      self.save_config()  # 保存配置
                      self.status.SetLabel("文件结构创建成功")
                      wx.MessageBox("文件结构创建成功!", "成功", wx.OK | wx.ICON_INFORMATION)
                      
                  except Exception as e:
                      import traceback
                      error_msg = f"创建失败: {str(e)}nn详细信息:n{traceback.format_exc()}"
                      wx.MessageBox(error_msg, "错误", wx.OK | wx.ICON_ERROR)
                      self.status.SetLabel(f"创建失败: {str(e)}")
              
              def refresh_tree(self):
                  self.tree.DeleteAllItems()
                  root = self.tree.AddRoot("Root")
                  
                  # 如果有创建的根文件夹,显示它;否则显示目标文件夹
                  display_path = self.created_root_folder if self.created_root_folder else self.target_folder
                  
                  if display_path and os.path.exists(display_path):
                      self.tree_root_path = display_path
                      try:
                          self.add_tree_nodes(root, display_path, depth=0, max_depth=10)
                          self.tree.ExpandAll()  # 展开所有节点以显示创建的结构
                      except Exception as e:
                          self.status.SetLabel(f"刷新树失败: {str(e)}")
              
              def add_tree_nodes(self, parent, path, depth=0, max_depth=10):
                  # 限制递归深度,避免死循环
                  if depth >= max_depth:
                      return
                  
                  try:
                      items = sorted(os.listdir(path))
                      # 限制每层最多显示100个项目
                      if len(items) > 100:
                          items = items[:100]
                      
                      for item in items:
                          full_path = os.path.join(path, item)
                          
                          # 跳过隐藏文件和系统文件
                          if item.startswith('.') and item not in ['.env', '.gitignore']:
                              continue
                          
                          try:
                              node = self.tree.AppendItem(parent, item)
                              self.tree.SetItemData(node, full_path)
                              
                              if os.path.isdir(full_path):
                                  # 递归添加子节点
                                  self.add_tree_nodes(node, full_path, depth + 1, max_depth)
                          except (PermissionError, OSError):
                              # 跳过无权限访问的文件/文件夹
                              continue
                              
                  except (PermissionError, OSError) as e:
                      # 无法访问该目录,跳过
                      pass
              
              def on_tree_select(self, event):
                  item = event.GetItem()
                  if item:
                      path = self.tree.GetItemData(item)
                      if path and os.path.isfile(path):
                          self.current_file_path = path  # 记录当前文件路径
                          try:
                              with open(path, 'r', encoding='utf-8', errors='ignore') as f:
                                  content = f.read()
                                  self.preview.SetValue(content)
                          except:
                              self.preview.SetValue("无法预览此文件")
                      else:
                          self.current_file_path = ""
                          self.preview.SetValue("")
              
              def on_scan_files(self, event):
                  dlg = wx.DirDialog(self, "选择要扫描的文件夹")
                  if dlg.ShowModal() == wx.ID_OK:
                      folder = dlg.GetPath()
                      self.last_scan_folder = folder  # 记录最后一次扫描的文件夹
                      self.save_config()              # 保存到配置文件
                      self.scan_today_files(folder)
                  dlg.Destroy()
          
              # 修复缺失的刷新方法
              def on_refresh_scan(self, event):
                  if self.last_scan_folder and os.path.exists(self.last_scan_folder):
                      self.scan_today_files(self.last_scan_folder)
                      self.status.SetLabel(f"已刷新扫描: {self.last_scan_folder}")
                  else:
                      wx.MessageBox("没有记录上次扫描的文件夹,请先点击'扫描文件'", "提示")
          
              # 修复缺失的保存预览方法
              def on_save_preview(self, event):
                  if not self.current_file_path:
                      wx.MessageBox("当前没有打开的文件", "错误")
                      return
                  
                  content = self.preview.GetValue()
                  try:
                      with open(self.current_file_path, 'w', encoding='utf-8') as f:
                          f.write(content)
                      self.status.SetLabel(f"已保存修改: {os.path.basename(self.current_file_path)}")
                      wx.MessageBox("文件保存成功!", "成功")
                  except Exception as e:
                      wx.MessageBox(f"保存失败: {str(e)}", "错误")
              def scan_today_files(self, folder):
                  self.listbox1.Clear()
                  today = datetime.now().date()
                  media_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.mp4', '.avi', '.mp3', '.wav', '.mov'}
                  
                  try:
                      for root, dirs, files in os.walk(folder):
                          for file in files:
                              full_path = os.path.join(root, file)
                              ext = os.path.splitext(file)[1].lower()
                              
                              if ext not in media_extensions:
                                  mod_time = datetime.fromtimestamp(os.path.getmtime(full_path)).date()
                                  if mod_time == today:
                                      self.listbox1.Append(full_path)
                      
                      self.status.SetLabel(f"找到 {self.listbox1.GetCount()} 个今日文件")
                  except Exception as e:
                      wx.MessageBox(f"扫描失败: {str(e)}", "错误", wx.OK | wx.ICON_ERROR)
              
              def on_copy_file(self, event):
                  # 获取选中的tree文件
                  tree_item = self.tree.GetSelection()
                  if not tree_item or not tree_item.IsOk():
                      wx.MessageBox("请先在树中选择目标文件", "提示", wx.OK | wx.ICON_WARNING)
                      return
                  
                  target_path = self.tree.GetItemData(tree_item)
                  if not target_path or os.path.isdir(target_path):
                      wx.MessageBox("请选择一个文件而不是文件夹", "提示", wx.OK | wx.ICON_WARNING)
                      return
                  
                  # 获取选中的源文件
                  selection = self.listbox1.GetSelection()
                  if selection == wx.NOT_FOUND:
                      wx.MessageBox("请先在列表中选择源文件", "提示", wx.OK | wx.ICON_WARNING)
                      return
                  
                  source_path = self.listbox1.GetString(selection)
                  
                  try:
                      shutil.copy2(source_path, target_path)
                      self.status.SetLabel(f"已覆盖: {os.path.basename(target_path)}")
                      # 刷新预览
                      with open(target_path, 'r', encoding='utf-8', errors='ignore') as f:
                          content = f.read(1000)
                          self.preview.SetValue(content)
                  except Exception as e:
                      wx.MessageBox(f"复制失败: {str(e)}", "错误", wx.OK | wx.ICON_ERROR)
              
              def on_open_root(self, event):
                  open_path = self.created_root_folder if self.created_root_folder else self.tree_root_path
                  if open_path and os.path.exists(open_path):
                      if os.name == 'nt':  # Windows
                          os.startfile(open_path)
                      elif os.name == 'posix':  # macOS/Linux
                          os.system(f'open "{open_path}"' if os.uname().sysname == 'Darwin' 
                                   else f'xdg-open "{open_path}"')
                  else:
                      wx.MessageBox("根目录不存在", "错误", wx.OK | wx.ICON_ERROR)
              
              def on_submit_note(self, event):
                  note = self.edit1.GetValue()
                  if note:
                      self.listbox2.Append(note)
                      self.edit1.Clear()
                      self.status.SetLabel("备注已添加")
              
              def on_note_select(self, event):
                  selection = self.listbox2.GetSelection()
                  if selection != wx.NOT_FOUND:
                      text = self.listbox2.GetString(selection)
                      if wx.TheClipboard.Open():
                          wx.TheClipboard.SetData(wx.TextDataObject(text))
                          wx.TheClipboard.Close()
                          self.status.SetLabel("已复制到剪贴板")
          
          if __name__ == '__main__':
              app = wx.App()
              frame = FileStructureManager()
              app.MainLoop()
          

          以上就是深度解析如何基于Python实现文件结构管理工具的详细内容,更多关于Python文件结构管理的资料请关注风君子博客其它相关文章!

          您可能感兴趣的文章:

          • 使用python快速生成nodejs项目文件结构
          • python项目文件结构实例详解
          • Python项目中的文件夹命名和结构设计建议最佳实践
          • Python+PyQt5实现文件夹结构映射工具
          • 使用Python实现一个强大的文件系统结构创建器
          • 使用Python解析PPT文件并生成JSON结构详解
          • 使用Python创建一个文件夹结构生成器
          • 使用wxPython创建一个文件夹结构生成器

          站内搜索