hookehuyr

feat(活动排行榜): 添加家庭活动排行榜页面及导航入口

- 创建活动排行榜页面,包含黄埔榜和上海榜两个榜单
- 实现排行榜前三名特殊展示样式和其他排名列表
- 在Dashboard页面添加排行榜入口按钮
- 更新app.config.js添加新页面路由
/*
* @Date: 2025-06-28 10:33:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-29 15:56:45
* @LastEditTime: 2025-09-01 13:08:15
* @FilePath: /lls_program/src/app.config.js
* @Description: 文件描述
*/
......@@ -32,6 +32,7 @@ export default {
'pages/ActivitiesCover/index',
'pages/PointsList/index',
'pages/UploadMedia/index',
'pages/FamilyRank/index',
],
window: {
backgroundTextStyle: 'light',
......
......@@ -79,6 +79,15 @@
</template>
</WeRunAuth>
<!-- 活动排行榜 -->
<view class="px-5 mb-4 mt-4">
<view @tap="openFamilyRank" class="w-full bg-blue-500 text-white py-3 rounded-lg flex flex-col items-center justify-center">
<view class="flex items-center justify-center">
活动排行榜
</view>
</view>
</view>
<!-- Family album -->
<view class="p-5 mt-4 mb-6 bg-white rounded-xl shadow-md mx-4">
<view class="flex justify-between items-center mb-2">
......@@ -89,16 +98,16 @@
</view>
<p class="text-sm text-gray-500 mb-3">记录每一个家庭活动瞬间</p>
<view class="grid grid-cols-2 gap-3">
<view
v-for="(item, index) in albumData"
<view
v-for="(item, index) in albumData"
:key="index"
class="rounded-lg overflow-hidden h-32 relative cursor-pointer"
@click="handleMediaClick(item, albumData)"
>
<image
:src="item.type === 'video' ? item.thumbnail : item.url"
alt="家庭活动照片"
class="w-full h-full object-cover rounded-lg"
<image
:src="item.type === 'video' ? item.thumbnail : item.url"
alt="家庭活动照片"
class="w-full h-full object-cover rounded-lg"
/>
<!-- 视频标识 -->
<view
......@@ -112,7 +121,7 @@
</view>
<BottomNav />
<!-- 图片预览 -->
<nut-image-preview
v-model:show="previewVisible"
......@@ -307,6 +316,13 @@ const openCamera = () => {
Taro.navigateTo({ url: '/pages/UploadMedia/index' });
}
/**
* 打开活动排行榜
*/
const openFamilyRank = () => {
Taro.navigateTo({ url: '/pages/FamilyRank/index' });
}
const initPageData = async () => {
// 获取用户信息
familyName.value = '张爷爷的家庭'
......
export default {
navigationBarTitleText: '活动排行榜',
usingComponents: {
},
}
<!--
* @Date: 2025-09-01 13:07:52
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-09-01 13:43:56
* @FilePath: /lls_program/src/pages/FamilyRank/index.vue
* @Description: 文件描述
-->
<template>
<view class="family-rank-page">
<!-- 顶部导航 -->
<view class="header">
<view class="nav-tabs">
<view
class="tab-item"
:class="{ active: activeTab === 'huangpu' }"
@click="switchTab('huangpu')"
>
黄埔榜
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'shanghai' }"
@click="switchTab('shanghai')"
>
上海榜
</view>
</view>
</view>
<!-- 排行榜内容 -->
<view class="rank-content">
<!-- 前三名展示 -->
<view class="top-three">
<!-- 第二名 -->
<view class="rank-item second">
<view class="crown crown-silver">👑</view>
<view class="avatar">
<image :src="topRanks[1]?.avatar" class="avatar-img" />
</view>
<view class="family-name">{{ topRanks[1]?.familyName }}</view>
<view class="leader-name">大家长:{{ topRanks[1]?.leaderName }}</view>
<view class="rank-number">2</view>
</view>
<!-- 第一名 -->
<view class="rank-item first">
<view class="crown crown-gold">👑</view>
<view class="avatar">
<image :src="topRanks[0]?.avatar" class="avatar-img" />
</view>
<view class="family-name">{{ topRanks[0]?.familyName }}</view>
<view class="leader-name">大家长:{{ topRanks[0]?.leaderName }}</view>
<view class="rank-number">1</view>
</view>
<!-- 第三名 -->
<view class="rank-item third">
<view class="crown crown-bronze">👑</view>
<view class="avatar">
<image :src="topRanks[2]?.avatar" class="avatar-img" />
</view>
<view class="family-name">{{ topRanks[2]?.familyName }}</view>
<view class="leader-name">大家长:{{ topRanks[2]?.leaderName }}</view>
<view class="rank-number">3</view>
</view>
</view>
<!-- 其他排名列表 -->
<view class="rank-list">
<view
v-for="(item, index) in otherRanks"
:key="index"
class="rank-list-item"
>
<view class="rank-info">
<view class="rank-num">{{ item.rank }}</view>
<view class="avatar-small">
<image :src="item.avatar" class="avatar-small-img" />
</view>
<view class="family-info">
<view class="family-name-small">{{ item.familyName }}</view>
<view class="leader-name-small">大家长:{{ item.leaderName }}</view>
</view>
</view>
<view class="steps-info">
<view class="steps">{{ formatSteps(item.steps) }}</view>
</view>
</view>
</view>
</view>
<!-- 我的排名悬浮卡片 -->
<view class="my-rank-card">
<view class="my-rank-content">
<view class="my-rank-left">
<view class="my-rank-number">{{ myRank.rank }}+</view>
<view class="my-avatar">
<image :src="myRank.avatar" class="my-avatar-img" />
</view>
<view class="my-family-info">
<view class="my-family-name">{{ myRank.familyName }}</view>
<view class="my-leader-name">大家长:{{ myRank.leaderName }}</view>
</view>
</view>
<view class="my-rank-right">
<view class="my-steps">{{ formatSteps(myRank.steps) }}</view>
<view class="rank-status">{{ myRank.status }}</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
// 当前激活的tab
const activeTab = ref('huangpu')
/**
* 切换tab
* @param {string} tab - tab名称
*/
const switchTab = (tab) => {
activeTab.value = tab
}
/**
* 格式化步数显示
* @param {number} steps - 步数
* @returns {string} 格式化后的步数
*/
const formatSteps = (steps) => {
if (steps >= 10000) {
return (steps / 10000).toFixed(1) + 'w'
} else if (steps >= 1000) {
return (steps / 1000).toFixed(1) + 'k'
}
return steps.toString()
}
// 前三名数据
const topRanks = ref([
{
rank: 1,
familyName: '明媚的晴',
leaderName: '张明',
avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
steps: 45670
},
{
rank: 2,
familyName: '甜心小桃',
leaderName: '李桃',
avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
steps: 42350
},
{
rank: 3,
familyName: '真心找爱',
leaderName: '王真',
avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
steps: 38920
}
])
// 其他排名数据
const otherRanks = ref([
{
rank: 4,
familyName: '藏点理想者',
leaderName: '陈理',
avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
steps: 35010
},
{
rank: 5,
familyName: '熊熊熊很大',
leaderName: '熊大',
avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
steps: 32780
},
{
rank: 6,
familyName: '夏花流年❤️',
leaderName: '夏花',
avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
steps: 28450
},
{
rank: 7,
familyName: '给大家拜个早年',
leaderName: '拜年',
avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
steps: 25890
},
{
rank: 8,
familyName: '寻一人觅真心',
leaderName: '觅心',
avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
steps: 23800
},
{
rank: 9,
familyName: '大咪花💕',
leaderName: '大咪',
avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
steps: 21669
}
])
// 我的排名信息
const myRank = ref({
rank: 99,
familyName: '和谐之家',
leaderName: '陈家明',
avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
steps: 8920,
status: '未上榜'
})
</script>
<style lang="less">
.family-rank-page {
min-height: 100vh;
background: linear-gradient(180deg, #4A90E2 0%, #357ABD 100%);
padding-bottom: 120rpx;
.header {
padding: 40rpx 0 20rpx;
.nav-tabs {
display: flex;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
border-radius: 60rpx;
padding: 8rpx;
margin: 0 80rpx;
.tab-item {
flex: 1;
padding: 20rpx 0;
text-align: center;
border-radius: 52rpx;
color: rgba(255, 255, 255, 0.8);
font-size: 28rpx;
font-weight: 500;
transition: all 0.3s ease;
&.active {
background: rgba(255, 255, 255, 1);
color: #4A90E2;
font-weight: 600;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
}
}
}
.rank-content {
padding: 0 40rpx;
}
.top-three {
position: relative;
display: flex;
justify-content: center;
align-items: flex-end;
margin: 180rpx 0 0 0;
height: 400rpx;
gap: -40rpx;
.rank-item {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
.crown {
font-size: 40rpx;
margin-bottom: 10rpx;
&.crown-gold {
filter: hue-rotate(45deg) brightness(1.2);
}
&.crown-silver {
filter: grayscale(0.3) brightness(1.1);
}
&.crown-bronze {
filter: hue-rotate(25deg) brightness(0.9);
}
}
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
overflow: hidden;
border: 6rpx solid rgba(255, 255, 255, 0.8);
margin-bottom: 16rpx;
.avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.family-name {
color: white;
font-size: 24rpx;
font-weight: 600;
margin-bottom: 8rpx;
text-align: center;
}
.leader-name {
color: rgba(255, 255, 255, 0.8);
font-size: 20rpx;
margin-bottom: 16rpx;
text-align: center;
}
.rank-number {
width: 160rpx;
height: 200rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 48rpx;
font-weight: bold;
color: white;
position: relative;
background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%);
border-radius: 20rpx 20rpx 0 0;
box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.2);
}
&.second {
order: 1;
margin-top: 60rpx;
margin-right: -20rpx;
z-index: 2;
.avatar {
box-shadow: 0 0 15rpx rgba(192, 192, 192, 0.4);
}
.rank-number {
width: 220rpx;
height: 180rpx;
background: linear-gradient(135deg, #C0C0C0 0%, #A0A0A0 100%);
}
}
&.first {
order: 2;
z-index: 3;
.avatar {
width: 140rpx;
height: 140rpx;
border-color: #FFD700;
box-shadow: 0 0 20rpx rgba(255, 215, 0, 0.5);
}
.rank-number {
width: 220rpx;
height: 240rpx;
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
}
}
&.third {
order: 3;
margin-top: 60rpx;
margin-left: -20rpx;
z-index: 1;
.avatar {
box-shadow: 0 0 15rpx rgba(205, 127, 50, 0.4);
}
.rank-number {
width: 220rpx;
height: 160rpx;
background: linear-gradient(135deg, #CD7F32 0%, #B8860B 100%);
}
}
}
}
.rank-list {
background: rgba(255, 255, 255, 0.95);
border-radius: 24rpx 24rpx 0 0;
padding: 20rpx 0 60rpx;
margin-top: 0;
position: relative;
z-index: 4;
.rank-list-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 32rpx;
border-bottom: 1rpx solid rgba(0, 0, 0, 0.05);
&:last-child {
border-bottom: none;
}
.rank-info {
display: flex;
align-items: center;
flex: 1;
.rank-num {
width: 48rpx;
font-size: 32rpx;
font-weight: bold;
color: #666;
margin-right: 24rpx;
}
.avatar-small {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
overflow: hidden;
margin-right: 24rpx;
.avatar-small-img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.family-info {
flex: 1;
.family-name-small {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.leader-name-small {
font-size: 24rpx;
color: #666;
}
}
}
.steps-info {
text-align: right;
.steps {
font-size: 32rpx;
font-weight: bold;
color: #4A90E2;
}
}
}
}
.my-rank-card {
position: fixed;
bottom: 40rpx;
left: 40rpx;
right: 40rpx;
background: rgba(255, 255, 255, 0.95);
border-radius: 24rpx;
padding: 24rpx 32rpx;
backdrop-filter: blur(10px);
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
z-index: 99;
.my-rank-content {
display: flex;
align-items: center;
justify-content: space-between;
.my-rank-left {
display: flex;
align-items: center;
flex: 1;
.my-rank-number {
width: 60rpx;
height: 60rpx;
background: #4A90E2;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
font-weight: bold;
color: white;
margin-right: 24rpx;
}
.my-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
overflow: hidden;
margin-right: 24rpx;
.my-avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.my-family-info {
flex: 1;
.my-family-name {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.my-leader-name {
font-size: 24rpx;
color: #666;
}
}
}
.my-rank-right {
text-align: right;
.my-steps {
font-size: 32rpx;
font-weight: bold;
color: #4A90E2;
margin-bottom: 8rpx;
}
.rank-status {
font-size: 24rpx;
color: #999;
}
}
}
}
}
</style>