Feat:抽奖2.0(中奖名单缺中奖等级,优惠券领取接口异常)

This commit is contained in:
LinCyJang
2026-04-29 00:52:34 +08:00
parent a14d89efff
commit 092ef55293
8 changed files with 662 additions and 405 deletions
+2 -4
View File
@@ -7,15 +7,14 @@
</view>
<view class="header-title">活动规则</view>
<view class="header-right">
<view class="right-dot"></view>
<view class="right-ring"></view>
<!-- <view class="right-dot"></view>
<view class="right-ring"></view> -->
</view>
</view>
</view>
<view class="page-body" :style="{ paddingTop: `${statusBarHeight + 88}px` }">
<view class="rule-card">
<view class="rule-title">香道汉平台满额抽奖活动规则</view>
<view class="rule-content">
<rich-text v-if="ruleContent" :nodes="ruleContent" />
<view class="state-text" v-else>{{ loading ? '规则加载中...' : '暂无活动规则' }}</view>
@@ -90,7 +89,6 @@ export default {
width: 56rpx;
height: 56rpx;
border-radius: 8rpx;
border: 2rpx solid #d8d8d8;
display: flex;
align-items: center;
justify-content: center;
+15 -19
View File
@@ -50,21 +50,28 @@
<view class="pool-desc">实付满{{ buyThresholdText }}元即可获得抽奖码定时统一开奖</view>
</view>
<view class="pool-body">
<swiper class="pool-swiper" :autoplay="true" :circular="true" :interval="5000" :duration="500" v-if="chunkedPrizeList.length">
<swiper-item v-for="(group, gIndex) in chunkedPrizeList" :key="gIndex">
<swiper
class="pool-swiper"
:autoplay="prizeList.length > 3"
:circular="prizeList.length > 3"
:interval="3000"
:duration="500"
:display-multiple-items="prizeList.length >= 3 ? 3 : prizeList.length"
v-if="prizeList.length"
>
<swiper-item v-for="(prize, gIndex) in prizeList" :key="gIndex">
<view class="pool-list-group">
<view
v-for="(item, index) in group"
:key="index"
class="pool-item"
style="margin: 0 auto;"
>
<view class="item-inner">
<view class="item-dashed">
<image class="pool-image" :src="item.prizeImage" mode="aspectFit" />
<image class="pool-image" :src="prize.prizeImage" mode="aspectFit" />
</view>
</view>
<image class="prize-tag" :src="item.levelIcon" v-if="item.levelIcon" mode="aspectFit" />
<view class="pool-name one-t">{{ item.prizeName }}</view>
<image class="prize-tag" :src="prize.levelIcon" v-if="prize.levelIcon" mode="aspectFit" />
<view class="pool-name one-t">{{ prize.prizeName }}</view>
</view>
</view>
</swiper-item>
@@ -152,16 +159,6 @@ export default {
prizeList() {
return this.activityInfo.prizePool || []
},
chunkedPrizeList() {
const list = this.prizeList
if (!list || !list.length) return []
const chunkSize = 3
const result = []
for (let i = 0; i < list.length; i += chunkSize) {
result.push(list.slice(i, i + chunkSize))
}
return result
},
activityGoodsList() {
const list = this.activityInfo.products || []
if (list.length > 0) {
@@ -331,7 +328,7 @@ export default {
uniqueId: currentSelect.unique || ''
}
if (this.activityId) {
q.activityId = Number(this.activityId)
q.drawActivityId = Number(this.activityId)
}
postCartAdd(q).then(res => {
this.isOpen = false
@@ -608,7 +605,6 @@ export default {
.pool-list-group {
display: flex;
flex-wrap: nowrap;
width: 100%;
}
.pool-item {
+191 -67
View File
@@ -75,11 +75,23 @@
<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 { getDrawMyTickets } from '@/api/lottery'
import { claimCoupon, getDrawMyTickets } from '@/api/lottery'
export default {
name: 'CountyMyTicketsPage',
@@ -88,7 +100,7 @@ export default {
statusBarHeight: 20,
loading: false,
activeTab: 'all',
activityId: 1,
activityId: 0,
tabs: [
{ key: 'all', label: '全部' },
{ key: 'wait', label: '待开奖' },
@@ -96,7 +108,9 @@ export default {
{ key: 'invalid', label: '已失效' }
],
list: [],
openedList: []
openedList: [],
showCouponSuccessPopup: false,
claiming: false
}
},
computed: {
@@ -109,7 +123,7 @@ export default {
},
onLoad(options) {
if (options && options.activityId) {
this.activityId = Number(options.activityId) || 1
this.activityId = Number(options.activityId) || 0
}
},
onShow() {
@@ -124,6 +138,46 @@ export default {
}
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
@@ -134,69 +188,24 @@ export default {
url: `/pkg-video/views/county-winners?activityId=${this.activityId}`
})
},
fetchList() {
async fetchList() {
this.loading = true
// 使用模拟数据以匹配设计图所有状态
setTimeout(() => {
const mockData = [
{
id: 1,
ticketCode: 'A8F92E',
status: 0,
sourceOrderId: '12345678910',
drawTime: '2026-05-30 10:00:00',
},
{
id: 2,
ticketCode: 'A8F92E',
status: 1,
claimStatus: 0,
sourceOrderId: '12345678910',
drawTime: '2026-05-30 10:00:00',
prizeName: '一等奖ipone17 pro max手机'
},
{
id: 3,
ticketCode: 'A8F92E',
status: 1,
claimStatus: 1,
sourceOrderId: '12345678910',
drawTime: '2026-05-30 10:00:00',
prizeName: '一等奖ipone17 pro max手机'
},
{
id: 4,
ticketCode: 'A8F92E',
status: -1,
claimStatus: 0,
sourceOrderId: '12345678910',
drawTime: '2026-05-30 10:00:00',
},
{
id: 5,
ticketCode: 'A8F92E',
status: -1,
claimStatus: -1,
sourceOrderId: '12345678910',
drawTime: '2026-05-30 10:00:00',
prizeName: '一等奖ipone17 pro max手机'
}
];
let filteredData = mockData;
if (this.activeTab === 'wait') {
filteredData = mockData.filter(item => item.status === 0);
} else if (this.activeTab === 'opened') {
filteredData = mockData.filter(item => item.status === 1 || item.status === 2);
} else if (this.activeTab === 'invalid') {
filteredData = mockData.filter(item => item.status === -1);
}
this.list = filteredData;
this.openedList = mockData.filter(item => item.status === 1 || item.status === 2);
this.loading = false;
}, 300);
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)
@@ -213,6 +222,13 @@ export default {
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, '/'))
@@ -224,8 +240,27 @@ export default {
const mm = String(date.getMinutes()).padStart(2, '0')
return `${y}${m}${d}${hh}:${mm}`
},
onClaim(item) {
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 || '')}`,
@@ -237,6 +272,15 @@ export default {
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({
@@ -554,4 +598,84 @@ export default {
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>
+142 -29
View File
@@ -10,14 +10,14 @@
</view>
<view class="content-wrap" v-if="loaded">
<view class="draw-info-wrap" :style="{ backgroundImage: `url(${webUrl}/zhongjiang5.png)` }">
<view v-if="!showHistory" class="draw-info-wrap" :style="{ backgroundImage: `url(${webUrl}/zhongjiang5.png)` }">
<view class="period-side left">
<text class="side-text"></text>
<text class="side-text">{{ periodLabel }}</text>
<text class="side-text"></text>
<text class="side-text v12-font-36"></text>
<text class="side-text v12-font-36">{{ periodLabel }}</text>
<text class="side-text v12-font-36"></text>
</view>
<view class="period-side right">
<view class="side-text side-text-count">
<view class="side-text side-text-count v12-font-26" @click="handleShowHistory">
<text></text>
<text></text>
<text></text>
@@ -26,12 +26,33 @@
<text></text>
</view>
</view>
<view class="draw-main">
<view class="draw-title">本期已开奖</view>
<view class="draw-time">开奖时间{{ drawTime }}</view>
</view>
</view>
<view v-else class="draw-info-wrap" style="padding: 0" :style="{ backgroundImage: `url(${webUrl}/zhongjiang01.png)` }">
<view
class="history-nav-btn left"
:class="{ disabled: !canSwitchPrev }"
@click="handleSwitchHistory('prev')"
>
</view>
<view
class="history-nav-btn right"
:class="{ disabled: !canSwitchNext }"
@click="handleSwitchHistory('next')"
>
</view>
<view class="top-wrap">
<view class="v12-font-bold v12-font-40">
{{ periodLabel }}
</view>
</view>
<view class="draw-main" style="padding: 0" >
<view class="draw-time" style="margin-top: 60rpx">开奖时间{{ drawTime }}</view>
</view>
</view>
<view class="win-banner" v-if="hasWon === 1 && userWinList.length > 0" :style="{ backgroundImage: `url(${webUrl}/zhongjiangn.png)` }">
<view class="win-banner-subtitle">
您的抽奖码 <text class="code">{{ userWinList[0].ticketCode }}</text> 获得了
@@ -98,9 +119,21 @@
</template>
<script>
import { getDrawActivityInfo, getActivityWinRecord, getDrawMyTickets } from '@/api/lottery'
import { getDrawActivityInfo, getActivityWinRecord, getDrawMyTickets, getDrawHistoryActivities } from '@/api/lottery'
export default {
computed: {
currentHistoryIndex() {
if (!this.historyList.length) return -1
return this.historyList.findIndex(item => Number(item.id) === Number(this.activityId))
},
canSwitchPrev() {
return this.currentHistoryIndex > -1 && this.currentHistoryIndex < this.historyList.length - 1
},
canSwitchNext() {
return this.currentHistoryIndex > 0
}
},
data() {
return {
webUrl: this.$VUE_APP_RESOURCES_URL,
@@ -111,8 +144,11 @@ export default {
hasWon: 0,
userWinList: [],
activityWinnerList: [],
historyActivities: [],
loaded: false,
showCouponSuccessPopup: false
showCouponSuccessPopup: false,
showHistory: false,
historyList: []
}
},
onLoad(options) {
@@ -122,6 +158,48 @@ export default {
this.fetchData()
},
methods: {
async handleShowHistory() {
uni.showLoading({
mask: true
})
const historyRes = await getDrawHistoryActivities()
const {data, success} = historyRes || {}
uni.hideLoading()
if(!success) return
this.historyList = (data || [])
.map(item => ({
id: Number(item.id || item.activityId || item.drawActivityId || 0),
periodNo: item.periodNo || '',
drawTime: item.drawTime || ''
}))
.filter(item => item.id > 0)
.sort((a, b) => this.getPeriodNoValue(b.periodNo) - this.getPeriodNoValue(a.periodNo))
this.showHistory = true
},
async handleSwitchHistory(direction) {
if (!this.historyList.length) {
uni.showToast({
title: "暂无可切换历史期",
icon: "none"
})
return
}
const currentIndex = this.currentHistoryIndex
if (currentIndex < 0) {
uni.showToast({
title: "当前期数不在历史列表中",
icon: "none"
})
return
}
const targetIndex = direction === 'prev' ? currentIndex + 1 : currentIndex - 1
if (targetIndex < 0 || targetIndex >= this.historyList.length) return
const target = this.historyList[targetIndex]
if (!target || Number(target.id) === Number(this.activityId)) return
this.activityId = Number(target.id)
this.loaded = false
await this.fetchData()
},
goBack() {
uni.navigateBack()
},
@@ -133,18 +211,17 @@ export default {
handleClaimNow() {
if (!this.userWinList.length) return
if (this.userWinList.length > 1) {
this.navToMyTickets()
return
}
const allCouponPrize = this.userWinList.every((item) => this.isCouponPrize(item))
if (allCouponPrize) {
this.showCouponSuccessPopup = true
return
}
if (this.userWinList.length === 1) {
this.navToReceiveInfo(this.userWinList[0])
return
}
this.navToMyTickets()
this.navToReceiveInfo(this.userWinList[0])
},
isCouponPrize(prize = {}) {
const prizeType = Number(prize.prizeType)
@@ -174,6 +251,20 @@ export default {
if (Array.isArray(res.list)) return res.list
return []
},
normalizeHistoryListData(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 []
},
getPeriodNoValue(periodNo) {
const text = String(periodNo || '')
const match = text.match(/\d+/g)
if (!match || !match.length) return 0
return Number(match.join('')) || 0
},
formatDrawTime(time) {
if (!time) return "--"
const date = new Date(String(time).replace(/-/g, "/"))
@@ -183,18 +274,6 @@ export default {
const d = String(date.getDate()).padStart(2, "0")
return `${y}${m}${d}`
},
parsePeriodLabel(info = {}, winners = []) {
const periodText = String(info.periodNo || info.currentPeriodNo || info.periodLabel || "")
if (periodText) {
const match = periodText.match(/\d+/)
if (match && match[0]) return match[0].padStart(2, "0")
}
const firstWinner = winners[0] || {}
const winnerPeriod = String(firstWinner.periodNo || "")
const winnerMatch = winnerPeriod.match(/\d+/)
if (winnerMatch && winnerMatch[0]) return winnerMatch[0].padStart(2, "0")
return "01"
},
mapUserWinList(ticketList = []) {
return ticketList.map(item => ({
levelName: item.levelName || item.prizeLevelName || "中奖奖品",
@@ -213,7 +292,7 @@ export default {
getDrawMyTickets(1)
])
const activityData = (activityRes && activityRes.data) || {}
const winnerList = this.normalizeListData(winnerRes)
const winnerList = winnerRes.data.activityWinnerList || []
const myTicketList = this.normalizeListData(myTicketsRes)
const filteredMyTicketList = this.activityId
@@ -224,7 +303,7 @@ export default {
this.activityWinnerList = winnerList
this.userWinList = this.mapUserWinList(filteredMyTicketList)
this.hasWon = this.userWinList.length > 0 ? 1 : 0
this.periodLabel = this.parsePeriodLabel(activityData, winnerList)
this.periodLabel = winnerRes.data.periodNo || "01"
} catch (e) {
uni.showToast({
title: "中奖数据加载失败",
@@ -725,4 +804,38 @@ export default {
line-height: 96rpx;
letter-spacing: 2rpx;
}
.top-wrap{
position: absolute;
top: 6px;
left: 0;
right: 0;
margin: auto;
width: fit-content;
color: #333;
}
.history-nav-btn {
width: 52rpx;
height: 52rpx;
position: absolute;
top: 16rpx;
border-radius: 50%;
color: #333;
font-size: 44rpx;
line-height: 48rpx;
text-align: center;
font-weight: 700;
&.left {
left: 240rpx;
}
&.right {
right: 240rpx;
}
&.disabled {
opacity: 0.4;
}
}
</style>