需求的由来是对象整理音效的时候,她收集的音效资源有嵌套的子文件夹,但是她想把所有的文件都提在一个里面之前有没有程序,她的操作是:将子文件夹中的文件复制到一级目录,删除子文件夹。如果子文件夹少,工作量不会太大,但是如果子文件夹嵌套很深,工作量就增加了,而且都是重复性的工作。于是她就来找我帮忙,我看我最近就是没学node,刚好是练习node的好方法。实现过程实现过程的主体是一个递归,遍历文件夹,如果是文件,则进行转移操作,如果不是,则传入一个新的文件夹路径,继续遍历。具体实现分为以下几个步骤:项目初始化获取文件路径,路径项目初始化下的所有文件传输操作和删除操作。因为使用的平台是windows,界面不能做的丑,技术选择是Electron+Node。虽然electron打包后的文件有50M,但是软件带来的收益远远大于需要付出的成本。关于electron和vue的整合,之前写过一篇Electron+vue从头搭建本地音乐播放器的文章。electron和vue的整合目前社区有两种解决方案:SimulatedGREG/electron-vue,比较老旧臃肿,但是如果是老项目的迁移,可以考虑使用。nklayman/vue-cli-plugin-electron-builder,支持最新稳定版electron和最新vue脚手架,代码中主要流程相关代码集成在background.js中,其他代码组织方式无异编写vue项目。文档也很不错,推荐使用这个。这里我做了一个非常简单的项目初始化模板,集成了最新稳定版的electron,和最新的vue-cli4,并添加了normalize.css初始化样式。electron+vue相关项目的初始化可以直接使用这个模板,点这里。获取文件路径,获取文件路径下的所有文件。这里的处理方式和之前的翻译项目一样(Electron+Vue从零开始搭建本地文件翻译器),通过两种方式获取需要的路径。设置inputwebkitdirectory目录属性,然后监听change事件,获取选中文件夹的路径。通过H5的拖放API监听drop事件,得到DataTransfer对象,用于保存拖放数据。这里的区别是需要对拖入的文件进行判断,必须拖入的是文件夹。constoriginFiles=[...e.dataTransfer.files];constisAllDir=originFiles.every(file=>fs.statSync(file.path).isDirectory());如果(!isAllDir){ipcRenderer.send("confirmDialog");返回假;}主流程ipcMain.on("confirmDialog",()=>{dialog.showMessageBox({type:"info",title:"Confirm",message:"请确认选中的文件是否为Folder"});});获取路径下的所有文件//获取文件路径下的所有文件asyncgetAllFiles(path){try{constres=awaitfsp.readdir(path);返回资源;}赶上(错误){console.log(错误);}},传输操作传输操作这里使用了fs模块的rename方法,需要判断是否是文件夹来判断是否需要递归操作。对于同名文件,会判断传输文件的大小与目标文件夹的大小是否一致,不一致则重命名。重命名需要在文件名后面生成一个文件id,保证文件不会被重命名。具体代码如下:判断是否为文件夹//判断是否为文件夹asyncisDir(path){try{constres=awaitfsp.stat(path);如果(res.isDirectory()){返回真;}else{返回错误;}}catch(错误){console.log(错误);}},生成的文件id//生成的文件iduuid(len,radix){constchars='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');让你=[],我;基数=基数||字符长度;if(len){//紧凑形式for(i=0;i{constfilePath=path.resolve(dirPath,file);consttargetFilePath=path.resolve(targetDirPath,file);letisDir=awaitthis.isDir(filePath);//如果不是目录则复制,否则递归if(!isDir){//检查移动目标文件夹中是否有同名文件console.log('遍历所有文件');console.log({filePath,targetFilePath});if(fs.existsSync(targetFilePath)){console.log(`目标文件${file}存在于目标文件夹${targetDirPath}`);console.log({filePath,targetFilePath});consttargetFiles=awaitthis.getAllFiles(targetDirPath);constfileIndex=targetFiles.indexOf(file);console.log(`${file}同名下标为${fileIndex}`);//获取两边文件的信息并进行比较consttargetFileInfo=awaitfsp.stat(targetFilePath);constoriginFileInfo=awaitfsp.stat(filePath);console.log(`原始文件和目标文件夹的大小为${originFileInfo.size}${targetFileInfo.size}`);//如果存在同名文件,判断文件大小是否一致if(fileIndex>=0&&originFileInfo.size!==targetFileInfo.size){//获取原文件的名称和后缀格式constfileExt=path.extname(文件路径);constfileName=path.basename(filePath,fileExt);//生成一个新文件名constnewFileName=`${fileName}-${this.uuid(6,16)}`;constnewPath=path.resolve(dirPath,`${newFileName}${fileExt}`);constnewTargetFilePath=path.resolve(targetDirPath,`${newFileName}${fileExt}`);//重命名awaitfsp.rename(filePath,newPath);//移动到新的目标文件夹awaitfsp.rename(newPath,newTargetFilePath);}else{console.log('目标文件同名但文件内容不同');console.log({文件路径,目标t文件路径});//非同名文件直接复制移动awaitfsp.rename(filePath,targetFilePath);}}else{console.log('目标文件夹中不存在目标文件');等待fsp.rename(filePath,targetFilePath);}}else{//是一个目录,进行递归操作awaitthis.moveFiles(filePath,targetDirPath);}lettimer=setTimeout(async()=>{awaitthis.removeDir(dirPath);this.loading=false;clearTimeout(timer);},1000);});}、删除操作由于复制移动方法执行后原文件夹还存在,所以需要删除删除操作。还需要查询是否是文件夹,如果是文件则执行fs.unlinkSync方法递归目录,最后删除保证只剩下目录,然后执行删除目录的操作通过fs.rmdirSync。具体代码如下//删除文件removeDir(url){if(fs.existsSync(url)){constfiles=fs.readdirSync(url);files.forEach((file,index)=>{constcurPath=path.join(url,file);if(fs.statSync(curPath).isDirectory()){console.log(`Directory${curPath}`检测到`);this.removeDir(curPath);}else{fs.unlinkSync(curPath);console.log(`删除文件${curPath}`);}});//删除文件夹fs.rmdirSync(url);}else{console.log('删除文件');}},最后,最近在搭档面前太过分了。在伙伴的眼里,我是一个闪闪发光的存在。她跟同学说程序有多牛逼(虽然在我们眼里是小事),然后同学们都说想找个程序员男朋友,我暗暗高兴,终于为我争光了我们的程序员!!!源代码在这里点击这里