hookehuyr

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

- 创建活动排行榜页面,包含黄埔榜和上海榜两个榜单
- 实现排行榜前三名特殊展示样式和其他排名列表
- 在Dashboard页面添加排行榜入口按钮
- 更新app.config.js添加新页面路由
1 /* 1 /*
2 * @Date: 2025-06-28 10:33:00 2 * @Date: 2025-06-28 10:33:00
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-08-29 15:56:45 4 + * @LastEditTime: 2025-09-01 13:08:15
5 * @FilePath: /lls_program/src/app.config.js 5 * @FilePath: /lls_program/src/app.config.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
...@@ -32,6 +32,7 @@ export default { ...@@ -32,6 +32,7 @@ export default {
32 'pages/ActivitiesCover/index', 32 'pages/ActivitiesCover/index',
33 'pages/PointsList/index', 33 'pages/PointsList/index',
34 'pages/UploadMedia/index', 34 'pages/UploadMedia/index',
35 + 'pages/FamilyRank/index',
35 ], 36 ],
36 window: { 37 window: {
37 backgroundTextStyle: 'light', 38 backgroundTextStyle: 'light',
......
...@@ -79,6 +79,15 @@ ...@@ -79,6 +79,15 @@
79 </template> 79 </template>
80 </WeRunAuth> 80 </WeRunAuth>
81 81
82 + <!-- 活动排行榜 -->
83 + <view class="px-5 mb-4 mt-4">
84 + <view @tap="openFamilyRank" class="w-full bg-blue-500 text-white py-3 rounded-lg flex flex-col items-center justify-center">
85 + <view class="flex items-center justify-center">
86 + 活动排行榜
87 + </view>
88 + </view>
89 + </view>
90 +
82 <!-- Family album --> 91 <!-- Family album -->
83 <view class="p-5 mt-4 mb-6 bg-white rounded-xl shadow-md mx-4"> 92 <view class="p-5 mt-4 mb-6 bg-white rounded-xl shadow-md mx-4">
84 <view class="flex justify-between items-center mb-2"> 93 <view class="flex justify-between items-center mb-2">
...@@ -89,16 +98,16 @@ ...@@ -89,16 +98,16 @@
89 </view> 98 </view>
90 <p class="text-sm text-gray-500 mb-3">记录每一个家庭活动瞬间</p> 99 <p class="text-sm text-gray-500 mb-3">记录每一个家庭活动瞬间</p>
91 <view class="grid grid-cols-2 gap-3"> 100 <view class="grid grid-cols-2 gap-3">
92 - <view 101 + <view
93 - v-for="(item, index) in albumData" 102 + v-for="(item, index) in albumData"
94 :key="index" 103 :key="index"
95 class="rounded-lg overflow-hidden h-32 relative cursor-pointer" 104 class="rounded-lg overflow-hidden h-32 relative cursor-pointer"
96 @click="handleMediaClick(item, albumData)" 105 @click="handleMediaClick(item, albumData)"
97 > 106 >
98 - <image 107 + <image
99 - :src="item.type === 'video' ? item.thumbnail : item.url" 108 + :src="item.type === 'video' ? item.thumbnail : item.url"
100 - alt="家庭活动照片" 109 + alt="家庭活动照片"
101 - class="w-full h-full object-cover rounded-lg" 110 + class="w-full h-full object-cover rounded-lg"
102 /> 111 />
103 <!-- 视频标识 --> 112 <!-- 视频标识 -->
104 <view 113 <view
...@@ -112,7 +121,7 @@ ...@@ -112,7 +121,7 @@
112 </view> 121 </view>
113 122
114 <BottomNav /> 123 <BottomNav />
115 - 124 +
116 <!-- 图片预览 --> 125 <!-- 图片预览 -->
117 <nut-image-preview 126 <nut-image-preview
118 v-model:show="previewVisible" 127 v-model:show="previewVisible"
...@@ -307,6 +316,13 @@ const openCamera = () => { ...@@ -307,6 +316,13 @@ const openCamera = () => {
307 Taro.navigateTo({ url: '/pages/UploadMedia/index' }); 316 Taro.navigateTo({ url: '/pages/UploadMedia/index' });
308 } 317 }
309 318
319 +/**
320 + * 打开活动排行榜
321 + */
322 +const openFamilyRank = () => {
323 + Taro.navigateTo({ url: '/pages/FamilyRank/index' });
324 +}
325 +
310 const initPageData = async () => { 326 const initPageData = async () => {
311 // 获取用户信息 327 // 获取用户信息
312 familyName.value = '张爷爷的家庭' 328 familyName.value = '张爷爷的家庭'
......
1 +export default {
2 + navigationBarTitleText: '活动排行榜',
3 + usingComponents: {
4 + },
5 +}
1 +.red {
2 + color: red;
3 +}
1 +<!--
2 + * @Date: 2025-09-01 13:07:52
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-09-01 13:43:56
5 + * @FilePath: /lls_program/src/pages/FamilyRank/index.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <view class="family-rank-page">
10 + <!-- 顶部导航 -->
11 + <view class="header">
12 + <view class="nav-tabs">
13 + <view
14 + class="tab-item"
15 + :class="{ active: activeTab === 'huangpu' }"
16 + @click="switchTab('huangpu')"
17 + >
18 + 黄埔榜
19 + </view>
20 + <view
21 + class="tab-item"
22 + :class="{ active: activeTab === 'shanghai' }"
23 + @click="switchTab('shanghai')"
24 + >
25 + 上海榜
26 + </view>
27 + </view>
28 + </view>
29 +
30 + <!-- 排行榜内容 -->
31 + <view class="rank-content">
32 + <!-- 前三名展示 -->
33 + <view class="top-three">
34 + <!-- 第二名 -->
35 + <view class="rank-item second">
36 + <view class="crown crown-silver">👑</view>
37 + <view class="avatar">
38 + <image :src="topRanks[1]?.avatar" class="avatar-img" />
39 + </view>
40 + <view class="family-name">{{ topRanks[1]?.familyName }}</view>
41 + <view class="leader-name">大家长:{{ topRanks[1]?.leaderName }}</view>
42 + <view class="rank-number">2</view>
43 + </view>
44 +
45 + <!-- 第一名 -->
46 + <view class="rank-item first">
47 + <view class="crown crown-gold">👑</view>
48 + <view class="avatar">
49 + <image :src="topRanks[0]?.avatar" class="avatar-img" />
50 + </view>
51 + <view class="family-name">{{ topRanks[0]?.familyName }}</view>
52 + <view class="leader-name">大家长:{{ topRanks[0]?.leaderName }}</view>
53 + <view class="rank-number">1</view>
54 + </view>
55 +
56 + <!-- 第三名 -->
57 + <view class="rank-item third">
58 + <view class="crown crown-bronze">👑</view>
59 + <view class="avatar">
60 + <image :src="topRanks[2]?.avatar" class="avatar-img" />
61 + </view>
62 + <view class="family-name">{{ topRanks[2]?.familyName }}</view>
63 + <view class="leader-name">大家长:{{ topRanks[2]?.leaderName }}</view>
64 + <view class="rank-number">3</view>
65 + </view>
66 + </view>
67 +
68 + <!-- 其他排名列表 -->
69 + <view class="rank-list">
70 + <view
71 + v-for="(item, index) in otherRanks"
72 + :key="index"
73 + class="rank-list-item"
74 + >
75 + <view class="rank-info">
76 + <view class="rank-num">{{ item.rank }}</view>
77 + <view class="avatar-small">
78 + <image :src="item.avatar" class="avatar-small-img" />
79 + </view>
80 + <view class="family-info">
81 + <view class="family-name-small">{{ item.familyName }}</view>
82 + <view class="leader-name-small">大家长:{{ item.leaderName }}</view>
83 + </view>
84 + </view>
85 + <view class="steps-info">
86 + <view class="steps">{{ formatSteps(item.steps) }}</view>
87 + </view>
88 + </view>
89 + </view>
90 + </view>
91 +
92 + <!-- 我的排名悬浮卡片 -->
93 + <view class="my-rank-card">
94 + <view class="my-rank-content">
95 + <view class="my-rank-left">
96 + <view class="my-rank-number">{{ myRank.rank }}+</view>
97 + <view class="my-avatar">
98 + <image :src="myRank.avatar" class="my-avatar-img" />
99 + </view>
100 + <view class="my-family-info">
101 + <view class="my-family-name">{{ myRank.familyName }}</view>
102 + <view class="my-leader-name">大家长:{{ myRank.leaderName }}</view>
103 + </view>
104 + </view>
105 + <view class="my-rank-right">
106 + <view class="my-steps">{{ formatSteps(myRank.steps) }}</view>
107 + <view class="rank-status">{{ myRank.status }}</view>
108 + </view>
109 + </view>
110 + </view>
111 + </view>
112 +</template>
113 +
114 +<script setup>
115 +import { ref, computed } from 'vue'
116 +
117 +// 当前激活的tab
118 +const activeTab = ref('huangpu')
119 +
120 +/**
121 + * 切换tab
122 + * @param {string} tab - tab名称
123 + */
124 +const switchTab = (tab) => {
125 + activeTab.value = tab
126 +}
127 +
128 +/**
129 + * 格式化步数显示
130 + * @param {number} steps - 步数
131 + * @returns {string} 格式化后的步数
132 + */
133 +const formatSteps = (steps) => {
134 + if (steps >= 10000) {
135 + return (steps / 10000).toFixed(1) + 'w'
136 + } else if (steps >= 1000) {
137 + return (steps / 1000).toFixed(1) + 'k'
138 + }
139 + return steps.toString()
140 +}
141 +
142 +// 前三名数据
143 +const topRanks = ref([
144 + {
145 + rank: 1,
146 + familyName: '明媚的晴',
147 + leaderName: '张明',
148 + avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
149 + steps: 45670
150 + },
151 + {
152 + rank: 2,
153 + familyName: '甜心小桃',
154 + leaderName: '李桃',
155 + avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
156 + steps: 42350
157 + },
158 + {
159 + rank: 3,
160 + familyName: '真心找爱',
161 + leaderName: '王真',
162 + avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
163 + steps: 38920
164 + }
165 +])
166 +
167 +// 其他排名数据
168 +const otherRanks = ref([
169 + {
170 + rank: 4,
171 + familyName: '藏点理想者',
172 + leaderName: '陈理',
173 + avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
174 + steps: 35010
175 + },
176 + {
177 + rank: 5,
178 + familyName: '熊熊熊很大',
179 + leaderName: '熊大',
180 + avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
181 + steps: 32780
182 + },
183 + {
184 + rank: 6,
185 + familyName: '夏花流年❤️',
186 + leaderName: '夏花',
187 + avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
188 + steps: 28450
189 + },
190 + {
191 + rank: 7,
192 + familyName: '给大家拜个早年',
193 + leaderName: '拜年',
194 + avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
195 + steps: 25890
196 + },
197 + {
198 + rank: 8,
199 + familyName: '寻一人觅真心',
200 + leaderName: '觅心',
201 + avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
202 + steps: 23800
203 + },
204 + {
205 + rank: 9,
206 + familyName: '大咪花💕',
207 + leaderName: '大咪',
208 + avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
209 + steps: 21669
210 + }
211 +])
212 +
213 +// 我的排名信息
214 +const myRank = ref({
215 + rank: 99,
216 + familyName: '和谐之家',
217 + leaderName: '陈家明',
218 + avatar: 'https://cdn.ipadbiz.cn/hager/0513-1_FsxMk28AGz6N_D1zZFFOl_EaRdss.png',
219 + steps: 8920,
220 + status: '未上榜'
221 +})
222 +</script>
223 +
224 +<style lang="less">
225 +.family-rank-page {
226 + min-height: 100vh;
227 + background: linear-gradient(180deg, #4A90E2 0%, #357ABD 100%);
228 + padding-bottom: 120rpx;
229 +
230 + .header {
231 + padding: 40rpx 0 20rpx;
232 +
233 + .nav-tabs {
234 + display: flex;
235 + justify-content: center;
236 + background: rgba(255, 255, 255, 0.2);
237 + border-radius: 60rpx;
238 + padding: 8rpx;
239 + margin: 0 80rpx;
240 +
241 + .tab-item {
242 + flex: 1;
243 + padding: 20rpx 0;
244 + text-align: center;
245 + border-radius: 52rpx;
246 + color: rgba(255, 255, 255, 0.8);
247 + font-size: 28rpx;
248 + font-weight: 500;
249 + transition: all 0.3s ease;
250 +
251 + &.active {
252 + background: rgba(255, 255, 255, 1);
253 + color: #4A90E2;
254 + font-weight: 600;
255 + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
256 + }
257 + }
258 + }
259 + }
260 +
261 + .rank-content {
262 + padding: 0 40rpx;
263 + }
264 +
265 + .top-three {
266 + position: relative;
267 + display: flex;
268 + justify-content: center;
269 + align-items: flex-end;
270 + margin: 180rpx 0 0 0;
271 + height: 400rpx;
272 + gap: -40rpx;
273 +
274 + .rank-item {
275 + display: flex;
276 + flex-direction: column;
277 + align-items: center;
278 + position: relative;
279 +
280 + .crown {
281 + font-size: 40rpx;
282 + margin-bottom: 10rpx;
283 +
284 + &.crown-gold {
285 + filter: hue-rotate(45deg) brightness(1.2);
286 + }
287 +
288 + &.crown-silver {
289 + filter: grayscale(0.3) brightness(1.1);
290 + }
291 +
292 + &.crown-bronze {
293 + filter: hue-rotate(25deg) brightness(0.9);
294 + }
295 + }
296 +
297 + .avatar {
298 + width: 120rpx;
299 + height: 120rpx;
300 + border-radius: 50%;
301 + overflow: hidden;
302 + border: 6rpx solid rgba(255, 255, 255, 0.8);
303 + margin-bottom: 16rpx;
304 +
305 + .avatar-img {
306 + width: 100%;
307 + height: 100%;
308 + object-fit: cover;
309 + }
310 + }
311 +
312 + .family-name {
313 + color: white;
314 + font-size: 24rpx;
315 + font-weight: 600;
316 + margin-bottom: 8rpx;
317 + text-align: center;
318 + }
319 +
320 + .leader-name {
321 + color: rgba(255, 255, 255, 0.8);
322 + font-size: 20rpx;
323 + margin-bottom: 16rpx;
324 + text-align: center;
325 + }
326 +
327 + .rank-number {
328 + width: 160rpx;
329 + height: 200rpx;
330 + display: flex;
331 + align-items: center;
332 + justify-content: center;
333 + font-size: 48rpx;
334 + font-weight: bold;
335 + color: white;
336 + position: relative;
337 + background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%);
338 + border-radius: 20rpx 20rpx 0 0;
339 + box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.2);
340 + }
341 +
342 + &.second {
343 + order: 1;
344 + margin-top: 60rpx;
345 + margin-right: -20rpx;
346 + z-index: 2;
347 +
348 + .avatar {
349 + box-shadow: 0 0 15rpx rgba(192, 192, 192, 0.4);
350 + }
351 +
352 + .rank-number {
353 + width: 220rpx;
354 + height: 180rpx;
355 + background: linear-gradient(135deg, #C0C0C0 0%, #A0A0A0 100%);
356 + }
357 + }
358 +
359 + &.first {
360 + order: 2;
361 + z-index: 3;
362 +
363 + .avatar {
364 + width: 140rpx;
365 + height: 140rpx;
366 + border-color: #FFD700;
367 + box-shadow: 0 0 20rpx rgba(255, 215, 0, 0.5);
368 + }
369 +
370 + .rank-number {
371 + width: 220rpx;
372 + height: 240rpx;
373 + background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
374 + }
375 + }
376 +
377 + &.third {
378 + order: 3;
379 + margin-top: 60rpx;
380 + margin-left: -20rpx;
381 + z-index: 1;
382 +
383 + .avatar {
384 + box-shadow: 0 0 15rpx rgba(205, 127, 50, 0.4);
385 + }
386 +
387 + .rank-number {
388 + width: 220rpx;
389 + height: 160rpx;
390 + background: linear-gradient(135deg, #CD7F32 0%, #B8860B 100%);
391 + }
392 + }
393 + }
394 + }
395 +
396 + .rank-list {
397 + background: rgba(255, 255, 255, 0.95);
398 + border-radius: 24rpx 24rpx 0 0;
399 + padding: 20rpx 0 60rpx;
400 + margin-top: 0;
401 + position: relative;
402 + z-index: 4;
403 +
404 + .rank-list-item {
405 + display: flex;
406 + align-items: center;
407 + justify-content: space-between;
408 + padding: 24rpx 32rpx;
409 + border-bottom: 1rpx solid rgba(0, 0, 0, 0.05);
410 +
411 + &:last-child {
412 + border-bottom: none;
413 + }
414 +
415 + .rank-info {
416 + display: flex;
417 + align-items: center;
418 + flex: 1;
419 +
420 + .rank-num {
421 + width: 48rpx;
422 + font-size: 32rpx;
423 + font-weight: bold;
424 + color: #666;
425 + margin-right: 24rpx;
426 + }
427 +
428 + .avatar-small {
429 + width: 80rpx;
430 + height: 80rpx;
431 + border-radius: 50%;
432 + overflow: hidden;
433 + margin-right: 24rpx;
434 +
435 + .avatar-small-img {
436 + width: 100%;
437 + height: 100%;
438 + object-fit: cover;
439 + }
440 + }
441 +
442 + .family-info {
443 + flex: 1;
444 +
445 + .family-name-small {
446 + font-size: 28rpx;
447 + font-weight: 600;
448 + color: #333;
449 + margin-bottom: 8rpx;
450 + }
451 +
452 + .leader-name-small {
453 + font-size: 24rpx;
454 + color: #666;
455 + }
456 + }
457 + }
458 +
459 + .steps-info {
460 + text-align: right;
461 +
462 + .steps {
463 + font-size: 32rpx;
464 + font-weight: bold;
465 + color: #4A90E2;
466 + }
467 + }
468 + }
469 + }
470 +
471 + .my-rank-card {
472 + position: fixed;
473 + bottom: 40rpx;
474 + left: 40rpx;
475 + right: 40rpx;
476 + background: rgba(255, 255, 255, 0.95);
477 + border-radius: 24rpx;
478 + padding: 24rpx 32rpx;
479 + backdrop-filter: blur(10px);
480 + box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
481 + z-index: 99;
482 +
483 + .my-rank-content {
484 + display: flex;
485 + align-items: center;
486 + justify-content: space-between;
487 +
488 + .my-rank-left {
489 + display: flex;
490 + align-items: center;
491 + flex: 1;
492 +
493 + .my-rank-number {
494 + width: 60rpx;
495 + height: 60rpx;
496 + background: #4A90E2;
497 + border-radius: 50%;
498 + display: flex;
499 + align-items: center;
500 + justify-content: center;
501 + font-size: 24rpx;
502 + font-weight: bold;
503 + color: white;
504 + margin-right: 24rpx;
505 + }
506 +
507 + .my-avatar {
508 + width: 80rpx;
509 + height: 80rpx;
510 + border-radius: 50%;
511 + overflow: hidden;
512 + margin-right: 24rpx;
513 +
514 + .my-avatar-img {
515 + width: 100%;
516 + height: 100%;
517 + object-fit: cover;
518 + }
519 + }
520 +
521 + .my-family-info {
522 + flex: 1;
523 +
524 + .my-family-name {
525 + font-size: 28rpx;
526 + font-weight: 600;
527 + color: #333;
528 + margin-bottom: 8rpx;
529 + }
530 +
531 + .my-leader-name {
532 + font-size: 24rpx;
533 + color: #666;
534 + }
535 + }
536 + }
537 +
538 + .my-rank-right {
539 + text-align: right;
540 +
541 + .my-steps {
542 + font-size: 32rpx;
543 + font-weight: bold;
544 + color: #4A90E2;
545 + margin-bottom: 8rpx;
546 + }
547 +
548 + .rank-status {
549 + font-size: 24rpx;
550 + color: #999;
551 + }
552 + }
553 + }
554 + }
555 +}
556 +
557 +</style>