feat(aigc): add animated progress and completion redirect

enhance GeneratingProgressPanel with smooth width transition and animated progress increments
add active/complete step styling for progress panel items
emit complete event when generation finishes, add navigation to AIGC detail page on completion
clean up MineSetting component: format menu-item v-for and comment out unused phone row
This commit is contained in:
duanshuwen
2026-07-07 11:06:34 +08:00
parent a6e369e1d5
commit 409559e885
4 changed files with 123 additions and 11 deletions

View File

@@ -12,6 +12,7 @@
v-for="(step, index) in steps"
:key="`${step}-${index}`"
class="generating-step-item"
:class="getStepClass(index)"
>
<view class="generating-step-dot" />
<text class="generating-step-label">{{ step }}</text>
@@ -25,7 +26,7 @@
</template>
<script setup>
import { computed } from "vue";
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
const props = defineProps({
progress: {
@@ -42,13 +43,97 @@ const props = defineProps({
},
});
const normalizedProgress = computed(() => {
const value = Number(props.progress);
const emit = defineEmits(["complete"]);
const displayProgress = ref(0);
let progressTimer = null;
let hasCompleted = false;
const normalizeProgress = (progress) => {
const value = Number(progress);
if (!Number.isFinite(value)) return 0;
return Math.max(0, Math.min(100, Math.round(value)));
};
const normalizedProgress = computed(() => {
return normalizeProgress(displayProgress.value);
});
const progressWidth = computed(() => `${normalizedProgress.value}%`);
const currentStepIndex = computed(() => {
const progress = normalizedProgress.value;
if (progress <= 24) return 0;
if (progress <= 49) return 1;
if (progress <= 79) return 2;
return 3;
});
const clearProgressTimer = () => {
if (progressTimer) {
clearInterval(progressTimer);
progressTimer = null;
}
};
const getNextProgress = (progress) => {
if (progress < 35) return Math.min(progress + 7, 35);
if (progress < 70) return Math.min(progress + 5, 70);
if (progress < 92) return Math.min(progress + 3, 92);
if (progress < 99) return Math.min(progress + 1, 99);
return 100;
};
const completeProgress = () => {
clearProgressTimer();
if (hasCompleted) return;
hasCompleted = true;
emit("complete");
};
const startProgressTimer = () => {
clearProgressTimer();
if (normalizedProgress.value >= 100) {
completeProgress();
return;
}
progressTimer = setInterval(() => {
displayProgress.value = getNextProgress(normalizedProgress.value);
if (normalizedProgress.value >= 100) {
completeProgress();
}
}, 320);
};
const resetProgress = () => {
hasCompleted = false;
displayProgress.value = normalizeProgress(props.progress);
startProgressTimer();
};
const getStepClass = (index) => ({
"is-complete": normalizedProgress.value >= 100 || index < currentStepIndex.value,
"is-active": normalizedProgress.value < 100 && index === currentStepIndex.value,
});
watch(
() => props.progress,
() => {
resetProgress();
}
);
onMounted(() => {
resetProgress();
});
onUnmounted(() => {
clearProgressTimer();
});
</script>
<style scoped lang="scss">

View File

@@ -23,6 +23,7 @@
min-width: 24px;
border-radius: 999px;
background: linear-gradient(90deg, #20cf75 0%, #f2d646 100%);
transition: width 0.28s ease;
}
.generating-progress-text {
@@ -52,6 +53,14 @@
box-sizing: border-box;
}
.generating-step-item.is-active {
background: rgba(255, 255, 255, 0.86);
}
.generating-step-item.is-complete {
background: rgba(236, 255, 244, 0.72);
}
.generating-step-dot {
flex: 0 0 18px;
width: 18px;
@@ -62,6 +71,17 @@
box-sizing: border-box;
}
.generating-step-item.is-active .generating-step-dot {
border-color: #20cf75;
background: #20cf75;
box-shadow: 0 0 0 4px rgba(32, 207, 117, 0.12);
}
.generating-step-item.is-complete .generating-step-dot {
border-color: rgba(32, 207, 117, 0.42);
background: rgba(32, 207, 117, 0.22);
}
.generating-step-label {
min-width: 0;
overflow: hidden;
@@ -73,6 +93,11 @@
white-space: nowrap;
}
.generating-step-item.is-active .generating-step-label,
.generating-step-item.is-complete .generating-step-label {
color: #172033;
}
.generating-freeze-text {
display: block;
overflow: hidden;

View File

@@ -23,6 +23,7 @@
v-else-if="currentStep === 'generating'"
:cost="currentVersion.cost || 100"
:progress="8"
@complete="handleGenerateComplete"
/>
<template v-else>
@@ -176,6 +177,12 @@ const handleGenerate = () => {
currentStep.value = "generating";
};
const handleGenerateComplete = () => {
uni.navigateTo({
url: "/pages-aigc/detail/detail",
});
};
const handleClosePointDialog = () => {
pointDialogVisible.value = false;
};