Files
electron-4/src/main/index.ts
2025-10-20 17:00:58 +08:00

589 lines
18 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.

import { app, shell, BrowserWindow, ipcMain, globalShortcut, dialog } 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");
// 开发环境路径处理 - 确保添加正确的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
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()
// 创建主窗口(保持原有配置,但先不显示)
const mainWindow = new BrowserWindow({
minWidth: 1780, // 添加最小宽度限制
minHeight: 920, // 添加最小高度限制
// 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
}
})
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
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', () => {
// 启动页进度条已完成,可以关闭启动页并显示主窗口
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)
});
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();
// 监听渲染进程创建新窗口的请求
// @ts-ignore
ipcMain.handle('create-new-window', async (event, params, url, option, id) => {
try {
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; // 抛出错误以便渲染进程捕获
}
})
//@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",
"-rtsp_transport",
"tcp",
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);
});
/* .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'))
}
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
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}`);
});
// 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'))
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
// app.on('window-all-closed', () => {
// if (process.platform !== 'darwin') {
// getServer().close(() => {
// // 关闭后台服务
// exec(stopBatPath.substring(1, 200), () => {
// app.quit()
// });
// })
// }
// })
// 退出时注销所有快捷键
app.on('will-quit', () => {
globalShortcut.unregisterAll()
})
// 用于跟踪是否正在执行退出流程
let isQuitting = false;
app.on('window-all-closed', () => {
windowAllClosed()
});
function windowAllClosed() {
// 防止重复触发退出流程
if (isQuitting) return;
isQuitting = true;
// macOS特殊处理保持应用活跃
if (process.platform === 'darwin') {
return;
}
console.log('所有窗口已关闭,执行清理脚本...');
getServer().close(() => {
// 执行批处理文件
const cleanupProcess = exec('D:/project/electron-4.0/electron-4/resources/java/stop.bat', (error, stdout, stderr) => {
if (error) {
console.error(`清理脚本执行失败: ${error.message}`);
}
if (stderr) {
console.error(`清理脚本错误输出: ${stderr}`);
}
if (stdout) {
console.log(`清理脚本输出: ${stdout}`);
}
if (isQuitting) {
forceQuit();
}
});
// 监听子进程退出事件(确保即使脚本出错也能退出)
cleanupProcess.on('exit', (code) => {
console.log(`清理脚本退出,代码: ${code}`);
});
// 超时保护:防止脚本卡住导致应用无法退出
setTimeout(() => {
if (isQuitting) {
console.log('清理脚本执行超时,强制退出');
cleanupProcess.kill(); // 终止卡住的脚本
forceQuit();
}
}, 3000); // 3秒超时
})
}
// 强制退出应用的辅助函数
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('------退出-------');
app.exit();
app.quit();
} else {
// 其他系统
process.exit(0);
}
}
function closeAllWindows() {
// 1. 获取所有已打开的窗口
const allWindows = BrowserWindow.getAllWindows();
// 2. 遍历关闭每个窗口
allWindows.forEach(window => {
if (!window.isDestroyed()) { // 避免操作已销毁的窗口(防止报错)
window.close();
}
});
}
console.log('=================================================')
global.sharedObject = {
hasService: false,
};
// 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.