Compare commits
115 Commits
Author | SHA1 | Date | |
---|---|---|---|
51c6ec9986 | |||
|
a0e0186c63 | ||
|
652105f534 | ||
|
bbc4bfd52f | ||
|
f85890bda8 | ||
67f542fe01 | |||
|
95a382835a | ||
922a74be41 | |||
|
1ebd7acf5a | ||
6444d9a612 | |||
|
6759333fb4 | ||
|
62d9296b46 | ||
|
711423dbe1 | ||
|
f3f31e5b5c | ||
|
4c8c1a355c | ||
|
f3bcacfc4a | ||
2e50b9653a | |||
|
861d5f35bf | ||
|
55af6bd54e | ||
|
8c6416ec87 | ||
|
f04d3bf636 | ||
|
0b87ecae94 | ||
|
9c001c5403 | ||
|
4b8a7829d9 | ||
|
164553abf0 | ||
|
cabe83d07d | ||
|
69dcbd22b0 | ||
|
4002db4c22 | ||
|
851234ab76 | ||
|
1876e98a0e | ||
|
7ab3a49323 | ||
|
3f8b659462 | ||
|
f25e5d7bc7 | ||
|
ea859cc6c3 | ||
|
3f0d519a99 | ||
|
b0f1901731 | ||
|
faedc09771 | ||
|
f502348786 | ||
|
ae9a2d53c5 | ||
|
6c8184fb1c | ||
|
7d6006d1fd | ||
|
94a90ba359 | ||
|
ea0d68036b | ||
|
85f614f414 | ||
|
dd6def02ac | ||
|
0b7f4a155c | ||
|
9a55460f11 | ||
|
11f28f12fa | ||
|
1d4e61f9ef | ||
|
4e9dc0b7c5 | ||
|
388765a357 | ||
|
90c2f68371 | ||
|
4eecb4f832 | ||
|
45939d62a2 | ||
|
9b1f3f725d | ||
|
f90002b969 | ||
|
e848ecc236 | ||
|
7ae783caa5 | ||
|
3baa033f18 | ||
|
535eaf6389 | ||
|
04569a3adf | ||
|
d2d8feccd7 | ||
|
d52398f14c | ||
|
2cf41a9c18 | ||
|
6d7ace6a94 | ||
|
8d78127e4d | ||
|
e247a9fd2a | ||
|
158f0d6275 | ||
|
40b363c3e5 | ||
|
f9a546cb97 | ||
|
977da05608 | ||
|
5251b5f0b5 | ||
|
ecca3a4ee6 | ||
|
1e34dd7620 | ||
|
064254f3af | ||
|
7691a8f66f | ||
|
516d1a4f37 | ||
|
f4d3482dd1 | ||
|
9bfd83434a | ||
|
b3df96855f | ||
|
7dfd433b11 | ||
|
243a09dbac | ||
|
671dc21393 | ||
|
b0fe955d1d | ||
|
1f37019715 | ||
|
f83bb9902e | ||
|
d645e6f05a | ||
|
4e18f01ae3 | ||
|
59c34af8b4 | ||
|
7b68f62dcd | ||
|
61c47da4b5 | ||
|
752193e419 | ||
|
e866f5488e | ||
|
65bdd8c2fc | ||
|
197c1b8342 | ||
|
96b4cb7989 | ||
|
e32fee491f | ||
|
ed218a7b80 | ||
|
7d26e2fcd4 | ||
|
6e9644794b | ||
|
37efadd7bc | ||
|
1ae8b905ff | ||
|
5b01d40f7a | ||
|
ffb214064b | ||
|
81c66c6b5b | ||
|
25aefe2aaa | ||
|
bb49dd0b09 | ||
|
cfdb36ca31 | ||
|
815e7a9870 | ||
|
bbaae44e0b | ||
|
28ea6eaa7f | ||
|
ca6dba024f | ||
|
ccd65d11ba | ||
|
fe70be3206 | ||
|
9d8a6dcbfe |
2
.gitignore
vendored
2
.gitignore
vendored
@ -108,3 +108,5 @@ data
|
||||
/.idea/modules.xml
|
||||
/.idea/open-renamer.iml
|
||||
/.idea/vcs.xml
|
||||
|
||||
package-lock.json
|
@ -1,7 +1,9 @@
|
||||
FROM node:lts-buster-slim
|
||||
WORKDIR /app
|
||||
COPY ./openRenamerBackend /app
|
||||
RUN chmod 777 -R /app
|
||||
# RUN chmod 777 -R /app && npm install -g pnpm typescript --registry https://registry.npmmirror.com
|
||||
# 注意此处未添加npm代理
|
||||
RUN chmod 777 -R /app && npm install -g pnpm typescript
|
||||
ENV PORT 80
|
||||
CMD ["bash", "start.sh"]
|
||||
|
||||
|
52
README.md
52
README.md
@ -1,52 +1,12 @@
|
||||
# open-renamer
|
||||
|
||||
![](https://qiniupic.fleyx.com/blog/202204071632882.png)
|
||||
![预览图](https://s3.fleyx.com/picbed/2022/11/18386180128d01eb1a59b8eacf652895.png)
|
||||
|
||||
renamer 的开源实现版本,BS 应用,支持全平台部署使用
|
||||
已打包镜像到 dockerhub 中:[hub.docker.com](https://hub.docker.com/r/fleyx/open-renamer)
|
||||
renamer 的开源实现版本,BS 应用,支持 arm/x86 部署使用,两种使用方式:
|
||||
|
||||
已实现如下三种处理规则:
|
||||
1. 部署容器到 nas
|
||||
已打包到 dockerhub 中:[hub.docker.com/r/fleyx/open-renamer](https://hub.docker.com/r/fleyx/open-renamer)
|
||||
|
||||
- 插入(支持季号识别)
|
||||
- 删除
|
||||
- 序列化
|
||||
2. 下载桌面应用使用,目前仅支持 windows,后续计划支持 mac,linux[下载地址](https://github.com/FleyX/open-renamer/releases/latest)
|
||||
|
||||
特点:
|
||||
|
||||
- 不限制规则数量
|
||||
- 支持将规则保存为模板,方便下次使用
|
||||
- 全平台(arm,x86)支持,可直接部署在 nas 中,通过浏览器访问
|
||||
|
||||
## 运行方式:
|
||||
|
||||
推荐通过 docker 部署到 nas 中,即可管理 nas 媒体文件
|
||||
|
||||
- docker 命令运行:
|
||||
|
||||
```bash
|
||||
# 管理/mnt/vdisk目录中的文件,通过8089端口访问服务
|
||||
docker run -itd --name openRenamer -v /mnt/vdisk:/data -p 8089:8089 -e PORT="8089" -e TOKEN="123456" fleyx/open-renamer
|
||||
```
|
||||
|
||||
- docker-compose 运行:
|
||||
|
||||
```yaml
|
||||
version: "3.6"
|
||||
openRenamer:
|
||||
container_name: openRenamer
|
||||
image: fleyx/open-renamer
|
||||
# 当前用户的uid,gid,使用root可不配置此项
|
||||
#user: "1000:1000"
|
||||
environment:
|
||||
# 指定启动端口
|
||||
- PORT=11004
|
||||
# 指定认证token,不设置此项无需认证
|
||||
- TOKEN=123456
|
||||
volumes:
|
||||
# 关键,把想要管理的文件夹映射到容器的data目录中,即可在程序中选择data目录进行重命名操作
|
||||
- /mnt/vdisk:/data
|
||||
# 存储模板数据,可不映射此目录
|
||||
- ./data/openRenamer:/app/data
|
||||
# 使用宿主机网络.即可通过"宿主机ip:11004"访问程序
|
||||
network_mode: host
|
||||
```
|
||||
[点击查看参考文档](https://blog.fleyx.com/blog/detail/20221130)
|
||||
|
8
build.sh
8
build.sh
@ -1,14 +1,18 @@
|
||||
#!/bin/bash
|
||||
base=$(cd "$(dirname "$0")";pwd)
|
||||
cd $base
|
||||
docker run -it --rm --name buildOpenRenamer --user ${UID} -v $base/openRenamerFront:/opt/front node:lts-buster-slim bash -c "cd /opt/front && yarn --registry https://registry.npmmirror.com && yarn build"
|
||||
docker run -it --rm --name buildOpenRenamer -v $base/openRenamerBackend:/app node:lts-buster-slim bash -c "cd /app && yarn config set registry=https://registry.npmmirror.com && yarn global add typescript && tsc"
|
||||
rm -rf openRenamerBackend/dist
|
||||
# 注意此处未添加npm代理
|
||||
# docker run -it --rm --name buildOpenRenamer --user ${UID} -v $base/openRenamerFront:/opt/front node:lts-slim bash -c "cd /opt/front && npm install -g pnpm --registry https://registry.npmmirror.com && pnpm install --registry https://registry.npmmirror.com && pnpm run build"
|
||||
docker run -it --rm --name buildOpenRenamer --user ${UID} -v $base/openRenamerFront:/opt/front node:lts-slim bash -c "cd /opt/front && npm install -g pnpm && pnpm install && pnpm run build"
|
||||
|
||||
rm -rf openRenamerBackend/static/*
|
||||
touch openRenamerBackend/static/.gitkeep
|
||||
mv openRenamerFront/dist/* openRenamerBackend/static
|
||||
rm -rf openRenamerBackend/node_modules
|
||||
|
||||
# 单平台打包并推送
|
||||
#docker build -t fleyx/open-renamer:$0 --push .
|
||||
# 多平台打包并推送
|
||||
docker buildx build -t fleyx/open-renamer:$1 --platform linux/amd64,linux/arm64 --push .
|
||||
docker buildx build -t fleyx/open-renamer:latest --platform linux/amd64,linux/arm64 --push .
|
||||
|
3
config.json
Normal file
3
config.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"version":1.3
|
||||
}
|
6
electron/.gitignore
vendored
Normal file
6
electron/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
dist
|
||||
openRenamerBackend
|
||||
build
|
||||
.idea
|
||||
*.exe
|
1
electron/.npmrc
Normal file
1
electron/.npmrc
Normal file
@ -0,0 +1 @@
|
||||
electron_mirror=https://npmmirror.com/mirrors/electron/
|
12
electron/build.sh
Normal file
12
electron/build.sh
Normal file
@ -0,0 +1,12 @@
|
||||
cd ../openRenamerFront
|
||||
yarn
|
||||
npm run build
|
||||
cd ../openRenamerBackend
|
||||
yarn
|
||||
tsc
|
||||
rm -rf ./static/js
|
||||
rm -rf ./static/css
|
||||
cp -r ../openRenamerFront/dist/* ./static
|
||||
cd ../electron
|
||||
mkdir -p dist
|
||||
npm run build
|
70
electron/index.html
Normal file
70
electron/index.html
Normal file
@ -0,0 +1,70 @@
|
||||
<html lang="">
|
||||
<head>
|
||||
<title>open-renamer</title>
|
||||
</head>
|
||||
<body class="center">
|
||||
<div class="center" style="flex-direction: column">
|
||||
<div class="loading"></div>
|
||||
<div id="text" style="font: 1.2em">loading...</div>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.center {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: -webkit-flex;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: relative;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
animation: satellite 3s infinite linear;
|
||||
border: 1px solid #000;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.loading:before,
|
||||
.loading:after {
|
||||
position: absolute;
|
||||
left: 1px;
|
||||
top: 1px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
content: "";
|
||||
border-radius: 100%;
|
||||
background-color: #000;
|
||||
box-shadow: 0 0 10px #000;
|
||||
}
|
||||
|
||||
.loading:after {
|
||||
right: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 13px;
|
||||
}
|
||||
|
||||
@keyframes satellite {
|
||||
from {
|
||||
transform: rotate(0) translateZ(0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg) translateZ(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
let text = document.getElementById("text");
|
||||
let count = 1;
|
||||
setInterval(() => {
|
||||
if (count > 3) {
|
||||
count = 1;
|
||||
}
|
||||
text.innerText = "Loading" + (count == 3 ? '...' : count == 2 ? '..' : '.');
|
||||
count++;
|
||||
}, 500);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
139
electron/main.js
Normal file
139
electron/main.js
Normal file
@ -0,0 +1,139 @@
|
||||
// main.js
|
||||
// 控制应用生命周期和创建原生浏览器窗口的模组
|
||||
const {app, BrowserWindow, Menu} = require('electron')
|
||||
const path = require('path')
|
||||
const fs = require('fs');
|
||||
const {spawn} = require('child_process');
|
||||
const net = require('net');
|
||||
const log = require('electron-log');
|
||||
|
||||
|
||||
const userHome = process.env.HOME || process.env.USERPROFILE;
|
||||
const dataPath = path.join(userHome, "openRenamer");
|
||||
|
||||
log.transports.file.resolvePathFn = () => path.join(dataPath, 'logs/main.log');
|
||||
|
||||
async function createWindow() {
|
||||
// 隐藏菜单栏
|
||||
Menu.setApplicationMenu(null)
|
||||
// 创建浏览器窗口
|
||||
const win = new BrowserWindow({
|
||||
//width: 800, //窗口宽度,单位像素. 默认是 800
|
||||
//height: 600, //窗口高度,单位像素. 默认是 600
|
||||
icon: './logo.ico', // 设置窗口左上角的图标
|
||||
show: false, //窗口创建的时候是否显示. 默认为 true
|
||||
webPreferences: {
|
||||
nodeIntegration: true, // 是否完整支持node。默认为 true
|
||||
preload: path.join(__dirname, 'preload.js') //界面的其它脚本运行之前预先加载一个指定脚本。
|
||||
}
|
||||
});
|
||||
//打开调试
|
||||
// win.webContents.openDevTools();
|
||||
|
||||
win.loadFile('./index.html');
|
||||
let startTime = Date.now();
|
||||
|
||||
// 下面这两行代码配合上面 new BrowserWindow 里面的 show: false,可以实现打开时窗口最大化
|
||||
win.maximize()
|
||||
win.show()
|
||||
log.info(__dirname);
|
||||
let port = await startBackend()
|
||||
log.info("backend service started")
|
||||
|
||||
let diff = Date.now() - startTime;
|
||||
let time = 2000;
|
||||
if (diff < time) {
|
||||
await sleep(time - diff);
|
||||
}
|
||||
win.loadURL(`http://localhost:` + port);
|
||||
// win.webContents.openDevTools()
|
||||
}
|
||||
|
||||
// Electron会在初始化完成并且准备好创建浏览器窗口时调用这个方法
|
||||
// 部分 API 在 ready 事件触发后才能使用。
|
||||
app.whenReady().then(createWindow)
|
||||
// 当所有窗口都被关闭后退出
|
||||
app.on('windows-all-closed', () => {
|
||||
// 在 macOS 上,除非用户用 Cmd + Q 确定地退出,
|
||||
// 否则绝大部分应用及其菜单栏会保持激活。
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
app.on('activate', () => {
|
||||
// 在macOS上,当单击dock图标并且没有其他窗口打开时,
|
||||
// 通常在应用程序中重新创建一个窗口。
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 启动后台服务
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
async function startBackend() {
|
||||
let port = 51000;
|
||||
while (true) {
|
||||
let ok = await checkPort(port);
|
||||
if (ok) {
|
||||
break;
|
||||
}
|
||||
port = port + 1;
|
||||
}
|
||||
log.info("start check folder exist", __dirname, __filename)
|
||||
let exist = fs.existsSync("openRenamerBackend");
|
||||
const childProcess = spawn('node', [(exist ? '' : '../') + 'openRenamerBackend/dist/index.js'], {
|
||||
env: {
|
||||
"PORT": port,
|
||||
"DATA_PATH": dataPath
|
||||
}
|
||||
});
|
||||
|
||||
childProcess.stdout.on('data', (data) => {
|
||||
log.info(`stdout: ${data}`);
|
||||
});
|
||||
|
||||
childProcess.stderr.on('data', (data) => {
|
||||
log.error(`stderr: ${data}`);
|
||||
});
|
||||
|
||||
childProcess.on('close', (code) => {
|
||||
log.info(`child process exited with code ${code}`);
|
||||
});
|
||||
log.info("check service start");
|
||||
while (true) {
|
||||
await sleep(100);
|
||||
let success = !(await checkPort(port));
|
||||
if (success) {
|
||||
log.info("service start");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断端口是否可用
|
||||
* @param port
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
function checkPort(port) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let server = net.createServer().listen(port);
|
||||
server.on("listening", function () {
|
||||
server.close();
|
||||
resolve(true);
|
||||
})
|
||||
server.on("error", function (err) {
|
||||
console.error(err);
|
||||
resolve(false);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function sleep(time) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => resolve(), time);
|
||||
})
|
||||
}
|
72
electron/package.json
Normal file
72
electron/package.json
Normal file
@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "renamer",
|
||||
"version": "1.8.0",
|
||||
"description": "",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "electron-builder --win --x64"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"electron": "^28.0.0",
|
||||
"electron-builder": "^24.9.1"
|
||||
},
|
||||
"build": {
|
||||
"productName": "openRenamer",
|
||||
"appId": "openRenamer.app",
|
||||
"directories": {
|
||||
"output": "build"
|
||||
},
|
||||
"files": [
|
||||
"main.js",
|
||||
"preload.js",
|
||||
"index.html"
|
||||
],
|
||||
"extraFiles": [
|
||||
{
|
||||
"from": "../openRenamerBackend",
|
||||
"to": "openRenamerBackend"
|
||||
}
|
||||
],
|
||||
"copyright": "open-renamer",
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowElevation": true,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"installerIcon": "./renamer.ico",
|
||||
"uninstallerIcon": "./renamer.ico",
|
||||
"installerHeaderIcon": "./renamer.ico",
|
||||
"createDesktopShortcut": true,
|
||||
"createStartMenuShortcut": true,
|
||||
"shortcutName": "openRenamer"
|
||||
},
|
||||
"win": {
|
||||
"icon": "./renamer.ico",
|
||||
"target": [
|
||||
"nsis",
|
||||
"zip"
|
||||
],
|
||||
"extraFiles": [
|
||||
{
|
||||
"from": "windows/node.exe",
|
||||
"to": "node.exe"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mac": {
|
||||
"target": [
|
||||
"dmg",
|
||||
"zip"
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
"icon": "build/icons"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-log": "^5.0.1"
|
||||
}
|
||||
}
|
12
electron/preload.js
Normal file
12
electron/preload.js
Normal file
@ -0,0 +1,12 @@
|
||||
// preload.js
|
||||
// 所有Node.js API都可以在预加载过程中使用。
|
||||
// 它拥有与Chrome扩展一样的沙盒。
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const replaceText = (selector, text) => {
|
||||
const element = document.getElementById(selector)
|
||||
if (element) element.innerText = text
|
||||
}
|
||||
for (const dependency of ['chrome', 'node', 'electron']) {
|
||||
replaceText(`${dependency}-version`, process.versions[dependency])
|
||||
}
|
||||
})
|
1
electron/readme.md
Normal file
1
electron/readme.md
Normal file
@ -0,0 +1 @@
|
||||
需要下载windows版的node.js压缩包,将其中的node.exe 放到windowes目录下
|
BIN
electron/renamer.ico
Normal file
BIN
electron/renamer.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
1705
electron/yarn.lock
Normal file
1705
electron/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
1
openRenamerBackend/.gitignore
vendored
1
openRenamerBackend/.gitignore
vendored
@ -14,3 +14,4 @@ sqls/history.json
|
||||
static/*
|
||||
!static/.gitkeep
|
||||
database.db
|
||||
.idea
|
8
openRenamerBackend/.idea/.gitignore
generated
vendored
Normal file
8
openRenamerBackend/.idea/.gitignore
generated
vendored
Normal 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
8
openRenamerBackend/.idea/modules.xml
generated
Normal 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>
|
12
openRenamerBackend/.idea/openRenamerBackend.iml
generated
Normal file
12
openRenamerBackend/.idea/openRenamerBackend.iml
generated
Normal file
@ -0,0 +1,12 @@
|
||||
<?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" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.vscode" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
openRenamerBackend/.idea/vcs.xml
generated
Normal file
6
openRenamerBackend/.idea/vcs.xml
generated
Normal 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>
|
@ -1,6 +1,5 @@
|
||||
import { Context } from "koa";
|
||||
import ApplicationRuleService from "../service/ApplicationRuleService";
|
||||
import config from "../config";
|
||||
|
||||
const router = {};
|
||||
|
||||
@ -11,6 +10,14 @@ router["GET /applicationRule"] = async function (ctx: Context) {
|
||||
ctx.body = await ApplicationRuleService.getAll();
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取默认模板
|
||||
*/
|
||||
router["GET /applicationRule/default"] = async function (ctx: Context) {
|
||||
;
|
||||
ctx.body = await ApplicationRuleService.getDefault();
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新或者插入
|
||||
*/
|
||||
@ -26,4 +33,6 @@ router["DELETE /applicationRule/:id"] = async function (ctx: Context) {
|
||||
ctx.body = "";
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default router;
|
||||
|
14
openRenamerBackend/api/AutoPlanApi.ts
Normal file
14
openRenamerBackend/api/AutoPlanApi.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Context } from "koa";
|
||||
import AutoPlanService from "../service/AutoPlanService";
|
||||
|
||||
const router = {};
|
||||
|
||||
/**
|
||||
* 获取目录下的文件列表
|
||||
*/
|
||||
router["POST /autoPlan/save"] = async function (ctx: Context) {
|
||||
ctx.body = await AutoPlanService.saveAutoConfig(ctx.request.body);
|
||||
};
|
||||
|
||||
|
||||
export default router;
|
@ -1,4 +1,4 @@
|
||||
import { Context } from "koa";
|
||||
import {Context} from "koa";
|
||||
import FileService from "../service/FileService";
|
||||
import config from "../config";
|
||||
|
||||
@ -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;
|
||||
|
36
openRenamerBackend/api/GlobalConfigApi.ts
Normal file
36
openRenamerBackend/api/GlobalConfigApi.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Context } from "koa";
|
||||
import service from "../service/GlobalConfigService";
|
||||
|
||||
const router = {};
|
||||
|
||||
/**
|
||||
* 获取单个配置
|
||||
*/
|
||||
router["GET /config/code"] = async function (ctx: Context) {
|
||||
ctx.body = await service.getVal(ctx.request.query.code as string);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取多个配置项
|
||||
*/
|
||||
router["POST /config/multCode"] = async function (ctx: Context) {
|
||||
ctx.body = await service.getMultVal(ctx.request.body);
|
||||
};
|
||||
|
||||
/**
|
||||
* 提交修改
|
||||
*/
|
||||
router["POST /config/update"] = async function (ctx: Context) {
|
||||
ctx.body = await service.updateVal(ctx.request.body.code, ctx.request.body.val);
|
||||
};
|
||||
|
||||
/**
|
||||
* 提交修改
|
||||
*/
|
||||
router["POST /config/insertOrUpdate"] = async function (ctx: Context) {
|
||||
ctx.body = await service.insertOrReplace(ctx.request.body);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default router;
|
28
openRenamerBackend/api/QbServiceApi.ts
Normal file
28
openRenamerBackend/api/QbServiceApi.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import {Context} from "koa";
|
||||
import service from "../service/QbService";
|
||||
|
||||
const router = {};
|
||||
|
||||
/**
|
||||
* 获取单个配置
|
||||
*/
|
||||
router["POST /qb/saveQbInfo"] = async function (ctx: Context) {
|
||||
ctx.body = await service.saveAddress(ctx.request.body);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取qb配置
|
||||
*/
|
||||
router["GET /qb/config"] = async function (ctx: Context) {
|
||||
ctx.body = await service.getConfig();
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取qb配置
|
||||
*/
|
||||
router["GET /qb/bt/list"] = async function (ctx: Context) {
|
||||
ctx.body = await service.getBtList();
|
||||
};
|
||||
|
||||
|
||||
export default router;
|
@ -1,26 +1,29 @@
|
||||
import * as path from 'path';
|
||||
import * as process from "process";
|
||||
|
||||
//后台所在绝对路径
|
||||
const rootPath = path.resolve(__dirname, '..');
|
||||
|
||||
let config = {
|
||||
rootPath,
|
||||
port: process.env.PORT ? parseInt(process.env.PORT) : 8089,
|
||||
token: process.env.TOKEN ? process.env.TOKEN : null,
|
||||
urlPrefix: '/openRenamer/api',
|
||||
//是否为windows平台
|
||||
isWindows: process.platform.toLocaleLowerCase().includes("win"),
|
||||
bodyLimit: {
|
||||
formLimit: '2mb',
|
||||
urlencoded: true,
|
||||
multipart: true,
|
||||
formidable: {
|
||||
uploadDir: path.join(rootPath, 'files', 'temp', 'uploads'),
|
||||
keepExtenstions: true,
|
||||
maxFieldsSize: 1024 * 1024
|
||||
}
|
||||
},
|
||||
publicPath: new Set(["POST/public/checkToken"])
|
||||
rootPath,
|
||||
dataPath: process.env.DATA_PATH ? process.env.DATA_PATH : path.join(rootPath, 'data'),
|
||||
port: process.env.PORT ? parseInt(process.env.PORT) : 8089,
|
||||
token: process.env.TOKEN ? process.env.TOKEN : null,
|
||||
urlPrefix: '/openRenamer/api',
|
||||
//是否为windows平台
|
||||
isWindows: process.platform.toLocaleLowerCase().includes("win"),
|
||||
bodyLimit: {
|
||||
formLimit: '200mb',
|
||||
jsonLimit: '200mb',
|
||||
urlencoded: true,
|
||||
multipart: true,
|
||||
formidable: {
|
||||
uploadDir: path.join(rootPath, 'files', 'temp', 'uploads'),
|
||||
keepExtenstions: true,
|
||||
maxFieldsSize: 1024 * 1024 * 200
|
||||
}
|
||||
},
|
||||
publicPath: new Set(["POST/public/checkToken"])
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import ErrorHelper from "../util/ErrorHelper";
|
||||
import ApplicationRule from "../entity/dto/ApplicationRule";
|
||||
import ApplicationRule from "../entity/po/ApplicationRule";
|
||||
import SqliteHelper from "../util/SqliteHelper";
|
||||
|
||||
export default class ApplicationRuleDao {
|
||||
@ -13,6 +13,18 @@ export default class ApplicationRuleDao {
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询id
|
||||
* @param id id
|
||||
* @returns
|
||||
*/
|
||||
static async getById(id: number): Promise<ApplicationRule> {
|
||||
let res = await SqliteHelper.pool.get('select * from application_rule where id=?', id);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 新增
|
||||
|
69
openRenamerBackend/dao/GlobalConfigDao.ts
Normal file
69
openRenamerBackend/dao/GlobalConfigDao.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import ErrorHelper from "../util/ErrorHelper";
|
||||
import SqliteHelper from "../util/SqliteHelper";
|
||||
import GlobalConfig from "../entity/po/GlobalConfig";
|
||||
|
||||
export default class GlobalConfigDao {
|
||||
|
||||
|
||||
/**
|
||||
* 新增
|
||||
* @param obj
|
||||
* @returns
|
||||
*/
|
||||
static async addOne(obj: GlobalConfig): Promise<void> {
|
||||
await SqliteHelper.pool.run('insert into global_config(code,val,description) values(?,?,?)'
|
||||
, obj.code, obj.val, obj.description);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* @param code code
|
||||
* @param val val
|
||||
*/
|
||||
static async updateOne(code: string, val: string): Promise<void> {
|
||||
await SqliteHelper.pool.run('update global_config set val=? where code=?', val, code);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param code
|
||||
*/
|
||||
static async deleteByCode(code: string): Promise<void> {
|
||||
let res = await SqliteHelper.pool.run('delete from global_config where code=?', code);
|
||||
if (res.changes == 0) {
|
||||
throw ErrorHelper.Error404("数据不存在");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询
|
||||
* @param code
|
||||
*/
|
||||
static async getByCode(code: string): Promise<string> {
|
||||
let res = await SqliteHelper.pool.get('select val from global_config where code=?', code);
|
||||
return res ? res.val : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询多个code
|
||||
* @param code
|
||||
*/
|
||||
static async getByMulCode(codes: Array<string>): Promise<Array<GlobalConfig>> {
|
||||
if (codes.length == 0) {
|
||||
return new Array();
|
||||
}
|
||||
let codeStr = codes.map(item => `'${item}'`).join(',');
|
||||
return await SqliteHelper.pool.all(`select * from global_config where code in (${codeStr})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入一条
|
||||
* @param body body
|
||||
*/
|
||||
static async insertOrReplace(body: GlobalConfig): Promise<void> {
|
||||
await SqliteHelper.pool.run(`insert or replace into global_config values (?,?,?)`, body.code, body.val, body.description);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import ErrorHelper from "../util/ErrorHelper";
|
||||
import SavePath from "../entity/dto/SavePath";
|
||||
import SavePath from "../entity/po/SavePath";
|
||||
import SqliteHelper from "../util/SqliteHelper";
|
||||
|
||||
export default class SavePathDao {
|
||||
|
91
openRenamerBackend/entity/bo/rules/AutoRule.ts
Normal file
91
openRenamerBackend/entity/bo/rules/AutoRule.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import RuleInterface from "./RuleInterface";
|
||||
import FileObj from "../../vo/FileObj";
|
||||
import path from 'path';
|
||||
import {getSeason} from "../../../util/MediaUtil";
|
||||
|
||||
|
||||
let pattern = new RegExp(/s(eason)?(\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 {
|
||||
|
||||
/**
|
||||
* 识别类型,season:季号,name:剧名/电影名识别
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* 前面追加
|
||||
*/
|
||||
frontAdd: string;
|
||||
/**
|
||||
* 后面追加
|
||||
*/
|
||||
endAdd: string;
|
||||
eNumWidth: number;
|
||||
|
||||
constructor(data: any) {
|
||||
this.type = data.type;
|
||||
this.frontAdd = data.frontAdd;
|
||||
this.endAdd = data.endAdd;
|
||||
this.eNumWidth = data.eNumWidth;
|
||||
}
|
||||
|
||||
|
||||
deal(file: FileObj): void {
|
||||
//识别到的内容
|
||||
let getStr = null;
|
||||
let season = getSeason(path.basename(file.path));
|
||||
if (this.type === 'season') {
|
||||
getStr = season;
|
||||
} else if (this.type === 'name') {
|
||||
let originName = null;
|
||||
if (season && season.length > 0) {
|
||||
//说明是剧集,取父文件夹的父文件夹名称
|
||||
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+[kp]/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;
|
||||
}
|
||||
}
|
||||
}
|
99
openRenamerBackend/entity/bo/rules/DeleteRule.ts
Normal file
99
openRenamerBackend/entity/bo/rules/DeleteRule.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import RuleInterface from "./RuleInterface";
|
||||
import {dealFileName} from "./RuleInterface";
|
||||
import FileObj from "../../vo/FileObj";
|
||||
import path from 'path';
|
||||
|
||||
export default class DeleteRule implements RuleInterface {
|
||||
/**
|
||||
* 类别:deletePart:部分删除,deleteAll:全部删除
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* 部分删除时的开始信息
|
||||
*/
|
||||
start: DeleteRuleItem;
|
||||
/**
|
||||
* 部分删除时的结束信息
|
||||
|
||||
*/
|
||||
end: DeleteRuleItem;
|
||||
/**
|
||||
* 忽略拓展名,true:忽略,false:不忽略
|
||||
*/
|
||||
ignorePostfix: boolean;
|
||||
/*
|
||||
* 是否区分大小写
|
||||
*/
|
||||
regI: boolean;
|
||||
|
||||
constructor(data: any) {
|
||||
this.type = data.type;
|
||||
this.regI = data.regI != undefined && data.regI;
|
||||
this.start = new DeleteRuleItem(data.start, this.regI);
|
||||
this.end = new DeleteRuleItem(data.end, this.regI);
|
||||
this.ignorePostfix = data.ignorePostfix;
|
||||
}
|
||||
|
||||
|
||||
deal(file: FileObj): void {
|
||||
let target = "";
|
||||
if (this.type === 'deleteAll') {
|
||||
target = "";
|
||||
} else {
|
||||
let str = file.realName + (this.ignorePostfix ? "" : file.expandName);
|
||||
let startIndex = this.start.calIndex(str, false);
|
||||
let endIndex = this.end.calIndex(str, true);
|
||||
if (startIndex < 0 || endIndex < 0 || startIndex > endIndex) {
|
||||
return;
|
||||
}
|
||||
str = str.substring(0, startIndex) + str.substring(endIndex + 1);
|
||||
target = str;
|
||||
}
|
||||
dealFileName(file, target, this.ignorePostfix);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DeleteRuleItem {
|
||||
/**
|
||||
* location:位置,text:文本,end:直到末尾
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* 对应的值
|
||||
*/
|
||||
value: string;
|
||||
/**
|
||||
* 正则对象
|
||||
*/
|
||||
reg: RegExp;
|
||||
|
||||
constructor(data: any, regI: boolean) {
|
||||
this.type = data.type;
|
||||
this.value = data.value;
|
||||
if (this.type === 'reg') {
|
||||
this.reg = regI ? new RegExp(this.value) : new RegExp(this.value, 'i');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算位置
|
||||
* @param str 字符串
|
||||
* @param end 是否末尾计算
|
||||
*/
|
||||
calIndex(str: string, end: boolean): number {
|
||||
if (this.type === 'location') {
|
||||
let val = parseInt(this.value);
|
||||
return val > 0 ? val - 1 : str.length + val;
|
||||
} else if (this.type === 'text') {
|
||||
let index = str.indexOf(this.value);
|
||||
return index + (end ? this.value.length - 1 : 0);
|
||||
} else if (this.type === 'end') {
|
||||
return str.length - 1;
|
||||
} else if (this.type === 'reg') {
|
||||
let res = this.reg.exec(str);
|
||||
return res == null ? -1 : (res.index + (end ? res[0].length - 1 : 0));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
95
openRenamerBackend/entity/bo/rules/InsertRule.ts
Normal file
95
openRenamerBackend/entity/bo/rules/InsertRule.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import RuleInterface from "./RuleInterface";
|
||||
import FileObj from "../../vo/FileObj";
|
||||
import path from 'path';
|
||||
import {getSeason} from "../../../util/MediaUtil";
|
||||
|
||||
|
||||
export default class InsertRule implements RuleInterface {
|
||||
|
||||
/**
|
||||
* 插入内容
|
||||
*/
|
||||
insertContent: string;
|
||||
/**
|
||||
* 操作类别,front:前缀,backend:后缀,at:位置,replace:替换当前文件名
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* 当type为at,时的位置,从1开始
|
||||
*/
|
||||
atInput: number;
|
||||
/**
|
||||
* 当type为at,时的方向,true:从右到左,false:从左到右
|
||||
*/
|
||||
atIsRightToleft: boolean;
|
||||
/**
|
||||
* 忽略拓展名,true:忽略,false:不忽略
|
||||
*/
|
||||
ignorePostfix: boolean;
|
||||
/**
|
||||
自动识别季号
|
||||
*/
|
||||
autoSeason: boolean;
|
||||
/**
|
||||
后缀过滤是否开启
|
||||
*/
|
||||
endFilter: boolean;
|
||||
/**
|
||||
有效后缀
|
||||
*/
|
||||
validEnd: Array<String>;
|
||||
|
||||
constructor(data: any) {
|
||||
this.insertContent = data.insertContent;
|
||||
this.type = data.type;
|
||||
this.atInput = data.atInput;
|
||||
this.atIsRightToleft = data.atIsRightToleft;
|
||||
this.ignorePostfix = data.ignorePostfix;
|
||||
this.autoSeason = data.autoSeason;
|
||||
this.endFilter = data.endFilter;
|
||||
this.validEnd = data.validEnd;
|
||||
}
|
||||
|
||||
|
||||
deal(file: FileObj): void {
|
||||
if (this.endFilter && file.expandName.length > 0 && this.validEnd.indexOf(file.expandName.substring(1)) == -1) {
|
||||
//拓展名不符,跳过
|
||||
return;
|
||||
}
|
||||
let str = this.ignorePostfix ? file.realName : file.name;
|
||||
let season = '';
|
||||
|
||||
if (this.autoSeason) {
|
||||
season = getSeason(path.basename(file.path));
|
||||
}
|
||||
switch (this.type) {
|
||||
case "front":
|
||||
str = this.insertContent + season + str;
|
||||
break;
|
||||
case "backend":
|
||||
str = str + this.insertContent + season;
|
||||
break;
|
||||
case "at":
|
||||
let index = this.atIsRightToleft ? str.length - this.atInput + 1 : this.atInput - 1;
|
||||
str = str.substring(0, index) + this.insertContent + season + str.substring(index);
|
||||
break;
|
||||
case "replace":
|
||||
str = this.insertContent + season;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (this.ignorePostfix) {
|
||||
file.realName = str;
|
||||
} else {
|
||||
file.expandName = path.extname(str);
|
||||
if (file.expandName.length > 0) {
|
||||
file.realName = str.substring(0, str.lastIndexOf("."));
|
||||
} else {
|
||||
file.realName = str;
|
||||
}
|
||||
}
|
||||
|
||||
file.name = file.realName + file.expandName;
|
||||
}
|
||||
}
|
112
openRenamerBackend/entity/bo/rules/ReplaceRule.ts
Normal file
112
openRenamerBackend/entity/bo/rules/ReplaceRule.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import RuleInterface from "./RuleInterface";
|
||||
import * as ValUtil from "../../../util/ValUtil";
|
||||
import FileObj from "../../vo/FileObj";
|
||||
import {dealFileName} from './RuleInterface';
|
||||
import path from 'path';
|
||||
|
||||
|
||||
export default class ReplaceRule implements RuleInterface {
|
||||
|
||||
/**
|
||||
* 1:替换第一个,2:替换最后一个,3:全部替换
|
||||
*/
|
||||
type: number;
|
||||
/**
|
||||
* 源
|
||||
*/
|
||||
source: string;
|
||||
/**
|
||||
* 目标
|
||||
*/
|
||||
target: string;
|
||||
/**
|
||||
* 是否正则模式
|
||||
*/
|
||||
regFlag: boolean;
|
||||
/**
|
||||
* 是否区分大小写
|
||||
*/
|
||||
regI: boolean;
|
||||
/**
|
||||
* 是否护理拓展名
|
||||
*/
|
||||
ignorePostfix: boolean;
|
||||
|
||||
constructor(data: any) {
|
||||
this.type = data.type;
|
||||
this.source = data.source;
|
||||
this.target = data.target;
|
||||
this.regFlag = ValUtil.nullToDefault(data.regFlag, false);
|
||||
this.regI = ValUtil.nullToDefault(data.regI, false);
|
||||
this.ignorePostfix = ValUtil.nullToDefault(data.ignorePostfix, false);
|
||||
}
|
||||
|
||||
|
||||
deal(file: FileObj): void {
|
||||
let targetStr = this.ignorePostfix ? file.realName : file.name;
|
||||
let res = this.regFlag ? this.dealReg(targetStr) : this.dealNoReg(targetStr);
|
||||
dealFileName(file, res, this.ignorePostfix);
|
||||
}
|
||||
|
||||
private dealNoReg(targetStr: string): string {
|
||||
let start = 0;
|
||||
let arr: number[] = [];
|
||||
for (let i = 0; i < (this.type == 1 ? 1 : 1000); i++) {
|
||||
let one = targetStr.indexOf(this.source, start);
|
||||
if (one == -1) {
|
||||
break;
|
||||
}
|
||||
arr.push(one);
|
||||
start = one + this.source.length;
|
||||
}
|
||||
if (arr.length == 0) {
|
||||
return targetStr;
|
||||
}
|
||||
let res = "";
|
||||
let needDealArr: number[] = this.type === 1 ? [arr[0]] : this.type === 2 ? [arr[arr.length - 1]] : arr;
|
||||
let lastIndex = 0;
|
||||
for (let i = 0; i < needDealArr.length; i++) {
|
||||
res += targetStr.substring(lastIndex, needDealArr[i]) + this.target;
|
||||
lastIndex = needDealArr[i] + this.source.length;
|
||||
}
|
||||
res += targetStr.substring(lastIndex);
|
||||
return res;
|
||||
}
|
||||
|
||||
private dealReg(targetStr: string): string {
|
||||
let templateReg = new RegExp("#\{group(\\d+\)}", "g");
|
||||
let templateArr: string[][] = [];
|
||||
while (true) {
|
||||
let one = templateReg.exec(this.target);
|
||||
if (one == null) {
|
||||
break;
|
||||
}
|
||||
templateArr.push([one[0], one[1]]);
|
||||
}
|
||||
|
||||
let reg = new RegExp(this.source, this.regI ? "g" : "ig");
|
||||
let arr: RegExpExecArray[] = [];
|
||||
for (let i = 0; i < (this.type == 1 ? 1 : 1000); i++) {
|
||||
let one = reg.exec(targetStr);
|
||||
if (one == null) {
|
||||
break;
|
||||
}
|
||||
arr.push(one);
|
||||
}
|
||||
if (arr.length == 0) {
|
||||
return targetStr;
|
||||
}
|
||||
let res = "";
|
||||
let needDealReg: RegExpExecArray[] = this.type === 1 ? [arr[0]] : this.type === 2 ? [arr[arr.length - 1]] : arr;
|
||||
let lastIndex = 0;
|
||||
for (let i = 0; i < needDealReg.length; i++) {
|
||||
let reg = needDealReg[i];
|
||||
let target = this.target;
|
||||
templateArr.forEach(item => target = target.replace(item[0], ValUtil.nullToDefault(reg[parseInt(item[1])], '')));
|
||||
res += targetStr.substring(lastIndex, reg.index) + target;
|
||||
lastIndex = reg.index + reg[0].length;
|
||||
}
|
||||
res += targetStr.substring(lastIndex);
|
||||
return res;
|
||||
}
|
||||
}
|
27
openRenamerBackend/entity/bo/rules/RuleInterface.ts
Normal file
27
openRenamerBackend/entity/bo/rules/RuleInterface.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import FileObj from "../../vo/FileObj";
|
||||
import * as path from 'path';
|
||||
|
||||
export default interface RuleInterface {
|
||||
|
||||
deal(file: FileObj): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新处理文件名
|
||||
* @param file
|
||||
* @param newFileName
|
||||
* @param ignorePostfix
|
||||
*/
|
||||
export function dealFileName(file: FileObj, newFileName: string, ignorePostfix: boolean) {
|
||||
if (ignorePostfix) {
|
||||
file.realName = newFileName;
|
||||
} else {
|
||||
file.expandName = path.extname(newFileName);
|
||||
if (file.expandName.length > 0) {
|
||||
file.realName = newFileName.substring(0, newFileName.lastIndexOf("."));
|
||||
} else {
|
||||
file.realName = newFileName;
|
||||
}
|
||||
}
|
||||
file.name = file.realName + file.expandName;
|
||||
}
|
87
openRenamerBackend/entity/bo/rules/SerializationRule.ts
Normal file
87
openRenamerBackend/entity/bo/rules/SerializationRule.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import RuleInterface from "./RuleInterface";
|
||||
import FileObj from "../../vo/FileObj";
|
||||
import path from 'path';
|
||||
|
||||
export default class InsertRule implements RuleInterface {
|
||||
/**
|
||||
* 开始位置
|
||||
*/
|
||||
start: number;
|
||||
/**
|
||||
* 记录当前的值是多少
|
||||
*/
|
||||
currentIndexMap: Map<string, number>;
|
||||
/**
|
||||
* 增量
|
||||
*/
|
||||
increment: number;
|
||||
/**
|
||||
* 是否填充0
|
||||
*/
|
||||
addZero: boolean;
|
||||
/**
|
||||
* 填充后长度
|
||||
*/
|
||||
numLength: number;
|
||||
/**
|
||||
* 插入位置,front:前缀,backend:后缀,at:位置
|
||||
*/
|
||||
insertType: string;
|
||||
/**
|
||||
* 插入的位置
|
||||
*/
|
||||
insertValue: number;
|
||||
/**
|
||||
* 忽略拓展名
|
||||
*/
|
||||
ignorePostfix: boolean;
|
||||
/**
|
||||
* 拓展名分组
|
||||
*/
|
||||
postfixGroup: boolean;
|
||||
|
||||
constructor(data: any) {
|
||||
this.start = data.start;
|
||||
this.currentIndexMap = new Map<string, number>();
|
||||
this.increment = data.increment;
|
||||
this.addZero = data.addZero;
|
||||
this.numLength = data.numLength;
|
||||
this.insertType = data.insertType;
|
||||
this.insertValue = data.insertValue;
|
||||
this.ignorePostfix = data.ignorePostfix;
|
||||
this.postfixGroup = data.postfixGroup;
|
||||
}
|
||||
|
||||
deal(file: FileObj): void {
|
||||
let expand = this.postfixGroup ? file.expandName : "";
|
||||
let currentIndex = this.currentIndexMap.has(expand) ? this.currentIndexMap.get(expand) : this.start;
|
||||
let length = currentIndex.toString().length;
|
||||
let numStr = (this.addZero && this.numLength > length ? "0".repeat(this.numLength - length) : "") + currentIndex;
|
||||
let str = this.ignorePostfix ? file.realName : file.name;
|
||||
switch (this.insertType) {
|
||||
case "front":
|
||||
str = numStr + str;
|
||||
break;
|
||||
case "backend":
|
||||
str = str + numStr;
|
||||
break;
|
||||
case "at":
|
||||
str = str.substring(0, this.insertValue - 1) + numStr + str.substring(this.insertValue - 1);
|
||||
break;
|
||||
}
|
||||
this.currentIndexMap.set(expand, currentIndex + this.increment);
|
||||
|
||||
if (this.ignorePostfix) {
|
||||
file.realName = str;
|
||||
} else {
|
||||
file.expandName = path.extname(str);
|
||||
if (file.expandName.length > 0) {
|
||||
file.realName = str.substring(0, str.lastIndexOf("."));
|
||||
} else {
|
||||
file.realName = str;
|
||||
}
|
||||
}
|
||||
|
||||
file.name = file.realName + file.expandName;
|
||||
}
|
||||
}
|
32
openRenamerBackend/entity/bo/rules/TranslateRole.ts
Normal file
32
openRenamerBackend/entity/bo/rules/TranslateRole.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import RuleInterface from "./RuleInterface";
|
||||
import FileObj from "../../vo/FileObj";
|
||||
import * as TranslateUtil from "../../../util/TranslateUtil";
|
||||
import path from 'path';
|
||||
|
||||
|
||||
export default class TranslateRole implements RuleInterface {
|
||||
|
||||
/**
|
||||
* 1:简体转繁体 2:繁体转简体
|
||||
*/
|
||||
type: number;
|
||||
/**
|
||||
* 0、繁体中文,1、港澳繁体,2、台湾正体
|
||||
*/
|
||||
traditionalType: number;
|
||||
|
||||
constructor(data: any) {
|
||||
this.type = data.type;
|
||||
this.traditionalType = data.traditionalType;
|
||||
}
|
||||
|
||||
|
||||
deal(file: FileObj): void {
|
||||
if (this.type == 1) {
|
||||
file.realName = TranslateUtil.toTraditionalChinese(file.realName, this.traditionalType);
|
||||
} else if (this.type == 2) {
|
||||
file.realName = TranslateUtil.toSimplifiedChinese(file.realName, this.traditionalType);
|
||||
}
|
||||
file.name = file.realName + file.expandName;
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 默认模板id
|
||||
*/
|
||||
export const DEFAULT_TEMPLETE_ID = "defaultTempleteId";
|
34
openRenamerBackend/entity/dto/AutoPlanConfigDto.ts
Normal file
34
openRenamerBackend/entity/dto/AutoPlanConfigDto.ts
Normal file
@ -0,0 +1,34 @@
|
||||
export default interface AutoPlanConfigDto {
|
||||
/**
|
||||
* 待处理的路径
|
||||
*/
|
||||
paths: Array<string>;
|
||||
/**
|
||||
* 版本
|
||||
*/
|
||||
version: Number;
|
||||
/**
|
||||
* 是否忽略season 0
|
||||
*/
|
||||
ignoreSeason0: Boolean;
|
||||
/**
|
||||
* 忽略的文件名
|
||||
*/
|
||||
ignorePaths: Array<string>;
|
||||
/**
|
||||
* 是否删除小于2m的视频文件
|
||||
*/
|
||||
deleteSmallVideo: boolean;
|
||||
/**
|
||||
* 重命名规则
|
||||
*/
|
||||
rules: Array<string>;
|
||||
/**
|
||||
* 是否忽略现有的文件
|
||||
*/
|
||||
ignoreExist: boolean;
|
||||
/**
|
||||
* 是否开始任务
|
||||
*/
|
||||
start: boolean;
|
||||
}
|
28
openRenamerBackend/entity/dto/BtListItemDto.ts
Normal file
28
openRenamerBackend/entity/dto/BtListItemDto.ts
Normal file
@ -0,0 +1,28 @@
|
||||
export default interface BtListItemDto {
|
||||
hash: string;
|
||||
/**
|
||||
* 添加时间
|
||||
*/
|
||||
added_on: number;
|
||||
/**
|
||||
* left bytes num
|
||||
*/
|
||||
amount_left: number;
|
||||
/**
|
||||
* Percentage of file pieces currently available
|
||||
*/
|
||||
availability: number;
|
||||
category: string;
|
||||
/**
|
||||
* Amount of transfer data completed (bytes)
|
||||
*/
|
||||
completed: number;
|
||||
/**
|
||||
* Time (Unix Epoch) when the torrent completed
|
||||
*/
|
||||
completion_on: number;
|
||||
/**
|
||||
* Absolute path of torrent content (root path for multifile torrents, absolute file path for singlefile torrents)
|
||||
*/
|
||||
content_path: string;
|
||||
}
|
22
openRenamerBackend/entity/dto/QbConfigDto.ts
Normal file
22
openRenamerBackend/entity/dto/QbConfigDto.ts
Normal file
@ -0,0 +1,22 @@
|
||||
export default interface QbConfigDto {
|
||||
address: string;
|
||||
username: string;
|
||||
password: string;
|
||||
valid: boolean;
|
||||
/**
|
||||
* qb version,null if config is error
|
||||
*/
|
||||
version: string;
|
||||
/**
|
||||
* Qbittorrent's download
|
||||
*/
|
||||
qbDownloadPath: string;
|
||||
/**
|
||||
* Qbittorrent's download path corresponds to current system path
|
||||
*/
|
||||
renameQbDownloadPath: string;
|
||||
/**
|
||||
* config path to select convenient
|
||||
*/
|
||||
configPaths: Array<string>;
|
||||
}
|
@ -20,4 +20,12 @@ export default class ApplicationRule {
|
||||
规则内容,json序列化后
|
||||
*/
|
||||
content: string;
|
||||
|
||||
constructor(name: string, comment: string, content: string) {
|
||||
this.createdDate = Date.now();
|
||||
this.updatedDate = this.createdDate;
|
||||
this.name = name;
|
||||
this.comment = comment;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
21
openRenamerBackend/entity/po/GlobalConfig.ts
Normal file
21
openRenamerBackend/entity/po/GlobalConfig.ts
Normal file
@ -0,0 +1,21 @@
|
||||
export default class GlobalConfig {
|
||||
/**
|
||||
code
|
||||
*/
|
||||
code: string;
|
||||
|
||||
/**
|
||||
规则内容,json序列化后
|
||||
*/
|
||||
val: string;
|
||||
/**
|
||||
描述
|
||||
*/
|
||||
description: string;
|
||||
|
||||
constructor(code: string, val: string, desc: string) {
|
||||
this.code = code;
|
||||
this.val = val;
|
||||
this.description = desc;
|
||||
}
|
||||
}
|
72
openRenamerBackend/entity/vo/FileObj.ts
Normal file
72
openRenamerBackend/entity/vo/FileObj.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import * as pathUtil from "path";
|
||||
import {isVideo, isSub, isNfo} from "../../util/MediaUtil"
|
||||
|
||||
export default class FileObj {
|
||||
/**
|
||||
* 变更后的文件名(包含拓展名)
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 去掉拓展名后的名字(不包含拓展名)
|
||||
*/
|
||||
realName: string;
|
||||
/**
|
||||
原始文件名(不变)
|
||||
*/
|
||||
originName: string;
|
||||
/**
|
||||
* 拓展名(最新的拓展名,每次应用规则后重新计算)
|
||||
*/
|
||||
expandName: string;
|
||||
|
||||
/**
|
||||
* 所属路径
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* 是否文件夹
|
||||
*/
|
||||
isFolder: boolean;
|
||||
/**
|
||||
* 文件大小
|
||||
*/
|
||||
size: number;
|
||||
/**
|
||||
* 重命名错误原因
|
||||
*/
|
||||
errorMessage: string;
|
||||
/**
|
||||
* 创建时间ms
|
||||
*/
|
||||
createdTime: number;
|
||||
/**
|
||||
* 更新时间ms
|
||||
*/
|
||||
updatedTime: number;
|
||||
/**
|
||||
* 是否广告文件
|
||||
*/
|
||||
isAdFile: boolean;
|
||||
|
||||
|
||||
constructor(name: string, path, isFolder, size: number, createdTime, updatedTime) {
|
||||
this.name = name;
|
||||
this.originName = name;
|
||||
this.expandName = pathUtil.extname(name);
|
||||
if (this.expandName.length > 0) {
|
||||
this.realName = name.substring(0, name.lastIndexOf("."));
|
||||
let end = this.expandName.toLowerCase().replace(".", "");
|
||||
if (isVideo(end)) {
|
||||
this.isAdFile = size < 5 * 1024 * 1024;
|
||||
} else this.isAdFile = !(isSub(end) || isNfo(end));
|
||||
} else {
|
||||
this.realName = name;
|
||||
}
|
||||
this.path = path;
|
||||
this.isFolder = isFolder;
|
||||
this.size = size;
|
||||
this.createdTime = createdTime;
|
||||
this.updatedTime = updatedTime;
|
||||
|
||||
}
|
||||
}
|
42
openRenamerBackend/entity/vo/RuleObj.ts
Normal file
42
openRenamerBackend/entity/vo/RuleObj.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import DeleteRule from "../bo/rules/DeleteRule";
|
||||
import InsertRule from "../bo/rules/InsertRule";
|
||||
import SerializationRule from "../bo/rules/SerializationRule";
|
||||
import AutoRule from "../bo/rules/AutoRule";
|
||||
import ReplaceRule from "../bo/rules/ReplaceRule";
|
||||
import TranslateRole from "../bo/rules/TranslateRole";
|
||||
|
||||
export default class RuleObj {
|
||||
type: string;
|
||||
message: string;
|
||||
/**
|
||||
* 具体参数
|
||||
*/
|
||||
data: any;
|
||||
|
||||
constructor(data: any) {
|
||||
this.type = data.type;
|
||||
this.message = data.message;
|
||||
switch (this.type) {
|
||||
case "delete":
|
||||
this.data = new DeleteRule(data.data);
|
||||
break;
|
||||
case "insert":
|
||||
this.data = new InsertRule(data.data);
|
||||
break;
|
||||
case "serialization":
|
||||
this.data = new SerializationRule(data.data);
|
||||
break;
|
||||
case "auto":
|
||||
this.data = new AutoRule(data.data);
|
||||
break;
|
||||
case "replace":
|
||||
this.data = new ReplaceRule(data.data);
|
||||
break;
|
||||
case "translate":
|
||||
this.data = new TranslateRole(data.data);
|
||||
break;
|
||||
default:
|
||||
throw new Error("不支持的规则:" + this.type);
|
||||
}
|
||||
}
|
||||
}
|
@ -9,13 +9,15 @@ import handleError from "./middleware/handleError";
|
||||
import init from "./middleware/init";
|
||||
import SqliteUtil from './util/SqliteHelper';
|
||||
import log from './util/LogUtil';
|
||||
|
||||
import QbService from './service/QbService';
|
||||
import qbService from "./service/QbService";
|
||||
|
||||
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')));
|
||||
@ -29,11 +31,12 @@ app.use(handleError);
|
||||
|
||||
app.use(RouterMW(router, path.join(config.rootPath, "dist/api")));
|
||||
(async () => {
|
||||
await SqliteUtil.createPool();
|
||||
app.listen(config.port);
|
||||
log.info(`server listened `, config.port);
|
||||
await SqliteUtil.createPool();
|
||||
await qbService.init();
|
||||
app.listen(config.port);
|
||||
log.info(`server listened `, config.port);
|
||||
})();
|
||||
|
||||
app.on("error", (error) => {
|
||||
console.error(error);
|
||||
console.error(error);
|
||||
})
|
||||
|
@ -9,22 +9,20 @@
|
||||
"author": "fxb",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "^5.0.4",
|
||||
"@types/koa": "^2.0.47",
|
||||
"@types/node": "^11.13.4",
|
||||
"axios": "^0.21.1",
|
||||
"fs-extra": "^7.0.0",
|
||||
"koa": "^2.5.3",
|
||||
"koa-body": "^4.0.4",
|
||||
"koa-router": "^7.4.0",
|
||||
"koa-static": "^5.0.0",
|
||||
"koa2-cors": "^2.0.6",
|
||||
"log4js": "^6.3.0",
|
||||
"moment": "^2.22.2",
|
||||
"mysql2": "^2.2.5",
|
||||
"sqlite": "^4.0.23",
|
||||
"sqlite3": "^5.0.2",
|
||||
"uuid": "^3.3.2",
|
||||
"winston": "^3.1.0"
|
||||
"@types/fs-extra": "5.0.4",
|
||||
"@types/koa": "2.13.12",
|
||||
"@types/node": "11.13.4",
|
||||
"axios": "0.21.4",
|
||||
"fs-extra": "7.0.0",
|
||||
"koa": "2.5.3",
|
||||
"koa-body": "4.0.4",
|
||||
"koa-router": "7.4.0",
|
||||
"koa-static": "5.0.0",
|
||||
"koa2-cors": "2.0.6",
|
||||
"log4js": "6.3.0",
|
||||
"moment": "2.22.2",
|
||||
"sqlite": "4.0.23",
|
||||
"sqlite3": "5.0.2",
|
||||
"uuid": "3.3.2"
|
||||
}
|
||||
}
|
||||
|
1966
openRenamerBackend/pnpm-lock.yaml
generated
Normal file
1966
openRenamerBackend/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,10 @@
|
||||
import config from '../config';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs-extra';
|
||||
import ApplicationRule from '../entity/dto/ApplicationRule';
|
||||
import ApplicationRule from '../entity/po/ApplicationRule';
|
||||
import ApplicationRuleDao from '../dao/ApplicationRuleDao';
|
||||
import GlobalConfigDao from '../dao/GlobalConfigDao';
|
||||
|
||||
import { DEFAULT_TEMPLETE_ID } from '../entity/constants/GlobalConfigCodeConstant';
|
||||
import GlobalConfig from '../entity/po/GlobalConfig';
|
||||
import ErrorHelper from '../util/ErrorHelper';
|
||||
|
||||
|
||||
class ApplicationRuleService {
|
||||
@ -25,9 +26,47 @@ class ApplicationRuleService {
|
||||
}
|
||||
|
||||
static async deleteById(id: number): Promise<void> {
|
||||
//禁止删除默认模板
|
||||
let idStr = await GlobalConfigDao.getByCode(DEFAULT_TEMPLETE_ID);
|
||||
if (id.toString() === idStr) {
|
||||
throw ErrorHelper.Error400("禁止删除默认模板");
|
||||
}
|
||||
await ApplicationRuleDao.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认模板
|
||||
*/
|
||||
static async getDefault(): Promise<ApplicationRule> {
|
||||
let res: ApplicationRule;
|
||||
let idStr = await GlobalConfigDao.getByCode(DEFAULT_TEMPLETE_ID);
|
||||
if (idStr == null) {
|
||||
let templteList = await ApplicationRuleDao.getAll();
|
||||
if (templteList.length == 0) {
|
||||
res = new ApplicationRule("默认模板", "此模板为系统创建", "[]");
|
||||
await ApplicationRuleService.saveOrAdd(res);
|
||||
} else {
|
||||
res = templteList[0];
|
||||
}
|
||||
await GlobalConfigDao.addOne(new GlobalConfig(DEFAULT_TEMPLETE_ID, res.id.toString(), "默认模板id"));
|
||||
} else {
|
||||
let templteList = await ApplicationRuleDao.getAll();
|
||||
if (templteList.length == 0) {
|
||||
res = new ApplicationRule("默认模板", "此模板为系统创建", "[]");
|
||||
await ApplicationRuleService.saveOrAdd(res);
|
||||
await GlobalConfigDao.updateOne(DEFAULT_TEMPLETE_ID, res.id.toString());
|
||||
} else {
|
||||
let temp = templteList.filter(item => item.id.toString() === idStr);
|
||||
if (temp.length > 0) {
|
||||
res = temp[0];
|
||||
} else {
|
||||
res = templteList[0];
|
||||
await GlobalConfigDao.updateOne(DEFAULT_TEMPLETE_ID, res.id.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export default ApplicationRuleService;
|
||||
|
172
openRenamerBackend/service/AutoPlanService.ts
Normal file
172
openRenamerBackend/service/AutoPlanService.ts
Normal file
@ -0,0 +1,172 @@
|
||||
import config from '../config';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs-extra';
|
||||
|
||||
import AutoPlanConfigDto from '../entity/dto/AutoPlanConfigDto';
|
||||
import GlobalConfig from 'entity/po/GlobalConfig';
|
||||
import GlobalConfigService from './GlobalConfigService';
|
||||
import ErrorHelper from '../util/ErrorHelper';
|
||||
import TimeUtil from '../util/TimeUtil';
|
||||
import { isSub, isVideo } from '../util/MediaUtil';
|
||||
import log from '../util/LogUtil';
|
||||
const autoConfigCode = "autoConfig";
|
||||
let isReadDir = false;
|
||||
/**
|
||||
* 需要处理的文件
|
||||
*/
|
||||
let needDeal = [];
|
||||
/**
|
||||
* 文件夹变更记录。key:变更前的目录,value:变更后的目录.当needDeal为空时清理pathMap
|
||||
*/
|
||||
let pathMap = {};
|
||||
/**
|
||||
* 自动化配置
|
||||
*/
|
||||
let autoConfig: AutoPlanConfigDto = null;
|
||||
|
||||
|
||||
class AutoPlanService {
|
||||
|
||||
static async init() {
|
||||
let str = await GlobalConfigService.getVal(autoConfigCode);
|
||||
if (str != null) {
|
||||
} else {
|
||||
autoConfig = JSON.parse(str);
|
||||
}
|
||||
setTimeout(async () => {
|
||||
while (true) {
|
||||
try {
|
||||
await TimeUtil.sleep(1000);
|
||||
await work();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存配置
|
||||
*/
|
||||
static async saveAutoConfig(body: AutoPlanConfigDto): Promise<void> {
|
||||
if (isReadDir) {
|
||||
throw ErrorHelper.Error400("正在处理中,请稍后再试");
|
||||
}
|
||||
if (body.start) {
|
||||
if (body.paths.length == 0) {
|
||||
throw ErrorHelper.Error400("视频路径为空");
|
||||
}
|
||||
if (body.rules.length == 0) {
|
||||
throw ErrorHelper.Error400("规则为空");
|
||||
}
|
||||
}
|
||||
let configBody: GlobalConfig = {
|
||||
code: autoConfigCode,
|
||||
val: JSON.stringify(body),
|
||||
description: "自动化计划配置"
|
||||
};
|
||||
await GlobalConfigService.insertOrReplace(configBody);
|
||||
autoConfig = body;
|
||||
if (body.start && !body.ignoreExist) {
|
||||
setTimeout(async () => {
|
||||
isReadDir = true;
|
||||
try {
|
||||
await readDir(body.paths);
|
||||
} finally {
|
||||
isReadDir = false;
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取目录,获取文件列表
|
||||
* @param dirList 要读取的目录
|
||||
*/
|
||||
async function readDir(dirList: Array<string>): Promise<void> {
|
||||
if (!dirList) {
|
||||
return;
|
||||
}
|
||||
for (let i in dirList) {
|
||||
let pathStr = dirList[i];
|
||||
if (checkIgnore(path.basename(pathStr))) {
|
||||
continue;
|
||||
}
|
||||
if (!(await fs.stat(pathStr)).isDirectory()) {
|
||||
let fileName = path.basename(pathStr);
|
||||
let strs = fileName.split('.').reverse();
|
||||
if (strs.length > 0 && (isSub(strs[0]) || isVideo(strs[1]))) {
|
||||
needDeal.push(pathStr);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let childs = null;
|
||||
try {
|
||||
childs = await fs.readdir(pathStr);
|
||||
} catch (error) {
|
||||
console.warn("读取报错:{}", error);
|
||||
}
|
||||
if (childs != null) {
|
||||
await readDir(childs.map(item => path.join(pathStr, item)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件名是否被忽略的
|
||||
*/
|
||||
function checkIgnore(str: string): boolean {
|
||||
for (let i in autoConfig.ignorePaths) {
|
||||
if (str.match(autoConfig.ignorePaths[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始处理
|
||||
*/
|
||||
async function work() {
|
||||
if (autoConfig == null || !autoConfig.start) {
|
||||
return;
|
||||
}
|
||||
while (needDeal.length > 0) {
|
||||
let file = needDeal.pop();
|
||||
try {
|
||||
await dealOnePath(file);
|
||||
} catch (error) {
|
||||
log.error("处理文件报错:{}", file);
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理一个文件路径
|
||||
* @param filePath 路径
|
||||
* @returns
|
||||
*/
|
||||
async function dealOnePath(filePath: string) {
|
||||
let exist = await fs.pathExists(filePath);
|
||||
if (!exist) {
|
||||
return;
|
||||
}
|
||||
let basePath = null;
|
||||
for (let i in autoConfig.paths) {
|
||||
if (filePath.startsWith(autoConfig.paths[i])) {
|
||||
basePath = autoConfig.paths[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (basePath == null) {
|
||||
log.warn("无法识别的文件:{}", filePath);
|
||||
return;
|
||||
}
|
||||
let relativePath = filePath.replace(basePath, "");
|
||||
let pathArrs = relativePath.split(path.sep).filter(item => item.length > 0);
|
||||
|
||||
}
|
||||
|
||||
export default AutoPlanService;
|
@ -3,136 +3,193 @@ import * as path from 'path';
|
||||
import * as fs from 'fs-extra';
|
||||
|
||||
import ProcessHelper from '../util/ProcesHelper';
|
||||
import FileObj from '../vo/FileObj';
|
||||
import FileObj from '../entity/vo/FileObj';
|
||||
import SavePathDao from '../dao/SavePathDao';
|
||||
import SavePath from '../entity/dto/SavePath';
|
||||
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 {
|
||||
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.size, stat.birthtime.getTime(), stat.mtime.getTime()),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
(stat.isDirectory() ? folderList : files).push(
|
||||
new FileObj(fileList[index], pathStr, stat.isDirectory(), stat.size, 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.size, 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;
|
||||
|
33
openRenamerBackend/service/GlobalConfigService.ts
Normal file
33
openRenamerBackend/service/GlobalConfigService.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import GlobalConfigDao from '../dao/GlobalConfigDao';
|
||||
|
||||
import { DEFAULT_TEMPLETE_ID } from '../entity/constants/GlobalConfigCodeConstant';
|
||||
import GlobalConfig from '../entity/po/GlobalConfig';
|
||||
|
||||
|
||||
class GlobalConfigService {
|
||||
|
||||
static async getVal(code: string): Promise<string> {
|
||||
return GlobalConfigDao.getByCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多个配置
|
||||
* @param codes codes
|
||||
* @returns
|
||||
*/
|
||||
static async getMultVal(codes: Array<string>): Promise<any> {
|
||||
let re = {};
|
||||
(await GlobalConfigDao.getByMulCode(codes)).forEach(item => re[item.code] = item.val);
|
||||
return re;
|
||||
}
|
||||
|
||||
static async updateVal(code: string, val: string): Promise<void> {
|
||||
return GlobalConfigDao.updateOne(code, val);
|
||||
}
|
||||
|
||||
static async insertOrReplace(body: GlobalConfig): Promise<void> {
|
||||
return GlobalConfigDao.insertOrReplace(body);
|
||||
}
|
||||
}
|
||||
|
||||
export default GlobalConfigService;
|
52
openRenamerBackend/service/QbService.ts
Normal file
52
openRenamerBackend/service/QbService.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import QbConfigDto from "../entity/dto/QbConfigDto";
|
||||
import {tryLogin, get, post, updateQbInfo, getQbInfo} from '../util/QbApiUtil';
|
||||
import GlobalConfigService from "./GlobalConfigService";
|
||||
import GlobalConfig from "../entity/po/GlobalConfig";
|
||||
import BtListItemDto from "../entity/dto/BtListItemDto";
|
||||
|
||||
class QbService {
|
||||
|
||||
/**
|
||||
* 保存地址
|
||||
* @param body
|
||||
*/
|
||||
static async saveAddress(body: QbConfigDto): Promise<QbConfigDto> {
|
||||
if (body.address.endsWith("/")) {
|
||||
body.address = body.address.substring(0, body.address.length - 1);
|
||||
}
|
||||
await GlobalConfigService.insertOrReplace(new GlobalConfig("qbConfig", JSON.stringify(body), "qb config"));
|
||||
updateQbInfo(body);
|
||||
body.valid = await tryLogin();
|
||||
body.version = body ? (await get("/app/version", null)) : null;
|
||||
return body;
|
||||
}
|
||||
a
|
||||
/**
|
||||
* 获取当前配置
|
||||
*/
|
||||
static async getConfig(): Promise<QbConfigDto> {
|
||||
return getQbInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* get torrents list from qb
|
||||
*/
|
||||
static async getBtList(): Promise<Array<BtListItemDto>> {
|
||||
let res = await get("/api/v2/torrents/info?category=&sort=added_on", null);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
static async init() {
|
||||
let config = await GlobalConfigService.getVal("qbConfig");
|
||||
let qbInfo: QbConfigDto = config == null ? {} : JSON.parse(config);
|
||||
updateQbInfo(qbInfo);
|
||||
qbInfo.valid = await tryLogin();
|
||||
qbInfo.version = qbInfo.valid ? (await get("/app/version", null)) : null;
|
||||
return qbInfo;
|
||||
}
|
||||
}
|
||||
|
||||
export default QbService;
|
@ -2,9 +2,9 @@ import config from '../config';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs-extra';
|
||||
|
||||
import FileObj from '../vo/FileObj';
|
||||
import RuleObj from '../vo/RuleObj';
|
||||
import RuleInterface from '../vo/rules/RuleInterface';
|
||||
import FileObj from '../entity/vo/FileObj';
|
||||
import RuleObj from '../entity/vo/RuleObj';
|
||||
import RuleInterface from '../entity/bo/rules/RuleInterface';
|
||||
|
||||
|
||||
class RenamerService {
|
||||
@ -14,10 +14,10 @@ class RenamerService {
|
||||
for (let i in fileList) {
|
||||
let obj = fileList[i];
|
||||
ruleObjs.forEach(item => (item.data as RuleInterface).deal(obj));
|
||||
if (newNameSet.has(obj.name)) {
|
||||
if (newNameSet.has(obj.path + obj.name)) {
|
||||
obj.errorMessage = "重名";
|
||||
}
|
||||
newNameSet.add(obj.name);
|
||||
newNameSet.add(obj.path + obj.name);
|
||||
}
|
||||
return fileList;
|
||||
}
|
||||
@ -27,6 +27,9 @@ class RenamerService {
|
||||
let old = fileList[i];
|
||||
let oldPath = path.join(fileList[i].path, fileList[i].name);
|
||||
let newPath = path.join(changedFileList[i].path, changedFileList[i].name);
|
||||
if (oldPath === newPath) {
|
||||
continue;
|
||||
}
|
||||
if ((await fs.pathExists(newPath))) {
|
||||
throw new Error("此路径已存在:" + newPath);
|
||||
}
|
||||
|
6
openRenamerBackend/sqls/v003_新增默认模板.sql
Normal file
6
openRenamerBackend/sqls/v003_新增默认模板.sql
Normal file
@ -0,0 +1,6 @@
|
||||
CREATE TABLE global_config (
|
||||
code TEXT(40),
|
||||
val TEXT(200),
|
||||
description TEXT(100) DEFAULT (''),
|
||||
CONSTRAINT global_config_PK PRIMARY KEY (code)
|
||||
);
|
19
openRenamerBackend/sqls/v004_新增记录表.sql
Normal file
19
openRenamerBackend/sqls/v004_新增记录表.sql
Normal file
@ -0,0 +1,19 @@
|
||||
-- 记录已处理过的路径
|
||||
CREATE TABLE dealed_file_path (
|
||||
key_str TEXT(32) NOT NULL,
|
||||
"path" TEXT(200) NOT NULL,
|
||||
CONSTRAINT dealed_file_path_PK PRIMARY KEY (key_str)
|
||||
);
|
||||
|
||||
CREATE TABLE auto_deal_history (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
createTime INTEGER NOT NULL,
|
||||
oldName TEXT(200) NOT NULL,
|
||||
newName TEXT(200) NOT NULL,
|
||||
-- 说明
|
||||
comment TEXT(200) NOT NULL,
|
||||
-- 1:文件重命名,2:剧集下无季文件夹,自动创建;3:操作失败
|
||||
"type" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX auto_deal_history_createTime_IDX ON auto_deal_history (createTime);
|
1
openRenamerBackend/sqls/v005_新增推荐规则.sql
Normal file
1
openRenamerBackend/sqls/v005_新增推荐规则.sql
Normal file
@ -0,0 +1 @@
|
||||
INSERT INTO application_rule (createdDate, updatedDate, name, comment, content ) VALUES (1669648328180, 1678279879110, '推荐剧集模板', '此模板为系统创建12121212', '[{"type":"delete","message":"删除:全部删除","data":{"type":"deleteAll","start":{"type":"location","value":"1"},"end":{"type":"location","value":"1"},"ignorePostfix":true},"checked":false},{"type":"auto","message":"自动识别:\"剧名/电影名识别\";","data":{"type":"name","frontAdd":"","endAdd":"","eNumWidth":2},"checked":false},{"type":"auto","message":"自动识别:\"季号识别\";前缀添加:.s","data":{"type":"season","frontAdd":".s","endAdd":"","eNumWidth":2},"checked":false},{"type":"auto","message":"自动识别:\"集数识别\";集数宽度:3;前缀添加:e","data":{"type":"eNum","frontAdd":"e","endAdd":"","eNumWidth":3},"checked":false},{"type":"auto","message":"自动识别:\"分辨率识别\";前缀添加:.","data":{"type":"resolution","frontAdd":".","endAdd":"","eNumWidth":2},"checked":false}]');
|
@ -1 +1 @@
|
||||
yarn config set registry=https://registry.npmmirror.com && yarn && node dist/index.js
|
||||
pnpm install --registry https://registry.npmmirror.com && tsc && node dist/index.js
|
||||
|
@ -5,9 +5,8 @@
|
||||
"module": "commonjs",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl":".",
|
||||
"baseUrl": ".",
|
||||
"rootDir": "./",
|
||||
"watch": false,
|
||||
"strict": true,
|
||||
"strictNullChecks": false,
|
||||
"esModuleInterop": true
|
||||
|
84
openRenamerBackend/util/MediaUtil.ts
Normal file
84
openRenamerBackend/util/MediaUtil.ts
Normal file
@ -0,0 +1,84 @@
|
||||
const path = require("path")
|
||||
const videoSet = new Set(["flv", 'avi', 'wmv', 'dat', 'vob', 'mpg', 'mpeg', 'mp4', '3gp', '3g2', 'mkv', 'rm', 'rmvb', 'mov', 'qt', 'ogg', 'ogv', 'oga', 'mod']);
|
||||
|
||||
/**
|
||||
* 判断文件后缀是否为视频类型
|
||||
* @param str 文件后缀
|
||||
*/
|
||||
export function isVideo(str: string) {
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
return videoSet.has(str.toLowerCase());
|
||||
}
|
||||
|
||||
const subSet = new Set(['sub', 'sst', 'son', 'srt', 'ssa', 'ass', 'smi', 'psb', 'pjs', 'stl', 'tts', 'vsf', 'zeg']);
|
||||
|
||||
/**
|
||||
* 判断文件是否为字幕文件
|
||||
* @param str 文件后缀
|
||||
*/
|
||||
export function isSub(str: string) {
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
return subSet.has(str.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断文件是否为字幕文件
|
||||
* @param str 文件后缀
|
||||
*/
|
||||
export function isNfo(str: string) {
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
return "nfo" == str;
|
||||
}
|
||||
|
||||
let pattern1 = new RegExp(/s(eason)?\.?(\d+)/);
|
||||
let pattern2 = new RegExp(/(\d+)/);
|
||||
let pattern3 = new RegExp(/([一二三四五六七八九十]+)/);
|
||||
let chineseNumMap = {
|
||||
"一": "1",
|
||||
"二": "2",
|
||||
"三": "3",
|
||||
"四": "4",
|
||||
"五": "5",
|
||||
"六": "6",
|
||||
"七": "7",
|
||||
"八": "8",
|
||||
"九": "9",
|
||||
"十": "1"
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 识别季号
|
||||
* @param name
|
||||
*/
|
||||
export function getSeason(name: string): string {
|
||||
name = name.replace(/[ ]+/, "").toLocaleLowerCase();
|
||||
let patternRes = name.match(pattern1);
|
||||
if (patternRes && patternRes[2]) {
|
||||
return patternRes[2];
|
||||
}
|
||||
patternRes = name.match(pattern2);
|
||||
if (patternRes && patternRes[1]) {
|
||||
return patternRes[1];
|
||||
}
|
||||
//中文支持
|
||||
patternRes = name.match(pattern3);
|
||||
if (patternRes && patternRes[1]) {
|
||||
let str = patternRes[1];
|
||||
let strs = str.split("");
|
||||
if (strs.length == 1) {
|
||||
return str == '十' ? "10" : chineseNumMap[str];
|
||||
} else if (strs.length == 2) {
|
||||
return strs[0] == '十' ? ("1" + chineseNumMap[strs[1]]) : chineseNumMap[strs[0]] + "0";
|
||||
} else if (strs.length == 3) {
|
||||
return chineseNumMap[strs[0]] + chineseNumMap[strs[2]];
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
import mysql from "mysql2/promise";
|
||||
import config from '../config';
|
||||
import * as fs from "fs-extra";
|
||||
import * as path from 'path';
|
||||
import log from '../util/LogUtil';
|
||||
|
||||
const HISTORY_NAME = "history.json";
|
||||
|
||||
interface Res {
|
||||
rows: any;
|
||||
fields: mysql.FieldPacket;
|
||||
}
|
||||
|
||||
class MysqlUtil {
|
||||
public static pool: mysql.Pool = null;
|
||||
|
||||
static async createPool(mysqlConfig: any) {
|
||||
MysqlUtil.pool = await mysql.createPool(mysqlConfig);
|
||||
let basePath = path.join(config.rootPath, "sqls");
|
||||
let hisPath = path.join(basePath, HISTORY_NAME);
|
||||
let history: Array<string>;
|
||||
if (fs.existsSync(hisPath)) {
|
||||
history = JSON.parse(await fs.readFile(hisPath, "utf-8"));
|
||||
} else {
|
||||
history = new Array();
|
||||
}
|
||||
//执行数据库
|
||||
let files = (await fs.readdir(basePath)).sort((a, b) => a.localeCompare(b)).filter(item => !(item === HISTORY_NAME));
|
||||
let error = null;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (history.indexOf(files[i]) > -1) {
|
||||
log.info("sql无需重复执行:", files[i]);
|
||||
continue;
|
||||
}
|
||||
let sqlLines = (await fs.readFile(path.join(basePath, files[i]), 'utf-8')).split(/[\r\n]/g);
|
||||
try {
|
||||
let sql = "";
|
||||
for (let j = 0; j < sqlLines.length; j++) {
|
||||
sql = sql + sqlLines[j];
|
||||
if (sqlLines[j].endsWith(";")) {
|
||||
await MysqlUtil.pool.execute(sql);
|
||||
sql = "";
|
||||
}
|
||||
}
|
||||
log.info("sql执行成功:", files[i]);
|
||||
history.push(files[i]);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
break;
|
||||
}
|
||||
}
|
||||
await fs.writeFile(hisPath, JSON.stringify(history));
|
||||
if (error != null) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async getRows(sql: string, params: Array<any>, connection: mysql.PoolConnection = null): Promise<Array<any>> {
|
||||
return (await MysqlUtil.execute(sql, params, connection)).rows;
|
||||
}
|
||||
|
||||
|
||||
static async getRow(sql: string, params: Array<any>, connection: mysql.PoolConnection = null): Promise<any> {
|
||||
let rows = (await MysqlUtil.execute(sql, params, connection)).rows;
|
||||
return rows.length > 0 ? rows[0] : null;
|
||||
}
|
||||
|
||||
static async getSingle(sql: string, params: Array<any>, connection: mysql.PoolConnection = null): Promise<any> {
|
||||
let rows = (await MysqlUtil.execute(sql, params, connection)).rows;
|
||||
if (rows.length == 0) {
|
||||
return null;
|
||||
}
|
||||
let row = rows[0];
|
||||
return row[Object.keys(row)[0]];
|
||||
}
|
||||
|
||||
|
||||
|
||||
static async execute(sql: string, params: Array<any>, connection: mysql.PoolConnection = null): Promise<Res> {
|
||||
let res: any = {};
|
||||
if (connection == null) {
|
||||
let [rows, fields] = await MysqlUtil.pool.query(sql, params);
|
||||
res['rows'] = fields === undefined ? null : rows;
|
||||
res['fields'] = fields === undefined ? rows : fields;
|
||||
} else {
|
||||
let [rows, fields] = await connection.query(sql, params);
|
||||
res['rows'] = rows;
|
||||
res['fields'] = fields;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static async test() {
|
||||
let connection = await MysqlUtil.pool.getConnection();
|
||||
connection.beginTransaction();
|
||||
connection.query(`insert into url value(6,"GET","asd","public")`);
|
||||
connection.query(`insert into url value(7,"GET","asd","public")`);
|
||||
await connection.commit();
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
MysqlUtil,
|
||||
Res,
|
||||
mysql
|
||||
}
|
83
openRenamerBackend/util/QbApiUtil.ts
Normal file
83
openRenamerBackend/util/QbApiUtil.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import {Method} from "axios";
|
||||
import axios from "axios";
|
||||
import querystring from "querystring";
|
||||
import QbConfigDto from "../entity/dto/QbConfigDto";
|
||||
import GlobalService from '../service/GlobalConfigService';
|
||||
|
||||
let qbInfo: QbConfigDto = null;
|
||||
let cookie: any = null;
|
||||
|
||||
export function updateQbInfo(info: QbConfigDto) {
|
||||
qbInfo = info;
|
||||
}
|
||||
|
||||
export function getQbInfo() {
|
||||
return qbInfo;
|
||||
}
|
||||
|
||||
export async function get(url: string, data: object) {
|
||||
return await request("get", url, data, null, false);
|
||||
}
|
||||
|
||||
export async function post(url: string, data: object, isForm = false) {
|
||||
return await request("post", url, null, data, isForm);
|
||||
}
|
||||
|
||||
async function request(method: Method, url: string, query: any, body: any, isForm = false) {
|
||||
if (!qbInfo.valid) {
|
||||
throw new Error("qbittorrent无法连接,请检查配置");
|
||||
}
|
||||
let isTryLogin = false;
|
||||
while (true) {
|
||||
let headers = {"Cookie": cookie};
|
||||
if (isForm) {
|
||||
headers['content-type'] = "multipart/form-data";
|
||||
} else if (method == "post") {
|
||||
headers['content-type'] = "application/json";
|
||||
}
|
||||
let res = await axios.request({
|
||||
baseURL: qbInfo.address,
|
||||
url: "/api/v2" + url,
|
||||
method,
|
||||
params: query,
|
||||
data: body,
|
||||
headers,
|
||||
});
|
||||
if (res.status == 200) {
|
||||
return res.data;
|
||||
}
|
||||
if (res.status == 403) {
|
||||
if (isTryLogin) {
|
||||
throw new Error("qb用户名密码设置有误");
|
||||
} else {
|
||||
await tryLogin();
|
||||
isTryLogin = true;
|
||||
}
|
||||
} else {
|
||||
throw new Error("请求报错:" + res.data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export async function tryLogin(): Promise<boolean> {
|
||||
if (qbInfo == null || qbInfo.address == null || qbInfo.address == "") {
|
||||
return false;
|
||||
}
|
||||
let body = {username: qbInfo.username, password: qbInfo.password};
|
||||
try {
|
||||
let res = await axios.post(qbInfo.address + `/api/v2/auth/login`, querystring.stringify(body), {
|
||||
headers: {"Content-Type": "application/x-www-form-urlencoded"}
|
||||
});
|
||||
let success = res.data.toLocaleLowerCase().indexOf('ok') > -1;
|
||||
if (success) {
|
||||
cookie = res.headers['set-cookie'];
|
||||
}
|
||||
qbInfo.valid = success;
|
||||
return success;
|
||||
} catch (error) {
|
||||
console.error("登录报错:", error);
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import sqlite3 from 'sqlite3';
|
||||
import { open, Database } from 'sqlite';
|
||||
import {open, Database} from 'sqlite';
|
||||
import config from '../config';
|
||||
import * as fs from "fs-extra";
|
||||
import * as path from 'path';
|
||||
@ -12,9 +12,9 @@ class SqliteHelper {
|
||||
public static pool: Database = null;
|
||||
|
||||
static async createPool() {
|
||||
let dataFolder = path.join(config.rootPath, "data");
|
||||
let dataFolder = config.dataPath;
|
||||
if (!fs.existsSync(dataFolder)) {
|
||||
fs.mkdir(dataFolder);
|
||||
await fs.mkdir(dataFolder);
|
||||
}
|
||||
SqliteHelper.pool = await open({
|
||||
filename: path.join(dataFolder, "database.db"),
|
||||
@ -26,7 +26,7 @@ class SqliteHelper {
|
||||
if (fs.existsSync(hisPath)) {
|
||||
history = JSON.parse(await fs.readFile(hisPath, "utf-8"));
|
||||
} else {
|
||||
history = new Array();
|
||||
history = [];
|
||||
}
|
||||
//执行数据库
|
||||
let files = (await fs.readdir(basePath)).sort((a, b) => a.localeCompare(b)).filter(item => !(item === HISTORY_NAME));
|
||||
@ -40,7 +40,7 @@ class SqliteHelper {
|
||||
try {
|
||||
let sql = "";
|
||||
for (let j = 0; j < sqlLines.length; j++) {
|
||||
sql = sql + sqlLines[j];
|
||||
sql = sql + " " + sqlLines[j];
|
||||
if (sqlLines[j].endsWith(";")) {
|
||||
await SqliteHelper.pool.run(sql);
|
||||
sql = "";
|
||||
|
344
openRenamerBackend/util/TranslateUtil.ts
Normal file
344
openRenamerBackend/util/TranslateUtil.ts
Normal file
@ -0,0 +1,344 @@
|
||||
// ToolGood.Words.Translate.js
|
||||
// 2020, Lin Zhijun, https://github.com/toolgood/ToolGood.Words
|
||||
// Licensed under the Apache License 2.0
|
||||
import {_s2t_s, _s2t_t, _t2hk_t, _t2hk_hk, _t2s_s, _t2tw_t, _t2s_t, _t2tw_tw} from './TranslateWord'
|
||||
|
||||
class TrieNode {
|
||||
Index = 0;
|
||||
Layer = 0;
|
||||
End = false;
|
||||
Char = '';
|
||||
Results = [];
|
||||
m_values = {};
|
||||
Failure = null;
|
||||
Parent = null;
|
||||
|
||||
public Add(c) {
|
||||
if (this.m_values[c] != null) {
|
||||
return this.m_values[c];
|
||||
}
|
||||
var node = new TrieNode();
|
||||
node.Parent = this;
|
||||
node.Char = c;
|
||||
this.m_values[c] = node;
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
public SetResults(index) {
|
||||
if (this.End == false) {
|
||||
this.End = true;
|
||||
}
|
||||
this.Results.push(index)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TrieNode2 {
|
||||
End = false;
|
||||
Results = [];
|
||||
m_values = {};
|
||||
minflag = 1;
|
||||
maxflag = 0;
|
||||
|
||||
public Add(c, node3) {
|
||||
if (typeof c !== 'number') {
|
||||
c = parseInt(c);
|
||||
}
|
||||
if (this.minflag > c) {
|
||||
this.minflag = c;
|
||||
}
|
||||
if (this.maxflag < c) {
|
||||
this.maxflag = c;
|
||||
}
|
||||
this.m_values[c] = node3;
|
||||
}
|
||||
|
||||
public SetResults(index) {
|
||||
if (this.End == false) {
|
||||
this.End = true;
|
||||
}
|
||||
if (this.Results.indexOf(index) == -1) {
|
||||
this.Results.push(index);
|
||||
}
|
||||
}
|
||||
|
||||
public HasKey(c) {
|
||||
return this.m_values[c] != undefined;
|
||||
}
|
||||
|
||||
public TryGetValue(c) {
|
||||
if (this.minflag <= c && this.maxflag >= c) {
|
||||
return this.m_values[c];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class WordsSearch {
|
||||
|
||||
|
||||
_first: TrieNode2 = null;
|
||||
_keywords = [];
|
||||
_others = [];
|
||||
|
||||
public SetKeywords(keywords) {
|
||||
this._keywords = keywords;
|
||||
let root = new TrieNode();
|
||||
|
||||
let allNodeLayer = {};
|
||||
for (let i = 0; i < this._keywords.length; i++) {
|
||||
let p = this._keywords[i];
|
||||
let nd = root;
|
||||
for (let j = 0; j < p.length; j++) {
|
||||
nd = nd.Add(p.charCodeAt(j));
|
||||
if (nd.Layer == 0) {
|
||||
nd.Layer = j + 1;
|
||||
if (allNodeLayer[nd.Layer]) {
|
||||
allNodeLayer[nd.Layer].push(nd)
|
||||
} else {
|
||||
allNodeLayer[nd.Layer] = [];
|
||||
allNodeLayer[nd.Layer].push(nd)
|
||||
}
|
||||
}
|
||||
}
|
||||
nd.SetResults(i);
|
||||
}
|
||||
|
||||
let allNode: TrieNode[] = [];
|
||||
allNode.push(root);
|
||||
for (let key in allNodeLayer) {
|
||||
let nds = allNodeLayer[key];
|
||||
for (let i = 0; i < nds.length; i++) {
|
||||
allNode.push(nds[i]);
|
||||
}
|
||||
}
|
||||
allNodeLayer = null;
|
||||
|
||||
for (let i = 1; i < allNode.length; i++) {
|
||||
let nd: TrieNode = allNode[i];
|
||||
nd.Index = i;
|
||||
let r = nd.Parent.Failure;
|
||||
let c = nd.Char;
|
||||
while (r != null && !r.m_values[c])
|
||||
r = r.Failure;
|
||||
if (r == null)
|
||||
nd.Failure = root;
|
||||
else {
|
||||
nd.Failure = r.m_values[c];
|
||||
for (let key2 in nd.Failure.Results) {
|
||||
if (nd.Failure.Results.hasOwnProperty(key2) == false) {
|
||||
continue;
|
||||
}
|
||||
let result = nd.Failure.Results[key2];
|
||||
nd.SetResults(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
root.Failure = root;
|
||||
|
||||
let allNode2 = [];
|
||||
for (let i = 0; i < allNode.length; i++) {
|
||||
allNode2.push(new TrieNode2());
|
||||
}
|
||||
for (let i = 0; i < allNode2.length; i++) {
|
||||
let oldNode = allNode[i];
|
||||
let newNode = allNode2[i];
|
||||
|
||||
for (let key in oldNode.m_values) {
|
||||
if (oldNode.m_values.hasOwnProperty(key) == false) {
|
||||
continue;
|
||||
}
|
||||
let index = oldNode.m_values[key].Index;
|
||||
newNode.Add(key, allNode2[index]);
|
||||
}
|
||||
for (let index = 0; index < oldNode.Results.length; index++) {
|
||||
let item = oldNode.Results[index];
|
||||
newNode.SetResults(item);
|
||||
}
|
||||
|
||||
oldNode = oldNode.Failure;
|
||||
while (oldNode != root) {
|
||||
for (let key in oldNode.m_values) {
|
||||
if (oldNode.m_values.hasOwnProperty(key) == false) {
|
||||
continue;
|
||||
}
|
||||
if (newNode.HasKey(key) == false) {
|
||||
let index = oldNode.m_values[key].Index;
|
||||
newNode.Add(key, allNode2[index]);
|
||||
}
|
||||
}
|
||||
for (let index = 0; index < oldNode.Results.length; index++) {
|
||||
let item = oldNode.Results[index];
|
||||
newNode.SetResults(item);
|
||||
}
|
||||
oldNode = oldNode.Failure;
|
||||
}
|
||||
}
|
||||
allNode = null;
|
||||
root = null;
|
||||
this._first = allNode2[0];
|
||||
}
|
||||
|
||||
public FindAll(text) {
|
||||
var ptr = null;
|
||||
var list = [];
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
var t = text.charCodeAt(i);
|
||||
var tn = null;
|
||||
if (ptr == null) {
|
||||
tn = this._first.TryGetValue(t);
|
||||
} else {
|
||||
tn = ptr.TryGetValue(t);
|
||||
if (!tn) {
|
||||
tn = this._first.TryGetValue(t);
|
||||
}
|
||||
}
|
||||
if (tn != null) {
|
||||
if (tn.End) {
|
||||
for (let j = 0; j < tn.Results.length; j++) {
|
||||
var item = tn.Results[j];
|
||||
var keyword = this._keywords[item];
|
||||
list.push({
|
||||
Keyword: keyword,
|
||||
Success: true,
|
||||
End: i,
|
||||
Start: i + 1 - this._keywords[item].length,
|
||||
Index: item,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
ptr = tn;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------
|
||||
|
||||
|
||||
//-----------------------
|
||||
var s2tSearch = null; // WordsSearch
|
||||
var t2sSearch = null;// WordsSearch
|
||||
var t2twSearch = null;// WordsSearch
|
||||
var tw2tSearch = null;// WordsSearch
|
||||
var t2hkSearch = null;// WordsSearch
|
||||
var hk2tSearch = null;// WordsSearch
|
||||
|
||||
/**
|
||||
* 转繁体中文
|
||||
* @param {any} text 原文本
|
||||
* @param {any} type 0、繁体中文,1、港澳繁体,2、台湾正体
|
||||
*/
|
||||
export function toTraditionalChinese(text: any, type: any) {
|
||||
if (type == undefined) {
|
||||
type = 0;
|
||||
}
|
||||
if (type > 2 || type < 0) {
|
||||
throw "type 不支持该类型";
|
||||
}
|
||||
|
||||
var s2t = GetWordsSearch(true, 0);
|
||||
text = TransformationReplace(text, s2t);
|
||||
if (type > 0) {
|
||||
var t2 = GetWordsSearch(true, type);
|
||||
text = TransformationReplace(text, t2);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转简体中文
|
||||
* @param {any} text 原文本
|
||||
* @param {any} srcType 0、繁体中文,1、港澳繁体,2、台湾正体
|
||||
*/
|
||||
export function toSimplifiedChinese(text: string, srcType: any) {
|
||||
if (srcType == undefined) {
|
||||
srcType = 0;
|
||||
}
|
||||
if (srcType > 2 || srcType < 0) {
|
||||
throw "srcType 不支持该类型";
|
||||
}
|
||||
if (srcType > 0) {
|
||||
var t2 = GetWordsSearch(false, srcType);
|
||||
text = TransformationReplace(text, t2);
|
||||
}
|
||||
var s2t = GetWordsSearch(false, 0);
|
||||
text = TransformationReplace(text, s2t);
|
||||
return text;
|
||||
}
|
||||
|
||||
function TransformationReplace(text: any, wordsSearch: any) {
|
||||
var ts = wordsSearch.FindAll(text);
|
||||
|
||||
var sb = "";
|
||||
var index = 0;
|
||||
while (index < text.length) {
|
||||
var t = null;
|
||||
var max = -1;
|
||||
for (var i = 0; i < ts.length; i++) {
|
||||
var f = ts[i];
|
||||
if (f.Start == index && f.End > max) {
|
||||
max = f.End;
|
||||
t = f;
|
||||
}
|
||||
}
|
||||
|
||||
if (t == null) {
|
||||
sb += text[index];
|
||||
index++;
|
||||
} else {
|
||||
sb += wordsSearch._others[t.Index]
|
||||
index = t.End + 1;
|
||||
}
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
function GetWordsSearch(s2t: boolean, srcType: number) {
|
||||
if (s2t) {
|
||||
if (srcType === 0) {
|
||||
if (s2tSearch == null) {
|
||||
s2tSearch = BuildWordsSearch(_s2t_s, _s2t_t);
|
||||
}
|
||||
return s2tSearch;
|
||||
} else if (srcType === 1) {
|
||||
if (t2hkSearch == null) {
|
||||
t2hkSearch = BuildWordsSearch(_t2hk_t, _t2hk_hk);
|
||||
}
|
||||
return t2hkSearch;
|
||||
} else if (srcType == 2) {
|
||||
if (t2twSearch == null) {
|
||||
t2twSearch = BuildWordsSearch(_t2tw_t, _t2tw_tw);
|
||||
}
|
||||
return t2twSearch;
|
||||
}
|
||||
}
|
||||
if (srcType == 0) {
|
||||
if (t2sSearch == null) {
|
||||
t2sSearch = BuildWordsSearch(_t2s_t, _t2s_s);
|
||||
}
|
||||
return t2sSearch;
|
||||
} else if (srcType == 1) {
|
||||
if (hk2tSearch == null) {
|
||||
hk2tSearch = BuildWordsSearch(_t2hk_hk, _t2hk_t);
|
||||
}
|
||||
return hk2tSearch;
|
||||
} else if (srcType == 2) {
|
||||
if (tw2tSearch == null) {
|
||||
tw2tSearch = BuildWordsSearch(_t2tw_tw, _t2tw_t);
|
||||
}
|
||||
return tw2tSearch;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function BuildWordsSearch(keywords: string[], toWords: any[]) {
|
||||
var wordsSearch = new WordsSearch();
|
||||
wordsSearch.SetKeywords(keywords);
|
||||
wordsSearch._others = toWords;
|
||||
return wordsSearch;
|
||||
}
|
8
openRenamerBackend/util/TranslateWord.ts
Normal file
8
openRenamerBackend/util/TranslateWord.ts
Normal file
File diff suppressed because one or more lines are too long
8
openRenamerBackend/util/ValUtil.ts
Normal file
8
openRenamerBackend/util/ValUtil.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* null to default
|
||||
* @param value
|
||||
* @param defaultVal
|
||||
*/
|
||||
export function nullToDefault(value: any, defaultVal: any): any {
|
||||
return value === undefined || value == null ? defaultVal : value;
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
import * as pathUtil from "path";
|
||||
export default class FileObj {
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 拓展名
|
||||
*/
|
||||
expandName: string;
|
||||
/**
|
||||
* 去掉拓展名后的名字
|
||||
*/
|
||||
realName: string;
|
||||
/**
|
||||
* 所属路径
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* 是否文件夹
|
||||
*/
|
||||
isFolder: boolean;
|
||||
/**
|
||||
* 重命名错误原因
|
||||
*/
|
||||
errorMessage: string;
|
||||
/**
|
||||
* 创建时间ms
|
||||
*/
|
||||
createdTime: number;
|
||||
/**
|
||||
* 更新时间ms
|
||||
*/
|
||||
updatedTime: number;
|
||||
|
||||
|
||||
constructor(name: string, path, isFolder, createdTime, updatedTime) {
|
||||
this.name = name;
|
||||
this.expandName = pathUtil.extname(name);
|
||||
if (this.expandName.length > 0) {
|
||||
this.realName = name.substring(0, name.lastIndexOf("."));
|
||||
} else {
|
||||
this.realName = name;
|
||||
}
|
||||
this.path = path;
|
||||
this.isFolder = isFolder;
|
||||
this.createdTime = createdTime;
|
||||
this.updatedTime = updatedTime;
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
import DeleteRule from "./rules/DeleteRule";
|
||||
import InsertRule from "./rules/InsertRule";
|
||||
import SerializationRule from "./rules/SerializationRule";
|
||||
|
||||
export default class RuleObj {
|
||||
type: string;
|
||||
message: string;
|
||||
/**
|
||||
* 具体参数
|
||||
*/
|
||||
data: any;
|
||||
|
||||
constructor(data: any) {
|
||||
this.type = data.type;
|
||||
this.message = data.message;
|
||||
switch (this.type) {
|
||||
case "delete":
|
||||
this.data = new DeleteRule(data.data);
|
||||
break;
|
||||
case "insert":
|
||||
this.data = new InsertRule(data.data);
|
||||
break;
|
||||
default:
|
||||
this.data = new SerializationRule(data.data);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
import RuleInterface from "./RuleInterface";
|
||||
import FileObj from "../FileObj";
|
||||
import path from 'path';
|
||||
|
||||
export default class DeleteRule implements RuleInterface {
|
||||
/**
|
||||
* 类别:deletePart:部分删除,deleteAll:全部删除
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* 部分删除时的开始信息
|
||||
*/
|
||||
start: DeleteRuleItem;
|
||||
/**
|
||||
* 部分删除时的结束信息
|
||||
|
||||
*/
|
||||
end: DeleteRuleItem;
|
||||
/**
|
||||
* 忽略拓展名,true:忽略,false:不忽略
|
||||
*/
|
||||
ignorePostfix: boolean;
|
||||
|
||||
constructor(data: any) {
|
||||
this.type = data.type;
|
||||
this.start = new DeleteRuleItem(data.start);
|
||||
this.end = new DeleteRuleItem(data.end);
|
||||
this.ignorePostfix = data.ignorePostfix;
|
||||
}
|
||||
|
||||
|
||||
|
||||
deal(file: FileObj): void {
|
||||
if (this.type === 'deleteAll') {
|
||||
file.realName = "";
|
||||
if (!this.ignorePostfix) {
|
||||
file.expandName = "";
|
||||
}
|
||||
} else {
|
||||
let str = file.realName + (this.ignorePostfix ? "" : file.expandName);
|
||||
let startIndex = this.start.calIndex(str);
|
||||
let endIndex = this.end.calIndex(str);
|
||||
if (startIndex < 0 || endIndex < 0) {
|
||||
return;
|
||||
}
|
||||
str = str.substring(0, startIndex) + str.substring(endIndex + 1);
|
||||
if (this.ignorePostfix) {
|
||||
file.realName = str;
|
||||
} else {
|
||||
file.expandName = path.extname(str);
|
||||
if (file.expandName.length > 0) {
|
||||
file.realName = str.substring(0, str.lastIndexOf("."));
|
||||
} else {
|
||||
file.realName = str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file.name = file.realName + file.expandName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DeleteRuleItem {
|
||||
/**
|
||||
* location:位置,text:文本,end:直到末尾
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* 对应的值
|
||||
*/
|
||||
value: string;
|
||||
|
||||
constructor(data: any) {
|
||||
this.type = data.type;
|
||||
this.value = data.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算位置
|
||||
*/
|
||||
calIndex(str: string): number {
|
||||
if (this.type === 'location') {
|
||||
return parseInt(this.value) - 1;
|
||||
} else if (this.type === 'text') {
|
||||
return str.indexOf(this.value);
|
||||
} else if (this.type === 'end') {
|
||||
return str.length - 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
import RuleInterface from "./RuleInterface";
|
||||
import FileObj from "../FileObj";
|
||||
import path from 'path';
|
||||
|
||||
|
||||
let pattern = new RegExp(/s(eason)?(\d+)/);
|
||||
export default class InsertRule implements RuleInterface {
|
||||
|
||||
/**
|
||||
* 插入内容
|
||||
*/
|
||||
insertContent: string;
|
||||
/**
|
||||
* 操作类别,front:前缀,backend:后缀,at:位置,replace:替换当前文件名
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* 当type为at,时的位置,从1开始
|
||||
*/
|
||||
atInput: number;
|
||||
/**
|
||||
* 当type为at,时的方向,true:从右到左,false:从左到右
|
||||
*/
|
||||
atIsRightToleft: boolean;
|
||||
/**
|
||||
* 忽略拓展名,true:忽略,false:不忽略
|
||||
*/
|
||||
ignorePostfix: boolean;
|
||||
/**
|
||||
自动识别季号
|
||||
*/
|
||||
autoSeason: boolean;
|
||||
|
||||
constructor(data: any) {
|
||||
this.insertContent = data.insertContent;
|
||||
this.type = data.type;
|
||||
this.atInput = data.atInput;
|
||||
this.atIsRightToleft = data.atIsRightToleft;
|
||||
this.ignorePostfix = data.ignorePostfix;
|
||||
this.autoSeason = data.autoSeason;
|
||||
}
|
||||
|
||||
|
||||
deal(file: FileObj): void {
|
||||
let str = this.ignorePostfix ? file.realName : file.name;
|
||||
let season = '';
|
||||
if (this.autoSeason) {
|
||||
let patternRes = path.basename(file.path).replace(/[ ]+/, "").toLocaleLowerCase().match(pattern);
|
||||
if (patternRes && patternRes[2]) {
|
||||
season = patternRes[2];
|
||||
}
|
||||
}
|
||||
switch (this.type) {
|
||||
case "front":
|
||||
str = this.insertContent + season + str;
|
||||
break;
|
||||
case "backend":
|
||||
str = str + this.insertContent + season;
|
||||
break;
|
||||
case "at":
|
||||
let index = this.atIsRightToleft ? str.length - this.atInput + 1 : this.atInput - 1;
|
||||
str = str.substring(0, index) + this.insertContent + season + str.substring(index);
|
||||
break;
|
||||
case "replace":
|
||||
str = this.insertContent + season;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (this.ignorePostfix) {
|
||||
file.realName = str;
|
||||
} else {
|
||||
file.expandName = path.extname(str);
|
||||
if (file.expandName.length > 0) {
|
||||
file.realName = str.substring(0, str.lastIndexOf("."));
|
||||
} else {
|
||||
file.realName = str;
|
||||
}
|
||||
}
|
||||
|
||||
file.name = file.realName + file.expandName;
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import FileObj from "../FileObj";
|
||||
|
||||
export default interface RuleInterface {
|
||||
|
||||
deal(file: FileObj): void;
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
import RuleInterface from "./RuleInterface";
|
||||
import FileObj from "../FileObj";
|
||||
import path from 'path';
|
||||
|
||||
export default class InsertRule implements RuleInterface {
|
||||
/**
|
||||
* 开始位置
|
||||
*/
|
||||
start: number;
|
||||
/**
|
||||
* 记录当前的值是多少
|
||||
*/
|
||||
currentIndex: number;
|
||||
/**
|
||||
* 增量
|
||||
*/
|
||||
increment: number;
|
||||
/**
|
||||
* 是否填充0
|
||||
*/
|
||||
addZero: boolean;
|
||||
/**
|
||||
* 填充后长度
|
||||
*/
|
||||
numLength: number;
|
||||
/**
|
||||
* 插入位置,front:前缀,backend:后缀,at:位置
|
||||
*/
|
||||
insertType: string;
|
||||
/**
|
||||
* 插入的位置
|
||||
*/
|
||||
insertValue: number;
|
||||
/**
|
||||
* 忽略拓展名
|
||||
*/
|
||||
ignorePostfix: boolean;
|
||||
|
||||
constructor(data: any) {
|
||||
this.start = data.start;
|
||||
this.currentIndex = data.start;
|
||||
this.increment = data.increment;
|
||||
this.addZero = data.addZero;
|
||||
this.numLength = data.numLength;
|
||||
this.insertType = data.insertType;
|
||||
this.insertValue = data.insertValue;
|
||||
this.ignorePostfix = data.ignorePostfix;
|
||||
}
|
||||
|
||||
deal(file: FileObj): void {
|
||||
let length = this.currentIndex.toString().length;
|
||||
let numStr = (this.addZero && this.numLength > length ? "0".repeat(this.numLength - length) : "") + this.currentIndex;
|
||||
let str = this.ignorePostfix ? file.realName : file.name;
|
||||
switch (this.insertType) {
|
||||
case "front":
|
||||
str = numStr + str;
|
||||
break;
|
||||
case "backend":
|
||||
str = str + numStr;
|
||||
break;
|
||||
case "at":
|
||||
str = str.substring(0, this.insertValue - 1) + numStr + str.substring(this.insertValue - 1);
|
||||
break;
|
||||
}
|
||||
this.currentIndex += this.increment;
|
||||
|
||||
if (this.ignorePostfix) {
|
||||
file.realName = str;
|
||||
} else {
|
||||
file.expandName = path.extname(str);
|
||||
if (file.expandName.length > 0) {
|
||||
file.realName = str.substring(0, str.lastIndexOf("."));
|
||||
} else {
|
||||
file.realName = str;
|
||||
}
|
||||
}
|
||||
|
||||
file.name = file.realName + file.expandName;
|
||||
}
|
||||
}
|
2
openRenamerFront/.gitignore
vendored
2
openRenamerFront/.gitignore
vendored
@ -22,3 +22,5 @@ pnpm-debug.log*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
.pnpm-store
|
12
openRenamerFront/jsconfig.json
Normal file
12
openRenamerFront/jsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"target": "ES6",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
@ -8,21 +8,21 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons": "^0.0.11",
|
||||
"axios": "^0.21.1",
|
||||
"core-js": "^3.6.5",
|
||||
"dayjs": "^1.10.7",
|
||||
"element-plus": "^2.2.5",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0-0"
|
||||
"@element-plus/icons-vue": "2.0.10",
|
||||
"axios": "1.6.0",
|
||||
"core-js": "3.35.0",
|
||||
"dayjs": "1.10.7",
|
||||
"element-plus": "2.2.25",
|
||||
"vue": "3.2.45",
|
||||
"vue-router": "4.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"less": "^3.0.4",
|
||||
"less-loader": "^5.0.0",
|
||||
"prettier": "^2.2.1"
|
||||
"@vue/cli-plugin-babel": "5.0.8",
|
||||
"@vue/cli-plugin-router": "5.0.8",
|
||||
"@vue/cli-service": "5.0.8",
|
||||
"@vue/compiler-sfc": "3.0.0",
|
||||
"less": "3.0.4",
|
||||
"less-loader": "5.0.0",
|
||||
"prettier": "2.2.1"
|
||||
}
|
||||
}
|
||||
|
8173
openRenamerFront/pnpm-lock.yaml
generated
Normal file
8173
openRenamerFront/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico"/>
|
||||
<title>renamer</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong
|
||||
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please
|
||||
enable it to
|
||||
continue.</strong
|
||||
>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
<!--<!– 页面PV –>-->
|
||||
<!--<div style="text-align: center;">-->
|
||||
<!-- <div id="qieziStatisticHtmlPost" style="display: none;">当前页面访问次数:<span id="qieziStatisticHtmlPostPv"></span>次 -->
|
||||
<!-- </div>-->
|
||||
<!--</div>-->
|
||||
<!--<!– 网站整体UV/PV –>-->
|
||||
<!--<div style="text-align: center;">-->
|
||||
<!-- <div id="qieziStatisticHtmlHostPv" style="display: none;">总访问次数:<span-->
|
||||
<!-- id="qieziStatisticHtmlHostPvValue"></span>次 -->
|
||||
<!-- </div>-->
|
||||
<!-- <div id="qieziStatisticHtmlHostUv" style="display: none;"> 总访客数:<span-->
|
||||
<!-- id="qieziStatisticHtmlHostUvValue"></span>人-->
|
||||
<!-- </div>-->
|
||||
<!--</div>-->
|
||||
<script>
|
||||
//设置上一节获取到的key
|
||||
window.qieziStatisticKey = "13ec82dd91294ae4a88b0d2cc6cbdf76";
|
||||
</script>
|
||||
<script src="https://qiezi.fleyx.com/qiezijs/1.0/qiezi_statistic.min.js" type="text/javascript" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,28 +1,85 @@
|
||||
<template>
|
||||
<div class="app">
|
||||
<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-sub-menu index="/download">
|
||||
<template #title>bt下载</template>
|
||||
<el-menu-item index="/download/center">下载中心</el-menu-item>
|
||||
<el-menu-item index="/download/config">配置</el-menu-item>
|
||||
</el-sub-menu> -->
|
||||
</el-menu>
|
||||
<div class="content">
|
||||
<router-view />
|
||||
<router-view/>
|
||||
</div>
|
||||
<div class="footer">版本:
|
||||
<el-tooltip effect="dark" content="点击查看更新记录" placement="top">
|
||||
<a href="https://blog.fleyx.com/blog/detail/20221130/#%E7%89%88%E6%9C%AC%E6%9B%B4%E6%96%B0%E8%AE%B0%E5%BD%95"
|
||||
target="_blank">
|
||||
{{ version }}</a>
|
||||
</el-tooltip>
|
||||
|
||||
<template v-if="latestVersion && showNewVersion">
|
||||
最新版本:
|
||||
<el-tooltip effect="dark" content="点击查看更新文档" placement="top">
|
||||
<a href="https://blog.fleyx.com/blog/detail/20221130/#%E7%89%88%E6%9C%AC%E6%9B%B4%E6%96%B0%E8%AE%B0%E5%BD%95"
|
||||
target="_blank">
|
||||
{{ latestVersion }}</a>
|
||||
</el-tooltip>
|
||||
|
||||
</template>
|
||||
开源地址:<a href="https://github.com/FleyX/open-renamer" target="_blank">open-renamer</a>
|
||||
<a href="https://github.com/FleyX/open-renamer/issues" target="_blank">反馈</a>
|
||||
</div>
|
||||
<div class="footer">版本:{{ version }} 开源地址:<a href="https://github.com/FleyX/open-renamer">open-renamer</a></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import httpUtil from "./utils/HttpUtil";
|
||||
|
||||
export default {
|
||||
name: "Home",
|
||||
data() {
|
||||
return {
|
||||
activeIndex: "dealCenter",
|
||||
version: "0.8",
|
||||
version: "1.8.0",
|
||||
latestVersion: null,
|
||||
activeIndex: location.pathname,
|
||||
showNewVersion: false
|
||||
};
|
||||
},
|
||||
async beforeCreate() {
|
||||
window.token = localStorage.getItem("token");
|
||||
window.isWindows = await httpUtil.get("/file/isWindows");
|
||||
},
|
||||
async created() {
|
||||
let token = localStorage.getItem("token");
|
||||
window.token = token;
|
||||
await httpUtil.get("/file/isWindows");
|
||||
//获取最新版本
|
||||
let config = await httpUtil.get("https://s3.fleyx.com/picbed/openRenamer/config.json");
|
||||
this.latestVersion = config.version;
|
||||
this.showNewVersion = checkVersion(this.version, this.latestVersion);
|
||||
|
||||
},
|
||||
async mounted() {
|
||||
console.log(this.$route);
|
||||
console.log(location);
|
||||
},
|
||||
};
|
||||
|
||||
function checkVersion(version, latestVersion) {
|
||||
let versions = version.split(".");
|
||||
let latestVersions = latestVersion.split('.');
|
||||
for (let i = 0; i < versions.length; i++) {
|
||||
if (i >= latestVersions.length) {
|
||||
return false;
|
||||
}
|
||||
let versionNum = parseInt(versions[i]);
|
||||
let latestVersionNum = parseInt(latestVersions[i]);
|
||||
if (versionNum !== latestVersionNum) {
|
||||
return versionNum < latestVersionNum;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@ -31,7 +88,9 @@ body {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
background-color: #e8e8e5;
|
||||
}
|
||||
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@ -45,8 +104,14 @@ body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
width: 80%;
|
||||
min-width: 800px;
|
||||
margin: 0 auto;
|
||||
background-color: white;
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 0 10px 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,4 +127,10 @@ body {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.head-text {
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
</style>
|
||||
|
@ -10,16 +10,21 @@
|
||||
|
||||
<div class="fileList">
|
||||
<div>
|
||||
<el-input style="display: inline-block; width: 150px" type="text" size="small" placeholder="关键词过滤" v-model="filterText" clearable />
|
||||
<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>
|
||||
<el-button v-if="curSavePathId" type="warning" @click="cancelSavePath" size="small">取消收藏</el-button>
|
||||
<el-button v-else type="primary" @click="showSave = true" size="small">收藏路径</el-button>
|
||||
<el-input style="display: inline-block; width: 150px" type="text" size="small" placeholder="关键词过滤"
|
||||
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>
|
||||
<el-button v-if="curSavePathId" type="warning" @click="cancelSavePath" size="small">取消收藏</el-button>
|
||||
<el-button v-else type="primary" @click="showSave = true" size="small">收藏路径</el-button>
|
||||
</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>{{ 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>
|
||||
|
||||
@ -28,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>
|
||||
@ -36,8 +41,12 @@
|
||||
|
||||
<script>
|
||||
import HttpUtil from "../utils/HttpUtil";
|
||||
import Bus from "../utils/Bus";
|
||||
|
||||
export default {
|
||||
name: "FileChose",
|
||||
//type:folder:选择文件夹。file:选择文件
|
||||
props: ["curChoosePath", "type"],
|
||||
data() {
|
||||
return {
|
||||
isWindows: false,
|
||||
@ -57,13 +66,24 @@ 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;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
async curChoosePath(newVal) {
|
||||
console.log("变更路径:", newVal);
|
||||
this.pathList = newVal;
|
||||
await this.breadcrumbClick(this.pathList.length - 1);
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
if (this.curChoosePath && this.curChoosePath.length > 0) {
|
||||
this.pathList = this.curChoosePath;
|
||||
}
|
||||
await this.breadcrumbClick(this.pathList.length - 1);
|
||||
await this.refreshSavePathList();
|
||||
Bus.$on("refreshSavePathList", this.refreshSavePathList);
|
||||
},
|
||||
|
||||
methods: {
|
||||
@ -77,15 +97,18 @@ export default {
|
||||
//点击面包蟹
|
||||
async breadcrumbClick(index) {
|
||||
this.loading = true;
|
||||
let path = this.createPath(index);
|
||||
let fileList = await HttpUtil.get("/file/query", {
|
||||
path: encodeURIComponent(path),
|
||||
showHidden: false,
|
||||
});
|
||||
fileList.forEach((item) => (item.checked = false));
|
||||
this.fileList = fileList;
|
||||
this.filterText = "";
|
||||
this.loading = false;
|
||||
try {
|
||||
let path = this.createPath(index);
|
||||
let fileList = await HttpUtil.get("/file/query", {
|
||||
path: encodeURIComponent(path),
|
||||
showHidden: false,
|
||||
});
|
||||
fileList.forEach((item) => (item.checked = false));
|
||||
this.fileList = fileList;
|
||||
this.filterText = "";
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
//文件列表点击
|
||||
@ -99,13 +122,13 @@ export default {
|
||||
},
|
||||
//全选
|
||||
selectAll(status) {
|
||||
this.filterFileList.filter((item) => !item.isFolder).forEach((item) => (item.checked = status));
|
||||
this.filterFileList.forEach((item) => (item.checked = status));
|
||||
},
|
||||
//根据index构建路径
|
||||
createPath(index) {
|
||||
console.log("当前路径为:", this.pathList);
|
||||
let path;
|
||||
if (index == -1) {
|
||||
if (index === -1) {
|
||||
path = "";
|
||||
this.pathList = [];
|
||||
} else {
|
||||
@ -117,36 +140,38 @@ export default {
|
||||
return path;
|
||||
},
|
||||
//点击确定
|
||||
submit() {
|
||||
let chosedFiles = this.fileList.filter((item) => item.checked);
|
||||
if (chosedFiles.length == 0) {
|
||||
this.$message({ message: "未选择文件", type: "warning" });
|
||||
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)));
|
||||
this.fileList.forEach((item) => (item.checked = false));
|
||||
this.fileList = [...this.fileList];
|
||||
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", JSON.parse(JSON.stringify(chosenFiles)));
|
||||
}
|
||||
this.filterText = "";
|
||||
|
||||
},
|
||||
//收藏路径
|
||||
async savePath() {
|
||||
let res = await HttpUtil.post("/file/path/save", null, { name: this.saveName, content: JSON.stringify(this.pathList) });
|
||||
this.$emit("refreshSavePathList");
|
||||
this.refreshSavePathList();
|
||||
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 });
|
||||
this.refreshSavePathList();
|
||||
this.$emit("refreshSavePathList");
|
||||
await HttpUtil.delete("/file/path/delete", {id: this.curSavePathId});
|
||||
Bus.$emit("refreshSavePathList");
|
||||
this.$message.success("操作成功");
|
||||
},
|
||||
changePath(item) {
|
||||
this.pathList = JSON.parse(item.content);
|
||||
this.breadcrumbClick(this.pathList.length - 1);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@ -155,6 +180,7 @@ export default {
|
||||
.main {
|
||||
height: 65vh;
|
||||
}
|
||||
|
||||
.fileList {
|
||||
padding: 1em;
|
||||
text-align: left;
|
||||
|
@ -3,13 +3,18 @@
|
||||
<el-menu style="width: 8em" mode="vertical" :default-active="currentIndex" @select="menuChange">
|
||||
<el-menu-item :disabled="editRule != null" index="insert">插入</el-menu-item>
|
||||
<el-menu-item :disabled="editRule != null" index="delete">删除</el-menu-item>
|
||||
<!-- <el-menu-item index="replace">替换</el-menu-item> -->
|
||||
<el-menu-item :disabled="editRule != null" index="serialization">序列化</el-menu-item>
|
||||
<el-menu-item :disabled="editRule != null" index="replace">替换</el-menu-item>
|
||||
<el-menu-item :disabled="editRule != null || isAutoPlan" index="serialization">序列化</el-menu-item>
|
||||
<el-menu-item :disabled="editRule != null" index="auto">自动识别</el-menu-item>
|
||||
<el-menu-item :disabled="editRule != null" index="translate">简繁转换</el-menu-item>
|
||||
</el-menu>
|
||||
<div class="rule">
|
||||
<insert-rule ref="rule" :editRule="editRule" v-if="currentIndex == 'insert'" />
|
||||
<delete-rule ref="rule" :editRule="editRule" v-else-if="currentIndex == 'delete'" />
|
||||
<serialization-rule ref="rule" :editRule="editRule" v-else-if="currentIndex == 'serialization'" />
|
||||
<insert-rule ref="rule" :editRule="editRule" v-if="currentIndex === 'insert'"/>
|
||||
<delete-rule ref="rule" :editRule="editRule" v-else-if="currentIndex === 'delete'"/>
|
||||
<replace-rule ref="rule" :editRule="editRule" v-else-if="currentIndex === 'replace'"/>
|
||||
<serialization-rule ref="rule" :editRule="editRule" v-else-if="currentIndex === 'serialization'"/>
|
||||
<auto-rule ref="rule" :editRule="editRule" v-else-if="currentIndex === 'auto'"/>
|
||||
<translate-rule ref="rule" :editRule="editRule" v-else-if="currentIndex === 'translate'"/>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center">
|
||||
@ -21,14 +26,19 @@
|
||||
import InsertRule from "./rules/InsertRule.vue";
|
||||
import DeleteRule from "./rules/DeleteRule.vue";
|
||||
import SerializationRule from "./rules/SerializationRule.vue";
|
||||
import AutoRule from "./rules/AutoRule";
|
||||
import ReplaceRule from "@/components/rules/ReplaceRule";
|
||||
import TranslateRule from '@/components/rules/TranslateRule.vue';
|
||||
|
||||
export default {
|
||||
components: { InsertRule, DeleteRule, SerializationRule },
|
||||
props: ["editRule"],
|
||||
components: {InsertRule, DeleteRule, SerializationRule, AutoRule, ReplaceRule, TranslateRule},
|
||||
props: ["editRule", "isAutoPlan"],
|
||||
emits: ["ruleAdd"],
|
||||
name: "Rule",
|
||||
data() {
|
||||
return {
|
||||
currentIndex: "insert",
|
||||
options: [{ label: "插入", value: "insert" }],
|
||||
options: [{label: "插入", value: "insert"}],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
|
22
openRenamerFront/src/components/Tips.vue
Normal file
22
openRenamerFront/src/components/Tips.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<el-tooltip effect="dark" :content="message" :placement="placement ? placement : 'top'">
|
||||
<el-icon>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { InfoFilled } from "@element-plus/icons-vue";
|
||||
import { ref, reactive, onMounted, computed, defineProps } from "vue";
|
||||
|
||||
defineProps({
|
||||
message: String,
|
||||
placement: String
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
121
openRenamerFront/src/components/rules/ApplicationRuleList.vue
Normal file
121
openRenamerFront/src/components/rules/ApplicationRuleList.vue
Normal file
@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div>
|
||||
<div style="text-align: right">
|
||||
<el-button type="primary" @click="showEditAddModal = true">新增模板</el-button>
|
||||
</div>
|
||||
<el-table :data="applicationRuleList" style="width: 100%">
|
||||
<el-table-column prop="createdDate" label="创建时间" width="130" :formatter="formatDateTime" />
|
||||
<el-table-column prop="updatedDate" label="更新时间" width="130" :formatter="formatDateTime" />
|
||||
<el-table-column prop="name" label="名称" width="180" />
|
||||
<el-table-column prop="comment" label="备注" />
|
||||
<el-table-column label="操作" width="250">
|
||||
<template #default="scope">
|
||||
<el-button text type="primary" style="margin-left: 0" size="small"
|
||||
@click="ruleTemplateAction('chose', scope.row)">选择</el-button>
|
||||
<el-button text type="primary" style="margin-left: 0" size="small"
|
||||
@click="ruleTemplateAction('edit', scope.row)">编辑</el-button>
|
||||
<el-button text type="warning" style="margin-left: 0" size="small"
|
||||
@click="ruleTemplateAction('delete', scope.row)">删除</el-button>
|
||||
<el-button v-if="defaultTemplateId != scope.row.id" text type="primary" size="small"
|
||||
@click="ruleTemplateAction('default', scope.row)">设为默认</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<el-dialog :title="curEdit != null ? '修改' : '新增模板'" v-model="showEditAddModal" width="40em" @close="closeAddEdit"
|
||||
append-to-body>
|
||||
<el-form-item label="名称">
|
||||
<el-input v-model="templateForm.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="templateForm.comment"></el-input>
|
||||
</el-form-item>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="saveTemplateDilalogShow = false">取消</el-button>
|
||||
<el-button type="primary" @click="templateSubmit">提交</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HttpUtil from "@/utils/HttpUtil";
|
||||
import dayjs from "dayjs";
|
||||
export default {
|
||||
name: "ApplicationRuleList",
|
||||
props: ["curId"],
|
||||
emits: ["templateUpdate"],
|
||||
data () {
|
||||
return {
|
||||
applicationRuleList: [],
|
||||
curEdit: null,
|
||||
defaultTemplateId: null,
|
||||
showEditAddModal: false,
|
||||
templateForm: {
|
||||
name: "",
|
||||
comment: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
async created () {
|
||||
await this.init();
|
||||
},
|
||||
methods: {
|
||||
async init () {
|
||||
this.defaultTemplateId = parseInt(await HttpUtil.get("/config/code?code=defaultTempleteId"));
|
||||
this.applicationRuleList = await HttpUtil.get("/applicationRule");
|
||||
},
|
||||
formatDateTime (row, column, value) {
|
||||
return dayjs(value).format("YYYY-MM-DD");
|
||||
},
|
||||
//操作
|
||||
async ruleTemplateAction (action, rowData) {
|
||||
if (action === "chose") {
|
||||
await this.$emit("templateUpdate", rowData);
|
||||
} else if (action === "default") {
|
||||
let body = { code: "defaultTempleteId", val: rowData.id.toString() };
|
||||
await HttpUtil.post("/config/update", null, body);
|
||||
this.defaultTemplateId = rowData.id;
|
||||
} else if (action === "edit") {
|
||||
this.curEdit = rowData;
|
||||
this.templateForm.name = rowData.name;
|
||||
this.templateForm.comment = rowData.comment;
|
||||
this.showEditAddModal = true;
|
||||
} else {
|
||||
if (this.curId == rowData.id) {
|
||||
this.$message({ message: "当前模板使用中,无法删除", type: "warning" });
|
||||
return;
|
||||
}
|
||||
await HttpUtil.delete("/applicationRule/" + rowData.id);
|
||||
await this.init();
|
||||
}
|
||||
},
|
||||
//表单提交
|
||||
async templateSubmit () {
|
||||
let body;
|
||||
if (this.curEdit) {
|
||||
body = JSON.parse(JSON.stringify(this.curEdit));
|
||||
body.name = this.templateForm.name;
|
||||
body.comment = this.templateForm.comment;
|
||||
} else {
|
||||
body = this.templateForm;
|
||||
body.content = "[]";
|
||||
}
|
||||
await HttpUtil.post("/applicationRule", null, body);
|
||||
await this.init();
|
||||
this.closeAddEdit();
|
||||
this.$message.success("操作成功");
|
||||
},
|
||||
closeAddEdit () {
|
||||
this.curEdit = null;
|
||||
this.showEditAddModal = false;
|
||||
this.templateForm = { name: "", templte: "" };
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
115
openRenamerFront/src/components/rules/AutoRule.vue
Normal file
115
openRenamerFront/src/components/rules/AutoRule.vue
Normal file
@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<span class="left">识别类型:</span>
|
||||
<div class="location">
|
||||
<el-radio v-for="item in radioList" :key="item.code" v-model="ruleObj.data.type" :label="item.code"
|
||||
>{{ item.label }}
|
||||
<el-tooltip effect="dark" :content="item.message" placement="top">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</el-radio>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex" v-if="ruleObj.data.type == 'eNum'">
|
||||
<div class="left">集数宽度:</div>
|
||||
<el-input-number :min="1" v-model="ruleObj.data.eNumWidth" />
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="left">前面追加:</div>
|
||||
<el-input v-model="ruleObj.data.frontAdd" placeholder="识别内容前面追加,未识别到不会追加" style="width: 20em" />
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="left">后面追加:</div>
|
||||
<el-input v-model="ruleObj.data.endAdd" placeholder="识别内容后面追加,未识别到不会追加" style="width: 20em" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { InfoFilled } from "@element-plus/icons-vue";
|
||||
export default {
|
||||
name: "AutoRule",
|
||||
props: ["editRule"],
|
||||
components: { InfoFilled },
|
||||
data() {
|
||||
return {
|
||||
radioList: [
|
||||
{
|
||||
label: "季号识别",
|
||||
message: '通过识别文件夹名称获取季号,放在插入文本最后,可识别"s1","s01","season 01","season01"等以s或season开头后接数字的',
|
||||
code: "season",
|
||||
},
|
||||
{
|
||||
label: "集数识别",
|
||||
message: "通过提取文件名称来提取集数,支持 E数字/e数字/(数字)/(数字)/.数字/-数字/纯数字 。优先级依次递减,如果存在多组纯数字,选择第一组",
|
||||
code: "eNum",
|
||||
},
|
||||
{
|
||||
label: "剧名/电影名识别",
|
||||
message:
|
||||
"如果父文件夹包含season字段,那么会从父文件夹的父文件夹名称中取剧名,否则将从父文件夹名称中取电影名。规则为从开头开始取,直到遇见第一个空格/./[等符号",
|
||||
code: "name",
|
||||
},
|
||||
{
|
||||
label: "分辨率识别",
|
||||
message: "通过文件名提取出分辨率,支持 数字P/数字p/1k/1K/2k/2K/4k/4K",
|
||||
code: "resolution",
|
||||
},
|
||||
],
|
||||
ruleObj: {
|
||||
type: "auto",
|
||||
message: "",
|
||||
data: {
|
||||
type: "",
|
||||
frontAdd: "",
|
||||
endAdd: "",
|
||||
eNumWidth: 3,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (this.editRule) {
|
||||
console.log(this.editRule);
|
||||
this.ruleObj = JSON.parse(JSON.stringify(this.editRule));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
exportObj() {
|
||||
if (this.ruleObj.data.type === "") {
|
||||
this.$message({ message: "请选择识别类型", type: "warning" });
|
||||
return null;
|
||||
}
|
||||
this.ruleObj.message = `自动识别:"${this.radioList.filter((item) => item.code === this.ruleObj.data.type)[0].label}";`;
|
||||
if (this.ruleObj.data.type === "eNum") {
|
||||
this.ruleObj.message += "集数宽度:" + this.ruleObj.data.eNumWidth + ";";
|
||||
}
|
||||
if (this.ruleObj.data.frontAdd.length > 0) {
|
||||
this.ruleObj.message += `前缀添加:${this.ruleObj.data.frontAdd}`;
|
||||
}
|
||||
if (this.ruleObj.data.endAdd.length > 0) {
|
||||
this.ruleObj.message += `后缀添加:${this.ruleObj.data.endAdd}`;
|
||||
}
|
||||
return this.ruleObj;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.flex {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
padding-top: 1em;
|
||||
|
||||
.left {
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
.location {
|
||||
justify-content: left;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -6,22 +6,30 @@
|
||||
<div>开始</div>
|
||||
<div class="line">
|
||||
<el-radio v-model="ruleObj.data.start.type" label="location" :disabled="deleteAll">位置:</el-radio>
|
||||
<el-input-number :min="1" size="small" :disabled="deleteAll" v-model="startIndex" />
|
||||
<el-input-number :min="1" size="small" :disabled="deleteAll" v-model="startIndex"/>
|
||||
</div>
|
||||
<div class="line">
|
||||
<el-radio v-model="ruleObj.data.start.type" label="text" :disabled="deleteAll">文本:</el-radio>
|
||||
<el-input v-model="startText" size="small" :disabled="deleteAll" />
|
||||
<el-input v-model="startText" size="small" :disabled="deleteAll"/>
|
||||
</div>
|
||||
<div class="line">
|
||||
<el-radio v-model="ruleObj.data.start.type" label="reg" :disabled="deleteAll">正则:</el-radio>
|
||||
<el-input v-model="startReg" size="small" :disabled="deleteAll"/>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-left: 4em">
|
||||
<div>结束</div>
|
||||
<div class="line">
|
||||
<el-radio v-model="ruleObj.data.end.type" label="location" :disabled="deleteAll">位置:</el-radio>
|
||||
<el-input-number size="small" :disabled="deleteAll" v-model="endIndex" />
|
||||
<el-input-number size="small" :disabled="deleteAll" v-model="endIndex"/>
|
||||
</div>
|
||||
<div class="line">
|
||||
<el-radio v-model="ruleObj.data.end.type" label="text" :disabled="deleteAll">文本:</el-radio>
|
||||
<el-input v-model="endText" size="small" :disabled="deleteAll" />
|
||||
<el-input v-model="endText" size="small" :disabled="deleteAll"/>
|
||||
</div>
|
||||
<div class="line">
|
||||
<el-radio v-model="ruleObj.data.end.type" label="reg" :disabled="deleteAll">正则:</el-radio>
|
||||
<el-input v-model="endReg" size="small" :disabled="deleteAll"/>
|
||||
</div>
|
||||
<div class="line">
|
||||
<el-radio v-model="ruleObj.data.end.type" label="end" :disabled="deleteAll">直到末尾</el-radio>
|
||||
@ -29,14 +37,18 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="ruleObj.data.start.type==='reg' || ruleObj.data.end.type==='reg'" class="flex">
|
||||
<div class="left">区分大小写:</div>
|
||||
<el-switch v-model="ruleObj.data.regI"/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="left">全部删除:</div>
|
||||
<el-switch v-model="deleteAll" @change="allDeleteChange" />
|
||||
<el-switch v-model="deleteAll" @change="allDeleteChange"/>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<div class="left">忽略拓展名:</div>
|
||||
<el-switch v-model="ruleObj.data.ignorePostfix" />
|
||||
<el-switch v-model="ruleObj.data.ignorePostfix"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -60,28 +72,36 @@ export default {
|
||||
value: "",
|
||||
},
|
||||
ignorePostfix: true,
|
||||
regI: false,//reg是否区分大小写
|
||||
},
|
||||
},
|
||||
startIndex: 1,
|
||||
endIndex: 1,
|
||||
startText: "",
|
||||
startReg: "",
|
||||
endText: "",
|
||||
endReg: "",
|
||||
deleteAll: false,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (this.editRule) {
|
||||
this.ruleObj = JSON.parse(JSON.stringify(this.editRule));
|
||||
if (this.ruleObj.data.type == "deletePart") {
|
||||
if (this.ruleObj.data.start.type == "location") {
|
||||
if (this.ruleObj.data.type === "deletePart") {
|
||||
if (this.ruleObj.data.start.type === "location") {
|
||||
this.startIndex = parseInt(this.ruleObj.data.start.value);
|
||||
} else {
|
||||
} else if (this.ruleObj.data.start.type === "text") {
|
||||
this.startText = this.ruleObj.data.start.value;
|
||||
}
|
||||
if (this.ruleObj.data.end.type == "location") {
|
||||
this.endIndex = parseInt(this.ruleObj.data.end.value);
|
||||
} else {
|
||||
this.startReg = this.ruleObj.data.start.value;
|
||||
}
|
||||
|
||||
if (this.ruleObj.data.end.type === "location") {
|
||||
this.endIndex = parseInt(this.ruleObj.data.end.value);
|
||||
} else if (this.ruleObj.data.end.type === "text") {
|
||||
this.endText = this.ruleObj.data.end.value;
|
||||
} else {
|
||||
this.endReg = this.ruleObj.data.end.value;
|
||||
}
|
||||
} else {
|
||||
this.deleteAll = true;
|
||||
@ -91,13 +111,15 @@ export default {
|
||||
methods: {
|
||||
exportObj() {
|
||||
if (this.ruleObj.data.type.length == 0) {
|
||||
this.$message({ message: "请填写完整", type: "warning" });
|
||||
this.$message({message: "请填写完整", type: "warning"});
|
||||
return null;
|
||||
}
|
||||
if (this.ruleObj.data.type == "deletePart") {
|
||||
if (this.ruleObj.data.type === "deletePart") {
|
||||
if (
|
||||
(this.ruleObj.data.start.type == "text" && this.startText.length == 0) ||
|
||||
(this.ruleObj.data.start.type == "text" && this.startText.length == 0)
|
||||
('text' === this.ruleObj.data.start.type && this.startText.length === 0) ||
|
||||
('text' === this.ruleObj.data.end.type && this.endText.length === 0) ||
|
||||
('reg' === this.ruleObj.data.start.type && this.startReg.length === 0) ||
|
||||
('reg' === this.ruleObj.data.end.type && this.endReg.length === 0)
|
||||
) {
|
||||
this.$message({
|
||||
message: "开始或者结束文本不能为空",
|
||||
@ -106,19 +128,20 @@ export default {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
this.ruleObj.data.start.value = this.ruleObj.data.start.type == "location" ? this.startIndex.toString() : this.startText;
|
||||
this.ruleObj.data.end.value = this.ruleObj.data.end.type == "location" ? this.endIndex.toString() : this.endText;
|
||||
let startType = this.ruleObj.data.start.type;
|
||||
this.ruleObj.data.start.value = startType === "location" ? this.startIndex.toString() : startType === 'text' ? this.startText : this.startReg;
|
||||
let endType = this.ruleObj.data.end.type;
|
||||
this.ruleObj.data.end.value = endType === "location" ? this.endIndex.toString() : endType === 'text' ? this.endText : this.endReg;
|
||||
let message = `删除:`;
|
||||
if (this.deleteAll) {
|
||||
message += "全部删除";
|
||||
} else {
|
||||
message += `从"${this.ruleObj.data.start.value}"到"${this.ruleObj.data.end.type == "untilEnd" ? "末尾" : this.ruleObj.data.end.value}"`;
|
||||
message += `从"${this.ruleObj.data.start.value}"到"${this.ruleObj.data.end.type === "end" ? "末尾" : this.ruleObj.data.end.value}"`;
|
||||
}
|
||||
this.ruleObj.message = message;
|
||||
return this.ruleObj;
|
||||
},
|
||||
allDeleteChange(val) {
|
||||
console.log(val);
|
||||
this.deleteAll = val;
|
||||
this.ruleObj.data.type = val ? "deleteAll" : "deletePart";
|
||||
},
|
||||
|
@ -23,16 +23,36 @@
|
||||
<div class="flex">
|
||||
<div class="left">季号识别:</div>
|
||||
<el-switch v-model="ruleObj.data.autoSeason" />
|
||||
通过识别文件夹名称获取季号,放在插入文本最后,可识别"s1","s01","season 01","season01"等以s或season开头后接数字的
|
||||
<el-tooltip effect="dark" :content="message1" placement="top">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="left">有效后缀:</div>
|
||||
<el-switch v-model="ruleObj.data.endFilter" />
|
||||
<template v-if="ruleObj.data.endFilter">
|
||||
<el-tag v-for="item in ruleObj.data.validEnd" closable :key="item" @close="deleteEnd(item)" text>{{ item }}</el-tag>
|
||||
<el-input v-if="validEndInputShow" v-model="validEndInput" style="width: 5em" size="small" @keyup.enter="validEndAdd" @blur="validEndAdd" />
|
||||
<el-button v-else class="button-new-tag ml-1" size="small" @click="validEndInputShow = true">+ 新增</el-button>
|
||||
</template>
|
||||
<el-tooltip effect="dark" :content="message2" placement="top">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { InfoFilled } from "@element-plus/icons-vue";
|
||||
export default {
|
||||
name: "InsertRule",
|
||||
props: ["editRule"],
|
||||
components: { InfoFilled },
|
||||
data() {
|
||||
return {
|
||||
message1: '通过识别文件夹名称获取季号,放在插入文本最后,可识别"s1","s01","season 01","season01"等以s或season开头后接数字的',
|
||||
message2: '开启本选项后,本规则只在后缀匹配时才会生效.(输入后缀不包含".")',
|
||||
validEndInputShow: false,
|
||||
validEndInput: "",
|
||||
ruleObj: {
|
||||
type: "insert",
|
||||
message: "",
|
||||
@ -43,6 +63,8 @@ export default {
|
||||
atIsRightToleft: false,
|
||||
ignorePostfix: true,
|
||||
autoSeason: false,
|
||||
endFilter: false,
|
||||
validEnd: ["srt", "ass"],
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -62,6 +84,14 @@ export default {
|
||||
this.ruleObj.message = `插入:"${this.ruleObj.data.insertContent}"`;
|
||||
return this.ruleObj;
|
||||
},
|
||||
validEndAdd() {
|
||||
this.ruleObj.data.validEnd.push(this.validEndInput);
|
||||
this.validEndInput = "";
|
||||
this.validEndInputShow = false;
|
||||
},
|
||||
deleteEnd(item) {
|
||||
this.ruleObj.data.validEnd.splice(this.ruleObj.data.validEnd.indexOf(item), 1);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
120
openRenamerFront/src/components/rules/ReplaceRule.vue
Normal file
120
openRenamerFront/src/components/rules/ReplaceRule.vue
Normal file
@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<span class="left">源:</span>
|
||||
<el-input style="width:20em" v-model="ruleObj.data.source" />
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="left">目标:</span>
|
||||
<el-input style="width:20em" v-model="ruleObj.data.target" />
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="left">正则模式:</div>
|
||||
<el-switch v-model="ruleObj.data.regFlag" />
|
||||
<el-tooltip effect="dark" :content="regTip" placement="right">
|
||||
<el-icon>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="left">区分大小写:</div>
|
||||
<el-switch v-model="ruleObj.data.regI" />
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="left">替换选项:</span>
|
||||
<div class="location">
|
||||
<el-radio v-for="item in radioList" :key="item.code" v-model="ruleObj.data.type" :label="item.code"
|
||||
>{{ item.label }}
|
||||
</el-radio>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="left">忽略拓展名:</div>
|
||||
<el-switch v-model="ruleObj.data.ignorePostfix"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { InfoFilled } from "@element-plus/icons-vue";
|
||||
import { nullToDefault } from "@/utils/ValUtil";
|
||||
|
||||
export default {
|
||||
name: "ReplaceRule",
|
||||
components: { InfoFilled },
|
||||
props: ["editRule"],
|
||||
data() {
|
||||
return {
|
||||
regTip: `开启支持js正则匹配,支持分组匹配,目标字符串支持模板#{groupN},N表示匹配到的第几组。比如#{group1}将被替换为匹配到的第一组数据`,
|
||||
radioList: [
|
||||
{
|
||||
label: "替换第一个",
|
||||
code: 1
|
||||
},
|
||||
{
|
||||
label: "替换最后一个",
|
||||
code: 2
|
||||
},
|
||||
{
|
||||
label: "全部替换",
|
||||
code: 3
|
||||
}
|
||||
],
|
||||
ruleObj: {
|
||||
type: "replace",
|
||||
message: "",
|
||||
data: {
|
||||
source: "",
|
||||
target: "",
|
||||
type: 1, //1:替换第一个,2:替换最后一个,3:全部替换
|
||||
ignorePostfix: true, //忽略拓展名
|
||||
regFlag: false, //正则模式
|
||||
regI: false //是否区分大小写
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (this.editRule) {
|
||||
console.log(this.editRule);
|
||||
this.ruleObj = JSON.parse(JSON.stringify(this.editRule));
|
||||
//兼容历史数据
|
||||
this.ruleObj.data.ignorePostfix = nullToDefault(this.ruleObj.data.ignorePostfix, true);
|
||||
this.ruleObj.data.regFlag = nullToDefault(this.ruleObj.data.regFlag, false);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
exportObj() {
|
||||
if (!this.ruleObj.data.source) {
|
||||
this.$message({ message: "源不能为空", type: "warning" });
|
||||
return null;
|
||||
}
|
||||
if (!this.ruleObj.data.type) {
|
||||
this.$message({ message: "请选择替换选项", type: "warning" });
|
||||
return null;
|
||||
}
|
||||
this.ruleObj.message = `替换:将${this.ruleObj.data.source}替换为${this.ruleObj.data.target};`
|
||||
+ this.radioList.filter(item => item.code === this.ruleObj.data.type)[0].label;
|
||||
return this.ruleObj;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.flex {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
padding-top: 1em;
|
||||
|
||||
.left {
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
.location {
|
||||
justify-content: left;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
214
openRenamerFront/src/components/rules/RuleBlock.vue
Normal file
214
openRenamerFront/src/components/rules/RuleBlock.vue
Normal file
@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<div class="main">
|
||||
<div class="menu">
|
||||
<el-button v-if="rules == undefined" type="warning" size="small" @click="block">禁用/启用</el-button>
|
||||
<el-button type="danger" size="small" @click="deleteRule">删除</el-button>
|
||||
<template v-if="rules == undefined">
|
||||
<el-button type="primary" size="small" @click="templateSubmit">保存</el-button>
|
||||
<el-button type="primary" size="small" @click="ruleTemplateShow = true">选择模板</el-button>
|
||||
</template>
|
||||
<template v-if="checkedRules.length == 1">
|
||||
<el-button type="primary" size="small" @click="editClick">
|
||||
<el-tooltip effect="dark" content="编辑规则" placement="top">
|
||||
<el-icon>
|
||||
<edit/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</el-button>
|
||||
<el-button type="primary" size="small" @click="move('top')">
|
||||
<el-tooltip effect="dark" content="上移规则" placement="top">
|
||||
<el-icon>
|
||||
<top/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</el-button>
|
||||
<el-button type="primary" size="small" @click="move('bottom')">
|
||||
<el-tooltip effect="dark" content="下移规则" placement="top"
|
||||
>
|
||||
<el-icon>
|
||||
<bottom/>
|
||||
</el-icon
|
||||
>
|
||||
</el-tooltip>
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
<div class="ruleBlock">
|
||||
<el-checkbox v-model="item.checked" v-for="(item, index) in ruleList" :key="index" @dblclick="editClick(item)">
|
||||
<s v-if="item.blocked">{{ item.message }}</s>
|
||||
<span v-else>{{ item.message }}</span>
|
||||
</el-checkbox>
|
||||
<div style="padding-top: 0.5em">
|
||||
<el-button type="primary" size="small" text @click="addRuleDialogShow = true">+ 新增规则</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog :title="editRule ? '编辑规则' : '新增规则'" v-model="addRuleDialogShow" width="70%"
|
||||
@close="ruleDialogClose">
|
||||
<rule :editRule="editRule" @ruleAdd="ruleAdd" v-if="addRuleDialogShow" :isAutoPlan="rules != undefined"/>
|
||||
</el-dialog>
|
||||
<el-dialog title="模板管理" v-model="ruleTemplateShow" width="70%">
|
||||
<application-rule-list v-if="ruleTemplateShow" :curId="chosedTemplate.id" @templateUpdate="templateUpdate"/>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Rule from "@/components/Rule";
|
||||
import ApplicationRuleList from "./ApplicationRuleList";
|
||||
import HttpUtil from "@/utils/HttpUtil";
|
||||
import {Top, Bottom, Edit} from "@element-plus/icons-vue";
|
||||
|
||||
export default {
|
||||
name: "RuleBlock",
|
||||
props: ["rules"],
|
||||
components: {
|
||||
Rule,
|
||||
ApplicationRuleList,
|
||||
Edit,
|
||||
Top,
|
||||
Bottom,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
addRuleDialogShow: false, //是否显示新增规则弹窗
|
||||
ruleTemplateShow: false, //是否显示选择规则模板弹窗
|
||||
ruleList: [],
|
||||
editRule: null, //当前编辑的规则
|
||||
chosedTemplate: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
//选中的规则
|
||||
checkedRules() {
|
||||
return this.ruleList.filter((item) => item.checked);
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
//如果外部传入了规则
|
||||
if (this.rules !== undefined) {
|
||||
this.ruleList = JSON.parse(JSON.stringify(this.rules));
|
||||
} else {
|
||||
this.chosedTemplate = await HttpUtil.get("/applicationRule/default");
|
||||
this.ruleList = JSON.parse(this.chosedTemplate.content);
|
||||
await this.ruleUpdate(false);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
rules: function (newVal, oldVal) {
|
||||
console.log("rules变化", newVal);
|
||||
this.ruleList = JSON.parse(JSON.stringify(newVal));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
//规则更新
|
||||
ruleUpdate(preview) {
|
||||
if (preview !== undefined && preview === false) {
|
||||
preview = false;
|
||||
} else {
|
||||
preview = true;
|
||||
}
|
||||
let temp = this.ruleList.filter((item) => !item.blocked);
|
||||
this.$emit("ruleUpdate", temp, preview);
|
||||
},
|
||||
//模板内容提交
|
||||
async templateSubmit() {
|
||||
this.chosedTemplate.content = JSON.stringify(this.ruleList);
|
||||
await HttpUtil.post("/applicationRule", null, this.chosedTemplate);
|
||||
this.$message.success("操作成功");
|
||||
},
|
||||
//切换模板
|
||||
async templateUpdate(newVal) {
|
||||
console.debug("新的模板:", newVal);
|
||||
this.ruleList = JSON.parse(newVal.content);
|
||||
this.chosedTemplate = newVal;
|
||||
this.ruleUpdate();
|
||||
this.ruleTemplateShow = false;
|
||||
},
|
||||
//新增规则
|
||||
async ruleAdd(data) {
|
||||
if (this.editRule != null) {
|
||||
let index = this.ruleList.indexOf(this.editRule);
|
||||
this.ruleList.splice(index, 1, data);
|
||||
this.editRule = null;
|
||||
} else {
|
||||
this.ruleList.push(data);
|
||||
}
|
||||
data.checked = false;
|
||||
this.ruleUpdate();
|
||||
this.addRuleDialogShow = false;
|
||||
},
|
||||
//禁用/启用
|
||||
async block() {
|
||||
this.ruleList
|
||||
.filter((item) => item.checked)
|
||||
.forEach((item) => {
|
||||
item.blocked = !item.blocked;
|
||||
item.checked = false;
|
||||
});
|
||||
await this.ruleUpdate();
|
||||
},
|
||||
//删除规则
|
||||
async deleteRule() {
|
||||
this.ruleList = this.ruleList.filter((item) => !item.checked);
|
||||
this.ruleUpdate();
|
||||
},
|
||||
//编辑规则
|
||||
editClick(rule) {
|
||||
this.editRule = rule && rule.data ? rule : this.checkedRules[0];
|
||||
this.addRuleDialogShow = true;
|
||||
},
|
||||
//移动规则
|
||||
async move(type) {
|
||||
let index = this.ruleList.indexOf(this.checkedRules[0]);
|
||||
let newIndex;
|
||||
if (type == "top") {
|
||||
if (index == 0) {
|
||||
return;
|
||||
}
|
||||
newIndex = index - 1;
|
||||
} else {
|
||||
if (index == this.ruleList.length - 1) {
|
||||
return;
|
||||
}
|
||||
newIndex = index + 1;
|
||||
}
|
||||
let temp = this.checkedRules[0];
|
||||
this.ruleList[index] = this.ruleList[newIndex];
|
||||
this.ruleList[newIndex] = temp;
|
||||
this.ruleList = [...this.ruleList];
|
||||
await this.ruleUpdate();
|
||||
},
|
||||
//规则弹窗关闭
|
||||
ruleDialogClose() {
|
||||
this.editRule = null;
|
||||
this.addRuleDialogShow = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.main {
|
||||
text-align: left;
|
||||
padding: 5px;
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ruleBlock {
|
||||
text-align: left;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.choseTemplate {
|
||||
text-align: center;
|
||||
padding-top: 2em;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -2,20 +2,20 @@
|
||||
<div class="flex">
|
||||
<span class="left">起始数:</span>
|
||||
<div class="right">
|
||||
<el-input-number :min="1" size="small" v-model="ruleObj.data.start" />
|
||||
<el-input-number :min="1" size="small" v-model="ruleObj.data.start"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="left">增量:</span>
|
||||
<div class="right">
|
||||
<el-input-number :min="1" size="small" v-model="ruleObj.data.increment" />
|
||||
<el-input-number :min="1" size="small" v-model="ruleObj.data.increment"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="left">填充0补足:</span>
|
||||
<div class="right">
|
||||
<el-switch v-model="ruleObj.data.addZero" />
|
||||
<el-input-number size="small" :min="1" :disabled="!ruleObj.data.addZero" v-model="ruleObj.data.numLength" />
|
||||
<el-switch v-model="ruleObj.data.addZero"/>
|
||||
<el-input-number size="small" :min="1" :disabled="!ruleObj.data.addZero" v-model="ruleObj.data.numLength"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
@ -24,21 +24,34 @@
|
||||
<el-radio style="margin-top: 1em" v-model="ruleObj.data.insertType" label="front">前缀</el-radio>
|
||||
<el-radio style="margin-top: 1em" v-model="ruleObj.data.insertType" label="backend">后缀</el-radio>
|
||||
<el-radio style="margin-top: 1em" v-model="ruleObj.data.insertType" label="at"
|
||||
>位置:<el-input-number size="small" :min="1" :disabled="ruleObj.data.insertType !== 'at'" v-model="ruleObj.data.insertValue" />
|
||||
>位置:
|
||||
<el-input-number size="small" :min="1" :disabled="ruleObj.data.insertType !== 'at'"
|
||||
v-model="ruleObj.data.insertValue"/>
|
||||
</el-radio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<div class="left">忽略拓展名:</div>
|
||||
<el-switch v-model="ruleObj.data.ignorePostfix" />
|
||||
<el-switch v-model="ruleObj.data.ignorePostfix"/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="left">拓展名分组:</div>
|
||||
<el-switch v-model="ruleObj.data.postfixGroup"/>
|
||||
<el-tooltip effect="dark" content="按照文件拓展名分别计数,方便多种类型文件同时生成序列" placement="right">
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {InfoFilled} from "@element-plus/icons-vue";
|
||||
|
||||
export default {
|
||||
name: "SerializationRule",
|
||||
|
||||
components: {InfoFilled},
|
||||
props: ["editRule"],
|
||||
data() {
|
||||
return {
|
||||
@ -53,6 +66,7 @@ export default {
|
||||
ignorePostfix: true,
|
||||
insertType: "front",
|
||||
insertValue: 1,
|
||||
postfixGroup: true
|
||||
},
|
||||
},
|
||||
};
|
||||
|
77
openRenamerFront/src/components/rules/TranslateRule.vue
Normal file
77
openRenamerFront/src/components/rules/TranslateRule.vue
Normal file
@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<span class="left">操作:</span>
|
||||
<div class="location">
|
||||
<el-radio v-model="ruleObj.data.type" :label="1">简体转繁体</el-radio>
|
||||
<el-radio v-model="ruleObj.data.type" :label="2">繁体转简体</el-radio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<span class="left">繁体类型:</span>
|
||||
<div class="location">
|
||||
<el-radio v-model="ruleObj.data.traditionalType" :label="0">繁体中文</el-radio>
|
||||
<el-radio v-model="ruleObj.data.traditionalType" :label="1">港澳繁体</el-radio>
|
||||
<el-radio v-model="ruleObj.data.traditionalType" :label="2">台湾正体</el-radio>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {InfoFilled} from "@element-plus/icons-vue";
|
||||
|
||||
const traTypeMap = {
|
||||
"0": "繁体中文",
|
||||
"1": "港澳繁体",
|
||||
"2": "台湾正体"
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "InsertRule",
|
||||
props: ["editRule"],
|
||||
components: {InfoFilled},
|
||||
data() {
|
||||
return {
|
||||
ruleObj: {
|
||||
type: "translate",
|
||||
message: "",
|
||||
data: {
|
||||
type: 1,
|
||||
traditionalType: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (this.editRule) {
|
||||
console.log(this.editRule);
|
||||
this.ruleObj = JSON.parse(JSON.stringify(this.editRule));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
exportObj() {
|
||||
this.ruleObj.message = `简繁转换:"${this.ruleObj.data.type === 1 ? '简体转繁体' : '繁体转简体'}",繁体类型:${traTypeMap[this.ruleObj.data.traditionalType]}`;
|
||||
return this.ruleObj;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.flex {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
padding-top: 1em;
|
||||
|
||||
.left {
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
.location {
|
||||
justify-content: left;
|
||||
//flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -7,8 +7,15 @@ const routes = [
|
||||
path: "/",
|
||||
name: "Home",
|
||||
component: Home,
|
||||
},
|
||||
{
|
||||
}, {
|
||||
path: "/auto",
|
||||
name: "Auto",
|
||||
component: () => import("@/views/auto/index"),
|
||||
}, {
|
||||
path: "/download/config",
|
||||
name: "downloadConfig",
|
||||
component: () => import("@/views/download/config/index"),
|
||||
}, {
|
||||
path: "/public/login",
|
||||
name: "login",
|
||||
component: Login,
|
||||
|
33
openRenamerFront/src/utils/Bus.js
Normal file
33
openRenamerFront/src/utils/Bus.js
Normal file
@ -0,0 +1,33 @@
|
||||
class Bus {
|
||||
|
||||
constructor() {
|
||||
|
||||
this.list = {
|
||||
}; // 收集订阅
|
||||
}
|
||||
// 订阅
|
||||
$on (name, fn) {
|
||||
|
||||
this.list[name] = this.list[name] || [];
|
||||
this.list[name].push(fn);
|
||||
}
|
||||
// 发布
|
||||
$emit (name, data) {
|
||||
|
||||
if (this.list[name]) {
|
||||
|
||||
this.list[name].forEach((fn) => {
|
||||
fn(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
// 取消订阅
|
||||
$off (name) {
|
||||
|
||||
if (this.list[name]) {
|
||||
|
||||
delete this.list[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
export default new Bus;
|
@ -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,
|
||||
};
|
||||
|
9
openRenamerFront/src/utils/ValUtil.js
Normal file
9
openRenamerFront/src/utils/ValUtil.js
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 空转default
|
||||
* @param val
|
||||
* @param defaultVal
|
||||
* @returns {*}
|
||||
*/
|
||||
export function nullToDefault(val, defaultVal) {
|
||||
return val === undefined || val == null ? defaultVal : val;
|
||||
}
|
164
openRenamerFront/src/views/auto/components/editForm.vue
Normal file
164
openRenamerFront/src/views/auto/components/editForm.vue
Normal file
@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form ref="configRuleRef" label-width="100px" :model="body" :rules="bodyRule">
|
||||
<input type="text" class="form-control" style="display: none" />
|
||||
<el-form-item label="剧集目录" prop="paths">
|
||||
<div style="text-align: left">
|
||||
<template v-for="(item, index) in body.paths" :key="item">
|
||||
<el-tag closable @close="closePath(index, 'folder')">{{ item }}</el-tag> <br />
|
||||
</template>
|
||||
<div style="display: flex; align-items: center">
|
||||
<el-button type="primary" link @click="showFolderDialog = true">+新增目录</el-button>
|
||||
<tips message="添加剧集的上级目录,此目录下的每一个文件夹都将被认为是一部剧" />
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="忽略文件">
|
||||
<div style="text-align: left; display: flex; align-items: center">
|
||||
<template v-for="(item, index) in body.ignorePaths" :key="item">
|
||||
<el-tag closable @close="closePath(index, 'ignore')">{{ item }}</el-tag> <br />
|
||||
</template>
|
||||
<el-input
|
||||
v-if="ignoreInputVisible"
|
||||
v-model="ignoreInput"
|
||||
class="ml-1 w-20"
|
||||
size="small"
|
||||
@keyup.enter="addIngoreFile"
|
||||
@blur="addIngoreFile"
|
||||
/>
|
||||
<el-button v-else class="button-new-tag ml-1" size="small" @click="ignoreInputVisible = true"> + 忽略 </el-button>
|
||||
<tips message="名字匹配的文件/文件夹将会忽略处理,支持js正则表达式" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="忽略特典">
|
||||
<el-switch v-model="body.ignoreSeason0" class="ml-2" />
|
||||
<tips message="season 0/特典通常命名不规范,建议手动处理" />
|
||||
</el-form-item>
|
||||
<el-form-item label="删除小文件">
|
||||
<el-switch v-model="body.deleteSmallVideo" class="ml-2" />
|
||||
<tips message="删除小于2M的视频文件,此类文件通常为广告" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规则" prop="rules">
|
||||
<RuleBlock :rules="body.rules" @ruleUpdate="ruleUpdate" />
|
||||
</el-form-item>
|
||||
<el-form-item label="忽略现有">
|
||||
<el-switch v-model="body.ignoreExist" class="ml-2" />
|
||||
<tips message="是否忽略现有文件.打开开关会忽略现有文件,关闭会重命名现有文件" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始任务">
|
||||
<el-switch v-model="body.start" class="ml-2" />
|
||||
<tips message="打开此开关,即开始自动化处理" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" @click="submit"> 保存自动化配置 </el-button>
|
||||
<el-dialog title="选择目录" v-model="showFolderDialog" width="70%">
|
||||
<file-chose ref="fileChose" type="folder" @folderChose="folderChose" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import FileChose from "@/components/FileChose.vue";
|
||||
import RuleBlock from "@/components/rules/RuleBlock.vue";
|
||||
import Tips from "@/components/Tips.vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { ref, reactive, onMounted } from "vue";
|
||||
import http from "@/utils/HttpUtil";
|
||||
|
||||
//配置中心数据
|
||||
let body = ref({
|
||||
paths: [],
|
||||
version: 1,
|
||||
ignoreSeason0: true,
|
||||
ignorePaths: [".*\.jpg", "tvshow.nfo", "season.nfo", "metadata"],
|
||||
deleteSmallVideo: true,
|
||||
rules: [],
|
||||
ignoreExist: false,
|
||||
start: false,
|
||||
});
|
||||
let bodyRule = reactive({
|
||||
paths: { type: "array", required: true, message: "目录不能为空", trigger: "change" },
|
||||
rules: { type: "array", required: true, message: "目录不能为空", trigger: "change" },
|
||||
});
|
||||
const configRuleRef = ref();
|
||||
let showFolderDialog = ref(false);
|
||||
let ignoreInputVisible = ref(false);
|
||||
let ignoreInput = ref("");
|
||||
|
||||
onMounted(async () => {
|
||||
let res = await http.post("/config/multCode", null, ["autoConfig", "firstUse"]);
|
||||
if (res.autoConfig == undefined && res.firstUse == undefined) {
|
||||
await http.post("/config/insertOrUpdate", null, { code: "firstUse", val: "1" });
|
||||
await ElMessageBox.alert("似乎是首次使用自动化,是否需要查看使用文档?", "提示", {
|
||||
confirmButtonText: "是",
|
||||
cancelButtonText: "否",
|
||||
showCancelButton: true,
|
||||
});
|
||||
alert("跳转到自动化使用文档");
|
||||
return;
|
||||
}
|
||||
if (res.autoConfig != undefined) {
|
||||
body.value = JSON.parse(res.autoConfig);
|
||||
}
|
||||
});
|
||||
|
||||
//新增文件夹
|
||||
async function folderChose(data) {
|
||||
if (body.value.paths.indexOf(data) > -1) {
|
||||
ElMessage({ type: "warning", message: "路径已存在" });
|
||||
return;
|
||||
}
|
||||
body.value.paths.push(data);
|
||||
showFolderDialog.value = false;
|
||||
}
|
||||
//新增忽略文件
|
||||
async function addIngoreFile() {
|
||||
if (body.value.ignorePaths.indexOf(ignoreInput.value) > -1) {
|
||||
ElMessage({ type: "warning", message: "名称已存在" });
|
||||
return;
|
||||
}
|
||||
if (ignoreInput.value.length > 0) {
|
||||
body.value.ignorePaths.push(ignoreInput.value);
|
||||
}
|
||||
ignoreInput.value = "";
|
||||
ignoreInputVisible.value = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
//删除路径
|
||||
async function closePath(index, type) {
|
||||
(type === "folder" ? body.value.paths : body.value.ignorePaths).splice(index, 1);
|
||||
}
|
||||
//更新规则
|
||||
function ruleUpdate(rules) {
|
||||
body.value.rules = rules;
|
||||
}
|
||||
//提交
|
||||
async function submit() {
|
||||
console.dir(configRuleRef.value);
|
||||
configRuleRef.value.validate(async (valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
await http.post("/autoPlan/save", null, body.value);
|
||||
ElMessage({ type: "success", message: "保存成功" });
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.item {
|
||||
display: flex;
|
||||
text-align: left;
|
||||
padding-bottom: 0.5em;
|
||||
|
||||
.left {
|
||||
width: 6em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
55
openRenamerFront/src/views/auto/index.vue
Normal file
55
openRenamerFront/src/views/auto/index.vue
Normal file
@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div>
|
||||
<edit-form />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from "vue";
|
||||
import editForm from "./components/editForm.vue";
|
||||
import http from "@/utils/HttpUtil";
|
||||
//表单
|
||||
const qbBody = reactive({
|
||||
address: "",
|
||||
username: "",
|
||||
password: "",
|
||||
});
|
||||
//配置中心数据
|
||||
let downloadConfig = reactive({});
|
||||
//qb是否可访问
|
||||
let qbReach = ref(true);
|
||||
let editInfo = ref(false);
|
||||
|
||||
const qbInfo = computed(() => {
|
||||
if (downloadConfig.qbAddress) {
|
||||
return downloadConfig.qbAddress + " 用户名:" + downloadConfig.qbUsername;
|
||||
} else {
|
||||
return "尚未配置";
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
downloadConfig = reactive(await http.post("/config/multCode", null, ["qbAddress", "qbUsername", "qbPassword"]));
|
||||
});
|
||||
|
||||
async function submit () {
|
||||
let res = await http.post("");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.item {
|
||||
display: flex;
|
||||
text-align: left;
|
||||
padding-bottom: 0.5em;
|
||||
|
||||
.left {
|
||||
width: 6em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
63
openRenamerFront/src/views/download/config/index.vue
Normal file
63
openRenamerFront/src/views/download/config/index.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div>配置qb</div>
|
||||
<el-form :model="data.qbConfig" label-width="8em">
|
||||
<el-form-item label="qb版本">
|
||||
{{ data.qbConfig.version ? data.qbConfig.version : "配置错误,无法访问" }}
|
||||
</el-form-item>
|
||||
<el-form-item label="访问地址">
|
||||
<el-input type="text" v-model="data.qbConfig.address" placeholder="例如:http://localhost:8080(仅支持v4.1及以上)"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户名">
|
||||
<el-input type="text" v-model="data.qbConfig.username" placeholder="qb访问用户名"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-input type="password" v-model="data.qbConfig.password" placeholder="qb访问密码"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="qb下载路径">
|
||||
<el-input type="text" v-model="data.qbConfig.qbDownloadPath" placeholder="qb下载路径(qb中选择的下载路径)"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="对应本系统路径">
|
||||
<el-input type="text" v-model="data.qbConfig.renameQbDownloadPath" placeholder="qb下载路径对应到本软件中的路径"/>
|
||||
</el-form-item>
|
||||
<div style="text-align: center">
|
||||
<el-button type="" @click="editInfo = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitQb">提交</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, reactive, onMounted, computed} from "vue";
|
||||
import http from "@/utils/HttpUtil";
|
||||
//表单
|
||||
const data = reactive({
|
||||
qbConfig: {},
|
||||
});
|
||||
//qb是否可访问
|
||||
let editInfo = ref(false);
|
||||
|
||||
onMounted(async () => {
|
||||
data.qbConfig = await http.get("/qb/config");
|
||||
});
|
||||
|
||||
async function submitQb() {
|
||||
data.qbConfig = await http.post("/qb/saveQbInfo", null, data.qbConfig);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.item {
|
||||
display: flex;
|
||||
text-align: left;
|
||||
padding-bottom: 0.5em;
|
||||
|
||||
.left {
|
||||
width: 6em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,43 +1,82 @@
|
||||
<template>
|
||||
<div v-loading="loading" element-loading-text="后台处理中,请稍候">
|
||||
<br />
|
||||
<el-button type="primary" @click="submit" size="default">重命名</el-button>
|
||||
<br /><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-button type="primary" size="small" @click="selectAllFiles">反选</el-button>
|
||||
<el-button type="danger" size="small" @click="deleteCheckedFiles">删除</el-button>
|
||||
收藏路径:<el-button v-for="item in savePathList" :key="item.id" @click="clickSavePath(item)" type="primary" text>{{ item.name }}</el-button>
|
||||
<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 style="margin-top: 5px">
|
||||
<el-button type="primary" size="small" @click="selectAllFiles">{{ allChecked ? "不选" : "全选" }}</el-button>
|
||||
<el-tooltip effect="dark" content="一键选中所有的非视频、字幕文件和小于5MB的视频文件" placement="bottom">
|
||||
<el-button type="success" size="small" @click="choseAdFile">一键选择</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/>
|
||||
</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-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">
|
||||
<!-- 左侧原始文件名 -->
|
||||
<div style="flex: 4">
|
||||
<div v-for="(item, index) in fileList" :key="index" class="oneLine">
|
||||
<el-checkbox v-model="item.checked" style="height: 1.2em">{{ item.name }}</el-checkbox>
|
||||
<div style="display: flex; align-items: center; padding-right: 4em">
|
||||
<ArrowDownBold
|
||||
style="width: 20px; padding-left: 10px; cursor: pointer"
|
||||
v-if="index < fileList.length - 1"
|
||||
@click.stop.prevent="moveIndex(index + 1, index)"
|
||||
/>
|
||||
<ArrowUpBold
|
||||
style="width: 20px; padding-left: 10px; cursor: pointer"
|
||||
v-if="index > 0"
|
||||
@click.stop.prevent="moveIndex(index - 1, index)"
|
||||
/>
|
||||
</div>
|
||||
<div style="width:50%;">
|
||||
<div v-for="(item, index) in showFileList" :key="index" class="oneLine">
|
||||
<el-checkbox class="oneLineText" style="width: 95%" v-model="item.checked">
|
||||
<el-tooltip show-after="300" style="color:white" effect="dark" :content="item.name" placement="top">
|
||||
<span class="oneLineText" style="width: 100%;display: inline-block">{{ item.name }}</span>
|
||||
</el-tooltip>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右边的预览文件名 -->
|
||||
<div style="flex: 4">
|
||||
<div v-for="(item, index) in changedFileList" :key="index" class="oneLine">
|
||||
<div style="flex: 4">{{ item.name }}</div>
|
||||
<div style="color: red; flex: 1">{{ item.errorMessage }}</div>
|
||||
<div style="width:50%">
|
||||
<div v-for="(item, index) in showChangedFileList" :key="index" class="oneLine">
|
||||
<div style="display:inline-block;width:72%;" class="oneLineText">
|
||||
<el-tooltip show-after="300" style="color:white" effect="dark" :content="item.name" placement="top">
|
||||
<span class="oneLineText" style="width: 100%;display: inline-block">{{ item.name }}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div style="display:inline-block;color: red; width: 25%">{{ item.errorMessage }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -45,27 +84,33 @@
|
||||
<!-- 新增文件弹窗 -->
|
||||
|
||||
<el-dialog title="新增文件" v-model="dialogVisible" width="70%">
|
||||
<file-chose ref="fileChose" :curSavePath="curSavePath" @addData="addData" @refreshSavePathList="refreshSavePathList" />
|
||||
<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 { ArrowDownBold, ArrowUpBold } from "@element-plus/icons";
|
||||
import {Top, Bottom, Edit} from "@element-plus/icons-vue";
|
||||
import HttpUtil from "../../utils/HttpUtil";
|
||||
import FileChose from "@/components/FileChose";
|
||||
import RuleBlock from "./components/RuleBlock.vue";
|
||||
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,
|
||||
ArrowDownBold,
|
||||
ArrowUpBold,
|
||||
RuleBlock,
|
||||
FileChose, RuleBlock, Top, Bottom, Tips, Edit
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -74,33 +119,55 @@ export default {
|
||||
ruleList: [], //当前生效的规则
|
||||
fileList: [], //选择的文件
|
||||
changedFileList: [], //执行修改后的文件
|
||||
needPreview: false, //需要点击预览
|
||||
applicationRule: null, //当前应用的应用规则模板
|
||||
savePathList: [], //收藏的路径列表
|
||||
curSavePath: null, //当前选择的收藏路径
|
||||
curChoosePath: null, //当前选择的收藏路径
|
||||
timer: null, //修改顺序计时器
|
||||
newName: "", //新的文件名
|
||||
currentEditFile: null,
|
||||
showNameEditDialog: false //显示编辑文件弹窗
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
allChecked() {
|
||||
return this.fileList.length > 0 && this.fileList.filter(item => item.checked).length === this.fileList.length;
|
||||
},
|
||||
showFileList() {
|
||||
return this.fileList.length > 500 ? this.fileList.slice(0, 500) : this.fileList;
|
||||
},
|
||||
showChangedFileList() {
|
||||
return this.changedFileList && this.changedFileList.length > 500 ? this.changedFileList.slice(0, 500) : this.changedFileList;
|
||||
}
|
||||
},
|
||||
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) {
|
||||
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, preview) {
|
||||
console.log(rules, preview);
|
||||
this.ruleList = rules;
|
||||
this.needPreview = true;
|
||||
await this.showResult();
|
||||
if (preview) {
|
||||
await this.showResult();
|
||||
}
|
||||
},
|
||||
//预览结果
|
||||
async showResult() {
|
||||
if (this.fileList.length > 500) {
|
||||
this.$message.info("文件数过多,仅展示前500个(不影响重命名)");
|
||||
}
|
||||
this.changedFileList = [];
|
||||
if (!this.checkRuleAndFile()) {
|
||||
return;
|
||||
}
|
||||
@ -111,7 +178,6 @@ export default {
|
||||
};
|
||||
this.changedFileList = await HttpUtil.post("/renamer/preview", null, body);
|
||||
this.fileList = [...this.fileList];
|
||||
this.needPreview = false;
|
||||
this.loading = false;
|
||||
},
|
||||
//提交
|
||||
@ -120,7 +186,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
if (this.changedFileList.filter((item) => item.errorMessage).length > 0) {
|
||||
this.$message({ message: "存在错误,无法执行操作", type: "error" });
|
||||
this.$message({message: "存在错误,无法执行操作", type: "error"});
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
@ -130,56 +196,118 @@ 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;
|
||||
},
|
||||
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() {
|
||||
this.fileList.forEach((item) => (item.checked = !item.checked));
|
||||
let checked = !this.allChecked;
|
||||
this.fileList.forEach((item) => (item.checked = checked));
|
||||
},
|
||||
//检查规则和文件
|
||||
checkRuleAndFile() {
|
||||
if (this.fileList.length == 0) {
|
||||
this.$message({ message: "请选择文件", type: "warning" });
|
||||
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(newIndex, index) {
|
||||
let temp = this.fileList[index];
|
||||
this.fileList[index] = this.fileList[newIndex];
|
||||
this.fileList[newIndex] = temp;
|
||||
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: "无法上移"});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (this.fileList.indexOf(temps[temps.length - 1]) == this.fileList.length - 1) {
|
||||
this.$message({type: "warning", message: "无法下移"});
|
||||
return;
|
||||
}
|
||||
temps = temps.reverse();
|
||||
}
|
||||
for (let i in temps) {
|
||||
let temp = temps[i];
|
||||
let index = this.fileList.indexOf(temp);
|
||||
let newIndex = index + (type == "top" ? -1 : 1);
|
||||
this.fileList[index] = this.fileList[newIndex];
|
||||
this.fileList[newIndex] = temp;
|
||||
}
|
||||
this.fileList = [...this.fileList];
|
||||
this.needPreview = true;
|
||||
await this.showResult();
|
||||
if (this.timer != null) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
this.timer = setTimeout(() => {
|
||||
this.showResult();
|
||||
this.timer = null;
|
||||
}, 1000);
|
||||
},
|
||||
showFileAdd() {
|
||||
this.dialogVisible = true;
|
||||
},
|
||||
//点击收藏路径
|
||||
clickSavePath(item) {
|
||||
async clickSavePath(item) {
|
||||
this.curChoosePath = JSON.parse(item.content);
|
||||
this.dialogVisible = true;
|
||||
},
|
||||
async deleteSavePath(item) {
|
||||
console.log(item);
|
||||
this.$nextTick(() => {
|
||||
this.$refs["fileChose"].changePath(item);
|
||||
});
|
||||
await HttpUtil.delete("/file/path/delete", {id: item.id});
|
||||
Bus.$emit("refreshSavePathList");
|
||||
},
|
||||
async refreshSavePathList() {
|
||||
this.savePathList = await HttpUtil.get("/file/path");
|
||||
},
|
||||
async choseAdFile() {
|
||||
this.fileList.forEach(item => item.checked = item.isAdFile);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -191,10 +319,10 @@ export default {
|
||||
function compareStr(a, b) {
|
||||
let an = a.length;
|
||||
let bn = b.length;
|
||||
for (let i = 0; i < an; ) {
|
||||
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) {
|
||||
@ -234,6 +362,11 @@ function readChar(a, i, n) {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.savePath {
|
||||
cursor: pointer;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.fileList {
|
||||
margin-top: 20px;
|
||||
text-align: left;
|
||||
@ -241,15 +374,29 @@ function readChar(a, i, n) {
|
||||
.fileBlock {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
|
||||
.el-checkbox__label {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
|
||||
.oneLine {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-top: 1px solid rgb(214, 212, 212);
|
||||
height: 1.5em;
|
||||
padding-top: 0.1em;
|
||||
padding-bottom: 0.1em;
|
||||
padding-right: 0.2em;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.oneLineText {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.oneFileName {
|
||||
|
@ -1,49 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table :data="applicationRuleList" style="width: 100%">
|
||||
<el-table-column prop="createdDate" label="创建时间" width="180" />
|
||||
<el-table-column prop="name" label="名称" width="180" />
|
||||
<el-table-column prop="comment" label="备注" />
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="scope">
|
||||
<el-button text type="primary" size="small" @click="ruleTemplateAction('chose', scope.row)">选择</el-button>
|
||||
<el-button text type="warning" size="small" @click="ruleTemplateAction('delete', scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HttpUtil from "../../../utils/HttpUtil";
|
||||
import dayjs from "dayjs";
|
||||
export default {
|
||||
name: "ApplicationRuleList",
|
||||
data() {
|
||||
return {
|
||||
applicationRuleList: [],
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
async init() {
|
||||
this.applicationRuleList = await HttpUtil.get("/applicationRule");
|
||||
this.applicationRuleList.forEach((item) => (item.createdDate = dayjs(item.createdDate).format("YYYY-MM-DD")));
|
||||
},
|
||||
//操作
|
||||
async ruleTemplateAction(action, rowData) {
|
||||
if (action === "chose") {
|
||||
await this.$emit("update:modelValue", rowData);
|
||||
await this.$emit("close");
|
||||
} else {
|
||||
await HttpUtil.delete("/applicationRule/" + rowData.id);
|
||||
await this.init();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
@ -1,175 +0,0 @@
|
||||
<template>
|
||||
<div class="main">
|
||||
<div class="menu">
|
||||
<span>应用规则</span>
|
||||
<el-button type="primary" size="small" @click="addRuleDialogShow = true">新增</el-button>
|
||||
<el-button type="primary" size="small" v-if="checkedRules.length == 1" @click="editClick">编辑</el-button>
|
||||
<el-button type="warning" size="small" @click="block">禁用/启用</el-button>
|
||||
<el-button type="danger" size="small" @click="deleteRule">删除</el-button>
|
||||
<el-button type="primary" size="small" v-if="chosedTemplate" @click="templateSubmit">保存规则</el-button>
|
||||
<el-button type="primary" size="small" v-if="chosedTemplate == null && ruleList.length > 0" @click="saveTemplateDilalogShow = true"
|
||||
>存为模板</el-button
|
||||
>
|
||||
</div>
|
||||
<div class="ruleBlock">
|
||||
<el-checkbox v-model="item.checked" v-for="(item, index) in ruleList" :key="index">
|
||||
<s v-if="item.blocked">{{ item.message }}</s>
|
||||
<span v-else>{{ item.message }}</span>
|
||||
</el-checkbox>
|
||||
<div v-if="ruleList.length == 0 && chosedTemplate == null" class="choseTemplate">
|
||||
<el-button type="primary" size="small" @click="ruleTemplateShow = true">选择模板</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 弹窗 -->
|
||||
<el-dialog title="新增规则" v-model="addRuleDialogShow" width="70%">
|
||||
<rule :editRule="editRule" @ruleAdd="ruleAdd" v-if="addRuleDialogShow" />
|
||||
</el-dialog>
|
||||
<el-dialog title="选择规则模板" v-model="ruleTemplateShow" width="70%">
|
||||
<application-rule-list v-if="ruleTemplateShow" v-model="chosedTemplate" @close="ruleTemplateShow = false" />
|
||||
</el-dialog>
|
||||
<el-dialog title="保存模板" v-model="saveTemplateDilalogShow" width="70%">
|
||||
<el-form-item label="名称">
|
||||
<el-input v-model="templateForm.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="templateForm.comment"></el-input>
|
||||
</el-form-item>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="saveTemplateDilalogShow = false">取消</el-button>
|
||||
<el-button type="primary" @click="templateSubmit">提交</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Rule from "../../../components/Rule";
|
||||
import ApplicationRuleList from "./ApplicationRuleList";
|
||||
import HttpUtil from "../../../utils/HttpUtil";
|
||||
export default {
|
||||
name: "RuleBlock",
|
||||
components: {
|
||||
Rule,
|
||||
ApplicationRuleList,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
addRuleDialogShow: false, //是否显示新增规则弹窗
|
||||
ruleTemplateShow: false, //是否显示选择规则模板弹窗
|
||||
saveTemplateDilalogShow: false, //是否显示保存模板弹窗
|
||||
ruleList: [],
|
||||
editRule: null, //当前编辑的规则
|
||||
chosedTemplate: null,
|
||||
templateForm: {
|
||||
name: "",
|
||||
comment: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
//选中的规则
|
||||
checkedRules() {
|
||||
return this.ruleList.filter((item) => item.checked);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
chosedTemplate(newVal, oldVal) {
|
||||
this.ruleList = JSON.parse(newVal.content);
|
||||
this.ruleUpdate();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
//规则更新
|
||||
ruleUpdate() {
|
||||
this.$emit(
|
||||
"ruleUpdate",
|
||||
this.ruleList.filter((item) => !item.blocked)
|
||||
);
|
||||
},
|
||||
//保存还是更新模板
|
||||
saveOrUpdateTemplate() {
|
||||
if (this.chosedTemplate != null) {
|
||||
this.templateSubmit();
|
||||
} else {
|
||||
this.saveTemplateDilalogShow = true;
|
||||
}
|
||||
},
|
||||
//模板内容提交
|
||||
async templateSubmit() {
|
||||
let body;
|
||||
if (this.chosedTemplate) {
|
||||
this.chosedTemplate.content = JSON.stringify(this.ruleList);
|
||||
body = this.chosedTemplate;
|
||||
} else {
|
||||
body = {
|
||||
name: this.templateForm.name,
|
||||
comment: this.templateForm.comment,
|
||||
content: JSON.stringify(this.ruleList),
|
||||
};
|
||||
}
|
||||
this.chosedTemplate = await HttpUtil.post("/applicationRule", null, body);
|
||||
this.saveTemplateDilalogShow = false;
|
||||
this.$message.success("操作成功");
|
||||
},
|
||||
//新增规则
|
||||
async ruleAdd(data) {
|
||||
if (this.editRule != null) {
|
||||
let index = this.ruleList.indexOf(this.editRule);
|
||||
this.ruleList.splice(index, 1, data);
|
||||
this.editRule = null;
|
||||
} else {
|
||||
this.ruleList.push(data);
|
||||
}
|
||||
data.checked = false;
|
||||
this.ruleUpdate();
|
||||
this.addRuleDialogShow = false;
|
||||
},
|
||||
//禁用/启用
|
||||
async block() {
|
||||
this.ruleList
|
||||
.filter((item) => item.checked)
|
||||
.forEach((item) => {
|
||||
item.blocked = !item.blocked;
|
||||
item.checked = false;
|
||||
});
|
||||
this.ruleUpdate();
|
||||
},
|
||||
//删除规则
|
||||
async deleteRule() {
|
||||
this.ruleList = this.ruleList.filter((item) => !item.checked);
|
||||
this.ruleUpdate();
|
||||
},
|
||||
//编辑规则
|
||||
editClick() {
|
||||
this.editRule = this.checkedRules[0];
|
||||
this.addRuleDialogShow = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.main {
|
||||
text-align: left;
|
||||
border: 1px solid black;
|
||||
padding: 5px;
|
||||
.menu {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
}
|
||||
.ruleBlock {
|
||||
text-align: left;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: baseline;
|
||||
}
|
||||
.choseTemplate {
|
||||
text-align: center;
|
||||
padding-top: 2em;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
devServer: {
|
||||
proxy: "http://localhost:8089",
|
||||
},
|
||||
devServer: {
|
||||
proxy: "http://localhost:8089",
|
||||
},
|
||||
publicPath: "./"
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user