682 lines
16 KiB
Vue
682 lines
16 KiB
Vue
<template>
|
||
<view class="county-my-tickets-page">
|
||
<view class="custom-header" :style="{ paddingTop: `${statusBarHeight}px` }">
|
||
<view class="header-inner">
|
||
<view class="header-back" @click="goBack">
|
||
<text class="header-back-icon">‹</text>
|
||
</view>
|
||
<view class="header-title">我的抽奖码</view>
|
||
<view class="header-right">
|
||
<view class="right-dot"></view>
|
||
<view class="right-ring"></view>
|
||
</view>
|
||
</view>
|
||
<view class="tabs">
|
||
<view
|
||
v-for="tab in tabs"
|
||
:key="tab.key"
|
||
class="tab-item"
|
||
:class="{ active: activeTab === tab.key }"
|
||
@click="onChangeTab(tab.key)"
|
||
>
|
||
{{ tab.label }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<scroll-view class="page-body" scroll-y :style="{ paddingTop: `calc(${statusBarHeight}px + 168rpx)` }">
|
||
<view class="tip-bar" v-if="summaryText">
|
||
<view class="tip-text one-t">{{ summaryText }}</view>
|
||
<view class="tip-btn" @click="goWinnersPage">查看全部 <text class="icon-expand">⤢</text></view>
|
||
</view>
|
||
|
||
<view class="ticket-list" v-if="displayList.length">
|
||
<view class="ticket-card" v-for="item in displayList" :key="item.id">
|
||
<view class="ticket-head">
|
||
<view class="ticket-code">
|
||
<text class="code-icon">🎟</text>
|
||
<text class="code-label">抽奖码:</text>
|
||
<text class="code-value">{{ item.ticketCode || '--' }}</text>
|
||
</view>
|
||
<view class="ticket-status" :class="[ `status-${item.status}`, item.status === 0 ? 'status-pill' : '', item.status === 1 && item.claimStatus === 1 ? 'status-claimed' : '' ]">{{ getStatusText(item) }}</view>
|
||
</view>
|
||
|
||
<view class="ticket-info-row">
|
||
<text class="label">来源订单</text>
|
||
<text class="value">{{ item.sourceOrderId || '--' }}</text>
|
||
</view>
|
||
<view class="ticket-info-row">
|
||
<text class="label">开奖时间</text>
|
||
<text class="value">{{ formatDrawTime(item.drawTime) }}</text>
|
||
</view>
|
||
|
||
<view class="prize-box" v-if="showPrize(item)">
|
||
恭喜获得:{{ item.prizeName || '神秘奖品' }}
|
||
</view>
|
||
|
||
<view class="ticket-footer" v-if="item.status === 1">
|
||
<template v-if="item.claimStatus === 0">
|
||
<view class="footer-note">注意:请在7天内领取,过期不予发放</view>
|
||
<view class="footer-btn btn-claim" @click="onClaim(item)">立 即 领 取</view>
|
||
</template>
|
||
<template v-else-if="item.claimStatus === 1">
|
||
<view class="footer-note done">奖品已领取</view>
|
||
<view class="footer-btn btn-order" @click="goOrder(item)">查看订单</view>
|
||
</template>
|
||
</view>
|
||
|
||
<view class="invalid-reason" v-if="item.status === -1">
|
||
失效原因:{{ getInvalidReason(item) }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="empty-wrap" v-else>
|
||
<view class="empty-text">{{ loading ? '加载中...' : '暂无抽奖码记录' }}</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view class="coupon-popup-mask" v-if="showCouponSuccessPopup" @click="closeCouponSuccessPopup">
|
||
<view class="coupon-popup" @click.stop>
|
||
<view class="popup-close" @click="closeCouponSuccessPopup">×</view>
|
||
<view class="popup-icon-wrap">
|
||
<text class="popup-icon">🎉</text>
|
||
</view>
|
||
<view class="popup-title">领取成功</view>
|
||
<view class="popup-desc">您的奖品已经领取成功,请前往“我的优惠券”查看详情</view>
|
||
<view class="popup-btn" @click="goMyCoupons">去查看我的优惠券</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { claimCoupon, getDrawMyTickets } from '@/api/lottery'
|
||
|
||
export default {
|
||
name: 'CountyMyTicketsPage',
|
||
data() {
|
||
return {
|
||
statusBarHeight: 20,
|
||
loading: false,
|
||
activeTab: 'all',
|
||
activityId: 0,
|
||
tabs: [
|
||
{ key: 'all', label: '全部' },
|
||
{ key: 'wait', label: '待开奖' },
|
||
{ key: 'opened', label: '已开奖' },
|
||
{ key: 'invalid', label: '已失效' }
|
||
],
|
||
list: [],
|
||
openedList: [],
|
||
showCouponSuccessPopup: false,
|
||
claiming: false
|
||
}
|
||
},
|
||
computed: {
|
||
displayList() {
|
||
return this.activeTab === 'opened' ? this.openedList : this.list
|
||
},
|
||
summaryText() {
|
||
return `恭喜海*在第01期抽奖活动中获得 iPhone17 pro 手机`
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
if (options && options.activityId) {
|
||
this.activityId = Number(options.activityId) || 0
|
||
}
|
||
},
|
||
onShow() {
|
||
this.statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 20
|
||
this.fetchList()
|
||
},
|
||
methods: {
|
||
mapTabToStatus(tab) {
|
||
const map = {
|
||
wait: 0,
|
||
invalid: -1
|
||
}
|
||
return map[tab]
|
||
},
|
||
normalizeListData(res) {
|
||
if (!res) return []
|
||
if (Array.isArray(res)) return res
|
||
if (Array.isArray(res.data)) return res.data
|
||
if (Array.isArray(res.data && res.data.list)) return res.data.list
|
||
if (Array.isArray(res.list)) return res.list
|
||
return []
|
||
},
|
||
normalizeTicketItem(item = {}) {
|
||
const statusNum = Number(item.status)
|
||
const claimStatusNum = Number(item.claimStatus)
|
||
return {
|
||
...item,
|
||
id: item.id || item.lotteryRecordId || item.ticketId || item.recordId || '',
|
||
ticketCode: item.ticketCode || item.code || '',
|
||
status: Number.isNaN(statusNum) ? -999 : statusNum,
|
||
claimStatus: Number.isNaN(claimStatusNum) ? 0 : claimStatusNum,
|
||
sourceOrderId: item.sourceOrderId || item.orderId || item.orderNo || '',
|
||
drawTime: item.drawTime || item.openTime || '',
|
||
prizeName: item.prizeName || item.awardName || '',
|
||
prizeType: item.prizeType || item.awardType || '',
|
||
prizeTypeName: item.prizeTypeName || item.awardTypeName || ''
|
||
}
|
||
},
|
||
filterByActivity(list = []) {
|
||
if (!this.activityId) return list
|
||
return list.filter(item => Number(item.activityId || item.drawActivityId || 0) === this.activityId)
|
||
},
|
||
filterByTab(list = []) {
|
||
if (this.activeTab === 'wait') {
|
||
return list.filter(item => item.status === 0)
|
||
}
|
||
if (this.activeTab === 'opened') {
|
||
return list.filter(item => item.status === 1 || item.status === 2)
|
||
}
|
||
if (this.activeTab === 'invalid') {
|
||
return list.filter(item => item.status === -1)
|
||
}
|
||
return list
|
||
},
|
||
onChangeTab(tab) {
|
||
if (this.activeTab === tab) return
|
||
this.activeTab = tab
|
||
this.fetchList()
|
||
},
|
||
goWinnersPage() {
|
||
uni.navigateTo({
|
||
url: `/pkg-video/views/county-winners?activityId=${this.activityId}`
|
||
})
|
||
},
|
||
async fetchList() {
|
||
this.loading = true
|
||
try {
|
||
const status = this.mapTabToStatus(this.activeTab)
|
||
const res = await getDrawMyTickets(status)
|
||
const allList = this.filterByActivity(this.normalizeListData(res).map(this.normalizeTicketItem))
|
||
this.openedList = allList.filter(item => item.status === 1 || item.status === 2)
|
||
this.list = this.filterByTab(allList)
|
||
} catch (e) {
|
||
this.list = []
|
||
this.openedList = []
|
||
uni.showToast({
|
||
title: '抽奖码加载失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
getStatusText(item) {
|
||
const status = Number(item.status)
|
||
if (status === 0) return '等待开奖'
|
||
if (status === 1) return item.claimStatus === 1 ? '已领取' : '已开奖'
|
||
if (status === 2) return '已开奖'
|
||
if (status === -1) return '已失效'
|
||
return '--'
|
||
},
|
||
showPrize(item) {
|
||
return [1, 2, -1].includes(Number(item.status)) && !!item.prizeName
|
||
},
|
||
getInvalidReason(item) {
|
||
if (Number(item.claimStatus) === -1) return '奖品过期未领取'
|
||
return '订单取消'
|
||
},
|
||
isCouponPrize(item = {}) {
|
||
const prizeType = String(item.prizeType || '').toLowerCase()
|
||
const prizeTypeName = String(item.prizeTypeName || '')
|
||
const prizeName = String(item.prizeName || '')
|
||
if (prizeType === 'coupon' || prizeType === '3') return true
|
||
return /券/.test(prizeTypeName) || /券/.test(prizeName)
|
||
},
|
||
formatDrawTime(time) {
|
||
if (!time) return '--'
|
||
const date = new Date(String(time).replace(/-/g, '/'))
|
||
if (Number.isNaN(date.getTime())) return time
|
||
const y = date.getFullYear()
|
||
const m = String(date.getMonth() + 1).padStart(2, '0')
|
||
const d = String(date.getDate()).padStart(2, '0')
|
||
const hh = String(date.getHours()).padStart(2, '0')
|
||
const mm = String(date.getMinutes()).padStart(2, '0')
|
||
return `${y}年${m}月${d}日 ${hh}:${mm}`
|
||
},
|
||
async onClaim(item) {
|
||
if (!item.id) return
|
||
if (this.claiming) return
|
||
if (this.isCouponPrize(item)) {
|
||
this.claiming = true
|
||
uni.showLoading({ title: '领取中...' })
|
||
try {
|
||
await claimCoupon(Number(item.id))
|
||
this.showCouponSuccessPopup = true
|
||
this.fetchList()
|
||
} catch (e) {
|
||
uni.showToast({
|
||
title: (e && e.msg) || (e && e.message) || '领取失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
uni.hideLoading()
|
||
this.claiming = false
|
||
}
|
||
return
|
||
}
|
||
const query = [
|
||
`lotteryRecordId=${item.id}`,
|
||
`ticketCode=${encodeURIComponent(item.ticketCode || '')}`,
|
||
`prizeName=${encodeURIComponent(item.prizeName || '')}`,
|
||
`prizeType=${encodeURIComponent(item.prizeType || '')}`,
|
||
`prizeTypeName=${encodeURIComponent(item.prizeTypeName || '')}`
|
||
].join('&')
|
||
uni.navigateTo({
|
||
url: `/pkg-video/views/county-claim-address?${query}`
|
||
})
|
||
},
|
||
closeCouponSuccessPopup() {
|
||
this.showCouponSuccessPopup = false
|
||
},
|
||
goMyCoupons() {
|
||
this.showCouponSuccessPopup = false
|
||
uni.navigateTo({
|
||
url: '/pkg_user/views/myCoupons'
|
||
})
|
||
},
|
||
goOrder(item) {
|
||
if (!item.sourceOrderId) {
|
||
uni.showToast({
|
||
title: '缺少订单号',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: `/pages/order/OrderDetails/index?id=${item.sourceOrderId}`
|
||
})
|
||
},
|
||
goBack() {
|
||
if (getCurrentPages().length > 1) {
|
||
uni.navigateBack()
|
||
return
|
||
}
|
||
uni.switchTab({
|
||
url: '/pages/home/index'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
.county-my-tickets-page {
|
||
min-height: 100vh;
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
.custom-header {
|
||
position: fixed;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
z-index: 20;
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
.header-inner {
|
||
height: 88rpx;
|
||
padding: 0 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.header-back {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
border-radius: 8rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.header-back-icon {
|
||
font-size: 60rpx;
|
||
color: #111;
|
||
line-height: 1;
|
||
margin-top: -4rpx;
|
||
}
|
||
|
||
.header-title {
|
||
flex: 1;
|
||
text-align: center;
|
||
margin-left: 56rpx;
|
||
font-size: 34rpx;
|
||
color: #333;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.header-right {
|
||
width: 88rpx;
|
||
height: 44rpx;
|
||
border-radius: 24rpx;
|
||
border: 2rpx solid #d8d8d8;
|
||
background: #fff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.right-dot {
|
||
width: 8rpx;
|
||
height: 8rpx;
|
||
border-radius: 50%;
|
||
background: #111;
|
||
margin-right: 14rpx;
|
||
}
|
||
|
||
.right-ring {
|
||
width: 18rpx;
|
||
height: 18rpx;
|
||
border-radius: 50%;
|
||
border: 2rpx solid #111;
|
||
}
|
||
|
||
.tabs {
|
||
height: 80rpx;
|
||
padding: 0 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
.tab-item {
|
||
margin-right: 20rpx;
|
||
padding: 8rpx 24rpx;
|
||
border-radius: 30rpx;
|
||
color: #666;
|
||
font-size: 26rpx;
|
||
background: #fff;
|
||
}
|
||
|
||
.tab-item.active {
|
||
color: #BD3124;
|
||
background: rgba(220,60,53,0.39);
|
||
}
|
||
|
||
.page-body {
|
||
box-sizing: border-box;
|
||
height: 100vh;
|
||
}
|
||
|
||
.tip-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 10rpx 24rpx;
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
background: #fff;
|
||
border-radius: 160rpx;
|
||
margin: 0 20rpx;
|
||
}
|
||
|
||
.tip-text {
|
||
flex: 1;
|
||
font-weight: 500;
|
||
color: #333;
|
||
}
|
||
|
||
.tip-btn {
|
||
background: #FCF9EA;
|
||
padding: 6rpx 16rpx;
|
||
border-radius: 20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
font-size: 22rpx;
|
||
color: #333;
|
||
margin-left: 10rpx;
|
||
}
|
||
|
||
.icon-expand {
|
||
font-size: 20rpx;
|
||
margin-left: 4rpx;
|
||
}
|
||
|
||
.ticket-list {
|
||
padding-top: 10rpx;
|
||
padding-bottom: 40rpx;
|
||
}
|
||
|
||
.ticket-card {
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
margin: 0 24rpx 20rpx;
|
||
padding: 24rpx;
|
||
}
|
||
|
||
.ticket-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding-bottom: 20rpx;
|
||
border-bottom: 2rpx dashed #eee;
|
||
margin-bottom: 20rpx;
|
||
position: relative;
|
||
}
|
||
|
||
.ticket-code {
|
||
display: flex;
|
||
align-items: center;
|
||
color: #333;
|
||
}
|
||
|
||
.code-icon {
|
||
margin-right: 8rpx;
|
||
color: #333;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.code-label {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
margin-right: 12rpx;
|
||
}
|
||
|
||
.code-value {
|
||
font-size: 34rpx;
|
||
font-weight: 700;
|
||
letter-spacing: 1rpx;
|
||
}
|
||
|
||
.ticket-status {
|
||
font-size: 26rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.status-pill {
|
||
background: #BD3124;
|
||
color: #fff;
|
||
padding: 4rpx 16rpx;
|
||
border-radius: 20rpx 0 0 20rpx;
|
||
font-size: 24rpx;
|
||
font-weight: normal;
|
||
position: absolute;
|
||
right: -24rpx;
|
||
}
|
||
|
||
.status-0 {
|
||
/* handled by pill */
|
||
}
|
||
|
||
.status-1,
|
||
.status-2 {
|
||
color: #BD3124;
|
||
}
|
||
|
||
.status-claimed {
|
||
color: #999 !important;
|
||
}
|
||
|
||
.status--1 {
|
||
color: #999;
|
||
}
|
||
|
||
.ticket-info-row {
|
||
margin-top: 14rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.ticket-info-row .label {
|
||
color: #999;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.ticket-info-row .value {
|
||
color: #333;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.prize-box {
|
||
margin-top: 24rpx;
|
||
border: 2rpx solid #BD3124;
|
||
border-radius: 40rpx;
|
||
color: #BD3124;
|
||
font-size: 30rpx;
|
||
text-align: center;
|
||
padding: 16rpx 0;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.ticket-footer {
|
||
margin-top: 20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.footer-note {
|
||
flex: 1;
|
||
color: #BD3124;
|
||
font-size: 22rpx;
|
||
}
|
||
|
||
.footer-note.done {
|
||
color: #BD3124;
|
||
}
|
||
|
||
.footer-btn {
|
||
width: 170rpx;
|
||
height: 54rpx;
|
||
border-radius: 30rpx;
|
||
color: #fff;
|
||
font-size: 26rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.btn-claim {
|
||
background: #BD3124;
|
||
}
|
||
|
||
.btn-order {
|
||
background: #BD3124;
|
||
}
|
||
|
||
.invalid-reason {
|
||
margin-top: 14rpx;
|
||
font-size: 24rpx;
|
||
color: #BD3124;
|
||
}
|
||
|
||
.empty-wrap {
|
||
padding-top: 120rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.empty-text {
|
||
color: #999;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.coupon-popup-mask {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 1000;
|
||
background: rgba(0, 0, 0, 0.55);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 24rpx;
|
||
}
|
||
|
||
.coupon-popup {
|
||
width: 100%;
|
||
max-width: 620rpx;
|
||
background: #fff;
|
||
border-radius: 32rpx;
|
||
padding: 26rpx 34rpx 42rpx;
|
||
position: relative;
|
||
}
|
||
|
||
.popup-close {
|
||
position: absolute;
|
||
right: 18rpx;
|
||
top: 18rpx;
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
border-radius: 50%;
|
||
border: 2rpx solid #333;
|
||
font-size: 40rpx;
|
||
font-weight: 700;
|
||
color: #333;
|
||
line-height: 40rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.popup-icon-wrap {
|
||
width: 112rpx;
|
||
height: 112rpx;
|
||
margin: 20rpx auto 8rpx;
|
||
border-radius: 8rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.popup-icon {
|
||
font-size: 70rpx;
|
||
}
|
||
|
||
.popup-title {
|
||
text-align: center;
|
||
font-size: 60rpx;
|
||
font-weight: 900;
|
||
color: #222;
|
||
margin-top: 12rpx;
|
||
}
|
||
|
||
.popup-desc {
|
||
margin: 38rpx auto 42rpx;
|
||
text-align: center;
|
||
font-size: 28rpx;
|
||
line-height: 1.6;
|
||
color: #9a9a9a;
|
||
width: 92%;
|
||
}
|
||
|
||
.popup-btn {
|
||
width: 86%;
|
||
height: 96rpx;
|
||
margin: 0 auto;
|
||
border-radius: 48rpx;
|
||
background: #c43124;
|
||
color: #fff;
|
||
font-size: 40rpx;
|
||
font-weight: 900;
|
||
text-align: center;
|
||
line-height: 96rpx;
|
||
letter-spacing: 2rpx;
|
||
}
|
||
</style>
|