fix: 重构新建项目逻辑,选定文件夹即项目
- 移除 prompt() 及 InputDialog 组件依赖 - 选定文件夹直接创建 .web2app 项目结构 - 默认以文件夹名作为项目名称 - 在编辑器中可自由修改名称、版本、图标、入口等配置 - 增强导入资源入口的文案提示
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,6 +2,8 @@ node_modules/
|
|||||||
dist/
|
dist/
|
||||||
.web2app/
|
.web2app/
|
||||||
release/
|
release/
|
||||||
|
installer/
|
||||||
|
__appImage-x64/
|
||||||
|
|
||||||
# Binaries & executables
|
# Binaries & executables
|
||||||
*.exe
|
*.exe
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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/**/*",
|
||||||
|
|||||||
@@ -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 = []
|
||||||
|
|||||||
@@ -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">☰</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">打开已有的 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
|
||||||
|
|||||||
@@ -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="导入文件夹">📁</button>
|
<button class="btn btn-sm btn-secondary" @click="importFromDir" title="从文件夹导入整个网站">📁 路径</button>
|
||||||
<button class="btn btn-sm btn-secondary" @click="importFromFiles" title="导入文件">📄</button>
|
<button class="btn btn-sm btn-secondary" @click="importFromFiles" title="选择单个文件导入">📄 文件</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">📁</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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user