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

@ -1,25 +1,32 @@
import { Context } from "koa";
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["POST /file/recursionQuery"] = async function (ctx: Context) {
ctx.body = await FileService.readRecursion(ctx.request.body);
};
/**
*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);
@ -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,86 +4,88 @@ 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([' ', '[', '.', '(', '']);
export default class InsertRule implements RuleInterface {
/**
* seasonname/
*/
type: string;
/**
*
*/
frontAdd: string;
/**
*
*/
endAdd: string;
eNumWidth: number;
/**
* seasonname/
*/
type: string;
/**
*
*/
frontAdd: string;
/**
*
*/
endAdd: string;
eNumWidth: number;
constructor(data: any) {
this.type = data.type;
this.frontAdd = data.frontAdd;
this.endAdd = data.endAdd;
}
constructor(data: any) {
this.type = data.type;
this.frontAdd = data.frontAdd;
this.endAdd = data.endAdd;
}
deal(file: FileObj): void {
//识别到的内容
let getStr = null;
let patternRes = path.basename(file.path).replace(/[ ]+/, "").toLocaleLowerCase().match(pattern);
if (this.type === 'season') {
if (patternRes && patternRes[2]) {
getStr = patternRes[2];
}
} else if (this.type === 'name') {
let originName = null;
if (patternRes && patternRes[2]) {
//说明是剧集,取父文件夹的父文件夹名称
originName = path.basename(path.resolve(file.path, '..'));
} else {
//说明是电影
originName = path.basename(file.path);
}
getStr = '';
for (let i = 0; i < originName.length; i++) {
let char = originName.charAt(i);
if (charSet.has(char)) {
break;
}
getStr += char;
}
} else if (this.type === 'eNum') {
let lowName = file.originName.toLocaleLowerCase().replace(/ /g, '');
for (let i in eNumPatternArr) {
let patternRes = lowName.match(eNumPatternArr[i]);
if (patternRes && patternRes.length > 1) {
getStr = patternRes[1];
for (let i = 0; i < this.eNumWidth - getStr.length; i++) {
getStr = '0' + getStr;
}
break;
}
}
} else if (this.type === 'resolution') {
let res = file.originName.match(resolutionPattern);
if (res && res.length > 1) {
getStr = res[1];
} else {
for (let i = 0; i < resolutionArr.length; i++) {
if (file.originName.indexOf(resolutionArr[i]) > -1) {
getStr = resolutionArr[i];
break;
}
}
}
}
if (getStr && getStr.length > 0) {
file.realName = file.realName + this.frontAdd + getStr + this.endAdd;
file.name = file.realName + file.expandName;
}
}
deal(file: FileObj): void {
//识别到的内容
let getStr = null;
let patternRes = path.basename(file.path).replace(/[ ]+/, "").toLocaleLowerCase().match(pattern);
if (this.type === 'season') {
if (patternRes && patternRes[2]) {
getStr = patternRes[2];
}
} else if (this.type === 'name') {
let originName = null;
if (patternRes && patternRes[2]) {
//说明是剧集,取父文件夹的父文件夹名称
originName = path.basename(path.resolve(file.path, '..'));
} else {
//说明是电影
originName = path.basename(file.path);
}
getStr = '';
for (let i = 0; i < originName.length; i++) {
let char = originName.charAt(i);
if (charSet.has(char)) {
break;
}
getStr += char;
}
} else if (this.type === 'eNum') {
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) {
getStr = patternRes[1];
for (let i = 0; i < this.eNumWidth - getStr.length; i++) {
getStr = '0' + getStr;
}
break;
}
}
} else if (this.type === 'resolution') {
let res = file.originName.match(resolutionPattern);
if (res && res.length > 1) {
getStr = res[1];
} else {
for (let i = 0; i < resolutionArr.length; i++) {
if (file.originName.indexOf(resolutionArr[i]) > -1) {
getStr = resolutionArr[i];
break;
}
}
}
}
if (getStr && getStr.length > 0) {
file.realName = file.realName + this.frontAdd + getStr + this.endAdd;
file.name = file.realName + file.expandName;
}
}
}

View File

