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:
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -11,20 +11,15 @@
|
||||
<text class="label">昵称</text>
|
||||
<text class="value">{{ userInfo.nickname }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<!-- <view class="row">
|
||||
<text class="label">手机号</text>
|
||||
<text class="value">{{ userInfo.phone }}</text>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
|
||||
<!-- 中间功能入口(循环渲染) -->
|
||||
<view class="menu-card">
|
||||
<view
|
||||
class="menu-item"
|
||||
v-for="(item, index) in menuList"
|
||||
:key="index"
|
||||
@click="handleMenuClick(item)"
|
||||
>
|
||||
<view class="menu-item" v-for="(item, index) in menuList" :key="index" @click="handleMenuClick(item)">
|
||||
<text class="label">{{ item.label }}</text>
|
||||
<uni-icons type="right" size="14" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
|
||||
Reference in New Issue
Block a user