mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-13 12:02:51 +00:00
perf: 优化下载工具
This commit is contained in:
parent
5bbffc4f5c
commit
a8a54593e2
91
electron/electron-down.js
vendored
91
electron/electron-down.js
vendored
@ -20,87 +20,88 @@ function initialize(options = {}) {
|
||||
...options,
|
||||
|
||||
onStarted: (item) => {
|
||||
downloadManager.addDownloadItem(item);
|
||||
syncDownloadItems();
|
||||
},
|
||||
onProgress: (item) => {
|
||||
downloadManager.updateDownloadItem(item.path);
|
||||
downloadManager.add(item);
|
||||
syncDownloadItems();
|
||||
},
|
||||
onCancel: (item) => {
|
||||
downloadManager.updateDownloadItem(item.getSavePath())
|
||||
downloadManager.refresh(item.getSavePath())
|
||||
syncDownloadItems();
|
||||
},
|
||||
onInterrupted: (item) => {
|
||||
downloadManager.refresh(item.getSavePath());
|
||||
syncDownloadItems();
|
||||
},
|
||||
onProgress: (item) => {
|
||||
downloadManager.refresh(item.path);
|
||||
syncDownloadItems();
|
||||
},
|
||||
onCompleted: (item) => {
|
||||
downloadManager.updateDownloadItem(item.path);
|
||||
downloadManager.refresh(item.path);
|
||||
syncDownloadItems();
|
||||
}
|
||||
});
|
||||
|
||||
// IPC - 获取下载任务
|
||||
ipcMain.handle('getDownloadTasks', () => {
|
||||
// IPC
|
||||
ipcMain.handle('downloadManager', async (event, {action, path}) => {
|
||||
switch (action) {
|
||||
case "get": {
|
||||
return {
|
||||
items: downloadManager.getDownloadItems()
|
||||
items: downloadManager.get()
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// IPC - 暂停下载任务
|
||||
ipcMain.handle('pauseDownloadTask', async (event, {path}) => {
|
||||
downloadManager.pauseDownloadItem(path);
|
||||
case "pause": {
|
||||
downloadManager.pause(path);
|
||||
syncDownloadItems();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// IPC - 恢复下载任务
|
||||
ipcMain.handle('resumeDownloadTask', async (event, {path}) => {
|
||||
downloadManager.resumeDownloadItem(path);
|
||||
case "resume": {
|
||||
downloadManager.resume(path);
|
||||
syncDownloadItems();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// IPC - 取消下载任务
|
||||
ipcMain.handle('cancelDownloadTask', async (event, {path}) => {
|
||||
downloadManager.cancelDownloadItem(path);
|
||||
case "cancel": {
|
||||
downloadManager.cancel(path);
|
||||
syncDownloadItems();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// IPC - 从下载历史中移除下载项
|
||||
ipcMain.handle('removeFromDownloadHistory', async (event, {path}) => {
|
||||
downloadManager.removeFromDownloadHistory(path);
|
||||
case "remove": {
|
||||
downloadManager.remove(path);
|
||||
syncDownloadItems();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// IPC - 清理下载历史
|
||||
ipcMain.handle('clearDownloadHistory', async () => {
|
||||
downloadManager.clearHistory();
|
||||
case "removeAll": {
|
||||
downloadManager.removeAll();
|
||||
syncDownloadItems();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// IPC - 打开下载文件
|
||||
ipcMain.handle('openDownloadedFile', async (event, {path}) => {
|
||||
if (fs.existsSync(path)) {
|
||||
case "openFile": {
|
||||
if (!fs.existsSync(path)) {
|
||||
throw new Error('file not found');
|
||||
}
|
||||
return shell.openPath(path);
|
||||
}
|
||||
throw new Error('file not found');
|
||||
});
|
||||
|
||||
// IPC - 显示下载文件
|
||||
ipcMain.handle('showDownloadedFileInFolder', async (event, {path}) => {
|
||||
if (fs.existsSync(path)) {
|
||||
case "showFolder": {
|
||||
if (!fs.existsSync(path)) {
|
||||
throw new Error('file not found');
|
||||
}
|
||||
shell.showItemInFolder(path);
|
||||
return true;
|
||||
}
|
||||
throw new Error('file not found');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function syncDownloadItems() {
|
||||
// 同步下载项到渲染进程
|
||||
if (downloadWindow) {
|
||||
downloadWindow.webContents.send('download-items', downloadManager.getDownloadItems());
|
||||
downloadWindow.webContents.send('download-items', downloadManager.get());
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,7 +124,7 @@ async function open(language = 'zh', theme = 'light') {
|
||||
// 如果窗口已存在,直接显示
|
||||
if (downloadWindow) {
|
||||
// 更新窗口数据
|
||||
await updateDownloadWindow(language, theme)
|
||||
await updateWindow(language, theme)
|
||||
// 显示窗口并聚焦
|
||||
downloadWindow.show();
|
||||
downloadWindow.focus();
|
||||
@ -215,7 +216,7 @@ async function open(language = 'zh', theme = 'light') {
|
||||
|
||||
// 将语言包发送到渲染进程
|
||||
downloadWindow.webContents.once('dom-ready', () => {
|
||||
updateDownloadWindow(language, theme)
|
||||
updateWindow(language, theme)
|
||||
});
|
||||
|
||||
// 显示窗口
|
||||
@ -236,7 +237,7 @@ function destroy() {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateDownloadWindow(language, theme) {
|
||||
async function updateWindow(language, theme) {
|
||||
if (downloadWindow) {
|
||||
try {
|
||||
const finalLanguage = getLanguagePack(language);
|
||||
@ -256,5 +257,5 @@ module.exports = {
|
||||
open,
|
||||
close,
|
||||
destroy,
|
||||
updateDownloadWindow
|
||||
updateWindow
|
||||
}
|
||||
|
||||
8
electron/electron.js
vendored
8
electron/electron.js
vendored
@ -2802,11 +2802,11 @@ ipcMain.on("rendererReq", async (event, args) => {
|
||||
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);
|
||||
ret = await electronDown.updateWindow(args.language, args.theme);
|
||||
break;
|
||||
case 'createDownload':
|
||||
ret = await electronDown.download(mainWindow, args.url, args.options || {});
|
||||
break;
|
||||
case 'watchFile':
|
||||
ret = await watchFile(args.path);
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
"url": "https://github.com/kuaifan/dootask.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dootask/electron-dl": "^4.0.0-rc.1",
|
||||
"@dootask/electron-dl": "^4.0.0-rc.2",
|
||||
"@electron-forge/cli": "^7.8.3",
|
||||
"@electron-forge/maker-deb": "^7.8.3",
|
||||
"@electron-forge/maker-rpm": "^7.8.3",
|
||||
|
||||
@ -1 +1,502 @@
|
||||
123
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Download</title>
|
||||
<link rel="stylesheet" href="./style.css">
|
||||
<script>
|
||||
const getQueryParam = (name) => {
|
||||
const url = window.location.href;
|
||||
const match = url.match(new RegExp('[?&]' + name + '=([^&#]*)'));
|
||||
return match ? decodeURIComponent(match[1]) : null;
|
||||
}
|
||||
|
||||
const updateTheme = (theme) => {
|
||||
const root = document.documentElement;
|
||||
root.classList.toggle('dark', theme === 'dark');
|
||||
root.classList.toggle('light', theme === 'light');
|
||||
};
|
||||
|
||||
updateTheme(getQueryParam('theme'))
|
||||
</script>
|
||||
<script src="../tabs/assets/js/vue.global.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="download-manager">
|
||||
<div class="toolbar">
|
||||
<label class="search">
|
||||
<input class="search-input" v-model.trim="query" :placeholder="lang.searchPlaceholder"></input>
|
||||
</label>
|
||||
<div class="actions">
|
||||
<button class="action-btn" @click="onRemoveAll" :disabled="items.length === 0">{{ lang.removeAll }}</button>
|
||||
<button class="action-btn" @click="onRefresh">{{ lang.refresh }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="tab-content all-tasks">
|
||||
<div v-if="list.length === 0" class="empty-state">
|
||||
<div class="empty-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="48" height="48" fill="currentColor">
|
||||
<path d="M5 20h14v-2H5v2zM19 9h-4V3H9v6H5l7 7 7-7z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="empty-text">{{ query ? lang.noSearchResult : lang.noItems }}</div>
|
||||
</div>
|
||||
<div v-else class="task-list">
|
||||
<div
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="task-item"
|
||||
:class="{'progressing-item': item.state === 'progressing'}"
|
||||
:style="item.state === 'progressing' ? {'--progress': item.percent + '%'} : {'--progress': '0%'}">
|
||||
<div class="task-icon">
|
||||
<div class="file-icon" :class="getFileTypeClass(item)" v-html="getFileIcon(item)"></div>
|
||||
</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">
|
||||
<div class="task-name-clickable" :title="item.filename" @click="onOpenFile(item)">{{ item.filename }}</div>
|
||||
</div>
|
||||
<div class="task-meta">
|
||||
<!-- 大小 -->
|
||||
<span v-if="item.state === 'progressing'" class="file-size">
|
||||
{{ formatBytes(item.received) }}<template v-if="item.total > 0"> / {{ formatBytes(item.total) }}</template> ({{ item.percent }}%)
|
||||
</span>
|
||||
<span v-else class="file-size">
|
||||
{{ formatBytes(item.total) }}
|
||||
</span>
|
||||
<!-- 时间 -->
|
||||
<span v-if="item.state === 'completed'" class="download-time">{{ formatTime(item.endTime) }}</span>
|
||||
<span v-else class="download-time">{{ formatTime(item.startTime) }}</span>
|
||||
<!-- 状态 -->
|
||||
<span v-if="item.state !== 'progressing' || item.paused" class="state" :class="getStateClass(item)">
|
||||
{{ getStateText(item) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-actions">
|
||||
<!-- 下载速度 -->
|
||||
<span v-if="item.state === 'progressing' && item.speed" class="speed">{{ formatBytes(item.speed) }}/s</span>
|
||||
<!-- 复制链接 -->
|
||||
<button @click="copyUrl(item)" class="icon-btn" :title="lang.copyLink">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet" focusable="false" viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
||||
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- 暂停和继续 -->
|
||||
<template v-if="item.state === 'progressing'">
|
||||
<button v-if="item.paused" @click="onResume(item)" class="icon-btn" :title="lang.resume">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M8 5v14l11-7z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button v-else @click="onPause(item)" class="icon-btn" :title="lang.pause">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
<!-- 显示文件夹 -->
|
||||
<button v-if="item.state === 'completed'" @click="onShowFolder(item)" class="icon-btn" :title="lang.showInFolder">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V8h16v10z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- 删除 -->
|
||||
<button @click="onRemove(item)" class="icon-btn danger" :title="lang.remove">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 信息提示框 -->
|
||||
<div v-if="toast.show" class="toast" :class="toast.type">
|
||||
<div class="toast-content">
|
||||
<svg v-if="toast.type === 'success'" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
|
||||
</svg>
|
||||
<svg v-else-if="toast.type === 'error'" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
|
||||
</svg>
|
||||
<span class="toast-message">{{ toast.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const {createApp} = Vue;
|
||||
|
||||
createApp({
|
||||
data() {
|
||||
return {
|
||||
query: '',
|
||||
items: [],
|
||||
lang: {
|
||||
// 语言设置
|
||||
locale: 'zh',
|
||||
title: '下载管理器',
|
||||
|
||||
// 界面文本
|
||||
searchPlaceholder: '搜索文件名或链接...',
|
||||
noItems: '暂无任务',
|
||||
noSearchResult: '未找到匹配的结果',
|
||||
|
||||
// 操作按钮
|
||||
refresh: '刷新',
|
||||
removeAll: "清空历史",
|
||||
copyLink: '复制链接',
|
||||
resume: '继续',
|
||||
pause: '暂停',
|
||||
cancel: '取消',
|
||||
remove: '删除',
|
||||
showInFolder: '显示在文件夹',
|
||||
|
||||
// 状态文本
|
||||
progressing: '下载中',
|
||||
completed: '已完成',
|
||||
cancelled: '已取消',
|
||||
interrupted: '失败',
|
||||
paused: '已暂停',
|
||||
|
||||
// 成功消息
|
||||
copied: "已复制",
|
||||
refreshSuccess: '刷新成功',
|
||||
|
||||
// 确认对话框
|
||||
confirmCancel: '确定要取消此下载任务并删除记录吗?',
|
||||
confirmRemove: '确定要从历史记录中删除此项吗?',
|
||||
confirmRemoveAll: '确定要清空下载历史吗?',
|
||||
|
||||
// 错误消息
|
||||
copyFailed: '复制失败: ',
|
||||
pauseFailed: '暂停失败: ',
|
||||
resumeFailed: '继续失败: ',
|
||||
removeFailed: '删除失败: ',
|
||||
removeAllFailed: '清空失败: ',
|
||||
openFailed: '打开文件失败: ',
|
||||
showFailed: '显示文件失败: ',
|
||||
},
|
||||
toast: {
|
||||
show: false,
|
||||
type: 'success', // success, error
|
||||
message: '',
|
||||
timer: null
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getList();
|
||||
|
||||
// 监听下载任务列表
|
||||
electron?.listener('download-items', (items) => {
|
||||
this.items = items
|
||||
});
|
||||
|
||||
// 接收主题
|
||||
electron?.listener('download-theme', (theme) => {
|
||||
updateTheme(theme)
|
||||
});
|
||||
|
||||
// 接收语言包
|
||||
electron?.listener('download-language', (lang) => {
|
||||
if (lang && typeof lang === 'object') {
|
||||
this.lang = {...this.lang, ...lang};
|
||||
document.title = this.lang.title || document.title;
|
||||
}
|
||||
});
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.toast.timer) {
|
||||
clearTimeout(this.toast.timer);
|
||||
this.toast.timer = null;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
list() {
|
||||
const q = (this.query || '').toLowerCase();
|
||||
return q
|
||||
? this.items.filter(t => (t.filename || '').toLowerCase().includes(q) || (t.url || '').toLowerCase().includes(q))
|
||||
: this.items;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async copyUrl({url, urls}) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(urls.length > 0 ? urls[0] : url);
|
||||
this.showToast(this.lang.copied);
|
||||
} catch (e) {
|
||||
this.errorToast(this.lang.copyFailed + e.message);
|
||||
}
|
||||
},
|
||||
|
||||
async getList() {
|
||||
try {
|
||||
const data = await this.sendAsync('get');
|
||||
this.items = data.items || [];
|
||||
} catch (e) {
|
||||
console.error('加载下载任务失败:', e);
|
||||
}
|
||||
},
|
||||
|
||||
async onRefresh() {
|
||||
await this.getList();
|
||||
this.showToast(this.lang.refreshSuccess, 'success');
|
||||
},
|
||||
|
||||
async onPause({path}) {
|
||||
try {
|
||||
await this.sendAsync('pause', {path});
|
||||
} catch (e) {
|
||||
this.errorToast(this.lang.pauseFailed + e.message);
|
||||
}
|
||||
},
|
||||
|
||||
async onResume({path}) {
|
||||
try {
|
||||
await this.sendAsync('resume', {path});
|
||||
} catch (e) {
|
||||
this.errorToast(this.lang.resumeFailed + e.message);
|
||||
}
|
||||
},
|
||||
|
||||
async onRemove({state, path}) {
|
||||
if (!confirm(state === 'progressing' ? this.lang.confirmCancel : this.lang.confirmRemove)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.sendAsync('remove', {path});
|
||||
} catch (e) {
|
||||
this.errorToast(this.lang.removeFailed + e.message);
|
||||
}
|
||||
},
|
||||
|
||||
async onRemoveAll() {
|
||||
if (!confirm(this.lang.confirmRemoveAll)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.sendAsync('removeAll');
|
||||
} catch (e) {
|
||||
this.errorToast(this.lang.removeAllFailed + e.message);
|
||||
}
|
||||
},
|
||||
|
||||
async onOpenFile({path}) {
|
||||
try {
|
||||
await this.sendAsync('openFile', {path});
|
||||
} catch (e) {
|
||||
this.errorToast(this.lang.openFailed + e.message);
|
||||
}
|
||||
},
|
||||
|
||||
async onShowFolder({path}) {
|
||||
try {
|
||||
await this.sendAsync('showFolder', {path});
|
||||
} catch (e) {
|
||||
this.errorToast(this.lang.showFailed + e.message);
|
||||
}
|
||||
},
|
||||
|
||||
isPaused({state, paused}) {
|
||||
return state === 'progressing' && paused;
|
||||
},
|
||||
|
||||
getStateText({state, paused}) {
|
||||
if (this.isPaused({state, paused})) {
|
||||
return this.lang.paused;
|
||||
}
|
||||
const stateMap = {
|
||||
'progressing': this.lang.progressing,
|
||||
'completed': this.lang.completed,
|
||||
'cancelled': this.lang.cancelled,
|
||||
'interrupted': this.lang.interrupted,
|
||||
};
|
||||
return stateMap[state] || state;
|
||||
},
|
||||
|
||||
getStateClass({state, paused}) {
|
||||
if (this.isPaused({state, paused})) {
|
||||
return 'paused';
|
||||
}
|
||||
return state
|
||||
},
|
||||
|
||||
getFileTypeClass({filename}) {
|
||||
const typeMap = {
|
||||
'pdf': 'file-pdf',
|
||||
'doc': 'file-word',
|
||||
'docx': 'file-word',
|
||||
'xls': 'file-excel',
|
||||
'xlsx': 'file-excel',
|
||||
'ppt': 'file-powerpoint',
|
||||
'pptx': 'file-powerpoint',
|
||||
'jpg': 'file-image',
|
||||
'jpeg': 'file-image',
|
||||
'png': 'file-image',
|
||||
'gif': 'file-image',
|
||||
'svg': 'file-image',
|
||||
'webp': 'file-image',
|
||||
'bmp': 'file-image',
|
||||
'mp4': 'file-video',
|
||||
'avi': 'file-video',
|
||||
'mov': 'file-video',
|
||||
'mkv': 'file-video',
|
||||
'webm': 'file-video',
|
||||
'wmv': 'file-video',
|
||||
'mp3': 'file-audio',
|
||||
'wav': 'file-audio',
|
||||
'flac': 'file-audio',
|
||||
'aac': 'file-audio',
|
||||
'm4a': 'file-audio',
|
||||
'zip': 'file-archive',
|
||||
'rar': 'file-archive',
|
||||
'7z': 'file-archive',
|
||||
'tar': 'file-archive',
|
||||
'gz': 'file-archive',
|
||||
'txt': 'file-text',
|
||||
'md': 'file-text',
|
||||
'rtf': 'file-text',
|
||||
'js': 'file-code',
|
||||
'ts': 'file-code',
|
||||
'css': 'file-code',
|
||||
'html': 'file-code',
|
||||
'php': 'file-code',
|
||||
'py': 'file-code',
|
||||
'java': 'file-code',
|
||||
'cpp': 'file-code',
|
||||
'c': 'file-code',
|
||||
'exe': 'file-exe',
|
||||
'msi': 'file-exe',
|
||||
'dmg': 'file-exe',
|
||||
'deb': 'file-exe',
|
||||
'rpm': 'file-exe',
|
||||
'_': 'file-unknown'
|
||||
};
|
||||
|
||||
return typeMap[this.getFileExt(filename)] || typeMap['_'];
|
||||
},
|
||||
|
||||
getFileIcon({filename}) {
|
||||
const iconMap = {
|
||||
'pdf': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#ea4335"><path d="M20 2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8.5 7.5c0 .83-.67 1.5-1.5 1.5H9v2H7.5V7H10c.83 0 1.5.67 1.5 1.5v1zm5 2c0 .83-.67 1.5-1.5 1.5h-2.5V7H15c.83 0 1.5.67 1.5 1.5v3zm4-3H19v1h1.5V11H19v1h1.5v1.5H17.5V7h4v1.5z"/></svg>',
|
||||
|
||||
'doc': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#4285f4"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 2 2h12c1.11 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/></svg>',
|
||||
'docx': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#4285f4"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 2 2h12c1.11 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/></svg>',
|
||||
|
||||
'xls': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#0f9d58"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 2 2h12c1.11 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/></svg>',
|
||||
'xlsx': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#0f9d58"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 2 2h12c1.11 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/></svg>',
|
||||
|
||||
'ppt': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#ff6d01"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 2 2h12c1.11 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/></svg>',
|
||||
'pptx': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#ff6d01"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 2 2h12c1.11 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/></svg>',
|
||||
|
||||
'jpg': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#9c27b0"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>',
|
||||
'jpeg': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#9c27b0"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>',
|
||||
'png': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#9c27b0"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>',
|
||||
'gif': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#9c27b0"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>',
|
||||
|
||||
'mp4': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#607d8b"><path d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/></svg>',
|
||||
'avi': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#607d8b"><path d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/></svg>',
|
||||
'mov': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#607d8b"><path d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/></svg>',
|
||||
|
||||
'mp3': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#ff9800"><path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21s4.5-2.01 4.5-4.5V7h4V3h-7z"/></svg>',
|
||||
'wav': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#ff9800"><path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21s4.5-2.01 4.5-4.5V7h4V3h-7z"/></svg>',
|
||||
|
||||
'zip': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#795548"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 2 2h12c1.11 0 2-.9 2-2V8l-6-6zm-1 7V3.5L18.5 9H13z"/></svg>',
|
||||
'rar': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#795548"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 2 2h12c1.11 0 2-.9 2-2V8l-6-6zm-1 7V3.5L18.5 9H13z"/></svg>',
|
||||
|
||||
'txt': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#757575"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 2 2h12c1.11 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/></svg>',
|
||||
|
||||
'js': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#f7df1e"><rect width="24" height="24" fill="#323330"/><path d="M12 12v8h2c2 0 3-1 3-3s-1-3-3-3h-2zm-2 0h-2v8h2v-3c0-1 1-2 2-2s2 1 2 2v3h2v-3c0-2-1-4-4-4s-4 2-4 4z" fill="#f7df1e"/></svg>',
|
||||
|
||||
'exe': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#424242"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 2 2h12c1.11 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/></svg>',
|
||||
|
||||
'_': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#9e9e9e"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 2 2h12c1.11 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/></svg>'
|
||||
};
|
||||
|
||||
return iconMap[this.getFileExt(filename)] || iconMap['_'];
|
||||
},
|
||||
|
||||
getFileExt(value) {
|
||||
return `${value}`.split('.').pop()?.toLowerCase();
|
||||
},
|
||||
|
||||
formatBytes(bytes) {
|
||||
if (!bytes || bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
},
|
||||
|
||||
formatTime(timestamp) {
|
||||
if (!timestamp) return '';
|
||||
const date = new Date(timestamp * 1000);
|
||||
const locales = {
|
||||
"zh": "zh-CN",
|
||||
"zh-CHT": "zh-TW",
|
||||
"en": "en-US",
|
||||
"ko": "ko-KR",
|
||||
"ja": "ja-JP",
|
||||
"de": "de-DE",
|
||||
"fr": "fr-FR",
|
||||
"id": "id-ID",
|
||||
"ru": "ru-RU"
|
||||
};
|
||||
const locale = locales[this.lang?.locale] || this.lang?.locale || locales['en'];
|
||||
return date.toLocaleString(locale, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
},
|
||||
|
||||
sendAsync(action, args = {}) {
|
||||
return electron?.sendAsync("downloadManager", {
|
||||
action,
|
||||
...args
|
||||
});
|
||||
},
|
||||
|
||||
showToast(message, type = 'success') {
|
||||
if (this.toast.timer) {
|
||||
clearTimeout(this.toast.timer);
|
||||
this.toast.timer = null;
|
||||
}
|
||||
|
||||
if (this.toast.show) {
|
||||
this.toast.show = false;
|
||||
setTimeout(() => {
|
||||
this.displayToast(message, type);
|
||||
}, 100);
|
||||
} else {
|
||||
this.displayToast(message, type);
|
||||
}
|
||||
},
|
||||
|
||||
errorToast(message) {
|
||||
this.showToast(message, 'error');
|
||||
},
|
||||
|
||||
displayToast(message, type) {
|
||||
this.toast.message = message;
|
||||
this.toast.type = type;
|
||||
this.toast.show = true;
|
||||
|
||||
this.toast.timer = setTimeout(() => {
|
||||
this.toast.show = false;
|
||||
this.toast.timer = null;
|
||||
}, 4000);
|
||||
}
|
||||
}
|
||||
}).mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
68
electron/render/download/style.css
vendored
68
electron/render/download/style.css
vendored
@ -48,12 +48,14 @@
|
||||
|
||||
--color-empty-state: #5f6368;
|
||||
--color-empty-text: #5f6368;
|
||||
--color-status-completed-bg: #e8f5e8;
|
||||
--color-status-completed-text: #137333;
|
||||
--color-status-failed-bg: #fce8e6;
|
||||
--color-status-failed-text: #d93025;
|
||||
--color-status-cancelled-bg: #e8eaed;
|
||||
--color-status-cancelled-text: #5f6368;
|
||||
--color-state-completed-bg: #e8f5e8;
|
||||
--color-state-completed-text: #137333;
|
||||
--color-state-failed-bg: #fce8e6;
|
||||
--color-state-failed-text: #d93025;
|
||||
--color-state-cancelled-bg: #e8eaed;
|
||||
--color-state-cancelled-text: #5f6368;
|
||||
--color-state-paused-bg: #fff3e0;
|
||||
--color-state-paused-text: #f57c00;
|
||||
|
||||
--scrollbar-thumb: rgba(0, 0, 0, 0.2);
|
||||
--scrollbar-thumb-hover: rgba(0, 0, 0, 0.3);
|
||||
@ -102,12 +104,14 @@
|
||||
|
||||
--color-empty-state: #9aa0a6;
|
||||
--color-empty-text: #9aa0a6;
|
||||
--color-status-completed-bg: #1e3a1e;
|
||||
--color-status-completed-text: #81c995;
|
||||
--color-status-failed-bg: #3d1a1a;
|
||||
--color-status-failed-text: #f28b82;
|
||||
--color-status-cancelled-bg: #3c4043;
|
||||
--color-status-cancelled-text: #9aa0a6;
|
||||
--color-state-completed-bg: #1e3a1e;
|
||||
--color-state-completed-text: #81c995;
|
||||
--color-state-failed-bg: #3d1a1a;
|
||||
--color-state-failed-text: #f28b82;
|
||||
--color-state-cancelled-bg: #3c4043;
|
||||
--color-state-cancelled-text: #9aa0a6;
|
||||
--color-state-paused-bg: #522f2f;
|
||||
--color-state-paused-text: #f57c00;
|
||||
|
||||
--scrollbar-thumb: rgba(255, 255, 255, 0.2);
|
||||
--scrollbar-thumb-hover: rgba(255, 255, 255, 0.3);
|
||||
@ -312,7 +316,7 @@ body {
|
||||
}
|
||||
|
||||
/* 整块背景进度条(下载中样式) */
|
||||
.task-item.downloading-item::before {
|
||||
.task-item.progressing-item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -360,9 +364,6 @@ body {
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
color: var(--color-task-name);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.task-name-clickable {
|
||||
@ -370,6 +371,10 @@ body {
|
||||
cursor: pointer;
|
||||
color: var(--color-task-name-clickable);
|
||||
text-decoration: none;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.task-name-clickable:hover {
|
||||
@ -420,26 +425,37 @@ body {
|
||||
color: var(--color-task-meta);
|
||||
}
|
||||
|
||||
.status {
|
||||
.task-meta > span {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.state {
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.status.completed {
|
||||
background: var(--color-status-completed-bg);
|
||||
color: var(--color-status-completed-text);
|
||||
.state.completed {
|
||||
background: var(--color-state-completed-bg);
|
||||
color: var(--color-state-completed-text);
|
||||
}
|
||||
|
||||
.status.failed {
|
||||
background: var(--color-status-failed-bg);
|
||||
color: var(--color-status-failed-text);
|
||||
.state.cancelled {
|
||||
background: var(--color-state-cancelled-bg);
|
||||
color: var(--color-state-cancelled-text);
|
||||
}
|
||||
|
||||
.status.cancelled {
|
||||
background: var(--color-status-cancelled-bg);
|
||||
color: var(--color-status-cancelled-text);
|
||||
.state.interrupted {
|
||||
background: var(--color-state-failed-bg);
|
||||
color: var(--color-state-failed-text);
|
||||
}
|
||||
|
||||
.state.paused {
|
||||
background: var(--color-state-paused-bg);
|
||||
color: var(--color-state-paused-text);
|
||||
}
|
||||
|
||||
.task-actions {
|
||||
|
||||
44
electron/utils/download.js
vendored
44
electron/utils/download.js
vendored
@ -30,7 +30,7 @@ class DownloadManager {
|
||||
* 转换下载项格式
|
||||
* @param {Electron.DownloadItem} downloadItem
|
||||
*/
|
||||
convertItem(downloadItem) {
|
||||
convert(downloadItem) {
|
||||
return {
|
||||
filename: downloadItem.getFilename(),
|
||||
path: downloadItem.getSavePath(),
|
||||
@ -52,27 +52,26 @@ class DownloadManager {
|
||||
* 添加下载项
|
||||
* @param {Electron.DownloadItem} downloadItem
|
||||
*/
|
||||
addDownloadItem(downloadItem) {
|
||||
add(downloadItem) {
|
||||
// 根据保存路径,如果下载项已存在,则取消下载(避免重复下载)
|
||||
this.cancelDownloadItem(downloadItem.getSavePath());
|
||||
this.cancel(downloadItem.getSavePath());
|
||||
|
||||
// 添加下载项
|
||||
this.downloadHistory.unshift({
|
||||
...this.convertItem(downloadItem),
|
||||
...this.convert(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() {
|
||||
get() {
|
||||
return this.downloadHistory.map(item => {
|
||||
return {
|
||||
...item,
|
||||
@ -87,7 +86,7 @@ class DownloadManager {
|
||||
* 更新下载项
|
||||
* @param {string} path
|
||||
*/
|
||||
updateDownloadItem(path) {
|
||||
refresh(path) {
|
||||
const item = this.downloadHistory.find(d => d.path === path)
|
||||
if (!item) {
|
||||
return;
|
||||
@ -97,16 +96,15 @@ class DownloadManager {
|
||||
loger.warn(`Download item not found for path: ${path}`);
|
||||
return;
|
||||
}
|
||||
Object.assign(item, this.convertItem(downloadItem))
|
||||
Object.assign(item, this.convert(downloadItem))
|
||||
store.set('downloadHistory', this.downloadHistory);
|
||||
loger.info(`Download item updated: ${path} - ${item.state} (${item.percent}%)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停下载项
|
||||
* @param {string} path
|
||||
*/
|
||||
pauseDownloadItem(path) {
|
||||
pause(path) {
|
||||
const item = this.downloadHistory.find(d => d.path === path)
|
||||
if (!item) {
|
||||
return;
|
||||
@ -117,14 +115,14 @@ class DownloadManager {
|
||||
return;
|
||||
}
|
||||
downloadItem.pause();
|
||||
this.updateDownloadItem(path);
|
||||
this.refresh(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复下载项
|
||||
* @param {string} path
|
||||
*/
|
||||
resumeDownloadItem(path) {
|
||||
resume(path) {
|
||||
const item = this.downloadHistory.find(d => d.path === path)
|
||||
if (!item) {
|
||||
return;
|
||||
@ -135,14 +133,14 @@ class DownloadManager {
|
||||
return;
|
||||
}
|
||||
downloadItem.resume();
|
||||
this.updateDownloadItem(path);
|
||||
this.refresh(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消下载项
|
||||
* @param {string} path
|
||||
*/
|
||||
cancelDownloadItem(path) {
|
||||
cancel(path) {
|
||||
const item = this.downloadHistory.find(d => d.path === path)
|
||||
if (!item) {
|
||||
return;
|
||||
@ -153,36 +151,36 @@ class DownloadManager {
|
||||
return;
|
||||
}
|
||||
downloadItem.cancel();
|
||||
this.updateDownloadItem(path);
|
||||
this.refresh(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消所有下载项
|
||||
*/
|
||||
cancelAllDownloadItems() {
|
||||
cancelAll() {
|
||||
this.downloadHistory.forEach(item => {
|
||||
this.cancelDownloadItem(item.path);
|
||||
this.cancel(item.path);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 从下载历史中移除下载项
|
||||
* 删除下载项
|
||||
* @param {string} path
|
||||
*/
|
||||
removeFromDownloadHistory(path) {
|
||||
remove(path) {
|
||||
const index = this.downloadHistory.findIndex(item => item.path === path);
|
||||
if (index > -1) {
|
||||
this.cancelDownloadItem(path);
|
||||
this.cancel(path);
|
||||
this.downloadHistory.splice(index, 1);
|
||||
store.set('downloadHistory', this.downloadHistory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空下载历史
|
||||
* 清空下载项
|
||||
*/
|
||||
clearHistory() {
|
||||
this.cancelAllDownloadItems();
|
||||
removeAll() {
|
||||
this.cancelAll();
|
||||
this.downloadHistory = [];
|
||||
store.set('downloadHistory', []);
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
{{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">
|
||||
<FormItem :label="$L('下载内容')">
|
||||
<div class="input-box">
|
||||
{{mateName}}<div class="input-box-push">+</div>{{altName}}<div class="input-box-push">+</div>L
|
||||
</div>
|
||||
|
||||
2
resources/assets/js/store/actions.js
vendored
2
resources/assets/js/store/actions.js
vendored
@ -466,7 +466,7 @@ export default {
|
||||
}
|
||||
if ($A.Electron) {
|
||||
$A.Electron.request({
|
||||
action: 'createDownloadTask',
|
||||
action: 'createDownload',
|
||||
url
|
||||
});
|
||||
$A.Electron.request({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user