From c0126fd07a370dda7cdc6273fd5e997f46217fe3 Mon Sep 17 00:00:00 2001 From: Allen Date: Sun, 18 May 2025 02:14:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9C=A8URL=E4=B8=8D=E5=8F=AF=E7=94=A8?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E6=98=BE=E7=A4=BA=E4=B8=80=E9=94=AE=E6=8A=A5?= =?UTF-8?q?=E9=9A=9C=E6=8C=89=E9=92=AE=EF=BC=8C=E5=AE=9E=E7=8E=B0=E4=B8=80?= =?UTF-8?q?=E9=94=AE=E4=B8=8A=E6=8A=A5=E6=95=85=E9=9A=9C=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 3 +- src/IpcMainHandler.ts | 161 ++++++++++++++++++++++++++++------------ src/index.css | 12 +++ src/index.ts | 83 ++++++++++++++++++--- src/login.ts | 3 +- src/preload.ts | 30 ++++++-- src/types/electron.d.ts | 24 ++++-- 7 files changed, 244 insertions(+), 72 deletions(-) diff --git a/index.html b/index.html index 9781257..3d21f41 100644 --- a/index.html +++ b/index.html @@ -43,7 +43,8 @@ diff --git a/src/IpcMainHandler.ts b/src/IpcMainHandler.ts index 2ab22b3..5cc9d29 100644 --- a/src/IpcMainHandler.ts +++ b/src/IpcMainHandler.ts @@ -1,12 +1,74 @@ -import { ipcMain, screen, session } from "electron"; +import { BrowserWindow, ipcMain, screen, session } from "electron"; import http from 'http'; import https from 'https'; +import os from 'os'; import { URL } from 'url'; -import { ApiResponse, MenuItem, TagResolutionConfig, EIACDesktopApi, SpecialPUrlItem } from './EIAC_Desktop_Api'; +import { ApiResponse, MenuItem, TagResolutionConfig, EIACDesktopApi, SpecialPUrlItem, FaultReportingResponse } from './EIAC_Desktop_Api'; + +const memoryCache = new Map(); export function initialize(): void { - // Get primary display(需要注意:size 是逻辑像素尺寸(logical screen size),已被 DPI 缩放影响,需要除以 DPI 缩放比例才是物理像素尺寸(physical screen size)) - ipcMain.handle('get-primary-display', () => screen.getPrimaryDisplay()); + // Set cache + ipcMain.handle('cache:set', (_event, key: string, value: any) => { + memoryCache.set(key, value); + }); + + // Get cache + ipcMain.handle('cache:get', (_event, key: string) => { + return memoryCache.get(key) ?? null; + }); + + // Get menu cache + ipcMain.handle('get-menu-cache', async () => menuData ?? await menuDataReadyPromise); + + // Get config cache + ipcMain.handle('get-config-cache', async () => configData ?? await configDataReadyPromise); + + // Get zoom factor + ipcMain.handle('get-zoom-factor-by-url', async (event, url: string) => { + const display: Electron.Display = screen.getPrimaryDisplay(); + const physicalSize: Electron.Size = { + width: display.size.width * display.scaleFactor, + height: display.size.height * display.scaleFactor + }; + const resolution: string = `${physicalSize.width}*${physicalSize.height}`; + console.log('PhysicalResolution:', resolution); + console.log(`Resolution: ${display.size.width}*${display.size.height}`); + console.log(`ScaleFactor: ${display.scaleFactor}`); + + if (!configData) { + await configDataReadyPromise; + } + + if (configData.code != 200 || configData.status != 0) { + console.error('Get config failed:', configData.msg + ', status: ' + configData.status + ', code: ' + configData.code); + return display.scaleFactor; + } + + const configList: TagResolutionConfig[] = configData.data; + let config: TagResolutionConfig = configList.find(c => c.Resolutions.includes(resolution)); + if (!config) { + config = configList.find(c => c.Resolutions === '*'); + } + if (!config) { + config = configList[0]; + } + console.log('Match Config:', config); + + let specialPUrl: SpecialPUrlItem = config.SpecialPUrl.find(s => s.SPUrl.toLowerCase() === url.toLowerCase()); + if (!specialPUrl) { + specialPUrl = config.SpecialPUrl.find(s => url.toLowerCase().startsWith(s.SPUrl.toLowerCase())); + } + console.log('specialPUrl:', specialPUrl); + + let percentage: number = typeof config.Percentage === 'string' ? parseInt(config.Percentage) : config.Percentage; + if (specialPUrl) { + percentage = typeof specialPUrl.SPPer === 'string' ? parseInt(specialPUrl.SPPer) : specialPUrl.SPPer; + } + + console.log('Percentage:', percentage); + return percentage; + }); // Check if the URL is available ipcMain.handle('check-url-available', async (event, rawUrl: string) => { @@ -99,59 +161,64 @@ export function initialize(): void { } }); - // Get menu cache - ipcMain.handle('get-menu-cache', async () => menuData ?? await menuDataReadyPromise); + // Fault reporting + ipcMain.handle('fault-reporting', async (event, url: string, message: string, status: number) => { + console.log('Fault reporting:', url, message, status); + const webContents = event.sender; - // Get config cache - ipcMain.handle('get-config-cache', async () => configData ?? await configDataReadyPromise); - - // Get zoom factor - ipcMain.handle('get-zoom-factor-by-url', async (event, url: string) => { - const display: Electron.Display = screen.getPrimaryDisplay(); - const physicalSize: Electron.Size = { - width: display.size.width * display.scaleFactor, - height: display.size.height * display.scaleFactor - }; - const resolution: string = `${physicalSize.width}*${physicalSize.height}`; - console.log('PhysicalResolution:', resolution); - console.log(`Resolution: ${display.size.width}*${display.size.height}`); - console.log(`ScaleFactor: ${display.scaleFactor}`); - - if (!configData) { - await configDataReadyPromise; + const win = BrowserWindow.fromWebContents(webContents); + if (!win) { + throw new Error('Not found BrowserWindow'); } - if (configData.code != 200 || configData.status != 0) { - console.error('Get config failed:', configData.msg + ', status: ' + configData.status + ', code: ' + configData.code); - return display.scaleFactor; - } + const base64 = await captureWindowAsBase64(win); + console.log('base64:', base64); - const configList: TagResolutionConfig[] = configData.data; - let config: TagResolutionConfig = configList.find(c => c.Resolutions.includes(resolution)); - if (!config) { - config = configList.find(c => c.Resolutions === '*'); - } - if (!config) { - config = configList[0]; - } - console.log('Match Config:', config); + try { + const response: ApiResponse = await EIACDesktopApi.Help.FaultReportingAsync({ + Account: memoryCache.get('Account'), + IP: getLocalIPAddress(), + Url: url, + ImgBase64: base64, + Explain: `message: ${message}, status: ${status}` + }); + console.log('Fault reporting response:', response); - let specialPUrl: SpecialPUrlItem = config.SpecialPUrl.find(s => s.SPUrl.toLowerCase() === url.toLowerCase()); - if (!specialPUrl) { - specialPUrl = config.SpecialPUrl.find(s => url.toLowerCase().startsWith(s.SPUrl.toLowerCase())); - } - console.log('specialPUrl:', specialPUrl); + if (response.code != 200 || response.status != 0) { + console.error('故障上报失败:', response.msg + ', status: ' + response.status + ', code: ' + response.code); + return { ok: false, status: -1, message: `故障上报失败: ${response.msg}, status: ${response.status}, code: ${response.code}` }; + } - let percentage: number = typeof config.Percentage === 'string' ? parseInt(config.Percentage) : config.Percentage; - if (specialPUrl) { - percentage = typeof specialPUrl.SPPer === 'string' ? parseInt(specialPUrl.SPPer) : specialPUrl.SPPer; + return { ok: true, status: response.status, message: response.msg }; + } catch (error) { + console.error('故障上报失败:', error); + return { ok: false, status: -1, message: error.message }; } - - console.log('Percentage:', percentage); - return percentage; }); } +function getLocalIPAddress(): string | null { + const interfaces = os.networkInterfaces(); + + for (const name of Object.keys(interfaces)) { + for (const net of interfaces[name] || []) { + // 排除 IPv6 和 127.0.0.1 内环地址 + if (net.family === 'IPv4' && !net.internal) { + return net.address; // 返回第一个非内网 IPv4 地址 + } + } + } + + return null; // 没有找到 +} + +async function captureWindowAsBase64(win: BrowserWindow): Promise { + const image = await win.capturePage(); // 截图 + const pngBuffer = image.toPNG(); // 转为 PNG Buffer + const base64 = pngBuffer.toString('base64'); // 转为 Base64 字符串 + return `data:image/png;base64,${base64}`; // 返回 DataURL +} + let menuData: ApiResponse = null; let menuDataReadyPromise: Promise>; let configData: ApiResponse = null; diff --git a/src/index.css b/src/index.css index 7828c31..415311d 100644 --- a/src/index.css +++ b/src/index.css @@ -172,6 +172,18 @@ body { background-color: #2980b9; } +.fault-reporting-btn { + background-color: #d0db34; + color: white; + border: none; + padding: 8px 20px; + border-radius: 4px; +} + +.fault-reporting-btn:hover { + background-color: #a6b929; +} + /* Tabs styles */ .tabs-container { display: flex; diff --git a/src/index.ts b/src/index.ts index d26e7c4..4c6dd34 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,13 +2,15 @@ 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('cookie'); + const cookie = window.electronAPI.getSessionStorage('cookies'); if (!cookie) { window.location.href = 'login.html'; return; @@ -116,16 +118,19 @@ function renderMenu(menuList: MenuItem[]): void { * @param menuItem 菜单项 * @returns 标签页 */ -async function addTabAsync(tabGroup: TabGroup, menuItem: MenuItem): Promise { +async function addTabAsync(tabGroup: TabGroup, menuItem: MenuItem): 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); - const cookies: string = window.electronAPI.getSessionStorage('cookie'); + 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 }; showErrorModal(`无法访问 ${url}\r\n异常原因:${result.message ?? `status ${result.status}`}\r\n请联系10000技术支持。`); + return null; } const zoomFactor: number = await window.electronAPI.getZoomFactorByUrl(url); @@ -233,8 +238,10 @@ function bindLogoClickEvent(tabGroup: TabGroup, menuItem: MenuItem): void { if (tab) { tab.activate(); } else { - const newTab: Tab = await addTabAsync(tabGroup, menuItem); - newTab.setPosition(0); + const newTab: Tab | null = await addTabAsync(tabGroup, menuItem); + if (newTab) { + newTab.setPosition(0); + } } }); } @@ -246,18 +253,21 @@ function bindLogoutEvent(): void { const logoutBtn: HTMLButtonElement = document.getElementById('btnLogout') as HTMLButtonElement; if (logoutBtn) { logoutBtn.addEventListener('click', () => { - window.electronAPI.removeSessionStorage('cookie'); + window.electronAPI.removeSessionStorage('cookies'); window.location.href = 'login.html'; }); } } /** - * 绑定关闭错误弹窗事件 + * 绑定错误弹窗事件 */ -function bindCloseErrorModalEvent(): void { +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', () => faultReportingAsync()); // Close button click event closeErrorModal.addEventListener('click', () => { @@ -283,6 +293,59 @@ function showErrorModal(message: string): void { 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) { + 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); + } + } +} + /** * 初始化 */ @@ -306,8 +369,8 @@ async function initialize(): Promise { // Bind logout event bindLogoutEvent(); - // Bind close error modal event - bindCloseErrorModalEvent(); + // Bind error modal event + bindErrorModalEvent(); } catch (error) { console.error('初始化失败:', error); } diff --git a/src/login.ts b/src/login.ts index 4ff745d..5c728a7 100644 --- a/src/login.ts +++ b/src/login.ts @@ -20,7 +20,8 @@ async function handleLogin(request: LoginRequest): Promise { } else { // 登录成功,将data.Cookies拼接为字符串,存储cookie const cookies: string = JSON.stringify(response.data.Cookies); - window.electronAPI.setSessionStorage('cookie', cookies); + window.electronAPI.setSessionStorage('cookies', cookies); + window.electronAPI.setCacheAsync('Account', request.Account); // 跳转到主页面 window.location.href = 'index.html'; } diff --git a/src/preload.ts b/src/preload.ts index fac0ec5..78ab07e 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -5,6 +5,18 @@ import { contextBridge, ipcRenderer } from 'electron'; import { ApiResponse, MenuItem, TagResolutionConfig } from './EIAC_Desktop_Api'; contextBridge.exposeInMainWorld('electronAPI', { + /** + * 获取缓存 + * @param key 键 + * @returns 缓存 + */ + getCacheAsync: (key: string) => ipcRenderer.invoke('cache:get', key) as Promise, + /** + * 设置缓存 + * @param key 键 + * @param value 值 + */ + setCacheAsync: (key: string, value: any) => ipcRenderer.invoke('cache:set', key, value) as Promise, /** * 获取菜单缓存 * @returns 菜单缓存 @@ -32,25 +44,27 @@ contextBridge.exposeInMainWorld('electronAPI', { ipcRenderer.on('webview-new-window', (_event, webContentId, url) => callback(webContentId, url)); }, - /** - * 获取当前屏幕的缩放比例和分辨率 - * @returns 缩放比例和分辨率 - */ - getPrimaryDisplay: () => ipcRenderer.invoke('get-primary-display') as Promise, - /** * 检查URL是否可用 * @param url 要检查的URL * @returns 是否可用 */ - checkUrlAvailable: (url: string) => ipcRenderer.invoke('check-url-available', url), + checkUrlAvailable: (url: string) => ipcRenderer.invoke('check-url-available', url) as Promise, /** * 设置webview的cookie * @param url 要设置cookie的URL * @param cookie cookie字符串 */ - setWebviewCookie: (url: string, cookie: string) => ipcRenderer.invoke('set-webview-cookie', url, cookie), + setWebviewCookie: (url: string, cookie: string) => ipcRenderer.invoke('set-webview-cookie', url, cookie) as Promise, + + /** + * 故障上报 + * @param url 要上报的URL + * @param message 要上报的消息 + * @param status 要上报的状态 + */ + faultReporting: (url: string, message: string, status: number) => ipcRenderer.invoke('fault-reporting', url, message, status) as Promise<{ ok: boolean; status: number; message?: string }>, /** * 按键将值设置到sessionStorage diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index b184290..5500b27 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -1,6 +1,18 @@ import { ApiResponse, MenuItem, TagResolutionConfig } from '../EIAC_Desktop_Api'; export interface ElectronAPI { + /** + * 获取缓存 + * @param key 键 + * @returns 缓存 + */ + getCacheAsync: (key: string) => Promise; + /** + * 设置缓存 + * @param key 键 + * @param value 值 + */ + setCacheAsync: (key: string, value: any) => Promise; /** * 获取菜单缓存 * @returns 菜单缓存 @@ -22,11 +34,6 @@ export interface ElectronAPI { * @param callback 回调函数,参数为webContentId和url。其中webContentId是请求打开URL的webview的id。 */ onOpenTab: (callback: (webContentId: number, url: string) => void) => void; - /** - * 获取当前屏幕的缩放比例和分辨率 - * @returns 缩放比例和分辨率 - */ - getPrimaryDisplay: () => Promise; /** * 检查URL是否可用 * @param url 要检查的URL @@ -39,6 +46,13 @@ export interface ElectronAPI { * @param cookie cookie字符串 */ setWebviewCookie: (url: string, cookie: string) => Promise; + /** + * 故障上报 + * @param url 要上报的URL + * @param message 要上报的消息 + * @param status 要上报的状态 + */ + faultReporting: (url: string, message: string, status: number) => Promise<{ ok: boolean; status: number; message?: string }>; /** * 按键将值设置到sessionStorage * @param key 键