Files
hhrs/pages/winners/winners.vue
T

458 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>
<view class="page">
<!-- 顶部公告区 -->
<view class="banner-area">
<!-- 返回按钮 -->
<view class="back-btn" @click="goBack">
<u-icon name="arrow-left" color="#333333" size="40"></u-icon>
<text class="back-text">返回</text>
</view>
<view class="banner-header">
<image class="banner-icon" src="https://aliyunoss.hn12301.net/hhrs/hhrs_zjgs.png" mode="aspectFit"></image>
<text class="banner-title">中奖名单公示</text>
</view>
<text class="banner-subtitle">总计 10,000份权益包,全程公证抽取</text>
<view class="stats-card">
<image class="stats-gift" src="https://aliyunoss.hn12301.net/hhrs/hhrs_xyckr.png" mode="aspectFit"></image>
<text class="stats-label">已产生</text>
<text class="stats-number">{{ totalWinners.toLocaleString() }}</text>
<text class="stats-label">名幸运持卡人</text>
</view>
</view>
<!-- 三期 Tab -->
<view class="tab-bar">
<view
class="tab-item"
:class="{ active: currentTab === index }"
v-for="(tab, index) in tabs"
:key="index"
@click="switchTab(index)"
>
<text class="tab-label">{{ tab.name }}</text>
<text class="tab-date">{{ tab.date }}</text>
<view class="tab-indicator" v-if="currentTab === index"></view>
</view>
</view>
<!-- 搜索栏 -->
<view class="search-area">
<view class="search-bar">
<u-icon name="search" color="#8e8e93" size="36"></u-icon>
<input class="search-input" v-model="keyword" placeholder="搜索姓名/手机号" confirm-type="search" @confirm="onSearch" />
<view class="search-btn" @click="onSearch">搜索</view>
</view>
</view>
<!-- 中奖者表格 -->
<view class="table-area">
<template v-if="currentList.length > 0">
<view class="table-header">
<text class="table-col col-name">姓名</text>
<text class="table-col col-idcard">身份证</text>
<text class="table-col col-phone">手机号码</text>
</view>
<view class="table-row" v-for="(item, index) in currentList" :key="index">
<text class="table-col col-name">{{ item.memberName }}</text>
<text class="table-col col-idcard">{{ item.memberIdentityNo }}</text>
<text class="table-col col-phone">{{ item.memberPhone }}</text>
</view>
<!-- xuchao: 名单不足一页时隐藏加载更多 -->
<view class="loadmore-area" v-if="(totals[currentTab] || 0) > pageSize">
<u-loadmore :status="loadStatus" :loading-text="'加载中...'" :loadmore-text="'上拉加载更多'" :nomore-text="'没有更多了'" />
</view>
</template>
<!-- xuchao: 无名单时展示空态 -->
<view class="empty-area" v-else>
<u-icon name="empty" size="60" color="#c8c8c8"></u-icon>
<text class="empty-text">暂无数据</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
currentTab: 0,
tabs: [],
winnerData: {},
// xuchao: 页码改为按期维护(原单一 pageNo 在切期后会被沿用,导致同一页重复追加/跳页)
pageNos: {},
pageSize: 10,
totals: {},
totalWinners: 0,
inFlight: 0,
loadStatusMap: {},
keyword: ''
}
},
computed: {
currentList() {
const list = this.winnerData[this.currentTab] || []
if (!this.keyword) return list
const kw = this.keyword.trim().toLowerCase()
if (!kw) return list
return list.filter(item =>
(item.memberName && item.memberName.toLowerCase().includes(kw)) ||
(item.memberPhone && item.memberPhone.includes(kw))
)
},
// xuchao: 各期独立的加载状态,避免并行请求互相覆盖
loadStatus() {
return this.loadStatusMap[this.currentTab] || 'nomore'
}
},
onLoad() {
this.loadDates()
},
onReachBottom() {
this.loadMore()
},
methods: {
loadDates() {
this.$u.api.getWinnerDates().then(res => {
if (res.errcode === 0 && res.data) {
const dates = res.data
this.tabs = dates.map((date, index) => ({
name: `第${index + 1}期`,
date: date
}))
const data = {}
dates.forEach((date, index) => {
data[index] = []
})
this.winnerData = data
// xuchao: 预置各期 key,保证 totals/pageNos/loadStatusMap 的响应式
this.totals = {}
this.pageNos = {}
this.loadStatusMap = {}
dates.forEach((date, index) => {
this.$set(this.totals, index, 0)
this.$set(this.pageNos, index, 1)
this.$set(this.loadStatusMap, index, 'loading')
})
// xuchao: 预取各期第一页,汇总已产生总人数
if (this.tabs.length > 0) {
this.tabs.forEach((tab, index) => {
this.loadWinnerList(index, 1, true)
})
}
}
})
},
loadWinnerList(tabIndex, pageNo, allowConcurrent) {
if (!this.tabs[tabIndex]) return
if (this.inFlight > 0 && !allowConcurrent) return
// xuchao: 本期页码独立计算,缺省续拉该期下一页
const page = pageNo || (this.pageNos[tabIndex] || 1)
this.inFlight++
this.$set(this.loadStatusMap, tabIndex, 'loading')
const drawDate = this.tabs[tabIndex].date
this.$u.api.getWinnerList({
pageNo: page,
pageSize: this.pageSize,
drawDate: drawDate
}).then(res => {
if (res.errcode === 0 && res.data) {
const list = res.data.list || []
const prev = this.winnerData[tabIndex] || []
this.$set(this.pageNos, tabIndex, page)
if (page === 1) {
this.$set(this.winnerData, tabIndex, list)
} else {
this.$set(this.winnerData, tabIndex, prev.concat(list))
}
this.$set(this.totals, tabIndex, res.data.total || 0)
// xuchao: 各期总人数求和,用于顶部幸运持卡人数
this.totalWinners = Object.values(this.totals).reduce((sum, n) => sum + n, 0)
}
}).finally(() => {
this.inFlight--
const len = (this.winnerData[tabIndex] || []).length
const total = this.totals[tabIndex] || 0
this.$set(this.loadStatusMap, tabIndex, len === 0 || len >= total ? 'nomore' : 'loadmore')
})
},
loadMore() {
const tab = this.currentTab
const total = this.totals[tab] || 0
const len = (this.winnerData[tab] || []).length
if (len < total && this.inFlight === 0) {
this.loadWinnerList(tab, (this.pageNos[tab] || 1) + 1)
}
},
switchTab(index) {
this.currentTab = index
// xuchao: 不再重置全局 pageNo(各期游标自带);空列表补拉首页时允许并发,
// 避免首屏预取仍在途时切期被丢弃后一直显示“暂无数据”
if ((this.winnerData[index] || []).length === 0) {
this.loadWinnerList(index, 1, true)
} else {
const total = this.totals[index] || 0
this.$set(this.loadStatusMap, index, this.winnerData[index].length >= total ? 'nomore' : 'loadmore')
}
},
goBack() {
// xuchao: 直接通过链接进入(无历史栈)时回到首页
const pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack()
} else {
uni.reLaunch({ url: '/pages/index/index' })
}
},
onSearch() {
// 前端过滤,无需重新请求
}
}
}
</script>
<style>
page {
background-color: #f2f2f7;
}
</style>
<style scoped lang="less">
/* 返回按钮 */
.back-btn {
align-self: flex-start;
display: flex;
align-items: center;
gap: 2px;
margin-top: 48px;
}
.back-text {
font-size: 14px;
color: #333333;
}
/* 顶部公告区 */
.banner-area {
background-color: #ffffff;
padding: 0 24px 24px;
margin-top: -24px;
display: flex;
flex-direction: column;
align-items: center;
gap: 17px;
}
.banner-header {
display: flex;
align-items: center;
gap: 4px;
margin-top: -12px;
}
.banner-icon {
width: 50px;
height: 50px;
}
.banner-title {
color: #0D387D;
font-weight: 700;
font-size: 32px;
line-height: normal;
letter-spacing: 0px;
text-align: left;
}
.banner-subtitle {
color: #8E8E93;
font-weight: 600;
font-size: 14px;
line-height: normal;
letter-spacing: 0px;
text-align: center;
}
.stats-card {
width: 100%;
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border-radius: 10px;
background: linear-gradient(-135deg, #4981D2 0%, #1853AF 100%);
}
.stats-gift {
width: 36px;
height: 36px;
}
.stats-label {
color: #FFFFFF;
font-size: 14px;
line-height: normal;
letter-spacing: 0px;
text-align: left;
}
.stats-number {
flex: 1;
font-weight: 700;
font-size: 36px;
line-height: normal;
letter-spacing: 0px;
text-align: center;
color: #fce7b0;
}
/* 三期 Tab */
.tab-bar {
display: flex;
background-color: #ffffff;
padding-top: 12px;
justify-content: space-around;
position: sticky;
top: 0;
z-index: 5;
border-bottom: 1px solid #f2f2f7;
}
.tab-item {
display: flex;
flex-direction: column;
align-items: center;
width: 88px;
position: relative;
padding-bottom: 12px;
}
.tab-label {
color: #B1B1B1;
font-weight: 600;
font-size: 16px;
line-height: 22px;
letter-spacing: 0px;
text-align: center;
}
.tab-date {
font-size: 14px;
font-weight: 600;
color: #B1B1B1;
line-height: 22px;
}
.tab-item.active .tab-label,
.tab-item.active .tab-date {
color: #3d85ff;
}
.tab-indicator {
position: absolute;
bottom: 0;
width: 24px;
height: 4px;
background-color: #3d85ff;
border-radius: 100px;
}
/* 搜索栏 */
.search-area {
background-color: #ffffff;
padding: 10px 12px 12px;
}
.search-bar {
display: flex;
align-items: center;
height: 44px;
background-color: #ffffff;
border: 1px solid #f2f2f7;
border-radius: 100px;
padding: 4px 8px;
gap: 12px;
}
.search-input {
flex: 1;
font-size: 16px;
color: #333;
}
.search-btn {
background-color: #0088ff;
color: #ffffff;
font-size: 14px;
padding: 4px 10px;
border-radius: 50px;
white-space: nowrap;
}
/* 中奖者表格 */
.table-area {
background-color: #ffffff;
width: 100%;
margin: 12px 0;
padding: 0 12px;
box-sizing: border-box;
}
.table-header,
.table-row {
display: flex;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f2f2f7;
}
.table-header {
color: #8e8e93;
font-size: 14px;
}
.table-row {
color: #000000;
font-size: 14px;
height: 64px;
}
.table-col {
flex: 1;
text-align: center;
}
.col-name {
width: 80px;
flex: none;
text-align: center;
}
.col-idcard {
flex: 1;
text-align: center;
}
.col-phone {
flex: 1;
text-align: center;
}
/* 加载更多 */
.loadmore-area {
padding: 24px 0;
color: #999999;
}
/* 空态 */
.empty-area {
display: flex;
flex-direction: column;
align-items: center;
padding: 80px 0;
color: #999999;
}
.empty-text {
margin-top: 12px;
font-size: 14px;
}
</style>