Files
Web2App/src/views/PackPage.vue
gmh01 2f3ec636e0 feat: 完成 Web2App 核心功能实现
- Electron 主进程:窗口管理、IPC、项目读写、打包引擎
- Vue 3 前端:4 个视图页面、3 个组件、Pinia 状态管理
- 生成应用模板:Express 后端 + Electron 窗口
- 支持资源导入、应用配置、跨平台打包
2026-07-23 18:25:35 +08:00

256 lines
5.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="pack-page">
<header class="pack-header">
<div class="header-left">
<button class="btn btn-secondary btn-sm" @click="goBack">&larr; 返回编辑</button>
<h2>打包应用</h2>
</div>
</header>
<div class="pack-body">
<div class="pack-config card">
<h3>打包配置</h3>
<div class="info-row">
<span class="label">应用名称</span>
<span>{{ store.config.name }}</span>
</div>
<div class="info-row">
<span class="label">版本号</span>
<span>{{ store.config.version }}</span>
</div>
<div class="info-row">
<span class="label">资源文件数</span>
<span>{{ fileCount }} 个文件</span>
</div>
<div class="form-group">
<label class="label">输出路径</label>
<div class="output-selector">
<input type="text" :value="outputPath || '默认位置'" readonly />
<button class="btn btn-sm btn-secondary" @click="selectOutputPath">选择</button>
</div>
</div>
<div class="info-row">
<span class="label">目标平台</span>
<span>{{ platformLabel }}</span>
</div>
</div>
<div v-if="!packStore.isPacking && !packStore.result" class="pack-actions">
<button class="btn btn-primary btn-start" @click="startPack" :disabled="!store.hasProject">
开始打包
</button>
<p class="pack-hint">打包过程可能需要几分钟请耐心等待</p>
</div>
<PackProgress
v-if="packStore.isPacking || packStore.result"
:progress="packStore.progress"
:result="packStore.result"
@cancel="packStore.cancelPacking()"
/>
<div v-if="packStore.result && packStore.result.success" class="result-success card">
<div class="result-icon">&#10003;</div>
<h3>打包完成</h3>
<p>安装包已生成</p>
<p v-if="packStore.result.outputPath" class="output-path">{{ packStore.result.outputPath }}</p>
<div class="result-actions">
<button class="btn btn-secondary" @click="goBack">返回编辑</button>
<button class="btn btn-primary" @click="startPack">重新打包</button>
</div>
</div>
<div v-if="packStore.result && !packStore.result.success" class="result-error card">
<div class="result-icon error">&#10007;</div>
<h3>打包失败</h3>
<p>{{ packStore.result.error }}</p>
<div class="result-actions">
<button class="btn btn-secondary" @click="goBack">返回编辑</button>
<button class="btn btn-primary" @click="startPack">重试</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router'
import { useProjectStore } from '../stores/project'
import { usePackagingStore } from '../stores/packaging'
import PackProgress from '../components/PackProgress.vue'
const router = useRouter()
const store = useProjectStore()
const packStore = usePackagingStore()
const outputPath = ref(null)
if (!store.hasProject) {
router.replace('/')
}
const fileCount = computed(() => countFiles(store.files))
const platformLabel = computed(() => {
const p = process.platform
if (p === 'win32') return 'Windows (.exe)'
if (p === 'darwin') return 'macOS (.dmg)'
if (p === 'linux') return 'Linux (.AppImage)'
return p
})
async function selectOutputPath() {
const ext = process.platform === 'win32' ? 'exe' : process.platform === 'darwin' ? 'dmg' : 'AppImage'
const path = await window.api.dialog.saveFile({
defaultPath: `${store.config.name}-Setup.${ext}`,
filters: [{ name: '安装包', extensions: [ext] }]
})
if (path) outputPath.value = path
}
function startPack() {
packStore.startPacking(store.projectPath, outputPath.value)
}
function goBack() {
packStore.reset()
router.push('/editor')
}
function countFiles(files) {
let count = 0
for (const f of files) {
if (f.type === 'file') count++
if (f.children) count += countFiles(f.children)
}
return count
}
onBeforeUnmount(() => {
if (!packStore.result) {
packStore.reset()
}
})
</script>
<style scoped>
.pack-page {
height: 100%;
display: flex;
flex-direction: column;
}
.pack-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;
}
.pack-body {
flex: 1;
padding: 24px;
max-width: 600px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 20px;
}
.pack-config {
padding: 20px;
display: flex;
flex-direction: column;
gap: 12px;
}
.pack-config h3 {
font-size: 15px;
font-weight: 600;
margin-bottom: 4px;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.form-group {
display: flex;
flex-direction: column;
gap: 4px;
}
.output-selector {
display: flex;
gap: 8px;
}
.output-selector input {
flex: 1;
}
.pack-actions {
text-align: center;
}
.btn-start {
padding: 12px 40px;
font-size: 16px;
}
.pack-hint {
margin-top: 8px;
font-size: 12px;
color: var(--text-muted);
}
.result-success, .result-error {
padding: 30px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.result-icon {
font-size: 48px;
color: var(--success);
}
.result-icon.error {
color: var(--danger);
}
.result-actions {
display: flex;
gap: 8px;
margin-top: 12px;
}
.output-path {
font-size: 12px;
color: var(--text-muted);
word-break: break-all;
max-width: 100%;
}
</style>