Files
hhrs/pages/winners/winners.vue
T
jacobxu666andClaude Fable 5 805596e4a1 style: 全站统一系统字体栈并规整字重为500/600/700三级
App.vue 全局定义字体栈(iOS/鸿蒙/安卓/Windows 系统字体优先),
移除 winners 页衬线体与 index 页局部字体声明;bold 别名统一为 700,
冗余 400 删除;同类元素字重对齐:导航标题/分区标题 600、
hero 主标题与大数字 700、卡片名与按钮 500。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 22:22:35 +08:00

450 lines
9.7 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://zhihuiyou.oss-cn-shenzhen.aliyuncs.com/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://zhihuiyou.oss-cn-shenzhen.aliyuncs.com/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: {},
pageNo: 1,
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/loadStatusMap 的响应式
this.totals = {}
this.loadStatusMap = {}
dates.forEach((date, index) => {
this.$set(this.totals, index, 0)
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
this.inFlight++
this.$set(this.loadStatusMap, tabIndex, 'loading')
const drawDate = this.tabs[tabIndex].date
this.$u.api.getWinnerList({
pageNo: pageNo || this.pageNo,
pageSize: this.pageSize,
drawDate: drawDate
}).then(res => {
if (res.errcode === 0 && res.data) {
const list = res.data.list || []
const page = pageNo || this.pageNo
if (page === 1) {
this.$set(this.winnerData, tabIndex, list)
} else {
this.winnerData[tabIndex] = this.winnerData[tabIndex].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 total = this.totals[this.currentTab] || 0
if (this.winnerData[this.currentTab].length < total && this.inFlight === 0) {
this.pageNo++
this.loadWinnerList(this.currentTab)
}
},
switchTab(index) {
this.currentTab = index
this.pageNo = 1
if (this.winnerData[index].length === 0) {
this.loadWinnerList(index, 1)
} 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>