Files
electron-4/src/main/index.ts

795 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @ts-nocheck
import { app, shell, BrowserWindow, ipcMain, globalShortcut, dialog, session } from 'electron'
import path, { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/earth.png?asset'
import { Recorder } from "../preload/recorder";
import fs from 'fs'
import { exec, spawn } from 'child_process'
import dayjs from 'dayjs'
import os from "os";
import { GetHomeDir } from './config'
import { start, getServer } from "./app";
const http = require("http");
const yaml = require("js-yaml");
let Store = require('electron-store')
Store.initRenderer();
const store = new Store()
process.on('unhandledRejection', (reason) => {
console.error('主进程异步异常:', reason);
});
// 开发环境路径处理 - 确保添加正确的file协议
const devSplashPath = path.resolve(
app.getAppPath(),
'src',
'renderer',
'public',
'startUp',
'startUp.html'
)
// 开发环境必须添加file协议使用URL构造器确保格式正确
const devSplashURL = new URL(`file:///${devSplashPath}`).href
// 生产环境路径处理
let prodSplashURL = ''
const prodPossiblePaths = [
path.join(process.resourcesPath, 'app.asar', 'out', 'renderer', 'startUp', 'startUp.html'),
path.join(process.resourcesPath, 'app.asar', 'renderer', 'startUp', 'startUp.html'),
path.join(process.resourcesPath, 'out', 'renderer', 'startUp', 'startUp.html'),
// 增加asar包外可能的路径
path.join(process.resourcesPath, 'app.asar.unpacked', 'resources', 'startUp', 'startUp.html')
]
// 寻找存在的生产环境路径
const prodSplashPath = prodPossiblePaths.find((p) => {
try {
// 检查路径是否存在处理asar内部文件检查
return fs.existsSync(p)
} catch (e: any) {
console.error('检查路径时出错:', p, e.message)
return false
}
})
if (prodSplashPath) {
// 使用URL构造器处理路径自动编码特殊字符
// 对于Windows系统需要处理盘符前的斜杠问题
const normalizedPath =
process.platform === 'win32' ? prodSplashPath.replace(/^(\w:)/, '/$1') : prodSplashPath
prodSplashURL = new URL(`file://${normalizedPath}`).href
} else {
console.error('未找到有效的启动页路径,检查打包配置和文件是否存在')
// 可以在这里添加默认路径或错误处理
}
// 最终的启动页URL
const splashURL = process.env.NODE_ENV === 'development' ? devSplashURL : prodSplashURL
let startBatPath = process.env.NODE_ENV === 'development' ? path.resolve(app.getAppPath(), 'resources', 'java', 'start.bat') : path.join(process.resourcesPath, 'app.asar.unpacked', 'resources', 'java', 'start.bat')
startBatPath = process.platform === 'win32' ? startBatPath.replace(/^(\w:)/, '/$1') : startBatPath
let stopBatPath = process.env.NODE_ENV === 'development' ? path.resolve(app.getAppPath(), 'resources', 'java', 'stop.bat') : path.join(process.resourcesPath, 'app.asar.unpacked', 'resources', 'java', 'stop.bat')
stopBatPath = process.platform === 'win32' ? stopBatPath.replace(/^(\w:)/, '/$1') : stopBatPath
// const splashURL =
// process.env.NODE_ENV === 'development'
// ? `${join(app.getAppPath(), 'src/renderer/public/startUp/startUp.html')}`
// : `file://${join(app.getAppPath(), 'resources/app.asar/out/renderer/startUp/startUp.html')}`
let isRestart = false
let mainWindow;
let isSeverInit = false
let isAppInit = false
let severError: any = undefined
function createWindow(): void {
// Create the browser window.
start();
// 创建启动窗口
const splashWindow = new BrowserWindow({
width: 896,
height: 510,
frame: false,
alwaysOnTop: true,
show: false,
webPreferences: {
nodeIntegration: true, // 开启 Node 集成
contextIsolation: false, // 关闭上下文隔离
devTools: true,
webSecurity: false,
allowRunningInsecureContent: true
}
})
// 显示启动页
splashWindow.loadURL(splashURL)
splashWindow.show()
// 创建主窗口(保持原有配置,但先不显示)
mainWindow = new BrowserWindow({
minWidth: 1280, // 添加最小宽度限制
minHeight: 768, // 添加最小高度限制
fullscreen: true,
show: false,
frame: true,
autoHideMenuBar: true,
useContentSize: true, // 窗口尺寸包含内容区域而非边框
simpleFullscreen: true, // 使用简单全屏模式(仅macOS有效)
backgroundColor: '#00000000', // 添加这行设置透明背景
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
nodeIntegration: true, // 开启 Node 集成
contextIsolation: false, // 关闭上下文隔离
devTools: true,
webSecurity: false,
allowRunningInsecureContent: true
}
})
mainWindow.webContents.on('before-input-event', (event, input) => {
// 条件:仅按了 Alt 键(无其他键组合、不是组合键、不是重复按键)
if (
input.key === 'Alt' && // 按键是 Alt
!input.ctrl && // 未按 Ctrl
!input.shift && // 未按 Shift
!input.meta && // 未按 MetaWin/Mac
!input.isComposing && // 非输入法组合态
input.type === 'keyDown' // 仅拦截按下事件
) {
event.preventDefault(); // 阻止 Alt 键的默认行为(激活菜单)
}
});
let ymlBatPath = process.env.NODE_ENV === 'development' ? path.resolve(app.getAppPath(), 'resources', 'java', 'app', 'application.yml') : path.join(process.resourcesPath, 'app.asar.unpacked', 'resources', 'java', 'app', 'application.yml')
ymlBatPath = process.platform === 'win32' ? ymlBatPath.replace(/^(\w:)/, '/$1') : ymlBatPath
let ymlPath = ymlBatPath.substring(1, 200)
const ymlContent = yaml.load(fs.readFileSync(ymlPath, 'utf8'));
ymlContent.server.path = path.dirname(store.path)
fs.writeFileSync(ymlPath, yaml.dump(ymlContent));
ipcMain.on("getIniConfig", (event, option) => {
let ymlPath = ymlBatPath.substring(1, 200)
console.log("iniPath", ymlPath);
const ymlContent = yaml.load(fs.readFileSync(ymlPath, 'utf8'));
if (option) {
ymlContent.server.port = option.port
fs.writeFileSync(ymlPath, yaml.dump(ymlContent));
}
event.sender.send("YmlConfig", ymlContent);
});
ipcMain.on("restart", () => {
// app.relaunch();
// app.quit();
// cleanupProcess.kill();
// app.relaunch();
isRestart = true
closeAllWindows()
});
// 监听启动页完成的消息
ipcMain.on('splash-completed', () => {
// 启动页进度条已完成,可以关闭启动页并显示主窗口
if (isSeverInit) {
mainWindow.webContents.send('program-init', severError)
}
isAppInit = true
setTimeout(() => {
splashWindow.destroy()
mainWindow.maximize() // 先最大化
mainWindow.show()
setTimeout(() => {
mainWindow.webContents.send('start-login-video')
}, 200) // 给一点时间让窗口完全显示
}, 1000)
})
ipcMain.on('quit-app', () => {
windowAllClosed()
})
ipcMain.on('renderNode', () => {
// 获取所有窗口并转发消息
BrowserWindow.getAllWindows().forEach((win) => {
// 移除条件判断,确保所有窗口都能收到消息
setTimeout(() => {
win.webContents.send('renderNode-reply')
}, 200)
})
})
ipcMain.on("open-directory-dialog", (event, option) => {
// @ts-ignore
dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), {
properties: option.properties,
filters: option.filters,
})
.then((files) => {
let arr = [];
if (!files.canceled) {
files.filePaths.forEach((url) => {
// @ts-ignore
arr.push(url.replace(/\\/g, "/"));
});
}
event.sender.send("selectedItem", arr);
});
});
ipcMain.on("saveFile", (event, { title, filename, filters }) => {
dialog
.showSaveDialog({
title,
defaultPath: filename,
filters,
})
.then((files) => {
let path = "";
if (!files.canceled) {
path = files.filePath.replace(/\\/g, "/");
}
event.sender.send("selectedFileItem", path);
});
});
ipcMain.on("saveNetFile", (event, { title, filename, filters, url }) => {
dialog
.showSaveDialog({
title,
defaultPath: filename,
filters,
})
.then((files) => {
let path = "";
if (!files.canceled) {
path = files.filePath.replace(/\\/g, "/");
function callBack(key) {
event.sender.send("saveNetFileRes", key);
}
function downloadFile(url, path) {
/*request(
url,
{ headers: { Accept: "application/octet-stream" } },
(err, res, body) => {
if (!err && res.statusCode === 200) {
const filePath = path;
fs.writeFileSync(filePath, body);
console.log(url);
console.log(`文件已保存到: ${filePath}`);
} else {
console.error("下载文件失败:", err);
}
}
);*/
http.get(url, (response) => {
// let contentLength = parseInt(
// response.headers["content-length"]
// );
// let downloadedLength = 0;
response.pipe(fs.createWriteStream(path));
response.on("end", () => {
callBack("success");
// Message.success('下载成功')
// dialog.showMessageBox(null,{type:'info',message:"下载完成"})
});
})
.on("error", (err) => {
console.log(err, "完成");
callBack("error");
});
}
downloadFile(url, path);
}
/* filePaths = path;
webContents.downloadURL(url);*/
//
});
});
ipcMain.handle('getIsFullScreen', () => {
return mainWindow.isFullScreen()
});
ipcMain.on('toggle-fullscreen', (event, flag = null) => {
const win = BrowserWindow.fromWebContents(event.sender);
let full = !win!.isFullScreen()
if (flag != null) {
full = flag
}
win!.setFullScreen(full);
});
mainWindow.on('enter-full-screen', () => {
mainWindow.webContents.send('fullscreen-status-changed', true)
});
mainWindow.on('leave-full-screen', () => {
mainWindow.webContents.send('fullscreen-status-changed', false)
});
mainWindow.on("close", (e) => {
if (isRestart) {
return
}
e.preventDefault();
dialog
.showMessageBox(mainWindow, {
type: "warning",
title: "提示",
message: "是否确认退出系统?",
buttons: ["cancel", "ok"],
})
.then((res) => {
if (res.response) {
setTimeout(() => {
mainWindow.destroy()
mainWindow = null;
}, 200);
windowAllClosed();
}
});
});
let recorder;
ipcMain.on("startRecoder", () => {
console.log("开始录制");
recorder = new Recorder();
recorder.start();
});
ipcMain.on("endRecoder", () => {
console.log("结束录制");
// 判断是否存在recorder是否有recorder.end方法
if (!recorder) {
console.log("recorder不存在");
return;
}
recorder.end(() => {
let path = dialog.showSaveDialogSync({
title: "保存视频文件",
defaultPath: dayjs().format("YYYYMMDDHHmmss") + "视频录制.mp4",
filters: [{ name: "文件类型", extensions: ["mp4"] }],
});
if (path != undefined) {
recorder.move(path, () => {
recorder = null;
});
} else {
recorder = null;
}
});
});
ipcMain.on("requireGEMarkerName", (event, obj) => {
/*
* obj= {
dirName: "GEMarker",
dirName1s: "GEMarker1s"
}
* */
// console.log('GetHomeDir()', GetHomeDir())
let prefix =
process.env.NODE_ENV === "development"
? "src/renderer/public"
: "resources/app.asar/out/renderer";
let data = {};
for (const objKey in obj) {
let files = fs.readdirSync(
path.join(
GetHomeDir(),
prefix,
obj[objKey]
)
);
// console.log(files);
for (let i = 0; i < files.length; i++) {
files[i] = obj[objKey] + "/" + files[i];
}
data[obj[objKey]] = files;
}
// let files = fs.readdirSync(path.join(global.__static ? global.__static : GetHomeDir() + "/static", dirName))
// data[dirName] = files
event.sender.send("dirFiles", data);
});
let _winMap = new Map();
// 监听渲染进程创建新窗口的请求
function createTempSession() {
// 生成唯一会话名称(避免冲突)
const sessionName = `temp-session-${Date.now()}-${Math.random().toString(36).slice(2)}`;
// 创建独立会话(基于默认分区,但命名唯一)
const tempSession = session.fromPartition(sessionName, {
cache: true, // 允许缓存,但会话独立
persistent: false // 非持久化,关闭后自动清理
});
// 清空会话初始缓存(确保无残留)
tempSession.clearStorageData({
storages: [
'appcache', 'cookies', 'localstorage', 'sessionstorage',
'indexdb', 'cachestorage', 'websql'
]
}).catch(err => console.error('清空临时会话缓存失败:', err));
return tempSession;
}
// @ts-ignore
ipcMain.handle('create-new-window', async (event, params, url, option, id) => {
// try {
// console.log('create-new-window', params, url, option, id)
// const newWindow = await new BrowserWindow(params)
// if (url) {
// await newWindow.loadURL(url)
// await newWindow.webContents.send("data", option)
// newWindow.on('closed', () => {
// _winMap.delete(id);
// // a.delete(newWindow.id)
// // closeCB(newWindow.id)
// });
// }
// _winMap.set(id, newWindow.id);
// return _winMap
// } catch (error) {
// console.error('创建窗口失败:', error);
// throw error; // 抛出错误以便渲染进程捕获
// }
try {
// 1. 创建独立临时会话
const tempSession = createTempSession();
try {
if (option && option.Authorization) {
tempSession.webRequest.onBeforeSendHeaders((details, callback) => {
const headers = Object.assign({}, details.requestHeaders);
headers['Authorization'] = option.Authorization;
callback({ requestHeaders: headers });
});
}
} catch (e) {
console.error('注入认证头失败:', e);
}
// 2. 合并窗口配置:注入独立会话
const windowConfig = {
...params,
webPreferences: {
...params.webPreferences,
session: tempSession, // 关键:使用独立会话
nodeIntegration: true,
contextIsolation: false,
devTools: true,
webSecurity: false,
allowRunningInsecureContent: true
}
};
// 3. 创建新窗口
const newWindow = new BrowserWindow(windowConfig);
// 4. 加载URL并发送初始化数据
if (url) {
await newWindow.loadURL(url);
await newWindow.webContents.send("data", option);
}
// 5. 窗口关闭时清理会话和映射
newWindow.on('closed', async () => {
_winMap.delete(id);
// 清空会话缓存并销毁
await tempSession.clearStorageData({
storages: ['all'] // 清空所有存储
});
// 解除会话引用触发GC
tempSession.clearCache();
session.removePartition(tempSession.getPartition());
});
// 6. 记录窗口映射
newWindow.webContents.on('before-input-event', (event, input) => {
// 条件:仅按了 Alt 键(无其他键组合、不是组合键、不是重复按键)
if (
input.key === 'Alt' && // 按键是 Alt
!input.control && // 未按 Ctrl
!input.shift && // 未按 Shift
!input.meta && // 未按 MetaWin/Mac
!input.isComposing && // 非输入法组合态
input.type === 'keyDown' // 仅拦截按下事件
) {
event.preventDefault(); // 阻止 Alt 键的默认行为(激活菜单)
}
});
_winMap.set(id, newWindow.id);
return _winMap;
} catch (error) {
console.error('创建窗口失败:', error);
throw error; // 抛出错误以便渲染进程捕获
}
})
//@ts-ignore
ipcMain.handle('show-window-by-id', async (event, id) => {
BrowserWindow.fromId(id)?.show()
})
ipcMain.handle('get-_winMap', () => {
return _winMap
})
ipcMain.on("openFFPlay", (e, obj) => {
let cmd = "";
let platform = os.platform();
if (platform === "win32") {
cmd = "ffplay.exe";
} else {
cmd = "ffplay";
}
let title = obj.name;
let child = spawn(
path.join(GetHomeDir(), `/ffplay/${cmd}`),
[
"-window_title",
title,
"-x",
"1300",
"-y",
"730",
obj.url,
],
{
cwd: path.join(GetHomeDir(), "/ffplay/"),
stdio: "ignore",
// shell: true,
}
).on("exit", (err) => {
console.log("out");
console.log(err);
e.sender.send("openFFPlayOut", err);
if (obj.id) {
e.sender.send("openFFPlayOut_" + obj.id, err, obj.id);
}
});
/* .on("stdout", function(err, m) {
console.log(m);
});*/
console.log("child", child.pid);
child.unref();
});
// 设置窗口标题和图标
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// 注册 F5 快捷键刷新
globalShortcut.register('CommandOrControl+F5', () => {
if (mainWindow) {
mainWindow.reload()
setTimeout(() => {
mainWindow.webContents.send('start-login-video')
}, 200) // 给一点时间让窗口完成刷新
}
})
globalShortcut.register('CommandOrControl+F12', () => {
mainWindow.webContents.openDevTools()
})
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
}
// 用于跟踪是否正在执行退出流程
let isQuitting = false;
function windowAllClosed() {
// 防止重复触发退出流程
if (isQuitting) return;
isQuitting = true;
// macOS特殊处理保持应用活跃
if (process.platform === 'darwin') {
return;
}
console.log('所有窗口已关闭,执行清理脚本...');
getServer().close(() => {
// 执行批处理文件
const cleanupProcess = spawn('cmd.exe', ['/c', stopBatPath.substring(1, 200)], {
stdio: 'ignore',
windowsHide: true,
});
cleanupProcess.on('exit', (code) => {
if (code === 0) {
console.log(code)
} else {
console.log(`脚本执行失败,退出码:${code}`);
}
});
cleanupProcess.on('error', (error) => {
console.log('error', error)
});
// const cleanupProcess = exec(stopBatPath.substring(1, 200), (error, stdout, stderr) => {
// if (error) {
// console.error(`清理脚本执行失败: ${error.message}`);
// }
// if (stderr) {
// console.error(`清理脚本错误输出: ${stderr}`);
// }
// if (stdout) {
// console.log(`清理脚本输出: ${stdout}`);
// }
// if (isQuitting) {
// forceQuit();
// }
// });
cleanupProcess.unref();
setTimeout(() => {
forceQuit();
}, 500);
})
}
// 强制退出应用的辅助函数
function forceQuit() {
// 终止所有剩余的子进程
if (process.platform === 'win32') {
// Windows系统特殊处理
// if (isRestart) {
// isRestart = false
// app.relaunch();
// }
// let child = exec('taskkill /F /T /PID ' + process.pid, (error) => {
// if (error) console.error('强制终止失败:', error);
// child.kill();
// });
if (isRestart) {
app.relaunch();
}
console.log('------退出-------');
setTimeout(() => {
app.exit();
app.quit();
}, 500);
} else {
// 其他系统
process.exit(0);
}
}
function closeAllWindows() {
// 1. 获取所有已打开的窗口
const allWindows = BrowserWindow.getAllWindows();
// 2. 遍历关闭每个窗口
allWindows.forEach(window => {
if (!window.isDestroyed()) { // 避免操作已销毁的窗口(防止报错)
window.close();
}
});
}
function getmainWindow() {
return mainWindow;
}
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on("ready", createWindow);
app.whenReady().then(() => {
// alert(devSplashURL)
// alert(prodSplashURL)
// dialog.showMessageBox({
// type: 'info',
// title: '信息1',
// message: devSplashURL,
// buttons: ['确定']
// })
// 执行批处理文件
// exec(startBatPath.substring(1, 200), (error, stdout, stderr) => {
// if (error) {
// console.error(`执行错误: ${error.message}`);
// return;
// }
// if (stderr) {
// console.error(`错误输出: ${stderr}`);
// return;
// }
// console.log(`批处理输出: ${stdout}`);
// });
const batProcess = spawn('cmd.exe', ['/c', startBatPath.substring(1, 200)]);
// 实时监听标准输出stdout
batProcess.stdout.on('data', (data) => {
// data 是 Buffer 类型,转为字符串后输出
let string = data.toString().trim()
// console.log(`批处理输出: ${string}`);
// 临时处理:应用启动失败或项目文档地址出现时,认为服务初始化完成;后续需后端配合
if (string.indexOf('APPLICATION FAILED TO START') !== -1) {
severError = '后端服务未正常启动,尝试更换地址或端口'
if (!isSeverInit) {
isSeverInit = true
if (isAppInit) {
mainWindow.webContents.send('program-init', severError)
}
}
}
else if (string.indexOf('项目文档地址') !== -1) {
if (!isSeverInit) {
isSeverInit = true
if (isAppInit) {
mainWindow.webContents.send('program-init', false)
}
}
}
});
ipcMain.on('judgment-isSeverInit', (event) => {
event.returnValue = {
isSeverInit: isSeverInit,
severError: severError
}
})
// 监听错误输出stderr
batProcess.stderr.on('data', () => {
// console.error(`错误输出: ${data.toString().trim()}`);
});
// 监听进程执行出错
batProcess.on('error', () => {
// console.error(`执行错误: ${error.message}`);
});
// 监听进程退出事件(执行结束后触发)
batProcess.on('close', (code) => {
console.log(`批处理执行完毕,退出码: ${code}`);
});
// Set app user model id for windows
electronApp.setAppUserModelId('com.electron')
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
// IPC test
ipcMain.on('ping', () => console.log('pong'))
})
// 退出时注销所有快捷键
app.on('will-quit', () => {
globalShortcut.unregisterAll()
})
app.on('window-all-closed', () => {
windowAllClosed()
});
app.on("second-instance", () => {
// 当运行第二个实例时,将会聚焦到myWindow这个窗口
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
// testNapi()
global.sharedObject = {
hasService: false,
};
}
export { getmainWindow };
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.