Tab页增强,捕获webview的网页标题和图标

This commit is contained in:
Allen 2025-05-17 21:13:07 +08:00
parent ba77eaae59
commit aad96a0866
3 changed files with 293 additions and 200 deletions

View File

@ -1,11 +1,13 @@
import { WebviewTag } from 'electron';
import { PageTitleUpdatedEvent } from 'electron';
import { TabGroup, Tab } from 'electron-tabs';
import { MenuItem, ApiResponse, EIACDesktopApi } from './EIAC_Desktop_Api';
const tabGroup: TabGroup = document.querySelector('tab-group') as TabGroup;
// Check login status
function checkLoginStatus() {
/**
*
*/
function checkLoginStatus(): void {
const cookie = window.electronAPI.getSessionStorage('cookie');
if (!cookie) {
window.location.href = 'login.html';
@ -19,39 +21,40 @@ function checkLoginStatus() {
}
}
// Handle logout
function handleLogout() {
window.electronAPI.removeSessionStorage('cookie');
window.location.href = 'login.html';
}
// Get menu list
async function getMenuList(): Promise<MenuItem[]> {
/**
*
* @returns
*/
async function getMenuListAsync(): Promise<MenuItem[]> {
try {
const response: ApiResponse<MenuItem[]> = await EIACDesktopApi.Menu.GetMenuAsync();
if (response.status === 0) {
const menuList: MenuItem[] = response.data;
return menuList;
return response.data;
} else {
throw new Error(response.msg || '获取菜单列表失败');
}
} catch (error) {
console.error('获取菜单列表失败:', error);
console.error(error);
throw error;
}
}
// Modify the createMenuItem function to handle new tab creation
/**
*
* @param item
* @param menuList
* @returns
*/
function createMenuItem(item: MenuItem, menuList: MenuItem[]): HTMLLIElement {
const li = document.createElement('li');
const li: HTMLLIElement = document.createElement('li');
li.className = 'menu-item';
const icon = document.createElement('img');
const icon: HTMLImageElement = document.createElement('img');
icon.src = item.IconConfig._1x.Default;
icon.alt = item.ShowName;
icon.className = 'menu-icon';
const span = document.createElement('span');
const span: HTMLSpanElement = document.createElement('span');
span.textContent = item.ShowName;
li.appendChild(icon);
@ -76,27 +79,30 @@ function createMenuItem(item: MenuItem, menuList: MenuItem[]): HTMLLIElement {
icon.src = item.IconConfig._1x.Selected;
// Create new tab
await addTab(tabGroup, item);
await addTabAsync(tabGroup, item);
});
}
return li;
}
// Render menu
function renderMenu(menuList: MenuItem[]) {
const menuContainer = document.getElementById('menuList');
/**
*
* @param menuList
*/
function renderMenu(menuList: MenuItem[]): void {
const menuContainer: HTMLUListElement = document.getElementById('menuList') as HTMLUListElement;
if (!menuContainer) return;
menuList.forEach(item => {
const menuItem = createMenuItem(item, menuList);
const menuItem: HTMLLIElement = createMenuItem(item, menuList);
menuContainer.appendChild(menuItem);
if (item.Children) {
const subMenu = document.createElement('ul');
const subMenu: HTMLUListElement = document.createElement('ul');
subMenu.className = 'submenu';
item.Children.forEach(child => {
const childItem = createMenuItem(child, menuList);
const childItem: HTMLLIElement = createMenuItem(child, menuList);
subMenu.appendChild(childItem);
});
menuContainer.appendChild(subMenu);
@ -104,24 +110,22 @@ function renderMenu(menuList: MenuItem[]) {
});
}
// Show error modal
function showErrorModal(message: string) {
const errorModal = document.getElementById('errorModal') as HTMLDivElement;
const errorMessage = document.getElementById('errorMessage') as HTMLParagraphElement;
errorMessage.textContent = message;
errorModal.style.display = 'block';
}
async function addTab(tabGroup: TabGroup, menuItem: MenuItem): Promise<Tab> {
const url = menuItem.Url.startsWith("http") ? menuItem.Url : `http://${menuItem.Url}`;
const result = await window.electronAPI.checkUrlAvailable(url);
/**
*
* @param tabGroup
* @param menuItem
* @returns
*/
async function addTabAsync(tabGroup: TabGroup, menuItem: MenuItem): Promise<Tab> {
const url: string = menuItem.Url.startsWith("http") ? menuItem.Url : `http://${menuItem.Url}`;
const result: { ok: boolean; status: number; message?: string } = await window.electronAPI.checkUrlAvailable(url);
if (result.ok && result.status >= 200 && result.status < 400) {
console.log('✅ URL 可访问:', result.status);
const cookies: string = window.electronAPI.getSessionStorage('cookie');
await window.electronAPI.setWebviewCookie(url, cookies);
} else {
console.warn('❌ URL 不可访问:', result.error ?? `status ${result.status}`);
showErrorModal(`无法访问 ${url}\r\n异常原因${result.error ?? `status ${result.status}`}\r\n请联系10000技术支持。`);
console.warn('❌ URL 不可访问:', result.message ?? `status ${result.status}`);
showErrorModal(`无法访问 ${url}\r\n异常原因${result.message ?? `status ${result.status}`}\r\n请联系10000技术支持。`);
}
const tab: Tab = tabGroup.addTab({
@ -136,53 +140,106 @@ async function addTab(tabGroup: TabGroup, menuItem: MenuItem): Promise<Tab> {
'allowpopups': true,
},
ready: (tab: Tab) => {
// 在加载完成后,获取标题
tab.once('webview-dom-ready', (detail: string) => {
console.log('webview-dom-ready detail:', detail);
const webview = tab.webview as WebviewTag;
const title = webview.getTitle();
tab.setTitle(title);
console.log('webview-dom-ready title:', title);
});
// 在webview加载完成后获取并设置标签页的标题和图标
const webview: Electron.WebviewTag = tab.webview as Electron.WebviewTag;
listenWebviewTitleChange(webview, tab);
}
});
return tab;
}
async function bindLogoClickEvent(tabGroup: TabGroup, menuItem: MenuItem) {
const logo = document.getElementById('logo') as HTMLImageElement;
/**
* webview的标题变化
* @param webview webview
* @param tab
*/
function listenWebviewTitleChange(webview: Electron.WebviewTag, tab: Tab): void {
// 在webview加载完成后获取并设置标签页的标题和图标
webview.addEventListener('did-finish-load', async () => {
const title: string = webview.getTitle();
tab.setTitle(title);
console.log('did-finish-load title:', title);
const faviconUrl: string = await getFaviconUrl(webview);
console.log('did-finish-load iconUrl:', faviconUrl);
if (faviconUrl) {
tab.setIcon(faviconUrl, null);
}
});
// 监听webview的标题变化并更新标签页的标题和图标
webview.addEventListener('page-title-updated', async (event: PageTitleUpdatedEvent) => {
console.log('title-changed title:', event.title);
tab.setTitle(event.title);
const faviconUrl: string = await getFaviconUrl(webview);
console.log('title-changed iconUrl:', faviconUrl);
if (faviconUrl) {
tab.setIcon(faviconUrl, null);
}
});
}
/**
* URL
* @param webview webview
* @returns URL
*/
async function getFaviconUrl(webview: Electron.WebviewTag): Promise<string> {
const iconUrl: string | null = await webview.executeJavaScript(`
(() => {
const relList = ['icon', 'shortcut icon', 'apple-touch-icon'];
const links = Array.from(document.getElementsByTagName('link'));
const iconLink = links.find(link => relList.includes(link.rel));
return iconLink ? iconLink.href : null;
})();
`);
if (iconUrl) {
return iconUrl;
}
const url: string = webview.getURL();
const defaultFaviconUrl: string = new URL(url).origin + '/favicon.ico';
return defaultFaviconUrl;
}
/**
* logo点击事件
*/
function bindLogoClickEvent(tabGroup: TabGroup, menuItem: MenuItem): void {
const logo: HTMLImageElement = document.getElementById('logo') as HTMLImageElement;
logo.addEventListener('click', async () => {
console.log('logo clicked');
const tab: Tab = await addTab(tabGroup, menuItem);
const tab: Tab = await addTabAsync(tabGroup, menuItem);
tab.setPosition(0);
});
}
// Modify the initialize function to create the first tab
async function initialize() {
// Check login status
checkLoginStatus();
try {
const menuList = await getMenuList();
renderMenu(menuList);
// Create initial tab
const firstMenuItem = menuList[0];
await addTab(tabGroup, firstMenuItem);
// Bind logout event
const logoutBtn = document.getElementById('btnLogout');
/**
*
*/
function bindLogoutEvent(): void {
const logoutBtn: HTMLButtonElement = document.getElementById('btnLogout') as HTMLButtonElement;
if (logoutBtn) {
logoutBtn.addEventListener('click', handleLogout);
logoutBtn.addEventListener('click', () => {
window.electronAPI.removeSessionStorage('cookie');
window.location.href = 'login.html';
});
}
}
const errorModal = document.getElementById('errorModal') as HTMLDivElement;
const closeErrorModal = document.getElementById('closeErrorModal') as HTMLButtonElement;
/**
*
*/
function bindCloseErrorModalEvent(): void {
const errorModal: HTMLDivElement = document.getElementById('errorModal') as HTMLDivElement;
const closeErrorModal: HTMLButtonElement = document.getElementById('closeErrorModal') as HTMLButtonElement;
// Close button click event
closeErrorModal.addEventListener('click', (event: Event) => {
closeErrorModal.addEventListener('click', () => {
errorModal.style.display = 'none';
});
@ -192,10 +249,44 @@ async function initialize() {
errorModal.style.display = 'none';
}
});
}
// Listen logo click event
const lastMenuItem = menuList[menuList.length - 1];
await bindLogoClickEvent(tabGroup, lastMenuItem);
/**
*
* @param message
*/
function showErrorModal(message: string): void {
const errorModal: HTMLDivElement = document.getElementById('errorModal') as HTMLDivElement;
const errorMessage: HTMLParagraphElement = document.getElementById('errorMessage') as HTMLParagraphElement;
errorMessage.textContent = message;
errorModal.style.display = 'block';
}
/**
*
*/
async function initialize(): Promise<void> {
// Check login status
checkLoginStatus();
try {
const menuList: MenuItem[] = await getMenuListAsync();
renderMenu(menuList);
// Create initial tab
const firstMenuItem: MenuItem = menuList[0];
await addTabAsync(tabGroup, firstMenuItem);
// Bind logo click event
const lastMenuItem: MenuItem = menuList[menuList.length - 1];
const logoMenuItem: MenuItem = lastMenuItem.Children[lastMenuItem.Children.length - 1];
bindLogoClickEvent(tabGroup, logoMenuItem);
// Bind logout event
bindLogoutEvent();
// Bind close error modal event
bindCloseErrorModalEvent();
} catch (error) {
console.error('初始化失败:', error);
}
@ -204,11 +295,14 @@ async function initialize() {
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', initialize);
// 监听新标签页打开事件
window.electronAPI.onOpenTab((webContentId: number, url: string) => {
addTab(tabGroup, {
console.log(new URL(url));
const defaultFaviconUrl = new URL(url).origin + '/favicon.ico';
addTabAsync(tabGroup, {
Url: url,
ShowName: '新标签页',
IconConfig: { _1x: { Default: '', Selected: '' }, _2x: { Default: '', Selected: '' } },
IconConfig: { _1x: { Default: defaultFaviconUrl, Selected: defaultFaviconUrl }, _2x: { Default: defaultFaviconUrl, Selected: defaultFaviconUrl } },
Children: null
});
});

View File

@ -1,14 +1,13 @@
import { app, BrowserWindow, globalShortcut, ipcMain, Menu, Tray, session, screen } from 'electron';
import { app, BrowserWindow, globalShortcut, ipcMain, Menu, session, screen, Tray } from 'electron';
import path from 'node:path';
import started from 'electron-squirrel-startup';
import * as http from 'http';
import * as https from 'https';
import { URL } from 'url';
import http from 'http';
import https from 'https';
const isDevelopment = process.env.NODE_ENV === 'development';
const isDevelopment: boolean = process.env.NODE_ENV === 'development';
// Ensure only one instance is running
const gotTheLock = app.requestSingleInstanceLock();
const gotTheLock: boolean = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.exit(0); // 使用 exit 而不是 quit确保立即退出
} else {
@ -69,10 +68,10 @@ ipcMain.handle('check-url-available', async (event, rawUrl: string) => {
getReq.destroy()
});
getReq.on('error', (err) => {
resolve({ ok: false, error: err.message });
resolve({ ok: false, status: -1, message: err.message });
});
getReq.on('timeout', () => {
resolve({ ok: false, error: 'Timeout' });
resolve({ ok: false, status: -1, message: 'GET Timeout' });
getReq.destroy();
});
} else {
@ -83,18 +82,18 @@ ipcMain.handle('check-url-available', async (event, rawUrl: string) => {
);
headReq.on('error', (err) => {
resolve({ ok: false, error: err.message });
resolve({ ok: false, status: -1, message: err.message });
});
headReq.on('timeout', () => {
resolve({ ok: false, error: 'Timeout' });
resolve({ ok: false, status: -1, message: 'HEAD Timeout' });
headReq.destroy();
});
headReq.end();
})
} catch (e) {
return { ok: false, error: 'Invalid URL' };
return { ok: false, status: -1, message: 'Invalid URL' };
}
});

View File

@ -14,7 +14,7 @@ export interface ElectronAPI {
* @param url URL
* @returns
*/
checkUrlAvailable: (url: string) => Promise<{ ok: boolean; status: number; error?: string }>;
checkUrlAvailable: (url: string) => Promise<{ ok: boolean; status: number; message?: string }>;
/**
* webview的cookie
* @param url cookie的URL