blob: 374ac836a4b760b90c8d6c2a2bd8bd8abd0be3df [file] [log] [blame]
ym9236e0ddcf2025-06-09 20:14:11 +08001import React, { useState, useEffect } from 'react';
2import {
3 Input,
4 Button,
5 List,
6 Typography,
7 Space,
8 Spin,
9 Popconfirm,
10 message,
11 Divider,
12 Avatar,
13} from 'antd';
14import {
15 UserAddOutlined,
16 DeleteOutlined,
17 ReloadOutlined,
18 CheckOutlined,
19 CloseOutlined,
20} from '@ant-design/icons';
21import {
22 addFriend,
23 deleteFriend,
24 getFriendsByUserId,
25 getPendingRequests,
26 acceptFriend,
27 rejectFriend,
28} from '../api/friends';
29import axios from 'axios';
30
31const { Title, Text } = Typography;
32
33const 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
359export default FriendManager;