Compare commits
18 Commits
851234ab76
...
4c8c1a355c
Author | SHA1 | Date | |
---|---|---|---|
|
4c8c1a355c | ||
|
f3bcacfc4a | ||
|
861d5f35bf | ||
|
55af6bd54e | ||
|
8c6416ec87 | ||
|
f04d3bf636 | ||
|
0b87ecae94 | ||
|
9c001c5403 | ||
|
4b8a7829d9 | ||
|
164553abf0 | ||
|
cabe83d07d | ||
|
69dcbd22b0 | ||
|
4002db4c22 | ||
|
1876e98a0e | ||
|
3f8b659462 | ||
|
ea859cc6c3 | ||
|
b0f1901731 | ||
|
ae9a2d53c5 |
@ -2,7 +2,11 @@
|
||||
|
||||
![预览图](https://s3.fleyx.com/picbed/2022/11/18386180128d01eb1a59b8eacf652895.png)
|
||||
|
||||
renamer 的开源实现版本,BS 应用,支持 arm/x86 部署使用
|
||||
已打包镜像到 dockerhub 中:[hub.docker.com/r/fleyx/open-renamer](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)
|
||||
|
||||
[点击查看参考文档](https://blog.fleyx.com/blog/detail/20221130)
|
||||
|
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/
|
11
electron/build.sh
Normal file
11
electron/build.sh
Normal file
@ -0,0 +1,11 @@
|
||||
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
|
||||
npm run build
|
129
electron/main.js
Normal file
129
electron/main.js
Normal file
@ -0,0 +1,129 @@
|
||||
// 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') //界面的其它脚本运行之前预先加载一个指定脚本。
|
||||
}
|
||||
})
|
||||
// 下面这两行代码配合上面 new BrowserWindow 里面的 show: false,可以实现打开时窗口最大化
|
||||
win.maximize()
|
||||
win.show()
|
||||
log.info(__dirname);
|
||||
let port = await startBackend()
|
||||
// 并且为你的应用加载index.html
|
||||
// win.loadFile('./dist/index.html')
|
||||
log.info("backend service started")
|
||||
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);
|
||||
})
|
||||
}
|
71
electron/package.json
Normal file
71
electron/package.json
Normal file
@ -0,0 +1,71 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "1.0.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"
|
||||
],
|
||||
"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
@ -10,5 +10,19 @@ 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,10 +1,12 @@
|
||||
import * as path from 'path';
|
||||
import * as process from "process";
|
||||
|
||||
//后台所在绝对路径
|
||||
const rootPath = path.resolve(__dirname, '..');
|
||||
|
||||
let config = {
|
||||
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',
|
||||
|
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;
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export default interface QbAddressDto {
|
||||
address: string;
|
||||
username: string;
|
||||
password: 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>;
|
||||
}
|
@ -9,8 +9,8 @@ import handleError from "./middleware/handleError";
|
||||
import init from "./middleware/init";
|
||||
import SqliteUtil from './util/SqliteHelper';
|
||||
import log from './util/LogUtil';
|
||||
import {updateQbInfo} from './util/QbApiUtil';
|
||||
|
||||
import QbService from './service/QbService';
|
||||
import qbService from "./service/QbService";
|
||||
|
||||
console.log(config);
|
||||
|
||||
@ -32,7 +32,7 @@ app.use(handleError);
|
||||
app.use(RouterMW(router, path.join(config.rootPath, "dist/api")));
|
||||
(async () => {
|
||||
await SqliteUtil.createPool();
|
||||
await updateQbInfo(null, null);
|
||||
await qbService.init();
|
||||
app.listen(config.port);
|
||||
log.info(`server listened `, config.port);
|
||||
})();
|
||||
|
@ -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.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",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
2014
openRenamerBackend/pnpm-lock.yaml
generated
2014
openRenamerBackend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,51 @@
|
||||
import QbAddressDto from "../entity/dto/QbAddressDto";
|
||||
import { tryLogin, updateQbInfo } from '../util/QbApiUtil';
|
||||
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 {
|
||||
|
||||
static async saveAddress(body: QbAddressDto) {
|
||||
await tryLogin(body.address, body.username, body.password, false);
|
||||
await GlobalConfigService.insertOrReplace(new GlobalConfig("qbAddress", body.address, "qbAdress"));
|
||||
await GlobalConfigService.insertOrReplace(new GlobalConfig("qbUsername", body.username, ""));
|
||||
await GlobalConfigService.insertOrReplace(new GlobalConfig("qbPassword", body.password, ""));
|
||||
await updateQbInfo(body, true);
|
||||
/**
|
||||
* 保存地址
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -1,48 +1,30 @@
|
||||
import {Method} 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 { setUncaughtExceptionCaptureCallback } from "process";
|
||||
|
||||
//qb状态,true正常,false:无法访问
|
||||
let qbStatus = true;
|
||||
let qbInfo: QbAddressDto = null;
|
||||
let cookie: string = null;
|
||||
let qbInfo: QbConfigDto = null;
|
||||
let cookie: any = null;
|
||||
|
||||
export function getQbStatus() {
|
||||
return qbStatus;
|
||||
}
|
||||
|
||||
export async function updateQbInfo(info: QbAddressDto, status: boolean) {
|
||||
if (!info) {
|
||||
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 {
|
||||
export function updateQbInfo(info: QbConfigDto) {
|
||||
qbInfo = info;
|
||||
}
|
||||
if (status) {
|
||||
qbStatus = status;
|
||||
|
||||
export function getQbInfo() {
|
||||
return qbInfo;
|
||||
}
|
||||
|
||||
axios.defaults.baseURL = qbInfo.address;
|
||||
export async function get(url: string, data: object) {
|
||||
return await request("get", url, data, null, false);
|
||||
}
|
||||
|
||||
export function get() {
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!qbStatus) {
|
||||
if (!qbInfo.valid) {
|
||||
throw new Error("qbittorrent无法连接,请检查配置");
|
||||
}
|
||||
let isTryLogin = false;
|
||||
@ -55,7 +37,7 @@ async function request(method: Method, url: string, query: any, body: any, isFor
|
||||
}
|
||||
let res = await axios.request({
|
||||
baseURL: qbInfo.address,
|
||||
url: url,
|
||||
url: "/api/v2" + url,
|
||||
method,
|
||||
params: query,
|
||||
data: body,
|
||||
@ -63,11 +45,12 @@ async function request(method: Method, url: string, query: any, body: any, isFor
|
||||
});
|
||||
if (res.status == 200) {
|
||||
return res.data;
|
||||
} if (res.status == 403) {
|
||||
}
|
||||
if (res.status == 403) {
|
||||
if (isTryLogin) {
|
||||
throw new Error("qb用户名密码设置有误");
|
||||
} else {
|
||||
await tryLogin(qbInfo.address, qbInfo.username, qbInfo.password, true);
|
||||
await tryLogin();
|
||||
isTryLogin = true;
|
||||
}
|
||||
} else {
|
||||
@ -77,26 +60,24 @@ async function request(method: Method, url: string, query: any, body: any, isFor
|
||||
|
||||
}
|
||||
|
||||
export async function tryLogin(address: string, username: string, password: string, updateStatus: boolean): Promise<void> {
|
||||
let body = { username, password };
|
||||
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(address + "/api/v2/auth/login", body, {
|
||||
headers: { "Content-Type": "multipart/form-data;boundary=--------------------------125002698093981740970152" }
|
||||
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().contains('ok');
|
||||
if (updateStatus) {
|
||||
qbStatus = success;
|
||||
}
|
||||
if (!success) {
|
||||
throw new Error("登录失败");
|
||||
} else {
|
||||
cookie = res.headers['Cookie'];
|
||||
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);
|
||||
if (updateStatus) {
|
||||
qbStatus = false;
|
||||
}
|
||||
throw new Error("登录出错");
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
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,21 +8,21 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.0.10",
|
||||
"axios": "^0.21.1",
|
||||
"core-js": "^3.6.5",
|
||||
"dayjs": "^1.10.7",
|
||||
"element-plus": "^2.2.25",
|
||||
"vue": "^3.2.45",
|
||||
"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": "~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"
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
|
4734
openRenamerFront/pnpm-lock.yaml
generated
4734
openRenamerFront/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -29,8 +29,8 @@
|
||||
</el-tooltip>
|
||||
|
||||
</template>
|
||||
开源地址:<a href="https://github.com/FleyX/open-renamer">open-renamer</a>
|
||||
<a href="https://github.com/FleyX/open-renamer/issues">反馈</a>
|
||||
开源地址:<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>
|
||||
</template>
|
||||
|
@ -14,7 +14,7 @@ import router from '../router/index';
|
||||
async function request(url, method, params, body, isForm) {
|
||||
let options = {
|
||||
url,
|
||||
baseURL: '/openRenamer/api',
|
||||
baseURL: window.baseUrl + '/openRenamer/api',
|
||||
method,
|
||||
params,
|
||||
headers: {token: window.token}
|
||||
|
@ -1,13 +1,24 @@
|
||||
<template>
|
||||
<div>配置qb</div>
|
||||
<div class="item">
|
||||
<div class="left">qb信息</div>
|
||||
<div class="right">{{ qbInfo }}<el-button @click="editInfo = true">编辑</el-button></div>
|
||||
</div>
|
||||
<el-form v-if="editInfo" :model="qbBody" label-width="4em">
|
||||
<el-form-item label="qb地址"><el-input type="text" v-model="qbBody.address" placeholder="例如:http://192.168.1.4:8080" /></el-form-item>
|
||||
<el-form-item label="用户名"><el-input type="text" v-model="qbBody.username" placeholder="qb访问用户名" /></el-form-item>
|
||||
<el-form-item label="密码"><el-input type="password" v-model="qbBody.password" placeholder="qb访问密码" /></el-form-item>
|
||||
<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>
|
||||
@ -19,31 +30,18 @@
|
||||
import {ref, reactive, onMounted, computed} from "vue";
|
||||
import http from "@/utils/HttpUtil";
|
||||
//表单
|
||||
const qbBody = reactive({
|
||||
address: "",
|
||||
username: "",
|
||||
password: "",
|
||||
const data = reactive({
|
||||
qbConfig: {},
|
||||
});
|
||||
//配置中心数据
|
||||
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"]));
|
||||
data.qbConfig = await http.get("/qb/config");
|
||||
});
|
||||
|
||||
async function submitQb() {
|
||||
let res = await http.post("");
|
||||
data.qbConfig = await http.post("/qb/saveQbInfo", null, data.qbConfig);
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -52,10 +50,12 @@ async function submitQb() {
|
||||
display: flex;
|
||||
text-align: left;
|
||||
padding-bottom: 0.5em;
|
||||
|
||||
.left {
|
||||
width: 6em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 1;
|
||||
}
|
||||
|
@ -2,4 +2,5 @@ module.exports = {
|
||||
devServer: {
|
||||
proxy: "http://localhost:8089",
|
||||
},
|
||||
publicPath: "./"
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user