- Electron 主进程:窗口管理、IPC、项目读写、打包引擎 - Vue 3 前端:4 个视图页面、3 个组件、Pinia 状态管理 - 生成应用模板:Express 后端 + Electron 窗口 - 支持资源导入、应用配置、跨平台打包
187 lines
5.0 KiB
JavaScript
187 lines
5.0 KiB
JavaScript
const fs = require('fs')
|
|
const path = require('path')
|
|
|
|
function getProjectDir(projectPath) {
|
|
return projectPath
|
|
}
|
|
|
|
function getSrcDir(projectPath) {
|
|
const dir = path.join(projectPath, '.web2app', 'src')
|
|
fs.mkdirSync(dir, { recursive: true })
|
|
return dir
|
|
}
|
|
|
|
function getConfigPath(projectPath) {
|
|
return path.join(projectPath, '.web2app', 'project.json')
|
|
}
|
|
|
|
function getDistDir(projectPath) {
|
|
const dir = path.join(projectPath, '.web2app', 'dist')
|
|
fs.mkdirSync(dir, { recursive: true })
|
|
return dir
|
|
}
|
|
|
|
function createProject(name, dir) {
|
|
const projectPath = path.join(dir, name)
|
|
if (fs.existsSync(projectPath)) {
|
|
throw new Error(`目录 ${projectPath} 已存在`)
|
|
}
|
|
fs.mkdirSync(path.join(projectPath, '.web2app', 'src'), { recursive: true })
|
|
fs.mkdirSync(path.join(projectPath, '.web2app', 'dist'), { recursive: true })
|
|
|
|
const config = {
|
|
name,
|
|
version: '1.0.0',
|
|
description: '',
|
|
author: '',
|
|
entry: 'index.html',
|
|
backendEntry: '',
|
|
windowWidth: 1200,
|
|
windowHeight: 800,
|
|
minWidth: 800,
|
|
minHeight: 600,
|
|
resizable: true,
|
|
icon: '',
|
|
copyright: '',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString()
|
|
}
|
|
|
|
fs.writeFileSync(getConfigPath(projectPath), JSON.stringify(config, null, 2))
|
|
return { projectPath, config }
|
|
}
|
|
|
|
function loadProject(projectPath) {
|
|
const configPath = getConfigPath(projectPath)
|
|
if (!fs.existsSync(configPath)) {
|
|
throw new Error('不是有效的 Web2App 项目')
|
|
}
|
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
|
|
|
const srcDir = getSrcDir(projectPath)
|
|
const files = readFileTree(srcDir)
|
|
|
|
return { projectPath, config, files }
|
|
}
|
|
|
|
function saveProject(projectPath, data) {
|
|
const configPath = getConfigPath(projectPath)
|
|
const existing = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
|
const updated = { ...existing, ...data, updatedAt: new Date().toISOString() }
|
|
fs.writeFileSync(configPath, JSON.stringify(updated, null, 2))
|
|
return updated
|
|
}
|
|
|
|
function importFiles(projectPath, files) {
|
|
const srcDir = getSrcDir(projectPath)
|
|
const imported = []
|
|
|
|
for (const filePath of files) {
|
|
const stat = fs.statSync(filePath)
|
|
if (stat.isDirectory()) {
|
|
const destDir = path.join(srcDir, path.basename(filePath))
|
|
copyDirSync(filePath, destDir)
|
|
imported.push({ source: filePath, dest: destDir })
|
|
} else {
|
|
const destPath = path.join(srcDir, path.basename(filePath))
|
|
fs.copyFileSync(filePath, destPath)
|
|
imported.push({ source: filePath, dest: destPath })
|
|
}
|
|
}
|
|
|
|
return readFileTree(srcDir)
|
|
}
|
|
|
|
function removeFile(projectPath, relativePath) {
|
|
const srcDir = getSrcDir(projectPath)
|
|
const fullPath = path.join(srcDir, relativePath)
|
|
|
|
if (!fs.existsSync(fullPath)) {
|
|
throw new Error(`文件不存在: ${relativePath}`)
|
|
}
|
|
|
|
const stat = fs.statSync(fullPath)
|
|
if (stat.isDirectory()) {
|
|
fs.rmSync(fullPath, { recursive: true, force: true })
|
|
} else {
|
|
fs.unlinkSync(fullPath)
|
|
}
|
|
|
|
return readFileTree(srcDir)
|
|
}
|
|
|
|
function setProjectIcon(projectPath, iconPath) {
|
|
const srcDir = getSrcDir(projectPath)
|
|
const ext = path.extname(iconPath)
|
|
const destName = `app-icon${ext}`
|
|
const destPath = path.join(srcDir, destName)
|
|
fs.copyFileSync(iconPath, destPath)
|
|
|
|
const config = JSON.parse(fs.readFileSync(getConfigPath(projectPath), 'utf-8'))
|
|
config.icon = destName
|
|
fs.writeFileSync(getConfigPath(projectPath), JSON.stringify(config, null, 2))
|
|
return config
|
|
}
|
|
|
|
function readFileTree(dirPath, basePath = '') {
|
|
const items = []
|
|
if (!fs.existsSync(dirPath)) return items
|
|
|
|
const entries = fs.readdirSync(dirPath, { withFileTypes: true })
|
|
for (const entry of entries) {
|
|
if (entry.name.startsWith('.')) continue
|
|
const relativePath = basePath ? path.join(basePath, entry.name) : entry.name
|
|
const fullPath = path.join(dirPath, entry.name)
|
|
if (entry.isDirectory()) {
|
|
items.push({
|
|
name: entry.name,
|
|
path: relativePath,
|
|
type: 'directory',
|
|
children: readFileTree(fullPath, relativePath)
|
|
})
|
|
} else {
|
|
items.push({
|
|
name: entry.name,
|
|
path: relativePath,
|
|
type: 'file',
|
|
size: fs.statSync(fullPath).size,
|
|
ext: path.extname(entry.name).toLowerCase()
|
|
})
|
|
}
|
|
}
|
|
|
|
items.sort((a, b) => {
|
|
if (a.type !== b.type) return a.type === 'directory' ? -1 : 1
|
|
return a.name.localeCompare(b.name)
|
|
})
|
|
return items
|
|
}
|
|
|
|
function copyDirSync(src, dest) {
|
|
fs.mkdirSync(dest, { recursive: true })
|
|
const entries = fs.readdirSync(src, { withFileTypes: true })
|
|
for (const entry of entries) {
|
|
if (entry.name.startsWith('.')) continue
|
|
const srcPath = path.join(src, entry.name)
|
|
const destPath = path.join(dest, entry.name)
|
|
if (entry.isDirectory()) {
|
|
copyDirSync(srcPath, destPath)
|
|
} else {
|
|
fs.copyFileSync(srcPath, destPath)
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
createProject,
|
|
loadProject,
|
|
saveProject,
|
|
importFiles,
|
|
removeFile,
|
|
setProjectIcon,
|
|
getSrcDir,
|
|
getDistDir,
|
|
getConfigPath,
|
|
getProjectDir
|
|
}
|