hookehuyr

feat(排行榜): 添加可滚动家庭列表组件替换弹幕功能

- 新增 ScrollableFamilyList 组件用于展示家庭信息
- 替换原有 NativeDanmuComponent 实现
- 优化支持页面的布局和样式
- 添加家庭数据获取和处理逻辑
...@@ -37,6 +37,7 @@ declare module 'vue' { ...@@ -37,6 +37,7 @@ declare module 'vue' {
37 RankingCard: typeof import('./src/components/RankingCard.vue')['default'] 37 RankingCard: typeof import('./src/components/RankingCard.vue')['default']
38 RouterLink: typeof import('vue-router')['RouterLink'] 38 RouterLink: typeof import('vue-router')['RouterLink']
39 RouterView: typeof import('vue-router')['RouterView'] 39 RouterView: typeof import('vue-router')['RouterView']
40 + ScrollableFamilyList: typeof import('./src/components/ScrollableFamilyList.vue')['default']
40 ShareButton: typeof import('./src/components/ShareButton/index.vue')['default'] 41 ShareButton: typeof import('./src/components/ShareButton/index.vue')['default']
41 TabBar: typeof import('./src/components/TabBar.vue')['default'] 42 TabBar: typeof import('./src/components/TabBar.vue')['default']
42 TotalPointsDisplay: typeof import('./src/components/TotalPointsDisplay.vue')['default'] 43 TotalPointsDisplay: typeof import('./src/components/TotalPointsDisplay.vue')['default']
......
1 <!-- 1 <!--
2 * @Date: 2025-01-09 00:00:00 2 * @Date: 2025-01-09 00:00:00
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-10-29 10:17:37 4 + * @LastEditTime: 2025-10-29 13:07:23
5 * @FilePath: /lls_program/src/components/RankingCard.vue 5 * @FilePath: /lls_program/src/components/RankingCard.vue
6 * @Description: 排行榜卡片组件 6 * @Description: 排行榜卡片组件
7 --> 7 -->
...@@ -42,9 +42,11 @@ ...@@ -42,9 +42,11 @@
42 <view class="rank-content" :class="{ 'content-switching': isContentSwitching }" style="margin: 40rpx;"> 42 <view class="rank-content" :class="{ 'content-switching': isContentSwitching }" style="margin: 40rpx;">
43 <!-- 排行榜日期 --> 43 <!-- 排行榜日期 -->
44 <view class="rank-date relative"> 44 <view class="rank-date relative">
45 - <!-- <view class="flex items-center justify-center"><text class="mr-2">截止昨日</text>{{ currentDate }}<text v-if="activeTab !== 'shanghai'" class="ml-2">排名</text><IconFont name="ask" size="16" class="ml-2" @click="handleRankAskClick"></IconFont></view> --> 45 + <view v-if="activeTab !== 'support'" class="flex items-center justify-center"><text class="mr-2">截止昨日</text>{{ currentDate }}<text class="ml-2">排名</text><IconFont name="ask" size="14" class="ml-2" @click="handleRankAskClick"></IconFont></view>
46 - <view class="flex items-center justify-center"><text class="mr-2">截止昨日</text>{{ currentDate }}<text class="ml-2">排名</text><IconFont v-if="activeTab !== 'support'" name="ask" size="16" class="ml-2" @click="handleRankAskClick"></IconFont></view> 46 + <!-- <view v-if="activeTab === 'support'" class="absolute font-bold text-white bg-orange-500 top-0 rounded-full px-4 py-1" style="right: 45rpx; top: -5rpx; font-size: 23rpx;" @tap="joinOrganization">助力码</view> -->
47 - <view v-if="activeTab === 'support'" class="absolute font-bold text-white bg-orange-500 top-0 rounded-full px-4 py-1" style="right: -30rpx; top: -5rpx; font-size: 23rpx; z-index: 999;" @tap="joinOrganization">助力码</view> 47 + <view v-if="activeTab === 'support'" class="flex items-center justify-center">
48 + <text class="font-bold text-white bg-orange-500 top-0 rounded-full px-4 py-1" style="font-size: 23rpx;" @tap="joinOrganization">助力码</text>
49 + </view>
48 </view> 50 </view>
49 51
50 <!-- <view v-if="activeTab === 'shanghai'" class="relative mb-2 text-white"> 52 <!-- <view v-if="activeTab === 'shanghai'" class="relative mb-2 text-white">
...@@ -125,7 +127,7 @@ ...@@ -125,7 +127,7 @@
125 </view> 127 </view>
126 128
127 <!-- 隐藏的弹幕组件,在页面加载时就开始滚动 --> 129 <!-- 隐藏的弹幕组件,在页面加载时就开始滚动 -->
128 - <view class="danmu-section" :style="{ visibility: activeTab === 'support' ? 'visible' : 'hidden', position: activeTab === 'support' ? 'static' : 'absolute', top: activeTab === 'support' ? 'auto' : '-9999px' }"> 130 + <!-- <view class="danmu-section" :style="{ visibility: activeTab === 'support' ? 'visible' : 'hidden', position: activeTab === 'support' ? 'static' : 'absolute', top: activeTab === 'support' ? 'auto' : '-9999px' }">
129 <NativeDanmuComponent 131 <NativeDanmuComponent
130 :container-height="700" 132 :container-height="700"
131 :show-controls="true" 133 :show-controls="true"
...@@ -134,6 +136,14 @@ ...@@ -134,6 +136,14 @@
134 @danmu-hover="handleDanmuHover" 136 @danmu-hover="handleDanmuHover"
135 ref="danmuRef" 137 ref="danmuRef"
136 /> 138 />
139 + </view> -->
140 +
141 + <!-- 可滚动家庭简介列表组件 -->
142 + <view v-if="activeTab === 'support'" class="scrollable-family-section mt-1" style="padding: 0 24rpx 34rpx;">
143 + <ScrollableFamilyList
144 + :family-data="familyDanmus"
145 + height="560rpx"
146 + />
137 </view> 147 </view>
138 148
139 <!-- 我的排名卡片 --> 149 <!-- 我的排名卡片 -->
...@@ -205,7 +215,8 @@ import { ref, computed, onMounted, watch, nextTick } from 'vue' ...@@ -205,7 +215,8 @@ import { ref, computed, onMounted, watch, nextTick } from 'vue'
205 import Taro from '@tarojs/taro' 215 import Taro from '@tarojs/taro'
206 import { IconFont } from '@nutui/icons-vue-taro'; 216 import { IconFont } from '@nutui/icons-vue-taro';
207 // import NumberRoll from './NumberRoll.vue' 217 // import NumberRoll from './NumberRoll.vue'
208 -import NativeDanmuComponent from '@/components/NativeDanmuComponent.vue' 218 +// import NativeDanmuComponent from '@/components/NativeDanmuComponent.vue'
219 +import ScrollableFamilyList from '@/components/ScrollableFamilyList.vue'
209 // 默认头像 220 // 默认头像
210 const defaultAvatar = 'https://cdn.ipadbiz.cn/lls_prog/images/%E5%85%A8%E5%AE%B6%E7%A6%8F3_%E5%89%AF%E6%9C%AC.jpg?imageMogr2/strip/quality/60' 221 const defaultAvatar = 'https://cdn.ipadbiz.cn/lls_prog/images/%E5%85%A8%E5%AE%B6%E7%A6%8F3_%E5%89%AF%E6%9C%AC.jpg?imageMogr2/strip/quality/60'
211 // 助力榜图片 222 // 助力榜图片
...@@ -324,6 +335,9 @@ const switchTab = async (tab) => { ...@@ -324,6 +335,9 @@ const switchTab = async (tab) => {
324 }, 200) 335 }, 200)
325 } 336 }
326 337
338 +// 家庭弹幕数据
339 +const familyDanmus = ref([])
340 +
327 /** 341 /**
328 * 加载排行榜数据 342 * 加载排行榜数据
329 * @param {boolean} isInitialLoad - 是否为初始加载,避免无限递归 343 * @param {boolean} isInitialLoad - 是否为初始加载,避免无限递归
...@@ -353,6 +367,15 @@ const loadLeaderboardData = async (isInitialLoad = false) => { ...@@ -353,6 +367,15 @@ const loadLeaderboardData = async (isInitialLoad = false) => {
353 currentDate.value = response.data.yesterday 367 currentDate.value = response.data.yesterday
354 // 设置总家庭数 368 // 设置总家庭数
355 currentTotalFamilySum.value = response.data.family_count || 0; 369 currentTotalFamilySum.value = response.data.family_count || 0;
370 + // 家庭弹幕数据处理
371 + if (response.data?.bullet_families) {
372 + familyDanmus.value = response.data.bullet_families.map(family => ({
373 + id: family.id,
374 + familyName: family.name,
375 + familyIntro: family.note,
376 + avatar: family.avatar_url || defaultAvatar
377 + }))
378 + }
356 } 379 }
357 } catch (error) { 380 } catch (error) {
358 console.error('获取排行榜数据失败:', error) 381 console.error('获取排行榜数据失败:', error)
......
1 +<template>
2 + <view class="scrollable-family-list" :style="{ height: containerHeight }">
3 + <scroll-view
4 + class="scroll-container"
5 + :scroll-y="true"
6 + :scroll-top="scrollTop"
7 + :enable-back-to-top="false"
8 + :scroll-with-animation="true"
9 + :enhanced="true"
10 + :bounces="true"
11 + :show-scrollbar="false"
12 + @scroll="onScroll"
13 + @scrolltoupper="onScrollToUpper"
14 + @scrolltolower="onScrollToLower"
15 + >
16 + <view class="content-wrapper" :style="{ opacity: contentOpacity }">
17 + <view
18 + v-for="(item, index) in currentPageData"
19 + :key="`${currentPage}-${index}`"
20 + class="family-item"
21 + :style="getItemStyle(index)"
22 + >
23 + <view class="family-content">
24 + <!-- 头像 -->
25 + <view class="family-avatar-container">
26 + <image
27 + :src="item.avatar"
28 + class="family-avatar"
29 + mode="aspectFill"
30 + />
31 + </view>
32 +
33 + <!-- 信息区域 -->
34 + <view class="family-info">
35 + <!-- 标题行 -->
36 + <view class="family-title-row">
37 + <text class="family-name">{{ item.familyName }}</text>
38 + </view>
39 +
40 + <!-- 介绍 -->
41 + <text class="family-intro">{{ item.familyIntro }}</text>
42 + </view>
43 + </view>
44 + </view>
45 + </view>
46 + </scroll-view>
47 + </view>
48 +</template>
49 +
50 +<script setup>
51 +import { ref, computed, watch, onMounted, nextTick } from 'vue'
52 +import Taro, { usePageScroll } from '@tarojs/taro'
53 +
54 +// Props
55 +const props = defineProps({
56 + // 家庭数据列表
57 + familyData: {
58 + type: Array,
59 + default: () => []
60 + },
61 + // 容器高度
62 + height: {
63 + type: String,
64 + default: '600rpx'
65 + },
66 + // 每页显示的行数
67 + itemsPerPage: {
68 + type: Number,
69 + default: 5
70 + },
71 + // 是否监听外部容器滚动
72 + listenExternalScroll: {
73 + type: Boolean,
74 + default: false
75 + }
76 +})
77 +
78 +// 响应式数据
79 +const scrollTop = ref(0)
80 +const currentPage = ref(0)
81 +const contentOpacity = ref(1)
82 +const isTransitioning = ref(false)
83 +const lastScrollTop = ref(0)
84 +const scrollDirection = ref('') // 'up' | 'down'
85 +
86 +// 计算属性
87 +const containerHeight = computed(() => props.height)
88 +
89 +const totalPages = computed(() => {
90 + return Math.ceil(props.familyData.length / props.itemsPerPage)
91 +})
92 +
93 +const currentPageData = computed(() => {
94 + const start = currentPage.value * props.itemsPerPage
95 + // 每页多显示一个item,有利于滚动触发
96 + const end = start + props.itemsPerPage + 2
97 + return props.familyData.slice(start, end)
98 +})
99 +
100 +// 随机水平位置数组
101 +const randomOffsets = ref([])
102 +
103 +// 生成随机水平偏移
104 +const generateRandomOffsets = () => {
105 + // 为实际显示的item数量生成偏移(包括多出的一个)
106 + const actualItemCount = Math.min(props.itemsPerPage + 2, props.familyData.length - currentPage.value * props.itemsPerPage)
107 +
108 + randomOffsets.value = Array.from({ length: actualItemCount }, (_, index) => {
109 + // 确保一屏内有靠最左和最右的简介,但不能被裁切
110 + if (index === 0) {
111 + // 第一个简介靠左,但留出足够边距避免裁切
112 + return 0 // 不偏移,保持在安全区域
113 + } else if (index === actualItemCount - 1) {
114 + // 最后一个简介靠最右,使用特殊标记
115 + return 100 // 使用特殊值标记需要贴右边
116 + } else {
117 + // 其他简介在中间随机分布,避免负偏移
118 + return Math.floor(Math.random() * 81) // 0rpx 到 80rpx
119 + }
120 + })
121 +}
122 +
123 +// 获取每个项目的样式
124 +const getItemStyle = (index) => {
125 + const offset = randomOffsets.value[index] || 0
126 + const isLastItem = index === randomOffsets.value.length - 1
127 +
128 + // 最后一个item贴右边的特殊处理
129 + if (isLastItem && offset === 100) {
130 + return {
131 + marginBottom: '24rpx',
132 + maxWidth: '100%',
133 + paddingLeft: '0',
134 + paddingRight: '0',
135 + display: 'flex',
136 + justifyContent: 'flex-end', // 让内容靠右对齐
137 + // 不使用transform,完全依靠justify-content来贴右边
138 + }
139 + }
140 +
141 + // 其他item的正常处理
142 + return {
143 + transform: `translateX(${offset}rpx)`,
144 + marginBottom: '24rpx',
145 + // 确保内容不被裁切,所有偏移都是正值或0
146 + paddingRight: offset > 50 ? '24rpx' : '0',
147 + maxWidth: '100%',
148 + // 确保左侧有足够的边距
149 + paddingLeft: '0'
150 + }
151 +}
152 +
153 +// 滚动事件处理
154 +const onScroll = (e) => {
155 + if (isTransitioning.value) return
156 +
157 + const currentScrollTop = e.detail.scrollTop
158 + const delta = currentScrollTop - lastScrollTop.value
159 +
160 + // 提高阈值,减少误触发
161 + if (Math.abs(delta) > 10) {
162 + scrollDirection.value = delta > 0 ? 'down' : 'up'
163 + }
164 +
165 + lastScrollTop.value = currentScrollTop
166 +}
167 +
168 +// 滚动到顶部 - 优化触发逻辑
169 +const onScrollToUpper = () => {
170 + if (isTransitioning.value) return
171 +
172 + // 添加延迟防抖,避免快速触发
173 + setTimeout(() => {
174 + if (scrollDirection.value === 'up' && !isTransitioning.value) {
175 + changePage('prev')
176 + }
177 + }, 100)
178 +}
179 +
180 +// 滚动到底部 - 优化触发逻辑
181 +const onScrollToLower = () => {
182 + if (isTransitioning.value) return
183 +
184 + // 添加延迟防抖,避免快速触发
185 + setTimeout(() => {
186 + if (scrollDirection.value === 'down' && !isTransitioning.value) {
187 + changePage('next')
188 + }
189 + }, 100)
190 +}
191 +
192 +// 切换页面 - 优化过渡效果
193 +const changePage = async (direction) => {
194 + if (isTransitioning.value) return
195 +
196 + isTransitioning.value = true
197 +
198 + // 重置滚动方向,避免连续触发
199 + scrollDirection.value = ''
200 +
201 + // 淡出效果
202 + contentOpacity.value = 0
203 +
204 + await new Promise(resolve => setTimeout(resolve, 250))
205 +
206 + // 更新页面
207 + if (direction === 'next') {
208 + currentPage.value = (currentPage.value + 1) % totalPages.value
209 + } else {
210 + currentPage.value = currentPage.value === 0
211 + ? totalPages.value - 1
212 + : currentPage.value - 1
213 + }
214 +
215 + generateRandomOffsets()
216 +
217 + // 重置滚动位置到中间位置,确保有足够的滚动空间
218 + // 设置一个中间值,让用户可以向上或向下滚动
219 + scrollTop.value = 100
220 +
221 + await nextTick()
222 +
223 + // 再次确保滚动位置重置,给足够的滚动触发空间
224 + setTimeout(() => {
225 + scrollTop.value = 50
226 + }, 50)
227 +
228 + // 淡入效果
229 + contentOpacity.value = 1
230 +
231 + // 延长过渡锁定时间,确保动画完成
232 + setTimeout(() => {
233 + isTransitioning.value = false
234 + // 最终重置到一个稳定的中间位置
235 + scrollTop.value = 80
236 + }, 400)
237 +}
238 +
239 +// 外部滚动监听
240 +const handleExternalScroll = (res) => {
241 + if (!props.listenExternalScroll || isTransitioning.value) return
242 +
243 + // 获取页面滚动信息
244 + const { scrollTop } = res
245 + const windowHeight = Taro.getSystemInfoSync().windowHeight
246 +
247 + // 使用createSelectorQuery获取页面高度
248 + const query = Taro.createSelectorQuery()
249 + query.selectViewport().scrollOffset()
250 + query.exec((queryRes) => {
251 + if (queryRes && queryRes[0]) {
252 + const { scrollHeight } = queryRes[0]
253 +
254 + // 判断是否滚动到底部(留一些余量)
255 + if (scrollTop + windowHeight >= scrollHeight - 50) {
256 + // 触发翻页到下一页
257 + changePage('next')
258 + }
259 + }
260 + })
261 +}
262 +
263 +// 使用usePageScroll监听页面滚动
264 +if (props.listenExternalScroll) {
265 + usePageScroll(handleExternalScroll)
266 +}
267 +
268 +// 监听数据变化
269 +watch(() => props.familyData, () => {
270 + currentPage.value = 0
271 + generateRandomOffsets()
272 +}, { immediate: true })
273 +
274 +// 组件挂载
275 +onMounted(() => {
276 + generateRandomOffsets()
277 +})
278 +</script>
279 +
280 +<style>
281 +.scrollable-family-list {
282 + position: relative;
283 + width: 100%;
284 + /* border-radius: 20rpx; */
285 + overflow: hidden;
286 + /* background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); */
287 +
288 +}
289 +
290 +.scroll-container {
291 + width: 100%;
292 + height: 100%;
293 +}
294 +
295 +.content-wrapper {
296 + /* padding: 32rpx 0; */
297 + padding-top: 32rpx;
298 + transition: opacity 0.3s ease;
299 + min-height: 100%;
300 +}
301 +
302 +.family-item {
303 + width: 100%;
304 + transition: transform 0.3s ease;
305 +}
306 +
307 +.family-content {
308 + display: flex;
309 + align-items: center;
310 + background: rgba(255, 255, 255, 0.75);
311 + border-radius: 30rpx;
312 + padding: 20rpx 24rpx;
313 + box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
314 + backdrop-filter: blur(12rpx);
315 + border: 2rpx solid rgba(255, 255, 255, 0.5);
316 + max-width: 500rpx;
317 + min-width: 350rpx;
318 + transition: all 0.3s ease;
319 +}
320 +
321 +.family-content:hover {
322 + transform: translateY(-4rpx);
323 + box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.12);
324 +}
325 +
326 +.family-avatar-container {
327 + flex-shrink: 0;
328 + margin-right: 20rpx;
329 + position: relative;
330 +}
331 +
332 +.family-avatar {
333 + width: 64rpx;
334 + height: 64rpx;
335 + border-radius: 50%;
336 + border: 3rpx solid #fff;
337 + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
338 +}
339 +
340 +.family-avatar-container::after {
341 + content: '';
342 + position: absolute;
343 + top: -3rpx;
344 + left: -3rpx;
345 + right: -3rpx;
346 + bottom: -3rpx;
347 + border-radius: 50%;
348 + background: linear-gradient(45deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4);
349 + z-index: -1;
350 + animation: rotate 4s linear infinite;
351 +}
352 +
353 +@keyframes rotate {
354 + from { transform: rotate(0deg); }
355 + to { transform: rotate(360deg); }
356 +}
357 +
358 +.family-info {
359 + flex: 1;
360 + min-width: 0;
361 +}
362 +
363 +.family-title-row {
364 + display: flex;
365 + align-items: center;
366 + margin-bottom: 8rpx;
367 +}
368 +
369 +.family-name {
370 + font-weight: 700;
371 + color: #1a202c;
372 + font-size: 32rpx;
373 + margin-right: 12rpx;
374 + flex-shrink: 0;
375 + text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.1);
376 + overflow: hidden;
377 + text-overflow: ellipsis;
378 + white-space: nowrap;
379 +}
380 +
381 +.family-intro {
382 + color: #4a5568;
383 + font-size: 26rpx;
384 + display: -webkit-box;
385 + overflow: hidden;
386 + text-overflow: ellipsis;
387 + -webkit-line-clamp: 2;
388 + -webkit-box-orient: vertical;
389 + line-height: 1.5;
390 + font-weight: 500;
391 +}
392 +
393 +.page-indicator {
394 + position: absolute;
395 + bottom: 16rpx;
396 + left: 50%;
397 + transform: translateX(-50%);
398 + display: flex;
399 + gap: 12rpx;
400 + z-index: 10;
401 +}
402 +
403 +.indicator-dot {
404 + width: 12rpx;
405 + height: 12rpx;
406 + border-radius: 50%;
407 + background: rgba(255, 255, 255, 0.4);
408 + transition: all 0.3s ease;
409 +}
410 +
411 +.indicator-dot.active {
412 + background: rgba(255, 255, 255, 0.9);
413 + transform: scale(1.2);
414 +}
415 +</style>
1 <!-- 1 <!--
2 * @Date: 2025-09-01 13:07:52 2 * @Date: 2025-09-01 13:07:52
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-10-29 10:18:35 4 + * @LastEditTime: 2025-10-29 12:49:27
5 * @FilePath: /lls_program/src/pages/FamilyRank/index.vue 5 * @FilePath: /lls_program/src/pages/FamilyRank/index.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
8 <template> 8 <template>
9 - <view class="family-rank-page"> 9 + <view class="family-rank-page" :style="{ paddingBottom: activeTab === 'support' ? '0' : '34rpx' }">
10 <!-- 顶部导航 --> 10 <!-- 顶部导航 -->
11 <view class="header"> 11 <view class="header">
12 <view class="nav-tabs"> 12 <view class="nav-tabs">
...@@ -40,9 +40,11 @@ ...@@ -40,9 +40,11 @@
40 40
41 <!-- 排行榜日期 --> 41 <!-- 排行榜日期 -->
42 <view v-if="!loading" class="rank-date relative"> 42 <view v-if="!loading" class="rank-date relative">
43 - <!-- <view class="flex items-center justify-center"><text class="mr-2">截止昨日</text>{{ currentDate }}<text v-if="activeTab !== 'shanghai'" class="ml-2">排名</text><IconFont name="ask" size="14" class="ml-2" @click="handleRankAskClick"></IconFont></view> --> 43 + <view v-if="activeTab !== 'support'" class="flex items-center justify-center"><text class="mr-2">截止昨日</text>{{ currentDate }}<text class="ml-2">排名</text><IconFont name="ask" size="14" class="ml-2" @click="handleRankAskClick"></IconFont></view>
44 - <view class="flex items-center justify-center"><text class="mr-2">截止昨日</text>{{ currentDate }}<text class="ml-2">排名</text><IconFont name="ask" size="14" class="ml-2" @click="handleRankAskClick"></IconFont></view> 44 + <!-- <view v-if="activeTab === 'support'" class="absolute font-bold text-white bg-orange-500 top-0 rounded-full px-4 py-1" style="right: 45rpx; top: -5rpx; font-size: 23rpx;" @tap="joinOrganization">助力码</view> -->
45 - <view v-if="activeTab === 'support'" class="absolute font-bold text-white bg-orange-500 top-0 rounded-full px-4 py-1" style="right: 45rpx; top: -5rpx; font-size: 23rpx;" @tap="joinOrganization">助力码</view> 45 + <view v-if="activeTab === 'support'" class="flex items-center justify-center">
46 + <text class="font-bold text-white bg-orange-500 top-0 rounded-full px-4 py-1" style="font-size: 23rpx;" @tap="joinOrganization">助力码</text>
47 + </view>
46 </view> 48 </view>
47 49
48 <!-- 参与家庭数量显示 --> 50 <!-- 参与家庭数量显示 -->
...@@ -157,7 +159,7 @@ ...@@ -157,7 +159,7 @@
157 </view> 159 </view>
158 160
159 <!-- 隐藏的弹幕组件,在页面加载时就开始滚动 --> 161 <!-- 隐藏的弹幕组件,在页面加载时就开始滚动 -->
160 - <view class="danmu-section mt-8" :style="{ visibility: activeTab === 'support' ? 'visible' : 'hidden', position: activeTab === 'support' ? 'static' : 'absolute', top: activeTab === 'support' ? 'auto' : '-9999px' }"> 162 + <!-- <view class="danmu-section mt-8" :style="{ visibility: activeTab === 'support' ? 'visible' : 'hidden', position: activeTab === 'support' ? 'static' : 'absolute', top: activeTab === 'support' ? 'auto' : '-9999px' }">
161 <NativeDanmuComponent 163 <NativeDanmuComponent
162 :container-height="1200" 164 :container-height="1200"
163 :show-controls="true" 165 :show-controls="true"
...@@ -166,6 +168,15 @@ ...@@ -166,6 +168,15 @@
166 @danmu-hover="handleDanmuHover" 168 @danmu-hover="handleDanmuHover"
167 ref="danmuRef" 169 ref="danmuRef"
168 /> 170 />
171 + </view> -->
172 +
173 + <!-- 可滚动家庭简介列表组件 -->
174 + <view v-if="activeTab === 'support'" class="scrollable-family-section mt-1" style="padding: 0 24rpx;">
175 + <ScrollableFamilyList
176 + :family-data="familyDanmus"
177 + height="1200rpx"
178 + :listen-external-scroll="true"
179 + />
169 </view> 180 </view>
170 181
171 <!-- 我的排名悬浮卡片 --> 182 <!-- 我的排名悬浮卡片 -->
...@@ -220,7 +231,8 @@ import { ref, computed, onMounted } from 'vue' ...@@ -220,7 +231,8 @@ import { ref, computed, onMounted } from 'vue'
220 import Taro from '@tarojs/taro' 231 import Taro from '@tarojs/taro'
221 import { IconFont } from '@nutui/icons-vue-taro'; 232 import { IconFont } from '@nutui/icons-vue-taro';
222 import BackToTop from '@/components/BackToTop.vue' 233 import BackToTop from '@/components/BackToTop.vue'
223 -import NativeDanmuComponent from '@/components/NativeDanmuComponent.vue' 234 +import ScrollableFamilyList from '@/components/ScrollableFamilyList.vue'
235 +// import NativeDanmuComponent from '@/components/NativeDanmuComponent.vue'
224 // import NumberRoll from '@/components/NumberRoll.vue' 236 // import NumberRoll from '@/components/NumberRoll.vue'
225 // 默认头像 237 // 默认头像
226 const defaultAvatar = 'https://cdn.ipadbiz.cn/lls_prog/images/%E5%85%A8%E5%AE%B6%E7%A6%8F3_%E5%89%AF%E6%9C%AC.jpg?imageMogr2/strip/quality/60' 238 const defaultAvatar = 'https://cdn.ipadbiz.cn/lls_prog/images/%E5%85%A8%E5%AE%B6%E7%A6%8F3_%E5%89%AF%E6%9C%AC.jpg?imageMogr2/strip/quality/60'
...@@ -274,6 +286,9 @@ const loading = ref(false) ...@@ -274,6 +286,9 @@ const loading = ref(false)
274 // 当前日期 286 // 当前日期
275 const currentDate = ref('') 287 const currentDate = ref('')
276 288
289 +// 家庭弹幕数据
290 +const familyDanmus = ref([])
291 +
277 /** 292 /**
278 * 处理排行榜须知点击事件 293 * 处理排行榜须知点击事件
279 */ 294 */
...@@ -285,22 +300,22 @@ const handleRankAskClick = () => { ...@@ -285,22 +300,22 @@ const handleRankAskClick = () => {
285 /** 300 /**
286 * 弹幕组件相关 301 * 弹幕组件相关
287 */ 302 */
288 -const danmuRef = ref(null) 303 +// const danmuRef = ref(null)
289 304
290 // 处理弹幕点击事件 305 // 处理弹幕点击事件
291 -const handleDanmuClick = (familyData) => { 306 +// const handleDanmuClick = (familyData) => {
292 - console.log('弹幕点击:', familyData) 307 +// console.log('弹幕点击:', familyData)
293 - // Taro.showToast({ 308 +// // Taro.showToast({
294 - // title: `点击了${familyData.familyName}`, 309 +// // title: `点击了${familyData.familyName}`,
295 - // icon: 'none', 310 +// // icon: 'none',
296 - // duration: 2000 311 +// // duration: 2000
297 - // }) 312 +// // })
298 -} 313 +// }
299 314
300 // 处理弹幕悬停事件 315 // 处理弹幕悬停事件
301 -const handleDanmuHover = (familyData) => { 316 +// const handleDanmuHover = (familyData) => {
302 - console.log('弹幕悬停:', familyData) 317 +// console.log('弹幕悬停:', familyData)
303 -} 318 +// }
304 319
305 /** 320 /**
306 * 触发数字滚动动画 321 * 触发数字滚动动画
...@@ -411,6 +426,15 @@ const loadLeaderboardData = async (isInitialLoad = false) => { ...@@ -411,6 +426,15 @@ const loadLeaderboardData = async (isInitialLoad = false) => {
411 currentDate.value = response.data.yesterday 426 currentDate.value = response.data.yesterday
412 // 设置总家庭数 427 // 设置总家庭数
413 currentTotalFamilySum.value = response.data.family_count || 0; 428 currentTotalFamilySum.value = response.data.family_count || 0;
429 + // 家庭弹幕数据处理
430 + if (response.data?.bullet_families) {
431 + familyDanmus.value = response.data.bullet_families.map(family => ({
432 + id: family.id,
433 + familyName: family.name,
434 + familyIntro: family.note,
435 + avatar: family.avatar_url || defaultAvatar
436 + }))
437 + }
414 } 438 }
415 } catch (error) { 439 } catch (error) {
416 console.error('获取排行榜数据失败:', error) 440 console.error('获取排行榜数据失败:', error)
...@@ -948,6 +972,14 @@ onMounted(async () => { ...@@ -948,6 +972,14 @@ onMounted(async () => {
948 } 972 }
949 } 973 }
950 974
975 + /* 可滚动家庭简介列表样式 */
976 + .scrollable-family-section {
977 + // margin: 32rpx 24rpx;
978 + // border-radius: 20rpx;
979 + // overflow: hidden;
980 + // box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
981 + }
982 +
951 /* 加载状态 */ 983 /* 加载状态 */
952 .loading-container { 984 .loading-container {
953 display: flex; 985 display: flex;
......