Compare commits

..

38 Commits
1.5 ... main

Author SHA1 Message Date
51c6ec9986 Merge pull request 'dev' (#52) from dev into main
Reviewed-on: #52
2024-11-17 20:05:06 +08:00
fanxb
a0e0186c63 feat:应用版本号修改 2024-11-17 20:04:49 +08:00
fanxb
652105f534 deploy:版本号更新 2024-11-17 20:02:02 +08:00
fanxb
bbc4bfd52f feat:替换支持正则,修复删除bug 2024-11-17 19:57:08 +08:00
fanxb
f85890bda8 feat:删除规则支持正则表达式,同时位置支持负数(从末尾计算,-1表示到倒数第一个字符) 2024-11-07 23:18:38 +08:00
67f542fe01 Merge pull request 'deploy:版本号更新' (#51) from dev into main
Reviewed-on: #51
2024-10-30 21:49:06 +08:00
fanxb
95a382835a deploy:版本号更新 2024-10-30 21:48:41 +08:00
922a74be41 Merge pull request 'deploy:增加loading页面' (#50) from dev into main
Reviewed-on: #50
2024-10-30 21:39:44 +08:00
fanxb
1ebd7acf5a deploy:增加loading页面 2024-10-30 21:34:05 +08:00
6444d9a612 Merge pull request 'dev' (#49) from dev into main
Reviewed-on: #49
2024-10-30 20:14:25 +08:00
fanxb
6759333fb4 feat:全选支持选中文件夹 2024-10-30 20:13:55 +08:00
fanxb
62d9296b46 feat:全选支持选中文件夹 2024-10-30 20:13:34 +08:00
fanxb
711423dbe1 feat:增加中英文转换util 2024-09-18 22:21:37 +08:00
fanxb
f3f31e5b5c feat:增加中英文转换util 2024-09-18 16:58:59 +08:00
fanxb
4c8c1a355c feat:增加中英文转换util 2024-07-01 21:36:47 +08:00
fanxb
f3bcacfc4a Merge branch 'refs/heads/qb' into electron
# Conflicts:
#	openRenamerBackend/index.ts
#	openRenamerBackend/package.json
#	openRenamerBackend/pnpm-lock.yaml
#	openRenamerFront/pnpm-lock.yaml
#	openRenamerFront/src/App.vue
2024-07-01 21:34:38 +08:00
2e50b9653a Merge pull request 'electron' (#48) from electron into main
Reviewed-on: #48
2024-01-07 15:35:43 +08:00
fanxb
861d5f35bf feat:支持windows桌面应用 2024-01-07 15:34:24 +08:00
fanxb
55af6bd54e temp 2024-01-03 22:26:24 +08:00
fanxb
8c6416ec87 temp 2024-01-03 22:11:08 +08:00
fanxb
f04d3bf636 feat:windows打包 2024-01-03 21:07:29 +08:00
fanxb
0b87ecae94 temp 2023-12-20 22:04:06 +08:00
fleyx
9c001c5403 electron兼容 2023-12-20 17:02:56 +08:00
fanxb
4b8a7829d9 fix:修复bug 2023-12-01 15:30:38 +08:00
fanxb
164553abf0 temp 2023-10-21 11:31:29 +08:00
fleyx
cabe83d07d temp 2023-10-11 17:34:26 +08:00
fanxb
69dcbd22b0 temp 2023-07-28 20:12:34 +08:00
fanxb
4002db4c22 Merge pull request 'feat:优化集数识别' (#47) from dev into main
Reviewed-on: #47
2023-06-25 22:12:22 +08:00
fanxb
851234ab76 feat:优化集数识别 2023-06-25 22:10:12 +08:00
fanxb
1876e98a0e Merge pull request 'fix:修复文件过多报错问题' (#46) from dev into main
Reviewed-on: #46
2023-06-15 20:56:11 +08:00
fanxb
7ab3a49323 fix:修复文件过多报错问题 2023-06-15 20:53:44 +08:00
fanxb
3f8b659462 Merge pull request 'fix:修复文件过多报错问题' (#45) from dev into main
Reviewed-on: #45
2023-06-15 20:17:35 +08:00
fanxb
f25e5d7bc7 fix:修复文件过多报错问题 2023-06-15 20:13:34 +08:00
fanxb
ea859cc6c3 Merge pull request 'feat:超长文本隐藏显示' (#44) from dev into main
Reviewed-on: #44
2023-05-25 18:33:08 +08:00
fanxb
3f0d519a99 feat:超长文本隐藏显示 2023-05-24 22:04:24 +08:00
fanxb
b0f1901731 Merge pull request 'dev' (#43) from dev into main
Reviewed-on: #43
2023-05-10 20:55:42 +08:00
fanxb
faedc09771 deploy:修改部署文档 2023-05-10 20:30:42 +08:00
fanxb
f502348786 feat:新增替换规则 2023-05-10 20:24:29 +08:00
51 changed files with 9893 additions and 5309 deletions

View File

@ -1,7 +1,9 @@
FROM node:lts-buster-slim FROM node:lts-buster-slim
WORKDIR /app WORKDIR /app
COPY ./openRenamerBackend /app COPY ./openRenamerBackend /app
RUN chmod 777 -R /app && npm install -g pnpm typescript --registry https://registry.npmmirror.com # 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 ENV PORT 80
CMD ["bash", "start.sh"] CMD ["bash", "start.sh"]

View File

@ -2,7 +2,11 @@
![预览图](https://s3.fleyx.com/picbed/2022/11/18386180128d01eb1a59b8eacf652895.png) ![预览图](https://s3.fleyx.com/picbed/2022/11/18386180128d01eb1a59b8eacf652895.png)
renamer 的开源实现版本BS 应用,支持 arm/x86 部署使用 renamer 的开源实现版本BS 应用,支持 arm/x86 部署使用,两种使用方式:
已打包镜像到 dockerhub 中:[hub.docker.com/r/fleyx/open-renamer](https://hub.docker.com/r/fleyx/open-renamer)
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)
[点击查看参考文档](https://blog.fleyx.com/blog/detail/20221130) [点击查看参考文档](https://blog.fleyx.com/blog/detail/20221130)

View File

@ -2,7 +2,9 @@
base=$(cd "$(dirname "$0")";pwd) base=$(cd "$(dirname "$0")";pwd)
cd $base cd $base
rm -rf openRenamerBackend/dist rm -rf openRenamerBackend/dist
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" # 注意此处未添加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/* rm -rf openRenamerBackend/static/*
touch openRenamerBackend/static/.gitkeep touch openRenamerBackend/static/.gitkeep

6
electron/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
node_modules
dist
openRenamerBackend
build
.idea
*.exe

1
electron/.npmrc Normal file
View File

@ -0,0 +1 @@
electron_mirror=https://npmmirror.com/mirrors/electron/

12
electron/build.sh Normal file
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1 @@
需要下载windows版的node.js压缩包将其中的node.exe 放到windowes目录下

BIN
electron/renamer.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

1705
electron/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/dist" /> <excludeFolder url="file://$MODULE_DIR$/dist" />
<excludeFolder url="file://$MODULE_DIR$/.vscode" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />

View File

@ -1,4 +1,4 @@
import { Context } from "koa"; import {Context} from "koa";
import service from "../service/QbService"; import service from "../service/QbService";
const router = {}; const router = {};
@ -7,7 +7,21 @@ const router = {};
* *
*/ */
router["POST /qb/saveQbInfo"] = async function (ctx: Context) { router["POST /qb/saveQbInfo"] = async function (ctx: Context) {
ctx.body = await service.saveAddress(ctx.request.body); 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();
}; };

View File

@ -1,26 +1,29 @@
import * as path from 'path'; import * as path from 'path';
import * as process from "process";
//后台所在绝对路径 //后台所在绝对路径
const rootPath = path.resolve(__dirname, '..'); const rootPath = path.resolve(__dirname, '..');
let config = { let config = {
rootPath, rootPath,
port: process.env.PORT ? parseInt(process.env.PORT) : 8089, dataPath: process.env.DATA_PATH ? process.env.DATA_PATH : path.join(rootPath, 'data'),
token: process.env.TOKEN ? process.env.TOKEN : null, port: process.env.PORT ? parseInt(process.env.PORT) : 8089,
urlPrefix: '/openRenamer/api', token: process.env.TOKEN ? process.env.TOKEN : null,
//是否为windows平台 urlPrefix: '/openRenamer/api',
isWindows: process.platform.toLocaleLowerCase().includes("win"), //是否为windows平台
bodyLimit: { isWindows: process.platform.toLocaleLowerCase().includes("win"),
formLimit: '2mb', bodyLimit: {
urlencoded: true, formLimit: '200mb',
multipart: true, jsonLimit: '200mb',
formidable: { urlencoded: true,
uploadDir: path.join(rootPath, 'files', 'temp', 'uploads'), multipart: true,
keepExtenstions: true, formidable: {
maxFieldsSize: 1024 * 1024 uploadDir: path.join(rootPath, 'files', 'temp', 'uploads'),
} keepExtenstions: true,
}, maxFieldsSize: 1024 * 1024 * 200
publicPath: new Set(["POST/public/checkToken"]) }
},
publicPath: new Set(["POST/public/checkToken"])
}; };
export default config; export default config;

View File

@ -58,7 +58,7 @@ export default class InsertRule implements RuleInterface {
} }
} else if (this.type === 'eNum') { } else if (this.type === 'eNum') {
let lowName = file.originName.toLocaleLowerCase().replace(/ /g, '') let lowName = file.originName.toLocaleLowerCase().replace(/ /g, '')
.replace(/\d+[a-df-z]/g, '')//去除4k,1080p等 .replace(/\d+[kp]/g, '')//去除4k,1080p等
.replace(/[xh]\d+/g, '')//去除x264,h264等 ; .replace(/[xh]\d+/g, '')//去除x264,h264等 ;
for (let i in eNumPatternArr) { for (let i in eNumPatternArr) {
let patternRes = lowName.match(eNumPatternArr[i]); let patternRes = lowName.match(eNumPatternArr[i]);

View File

@ -1,92 +1,99 @@
import RuleInterface from "./RuleInterface"; import RuleInterface from "./RuleInterface";
import {dealFileName} from "./RuleInterface";
import FileObj from "../../vo/FileObj"; import FileObj from "../../vo/FileObj";
import path from 'path'; import path from 'path';
export default class DeleteRule implements RuleInterface { export default class DeleteRule implements RuleInterface {
/** /**
* deletePart:部分删除deleteAll:全部删除 * deletePart:部分删除deleteAll:全部删除
*/ */
type: string; type: string;
/** /**
* *
*/ */
start: DeleteRuleItem; start: DeleteRuleItem;
/** /**
* *
*/ */
end: DeleteRuleItem; end: DeleteRuleItem;
/** /**
* true:false * true:false
*/ */
ignorePostfix: boolean; ignorePostfix: boolean;
/*
*
*/
regI: boolean;
constructor(data: any) { constructor(data: any) {
this.type = data.type; this.type = data.type;
this.start = new DeleteRuleItem(data.start); this.regI = data.regI != undefined && data.regI;
this.end = new DeleteRuleItem(data.end); this.start = new DeleteRuleItem(data.start, this.regI);
this.ignorePostfix = data.ignorePostfix; this.end = new DeleteRuleItem(data.end, this.regI);
} this.ignorePostfix = data.ignorePostfix;
}
deal(file: FileObj): void {
deal(file: FileObj): void { let target = "";
if (this.type === 'deleteAll') { if (this.type === 'deleteAll') {
file.realName = ""; target = "";
if (!this.ignorePostfix) { } else {
file.expandName = ""; let str = file.realName + (this.ignorePostfix ? "" : file.expandName);
} let startIndex = this.start.calIndex(str, false);
} else { let endIndex = this.end.calIndex(str, true);
let str = file.realName + (this.ignorePostfix ? "" : file.expandName); if (startIndex < 0 || endIndex < 0 || startIndex > endIndex) {
let startIndex = this.start.calIndex(str); return;
let endIndex = this.end.calIndex(str); }
if (startIndex < 0 || endIndex < 0) { str = str.substring(0, startIndex) + str.substring(endIndex + 1);
return; target = str;
} }
str = str.substring(0, startIndex) + str.substring(endIndex + 1); dealFileName(file, target, this.ignorePostfix);
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 { class DeleteRuleItem {
/** /**
* location:位置text:文本end:直到末尾 * location:位置text:文本end:直到末尾
*/ */
type: string; type: string;
/** /**
* *
*/ */
value: string; value: string;
/**
*
*/
reg: RegExp;
constructor(data: any) { constructor(data: any, regI: boolean) {
this.type = data.type; this.type = data.type;
this.value = data.value; this.value = data.value;
} if (this.type === 'reg') {
this.reg = regI ? new RegExp(this.value) : new RegExp(this.value, 'i');
}
}
/** /**
* *
*/ * @param str
calIndex(str: string): number { * @param end
if (this.type === 'location') { */
return parseInt(this.value) - 1; calIndex(str: string, end: boolean): number {
} else if (this.type === 'text') { if (this.type === 'location') {
return str.indexOf(this.value); let val = parseInt(this.value);
} else if (this.type === 'end') { return val > 0 ? val - 1 : str.length + val;
return str.length - 1; } else if (this.type === 'text') {
} let index = str.indexOf(this.value);
return -1; 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;
}
} }

View 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:替换第一个23
*/
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;
}
}

View File

@ -1,6 +1,27 @@
import FileObj from "../../vo/FileObj"; import FileObj from "../../vo/FileObj";
import * as path from 'path';
export default interface RuleInterface { export default interface RuleInterface {
deal(file: FileObj): void; 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;
} }

View 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;
/**
* 012
*/
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;
}
}

View 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;
}

View File

@ -1,5 +0,0 @@
export default interface QbAddressDto {
address: string;
username: string;
password: string;
}

View 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>;
}

View File

@ -3,21 +3,22 @@ import {isVideo, isSub, isNfo} from "../../util/MediaUtil"
export default class FileObj { export default class FileObj {
/** /**
* * ()
*/ */
name: string; name: string;
/** /**
* ()
*/
realName: string;
/**
()
*/ */
originName: string; originName: string;
/** /**
* * ()
*/ */
expandName: string; expandName: string;
/**
*
*/
realName: string;
/** /**
* *
*/ */

View File

@ -2,34 +2,41 @@ import DeleteRule from "../bo/rules/DeleteRule";
import InsertRule from "../bo/rules/InsertRule"; import InsertRule from "../bo/rules/InsertRule";
import SerializationRule from "../bo/rules/SerializationRule"; import SerializationRule from "../bo/rules/SerializationRule";
import AutoRule from "../bo/rules/AutoRule"; import AutoRule from "../bo/rules/AutoRule";
import ReplaceRule from "../bo/rules/ReplaceRule";
import TranslateRole from "../bo/rules/TranslateRole";
export default class RuleObj { export default class RuleObj {
type: string; type: string;
message: string; message: string;
/** /**
* *
*/ */
data: any; data: any;
constructor(data: any) { constructor(data: any) {
this.type = data.type; this.type = data.type;
this.message = data.message; this.message = data.message;
switch (this.type) { switch (this.type) {
case "delete": case "delete":
this.data = new DeleteRule(data.data); this.data = new DeleteRule(data.data);
break; break;
case "insert": case "insert":
this.data = new InsertRule(data.data); this.data = new InsertRule(data.data);
break; break;
case "serialization": case "serialization":
this.data = new SerializationRule(data.data); this.data = new SerializationRule(data.data);
break; break;
case "auto": case "auto":
this.data = new AutoRule(data.data); this.data = new AutoRule(data.data);
break; break;
default: case "replace":
throw new Error("不支持的规则:" + this.type); this.data = new ReplaceRule(data.data);
break;
} case "translate":
} this.data = new TranslateRole(data.data);
break;
default:
throw new Error("不支持的规则:" + this.type);
}
}
} }

View File

@ -9,8 +9,8 @@ import handleError from "./middleware/handleError";
import init from "./middleware/init"; import init from "./middleware/init";
import SqliteUtil from './util/SqliteHelper'; import SqliteUtil from './util/SqliteHelper';
import log from './util/LogUtil'; import log from './util/LogUtil';
import {updateQbInfo} from './util/QbApiUtil'; import QbService from './service/QbService';
import qbService from "./service/QbService";
console.log(config); console.log(config);
@ -32,7 +32,7 @@ app.use(handleError);
app.use(RouterMW(router, path.join(config.rootPath, "dist/api"))); app.use(RouterMW(router, path.join(config.rootPath, "dist/api")));
(async () => { (async () => {
await SqliteUtil.createPool(); await SqliteUtil.createPool();
await updateQbInfo(null, null); await qbService.init();
app.listen(config.port); app.listen(config.port);
log.info(`server listened `, config.port); log.info(`server listened `, config.port);
})(); })();

View File

@ -9,22 +9,20 @@
"author": "fxb", "author": "fxb",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@types/fs-extra": "^5.0.4", "@types/fs-extra": "5.0.4",
"@types/koa": "^2.0.47", "@types/koa": "2.13.12",
"@types/node": "^11.13.4", "@types/node": "11.13.4",
"axios": "^0.21.4", "axios": "0.21.4",
"fs-extra": "^7.0.0", "fs-extra": "7.0.0",
"koa": "^2.5.3", "koa": "2.5.3",
"koa-body": "^4.0.4", "koa-body": "4.0.4",
"koa-router": "^7.4.0", "koa-router": "7.4.0",
"koa-static": "^5.0.0", "koa-static": "5.0.0",
"koa2-cors": "^2.0.6", "koa2-cors": "2.0.6",
"log4js": "^6.3.0", "log4js": "6.3.0",
"moment": "^2.22.2", "moment": "2.22.2",
"mysql2": "^2.2.5", "sqlite": "4.0.23",
"sqlite": "^4.0.23", "sqlite3": "5.0.2",
"sqlite3": "^5.0.2", "uuid": "3.3.2"
"uuid": "^3.3.2",
"winston": "^3.1.0"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,52 @@
import QbAddressDto from "../entity/dto/QbAddressDto"; import QbConfigDto from "../entity/dto/QbConfigDto";
import { tryLogin, updateQbInfo } from '../util/QbApiUtil'; import {tryLogin, get, post, updateQbInfo, getQbInfo} from '../util/QbApiUtil';
import GlobalConfigService from "./GlobalConfigService"; import GlobalConfigService from "./GlobalConfigService";
import GlobalConfig from "../entity/po/GlobalConfig"; import GlobalConfig from "../entity/po/GlobalConfig";
import BtListItemDto from "../entity/dto/BtListItemDto";
class QbService { class QbService {
static async saveAddress(body: QbAddressDto) { /**
await tryLogin(body.address, body.username, body.password, false); *
await GlobalConfigService.insertOrReplace(new GlobalConfig("qbAddress", body.address, "qbAdress")); * @param body
await GlobalConfigService.insertOrReplace(new GlobalConfig("qbUsername", body.username, "")); */
await GlobalConfigService.insertOrReplace(new GlobalConfig("qbPassword", body.password, "")); static async saveAddress(body: QbConfigDto): Promise<QbConfigDto> {
await updateQbInfo(body, true); 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; export default QbService;

View File

@ -14,10 +14,10 @@ class RenamerService {
for (let i in fileList) { for (let i in fileList) {
let obj = fileList[i]; let obj = fileList[i];
ruleObjs.forEach(item => (item.data as RuleInterface).deal(obj)); ruleObjs.forEach(item => (item.data as RuleInterface).deal(obj));
if (newNameSet.has(obj.name)) { if (newNameSet.has(obj.path + obj.name)) {
obj.errorMessage = "重名"; obj.errorMessage = "重名";
} }
newNameSet.add(obj.name); newNameSet.add(obj.path + obj.name);
} }
return fileList; return fileList;
} }

View File

@ -7,7 +7,6 @@
"outDir": "./dist", "outDir": "./dist",
"baseUrl": ".", "baseUrl": ".",
"rootDir": "./", "rootDir": "./",
"watch": false,
"strict": true, "strict": true,
"strictNullChecks": false, "strictNullChecks": false,
"esModuleInterop": true "esModuleInterop": true

View File

@ -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
}

View File

@ -1,102 +1,83 @@
import { Method } from "axios"; import {Method} from "axios";
import axios from "axios"; import axios from "axios";
import QbAddressDto from "../entity/dto/QbAddressDto"; import querystring from "querystring";
import QbConfigDto from "../entity/dto/QbConfigDto";
import GlobalService from '../service/GlobalConfigService'; import GlobalService from '../service/GlobalConfigService';
import { setUncaughtExceptionCaptureCallback } from "process";
//qb状态true正常false:无法访问 let qbInfo: QbConfigDto = null;
let qbStatus = true; let cookie: any = null;
let qbInfo: QbAddressDto = null;
let cookie: string = null;
export function getQbStatus() { export function updateQbInfo(info: QbConfigDto) {
return qbStatus; qbInfo = info;
} }
export async function updateQbInfo(info: QbAddressDto, status: boolean) { export function getQbInfo() {
if (!info) { return qbInfo;
let obj = await GlobalService.getMultVal(["qbAddress", "qbUsername", "qbPassword"]);
if (!obj.qbAddress) {
qbStatus = false;
return;
}
qbInfo.address = obj.qbAddress;
qbInfo.username = obj.qbUsername;
qbInfo.password = obj.qbPassword;
} else {
qbInfo = info;
}
if (status) {
qbStatus = status;
}
axios.defaults.baseURL = qbInfo.address;
} }
export function get() { export async function get(url: string, data: object) {
return await request("get", url, data, null, false);
} }
export function post() { 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) { async function request(method: Method, url: string, query: any, body: any, isForm = false) {
if (!qbStatus) { if (!qbInfo.valid) {
throw new Error("qbittorrent无法连接请检查配置"); throw new Error("qbittorrent无法连接请检查配置");
} }
let isTryLogin = false; let isTryLogin = false;
while (true) { while (true) {
let headers = { "Cookie": cookie }; let headers = {"Cookie": cookie};
if (isForm) { if (isForm) {
headers['content-type'] = "multipart/form-data"; headers['content-type'] = "multipart/form-data";
} else if (method == "post") { } else if (method == "post") {
headers['content-type'] = "application/json"; headers['content-type'] = "application/json";
} }
let res = await axios.request({ let res = await axios.request({
baseURL: qbInfo.address, baseURL: qbInfo.address,
url: url, url: "/api/v2" + url,
method, method,
params: query, params: query,
data: body, data: body,
headers, headers,
}); });
if (res.status == 200) { if (res.status == 200) {
return res.data; return res.data;
} if (res.status == 403) { }
if (isTryLogin) { if (res.status == 403) {
throw new Error("qb用户名密码设置有误"); if (isTryLogin) {
} else { throw new Error("qb用户名密码设置有误");
await tryLogin(qbInfo.address, qbInfo.username, qbInfo.password, true); } else {
isTryLogin = true; await tryLogin();
} isTryLogin = true;
} else { }
throw new Error("请求报错:" + res.data); } else {
} throw new Error("请求报错:" + res.data);
} }
}
} }
export async function tryLogin(address: string, username: string, password: string, updateStatus: boolean): Promise<void> { export async function tryLogin(): Promise<boolean> {
let body = { username, password }; if (qbInfo == null || qbInfo.address == null || qbInfo.address == "") {
try { return false;
let res = await axios.post(address + "/api/v2/auth/login", body, { }
headers: { "Content-Type": "multipart/form-data;boundary=--------------------------125002698093981740970152" } let body = {username: qbInfo.username, password: qbInfo.password};
}); try {
let success = res.data.toLocaleLowerCase().contains('ok'); let res = await axios.post(qbInfo.address + `/api/v2/auth/login`, querystring.stringify(body), {
if (updateStatus) { headers: {"Content-Type": "application/x-www-form-urlencoded"}
qbStatus = success; });
} let success = res.data.toLocaleLowerCase().indexOf('ok') > -1;
if (!success) { if (success) {
throw new Error("登录失败"); cookie = res.headers['set-cookie'];
} else { }
cookie = res.headers['Cookie']; qbInfo.valid = success;
} return success;
} catch (error) { } catch (error) {
console.error("登录报错:", error); console.error("登录报错:", error);
if (updateStatus) { return false;
qbStatus = false;
} }
throw new Error("登录出错");
}
} }

View File

@ -1,5 +1,5 @@
import sqlite3 from 'sqlite3'; import sqlite3 from 'sqlite3';
import { open, Database } from 'sqlite'; import {open, Database} from 'sqlite';
import config from '../config'; import config from '../config';
import * as fs from "fs-extra"; import * as fs from "fs-extra";
import * as path from 'path'; import * as path from 'path';
@ -12,9 +12,9 @@ class SqliteHelper {
public static pool: Database = null; public static pool: Database = null;
static async createPool() { static async createPool() {
let dataFolder = path.join(config.rootPath, "data"); let dataFolder = config.dataPath;
if (!fs.existsSync(dataFolder)) { if (!fs.existsSync(dataFolder)) {
fs.mkdir(dataFolder); await fs.mkdir(dataFolder);
} }
SqliteHelper.pool = await open({ SqliteHelper.pool = await open({
filename: path.join(dataFolder, "database.db"), filename: path.join(dataFolder, "database.db"),
@ -26,7 +26,7 @@ class SqliteHelper {
if (fs.existsSync(hisPath)) { if (fs.existsSync(hisPath)) {
history = JSON.parse(await fs.readFile(hisPath, "utf-8")); history = JSON.parse(await fs.readFile(hisPath, "utf-8"));
} else { } else {
history = new Array(); history = [];
} }
//执行数据库 //执行数据库
let files = (await fs.readdir(basePath)).sort((a, b) => a.localeCompare(b)).filter(item => !(item === HISTORY_NAME)); let files = (await fs.readdir(basePath)).sort((a, b) => a.localeCompare(b)).filter(item => !(item === HISTORY_NAME));

View 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 012
*/
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 012
*/
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;
}

File diff suppressed because one or more lines are too long

View 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;
}

View File

@ -8,21 +8,21 @@
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.0.10", "@element-plus/icons-vue": "2.0.10",
"axios": "^0.21.1", "axios": "1.6.0",
"core-js": "^3.6.5", "core-js": "3.35.0",
"dayjs": "^1.10.7", "dayjs": "1.10.7",
"element-plus": "^2.2.25", "element-plus": "2.2.25",
"vue": "^3.2.45", "vue": "3.2.45",
"vue-router": "^4.0.0-0" "vue-router": "4.2.5"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "~5.0.8", "@vue/cli-plugin-babel": "5.0.8",
"@vue/cli-plugin-router": "~5.0.8", "@vue/cli-plugin-router": "5.0.8",
"@vue/cli-service": "~5.0.8", "@vue/cli-service": "5.0.8",
"@vue/compiler-sfc": "^3.0.0", "@vue/compiler-sfc": "3.0.0",
"less": "^3.0.4", "less": "3.0.4",
"less-loader": "^5.0.0", "less-loader": "5.0.0",
"prettier": "^2.2.1" "prettier": "2.2.1"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -29,8 +29,8 @@
</el-tooltip> </el-tooltip>
&nbsp;&nbsp; &nbsp;&nbsp;
</template> </template>
开源地址:<a href="https://github.com/FleyX/open-renamer">open-renamer</a> 开源地址:<a href="https://github.com/FleyX/open-renamer" target="_blank">open-renamer</a>
&nbsp;&nbsp;<a href="https://github.com/FleyX/open-renamer/issues">反馈</a> &nbsp;&nbsp;<a href="https://github.com/FleyX/open-renamer/issues" target="_blank">反馈</a>
</div> </div>
</div> </div>
</template> </template>
@ -42,7 +42,7 @@ export default {
name: "Home", name: "Home",
data() { data() {
return { return {
version: "1.5", version: "1.8.0",
latestVersion: null, latestVersion: null,
activeIndex: location.pathname, activeIndex: location.pathname,
showNewVersion: false showNewVersion: false

View File

@ -122,7 +122,7 @@ export default {
}, },
// //
selectAll(status) { selectAll(status) {
this.filterFileList.filter((item) => !item.isFolder).forEach((item) => (item.checked = status)); this.filterFileList.forEach((item) => (item.checked = status));
}, },
//index //index
createPath(index) { createPath(index) {

View File

@ -3,15 +3,18 @@
<el-menu style="width: 8em" mode="vertical" :default-active="currentIndex" @select="menuChange"> <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="insert">插入</el-menu-item>
<el-menu-item :disabled="editRule != null" index="delete">删除</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="replace">替换</el-menu-item>
<el-menu-item :disabled="editRule != null || isAutoPlan" index="serialization">序列化</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="auto">自动识别</el-menu-item>
<el-menu-item :disabled="editRule != null" index="translate">简繁转换</el-menu-item>
</el-menu> </el-menu>
<div class="rule"> <div class="rule">
<insert-rule ref="rule" :editRule="editRule" v-if="currentIndex == 'insert'" /> <insert-rule ref="rule" :editRule="editRule" v-if="currentIndex === 'insert'"/>
<delete-rule ref="rule" :editRule="editRule" v-else-if="currentIndex == 'delete'" /> <delete-rule ref="rule" :editRule="editRule" v-else-if="currentIndex === 'delete'"/>
<serialization-rule ref="rule" :editRule="editRule" v-else-if="currentIndex == 'serialization'" /> <replace-rule ref="rule" :editRule="editRule" v-else-if="currentIndex === 'replace'"/>
<auto-rule ref="rule" :editRule="editRule" v-else-if="currentIndex == 'auto'" /> <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> </div>
<div style="text-align: center"> <div style="text-align: center">
@ -24,27 +27,30 @@ import InsertRule from "./rules/InsertRule.vue";
import DeleteRule from "./rules/DeleteRule.vue"; import DeleteRule from "./rules/DeleteRule.vue";
import SerializationRule from "./rules/SerializationRule.vue"; import SerializationRule from "./rules/SerializationRule.vue";
import AutoRule from "./rules/AutoRule"; import AutoRule from "./rules/AutoRule";
import ReplaceRule from "@/components/rules/ReplaceRule";
import TranslateRule from '@/components/rules/TranslateRule.vue';
export default { export default {
components: { InsertRule, DeleteRule, SerializationRule, AutoRule }, components: {InsertRule, DeleteRule, SerializationRule, AutoRule, ReplaceRule, TranslateRule},
props: ["editRule", "isAutoPlan"], props: ["editRule", "isAutoPlan"],
emits: ["ruleAdd"], emits: ["ruleAdd"],
name: "Rule", name: "Rule",
data () { data() {
return { return {
currentIndex: "insert", currentIndex: "insert",
options: [{ label: "插入", value: "insert" }], options: [{label: "插入", value: "insert"}],
}; };
}, },
created () { created() {
if (this.editRule) { if (this.editRule) {
this.currentIndex = this.editRule.type; this.currentIndex = this.editRule.type;
} }
}, },
methods: { methods: {
menuChange (index) { menuChange(index) {
this.currentIndex = index; this.currentIndex = index;
}, },
submit () { submit() {
let data = this.$refs["rule"].exportObj(); let data = this.$refs["rule"].exportObj();
if (data != null) { if (data != null) {
this.$emit("ruleAdd", data); this.$emit("ruleAdd", data);

View File

@ -6,22 +6,30 @@
<div>开始</div> <div>开始</div>
<div class="line"> <div class="line">
<el-radio v-model="ruleObj.data.start.type" label="location" :disabled="deleteAll">位置</el-radio> <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>
<div class="line"> <div class="line">
<el-radio v-model="ruleObj.data.start.type" label="text" :disabled="deleteAll">文本:</el-radio> <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> </div>
<div style="margin-left: 4em"> <div style="margin-left: 4em">
<div>结束</div> <div>结束</div>
<div class="line"> <div class="line">
<el-radio v-model="ruleObj.data.end.type" label="location" :disabled="deleteAll">位置</el-radio> <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>
<div class="line"> <div class="line">
<el-radio v-model="ruleObj.data.end.type" label="text" :disabled="deleteAll">文本:</el-radio> <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>
<div class="line"> <div class="line">
<el-radio v-model="ruleObj.data.end.type" label="end" :disabled="deleteAll">直到末尾</el-radio> <el-radio v-model="ruleObj.data.end.type" label="end" :disabled="deleteAll">直到末尾</el-radio>
@ -29,14 +37,18 @@
</div> </div>
</div> </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="flex">
<div class="left">全部删除:</div> <div class="left">全部删除:</div>
<el-switch v-model="deleteAll" @change="allDeleteChange" /> <el-switch v-model="deleteAll" @change="allDeleteChange"/>
</div> </div>
<div class="flex"> <div class="flex">
<div class="left">忽略拓展名:</div> <div class="left">忽略拓展名:</div>
<el-switch v-model="ruleObj.data.ignorePostfix" /> <el-switch v-model="ruleObj.data.ignorePostfix"/>
</div> </div>
</template> </template>
@ -60,28 +72,36 @@ export default {
value: "", value: "",
}, },
ignorePostfix: true, ignorePostfix: true,
regI: false,//reg
}, },
}, },
startIndex: 1, startIndex: 1,
endIndex: 1, endIndex: 1,
startText: "", startText: "",
startReg: "",
endText: "", endText: "",
endReg: "",
deleteAll: false, deleteAll: false,
}; };
}, },
created() { created() {
if (this.editRule) { if (this.editRule) {
this.ruleObj = JSON.parse(JSON.stringify(this.editRule)); this.ruleObj = JSON.parse(JSON.stringify(this.editRule));
if (this.ruleObj.data.type == "deletePart") { if (this.ruleObj.data.type === "deletePart") {
if (this.ruleObj.data.start.type == "location") { if (this.ruleObj.data.start.type === "location") {
this.startIndex = parseInt(this.ruleObj.data.start.value); this.startIndex = parseInt(this.ruleObj.data.start.value);
} else { } else if (this.ruleObj.data.start.type === "text") {
this.startText = this.ruleObj.data.start.value; this.startText = this.ruleObj.data.start.value;
}
if (this.ruleObj.data.end.type == "location") {
this.endIndex = parseInt(this.ruleObj.data.end.value);
} else { } 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; this.endText = this.ruleObj.data.end.value;
} else {
this.endReg = this.ruleObj.data.end.value;
} }
} else { } else {
this.deleteAll = true; this.deleteAll = true;
@ -91,13 +111,15 @@ export default {
methods: { methods: {
exportObj() { exportObj() {
if (this.ruleObj.data.type.length == 0) { if (this.ruleObj.data.type.length == 0) {
this.$message({ message: "请填写完整", type: "warning" }); this.$message({message: "请填写完整", type: "warning"});
return null; return null;
} }
if (this.ruleObj.data.type == "deletePart") { if (this.ruleObj.data.type === "deletePart") {
if ( if (
(this.ruleObj.data.start.type == "text" && this.startText.length == 0) || ('text' === this.ruleObj.data.start.type && this.startText.length === 0) ||
(this.ruleObj.data.start.type == "text" && 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({ this.$message({
message: "开始或者结束文本不能为空", message: "开始或者结束文本不能为空",
@ -106,19 +128,20 @@ export default {
return null; return null;
} }
} }
this.ruleObj.data.start.value = this.ruleObj.data.start.type == "location" ? this.startIndex.toString() : this.startText; let startType = this.ruleObj.data.start.type;
this.ruleObj.data.end.value = this.ruleObj.data.end.type == "location" ? this.endIndex.toString() : this.endText; 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 = `删除:`; let message = `删除:`;
if (this.deleteAll) { if (this.deleteAll) {
message += "全部删除"; message += "全部删除";
} else { } 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; this.ruleObj.message = message;
return this.ruleObj; return this.ruleObj;
}, },
allDeleteChange(val) { allDeleteChange(val) {
console.log(val);
this.deleteAll = val; this.deleteAll = val;
this.ruleObj.data.type = val ? "deleteAll" : "deletePart"; this.ruleObj.data.type = val ? "deleteAll" : "deletePart";
}, },

View 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:23
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>

View File

@ -90,7 +90,7 @@ export default {
} else { } else {
this.chosedTemplate = await HttpUtil.get("/applicationRule/default"); this.chosedTemplate = await HttpUtil.get("/applicationRule/default");
this.ruleList = JSON.parse(this.chosedTemplate.content); this.ruleList = JSON.parse(this.chosedTemplate.content);
await this.ruleUpdate(); await this.ruleUpdate(false);
} }
}, },
watch: { watch: {
@ -101,9 +101,14 @@ export default {
}, },
methods: { methods: {
// //
ruleUpdate() { ruleUpdate(preview) {
if (preview !== undefined && preview === false) {
preview = false;
} else {
preview = true;
}
let temp = this.ruleList.filter((item) => !item.blocked); let temp = this.ruleList.filter((item) => !item.blocked);
this.$emit("ruleUpdate", temp); this.$emit("ruleUpdate", temp, preview);
}, },
// //
async templateSubmit() { async templateSubmit() {

View 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>

View File

@ -0,0 +1,9 @@
/**
* 空转default
* @param val
* @param defaultVal
* @returns {*}
*/
export function nullToDefault(val, defaultVal) {
return val === undefined || val == null ? defaultVal : val;
}

View File

@ -1,13 +1,24 @@
<template> <template>
<div>配置qb</div> <div>配置qb</div>
<div class="item"> <el-form :model="data.qbConfig" label-width="8em">
<div class="left">qb信息</div> <el-form-item label="qb版本">
<div class="right">{{ qbInfo }}<el-button @click="editInfo = true">编辑</el-button></div> {{ data.qbConfig.version ? data.qbConfig.version : "配置错误,无法访问" }}
</div> </el-form-item>
<el-form v-if="editInfo" :model="qbBody" label-width="4em"> <el-form-item label="访问地址">
<el-form-item label="qb地址"><el-input type="text" v-model="qbBody.address" placeholder="例如:http://192.168.1.4:8080" /></el-form-item> <el-input type="text" v-model="data.qbConfig.address" placeholder="例如:http://localhost:8080(仅支持v4.1及以上)"/>
<el-form-item label="用户名"><el-input type="text" v-model="qbBody.username" placeholder="qb访问用户名" /></el-form-item> </el-form-item>
<el-form-item label="密码"><el-input type="password" v-model="qbBody.password" placeholder="qb访问密码" /></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"> <div style="text-align: center">
<el-button type="" @click="editInfo = false">取消</el-button> <el-button type="" @click="editInfo = false">取消</el-button>
<el-button type="primary" @click="submitQb">提交</el-button> <el-button type="primary" @click="submitQb">提交</el-button>
@ -16,34 +27,21 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, computed } from "vue"; import {ref, reactive, onMounted, computed} from "vue";
import http from "@/utils/HttpUtil"; import http from "@/utils/HttpUtil";
// //
const qbBody = reactive({ const data = reactive({
address: "", qbConfig: {},
username: "",
password: "",
}); });
//
let downloadConfig = reactive({});
//qb访 //qb访
let qbReach = ref(true);
let editInfo = ref(false); let editInfo = ref(false);
const qbInfo = computed(() => {
if (downloadConfig.qbAddress) {
return downloadConfig.qbAddress + " 用户名:" + downloadConfig.qbUsername;
} else {
return "尚未配置";
}
});
onMounted(async () => { onMounted(async () => {
downloadConfig = reactive(await http.post("/config/multCode", null, ["qbAddress", "qbUsername", "qbPassword"])); data.qbConfig = await http.get("/qb/config");
}); });
async function submitQb() { async function submitQb() {
let res = await http.post(""); data.qbConfig = await http.post("/qb/saveQbInfo", null, data.qbConfig);
} }
</script> </script>
@ -52,10 +50,12 @@ async function submitQb() {
display: flex; display: flex;
text-align: left; text-align: left;
padding-bottom: 0.5em; padding-bottom: 0.5em;
.left { .left {
width: 6em; width: 6em;
font-weight: 600; font-weight: 600;
} }
.right { .right {
flex: 1; flex: 1;
} }

View File

@ -59,16 +59,24 @@
</div> </div>
<div class="fileBlock"> <div class="fileBlock">
<!-- 左侧原始文件名 --> <!-- 左侧原始文件名 -->
<div style="flex: 4"> <div style="width:50%;">
<div v-for="(item, index) in fileList" :key="index" class="oneLine"> <div v-for="(item, index) in showFileList" :key="index" class="oneLine">
<el-checkbox v-model="item.checked" style="height: 1.2em">{{ item.name }}</el-checkbox> <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> </div>
<!-- 右边的预览文件名 --> <!-- 右边的预览文件名 -->
<div style="flex: 4"> <div style="width:50%">
<div v-for="(item, index) in changedFileList" :key="index" class="oneLine"> <div v-for="(item, index) in showChangedFileList" :key="index" class="oneLine">
<div style="flex: 4">{{ item.name }}</div> <div style="display:inline-block;width:72%;" class="oneLineText">
<div style="color: red; flex: 1">{{ item.errorMessage }}</div> <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> </div>
</div> </div>
@ -123,6 +131,12 @@ export default {
computed: { computed: {
allChecked() { allChecked() {
return this.fileList.length > 0 && this.fileList.filter(item => item.checked).length === this.fileList.length; 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() { async created() {
@ -141,12 +155,18 @@ export default {
this.dialogVisible = false; this.dialogVisible = false;
await this.showResult(); await this.showResult();
}, },
async ruleUpdate(rules) { async ruleUpdate(rules, preview) {
console.log(rules, preview);
this.ruleList = rules; this.ruleList = rules;
await this.showResult(); if (preview) {
await this.showResult();
}
}, },
// //
async showResult() { async showResult() {
if (this.fileList.length > 500) {
this.$message.info("文件数过多仅展示前500个(不影响重命名)");
}
this.changedFileList = []; this.changedFileList = [];
if (!this.checkRuleAndFile()) { if (!this.checkRuleAndFile()) {
return; return;
@ -355,15 +375,28 @@ function readChar(a, i, n) {
margin-top: 20px; margin-top: 20px;
display: flex; display: flex;
.el-checkbox__label {
width: 95%;
}
.oneLine { .oneLine {
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
border-top: 1px solid rgb(214, 212, 212); border-top: 1px solid rgb(214, 212, 212);
height: 1.5em; height: 1.5em;
padding-top: 0.1em; padding-top: 0.1em;
padding-bottom: 0.1em; padding-bottom: 0.1em;
padding-right: 0.2em; padding-right: 0.2em;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.oneLineText {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
} }
.oneFileName { .oneFileName {

View File

@ -1,5 +1,6 @@
module.exports = { module.exports = {
devServer: { devServer: {
proxy: "http://localhost:8089", proxy: "http://localhost:8089",
}, },
publicPath: "./"
}; };