229 lines
6.0 KiB
Vue
229 lines
6.0 KiB
Vue
<template>
|
|
<view class="flip-root">
|
|
<view v-if="controls" class="flip-actions">
|
|
<button class="flip-btn" @tap="onDraw(1)">单次抽选</button>
|
|
<button class="flip-btn" @tap="onDraw(10)">十次抽选</button>
|
|
</view>
|
|
<view class="flip-grid">
|
|
<view v-for="(cell, i) in cells" :key="i" class="flip-card" :class="{ flipped: cell.flipped }">
|
|
<view class="flip-inner">
|
|
<view class="flip-front">
|
|
<view class="front-placeholder"></view>
|
|
</view>
|
|
<view class="flip-back" @tap="onPreview(cell)">
|
|
<image v-if="cell.image" class="flip-image" :src="cell.image" mode="widthFix" />
|
|
<text class="flip-title">{{ cell.title || '' }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view v-if="controls" class="flip-toolbar">
|
|
<button class="flip-reset" @tap="reset">重置</button>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref } from 'vue'
|
|
|
|
const props = defineProps({ rewards: { type: Array, default: () => [] }, controls: { type: Boolean, default: true } })
|
|
const emit = defineEmits(['draw'])
|
|
|
|
const total = 16
|
|
const cells = ref(Array(total).fill(0).map(() => ({ flipped: false, title: '', image: '' })))
|
|
|
|
function onDraw(count) { emit('draw', count) }
|
|
|
|
function revealResults(list) {
|
|
const arr = Array.isArray(list) ? list : list ? [list] : []
|
|
const toFill = Math.min(arr.length, total)
|
|
const indices = Array(total).fill(0).map((_, i) => i)
|
|
for (let i = indices.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); const tmp = indices[i]; indices[i] = indices[j]; indices[j] = tmp }
|
|
const chosen = indices.slice(0, toFill)
|
|
const res = arr.slice(0, toFill)
|
|
for (let i = res.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); const t = res[i]; res[i] = res[j]; res[j] = t }
|
|
chosen.forEach((pos, i) => {
|
|
const it = res[i] || {}
|
|
const title = String(it.title || it.name || '')
|
|
const image = String(it.image || it.img || it.pic || '')
|
|
cells.value[pos] = { flipped: false, title, image }
|
|
const delay = 100 * i + Math.floor(Math.random() * 120)
|
|
setTimeout(() => { cells.value[pos].flipped = true }, delay)
|
|
})
|
|
}
|
|
|
|
function reset() {
|
|
cells.value = Array(total).fill(0).map(() => ({ flipped: false, title: '', image: '' }))
|
|
}
|
|
|
|
function onPreview(cell) {
|
|
const img = String(cell && cell.image || '')
|
|
if (img) uni.previewImage({ urls: [img], current: img })
|
|
}
|
|
|
|
defineExpose({ revealResults, reset })
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
/* ============================================
|
|
柯大鸭潮玩 - 翻牌动画组件
|
|
采用暖橙色调的开箱效果
|
|
============================================ */
|
|
|
|
.flip-root {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: $spacing-md;
|
|
padding: $spacing-md;
|
|
}
|
|
|
|
.flip-actions {
|
|
display: flex;
|
|
gap: $spacing-sm;
|
|
}
|
|
.flip-btn {
|
|
flex: 1;
|
|
background: $gradient-brand !important;
|
|
color: #FFFFFF !important;
|
|
border-radius: $radius-md;
|
|
font-weight: 600;
|
|
box-shadow: $shadow-md;
|
|
border: none;
|
|
font-size: $font-md;
|
|
transition: all 0.2s ease;
|
|
|
|
&:active {
|
|
transform: scale(0.97);
|
|
box-shadow: $shadow-sm;
|
|
}
|
|
}
|
|
|
|
.flip-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: $spacing-sm;
|
|
background: rgba(255, 255, 255, 0.5);
|
|
backdrop-filter: blur(10px);
|
|
border-radius: $radius-lg;
|
|
padding: $spacing-sm;
|
|
box-shadow: inset 0 0 20rpx rgba(255, 255, 255, 0.5);
|
|
}
|
|
|
|
.flip-card {
|
|
perspective: 1200px;
|
|
transform: translateZ(0);
|
|
}
|
|
|
|
.flip-inner {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 220rpx;
|
|
transform-style: preserve-3d;
|
|
-webkit-transform-style: preserve-3d;
|
|
transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
|
will-change: transform;
|
|
}
|
|
|
|
.flip-card.flipped .flip-inner {
|
|
transform: rotateY(180deg);
|
|
animation: flip-reveal 0.9s cubic-bezier(0.2, 0.9, 0.2, 1) both;
|
|
}
|
|
|
|
.flip-card.flipped {
|
|
animation: flip-pop 0.35s ease-out;
|
|
}
|
|
|
|
.flip-front, .flip-back {
|
|
position: absolute;
|
|
width: 100%;
|
|
height: 100%;
|
|
backface-visibility: hidden;
|
|
-webkit-backface-visibility: hidden;
|
|
border-radius: $radius-md;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.flip-front {
|
|
background: linear-gradient(145deg, #FFF8F3, #FFE8D1);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: 2rpx solid rgba($brand-primary, 0.2);
|
|
box-shadow: $shadow-sm;
|
|
}
|
|
|
|
.front-placeholder {
|
|
width: 60%;
|
|
height: 60%;
|
|
border-radius: $radius-md;
|
|
background: linear-gradient(135deg, rgba($brand-primary, 0.3), rgba($brand-primary-light, 0.2));
|
|
animation: pulse 2s ease-in-out infinite;
|
|
}
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 0.6; transform: scale(1); }
|
|
50% { opacity: 1; transform: scale(1.05); }
|
|
}
|
|
|
|
@keyframes flip-pop {
|
|
0% { transform: translateZ(0) scale(1); }
|
|
60% { transform: translateZ(0) scale(1.06); }
|
|
100% { transform: translateZ(0) scale(1); }
|
|
}
|
|
|
|
@keyframes flip-reveal {
|
|
0% { transform: rotateY(0deg) rotateX(0deg) rotateZ(0deg) scale(1); }
|
|
35% { transform: rotateY(120deg) rotateX(14deg) rotateZ(-6deg) scale(1.08); }
|
|
70% { transform: rotateY(210deg) rotateX(-10deg) rotateZ(4deg) scale(1.02); }
|
|
100% { transform: rotateY(180deg) rotateX(0deg) rotateZ(0deg) scale(1); }
|
|
}
|
|
|
|
.flip-back {
|
|
background: $bg-card;
|
|
transform: rotateY(180deg);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: $spacing-sm;
|
|
border: 2rpx solid rgba($brand-primary, 0.3);
|
|
box-shadow: $shadow-md;
|
|
}
|
|
|
|
.flip-image {
|
|
width: 75%;
|
|
border-radius: $radius-sm;
|
|
margin-bottom: $spacing-xs;
|
|
background: linear-gradient(145deg, #FFF8F3, #FFF4E6);
|
|
}
|
|
|
|
.flip-title {
|
|
font-size: $font-xs;
|
|
font-weight: 600;
|
|
color: $text-main;
|
|
text-align: center;
|
|
max-width: 90%;
|
|
word-break: break-all;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.flip-toolbar {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.flip-reset {
|
|
background: linear-gradient(135deg, $accent-gold, $brand-primary-light) !important;
|
|
color: #6b4b1f !important;
|
|
border-radius: 999rpx;
|
|
font-weight: 600;
|
|
box-shadow: $shadow-sm;
|
|
font-size: $font-sm;
|
|
padding: 0 40rpx;
|
|
transition: all 0.2s ease;
|
|
|
|
&:active {
|
|
transform: scale(0.96);
|
|
}
|
|
}
|
|
</style>
|