feat(积分收集): 添加积分收集完成后的总积分显示组件
新增TotalPointsDisplay组件用于展示积分收集完成后的总积分 修改PointsCollector组件在收集完成时触发collection-complete事件 在Dashboard页面根据收集状态切换显示PointsCollector或TotalPointsDisplay
Showing
4 changed files
with
179 additions
and
3 deletions
| ... | @@ -27,6 +27,7 @@ declare module 'vue' { | ... | @@ -27,6 +27,7 @@ declare module 'vue' { |
| 27 | RouterLink: typeof import('vue-router')['RouterLink'] | 27 | RouterLink: typeof import('vue-router')['RouterLink'] |
| 28 | RouterView: typeof import('vue-router')['RouterView'] | 28 | RouterView: typeof import('vue-router')['RouterView'] |
| 29 | TabBar: typeof import('./src/components/TabBar.vue')['default'] | 29 | TabBar: typeof import('./src/components/TabBar.vue')['default'] |
| 30 | + TotalPointsDisplay: typeof import('./src/components/TotalPointsDisplay.vue')['default'] | ||
| 30 | WeRunAuth: typeof import('./src/components/WeRunAuth.vue')['default'] | 31 | WeRunAuth: typeof import('./src/components/WeRunAuth.vue')['default'] |
| 31 | } | 32 | } |
| 32 | } | 33 | } | ... | ... |
| ... | @@ -38,10 +38,10 @@ | ... | @@ -38,10 +38,10 @@ |
| 38 | </template> | 38 | </template> |
| 39 | 39 | ||
| 40 | <script setup> | 40 | <script setup> |
| 41 | -import { ref, onMounted, defineProps, defineExpose } from 'vue' | 41 | +import { ref, onMounted, defineProps, defineExpose, defineEmits } from 'vue' |
| 42 | import Taro, { useDidShow } from '@tarojs/taro' | 42 | import Taro, { useDidShow } from '@tarojs/taro' |
| 43 | 43 | ||
| 44 | -// 定义props | 44 | +const emit = defineEmits(['collection-complete']) |
| 45 | const props = defineProps({ | 45 | const props = defineProps({ |
| 46 | height: { | 46 | height: { |
| 47 | type: String, | 47 | type: String, |
| ... | @@ -179,6 +179,9 @@ const collectItem = (item) => { | ... | @@ -179,6 +179,9 @@ const collectItem = (item) => { |
| 179 | totalPoints.value += totalToAdd; | 179 | totalPoints.value += totalToAdd; |
| 180 | 180 | ||
| 181 | floatingItems.value = floatingItems.value.filter(i => i.id !== item.id); | 181 | floatingItems.value = floatingItems.value.filter(i => i.id !== item.id); |
| 182 | + if (floatingItems.value.length === 0) { | ||
| 183 | + emit('collection-complete', totalPoints.value); | ||
| 184 | + } | ||
| 182 | }, 800); // 动画时长 | 185 | }, 800); // 动画时长 |
| 183 | } | 186 | } |
| 184 | 187 | ||
| ... | @@ -206,6 +209,7 @@ const collectAll = async () => { | ... | @@ -206,6 +209,7 @@ const collectAll = async () => { |
| 206 | 209 | ||
| 207 | floatingItems.value = []; | 210 | floatingItems.value = []; |
| 208 | isCollecting.value = false; | 211 | isCollecting.value = false; |
| 212 | + emit('collection-complete', totalPoints.value); | ||
| 209 | }, totalAnimationTime); | 213 | }, totalAnimationTime); |
| 210 | } | 214 | } |
| 211 | 215 | ... | ... |
src/components/TotalPointsDisplay.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="total-points-display" :style="{ height: height }"> | ||
| 3 | + <!-- 中心圆形显示总积分 --> | ||
| 4 | + <view class="center-circle1"> | ||
| 5 | + <view class="total-points" @tap="handleGoToRewards"> | ||
| 6 | + <text class="points-number">{{ animatedTotalPoints }}分</text> | ||
| 7 | + <text class="points-label">去兑换</text> | ||
| 8 | + </view> | ||
| 9 | + </view> | ||
| 10 | + </view> | ||
| 11 | +</template> | ||
| 12 | + | ||
| 13 | +<script setup> | ||
| 14 | +import { ref, watch, onMounted, defineProps } from 'vue' | ||
| 15 | +import Taro from '@tarojs/taro' | ||
| 16 | + | ||
| 17 | +// 定义props | ||
| 18 | +const props = defineProps({ | ||
| 19 | + totalPoints: { | ||
| 20 | + type: Number, | ||
| 21 | + required: true, | ||
| 22 | + default: 0 | ||
| 23 | + }, | ||
| 24 | + height: { | ||
| 25 | + type: String, | ||
| 26 | + default: '30vh' | ||
| 27 | + } | ||
| 28 | +}) | ||
| 29 | + | ||
| 30 | +// 响应式数据 | ||
| 31 | +const animatedTotalPoints = ref(0) | ||
| 32 | + | ||
| 33 | +/** | ||
| 34 | + * 数字滚动动画 | ||
| 35 | + * @param {number} start - 动画开始值 | ||
| 36 | + * @param {number} end - 动画结束值 | ||
| 37 | + */ | ||
| 38 | +const animateNumber = (start, end) => { | ||
| 39 | + if (start === end) { | ||
| 40 | + animatedTotalPoints.value = end; | ||
| 41 | + return; | ||
| 42 | + } | ||
| 43 | + const duration = 800; | ||
| 44 | + const startTime = Date.now(); | ||
| 45 | + const difference = end - start; | ||
| 46 | + | ||
| 47 | + const animate = () => { | ||
| 48 | + const elapsed = Date.now() - startTime; | ||
| 49 | + const progress = Math.min(elapsed / duration, 1); | ||
| 50 | + const easeOut = 1 - Math.pow(1 - progress, 3); | ||
| 51 | + animatedTotalPoints.value = Math.floor(start + difference * easeOut); | ||
| 52 | + | ||
| 53 | + if (progress < 1) { | ||
| 54 | + requestAnimationFrame(animate); | ||
| 55 | + } else { | ||
| 56 | + animatedTotalPoints.value = end; | ||
| 57 | + } | ||
| 58 | + }; | ||
| 59 | + animate(); | ||
| 60 | +} | ||
| 61 | + | ||
| 62 | +// 监听totalPoints变化,并执行动画 | ||
| 63 | +watch(() => props.totalPoints, (newValue, oldValue) => { | ||
| 64 | + animateNumber(oldValue, newValue); | ||
| 65 | +}) | ||
| 66 | + | ||
| 67 | +onMounted(() => { | ||
| 68 | + animateNumber(0, props.totalPoints) | ||
| 69 | +}) | ||
| 70 | + | ||
| 71 | +/** | ||
| 72 | + * 处理去兑换点击事件 | ||
| 73 | + */ | ||
| 74 | +const handleGoToRewards = () => { | ||
| 75 | + Taro.navigateTo({ | ||
| 76 | + url: '/pages/RewardCategories/index', | ||
| 77 | + }) | ||
| 78 | +} | ||
| 79 | +</script> | ||
| 80 | + | ||
| 81 | +<style lang="less"> | ||
| 82 | +.total-points-display { | ||
| 83 | + position: relative; | ||
| 84 | + width: 100vw; | ||
| 85 | + overflow: hidden; | ||
| 86 | + display: flex; | ||
| 87 | + justify-content: center; | ||
| 88 | + align-items: center; | ||
| 89 | +} | ||
| 90 | + | ||
| 91 | +.center-circle1 { | ||
| 92 | + position: absolute; | ||
| 93 | + left: 50%; | ||
| 94 | + top: 50%; | ||
| 95 | + transform: translate(-50%, -50%); | ||
| 96 | + width: 170rpx; | ||
| 97 | + height: 170rpx; | ||
| 98 | + background: linear-gradient(135deg, #4A90E2, #4d96ea); | ||
| 99 | + border-radius: 50%; | ||
| 100 | + display: flex; | ||
| 101 | + align-items: center; | ||
| 102 | + justify-content: center; | ||
| 103 | + box-shadow: 0 8rpx 32rpx rgba(53, 144, 255, 0.3); | ||
| 104 | + z-index: 10; | ||
| 105 | +} | ||
| 106 | + | ||
| 107 | +.total-points { | ||
| 108 | + text-align: center; | ||
| 109 | + color: white; | ||
| 110 | +} | ||
| 111 | + | ||
| 112 | +.points-number { | ||
| 113 | + display: block; | ||
| 114 | + font-size: 48rpx; | ||
| 115 | + font-weight: bold; | ||
| 116 | + line-height: 1; | ||
| 117 | +} | ||
| 118 | + | ||
| 119 | +.points-label { | ||
| 120 | + display: block; | ||
| 121 | + font-size: 24rpx; | ||
| 122 | + margin-top: 8rpx; | ||
| 123 | + opacity: 0.9; | ||
| 124 | +} | ||
| 125 | + | ||
| 126 | +// 响应式适配 | ||
| 127 | +@media screen and (max-width: 750px) { | ||
| 128 | + .center-circle { | ||
| 129 | + width: 160rpx; | ||
| 130 | + height: 160rpx; | ||
| 131 | + } | ||
| 132 | + | ||
| 133 | + .points-number { | ||
| 134 | + font-size: 36rpx; | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + .points-label { | ||
| 138 | + font-size: 20rpx; | ||
| 139 | + } | ||
| 140 | +} | ||
| 141 | +</style> |
| ... | @@ -55,7 +55,20 @@ | ... | @@ -55,7 +55,20 @@ |
| 55 | 55 | ||
| 56 | <!-- Points circles --> | 56 | <!-- Points circles --> |
| 57 | <view class="flex justify-between px-5 py-6 my-4 bg-white rounded-xl shadow-md mx-4"> | 57 | <view class="flex justify-between px-5 py-6 my-4 bg-white rounded-xl shadow-md mx-4"> |
| 58 | - <PointsCollector ref="pointsCollectorRef" height="30vh" /> | 58 | + <template v-if="!showTotalPointsOnly"> |
| 59 | + <PointsCollector | ||
| 60 | + ref="pointsCollectorRef" | ||
| 61 | + height="30vh" | ||
| 62 | + :total-points="finalTotalPoints" | ||
| 63 | + @collection-complete="handleCollectionComplete" | ||
| 64 | + /> | ||
| 65 | + </template> | ||
| 66 | + <template v-else> | ||
| 67 | + <TotalPointsDisplay | ||
| 68 | + :total-points="finalTotalPoints" | ||
| 69 | + height="13vh" | ||
| 70 | + /> | ||
| 71 | + </template> | ||
| 59 | </view> | 72 | </view> |
| 60 | 73 | ||
| 61 | <!-- Photo button --> | 74 | <!-- Photo button --> |
| ... | @@ -184,6 +197,7 @@ import { ref, computed, onMounted } from 'vue'; | ... | @@ -184,6 +197,7 @@ import { ref, computed, onMounted } from 'vue'; |
| 184 | import Taro, { useDidShow } from '@tarojs/taro'; | 197 | import Taro, { useDidShow } from '@tarojs/taro'; |
| 185 | import { Setting, Photograph, Right, Close, Category } from '@nutui/icons-vue-taro'; | 198 | import { Setting, Photograph, Right, Close, Category } from '@nutui/icons-vue-taro'; |
| 186 | import BottomNav from '../../components/BottomNav.vue'; | 199 | import BottomNav from '../../components/BottomNav.vue'; |
| 200 | +import TotalPointsDisplay from '@/components/TotalPointsDisplay.vue'; | ||
| 187 | import PointsCollector from '@/components/PointsCollector.vue' | 201 | import PointsCollector from '@/components/PointsCollector.vue' |
| 188 | import WeRunAuth from '@/components/WeRunAuth.vue' | 202 | import WeRunAuth from '@/components/WeRunAuth.vue' |
| 189 | import { useMediaPreview } from '@/composables/useMediaPreview'; | 203 | import { useMediaPreview } from '@/composables/useMediaPreview'; |
| ... | @@ -192,6 +206,8 @@ const todaySteps = ref(0); | ... | @@ -192,6 +206,8 @@ const todaySteps = ref(0); |
| 192 | const isWeRunAuthorized = ref(false); | 206 | const isWeRunAuthorized = ref(false); |
| 193 | const pointsCollectorRef = ref(null) | 207 | const pointsCollectorRef = ref(null) |
| 194 | const weRunAuthRef = ref(null) | 208 | const weRunAuthRef = ref(null) |
| 209 | +const showTotalPointsOnly = ref(false) | ||
| 210 | +const finalTotalPoints = ref(0) | ||
| 195 | 211 | ||
| 196 | const familyName = ref('') | 212 | const familyName = ref('') |
| 197 | const familySlogn = ref('') | 213 | const familySlogn = ref('') |
| ... | @@ -240,6 +256,15 @@ const handleCollectAll = () => { | ... | @@ -240,6 +256,15 @@ const handleCollectAll = () => { |
| 240 | } | 256 | } |
| 241 | 257 | ||
| 242 | /** | 258 | /** |
| 259 | + * 处理积分收集完成事件 | ||
| 260 | + * @param {number} points - 最终的积分 | ||
| 261 | + */ | ||
| 262 | +const handleCollectionComplete = (points) => { | ||
| 263 | + finalTotalPoints.value = points; | ||
| 264 | + showTotalPointsOnly.value = true; | ||
| 265 | +}; | ||
| 266 | + | ||
| 267 | +/** | ||
| 243 | * 手动刷新微信步数 | 268 | * 手动刷新微信步数 |
| 244 | */ | 269 | */ |
| 245 | const handleRefreshSteps = async () => { | 270 | const handleRefreshSteps = async () => { |
| ... | @@ -348,6 +373,11 @@ useDidShow(() => { | ... | @@ -348,6 +373,11 @@ useDidShow(() => { |
| 348 | // Taro.redirectTo({ url: '/pages/Welcome/index' }); | 373 | // Taro.redirectTo({ url: '/pages/Welcome/index' }); |
| 349 | // } | 374 | // } |
| 350 | initPageData(); | 375 | initPageData(); |
| 376 | + | ||
| 377 | + // TODO: 真实情况下 需要重新获取积分列表, 测试随机积分获取状态 | ||
| 378 | + showTotalPointsOnly.value = Math.random() > 0.5 ? true : false; | ||
| 379 | + // 总积分数量也要从接口获取传进去 | ||
| 380 | + finalTotalPoints.value = 10086; | ||
| 351 | }) | 381 | }) |
| 352 | </script> | 382 | </script> |
| 353 | 383 | ... | ... |
-
Please register or login to post a comment