fix: 修复新建项目无响应问题
- 替换 prompt() 为 InputDialog Vue 模态组件 - 替换 alert()/confirm() 为 Electron showMessageBox IPC - 添加 dialog:showMessage / dialog:showConfirm IPC 通道
This commit is contained in:
@@ -85,6 +85,28 @@ ipcMain.handle('dialog:saveFile', async (_event, options) => {
|
||||
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) => {
|
||||
return readDirectoryTree(dirPath)
|
||||
})
|
||||
|
||||
@@ -5,7 +5,9 @@ contextBridge.exposeInMainWorld('api', {
|
||||
selectDirectory: () => ipcRenderer.invoke('dialog:selectDirectory'),
|
||||
selectFile: (options) => ipcRenderer.invoke('dialog:selectFile', options),
|
||||
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: {
|
||||
readTree: (dirPath) => ipcRenderer.invoke('dir:readTree', dirPath)
|
||||
|
||||
10
package.json
10
package.json
@@ -2,12 +2,19 @@
|
||||
"name": "web2app",
|
||||
"version": "1.0.0",
|
||||
"description": "将网站转化为独立运行的桌面应用",
|
||||
"homepage": "https://github.com/web2app/web2app",
|
||||
"author": {
|
||||
"name": "Web2App Team",
|
||||
"email": "team@web2app.dev"
|
||||
},
|
||||
"main": "electron/main.js",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"electron:dev": "concurrently \"vite\" \"wait-on http://localhost:5173 && electron .\"",
|
||||
"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"
|
||||
},
|
||||
"build": {
|
||||
@@ -55,7 +62,8 @@
|
||||
}
|
||||
],
|
||||
"icon": null,
|
||||
"category": "Utility"
|
||||
"category": "Utility",
|
||||
"maintainer": "Web2App Team <team@web2app.dev>"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -344,23 +344,25 @@
|
||||
<div class="platform-icon">🪟</div>
|
||||
<h4>Windows</h4>
|
||||
<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>
|
||||
下载便携版
|
||||
下载安装包
|
||||
</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 class="platform-card">
|
||||
<div class="platform-icon">🍎</div>
|
||||
<h4>macOS</h4>
|
||||
<p class="version">v1.0.0 · Intel / Apple Silicon</p>
|
||||
<span class="coming-soon">即将发布</span>
|
||||
<div style="margin-top:8px;font-size:12px;color:var(--text-muted);">可通过源码自行构建</div>
|
||||
</div>
|
||||
<div class="platform-card">
|
||||
<div class="platform-icon">🐧</div>
|
||||
<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>
|
||||
<div style="margin-top:8px;font-size:12px;color:var(--text-muted);">可通过源码自行构建</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -409,7 +411,7 @@
|
||||
<footer>
|
||||
<div class="container">
|
||||
<span>© 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>
|
||||
</footer>
|
||||
|
||||
|
||||
100
src/components/InputDialog.vue
Normal file
100
src/components/InputDialog.vue
Normal 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>
|
||||
@@ -42,6 +42,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<InputDialog
|
||||
:visible="showInput"
|
||||
title="新建项目"
|
||||
message="请输入项目名称"
|
||||
placeholder="例如:MyApp"
|
||||
@confirm="onNameConfirmed"
|
||||
@cancel="onNameCancelled"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -49,25 +58,40 @@
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useProjectStore } from '../stores/project'
|
||||
import InputDialog from '../components/InputDialog.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const store = useProjectStore()
|
||||
const recentProjects = ref(loadRecentProjects())
|
||||
const showInput = ref(false)
|
||||
const pendingDir = ref('')
|
||||
|
||||
async function createNewProject() {
|
||||
const dir = await window.api.dialog.selectDirectory()
|
||||
if (!dir) return
|
||||
const name = prompt('请输入项目名称:')
|
||||
if (!name) return
|
||||
pendingDir.value = dir
|
||||
showInput.value = true
|
||||
}
|
||||
|
||||
async function onNameConfirmed(name) {
|
||||
showInput.value = false
|
||||
try {
|
||||
await store.createProject(name, dir)
|
||||
addRecentProject(dir, name)
|
||||
await store.createProject(name, pendingDir.value)
|
||||
addRecentProject(pendingDir.value, name)
|
||||
router.push('/editor')
|
||||
} catch (err) {
|
||||
alert('创建失败: ' + err.message)
|
||||
await window.api.dialog.showMessage({
|
||||
type: 'error',
|
||||
title: '创建失败',
|
||||
message: err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function onNameCancelled() {
|
||||
showInput.value = false
|
||||
}
|
||||
|
||||
async function openExistingProject() {
|
||||
const dir = await window.api.dialog.selectDirectory()
|
||||
if (!dir) return
|
||||
@@ -80,7 +104,11 @@ async function openProject(dir) {
|
||||
addRecentProject(dir, store.config.name)
|
||||
router.push('/editor')
|
||||
} catch (err) {
|
||||
alert('打开失败: ' + err.message)
|
||||
await window.api.dialog.showMessage({
|
||||
type: 'error',
|
||||
title: '打开失败',
|
||||
message: err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,11 @@ async function importFromFiles() {
|
||||
}
|
||||
|
||||
async function handleDelete(relativePath) {
|
||||
if (confirm(`确定删除 "${relativePath}"?`)) {
|
||||
const ok = await window.api.dialog.showConfirm({
|
||||
title: '删除确认',
|
||||
message: `确定要删除 "${relativePath}" 吗?`
|
||||
})
|
||||
if (ok) {
|
||||
await store.removeFile(relativePath)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user