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
|
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)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -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": {
|
||||||
|
|||||||
@@ -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>© 2026 Web2App. MIT License.</span>
|
<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>
|
</div>
|
||||||
</footer>
|
</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>
|
</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
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user