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"
|
v-for="(step, index) in steps"
|
||||||
:key="`${step}-${index}`"
|
:key="`${step}-${index}`"
|
||||||
class="generating-step-item"
|
class="generating-step-item"
|
||||||
|
:class="getStepClass(index)"
|
||||||
>
|
>
|
||||||
<view class="generating-step-dot" />
|
<view class="generating-step-dot" />
|
||||||
<text class="generating-step-label">{{ step }}</text>
|
<text class="generating-step-label">{{ step }}</text>
|
||||||
@@ -25,7 +26,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from "vue";
|
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
progress: {
|
progress: {
|
||||||
@@ -42,13 +43,97 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const normalizedProgress = computed(() => {
|
const emit = defineEmits(["complete"]);
|
||||||
const value = Number(props.progress);
|
|
||||||
|
const displayProgress = ref(0);
|
||||||
|
let progressTimer = null;
|
||||||
|
let hasCompleted = false;
|
||||||
|
|
||||||
|
const normalizeProgress = (progress) => {
|
||||||
|
const value = Number(progress);
|
||||||
if (!Number.isFinite(value)) return 0;
|
if (!Number.isFinite(value)) return 0;
|
||||||
return Math.max(0, Math.min(100, Math.round(value)));
|
return Math.max(0, Math.min(100, Math.round(value)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizedProgress = computed(() => {
|
||||||
|
return normalizeProgress(displayProgress.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
const progressWidth = computed(() => `${normalizedProgress.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>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
min-width: 24px;
|
min-width: 24px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: linear-gradient(90deg, #20cf75 0%, #f2d646 100%);
|
background: linear-gradient(90deg, #20cf75 0%, #f2d646 100%);
|
||||||
|
transition: width 0.28s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.generating-progress-text {
|
.generating-progress-text {
|
||||||
@@ -52,6 +53,14 @@
|
|||||||
box-sizing: border-box;
|
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 {
|
.generating-step-dot {
|
||||||
flex: 0 0 18px;
|
flex: 0 0 18px;
|
||||||
width: 18px;
|
width: 18px;
|
||||||
@@ -62,6 +71,17 @@
|
|||||||
box-sizing: border-box;
|
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 {
|
.generating-step-label {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -73,6 +93,11 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.generating-step-item.is-active .generating-step-label,
|
||||||
|
.generating-step-item.is-complete .generating-step-label {
|
||||||
|
color: #172033;
|
||||||
|
}
|
||||||
|
|
||||||
.generating-freeze-text {
|
.generating-freeze-text {
|
||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
v-else-if="currentStep === 'generating'"
|
v-else-if="currentStep === 'generating'"
|
||||||
:cost="currentVersion.cost || 100"
|
:cost="currentVersion.cost || 100"
|
||||||
:progress="8"
|
:progress="8"
|
||||||
|
@complete="handleGenerateComplete"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -176,6 +177,12 @@ const handleGenerate = () => {
|
|||||||
currentStep.value = "generating";
|
currentStep.value = "generating";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleGenerateComplete = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: "/pages-aigc/detail/detail",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleClosePointDialog = () => {
|
const handleClosePointDialog = () => {
|
||||||
pointDialogVisible.value = false;
|
pointDialogVisible.value = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,20 +11,15 @@
|
|||||||
<text class="label">昵称</text>
|
<text class="label">昵称</text>
|
||||||
<text class="value">{{ userInfo.nickname }}</text>
|
<text class="value">{{ userInfo.nickname }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="row">
|
<!-- <view class="row">
|
||||||
<text class="label">手机号</text>
|
<text class="label">手机号</text>
|
||||||
<text class="value">{{ userInfo.phone }}</text>
|
<text class="value">{{ userInfo.phone }}</text>
|
||||||
</view>
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 中间功能入口(循环渲染) -->
|
<!-- 中间功能入口(循环渲染) -->
|
||||||
<view class="menu-card">
|
<view class="menu-card">
|
||||||
<view
|
<view class="menu-item" v-for="(item, index) in menuList" :key="index" @click="handleMenuClick(item)">
|
||||||
class="menu-item"
|
|
||||||
v-for="(item, index) in menuList"
|
|
||||||
:key="index"
|
|
||||||
@click="handleMenuClick(item)"
|
|
||||||
>
|
|
||||||
<text class="label">{{ item.label }}</text>
|
<text class="label">{{ item.label }}</text>
|
||||||
<uni-icons type="right" size="14" color="#ccc"></uni-icons>
|
<uni-icons type="right" size="14" color="#ccc"></uni-icons>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
Reference in New Issue
Block a user