This commit is contained in:
fanxb 2023-03-07 00:06:59 +08:00
parent 8d78127e4d
commit 2cf41a9c18
13 changed files with 515 additions and 385 deletions

8
openRenamerBackend/.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
openRenamerBackend/.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/openRenamerBackend.iml" filepath="$PROJECT_DIR$/.idea/openRenamerBackend.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/dist" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
openRenamerBackend/.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@ -1,49 +0,0 @@
import { Context } from "koa";
import FileService from "../service/FileService";
import config from "../config";
const router = {};
/**
*
*/
router["GET /file/query"] = async function (ctx: Context) {
ctx.body = await FileService.readPath(ctx.query.path as string, ctx.query.showHidden === '1');
};
/**
*windows
*/
router['GET /file/isWindows'] = async function (ctx: Context) {
ctx.body = config.isWindows;
}
/**
*
*/
router["GET /file/path/exist"] = async function (ctx: Context) {
ctx.body = await FileService.checkExist(ctx.query.path as string);
};
/**
*
*/
router["POST /file/path/save"] = async function (ctx: Context) {
ctx.body = await FileService.savePath(ctx.request.body);
};
/**
*
*/
router["GET /file/path"] = async function (ctx: Context) {
ctx.body = await FileService.getSaveList();
};
/**
*
*/
router["DELETE /file/path/delete"] = async function (ctx: Context) {
ctx.body = await FileService.deleteOne(ctx.query.id);
};
export default router;

View File

@ -11,6 +11,13 @@ router["GET /file/query"] = async function (ctx: Context) {
ctx.body = await FileService.readPath(ctx.query.path as string, ctx.query.showHidden === '1');
};
/**
*
*/
router["POST /file/recursionQuery"] = async function (ctx: Context) {
ctx.body = await FileService.readRecursion(ctx.request.body);
};
/**
*windows
*/
@ -46,4 +53,19 @@ router["DELETE /file/path/delete"] = async function (ctx: Context) {
ctx.body = await FileService.deleteOne(ctx.query.id);
};
/**
* delete file batch
*/
router["POST /file/deleteBatch"] = async function (ctx: Context) {
ctx.body = await FileService.deleteBatch(ctx.request.body);
};
/**
* rename file
*/
router["POST /file/rename"] = async function (ctx: Context) {
await FileService.rename(ctx.request.body.source, ctx.request.body.target);
ctx.body = "";
};
export default router;

View File

