在URL不可用时,显示一键报障按钮,实现一键上报故障功能。
This commit is contained in:
parent
f51a0aa21d
commit
c0126fd07a
@ -43,7 +43,8 @@
|
||||
<div class="modal-content">
|
||||
<h2>访问失败</h2>
|
||||
<p id="errorMessage"></p>
|
||||
<button id="closeErrorModal" class="close-btn">确定</button>
|
||||
<button id="faultReporting" class="fault-reporting-btn">一键报障</button>
|
||||
<button id="closeErrorModal" class="close-btn">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -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<string, any>();
|
||||
|
||||
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<FaultReportingResponse> = 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<string> {
|
||||
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<MenuItem[]> = null;
|
||||
let menuDataReadyPromise: Promise<ApiResponse<MenuItem[]>>;
|
||||
let configData: ApiResponse<TagResolutionConfig[]> = null;
|
||||
|
@ -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;
|
||||
|
83
src/index.ts
83
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<Tab> {
|
||||
async function addTabAsync(tabGroup: TabGroup, menuItem: MenuItem): Promise<Tab | null> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
// Bind logout event
|
||||
bindLogoutEvent();
|
||||
|
||||
// Bind close error modal event
|
||||
bindCloseErrorModalEvent();
|
||||
// Bind error modal event
|
||||
bindErrorModalEvent();
|
||||
} catch (error) {
|
||||
console.error('初始化失败:', error);
|
||||
}
|
||||
|
@ -20,7 +20,8 @@ async function handleLogin(request: LoginRequest): Promise<void> {
|
||||
} 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';
|
||||
}
|
||||
|
@ -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<any>,
|
||||
/**
|
||||
* 设置缓存
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
*/
|
||||
setCacheAsync: (key: string, value: any) => ipcRenderer.invoke('cache:set', key, value) as Promise<void>,
|
||||
/**
|
||||
* 获取菜单缓存
|
||||
* @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<Electron.Display>,
|
||||
|
||||
/**
|
||||
* 检查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<boolean>,
|
||||
|
||||
/**
|
||||
* 设置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<boolean>,
|
||||
|
||||
/**
|
||||
* 故障上报
|
||||
* @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
|
||||
|
24
src/types/electron.d.ts
vendored
24
src/types/electron.d.ts
vendored
@ -1,6 +1,18 @@
|
||||
import { ApiResponse, MenuItem, TagResolutionConfig } from '../EIAC_Desktop_Api';
|
||||
|
||||
export interface ElectronAPI {
|
||||
/**
|
||||
* 获取缓存
|
||||
* @param key 键
|
||||
* @returns 缓存
|
||||
*/
|
||||
getCacheAsync: (key: string) => Promise<any>;
|
||||
/**
|
||||
* 设置缓存
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
*/
|
||||
setCacheAsync: (key: string, value: any) => Promise<void>;
|
||||
/**
|
||||
* 获取菜单缓存
|
||||
* @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<Electron.Display>;
|
||||
/**
|
||||
* 检查URL是否可用
|
||||
* @param url 要检查的URL
|
||||
@ -39,6 +46,13 @@ export interface ElectronAPI {
|
||||
* @param cookie cookie字符串
|
||||
*/
|
||||
setWebviewCookie: (url: string, cookie: string) => Promise<boolean>;
|
||||
/**
|
||||
* 故障上报
|
||||
* @param url 要上报的URL
|
||||
* @param message 要上报的消息
|
||||
* @param status 要上报的状态
|
||||
*/
|
||||
faultReporting: (url: string, message: string, status: number) => Promise<{ ok: boolean; status: number; message?: string }>;
|
||||
/**
|
||||
* 按键将值设置到sessionStorage
|
||||
* @param key 键
|
||||
|
Loading…
x
Reference in New Issue
Block a user