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.