refactor: 优化 webTab 管理和状态同步

- 封装 safeCloseWebTab 方法,复用标签关闭时的未保存数据检查逻辑
  - 添加 recreatePreloadPool,支持主题切换后重建预加载池
  - broadcastCommand 扩展到 webTab views,确保子窗口收到同步消息
  - 修复 synchTheme 和 saveDialogDraft 的跨窗口参数传递
  - IDBDel 返回 Promise 并正确 await
This commit is contained in:
kuaifan 2026-01-14 10:11:41 +08:00
parent 9c7ec58bb6
commit a34b0c88d5
6 changed files with 93 additions and 58 deletions

View File

@ -789,11 +789,20 @@ ipcMain.on('windowMax', (event) => {
ipcMain.on('broadcastCommand', (event, args) => { ipcMain.on('broadcastCommand', (event, args) => {
const channel = args.channel || args.command const channel = args.channel || args.command
const payload = args.payload || args.data const payload = args.payload || args.data
// 广播给所有 BrowserWindow
BrowserWindow.getAllWindows().forEach(window => { BrowserWindow.getAllWindows().forEach(window => {
if (window.webContents.id !== event.sender.id) { if (window.webContents.id !== event.sender.id) {
window.webContents.send(channel, payload) 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" event.returnValue = "ok"
}) })

View File

@ -220,6 +220,38 @@ function clearPreloadPool() {
preloadViewPool = [] 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 return
} }
// 点击窗口关闭按钮:依次检查并关闭每个标签页 // 点击窗口关闭按钮:依次检查并关闭每个标签页
const checkAndCloseTabs = async () => { 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 * @param windowId 源窗口ID
@ -1432,6 +1490,14 @@ function destroyAllWindowMode() {
function registerIPC() { function registerIPC() {
const electronMenu = getElectronMenu() const electronMenu = getElectronMenu()
/**
* 重建预加载池
*/
ipcMain.on('recreatePreloadPool', (event) => {
recreatePreloadPool()
event.returnValue = "ok"
})
/** /**
* 获取路由窗口信息 webTabWindows 中查找 mode='window' 的窗口 * 获取路由窗口信息 webTabWindows 中查找 mode='window' 的窗口
*/ */
@ -1598,21 +1664,7 @@ function registerIPC() {
windowId = findWindowIdByTabId(tabId) windowId = findWindowIdByTabId(tabId)
} }
if (windowId) { if (windowId) {
const windowData = webTabWindows.get(windowId) safeCloseWebTab(windowId, tabId)
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)
} }
event.returnValue = "ok" event.returnValue = "ok"
}) })
@ -1726,23 +1778,6 @@ function registerIPC() {
event.returnValue = "ok" 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 tabId = event.sender.id
const windowId = findWindowIdByTabId(tabId) const windowId = findWindowIdByTabId(tabId)
if (windowId !== null) { if (windowId !== null) {
const windowData = webTabWindows.get(windowId) safeCloseWebTab(windowId, tabId)
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)
} else { } else {
const win = BrowserWindow.fromWebContents(event.sender) const win = BrowserWindow.fromWebContents(event.sender)
win?.close() win?.close()

View File

@ -889,10 +889,10 @@ const timezone = require("dayjs/plugin/timezone");
/** /**
* 输入框数字限制 * 输入框数字限制
* @param object * @param object
* @param min * @param min
* @param max * @param max
* @returns * @returns
*/ */
inputNumberLimit(object, min = null, max = null) { inputNumberLimit(object, min = null, max = null) {
if (object === null || typeof object !== "object") return; if (object === null || typeof object !== "object") return;
@ -1674,7 +1674,7 @@ const timezone = require("dayjs/plugin/timezone");
}, },
IDBDel(key) { IDBDel(key) {
localforage.removeItem(key).then(_ => {}) return localforage.removeItem(key)
}, },
IDBSet(key, value) { IDBSet(key, value) {

View File

@ -95,7 +95,7 @@ function setLanguage(language, silence = false) {
if (silence) { if (silence) {
utils.saveLanguage(language); utils.saveLanguage(language);
(async () => { (async () => {
$A.IDBDel("callAt") await $A.IDBDel("callAt")
$A.reloadUrl() $A.reloadUrl()
})() })()
} else { } else {

View File

@ -56,6 +56,7 @@ export default {
return return
} }
$A.messageSuccess('保存成功'); $A.messageSuccess('保存成功');
$A.Electron?.sendMessage('recreatePreloadPool');
}) })
} }
}) })

View File

@ -556,7 +556,7 @@ export default {
resolve(false) resolve(false)
return; return;
} }
dispatch("synchTheme", mode) dispatch("synchTheme", {mode})
resolve(true) resolve(true)
}); });
}, },
@ -566,8 +566,11 @@ export default {
* @param state * @param state
* @param dispatch * @param dispatch
* @param mode * @param mode
* @param args
*/ */
synchTheme({state, dispatch}, mode = undefined) { synchTheme({state, dispatch}, {mode, ...args} = {}) {
$A.syncDispatch("synchTheme", {...args, mode})
//
if (typeof mode === "undefined") { if (typeof mode === "undefined") {
mode = state.themeConf mode = state.themeConf
} else { } else {
@ -3814,13 +3817,14 @@ export default {
* @param id * @param id
* @param content * @param content
* @param immediate * @param immediate
* @param args
*/ */
saveDialogDraft({commit}, {id, content, immediate = false}) { saveDialogDraft({commit}, {id, content, immediate = false, ...args}) {
if ($A.isSubElectron) { if ($A.isSubElectron) {
dialogDraftState.subTemp = {id, content, immediate: true} dialogDraftState.subTemp = {id, content, immediate: true}
return return
} }
$A.syncDispatch("saveDialogDraft", {id, content, immediate}) $A.syncDispatch("saveDialogDraft", {...args, id, content, immediate})
// 清除已有的计时器 // 清除已有的计时器
if (dialogDraftState.timer[id]) { if (dialogDraftState.timer[id]) {