diff --git a/src/components/PackProgress.vue b/src/components/PackProgress.vue
deleted file mode 100644
index a6bd741..0000000
--- a/src/components/PackProgress.vue
+++ /dev/null
@@ -1,205 +0,0 @@
-
-
-
-
-
-
-
- {{ stageLabel }}
- {{ displayPercent }}
-
-
-
-
{{ progress.message }}
-
-
-
-
-
-
-
-
错误详情:
-
{{ result.error }}
-
-
-
-
-
-
-
diff --git a/src/stores/packaging.js b/src/stores/packaging.js
deleted file mode 100644
index 2314325..0000000
--- a/src/stores/packaging.js
+++ /dev/null
@@ -1,57 +0,0 @@
-import { defineStore } from 'pinia'
-import { nextTick } from 'vue'
-
-export const usePackagingStore = defineStore('packaging', {
- state: () => ({
- isPacking: false,
- progress: {
- stage: '',
- message: '',
- percent: 0
- },
- result: null,
- _cleanup: null
- }),
-
- actions: {
- async startPacking(projectPath, outputPath) {
- this._cleanup?.()
-
- this.isPacking = true
- this.progress = { stage: 'starting', message: '准备开始...', percent: 0 }
- this.result = null
-
- this._cleanup = window.api.pack.onProgress((progress) => {
- this.progress = progress
- })
-
- await nextTick()
-
- try {
- const result = await window.api.pack.start(projectPath, outputPath)
- this.isPacking = false
- this.result = result
- } catch (err) {
- this.isPacking = false
- this.result = { success: false, error: err.message }
- }
- this._cleanup?.()
- this._cleanup = null
- },
-
- cancelPacking() {
- window.api.pack.cancel()
- this.isPacking = false
- this._cleanup?.()
- this._cleanup = null
- },
-
- reset() {
- this.isPacking = false
- this.progress = { stage: '', message: '', percent: 0 }
- this.result = null
- this._cleanup?.()
- this._cleanup = null
- }
- }
-})
diff --git a/src/views/PackPage.vue b/src/views/PackPage.vue
index c9fd313..5d35e78 100644
--- a/src/views/PackPage.vue
+++ b/src/views/PackPage.vue
@@ -38,41 +38,54 @@
-
+
-
-
-
+
✓
打包完成!
安装包已生成
-
{{ packStore.result.outputPath }}
+
{{ result.outputPath }}
-
+
✗
打包失败
-
{{ packStore.result.error }}
+
{{ result.error }}
+
+
+
+
+
{{ stageLabel }}
+
+
{{ progressMsg }}
+
+
+
@@ -80,18 +93,22 @@
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 outputPath = ref(null)
+const isPacking = ref(false)
+const result = ref(null)
+const progressStage = ref('')
+const progressMsg = ref('')
+const progressPct = ref(0)
+const showOverlay = ref(false)
+
const fileCount = computed(() => countFiles(store.files))
const platformLabel = computed(() => {
@@ -102,6 +119,74 @@ const platformLabel = computed(() => {
return p
})
+const stageLabel = computed(() => {
+ const labels = {
+ 'starting': '准备开始…',
+ 'prepare': '准备中…',
+ 'copy-files': '复制文件…',
+ 'configure': '配置应用…',
+ 'install': '安装依赖…',
+ 'download': '下载中…',
+ 'build': '打包中…',
+ 'sign': '签名中…',
+ 'complete': '打包完成',
+ 'error': '出错了'
+ }
+ return labels[progressStage.value] || progressStage.value || '准备开始…'
+})
+
+const isIndeterminate = computed(() => {
+ return ['starting', 'install', 'download'].includes(progressStage.value)
+})
+
+const barStyle = computed(() => {
+ if (isIndeterminate.value) return {}
+ return { width: Math.round(progressPct.value) + '%' }
+})
+
+async function startPack() {
+ isPacking.value = true
+ result.value = null
+ progressStage.value = 'starting'
+ progressMsg.value = '正在准备打包环境…'
+ progressPct.value = 0
+ showOverlay.value = true
+
+ const cleanup = window.api.pack.onProgress((p) => {
+ progressStage.value = p.stage
+ progressMsg.value = p.message
+ if (p.percent != null) progressPct.value = p.percent
+ })
+
+ try {
+ const res = await window.api.pack.start(store.projectPath, outputPath.value)
+ isPacking.value = false
+ result.value = res
+ if (res.error) {
+ progressStage.value = 'error'
+ progressMsg.value = res.error
+ } else {
+ progressStage.value = 'complete'
+ progressMsg.value = '打包完成!'
+ progressPct.value = 100
+ }
+ } catch (err) {
+ isPacking.value = false
+ result.value = { success: false, error: err.message }
+ progressStage.value = 'error'
+ progressMsg.value = err.message
+ }
+ cleanup?.()
+}
+
+function cancelPack() {
+ window.api.pack.cancel()
+ isPacking.value = false
+ showOverlay.value = false
+ progressStage.value = ''
+ progressMsg.value = ''
+}
+
async function selectOutputPath() {
const ext = process.platform === 'win32' ? 'exe' : process.platform === 'darwin' ? 'dmg' : 'AppImage'
const path = await window.api.dialog.saveFile({
@@ -111,12 +196,10 @@ async function selectOutputPath() {
if (path) outputPath.value = path
}
-function startPack() {
- packStore.startPacking(store.projectPath, outputPath.value)
-}
-
function goBack() {
- packStore.reset()
+ showOverlay.value = false
+ isPacking.value = false
+ result.value = null
router.push('/editor')
}
@@ -130,8 +213,8 @@ function countFiles(files) {
}
onBeforeUnmount(() => {
- if (!packStore.result) {
- packStore.reset()
+ if (isPacking.value) {
+ window.api.pack.cancel()
}
})
@@ -252,4 +335,96 @@ onBeforeUnmount(() => {
word-break: break-all;
max-width: 100%;
}
+
+.pack-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(15, 23, 42, 0.85);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 9999;
+}
+
+.overlay-content {
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-md);
+ padding: 32px 40px;
+ min-width: 380px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 16px;
+}
+
+.overlay-spinner {
+ width: 48px;
+ height: 48px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.spinner-ring {
+ width: 36px;
+ height: 36px;
+ border: 3px solid var(--border);
+ border-top-color: var(--primary);
+ border-radius: 50%;
+ animation: spin 0.7s linear infinite;
+}
+
+@keyframes spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+}
+
+.overlay-stage {
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--text);
+}
+
+.overlay-bar {
+ width: 100%;
+ height: 6px;
+ background: var(--border);
+ border-radius: 3px;
+ overflow: hidden;
+}
+
+.overlay-bar-fill {
+ height: 100%;
+ background: linear-gradient(90deg, var(--primary), #818cf8);
+ border-radius: 3px;
+ transition: width 0.25s ease;
+}
+
+.overlay-bar-fill.pulse {
+ width: 28%;
+ animation: slide 1.2s ease-in-out infinite;
+}
+
+@keyframes slide {
+ 0% { transform: translateX(-100%); }
+ 50% { transform: translateX(200%); }
+ 100% { transform: translateX(400%); }
+}
+
+.overlay-msg {
+ font-size: 13px;
+ color: var(--text-secondary);
+ text-align: center;
+ word-break: break-all;
+ max-width: 360px;
+}
+
+.overlay-cancel {
+ margin-top: 4px;
+}