mirror of
https://github.com/kuaifan/dootask.git
synced 2026-01-15 11:18:12 +00:00
refactor: 优化标签页加载状态管理与 URL 加载逻辑
- 新增 loadContentUrl 方法统一处理完整 URL 和相对路径的加载 - 优化标签页加载状态,忽略 SPA 路由切换(isSameDocument),避免频繁闪烁 - 添加定时检查器确保加载状态正确停止 - windowClose/windowDestroy 支持识别 tab 页面发送者,仅关闭对应标签 - 子窗口重启过程中不再意外销毁窗口 - 微应用打开标签页时传递标题信息 - isLocalHost 对空 URL 和相对路径返回 true
This commit is contained in:
parent
ce42c2a660
commit
4929d44ce7
102
electron/electron.js
vendored
102
electron/electron.js
vendored
@ -625,10 +625,10 @@ function createChildWindow(args) {
|
|||||||
// 加载地址
|
// 加载地址
|
||||||
const hash = `${args.hash || args.path}`;
|
const hash = `${args.hash || args.path}`;
|
||||||
if (/^https?:/i.test(hash)) {
|
if (/^https?:/i.test(hash)) {
|
||||||
browser.loadURL(hash)
|
// 完整 URL 直接加载
|
||||||
.then(_ => { })
|
browser.loadURL(hash).then(_ => { }).catch(_ => { })
|
||||||
.catch(_ => { })
|
|
||||||
} else if (isPreload) {
|
} else if (isPreload) {
|
||||||
|
// preload 窗口尝试调用 __initializeApp,失败则 loadUrl
|
||||||
browser
|
browser
|
||||||
.webContents
|
.webContents
|
||||||
.executeJavaScript(`if(typeof window.__initializeApp === 'function'){window.__initializeApp('${hash}')}else{throw new Error('no function')}`, true)
|
.executeJavaScript(`if(typeof window.__initializeApp === 'function'){window.__initializeApp('${hash}')}else{throw new Error('no function')}`, true)
|
||||||
@ -636,6 +636,7 @@ function createChildWindow(args) {
|
|||||||
utils.loadUrl(browser, serverUrl, hash)
|
utils.loadUrl(browser, serverUrl, hash)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// 相对路径使用 loadUrl
|
||||||
utils.loadUrl(browser, serverUrl, hash)
|
utils.loadUrl(browser, serverUrl, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -774,7 +775,7 @@ function createWebTabWindow(args) {
|
|||||||
|
|
||||||
// force=true 时重新加载
|
// force=true 时重新加载
|
||||||
if (args.force === true && args.url) {
|
if (args.force === true && args.url) {
|
||||||
viewItem.view.webContents.loadURL(args.url).catch(_ => {});
|
utils.loadContentUrl(viewItem.view.webContents, serverUrl, args.url);
|
||||||
}
|
}
|
||||||
return existing.windowId;
|
return existing.windowId;
|
||||||
}
|
}
|
||||||
@ -1050,10 +1051,13 @@ function createWebTabView(windowId, args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
browserView.webContents.on('destroyed', () => {
|
browserView.webContents.on('destroyed', () => {
|
||||||
// 清理 name 映射
|
|
||||||
if (browserView.tabName) {
|
if (browserView.tabName) {
|
||||||
webTabNameMap.delete(browserView.tabName);
|
webTabNameMap.delete(browserView.tabName);
|
||||||
}
|
}
|
||||||
|
if (browserView._loadingChecker) {
|
||||||
|
clearInterval(browserView._loadingChecker);
|
||||||
|
browserView._loadingChecker = null;
|
||||||
|
}
|
||||||
closeWebTabInWindow(windowId, browserView.webContents.id);
|
closeWebTabInWindow(windowId, browserView.webContents.id);
|
||||||
});
|
});
|
||||||
browserView.webContents.setWindowOpenHandler(({url}) => {
|
browserView.webContents.setWindowOpenHandler(({url}) => {
|
||||||
@ -1134,27 +1138,46 @@ function createWebTabView(windowId, args) {
|
|||||||
favicon: base64Favicon || ''
|
favicon: base64Favicon || ''
|
||||||
}).then(_ => { });
|
}).then(_ => { });
|
||||||
});
|
});
|
||||||
browserView.webContents.on('did-start-loading', _ => {
|
// 页面加载状态管理,忽略SPA路由切换(isSameDocument)
|
||||||
// 使用动态窗口ID,支持标签在窗口间转移
|
browserView._loadingActive = false;
|
||||||
const currentWindowId = browserView.webTabWindowId;
|
browserView._loadingChecker = null;
|
||||||
const wd = webTabWindows.get(currentWindowId);
|
const dispatchLoading = (event) => {
|
||||||
if (!wd || !wd.window) return;
|
const wd = webTabWindows.get(browserView.webTabWindowId);
|
||||||
utils.onDispatchEvent(wd.window.webContents, {
|
if (wd && wd.window) {
|
||||||
event: 'start-loading',
|
utils.onDispatchEvent(wd.window.webContents, {
|
||||||
id: browserView.webContents.id,
|
event,
|
||||||
}).then(_ => { });
|
id: browserView.webContents.id,
|
||||||
|
}).then(_ => { });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const startLoading = () => {
|
||||||
|
if (browserView._loadingActive) return;
|
||||||
|
browserView._loadingActive = true;
|
||||||
|
dispatchLoading('start-loading');
|
||||||
|
if (!browserView._loadingChecker) {
|
||||||
|
browserView._loadingChecker = setInterval(() => {
|
||||||
|
if (browserView.webContents.isDestroyed() || !browserView.webContents.isLoading()) {
|
||||||
|
stopLoading();
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const stopLoading = () => {
|
||||||
|
if (browserView._loadingChecker) {
|
||||||
|
clearInterval(browserView._loadingChecker);
|
||||||
|
browserView._loadingChecker = null;
|
||||||
|
}
|
||||||
|
if (!browserView._loadingActive) return;
|
||||||
|
browserView._loadingActive = false;
|
||||||
|
dispatchLoading('stop-loading');
|
||||||
|
};
|
||||||
|
browserView.webContents.on('did-start-navigation', (_, _url, _isInPlace, isMainFrame, _frameProcessId, _frameRoutingId, _navigationId, isSameDocument) => {
|
||||||
|
if (isMainFrame && !isSameDocument) {
|
||||||
|
startLoading();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
browserView.webContents.on('did-stop-loading', _ => {
|
browserView.webContents.on('did-stop-loading', _ => {
|
||||||
// 使用动态窗口ID,支持标签在窗口间转移
|
stopLoading();
|
||||||
const currentWindowId = browserView.webTabWindowId;
|
|
||||||
const wd = webTabWindows.get(currentWindowId);
|
|
||||||
if (!wd || !wd.window) return;
|
|
||||||
utils.onDispatchEvent(wd.window.webContents, {
|
|
||||||
event: 'stop-loading',
|
|
||||||
id: browserView.webContents.id,
|
|
||||||
}).then(_ => { });
|
|
||||||
|
|
||||||
// 加载完成暗黑模式下把窗口背景色改成白色,避免透明网站背景色穿透
|
|
||||||
if (nativeTheme.shouldUseDarkColors) {
|
if (nativeTheme.shouldUseDarkColors) {
|
||||||
browserView.setBackgroundColor('#FFFFFF');
|
browserView.setBackgroundColor('#FFFFFF');
|
||||||
}
|
}
|
||||||
@ -1179,7 +1202,8 @@ function createWebTabView(windowId, args) {
|
|||||||
|
|
||||||
electronMenu.webContentsMenu(browserView.webContents, true);
|
electronMenu.webContentsMenu(browserView.webContents, true);
|
||||||
|
|
||||||
browserView.webContents.loadURL(args.url).then(_ => { }).catch(_ => { });
|
// 加载地址
|
||||||
|
utils.loadContentUrl(browserView.webContents, serverUrl, args.url);
|
||||||
|
|
||||||
browserView.setVisible(true);
|
browserView.setVisible(true);
|
||||||
|
|
||||||
@ -2128,20 +2152,36 @@ ipcMain.on('windowHidden', (event) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭窗口
|
* 关闭窗口(或关闭 tab,如果发送者是 tab 中的页面)
|
||||||
*/
|
*/
|
||||||
ipcMain.on('windowClose', (event) => {
|
ipcMain.on('windowClose', (event) => {
|
||||||
const win = BrowserWindow.fromWebContents(event.sender);
|
const tabId = event.sender.id;
|
||||||
win.close()
|
const windowId = findWindowIdByTabId(tabId);
|
||||||
|
if (windowId !== null) {
|
||||||
|
// 发送者是 tab 中的页面,只关闭这个 tab
|
||||||
|
closeWebTabInWindow(windowId, tabId);
|
||||||
|
} else {
|
||||||
|
// 发送者是独立窗口,关闭整个窗口
|
||||||
|
const win = BrowserWindow.fromWebContents(event.sender);
|
||||||
|
win?.close()
|
||||||
|
}
|
||||||
event.returnValue = "ok"
|
event.returnValue = "ok"
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 销毁窗口
|
* 销毁窗口(或销毁 tab,如果发送者是 tab 中的页面)
|
||||||
*/
|
*/
|
||||||
ipcMain.on('windowDestroy', (event) => {
|
ipcMain.on('windowDestroy', (event) => {
|
||||||
const win = BrowserWindow.fromWebContents(event.sender);
|
const tabId = event.sender.id;
|
||||||
win.destroy()
|
const windowId = findWindowIdByTabId(tabId);
|
||||||
|
if (windowId !== null) {
|
||||||
|
// 发送者是 tab 中的页面,只关闭这个 tab
|
||||||
|
closeWebTabInWindow(windowId, tabId);
|
||||||
|
} else {
|
||||||
|
// 发送者是独立窗口,销毁整个窗口
|
||||||
|
const win = BrowserWindow.fromWebContents(event.sender);
|
||||||
|
win?.destroy()
|
||||||
|
}
|
||||||
event.returnValue = "ok"
|
event.returnValue = "ok"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
17
electron/lib/utils.js
vendored
17
electron/lib/utils.js
vendored
@ -768,6 +768,23 @@ const utils = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载内容 URL(自动判断完整 URL 或相对路径)
|
||||||
|
* @param webContents - BrowserWindow 或 WebContents 对象
|
||||||
|
* @param serverUrl - 服务器地址
|
||||||
|
* @param url - 要加载的 URL(完整 URL 或相对路径)
|
||||||
|
*/
|
||||||
|
loadContentUrl(webContents, serverUrl, url) {
|
||||||
|
if (!url) return;
|
||||||
|
if (/^https?:/i.test(url)) {
|
||||||
|
// 完整 URL 直接加载
|
||||||
|
webContents.loadURL(url).then(_ => { }).catch(_ => { })
|
||||||
|
} else {
|
||||||
|
// 相对路径使用 loadUrl 处理
|
||||||
|
utils.loadUrl(webContents, serverUrl, url)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取主题名称
|
* 获取主题名称
|
||||||
* @returns {string|*}
|
* @returns {string|*}
|
||||||
|
|||||||
1
electron/render/tabs/assets/image/more.svg
Normal file
1
electron/render/tabs/assets/image/more.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32"><g fill="none"><path d="M9.5 16a2.5 2.5 0 1 1-5 0a2.5 2.5 0 0 1 5 0zm9 0a2.5 2.5 0 1 1-5 0a2.5 2.5 0 0 1 5 0zm6.5 2.5a2.5 2.5 0 1 0 0-5a2.5 2.5 0 0 0 0 5z" fill="currentColor"></path></g></svg>
|
||||||
|
After Width: | Height: | Size: 296 B |
@ -617,7 +617,10 @@
|
|||||||
*/
|
*/
|
||||||
isLocalHost(url) {
|
isLocalHost(url) {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return false
|
return true
|
||||||
|
}
|
||||||
|
if (!/^https?:\/\//i.test(url)) {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const uri = new URL(url)
|
const uri = new URL(url)
|
||||||
|
|||||||
@ -119,6 +119,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
assistShow: false,
|
assistShow: false,
|
||||||
|
isRestarting: false,
|
||||||
userSelectOptions: {value: [], config: {}},
|
userSelectOptions: {value: [], config: {}},
|
||||||
|
|
||||||
backupConfigs: {},
|
backupConfigs: {},
|
||||||
@ -160,8 +161,8 @@ export default {
|
|||||||
this.unmountAllMicroApp()
|
this.unmountAllMicroApp()
|
||||||
},
|
},
|
||||||
assistShow(show) {
|
assistShow(show) {
|
||||||
if (!show && $A.isSubElectron) {
|
if (!show && $A.isSubElectron && !this.isRestarting) {
|
||||||
// 如果是子 Electron 窗口,关闭窗口助理时销毁窗口
|
// 如果是子 Electron 窗口,关闭窗口助理时销毁窗口(但是重启过程中不销毁)
|
||||||
$A.Electron.sendMessage('windowDestroy');
|
$A.Electron.sendMessage('windowDestroy');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -486,7 +487,7 @@ export default {
|
|||||||
path: path,
|
path: path,
|
||||||
force: false,
|
force: false,
|
||||||
config: Object.assign({
|
config: Object.assign({
|
||||||
title: ' ',
|
title: appConfig.title || ' ',
|
||||||
parent: null,
|
parent: null,
|
||||||
width: Math.min(window.screen.availWidth, 1440),
|
width: Math.min(window.screen.availWidth, 1440),
|
||||||
height: Math.min(window.screen.availHeight, 900),
|
height: Math.min(window.screen.availHeight, 900),
|
||||||
@ -518,7 +519,7 @@ export default {
|
|||||||
path: config.url,
|
path: config.url,
|
||||||
force: false,
|
force: false,
|
||||||
config: {
|
config: {
|
||||||
title: ' ',
|
title: config.title || ' ',
|
||||||
parent: null,
|
parent: null,
|
||||||
width: Math.min(window.screen.availWidth, 1440),
|
width: Math.min(window.screen.availWidth, 1440),
|
||||||
height: Math.min(window.screen.availHeight, 900),
|
height: Math.min(window.screen.availHeight, 900),
|
||||||
@ -706,15 +707,20 @@ export default {
|
|||||||
* @param name
|
* @param name
|
||||||
*/
|
*/
|
||||||
async onRestartApp(name) {
|
async onRestartApp(name) {
|
||||||
this.closeMicroApp(name, true)
|
this.isRestarting = true
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
try {
|
||||||
|
this.closeMicroApp(name, true)
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 300));
|
||||||
|
|
||||||
const app = this.backupConfigs[name];
|
const app = this.backupConfigs[name];
|
||||||
if (!app) {
|
if (!app) {
|
||||||
$A.modalError("应用不存在");
|
$A.modalError("应用不存在");
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
await this.onOpen(app)
|
||||||
|
} finally {
|
||||||
|
this.isRestarting = false
|
||||||
}
|
}
|
||||||
await this.onOpen(app)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
1
resources/assets/js/store/actions.js
vendored
1
resources/assets/js/store/actions.js
vendored
@ -5120,6 +5120,7 @@ export default {
|
|||||||
const config = {
|
const config = {
|
||||||
id: microAppId,
|
id: microAppId,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
|
title: data.label || data.title || data.name,
|
||||||
url: $A.mainUrl(url),
|
url: $A.mainUrl(url),
|
||||||
type: data.type || data.url_type,
|
type: data.type || data.url_type,
|
||||||
background: data.background || null,
|
background: data.background || null,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user