hookehuyr

feat(积分收集): 重构积分收集组件以支持后端数据

- 移除模拟数据,使用接口返回的待收集积分数据
- 添加积分来源类型映射和显示
- 修改总步数计算逻辑以匹配新数据结构
- 添加对积分和待收集积分的监听
...@@ -16,13 +16,9 @@ ...@@ -16,13 +16,9 @@
16 :style="getItemStyle(item)" 16 :style="getItemStyle(item)"
17 @tap="collectItem(item)" 17 @tap="collectItem(item)"
18 > 18 >
19 - <view v-if="item.type === 'steps'" class="item-content"> 19 + <view class="item-content">
20 - <text class="item-value">{{ item.steps }}步</text> 20 + <text class="item-value">{{ item.sourceLabel }}</text>
21 - <text class="item-type">{{ item.value }}分</text> 21 + <text class="item-type">{{ item.points }}分</text>
22 - </view>
23 - <view v-else class="item-content">
24 - <text class="item-value">奖励</text>
25 - <text class="item-type">{{ item.value }}分</text>
26 </view> 22 </view>
27 </view> 23 </view>
28 24
...@@ -38,7 +34,7 @@ ...@@ -38,7 +34,7 @@
38 </template> 34 </template>
39 35
40 <script setup> 36 <script setup>
41 -import { ref, onMounted, defineProps, defineExpose, defineEmits } from 'vue' 37 +import { ref, onMounted, defineProps, defineExpose, defineEmits, watch } from 'vue'
42 import Taro, { useDidShow } from '@tarojs/taro' 38 import Taro, { useDidShow } from '@tarojs/taro'
43 39
44 const emit = defineEmits(['collection-complete']) 40 const emit = defineEmits(['collection-complete'])
...@@ -46,46 +42,64 @@ const props = defineProps({ ...@@ -46,46 +42,64 @@ const props = defineProps({
46 height: { 42 height: {
47 type: String, 43 type: String,
48 default: '30vh' 44 default: '30vh'
45 + },
46 + pendingPoints: {
47 + type: Array,
48 + default: () => []
49 + },
50 + totalPoints: {
51 + type: Number,
52 + default: 0
49 } 53 }
50 }) 54 })
51 55
52 // 响应式数据 56 // 响应式数据
53 -const totalPoints = ref(12500) // 总积分 57 +const animatedTotalPoints = ref(props.totalPoints) // 动画中的总积分
54 -const animatedTotalPoints = ref(12500) // 动画中的总积分
55 const floatingItems = ref([]) // 漂浮的积分项 58 const floatingItems = ref([]) // 漂浮的积分项
56 const isCollecting = ref(false) // 是否正在收集,防止重复触发 59 const isCollecting = ref(false) // 是否正在收集,防止重复触发
57 60
61 +// source_type 中英文映射
62 +const sourceTypeMap = {
63 + 'WALKING': '步数积分',
64 + 'CHECK_IN': '活动打卡',
65 + 'CHECK_IN_COUNT': '完成活动',
66 + 'FAMILY_SIZE': '家庭成员',
67 + 'COMPANION_PHOTO': '陪伴拍照',
68 + 'WHEELCHAIR_COMPANION': '陪伴轮椅'
69 +}
70 +
58 /** 71 /**
59 - * 生成模拟数据并分配随机位置 72 + * 根据pending_points数据生成漂浮积分项并分配随机位置
60 */ 73 */
61 -const generateMockData = () => { 74 +const generatePointsData = () => {
62 - const mockItems = [ 75 + if (!props.pendingPoints || props.pendingPoints.length === 0) {
63 - { id: 1, type: 'steps', value: 5000, steps: 5000 }, 76 + return [];
64 - { id: 2, type: 'points', value: 500 }, 77 + }
65 - { id: 3, type: 'steps', value: 8000, steps: 5000 }, 78 +
66 - { id: 4, type: 'points', value: 30 }, 79 + const pointsItems = props.pendingPoints.map(item => ({
67 - { id: 5, type: 'steps', value: 12000, steps: 12000 }, 80 + id: item.id,
68 - { id: 6, type: 'points', value: 800 }, 81 + points: parseInt(item.points),
69 - { id: 7, type: 'points', value: 250 }, 82 + sourceType: item.source_type,
70 - { id: 8, type: 'steps', value: 6000, steps: 6000 }, 83 + sourceLabel: sourceTypeMap[item.source_type] || item.source_type,
71 - { id: 9, type: 'points', value: 1000 }, 84 + title: item.title,
72 - { id: 10, type: 'steps', value: 10000, steps: 10000 }, 85 + note: item.note,
73 - ].map(item => ({ ...item, collecting: false })); // 初始化collecting状态 86 + collecting: false
74 - 87 + }));
75 - const maxValue = Math.max(...mockItems.map(i => i.value)); 88 +
89 + const maxValue = Math.max(...pointsItems.map(i => i.points), 1);
76 const baseSize = 80; 90 const baseSize = 80;
77 const { windowWidth, windowHeight } = Taro.getWindowInfo(); 91 const { windowWidth, windowHeight } = Taro.getWindowInfo();
78 const positionedItems = []; 92 const positionedItems = [];
79 93
80 // 为每个项目分配一个不重叠的随机位置 94 // 为每个项目分配一个不重叠的随机位置
81 - return mockItems.map(item => { 95 + return pointsItems.map(item => {
82 let x, y, hasCollision; 96 let x, y, hasCollision;
83 const maxAttempts = 100; // 限制尝试次数以避免无限循环 97 const maxAttempts = 100; // 限制尝试次数以避免无限循环
84 let attempts = 0; 98 let attempts = 0;
85 const centerNoFlyZone = 25; // 中心圆形25%的禁飞区半径 99 const centerNoFlyZone = 25; // 中心圆形25%的禁飞区半径
86 100
87 // 计算项目大小和半径 101 // 计算项目大小和半径
88 - const sizeRatio = item.value / maxValue; 102 + const sizeRatio = item.points / maxValue;
89 const size = baseSize + (sizeRatio * 40); // rpx 103 const size = baseSize + (sizeRatio * 40); // rpx
90 const radiusXPercent = (size / 750) * 100 / 2 * (windowWidth / windowHeight) ; // 归一化为高度的百分比 104 const radiusXPercent = (size / 750) * 100 / 2 * (windowWidth / windowHeight) ; // 归一化为高度的百分比
91 105
...@@ -140,8 +154,8 @@ const generateMockData = () => { ...@@ -140,8 +154,8 @@ const generateMockData = () => {
140 */ 154 */
141 const getItemStyle = (item) => { 155 const getItemStyle = (item) => {
142 const baseSize = 80; 156 const baseSize = 80;
143 - const maxValue = Math.max(...(floatingItems.value.map(i => i.value).length > 0 ? floatingItems.value.map(i => i.value) : [1])); 157 + const maxValue = Math.max(...(floatingItems.value.map(i => i.points).length > 0 ? floatingItems.value.map(i => i.points) : [1]));
144 - const sizeRatio = item.value / maxValue; 158 + const sizeRatio = item.points / maxValue;
145 const size = baseSize + (sizeRatio * 40); 159 const size = baseSize + (sizeRatio * 40);
146 160
147 const style = { 161 const style = {
...@@ -174,13 +188,13 @@ const collectItem = (item) => { ...@@ -174,13 +188,13 @@ const collectItem = (item) => {
174 item.collecting = true; 188 item.collecting = true;
175 189
176 setTimeout(() => { 190 setTimeout(() => {
177 - const totalToAdd = item.type === 'steps' ? Math.floor(item.value / 10) : item.value; 191 + const totalToAdd = item.points;
178 - animateNumber(totalPoints.value, totalPoints.value + totalToAdd); 192 + const newTotal = props.totalPoints + totalToAdd;
179 - totalPoints.value += totalToAdd; 193 + animateNumber(animatedTotalPoints.value, newTotal);
180 194
181 floatingItems.value = floatingItems.value.filter(i => i.id !== item.id); 195 floatingItems.value = floatingItems.value.filter(i => i.id !== item.id);
182 if (floatingItems.value.length === 0) { 196 if (floatingItems.value.length === 0) {
183 - emit('collection-complete', totalPoints.value); 197 + emit('collection-complete', newTotal);
184 } 198 }
185 }, 800); // 动画时长 199 }, 800); // 动画时长
186 } 200 }
...@@ -199,17 +213,17 @@ const collectAll = async () => { ...@@ -199,17 +213,17 @@ const collectAll = async () => {
199 setTimeout(() => { 213 setTimeout(() => {
200 item.collecting = true; 214 item.collecting = true;
201 }, index * 80); // 依次触发动画 215 }, index * 80); // 依次触发动画
202 - totalToAdd += item.value; 216 + totalToAdd += item.points;
203 }); 217 });
204 218
205 const totalAnimationTime = itemsToCollect.length * 80 + 800; 219 const totalAnimationTime = itemsToCollect.length * 80 + 800;
206 setTimeout(() => { 220 setTimeout(() => {
207 - animateNumber(totalPoints.value, totalPoints.value + totalToAdd); 221 + const newTotal = props.totalPoints + totalToAdd;
208 - totalPoints.value += totalToAdd; 222 + animateNumber(animatedTotalPoints.value, newTotal);
209 223
210 floatingItems.value = []; 224 floatingItems.value = [];
211 isCollecting.value = false; 225 isCollecting.value = false;
212 - emit('collection-complete', totalPoints.value); 226 + emit('collection-complete', newTotal);
213 }, totalAnimationTime); 227 }, totalAnimationTime);
214 } 228 }
215 229
...@@ -243,10 +257,16 @@ defineExpose({ ...@@ -243,10 +257,16 @@ defineExpose({
243 * 初始化数据 257 * 初始化数据
244 */ 258 */
245 const initData = async () => { 259 const initData = async () => {
246 - floatingItems.value = generateMockData(); 260 + floatingItems.value = generatePointsData();
247 - console.warn('初始化数据') 261 + animatedTotalPoints.value = props.totalPoints;
262 + console.warn('初始化积分数据', props.pendingPoints)
248 } 263 }
249 264
265 +// 监听props变化
266 +watch(() => [props.pendingPoints, props.totalPoints], () => {
267 + initData();
268 +}, { deep: true, immediate: true })
269 +
250 // 组件挂载时初始化数据 270 // 组件挂载时初始化数据
251 onMounted(() => { 271 onMounted(() => {
252 initData(); 272 initData();
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
29 <view class="flex justify-between items-center"> 29 <view class="flex justify-between items-center">
30 <view class="flex items-baseline"> 30 <view class="flex items-baseline">
31 <span class="text-4xl font-bold"> 31 <span class="text-4xl font-bold">
32 - {{ todaySteps }} 32 + {{ todaySteps?.toLocaleString() }}
33 </span> 33 </span>
34 <span class="ml-1 text-gray-500">步</span> 34 <span class="ml-1 text-gray-500">步</span>
35 </view> 35 </view>
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
51 ref="pointsCollectorRef" 51 ref="pointsCollectorRef"
52 height="30vh" 52 height="30vh"
53 :total-points="finalTotalPoints" 53 :total-points="finalTotalPoints"
54 + :pending-points="pendingPoints"
54 @collection-complete="handleCollectionComplete" 55 @collection-complete="handleCollectionComplete"
55 /> 56 />
56 </template> 57 </template>
...@@ -77,14 +78,14 @@ ...@@ -77,14 +78,14 @@
77 <view class="flex justify-between items-center mb-4"> 78 <view class="flex justify-between items-center mb-4">
78 <h2 class="font-medium text-lg">今日家庭步数排行</h2> 79 <h2 class="font-medium text-lg">今日家庭步数排行</h2>
79 <span class="text-sm text-gray-500"> 80 <span class="text-sm text-gray-500">
80 - 总计 {{ getTotalSteps(totalFamilySteps).toLocaleString() }} 步 81 + 总计 {{ getTotalSteps() }} 步
81 </span> 82 </span>
82 </view> 83 </view>
83 <view class="grid grid-cols-4 gap-2"> 84 <view class="grid grid-cols-4 gap-2">
84 - <view v-for="member in familyMembers" :key="member.id" class="flex flex-col items-center"> 85 + <view v-for="member in familyMembers" :key="member.user_id" class="flex flex-col items-center">
85 - <image :src="member.avatar" :alt="member.name" class="w-16 h-16 rounded-full mb-1" /> 86 + <image :src="member.avatar_url || defaultAvatar" :alt="member.role" class="w-16 h-16 rounded-full mb-1" />
86 <span class="text-sm text-gray-700"> 87 <span class="text-sm text-gray-700">
87 - {{ member.steps.toLocaleString() }}步 88 + {{ member?.today_step?.toLocaleString() }}步
88 </span> 89 </span>
89 </view> 90 </view>
90 </view> 91 </view>
...@@ -205,6 +206,7 @@ const pointsCollectorRef = ref(null) ...@@ -205,6 +206,7 @@ const pointsCollectorRef = ref(null)
205 const weRunAuthRef = ref(null) 206 const weRunAuthRef = ref(null)
206 const showTotalPointsOnly = ref(false) 207 const showTotalPointsOnly = ref(false)
207 const finalTotalPoints = ref(0) 208 const finalTotalPoints = ref(0)
209 +const pendingPoints = ref([]) // 待收集的积分数据
208 210
209 const familyName = ref('') 211 const familyName = ref('')
210 const familySlogn = ref('') 212 const familySlogn = ref('')
...@@ -291,60 +293,14 @@ const handleStepsSynced = async (data) => { ...@@ -291,60 +293,14 @@ const handleStepsSynced = async (data) => {
291 293
292 /** 294 /**
293 * 计算总步数(包含用户步数和家庭成员步数) 295 * 计算总步数(包含用户步数和家庭成员步数)
294 - * @param {number} userSteps - 用户步数 296 + * @returns {string} 格式化后的总步数
295 - * @returns {number} 总步数
296 */ 297 */
297 -const getTotalSteps = (userSteps) => { 298 +const getTotalSteps = () => {
298 - return familyMembers.value.reduce((sum, member) => sum + member.steps, 0) + userSteps 299 + return familyMembers.value.reduce((sum, member) => sum + member.today_step, 0).toLocaleString()
299 } 300 }
300 301
301 // Mock data for family members 302 // Mock data for family members
302 -const familyMembers = ref([ 303 +const familyMembers = ref([]);
303 - {
304 - id: 1,
305 - name: '妈妈',
306 - steps: 7000,
307 - avatar: 'https://randomuser.me/api/portraits/women/44.jpg'
308 - },
309 - {
310 - id: 2,
311 - name: '爸爸',
312 - steps: 6000,
313 - avatar: 'https://randomuser.me/api/portraits/men/32.jpg'
314 - },
315 - {
316 - id: 3,
317 - name: '儿子',
318 - steps: 5000,
319 - avatar: 'https://randomuser.me/api/portraits/men/22.jpg'
320 - },
321 - {
322 - id: 4,
323 - name: '女儿',
324 - steps: 4000,
325 - avatar: 'https://randomuser.me/api/portraits/women/29.jpg'
326 - },
327 - {
328 - id: 5,
329 - name: '孙子',
330 - steps: 3000,
331 - avatar: 'https://randomuser.me/api/portraits/men/25.jpg'
332 - },
333 - {
334 - id: 6,
335 - name: '孙女',
336 - steps: 20000,
337 - avatar: 'https://randomuser.me/api/portraits/women/27.jpg'
338 - },
339 -]);
340 -
341 -// 注意:totalSteps 计算逻辑已移至 getTotalSteps 方法中
342 -
343 -const handleSyncSteps = () => {
344 - // In a real app, this would sync with a health API
345 - // For demo purposes, we'll just log and do nothing
346 - console.log('Syncing steps...');
347 -};
348 304
349 const goToProfile = () => { 305 const goToProfile = () => {
350 Taro.navigateTo({ url: '/pages/EditFamily/index' }); 306 Taro.navigateTo({ url: '/pages/EditFamily/index' });
...@@ -376,10 +332,14 @@ const initPageData = async () => { ...@@ -376,10 +332,14 @@ const initPageData = async () => {
376 if (code) { 332 if (code) {
377 // 获取用户信息 333 // 获取用户信息
378 console.warn(data); 334 console.warn(data);
379 - // 真实情况下 需要重新获取积分列表, 测试随机积分获取状态, 接口有积分列表就显示出来, 没有就不显示 335 + // 设置待收集的积分数据
336 + pendingPoints.value = data.pending_points || [];
337 + // 获取今日家庭总步数
338 + familyMembers.value = data.step_ranking || [];
339 + // 根据是否有待收集积分决定显示模式
380 showTotalPointsOnly.value = !data.pending_points.length; 340 showTotalPointsOnly.value = !data.pending_points.length;
381 - // 总积分数量也要从接口获取传进去 341 + // 总积分数量从接口获取
382 - finalTotalPoints.value = 10086; 342 + finalTotalPoints.value = data.family.total_points || 0;
383 // 获取用户信息 343 // 获取用户信息
384 familyName.value = data.family.name; 344 familyName.value = data.family.name;
385 familySlogn.value = data.family.note; 345 familySlogn.value = data.family.note;
......