From 5bbffc4f5cf9b455d05ef7075caaa980488ee859 Mon Sep 17 00:00:00 2001 From: kuaifan Date: Thu, 14 Aug 2025 17:33:02 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/electron-down.js | 62 +++- electron/electron.js | 5 +- electron/render/download/style.css | 527 +++++++++++++++++++++++++++++ electron/utils/download.js | 42 ++- 4 files changed, 627 insertions(+), 9 deletions(-) create mode 100644 electron/render/download/style.css diff --git a/electron/electron-down.js b/electron/electron-down.js index 1b2302ecd..223f7035b 100644 --- a/electron/electron-down.js +++ b/electron/electron-down.js @@ -1,4 +1,5 @@ -const {BrowserWindow, screen} = require('electron') +const {BrowserWindow, screen, shell, ipcMain} = require('electron') +const fs = require('fs'); const path = require('path'); const Store = require("electron-store"); const loger = require("electron-log"); @@ -35,6 +36,65 @@ function initialize(options = {}) { syncDownloadItems(); } }); + + // IPC - 获取下载任务 + ipcMain.handle('getDownloadTasks', () => { + return { + items: downloadManager.getDownloadItems() + }; + }); + + // IPC - 暂停下载任务 + ipcMain.handle('pauseDownloadTask', async (event, {path}) => { + downloadManager.pauseDownloadItem(path); + syncDownloadItems(); + return true; + }); + + // IPC - 恢复下载任务 + ipcMain.handle('resumeDownloadTask', async (event, {path}) => { + downloadManager.resumeDownloadItem(path); + syncDownloadItems(); + return true; + }); + + // IPC - 取消下载任务 + ipcMain.handle('cancelDownloadTask', async (event, {path}) => { + downloadManager.cancelDownloadItem(path); + syncDownloadItems(); + return true; + }); + + // IPC - 从下载历史中移除下载项 + ipcMain.handle('removeFromDownloadHistory', async (event, {path}) => { + downloadManager.removeFromDownloadHistory(path); + syncDownloadItems(); + return true; + }); + + // IPC - 清理下载历史 + ipcMain.handle('clearDownloadHistory', async () => { + downloadManager.clearHistory(); + syncDownloadItems(); + return true; + }); + + // IPC - 打开下载文件 + ipcMain.handle('openDownloadedFile', async (event, {path}) => { + if (fs.existsSync(path)) { + return shell.openPath(path); + } + throw new Error('file not found'); + }); + + // IPC - 显示下载文件 + ipcMain.handle('showDownloadedFileInFolder', async (event, {path}) => { + if (fs.existsSync(path)) { + shell.showItemInFolder(path); + return true; + } + throw new Error('file not found'); + }); } function syncDownloadItems() { diff --git a/electron/electron.js b/electron/electron.js index 2cbc84219..e3fd2784f 100644 --- a/electron/electron.js +++ b/electron/electron.js @@ -123,6 +123,9 @@ if (!fs.existsSync(cacheDir)) { fs.mkdirSync(cacheDir, { recursive: true }); } +// 初始化下载 +electronDown.initialize() + /** * 启动web服务 */ @@ -1099,8 +1102,6 @@ if (!getTheLock) { } // SameSite utils.useCookie() - // 初始化下载 - electronDown.initialize() // 创建主窗口 createMainWindow() // 预创建子窗口 diff --git a/electron/render/download/style.css b/electron/render/download/style.css new file mode 100644 index 000000000..9e204b1a1 --- /dev/null +++ b/electron/render/download/style.css @@ -0,0 +1,527 @@ +/* 下载管理器样式 - Chrome 风格 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --color-bg: #fff; + --color-text: #202124; + + --color-toolbar-bg: #fff; + --color-toolbar-border: #dadce0; + + --color-input-bg: #efefef; + --color-input-text: #202124; + --color-input-placeholder: #5f6368; + --color-input-focus-border: #1a73e8; + --color-input-focus-ring: rgba(26, 115, 232, .2); + + --color-action-btn-bg: #fff; + --color-action-btn-border: #dadce0; + --color-action-btn-text: #1a73e8; + --color-action-btn-hover-bg: #f8f9fa; + --color-action-btn-hover-border: #c8c9ca; + --color-action-btn-danger-text: #d93025; + --color-action-btn-danger-hover-bg: #fce8e6; + + --color-icon-btn: #5f6368; + --color-icon-btn-hover-bg: #f8f9fa; + --color-icon-btn-hover-color: #202124; + --color-icon-btn-danger: #d93025; + --color-icon-btn-danger-hover-bg: #fce8e6; + --color-icon-btn-danger-hover-color: #d93025; + + --color-content-bg: #fff; + --color-task-item-bg: #fff; + --color-task-item-border: #e8eaed; + --color-task-item-hover-bg: #f8f9fa; + --color-task-name: #202124; + --color-task-name-clickable: #1a73e8; + + --color-progress-bar: #e8eaed; + --color-progress-fill: #1a73e8; + --color-progress-text: #5f6368; + --color-task-meta: #5f6368; + --color-speed: #1a73e8; + + --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; + + --scrollbar-thumb: rgba(0, 0, 0, 0.2); + --scrollbar-thumb-hover: rgba(0, 0, 0, 0.3); +} + +.dark { + --color-bg: #202124; + --color-text: #e8eaed; + + --color-toolbar-bg: #2d2e30; + --color-toolbar-border: #3c4043; + + --color-input-bg: #282828; + --color-input-text: #e8eaed; + --color-input-placeholder: #9aa0a6; + --color-input-focus-border: #8ab4f8; + --color-input-focus-ring: rgba(138, 180, 248, .12); + + --color-action-btn-bg: #2d2e30; + --color-action-btn-border: #5f6368; + --color-action-btn-text: #8ab4f8; + --color-action-btn-hover-bg: #35363a; + --color-action-btn-hover-border: #70757a; + --color-action-btn-danger-text: #f28b82; + --color-action-btn-danger-hover-bg: #35363a; + + --color-icon-btn: #9aa0a6; + --color-icon-btn-hover-bg: #35363a; + --color-icon-btn-hover-color: #e8eaed; + --color-icon-btn-danger: #f28b82; + --color-icon-btn-danger-hover-bg: #3d1a1a; + --color-icon-btn-danger-hover-color: #f28b82; + + --color-content-bg: #2d2e30; + --color-task-item-bg: #2d2e30; + --color-task-item-border: #3c4043; + --color-task-item-hover-bg: #35363a; + --color-task-name: #e8eaed; + --color-task-name-clickable: #8ab4f8; + + --color-progress-bar: #3c4043; + --color-progress-fill: #8ab4f8; + --color-progress-text: #9aa0a6; + --color-task-meta: #9aa0a6; + --color-speed: #8ab4f8; + + --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; + + --scrollbar-thumb: rgba(255, 255, 255, 0.2); + --scrollbar-thumb-hover: rgba(255, 255, 255, 0.3); + + body { + color-scheme: dark; + } +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: var(--color-bg); + color: var(--color-text); + font-size: 13px; + overflow: hidden; +} + +.download-manager { + display: flex; + flex-direction: column; + height: 100vh; +} + +/* 工具栏 */ +.toolbar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 32px; + padding: 0 24px; + background: var(--color-toolbar-bg); + border-bottom: 1px solid var(--color-toolbar-border); + flex-shrink: 0; +} + +.search { + flex: 1; + padding: 8px 0; +} + +.search-input { + width: 100%; + max-width: 380px; + height: 32px; + padding: 0 12px 0 32px; + border: 0; + border-radius: 6px; + margin: 1px 0; + font-size: 13px; + outline: none; + color: var(--color-input-text); + background: var(--color-input-bg) url('data:image/svg+xml;utf8,') no-repeat 8px center; + background-size: 18px 18px; + transition: all 0.3s; +} + +.search-input::placeholder { + color: var(--color-input-placeholder); +} + +.search-input:focus { + border-color: var(--color-input-focus-border); + box-shadow: 0 0 0 2px var(--color-input-focus-ring); +} + +.actions { + display: flex; + gap: 10px; +} + +.action-btn { + padding: 6px 12px; + border: 1px solid var(--color-action-btn-border); + background: var(--color-action-btn-bg); + cursor: pointer; + border-radius: 4px; + font-size: 12px; + color: var(--color-action-btn-text); + transition: all 0.15s; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.action-btn:hover:not(:disabled) { + background: var(--color-action-btn-hover-bg); + border-color: var(--color-action-btn-hover-border); +} + +.action-btn:disabled { + opacity: 0.38; + cursor: not-allowed; +} + +.action-btn.small { + padding: 4px 8px; + font-size: 11px; +} + +.action-btn.danger { + color: var(--color-action-btn-danger-text); +} + +.action-btn.danger:hover:not(:disabled) { + background: var(--color-action-btn-danger-hover-bg); +} + +/* 图标按钮样式 */ +.icon-btn { + padding: 6px; + border: none; + background: transparent; + cursor: pointer; + border-radius: 4px; + color: var(--color-icon-btn); + transition: all 0.15s; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.icon-btn svg { + width: 20px; + height: 20px; +} + +.icon-btn:hover { + background: var(--color-icon-btn-hover-bg); + color: var(--color-icon-btn-hover-color); +} + +.icon-btn.danger { + color: var(--color-icon-btn-danger); +} + +.icon-btn.danger:hover { + background: var(--color-icon-btn-danger-hover-bg); + color: var(--color-icon-btn-danger-hover-color); +} + +/* 内容区 */ +.content { + flex: 1; + overflow: hidden; + background: var(--color-content-bg); +} + +.tab-content { + height: 100%; + overflow-y: auto; +} + +/* 空状态 */ +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + padding: 48px 24px; + color: var(--color-empty-state); + text-align: center; +} + +.empty-icon { + margin-bottom: 20px; + opacity: 0.6; +} + +.empty-text { + font-size: 14px; + color: var(--color-empty-text); +} + +/* 任务列表 */ +.task-list { + padding: 0; +} + +.task-item { + display: flex; + align-items: center; + padding: 16px 24px; + background: var(--color-task-item-bg); + border-bottom: 1px solid var(--color-task-item-border); + transition: background-color 0.15s; + position: relative; + overflow: hidden; +} + +.task-item > * { + position: relative; + z-index: 1; +} + +.task-item:hover { + background: var(--color-task-item-hover-bg); +} + +.task-item:last-child { + border-bottom: none; +} + +/* 整块背景进度条(下载中样式) */ +.task-item.downloading-item::before { + content: ''; + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: var(--progress, 0%); + background: var(--color-progress-fill); + opacity: 0.12; + pointer-events: none; + transition: width 0.3s ease; + z-index: 0; +} + +.task-icon { + margin-right: 16px; + flex-shrink: 0; +} + +.file-icon { + width: 40px; + height: 40px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; +} + +.file-icon svg { + width: 28px; + height: 28px; +} + +.task-info { + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + gap: 6px; + min-width: 0; +} + +.task-name { + min-width: 0; + width: auto; + font-weight: 400; + font-size: 13px; + color: var(--color-task-name); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.task-name-clickable { + display: inline-block; + cursor: pointer; + color: var(--color-task-name-clickable); + text-decoration: none; +} + +.task-name-clickable:hover { + text-decoration: underline; +} + +.task-progress { + margin-bottom: 4px; +} + +.progress-bar { + width: 100%; + height: 4px; + background: var(--color-progress-bar); + border-radius: 2px; + margin-bottom: 6px; + overflow: hidden; +} + +.progress-fill { + height: 100%; + background: var(--color-progress-fill); + border-radius: 2px; + transition: width 0.3s ease; +} + +.progress-text { + font-size: 11px; + color: var(--color-progress-text); + display: flex; + justify-content: space-between; + align-items: center; +} + +.speed { + display: flex; + align-items: center; + color: var(--color-speed); + font-weight: 400; +} + +.task-meta { + display: flex; + align-items: center; + gap: 12px; + height: 20px; + font-size: 12px; + color: var(--color-task-meta); +} + +.status { + 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); +} + +.status.failed { + background: var(--color-status-failed-bg); + color: var(--color-status-failed-text); +} + +.status.cancelled { + background: var(--color-status-cancelled-bg); + color: var(--color-status-cancelled-text); +} + +.task-actions { + display: flex; + gap: 12px; + margin-left: 16px; + flex-shrink: 0; +} + + +/* 平台特定样式 */ +body.darwin { + font-family: -apple-system, BlinkMacSystemFont, sans-serif; +} + +body.win32 { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +/* 滚动条样式 */ +.tab-content::-webkit-scrollbar { + width: 8px; +} + +.tab-content::-webkit-scrollbar-track { + background: transparent; +} + +.tab-content::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); + border-radius: 4px; +} + +.tab-content::-webkit-scrollbar-thumb:hover { + background: var(--scrollbar-thumb-hover); +} + +/* 深色模式通过 .dark 类启用变量,不再依赖系统配色偏好 */ + +/* Toast 提示框样式 */ +.toast { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 1000; + animation: toast-slide-up 0.3s ease-out; +} + +.toast-content { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + background: #323232; + color: #fff; + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + font-size: 13px; + min-width: 200px; + white-space: nowrap; +} + +.toast.success .toast-content { + background: #4caf50; +} + +.toast.error .toast-content { + background: #f44336; +} + +.toast-message { + flex: 1; +} + +@keyframes toast-slide-up { + from { + opacity: 0; + transform: translate(-50%, 20px); + } + to { + opacity: 1; + transform: translate(-50%, 0); + } +} diff --git a/electron/utils/download.js b/electron/utils/download.js index f74f47580..33e9ccc10 100644 --- a/electron/utils/download.js +++ b/electron/utils/download.js @@ -1,5 +1,6 @@ const loger = require("electron-log"); const Store = require('electron-store'); +const utils = require("./index"); const store = new Store({ name: 'download-manager', defaults: { @@ -9,7 +10,20 @@ const store = new Store({ class DownloadManager { constructor() { - this.downloadHistory = store.get('downloadHistory', []); + const history = store.get('downloadHistory', []); + if (utils.isArray(history)) { + this.downloadHistory = history.map(item => ({ + ...item, + + // 历史记录中,将 progressing 状态改为 interrupted + state: item.state === 'progressing' ? 'interrupted' : item.state, + + // 移除源对象,避免序列化问题 + _source: undefined, + })); + } else { + this.downloadHistory = []; + } } /** @@ -39,6 +53,10 @@ class DownloadManager { * @param {Electron.DownloadItem} downloadItem */ addDownloadItem(downloadItem) { + // 根据保存路径,如果下载项已存在,则取消下载(避免重复下载) + this.cancelDownloadItem(downloadItem.getSavePath()); + + // 添加下载项 this.downloadHistory.unshift({ ...this.convertItem(downloadItem), _source: downloadItem, @@ -58,7 +76,9 @@ class DownloadManager { return this.downloadHistory.map(item => { return { ...item, - _source: undefined, // 移除源对象,避免序列化问题 + + // 移除源对象,避免序列化问题 + _source: undefined, }; }); } @@ -70,7 +90,6 @@ class DownloadManager { 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; @@ -90,7 +109,6 @@ class DownloadManager { 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; @@ -109,7 +127,6 @@ class DownloadManager { 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; @@ -128,7 +145,6 @@ class DownloadManager { 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; @@ -149,10 +165,24 @@ class DownloadManager { }); } + /** + * 从下载历史中移除下载项 + * @param {string} path + */ + removeFromDownloadHistory(path) { + const index = this.downloadHistory.findIndex(item => item.path === path); + if (index > -1) { + this.cancelDownloadItem(path); + this.downloadHistory.splice(index, 1); + store.set('downloadHistory', this.downloadHistory); + } + } + /** * 清空下载历史 */ clearHistory() { + this.cancelAllDownloadItems(); this.downloadHistory = []; store.set('downloadHistory', []); }