topfans/frontend/pages/castlove/laser/laser-result.vue
2026-06-03 22:19:22 +08:00

305 lines
6.7 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="laser-result-page">
<image class="background-image" src="/static/background/exhibitionSuccess.png" mode="aspectFill" />
<!-- 礼盒打开瞬间的全局扫光 -->
<view v-if="showOpeningSweep" class="opening-sweep" />
<!-- 5 张候选卡的金字塔 -->
<view class="cards-container" :class="{ 'cards-visible': isGiftOpened }">
<LaserVariantPyramid
:paths="generatedImages"
:selectedIndex="selectedIndex"
:opened="isGiftOpened"
@select="handleSelect"
/>
</view>
<!-- 礼盒 -->
<view class="gift-box" :class="{ 'gift-opened': isGiftOpened }">
<image v-if="!isGiftOpened" class="gift-image" src="/static/nft/lihe.png" mode="aspectFit" />
<image
v-else
class="gift-image gift-image-opened"
src="/static/nft/lihe_kaiqi.png"
mode="aspectFit"
/>
</view>
<!-- 底部操作 -->
<view class="bottom-action">
<view class="action-button action-button--secondary" @tap="handleBack">
<text class="button-text">重新选择</text>
</view>
<view class="action-button" @tap="selectAsset">
<text class="button-text">开始铸造</text>
</view>
</view>
<!-- 铸造确认弹窗 -->
<ConfirmModal
v-if="showConfirmModal"
:visible="showConfirmModal"
:cost-crystal="confirmCostInfo.costCrystal"
:current-balance="confirmCostInfo.currentBalance"
:mint-count="confirmCostInfo.mintCount"
:next-tier-cost="confirmCostInfo.nextTierCost"
@confirm="handleConfirmMint"
@cancel="handleCancelMint"
/>
</view>
</template>
<script setup>
import { computed, onMounted, ref, watch } from 'vue'
import ConfirmModal from '@/components/ConfirmModal.vue'
import LaserVariantPyramid from '@/components/laser/LaserVariantPyramid.vue'
import { useLaserMint } from '@/composables/useLaserMint.js'
import { CASTLOVE_FORM_KEY, GENERATED_IMAGES_KEY, GENERATION_RESULT_META_KEY } from '@/utils/castloveGenerationFlow.js'
const generatedImages = ref([])
const selectedIndex = ref(-1)
const instanceNo = ref('')
const craftFormData = ref(null)
const isGiftOpened = ref(false)
const showOpeningSweep = ref(false)
/**
* 从 generatedImages 中提取 URL兼容旧格式纯字符串和新格式 { url, oss_key }
*/
const resolveImageUrl = (item) => {
if (!item) return ''
return typeof item === 'object' && item.url ? item.url : String(item)
}
/**
* 从 generatedImages 中提取 oss_key仅新格式有值
*/
const resolveOssKey = (item) => {
if (!item) return ''
return typeof item === 'object' ? (item.oss_key || '') : ''
}
const selectedImagePath = computed(() => resolveImageUrl(generatedImages.value?.[selectedIndex.value]))
const getSelectedImagePath = () => resolveImageUrl(generatedImages.value?.[selectedIndex.value])
const getSelectedOssKey = () => resolveOssKey(generatedImages.value?.[selectedIndex.value])
const {
showConfirmModal,
confirmCostInfo,
selectAsset,
handleConfirmMint,
handleCancelMint,
} = useLaserMint({
getSelectedImagePath,
getSelectedOssKey,
getSelectedPresetIndex: () => selectedIndex.value,
formDataRef: craftFormData,
getInstanceNo: () => instanceNo.value,
})
const handleSelect = (idx) => {
if (selectedIndex.value === idx) return
selectedIndex.value = idx
}
const handleBack = () => {
uni.navigateBack({ delta: 1 })
}
// 礼盒打开后:白光扫光 → 选中默认卡(克制,无彩虹过曝)
const playOpenSequence = () => {
showOpeningSweep.value = true
setTimeout(() => {
showOpeningSweep.value = false
}, 700)
setTimeout(() => {
if (selectedIndex.value < 0) {
selectedIndex.value = 2 // 默认选中金字塔顶部那张
}
}, 900)
}
onMounted(() => {
try {
const formStr = uni.getStorageSync(CASTLOVE_FORM_KEY)
if (formStr) craftFormData.value = JSON.parse(formStr)
const imagesStr = uni.getStorageSync(GENERATED_IMAGES_KEY)
if (imagesStr) {
generatedImages.value = JSON.parse(imagesStr)
}
const metaStr = uni.getStorageSync(GENERATION_RESULT_META_KEY)
if (metaStr) {
const meta = JSON.parse(metaStr)
instanceNo.value = meta.instanceNo || ''
}
} catch (e) {
console.error('[laser-result] init failed', e)
uni.showToast({ title: '数据错误', icon: 'none' })
setTimeout(() => uni.navigateBack(), 1200)
}
// 800ms 礼盒打开 + 进场动效序列
setTimeout(() => {
isGiftOpened.value = true
playOpenSequence()
}, 800)
})
</script>
<style scoped>
.laser-result-page {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
background: #000;
}
.background-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
/* 礼盒打开瞬间的全局扫光:克制白光从右上到左下斜切(不抢戏) */
.opening-sweep {
position: absolute;
inset: 0;
z-index: 50;
pointer-events: none;
background: linear-gradient(
115deg,
transparent 38%,
rgba(255, 255, 255, 0.45) 50%,
rgba(220, 230, 240, 0.25) 60%,
transparent 75%
);
background-size: 250% 250%;
background-position: 120% -120%;
animation: opening-sweep-anim 0.7s ease-out forwards;
}
@keyframes opening-sweep-anim {
0% {
background-position: 120% -120%;
opacity: 1;
}
100% {
background-position: -120% 220%;
opacity: 0;
}
}
.cards-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 20;
opacity: 0;
pointer-events: none;
transition: opacity 0.5s ease-in-out;
}
.cards-container.cards-visible {
opacity: 1;
pointer-events: auto;
}
.gift-box {
position: absolute;
bottom: 20%;
left: 50%;
transform: translateX(-50%);
width: 480rpx;
height: 480rpx;
z-index: 3;
animation: giftFloat 3s ease-in-out infinite;
transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}
.gift-box.gift-opened {
width: 17rem;
bottom: 15%;
animation: none;
}
@keyframes giftFloat {
0%,
100% {
transform: translateX(-50%) translateY(0);
}
50% {
transform: translateX(-50%) translateY(-20rpx);
}
}
.gift-image {
width: 100%;
height: 100%;
transition: opacity 0.5s ease-in-out;
}
.gift-image-opened {
width: 17rem;
animation: giftOpenPop 0.5s ease-out;
}
@keyframes giftOpenPop {
0% {
transform: scale(0.8);
opacity: 0;
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
opacity: 1;
}
}
.bottom-action {
position: absolute;
left: 0;
right: 0;
bottom: 70rpx;
z-index: 30;
display: flex;
justify-content: center;
gap: 24rpx;
padding: 0 40rpx;
}
.action-button {
min-width: 260rpx;
height: 92rpx;
border-radius: 50rpx;
display: flex;
align-items: center;
justify-content: center;
background-image: url('/static/icon/confirmbj.png');
background-size: cover;
color: #fff;
font-weight: 700;
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.35);
}
.action-button--secondary {
background-image: none;
background: rgba(255, 255, 255, 0.12);
border: 2rpx solid rgba(255, 255, 255, 0.45);
}
.button-text {
font-size: 34rpx;
}
</style>