diff --git a/src/app.config.ts b/src/app.config.ts index 1a1b247..6b57329 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -1,5 +1,6 @@ export default defineAppConfig({ pages: [ + "home_pages/index", //中转页 "login_pages/index/index", "login_pages/verification/index", @@ -23,6 +24,7 @@ export default defineAppConfig({ "myself/index", // 个人中心 "edit/index", // 编辑个人中心 "other/index", // 他人个人主页 + "follow/index", // 球友关注页 ], }, // { diff --git a/src/components/FollowUserCard/index.scss b/src/components/FollowUserCard/index.scss new file mode 100644 index 0000000..44c844f --- /dev/null +++ b/src/components/FollowUserCard/index.scss @@ -0,0 +1,99 @@ +// 球友卡片样式 +.follow_user_card { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 20px; + background: #ffffff; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + + .user_info { + display: flex; + align-items: center; + flex: 1; + gap: 12px; + + .avatar { + width: 40px; + height: 40px; + border-radius: 50%; + object-fit: cover; + } + + .user_details { + display: flex; + flex-direction: column; + gap: 8px; + flex: 1; + max-width: 200px; + + .nickname { + font-family: PingFang SC; + font-weight: 600; + font-size: 14px; + line-height: 16px; + color: rgba(0, 0, 0, 0.8); + } + + .signature { + font-family: PingFang SC; + font-weight: 400; + font-size: 12px; + line-height: 16px; + color: rgba(60, 60, 67, 0.6); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 100%; + } + } + } + + .action_button { + display: flex; + justify-content: center; + align-items: center; + padding: 4px 16px; + border-radius: 20px; + min-width: 60px; + + .button_text { + font-family: PingFang SC; + font-weight: 400; + font-size: 12px; + line-height: 16px; + } + + &.follow_button { + border: 0.5px solid #000000 !important; + background: transparent !important; + + .button_text { + color: #000000 !important; + } + } + + &.following_button { + border: 0.5px solid rgba(120, 120, 128, 0.12) !important; + background: transparent !important; + + .button_text { + color: rgba(0, 0, 0, 0.8) !important; + } + } + + &.mutual_button { + border: 0.5px solid rgba(120, 120, 128, 0.12) !important; + background: transparent !important; + + .button_text { + color: rgba(0, 0, 0, 0.8) !important; + } + } + + &.processing { + opacity: 0.6; + pointer-events: none; + } + } +} \ No newline at end of file diff --git a/src/components/FollowUserCard/index.tsx b/src/components/FollowUserCard/index.tsx new file mode 100644 index 0000000..6cd45b1 --- /dev/null +++ b/src/components/FollowUserCard/index.tsx @@ -0,0 +1,112 @@ +import React, { useState } from 'react'; +import { View, Text, Image } from '@tarojs/components'; +import Taro from '@tarojs/taro'; +import { FollowUser } from '@/services/followService'; +import './index.scss'; + +interface FollowUserCardProps { + user: FollowUser; + onFollowChange?: (userId: number, isFollowing: boolean) => void; +} + +const FollowUserCard: React.FC = ({ user, onFollowChange }) => { + const [isProcessing, setIsProcessing] = useState(false); + + // 防御性检查 + if (!user || !user.id) { + return null; + } + + // 处理关注操作 + const handle_follow_action = async () => { + if (isProcessing) return; + + try { + setIsProcessing(true); + + // 根据当前状态决定操作 + let new_status = false; + if (user.follow_status === 'follower' || user.follow_status === 'recommend') { + // 粉丝或推荐用户,执行关注操作 + new_status = true; + } else if (user.follow_status === 'following' || user.follow_status === 'mutual_follow') { + // 已关注或互相关注,执行取消关注操作 + new_status = false; + } + + onFollowChange?.(user.id, new_status); + } catch (error) { + console.error('关注操作失败:', error); + Taro.showToast({ + title: '操作失败', + icon: 'none' + }); + } finally { + setIsProcessing(false); + } + }; + + // 处理用户点击 + const handle_user_click = () => { + Taro.navigateTo({ + url: `/user_pages/other/index?userid=${user.id}` + }); + }; + + // 获取按钮文本和样式 + const get_button_config = () => { + switch (user.follow_status) { + case 'follower': + case 'recommend': + return { + text: '回关', + className: 'follow_button' + }; + case 'following': + return { + text: '已关注', + className: 'following_button' + }; + case 'mutual_follow': + return { + text: '互相关注', + className: 'mutual_button' + }; + default: + return { + text: '关注', + className: 'follow_button' + }; + } + }; + + const button_config = get_button_config(); + + return ( + + + + + {user.nickname} + + {user.personal_profile || '签名写在这里'} + + + + + + + {isProcessing ? '处理中...' : button_config.text} + + + + ); +}; + +export default FollowUserCard; \ No newline at end of file diff --git a/src/components/UserInfo/index.scss b/src/components/UserInfo/index.scss index 84ece5c..38a68bd 100644 --- a/src/components/UserInfo/index.scss +++ b/src/components/UserInfo/index.scss @@ -91,6 +91,31 @@ letter-spacing: 3.2%; color: rgba(0, 0, 0, 0.35); } + + // 可点击的统计项样式 + &.clickable { + cursor: pointer; + transition: all 0.2s ease; + padding: 4px 8px; + border-radius: 8px; + + &:hover { + background-color: rgba(0, 0, 0, 0.05); + } + + &:active { + background-color: rgba(0, 0, 0, 0.1); + transform: scale(0.98); + } + + .stat_number { + color: rgba(0, 0, 0, 0.9); + } + + .stat_label { + color: rgba(0, 0, 0, 0.5); + } + } } } diff --git a/src/components/UserInfo/index.tsx b/src/components/UserInfo/index.tsx index 7be961f..3989b9f 100644 --- a/src/components/UserInfo/index.tsx +++ b/src/components/UserInfo/index.tsx @@ -115,6 +115,31 @@ export const UserInfoCard: React.FC = ({ setEditingField(""); }; + // 处理统计项点击 + const handle_stats_click = (type: 'following' | 'friends' | 'hosted' | 'participated') => { + // 只有当前用户才能查看关注相关页面 + if (!is_current_user) { + Taro.showToast({ + title: '暂不支持查看他人关注信息', + icon: 'none' + }); + return; + } + + if (type === 'following') { + // 跳转到关注列表页面 + Taro.navigateTo({ + url: '/user_pages/follow/index?tab=following' + }); + } else if (type === 'friends') { + // 跳转到球友(粉丝)页面,显示粉丝标签 + Taro.navigateTo({ + url: '/user_pages/follow/index?tab=follower' + }); + } + // 主办和参加暂时不处理,可以后续扩展 + }; + return ( {/* 头像和基本信息 */} @@ -139,11 +164,11 @@ export const UserInfoCard: React.FC = ({ {/* 统计数据 */} - + handle_stats_click('following')}> {user_info.stats.following} 关注 - + handle_stats_click('friends')}> {user_info.stats.friends} 球友 diff --git a/src/components/index.ts b/src/components/index.ts index 44bc01e..da44688 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -17,7 +17,8 @@ import withAuth from "./Auth"; import { CustomPicker, PopupPicker } from "./Picker"; import NTRPEvaluatePopup from "./NTRPEvaluatePopup"; import RefundPopup from "./refundPopup"; -import GameManagePopup from './GameManagePopup' +import GameManagePopup from './GameManagePopup'; +import FollowUserCard from './FollowUserCard/index'; export { ActivityTypeSwitch, @@ -41,4 +42,5 @@ export { NTRPEvaluatePopup, RefundPopup, GameManagePopup, + FollowUserCard, }; diff --git a/src/services/followService.ts b/src/services/followService.ts new file mode 100644 index 0000000..094d1ea --- /dev/null +++ b/src/services/followService.ts @@ -0,0 +1,232 @@ +import { API_CONFIG } from '@/config/api'; +import httpService, { ApiResponse } from './httpService'; + +// 球友信息接口 +export interface FollowUser { + id: number; + nickname: string; + avatar_url: string; + personal_profile?: string; + gender?: string; + city?: string; + ntrp_level?: number; + // 关注状态:mutual_follow=互相关注, following=已关注, follower=粉丝, recommend=推荐 + follow_status: 'mutual_follow' | 'following' | 'follower' | 'recommend'; + follow_time?: string; // 关注时间 + is_mutual?: boolean; // 是否互关 + is_following?: boolean; // 是否已关注 + common_games_count?: number; // 共同参与球局数量 +} + +// 关注列表响应接口 +export interface FollowListResponse { + list: FollowUser[]; + total: number; + current_city?: string; // 当前用户城市(推荐接口返回) +} + +// 球友关注服务类 +export class FollowService { + + // 获取互关用户列表 + static async get_mutual_follow_list( + page: number = 1, + page_size: number = 20 + ): Promise { + try { + const response = await httpService.post( + '/user_follow/mutual_follow_list', + { page, page_size }, + { showLoading: false } + ); + + if (response.code === 0) { + // 为数据添加 follow_status 标识 + const list = response.data.list.map(user => ({ + ...user, + follow_status: 'mutual_follow' as const + })); + return { ...response.data, list }; + } else { + throw new Error(response.message || '获取互关列表失败'); + } + } catch (error) { + console.error('获取互关列表失败:', error); + return { list: [], total: 0 }; + } + } + + // 获取我的粉丝列表 + static async get_fans_list( + page: number = 1, + page_size: number = 20 + ): Promise { + try { + const response = await httpService.post( + '/user_follow/my_fans_list', + { page, page_size }, + { showLoading: false } + ); + + if (response.code === 0) { + // 为数据添加 follow_status 标识 + const list = response.data.list.map(user => ({ + ...user, + follow_status: user.is_mutual ? 'mutual_follow' as const : 'follower' as const + })); + return { ...response.data, list }; + } else { + throw new Error(response.message || '获取粉丝列表失败'); + } + } catch (error) { + console.error('获取粉丝列表失败:', error); + return { list: [], total: 0 }; + } + } + + // 获取我的关注列表 + static async get_following_list( + page: number = 1, + page_size: number = 20 + ): Promise { + try { + const response = await httpService.post( + '/user_follow/my_following_list', + { page, page_size }, + { showLoading: false } + ); + + if (response.code === 0) { + // 为数据添加 follow_status 标识 + const list = response.data.list.map(user => ({ + ...user, + follow_status: user.is_mutual ? 'mutual_follow' as const : 'following' as const + })); + return { ...response.data, list }; + } else { + throw new Error(response.message || '获取关注列表失败'); + } + } catch (error) { + console.error('获取关注列表失败:', error); + return { list: [], total: 0 }; + } + } + + // 获取关注列表(统一入口) + static async get_follow_list( + type: 'mutual_follow' | 'following' | 'follower' | 'recommend', + page: number = 1, + page_size: number = 20 + ): Promise { + switch (type) { + case 'mutual_follow': + return this.get_mutual_follow_list(page, page_size); + case 'following': + return this.get_following_list(page, page_size); + case 'follower': + return this.get_fans_list(page, page_size); + case 'recommend': + return this.get_recommend_users(page, page_size); + default: + return { list: [], total: 0 }; + } + } + + // 关注用户 + static async follow_user(user_id: number): Promise { + try { + const response = await httpService.post( + API_CONFIG.USER.FOLLOW, + { following_id: user_id }, + { + showLoading: true, + loadingText: '关注中...' + } + ); + + if (response.code === 0) { + return true; + } else { + throw new Error(response.message || '关注失败'); + } + } catch (error) { + console.error('关注失败:', error); + throw error; + } + } + + // 取消关注用户 + static async unfollow_user(user_id: number): Promise { + try { + const response = await httpService.post( + API_CONFIG.USER.UNFOLLOW, + { following_id: user_id }, + { + showLoading: true, + loadingText: '取消关注中...' + } + ); + + if (response.code === 0) { + return true; + } else { + throw new Error(response.message || '取消关注失败'); + } + } catch (error) { + console.error('取消关注失败:', error); + throw error; + } + } + + // 回关用户(关注粉丝) + static async follow_back(user_id: number): Promise { + return this.follow_user(user_id); + } + + // 获取同城推荐用户 + static async get_recommend_users(page: number = 1, page_size: number = 10): Promise { + try { + const response = await httpService.post( + '/user_follow/recommend_same_city', + { page, page_size }, + { showLoading: false } + ); + + if (response.code === 0) { + // 为数据添加 follow_status 标识 + const list = response.data.list.map(user => ({ + ...user, + follow_status: 'recommend' as const + })); + return { ...response.data, list }; + } else { + throw new Error(response.message || '获取推荐用户失败'); + } + } catch (error) { + console.error('获取推荐用户失败:', error); + return { list: [], total: 0 }; + } + } + + // 检查关注状态 + static async check_follow_status(user_id: number): Promise<'mutual_follow' | 'following' | 'follower' | 'none'> { + try { + const response = await httpService.post<{ status: string }>( + '/wch_users/follow_status', + { user_id }, + { showLoading: false } + ); + + if (response.code === 0) { + return response.data.status as 'mutual_follow' | 'following' | 'follower' | 'none'; + } else { + return 'none'; + } + } catch (error) { + console.error('检查关注状态失败:', error); + return 'none'; + } + } +} + +export default FollowService; \ No newline at end of file diff --git a/src/user_pages/follow/README.md b/src/user_pages/follow/README.md new file mode 100644 index 0000000..6c28053 --- /dev/null +++ b/src/user_pages/follow/README.md @@ -0,0 +1,123 @@ +# 球友关注功能 + +## 功能概述 +球友关注功能模块,实现用户之间的关注、粉丝、互关和推荐功能。 + +## 功能特性 + +### 1. 四个标签页 +- **互相关注**: 显示互相关注的用户列表 +- **关注**: 显示我关注的用户列表 +- **粉丝**: 显示关注我的用户列表(默认页面) +- **推荐**: 显示同城推荐用户列表 + +### 2. 用户操作 +- **关注/取消关注**: 支持关注和取消关注操作 +- **回关**: 对粉丝进行回关操作 +- **用户详情**: 点击头像或昵称跳转到用户详情页 + +### 3. 列表功能 +- **下拉刷新**: 支持下拉刷新数据 +- **分页加载**: 支持滑动到底部加载更多 +- **实时状态**: 操作后实时更新关注状态 + +## API 接口 + +### 后端接口列表 +- `POST /user_follow/mutual_follow_list` - 获取互关用户列表 +- `POST /user_follow/my_fans_list` - 获取我的粉丝列表 +- `POST /user_follow/my_following_list` - 获取我的关注列表 +- `POST /user_follow/recommend_same_city` - 获取同城推荐用户 +- `POST /wch_users/follow` - 关注用户 +- `POST /wch_users/unfollow` - 取消关注用户 + +### 前端服务 +- `FollowService` - 关注相关API服务封装 +- `FollowUserCard` - 用户卡片组件 +- `FollowPage` - 球友关注主页面 + +## 文件结构 + +``` +src/ +├── services/ +│ └── followService.ts # 关注服务API +├── components/ +│ └── FollowUserCard/ # 用户卡片组件 +│ ├── index.tsx +│ ├── index.module.scss +│ └── index.ts +└── user_pages/ + └── follow/ # 球友关注页面 + ├── index.tsx + ├── index.scss + ├── index.config.ts + └── README.md +``` + +## 使用方法 + +### 1. 页面跳转 +```typescript +// 跳转到球友关注页面 +Taro.navigateTo({ + url: '/user_pages/follow/index' +}); +``` + +### 2. 服务调用 +```typescript +import { FollowService } from '@/services/followService'; + +// 获取粉丝列表 +const response = await FollowService.get_fans_list(1, 20); + +// 关注用户 +await FollowService.follow_user(userId); +``` + +### 3. 组件使用 +```tsx +import FollowUserCard from '@/components/FollowUserCard'; + + +``` + +## 设计规范 + +### UI 设计 +- 遵循 Figma 设计稿 `球友-粉丝` 页面设计 +- 标签页切换交互 +- 用户卡片布局和样式 +- 关注按钮状态设计 + +### 数据状态 +- `mutual_follow`: 互相关注 +- `following`: 已关注 +- `follower`: 粉丝 +- `recommend`: 推荐 + +### 按钮状态 +- **回关**: 对粉丝和推荐用户显示 +- **已关注**: 对已关注用户显示 +- **互相关注**: 对互关用户显示 + +## 注意事项 + +1. **权限控制**: 页面需要用户登录,使用 `withAuth` HOC 包装 +2. **错误处理**: 所有 API 调用都有错误处理和友好提示 +3. **性能优化**: 采用分页加载,避免一次性加载过多数据 +4. **状态同步**: 关注操作后实时更新所有相关列表的状态 +5. **网络优化**: 推荐页面单独的分页大小(10条),其他页面(20条) + +## 扩展功能 + +### 可能的扩展点 +1. **搜索功能**: 在关注列表中搜索特定用户 +2. **筛选功能**: 按城市、NTRP等级等条件筛选 +3. **批量操作**: 批量关注/取消关注 +4. **推荐算法**: 基于更多维度的智能推荐 +5. **社交互动**: 查看共同关注、共同参与的球局等 \ No newline at end of file diff --git a/src/user_pages/follow/index.config.ts b/src/user_pages/follow/index.config.ts new file mode 100644 index 0000000..51b9da5 --- /dev/null +++ b/src/user_pages/follow/index.config.ts @@ -0,0 +1,6 @@ +export default definePageConfig({ + navigationBarTitleText: '球友', + navigationStyle: 'custom', + enablePullDownRefresh: false, + backgroundColor: '#FAFAFA' +}) \ No newline at end of file diff --git a/src/user_pages/follow/index.scss b/src/user_pages/follow/index.scss new file mode 100644 index 0000000..aec5031 --- /dev/null +++ b/src/user_pages/follow/index.scss @@ -0,0 +1,178 @@ +// 球友关注页面样式 +.follow_page { + min-height: 100vh; + background: #FAFAFA; + + // 导航栏内容 + .navbar_content { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + height: 44px; + + .navbar_back { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + margin-left: 10px; + + .back_icon { + width: 8px; + height: 16px; + background: url("data:image/svg+xml,%3Csvg width='8' height='16' viewBox='0 0 8 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.66667 2L1.33333 8L6.66667 14' stroke='%23000000' stroke-width='2.66667' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat center; + background-size: contain; + } + } + + .navbar_title { + font-family: PingFang SC; + font-weight: 600; + font-size: 20px; + line-height: 28px; + letter-spacing: 1.9%; + color: #000000; + position: absolute; + left: 50px; + } + + .navbar_action { + display: flex; + align-items: center; + justify-content: center; + width: 83px; + height: 30px; + margin-right: 7px; + + .action_icon { + width: 20px; + height: 20px; + background: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Ccircle cx='5' cy='10' r='1.5' fill='%23191919'/%3E%3Ccircle cx='10' cy='10' r='1.5' fill='%23191919'/%3E%3Ccircle cx='15' cy='10' r='1.5' fill='%23191919'/%3E%3C/g%3E%3C/svg%3E") no-repeat center; + background-size: contain; + } + } + } + + // 标签页导航 + .tab_navigation { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 30px; + background: #ffffff; + height: 44px; + overflow-x: auto; + margin-top: 110px; + + + + .tab_item { + display: flex; + align-items: center; + gap: 4px; + padding: 8px 4px; + position: relative; + flex-shrink: 0; + min-width: fit-content; + + .tab_text { + font-family: PingFang SC; + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: -1.64%; + color: rgba(0, 0, 0, 0.5); + transition: all 0.2s ease; + white-space: nowrap; + + // 小屏幕适配 + @media (max-width: 375px) { + font-size: 13px; + } + } + + // 推荐图标 + .recommend_icon { + .icon_container { + width: 10px; + height: 10px; + position: relative; + + .star_icon { + width: 100%; + height: 100%; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5 0.83L6.09 3.09L8.75 3.09L6.58 4.91L7.25 7.17L5 5.83L2.75 7.17L3.42 4.91L1.25 3.09L3.91 3.09L5 0.83Z' stroke='%238C8C8C' stroke-width='0.83' fill='%238C8C8C'/%3E%3C/svg%3E") no-repeat center; + background-size: contain; + } + } + } + } + + // 激活状态 + &.active { + /* 添加调试背景色 */ + + .tab_text { + font-weight: 600 !important; + color: #000000 !important; + } + + // 下边框 + &::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 2px; + background: #000000 !important; + z-index: 1; + } + } + + // 移除默认的粉丝标签激活样式,完全依赖 active 类 + } + } + + // 用户列表容器 + .user_list_container { + flex: 1; + margin-top: 12px; + background: #ffffff; + + // 加载状态提示 + .loading_tip, + .empty_tip, + .load_more_tip { + display: flex; + justify-content: center; + align-items: center; + padding: 40px 20px; + + text { + font-family: PingFang SC; + font-weight: 400; + font-size: 14px; + color: rgba(0, 0, 0, 0.5); + } + } + + .empty_tip { + padding: 80px 20px; + } + + .load_more_tip { + padding: 20px; + } + } +} \ No newline at end of file diff --git a/src/user_pages/follow/index.tsx b/src/user_pages/follow/index.tsx new file mode 100644 index 0000000..055c507 --- /dev/null +++ b/src/user_pages/follow/index.tsx @@ -0,0 +1,271 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, ScrollView } from '@tarojs/components'; +import Taro from '@tarojs/taro'; +import CustomNavbar from '@/components/CustomNavbar'; +import FollowUserCard from '@/components/FollowUserCard'; +import { FollowService, FollowUser } from '@/services/followService'; +import { withAuth } from '@/components'; +import './index.scss'; + +// 标签页类型 +type TabType = 'mutual_follow' | 'following' | 'follower' | 'recommend'; + +// 标签页配置 +const TAB_CONFIG = [ + { key: 'mutual_follow' as TabType, label: '互相关注' }, + { key: 'following' as TabType, label: '关注' }, + { key: 'follower' as TabType, label: '粉丝' }, + { key: 'recommend' as TabType, label: '推荐' } +]; + +const FollowPage: React.FC = () => { + // 获取页面参数,支持指定默认标签页 + const instance = Taro.getCurrentInstance(); + const default_tab = (instance.router?.params?.tab as TabType) || 'mutual_follow'; + + // 当前激活的标签页 - 根据设计稿默认显示互相关注 + const [active_tab, set_active_tab] = useState(default_tab); + + // 用户列表数据 + const [user_lists, set_user_lists] = useState>({ + mutual_follow: [], + following: [], + follower: [], + recommend: [] + }); + + // 加载状态 - 根据默认标签页设置初始加载状态 + const [loading_states, set_loading_states] = useState>(() => ({ + mutual_follow: default_tab === 'mutual_follow', + following: default_tab === 'following', + follower: default_tab === 'follower', + recommend: default_tab === 'recommend' + })); + + // 分页信息 + const [page_info, set_page_info] = useState>(() => ({ + mutual_follow: { page: 1, total: 0, has_more: true }, + following: { page: 1, total: 0, has_more: true }, + follower: { page: 1, total: 0, has_more: true }, + recommend: { page: 1, total: 0, has_more: true } + })); + + // 加载用户列表 + const load_user_list = async (tab: TabType, is_refresh: boolean = false) => { + const current_page = is_refresh ? 1 : page_info[tab].page; + const page_size = tab === 'recommend' ? 10 : 20; + + // 设置加载状态 + set_loading_states(prev => ({ ...prev, [tab]: true })); + + try { + const response = await FollowService.get_follow_list(tab, current_page, page_size); + + // 调试日志 + console.log(`加载${TAB_CONFIG.find(t => t.key === tab)?.label}数据:`, response); + + // 更新用户列表 + set_user_lists(prev => ({ + ...prev, + [tab]: is_refresh ? response.list : [...(prev[tab] || []), ...response.list] + })); + + // 更新分页信息 + set_page_info(prev => ({ + ...prev, + [tab]: { + page: current_page + 1, + total: response.total, + has_more: response.list.length === page_size && (current_page * page_size) < response.total + } + })); + + } catch (error) { + console.error(`加载${TAB_CONFIG.find(t => t.key === tab)?.label}列表失败:`, error); + Taro.showToast({ + title: '加载失败', + icon: 'none' + }); + } finally { + set_loading_states(prev => ({ ...prev, [tab]: false })); + } + }; + + // 处理标签页切换 + const handle_tab_change = (tab: TabType) => { + if (tab === active_tab) return; + + set_active_tab(tab); + + // 如果该标签页还没有数据,则加载 + if (user_lists[tab].length === 0) { + load_user_list(tab, true); + } + }; + + // 处理关注状态变化 + const handle_follow_change = async (user_id: number, is_following: boolean) => { + try { + if (is_following) { + await FollowService.follow_user(user_id); + Taro.showToast({ + title: '关注成功', + icon: 'success' + }); + } else { + await FollowService.unfollow_user(user_id); + Taro.showToast({ + title: '取消关注成功', + icon: 'success' + }); + } + + // 更新用户列表中的关注状态 + set_user_lists(prev => { + const new_lists = { ...prev }; + + // 更新所有标签页中的用户状态 + Object.keys(new_lists).forEach(tab_key => { + const tab = tab_key as TabType; + if (new_lists[tab] && Array.isArray(new_lists[tab])) { + new_lists[tab] = new_lists[tab].map(user => { + if (user.id === user_id) { + // 根据操作结果更新状态 + let new_status = user.follow_status; + if (is_following) { + if (user.follow_status === 'follower') { + new_status = 'mutual_follow'; + } else if (user.follow_status === 'recommend') { + new_status = 'following'; + } + } else { + if (user.follow_status === 'mutual_follow') { + new_status = 'follower'; + } else if (user.follow_status === 'following') { + new_status = 'recommend'; + } + } + return { ...user, follow_status: new_status }; + } + return user; + }); + } + }); + + return new_lists; + }); + + } catch (error) { + console.error('关注操作失败:', error); + Taro.showToast({ + title: '操作失败', + icon: 'none' + }); + } + }; + + // 处理下拉刷新 + const handle_refresh = () => { + load_user_list(active_tab, true); + }; + + // 处理加载更多 + const handle_load_more = () => { + if (page_info[active_tab]?.has_more && !loading_states[active_tab]) { + load_user_list(active_tab, false); + } + }; + + // 初始化加载 + useEffect(() => { + try { + load_user_list(default_tab, true); + } catch (error) { + console.error('初始化加载失败:', error); + Taro.showToast({ + title: '初始化失败', + icon: 'none' + }); + } + }, [default_tab]); + + return ( + + {/* 自定义导航栏 */} + + + Taro.navigateBack()}> + + + 球友 + + + + + + + {/* 标签页导航 */} + + {TAB_CONFIG.map(tab => ( + handle_tab_change(tab.key)} + > + {tab.label} + {tab.key === 'recommend' && ( + + {/* 推荐图标 SVG */} + + + + + )} + + ))} + + + {/* 用户列表 */} + + {user_lists[active_tab]?.map(user => ( + + )) || []} + + {/* 加载状态提示 */} + {loading_states[active_tab] && (user_lists[active_tab]?.length || 0) === 0 && ( + + 加载中... + + )} + + {/* 空状态提示 */} + {!loading_states[active_tab] && (user_lists[active_tab]?.length || 0) === 0 && ( + + 暂无{TAB_CONFIG.find(t => t.key === active_tab)?.label}用户 + + )} + + {/* 加载更多提示 */} + {(user_lists[active_tab]?.length || 0) > 0 && !page_info[active_tab]?.has_more && ( + + 已加载全部 + + )} + + + ); +}; + +export default withAuth(FollowPage); \ No newline at end of file