Compare commits
No commits in common. "fdc008f33cb68dc2211f9630e6b7e3d590ee4620" and "89ee08789dcb8d6293c6af4be2b114a3d337859b" have entirely different histories.
fdc008f33c
...
89ee08789d
113
README.md
113
README.md
@ -1,113 +0,0 @@
|
|||||||
# EIAC Desktop Application
|
|
||||||
|
|
||||||
一个基于 Electron 框架的跨平台桌面应用程序,用于访问和管理企业内部应用系统。
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
EIAC Desktop Application 是一个基于 Electron 框架的桌面应用程序。
|
|
||||||
|
|
||||||
主要功能包括:
|
|
||||||
|
|
||||||
- 🏢 统一的企业应用访问入口
|
|
||||||
- 📑 多标签页管理
|
|
||||||
- 🔍 自定义页面缩放
|
|
||||||
- 🔐 统一的用户认证
|
|
||||||
- 🐛 故障上报功能
|
|
||||||
- 🖥️ 跨平台支持(Windows、macOS、Linux)
|
|
||||||
|
|
||||||
本项目使用 `npx create-electron-app@latest china-telecom-app --template=vite-typescript` 创建。
|
|
||||||
|
|
||||||
## 开发
|
|
||||||
|
|
||||||
### 环境要求
|
|
||||||
|
|
||||||
- Node.js >= 22.11.0
|
|
||||||
- npm >= 11.3.0
|
|
||||||
- Git
|
|
||||||
|
|
||||||
### 开发环境设置
|
|
||||||
|
|
||||||
1. 克隆仓库
|
|
||||||
```bash
|
|
||||||
git clone [repository-url]
|
|
||||||
cd china-telecom-app
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 安装依赖
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
3. 启动开发服务器
|
|
||||||
```bash
|
|
||||||
npm run start
|
|
||||||
```
|
|
||||||
|
|
||||||
### 开发指南
|
|
||||||
|
|
||||||
- 使用 TypeScript 进行开发
|
|
||||||
- 遵循 ESLint 规范
|
|
||||||
- 使用 Prettier 进行代码格式化
|
|
||||||
- 主要开发文件位于 `src` 目录下
|
|
||||||
- 使用 IPC 通信进行主进程和渲染进程的通信
|
|
||||||
|
|
||||||
## 构建
|
|
||||||
|
|
||||||
### 构建命令
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run package
|
|
||||||
```
|
|
||||||
|
|
||||||
### 构建配置
|
|
||||||
|
|
||||||
- 构建配置位于 `forge.config.js`
|
|
||||||
- 支持自定义应用图标
|
|
||||||
- 支持自定义应用名称
|
|
||||||
- 支持自定义构建目标平台
|
|
||||||
|
|
||||||
## 发布
|
|
||||||
|
|
||||||
### 发布流程
|
|
||||||
|
|
||||||
1. 更新版本号
|
|
||||||
|
|
||||||
在确认仓库没有任何未提交的更改后,执行以下命令更新版本号:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm version [patch|minor|major]
|
|
||||||
```
|
|
||||||
|
|
||||||
如有未更改的提交,执行以上命令会报错:`npm error Git working directory not clean.`
|
|
||||||
|
|
||||||
或者手工编辑 `package.json` 文件,将 `version` 字段更新为新版本号。
|
|
||||||
|
|
||||||
2. 构建版本
|
|
||||||
```bash
|
|
||||||
npm run package
|
|
||||||
```
|
|
||||||
|
|
||||||
3. 打包版本
|
|
||||||
```bash
|
|
||||||
npm run make
|
|
||||||
```
|
|
||||||
|
|
||||||
4. 发布到发布服务器
|
|
||||||
```bash
|
|
||||||
npm run publish
|
|
||||||
```
|
|
||||||
|
|
||||||
### 发布注意事项
|
|
||||||
|
|
||||||
- 确保版本号正确更新
|
|
||||||
- 确保所有依赖都是最新的稳定版本
|
|
||||||
- 确保构建配置正确
|
|
||||||
- 测试发布版本的功能完整性
|
|
||||||
|
|
||||||
## 参考文档
|
|
||||||
|
|
||||||
- [Electron](https://www.electronjs.org/) - 跨平台桌面应用框架
|
|
||||||
- [Electron Forge](https://www.electronforge.io/) - Electron 应用打包工具
|
|
||||||
- [TypeScript](https://www.typescriptlang.org/) - JavaScript 的超集
|
|
||||||
- [electron-tabs](https://github.com/brrd/electron-tabs) - Electron 标签页管理
|
|
||||||
- [vite](https://vite.dev/) - 现代前端构建工具
|
|
BIN
assets/help.png
BIN
assets/help.png
Binary file not shown.
Before Width: | Height: | Size: 2.4 KiB |
@ -13,25 +13,10 @@ const config: ForgeConfig = {
|
|||||||
extraResource: [
|
extraResource: [
|
||||||
'assets/'
|
'assets/'
|
||||||
],
|
],
|
||||||
icon: 'assets/icon',
|
icon: 'assets/icon.ico',
|
||||||
},
|
},
|
||||||
rebuildConfig: {},
|
rebuildConfig: {},
|
||||||
makers: [
|
makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})],
|
||||||
new MakerSquirrel({
|
|
||||||
authors: 'Allen Cai',
|
|
||||||
owners: 'Allen Cai',
|
|
||||||
exe: 'china-telecom-app.exe',
|
|
||||||
name: 'china-telecom-app',
|
|
||||||
version: require('./package.json').version,
|
|
||||||
description: 'China Telecom App',
|
|
||||||
copyright: 'Copyright © 2025 Allen Cai',
|
|
||||||
noMsi: true,
|
|
||||||
setupIcon: 'assets/icon.ico',
|
|
||||||
}),
|
|
||||||
new MakerZIP({}, ['darwin']),
|
|
||||||
new MakerRpm({}),
|
|
||||||
new MakerDeb({})
|
|
||||||
],
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new VitePlugin({
|
new VitePlugin({
|
||||||
// `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
|
// `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<span id="userInfo"></span>
|
<span id="userInfo"></span>
|
||||||
<button id="btnExit" class="exit-btn">退出</button>
|
<button id="btnLogout" class="logout-btn">退出登录</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@ -48,11 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 帮助图标 -->
|
<script src="node_modules/electron-tabs/dist/electron-tabs.js"></script>
|
||||||
<img id="helpIcon" src="./assets/help.png" alt="帮助图标" class="help-icon">
|
|
||||||
<script type="module">
|
|
||||||
import 'electron-tabs';
|
|
||||||
</script>
|
|
||||||
<script type="module" src="./src/index.ts"></script>
|
<script type="module" src="./src/index.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "china-telecom-app",
|
"name": "china-telecom-app",
|
||||||
"productName": "china-telecom-app",
|
"productName": "china-telecom-app",
|
||||||
"version": "1.1.1",
|
"version": "1.0.0",
|
||||||
"description": "China Telecom App",
|
"description": "My Electron application description",
|
||||||
"main": ".vite/build/main.js",
|
"main": ".vite/build/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cross-env NODE_ENV=development electron-forge start",
|
"start": "cross-env NODE_ENV=development electron-forge start",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { BrowserWindow, ipcMain, screen, session, app } from "electron";
|
import { BrowserWindow, ipcMain, screen, session } from "electron";
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import https from 'https';
|
import https from 'https';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
@ -8,11 +8,6 @@ import { ApiResponse, MenuItem, TagResolutionConfig, EIACDesktopApi, SpecialPUrl
|
|||||||
const memoryCache = new Map<string, any>();
|
const memoryCache = new Map<string, any>();
|
||||||
|
|
||||||
export function initialize(): void {
|
export function initialize(): void {
|
||||||
// Close app
|
|
||||||
ipcMain.handle('app:close', (): void => {
|
|
||||||
app.exit(0); // 使用 exit 而不是 quit,确保立即退出
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set cache
|
// Set cache
|
||||||
ipcMain.handle('cache:set', (_event, key: string, value: any): void => {
|
ipcMain.handle('cache:set', (_event, key: string, value: any): void => {
|
||||||
memoryCache.set(key, value);
|
memoryCache.set(key, value);
|
||||||
@ -45,7 +40,7 @@ export function initialize(): void {
|
|||||||
return helperDescrip ? helperDescrip.Descrip : null;
|
return helperDescrip ? helperDescrip.Descrip : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get zoom factor by url
|
// Get zoom factor
|
||||||
ipcMain.handle('get-zoom-factor-by-url', async (event, url: string): Promise<number> => {
|
ipcMain.handle('get-zoom-factor-by-url', async (event, url: string): Promise<number> => {
|
||||||
const display: Electron.Display = screen.getPrimaryDisplay();
|
const display: Electron.Display = screen.getPrimaryDisplay();
|
||||||
const physicalSize: Electron.Size = {
|
const physicalSize: Electron.Size = {
|
||||||
@ -118,13 +113,7 @@ export function initialize(): void {
|
|||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
console.log('check-url-available HEAD', url.toString(), res.statusCode, res.statusMessage);
|
console.log('check-url-available HEAD', url.toString(), res.statusCode, res.statusMessage);
|
||||||
/**
|
if (res.statusCode === 403 || res.statusCode === 404) {
|
||||||
* 403: 禁止访问
|
|
||||||
* 404: 未找到
|
|
||||||
* 405: 方法不允许
|
|
||||||
*/
|
|
||||||
const requiresRetryStatusCodes: number[] = [403, 404, 405];
|
|
||||||
if (requiresRetryStatusCodes.includes(res.statusCode)) {
|
|
||||||
headReq.destroy()
|
headReq.destroy()
|
||||||
const getReq = lib.get({
|
const getReq = lib.get({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -164,7 +153,7 @@ export function initialize(): void {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set webview's cookie
|
// Set webview‘s cookie
|
||||||
ipcMain.handle('set-webview-cookie', async (event, url: string, cookie: string): Promise<boolean> => {
|
ipcMain.handle('set-webview-cookie', async (event, url: string, cookie: string): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
@ -199,24 +188,12 @@ export function initialize(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const base64 = await captureWindowAsBase64(win);
|
const base64 = await captureWindowAsBase64(win);
|
||||||
console.debug('base64:', base64);
|
console.log('base64:', base64);
|
||||||
|
|
||||||
const account: string = memoryCache.get('Account');
|
|
||||||
if (!account) {
|
|
||||||
throw new Error('Not found account');
|
|
||||||
}
|
|
||||||
console.log('account:', account);
|
|
||||||
|
|
||||||
const ip: string = getLocalIPAddress();
|
|
||||||
if (!ip) {
|
|
||||||
throw new Error('Not found ip');
|
|
||||||
}
|
|
||||||
console.log('ip:', ip);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response: ApiResponse<FaultReportingResponse> = await EIACDesktopApi.Help.FaultReportingAsync({
|
const response: ApiResponse<FaultReportingResponse> = await EIACDesktopApi.Help.FaultReportingAsync({
|
||||||
Account: account,
|
Account: memoryCache.get('Account'),
|
||||||
IP: ip,
|
IP: getLocalIPAddress(),
|
||||||
Url: url,
|
Url: url,
|
||||||
ImgBase64: base64,
|
ImgBase64: base64,
|
||||||
Explain: `message: ${message}, status: ${status}`
|
Explain: `message: ${message}, status: ${status}`
|
||||||
|
103
src/index.css
103
src/index.css
@ -44,7 +44,7 @@ body {
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.exit-btn {
|
.logout-btn {
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
background-color: #f44336;
|
background-color: #f44336;
|
||||||
color: white;
|
color: white;
|
||||||
@ -53,7 +53,7 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.exit-btn:hover {
|
.logout-btn:hover {
|
||||||
background-color: #d32f2f;
|
background-color: #d32f2f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,89 +126,68 @@ body {
|
|||||||
|
|
||||||
/* 故障窗口样式 */
|
/* 故障窗口样式 */
|
||||||
.modal {
|
.modal {
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
background-color: white;
|
background-color: white;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content h2 {
|
.modal-content h2 {
|
||||||
color: #e74c3c;
|
color: #e74c3c;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content p {
|
.modal-content p {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-btn {
|
.close-btn {
|
||||||
background-color: #ed7226;
|
background-color: #ed7226;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 8px 20px;
|
padding: 8px 20px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-btn:hover {
|
.close-btn:hover {
|
||||||
background-color: #ce6524;
|
background-color: #ce6524;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fault-reporting-btn {
|
.fault-reporting-btn {
|
||||||
background-color: #3498db;
|
background-color: #3498db;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 8px 20px;
|
padding: 8px 20px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fault-reporting-btn:hover {
|
.fault-reporting-btn:hover {
|
||||||
background-color: #2980b9;
|
background-color: #2980b9;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tabs styles */
|
/* Tabs styles */
|
||||||
.tabs-container {
|
.tabs-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
|
||||||
|
|
||||||
/* Help icon */
|
|
||||||
.help-icon {
|
|
||||||
min-width: 32px;
|
|
||||||
min-height: 32px;
|
|
||||||
max-width: 64px;
|
|
||||||
max-height: 64px;
|
|
||||||
position: fixed;
|
|
||||||
top: 50%;
|
|
||||||
right: 20px;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
z-index: 9999;
|
|
||||||
transition: transform 0.6s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.help-icon:hover {
|
|
||||||
transform: translateY(-50%) scale(1.2) rotate(360deg);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
111
src/index.ts
111
src/index.ts
@ -116,23 +116,21 @@ function renderMenu(menuList: MenuItem[]): void {
|
|||||||
* 添加标签页
|
* 添加标签页
|
||||||
* @param tabGroup 标签组
|
* @param tabGroup 标签组
|
||||||
* @param menuItem 菜单项
|
* @param menuItem 菜单项
|
||||||
* @param allowCloseTab 是否允许关闭标签页
|
|
||||||
* @returns 标签页
|
* @returns 标签页
|
||||||
*/
|
*/
|
||||||
async function addTabAsync(tabGroup: TabGroup, menuItem: MenuItem, allowCloseTab: boolean = true): Promise<Tab | null> {
|
async function addTabAsync(tabGroup: TabGroup, menuItem: MenuItem): Promise<Tab | null> {
|
||||||
const url: string = menuItem.Url.startsWith("http") ? menuItem.Url : `http://${menuItem.Url}`;
|
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);
|
const result: { ok: boolean; status: number; message?: string } = 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 ${url} 可访问:`, result.status);
|
console.log(`✅ URL ${url} 可访问:`, result.status);
|
||||||
lastInvalidUrlResult = null;
|
lastInvalidUrlResult = null;
|
||||||
|
|
||||||
const cookies: string = window.electronAPI.getSessionStorage('cookies');
|
const cookies: string = window.electronAPI.getSessionStorage('cookies');
|
||||||
await window.electronAPI.setWebviewCookie(url, cookies);
|
await window.electronAPI.setWebviewCookie(url, cookies);
|
||||||
} else {
|
} else {
|
||||||
console.warn(`❌ URL ${url} 不可访问:`, result.message ?? `status ${result.status}`);
|
console.warn(`❌ URL ${url} 不可访问:`, result.message ?? `status ${result.status}`);
|
||||||
lastInvalidUrlResult = { url, message: 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请联系技术支持。`;
|
const helpDescrip: string = await window.electronAPI.getHelperDescripAsync(result.status.toString()) ?? `无法访问 {URL}\r\n异常原因:${result.message ?? `status ${result.status}`}\r\n${helpDescrip ?? ''}`;
|
||||||
showErrorModal(helpDescrip.replace('{URL}', url));
|
showErrorModal(helpDescrip.replace('{URL}', url));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -141,7 +139,7 @@ async function addTabAsync(tabGroup: TabGroup, menuItem: MenuItem, allowCloseTab
|
|||||||
|
|
||||||
const tab: Tab = tabGroup.addTab({
|
const tab: Tab = tabGroup.addTab({
|
||||||
active: true,
|
active: true,
|
||||||
closable: allowCloseTab,
|
closable: true,
|
||||||
title: menuItem.ShowName,
|
title: menuItem.ShowName,
|
||||||
src: url,
|
src: url,
|
||||||
iconURL: menuItem.IconConfig._1x.Default,
|
iconURL: menuItem.IconConfig._1x.Default,
|
||||||
@ -155,9 +153,7 @@ async function addTabAsync(tabGroup: TabGroup, menuItem: MenuItem, allowCloseTab
|
|||||||
const webview: Electron.WebviewTag = tab.webview as Electron.WebviewTag;
|
const webview: Electron.WebviewTag = tab.webview as Electron.WebviewTag;
|
||||||
listenWebviewTitleChange(webview, tab);
|
listenWebviewTitleChange(webview, tab);
|
||||||
|
|
||||||
// 监听 webview 的 DOM 加载完成事件
|
|
||||||
tab.once('webview-dom-ready', () => {
|
tab.once('webview-dom-ready', () => {
|
||||||
// 设置 webview 的缩放比例
|
|
||||||
const webview: Electron.WebviewTag = tab.webview as Electron.WebviewTag;
|
const webview: Electron.WebviewTag = tab.webview as Electron.WebviewTag;
|
||||||
const defaultZoomFactor: number = webview.getZoomFactor();
|
const defaultZoomFactor: number = webview.getZoomFactor();
|
||||||
console.log('Default zoom factor:', defaultZoomFactor);
|
console.log('Default zoom factor:', defaultZoomFactor);
|
||||||
@ -168,18 +164,6 @@ async function addTabAsync(tabGroup: TabGroup, menuItem: MenuItem, allowCloseTab
|
|||||||
} else {
|
} else {
|
||||||
console.log('Default zoom factor is the same as the zoom factor:', zoomFactor);
|
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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -193,31 +177,7 @@ async function addTabAsync(tabGroup: TabGroup, menuItem: MenuItem, allowCloseTab
|
|||||||
* @param tab 标签页
|
* @param tab 标签页
|
||||||
*/
|
*/
|
||||||
function listenWebviewTitleChange(webview: Electron.WebviewTag, tab: Tab): void {
|
function listenWebviewTitleChange(webview: Electron.WebviewTag, tab: Tab): void {
|
||||||
// 监听 URL 变化事件
|
// 在webview加载完成后,获取并设置标签页的标题和图标
|
||||||
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 () => {
|
webview.addEventListener('did-finish-load', async () => {
|
||||||
const title: string = webview.getTitle();
|
const title: string = webview.getTitle();
|
||||||
tab.setTitle(title);
|
tab.setTitle(title);
|
||||||
@ -267,29 +227,6 @@ async function getFaviconUrl(webview: Electron.WebviewTag): Promise<string> {
|
|||||||
return defaultFaviconUrl;
|
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点击事件
|
* 绑定logo点击事件
|
||||||
*/
|
*/
|
||||||
@ -312,19 +249,18 @@ function bindLogoClickEvent(tabGroup: TabGroup, menuItem: MenuItem): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 绑定退出事件
|
* 绑定登出事件
|
||||||
*/
|
*/
|
||||||
function bindExitEvent(): void {
|
function bindLogoutEvent(): void {
|
||||||
const exitBtn: HTMLButtonElement = document.getElementById('btnExit') as HTMLButtonElement;
|
const logoutBtn: HTMLButtonElement = document.getElementById('btnLogout') as HTMLButtonElement;
|
||||||
if (exitBtn) {
|
if (logoutBtn) {
|
||||||
exitBtn.addEventListener('click', async () => {
|
logoutBtn.addEventListener('click', () => {
|
||||||
window.electronAPI.removeSessionStorage('cookies');
|
window.electronAPI.removeSessionStorage('cookies');
|
||||||
await window.electronAPI.closeApp();
|
window.location.href = 'login.html';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let closeCount: number = 0;
|
|
||||||
/**
|
/**
|
||||||
* 绑定错误弹窗事件
|
* 绑定错误弹窗事件
|
||||||
*/
|
*/
|
||||||
@ -333,31 +269,17 @@ function bindErrorModalEvent(): void {
|
|||||||
const closeErrorModal: HTMLButtonElement = document.getElementById('closeErrorModal') as HTMLButtonElement;
|
const closeErrorModal: HTMLButtonElement = document.getElementById('closeErrorModal') as HTMLButtonElement;
|
||||||
const faultReporting: HTMLButtonElement = document.getElementById('faultReporting') as HTMLButtonElement;
|
const faultReporting: HTMLButtonElement = document.getElementById('faultReporting') as HTMLButtonElement;
|
||||||
|
|
||||||
faultReporting.addEventListener('click', async () => await faultReportingAsync());
|
faultReporting.addEventListener('click', () => faultReportingAsync());
|
||||||
|
|
||||||
// Close button click event
|
// Close button click event
|
||||||
closeErrorModal.addEventListener('click', () => {
|
closeErrorModal.addEventListener('click', () => {
|
||||||
errorModal.style.display = 'none';
|
errorModal.style.display = 'none';
|
||||||
closeCount++;
|
|
||||||
|
|
||||||
// 如果关闭次数大于等于2,则重置计数并且清空lastInvalidUrlResult
|
|
||||||
if (closeCount >= 2) {
|
|
||||||
closeCount = 0;
|
|
||||||
lastInvalidUrlResult = null;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Click outside to close
|
// Click outside to close
|
||||||
window.addEventListener('click', (event: Event) => {
|
window.addEventListener('click', (event: Event) => {
|
||||||
if (event.target === errorModal) {
|
if (event.target === errorModal) {
|
||||||
errorModal.style.display = 'none';
|
errorModal.style.display = 'none';
|
||||||
closeCount++;
|
|
||||||
|
|
||||||
// 如果关闭次数大于等于2,则重置计数并且清空lastInvalidUrlResult
|
|
||||||
if (closeCount >= 2) {
|
|
||||||
closeCount = 0;
|
|
||||||
lastInvalidUrlResult = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -394,7 +316,6 @@ async function faultReportingAsync(): Promise<void> {
|
|||||||
|
|
||||||
// 显示请求结果
|
// 显示请求结果
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
lastInvalidUrlResult = null;
|
|
||||||
faultReportingBtn.textContent = '上报成功';
|
faultReportingBtn.textContent = '上报成功';
|
||||||
faultReportingBtn.style.backgroundColor = '#4CAF50';
|
faultReportingBtn.style.backgroundColor = '#4CAF50';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -440,18 +361,14 @@ async function initialize(): Promise<void> {
|
|||||||
|
|
||||||
// Create initial tab
|
// Create initial tab
|
||||||
const firstMenuItem: MenuItem = menuList[0];
|
const firstMenuItem: MenuItem = menuList[0];
|
||||||
await addTabAsync(tabGroup, firstMenuItem, false);
|
await addTabAsync(tabGroup, firstMenuItem);
|
||||||
|
|
||||||
// Bind help icon click event
|
|
||||||
const helpMenuItem: MenuItem = menuList[menuList.length - 2];
|
|
||||||
bindHelpIconClickEvent(helpMenuItem);
|
|
||||||
|
|
||||||
// Bind logo click event
|
// Bind logo click event
|
||||||
const logoMenuItem: MenuItem = menuList[menuList.length - 1];
|
const logoMenuItem: MenuItem = menuList[menuList.length - 1];
|
||||||
bindLogoClickEvent(tabGroup, logoMenuItem);
|
bindLogoClickEvent(tabGroup, logoMenuItem);
|
||||||
|
|
||||||
// Bind exit event
|
// Bind logout event
|
||||||
bindExitEvent();
|
bindLogoutEvent();
|
||||||
|
|
||||||
// Bind error modal event
|
// Bind error modal event
|
||||||
bindErrorModalEvent();
|
bindErrorModalEvent();
|
||||||
|
@ -5,48 +5,43 @@ import { contextBridge, ipcRenderer } from 'electron';
|
|||||||
import { ApiResponse, MenuItem, TagResolutionConfig } from './EIAC_Desktop_Api';
|
import { ApiResponse, MenuItem, TagResolutionConfig } from './EIAC_Desktop_Api';
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('electronAPI', {
|
contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
/**
|
|
||||||
* 关闭应用程序
|
|
||||||
*/
|
|
||||||
closeApp: (): Promise<void> => ipcRenderer.invoke('app:close'),
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取缓存
|
* 获取缓存
|
||||||
* @param key 键
|
* @param key 键
|
||||||
* @returns 缓存
|
* @returns 缓存
|
||||||
*/
|
*/
|
||||||
getCacheAsync: (key: string): Promise<any> => ipcRenderer.invoke('cache:get', key),
|
getCacheAsync: (key: string) => ipcRenderer.invoke('cache:get', key) as Promise<any>,
|
||||||
/**
|
/**
|
||||||
* 设置缓存
|
* 设置缓存
|
||||||
* @param key 键
|
* @param key 键
|
||||||
* @param value 值
|
* @param value 值
|
||||||
*/
|
*/
|
||||||
setCacheAsync: (key: string, value: any): Promise<void> => ipcRenderer.invoke('cache:set', key, value),
|
setCacheAsync: (key: string, value: any) => ipcRenderer.invoke('cache:set', key, value) as Promise<void>,
|
||||||
/**
|
/**
|
||||||
* 获取菜单缓存
|
* 获取菜单缓存
|
||||||
* @returns 菜单缓存
|
* @returns 菜单缓存
|
||||||
*/
|
*/
|
||||||
getMenuCacheAsync: (): Promise<ApiResponse<MenuItem[]>> => ipcRenderer.invoke('get-menu-cache'),
|
getMenuCacheAsync: () => ipcRenderer.invoke('get-menu-cache') as Promise<ApiResponse<MenuItem[]>>,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取帮助描述
|
* 获取帮助描述
|
||||||
* @param code 帮助描述代码
|
* @param code 帮助描述代码
|
||||||
* @returns 帮助描述
|
* @returns 帮助描述
|
||||||
*/
|
*/
|
||||||
getHelperDescripAsync: (code: string): Promise<string | null> => ipcRenderer.invoke('get-helper-descrip', code),
|
getHelperDescripAsync: (code: string) => ipcRenderer.invoke('get-helper-descrip', code) as Promise<string | null>,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定URL的缩放比例
|
* 获取指定URL的缩放比例
|
||||||
* @param url 要获取缩放比例的URL
|
* @param url 要获取缩放比例的URL
|
||||||
* @returns 缩放比例
|
* @returns 缩放比例
|
||||||
*/
|
*/
|
||||||
getZoomFactorByUrl: (url: string): Promise<number> => ipcRenderer.invoke('get-zoom-factor-by-url', url),
|
getZoomFactorByUrl: (url: string) => ipcRenderer.invoke('get-zoom-factor-by-url', url) as Promise<number>,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在新标签页打开URL
|
* 在新标签页打开URL
|
||||||
* @param callback 回调函数,参数为webContentId和url。其中webContentId是请求打开URL的webview的id。
|
* @param callback 回调函数,参数为webContentId和url。其中webContentId是请求打开URL的webview的id。
|
||||||
*/
|
*/
|
||||||
onOpenTab: (callback: (webContentId: number, url: string) => void): void => {
|
onOpenTab: (callback: (webContentId: number, url: string) => void) => {
|
||||||
ipcRenderer.on('webview-new-window', (_event, webContentId, url) => callback(webContentId, url));
|
ipcRenderer.on('webview-new-window', (_event, webContentId, url) => callback(webContentId, url));
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -55,14 +50,14 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
* @param url 要检查的URL
|
* @param url 要检查的URL
|
||||||
* @returns 是否可用
|
* @returns 是否可用
|
||||||
*/
|
*/
|
||||||
checkUrlAvailable: (url: string): Promise<boolean> => ipcRenderer.invoke('check-url-available', url),
|
checkUrlAvailable: (url: string) => ipcRenderer.invoke('check-url-available', url) as Promise<boolean>,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置webview的cookie
|
* 设置webview的cookie
|
||||||
* @param url 要设置cookie的URL
|
* @param url 要设置cookie的URL
|
||||||
* @param cookie cookie字符串
|
* @param cookie cookie字符串
|
||||||
*/
|
*/
|
||||||
setWebviewCookie: (url: string, cookie: string): Promise<boolean> => ipcRenderer.invoke('set-webview-cookie', url, cookie),
|
setWebviewCookie: (url: string, cookie: string) => ipcRenderer.invoke('set-webview-cookie', url, cookie) as Promise<boolean>,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 故障上报
|
* 故障上报
|
||||||
@ -70,14 +65,14 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
* @param message 要上报的消息
|
* @param message 要上报的消息
|
||||||
* @param status 要上报的状态
|
* @param status 要上报的状态
|
||||||
*/
|
*/
|
||||||
faultReporting: (url: string, message: string, status: number): Promise<{ ok: boolean; status: number; message?: string }> => ipcRenderer.invoke('fault-reporting', url, message, status),
|
faultReporting: (url: string, message: string, status: number) => ipcRenderer.invoke('fault-reporting', url, message, status) as Promise<{ ok: boolean; status: number; message?: string }>,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按键将值设置到sessionStorage
|
* 按键将值设置到sessionStorage
|
||||||
* @param key 键
|
* @param key 键
|
||||||
* @param value 值
|
* @param value 值
|
||||||
*/
|
*/
|
||||||
setSessionStorage: (key: string, value: string): void => {
|
setSessionStorage: (key: string, value: string) => {
|
||||||
window.sessionStorage.setItem(key, value);
|
window.sessionStorage.setItem(key, value);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -86,7 +81,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
* @param key 键
|
* @param key 键
|
||||||
* @returns 值
|
* @returns 值
|
||||||
*/
|
*/
|
||||||
getSessionStorage: (key: string): string | null => {
|
getSessionStorage: (key: string) => {
|
||||||
return window.sessionStorage.getItem(key);
|
return window.sessionStorage.getItem(key);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -94,14 +89,14 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
* 从sessionStorage中删除指定键的值
|
* 从sessionStorage中删除指定键的值
|
||||||
* @param key 键
|
* @param key 键
|
||||||
*/
|
*/
|
||||||
removeSessionStorage: (key: string): void => {
|
removeSessionStorage: (key: string) => {
|
||||||
window.sessionStorage.removeItem(key);
|
window.sessionStorage.removeItem(key);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清空sessionStorage
|
* 清空sessionStorage
|
||||||
*/
|
*/
|
||||||
clearSessionStorage: (): void => {
|
clearSessionStorage: () => {
|
||||||
window.sessionStorage.clear();
|
window.sessionStorage.clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
4
src/types/electron.d.ts
vendored
4
src/types/electron.d.ts
vendored
@ -1,10 +1,6 @@
|
|||||||
import { ApiResponse, MenuItem, TagResolutionConfig } from '../EIAC_Desktop_Api';
|
import { ApiResponse, MenuItem, TagResolutionConfig } from '../EIAC_Desktop_Api';
|
||||||
|
|
||||||
export interface ElectronAPI {
|
export interface ElectronAPI {
|
||||||
/**
|
|
||||||
* 关闭应用程序
|
|
||||||
*/
|
|
||||||
closeApp: () => Promise<void>;
|
|
||||||
/**
|
/**
|
||||||
* 获取缓存
|
* 获取缓存
|
||||||
* @param key 键
|
* @param key 键
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"module": "ESNext",
|
"module": "commonjs",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
@ -20,18 +20,9 @@ export default defineConfig({
|
|||||||
input: {
|
input: {
|
||||||
login: 'login.html',
|
login: 'login.html',
|
||||||
index: 'index.html'
|
index: 'index.html'
|
||||||
},
|
|
||||||
output: {
|
|
||||||
// 确保 electron-tabs.js 被正确打包
|
|
||||||
manualChunks: {
|
|
||||||
'electron-tabs': ['electron-tabs']
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
|
||||||
include: ['electron-tabs']
|
|
||||||
},
|
|
||||||
// 使用 Vite 的 env 配置
|
// 使用 Vite 的 env 配置
|
||||||
envPrefix: ['EIAC_DESKTOP_API_HOST', 'NODE_ENV'],
|
envPrefix: ['EIAC_DESKTOP_API_HOST', 'NODE_ENV'],
|
||||||
// 定义环境变量
|
// 定义环境变量
|
||||||
|
Loading…
x
Reference in New Issue
Block a user