430 lines
10 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>
<ActivityPageLayout :cover-url="coverUrl">
<template #header>
<ActivityHeader
:title="detail.name || detail.title || '一番赏活动'"
:price="detail.price_draw"
price-unit="/"
:cover-url="coverUrl"
:tags="['公开透明', '拒绝套路']"
:scheduled-time="scheduledTimeText"
@show-rules="showRules"
@go-cabinet="goCabinet"
/>
</template>
<template #content>
<!-- 赏品概览 -->
<ActivityTabs v-model="tabActive" :stagger="1">
<template #pool>
<RewardsPreview
title="奖品配置"
:rewards="currentIssueRewards"
:grouped="false"
@view-all="rewardsVisible = true"
/>
</template>
<template #records>
<RecordsList :records="winRecords" />
</template>
</ActivityTabs>
<!-- 选号区域一番赏专属 -->
<view class="section-container selector-container animate-enter stagger-2">
<!-- 期号切换 -->
<view class="issue-header">
<view class="issue-switch-btn" @click="prevIssue">
<text class="arrow"></text>
</view>
<view class="issue-info-center">
<text class="issue-current-text">{{ currentIssueTitle }}</text>
<text class="issue-status-badge">进行中</text>
</view>
<view class="issue-switch-btn" @click="nextIssue">
<text class="arrow"></text>
</view>
</view>
<view class="issue-block-tip" v-if="!isOrderAllowed">
<text class="issue-block-text">{{ orderBlockedReason }}</text>
</view>
<!-- 选号组件 -->
<view class="selector-body" v-if="activityId && currentIssueId">
<YifanSelector
:activity-id="activityId"
:issue-id="currentIssueId"
:price-per-draw="Number(detail.price_draw || 0) / 100"
:disabled="!isOrderAllowed"
:disabled-text="orderBlockedReason"
@payment-success="onPaymentSuccess"
/>
</view>
</view>
</template>
<template #modals>
<!-- 翻牌弹窗 -->
<view v-if="showFlip" class="flip-overlay" @touchmove.stop.prevent>
<view class="flip-mask" @tap="closeFlip"></view>
<view class="flip-content" @tap.stop>
<FlipGrid ref="flipRef" :rewards="currentIssueRewards" :controls="false" />
<button class="overlay-close" @tap="closeFlip">关闭</button>
</view>
</view>
<!-- 奖品弹窗 -->
<RewardsPopup
v-model:visible="rewardsVisible"
:title="`${currentIssueTitle} · 奖品与概率`"
:reward-groups="rewardGroups"
/>
</template>
</ActivityPageLayout>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { onLoad, onUnload } from '@dcloudio/uni-app'
// 公共组件 - uni-app需要直接导入.vue文件
import ActivityPageLayout from '@/components/activity/ActivityPageLayout.vue'
import ActivityHeader from '@/components/activity/ActivityHeader.vue'
import ActivityTabs from '@/components/activity/ActivityTabs.vue'
import RewardsPreview from '@/components/activity/RewardsPreview.vue'
import RewardsPopup from '@/components/activity/RewardsPopup.vue'
import RecordsList from '@/components/activity/RecordsList.vue'
import FlipGrid from '@/components/FlipGrid.vue'
import YifanSelector from '@/components/YifanSelector.vue'
// Composables
import { useActivity, useIssues, useRewards, useRecords } from '@/composables'
// Utils
import { formatDateTime, parseTimeMs } from '@/utils/format'
// ============ 使用Composables ============
const activityId = ref('')
const {
detail,
coverUrl,
fetchDetail,
setNavigationTitle
} = useActivity(activityId)
const {
issues,
currentIssueId,
currentIssueTitle,
fetchIssues,
prevIssue,
nextIssue
} = useIssues(activityId)
const {
currentIssueRewards,
rewardGroups,
fetchRewardsForIssues
} = useRewards(activityId, currentIssueId)
const {
winRecords,
fetchWinRecords
} = useRecords()
// ============ 本地状态 ============
const tabActive = ref('pool')
const rewardsVisible = ref(false)
const showFlip = ref(false)
const flipRef = ref(null)
// ============ 倒计时相关(一番赏专属) ============
const nowMs = ref(Date.now())
let nowTimer = null
function startNowTicker() {
stopNowTicker()
nowMs.value = Date.now()
nowTimer = setInterval(() => { nowMs.value = Date.now() }, 1000)
}
function stopNowTicker() {
if (nowTimer) {
clearInterval(nowTimer)
nowTimer = null
}
}
const scheduledTime = computed(() => detail.value?.scheduled_time || detail.value?.scheduledTime || '')
const scheduledTimeMs = computed(() => parseTimeMs(scheduledTime.value))
const scheduledTimeText = computed(() => formatDateTime(scheduledTime.value))
const remainMs = computed(() => {
const end = scheduledTimeMs.value
if (!end) return null
return end - nowMs.value
})
const isOrderAllowed = computed(() => {
const ms = remainMs.value
if (ms === null) return true
return ms > 25000
})
const orderBlockedReason = computed(() => {
const ms = remainMs.value
if (ms === null) return ''
if (ms <= 0) return '本期已结束,暂不可下单'
if (ms <= 25000) return '距本期结束不足25秒暂不可下单'
return ''
})
// ============ 业务方法 ============
function showRules() {
uni.showModal({
title: '活动规则',
content: detail.value.rules || '1. 选择号码进行抽选\n2. 每个号码对应一个奖品\n3. 已售号码不可再选\n4.未满足开赏条件,将自动为所有参与用户退款,款项将原路返回',
showCancel: false
})
}
function goCabinet() {
uni.switchTab({ url: '/pages/cabinet/index' })
}
function closeFlip() {
showFlip.value = false
}
function onPaymentSuccess(payload) {
console.log('Payment Success:', payload)
const result = payload.result
const status = String(result?.status || result?.data?.status || result?.result?.status || '')
if (status === 'paid_waiting') {
const next = result?.next_draw_time || result?.nextDrawTime || result?.next_draw_at || result?.nextDrawAt
const nextText = next ? formatDateTime(next) : ''
const content = nextText
? `下单成功,等待系统自动开启本期赏品。\n预计开赏时间${nextText}`
: '下单成功,等待系统自动开启本期赏品。'
uni.showModal({
title: '下单成功',
content,
showCancel: false
})
return
}
let wonItems = []
if (Array.isArray(result)) {
wonItems = result
} else if (result?.list) {
wonItems = result.list
} else if (result?.data) {
wonItems = result.data
} else if (result?.rewards) {
wonItems = result.rewards
} else {
wonItems = result ? [result] : []
}
const items = wonItems.map(data => ({
title: String(data?.title || data?.name || data?.product_name || data?.reward_name || '未知奖励'),
image: String(data?.image || data?.img || data?.pic || data?.product_image || data?.reward_image || '')
}))
showFlip.value = true
try { flipRef.value?.reset?.() } catch (_) {}
setTimeout(() => {
flipRef.value?.revealResults?.(items)
}, 100)
}
// ============ 生命周期 ============
onLoad(async (opts) => {
startNowTicker()
const id = opts?.id || ''
if (id) {
activityId.value = id
await fetchDetail()
setNavigationTitle('一番赏')
await fetchIssues()
await fetchRewardsForIssues(issues.value)
if (currentIssueId.value) {
fetchWinRecords(id, currentIssueId.value)
}
}
})
onUnload(() => {
stopNowTicker()
})
// 监听期切换,刷新记录
watch(currentIssueId, (newId) => {
if (newId && activityId.value) {
fetchWinRecords(activityId.value, newId)
}
})
</script>
<style lang="scss" scoped>
/* 选号容器 - 与原始设计一致 */
.section-container {
margin: 0 $spacing-lg $spacing-lg;
background: rgba(255, 255, 255, 0.9);
border-radius: $radius-xl;
padding: $spacing-lg;
box-shadow: $shadow-sm;
backdrop-filter: blur(10rpx);
}
.selector-container {
margin-top: $spacing-md;
}
/* 期号切换 - 与原始设计一致 */
.issue-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 30rpx;
background: $bg-grey;
border-radius: $radius-round;
padding: 10rpx;
border: 1rpx solid $border-color-light;
}
.issue-switch-btn {
width: 72rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
background: $bg-card;
border-radius: 50%;
box-shadow: $shadow-sm;
transition: all 0.2s;
color: $text-secondary;
&:active {
transform: scale(0.9);
background: $bg-secondary;
color: $brand-primary;
}
}
.arrow {
font-size: $font-sm;
font-weight: 800;
}
.issue-info-center {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.issue-current-text {
font-size: $font-lg;
font-weight: 700;
color: $text-main;
}
.issue-status-badge {
font-size: $font-xs;
color: $uni-color-success;
background: rgba($uni-color-success, 0.1);
padding: 2rpx $spacing-md;
border-radius: $radius-round;
margin-top: 4rpx;
font-weight: 600;
}
.issue-block-tip {
background: rgba($color-warning, 0.1);
padding: $spacing-sm $spacing-md;
border-radius: $radius-md;
margin-bottom: $spacing-md;
}
.issue-block-text {
font-size: $font-sm;
color: $color-warning;
font-weight: 500;
}
.selector-body {
margin-top: $spacing-sm;
}
/* 入场动画 */
.animate-enter {
animation: slideUp 0.5s ease-out both;
}
.stagger-2 {
animation-delay: 0.2s;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 翻牌弹窗 */
.flip-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1001;
display: flex;
align-items: center;
justify-content: center;
}
.flip-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
}
.flip-content {
position: relative;
width: 90%;
max-height: 85vh;
background: $bg-card;
border-radius: $radius-xl;
padding: $spacing-lg;
overflow: hidden;
}
.overlay-close {
margin-top: $spacing-lg;
width: 100%;
background: $gradient-brand;
color: #fff;
border: none;
border-radius: $radius-lg;
font-size: $font-md;
font-weight: 600;
padding: $spacing-md;
&::after {
border: none;
}
}
</style>