@ -9,14 +9,15 @@ import handleError from "./middleware/handleError";
import init from "./middleware/init";
import SqliteUtil from './util/SqliteHelper';
import log from './util/LogUtil';
import { updateQbInfo } from './util/QbApiUtil';
import {updateQbInfo} from './util/QbApiUtil';
console.log(config);
const app = new koa();
let router = new Router({
prefix: config.urlPrefix
prefix: config.urlPrefix
});
app.use(require('koa-static')(path.join(config.rootPath, 'static')));
@ -30,12 +31,12 @@ app.use(handleError);
app.use(RouterMW(router, path.join(config.rootPath, "dist/api")));
(async () => {
await SqliteUtil.createPool();
await updateQbInfo(null, null);
app.listen(config.port);
log.info(`server listened `, config.port);
await SqliteUtil.createPool();
await updateQbInfo(null, null);
app.listen(config.port);
log.info(`server listened `, config.port);
})();
app.on("error", (error) => {
console.error(error);
console.error(error);
})

View File

@ -6,136 +6,190 @@ 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();
if (pathStr.trim().length == 0) {
//获取根目录路径
if (config.isWindows) {
//windows下
let std: string = (await ProcessHelper.exec('wmic logicaldisk get caption')).replace('Caption', '');
fileList = std
.split('\r\n')
.filter((item) => item.trim().length > 0)
.map((item) => item.trim());
} else {
//linux下
pathStr = '/';
fileList = await fs.readdir(pathStr);
}
} else {
if (!(fs.pathExists(pathStr))) {
throw new Error("路径不存在");
}
fileList = await fs.readdir(pathStr);
}
let folderList: Array<FileObj> = new Array();
let files: Array<FileObj> = new Array();
for (let index in fileList) {
try {
let stat = await fs.stat(path.join(pathStr, fileList[index]));
if (fileList[index].startsWith('.')) {
if (showHidden) {
(stat.isDirectory() ? folderList : files).push(
new FileObj(fileList[index], pathStr, stat.isDirectory(), stat.birthtime.getTime(), stat.mtime.getTime()),
);
}
} else {
(stat.isDirectory() ? folderList : files).push(
new FileObj(fileList[index], pathStr, stat.isDirectory(), stat.birthtime.getTime(), stat.mtime.getTime()),
);
}
} catch (e) {
console.error(e);
}
}
folderList.sort((a, b) => FileService.compareStr(a.name, b.name)).push(...files.sort((a, b) => FileService.compareStr(a.name, b.name)));
return folderList;
}
static async readPath(pathStr: string, showHidden: boolean): Promise<Array<FileObj>> {
pathStr = decodeURIComponent(pathStr);
let fileList = [];
if (pathStr.trim().length == 0) {
//获取根目录路径
if (config.isWindows) {
//windows下
let std: string = (await ProcessHelper.exec('wmic logicaldisk get caption')).replace('Caption', '');
fileList = std
.split('\r\n')
.filter((item) => item.trim().length > 0)
.map((item) => item.trim());
} else {
//linux下
pathStr = '/';
fileList = await fs.readdir(pathStr);
}
} else {
if (!(fs.pathExists(pathStr))) {
throw new Error("路径不存在");
}
fileList = await fs.readdir(pathStr);
}
let folderList: Array<FileObj> = [];
let files: Array<FileObj> = [];
for (let index in fileList) {
try {
let stat = await fs.stat(path.join(pathStr, fileList[index]));
if (fileList[index].startsWith('.')) {
if (showHidden) {
(stat.isDirectory() ? folderList : files).push(
new FileObj(fileList[index], pathStr, stat.isDirectory(), stat.birthtime.getTime(), stat.mtime.getTime()),
);
}
} else {
(stat.isDirectory() ? folderList : files).push(
new FileObj(fileList[index], pathStr, stat.isDirectory(), stat.birthtime.getTime(), stat.mtime.getTime()),
);
}
} catch (e) {
console.error(e);
}
}
folderList.sort((a, b) => FileService.compareStr(a.name, b.name)).push(...files.sort((a, b) => FileService.compareStr(a.name, b.name)));
return folderList;
}
static async checkExist(pathStr: string) {
return await fs.pathExists(pathStr);
}
/**
*
*/
static async readRecursion(folders: Array<FileObj>): Promise<Array<FileObj>> {
let res = [];
await this.readDirRecursion(res, folders, 1);
return res;
}
/**
*
* @param saveObj
* @returns
*/
static async savePath(saveObj: SavePath) {
await SavePathDao.addOne(saveObj);
return saveObj;
}
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);
}
}
}
/**
*
* @returns
*/
static async getSaveList() {
return await SavePathDao.getAll();
}
static async checkExist(pathStr: string) {
return await fs.pathExists(pathStr);
}
/**
*
* @param id
* @returns
*/
static async deleteOne(id) {
return await SavePathDao.delete(id);
}
/**
*
* @param saveObj
* @returns
*/
static async savePath(saveObj: SavePath) {
await SavePathDao.addOne(saveObj);
return saveObj;
}
/**
*
* @param a str
* @param b str
*/
static compareStr(a: string, b: string) {
let an = a.length;
let bn = b.length;
for (let i = 0; i < an;) {
let charA = FileService.readChar(a, i, an);
let charB = FileService.readChar(b, i, bn);
if (charB.length == 0) {
return 1;
}
if (charA !== charB) {
//读取字符串不相等说明可以得到排序结果
//如果都为数字,按照数字的比较方法,否则按照字符串比较
return numberSet.has(charA.charAt(0)) && numberSet.has(charB.charAt(0)) ? Number(charA) - Number(charB) : charA.localeCompare(charB);
}
i += charA.length;
}
//排到最后都没分结果说明相等
return 0;
}
/**
*
* @returns
*/
static async getSaveList() {
return await SavePathDao.getAll();
}
/**
*
* @param a a
* @param n
*/
static readChar(a: string, i: number, n: number) {
let res = "";
for (; i < n; i++) {
let char = a.charAt(i);
if (numberSet.has(char)) {
//如果当前字符是数字,添加到结果中
res += char;
} else {
//如果不为数字但是为第一个字符直接返回否则返回res
if (res.length == 0) {
return char;
} else {
return res;
}
}
}
return res;
}
/**
*
* @param id
* @returns
*/
static async deleteOne(id) {
return await SavePathDao.delete(id);
}
/**
*
* @param a str
* @param b str
*/
static compareStr(a: string, b: string) {
let an = a.length;
let bn = b.length;
for (let i = 0; i < an;) {
let charA = FileService.readChar(a, i, an);
let charB = FileService.readChar(b, i, bn);
if (charB.length == 0) {
return 1;
}
if (charA !== charB) {
//读取字符串不相等说明可以得到排序结果
//如果都为数字,按照数字的比较方法,否则按照字符串比较
return numberSet.has(charA.charAt(0)) && numberSet.has(charB.charAt(0)) ? Number(charA) - Number(charB) : charA.localeCompare(charB);
}
i += charA.length;
}
//排到最后都没分结果说明相等
return 0;
}
/**
*
* @param a a
* @param n
*/
static readChar(a: string, i: number, n: number) {
let res = "";
for (; i < n; i++) {
let char = a.charAt(i);
if (numberSet.has(char)) {
//如果当前字符是数字,添加到结果中
res += char;
} else {
//如果不为数字但是为第一个字符直接返回否则返回res
if (res.length == 0) {
return char;
} else {
return res;
}
}
}
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

@ -1,9 +1,9 @@
<template>
<div class="app">
<el-menu :default-active="activeIndex" mode="horizontal" background-color="#545c64" text-color="#fff"
active-text-color="#ffd04b" router>
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>
@ -11,7 +11,7 @@
</el-sub-menu> -->
</el-menu>
<div class="content">
<router-view />
<router-view/>
</div>
<div class="footer">版本{{ version }}&nbsp;&nbsp;开源地址:<a
href="https://github.com/FleyX/open-renamer">open-renamer</a></div>
@ -20,22 +20,23 @@
<script>
import httpUtil from "./utils/HttpUtil";
export default {
name: "Home",
data () {
data() {
return {
version: "1.2",
activeIndex: location.pathname,
};
},
async created () {
async created() {
let token = localStorage.getItem("token");
window.token = token;
window.isWindows = await httpUtil.get("/file/isWindows");
console.log(this.$route);
console.log(this.activeIndex);
},
async mounted () {
async mounted() {
console.log(this.$route);
console.log(location);
},

View File

@ -11,8 +11,8 @@
<div class="fileList">
<div>
<el-input style="display: inline-block; width: 150px" type="text" size="small" placeholder="关键词过滤"
v-model="filterText" clearable />
<template v-if="type == 'file'">
v-model="filterText" clearable/>
<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>
@ -32,7 +33,7 @@
</div>
<el-dialog title="保存路径" v-model="showSave" width="40em">
<el-input type="text" v-model="saveName" placeholder="输入名称" />
<el-input type="text" v-model="saveName" placeholder="输入名称"/>
<el-button type="primary" @click="savePath" style="padding-top: 1em">提交</el-button>
</el-dialog>
</div>
@ -41,10 +42,12 @@
<script>
import HttpUtil from "../utils/HttpUtil";
import Bus from "../utils/Bus";
export default {
name: "FileChose",
//type:folder:file:
props: ["curChoosePath", "type"],
data () {
data() {
return {
isWindows: false,
fileList: [], //
@ -57,24 +60,24 @@ export default {
};
},
computed: {
filterFileList () {
filterFileList() {
let text = this.filterText.trim();
return text === "" ? this.fileList : this.fileList.filter((item) => item.name.indexOf(text) > -1);
},
curSavePathId () {
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;
},
},
watch: {
async curChoosePath (newVal) {
async curChoosePath(newVal) {
console.log("变更路径:", newVal);
this.pathList = newVal;
await this.breadcrumbClick(this.pathList.length - 1);
},
},
async created () {
async created() {
if (this.curChoosePath && this.curChoosePath.length > 0) {
this.pathList = this.curChoosePath;
}
@ -84,15 +87,15 @@ export default {
},
methods: {
async refresh () {
async refresh() {
await this.breadcrumbClick(this.pathList.length - 1);
},
//
async refreshSavePathList () {
async refreshSavePathList() {
this.savePathList = await HttpUtil.get("/file/path");
},
//
async breadcrumbClick (index) {
async breadcrumbClick(index) {
this.loading = true;
try {
let path = this.createPath(index);
@ -109,7 +112,7 @@ export default {
return false;
},
//
fileClick (item) {
fileClick(item) {
if (item.isFolder) {
this.pathList.push(item.name);
this.breadcrumbClick(this.pathList.length);
@ -118,14 +121,14 @@ export default {
}
},
//
selectAll (status) {
selectAll(status) {
this.filterFileList.filter((item) => !item.isFolder).forEach((item) => (item.checked = status));
},
//index
createPath (index) {
createPath(index) {
console.log("当前路径为:", this.pathList);
let path;
if (index == -1) {
if (index === -1) {
path = "";
this.pathList = [];
} else {
@ -137,34 +140,35 @@ export default {
return path;
},
//
submit () {
async submit() {
let chosenFiles = this.fileList.filter((item) => item.checked);
if (chosenFiles.length === 0) {
this.$message({message: "未选择文件", type: "warning"});
return;
}
if (this.type === 'file') {
let chosedFiles = this.fileList.filter((item) => item.checked);
if (chosedFiles.length == 0) {
this.$message({ message: "未选择文件", type: "warning" });
return;
}
this.$emit("addData", JSON.parse(JSON.stringify(chosedFiles)));
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 = "";
},
//
async savePath () {
await HttpUtil.post("/file/path/save", null, { name: this.saveName, content: JSON.stringify(this.pathList) });
async savePath() {
await HttpUtil.post("/file/path/save", null, {name: this.saveName, content: JSON.stringify(this.pathList)});
Bus.$emit("refreshSavePathList");
this.saveName = "";
this.showSave = false;
this.$message.success("操作成功");
},
//
async cancelSavePath () {
await HttpUtil.delete("/file/path/delete", { id: this.curSavePathId });
async cancelSavePath() {
await HttpUtil.delete("/file/path/delete", {id: this.curSavePathId});
Bus.$emit("refreshSavePathList");
this.$message.success("操作成功");
},

View File

@ -11,35 +11,37 @@ import router from '../router/index';
* @param {*} redirect 接口返回未认证是否跳转到登陆
* @returns 数据
*/
async function request (url, method, params, body, isForm) {
let options = {
url,
baseURL: '/openRenamer/api',
method,
params,
headers: { token: window.token }
};
if (isForm) {
options.headers['Content-Type'] = 'multipart/form-data';
}
if (body) {
options.data = body;
}
let res;
try {
res = await http.default.request(options);
} catch (err) {
console.log(Object.keys(err));
console.log(err.response);
if (err.response.status == 401) {
window.vueInstance.config.globalProperties.$message.error('密钥验证错误');
router.push("/public/login");
} else {
window.vueInstance.config.globalProperties.$message.error('发生了某些异常问题');
async function request(url, method, params, body, isForm) {
let options = {
url,
baseURL: '/openRenamer/api',
method,
params,
headers: {token: window.token}
};
if (isForm) {
options.headers['Content-Type'] = 'multipart/form-data';
}
throw err;
}
return res.data;
if (body) {
options.data = body;
}
let res;
try {
res = await http.default.request(options);
} catch (err) {
console.log(Object.keys(err));
console.log(err.response);
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('发生了某些异常问题');
}
throw err;
}
return res.data;
}
/**
@ -48,8 +50,8 @@ async function request (url, method, params, body, isForm) {
* @param {*} params url参数
* @param {*} redirect 未登陆是否跳转到登陆页
*/
async function get (url, params = null) {
return request(url, 'get', params, null, false);
async function get(url, params = null) {
return request(url, 'get', params, null, false);
}
/**
@ -60,8 +62,8 @@ async function get (url, params = null) {
* @param {*} isForm 是否表单数据
* @param {*} redirect 是否重定向
*/
async function post (url, params, body, isForm = false) {
return request(url, 'post', params, body, isForm);
async function post(url, params, body, isForm = false) {
return request(url, 'post', params, body, isForm);
}
/**
@ -72,8 +74,8 @@ async function post (url, params, body, isForm = false) {
* @param {*} isForm 是否表单数据
* @param {*} redirect 是否重定向
*/
async function put (url, params, body, isForm = false) {
return request(url, 'put', params, body, isForm);
async function put(url, params, body, isForm = false) {
return request(url, 'put', params, body, isForm);
}
/**
@ -82,13 +84,13 @@ async function put (url, params, body, isForm = false) {
* @param {*} params url参数
* @param {*} redirect 是否重定向
*/
async function deletes (url, params = null) {
return request(url, 'delete', params, null);
async function deletes(url, params = null) {
return request(url, 'delete', params, null);
}
export default {
get,
post,
put,
delete: deletes,
get,
post,
put,
delete: deletes,
};

View File

@ -1,36 +1,60 @@
<template>
<div v-loading="loading" element-loading-text="后台处理中,请稍候">
<br />
<br/>
<el-button type="success" @click="submit" size="default">开始重命名</el-button>
<el-divider content-position="left">
<div class="head-text">规则设置</div>
</el-divider>
<!-- 规则列表 -->
<rule-block @ruleUpdate="ruleUpdate" />
<rule-block @ruleUpdate="ruleUpdate"/>
<el-divider content-position="left">
<div class="head-text">文件预览</div>
</el-divider>
<!-- 文件预览列表 -->
<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>
<top />
<top/>
</el-icon>
</el-tooltip>
</el-button>
<el-button type="primary" size="small" @click="moveIndex('bottom')">
<el-tooltip effect="dark" content="下移规则" placement="top"><el-icon>
<bottom />
</el-icon></el-tooltip>
<el-tooltip effect="dark" content="下移规则" placement="top">
<el-icon>
<bottom/>
</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">
@ -53,66 +77,72 @@
<el-dialog title="新增文件" v-model="dialogVisible" width="70%">
<file-chose ref="fileChose" type="file" :curChoosePath="curChoosePath" @addData="addData"
@refreshSavePathList="refreshSavePathList" />
@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 () {
data() {
return {
loading: false, //
dialogVisible: false, //
ruleList: [], //
fileList: [], //
changedFileList: [], //
needPreview: false, //
applicationRule: null, //
savePathList: [], //
curChoosePath: null, //
timer: null, //
newName: "", //
currentEditFile: null,
showNameEditDialog: false //
};
},
computed: {},
async created () {
async created() {
this.savePathList = await HttpUtil.get("/file/path");
window.isWindows = await HttpUtil.get("/file/isWindows");
Bus.$on("refreshSavePathList", this.refreshSavePathList);
},
methods: {
//
async addData (data) {
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) {
async ruleUpdate(rules) {
this.ruleList = rules;
this.needPreview = true;
await this.showResult();
},
//
async showResult () {
async showResult() {
this.changedFileList = [];
if (!this.checkRuleAndFile()) {
return;
@ -124,16 +154,15 @@ export default {
};
this.changedFileList = await HttpUtil.post("/renamer/preview", null, body);
this.fileList = [...this.fileList];
this.needPreview = false;
this.loading = false;
},
//
async submit () {
async submit() {
if (!this.checkRuleAndFile()) {
return;
}
if (this.changedFileList.filter((item) => item.errorMessage).length > 0) {
this.$message({ message: "存在错误,无法执行操作", type: "error" });
this.$message({message: "存在错误,无法执行操作", type: "error"});
return;
}
this.loading = true;
@ -143,48 +172,80 @@ export default {
};
try {
await HttpUtil.post("/renamer/submit", null, body);
this.$message({ message: "重命名成功", type: "success" });
this.$message({message: "重命名成功", type: "success"});
} finally {
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();
},
//
selectAllFiles () {
selectAllFiles() {
this.fileList.forEach((item) => (item.checked = !item.checked));
},
//
checkRuleAndFile () {
if (this.fileList.length == 0) {
this.$message({ message: "请选择文件", type: "warning" });
checkRuleAndFile() {
if (this.fileList.length === 0) {
this.$message({message: "请选择文件", type: "warning"});
return false;
}
if (this.ruleList.filter((item) => !item.blocked).length == 0) {
this.$message({ message: "无生效规则", type: "warning" });
if (this.ruleList.filter((item) => !item.blocked).length === 0) {
this.$message({message: "无生效规则", type: "warning"});
return false;
}
return true;
},
//
async moveIndex (type) {
let temps = this.fileList.filter((item) => item.checked == true);
if (temps.length == 0) {
this.$message({ type: "warning", message: "未选中文件,无法移动" });
async moveIndex(type) {
let temps = this.fileList.filter((item) => item.checked === true);
if (temps.length === 0) {
this.$message({type: "warning", message: "未选中文件,无法移动"});
return;
}
if (type == "top") {
if (this.fileList.indexOf(temps[0]) == 0) {
this.$message({ type: "warning", message: "无法上移" });
this.$message({type: "warning", message: "无法上移"});
return;
}
} else {
if (this.fileList.indexOf(temps[temps.length - 1]) == this.fileList.length - 1) {
this.$message({ type: "warning", message: "无法下移" });
this.$message({type: "warning", message: "无法下移"});
return;
}
temps = temps.reverse();
@ -197,7 +258,6 @@ export default {
this.fileList[newIndex] = temp;
}
this.fileList = [...this.fileList];
this.needPreview = true;
if (this.timer != null) {
clearTimeout(this.timer);
}
@ -206,20 +266,20 @@ export default {
this.timer = null;
}, 1000);
},
showFileAdd () {
showFileAdd() {
this.dialogVisible = true;
},
//
async clickSavePath (item) {
async clickSavePath(item) {
this.curChoosePath = JSON.parse(item.content);
this.dialogVisible = true;
},
async deleteSavePath (item) {
async deleteSavePath(item) {
console.log(item);
await HttpUtil.delete("/file/path/delete", { id: item.id });
await HttpUtil.delete("/file/path/delete", {id: item.id});
Bus.$emit("refreshSavePathList");
},
async refreshSavePathList () {
async refreshSavePathList() {
this.savePathList = await HttpUtil.get("/file/path");
},
},
@ -230,13 +290,13 @@ export default {
* @param a str
* @param b str
*/
function compareStr (a, b) {
function compareStr(a, b) {
let an = a.length;
let bn = b.length;
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) {
@ -255,7 +315,7 @@ function compareStr (a, b) {
* @param a a
* @param n 数字长度
*/
function readChar (a, i, n) {
function readChar(a, i, n) {
let res = "";
for (; i < n; i++) {
let char = a.charAt(i);