fix: 修复新建项目无响应问题

- 替换 prompt() 为 InputDialog Vue 模态组件
- 替换 alert()/confirm() 为 Electron showMessageBox IPC
- 添加 dialog:showMessage / dialog:showConfirm IPC 通道
This commit is contained in:
gmh01
2026-07-23 19:44:04 +08:00
parent 95116e2338
commit 686d94e178
7 changed files with 180 additions and 14 deletions

View File

@@ -85,6 +85,28 @@ ipcMain.handle('dialog:saveFile', async (_event, options) => {
return null return null
}) })
ipcMain.handle('dialog:showMessage', async (_event, options) => {
const result = await dialog.showMessageBox(mainWindow, {
type: options?.type || 'info',
buttons: options?.buttons || ['确定'],
title: options?.title || 'Web2App',
message: options?.message || ''
})
return result.response
})
ipcMain.handle('dialog:showConfirm', async (_event, options) => {
const result = await dialog.showMessageBox(mainWindow, {
type: 'question',
buttons: ['取消', '确定'],
defaultId: 1,
cancelId: 0,
title: options?.title || 'Web2App',
message: options?.message || '确认?'
})
return result.response === 1
})
ipcMain.handle('dir:readTree', async (_event, dirPath) => { ipcMain.handle('dir:readTree', async (_event, dirPath) => {
return readDirectoryTree(dirPath) return readDirectoryTree(dirPath)
}) })

View File

