Merge branch 'refs/heads/main' into develop

# Conflicts:
#	electron/storage/config.ts
#	src/views/config/index.vue
This commit is contained in:
刘嘉伟 2024-08-05 20:06:56 +08:00
commit c0519e7a5a
12 changed files with 303 additions and 196 deletions

View File

@ -16,15 +16,30 @@
<h3 align="center">Frpc-Desktop</h3> <h3 align="center">Frpc-Desktop</h3>
<p align="center"> <p align="center">
🎉 一个Frp Client 跨平台桌面端 🎉 一个 Frp 跨平台桌面客户 支持多个frp版本
<br /> <br />
</p> </p>
</div> </div>
## TODO
- [x] 开机自启动
- [ ] 支持配置的导出导入
- [ ] 适配多用户 user & meta_token
- [ ] 优化配置
- [ ] 便携版
## 里程碑 ## 里程碑
- 2024-07-17: 发布v1.0.3版本 修复已知bug 增加开机自启 增加删除frp版本
- 2024-01-29: 发布v1.0.2版本 增加Linux客户端和代理模式
- 2023-12-01: 发布v1.0.1版本 - 2023-12-01: 发布v1.0.1版本
- 2023-11-28: 发布v1.0版本 - 2023-11-28: 发布v1.0版本
## 社区
微信扫描加入开源项目交流群 广告勿进!!!
<img src="wechat-qr.png" alt="Logo" width="200">
## 演示 ## 演示
![connect server](https://github.com/luckjiawei/frpc-desktop/blob/main/demo/conn.png?raw=true) ![connect server](https://github.com/luckjiawei/frpc-desktop/blob/main/demo/conn.png?raw=true)
@ -39,6 +54,8 @@
[MIT](LICENSE) [MIT](LICENSE)
## Stargazers over time
[![Stargazers over time](https://starchart.cc/luckjiawei/frpc-desktop.svg?variant=adaptive)](https://starchart.cc/luckjiawei/frpc-desktop)
<!-- MARKDOWN LINKS & IMAGES --> <!-- MARKDOWN LINKS & IMAGES -->
[forks-shield]: https://img.shields.io/github/forks/luckjiawei/frpc-desktop.svg?style=for-the-badge [forks-shield]: https://img.shields.io/github/forks/luckjiawei/frpc-desktop.svg?style=for-the-badge
[forks-url]: https://github.com/luckjiawei/frpc-desktop/network/members [forks-url]: https://github.com/luckjiawei/frpc-desktop/network/members

View File

@ -1,42 +1,50 @@
import { ipcMain } from "electron"; import {app, ipcMain} from "electron";
import { getConfig, saveConfig } from "../storage/config"; import {getConfig, saveConfig} from "../storage/config";
import { listVersion } from "../storage/version"; import {listVersion} from "../storage/version";
export const initConfigApi = () => { export const initConfigApi = () => {
ipcMain.on("config.saveConfig", async (event, args) => { ipcMain.on("config.saveConfig", async (event, args) => {
saveConfig(args, (err, numberOfUpdated, upsert) => { saveConfig(args, (err, numberOfUpdated, upsert) => {
event.reply("Config.saveConfig.hook", { if (!err) {
err: err, const start = args.systemSelfStart || false;
numberOfUpdated: numberOfUpdated, console.log('开机自启', start)
upsert: upsert app.setLoginItemSettings({
}); openAtLogin: start, //win
openAsHidden: start, //macOs
});
}
event.reply("Config.saveConfig.hook", {
err: err,
numberOfUpdated: numberOfUpdated,
upsert: upsert
});
});
}); });
});
ipcMain.on("config.getConfig", async (event, args) => { ipcMain.on("config.getConfig", async (event, args) => {
getConfig((err, doc) => { getConfig((err, doc) => {
event.reply("Config.getConfig.hook", { event.reply("Config.getConfig.hook", {
err: err, err: err,
data: doc data: doc
}); });
});
}); });
});
ipcMain.on("config.versions", event => { ipcMain.on("config.versions", event => {
listVersion((err, doc) => { listVersion((err, doc) => {
event.reply("Config.versions.hook", { event.reply("Config.versions.hook", {
err: err, err: err,
data: doc data: doc
}); });
});
}); });
});
ipcMain.on("config.hasConfig", event => { ipcMain.on("config.hasConfig", event => {
getConfig((err, doc) => { getConfig((err, doc) => {
event.reply("Config.getConfig.hook", { event.reply("Config.getConfig.hook", {
err: err, err: err,
data: doc data: doc
}); });
});
}); });
});
}; };

View File

@ -1,6 +1,6 @@
import {app, ipcMain, Notification} from "electron"; import {app, ipcMain, Notification} from "electron";
import {Config, getConfig} from "../storage/config"; import {Config, getConfig} from "../storage/config";
import {listProxy} from "../storage/proxy"; import {listProxy, Proxy} from "../storage/proxy";
import {getVersionById} from "../storage/version"; import {getVersionById} from "../storage/version";
import treeKill from "tree-kill"; import treeKill from "tree-kill";
@ -25,43 +25,42 @@ const getFrpcVersionWorkerPath = (
) => { ) => {
getVersionById(versionId, (err2, version) => { getVersionById(versionId, (err2, version) => {
if (!err2) { if (!err2) {
callback(version["frpcVersionPath"]); if (version) {
callback(version["frpcVersionPath"]);
}
} }
}); });
}; };
/** /**
* * toml配置文件
* @param config
* @param proxys
*/ */
export const generateConfig = ( const genTomlConfig = (config: Config, proxys: Proxy[]) => {
config: Config, const proxyToml = proxys.map(m => {
callback: (configPath: string) => void let toml = `
) => {
listProxy((err3, proxys) => {
if (!err3) {
const proxyToml = proxys.map(m => {
let toml = `
[[proxies]] [[proxies]]
name = "${m.name}" name = "${m.name}"
type = "${m.type}" type = "${m.type}"
localIP = "${m.localIp}" localIP = "${m.localIp}"
localPort = ${m.localPort} localPort = ${m.localPort}
`; `;
switch (m.type) { switch (m.type) {
case "tcp": case "tcp":
toml += `remotePort = ${m.remotePort}`; toml += `remotePort = ${m.remotePort}`;
break; break;
case "http": case "http":
case "https": case "https":
toml += `customDomains=[${m.customDomains.map(m => `"${m}"`)}]`; toml += `customDomains=[${m.customDomains.map(m => `"${m}"`)}]`;
break; break;
default: default:
break; break;
} }
return toml; return toml;
}); });
let toml = ` const toml = `
serverAddr = "${config.serverAddr}" serverAddr = "${config.serverAddr}"
serverPort = ${config.serverPort} serverPort = ${config.serverPort}
auth.method = "${config.authMethod}" auth.method = "${config.authMethod}"
@ -82,17 +81,92 @@ ${config.proxyConfigEnable ? `
transport.proxyURL = "${config.proxyConfigProxyUrl}" transport.proxyURL = "${config.proxyConfigProxyUrl}"
` : ""} ` : ""}
${proxyToml.join("")} ${proxyToml.join("")}
`; `;
return toml;
}
// const configPath = path.join("frp.toml");
const filename = "frp.toml"; /**
* ini配置
* @param config
* @param proxys
*/
const genIniConfig = (config: Config, proxys: Proxy[]) => {
const proxyIni = proxys.map(m => {
let ini = `
[${m.name}]
type = "${m.type}"
local_ip = "${m.localIp}"
local_port = ${m.localPort}
`;
switch (m.type) {
case "tcp":
ini += `remote_port = ${m.remotePort}`;
break;
case "http":
case "https":
ini += `custom_domains=[${m.customDomains.map(m => `"${m}"`)}]`;
break;
default:
break;
}
return ini;
});
const ini = `
[common]
server_addr = ${config.serverAddr}
server_port = ${config.serverPort}
authentication_method = "${config.authMethod}"
auth_token = "${config.authToken}"
log_file = "frpc.log"
log_level = ${config.logLevel}
log_max_days = ${config.logMaxDays}
admin_addr = 127.0.0.1
admin_port = 57400
tls_enable = ${config.tlsConfigEnable}
${config.tlsConfigEnable ? `
tls_cert_file = ${config.tlsConfigCertFile}
tls_key_file = ${config.tlsConfigKeyFile}
tls_trusted_ca_file = ${config.tlsConfigTrustedCaFile}
tls_server_name = ${config.tlsConfigServerName}
` : ""}
${config.proxyConfigEnable ? `
http_proxy = "${config.proxyConfigProxyUrl}"
` : ""}
${proxyIni.join("")}
`
return ini;
}
/**
*
*/
export const generateConfig = (
config: Config,
callback: (configPath: string) => void
) => {
listProxy((err3, proxys) => {
if (!err3) {
console.log(config, 'config')
const {currentVersion} = config;
let filename = null;
let configContent = "";
if (currentVersion < 124395282) {
// 版本小于v0.52.0
filename = "frp.ini";
configContent = genIniConfig(config, proxys)
} else {
filename = "frp.toml";
configContent = genTomlConfig(config, proxys)
}
const configPath = path.join(app.getPath("userData"), filename) const configPath = path.join(app.getPath("userData"), filename)
console.debug("生成配置成功", configPath) console.debug("生成配置成功", configPath)
fs.writeFile( fs.writeFile(
configPath, // 配置文件目录 configPath, // 配置文件目录
toml, // 配置文件内容 configContent, // 配置文件内容
{flag: "w"}, {flag: "w"},
err => { err => {
if (!err) { if (!err) {
@ -111,6 +185,7 @@ ${proxyToml.join("")}
* @param configPath * @param configPath
*/ */
const startFrpcProcess = (commandPath: string, configPath: string) => { const startFrpcProcess = (commandPath: string, configPath: string) => {
console.log(commandPath, 'commandP')
const command = `${commandPath} -c ${configPath}`; const command = `${commandPath} -c ${configPath}`;
console.info("启动", command) console.info("启动", command)
frpcProcess = spawn(command, { frpcProcess = spawn(command, {
@ -124,7 +199,8 @@ const startFrpcProcess = (commandPath: string, configPath: string) => {
}); });
frpcProcess.stdout.on("error", data => { frpcProcess.stdout.on("error", data => {
console.log("启动错误", data) console.log("启动错误", data)
stopFrpcProcess() stopFrpcProcess(() => {
})
}); });
frpcStatusListener = setInterval(() => { frpcStatusListener = setInterval(() => {
const status = frpcProcessStatus() const status = frpcProcessStatus()
@ -196,6 +272,35 @@ export const frpcProcessStatus = () => {
} }
} }
/**
* frpc流程
* @param config
*/
export const startFrpWorkerProcess = (config: Config) => {
getFrpcVersionWorkerPath(
config.currentVersion,
(frpcVersionPath: string) => {
console.log(1, '1')
if (frpcVersionPath) {
generateConfig(config, configPath => {
const platform = process.platform;
if (platform === 'win32') {
startFrpcProcess(
path.join(frpcVersionPath, "frpc.exe"),
configPath
);
} else {
startFrpcProcess(
path.join(frpcVersionPath, "frpc"),
configPath
);
}
});
}
}
);
}
export const initFrpcApi = () => { export const initFrpcApi = () => {
ipcMain.handle("frpc.running", async (event, args) => { ipcMain.handle("frpc.running", async (event, args) => {
@ -206,38 +311,21 @@ export const initFrpcApi = () => {
getConfig((err1, config) => { getConfig((err1, config) => {
if (!err1) { if (!err1) {
if (config) { if (config) {
getFrpcVersionWorkerPath( startFrpWorkerProcess(config)
config.currentVersion,
(frpcVersionPath: string) => {
generateConfig(config, configPath => {
const platform = process.platform;
if (platform === 'win32') {
startFrpcProcess(
path.join(frpcVersionPath, "frpc.exe"),
configPath
);
} else {
startFrpcProcess(
path.join(frpcVersionPath, "frpc"),
configPath
);
}
});
}
);
} else { } else {
event.reply( event.reply(
"Home.frpc.start.error.hook", "Home.frpc.start.error.hook", "请先前往设置页面,修改配置后再启动"
"请先前往设置页面,修改配置后再启动"
); );
} }
} }
}); });
}); });
ipcMain.on("frpc.stop", () => { ipcMain.on("frpc.stop", () => {
if (frpcProcess && !frpcProcess.killed) { if (frpcProcess && !frpcProcess.killed) {
stopFrpcProcess() stopFrpcProcess(() => {
})
} }
}); });
}; };

View File

@ -1,5 +1,5 @@
import {app, BrowserWindow, ipcMain, net, shell} from "electron"; import {app, BrowserWindow, ipcMain, net, shell} from "electron";
import {insertVersion} from "../storage/version"; import {deleteVersionById, insertVersion} from "../storage/version";
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
@ -86,7 +86,7 @@ export const initGitHubApi = () => {
ipcMain.on("github.getFrpVersions", async event => { ipcMain.on("github.getFrpVersions", async event => {
const request = net.request({ const request = net.request({
method: "get", method: "get",
url: "https://api.github.com/repos/fatedier/frp/releases" url: "https://api.github.com/repos/fatedier/frp/releases?page=1&per_page=1000"
}); });
request.on("response", response => { request.on("response", response => {
let responseData: Buffer = Buffer.alloc(0); let responseData: Buffer = Buffer.alloc(0);
@ -104,6 +104,7 @@ export const initGitHubApi = () => {
const asset = getAdaptiveAsset(m.id); const asset = getAdaptiveAsset(m.id);
if (asset) { if (asset) {
const absPath = `${downloadPath}/${asset.name}`; const absPath = `${downloadPath}/${asset.name}`;
m.absPath = absPath;
m.download_completed = fs.existsSync(absPath); m.download_completed = fs.existsSync(absPath);
} }
return m; return m;
@ -163,6 +164,23 @@ export const initGitHubApi = () => {
}); });
}); });
/**
*
*/
ipcMain.on("github.deleteVersion", async (event, args) => {
const {absPath, id} = args;
console.log('删除下载', args)
if (fs.existsSync(absPath)) {
deleteVersionById(id, () => {
fs.unlinkSync(absPath)
})
}
event.reply("Download.deleteVersion.hook", {
err: null,
data: "删除成功"
});
})
/** /**
* GitHub * GitHub
*/ */

View File

@ -4,9 +4,10 @@ import node_path, {join} from "node:path";
import {initGitHubApi} from "../api/github"; import {initGitHubApi} from "../api/github";
import {initConfigApi} from "../api/config"; import {initConfigApi} from "../api/config";
import {initProxyApi} from "../api/proxy"; import {initProxyApi} from "../api/proxy";
import {initFrpcApi, stopFrpcProcess} from "../api/frpc"; import {initFrpcApi, startFrpWorkerProcess, stopFrpcProcess} from "../api/frpc";
import {initLoggerApi} from "../api/logger"; import {initLoggerApi} from "../api/logger";
import {initFileApi} from "../api/file"; import {initFileApi} from "../api/file";
import {getConfig} from "../storage/config";
// The built directory structure // The built directory structure
// //
// ├─┬ dist-electron // ├─┬ dist-electron
@ -135,6 +136,16 @@ export const createTray = () => {
tray.on('double-click', () => { tray.on('double-click', () => {
win.show(); win.show();
}) })
getConfig((err, config) => {
if (!err) {
if (config) {
if (config.systemStartupConnect) {
startFrpWorkerProcess(config)
}
}
}
})
} }
app.whenReady().then(() => { app.whenReady().then(() => {

View File

@ -1,11 +1,11 @@
import Datastore from "nedb"; import Datastore from "nedb";
import path from "path"; import path from "path";
import { Proxy } from "./proxy"; import {Proxy} from "./proxy";
import { app } from "electron"; import {app} from "electron";
const versionDB = new Datastore({ const versionDB = new Datastore({
autoload: true, autoload: true,
filename: path.join(app.getPath("userData"), "version.db") filename: path.join(app.getPath("userData"), "version.db")
}); });
/** /**
@ -14,10 +14,10 @@ const versionDB = new Datastore({
* @param cb * @param cb
*/ */
export const insertVersion = ( export const insertVersion = (
version: any, version: any,
cb?: (err: Error | null, document: any) => void cb?: (err: Error | null, document: any) => void
) => { ) => {
versionDB.insert(version, cb); versionDB.insert(version, cb);
}; };
/** /**
@ -25,14 +25,18 @@ export const insertVersion = (
* @param cb * @param cb
*/ */
export const listVersion = ( export const listVersion = (
callback: (err: Error | null, documents: any[]) => void callback: (err: Error | null, documents: any[]) => void
) => { ) => {
versionDB.find({}, callback); versionDB.find({}, callback);
}; };
export const getVersionById = ( export const getVersionById = (
id: string, id: string,
callback: (err: Error | null, document: any) => void callback: (err: Error | null, document: any) => void
) => { ) => {
versionDB.findOne({ id: id }, callback); versionDB.findOne({id: id}, callback);
}; };
export const deleteVersionById = (id: string, callback: (err: Error | null, document: any) => void) => {
versionDB.remove({id: id}, callback);
}

View File

@ -1,6 +1,6 @@
{ {
"name": "Frpc-Desktop", "name": "Frpc-Desktop",
"version": "1.0.2", "version": "1.0.3",
"main": "dist-electron/main/index.js", "main": "dist-electron/main/index.js",
"description": "一个frpc桌面客户端", "description": "一个frpc桌面客户端",
"author": "刘嘉伟 <8473136@qq.com>", "author": "刘嘉伟 <8473136@qq.com>",
@ -54,7 +54,7 @@
"vite-plugin-electron-renderer": "^0.14.5", "vite-plugin-electron-renderer": "^0.14.5",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-router": "^4.2.4", "vue-router": "^4.2.4",
"vue-tsc": "^1.8.8", "vue-tsc": "^2.0.22",
"vue-types": "^5.1.1" "vue-types": "^5.1.1"
}, },
"dependencies": { "dependencies": {

View File

@ -19,7 +19,7 @@ const currentRoute = computed(() => {
* 菜单切换 * 菜单切换
* @param mi 菜单索引 * @param mi 菜单索引
*/ */
const handleMenuChange = (route: RouteRecordRaw) => { const handleMenuChange = (route: any) => {
if (currentRoute.value.name === route.name) { if (currentRoute.value.name === route.name) {
return; return;
} }

View File

@ -1,5 +1,6 @@
$main-bg: #F3F3F3; $main-bg: #F3F3F3;
$primary-color: #5F3BB0; $primary-color: #5F3BB0;
$danger-color: #F56C6C;
.main-container { .main-container {
background: $main-bg; background: $main-bg;
@ -84,3 +85,7 @@ $primary-color: #5F3BB0;
.primary-text { .primary-text {
color: $primary-color; color: $primary-color;
} }
.danger-text {
color: $danger-color !important;
}

View File

@ -229,23 +229,6 @@ onUnmounted(() => {
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="服务器地址:" prop="serverAddr"> <el-form-item label="服务器地址:" prop="serverAddr">
<template #label>
<div class="h-full flex items-center mr-1">
<el-popover
placement="top"
trigger="hover"
>
<template #default>
Frps服务端地址 <br/> 支持 <span class="font-black text-[#5A3DAA]">域名</span><span
class="font-black text-[#5A3DAA]">IP</span>
</template>
<template #reference>
<Icon class="text-base" color="#5A3DAA" icon="material-symbols:info"/>
</template>
</el-popover>
</div>
服务器地址
</template>
<el-input <el-input
v-model="formData.serverAddr" v-model="formData.serverAddr"
placeholder="127.0.0.1" placeholder="127.0.0.1"
@ -266,35 +249,17 @@ onUnmounted(() => {
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="验证方式:" prop="authMethod"> <el-form-item label="验证方式:" prop="authMethod">
<template #label>
<div class="h-full flex items-center mr-1">
<el-popover
width="200"
placement="top"
trigger="hover"
>
<template #default>
对应参数<span class="font-black text-[#5A3DAA]">auth.method</span>
</template>
<template #reference>
<Icon class="text-base" color="#5A3DAA" icon="material-symbols:info"/>
</template>
</el-popover>
</div>
验证方式
</template>
<el-select <el-select
v-model="formData.authMethod" v-model="formData.authMethod"
placeholder="请选择验证方式" placeholder="请选择验证方式"
clearable clearable
> >
<el-option label="令牌token" value="token"></el-option> <el-option label="token" value="token"></el-option>
<el-option label="多用户" value="multiuser"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" v-if="formData.authMethod === 'token'"> <el-col :span="24" v-if="formData.authMethod === 'token'">
<el-form-item label="令牌(token" prop="authToken"> <el-form-item label="token" prop="authToken">
<el-input <el-input
placeholder="token" placeholder="token"
type="password" type="password"
@ -302,56 +267,6 @@ onUnmounted(() => {
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" v-if="formData.authMethod === 'multiuser'">
<el-form-item label="用户:" prop="user">
<template #label>
<div class="h-full flex items-center mr-1">
<el-popover
placement="top"
trigger="hover"
>
<template #default>
对应参数<span class="font-black text-[#5A3DAA]">user</span>
</template>
<template #reference>
<Icon class="text-base" color="#5A3DAA" icon="material-symbols:info"/>
</template>
</el-popover>
</div>
用户
</template>
<el-input
placeholder="请输入用户"
v-model="formData.user"
/>
</el-form-item>
</el-col>
<el-col :span="12" v-if="formData.authMethod === 'multiuser'">
<el-form-item label="用户令牌:" prop="metaToken">
<template #label>
<div class="h-full flex items-center mr-1">
<el-popover
width="200"
placement="top"
trigger="hover"
>
<template #default>
对应参数<span class="font-black text-[#5A3DAA]">meta_token</span>
</template>
<template #reference>
<Icon class="text-base" color="#5A3DAA" icon="material-symbols:info"/>
</template>
</el-popover>
</div>
用户令牌
</template>
<el-input
placeholder="请输入用户令牌"
type="password"
v-model="formData.metaToken"
/>
</el-form-item>
</el-col>
<el-col :span="24"> <el-col :span="24">
<div class="h2">TSL Config</div> <div class="h2">TSL Config</div>
</el-col> </el-col>

View File

@ -4,6 +4,7 @@ import {ipcRenderer} from "electron";
import moment from "moment"; import moment from "moment";
import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue"; import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
import {Icon} from "@iconify/vue"; import {Icon} from "@iconify/vue";
import {ElMessage} from "element-plus";
defineComponent({ defineComponent({
name: "Download" name: "Download"
@ -18,6 +19,7 @@ type Version = {
name: string; name: string;
published_at: string; published_at: string;
download_completed: boolean; download_completed: boolean;
absPath: string;
assets: Asset[] assets: Asset[]
}; };
@ -42,6 +44,17 @@ const handleDownload = (version: Version) => {
downloading.value.set(version.id, 0); downloading.value.set(version.id, 0);
}; };
/**
* 删除下载
* @param version
*/
const handleDeleteVersion = (version: Version) => {
ipcRenderer.send("github.deleteVersion", {
id: version.id,
absPath: version.absPath
});
}
const handleInitDownloadHook = () => { const handleInitDownloadHook = () => {
ipcRenderer.on("Download.frpVersionHook", (event, args) => { ipcRenderer.on("Download.frpVersionHook", (event, args) => {
loading.value--; loading.value--;
@ -49,6 +62,7 @@ const handleInitDownloadHook = () => {
m.published_at = moment(m.published_at).format("YYYY-MM-DD HH:mm:ss") m.published_at = moment(m.published_at).format("YYYY-MM-DD HH:mm:ss")
return m as Version; return m as Version;
}) as Array<Version>; }) as Array<Version>;
console.log(versions, 'versions')
}); });
// //
ipcRenderer.on("Download.frpVersionDownloadOnProgress", (event, args) => { ipcRenderer.on("Download.frpVersionDownloadOnProgress", (event, args) => {
@ -67,6 +81,18 @@ const handleInitDownloadHook = () => {
version.download_completed = true; version.download_completed = true;
} }
}); });
ipcRenderer.on("Download.deleteVersion.hook", (event, args) => {
const {err, data} = args
if (!err) {
loading.value++;
ElMessage({
type: "success",
message: "删除成功"
});
handleLoadVersions();
}
})
}; };
onMounted(() => { onMounted(() => {
@ -81,6 +107,7 @@ onUnmounted(() => {
ipcRenderer.removeAllListeners("Download.frpVersionDownloadOnProgress"); ipcRenderer.removeAllListeners("Download.frpVersionDownloadOnProgress");
ipcRenderer.removeAllListeners("Download.frpVersionDownloadOnCompleted"); ipcRenderer.removeAllListeners("Download.frpVersionDownloadOnCompleted");
ipcRenderer.removeAllListeners("Download.frpVersionHook"); ipcRenderer.removeAllListeners("Download.frpVersionHook");
ipcRenderer.removeAllListeners("Download.deleteVersion.hook");
}); });
</script> </script>
<template> <template>
@ -96,7 +123,7 @@ onUnmounted(() => {
<div class="left"> <div class="left">
<div class="mb-2"> <div class="mb-2">
<el-tag>{{ version.name }}</el-tag> <el-tag>{{ version.name }}</el-tag>
<!-- <el-tag class="ml-2">原文件名{{ version.assets[0]?.name }}</el-tag>--> <!-- <el-tag class="ml-2">原文件名{{ version.assets[0]?.name }}</el-tag>-->
</div> </div>
<div class="text-sm"> <div class="text-sm">
发布时间<span class="text-gray-00">{{ 发布时间<span class="text-gray-00">{{
@ -106,11 +133,25 @@ onUnmounted(() => {
</div> </div>
</div> </div>
<div class="right"> <div class="right">
<span <div v-if="version.download_completed">
class="primary-text text-sm font-bold ml-2" <el-button type="text">已下载</el-button>
v-if="version.download_completed" <!-- <span-->
>已下载</span <!-- class="primary-text text-sm font-bold ml-2"-->
> <!-- >已下载</span>-->
<el-button type="text" class="danger-text" @click="handleDeleteVersion(version)">
<Icon class="mr-1" icon="material-symbols:delete"/>
删除
</el-button>
<!-- <div>-->
<!-- <Icon class="mr-1" icon="material-symbols:download-2"/>-->
<!-- <span-->
<!-- class="danger-text text-sm font-bold ml-2"-->
<!-- >删除下载</span>-->
<!-- </div>-->
</div>
<template v-else> <template v-else>
<div class="w-32" v-if="downloading.has(version.id)"> <div class="w-32" v-if="downloading.has(version.id)">
<el-progress <el-progress

BIN
wechat-qr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB