From a34b0c88d5c42de318166a3f1a96920afb2e6be3 Mon Sep 17 00:00:00 2001 From: kuaifan Date: Wed, 14 Jan 2026 10:11:41 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=20webTab=20?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=92=8C=E7=8A=B6=E6=80=81=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 封装 safeCloseWebTab 方法,复用标签关闭时的未保存数据检查逻辑 - 添加 recreatePreloadPool,支持主题切换后重建预加载池 - broadcastCommand 扩展到 webTab views,确保子窗口收到同步消息 - 修复 synchTheme 和 saveDialogDraft 的跨窗口参数传递 - IDBDel 返回 Promise 并正确 await --- electron/electron.js | 9 ++ electron/lib/web-tab-manager.js | 117 +++++++++++------- resources/assets/js/functions/common.js | 10 +- resources/assets/js/language/index.js | 2 +- .../assets/js/pages/manage/setting/theme.vue | 1 + resources/assets/js/store/actions.js | 12 +- 6 files changed, 93 insertions(+), 58 deletions(-) diff --git a/electron/electron.js b/electron/electron.js index 5e6acc2e6..431de56d8 100644 --- a/electron/electron.js +++ b/electron/electron.js @@ -789,11 +789,20 @@ ipcMain.on('windowMax', (event) => { ipcMain.on('broadcastCommand', (event, args) => { const channel = args.channel || args.command const payload = args.payload || args.data + // 广播给所有 BrowserWindow BrowserWindow.getAllWindows().forEach(window => { if (window.webContents.id !== event.sender.id) { window.webContents.send(channel, payload) } }) + // 广播给 webTabManager 中的所有 view + for (const [, windowData] of webTabManager.getWebTabWindows()) { + windowData.views?.forEach(({ view }) => { + if (view && !view.webContents.isDestroyed() && view.webContents.id !== event.sender.id) { + view.webContents.send(channel, payload) + } + }) + } event.returnValue = "ok" }) diff --git a/electron/lib/web-tab-manager.js b/electron/lib/web-tab-manager.js index 3f9f9dfd9..94179d76f 100644 --- a/electron/lib/web-tab-manager.js +++ b/electron/lib/web-tab-manager.js @@ -220,6 +220,38 @@ function clearPreloadPool() { preloadViewPool = [] } +/** + * 重建预加载池(清理后重新创建) + */ +function recreatePreloadPool() { + clearPreloadPool() + while (preloadViewPool.length < PRELOAD_CONFIG.poolSize) { + const view = createPreloadView() + if (view) { + preloadViewPool.push(view) + } else { + break + } + } +} + +/** + * 内置浏览器 - 延迟发送导航状态 + */ +function notifyNavigationState(item) { + setTimeout(() => { + const wd = webTabWindows.get(item.view.webTabWindowId) + if (wd && wd.window) { + utils.onDispatchEvent(wd.window.webContents, { + event: 'navigation-state', + id: item.id, + canGoBack: item.view.webContents.navigationHistory.canGoBack(), + canGoForward: item.view.webContents.navigationHistory.canGoForward() + }).then(_ => { }) + } + }, 100) +} + // ============================================================ // 核心函数 // ============================================================ @@ -527,7 +559,7 @@ function createWebTabWindowInstance(windowId, position, mode = 'tab') { }) return } - + // 点击窗口关闭按钮:依次检查并关闭每个标签页 const checkAndCloseTabs = async () => { // 复制标签列表,因为关闭时会修改原数组 @@ -1080,6 +1112,32 @@ function closeWebTabInWindow(windowId, id) { } } +/** + * 安全关闭标签(检查未保存数据后关闭) + * @param windowId 窗口ID + * @param tabId 标签ID + */ +function safeCloseWebTab(windowId, tabId) { + const windowData = webTabWindows.get(windowId) + if (!windowData) { + closeWebTabInWindow(windowId, tabId) + return + } + + const tab = windowData.views.find(v => v.id === tabId) + if (!tab) { + closeWebTabInWindow(windowId, tabId) + return + } + + const proxyWindow = Object.create(windowData.window, { + webContents: { get: () => tab.view.webContents } + }) + utils.onBeforeUnload({ preventDefault: () => {} }, proxyWindow).then(() => { + closeWebTabInWindow(windowId, tabId) + }) +} + /** * 分离标签到新窗口 * @param windowId 源窗口ID @@ -1432,6 +1490,14 @@ function destroyAllWindowMode() { function registerIPC() { const electronMenu = getElectronMenu() + /** + * 重建预加载池 + */ + ipcMain.on('recreatePreloadPool', (event) => { + recreatePreloadPool() + event.returnValue = "ok" + }) + /** * 获取路由窗口信息(从 webTabWindows 中查找 mode='window' 的窗口) */ @@ -1598,21 +1664,7 @@ function registerIPC() { windowId = findWindowIdByTabId(tabId) } if (windowId) { - const windowData = webTabWindows.get(windowId) - if (windowData) { - const tab = windowData.views.find(v => v.id === tabId) - if (tab) { - const proxyWindow = Object.create(windowData.window, { - webContents: { get: () => tab.view.webContents } - }) - utils.onBeforeUnload({ preventDefault: () => {} }, proxyWindow).then(() => { - closeWebTabInWindow(windowId, tabId) - }) - event.returnValue = "ok" - return - } - } - closeWebTabInWindow(windowId, tabId) + safeCloseWebTab(windowId, tabId) } event.returnValue = "ok" }) @@ -1726,23 +1778,6 @@ function registerIPC() { event.returnValue = "ok" }) - /** - * 内置浏览器 - 延迟发送导航状态 - */ - function notifyNavigationState(item) { - setTimeout(() => { - const wd = webTabWindows.get(item.view.webTabWindowId) - if (wd && wd.window) { - utils.onDispatchEvent(wd.window.webContents, { - event: 'navigation-state', - id: item.id, - canGoBack: item.view.webContents.navigationHistory.canGoBack(), - canGoForward: item.view.webContents.navigationHistory.canGoForward() - }).then(_ => { }) - } - }, 100) - } - /** * 内置浏览器 - 后退 */ @@ -1864,21 +1899,7 @@ function registerIPC() { const tabId = event.sender.id const windowId = findWindowIdByTabId(tabId) if (windowId !== null) { - const windowData = webTabWindows.get(windowId) - if (windowData) { - const tab = windowData.views.find(v => v.id === tabId) - if (tab) { - const proxyWindow = Object.create(windowData.window, { - webContents: { get: () => tab.view.webContents } - }) - utils.onBeforeUnload({ preventDefault: () => {} }, proxyWindow).then(() => { - closeWebTabInWindow(windowId, tabId) - }) - event.returnValue = "ok" - return - } - } - closeWebTabInWindow(windowId, tabId) + safeCloseWebTab(windowId, tabId) } else { const win = BrowserWindow.fromWebContents(event.sender) win?.close() diff --git a/resources/assets/js/functions/common.js b/resources/assets/js/functions/common.js index 3b0cce599..ca13f3fae 100755 --- a/resources/assets/js/functions/common.js +++ b/resources/assets/js/functions/common.js @@ -889,10 +889,10 @@ const timezone = require("dayjs/plugin/timezone"); /** * 输入框数字限制 - * @param object - * @param min - * @param max - * @returns + * @param object + * @param min + * @param max + * @returns */ inputNumberLimit(object, min = null, max = null) { if (object === null || typeof object !== "object") return; @@ -1674,7 +1674,7 @@ const timezone = require("dayjs/plugin/timezone"); }, IDBDel(key) { - localforage.removeItem(key).then(_ => {}) + return localforage.removeItem(key) }, IDBSet(key, value) { diff --git a/resources/assets/js/language/index.js b/resources/assets/js/language/index.js index 202a9bc73..7019e5f8d 100644 --- a/resources/assets/js/language/index.js +++ b/resources/assets/js/language/index.js @@ -95,7 +95,7 @@ function setLanguage(language, silence = false) { if (silence) { utils.saveLanguage(language); (async () => { - $A.IDBDel("callAt") + await $A.IDBDel("callAt") $A.reloadUrl() })() } else { diff --git a/resources/assets/js/pages/manage/setting/theme.vue b/resources/assets/js/pages/manage/setting/theme.vue index b4bdb7148..26137886a 100644 --- a/resources/assets/js/pages/manage/setting/theme.vue +++ b/resources/assets/js/pages/manage/setting/theme.vue @@ -56,6 +56,7 @@ export default { return } $A.messageSuccess('保存成功'); + $A.Electron?.sendMessage('recreatePreloadPool'); }) } }) diff --git a/resources/assets/js/store/actions.js b/resources/assets/js/store/actions.js index 16de1643a..263c3e9da 100644 --- a/resources/assets/js/store/actions.js +++ b/resources/assets/js/store/actions.js @@ -556,7 +556,7 @@ export default { resolve(false) return; } - dispatch("synchTheme", mode) + dispatch("synchTheme", {mode}) resolve(true) }); }, @@ -566,8 +566,11 @@ export default { * @param state * @param dispatch * @param mode + * @param args */ - synchTheme({state, dispatch}, mode = undefined) { + synchTheme({state, dispatch}, {mode, ...args} = {}) { + $A.syncDispatch("synchTheme", {...args, mode}) + // if (typeof mode === "undefined") { mode = state.themeConf } else { @@ -3814,13 +3817,14 @@ export default { * @param id * @param content * @param immediate + * @param args */ - saveDialogDraft({commit}, {id, content, immediate = false}) { + saveDialogDraft({commit}, {id, content, immediate = false, ...args}) { if ($A.isSubElectron) { dialogDraftState.subTemp = {id, content, immediate: true} return } - $A.syncDispatch("saveDialogDraft", {id, content, immediate}) + $A.syncDispatch("saveDialogDraft", {...args, id, content, immediate}) // 清除已有的计时器 if (dialogDraftState.timer[id]) {