ym923 | 6e0ddcf | 2025-06-09 20:14:11 +0800 | [diff] [blame^] | 1 | import React, { useState, useEffect } from 'react'; |
| 2 | import { |
| 3 | Input, |
| 4 | Button, |
| 5 | List, |
| 6 | Typography, |
| 7 | Space, |
| 8 | Spin, |
| 9 | Popconfirm, |
| 10 | message, |
| 11 | Divider, |
| 12 | Avatar, |
| 13 | } from 'antd'; |
| 14 | import { |
| 15 | UserAddOutlined, |
| 16 | DeleteOutlined, |
| 17 | ReloadOutlined, |
| 18 | CheckOutlined, |
| 19 | CloseOutlined, |
| 20 | } from '@ant-design/icons'; |
| 21 | import { |
| 22 | addFriend, |
| 23 | deleteFriend, |
| 24 | getFriendsByUserId, |
| 25 | getPendingRequests, |
| 26 | acceptFriend, |
| 27 | rejectFriend, |
| 28 | } from '../api/friends'; |
| 29 | import axios from 'axios'; |
| 30 | |
| 31 | const { Title, Text } = Typography; |
| 32 | |
| 33 | const FriendManager = ({ currentUser, onSelectRelation }) => { |
| 34 | const currentUserId = currentUser?.userid; |
| 35 | |
| 36 | const [friendName, setFriendName] = useState(''); |
| 37 | const [friends, setFriends] = useState([]); |
| 38 | const [pendingRequests, setPendingRequests] = useState([]); |
| 39 | const [userInfoMap, setUserInfoMap] = useState({}); |
| 40 | const [loading, setLoading] = useState(false); |
| 41 | const [refreshing, setRefreshing] = useState(false); |
| 42 | const [pendingLoading, setPendingLoading] = useState(false); |
| 43 | |
| 44 | useEffect(() => { |
| 45 | if (currentUserId) { |
| 46 | refreshData(); |
| 47 | } |
| 48 | }, [currentUserId]); |
| 49 | |
| 50 | const refreshData = () => { |
| 51 | loadFriends(currentUserId); |
| 52 | loadPendingRequests(currentUserId); |
| 53 | }; |
| 54 | |
| 55 | const fetchUserInfo = async (userId) => { |
| 56 | if (userInfoMap[userId]) return; |
| 57 | try { |
| 58 | const res = await axios.get(`http://localhost:8080/user/getDecoration?userid=${userId}`); |
| 59 | const info = res.data?.data; |
| 60 | if (info) { |
| 61 | setUserInfoMap((prev) => ({ |
| 62 | ...prev, |
| 63 | [userId]: { |
| 64 | username: info.username, |
| 65 | avatar: info.image, |
| 66 | }, |
| 67 | })); |
| 68 | } |
| 69 | } catch { |
| 70 | setUserInfoMap((prev) => ({ |
| 71 | ...prev, |
| 72 | [userId]: { |
| 73 | username: `用户${userId}`, |
| 74 | avatar: null, |
| 75 | }, |
| 76 | })); |
| 77 | } |
| 78 | }; |
| 79 | |
| 80 | const loadFriends = async (userId) => { |
| 81 | setRefreshing(true); |
| 82 | try { |
| 83 | const res = await getFriendsByUserId(userId); |
| 84 | const list = res.data || []; |
| 85 | setFriends(list); |
| 86 | list.forEach(f => fetchUserInfo(getFriendUserId(f))); |
| 87 | } catch { |
| 88 | message.error('加载好友失败,请稍后重试'); |
| 89 | } |
| 90 | setRefreshing(false); |
| 91 | }; |
| 92 | |
| 93 | const loadPendingRequests = async (userId) => { |
| 94 | setPendingLoading(true); |
| 95 | try { |
| 96 | const res = await getPendingRequests(userId); |
| 97 | const list = res.data || []; |
| 98 | setPendingRequests(list); |
| 99 | list.forEach(req => { |
| 100 | const otherId = req.friend1 === currentUserId ? req.friend2 : req.friend1; |
| 101 | fetchUserInfo(otherId); |
| 102 | }); |
| 103 | } catch { |
| 104 | message.error('加载好友申请失败'); |
| 105 | } |
| 106 | setPendingLoading(false); |
| 107 | }; |
| 108 | |
| 109 | const handleAddFriend = async () => { |
| 110 | if (!friendName.trim()) return message.warning('请输入好友用户名'); |
| 111 | |
| 112 | setLoading(true); |
| 113 | try { |
| 114 | const res = await axios.get(`http://localhost:8080/user/getUserid?username=${friendName.trim()}`); |
| 115 | const newFriendId = res.data?.data; |
| 116 | if (!newFriendId) { |
| 117 | message.error('未找到该用户名对应的用户'); |
| 118 | setLoading(false); |
| 119 | return; |
| 120 | } |
| 121 | |
| 122 | if (newFriendId === currentUserId) { |
| 123 | message.warning('不能添加自己为好友'); |
| 124 | setLoading(false); |
| 125 | return; |
| 126 | } |
| 127 | |
| 128 | const isAlreadyFriend = friends.some(f => |
| 129 | (f.friend1 === currentUserId && f.friend2 === newFriendId) || |
| 130 | (f.friend1 === newFriendId && f.friend2 === currentUserId) |
| 131 | ); |
| 132 | if (isAlreadyFriend) { |
| 133 | message.warning('该用户已是您的好友'); |
| 134 | setLoading(false); |
| 135 | return; |
| 136 | } |
| 137 | |
| 138 | const result = await addFriend({ friend1: currentUserId, friend2: newFriendId }); |
| 139 | if (result.data) { |
| 140 | message.success('好友请求已发送'); |
| 141 | setFriendName(''); |
| 142 | loadPendingRequests(currentUserId); |
| 143 | } else { |
| 144 | message.error('添加失败'); |
| 145 | } |
| 146 | } catch { |
| 147 | message.error('添加好友失败,请稍后重试'); |
| 148 | } finally { |
| 149 | setLoading(false); |
| 150 | } |
| 151 | }; |
| 152 | |
| 153 | const handleDelete = async (friend1, friend2) => { |
| 154 | setLoading(true); |
| 155 | try { |
| 156 | const res = await deleteFriend(friend1, friend2); |
| 157 | if (res.data) { |
| 158 | message.success('删除成功'); |
| 159 | loadFriends(currentUserId); |
| 160 | } else { |
| 161 | message.error('删除失败'); |
| 162 | } |
| 163 | } catch { |
| 164 | message.error('删除好友失败'); |
| 165 | } finally { |
| 166 | setLoading(false); |
| 167 | } |
| 168 | }; |
| 169 | |
| 170 | const handleAccept = async (friend1, friend2) => { |
| 171 | setPendingLoading(true); |
| 172 | try { |
| 173 | const res = await acceptFriend(friend1, friend2); |
| 174 | if (res.data) { |
| 175 | message.success('已同意好友请求'); |
| 176 | refreshData(); |
| 177 | } else { |
| 178 | message.error('操作失败'); |
| 179 | } |
| 180 | } catch { |
| 181 | message.error('同意失败'); |
| 182 | } finally { |
| 183 | setPendingLoading(false); |
| 184 | } |
| 185 | }; |
| 186 | |
| 187 | const handleReject = async (friend1, friend2) => { |
| 188 | setPendingLoading(true); |
| 189 | try { |
| 190 | const res = await rejectFriend(friend1, friend2); |
| 191 | if (res.data) { |
| 192 | message.info('已拒绝好友请求'); |
| 193 | loadPendingRequests(currentUserId); |
| 194 | } else { |
| 195 | message.error('操作失败'); |
| 196 | } |
| 197 | } catch { |
| 198 | message.error('拒绝失败'); |
| 199 | } finally { |
| 200 | setPendingLoading(false); |
| 201 | } |
| 202 | }; |
| 203 | |
| 204 | const getFriendUserId = (f) => f.friend1 === currentUserId ? f.friend2 : f.friend1; |
| 205 | |
| 206 | const renderUserMeta = (userId, timeLabel) => { |
| 207 | const user = userInfoMap[userId] || {}; |
| 208 | return { |
| 209 | avatar: <Avatar src={user.avatar} />, |
| 210 | title: user.username ? `${user.username}(ID: ${userId})` : `用户ID:${userId}`, |
| 211 | description: timeLabel, |
| 212 | }; |
| 213 | }; |
| 214 | |
| 215 | return ( |
| 216 | <div style={{ maxWidth: 700, margin: 'auto', padding: 24 }}> |
| 217 | <Title level={3} style={{ textAlign: 'center', marginBottom: 24 }}> |
| 218 | 好友管理 |
| 219 | </Title> |
| 220 | |
| 221 | <Space style={{ marginBottom: 24 }} align="start"> |
| 222 | <Input |
| 223 | placeholder="输入好友用户名" |
| 224 | value={friendName} |
| 225 | onChange={(e) => setFriendName(e.target.value)} |
| 226 | style={{ width: 220 }} |
| 227 | allowClear |
| 228 | prefix={<UserAddOutlined />} |
| 229 | /> |
| 230 | <Button |
| 231 | type="primary" |
| 232 | loading={loading} |
| 233 | onClick={handleAddFriend} |
| 234 | disabled={!friendName.trim()} |
| 235 | > |
| 236 | 添加好友 |
| 237 | </Button> |
| 238 | </Space> |
| 239 | |
| 240 | <Divider /> |
| 241 | |
| 242 | <Title level={4}>好友申请</Title> |
| 243 | <Spin spinning={pendingLoading}> |
| 244 | {pendingRequests.length === 0 ? ( |
| 245 | <Text type="secondary">暂无好友申请</Text> |
| 246 | ) : ( |
| 247 | <List |
| 248 | itemLayout="horizontal" |
| 249 | dataSource={pendingRequests} |
| 250 | renderItem={(item) => { |
| 251 | const otherId = item.friend1 === currentUserId ? item.friend2 : item.friend1; |
| 252 | return ( |
| 253 | <List.Item |
| 254 | actions={[ |
| 255 | <Button |
| 256 | key="accept" |
| 257 | type="primary" |
| 258 | icon={<CheckOutlined />} |
| 259 | onClick={() => handleAccept(item.friend1, item.friend2)} |
| 260 | loading={pendingLoading} |
| 261 | size="small" |
| 262 | > |
| 263 | 同意 |
| 264 | </Button>, |
| 265 | <Popconfirm |
| 266 | key="reject" |
| 267 | title="确定拒绝该好友请求?" |
| 268 | onConfirm={() => handleReject(item.friend1, item.friend2)} |
| 269 | okText="确认" |
| 270 | cancelText="取消" |
| 271 | > |
| 272 | <Button |
| 273 | danger |
| 274 | icon={<CloseOutlined />} |
| 275 | loading={pendingLoading} |
| 276 | size="small" |
| 277 | > |
| 278 | 拒绝 |
| 279 | </Button> |
| 280 | </Popconfirm>, |
| 281 | ]} |
| 282 | > |
| 283 | <List.Item.Meta {...renderUserMeta(otherId, `申请时间:${new Date(item.requestTime).toLocaleString()}`)} /> |
| 284 | </List.Item> |
| 285 | ); |
| 286 | }} |
| 287 | /> |
| 288 | )} |
| 289 | </Spin> |
| 290 | |
| 291 | <Divider /> |
| 292 | |
| 293 | <Space align="center" style={{ marginBottom: 12, justifyContent: 'space-between', width: '100%' }}> |
| 294 | <Title level={4} style={{ margin: 0 }}> |
| 295 | 我的好友列表 |
| 296 | </Title> |
| 297 | <Button |
| 298 | icon={<ReloadOutlined />} |
| 299 | onClick={() => refreshData()} |
| 300 | loading={refreshing || pendingLoading} |
| 301 | type="link" |
| 302 | > |
| 303 | 刷新 |
| 304 | </Button> |
| 305 | </Space> |
| 306 | <Spin spinning={refreshing}> |
| 307 | {friends.length === 0 ? ( |
| 308 | <Text type="secondary">暂无好友</Text> |
| 309 | ) : ( |
| 310 | <List |
| 311 | itemLayout="horizontal" |
| 312 | dataSource={friends} |
| 313 | renderItem={(f) => { |
| 314 | const friendUserId = getFriendUserId(f); |
| 315 | return ( |
| 316 | <List.Item |
| 317 | onClick={() => |
| 318 | onSelectRelation({ |
| 319 | relationid: f.relationid, |
| 320 | friendId: friendUserId, |
| 321 | }) |
| 322 | } |
| 323 | style={{ cursor: 'pointer' }} |
| 324 | actions={[ |
| 325 | <Popconfirm |
| 326 | title="确定删除该好友?" |
| 327 | onConfirm={(e) => { |
| 328 | e.stopPropagation(); |
| 329 | handleDelete(f.friend1, f.friend2); |
| 330 | }} |
| 331 | okText="确认" |
| 332 | cancelText="取消" |
| 333 | key="delete" |
| 334 | > |
| 335 | <Button |
| 336 | danger |
| 337 | icon={<DeleteOutlined />} |
| 338 | loading={loading} |
| 339 | size="small" |
| 340 | > |
| 341 | 删除 |
| 342 | </Button> |
| 343 | </Popconfirm>, |
| 344 | ]} |
| 345 | > |
| 346 | <List.Item.Meta |
| 347 | {...renderUserMeta(friendUserId, `添加时间:${new Date(f.requestTime).toLocaleString()}`)} |
| 348 | /> |
| 349 | </List.Item> |
| 350 | ); |
| 351 | }} |
| 352 | /> |
| 353 | )} |
| 354 | </Spin> |
| 355 | </div> |
| 356 | ); |
| 357 | }; |
| 358 | |
| 359 | export default FriendManager; |