qiushang 4 月之前
父节点
当前提交
5cdfd9170e
共有 2 个文件被更改,包括 352 次插入78 次删除
  1. 351 76
      src/renderer/components/home.vue
  2. 1 2
      src/renderer/utils/utils.js

+ 351 - 76
src/renderer/components/home.vue

@@ -9,7 +9,7 @@
 				<div class="content-top">
 					<el-row>
 						<el-button type="primary" size="small" @click="pickDir()">添加文件路径</el-button>
-						<el-input readonly ref="search-input" size="small" placeholder="搜索目录路径" v-model="searchDir" style="width:400px;" prefix-icon="el-icon-folder"></el-input>
+						<el-input readonly ref="search-input" size="small" placeholder="搜索目录路径" v-model="searchDir" style="width:460px;" prefix-icon="el-icon-folder"></el-input>
 						<el-checkbox v-model="subFolder" size="mini">搜索多级子文件夹</el-checkbox>
 					</el-row>
 					
@@ -23,15 +23,28 @@
 					<div class="content-right">
 						
 						<div class="handle-item">
-							<b>检索方式:</b>
-							<el-select size="mini" style="width: 160px;" v-model="findType" placeholder="请选择检索方式">
+							<b>检索类型:</b>
+							<el-select size="mini" style="width: 170px;" v-model="findType" placeholder="请选择检索类型">
 								<el-option value="1" label="按文件名检索"></el-option>
+								<el-option value="5" label="按文件夹名检索"></el-option>
 								<el-option value="2" label="按创建时间检索"></el-option>
 								<el-option value="3" label="按修改时间检索"></el-option>
+								<el-option value="4" label="按文件类型检索"></el-option>
 							</el-select>
 						</div>
 						
-						<template v-if="findType == '1'">
+						<template v-if="findType == '1' || findType == '5'">
+							<b>检索方式:</b>
+							<el-select size="mini" style="width: 170px;" v-model="handleData.rule" placeholder="请选择检索方式">
+								<el-option value="1" label="精确(名称相同)"></el-option>
+								<el-option value="2" label="模糊(包含名称)"></el-option>
+								<el-option value="3" label="精确到格式(名称和后缀都相同)"></el-option>
+							</el-select>
+							<!-- <el-radio-group class="line-radio" v-model="handleData.rule" size="mini">
+								<el-radio label="1">精确(文件名相同)</el-radio>
+								<el-radio label="2">模糊(包含文件名)</el-radio>
+								<el-radio label="3">精确到格式(文件名和后缀都相同)</el-radio>
+							</el-radio-group> -->
 							<div class="handle-item" style="margin-bottom: 5px;">
 								<p><b>文件名检索清单:</b><span style="color: #F56C6C; font-size: 12px;">(一行填一个,首尾空格清除)</span></p>
 							</div>
@@ -40,28 +53,51 @@
 							名称2
 							名称3" v-model="handleData.findText"></el-input>
 						</template>
-						<template v-else>
+						<template v-else-if="findType == '2' || findType == '3'">
 							<div class="handle-item">
 								<b>开始时间:</b>
-								<el-date-picker type="datetime" v-model="handleData.startTime" placeholder="开始时间" style="width:160px;" size="mini"></el-date-picker>
+								<el-date-picker type="datetime" v-model="handleData.startTime" placeholder="开始时间" style="width:170px;" size="mini"></el-date-picker>
 							</div>
 							<div class="handle-item">
 								<b>结束时间:</b>
-								<el-date-picker type="datetime" v-model="handleData.endTime" placeholder="结束时间" style="width:160px;" size="mini"></el-date-picker>
+								<el-date-picker type="datetime" v-model="handleData.endTime" placeholder="结束时间" style="width:170px;" size="mini"></el-date-picker>
 							</div>
