- 替换 prompt() 为 InputDialog Vue 模态组件 - 替换 alert()/confirm() 为 Electron showMessageBox IPC - 添加 dialog:showMessage / dialog:showConfirm IPC 通道
199 lines
4.0 KiB
Vue
199 lines
4.0 KiB
Vue
<template>
|
|
<div class="editor">
|
|
<header class="editor-header">
|
|
<div class="header-left">
|
|
<button class="btn btn-secondary btn-sm" @click="goHome">← 返回</button>
|
|
<h2>{{ store.projectName }}</h2>
|
|
<span v-if="store.isDirty" class="dirty-badge">未保存</span>
|
|
</div>
|
|
<div class="header-right">
|
|
<button class="btn btn-secondary btn-sm" @click="saveProject" :disabled="!store.isDirty">
|
|
保存
|
|
</button>
|
|
<button class="btn btn-primary" @click="goPack">
|
|
打包应用 →
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="editor-body">
|
|
<aside class="sidebar">
|
|
<div class="sidebar-header">
|
|
<h3>资源文件</h3>
|
|
<div class="sidebar-actions">
|
|
<button class="btn btn-sm btn-secondary" @click="importFromDir" title="导入文件夹">📁</button>
|
|
<button class="btn btn-sm btn-secondary" @click="importFromFiles" title="导入文件">📄</button>
|
|
</div>
|
|
</div>
|
|
<div class="file-tree-container">
|
|
<ResourceTree
|
|
v-if="store.files.length > 0"
|
|
:files="store.files"
|
|
@delete="handleDelete"
|
|
/>
|
|
<div v-else class="empty-state">
|
|
<p>暂无资源</p>
|
|
<p class="hint">点击上方按钮导入网站文件</p>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<main class="main-content">
|
|
<ConfigPanel />
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { onBeforeUnmount } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { useProjectStore } from '../stores/project'
|
|
import ResourceTree from '../components/ResourceTree.vue'
|
|
import ConfigPanel from '../components/ConfigPanel.vue'
|
|
|
|
const router = useRouter()
|
|
const store = useProjectStore()
|
|
|
|
if (!store.hasProject) {
|
|
router.replace('/')
|
|
}
|
|
|
|
async function saveProject() {
|
|
await store.saveProject()
|
|
}
|
|
|
|
async function importFromDir() {
|
|
const count = await store.importFromDirectory()
|
|
if (count > 0) store.isDirty = true
|
|
}
|
|
|
|
async function importFromFiles() {
|
|
const count = await store.importFromFiles()
|
|
if (count > 0) store.isDirty = true
|
|
}
|
|
|
|
async function handleDelete(relativePath) {
|
|
const ok = await window.api.dialog.showConfirm({
|
|
title: '删除确认',
|
|
message: `确定要删除 "${relativePath}" 吗?`
|
|
})
|
|
if (ok) {
|
|
await store.removeFile(relativePath)
|
|
}
|
|
}
|
|
|
|
function goHome() {
|
|
if (store.isDirty) {
|
|
store.saveProject()
|
|
}
|
|
router.push('/')
|
|
}
|
|
|
|
function goPack() {
|
|
if (store.isDirty) {
|
|
store.saveProject()
|
|
}
|
|
router.push('/pack')
|
|
}
|
|
|
|
onBeforeUnmount(() => {
|
|
if (store.isDirty) {
|
|
store.saveProject()
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.editor {
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.editor-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 20px;
|
|
background: var(--surface);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.header-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.header-left h2 {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.dirty-badge {
|
|
font-size: 11px;
|
|
background: var(--warning);
|
|
color: white;
|
|
padding: 2px 8px;
|
|
border-radius: 10px;
|
|
}
|
|
|
|
.header-right {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.editor-body {
|
|
flex: 1;
|
|
display: flex;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.sidebar {
|
|
width: 280px;
|
|
min-width: 280px;
|
|
border-right: 1px solid var(--border);
|
|
background: var(--surface);
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.sidebar-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.sidebar-header h3 {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.sidebar-actions {
|
|
display: flex;
|
|
gap: 4px;
|
|
}
|
|
|
|
.file-tree-container {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 8px;
|
|
}
|
|
|
|
.hint {
|
|
font-size: 12px;
|
|
}
|
|
|
|
.main-content {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 20px;
|
|
}
|
|
</style>
|