mirror of
https://gitee.com/czh-dev/upload-hub
synced 2026-09-05 23:47:46 +08:00
feat:完善批量修改文件名功能
This commit is contained in:
@@ -24,4 +24,10 @@ public class GlobalExceptionHandler {
|
|||||||
log.error("业务异常: {}", e.getMessage(), e);
|
log.error("业务异常: {}", e.getMessage(), e);
|
||||||
return Result.error(500, e.getMessage());
|
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;
|
package cn.czh.controller;
|
||||||
|
|
||||||
import cn.czh.base.Result;
|
import cn.czh.base.Result;
|
||||||
|
import cn.czh.dto.req.RenameFile;
|
||||||
import cn.czh.service.ILocalFileService;
|
import cn.czh.service.ILocalFileService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@@ -74,6 +75,9 @@ public class LocalFileController {
|
|||||||
return Result.success(localFileService.getFileTree(path, showFiles, showFolders, maxDepth));
|
return Result.success(localFileService.getFileTree(path, showFiles, showFolders, maxDepth));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件加密
|
||||||
|
*/
|
||||||
@PostMapping("/encrypt")
|
@PostMapping("/encrypt")
|
||||||
public Result<?> encryptFile(@RequestParam String filePath, @RequestParam String password) {
|
public Result<?> encryptFile(@RequestParam String filePath, @RequestParam String password) {
|
||||||
File file = new File(filePath);
|
File file = new File(filePath);
|
||||||
@@ -86,6 +90,9 @@ public class LocalFileController {
|
|||||||
return Result.success(localFileService.encryptFile(filePath, password));
|
return Result.success(localFileService.encryptFile(filePath, password));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件解密
|
||||||
|
*/
|
||||||
@PostMapping("/decrypt")
|
@PostMapping("/decrypt")
|
||||||
public Result<?> decryptFile(@RequestParam String filePath, @RequestParam String password) {
|
public Result<?> decryptFile(@RequestParam String filePath, @RequestParam String password) {
|
||||||
File file = new File(filePath);
|
File file = new File(filePath);
|
||||||
@@ -97,4 +104,13 @@ public class LocalFileController {
|
|||||||
}
|
}
|
||||||
return Result.success(localFileService.decryptFile(filePath, password));
|
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.FileNode;
|
||||||
import cn.czh.dto.IndexResult;
|
import cn.czh.dto.IndexResult;
|
||||||
|
import cn.czh.dto.req.RenameFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -19,4 +20,7 @@ public interface ILocalFileService {
|
|||||||
String encryptFile(String filePath, String password);
|
String encryptFile(String filePath, String password);
|
||||||
|
|
||||||
String decryptFile(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.base.BusinessException;
|
||||||
import cn.czh.dto.FileNode;
|
import cn.czh.dto.FileNode;
|
||||||
import cn.czh.dto.IndexResult;
|
import cn.czh.dto.IndexResult;
|
||||||
|
import cn.czh.dto.req.RenameFile;
|
||||||
import cn.czh.service.ILocalFileService;
|
import cn.czh.service.ILocalFileService;
|
||||||
import cn.czh.utils.FileSearchUtil;
|
import cn.czh.utils.FileSearchUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.compress.utils.Lists;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.PreDestroy;
|
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 {
|
private SecretKey generateKey(String password, byte[] salt) throws Exception {
|
||||||
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
|
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
|
||||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||||
|
|||||||
@@ -5,16 +5,33 @@
|
|||||||
<el-container>
|
<el-container>
|
||||||
<el-main>
|
<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">
|
<el-card v-show="files.length > 0" class="mt-4">
|
||||||
<div slot="header" class="flex justify-between items-center">
|
<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;">
|
<el-button type="danger" size="small" @click="clearFiles" style="margin-left: 10px;">
|
||||||
<i class="el-icon-delete mr-1"></i>全部清除
|
<i class="el-icon-delete mr-1"></i>全部清除
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-table :data="files" max-height="250">
|
<el-table :data="previewData" max-height="400">
|
||||||
<el-table-column prop="name" label="文件名" width="400"></el-table-column>
|
<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">
|
<el-table-column label="操作" width="100">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button type="text" size="small" @click="removeFile(scope.$index)">
|
<el-button type="text" size="small" @click="removeFile(scope.$index)">
|
||||||
@@ -25,58 +42,12 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</el-card>
|
</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">
|
<div class="action-buttons mt-4">
|
||||||
<el-button size="medium" @click="reset">重置</el-button>
|
<el-button size="medium" @click="reset" :disabled="files.length === 0">重置</el-button>
|
||||||
<el-button size="medium" type="info" @click="showPreview">预览</el-button>
|
<el-button size="medium" type="primary" @click="rename" :disabled="files.length === 0">重命名</el-button>
|
||||||
<el-button size="medium" type="primary" @click="rename">重命名</el-button>
|
|
||||||
</div>
|
</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-main>
|
||||||
</el-container>
|
</el-container>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,17 +58,14 @@ import SelectDir from '@/components/SelectDir/SelectDir.vue'
|
|||||||
import InsertTab from './BatchRename/InsertTab.vue'
|
import InsertTab from './BatchRename/InsertTab.vue'
|
||||||
import DeleteTab from './BatchRename/DeleteTab.vue'
|
import DeleteTab from './BatchRename/DeleteTab.vue'
|
||||||
import NumberingTab from './BatchRename/NumberingTab.vue'
|
import NumberingTab from './BatchRename/NumberingTab.vue'
|
||||||
import CleanupTab from './BatchRename/CleanupTab.vue'
|
import { batchRenameFile } from '@/utils/api'
|
||||||
import AdvancedTab from './BatchRename/AdvancedTab.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
SelectDir,
|
SelectDir,
|
||||||
InsertTab,
|
InsertTab,
|
||||||
DeleteTab,
|
DeleteTab,
|
||||||
NumberingTab,
|
NumberingTab
|
||||||
CleanupTab,
|
|
||||||
AdvancedTab
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -105,33 +73,212 @@ export default {
|
|||||||
activeTab: 'insert',
|
activeTab: 'insert',
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
filePaths: '',
|
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: {
|
computed: {
|
||||||
previewData() {
|
previewData() {
|
||||||
return this.files.map(file => ({
|
return this.files.map((file, index) => {
|
||||||
original: file.name,
|
let newName = file.name
|
||||||
new: 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: {
|
methods: {
|
||||||
handlePathChange(newPaths) {
|
handlePathChange(newPaths) {
|
||||||
this.filePaths = newPaths
|
this.filePaths = newPaths
|
||||||
|
|
||||||
// Handle both string and array inputs
|
// Handle both string and array inputs
|
||||||
const pathArray = Array.isArray(newPaths) ? newPaths : [newPaths]
|
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
|
// Get the paths of currently selected files
|
||||||
path: path // Store the full path
|
const currentPaths = new Set(pathArray)
|
||||||
}))
|
|
||||||
|
// Remove files that are no longer selected
|
||||||
// Filter out duplicates before adding
|
this.files = this.files.filter(file => currentPaths.has(file.path))
|
||||||
const uniqueNewFiles = newFiles.filter(newFile =>
|
|
||||||
!this.files.some(existingFile => existingFile.path === newFile.path)
|
// Add new files that weren't in the list before
|
||||||
)
|
const newFiles = pathArray
|
||||||
|
.filter(path => !this.files.some(file => file.path === path))
|
||||||
this.files.push(...uniqueNewFiles)
|
.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) {
|
handleDrop(e) {
|
||||||
this.unhighlight()
|
this.unhighlight()
|
||||||
@@ -172,21 +319,35 @@ export default {
|
|||||||
this.$message.warning('请选择要重命名的文件')
|
this.$message.warning('请选择要重命名的文件')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.$message.success(`将重命名 ${this.files.length} 个文件(此演示为模拟操作)`)
|
|
||||||
},
|
// Prepare the data for the API call using the preview data
|
||||||
showPreview() {
|
const fileRenames = this.previewData.map(file => ({
|
||||||
if (this.files.length === 0) {
|
absolutePath: file.path,
|
||||||
this.$message.warning('请选择要重命名的文件')
|
newName: file.new
|
||||||
return
|
}))
|
||||||
}
|
|
||||||
this.previewDialogVisible = true
|
// Call the backend API
|
||||||
},
|
batchRenameFile(fileRenames)
|
||||||
handlePreviewClose(done) {
|
.then(response => {
|
||||||
this.$confirm('确认关闭预览?')
|
console.log(response)
|
||||||
.then(() => {
|
this.$message.success('文件重命名成功')
|
||||||
done()
|
// 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 class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<el-form label-width="80px">
|
<el-form label-width="80px">
|
||||||
<el-form-item label="按文本删除">
|
<el-form-item label="文本移除">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<el-input v-model="deleteText" placeholder="要删除的文本" class="flex-1"></el-input>
|
<el-input v-model="deleteText" placeholder="要删除的文本" class="flex-1"></el-input>
|
||||||
<el-select v-model="deleteOption" class="flex-1">
|
<el-select v-model="deleteOption" class="flex-1">
|
||||||
@@ -12,11 +12,11 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="按位置删除">
|
<el-form-item label="索引移除">
|
||||||
<div class="flex gap-2 items-center">
|
<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>
|
<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>
|
<span>字符</span>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -24,11 +24,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<el-form label-width="80px">
|
<el-form label-width="80px">
|
||||||
<el-form-item label="删除文件名">
|
<el-form-item label="清空名称">
|
||||||
<el-checkbox v-model="keepExt">保留扩展名</el-checkbox>
|
<el-checkbox v-model="keepExt">保留扩展名</el-checkbox>
|
||||||
<el-button type="danger" size="small" class="ml-2" @click="deleteAllNames">删除所有名称</el-button>
|
<el-button type="danger" size="small" class="ml-2" @click="deleteAllNames">删除所有名称</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="删除扩展名">
|
<el-form-item label="移除扩展">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<el-button size="small" @click="removeAllExt">移除所有扩展名</el-button>
|
<el-button size="small" @click="removeAllExt">移除所有扩展名</el-button>
|
||||||
<el-input v-model="specificExt" placeholder=".ext" style="width: 100px;"></el-input>
|
<el-input v-model="specificExt" placeholder=".ext" style="width: 100px;"></el-input>
|
||||||
@@ -42,29 +42,61 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
props: {
|
||||||
|
rules: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
deleteText: '',
|
deleteText: this.rules.deleteText || '',
|
||||||
deleteOption: 'all',
|
deleteOption: this.rules.deleteOption || 'all',
|
||||||
startPos: 1,
|
startPos: this.rules.startPos || 0,
|
||||||
endPos: 1,
|
endPos: this.rules.endPos || 0,
|
||||||
keepExt: true,
|
keepExt: this.rules.keepExt !== undefined ? this.rules.keepExt : true,
|
||||||
specificExt: ''
|
specificExt: this.rules.specificExt || ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
deleteText() { this.emitRules() },
|
||||||
|
deleteOption() { this.emitRules() },
|
||||||
|
startPos() { this.emitRules() },
|
||||||
|
endPos() { this.emitRules() },
|
||||||
|
keepExt() { this.emitRules() },
|
||||||
|
specificExt() { this.emitRules() }
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
deleteAllNames() {
|
deleteAllNames() {
|
||||||
this.$message.info('删除所有名称(模拟操作)')
|
this.$emit('updateRules', { ...this.getRules(), deleteAllNames: true })
|
||||||
|
this.$message.success('已删除所有文件名')
|
||||||
},
|
},
|
||||||
removeAllExt() {
|
removeAllExt() {
|
||||||
this.$message.info('移除所有扩展名(模拟操作)')
|
this.$emit('updateRules', { ...this.getRules(), removeAllExt: true })
|
||||||
|
this.$message.success('已移除所有扩展名')
|
||||||
},
|
},
|
||||||
removeSpecificExt() {
|
removeSpecificExt() {
|
||||||
if (!this.specificExt) {
|
if (!this.specificExt) {
|
||||||
this.$message.warning('请输入要移除的扩展名')
|
this.$message.warning('请输入要移除的扩展名')
|
||||||
return
|
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>
|
<div>
|
||||||
<el-form label-width="80px">
|
<el-form label-width="80px">
|
||||||
<el-form-item label="插入文本">
|
<el-form-item label="插入文本">
|
||||||
<div class="flex gap-2">
|
<div class="insert-form-row">
|
||||||
<el-select v-model="insertPosition" class="flex-1">
|
<el-select v-model="insertPosition">
|
||||||
<el-option label="在开头" value="start"></el-option>
|
<el-option label="在开头" value="start"></el-option>
|
||||||
<el-option label="在结尾" value="end"></el-option>
|
<el-option label="在结尾" value="end"></el-option>
|
||||||
<el-option label="在指定位置" value="position"></el-option>
|
<el-option label="在指定位置" value="position"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-input v-model="insertText" placeholder="要插入的文本" class="flex-1"></el-input>
|
<el-input v-model="insertText" placeholder="要插入的文本"></el-input>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="替换文本">
|
<el-form-item label="替换文本">
|
||||||
<el-input v-model="searchText" placeholder="查找" class="mb-2"></el-input>
|
<div class="replace-form-row">
|
||||||
<el-input v-model="replaceText" placeholder="替换为" class="mb-2"></el-input>
|
<el-input v-model="searchText" placeholder="查找"></el-input>
|
||||||
<el-radio-group v-model="replaceOption">
|
<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="all">全部替换</el-radio>
|
||||||
<el-radio label="first">仅首次</el-radio>
|
<el-radio label="first">仅首次</el-radio>
|
||||||
<el-radio label="last">仅最后</el-radio>
|
<el-radio label="last">仅最后</el-radio>
|
||||||
@@ -25,18 +27,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<el-form label-width="80px">
|
<el-form label-width="80px">
|
||||||
<el-form-item label="正则表达式">
|
<el-form-item label="正则表达">
|
||||||
<el-input v-model="regexPattern" placeholder="正则模式" class="mb-2"></el-input>
|
<div class="regex-form-row">
|
||||||
<el-input v-model="regexReplace" placeholder="替换为 ($1, $2...)" class="mb-2"></el-input>
|
<el-input v-model="regexPattern" placeholder="正则模式"></el-input>
|
||||||
<div class="text-xs text-gray-500">
|
<el-input v-model="regexReplace" placeholder="替换为 ($1, $2...)"></el-input>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 mt-2">
|
||||||
使用 $1, $2 等引用捕获组。例如:"(.*)\.(.*)" → "$2.$1" 可交换点前后的部分。
|
使用 $1, $2 等引用捕获组。例如:"(.*)\.(.*)" → "$2.$1" 可交换点前后的部分。
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="大小写选项">
|
<el-form-item label="大小写">
|
||||||
<el-button-group>
|
<el-button-group>
|
||||||
<el-button size="small">全大写</el-button>
|
<el-button size="small" @click="handleCaseChange('uppercase')" :type="caseOption === 'uppercase' ? 'primary' : ''">全大写</el-button>
|
||||||
<el-button size="small">全小写</el-button>
|
<el-button size="small" @click="handleCaseChange('lowercase')" :type="caseOption === 'lowercase' ? 'primary' : ''">全小写</el-button>
|
||||||
<el-button size="small">标题格式</el-button>
|
<el-button size="small" @click="handleCaseChange('titlecase')" :type="caseOption === 'titlecase' ? 'primary' : ''">标题格式</el-button>
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
@@ -46,16 +50,119 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
props: {
|
||||||
|
rules: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
insertPosition: 'start',
|
insertPosition: this.rules.position || 'start',
|
||||||
insertText: '',
|
insertText: this.rules.text || '',
|
||||||
searchText: '',
|
searchText: this.rules.searchText || '',
|
||||||
replaceText: '',
|
replaceText: this.rules.replaceText || '',
|
||||||
replaceOption: 'all',
|
replaceOption: this.rules.replaceOption || 'all',
|
||||||
regexPattern: '',
|
regexPattern: this.rules.regexPattern || '',
|
||||||
regexReplace: ''
|
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 label-width="80px">
|
||||||
<el-form-item label="编号选项">
|
<el-form-item label="编号选项">
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<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-input-number v-model="startNum" :min="0" size="small"></el-input-number>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="步长" label-width="60px">
|
<el-form-item label="步长" label-width="60px">
|
||||||
<el-input-number v-model="step" :min="1" size="small"></el-input-number>
|
<el-input-number v-model="step" :min="1" size="small"></el-input-number>
|
||||||
</el-form-item>
|
</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-input-number v-model="repeat" :min="1" size="small"></el-input-number>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="位数" label-width="60px">
|
<el-form-item label="位数" label-width="60px">
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
<el-button type="primary" size="small" @click="previewNumber">预览</el-button>
|
<el-button type="primary" size="small" @click="previewNumber">预览</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-gray-500 mt-1">
|
<div class="text-xs text-gray-500 mt-1">
|
||||||
使用 {n} 表示数字,{name} 表示原文件名,{ext} 表示扩展名
|
使用 {n} 表示数字,{name} 表示原文件名,{ext} 表示扩展名(替换整个名称时可用)
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
@@ -53,22 +53,67 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
props: {
|
||||||
|
rules: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
startNum: 1,
|
startNum: this.rules.startNum || 1,
|
||||||
step: 1,
|
step: this.rules.step || 1,
|
||||||
repeat: 1,
|
repeat: this.rules.repeat || 1,
|
||||||
digits: 1,
|
digits: this.rules.digits || 1,
|
||||||
numberPosition: 'start',
|
numberPosition: this.rules.numberPosition || 'start',
|
||||||
position: 1,
|
position: this.rules.position || 1,
|
||||||
padZero: true,
|
padZero: this.rules.padZero !== undefined ? this.rules.padZero : true,
|
||||||
padChar: '0',
|
padChar: this.rules.padChar || '0',
|
||||||
format: '{n}'
|
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: {
|
methods: {
|
||||||
previewNumber() {
|
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: '/',
|
path: '/',
|
||||||
name: 'home',
|
name: 'home',
|
||||||
component: MainPage
|
component: MainPage
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/LabelStudio',
|
||||||
|
name: 'LabelStudio'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -351,6 +351,15 @@ const decryptFile = (filePath, password) => {
|
|||||||
return http.post("/localFile/decrypt", formData);
|
return http.post("/localFile/decrypt", formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量修改文件名
|
||||||
|
* @param {array} fileRenames
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const batchRenameFile = (fileRenames) => {
|
||||||
|
return http.post("/localFile/batchRenameFile", fileRenames);
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getUploadProgress,
|
getUploadProgress,
|
||||||
createMultipartUpload,
|
createMultipartUpload,
|
||||||
@@ -378,5 +387,6 @@ export {
|
|||||||
getFileTree,
|
getFileTree,
|
||||||
encryptFile,
|
encryptFile,
|
||||||
decryptFile,
|
decryptFile,
|
||||||
|
batchRenameFile,
|
||||||
httpExtra
|
httpExtra
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user