-							<div class="find-textarea" style="height: calc(100% - 258px);">
-								
+						</template>
+						<template v-else-if="findType == '4'">
+							<div class="handle-item">
+								<b>文件类型:</b>
+								<el-select size="mini" style="width: 170px;" v-model="handleData.suffixType" placeholder="请选择文件类型">
+									<el-option value="1" label="文本文档"></el-option>
+									<el-option value="2" label="图像文件"></el-option>
+									<el-option value="3" label="音频文件"></el-option>
+									<el-option value="4" label="视频文件"></el-option>
+									<el-option value="5" label="压缩文件"></el-option>
+									<el-option value="6" label="数据库文件"></el-option>
+									<el-option value="10" label="自定义后缀文件"></el-option>
+								</el-select>
+							</div>
+							
+							<div class="handle-item" v-if="handleData.suffixType == '10'">
+								<b>后缀名称:</b>
+								<el-input size="small" placeholder="自定义后缀(不包含.)" v-model="handleData.suffix" style="width:170px"></el-input>
 							</div>
+							
+							<el-popover placement="bottom" popper-class="popper-open" trigger="hover">
+								<div style="text-align: left;">
+									<p>文本文档:{{suffixList[0]}}</p>
+									<p>图像文件:{{suffixList[1]}}</p>
+									<p>音频文件:{{suffixList[2]}}</p>
+									<p>视频文件:{{suffixList[3]}}</p>
+									<p>压缩文件:{{suffixList[4]}}</p>
+									<p>数据库文件:{{suffixList[5]}}</p>
+									<p>以上文件类型只做后缀名匹配</p>
+								</div>
+								<span slot="reference" class="i-tips" style="margin-top: 20px;">查看支持的文件类型</span>
+							</el-popover>
 						</template>
 						
-						<div style="position: relative; margin: 10px 0;">
+						<div style="position: relative; margin: 10px 0;" >
 							<el-button type="danger" size="mini" style="position: absolute; right: 0; top: 0;" :loading="searchLoading" @click="search()">搜索</el-button>
-							<div style="padding: 10px; font-weight: 600;">检索方式:</div>
-							<el-radio-group class="line-radio" v-model="handleData.rule" size="small">
-								<el-radio label="1">精确(文件名相同)</el-radio>
-								<el-radio label="2">模糊(包含文件名)</el-radio>
-								<el-radio label="3">精确到格式(文件名和格式都相同)</el-radio>
-							</el-radio-group>
 						</div>
 					</div>
 					
@@ -80,7 +116,9 @@
 								:loading-config="{icon: 'vxe-icon-indicator roll', text: '任务处理中...'}" :row-config="{isHover: true}"
 								:edit-config="{trigger: 'click', mode: 'cell'}" :data="fileList" :scroll-y="{enabled: true}">
 								<vxe-column type="seq" width="60" title="编号" align="center"></vxe-column>
-								<vxe-column field="match" title="匹配词" width="150" min-width="120" align="center"></vxe-column>
+								<vxe-column field="match" title="匹配词" v-if="findType == '1' || findType == '5'" width="150" min-width="120" align="center"></vxe-column>
+								<vxe-column field="rangeTime" title="时间" v-else-if="findType == '2' || findType == '3'" width="150" min-width="120" align="center"></vxe-column>
+								<vxe-column field="match" title="类型" v-else-if="findType == '4'" width="150" min-width="120" align="center"></vxe-column>
 								<vxe-column field="path" title="文件位置/名称">
 									<template #default="{ row }">
 										<img v-if="row.isDirectory" src="../assets/image/folder.png" style="width: 20px; vertical-align: middle;"/>
@@ -155,9 +193,9 @@
 							</div>
 							<div class="handle-item">
 								<label v-if="handleData.type != '1'" class="handle-label"><el-link icon="el-icon-warning" :underline="false" type="danger"></el-link></label>
-								<el-link style="font-weight: 600;" v-if="handleData.type == '2'" :underline="false" type="danger">当前为"剪切"操作,剪切后文件无法恢复原来位置,您可以做好备份后操作。</el-link>
-								<el-link style="font-weight: 600;" v-if="handleData.type == '3'" :underline="false" type="danger">当前为"删除"操作,删除后文件无法恢复原来位置,您可以做好备份后操作。</el-link>
-								<el-link style="font-weight: 600;" v-if="handleData.type == '4'" :underline="false" type="danger">当前为"覆盖"操作,覆盖后原文件将无法恢复,您可以做好备份后操作。</el-link>
+								<el-link style="font-weight: 600;" v-if="handleData.type == '2'" :underline="false" type="danger">当前为"剪切"操作,剪切后文件无法恢复原来位置,做好备份后操作。</el-link>
+								<el-link style="font-weight: 600;" v-if="handleData.type == '3'" :underline="false" type="danger">当前为"删除"操作,删除后文件无法恢复原来位置,做好备份后操作。</el-link>
+								<el-link style="font-weight: 600;" v-if="handleData.type == '4'" :underline="false" type="danger">当前为"覆盖"操作,覆盖后原文件将无法恢复,做好备份后操作。</el-link>
 							</div>
 							
 							<el-button type="danger" style="position: absolute; top: 40px; right: 10px;" @click="exportFile()" :loading="exportLoading">开始处理</el-button>
