web-base-h5/components/RankingPopup.vue

671 lines
18 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>
<!-- 直推人数排行榜弹窗 -->
<u-popup
class="ranking-popup"
mode="center"
:show="showPeopleRanking"
:closeOnClickOverlay="false"
border-radius="20"
>
<view style="width: 90vw; height: 85vh" class="popup-container">
<!-- 弹窗头部 -->
<view class="popup-header">
<view class="header-title">直推人数排行榜</view>
</view>
<!-- 前三名特殊展示区域 -->
<view class="top-three-section">
<view class="podium-container">
<!-- 第二名 -->
<view class="podium-item second-place" v-if="peopleTopThree[1]">
<view class="player-area">
<view class="rank-number">
<img src="@/static/images/rank-2.svg" alt="" />
</view>
<view
class="member-name"
:class="{ highlight: peopleTopThree[1].isLoginMember == 1 }"
>
{{ peopleTopThree[1].memberName }}
</view>
<view class="member-code">{{
peopleTopThree[1].memberCode
}}</view>
<view class="score">{{ peopleTopThree[1].count }}</view>
</view>
</view>
<!-- 第一名 -->
<view class="podium-item first-place" v-if="peopleTopThree[0]">
<view class="player-area">
<view class="rank-number">
<img src="@/static/images/rank-1.svg" alt="" />
</view>
<view
class="member-name"
:class="{ highlight: peopleTopThree[0].isLoginMember == 1 }"
>
{{ peopleTopThree[0].memberName }}
</view>
<view class="member-code">{{
peopleTopThree[0].memberCode
}}</view>
<view class="score">{{ peopleTopThree[0].count }}</view>
</view>
</view>
<!-- 第三名 -->
<view class="podium-item third-place" v-if="peopleTopThree[2]">
<view class="player-area">
<view class="rank-number">
<img src="@/static/images/rank-3.svg" alt="" />
</view>
<view
class="member-name"
:class="{ highlight: peopleTopThree[2].isLoginMember == 1 }"
>
{{ peopleTopThree[2].memberName }}
</view>
<view class="member-code">{{
peopleTopThree[2].memberCode
}}</view>
<view class="score">{{ peopleTopThree[2].count }}</view>
</view>
</view>
</view>
</view>
<!-- 4-30名滚动列表 -->
<view class="ranking-list">
<view class="list-header">
<text class="list-title">完整排行榜</text>
</view>
<scroll-view
class="scroll-container"
scroll-y="true"
:show-scrollbar="false"
>
<view
class="list-item"
v-for="(item, index) in peopleRemainingList"
:key="index"
>
<view class="item-rank">{{ index + 4 }}</view>
<view class="item-info">
<view
class="item-name"
:class="{ highlight: item.isLoginMember == 1 }"
>
{{ item.memberName }}
</view>
<view class="item-code">{{ item.memberCode }}</view>
</view>
<view class="item-score">{{ item.count }}人</view>
</view>
<view class="list-footer" v-if="peopleRemainingList.length === 0">
<text class="empty-text">暂无更多数据</text>
</view>
</scroll-view>
</view>
<!-- 底部关闭按钮 -->
<view class="popup-footer">
<view class="close-text-btn" @click="closePeopleRanking">
关 闭
</view>
</view>
</view>
</u-popup>
<!-- 直推金额排行榜弹窗 -->
<u-popup
class="ranking-popup"
width="85%"
height="85%"
mode="center"
:show="showAmountRanking"
:closeOnClickOverlay="false"
border-radius="20"
>
<view style="width: 90vw; height: 85vh" class="popup-container">
<!-- 弹窗头部 -->
<view class="popup-header">
<view class="header-title">直推金额排行榜</view>
</view>
<!-- 前三名特殊展示区域 -->
<view class="top-three-section">
<view class="podium-container">
<!-- 第二名 -->
<view class="podium-item second-place" v-if="amountTopThree[1]">
<view class="player-area">
<view class="rank-number">
<img src="@/static/images/rank-2.svg" alt="" />
</view>
<view
class="member-name"
:class="{ highlight: amountTopThree[1].isLoginMember == 1 }"
>
{{ amountTopThree[1].memberName }}
</view>
<view class="member-code">{{
amountTopThree[1].memberCode
}}</view>
<view class="score">{{
formatAmount(amountTopThree[1].amount)
}}</view>
</view>
</view>
<!-- 第一名 -->
<view class="podium-item first-place" v-if="amountTopThree[0]">
<view class="player-area">
<view class="rank-number">
<img src="@/static/images/rank-1.svg" alt="" />
</view>
<view
class="member-name"
:class="{ highlight: amountTopThree[0].isLoginMember == 1 }"
>
{{ amountTopThree[0].memberName }}
</view>
<view class="member-code">{{
amountTopThree[0].memberCode
}}</view>
<view class="score">{{
formatAmount(amountTopThree[0].amount)
}}</view>
</view>
</view>
<!-- 第三名 -->
<view class="podium-item third-place" v-if="amountTopThree[2]">
<view class="player-area">
<view class="rank-number">
<img src="@/static/images/rank-3.svg" alt="" />
</view>
<view
class="member-name"
:class="{ highlight: amountTopThree[2].isLoginMember == 1 }"
>
{{ amountTopThree[2].memberName }}
</view>
<view class="member-code">{{
amountTopThree[2].memberCode
}}</view>
<view class="score">{{
formatAmount(amountTopThree[2].amount)
}}</view>
</view>
</view>
</view>
</view>
<!-- 4-30名滚动列表 -->
<view class="ranking-list">
<view class="list-header">
<text class="list-title">完整排行榜</text>
</view>
<scroll-view
class="scroll-container"
scroll-y="true"
:show-scrollbar="false"
>
<view
class="list-item"
v-for="(item, index) in amountRemainingList"
:key="index"
>
<view class="item-rank">{{ index + 4 }}</view>
<view class="item-info">
<view
class="item-name"
:class="{ highlight: item.isLoginMember == 1 }"
>
{{ item.memberName }}
</view>
<view class="item-code">{{ item.memberCode }}</view>
</view>
<view class="item-score">{{ formatAmount(item.amount) }}</view>
</view>
<view class="list-footer" v-if="amountRemainingList.length === 0">
<text class="empty-text">暂无更多数据</text>
</view>
</scroll-view>
</view>
<!-- 底部关闭按钮 -->
<view class="popup-footer">
<view class="close-text-btn" @click="closeAmountRanking">
</view>
</view>
</view>
</u-popup>
<!-- 加载状态 -->
<u-loading-page
:loading="loading"
loading-text="加载中..."
></u-loading-page>
</view>
</template>
<script>
import * as api from '@/config/index.js'
export default {
name: 'RankingPopup',
data() {
return {
// 控制弹窗显示
showPeopleRanking: false,
showAmountRanking: false,
loading: false,
// 人数排行数据
peopleRankingList: [],
peopleTopThree: [],
peopleRemainingList: [],
// 金额排行数据
amountRankingList: [],
amountTopThree: [],
amountRemainingList: [],
// 用户信息
userInfo: uni.getStorageSync('User') || {},
}
},
methods: {
// 显示排行榜弹窗(先显示人数排行)
async showRankingPopups() {
this.loading = true
try {
// 并行加载两个排行榜数据
await Promise.all([this.loadPeopleRanking(), this.loadAmountRanking()])
// 先显示人数排行榜
this.showPeopleRanking = true
} catch (error) {
console.error('加载排行榜数据失败:', error)
uni.showToast({
title: '加载失败',
icon: 'error',
})
} finally {
this.loading = false
}
},
// 获取当前年月参数
getCurrentYearMonth() {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0') // 月份补0
return { year, month }
},
// 加载人数排行榜数据
async loadPeopleRanking() {
try {
const { year, month } = this.getCurrentYearMonth()
const params = {
year,
month,
faker: true,
}
const res = await api.getTopPeople(params)
if (res.code === 200 && res.data) {
// 数据字段映射
const mappedData = res.data.map(item => ({
...item,
memberName: item.memberName,
memberCode: item.memberCode,
count: item.numberOfPeople, // 人数字段映射
isLoginMember: item.memberId === this.userInfo.memberId ? 1 : 0, // 判断是否为当前用户
}))
this.peopleRankingList = mappedData.slice(0, 30) // 取前30名
this.peopleTopThree = this.peopleRankingList.slice(0, 3)
this.peopleRemainingList = this.peopleRankingList.slice(3)
}
} catch (error) {
console.error('加载人数排行榜失败:', error)
throw error
}
},
// 加载金额排行榜数据
async loadAmountRanking() {
try {
const { year, month } = this.getCurrentYearMonth()
const params = {
year,
month,
faker: true,
}
const res = await api.getTopAmount(params)
if (res.code === 200 && res.data) {
// 数据字段映射
const mappedData = res.data.map(item => ({
...item,
memberName: item.memberName,
memberCode: item.memberCode,
amount: item.numberOfAmount, // 金额字段映射
isLoginMember: item.memberId === this.userInfo.memberId ? 1 : 0, // 判断是否为当前用户
}))
this.amountRankingList = mappedData.slice(0, 30) // 取前30名
this.amountTopThree = this.amountRankingList.slice(0, 3)
this.amountRemainingList = this.amountRankingList.slice(3)
}
} catch (error) {
console.error('加载金额排行榜失败:', error)
throw error
}
},
// 关闭人数排行榜,显示金额排行榜
closePeopleRanking() {
this.showPeopleRanking = false
// 延迟显示金额排行榜,让关闭动画完成
setTimeout(() => {
this.showAmountRanking = true
}, 300)
},
// 关闭金额排行榜
closeAmountRanking() {
this.showAmountRanking = false
// 触发完成事件
this.$emit('onRankingComplete')
},
// 格式化金额显示
formatAmount(amount) {
return amount
},
},
}
</script>
<style lang="scss" scoped>
::v-deep .u-popup__content {
background-color: rgba(0, 0, 0, 0);
}
.popup-container {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #005bac 0%, #0077cc 50%, #003d7a 100%);
border-radius: 20rpx;
display: flex;
flex-direction: column;
overflow: hidden;
}
.popup-header {
padding: 30rpx 40rpx 20rpx;
flex-shrink: 0;
.header-title {
font-size: 36rpx;
font-weight: bold;
color: #fff;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
text-align: center;
}
}
.top-three-section {
padding: 40rpx 20rpx;
flex-shrink: 0;
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0.05) 0%,
transparent 100%
);
}
.podium-container {
display: flex;
justify-content: center;
align-items: center;
// height: 320rpx;
position: relative;
// padding: 30rpx 20rpx;
// background: rgba(255, 255, 255, 0.05);
border-radius: 25rpx;
margin: 0 10rpx;
.second-place {
}
}
.podium-item {
display: flex;
flex-direction: column;
align-items: center;
margin: 0 15rpx;
position: relative;
.player-area {
border-radius: 20rpx;
// padding: 25rpx 18rpx;
min-width: 140rpx;
text-align: center;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.crown {
position: absolute;
top: -25rpx;
font-size: 50rpx;
z-index: 10;
filter: drop-shadow(0 4rpx 12rpx rgba(255, 215, 0, 0.4));
animation: crown-bounce 2s ease-in-out infinite;
}
.rank-number {
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
font-weight: bold;
color: #fff;
margin-bottom: 15rpx;
img {
height: 60rpx;
width: 60rpx;
}
}
.member-name {
font-size: 26rpx;
color: #fff;
text-align: center;
margin-bottom: 8rpx;
font-weight: bold;
text-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.4);
max-width: 120rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&.highlight {
color: #ffd700;
text-shadow: 0 2rpx 8rpx rgba(255, 215, 0, 0.6);
}
}
.member-code {
color: rgba(255, 255, 255, 0.8);
text-align: center;
margin-bottom: 10rpx;
font-size: 24rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.score {
font-size: 22rpx;
color: #fff;
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.25) 0%,
rgba(255, 255, 255, 0.15) 100%
);
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-weight: bold;
border: 1rpx solid rgba(255, 255, 255, 0.3);
text-shadow: 0 1rpx 3rpx rgba(0, 0, 0, 0.3);
}
}
.first-place {
.score {
background: linear-gradient(
135deg,
rgba(255, 215, 0, 0.3) 0%,
rgba(255, 215, 0, 0.15) 100%
);
border-color: rgba(255, 215, 0, 0.4);
color: #ffd700;
}
}
@keyframes crown-bounce {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-8rpx);
}
}
.ranking-list {
flex: 1;
background: rgba(255, 255, 255, 0.95);
margin: 20rpx 20rpx 0 20rpx;
border-radius: 20rpx 20rpx 0 0;
overflow: hidden;
backdrop-filter: blur(10rpx);
display: flex;
flex-direction: column;
}
.list-header {
padding: 30rpx 40rpx 20rpx;
border-bottom: 2rpx solid #f0f0f0;
flex-shrink: 0;
.list-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
}
.scroll-container {
flex: 1;
height: 0; /* 关键在flex布局中scroll-view需要明确高度 */
}
.list-item {
display: flex;
align-items: center;
padding: 20rpx 40rpx;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
.item-rank {
width: 80rpx;
font-size: 24rpx;
font-weight: bold;
color: #666;
text-align: center;
margin-right: 20rpx;
}
.item-info {
flex: 1;
display: flex;
flex-direction: column;
}
.item-name {
font-size: 26rpx;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 4rpx;
&.highlight {
color: #005bac;
font-weight: bold;
}
}
.item-code {
font-size: 20rpx;
color: #999;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item-score {
font-size: 24rpx;
color: #666;
font-weight: bold;
min-width: 100rpx;
text-align: right;
}
}
.list-footer {
padding: 40rpx;
text-align: center;
.empty-text {
font-size: 24rpx;
color: #999;
}
}
.popup-footer {
padding: 20rpx 40rpx 30rpx;
flex-shrink: 0;
.close-text-btn {
background: rgba(255, 255, 255, 0.9);
color: #005bac;
text-align: center;
padding: 24rpx 40rpx;
border-radius: 50rpx;
font-size: 28rpx;
font-weight: bold;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
background: rgba(255, 255, 255, 0.8);
}
}
}
</style>