mirror of
https://gitee.com/czh-dev/upload-hub
synced 2026-07-12 08:21:21 +08:00
feat:完善批量修改文件名功能
This commit is contained in:
@@ -24,4 +24,10 @@ public class GlobalExceptionHandler {
|
||||
log.error("业务异常: {}", e.getMessage(), e);
|
||||
return Result.error(500, e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public Result<?> handleException(Exception e) {
|
||||
log.error("服务器异常: {}", e.getMessage(), e);
|
||||
return Result.error(500, "服务器异常");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package cn.czh.controller;
|
||||
|
||||
import cn.czh.base.Result;
|
||||
import cn.czh.dto.req.RenameFile;
|
||||
import cn.czh.service.ILocalFileService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -74,6 +75,9 @@ public class LocalFileController {
|
||||
return Result.success(localFileService.getFileTree(path, showFiles, showFolders, maxDepth));
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件加密
|
||||
*/
|
||||
@PostMapping("/encrypt")
|
||||
public Result<?> encryptFile(@RequestParam String filePath, @RequestParam String password) {
|
||||
File file = new File(filePath);
|
||||
@@ -86,6 +90,9 @@ public class LocalFileController {
|
||||
return Result.success(localFileService.encryptFile(filePath, password));
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件解密
|
||||
*/
|
||||
@PostMapping("/decrypt")
|
||||
public Result<?> decryptFile(@RequestParam String filePath, @RequestParam String password) {
|
||||
File file = new File(filePath);
|
||||
@@ -97,4 +104,13 @@ public class LocalFileController {
|
||||
}
|
||||
return Result.success(localFileService.decryptFile(filePath, password));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量修改文件名
|
||||
*/
|
||||
@PostMapping("/batchRenameFile")
|
||||
public Result<?> batchRenameFile(@RequestBody List<RenameFile> fileRenames) {
|
||||
localFileService.batchRenameFile(fileRenames);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package cn.czh.dto.req;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RenameFile {
|
||||
|
||||
private String absolutePath;
|
||||
|
||||
private String newName;
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package cn.czh.service;
|
||||
|
||||
import cn.czh.dto.FileNode;
|
||||
import cn.czh.dto.IndexResult;
|
||||
import cn.czh.dto.req.RenameFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -19,4 +20,7 @@ public interface ILocalFileService {
|
||||
String encryptFile(String filePath, String password);
|
||||
|
||||
String decryptFile(String filePath, String password);
|
||||
|
||||
void batchRenameFile(List<RenameFile> fileRenames);
|
||||
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ package cn.czh.service.impl;
|
||||
import cn.czh.base.BusinessException;
|
||||
import cn.czh.dto.FileNode;
|
||||
import cn.czh.dto.IndexResult;
|
||||
import cn.czh.dto.req.RenameFile;
|
||||
import cn.czh.service.ILocalFileService;
|
||||
import cn.czh.utils.FileSearchUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.compress.utils.Lists;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
@@ -250,6 +252,65 @@ public class LocalFileServiceImpl implements ILocalFileService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void batchRenameFile(List<RenameFile> fileRenames) {
|
||||
if (fileRenames == null || fileRenames.isEmpty()) {
|
||||
throw new BusinessException("文件重命名列表为空");
|
||||
}
|
||||
|
||||
List<File> successfullyRenamed = Lists.newArrayList();
|
||||
List<String> originalPaths = Lists.newArrayList();
|
||||
|
||||
try {
|
||||
for (RenameFile fileRename : fileRenames) {
|
||||
File oldFile = new File(fileRename.getAbsolutePath());
|
||||
|
||||
if (!oldFile.exists()) {
|
||||
throw new BusinessException("文件未找到: " + fileRename.getAbsolutePath());
|
||||
}
|
||||
|
||||
String parentPath = oldFile.getParent();
|
||||
File newFile = new File(parentPath + File.separator + fileRename.getNewName());
|
||||
|
||||
// 执行重命名
|
||||
if (oldFile.renameTo(newFile)) {
|
||||
successfullyRenamed.add(newFile);
|
||||
originalPaths.add(fileRename.getAbsolutePath());
|
||||
} else {
|
||||
throw new BusinessException("文件重命名失败: " + fileRename.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
rollbackRenamedFiles(successfullyRenamed, originalPaths);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚已重命名的文件
|
||||
* @param renamedFiles 已重命名的文件列表
|
||||
* @param originalPaths 原始文件路径列表
|
||||
*/
|
||||
private void rollbackRenamedFiles(List<File> renamedFiles, List<String> originalPaths) {
|
||||
if (renamedFiles.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < renamedFiles.size(); i++) {
|
||||
File renamedFile = renamedFiles.get(i);
|
||||
File originalFile = new File(originalPaths.get(i));
|
||||
try {
|
||||
if (renamedFile.exists()) {
|
||||
if (!renamedFile.renameTo(originalFile)) {
|
||||
log.error("回滚失败: {}", renamedFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.error("回滚失败: {}", ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SecretKey generateKey(String password, byte[] salt) throws Exception {
|
||||
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
|
||||
@@ -5,16 +5,33 @@
|
||||
<el-container>
|
||||
<el-main>
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<!-- 重命名选项 -->
|
||||
<el-card class="mt-4">
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="插入/替换" name="insert">
|
||||
<InsertTab :rules="insertRules" @updateRules="updateInsertRules" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="删除" name="delete">
|
||||
<DeleteTab :rules="deleteRules" @updateRules="updateDeleteRules" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="编号" name="numbering">
|
||||
<NumberingTab :rules="numberingRules" @updateRules="updateNumberingRules" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
|
||||
<!-- 文件列表与预览 -->
|
||||
<el-card v-show="files.length > 0" class="mt-4">
|
||||
<div slot="header" class="flex justify-between items-center">
|
||||
<span>已选择的文件</span>
|
||||
<span>文件重命名预览</span>
|
||||
<el-button type="danger" size="small" @click="clearFiles" style="margin-left: 10px;">
|
||||
<i class="el-icon-delete mr-1"></i>全部清除
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="files" max-height="250">
|
||||
<el-table-column prop="name" label="文件名" width="400"></el-table-column>
|
||||
<el-table :data="previewData" max-height="400">
|
||||
<el-table-column prop="original" label="原文件名" width="300"></el-table-column>
|
||||
<el-table-column label="→" width="50" align="center"></el-table-column>
|
||||
<el-table-column prop="new" label="新文件名" width="300"></el-table-column>
|
||||
<el-table-column label="操作" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="removeFile(scope.$index)">
|
||||
@@ -25,58 +42,12 @@
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 重命名选项 -->
|
||||
<el-card class="mt-4">
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="插入/替换" name="insert">
|
||||
<InsertTab />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="删除" name="delete">
|
||||
<DeleteTab />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="编号" name="numbering">
|
||||
<NumberingTab />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="清理" name="cleanup">
|
||||
<CleanupTab />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="高级" name="advanced">
|
||||
<AdvancedTab />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
|
||||
<!-- 操作按钮组 -->
|
||||
<div class="action-buttons mt-4">
|
||||
<el-button size="medium" @click="reset">重置</el-button>
|
||||
<el-button size="medium" type="info" @click="showPreview">预览</el-button>
|
||||
<el-button size="medium" type="primary" @click="rename">重命名</el-button>
|
||||
<el-button size="medium" @click="reset" :disabled="files.length === 0">重置</el-button>
|
||||
<el-button size="medium" type="primary" @click="rename" :disabled="files.length === 0">重命名</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 预览对话框 -->
|
||||
<el-dialog
|
||||
title="重命名预览"
|
||||
:visible.sync="previewDialogVisible"
|
||||
width="70%"
|
||||
:modal="false"
|
||||
:append-to-body="true"
|
||||
:before-close="handlePreviewClose">
|
||||
<div class="preview-area">
|
||||
<div v-if="files.length === 0" class="text-center text-gray-500 py-8">
|
||||
<i class="el-icon-document text-3xl mb-2"></i>
|
||||
<p>未选择文件或预览不可用</p>
|
||||
</div>
|
||||
<el-table v-else :data="previewData" max-height="400">
|
||||
<el-table-column prop="original" label="原文件名" width="300"></el-table-column>
|
||||
<el-table-column label="→" width="50" align="center"></el-table-column>
|
||||
<el-table-column prop="new" label="新文件名" width="300"></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="previewDialogVisible = false">关闭</el-button>
|
||||
<el-button type="primary" @click="rename">确认重命名</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</div>
|
||||
@@ -87,17 +58,14 @@ import SelectDir from '@/components/SelectDir/SelectDir.vue'
|
||||
import InsertTab from './BatchRename/InsertTab.vue'
|
||||
import DeleteTab from './BatchRename/DeleteTab.vue'
|
||||
import NumberingTab from './BatchRename/NumberingTab.vue'
|
||||
import CleanupTab from './BatchRename/CleanupTab.vue'
|
||||
import AdvancedTab from './BatchRename/AdvancedTab.vue'
|
||||
import { batchRenameFile } from '@/utils/api'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SelectDir,
|
||||
InsertTab,
|
||||
DeleteTab,
|
||||
NumberingTab,
|
||||
CleanupTab,
|
||||
AdvancedTab
|
||||
NumberingTab
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -105,33 +73,212 @@ export default {
|
||||
activeTab: 'insert',
|
||||
isDragging: false,
|
||||
filePaths: '',
|
||||
previewDialogVisible: false
|
||||
// Insert/Replace tab rules
|
||||
insertRules: {
|
||||
position: 'start',
|
||||
text: '',
|
||||
searchText: '',
|
||||
replaceText: '',
|
||||
replaceOption: 'all',
|
||||
regexPattern: '',
|
||||
regexReplace: '',
|
||||
caseOption: ''
|
||||
},
|
||||
// Delete tab rules
|
||||
deleteRules: {
|
||||
deleteText: '',
|
||||
deleteOption: 'all',
|
||||
startPos: 0,
|
||||
endPos: 0,
|
||||
keepExt: true,
|
||||
deleteAllNames: false,
|
||||
removeAllExt: false,
|
||||
specificExt: ''
|
||||
},
|
||||
// Numbering tab rules
|
||||
numberingRules: {
|
||||
startNum: 1,
|
||||
step: 1,
|
||||
repeat: 1,
|
||||
digits: 1,
|
||||
numberPosition: 'start',
|
||||
position: 1,
|
||||
padZero: true,
|
||||
padChar: '0',
|
||||
format: '{n}'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
previewData() {
|
||||
return this.files.map(file => ({
|
||||
original: file.name,
|
||||
new: file.name // 这里应实现实际的重命名逻辑
|
||||
}))
|
||||
return this.files.map((file, index) => {
|
||||
let newName = file.name
|
||||
let ext = newName.includes('.') ? newName.substring(newName.lastIndexOf('.')) : ''
|
||||
let nameWithoutExt = newName.includes('.') ? newName.substring(0, newName.lastIndexOf('.')) : newName
|
||||
let num, paddedNum, formattedNum, pos
|
||||
|
||||
// Apply rules based on active tab
|
||||
switch (this.activeTab) {
|
||||
case 'insert':
|
||||
// Insert/Replace rules
|
||||
if (this.insertRules.text) {
|
||||
if (this.insertRules.position === 'start') {
|
||||
nameWithoutExt = this.insertRules.text + nameWithoutExt
|
||||
} else if (this.insertRules.position === 'end') {
|
||||
nameWithoutExt = nameWithoutExt + this.insertRules.text
|
||||
}
|
||||
}
|
||||
|
||||
if (this.insertRules.searchText && this.insertRules.replaceText) {
|
||||
if (this.insertRules.replaceOption === 'all') {
|
||||
const regex = new RegExp(this.insertRules.searchText, 'g')
|
||||
nameWithoutExt = nameWithoutExt.replace(regex, this.insertRules.replaceText)
|
||||
} else if (this.insertRules.replaceOption === 'first') {
|
||||
const index = nameWithoutExt.indexOf(this.insertRules.searchText)
|
||||
if (index !== -1) {
|
||||
nameWithoutExt = nameWithoutExt.substring(0, index) +
|
||||
this.insertRules.replaceText +
|
||||
nameWithoutExt.substring(index + this.insertRules.searchText.length)
|
||||
}
|
||||
} else if (this.insertRules.replaceOption === 'last') {
|
||||
const lastIndex = nameWithoutExt.lastIndexOf(this.insertRules.searchText)
|
||||
if (lastIndex !== -1) {
|
||||
nameWithoutExt = nameWithoutExt.substring(0, lastIndex) +
|
||||
this.insertRules.replaceText +
|
||||
nameWithoutExt.substring(lastIndex + this.insertRules.searchText.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.insertRules.regexPattern && this.insertRules.regexReplace) {
|
||||
try {
|
||||
const regex = new RegExp(this.insertRules.regexPattern)
|
||||
nameWithoutExt = nameWithoutExt.replace(regex, this.insertRules.regexReplace)
|
||||
} catch (e) {
|
||||
console.error('Invalid regex pattern:', e)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.insertRules.caseOption) {
|
||||
switch (this.insertRules.caseOption) {
|
||||
case 'uppercase':
|
||||
nameWithoutExt = nameWithoutExt.toUpperCase()
|
||||
break
|
||||
case 'lowercase':
|
||||
nameWithoutExt = nameWithoutExt.toLowerCase()
|
||||
break
|
||||
case 'titlecase':
|
||||
nameWithoutExt = nameWithoutExt.split(' ').map(word =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
||||
).join(' ')
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 'delete':
|
||||
// Delete rules
|
||||
if (this.deleteRules.deleteText && nameWithoutExt) {
|
||||
if (this.deleteRules.deleteOption === 'all') {
|
||||
const regex = new RegExp(this.deleteRules.deleteText, 'g')
|
||||
nameWithoutExt = nameWithoutExt.replace(regex, '')
|
||||
} else if (this.deleteRules.deleteOption === 'first') {
|
||||
const index = nameWithoutExt.indexOf(this.deleteRules.deleteText)
|
||||
if (index !== -1) {
|
||||
nameWithoutExt = nameWithoutExt.substring(0, index) +
|
||||
nameWithoutExt.substring(index + this.deleteRules.deleteText.length)
|
||||
}
|
||||
} else if (this.deleteRules.deleteOption === 'last') {
|
||||
const lastIndex = nameWithoutExt.lastIndexOf(this.deleteRules.deleteText)
|
||||
if (lastIndex !== -1) {
|
||||
nameWithoutExt = nameWithoutExt.substring(0, lastIndex) +
|
||||
nameWithoutExt.substring(lastIndex + this.deleteRules.deleteText.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.deleteRules.startPos && this.deleteRules.endPos) {
|
||||
const start = Math.max(0, this.deleteRules.startPos - 1)
|
||||
const end = Math.min(nameWithoutExt.length, this.deleteRules.endPos)
|
||||
if (start < end) {
|
||||
nameWithoutExt = nameWithoutExt.substring(0, start) + nameWithoutExt.substring(end)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.deleteRules.deleteAllNames) {
|
||||
nameWithoutExt = ''
|
||||
}
|
||||
|
||||
if (this.deleteRules.removeAllExt) {
|
||||
ext = ''
|
||||
} else if (this.deleteRules.specificExt && ext.toLowerCase() === this.deleteRules.specificExt.toLowerCase()) {
|
||||
ext = ''
|
||||
}
|
||||
break
|
||||
|
||||
case 'numbering':
|
||||
// Numbering rules
|
||||
num = this.numberingRules.startNum + (index * this.numberingRules.step)
|
||||
paddedNum = this.numberingRules.padZero
|
||||
? String(num).padStart(this.numberingRules.digits, this.numberingRules.padChar)
|
||||
: String(num)
|
||||
|
||||
formattedNum = this.numberingRules.format
|
||||
.replace('{n}', paddedNum)
|
||||
.replace('{name}', nameWithoutExt)
|
||||
.replace('{ext}', ext)
|
||||
|
||||
switch (this.numberingRules.numberPosition) {
|
||||
case 'start':
|
||||
nameWithoutExt = paddedNum + nameWithoutExt
|
||||
break
|
||||
case 'end':
|
||||
nameWithoutExt = nameWithoutExt + paddedNum
|
||||
break
|
||||
case 'position':
|
||||
pos = Math.min(this.numberingRules.position - 1, nameWithoutExt.length)
|
||||
nameWithoutExt = nameWithoutExt.substring(0, pos) + paddedNum + nameWithoutExt.substring(pos)
|
||||
break
|
||||
case 'replace':
|
||||
nameWithoutExt = formattedNum
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Combine name and extension
|
||||
newName = nameWithoutExt + (this.deleteRules.keepExt ? ext : '')
|
||||
|
||||
return {
|
||||
original: file.name,
|
||||
new: newName,
|
||||
path: file.path
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handlePathChange(newPaths) {
|
||||
this.filePaths = newPaths
|
||||
|
||||
// Handle both string and array inputs
|
||||
const pathArray = Array.isArray(newPaths) ? newPaths : [newPaths]
|
||||
const newFiles = pathArray.map(path => ({
|
||||
name: path.split('\\').pop(), // Get the last part of the path as filename
|
||||
path: path // Store the full path
|
||||
}))
|
||||
|
||||
// Filter out duplicates before adding
|
||||
const uniqueNewFiles = newFiles.filter(newFile =>
|
||||
!this.files.some(existingFile => existingFile.path === newFile.path)
|
||||
)
|
||||
|
||||
this.files.push(...uniqueNewFiles)
|
||||
|
||||
// Get the paths of currently selected files
|
||||
const currentPaths = new Set(pathArray)
|
||||
|
||||
// Remove files that are no longer selected
|
||||
this.files = this.files.filter(file => currentPaths.has(file.path))
|
||||
|
||||
// Add new files that weren't in the list before
|
||||
const newFiles = pathArray
|
||||
.filter(path => !this.files.some(file => file.path === path))
|
||||
.map(path => ({
|
||||
name: path.split('\\').pop(), // Get the last part of the path as filename
|
||||
path: path // Store the full path
|
||||
}))
|
||||
|
||||
this.files.push(...newFiles)
|
||||
},
|
||||
handleDrop(e) {
|
||||
this.unhighlight()
|
||||
@@ -172,21 +319,35 @@ export default {
|
||||
this.$message.warning('请选择要重命名的文件')
|
||||
return
|
||||
}
|
||||
this.$message.success(`将重命名 ${this.files.length} 个文件(此演示为模拟操作)`)
|
||||
},
|
||||
showPreview() {
|
||||
if (this.files.length === 0) {
|
||||
this.$message.warning('请选择要重命名的文件')
|
||||
return
|
||||
}
|
||||
this.previewDialogVisible = true
|
||||
},
|
||||
handlePreviewClose(done) {
|
||||
this.$confirm('确认关闭预览?')
|
||||
.then(() => {
|
||||
done()
|
||||
|
||||
// Prepare the data for the API call using the preview data
|
||||
const fileRenames = this.previewData.map(file => ({
|
||||
absolutePath: file.path,
|
||||
newName: file.new
|
||||
}))
|
||||
|
||||
// Call the backend API
|
||||
batchRenameFile(fileRenames)
|
||||
.then(response => {
|
||||
console.log(response)
|
||||
this.$message.success('文件重命名成功')
|
||||
// Clear both the file list and SelectDir component
|
||||
this.clearFiles()
|
||||
this.filePaths = '' // Clear the SelectDir component
|
||||
})
|
||||
.catch(() => {})
|
||||
.catch(error => {
|
||||
console.error('重命名失败:', error)
|
||||
this.$message.error('文件重命名失败: ' + (error.message || '未知错误'))
|
||||
})
|
||||
},
|
||||
updateInsertRules(rules) {
|
||||
this.insertRules = { ...this.insertRules, ...rules }
|
||||
},
|
||||
updateDeleteRules(rules) {
|
||||
this.deleteRules = { ...this.deleteRules, ...rules }
|
||||
},
|
||||
updateNumberingRules(rules) {
|
||||
this.numberingRules = { ...this.numberingRules, ...rules }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
<template>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="随机字符串">
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<el-form-item label="长度" label-width="40px">
|
||||
<el-input-number v-model="randomLength" :min="1" :max="32" size="small"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" label-width="40px">
|
||||
<el-select v-model="randomType" size="small">
|
||||
<el-option label="字母数字混合" value="alphanum"></el-option>
|
||||
<el-option label="仅字母" value="alpha"></el-option>
|
||||
<el-option label="仅数字" value="num"></el-option>
|
||||
<el-option label="自定义字符" value="custom"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<el-checkbox v-model="useUpper">大写</el-checkbox>
|
||||
<el-checkbox v-model="useLower" class="ml-4">小写</el-checkbox>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="日期/时间">
|
||||
<div class="flex gap-2">
|
||||
<el-select v-model="dateType" class="flex-1">
|
||||
<el-option label="创建日期" value="created"></el-option>
|
||||
<el-option label="修改日期" value="modified"></el-option>
|
||||
<el-option label="当前日期" value="current"></el-option>
|
||||
</el-select>
|
||||
<el-input v-model="dateFormat" class="flex-1" placeholder="YYYY-MM-DD"></el-input>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-1">
|
||||
格式:YYYY(年),MM(月),DD(日),HH(时),mm(分),ss(秒)
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div>
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="文件元数据">
|
||||
<div class="flex gap-2">
|
||||
<el-select v-model="metadataType" class="flex-1">
|
||||
<el-option label="文件大小" value="size"></el-option>
|
||||
<el-option label="MD5 哈希" value="md5"></el-option>
|
||||
<el-option label="SHA-1 哈希" value="sha1"></el-option>
|
||||
<el-option label="尺寸(图片)" value="dimensions"></el-option>
|
||||
<el-option label="时长(媒体)" value="duration"></el-option>
|
||||
</el-select>
|
||||
<el-select v-model="metadataPosition" class="flex-1">
|
||||
<el-option label="在开头" value="start"></el-option>
|
||||
<el-option label="在结尾" value="end"></el-option>
|
||||
<el-option label="替换整个名称" value="replace"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="JS 规则">
|
||||
<el-input type="textarea" v-model="jsRule" :rows="4" placeholder="function rename(name, index) { return name.toUpperCase(); }"></el-input>
|
||||
<div class="text-xs text-gray-500 mt-1">
|
||||
可用变量:name(文件名),ext(扩展名),index(文件编号)
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
randomLength: 8,
|
||||
randomType: 'alphanum',
|
||||
useUpper: false,
|
||||
useLower: true,
|
||||
dateType: 'created',
|
||||
dateFormat: 'YYYY-MM-DD',
|
||||
metadataType: 'size',
|
||||
metadataPosition: 'start',
|
||||
jsRule: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,67 +0,0 @@
|
||||
<template>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="移除内容">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<el-button size="small" @click="removeParentheses">移除括号 () 中的文本</el-button>
|
||||
<el-button size="small" @click="removeBrackets">移除方括号 [] 中的文本</el-button>
|
||||
<el-button size="small" @click="removeBraces">移除大括号 {} 中的文本</el-button>
|
||||
<el-button size="small" @click="removeAllBrackets">移除所有括号</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="修剪选项">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<el-button size="small" @click="trimSpaces">修剪空格</el-button>
|
||||
<el-button size="small" @click="trimUnderscores">修剪下划线</el-button>
|
||||
<el-button size="small" @click="trimDots">修剪点号</el-button>
|
||||
<el-button size="small" @click="trimAll">修剪所有</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div>
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="替换字符">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<el-button size="small" @click="spaceToUnderscore">空格转为下划线</el-button>
|
||||
<el-button size="small" @click="underscoreToSpace">下划线转为空格</el-button>
|
||||
<el-button size="small" @click="spaceToDot">空格转为点号</el-button>
|
||||
<el-button size="small" @click="dotToSpace">点号转为空格</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="重复字符">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<el-button size="small" @click="removeDupSpaces">移除重复空格</el-button>
|
||||
<el-button size="small" @click="removeDupUnderscores">移除重复下划线</el-button>
|
||||
<el-button size="small" @click="removeDupDots">移除重复点号</el-button>
|
||||
<el-button size="small" @click="removeAllDup">移除所有重复</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
removeParentheses() { this.$message.info('移除括号内容(模拟操作)') },
|
||||
removeBrackets() { this.$message.info('移除方括号内容(模拟操作)') },
|
||||
removeBraces() { this.$message.info('移除大括号内容(模拟操作)') },
|
||||
removeAllBrackets() { this.$message.info('移除所有括号(模拟操作)') },
|
||||
trimSpaces() { this.$message.info('修剪空格(模拟操作)') },
|
||||
trimUnderscores() { this.$message.info('修剪下划线(模拟操作)') },
|
||||
trimDots() { this.$message.info('修剪点号(模拟操作)') },
|
||||
trimAll() { this.$message.info('修剪所有(模拟操作)') },
|
||||
spaceToUnderscore() { this.$message.info('空格转为下划线(模拟操作)') },
|
||||
underscoreToSpace() { this.$message.info('下划线转为空格(模拟操作)') },
|
||||
spaceToDot() { this.$message.info('空格转为点号(模拟操作)') },
|
||||
dotToSpace() { this.$message.info('点号转为空格(模拟操作)') },
|
||||
removeDupSpaces() { this.$message.info('移除重复空格(模拟操作)') },
|
||||
removeDupUnderscores() { this.$message.info('移除重复下划线(模拟操作)') },
|
||||
removeDupDots() { this.$message.info('移除重复点号(模拟操作)') },
|
||||
removeAllDup() { this.$message.info('移除所有重复(模拟操作)') }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="按文本删除">
|
||||
<el-form-item label="文本移除">
|
||||
<div class="flex gap-2">
|
||||
<el-input v-model="deleteText" placeholder="要删除的文本" class="flex-1"></el-input>
|
||||
<el-select v-model="deleteOption" class="flex-1">
|
||||
@@ -12,11 +12,11 @@
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="按位置删除">
|
||||
<el-form-item label="索引移除">
|
||||
<div class="flex gap-2 items-center">
|
||||
<el-input-number v-model="startPos" :min="1" size="small" placeholder="起始"></el-input-number>
|
||||
<el-input-number v-model="startPos" :min="0" size="small" placeholder="起始"></el-input-number>
|
||||
<span>至</span>
|
||||
<el-input-number v-model="endPos" :min="1" size="small" placeholder="结束"></el-input-number>
|
||||
<el-input-number v-model="endPos" :min="0" size="small" placeholder="结束"></el-input-number>
|
||||
<span>字符</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
@@ -24,11 +24,11 @@
|
||||
</div>
|
||||
<div>
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="删除文件名">
|
||||
<el-form-item label="清空名称">
|
||||
<el-checkbox v-model="keepExt">保留扩展名</el-checkbox>
|
||||
<el-button type="danger" size="small" class="ml-2" @click="deleteAllNames">删除所有名称</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="删除扩展名">
|
||||
<el-form-item label="移除扩展">
|
||||
<div class="flex gap-2">
|
||||
<el-button size="small" @click="removeAllExt">移除所有扩展名</el-button>
|
||||
<el-input v-model="specificExt" placeholder=".ext" style="width: 100px;"></el-input>
|
||||
@@ -42,29 +42,61 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
rules: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
deleteText: '',
|
||||
deleteOption: 'all',
|
||||
startPos: 1,
|
||||
endPos: 1,
|
||||
keepExt: true,
|
||||
specificExt: ''
|
||||
deleteText: this.rules.deleteText || '',
|
||||
deleteOption: this.rules.deleteOption || 'all',
|
||||
startPos: this.rules.startPos || 0,
|
||||
endPos: this.rules.endPos || 0,
|
||||
keepExt: this.rules.keepExt !== undefined ? this.rules.keepExt : true,
|
||||
specificExt: this.rules.specificExt || ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
deleteText() { this.emitRules() },
|
||||
deleteOption() { this.emitRules() },
|
||||
startPos() { this.emitRules() },
|
||||
endPos() { this.emitRules() },
|
||||
keepExt() { this.emitRules() },
|
||||
specificExt() { this.emitRules() }
|
||||
},
|
||||
methods: {
|
||||
deleteAllNames() {
|
||||
this.$message.info('删除所有名称(模拟操作)')
|
||||
this.$emit('updateRules', { ...this.getRules(), deleteAllNames: true })
|
||||
this.$message.success('已删除所有文件名')
|
||||
},
|
||||
removeAllExt() {
|
||||
this.$message.info('移除所有扩展名(模拟操作)')
|
||||
this.$emit('updateRules', { ...this.getRules(), removeAllExt: true })
|
||||
this.$message.success('已移除所有扩展名')
|
||||
},
|
||||
removeSpecificExt() {
|
||||
if (!this.specificExt) {
|
||||
this.$message.warning('请输入要移除的扩展名')
|
||||
return
|
||||
}
|
||||
this.$message.info(`移除扩展名 ${this.specificExt}(模拟操作)`)
|
||||
this.emitRules()
|
||||
this.$message.success(`已移除扩展名 ${this.specificExt}`)
|
||||
},
|
||||
getRules() {
|
||||
return {
|
||||
deleteText: this.deleteText,
|
||||
deleteOption: this.deleteOption,
|
||||
startPos: this.startPos,
|
||||
endPos: this.endPos,
|
||||
keepExt: this.keepExt,
|
||||
deleteAllNames: false,
|
||||
removeAllExt: false,
|
||||
specificExt: this.specificExt
|
||||
}
|
||||
},
|
||||
emitRules() {
|
||||
this.$emit('updateRules', this.getRules())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,19 +3,21 @@
|
||||
<div>
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="插入文本">
|
||||
<div class="flex gap-2">
|
||||
<el-select v-model="insertPosition" class="flex-1">
|
||||
<div class="insert-form-row">
|
||||
<el-select v-model="insertPosition">
|
||||
<el-option label="在开头" value="start"></el-option>
|
||||
<el-option label="在结尾" value="end"></el-option>
|
||||
<el-option label="在指定位置" value="position"></el-option>
|
||||
</el-select>
|
||||
<el-input v-model="insertText" placeholder="要插入的文本" class="flex-1"></el-input>
|
||||
<el-input v-model="insertText" placeholder="要插入的文本"></el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="替换文本">
|
||||
<el-input v-model="searchText" placeholder="查找" class="mb-2"></el-input>
|
||||
<el-input v-model="replaceText" placeholder="替换为" class="mb-2"></el-input>
|
||||
<el-radio-group v-model="replaceOption">
|
||||
<div class="replace-form-row">
|
||||
<el-input v-model="searchText" placeholder="查找"></el-input>
|
||||
<el-input v-model="replaceText" placeholder="替换为"></el-input>
|
||||
</div>
|
||||
<el-radio-group v-model="replaceOption" class="mt-2">
|
||||
<el-radio label="all">全部替换</el-radio>
|
||||
<el-radio label="first">仅首次</el-radio>
|
||||
<el-radio label="last">仅最后</el-radio>
|
||||
@@ -25,18 +27,20 @@
|
||||
</div>
|
||||
<div>
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="正则表达式">
|
||||
<el-input v-model="regexPattern" placeholder="正则模式" class="mb-2"></el-input>
|
||||
<el-input v-model="regexReplace" placeholder="替换为 ($1, $2...)" class="mb-2"></el-input>
|
||||
<div class="text-xs text-gray-500">
|
||||
<el-form-item label="正则表达">
|
||||
<div class="regex-form-row">
|
||||
<el-input v-model="regexPattern" placeholder="正则模式"></el-input>
|
||||
<el-input v-model="regexReplace" placeholder="替换为 ($1, $2...)"></el-input>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-2">
|
||||
使用 $1, $2 等引用捕获组。例如:"(.*)\.(.*)" → "$2.$1" 可交换点前后的部分。
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="大小写选项">
|
||||
<el-form-item label="大小写">
|
||||
<el-button-group>
|
||||
<el-button size="small">全大写</el-button>
|
||||
<el-button size="small">全小写</el-button>
|
||||
<el-button size="small">标题格式</el-button>
|
||||
<el-button size="small" @click="handleCaseChange('uppercase')" :type="caseOption === 'uppercase' ? 'primary' : ''">全大写</el-button>
|
||||
<el-button size="small" @click="handleCaseChange('lowercase')" :type="caseOption === 'lowercase' ? 'primary' : ''">全小写</el-button>
|
||||
<el-button size="small" @click="handleCaseChange('titlecase')" :type="caseOption === 'titlecase' ? 'primary' : ''">标题格式</el-button>
|
||||
</el-button-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -46,16 +50,119 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
rules: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
insertPosition: 'start',
|
||||
insertText: '',
|
||||
searchText: '',
|
||||
replaceText: '',
|
||||
replaceOption: 'all',
|
||||
regexPattern: '',
|
||||
regexReplace: ''
|
||||
insertPosition: this.rules.position || 'start',
|
||||
insertText: this.rules.text || '',
|
||||
searchText: this.rules.searchText || '',
|
||||
replaceText: this.rules.replaceText || '',
|
||||
replaceOption: this.rules.replaceOption || 'all',
|
||||
regexPattern: this.rules.regexPattern || '',
|
||||
regexReplace: this.rules.regexReplace || '',
|
||||
caseOption: this.rules.caseOption || ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
insertPosition: {
|
||||
handler(newVal) {
|
||||
this.emitRulesUpdate(newVal)
|
||||
}
|
||||
},
|
||||
insertText: {
|
||||
handler(newVal) {
|
||||
this.emitRulesUpdate(newVal)
|
||||
}
|
||||
},
|
||||
searchText: {
|
||||
handler(newVal) {
|
||||
this.emitRulesUpdate(newVal)
|
||||
}
|
||||
},
|
||||
replaceText: {
|
||||
handler(newVal) {
|
||||
this.emitRulesUpdate(newVal)
|
||||
}
|
||||
},
|
||||
replaceOption: {
|
||||
handler(newVal) {
|
||||
this.emitRulesUpdate(newVal)
|
||||
}
|
||||
},
|
||||
regexPattern: {
|
||||
handler(newVal) {
|
||||
this.emitRulesUpdate(newVal)
|
||||
}
|
||||
},
|
||||
regexReplace: {
|
||||
handler(newVal) {
|
||||
this.emitRulesUpdate(newVal)
|
||||
}
|
||||
},
|
||||
caseOption: {
|
||||
handler(newVal) {
|
||||
this.emitRulesUpdate(newVal)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCaseChange(option) {
|
||||
this.caseOption = this.caseOption === option ? '' : option
|
||||
this.emitRulesUpdate()
|
||||
},
|
||||
emitRulesUpdate() {
|
||||
this.$emit('updateRules', {
|
||||
position: this.insertPosition,
|
||||
text: this.insertText,
|
||||
searchText: this.searchText,
|
||||
replaceText: this.replaceText,
|
||||
replaceOption: this.replaceOption,
|
||||
regexPattern: this.regexPattern,
|
||||
regexReplace: this.regexReplace,
|
||||
caseOption: this.caseOption
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.insert-form-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.insert-form-row :deep(.el-select) {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.insert-form-row :deep(.el-input) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.replace-form-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.replace-form-row :deep(.el-input) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.regex-form-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.regex-form-row :deep(.el-input) {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -4,13 +4,13 @@
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="编号选项">
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<el-form-item label="起始数字" label-width="60px">
|
||||
<el-form-item label="起始" label-width="60px">
|
||||
<el-input-number v-model="startNum" :min="0" size="small"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="步长" label-width="60px">
|
||||
<el-input-number v-model="step" :min="1" size="small"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="重复次数" label-width="60px">
|
||||
<el-form-item label="重复" label-width="60px">
|
||||
<el-input-number v-model="repeat" :min="1" size="small"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="位数" label-width="60px">
|
||||
@@ -43,7 +43,7 @@
|
||||
<el-button type="primary" size="small" @click="previewNumber">预览</el-button>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-1">
|
||||
使用 {n} 表示数字,{name} 表示原文件名,{ext} 表示扩展名
|
||||
使用 {n} 表示数字,{name} 表示原文件名,{ext} 表示扩展名(替换整个名称时可用)
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -53,22 +53,67 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
rules: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
startNum: 1,
|
||||
step: 1,
|
||||
repeat: 1,
|
||||
digits: 1,
|
||||
numberPosition: 'start',
|
||||
position: 1,
|
||||
padZero: true,
|
||||
padChar: '0',
|
||||
format: '{n}'
|
||||
startNum: this.rules.startNum || 1,
|
||||
step: this.rules.step || 1,
|
||||
repeat: this.rules.repeat || 1,
|
||||
digits: this.rules.digits || 1,
|
||||
numberPosition: this.rules.numberPosition || 'start',
|
||||
position: this.rules.position || 1,
|
||||
padZero: this.rules.padZero !== undefined ? this.rules.padZero : true,
|
||||
padChar: this.rules.padChar || '0',
|
||||
format: this.rules.format || '{n}'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
startNum() { this.emitRules() },
|
||||
step() { this.emitRules() },
|
||||
repeat() { this.emitRules() },
|
||||
digits() { this.emitRules() },
|
||||
numberPosition() { this.emitRules() },
|
||||
position() { this.emitRules() },
|
||||
padZero() { this.emitRules() },
|
||||
padChar() { this.emitRules() },
|
||||
format() { this.emitRules() }
|
||||
},
|
||||
methods: {
|
||||
previewNumber() {
|
||||
this.$message.info('预览数字格式(模拟操作)')
|
||||
let result = []
|
||||
for (let i = 0; i < this.repeat; i++) {
|
||||
const num = this.startNum + (i * this.step)
|
||||
const paddedNum = this.padZero
|
||||
? String(num).padStart(this.digits, this.padChar)
|
||||
: String(num)
|
||||
|
||||
const preview = this.format
|
||||
.replace('{n}', paddedNum)
|
||||
.replace('{name}', 'example')
|
||||
.replace('{ext}', '.txt')
|
||||
|
||||
result.push(preview)
|
||||
}
|
||||
|
||||
this.$message.success(`预览: ${result.join(', ')}`)
|
||||
},
|
||||
emitRules() {
|
||||
this.$emit('updateRules', {
|
||||
startNum: this.startNum,
|
||||
step: this.step,
|
||||
repeat: this.repeat,
|
||||
digits: this.digits,
|
||||
numberPosition: this.numberPosition,
|
||||
position: this.position,
|
||||
padZero: this.padZero,
|
||||
padChar: this.padChar,
|
||||
format: this.format
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ const routes = [
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: MainPage
|
||||
},
|
||||
{
|
||||
path: '/LabelStudio',
|
||||
name: 'LabelStudio'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -351,6 +351,15 @@ const decryptFile = (filePath, password) => {
|
||||
return http.post("/localFile/decrypt", formData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量修改文件名
|
||||
* @param {array} fileRenames
|
||||
* @returns
|
||||
*/
|
||||
const batchRenameFile = (fileRenames) => {
|
||||
return http.post("/localFile/batchRenameFile", fileRenames);
|
||||
}
|
||||
|
||||
export {
|
||||
getUploadProgress,
|
||||
createMultipartUpload,
|
||||
@@ -378,5 +387,6 @@ export {
|
||||
getFileTree,
|
||||
encryptFile,
|
||||
decryptFile,
|
||||
batchRenameFile,
|
||||
httpExtra
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user