hookehuyr

feat(JoinFamily): 添加家庭列表分页加载功能并优化布局

重构家庭选择弹窗布局,使用flexbox替代固定高度计算
添加分页加载功能,包括加载更多按钮和没有更多数据提示
移除不再需要的高度计算逻辑,简化代码结构
...@@ -80,14 +80,14 @@ ...@@ -80,14 +80,14 @@
80 closeable 80 closeable
81 @close="closeFamilySelector" 81 @close="closeFamilySelector"
82 > 82 >
83 - <view class="family-selector-container"> 83 + <view class="family-selector-container h-full flex flex-col">
84 <!-- 标题 --> 84 <!-- 标题 -->
85 - <view class="text-lg font-bold text-center py-4 border-b border-gray-100"> 85 + <view class="text-lg font-bold text-center py-4 border-b border-gray-100 flex-shrink-0">
86 选择要加入的家庭 86 选择要加入的家庭
87 </view> 87 </view>
88 88
89 <!-- 搜索框 --> 89 <!-- 搜索框 -->
90 - <view class="p-4"> 90 + <view class="p-4 flex-shrink-0">
91 <nut-searchbar 91 <nut-searchbar
92 v-model="searchKeyword" 92 v-model="searchKeyword"
93 placeholder="搜索家庭名称" 93 placeholder="搜索家庭名称"
...@@ -97,11 +97,10 @@ ...@@ -97,11 +97,10 @@
97 </view> 97 </view>
98 98
99 <!-- 家庭列表 --> 99 <!-- 家庭列表 -->
100 - <view class="flex-1 px-4 pb-4"> 100 + <view class="flex-1 px-4 pb-4 overflow-hidden">
101 <view 101 <view
102 ref="familyListContainer" 102 ref="familyListContainer"
103 - class="space-y-3 overflow-y-auto" 103 + class="h-full space-y-3 overflow-y-auto"
104 - :style="{ maxHeight: familyListHeight + 'rpx' }"
105 > 104 >
106 <view 105 <view
107 v-for="family in filteredFamilies" 106 v-for="family in filteredFamilies"
...@@ -138,11 +137,29 @@ ...@@ -138,11 +137,29 @@
138 <Check size="20" /> 137 <Check size="20" />
139 </view> 138 </view>
140 </view> 139 </view>
140 +
141 + <!-- 加载更多按钮 -->
142 + <view v-if="hasMoreData && !searchKeyword" class="text-center py-4">
143 + <nut-button
144 + @click="loadMoreFamilies"
145 + :loading="isLoadingMore"
146 + type="default"
147 + size="small"
148 + plain
149 + >
150 + {{ isLoadingMore ? '加载中...' : '加载更多' }}
151 + </nut-button>
152 + </view>
153 +
154 + <!-- 没有更多数据提示 -->
155 + <view v-if="!hasMoreData && totalFamilies.length > 0 && !searchKeyword" class="text-center py-4 text-gray-500 text-sm">
156 + 没有更多家庭了
157 + </view>
141 </view> 158 </view>
142 </view> 159 </view>
143 160
144 <!-- 底部按钮 --> 161 <!-- 底部按钮 -->
145 - <view class="flex gap-3 p-4 border-t border-gray-100"> 162 + <view class="flex gap-3 p-4 border-t border-gray-100 flex-shrink-0">
146 <nut-button 163 <nut-button
147 @click="closeFamilySelector" 164 @click="closeFamilySelector"
148 class="flex-1" 165 class="flex-1"
...@@ -189,7 +206,14 @@ const searchKeyword = ref(''); ...@@ -189,7 +206,14 @@ const searchKeyword = ref('');
189 const selectedFamilyId = ref(''); 206 const selectedFamilyId = ref('');
190 const mockFamilies = ref([]); 207 const mockFamilies = ref([]);
191 const familyListContainer = ref(null); 208 const familyListContainer = ref(null);
192 -const familyListHeight = ref(400); // 默认高度 209 +// 移除不再需要的familyListHeight变量,因为现在使用flexbox布局
210 +
211 +// 分页相关数据
212 +const currentPage = ref(0);
213 +const pageSize = ref(10);
214 +const hasMoreData = ref(true);
215 +const isLoadingMore = ref(false);
216 +const totalFamilies = ref([]);
193 217
194 const handleInputChange = (index, value) => { 218 const handleInputChange = (index, value) => {
195 // 允许输入多个字符,但只保留第一个有效字符(汉字、数字、大小写字母),兼容输入法 219 // 允许输入多个字符,但只保留第一个有效字符(汉字、数字、大小写字母),兼容输入法
...@@ -297,40 +321,11 @@ const closeFamilySelector = () => { ...@@ -297,40 +321,11 @@ const closeFamilySelector = () => {
297 searchKeyword.value = '' 321 searchKeyword.value = ''
298 } 322 }
299 323
300 -/** 324 +// 监听弹窗显示状态,重置选中状态
301 - * 计算家庭列表容器的可用高度
302 - */
303 -const calculateFamilyListHeight = async () => {
304 - try {
305 - // 获取系统信息
306 - const systemInfo = await Taro.getSystemInfo()
307 - const windowHeight = systemInfo.windowHeight
308 -
309 - // 弹窗高度为70vh
310 - const popupHeight = windowHeight * 0.8
311 -
312 - // 减去固定元素的高度(估算值,单位px转rpx需要乘以2)
313 - const titleHeight = 60 * 2 // 标题区域
314 - const searchHeight = 80 * 2 // 搜索区域
315 - const buttonHeight = 80 * 2 // 底部按钮区域
316 - const padding = 32 * 2 // 内边距
317 -
318 - // 计算可用高度(px转rpx)
319 - const availableHeight = (popupHeight - (titleHeight + searchHeight + buttonHeight + padding) / 2)
320 -
321 - // 设置最小高度和最大高度
322 - familyListHeight.value = Math.max(200, Math.min(availableHeight * 2, 800))
323 - } catch (error) {
324 - console.error('计算高度失败:', error)
325 - familyListHeight.value = 400 // 使用默认值
326 - }
327 -}
328 -
329 -// 监听弹窗显示状态,动态计算高度
330 watch(showFamilySelector, async (newVal) => { 325 watch(showFamilySelector, async (newVal) => {
331 if (newVal) { 326 if (newVal) {
332 await nextTick() 327 await nextTick()
333 - await calculateFamilyListHeight() 328 + // 移除高度计算逻辑,现在使用flexbox自动布局
334 } 329 }
335 }) 330 })
336 331
...@@ -378,18 +373,28 @@ const handleJoinFamily = async () => { ...@@ -378,18 +373,28 @@ const handleJoinFamily = async () => {
378 const motto = mottoChars.value.join('') 373 const motto = mottoChars.value.join('')
379 374
380 try { 375 try {
381 - // 调用API查询家庭 376 + // 重置分页数据
377 + currentPage.value = 0;
378 + hasMoreData.value = true;
379 + totalFamilies.value = [];
380 +
381 + // 调用API查询家庭(第一页)
382 const { code, data } = await searchFamilyByPassphraseAPI({ 382 const { code, data } = await searchFamilyByPassphraseAPI({
383 passphrase: motto, 383 passphrase: motto,
384 - page: 0, 384 + page: currentPage.value,
385 - limit: 9999 385 + limit: pageSize.value
386 }) 386 })
387 387
388 let families = []; 388 let families = [];
389 389
390 if (code) { 390 if (code) {
391 families = data; 391 families = data;
392 - console.log('查询家庭:', { motto, role: selectedRole.value, families }) 392 + totalFamilies.value = families;
393 +
394 + // 检查是否还有更多数据
395 + hasMoreData.value = families.length === pageSize.value;
396 +
397 + console.log('查询家庭:', { motto, role: selectedRole.value, families, hasMore: hasMoreData.value })
393 398
394 if (families.length === 0) { 399 if (families.length === 0) {
395 Taro.showToast({ 400 Taro.showToast({
...@@ -406,7 +411,7 @@ const handleJoinFamily = async () => { ...@@ -406,7 +411,7 @@ const handleJoinFamily = async () => {
406 if (family.is_kicked) { 411 if (family.is_kicked) {
407 // 被踢出状态,显示选择弹窗让用户知道 412 // 被踢出状态,显示选择弹窗让用户知道
408 showFamilySelector.value = true; 413 showFamilySelector.value = true;
409 - mockFamilies.value = families; 414 + mockFamilies.value = totalFamilies.value;
410 } else { 415 } else {
411 // 未被踢出,直接加入 416 // 未被踢出,直接加入
412 const joinFamily = await joinFamilyAPI({ 417 const joinFamily = await joinFamilyAPI({
...@@ -430,7 +435,7 @@ const handleJoinFamily = async () => { ...@@ -430,7 +435,7 @@ const handleJoinFamily = async () => {
430 } else { 435 } else {
431 // 多个家庭,显示选择弹窗 436 // 多个家庭,显示选择弹窗
432 showFamilySelector.value = true 437 showFamilySelector.value = true
433 - mockFamilies.value = families 438 + mockFamilies.value = totalFamilies.value
434 } 439 }
435 } 440 }
436 } catch (error) { 441 } catch (error) {
...@@ -441,6 +446,57 @@ const handleJoinFamily = async () => { ...@@ -441,6 +446,57 @@ const handleJoinFamily = async () => {
441 }) 446 })
442 } 447 }
443 }; 448 };
449 +
450 +// 加载更多家庭数据
451 +const loadMoreFamilies = async () => {
452 + if (isLoadingMore.value || !hasMoreData.value) return;
453 +
454 + isLoadingMore.value = true;
455 +
456 + try {
457 + const motto = mottoChars.value.join('');
458 + currentPage.value += 1;
459 +
460 + const { code, data } = await searchFamilyByPassphraseAPI({
461 + passphrase: motto,
462 + page: currentPage.value,
463 + limit: pageSize.value
464 + });
465 +
466 + if (code && data) {
467 + // 合并新数据到现有数据
468 + totalFamilies.value = [...totalFamilies.value, ...data];
469 +
470 + // 检查是否还有更多数据
471 + hasMoreData.value = data.length === pageSize.value;
472 +
473 + // 更新mockFamilies用于显示
474 + mockFamilies.value = totalFamilies.value;
475 +
476 + console.log('加载更多家庭:', {
477 + page: currentPage.value,
478 + newCount: data.length,
479 + totalCount: totalFamilies.value.length,
480 + hasMore: hasMoreData.value
481 + });
482 + } else {
483 + hasMoreData.value = false;
484 + Taro.showToast({
485 + title: '加载失败',
486 + icon: 'none'
487 + });
488 + }
489 + } catch (error) {
490 + console.error('加载更多家庭失败:', error);
491 + currentPage.value -= 1; // 回退页码
492 + Taro.showToast({
493 + title: '加载失败,请重试',
494 + icon: 'none'
495 + });
496 + } finally {
497 + isLoadingMore.value = false;
498 + }
499 +};
444 </script> 500 </script>
445 <style lang="less"> 501 <style lang="less">
446 @import './index.less'; 502 @import './index.less';
......