feat: 完成 Web2App 核心功能实现
- Electron 主进程:窗口管理、IPC、项目读写、打包引擎 - Vue 3 前端:4 个视图页面、3 个组件、Pinia 状态管理 - 生成应用模板:Express 后端 + Electron 窗口 - 支持资源导入、应用配置、跨平台打包
This commit is contained in:
163
electron/main.js
Normal file
163
electron/main.js
Normal file
@@ -0,0 +1,163 @@
|
||||
const { app, BrowserWindow, ipcMain, dialog } = require('electron')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const { createProject, loadProject, saveProject, importFiles, removeFile, setProjectIcon } = require('./project')
|
||||
const { startPack, cancelPack } = require('./builder')
|
||||
|
||||
let mainWindow
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
minWidth: 900,
|
||||
minHeight: 600,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false
|
||||
},
|
||||
title: 'Web2App',
|
||||
show: false
|
||||
})
|
||||
|
||||
if (process.env.NODE_ENV === 'development' || process.argv.includes('--dev')) {
|
||||
mainWindow.loadURL('http://localhost:5173')
|
||||
mainWindow.webContents.openDevTools()
|
||||
} else {
|
||||
mainWindow.loadFile(path.join(__dirname, '..', 'dist', 'index.html'))
|
||||
}
|
||||
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
mainWindow.show()
|
||||
})
|
||||
}
|
||||
|
||||
ipcMain.handle('dialog:selectDirectory', async () => {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openDirectory']
|
||||
})
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
return result.filePaths[0]
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
ipcMain.handle('dialog:selectFile', async (_event, options) => {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openFile', 'multiSelections'],
|
||||
filters: options?.filters || [
|
||||
{ name: '所有文件', extensions: ['*'] }
|
||||
]
|
||||
})
|
||||
if (!result.canceled) {
|
||||
return result.filePaths
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
ipcMain.handle('dialog:selectIcon', async () => {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: '图标文件', extensions: ['ico', 'png', 'icns'] }
|
||||
]
|
||||
})
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
return result.filePaths[0]
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
ipcMain.handle('dialog:saveFile', async (_event, options) => {
|
||||
const result = await dialog.showSaveDialog(mainWindow, {
|
||||
defaultPath: options?.defaultPath,
|
||||
filters: options?.filters || [{ name: '安装包', extensions: ['exe', 'dmg', 'AppImage'] }]
|
||||
})
|
||||
if (!result.canceled && result.filePath) {
|
||||
return result.filePath
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
ipcMain.handle('dir:readTree', async (_event, dirPath) => {
|
||||
return readDirectoryTree(dirPath)
|
||||
})
|
||||
|
||||
ipcMain.handle('project:create', async (_event, name, dir) => {
|
||||
return createProject(name, dir)
|
||||
})
|
||||
|
||||
ipcMain.handle('project:load', async (_event, projectPath) => {
|
||||
return loadProject(projectPath)
|
||||
})
|
||||
|
||||
ipcMain.handle('project:save', async (_event, projectPath, data) => {
|
||||
return saveProject(projectPath, data)
|
||||
})
|
||||
|
||||
ipcMain.handle('project:importFiles', async (_event, projectPath, files) => {
|
||||
return importFiles(projectPath, files)
|
||||
})
|
||||
|
||||
ipcMain.handle('project:removeFile', async (_event, projectPath, relativePath) => {
|
||||
return removeFile(projectPath, relativePath)
|
||||
})
|
||||
|
||||
ipcMain.handle('project:setIcon', async (_event, projectPath, iconPath) => {
|
||||
return setProjectIcon(projectPath, iconPath)
|
||||
})
|
||||
|
||||
ipcMain.handle('pack:start', async (_event, projectPath, outputPath) => {
|
||||
const result = await startPack(projectPath, outputPath, (progress) => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('pack:progress', progress)
|
||||
}
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
ipcMain.handle('pack:cancel', () => {
|
||||
cancelPack()
|
||||
return true
|
||||
})
|
||||
|
||||
function readDirectoryTree(dirPath) {
|
||||
const items = fs.readdirSync(dirPath, { withFileTypes: true })
|
||||
const result = []
|
||||
for (const item of items) {
|
||||
if (item.name.startsWith('.')) continue
|
||||
const fullPath = path.join(dirPath, item.name)
|
||||
if (item.isDirectory()) {
|
||||
result.push({
|
||||
name: item.name,
|
||||
path: fullPath,
|
||||
type: 'directory',
|
||||
children: readDirectoryTree(fullPath)
|
||||
})
|
||||
} else {
|
||||
result.push({
|
||||
name: item.name,
|
||||
path: fullPath,
|
||||
type: 'file',
|
||||
size: fs.statSync(fullPath).size,
|
||||
ext: path.extname(item.name).toLowerCase()
|
||||
})
|
||||
}
|
||||
}
|
||||
return result.sort((a, b) => {
|
||||
if (a.type !== b.type) return a.type === 'directory' ? -1 : 1
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
Reference in New Issue
Block a user