@@ -214,8 +252,10 @@ export default {
 				type: '1',
 				rule: '1',
 				findText: '',
-				startTime: '',
-				endTime: '',
+				startTime: new Date(),
+				endTime: new Date(),
+				suffixType: '1',
+				suffix: ''
 			},
 			findType: '1',
 			exportLoading: false,
@@ -231,6 +271,15 @@ export default {
 			
 			execlimit: 5,
 			xlsxLoading: false,
+			
+			suffixList: [
+				['txt', 'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'pdf', 'csv'],
+				['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'bmp', 'tif', 'tiff', 'psd', 'raw', 'heif', 'heic', 'eps‌', 'ai', 'ico'],
+				['mp3', 'aac', 'm4a', 'flac', 'alac', 'wav', 'waf', 'aiff', 'ogg', 'amr', 'ape', 'wma'],
+				['mp4', 'mkv', 'avi', 'mov', 'webm', 'mxf', 'flv', 'ts', 'm3u8', '3gp', 'wmv', 'rm', 'rmvb', 'vob'],
+				['zip', 'rar', '7z'],
+				['sql', 'db', 'sqlite', 'mdb', 'accdb‌', 'mdf', 'accdb', 'dbf']
+			],
 	    };
 	},
 	mounted() {
@@ -295,6 +344,10 @@ export default {
 					type: '1',
 					rule: '1',
 					findText: '',
+					startTime: new Date(),
+					endTime: new Date(),
+					suffixType: '1',
+					suffix: ''
 				};
 				this.fileList = [];
 			}).catch(() => {
@@ -335,8 +388,13 @@ export default {
 				this.$message({message: '请输入要查找的文件搜索清单' , type: 'error'});
 				return false;
 			}
-			if(this.findType != '1' && !this.handleData.startTime && !this.handleData.endTime){ // 根据时间搜索
-				this.$message({message: '请选择搜索的时间范围' , type: 'error'});
+			
+			if(this.findType == '2' || this.findType == '3'){ //按照时间搜索
+				this.timeSearch();
+				return false;
+			}
+			if(this.findType == '4'){ //按照文件类型匹配
+				this.typeSearch();
 				return false;
 			}
 			
@@ -345,63 +403,101 @@ export default {
 				this.searchLoading = true;
 				setTimeout(() => {
 					this.searchLoading = false;
-				}, 2000)
+				}, 5000)
 				let findText = this.handleData.findText.split('\n');
 				let searchDir = this.searchDir.replace(/\\/g, '/');
-				let paramArr = []; // 匹配规则1
-				let paramArr2 = []; // 匹配规则2 - 文件名称中带.
-				for(let i=0; i< findText.length; i++){
-					if(findText[i].trim()){
-						if(findText[i].indexOf('.') > -1){
-							paramArr2.push(this.filterReg(findText[i].trim()));
-						}else{
-							paramArr.push(this.filterReg(findText[i].trim()));
-						}
-						
-					}
-				}
-				let params = paramArr.join(',');
-				let params2 = paramArr2.join(',');
 				let ruleStr = [];
 				let subStr = '/';
 				if(this.subFolder){ // 是否匹配子文件夹内文件
 					subStr = '/**/'
 				}
-				switch(this.handleData.rule){
-					case '1': //相同文件名
-						if(paramArr.length > 0){
-							ruleStr.push(searchDir + subStr + '{' + params + ',<}?(.[!.]+)');
-						}
-						if(paramArr2.length > 0){
-							ruleStr.push(searchDir + subStr + '{' + params2 + ',<}.[!.]+');
-						}
-						break;
-					case '2': //包含文件名
-						if(paramArr.length > 0){
-							ruleStr.push(searchDir + subStr + '*{' + params + ',<}*.[!.]+');
-							ruleStr.push(searchDir + subStr + '{' + params + ',<}');
-							ruleStr.push(searchDir + subStr + '[!.]+{' + params + ',<}');
-							ruleStr.push(searchDir + subStr + '{' + params + ',<}[!.]+');
-							ruleStr.push(searchDir + subStr + '[!.]+{' + params + ',<}[!.]+');
-						}
-						if(paramArr2.length > 0){
-							ruleStr.push(searchDir + subStr + '*{' + params2 + ',<}*.[!.]+');
-						}
-						break;
-					case '3': //文件名格式相同
-						if(paramArr.length > 0){
-							ruleStr.push(searchDir + subStr + '{' + params + ',<}');
+				
+				let paramArr = []; // 匹配规则1
+				let paramArr2 = []; // 匹配规则2 - 文件名称中带.
+				let paramArr3 = []; // 匹配规则3 - 文件夹
+				if(this.findType == '1'){
+					for(let i=0; i< findText.length; i++){
+						if(findText[i].trim()){
+							if(findText[i].indexOf('.') > -1){
+								paramArr2.push(this.filterReg(findText[i].trim()));
+							}else{
+								paramArr.push(this.filterReg(findText[i].trim()));
+							}
+							
 						}
-						if(paramArr2.length > 0){
-							ruleStr.push(searchDir + subStr + '{' + params2 + ',<}');
+					}
+					let params = paramArr.join(',');
+					let params2 = paramArr2.join(',');
+					switch(this.handleData.rule){
+						case '1': //相同文件名
+							if(paramArr.length > 0){
+								ruleStr.push(searchDir + subStr + '{' + params + ',<}?(.[!.]+)');
+							}
+							if(paramArr2.length > 0){
+								ruleStr.push(searchDir + subStr + '{' + params2 + ',<}.[!.]+');
+							}
+							break;
+						case '2': //包含文件名
+							if(paramArr.length > 0){
+								ruleStr.push(searchDir + subStr + '*{' + params + ',<}*.[!.]+');
+								ruleStr.push(searchDir + subStr + '{' + params + ',<}');
+								ruleStr.push(searchDir + subStr + '[!.]+{' + params + ',<}');
+								ruleStr.push(searchDir + subStr + '{' + params + ',<}[!.]+');
+								ruleStr.push(searchDir + subStr + '[!.]+{' + params + ',<}[!.]+');
+							}
+							if(paramArr2.length > 0){
+								ruleStr.push(searchDir + subStr + '*{' + params2 + ',<}*.[!.]+');
+							}
+							break;
+						case '3': //文件名格式相同
+							if(paramArr.length > 0){
+								ruleStr.push(searchDir + subStr + '{' + params + ',<}');
+							}
+							if(paramArr2.length > 0){
+								ruleStr.push(searchDir + subStr + '{' + params2 + ',<}');
+							}
+							break;
+					}
+				}else if(this.findType == '5'){
+					for(let i=0; i< findText.length; i++){
+						if(findText[i].trim()){
+							paramArr3.push(this.filterReg(findText[i].trim()));
 						}
-						break;
+					}
+					let params = paramArr3.join(',');
+					switch(this.handleData.rule){
+						case '1': //相同文件名
+							if(paramArr3.length > 0){
+								ruleStr.push(searchDir + subStr + '{' + params + ',<}');
+							}
+							break;
+						case '2': //包含文件名
+							if(paramArr3.length > 0){
+								ruleStr.push(searchDir + subStr + '*{' + params + ',<}*');
+							}
+							break;
+						case '3': //文件名格式相同
+							if(paramArr3.length > 0){
+								ruleStr.push(searchDir + subStr + '{' + params + ',<}');
+							}
+							break;
+					}
 				}
+				
 				console.log('----验证规则----', ruleStr);
 				try{
-					const files = await fg(ruleStr, { dot: true, onlyFiles:true, objectMode: true,markDirectories: true});
+					let obj = {
+						dot: true, onlyFiles:true, objectMode: true,markDirectories: true
+					};
+					if(this.findType == '5'){
+						obj = {
+							dot: true, onlyDirectories:true, objectMode: true,markDirectories: true,
+						};
+					}
+					const files = await fg(ruleStr, obj);
 					let regExp = new RegExp(paramArr.join('|'));
 					let regExp2 = new RegExp(paramArr2.join('|'));
+					let regExp3 = new RegExp(paramArr3.join('|'));
 					this.fileList = [];
 					files.map(item => {
 						let isDirectory = item.dirent.isDirectory();
@@ -412,10 +508,21 @@ export default {
 							title = filename;
 							suffix = '';
 						}
-						let res = regExp.exec(filename);
-						if(!res){
-							res = regExp2.exec(filename);
+						if(this.findType == '5'){
+							suffix = '';
 						}
+						let res = '';
+						if(this.findType == '1'){
+							res = regExp.exec(filename);
+							if(!res){
+								res = regExp2.exec(filename);
+							}
+						}
+						
+						if(this.findType == '5'){
+							res = regExp3.exec(filename);
+						}
+						
 						this.fileList.push({
 							title: title,
 							isDirectory: isDirectory,
@@ -435,6 +542,152 @@ export default {
 				}
 				
 			})();
+		},
+		// 按照时间搜索
+		timeSearch(){
+			if(!this.handleData.startTime && !this.handleData.endTime){ // 根据时间搜索
+				this.$message({message: '请选择搜索的时间范围' , type: 'error'});
+				return false;
+			}
+			
+			(async () => {
+				let startTime = new Date().getTime();
+				this.searchLoading = true;
+				setTimeout(() => {
+					this.searchLoading = false;
+				}, 5000);
+				
+				let searchDir = this.searchDir.replace(/\\/g, '/');
+				let ruleStr = [];
+				let subStr = '/';
+				if(this.subFolder){ // 是否匹配子文件夹内文件
+					subStr = '/**/'
+				}
+				ruleStr.push(searchDir + subStr + '*');
+				console.log('----验证规则----', ruleStr);
+				try{
+					const fsPromises = fs.promises;
+					const files = await fg(ruleStr, { dot: true, onlyFiles:true, suppressErrors: true});
+					this.fileList = [];
+					const BATCH_SIZE = 1000;
+					const matchedFiles = [];
+					for (let i = 0; i < files.length; i += BATCH_SIZE) {
+						const batch = files.slice(i, i + BATCH_SIZE);
+						const statsPromises = batch.map(file => fsPromises.stat(file).catch(() => null));
+						const statsResults = await Promise.all(statsPromises);
+						statsResults.forEach((stats, index) => {
+							if (!stats) return;
+							let rangeTime = stats.birthtime;
+							if(this.findType == '2'){ //创建时间
+								rangeTime = stats.birthtime
+							}else if(this.findType == '3'){ //修改时间
+								rangeTime = stats.mtime;
+							}
+							
+							let ruleFlag = false;
+							if(this.handleData.startTime && !this.handleData.endTime && rangeTime >= this.handleData.startTime){
+								ruleFlag = true;
+							}else if(!this.handleData.startTime && this.handleData.endTime && rangeTime <= this.handleData.endTime){
+								ruleFlag = true;
+							}else if (rangeTime >= this.handleData.startTime && rangeTime <= this.handleData.endTime) {
+								ruleFlag = true;
+							}
+							if(ruleFlag){
+								let filename = batch[index].substr(batch[index].lastIndexOf('/')+1);;
+								let title = filename.substr(0, filename.lastIndexOf('.'));
+								let suffix = filename.substr(filename.lastIndexOf('.'));
+								this.fileList.push({
+									title: title,
+									isDirectory: false,
+									suffix: suffix, // 带.
+									name: filename,
+									match: '-',
+									path: batch[index],
+									rangeTime: this.$utils.formatTime(rangeTime),
+									status: '1'
+								});
+							}
+							
+						});
+					}
+					
+					this.searchLoading = false;
+					let endTime = new Date().getTime();
+					this.times = endTime - startTime;
+					// console.log('----查找结果----', files);
+				}catch(e){
+					this.showError(e);
+				}
+				
+			})();
+		},
+		// 按照文件类型匹配
+		typeSearch(){
+			if(this.handleData.suffixType == '10' && this.handleData.suffix.trim() == ''){
+				this.$message({message: '请输入需要搜索的后缀名' , type: 'error'});
+				return false;
+			}
+			
+			(async () => {
+				let startTime = new Date().getTime();
+				this.searchLoading = true;
+				setTimeout(() => {
+					this.searchLoading = false;
+				}, 5000)
+				let searchDir = this.searchDir.replace(/\\/g, '/');
+				
+				
+				let ruleStr = [];
+				let subStr = '/';
+				if(this.subFolder){ // 是否匹配子文件夹内文件
+					subStr = '/**/'
+				}
+				let params = '';
+				if(this.handleData.suffixType != '10'){
+					let suffixIndex = Number(this.handleData.suffixType) - 1;
+					params = this.suffixList[suffixIndex].join(',');
+				}
+				
+				let customSuffix = '*.{' + params + '}';
+				if(this.handleData.suffixType == '10' && this.handleData.suffix.trim()){
+					customSuffix = '*.' + this.handleData.suffix.trimEnd();
+				}
+				
+				ruleStr.push(searchDir + subStr + customSuffix);
+				
+				console.log('----验证规则----', ruleStr);
+				try{
+					const files = await fg(ruleStr, { dot: true, caseSensitiveMatch: false, onlyFiles:true, objectMode: true,markDirectories: true});
+					this.fileList = [];
+					files.map(item => {
+						let isDirectory = false;
+						let filename = item.name;
+						let title = filename.substr(0, filename.lastIndexOf('.'));
+						let suffix = filename.substr(filename.lastIndexOf('.'));
+						if(filename.indexOf('.') < 0){
+							title = filename;
+							suffix = '';
+						}
+						this.fileList.push({
+							title: title,
+							isDirectory: isDirectory,
+							suffix: suffix, // 带.
+							name: filename,
+							match: suffix,
+							path: item.path,
+							status: '1'
+						});
+					});
+					this.searchLoading = false;
+					let endTime = new Date().getTime();
+					this.times = endTime - startTime;
+					// console.log('----查找结果----', files);
+				}catch(e){
+					this.showError(e);
+				}
+				
+			})();
+			
 			
 		},
 		// 导出检索结果
@@ -561,14 +814,29 @@ export default {
 										item.status = '3'
 									}catch(e){
 										let str = e.toString();
+										
+										if(str.indexOf('busy or locked') > -1){
+											this.$notify.error({
+												title: '提示',
+												message: '文件被系统或其他进程占用,无法剪切'+item.path
+											});
+										}
+										
 										if(str.indexOf('already exists') > -1){
 											newFilePath = newPath + middlePath + item.title + '重复文件' + uuidv4().substr(0, 18) + i.toString() + item.suffix;
-											fse.move(item.path, newFilePath).then(() => {
-												item.status = '3';
-											}).catch(err => {
-												item.status = '6';
-											})
+											try{
+												fse.move(item.path, newFilePath).then(() => {
+													item.status = '3';
+												}).catch(err => {
+													item.status = '6';
+												})
+											}catch(e){
+												console.log(e.toString());
+											}
+											
 										}
+										
+										
 									}
 								}
 								
@@ -577,6 +845,13 @@ export default {
 									fse.remove(item.path).then(() => {
 										item.status = '4';
 									}).catch(err => {
+										let str = err.toString();
+										if(str.indexOf('busy or locked') > -1){
+											this.$notify.error({
+												title: '提示',
+												message: '文件被系统或其他进程占用,无法删除'+item.path
+											});
+										}
 										item.status = '6';
 									})
 								}
@@ -881,7 +1156,7 @@ export default {
 	}
 	
 	.find-textarea{
-		height: calc(100% - 216px);
+		height: calc(100% - 160px);
 		
 		textarea{
 			height: 100%;

+ 1 - 2
src/renderer/utils/utils.js

@@ -49,8 +49,7 @@ function formatDate(str){
 }
 
 //格式化时间
-function formatTime(str){
-    let date = new Date(str*1000);
+function formatTime(date){
     const year = date.getFullYear();
     const month = date.getMonth() + 1;
     const day = date.getDate();