import { PageTitleUpdatedEvent } from 'electron'; import { TabGroup, Tab } from 'electron-tabs'; import { MenuItem, ApiResponse } from './EIAC_Desktop_Api'; // 保存最后一个无效URL的结果 let lastInvalidUrlResult: { url: string, message: string, status: number } | null = null; const tabGroup: TabGroup = document.querySelector('tab-group') as TabGroup; /** * 检查登录状态 */ function checkLoginStatus(): void { const cookie = window.electronAPI.getSessionStorage('cookies'); if (!cookie) { window.location.href = 'login.html'; return; } // Show user information const userInfo = document.getElementById('userInfo'); if (userInfo) { userInfo.textContent = '欢迎使用'; } } /** * 获取菜单列表 * @returns 菜单列表 */ async function getMenuListAsync(): Promise { try { const response: ApiResponse = await window.electronAPI.getMenuCacheAsync(); if (response.status === 0) { return response.data; } else { throw new Error(response.msg || '获取菜单列表失败'); } } catch (error) { console.error(error); throw error; } } /** * 创建菜单项 * @param item 菜单项 * @param menuList 菜单列表 * @returns 菜单项 */ function createMenuItem(item: MenuItem, menuList: MenuItem[]): HTMLLIElement { const li: HTMLLIElement = document.createElement('li'); li.className = 'menu-item'; const icon: HTMLImageElement = document.createElement('img'); icon.src = item.IconConfig._1x.Default; icon.alt = item.ShowName; icon.className = 'menu-icon'; const span: HTMLSpanElement = document.createElement('span'); span.textContent = item.ShowName; li.appendChild(icon); li.appendChild(span); if (item.Url) { li.addEventListener('click', async () => { // Remove active state from other menu items document.querySelectorAll('.menu-item').forEach(menuItem => { menuItem.classList.remove('active'); const menuIcon = menuItem.querySelector('.menu-icon') as HTMLImageElement; if (menuIcon) { const menuItemData = menuList.find((m: MenuItem) => m.ShowName === menuItem.querySelector('span')?.textContent); if (menuItemData) { menuIcon.src = menuItemData.IconConfig._1x.Default; } } }); // Add active state to current menu item li.classList.add('active'); icon.src = item.IconConfig._1x.Selected; // Create new tab await addTabAsync(tabGroup, item); }); } return li; } /** * 渲染菜单 * @param menuList 菜单列表 */ function renderMenu(menuList: MenuItem[]): void { const menuContainer: HTMLUListElement = document.getElementById('menuList') as HTMLUListElement; if (!menuContainer) return; menuList.forEach(item => { const menuItem: HTMLLIElement = createMenuItem(item, menuList); menuContainer.appendChild(menuItem); if (item.Children) { const subMenu: HTMLUListElement = document.createElement('ul'); subMenu.className = 'submenu'; item.Children.forEach(child => { const childItem: HTMLLIElement = createMenuItem(child, menuList); subMenu.appendChild(childItem); }); menuContainer.appendChild(subMenu); } }); } /** * 添加标签页 * @param tabGroup 标签组 * @param menuItem 菜单项 * @param allowCloseTab 是否允许关闭标签页 * @returns 标签页 */ async function addTabAsync(tabGroup: TabGroup, menuItem: MenuItem, allowCloseTab: boolean = true): Promise { 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 ${url} 可访问:`, result.status); lastInvalidUrlResult = null; const cookies: string = window.electronAPI.getSessionStorage('cookies'); await window.electronAPI.setWebviewCookie(url, cookies); } else { console.warn(`❌ URL ${url} 不可访问:`, result.message ?? `status ${result.status}`); lastInvalidUrlResult = { url, message: result.message, status: result.status }; const helpDescrip: string = await window.electronAPI.getHelperDescripAsync(result.status.toString()) ?? `无法访问{URL}\r\n异常原因:${result.message ?? `status ${result.status}`}\r\n请联系技术支持。`; showErrorModal(helpDescrip.replace('{URL}', url)); return null; } const zoomFactor: number = await window.electronAPI.getZoomFactorByUrl(url); const tab: Tab = tabGroup.addTab({ active: true, closable: allowCloseTab, title: menuItem.ShowName, src: url, iconURL: menuItem.IconConfig._1x.Default, webviewAttributes: { 'webpreferences': 'contextIsolation=yes, nodeIntegration=no', 'autosize': 'on', 'allowpopups': true }, ready: (tab: Tab) => { // 在webview加载完成后,获取并设置标签页的标题和图标 const webview: Electron.WebviewTag = tab.webview as Electron.WebviewTag; listenWebviewTitleChange(webview, tab); // 监听 webview 的 DOM 加载完成事件 tab.once('webview-dom-ready', () => { // 设置 webview 的缩放比例 const webview: Electron.WebviewTag = tab.webview as Electron.WebviewTag; const defaultZoomFactor: number = webview.getZoomFactor(); console.log('Default zoom factor:', defaultZoomFactor); if (defaultZoomFactor != zoomFactor) { webview.setZoomFactor(zoomFactor); console.log('Modify zoom factor:', zoomFactor); } else { console.log('Default zoom factor is the same as the zoom factor:', zoomFactor); } // 监听 webview 的关闭事件 webview.addEventListener('destroyed', (_event: Event) => { console.log('Webview destroyed, closing tab:', tab.title); tab.close(true); }); // 监听 webview 的关闭事件(当页面调用 window.close() 时触发) webview.addEventListener('close', (_event: Event) => { console.log('Webview close event triggered, closing tab:', tab.title); tab.close(true); }); }); } }); return tab; } /** * 监听webview的标题变化,并更新标签页的标题和图标 * @param webview webview * @param tab 标签页 */ function listenWebviewTitleChange(webview: Electron.WebviewTag, tab: Tab): void { // 监听 URL 变化事件 webview.addEventListener('did-navigate', async (event: Electron.DidNavigateEvent) => { const url: string = event.url; const zoomFactor: number = await window.electronAPI.getZoomFactorByUrl(url); const currentZoomFactor: number = webview.getZoomFactor(); if (currentZoomFactor !== zoomFactor) { webview.setZoomFactor(zoomFactor); console.log('URL changed, modify zoom factor:', zoomFactor); } }); // 监听 URL 在同一个页面内的变化事件(如 hash 变化) webview.addEventListener('did-navigate-in-page', async (event: Electron.DidNavigateInPageEvent) => { const url: string = event.url; const zoomFactor: number = await window.electronAPI.getZoomFactorByUrl(url); const currentZoomFactor: number = webview.getZoomFactor(); if (currentZoomFactor !== zoomFactor) { webview.setZoomFactor(zoomFactor); console.log('URL in-page changed, modify zoom factor:', zoomFactor); } }); // 监听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 { 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; } /** * 绑定Help图标点击事件 */ function bindHelpIconClickEvent(menuItem: MenuItem): void { const helpIcon: HTMLImageElement = document.getElementById('helpIcon') as HTMLImageElement; helpIcon.src = menuItem.IconConfig._1x.Default; helpIcon.addEventListener('mouseenter', (event) => helpIcon.src = menuItem.IconConfig._1x.Selected); helpIcon.addEventListener('mouseleave', () => helpIcon.src = menuItem.IconConfig._1x.Default); helpIcon.addEventListener('click', async (event) => { if (lastInvalidUrlResult) { const helpDescrip: string = await window.electronAPI.getHelperDescripAsync(lastInvalidUrlResult.status.toString()) ?? `无法访问{URL}\r\n异常原因:${lastInvalidUrlResult.message ?? `status ${lastInvalidUrlResult.status}`}\r\n请联系技术支持。`; showErrorModal(helpDescrip.replace('{URL}', lastInvalidUrlResult.url)); } else { const tab: Tab | null = tabGroup.tabs.find(tab => (tab.webview as Electron.WebviewTag).getURL() === menuItem.Url); if (tab) { tab.activate(); } else { await addTabAsync(tabGroup, menuItem); } } }); } /** * 绑定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 | null = tabGroup.tabs.find(tab => (tab.webview as Electron.WebviewTag).getURL() === menuItem.Url); if (tab) { tab.activate(); } else { const newTab: Tab | null = await addTabAsync(tabGroup, menuItem); if (newTab) { newTab.setPosition(0); } } }); } /** * 绑定退出事件 */ function bindExitEvent(): void { const exitBtn: HTMLButtonElement = document.getElementById('btnExit') as HTMLButtonElement; if (exitBtn) { exitBtn.addEventListener('click', async () => { window.electronAPI.removeSessionStorage('cookies'); await window.electronAPI.closeApp(); }); } } let closeCount: number = 0; /** * 绑定错误弹窗事件 */ function bindErrorModalEvent(): void { const errorModal: HTMLDivElement = document.getElementById('errorModal') as HTMLDivElement; const closeErrorModal: HTMLButtonElement = document.getElementById('closeErrorModal') as HTMLButtonElement; const faultReporting: HTMLButtonElement = document.getElementById('faultReporting') as HTMLButtonElement; faultReporting.addEventListener('click', async () => await faultReportingAsync()); // Close button click event closeErrorModal.addEventListener('click', () => { errorModal.style.display = 'none'; closeCount++; // 如果关闭次数大于等于2,则重置计数并且清空lastInvalidUrlResult if (closeCount >= 2) { closeCount = 0; lastInvalidUrlResult = null; } }); // Click outside to close window.addEventListener('click', (event: Event) => { if (event.target === errorModal) { errorModal.style.display = 'none'; closeCount++; // 如果关闭次数大于等于2,则重置计数并且清空lastInvalidUrlResult if (closeCount >= 2) { closeCount = 0; lastInvalidUrlResult = null; } } }); } /** * 显示错误弹窗 * @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 faultReportingAsync(): Promise { if (lastInvalidUrlResult) { const faultReportingBtn = document.getElementById('faultReporting') as HTMLButtonElement; const originalText = faultReportingBtn.textContent; try { // 显示请求中状态 faultReportingBtn.disabled = true; faultReportingBtn.textContent = '上报中...'; const result: { ok: boolean; status: number; message?: string } = await window.electronAPI.faultReporting( lastInvalidUrlResult.url, lastInvalidUrlResult.message, lastInvalidUrlResult.status ); // 显示请求结果 if (result.ok) { lastInvalidUrlResult = null; faultReportingBtn.textContent = '上报成功'; faultReportingBtn.style.backgroundColor = '#4CAF50'; setTimeout(() => { faultReportingBtn.textContent = originalText; faultReportingBtn.style.backgroundColor = ''; faultReportingBtn.disabled = false; // 关闭错误弹窗 const errorModal = document.getElementById('errorModal') as HTMLDivElement; errorModal.style.display = 'none'; }, 2000); } else { faultReportingBtn.textContent = '上报失败'; faultReportingBtn.style.backgroundColor = '#f44336'; setTimeout(() => { faultReportingBtn.textContent = originalText; faultReportingBtn.style.backgroundColor = ''; faultReportingBtn.disabled = false; }, 2000); } } catch (error) { console.error('故障上报异常:', error); faultReportingBtn.textContent = '上报异常'; faultReportingBtn.style.backgroundColor = '#f44336'; setTimeout(() => { faultReportingBtn.textContent = originalText; faultReportingBtn.style.backgroundColor = ''; faultReportingBtn.disabled = false; }, 2000); } } } /** * 初始化 */ async function initialize(): Promise { // Check login status checkLoginStatus(); try { const menuList: MenuItem[] = await getMenuListAsync(); renderMenu(menuList); // Create initial tab const firstMenuItem: MenuItem = menuList[0]; await addTabAsync(tabGroup, firstMenuItem, false); // Bind help icon click event const helpMenuItem: MenuItem = menuList[menuList.length - 2]; bindHelpIconClickEvent(helpMenuItem); // Bind logo click event const logoMenuItem: MenuItem = menuList[menuList.length - 1]; bindLogoClickEvent(tabGroup, logoMenuItem); // Bind exit event bindExitEvent(); // Bind error modal event bindErrorModalEvent(); } catch (error) { console.error('初始化失败:', error); } } // 页面加载完成后初始化 document.addEventListener('DOMContentLoaded', initialize); // 监听新标签页打开事件 window.electronAPI.onOpenTab((webContentId: number, url: string) => { const defaultFaviconUrl = new URL(url).origin + '/favicon.ico'; addTabAsync(tabGroup, { Url: url, ShowName: '新标签页', IconConfig: { _1x: { Default: defaultFaviconUrl, Selected: defaultFaviconUrl }, _2x: { Default: defaultFaviconUrl, Selected: defaultFaviconUrl } }, Children: null }); });