mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-11 02:12:53 +00:00
perf: 优化下载工具
This commit is contained in:
parent
f6850fc795
commit
0833018399
314
electron/electron-down.js
vendored
314
electron/electron-down.js
vendored
@ -1,149 +1,14 @@
|
||||
const {BrowserWindow, screen} = require('electron')
|
||||
const path = require('path');
|
||||
const Store = require("electron-store");
|
||||
const loger = require("electron-log");
|
||||
const Store = require('electron-store');
|
||||
const store = new Store({
|
||||
name: 'download-manager',
|
||||
defaults: {
|
||||
downloadHistory: [],
|
||||
}
|
||||
});
|
||||
const electronDl = require("@dootask/electron-dl").default;
|
||||
|
||||
class DownloadManager {
|
||||
constructor() {
|
||||
this.downloadHistory = store.get('downloadHistory', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换下载项格式
|
||||
* @param {Electron.DownloadItem} downloadItem
|
||||
*/
|
||||
convertItem(downloadItem) {
|
||||
return {
|
||||
filename: downloadItem.getFilename(),
|
||||
path: downloadItem.getSavePath(),
|
||||
url: downloadItem.getURL(),
|
||||
urls: downloadItem.getURLChain(),
|
||||
mine: downloadItem.getMimeType(),
|
||||
received: downloadItem.getReceivedBytes(),
|
||||
total: downloadItem.getTotalBytes(),
|
||||
percent: downloadItem.getPercentComplete(),
|
||||
speed: downloadItem.getCurrentBytesPerSecond(),
|
||||
state: downloadItem.getState(),
|
||||
paused: downloadItem.isPaused(),
|
||||
startTime: downloadItem.getStartTime(),
|
||||
endTime: downloadItem.getEndTime(),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加下载项
|
||||
* @param {Electron.DownloadItem} downloadItem
|
||||
*/
|
||||
addDownloadItem(downloadItem) {
|
||||
this.downloadHistory.unshift({
|
||||
...this.convertItem(downloadItem),
|
||||
_source: downloadItem,
|
||||
});
|
||||
store.set('downloadHistory', this.downloadHistory.slice(0, 100)); // 限制最多100个下载项
|
||||
loger.info(`Download item added: ${downloadItem.getSavePath()}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新下载项
|
||||
* @param {string} path
|
||||
*/
|
||||
updateDownloadItem(path) {
|
||||
const item = this.downloadHistory.find(d => d.path === path)
|
||||
if (!item) {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
const downloadItem = item._source;
|
||||
if (!downloadItem) {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
Object.assign(item, this.convertItem(downloadItem))
|
||||
store.set('downloadHistory', this.downloadHistory);
|
||||
loger.info(`Download item updated: ${path} - ${item.state} (${item.percent}%)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停下载项
|
||||
* @param {string} path
|
||||
*/
|
||||
pauseDownloadItem(path) {
|
||||
const item = this.downloadHistory.find(d => d.path === path)
|
||||
if (!item) {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
const downloadItem = item._source;
|
||||
if (!downloadItem) {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
downloadItem.pause();
|
||||
this.updateDownloadItem(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复下载项
|
||||
* @param {string} path
|
||||
*/
|
||||
resumeDownloadItem(path) {
|
||||
const item = this.downloadHistory.find(d => d.path === path)
|
||||
if (!item) {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
const downloadItem = item._source;
|
||||
if (!downloadItem) {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
downloadItem.resume();
|
||||
this.updateDownloadItem(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消下载项
|
||||
* @param {string} path
|
||||
*/
|
||||
cancelDownloadItem(path) {
|
||||
const item = this.downloadHistory.find(d => d.path === path)
|
||||
if (!item) {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
const downloadItem = item._source;
|
||||
if (!downloadItem) {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
downloadItem.cancel();
|
||||
this.updateDownloadItem(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消所有下载项
|
||||
*/
|
||||
cancelAllDownloadItems() {
|
||||
this.downloadHistory.forEach(item => {
|
||||
this.cancelDownloadItem(item.path);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空下载历史
|
||||
*/
|
||||
clearHistory() {
|
||||
this.downloadHistory = [];
|
||||
store.set('downloadHistory', []);
|
||||
}
|
||||
}
|
||||
const {default: electronDl, download} = require("@dootask/electron-dl");
|
||||
const utils = require("./utils");
|
||||
const {DownloadManager} = require("./utils/download");
|
||||
|
||||
const store = new Store();
|
||||
const downloadManager = new DownloadManager();
|
||||
let downloadWindow = null;
|
||||
|
||||
function initialize(options = {}) {
|
||||
// 下载配置
|
||||
@ -155,20 +20,181 @@ function initialize(options = {}) {
|
||||
|
||||
onStarted: (item) => {
|
||||
downloadManager.addDownloadItem(item);
|
||||
syncDownloadItems();
|
||||
},
|
||||
onProgress: (item) => {
|
||||
downloadManager.updateDownloadItem(item.path);
|
||||
syncDownloadItems();
|
||||
},
|
||||
onCancel: (item) => {
|
||||
downloadManager.updateDownloadItem(item.getSavePath())
|
||||
syncDownloadItems();
|
||||
},
|
||||
onCompleted: (item) => {
|
||||
downloadManager.updateDownloadItem(item.path);
|
||||
syncDownloadItems();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function syncDownloadItems() {
|
||||
// 同步下载项到渲染进程
|
||||
if (downloadWindow) {
|
||||
downloadWindow.webContents.send('download-items', downloadManager.getDownloadItems());
|
||||
}
|
||||
}
|
||||
|
||||
function getLanguagePack(codeOrPack) {
|
||||
if (codeOrPack && typeof codeOrPack === 'object') {
|
||||
return codeOrPack;
|
||||
}
|
||||
const code = (codeOrPack || 'zh').toString();
|
||||
return {
|
||||
code,
|
||||
title: '下载管理器',
|
||||
// todo
|
||||
}
|
||||
}
|
||||
|
||||
async function open(language = 'zh', theme = 'light') {
|
||||
// 获取语言包
|
||||
const finalLanguage = getLanguagePack(language);
|
||||
|
||||
// 如果窗口已存在,直接显示
|
||||
if (downloadWindow) {
|
||||
// 更新窗口数据
|
||||
await updateDownloadWindow(language, theme)
|
||||
// 显示窗口并聚焦
|
||||
downloadWindow.show();
|
||||
downloadWindow.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// 窗口默认参数
|
||||
const downloadWindowOptions = {
|
||||
width: 700,
|
||||
height: 480,
|
||||
minWidth: 500,
|
||||
minHeight: 350,
|
||||
center: true,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
title: finalLanguage.title,
|
||||
backgroundColor: utils.getDefaultBackgroundColor(),
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'electron-preload.js'),
|
||||
webSecurity: true,
|
||||
nodeIntegration: true,
|
||||
contextIsolation: true,
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复窗口位置
|
||||
const downloadWindowBounds = store.get('downloadWindowBounds', {});
|
||||
if (
|
||||
downloadWindowBounds.width !== undefined &&
|
||||
downloadWindowBounds.height !== undefined &&
|
||||
downloadWindowBounds.x !== undefined &&
|
||||
downloadWindowBounds.y !== undefined
|
||||
) {
|
||||
// 获取所有显示器的可用区域
|
||||
const displays = screen.getAllDisplays();
|
||||
// 检查窗口是否在任意一个屏幕内
|
||||
let isInScreen = false;
|
||||
for (const display of displays) {
|
||||
const area = display.workArea;
|
||||
if (
|
||||
downloadWindowBounds.x + downloadWindowBounds.width > area.x &&
|
||||
downloadWindowBounds.x < area.x + area.width &&
|
||||
downloadWindowBounds.y + downloadWindowBounds.height > area.y &&
|
||||
downloadWindowBounds.y < area.y + area.height
|
||||
) {
|
||||
isInScreen = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 如果超出所有屏幕,则移动到主屏幕可见区域
|
||||
if (!isInScreen) {
|
||||
const primaryArea = screen.getPrimaryDisplay().workArea;
|
||||
downloadWindowBounds.x = primaryArea.x + 50;
|
||||
downloadWindowBounds.y = primaryArea.y + 50;
|
||||
// 防止窗口太大超出屏幕
|
||||
downloadWindowBounds.width = Math.min(downloadWindowBounds.width, primaryArea.width - 100);
|
||||
downloadWindowBounds.height = Math.min(downloadWindowBounds.height, primaryArea.height - 100);
|
||||
}
|
||||
downloadWindowOptions.width = downloadWindowBounds.width;
|
||||
downloadWindowOptions.height = downloadWindowBounds.height;
|
||||
downloadWindowOptions.center = false;
|
||||
downloadWindowOptions.x = downloadWindowBounds.x;
|
||||
downloadWindowOptions.y = downloadWindowBounds.y;
|
||||
}
|
||||
|
||||
// 创建窗口
|
||||
downloadWindow = new BrowserWindow(downloadWindowOptions);
|
||||
|
||||
// 禁止修改窗口标题
|
||||
downloadWindow.on('page-title-updated', (event) => {
|
||||
event.preventDefault()
|
||||
})
|
||||
|
||||
// 监听窗口关闭保存窗口位置
|
||||
downloadWindow.on('close', () => {
|
||||
const bounds = downloadWindow.getBounds();
|
||||
store.set('downloadWindowBounds', bounds);
|
||||
});
|
||||
|
||||
// 监听窗口关闭事件
|
||||
downloadWindow.on('closed', () => {
|
||||
downloadWindow = null;
|
||||
});
|
||||
|
||||
// 加载下载管理器页面
|
||||
const htmlPath = path.join(__dirname, 'render', 'download', 'index.html');
|
||||
const themeParam = (theme === 'dark' ? 'dark' : 'light');
|
||||
await downloadWindow.loadFile(htmlPath, {query: {theme: themeParam}});
|
||||
|
||||
// 将语言包发送到渲染进程
|
||||
downloadWindow.webContents.once('dom-ready', () => {
|
||||
updateDownloadWindow(language, theme)
|
||||
});
|
||||
|
||||
// 显示窗口
|
||||
downloadWindow.show();
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (downloadWindow) {
|
||||
downloadWindow.close();
|
||||
downloadWindow = null;
|
||||
}
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
if (downloadWindow) {
|
||||
downloadWindow.destroy();
|
||||
downloadWindow = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function updateDownloadWindow(language, theme) {
|
||||
if (downloadWindow) {
|
||||
try {
|
||||
const finalLanguage = getLanguagePack(language);
|
||||
downloadWindow.setTitle(finalLanguage.title);
|
||||
downloadWindow.webContents.send('download-theme', theme);
|
||||
downloadWindow.webContents.send('download-language', finalLanguage);
|
||||
syncDownloadItems()
|
||||
} catch (error) {
|
||||
loger.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initialize
|
||||
initialize,
|
||||
download,
|
||||
open,
|
||||
close,
|
||||
destroy,
|
||||
updateDownloadWindow
|
||||
}
|
||||
|
||||
8
electron/electron-preload.js
vendored
8
electron/electron-preload.js
vendored
@ -32,7 +32,13 @@ contextBridge.exposeInMainWorld(
|
||||
'electron', {
|
||||
request: (msg, callback, error) => {
|
||||
msg.reqId = reqId++;
|
||||
reqInfo[msg.reqId] = {callback: callback, error: error};
|
||||
if (typeof callback !== "function") {
|
||||
callback = function () {};
|
||||
}
|
||||
if (typeof error !== "function") {
|
||||
error = function () {};
|
||||
}
|
||||
reqInfo[msg.reqId] = {callback, error};
|
||||
if (msg.action == 'watchFile') {
|
||||
fileChangedListeners[msg.path] = msg.listener;
|
||||
delete msg.listener;
|
||||
|
||||
55
electron/electron.js
vendored
55
electron/electron.js
vendored
@ -123,9 +123,6 @@ if (!fs.existsSync(cacheDir)) {
|
||||
fs.mkdirSync(cacheDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 初始化下载配置
|
||||
electronDown.initialize()
|
||||
|
||||
/**
|
||||
* 启动web服务
|
||||
*/
|
||||
@ -345,10 +342,10 @@ function createMainWindow() {
|
||||
// 新窗口处理
|
||||
mainWindow.webContents.setWindowOpenHandler(({url}) => {
|
||||
if (allowedCalls.test(url)) {
|
||||
openExternal(url)
|
||||
openExternal(url).catch(() => {})
|
||||
} else {
|
||||
utils.onBeforeOpenWindow(mainWindow.webContents, url).then(() => {
|
||||
openExternal(url)
|
||||
openExternal(url).catch(() => {})
|
||||
})
|
||||
}
|
||||
return {action: 'deny'}
|
||||
@ -587,10 +584,10 @@ function createChildWindow(args) {
|
||||
// 新窗口处理
|
||||
browser.webContents.setWindowOpenHandler(({url}) => {
|
||||
if (allowedCalls.test(url)) {
|
||||
openExternal(url)
|
||||
openExternal(url).catch(() => {})
|
||||
} else {
|
||||
utils.onBeforeOpenWindow(browser.webContents, url).then(() => {
|
||||
openExternal(url)
|
||||
openExternal(url).catch(() => {})
|
||||
})
|
||||
}
|
||||
return {action: 'deny'}
|
||||
@ -866,7 +863,7 @@ function createWebTabWindow(args) {
|
||||
})
|
||||
browserView.webContents.setWindowOpenHandler(({url}) => {
|
||||
if (allowedCalls.test(url)) {
|
||||
openExternal(url)
|
||||
openExternal(url).catch(() => {})
|
||||
} else {
|
||||
createWebTabWindow({url})
|
||||
}
|
||||
@ -1102,6 +1099,8 @@ if (!getTheLock) {
|
||||
}
|
||||
// SameSite
|
||||
utils.useCookie()
|
||||
// 初始化下载
|
||||
electronDown.initialize()
|
||||
// 创建主窗口
|
||||
createMainWindow()
|
||||
// 预创建子窗口
|
||||
@ -1293,7 +1292,7 @@ ipcMain.on('webTabExternal', (event) => {
|
||||
if (!item) {
|
||||
return
|
||||
}
|
||||
openExternal(item.view.webContents.getURL())
|
||||
openExternal(item.view.webContents.getURL()).catch(() => {})
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
|
||||
@ -1358,6 +1357,7 @@ ipcMain.on('childWindowCloseAll', (event) => {
|
||||
})
|
||||
preloadWindow?.close()
|
||||
mediaWindow?.close()
|
||||
electronDown.close()
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
|
||||
@ -1370,6 +1370,7 @@ ipcMain.on('childWindowDestroyAll', (event) => {
|
||||
})
|
||||
preloadWindow?.destroy()
|
||||
mediaWindow?.destroy()
|
||||
electronDown.destroy()
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
|
||||
@ -1714,6 +1715,7 @@ ipcMain.on('updateQuitAndInstall', (event, args) => {
|
||||
})
|
||||
preloadWindow?.destroy()
|
||||
mediaWindow?.destroy()
|
||||
electronDown.destroy()
|
||||
|
||||
// 启动更新子窗口
|
||||
createUpdaterWindow(args.updateTitle)
|
||||
@ -2611,7 +2613,7 @@ function getPluginFile(plugin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function uninstallPlugin(plugin) {
|
||||
async function uninstallPlugin(plugin) {
|
||||
const pluginFile = getPluginFile(plugin);
|
||||
|
||||
if (pluginFile != null) {
|
||||
@ -2672,7 +2674,7 @@ async function deleteFile(file) {
|
||||
}
|
||||
}
|
||||
|
||||
function windowAction(method) {
|
||||
async function windowAction(method) {
|
||||
let win = BrowserWindow.getFocusedWindow();
|
||||
|
||||
if (win) {
|
||||
@ -2692,16 +2694,14 @@ function windowAction(method) {
|
||||
}
|
||||
}
|
||||
|
||||
function openExternal(url) {
|
||||
async function openExternal(url) {
|
||||
//Only open http(s), mailto, tel, and callto links
|
||||
if (allowedUrls.test(url)) {
|
||||
shell.openExternal(url).catch(_ => {});
|
||||
return true;
|
||||
await shell.openExternal(url)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function watchFile(path) {
|
||||
async function watchFile(path) {
|
||||
let win = BrowserWindow.getFocusedWindow();
|
||||
|
||||
if (win) {
|
||||
@ -2713,12 +2713,13 @@ function watchFile(path) {
|
||||
prev: prev
|
||||
});
|
||||
} catch (e) {
|
||||
} // Ignore
|
||||
// Ignore
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function unwatchFile(path) {
|
||||
async function unwatchFile(path) {
|
||||
fs.unwatchFile(path);
|
||||
}
|
||||
|
||||
@ -2747,7 +2748,7 @@ ipcMain.on("rendererReq", async (event, args) => {
|
||||
ret = await getDocumentsFolder();
|
||||
break;
|
||||
case 'checkFileExists':
|
||||
ret = await checkFileExists(args.pathParts);
|
||||
ret = checkFileExists(args.pathParts);
|
||||
break;
|
||||
case 'showOpenDialog':
|
||||
dialogOpen = true;
|
||||
@ -2768,7 +2769,7 @@ ipcMain.on("rendererReq", async (event, args) => {
|
||||
ret = await uninstallPlugin(args.plugin);
|
||||
break;
|
||||
case 'getPluginFile':
|
||||
ret = await getPluginFile(args.plugin);
|
||||
ret = getPluginFile(args.plugin);
|
||||
break;
|
||||
case 'isPluginsEnabled':
|
||||
ret = enablePlugins;
|
||||
@ -2780,7 +2781,7 @@ ipcMain.on("rendererReq", async (event, args) => {
|
||||
ret = await readFile(args.filename, args.encoding);
|
||||
break;
|
||||
case 'clipboardAction':
|
||||
ret = await clipboardAction(args.method, args.data);
|
||||
ret = clipboardAction(args.method, args.data);
|
||||
break;
|
||||
case 'deleteFile':
|
||||
ret = await deleteFile(args.file);
|
||||
@ -2797,6 +2798,15 @@ ipcMain.on("rendererReq", async (event, args) => {
|
||||
case 'openExternal':
|
||||
ret = await openExternal(args.url);
|
||||
break;
|
||||
case 'openDownloadWindow':
|
||||
ret = await electronDown.open(args.language || 'zh', args.theme || 'light');
|
||||
break;
|
||||
case 'createDownloadTask':
|
||||
ret = await electronDown.download(mainWindow, args.url, args.options || {});
|
||||
break;
|
||||
case 'updateDownloadWindow':
|
||||
ret = await electronDown.updateDownloadWindow(args.language, args.theme);
|
||||
break;
|
||||
case 'watchFile':
|
||||
ret = await watchFile(args.path);
|
||||
break;
|
||||
@ -2804,12 +2814,13 @@ ipcMain.on("rendererReq", async (event, args) => {
|
||||
ret = await unwatchFile(args.path);
|
||||
break;
|
||||
case 'getCurDir':
|
||||
ret = await getCurDir();
|
||||
ret = getCurDir();
|
||||
break;
|
||||
}
|
||||
|
||||
event.reply('mainResp', {success: true, data: ret, reqId: args.reqId});
|
||||
} catch (e) {
|
||||
event.reply('mainResp', {error: true, msg: e.message, e: e, reqId: args.reqId});
|
||||
loger.error('Renderer request error', e.message, e.stack);
|
||||
}
|
||||
});
|
||||
|
||||
@ -79,10 +79,10 @@
|
||||
"files": [
|
||||
"render/**/*",
|
||||
"public/**/*",
|
||||
"utils/**/*",
|
||||
"electron-menu.js",
|
||||
"electron-preload.js",
|
||||
"electron.js",
|
||||
"utils.js"
|
||||
"electron.js"
|
||||
],
|
||||
"extraFiles": [
|
||||
{
|
||||
|
||||
1
electron/render/download/index.html
Normal file
1
electron/render/download/index.html
Normal file
@ -0,0 +1 @@
|
||||
123
|
||||
161
electron/utils/download.js
vendored
Normal file
161
electron/utils/download.js
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
const loger = require("electron-log");
|
||||
const Store = require('electron-store');
|
||||
const store = new Store({
|
||||
name: 'download-manager',
|
||||
defaults: {
|
||||
downloadHistory: [],
|
||||
}
|
||||
});
|
||||
|
||||
class DownloadManager {
|
||||
constructor() {
|
||||
this.downloadHistory = store.get('downloadHistory', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换下载项格式
|
||||
* @param {Electron.DownloadItem} downloadItem
|
||||
*/
|
||||
convertItem(downloadItem) {
|
||||
return {
|
||||
filename: downloadItem.getFilename(),
|
||||
path: downloadItem.getSavePath(),
|
||||
url: downloadItem.getURL(),
|
||||
urls: downloadItem.getURLChain(),
|
||||
mine: downloadItem.getMimeType(),
|
||||
received: downloadItem.getReceivedBytes(),
|
||||
total: downloadItem.getTotalBytes(),
|
||||
percent: downloadItem.getPercentComplete(),
|
||||
speed: downloadItem.getCurrentBytesPerSecond(),
|
||||
state: downloadItem.getState(),
|
||||
paused: downloadItem.isPaused(),
|
||||
startTime: downloadItem.getStartTime(),
|
||||
endTime: downloadItem.getEndTime(),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加下载项
|
||||
* @param {Electron.DownloadItem} downloadItem
|
||||
*/
|
||||
addDownloadItem(downloadItem) {
|
||||
this.downloadHistory.unshift({
|
||||
...this.convertItem(downloadItem),
|
||||
_source: downloadItem,
|
||||
});
|
||||
if (this.downloadHistory.length > 1000) {
|
||||
this.downloadHistory = this.downloadHistory.slice(0, 1000);
|
||||
}
|
||||
store.set('downloadHistory', this.downloadHistory);
|
||||
loger.info(`Download item added: ${downloadItem.getSavePath()}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下载列表
|
||||
* @returns {*}
|
||||
*/
|
||||
getDownloadItems() {
|
||||
return this.downloadHistory.map(item => {
|
||||
return {
|
||||
...item,
|
||||
_source: undefined, // 移除源对象,避免序列化问题
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新下载项
|
||||
* @param {string} path
|
||||
*/
|
||||
updateDownloadItem(path) {
|
||||
const item = this.downloadHistory.find(d => d.path === path)
|
||||
if (!item) {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
const downloadItem = item._source;
|
||||
if (!downloadItem) {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
Object.assign(item, this.convertItem(downloadItem))
|
||||
store.set('downloadHistory', this.downloadHistory);
|
||||
loger.info(`Download item updated: ${path} - ${item.state} (${item.percent}%)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停下载项
|
||||
* @param {string} path
|
||||
*/
|
||||
pauseDownloadItem(path) {
|
||||
const item = this.downloadHistory.find(d => d.path === path)
|
||||
if (!item) {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
const downloadItem = item._source;
|
||||
if (!downloadItem) {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
downloadItem.pause();
|
||||
this.updateDownloadItem(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复下载项
|
||||
* @param {string} path
|
||||
*/
|
||||
resumeDownloadItem(path) {
|
||||
const item = this.downloadHistory.find(d => d.path === path)
|
||||
if (!item) {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
const downloadItem = item._source;
|
||||
if (!downloadItem) {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
downloadItem.resume();
|
||||
this.updateDownloadItem(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消下载项
|
||||
* @param {string} path
|
||||
*/
|
||||
cancelDownloadItem(path) {
|
||||
const item = this.downloadHistory.find(d => d.path === path)
|
||||
if (!item) {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
const downloadItem = item._source;
|
||||
if (!downloadItem) {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
downloadItem.cancel();
|
||||
this.updateDownloadItem(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消所有下载项
|
||||
*/
|
||||
cancelAllDownloadItems() {
|
||||
this.downloadHistory.forEach(item => {
|
||||
this.cancelDownloadItem(item.path);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空下载历史
|
||||
*/
|
||||
clearHistory() {
|
||||
this.downloadHistory = [];
|
||||
store.set('downloadHistory', []);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { DownloadManager };
|
||||
@ -2150,3 +2150,4 @@ API URL
|
||||
需要先设置 AI 助理
|
||||
|
||||
打开签到机器人
|
||||
下载内容
|
||||
|
||||
@ -401,20 +401,14 @@ export default {
|
||||
if (this.isMeetingUrlStrict(url)) {
|
||||
return 1;
|
||||
}
|
||||
// 同域名下载链接
|
||||
if (this.isDownloadUrl(url)) {
|
||||
return 1;
|
||||
}
|
||||
// 同域名规则
|
||||
if ($A.getDomain(url) == $A.getDomain($A.mainUrl())) {
|
||||
try {
|
||||
const {pathname, searchParams} = new URL(url);
|
||||
// uploads/ 上传文件
|
||||
// api/dialog/msg/download 会话文件
|
||||
// api/project/task/filedown 任务文件
|
||||
if (/^\/(uploads|api\/dialog\/msg\/download|api\/project\/task\/filedown)/.test(pathname)) {
|
||||
return 1;
|
||||
}
|
||||
// api/file/content?down=yes 文件下载
|
||||
if (/^\/api\/file\/content/.test(pathname) && searchParams.get('down') === 'yes') {
|
||||
return 1;
|
||||
}
|
||||
// meeting/1234567890/xxxxx 会议
|
||||
if (/^\/meeting\/\d+\/\S+$/.test(pathname)) {
|
||||
const meetingId = pathname.split('/')[2];
|
||||
@ -438,6 +432,32 @@ export default {
|
||||
return 0;
|
||||
},
|
||||
|
||||
isDownloadUrl(url) {
|
||||
if ($A.getDomain(url) == $A.getDomain($A.mainUrl())) {
|
||||
try {
|
||||
const {pathname, searchParams} = new URL(url);
|
||||
// 匹配常见的下载相关路径
|
||||
const downloadPathPatterns = [
|
||||
'/uploads', // 上传文件
|
||||
'/api/dialog/msg/download', // 会话文件
|
||||
'/api/project/task/filedown', // 任务文件
|
||||
'/api/file/download/pack', // 文件打包下载
|
||||
'/api/approve/down', // 审批导出下载
|
||||
'/api/project/task/down', // 任务导出下载
|
||||
'/api/system/checkin/down' // 签到导出下载
|
||||
];
|
||||
if (downloadPathPatterns.some(pattern => $A.leftExists(pathname, pattern))) {
|
||||
return true;
|
||||
}
|
||||
// 匹配文件内容下载(/api/file/content 带参数 down=yes)
|
||||
if ($A.leftExists(pathname, '/api/file/content') && searchParams.get('down') === 'yes') {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
isApplicationProtocol(url) {
|
||||
const protocols = [
|
||||
'thunder:', // 迅雷专有链接
|
||||
@ -576,6 +596,10 @@ export default {
|
||||
return true;
|
||||
} else if (urlType === 1) {
|
||||
// 使用默认浏览器打开
|
||||
if (this.isDownloadUrl(url)) {
|
||||
this.$store.dispatch('downUrl', url)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// 使用内置浏览器打开
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
<!-- 胶囊工具栏 -->
|
||||
<div v-if="capsuleMenuShow" class="micro-modal-cmask"></div>
|
||||
<div class="micro-modal-capsule" :style="capsuleStyle">
|
||||
<div class="micro-modal-capsule-item" v-touchclick="onCapsuleMore">
|
||||
<div class="micro-modal-capsule-item" @click="onCapsuleMore">
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 11C3.10457 11 4 10.1046 4 9C4 7.89543 3.10457 7 2 7C0.895431 7 0 7.89543 0 9C0 10.1046 0.895431 11 2 11Z" fill="currentColor"/>
|
||||
<path d="M9 12C10.6569 12 12 10.6569 12 9C12 7.34315 10.6569 6 9 6C7.34315 6 6 7.34315 6 9C6 10.6569 7.34315 12 9 12Z" fill="currentColor"/>
|
||||
@ -17,7 +17,7 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="micro-modal-capsule-line"></div>
|
||||
<div class="micro-modal-capsule-item" v-touchclick="attemptClose">
|
||||
<div class="micro-modal-capsule-item" @click="attemptClose">
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 16C12.866 16 16 12.866 16 9C16 5.13401 12.866 2 9 2C5.13401 2 2 5.13401 2 9C2 12.866 5.13401 16 9 16Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 12C10.6569 12 12 10.6569 12 9C12 7.34315 10.6569 6 9 6C7.34315 6 6 7.34315 6 9C6 10.6569 7.34315 12 9 12Z" fill="currentColor"/>
|
||||
@ -48,13 +48,12 @@
|
||||
|
||||
<script>
|
||||
import TransferDom from "../../directives/transfer-dom";
|
||||
import touchclick from "../../directives/touchclick";
|
||||
import ResizeLine from "../ResizeLine.vue";
|
||||
|
||||
export default {
|
||||
name: 'MicroModal',
|
||||
components: {ResizeLine},
|
||||
directives: {TransferDom, touchclick},
|
||||
directives: {TransferDom},
|
||||
props: {
|
||||
open: {
|
||||
type: Boolean,
|
||||
|
||||
@ -392,6 +392,7 @@ import notificationKoro from "notification-koro1";
|
||||
import emitter from "../store/events";
|
||||
import SearchBox from "../components/SearchBox.vue";
|
||||
import transformEmojiToHtml from "../utils/emoji";
|
||||
import {languageName} from "../language";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -516,6 +517,7 @@ export default {
|
||||
'cacheDialogs',
|
||||
'cacheProjects',
|
||||
'projectTotal',
|
||||
'themeName',
|
||||
'wsOpenNum',
|
||||
'columnTemplate',
|
||||
|
||||
@ -629,7 +631,8 @@ export default {
|
||||
menu() {
|
||||
const {userIsAdmin} = this;
|
||||
const array = [
|
||||
{path: 'taskBrowse', name: '最近打开的任务'}
|
||||
{path: 'taskBrowse', name: '最近打开的任务'},
|
||||
{path: 'download', name: '下载内容', visible: !!this.$Electron},
|
||||
];
|
||||
if (userIsAdmin) {
|
||||
array.push(...[
|
||||
@ -643,7 +646,6 @@ export default {
|
||||
{path: 'archivedProject', name: '已归档的项目'},
|
||||
|
||||
{path: 'team', name: '团队管理', divided: true},
|
||||
{path: 'complaint', name: '举报管理'},
|
||||
])
|
||||
} else {
|
||||
array.push(...[
|
||||
@ -727,6 +729,19 @@ export default {
|
||||
this.$store.dispatch("getProjectByQueue", 600);
|
||||
},
|
||||
|
||||
themeName: {
|
||||
handler(theme) {
|
||||
if (this.$Electron) {
|
||||
$A.Electron.request({
|
||||
action: 'updateDownloadWindow',
|
||||
language: languageName,
|
||||
theme,
|
||||
});
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
|
||||
'cacheProjects.length': {
|
||||
handler() {
|
||||
this.$nextTick(_ => {
|
||||
@ -819,6 +834,13 @@ export default {
|
||||
case 'complaint':
|
||||
this.complaintShow = true;
|
||||
return;
|
||||
case 'download':
|
||||
$A.Electron.request({
|
||||
action: 'openDownloadWindow',
|
||||
language: languageName,
|
||||
theme: this.themeName,
|
||||
});
|
||||
return;
|
||||
case 'logout':
|
||||
$A.modalConfirm({
|
||||
title: '退出登录',
|
||||
@ -978,6 +1000,13 @@ export default {
|
||||
this.onAddMenu('task')
|
||||
break;
|
||||
|
||||
case 76: // L - 下载内容(+ alt)
|
||||
if (e.altKey) {
|
||||
e.preventDefault();
|
||||
this.settingRoute('download')
|
||||
}
|
||||
break;
|
||||
|
||||
case 85: // U - 创建群组
|
||||
this.onCreateGroup([this.userId])
|
||||
break;
|
||||
|
||||
@ -88,7 +88,7 @@ export default {
|
||||
]
|
||||
|
||||
if (this.$Electron || this.$isEEUIApp) {
|
||||
menu.push({path: 'keyboard', name: '键盘设置', desc: ' (Beta)'})
|
||||
menu.push({path: 'keyboard', name: '键盘设置'})
|
||||
}
|
||||
|
||||
if ($A.isDooServer() && this.$isEEUIApp) {
|
||||
|
||||
@ -7,6 +7,11 @@
|
||||
{{mateName}}<div class="input-box-push">+</div>Shift<div class="input-box-push">+</div><Input class="input-box-key" v-model="formData.screenshot_key" :maxlength="2"/>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem :label="$L('下载内容')" prop="download_key">
|
||||
<div class="input-box">
|
||||
{{mateName}}<div class="input-box-push">+</div>{{altName}}<div class="input-box-push">+</div>L
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem :label="$L('新建项目')">
|
||||
<div class="input-box">
|
||||
{{mateName}}<div class="input-box-push">+</div>B
|
||||
@ -14,7 +19,7 @@
|
||||
</FormItem>
|
||||
<FormItem :label="$L('新建任务')">
|
||||
<div class="input-box">
|
||||
{{mateName}}<div class="input-box-push">+</div>N (K)
|
||||
{{mateName}}<div class="input-box-push">+</div>N
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem :label="$L('新会议')">
|
||||
@ -71,7 +76,7 @@ export default {
|
||||
loadIng: 0,
|
||||
|
||||
mateName: /macintosh|mac os x/i.test(navigator.userAgent) ? 'Command' : 'Ctrl',
|
||||
|
||||
altName: /macintosh|mac os x/i.test(navigator.userAgent) ? 'Option' : 'Alt',
|
||||
|
||||
formData: {
|
||||
screenshot_key: '',
|
||||
|
||||
12
resources/assets/js/store/actions.js
vendored
12
resources/assets/js/store/actions.js
vendored
@ -465,10 +465,14 @@ export default {
|
||||
url = $A.urlAddParams(url, params);
|
||||
}
|
||||
if ($A.Electron) {
|
||||
$A.Electron.request({action: 'openExternal', url}, () => {
|
||||
// 成功
|
||||
}, () => {
|
||||
// 失败
|
||||
$A.Electron.request({
|
||||
action: 'createDownloadTask',
|
||||
url
|
||||
});
|
||||
$A.Electron.request({
|
||||
action: 'openDownloadWindow',
|
||||
language: languageName,
|
||||
theme: state.themeName,
|
||||
});
|
||||
} else if ($A.isEEUIApp) {
|
||||
$A.eeuiAppOpenWeb(url);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user