web-base-h5/components/RankingPopup.vue

964 lines
24 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,
#003d7a 0%,
#005bac 30%,
#0077cc 70%,
#4a90e2 100%
);
border-radius: 20rpx;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
// 添加微妙的光效
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(
ellipse at 30% 20%,
rgba(255, 255, 255, 0.1) 0%,
transparent 50%
);
pointer-events: none;
z-index: 1;
}
}
@keyframes sparkle {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-100rpx);
}
}
.popup-header {
padding: 30rpx 40rpx 20rpx;
flex-shrink: 0;
position: relative;
z-index: 2;
.header-title {
font-size: 38rpx;
font-weight: bold;
color: #ffffff;
text-shadow:
0 2rpx 8rpx rgba(0, 0, 0, 0.6),
0 0 15rpx rgba(74, 144, 226, 0.8),
0 0 25rpx rgba(255, 255, 255, 0.4);
text-align: center;
position: relative;
animation: title-bling 2s ease-in-out infinite alternate;
}
}
.top-three-section {
padding: 20rpx 20rpx 40rpx;
flex-shrink: 0;
position: relative;
z-index: 2;
}
.podium-container {
display: flex;
justify-content: center;
align-items: flex-end;
position: relative;
margin: 0 10rpx;
height: 290rpx;
// 添加领奖台基座
// &::after {
// content: '';
// position: absolute;
// bottom: 0;
// left: 50%;
// transform: translateX(-50%);
// width: 85%;
// height: 30rpx;
// background: linear-gradient(135deg, #424242 0%, #616161 50%, #424242 100%);
// border-radius: 15rpx 15rpx 0 0;
// box-shadow: 0 -6rpx 20rpx rgba(0, 0, 0, 0.3);
// }
}
.podium-item {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
margin: 0 8rpx;
.player-area {
border-radius: 20rpx;
min-width: 140rpx;
text-align: center;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
position: relative;
z-index: 3;
}
// 创建领奖台台阶
// &::after {
// content: '';
// position: absolute;
// bottom: 0;
// left: 50%;
// transform: translateX(-50%);
// border-radius: 12rpx 12rpx 0 0;
// box-shadow:
// 0 -6rpx 15rpx rgba(0, 0, 0, 0.25),
// inset 0 3rpx 6rpx rgba(255, 255, 255, 0.15);
// }
.rank-number {
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
font-weight: bold;
color: #fff;
margin-bottom: 15rpx;
position: relative;
img {
filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.3));
transition: all 0.3s ease;
}
}
.member-name {
font-size: 24rpx;
color: #fff;
text-align: center;
font-weight: bold;
max-width: 110rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 6rpx 10rpx;
border-radius: 15rpx;
}
.member-code {
color: #fff;
text-align: center;
margin-bottom: 8rpx;
font-size: 24rpx;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 4rpx 8rpx;
border-radius: 12rpx;
backdrop-filter: blur(5rpx);
}
.score {
font-size: 22rpx;
color: #fff;
font-weight: bold;
min-width: 60rpx;
text-align: center;
}
}
// 第二名台阶
.second-place {
.player-area {
transform: translateY(10rpx);
}
&::after {
width: 130rpx;
height: 100rpx;
background: linear-gradient(135deg, #c0c0c0 0%, #e8e8e8 50%, #c0c0c0 100%);
}
.rank-number img {
height: 70rpx;
width: 70rpx;
animation: silver-glow 2s ease-in-out infinite alternate;
}
}
// 第一名台阶 - 最高
.first-place {
.player-area {
transform: translateY(-40rpx);
}
&::after {
width: 150rpx;
height: 140rpx;
background: linear-gradient(135deg, #ffd700 0%, #ffed4e 50%, #ffd700 100%);
box-shadow:
0 -8rpx 20rpx rgba(0, 0, 0, 0.3),
inset 0 3rpx 6rpx rgba(255, 255, 255, 0.3),
0 0 25rpx rgba(255, 215, 0, 0.4);
}
.rank-number img {
height: 80rpx;
width: 80rpx;
animation: champion-glow 2s ease-in-out infinite alternate;
}
.member-name {
font-size: 28rpx;
color: #ffd700;
animation: champion-text-glow 2s ease-in-out infinite alternate;
}
.member-code {
font-size: 22rpx;
color: #ffd700;
font-weight: 600;
}
.score {
color: #ffd700;
font-size: 24rpx;
font-weight: 900;
min-width: 70rpx;
}
}
// 第三名台阶
.third-place {
.player-area {
transform: translateY(10rpx);
}
&::after {
width: 110rpx;
height: 80rpx;
background: linear-gradient(135deg, #cd7f32 0%, #deb887 50%, #cd7f32 100%);
}
.rank-number img {
height: 65rpx;
width: 65rpx;
animation: bronze-glow 2s ease-in-out infinite alternate;
}
}
@keyframes title-bling {
0% {
text-shadow:
0 2rpx 8rpx rgba(0, 0, 0, 0.6),
0 0 15rpx rgba(74, 144, 226, 0.8),
0 0 25rpx rgba(255, 255, 255, 0.4);
}
100% {
text-shadow:
0 2rpx 8rpx rgba(0, 0, 0, 0.6),
0 0 25rpx rgba(74, 144, 226, 1),
0 0 35rpx rgba(255, 255, 255, 0.7),
0 0 45rpx rgba(0, 119, 204, 0.5);
}
}
@keyframes golden-pulse {
0% {
background: radial-gradient(
ellipse at center,
rgba(255, 215, 0, 0.2) 0%,
transparent 70%
);
}
100% {
background: radial-gradient(
ellipse at center,
rgba(255, 215, 0, 0.4) 0%,
transparent 70%
);
}
}
@keyframes champion-glow {
0% {
filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.3))
drop-shadow(0 0 10rpx rgba(255, 215, 0, 0.3));
transform: scale(1);
}
100% {
filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.3))
drop-shadow(0 0 20rpx rgba(255, 215, 0, 0.6));
transform: scale(1.05);
}
}
@keyframes champion-text-glow {
0% {
text-shadow: 0 2rpx 8rpx rgba(255, 215, 0, 0.8);
}
100% {
text-shadow:
0 2rpx 8rpx rgba(255, 215, 0, 0.8),
0 0 15rpx rgba(255, 215, 0, 0.5);
}
}
@keyframes silver-glow {
0% {
filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.3))
drop-shadow(0 0 8rpx rgba(192, 192, 192, 0.3));
}
100% {
filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.3))
drop-shadow(0 0 15rpx rgba(192, 192, 192, 0.5));
}
}
@keyframes bronze-glow {
0% {
filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.3))
drop-shadow(0 0 8rpx rgba(205, 127, 50, 0.3));
}
100% {
filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.3))
drop-shadow(0 0 15rpx rgba(205, 127, 50, 0.5));
}
}
.ranking-list {
flex: 1;
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.98) 0%,
rgba(248, 250, 252, 0.95) 100%
);
margin: 20rpx 20rpx 0 20rpx;
border-radius: 25rpx 25rpx 0 0;
overflow: hidden;
backdrop-filter: blur(15rpx);
display: flex;
flex-direction: column;
position: relative;
z-index: 2;
box-shadow:
0 -8rpx 25rpx rgba(0, 0, 0, 0.1),
inset 0 1rpx 0 rgba(255, 255, 255, 0.8);
}
.list-header {
padding: 30rpx 40rpx 20rpx;
border-bottom: 2rpx solid rgba(139, 69, 19, 0.1);
flex-shrink: 0;
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.9) 0%,
rgba(248, 250, 252, 0.8) 100%
);
.list-title {
font-size: 28rpx;
font-weight: bold;
color: #2d3748;
position: relative;
text-align: center;
&::after {
content: '';
position: absolute;
bottom: -8rpx;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 3rpx;
background: linear-gradient(90deg, transparent, #9c27b0, transparent);
border-radius: 2rpx;
}
}
}
.scroll-container {
flex: 1;
height: 0; /* 关键在flex布局中scroll-view需要明确高度 */
}
.list-item {
display: flex;
align-items: center;
padding: 24rpx 40rpx;
border-bottom: 1rpx solid rgba(226, 232, 240, 0.8);
position: relative;
transition: all 0.3s ease;
&:last-child {
border-bottom: none;
}
&:hover {
background: linear-gradient(
135deg,
rgba(159, 122, 234, 0.05) 0%,
rgba(139, 92, 246, 0.03) 100%
);
transform: translateX(5rpx);
}
.item-rank {
font-size: 24rpx;
font-weight: bold;
color: #64748b;
text-align: center;
margin-right: 20rpx;
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
border-radius: 50%;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
}
.item-info {
flex: 1;
display: flex;
flex-direction: column;
margin-left: 15rpx;
}
.item-name {
font-size: 28rpx;
color: #1e293b;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 6rpx;
font-weight: 600;
&.highlight {
color: #7c3aed;
font-weight: bold;
background: linear-gradient(135deg, #7c3aed, #a855f7);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
position: relative;
&::before {
content: '';
position: absolute;
left: -8rpx;
top: 50%;
transform: translateY(-50%);
width: 4rpx;
height: 20rpx;
background: linear-gradient(135deg, #7c3aed, #a855f7);
border-radius: 2rpx;
}
}
}
.item-code {
font-size: 22rpx;
color: #64748b;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item-score {
font-size: 24rpx;
color: #374151;
font-weight: bold;
min-width: 100rpx;
text-align: center;
padding: 8rpx 16rpx;
border-radius: 20rpx;
}
}
.list-footer {
padding: 40rpx;
text-align: center;
.empty-text {
font-size: 26rpx;
color: #64748b;
font-style: italic;
}
}
.popup-footer {
padding: 25rpx 40rpx 35rpx;
flex-shrink: 0;
position: relative;
z-index: 2;
.close-text-btn {
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.95) 0%,
rgba(248, 250, 252, 0.9) 100%
);
color: #005bac;
text-align: center;
padding: 28rpx 50rpx;
border-radius: 50rpx;
font-size: 30rpx;
font-weight: bold;
box-shadow:
0 8rpx 25rpx rgba(0, 0, 0, 0.15),
inset 0 1rpx 0 rgba(255, 255, 255, 0.8),
0 0 0 1rpx rgba(0, 91, 172, 0.3);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.4),
transparent
);
transition: left 0.5s ease;
}
&:active {
transform: scale(0.98);
background: linear-gradient(
135deg,
rgba(248, 250, 252, 0.9) 0%,
rgba(241, 245, 249, 0.85) 100%
);
box-shadow:
0 4rpx 15rpx rgba(0, 0, 0, 0.1),
inset 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
&::before {
left: 100%;
}
}
&:hover::before {
left: 100%;
}
}
}
</style>