topfans/frontend/pages/discover/generation-loading.vue

319 lines
7.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="generation-loading">
<image class="background-image" src="/static/background/exhibitionSuccess.png" mode="aspectFill" />
<view class="gift-box">
<image class="gift-image" src="/static/nft/lihe.png" mode="aspectFit" />
</view>
<view class="progress-container">
<view class="progress-bar-wrapper">
<view class="progress-bar">
<view class="progress-fill" :style="{ width: progress + '%' }"></view>
</view>
<view class="progress-icon" :style="{ left: progress + '%' }">
<view class="icon-circle"></view>
</view>
</view>
<text class="progress-text">{{ Math.round(progress) }}%</text>
<text class="loading-text">Thinking</text>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { imageGenerationApi } from '@/utils/api.js';
import {
consumeGenerationFlowPayload,
FLOW_MODE_PREFILLED,
FLOW_MODE_LENTICULAR,
GENERATED_IMAGES_KEY,
GENERATION_REQUEST_KEY,
CASTLOVE_FORM_KEY,
persistLenticularPreviewMeta,
} from '@/utils/castloveGenerationFlow.js';
const progress = ref(0);
let progressTimer = null;
let generationData = null;
let flowStartedAt = 0;
// 模拟进度增长
const simulateProgress = () => {
progressTimer = setInterval(() => {
if (progress.value < 90) {
// 进度到90%前缓慢增长
const increment = Math.random() * 5 + 2;
progress.value = Math.min(90, progress.value + increment);
}
}, 500);
};
// 停止进度模拟
const stopProgress = () => {
if (progressTimer) {
clearInterval(progressTimer);
progressTimer = null;
}
};
// 完成进度
const completeProgress = () => {
stopProgress();
const finalInterval = setInterval(() => {
if (progress.value < 100) {
progress.value = Math.min(100, progress.value + 5);
} else {
clearInterval(finalInterval);
// 进度完成后延迟跳转
setTimeout(() => {
handleSuccess();
}, 500);
}
}, 50);
};
// 回退进度
const revertProgress = () => {
stopProgress();
const revertInterval = setInterval(() => {
if (progress.value > 0) {
progress.value = Math.max(0, progress.value - 10);
} else {
clearInterval(revertInterval);
// 回退完成后返回上一页
setTimeout(() => {
uni.navigateBack();
}, 500);
}
}, 100);
};
const waitMinDuration = async (minMs) => {
if (!minMs || minMs <= 0) return;
const elapsed = Date.now() - flowStartedAt;
if (elapsed < minMs) {
await new Promise((r) => setTimeout(r, minMs - elapsed));
}
};
const handleSuccess = () => {
uni.showToast({ title: '生成成功', icon: 'success', duration: 1200 });
setTimeout(() => {
uni.navigateTo({ url: '/pages/discover/generation-result' });
}, 1200);
};
const runPrefilledFlow = async (flow) => {
try {
const images = flow.images || [];
if (!images.length) throw new Error('无候选图');
await waitMinDuration(flow.minDurationMs ?? 1400);
uni.setStorageSync(GENERATED_IMAGES_KEY, JSON.stringify(images));
completeProgress();
} catch (err) {
console.error('[GenerationLoading] prefilled', err);
uni.showToast({ title: err.message || '加载失败', icon: 'none' });
revertProgress();
}
};
const runLenticularFlow = async (flow) => {
try {
const formStr = uni.getStorageSync(CASTLOVE_FORM_KEY);
if (!formStr) throw new Error('缺少表单数据');
const formData = JSON.parse(formStr);
await waitMinDuration(flow.minDurationMs ?? 1600);
persistLenticularPreviewMeta(formData);
completeProgress();
} catch (err) {
console.error('[GenerationLoading] lenticular', err);
uni.showToast({ title: err.message || '光栅预览失败', icon: 'none' });
revertProgress();
}
};
const callImageGeneration = async () => {
try {
const res = await imageGenerationApi(generationData);
if (res.data?.images?.length > 0) {
uni.setStorageSync(GENERATED_IMAGES_KEY, JSON.stringify(res.data.images));
completeProgress();
} else {
uni.showToast({ title: '未生成图片', icon: 'none' });
revertProgress();
}
} catch (err) {
console.error('[GenerationLoading] API', err);
uni.showToast({ title: err.message || '生成失败', icon: 'none' });
revertProgress();
}
};
onMounted(() => {
flowStartedAt = Date.now();
simulateProgress();
try {
const flow = consumeGenerationFlowPayload();
// 镭射卡不走本页create → laser-thinking → laser-result见 startCraftGenerationFlow / navigateToStudioLoading
if (flow?.mode === FLOW_MODE_LENTICULAR) {
runLenticularFlow(flow);
return;
}
if (flow?.mode === FLOW_MODE_PREFILLED) {
runPrefilledFlow(flow);
return;
}
const requestDataStr = uni.getStorageSync(GENERATION_REQUEST_KEY);
if (!requestDataStr) {
uni.showToast({ title: '数据错误', icon: 'none' });
setTimeout(() => uni.navigateBack(), 1500);
return;
}
generationData = JSON.parse(requestDataStr);
const castloveDataStr = uni.getStorageSync(CASTLOVE_FORM_KEY);
if (castloveDataStr) {
const castloveData = JSON.parse(castloveDataStr);
if (generationData.subject_reference?.[0]) {
generationData.subject_reference[0].image_file =
castloveData.imageBase64 ||
castloveData.lenticularSubjectBase64 ||
'';
}
}
uni.removeStorageSync(GENERATION_REQUEST_KEY);
callImageGeneration();
} catch (e) {
console.error('[GenerationLoading] mount', e);
uni.showToast({ title: '数据错误', icon: 'none' });
setTimeout(() => uni.navigateBack(), 1500);
}
});
</script>
<style scoped>
.generation-loading {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
background: #000;
}
.background-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.gift-box {
position: absolute;
top: 25%;
left: 50%;
transform: translateX(-50%);
width: 480rpx;
height: 480rpx;
z-index: 2;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%,
100% {
transform: translateX(-50%) translateY(0) rotate(0deg);
}
50% {
transform: translateX(-50%) translateY(-20rpx) rotate(2deg);
}
}
.gift-image {
width: 100%;
height: 100%;
}
.progress-container {
position: absolute;
bottom: 25%;
left: 50%;
transform: translateX(-50%);
width: 600rpx;
display: flex;
flex-direction: column;
align-items: center;
z-index: 3;
}
.progress-bar-wrapper {
position: relative;
width: 100%;
height: 40rpx;
margin-bottom: 20rpx;
}
.progress-bar {
width: 100%;
height: 40rpx;
background: rgba(255, 255, 255, 0.3);
border-radius: 20rpx;
overflow: hidden;
border: 2rpx solid rgba(255, 255, 255, 0.5);
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #ff6b9d 0%, #ffa8c5 50%, #ffb6d9 100%);
border-radius: 20rpx;
transition: width 0.3s ease;
}
.progress-icon {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: 60rpx;
height: 60rpx;
transition: left 0.3s ease;
}
.icon-circle {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #ffb6d9 0%, #ff6b9d 100%);
border-radius: 50%;
border: 4rpx solid #ffffff;
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
.progress-text {
font-size: 56rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 10rpx;
}
.loading-text {
font-size: 36rpx;
color: #ffffff;
letter-spacing: 6rpx;
font-weight: 500;
}
</style>