@ -4,7 +4,7 @@ import path from 'path';
let pattern = new RegExp(/s(eason)?(\d+)/);
let eNumPatternArr = [new RegExp(/e[p]?(\d+)/), new RegExp(/[\(\[](\d+)[\)\]]/), new RegExp(/[\.-](\d+)/), new RegExp(/(\d+)/)];
let eNumPatternArr = [new RegExp(/ep?(\d+)/), new RegExp(/[\(\[](\d+)[\)\]]/), new RegExp(/[\.-](\d+)/), new RegExp(/(\d+)/)];
let resolutionPattern = new RegExp(/(\d{3,}[pP])/);
let resolutionArr = ['1k', '1K', '2k', '2K', '4k', '4K', '8k', '8K'];
let charSet = new Set([' ', '[', '.', '(', '']);
@ -57,7 +57,9 @@ export default class InsertRule implements RuleInterface {
getStr += char;
}
} else if (this.type === 'eNum') {
let lowName = file.originName.toLocaleLowerCase().replace(/ /g, '');
let lowName = file.originName.toLocaleLowerCase().replace(/ /g, '')
.replace(/\d+[a-z]/g, '')//去除4k,1080p等
.replace(/[xh]\d+/g, '')//去除x264,h264等 ;
for (let i in eNumPatternArr) {
let patternRes = lowName.match(eNumPatternArr[i]);
if (patternRes && patternRes.length > 1) {

View File

@ -13,6 +13,7 @@ import { updateQbInfo } from './util/QbApiUtil';
console.log(config);
const app = new koa();
let router = new Router({

View File

@ -6,13 +6,14 @@ import ProcessHelper from '../util/ProcesHelper';
import FileObj from '../entity/vo/FileObj';
import SavePathDao from '../dao/SavePathDao';
import SavePath from '../entity/po/SavePath';
import ErrorHelper from "../util/ErrorHelper";
let numberSet = new Set(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]);
class FileService {
static async readPath(pathStr: string, showHidden: boolean): Promise<Array<FileObj>> {
pathStr = decodeURIComponent(pathStr);
let fileList = new Array();
let fileList = [];
if (pathStr.trim().length == 0) {
//获取根目录路径
if (config.isWindows) {
@ -33,8 +34,8 @@ class FileService {
}
fileList = await fs.readdir(pathStr);
}
let folderList: Array<FileObj> = new Array();
let files: Array<FileObj> = new Array();
let folderList: Array<FileObj> = [];
let files: Array<FileObj> = [];
for (let index in fileList) {
try {
let stat = await fs.stat(path.join(pathStr, fileList[index]));
@ -57,6 +58,37 @@ class FileService {
return folderList;
}
/**
*
*/
static async readRecursion(folders: Array<FileObj>): Promise<Array<FileObj>> {
let res = [];
await this.readDirRecursion(res, folders, 1);
return res;
}
private static async readDirRecursion(res: Array<FileObj>, folders: Array<FileObj>, depth: number): Promise<void> {
if (depth > 10) {
throw ErrorHelper.Error400("递归读取超过10层,强制结束");
}
if (folders == null || folders.length == 0) {
return;
}
for (let i in folders) {
let file = folders[i];
if (!file.isFolder) {
res.push(file);
} else {
let filePath = path.join(file.path, file.name);
let temp = (await fs.readdir(filePath)).map(item => {
let stat = fs.statSync(path.join(filePath, item));
return new FileObj(item, filePath, stat.isDirectory(), stat.birthtime.getTime(), stat.mtime.getTime());
});
await FileService.readDirRecursion(res, temp, depth + 1);
}
}
}
static async checkExist(pathStr: string) {
return await fs.pathExists(pathStr);
}
@ -136,6 +168,28 @@ class FileService {
}
return res;
}
/**
* delete batch
* @param files files
*/
static async deleteBatch(files: Array<FileObj>): Promise<void> {
if (files == null || files.length == 0) {
return;
}
for (let i in files) {
await fs.remove(path.join(files[i].path, files[i].name));
}
}
/**
* rename file from source to target
* @param source sourceFile
* @param target targetFile
*/
static async rename(source: FileObj, target: FileObj): Promise<void> {
await fs.rename(path.join(source.path, source.name), path.join(target.path, target.name));
}
}
export default FileService;

View File

@ -3,7 +3,7 @@
<el-menu :default-active="activeIndex" mode="horizontal" background-color="#545c64" text-color="#fff"
active-text-color="#ffd04b" router>
<el-menu-item index="/">重命名</el-menu-item>
<el-menu-item index="/auto">自动化</el-menu-item>
<!-- <el-menu-item index="/auto">自动化</el-menu-item>-->
<!-- <el-sub-menu index="/download">
<template #title>bt下载</template>
<el-menu-item index="/download/center">下载中心</el-menu-item>
@ -20,6 +20,7 @@
<script>
import httpUtil from "./utils/HttpUtil";
export default {
name: "Home",
data() {

View File

@ -12,7 +12,7 @@
<div>
<el-input style="display: inline-block; width: 150px" type="text" size="small" placeholder="关键词过滤"
v-model="filterText" clearable/>
<template v-if="type == 'file'">
<template v-if="type === 'file'">
<el-button type="primary" @click="selectAll(true)" size="small">全选</el-button>
<el-button type="primary" @click="selectAll(false)" size="small">全不选</el-button>
<el-button type="primary" @click="refresh" size="small">刷新</el-button>
@ -21,9 +21,10 @@
</template>
</div>
<div v-for="(item, index) in filterFileList" :key="index">
<span class="folder" v-if="item.isFolder" @click="fileClick(item)">{{ item.name }}</span>
<el-checkbox style="height: 1.4em" v-model="item.checked" v-else-if="type == 'file'">{{ item.name
}}</el-checkbox>
<el-checkbox style="height: 1.4em" v-model="item.checked" :disabled="type==='folder' && !item.isFolder">
<a v-if="item.isFolder" @click="fileClick(item)" style="color: #289fff">{{ item.name }}</a>
<span v-else>{{ item.name }}</span>
</el-checkbox>
</div>
</div>
@ -41,8 +42,10 @@
<script>
import HttpUtil from "../utils/HttpUtil";
import Bus from "../utils/Bus";
export default {
name: "FileChose",
//type:folder:file:
props: ["curChoosePath", "type"],
data() {
return {
@ -63,7 +66,7 @@ export default {
},
curSavePathId() {
let curPath = JSON.stringify(this.pathList);
let targetList = this.savePathList.filter((item) => item.content == curPath);
let targetList = this.savePathList.filter((item) => item.content === curPath);
return targetList.length > 0 ? targetList[0].id : null;
},
},
@ -125,7 +128,7 @@ export default {
createPath(index) {
console.log("当前路径为:", this.pathList);
let path;
if (index == -1) {
if (index === -1) {
path = "";
this.pathList = [];
} else {
@ -137,19 +140,20 @@ export default {
return path;
},
//
submit () {
if (this.type === 'file') {
let chosedFiles = this.fileList.filter((item) => item.checked);
if (chosedFiles.length == 0) {
async submit() {
let chosenFiles = this.fileList.filter((item) => item.checked);
if (chosenFiles.length === 0) {
this.$message({message: "未选择文件", type: "warning"});
return;
}
this.$emit("addData", JSON.parse(JSON.stringify(chosedFiles)));
if (this.type === 'file') {
let body = await HttpUtil.post("/file/recursionQuery", null, chosenFiles);
this.$emit("addData", JSON.parse(JSON.stringify(body)));
this.fileList.forEach((item) => (item.checked = false));
this.fileList = [...this.fileList];
} else if (this.type === 'folder') {
//
this.$emit("folderChose", this.createPath(this.pathList.length - 1));
this.$emit("folderChose", JSON.parse(JSON.stringify(chosenFiles)));
}
this.filterText = "";

View File

@ -31,9 +31,11 @@ async function request (url, method, params, body, isForm) {
} catch (err) {
console.log(Object.keys(err));
console.log(err.response);
if (err.response.status == 401) {
if (err.response.status === 401) {
window.vueInstance.config.globalProperties.$message.error('密钥验证错误');
router.push("/public/login");
} else if (err.response.status === 400) {
window.vueInstance.config.globalProperties.$message.error(err.response.data);
} else {
window.vueInstance.config.globalProperties.$message.error('发生了某些异常问题');
}

View File

@ -13,13 +13,28 @@
<!-- 文件预览列表 -->
<div class="fileList">
<div>
<el-button type="primary" @click="showFileAdd" size="small">新增</el-button>
收藏路径:<el-tag v-for="item in savePathList" :round="true" class="savePath" closable :key="item.id"
@click="clickSavePath(item)" @close="deleteSavePath(item)" text>{{ item.name }}</el-tag>
<el-tooltip effect="dark" content="添加需要重命名的文件" placement="top">
<el-button type="primary" @click="showFileAdd" size="small">添加</el-button>
</el-tooltip>
收藏路径:
<el-tag v-for="item in savePathList" :round="true" class="savePath" closable :key="item.id"
@click="clickSavePath(item)" @close="deleteSavePath(item)" text>{{ item.name }}
</el-tag>
</div>
<div>
<div style="margin-top: 5px">
<el-button type="primary" size="small" @click="selectAllFiles">反选</el-button>
<el-button type="danger" size="small" @click="deleteCheckedFiles">删除</el-button>
<el-tooltip effect="dark" content="一键选中所有的非视频、字幕文件和小于5MB的视频文件" placement="bottom">
<el-button type="success" size="small" @click="">一键选择</el-button>
</el-tooltip>
<el-tooltip effect="dark" content="移除(非删除)需要重命名的文件" placement="bottom">
<el-button type="warning" size="small" @click="removeCheckedFiles">移除</el-button>
</el-tooltip>
<el-popconfirm width="250" confirm-button-text="确认" cancel-button-text="取消"
title="确认删除勾选的文件(无法恢复)" @confirm="deleteCheckedFiles">
<template #reference>
<el-button type="danger" size="small">删除</el-button>
</template>
</el-popconfirm>
<el-button type="primary" size="small" @click="moveIndex('top')">
<el-tooltip effect="dark" content="上移规则" placement="top">
<el-icon>
@ -28,9 +43,18 @@
</el-tooltip>
</el-button>
<el-button type="primary" size="small" @click="moveIndex('bottom')">
<el-tooltip effect="dark" content="下移规则" placement="top"><el-icon>
<el-tooltip effect="dark" content="下移规则" placement="top">
<el-icon>
<bottom/>
</el-icon></el-tooltip>
</el-icon>
</el-tooltip>
</el-button>
<el-button type="primary" size="small" @click="editFile">
<el-tooltip effect="dark" content="修改文件名" placement="top">
<el-icon>
<Edit/>
</el-icon>
</el-tooltip>
</el-button>
</div>
<div class="fileBlock">
@ -55,26 +79,30 @@
<file-chose ref="fileChose" type="file" :curChoosePath="curChoosePath" @addData="addData"
@refreshSavePathList="refreshSavePathList"/>
</el-dialog>
<el-dialog title="编辑名称" v-model="showNameEditDialog" width="50%">
<el-input type="text" v-model="newName"/>
<div>
<el-button type="primary" @click="doEditFile">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
// @ is an alias to /src
import { Top, Bottom } from "@element-plus/icons-vue";
import {Top, Bottom, Edit} from "@element-plus/icons-vue";
import HttpUtil from "../../utils/HttpUtil";
import FileChose from "@/components/FileChose";
import RuleBlock from "@/components/rules/RuleBlock.vue";
import Bus from "../../utils/Bus";
import Tips from '@/components/Tips';
let numberSet = new Set(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]);
export default {
name: "Home",
components: {
FileChose,
RuleBlock,
Top,
Bottom,
FileChose, RuleBlock, Top, Bottom, Tips, Edit
},
data() {
return {
@ -83,11 +111,13 @@ export default {
ruleList: [], //
fileList: [], //
changedFileList: [], //
needPreview: false, //
applicationRule: null, //
savePathList: [], //
curChoosePath: null, //
timer: null, //
newName: "", //
currentEditFile: null,
showNameEditDialog: false //
};
},
computed: {},
@ -99,16 +129,16 @@ export default {
methods: {
//
async addData(data) {
let existSet = new Set();
data.forEach((item) => (item.checked = false));
this.fileList.push(...data);
this.fileList.forEach(item => existSet.add(item.path + item.name));
this.fileList.push(...data.filter(item => !existSet.has(item.path + item.name)));
this.fileList = [...this.fileList.sort((a, b) => compareStr(a.name, b.name))];
this.dialogVisible = false;
this.needPreview = true;
await this.showResult();
},
async ruleUpdate(rules) {
this.ruleList = rules;
this.needPreview = true;
await this.showResult();
},
//
@ -124,7 +154,6 @@ export default {
};
this.changedFileList = await HttpUtil.post("/renamer/preview", null, body);
this.fileList = [...this.fileList];
this.needPreview = false;
this.loading = false;
},
//
@ -148,10 +177,42 @@ export default {
this.loading = false;
}
},
//
async deleteCheckedFiles () {
//
async removeCheckedFiles() {
this.fileList = this.fileList.filter((item) => !item.checked);
this.needPreview = true;
await this.showResult();
},
//delete checked item
async deleteCheckedFiles() {
let body = this.fileList.filter((item) => item.checked);
await HttpUtil.post("/file/deleteBatch", null, body);
this.fileList = this.fileList.filter((item) => !item.checked);
await this.showResult();
},
//edit file
async editFile() {
let list = this.fileList.filter((item) => item.checked);
if (list.length === 0 || list.length > 1) {
this.$message({message: "只能选择一个进行编辑", type: "warning"});
return;
}
this.newName = list[0].name;
this.currentEditFile = list[0];
this.showNameEditDialog = true;
await this.showResult();
},
async doEditFile() {
if (!this.newName) {
this.$message({message: "文件名不能为空", type: "warning"});
return;
}
let target = JSON.parse(JSON.stringify(this.currentEditFile));
target.name = this.newName;
await HttpUtil.post("/file/rename", null, {source: this.currentEditFile, target});
this.currentEditFile.name = this.newName;
this.fileList = [...this.fileList];
this.currentEditFile = null;
this.showNameEditDialog = false;
await this.showResult();
},
//
@ -160,11 +221,11 @@ export default {
},
//
checkRuleAndFile() {
if (this.fileList.length == 0) {
if (this.fileList.length === 0) {
this.$message({message: "请选择文件", type: "warning"});
return false;
}
if (this.ruleList.filter((item) => !item.blocked).length == 0) {
if (this.ruleList.filter((item) => !item.blocked).length === 0) {
this.$message({message: "无生效规则", type: "warning"});
return false;
}
@ -172,8 +233,8 @@ export default {
},
//
async moveIndex(type) {
let temps = this.fileList.filter((item) => item.checked == true);
if (temps.length == 0) {
let temps = this.fileList.filter((item) => item.checked === true);
if (temps.length === 0) {
this.$message({type: "warning", message: "未选中文件,无法移动"});
return;
}
@ -197,7 +258,6 @@ export default {
this.fileList[newIndex] = temp;
}
this.fileList = [...this.fileList];
this.needPreview = true;
if (this.timer != null) {
clearTimeout(this.timer);
}
@ -236,7 +296,7 @@ function compareStr (a, b) {
for (let i = 0; i < an;) {
let charA = readChar(a, i, an);
let charB = readChar(b, i, bn);
if (charB.length == 0) {
if (charB.length === 0) {
return 1;
}
if (charA !== charB) {