Tab页增强,捕获webview的网页标题和图标
This commit is contained in:
parent
ba77eaae59
commit
aad96a0866
240
src/index.ts
240
src/index.ts
@ -1,11 +1,13 @@
|
|||||||
import { WebviewTag } from 'electron';
|
import { PageTitleUpdatedEvent } from 'electron';
|
||||||
import { TabGroup, Tab } from 'electron-tabs';
|
import { TabGroup, Tab } from 'electron-tabs';
|
||||||
import { MenuItem, ApiResponse, EIACDesktopApi } from './EIAC_Desktop_Api';
|
import { MenuItem, ApiResponse, EIACDesktopApi } from './EIAC_Desktop_Api';
|
||||||
|
|
||||||
const tabGroup: TabGroup = document.querySelector('tab-group') as TabGroup;
|
const tabGroup: TabGroup = document.querySelector('tab-group') as TabGroup;
|
||||||
|
|
||||||
// Check login status
|
/**
|
||||||
function checkLoginStatus() {
|
* 检查登录状态
|
||||||
|
*/
|
||||||
|
function checkLoginStatus(): void {
|
||||||
const cookie = window.electronAPI.getSessionStorage('cookie');
|
const cookie = window.electronAPI.getSessionStorage('cookie');
|
||||||
if (!cookie) {
|
if (!cookie) {
|
||||||
window.location.href = 'login.html';
|
window.location.href = 'login.html';
|
||||||
@ -19,39 +21,40 @@ function checkLoginStatus() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle logout
|
/**
|
||||||
function handleLogout() {
|
* 获取菜单列表
|
||||||
window.electronAPI.removeSessionStorage('cookie');
|
* @returns 菜单列表
|
||||||
window.location.href = 'login.html';
|
*/
|
||||||
}
|
async function getMenuListAsync(): Promise<MenuItem[]> {
|
||||||
|
|
||||||
// Get menu list
|
|
||||||
async function getMenuList(): Promise<MenuItem[]> {
|
|
||||||
try {
|
try {
|
||||||
const response: ApiResponse<MenuItem[]> = await EIACDesktopApi.Menu.GetMenuAsync();
|
const response: ApiResponse<MenuItem[]> = await EIACDesktopApi.Menu.GetMenuAsync();
|
||||||
if (response.status === 0) {
|
if (response.status === 0) {
|
||||||
const menuList: MenuItem[] = response.data;
|
return response.data;
|
||||||
return menuList;
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.msg || '获取菜单列表失败');
|
throw new Error(response.msg || '获取菜单列表失败');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取菜单列表失败:', error);
|
console.error(error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modify the createMenuItem function to handle new tab creation
|
/**
|
||||||
|
* 创建菜单项
|
||||||
|
* @param item 菜单项
|
||||||
|
* @param menuList 菜单列表
|
||||||
|
* @returns 菜单项
|
||||||
|
*/
|
||||||
function createMenuItem(item: MenuItem, menuList: MenuItem[]): HTMLLIElement {
|
function createMenuItem(item: MenuItem, menuList: MenuItem[]): HTMLLIElement {
|
||||||
const li = document.createElement('li');
|
const li: HTMLLIElement = document.createElement('li');
|
||||||
li.className = 'menu-item';
|
li.className = 'menu-item';
|
||||||
|
|
||||||
const icon = document.createElement('img');
|
const icon: HTMLImageElement = document.createElement('img');
|
||||||
icon.src = item.IconConfig._1x.Default;
|
icon.src = item.IconConfig._1x.Default;
|
||||||
icon.alt = item.ShowName;
|
icon.alt = item.ShowName;
|
||||||
icon.className = 'menu-icon';
|
icon.className = 'menu-icon';
|
||||||
|
|
||||||
const span = document.createElement('span');
|
const span: HTMLSpanElement = document.createElement('span');
|
||||||
span.textContent = item.ShowName;
|
span.textContent = item.ShowName;
|
||||||
|
|
||||||
li.appendChild(icon);
|
li.appendChild(icon);
|
||||||
@ -76,27 +79,30 @@ function createMenuItem(item: MenuItem, menuList: MenuItem[]): HTMLLIElement {
|
|||||||
icon.src = item.IconConfig._1x.Selected;
|
icon.src = item.IconConfig._1x.Selected;
|
||||||
|
|
||||||
// Create new tab
|
// Create new tab
|
||||||
await addTab(tabGroup, item);
|
await addTabAsync(tabGroup, item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return li;
|
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;
|
if (!menuContainer) return;
|
||||||
|
|
||||||
menuList.forEach(item => {
|
menuList.forEach(item => {
|
||||||
const menuItem = createMenuItem(item, menuList);
|
const menuItem: HTMLLIElement = createMenuItem(item, menuList);
|
||||||
menuContainer.appendChild(menuItem);
|
menuContainer.appendChild(menuItem);
|
||||||
|
|
||||||
if (item.Children) {
|
if (item.Children) {
|
||||||
const subMenu = document.createElement('ul');
|
const subMenu: HTMLUListElement = document.createElement('ul');
|
||||||
subMenu.className = 'submenu';
|
subMenu.className = 'submenu';
|
||||||
item.Children.forEach(child => {
|
item.Children.forEach(child => {
|
||||||
const childItem = createMenuItem(child, menuList);
|
const childItem: HTMLLIElement = createMenuItem(child, menuList);
|
||||||
subMenu.appendChild(childItem);
|
subMenu.appendChild(childItem);
|
||||||
});
|
});
|
||||||
menuContainer.appendChild(subMenu);
|
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;
|
* @param tabGroup 标签组
|
||||||
const errorMessage = document.getElementById('errorMessage') as HTMLParagraphElement;
|
* @param menuItem 菜单项
|
||||||
errorMessage.textContent = message;
|
* @returns 标签页
|
||||||
errorModal.style.display = 'block';
|
*/
|
||||||
}
|
async function addTabAsync(tabGroup: TabGroup, menuItem: MenuItem): Promise<Tab> {
|
||||||
|
const url: string = menuItem.Url.startsWith("http") ? menuItem.Url : `http://${menuItem.Url}`;
|
||||||
async function addTab(tabGroup: TabGroup, menuItem: MenuItem): Promise<Tab> {
|
const result: { ok: boolean; status: number; message?: string } = await window.electronAPI.checkUrlAvailable(url);
|
||||||
const url = menuItem.Url.startsWith("http") ? menuItem.Url : `http://${menuItem.Url}`;
|
|
||||||
const result = await window.electronAPI.checkUrlAvailable(url);
|
|
||||||
if (result.ok && result.status >= 200 && result.status < 400) {
|
if (result.ok && result.status >= 200 && result.status < 400) {
|
||||||
console.log('✅ URL 可访问:', result.status);
|
console.log('✅ URL 可访问:', result.status);
|
||||||
const cookies: string = window.electronAPI.getSessionStorage('cookie');
|
const cookies: string = window.electronAPI.getSessionStorage('cookie');
|
||||||
await window.electronAPI.setWebviewCookie(url, cookies);
|
await window.electronAPI.setWebviewCookie(url, cookies);
|
||||||
} else {
|
} else {
|
||||||
console.warn('❌ URL 不可访问:', result.error ?? `status ${result.status}`);
|
console.warn('❌ URL 不可访问:', result.message ?? `status ${result.status}`);
|
||||||
showErrorModal(`无法访问 ${url}\r\n异常原因:${result.error ?? `status ${result.status}`}\r\n请联系10000技术支持。`);
|
showErrorModal(`无法访问 ${url}\r\n异常原因:${result.message ?? `status ${result.status}`}\r\n请联系10000技术支持。`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tab: Tab = tabGroup.addTab({
|
const tab: Tab = tabGroup.addTab({
|
||||||
@ -136,53 +140,106 @@ async function addTab(tabGroup: TabGroup, menuItem: MenuItem): Promise<Tab> {
|
|||||||
'allowpopups': true,
|
'allowpopups': true,
|
||||||
},
|
},
|
||||||
ready: (tab: Tab) => {
|
ready: (tab: Tab) => {
|
||||||
// 在加载完成后,获取标题
|
// 在webview加载完成后,获取并设置标签页的标题和图标
|
||||||
tab.once('webview-dom-ready', (detail: string) => {
|
const webview: Electron.WebviewTag = tab.webview as Electron.WebviewTag;
|
||||||
console.log('webview-dom-ready detail:', detail);
|
listenWebviewTitleChange(webview, tab);
|
||||||
const webview = tab.webview as WebviewTag;
|
|
||||||
const title = webview.getTitle();
|
|
||||||
tab.setTitle(title);
|
|
||||||
console.log('webview-dom-ready title:', title);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return 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 () => {
|
logo.addEventListener('click', async () => {
|
||||||
console.log('logo clicked');
|
console.log('logo clicked');
|
||||||
|
|
||||||
const tab: Tab = await addTab(tabGroup, menuItem);
|
const tab: Tab = await addTabAsync(tabGroup, menuItem);
|
||||||
tab.setPosition(0);
|
tab.setPosition(0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modify the initialize function to create the first tab
|
/**
|
||||||
async function initialize() {
|
* 绑定登出事件
|
||||||
// Check login status
|
*/
|
||||||
checkLoginStatus();
|
function bindLogoutEvent(): void {
|
||||||
|
const logoutBtn: HTMLButtonElement = document.getElementById('btnLogout') as HTMLButtonElement;
|
||||||
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');
|
|
||||||
if (logoutBtn) {
|
if (logoutBtn) {
|
||||||
logoutBtn.addEventListener('click', handleLogout);
|
logoutBtn.addEventListener('click', () => {
|
||||||
|
window.electronAPI.removeSessionStorage('cookie');
|
||||||
|
window.location.href = 'login.html';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定关闭错误弹窗事件
|
||||||
|
*/
|
||||||
|
function bindCloseErrorModalEvent(): void {
|
||||||
|
const errorModal: HTMLDivElement = document.getElementById('errorModal') as HTMLDivElement;
|
||||||
|
const closeErrorModal: HTMLButtonElement = document.getElementById('closeErrorModal') as HTMLButtonElement;
|
||||||
|
|
||||||
const errorModal = document.getElementById('errorModal') as HTMLDivElement;
|
|
||||||
const closeErrorModal = document.getElementById('closeErrorModal') as HTMLButtonElement;
|
|
||||||
// Close button click event
|
// Close button click event
|
||||||
closeErrorModal.addEventListener('click', (event: Event) => {
|
closeErrorModal.addEventListener('click', () => {
|
||||||
errorModal.style.display = 'none';
|
errorModal.style.display = 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -192,10 +249,44 @@ async function initialize() {
|
|||||||
errorModal.style.display = 'none';
|
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) {
|
} catch (error) {
|
||||||
console.error('初始化失败:', error);
|
console.error('初始化失败:', error);
|
||||||
}
|
}
|
||||||
@ -204,11 +295,14 @@ async function initialize() {
|
|||||||
// 页面加载完成后初始化
|
// 页面加载完成后初始化
|
||||||
document.addEventListener('DOMContentLoaded', initialize);
|
document.addEventListener('DOMContentLoaded', initialize);
|
||||||
|
|
||||||
|
// 监听新标签页打开事件
|
||||||
window.electronAPI.onOpenTab((webContentId: number, url: string) => {
|
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,
|
Url: url,
|
||||||
ShowName: '新标签页',
|
ShowName: '新标签页',
|
||||||
IconConfig: { _1x: { Default: '', Selected: '' }, _2x: { Default: '', Selected: '' } },
|
IconConfig: { _1x: { Default: defaultFaviconUrl, Selected: defaultFaviconUrl }, _2x: { Default: defaultFaviconUrl, Selected: defaultFaviconUrl } },
|
||||||
Children: null
|
Children: null
|
||||||
});
|
});
|
||||||
});
|
});
|
21
src/main.ts
21
src/main.ts
@ -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 path from 'node:path';
|
||||||
import started from 'electron-squirrel-startup';
|
import started from 'electron-squirrel-startup';
|
||||||
import * as http from 'http';
|
import http from 'http';
|
||||||
import * as https from 'https';
|
import https from 'https';
|
||||||
import { URL } from 'url';
|
|
||||||
|
|
||||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
const isDevelopment: boolean = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
// Ensure only one instance is running
|
// Ensure only one instance is running
|
||||||
const gotTheLock = app.requestSingleInstanceLock();
|
const gotTheLock: boolean = app.requestSingleInstanceLock();
|
||||||
if (!gotTheLock) {
|
if (!gotTheLock) {
|
||||||
app.exit(0); // 使用 exit 而不是 quit,确保立即退出
|
app.exit(0); // 使用 exit 而不是 quit,确保立即退出
|
||||||
} else {
|
} else {
|
||||||
@ -69,10 +68,10 @@ ipcMain.handle('check-url-available', async (event, rawUrl: string) => {
|
|||||||
getReq.destroy()
|
getReq.destroy()
|
||||||
});
|
});
|
||||||
getReq.on('error', (err) => {
|
getReq.on('error', (err) => {
|
||||||
resolve({ ok: false, error: err.message });
|
resolve({ ok: false, status: -1, message: err.message });
|
||||||
});
|
});
|
||||||
getReq.on('timeout', () => {
|
getReq.on('timeout', () => {
|
||||||
resolve({ ok: false, error: 'Timeout' });
|
resolve({ ok: false, status: -1, message: 'GET Timeout' });
|
||||||
getReq.destroy();
|
getReq.destroy();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -83,18 +82,18 @@ ipcMain.handle('check-url-available', async (event, rawUrl: string) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
headReq.on('error', (err) => {
|
headReq.on('error', (err) => {
|
||||||
resolve({ ok: false, error: err.message });
|
resolve({ ok: false, status: -1, message: err.message });
|
||||||
});
|
});
|
||||||
|
|
||||||
headReq.on('timeout', () => {
|
headReq.on('timeout', () => {
|
||||||
resolve({ ok: false, error: 'Timeout' });
|
resolve({ ok: false, status: -1, message: 'HEAD Timeout' });
|
||||||
headReq.destroy();
|
headReq.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
headReq.end();
|
headReq.end();
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { ok: false, error: 'Invalid URL' };
|
return { ok: false, status: -1, message: 'Invalid URL' };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
2
src/types/electron.d.ts
vendored
2
src/types/electron.d.ts
vendored
@ -14,7 +14,7 @@ export interface ElectronAPI {
|
|||||||
* @param url 要检查的URL
|
* @param url 要检查的URL
|
||||||
* @returns 是否可用
|
* @returns 是否可用
|
||||||
*/
|
*/
|
||||||
checkUrlAvailable: (url: string) => Promise<{ ok: boolean; status: number; error?: string }>;
|
checkUrlAvailable: (url: string) => Promise<{ ok: boolean; status: number; message?: string }>;
|
||||||
/**
|
/**
|
||||||
* 设置webview的cookie
|
* 设置webview的cookie
|
||||||
* @param url 要设置cookie的URL
|
* @param url 要设置cookie的URL
|
||||||
|
Loading…
x
Reference in New Issue
Block a user