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 @@ - - - - - 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; +}