@@ -5,7 +5,9 @@ contextBridge.exposeInMainWorld('api', {
selectDirectory: () => ipcRenderer.invoke('dialog:selectDirectory'), selectDirectory: () => ipcRenderer.invoke('dialog:selectDirectory'),
selectFile: (options) => ipcRenderer.invoke('dialog:selectFile', options), selectFile: (options) => ipcRenderer.invoke('dialog:selectFile', options),
selectIcon: () => ipcRenderer.invoke('dialog:selectIcon'), selectIcon: () => ipcRenderer.invoke('dialog:selectIcon'),
saveFile: (options) => ipcRenderer.invoke('dialog:saveFile', options) saveFile: (options) => ipcRenderer.invoke('dialog:saveFile', options),
showMessage: (options) => ipcRenderer.invoke('dialog:showMessage', options),
showConfirm: (options) => ipcRenderer.invoke('dialog:showConfirm', options)
}, },
dir: { dir: {
readTree: (dirPath) => ipcRenderer.invoke('dir:readTree', dirPath) readTree: (dirPath) => ipcRenderer.invoke('dir:readTree', dirPath)

View File

@@ -2,12 +2,19 @@
"name": "web2app", "name": "web2app",
"version": "1.0.0", "version": "1.0.0",
"description": "将网站转化为独立运行的桌面应用", "description": "将网站转化为独立运行的桌面应用",
"homepage": "https://github.com/web2app/web2app",
"author": {
"name": "Web2App Team",
"email": "team@web2app.dev"
},
"main": "electron/main.js", "main": "electron/main.js",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"electron:dev": "concurrently \"vite\" \"wait-on http://localhost:5173 && electron .\"", "electron:dev": "concurrently \"vite\" \"wait-on http://localhost:5173 && electron .\"",
"electron:build": "vite build && electron-builder --config", "electron:build": "vite build && electron-builder --config",
"electron:build:linux": "vite build && electron-builder --config --linux",
"electron:build:deb": "vite build && electron-builder --config --linux deb",
"preview": "vite preview" "preview": "vite preview"
}, },
"build": { "build": {
@@ -55,7 +62,8 @@
} }
], ],
"icon": null, "icon": null,
"category": "Utility" "category": "Utility",
"maintainer": "Web2App Team <team@web2app.dev>"
} }
}, },
"dependencies": { "dependencies": {

View File

@@ -344,23 +344,25 @@
<div class="platform-icon">🪟</div> <div class="platform-icon">🪟</div>
<h4>Windows</h4> <h4>Windows</h4>
<p class="version">v1.0.0 · x64</p> <p class="version">v1.0.0 · x64</p>
<a href="../release/win-unpacked/Web2App.exe" class="btn btn-primary" download> <a href="https://web2app.qmiweb.com/resource/Web2App%20Setup%201.0.0.exe" class="btn btn-primary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>
下载便携版 下载安装包
</a> </a>
<div style="margin-top:10px;font-size:13px;color:var(--text-muted);">可执行安装包请通过 <code>npm run electron:build</code> 构建</div> <div style="margin-top:10px;font-size:13px;color:var(--text-muted);">Windows 7+ / x64 · 含安装向导</div>
</div> </div>
<div class="platform-card"> <div class="platform-card">
<div class="platform-icon">🍎</div> <div class="platform-icon">🍎</div>
<h4>macOS</h4> <h4>macOS</h4>
<p class="version">v1.0.0 · Intel / Apple Silicon</p> <p class="version">v1.0.0 · Intel / Apple Silicon</p>
<span class="coming-soon">即将发布</span> <span class="coming-soon">即将发布</span>
<div style="margin-top:8px;font-size:12px;color:var(--text-muted);">可通过源码自行构建</div>
</div> </div>
<div class="platform-card"> <div class="platform-card">
<div class="platform-icon">🐧</div> <div class="platform-icon">🐧</div>
<h4>Linux</h4> <h4>Linux</h4>
<p class="version">v1.0.0 · .AppImage</p> <p class="version">v1.0.0 · .AppImage / .deb</p>
<span class="coming-soon">即将发布</span> <span class="coming-soon">即将发布</span>
<div style="margin-top:8px;font-size:12px;color:var(--text-muted);">可通过源码自行构建</div>
</div> </div>
</div> </div>
</div> </div>
@@ -409,7 +411,7 @@
<footer> <footer>
<div class="container"> <div class="container">
<span>&copy; 2026 Web2App. MIT License.</span> <span>&copy; 2026 Web2App. MIT License.</span>
<a href="https://github.com/anomalyco/WebpageExer" target="_blank">GitHub 仓库</a> <a href="https://git.qmikeji.cn/gmh/Web2App.git" target="_blank">Git 仓库</a>
</div> </div>
</footer> </footer>

View File

@@ -0,0 +1,100 @@
<template>
<Teleport to="body">
<div v-if="visible" class="dialog-overlay" @click.self="cancel">
<div class="dialog-box card">
<h3 class="dialog-title">{{ title }}</h3>
<p v-if="message" class="dialog-message">{{ message }}</p>
<input
ref="inputRef"
class="dialog-input"
v-model="inputValue"
:placeholder="placeholder"
@keydown.enter="confirm"
@keydown.esc="cancel"
/>
<div class="dialog-actions">
<button class="btn btn-secondary" @click="cancel">{{ cancelText }}</button>
<button class="btn btn-primary" @click="confirm" :disabled="!inputValue.trim()">{{ okText }}</button>
</div>
</div>
</div>
</Teleport>
</template>
<script setup>
import { ref, watch, nextTick } from 'vue'
const props = defineProps({
visible: Boolean,
title: { type: String, default: '输入' },
message: { type: String, default: '' },
placeholder: { type: String, default: '' },
defaultValue: { type: String, default: '' },
cancelText: { type: String, default: '取消' },
okText: { type: String, default: '确定' }
})
const emit = defineEmits(['confirm', 'cancel'])
const inputValue = ref('')
const inputRef = ref(null)
watch(() => props.visible, (val) => {
if (val) {
inputValue.value = props.defaultValue
nextTick(() => {
inputRef.value?.focus()
inputRef.value?.select()
})
}
})
function confirm() {
if (inputValue.value.trim()) {
emit('confirm', inputValue.value.trim())
}
}
function cancel() {
emit('cancel')
}
</script>
<style scoped>
.dialog-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.dialog-box {
width: 400px;
padding: 24px;
}
.dialog-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 8px;
}
.dialog-message {
color: var(--text-secondary);
font-size: 13px;
margin-bottom: 12px;
}
.dialog-input {
width: 100%;
margin-bottom: 16px;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
</style>

View File

@@ -42,6 +42,15 @@
</div> </div>
</div> </div>
</div> </div>
<InputDialog
:visible="showInput"
title="新建项目"
message="请输入项目名称"
placeholder="例如MyApp"
@confirm="onNameConfirmed"
@cancel="onNameCancelled"
/>
</div> </div>
</template> </template>
@@ -49,25 +58,40 @@
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
const name = prompt('请输入项目名称:') pendingDir.value = dir
if (!name) return showInput.value = true
}
async function onNameConfirmed(name) {
showInput.value = false
try { try {
await store.createProject(name, dir) await store.createProject(name, pendingDir.value)
addRecentProject(dir, name) addRecentProject(pendingDir.value, name)
router.push('/editor') router.push('/editor')
} catch (err) { } catch (err) {
alert('创建失败: ' + err.message) await window.api.dialog.showMessage({
type: 'error',
title: '创建失败',
message: err.message
})
} }
} }
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
@@ -80,7 +104,11 @@ async function openProject(dir) {
addRecentProject(dir, store.config.name) addRecentProject(dir, store.config.name)
router.push('/editor') router.push('/editor')
} catch (err) { } catch (err) {
alert('打开失败: ' + err.message) await window.api.dialog.showMessage({
type: 'error',
title: '打开失败',
message: err.message
})
} }
} }

View File

@@ -74,7 +74,11 @@ async function importFromFiles() {
} }
async function handleDelete(relativePath) { async function handleDelete(relativePath) {
if (confirm(`确定删除 "${relativePath}"`)) { const ok = await window.api.dialog.showConfirm({
title: '删除确认',
message: `确定要删除 "${relativePath}" 吗?`
})
if (ok) {
await store.removeFile(relativePath) await store.removeFile(relativePath)
} }
} }