main.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. const { app, BrowserWindow ,ipcMain} = require('electron')
  2. const path = require('path')
  3. const http = require('http')
  4. const os = require('os')
  5. const fs = require('fs')
  6. const config = require('../configs/config.js')
  7. const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged
  8. // 修复缓存权限问题:设置用户数据目录到有权限的位置
  9. // 必须在 app.whenReady() 之前调用
  10. if (process.platform === 'win32') {
  11. try {
  12. // 设置缓存目录到用户临时目录,避免权限问题
  13. const userDataPath = path.join(os.tmpdir(), 'electron-react-vite-app')
  14. // 确保目录存在
  15. if (!fs.existsSync(userDataPath)) {
  16. fs.mkdirSync(userDataPath, { recursive: true })
  17. }
  18. // 创建缓存子目录
  19. const cacheDir = path.join(userDataPath, 'cache')
  20. const gpuCacheDir = path.join(userDataPath, 'gpu-cache')
  21. if (!fs.existsSync(cacheDir)) {
  22. fs.mkdirSync(cacheDir, { recursive: true })
  23. }
  24. if (!fs.existsSync(gpuCacheDir)) {
  25. fs.mkdirSync(gpuCacheDir, { recursive: true })
  26. }
  27. // 设置用户数据路径(必须在 app.whenReady() 之前)
  28. app.setPath('userData', userDataPath)
  29. // 设置缓存目录到有权限的位置
  30. app.commandLine.appendSwitch('disk-cache-dir', cacheDir)
  31. app.commandLine.appendSwitch('gpu-disk-cache-dir', gpuCacheDir)
  32. console.log(`[OK] Cache directories set to: ${userDataPath}`)
  33. } catch (error) {
  34. console.warn('[WARN] Failed to set cache directories:', error.message)
  35. // 如果设置失败,尝试禁用 GPU 缓存作为备选方案
  36. app.commandLine.appendSwitch('disable-gpu-sandbox')
  37. }
  38. }
  39. /**
  40. * 检测 Vite 开发服务器实际使用的端口
  41. * 如果配置的端口被占用,Vite 会自动尝试下一个端口
  42. * 需要确认是 Vite 服务器,而不仅仅是端口响应
  43. */
  44. async function findVitePort(startPort, maxAttempts = 10) {
  45. const viteHost = config.vite?.host || 'localhost'
  46. for (let offset = 0; offset < maxAttempts; offset++) {
  47. const port = startPort + offset
  48. const isViteServer = await new Promise((resolve) => {
  49. const req = http.get(`http://${viteHost}:${port}`, (res) => {
  50. // 检查状态码
  51. if (res.statusCode !== 200) {
  52. resolve(false)
  53. return
  54. }
  55. // 读取响应数据确认是否是 Vite 服务器
  56. let data = ''
  57. let resolved = false
  58. res.on('data', (chunk) => {
  59. if (resolved) return
  60. data += chunk.toString()
  61. // 如果响应包含 Vite 特征,立即确认
  62. if (data.length > 100 && (data.includes('/vite') || data.includes('Vite') || data.includes('vite/client'))) {
  63. resolved = true
  64. resolve(true)
  65. }
  66. })
  67. res.on('end', () => {
  68. if (resolved) return
  69. // 检查响应内容是否包含 Vite 特征
  70. if (data.includes('/vite') || data.includes('Vite') || data.includes('vite/client')) {
  71. resolve(true)
  72. } else {
  73. resolve(false)
  74. }
  75. })
  76. })
  77. req.on('error', () => {
  78. resolve(false)
  79. })
  80. req.setTimeout(2000, () => {
  81. req.destroy()
  82. resolve(false)
  83. })
  84. })
  85. if (isViteServer) {
  86. return port
  87. }
  88. }
  89. // 如果找不到,返回配置的端口
  90. return startPort
  91. }
  92. async function createWindow() {
  93. const mainWindow = new BrowserWindow({
  94. width: config.window.width,
  95. height: config.window.height,
  96. autoHideMenuBar: config.window.autoHideMenuBar, // 从配置文件读取
  97. webPreferences: {
  98. nodeIntegration: false,
  99. contextIsolation: true,
  100. preload: path.join(__dirname, 'preload.js')
  101. }
  102. })
  103. if (isDev) {
  104. // 从配置文件读取 Vite 开发服务器端口
  105. const configPort = config.vite?.port || 5173
  106. // 检测实际使用的端口(如果配置端口被占用,Vite 会自动尝试下一个)
  107. const vitePort = await findVitePort(configPort)
  108. const viteHost = config.vite?.host || 'localhost'
  109. console.log(`Loading Vite dev server at http://${viteHost}:${vitePort}`)
  110. mainWindow.loadURL(`http://${viteHost}:${vitePort}`)
  111. // 根据配置文件决定是否打开调试侧边栏
  112. if (config.devTools.enabled) {
  113. mainWindow.webContents.openDevTools()
  114. }
  115. } else {
  116. mainWindow.loadFile(path.join(__dirname, '../dist/index.html'))
  117. }
  118. }
  119. const { spawn } = require('child_process')
  120. // Execute Node.js script
  121. ipcMain.handle('run-nodejs-script', async (event, scriptName, ...parameters) => {
  122. return new Promise((resolve, reject) => {
  123. const scriptPath = path.join(__dirname, '../nodejs', `${scriptName}.js`)
  124. const nodeProcess = spawn('node', [scriptPath, ...parameters])
  125. let stdout = ''
  126. let stderr = ''
  127. nodeProcess.stdout.on('data', (data) => {
  128. stdout += data.toString()
  129. })
  130. nodeProcess.stderr.on('data', (data) => {
  131. stderr += data.toString()
  132. })
  133. nodeProcess.on('close', (code) => {
  134. resolve({
  135. success: code === 0,
  136. stdout: stdout.trim(),
  137. stderr: stderr.trim(),
  138. exitCode: code
  139. })
  140. })
  141. nodeProcess.on('error', (error) => {
  142. reject(error)
  143. })
  144. })
  145. })
  146. // Execute Python script
  147. ipcMain.handle('run-python-script', async (event, scriptName, ...parameters) => {
  148. return new Promise((resolve, reject) => {
  149. let pythonPath = 'python'
  150. if (config.pythonPath?.path) {
  151. const configPythonPath = path.join(config.pythonPath.path, 'python.exe')
  152. if (fs.existsSync(configPythonPath)) {
  153. pythonPath = configPythonPath
  154. }
  155. }
  156. const scriptPath = path.join(__dirname, '../python/scripts', `${scriptName}.py`)
  157. if (!fs.existsSync(scriptPath)) {
  158. reject({
  159. success: false,
  160. error: `Script file not found: ${scriptPath}`,
  161. stderr: `Script file not found: ${scriptPath}`
  162. })
  163. return
  164. }
  165. const pythonProcess = spawn(pythonPath, [scriptPath, ...parameters])
  166. let stdout = ''
  167. let stderr = ''
  168. pythonProcess.stdout.on('data', (data) => {
  169. stdout += data.toString()
  170. })
  171. pythonProcess.stderr.on('data', (data) => {
  172. stderr += data.toString()
  173. })
  174. pythonProcess.on('close', (code) => {
  175. resolve({
  176. success: code === 0,
  177. stdout: stdout.trim(),
  178. stderr: stderr.trim(),
  179. exitCode: code
  180. })
  181. })
  182. pythonProcess.on('error', (error) => {
  183. reject({
  184. success: false,
  185. error: error.message,
  186. stderr: error.message
  187. })
  188. })
  189. })
  190. })
  191. app.whenReady().then(() => {
  192. createWindow()
  193. app.on('activate', () => {
  194. if (BrowserWindow.getAllWindows().length === 0) {
  195. createWindow()
  196. }
  197. })
  198. })
  199. app.on('window-all-closed', () => {
  200. if (process.platform !== 'darwin') {
  201. app.quit()
  202. }
  203. })