fix: 重构新建项目逻辑,选定文件夹即项目

- 移除 prompt() 及 InputDialog 组件依赖
- 选定文件夹直接创建 .web2app 项目结构
- 默认以文件夹名作为项目名称
- 在编辑器中可自由修改名称、版本、图标、入口等配置
- 增强导入资源入口的文案提示
This commit is contained in:
gmh01
2026-07-23 20:40:45 +08:00
parent 686d94e178
commit 8dc102a7cb
8 changed files with 34 additions and 42 deletions

2
.gitignore vendored
View File

@@ -2,6 +2,8 @@ node_modules/
dist/ dist/
.web2app/ .web2app/
release/ release/
installer/
__appImage-x64/
# Binaries & executables # Binaries & executables
*.exe *.exe

View File

@@ -111,8 +111,8 @@ ipcMain.handle('dir:readTree', async (_event, dirPath) => {
return readDirectoryTree(dirPath) return readDirectoryTree(dirPath)
}) })
ipcMain.handle('project:create', async (_event, name, dir) => { ipcMain.handle('project:create', async (_event, projectPath) => {
return createProject(name, dir) return createProject(projectPath)
}) })
ipcMain.handle('project:load', async (_event, projectPath) => { ipcMain.handle('project:load', async (_event, projectPath) => {

View File

@@ -13,7 +13,7 @@ contextBridge.exposeInMainWorld('api', {
readTree: (dirPath) => ipcRenderer.invoke('dir:readTree', dirPath) readTree: (dirPath) => ipcRenderer.invoke('dir:readTree', dirPath)
}, },
project: { project: {
create: (name, dir) => ipcRenderer.invoke('project:create', name, dir), create: (projectPath) => ipcRenderer.invoke('project:create', projectPath),
load: (projectPath) => ipcRenderer.invoke('project:load', projectPath), load: (projectPath) => ipcRenderer.invoke('project:load', projectPath),
save: (projectPath, data) => ipcRenderer.invoke('project:save', projectPath, data), save: (projectPath, data) => ipcRenderer.invoke('project:save', projectPath, data),
importFiles: (projectPath, files) => ipcRenderer.invoke('project:importFiles', projectPath, files), importFiles: (projectPath, files) => ipcRenderer.invoke('project:importFiles', projectPath, files),

View File

@@ -21,16 +21,21 @@ function getDistDir(projectPath) {
return dir return dir
} }
function createProject(name, dir) { function createProject(projectPath) {
const projectPath = path.join(dir, name) if (!fs.existsSync(projectPath)) {
if (fs.existsSync(projectPath)) { fs.mkdirSync(projectPath, { recursive: true })
throw new Error(`目录 ${projectPath} 已存在`)
} }
const configPath = getConfigPath(projectPath)
if (fs.existsSync(configPath)) {
throw new Error(`该目录已是 Web2App 项目`)
}
fs.mkdirSync(path.join(projectPath, '.web2app', 'src'), { recursive: true }) fs.mkdirSync(path.join(projectPath, '.web2app', 'src'), { recursive: true })
fs.mkdirSync(path.join(projectPath, '.web2app', 'dist'), { recursive: true }) fs.mkdirSync(path.join(projectPath, '.web2app', 'dist'), { recursive: true })
const config = { const config = {
name, name: path.basename(projectPath),
version: '1.0.0', version: '1.0.0',
description: '', description: '',
author: '', author: '',
@@ -47,7 +52,7 @@ function createProject(name, dir) {
updatedAt: new Date().toISOString() updatedAt: new Date().toISOString()
} }
fs.writeFileSync(getConfigPath(projectPath), JSON.stringify(config, null, 2)) fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
return { projectPath, config } return { projectPath, config }
} }

View File

@@ -21,7 +21,7 @@
"appId": "com.web2app.app", "appId": "com.web2app.app",
"productName": "Web2App", "productName": "Web2App",
"directories": { "directories": {
"output": "release" "output": "installer"
}, },
"files": [ "files": [
"electron/**/*", "electron/**/*",

View File

@@ -28,8 +28,8 @@ export const useProjectStore = defineStore('project', {
}, },
actions: { actions: {
async createProject(name, dir) { async createProject(projectPath) {
const result = await window.api.project.create(name, dir) const result = await window.api.project.create(projectPath)
this.projectPath = result.projectPath this.projectPath = result.projectPath
this.config = result.config this.config = result.config
this.files = [] this.files = []

View File

@@ -12,13 +12,13 @@
<div class="action-card" @click="createNewProject"> <div class="action-card" @click="createNewProject">
<span class="action-icon">+</span> <span class="action-icon">+</span>
<span class="action-title">新建项目</span> <span class="action-title">新建项目</span>
<span class="action-desc">创建新的 Web2App 项目</span> <span class="action-desc">选择文件夹快速创建项目</span>
</div> </div>
<div class="action-card" @click="openExistingProject"> <div class="action-card" @click="openExistingProject">
<span class="action-icon">&#9776;</span> <span class="action-icon">&#9776;</span>
<span class="action-title">打开项目</span> <span class="action-title">打开项目</span>
<span class="action-desc">打开已有的 .web2app 项目</span> <span class="action-desc">打开已有的 Web2App 项目</span>
</div> </div>
<div class="action-card" @click="goHelp"> <div class="action-card" @click="goHelp">
@@ -42,15 +42,6 @@
</div> </div>
</div> </div>
</div> </div>
<InputDialog
:visible="showInput"
title="新建项目"
message="请输入项目名称"
placeholder="例如MyApp"
@confirm="onNameConfirmed"
@cancel="onNameCancelled"
/>
</div> </div>
</template> </template>
@@ -58,26 +49,17 @@
import { ref } from 'vue' import { ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useProjectStore } from '../stores/project' import { useProjectStore } from '../stores/project'
import InputDialog from '../components/InputDialog.vue'
const router = useRouter() const router = useRouter()
const store = useProjectStore() const store = useProjectStore()
const recentProjects = ref(loadRecentProjects()) const recentProjects = ref(loadRecentProjects())
const showInput = ref(false)
const pendingDir = ref('')
async function createNewProject() { async function createNewProject() {
const dir = await window.api.dialog.selectDirectory() const dir = await window.api.dialog.selectDirectory()
if (!dir) return if (!dir) return
pendingDir.value = dir
showInput.value = true
}
async function onNameConfirmed(name) {
showInput.value = false
try { try {
await store.createProject(name, pendingDir.value) await store.createProject(dir)
addRecentProject(pendingDir.value, name) addRecentProject(dir, store.config.name)
router.push('/editor') router.push('/editor')
} catch (err) { } catch (err) {
await window.api.dialog.showMessage({ await window.api.dialog.showMessage({
@@ -88,10 +70,6 @@ async function onNameConfirmed(name) {
} }
} }
function onNameCancelled() {
showInput.value = false
}
async function openExistingProject() { async function openExistingProject() {
const dir = await window.api.dialog.selectDirectory() const dir = await window.api.dialog.selectDirectory()
if (!dir) return if (!dir) return

View File

@@ -21,8 +21,8 @@
<div class="sidebar-header"> <div class="sidebar-header">
<h3>资源文件</h3> <h3>资源文件</h3>
<div class="sidebar-actions"> <div class="sidebar-actions">
<button class="btn btn-sm btn-secondary" @click="importFromDir" title="导入文件夹">&#128193;</button> <button class="btn btn-sm btn-secondary" @click="importFromDir" title="文件夹导入整个网站">&#128193; 路径</button>
<button class="btn btn-sm btn-secondary" @click="importFromFiles" title="导入文件">&#128196;</button> <button class="btn btn-sm btn-secondary" @click="importFromFiles" title="选择单个文件导入">&#128196; 文件</button>
</div> </div>
</div> </div>
<div class="file-tree-container"> <div class="file-tree-container">
@@ -32,8 +32,9 @@
@delete="handleDelete" @delete="handleDelete"
/> />
<div v-else class="empty-state"> <div v-else class="empty-state">
<p>暂无资源</p> <div class="empty-icon">&#128193;</div>
<p class="hint">点击上方按钮导入网站文件</p> <p>尚未设置资源路径</p>
<p class="hint">点击上方路径按钮选择你的网站目录导入</p>
</div> </div>
</div> </div>
</aside> </aside>
@@ -186,6 +187,12 @@ onBeforeUnmount(() => {
padding: 8px; padding: 8px;
} }
.empty-icon {
font-size: 48px;
opacity: 0.3;
margin-bottom: 8px;
}
.hint { .hint {
font-size: 12px; font-size: 12px;
} }