fix: 修复进度监听器泄露问题,添加 nextTick 确保动画先于 IPC 渲染

This commit is contained in:
gmh01
2026-07-23 21:12:45 +08:00
parent afdfce6502
commit 5e890108a1
2 changed files with 35 additions and 30 deletions

View File

@@ -1,9 +1,7 @@
<template> <template>
<div class="pack-progress card"> <div class="pack-progress card">
<div class="progress-header"> <div class="progress-header">
<div class="spinner" :class="{ active: !result }"> <span class="spinner" v-if="!result"><span class="spinner-dot"></span></span>
<div class="spinner-ring"></div>
</div>
<h3>打包进度</h3> <h3>打包进度</h3>
</div> </div>
@@ -11,11 +9,11 @@
<div <div
class="progress-bar" class="progress-bar"
:class="{ indeterminate: isIndeterminate }" :class="{ indeterminate: isIndeterminate }"
:style="barStyle" :style="!isIndeterminate ? { width: displayPercent } : {}"
></div> ></div>
</div> </div>
<div v-if="progress.stage" class="progress-info"> <div class="progress-info">
<span class="progress-label">{{ stageLabel }}</span> <span class="progress-label">{{ stageLabel }}</span>
<span class="progress-percent">{{ displayPercent }}</span> <span class="progress-percent">{{ displayPercent }}</span>
</div> </div>
@@ -70,12 +68,7 @@ const stageLabel = computed(() => {
}) })
const isIndeterminate = computed(() => { const isIndeterminate = computed(() => {
return ['install', 'download'].includes(props.progress.stage) return ['starting', 'install', 'download'].includes(props.progress.stage)
})
const barStyle = computed(() => {
if (isIndeterminate.value) return {}
return { width: Math.round(props.progress.percent) + '%' }
}) })
const displayPercent = computed(() => { const displayPercent = computed(() => {
@@ -102,23 +95,24 @@ const displayPercent = computed(() => {
} }
.spinner { .spinner {
width: 20px; display: inline-flex;
height: 20px;
display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 20px;
height: 20px;
} }
.spinner.active .spinner-ring { .spinner-dot {
width: 16px; width: 14px;
height: 16px; height: 14px;
border: 2.5px solid var(--border); border: 2.5px solid var(--border);
border-top-color: var(--primary); border-top-color: var(--primary);
border-radius: 50%; border-radius: 50%;
animation: spin 0.8s linear infinite; animation: spin 0.7s linear infinite;
} }
@keyframes spin { @keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); } to { transform: rotate(360deg); }
} }
@@ -132,18 +126,18 @@ const displayPercent = computed(() => {
.progress-bar { .progress-bar {
height: 100%; height: 100%;
background: var(--primary); background: linear-gradient(90deg, var(--primary), #818cf8);
border-radius: 4px; border-radius: 4px;
transition: width 0.3s ease; transition: width 0.3s ease;
width: 0%; min-width: 0%;
} }
.progress-bar.indeterminate { .progress-bar.indeterminate {
width: 30%; width: 30%;
animation: indeterminate 1.5s ease-in-out infinite; animation: slide 1.2s ease-in-out infinite;
} }
@keyframes indeterminate { @keyframes slide {
0% { transform: translateX(-100%); } 0% { transform: translateX(-100%); }
50% { transform: translateX(200%); } 50% { transform: translateX(200%); }
100% { transform: translateX(400%); } 100% { transform: translateX(400%); }

View File

@@ -1,4 +1,5 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { nextTick } from 'vue'
export const usePackagingStore = defineStore('packaging', { export const usePackagingStore = defineStore('packaging', {
state: () => ({ state: () => ({
@@ -8,39 +9,49 @@ export const usePackagingStore = defineStore('packaging', {
message: '', message: '',
percent: 0 percent: 0
}, },
result: null result: null,
_cleanup: null
}), }),
actions: { actions: {
startPacking(projectPath, outputPath) { async startPacking(projectPath, outputPath) {
this._cleanup?.()
this.isPacking = true this.isPacking = true
this.progress = { stage: 'starting', message: '准备开始...', percent: 0 } this.progress = { stage: 'starting', message: '准备开始...', percent: 0 }
this.result = null this.result = null
const cleanup = window.api.pack.onProgress((progress) => { this._cleanup = window.api.pack.onProgress((progress) => {
this.progress = progress this.progress = progress
}) })
window.api.pack.start(projectPath, outputPath).then((result) => { await nextTick()
try {
const result = await window.api.pack.start(projectPath, outputPath)
this.isPacking = false this.isPacking = false
this.result = result this.result = result
cleanup() } catch (err) {
}).catch((err) => {
this.isPacking = false this.isPacking = false
this.result = { success: false, error: err.message } this.result = { success: false, error: err.message }
cleanup() }
}) this._cleanup?.()
this._cleanup = null
}, },
cancelPacking() { cancelPacking() {
window.api.pack.cancel() window.api.pack.cancel()
this.isPacking = false this.isPacking = false
this._cleanup?.()
this._cleanup = null
}, },
reset() { reset() {
this.isPacking = false this.isPacking = false
this.progress = { stage: '', message: '', percent: 0 } this.progress = { stage: '', message: '', percent: 0 }
this.result = null this.result = null
this._cleanup?.()
this._cleanup = null
} }
} }
}) })