好友的相关前端
Change-Id: Iebe50bff7e96fcf6c13b57c159182f54d0a38b93
diff --git a/src/components/FriendManager.css b/src/components/FriendManager.css
new file mode 100644
index 0000000..97cf2a9
--- /dev/null
+++ b/src/components/FriendManager.css
@@ -0,0 +1,155 @@
+/* FriendManager.css */
+
+.friend-manager-container {
+ max-width: 800px;
+ margin: 20px auto;
+ padding: 0 15px;
+}
+
+.friend-card {
+ border-radius: 10px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+}
+
+.friend-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 10px;
+}
+
+.header-icon {
+ font-size: 24px;
+ color: #1890ff;
+ margin-right: 12px;
+}
+
+.header-title {
+ margin: 0 !important;
+}
+
+.search-section {
+ display: flex;
+ margin-bottom: 24px;
+ gap: 12px;
+}
+
+.search-input {
+ flex: 1;
+ border-radius: 20px;
+ padding: 10px 16px;
+}
+
+.search-button {
+ border-radius: 20px;
+ padding: 0 24px;
+ height: 40px;
+}
+
+.search-results-container {
+ margin-bottom: 24px;
+ background: #fafafa;
+ border-radius: 8px;
+ padding: 16px;
+ border: 1px solid #f0f0f0;
+}
+
+.results-title {
+ margin-bottom: 16px !important;
+ color: #333;
+}
+
+.user-item {
+ padding: 12px;
+ border-radius: 8px;
+ transition: all 0.3s;
+ cursor: pointer;
+}
+
+.user-item:hover {
+ background-color: #f5f7fa;
+}
+
+.user-avatar {
+ background-color: #1890ff;
+}
+
+.add-friend-button {
+ border-radius: 16px;
+}
+
+.section-title {
+ margin-bottom: 16px !important;
+ color: #333;
+ position: relative;
+ padding-left: 10px;
+}
+
+.section-title::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 5px;
+ height: 20px;
+ width: 4px;
+ background-color: #1890ff;
+ border-radius: 2px;
+}
+
+.request-item,
+.friend-item {
+ padding: 12px 16px;
+ border-radius: 8px;
+ margin-bottom: 8px;
+ border: 1px solid #f0f0f0;
+ transition: all 0.3s;
+}
+
+.request-item:hover,
+.friend-item:hover {
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ transform: translateY(-2px);
+ border-color: #d0e0ff;
+}
+
+.accept-button {
+ background-color: #52c41a;
+ border-color: #52c41a;
+}
+
+.reject-button {
+ background-color: #f5222d;
+ border-color: #f5222d;
+ color: white;
+}
+
+.delete-button {
+ border-radius: 16px;
+}
+
+.friend-list-section {
+ margin-top: 24px;
+}
+
+.friend-list-header {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 16px;
+}
+
+.refresh-button {
+ color: #1890ff;
+}
+
+@media (max-width: 768px) {
+ .search-section {
+ flex-direction: column;
+ }
+
+ .search-button {
+ width: 100%;
+ }
+
+ .friend-card {
+ padding: 16px;
+ }
+}
\ No newline at end of file
diff --git a/src/components/FriendManager.jsx b/src/components/FriendManager.jsx
new file mode 100644
index 0000000..374ac83
--- /dev/null
+++ b/src/components/FriendManager.jsx
@@ -0,0 +1,359 @@
+import React, { useState, useEffect } from 'react';
+import {
+ Input,
+ Button,
+ List,
+ Typography,
+ Space,
+ Spin,
+ Popconfirm,
+ message,
+ Divider,
+ Avatar,
+} from 'antd';
+import {
+ UserAddOutlined,
+ DeleteOutlined,
+ ReloadOutlined,
+ CheckOutlined,
+ CloseOutlined,
+} from '@ant-design/icons';
+import {
+ addFriend,
+ deleteFriend,
+ getFriendsByUserId,
+ getPendingRequests,
+ acceptFriend,
+ rejectFriend,
+} from '../api/friends';
+import axios from 'axios';
+
+const { Title, Text } = Typography;
+
+const FriendManager = ({ currentUser, onSelectRelation }) => {
+ const currentUserId = currentUser?.userid;
+
+ const [friendName, setFriendName] = useState('');
+ const [friends, setFriends] = useState([]);
+ const [pendingRequests, setPendingRequests] = useState([]);
+ const [userInfoMap, setUserInfoMap] = useState({});
+ const [loading, setLoading] = useState(false);
+ const [refreshing, setRefreshing] = useState(false);
+ const [pendingLoading, setPendingLoading] = useState(false);
+
+ useEffect(() => {
+ if (currentUserId) {
+ refreshData();
+ }
+ }, [currentUserId]);
+
+ const refreshData = () => {
+ loadFriends(currentUserId);
+ loadPendingRequests(currentUserId);
+ };
+
+ const fetchUserInfo = async (userId) => {
+ if (userInfoMap[userId]) return;
+ try {
+ const res = await axios.get(`http://localhost:8080/user/getDecoration?userid=${userId}`);
+ const info = res.data?.data;
+ if (info) {
+ setUserInfoMap((prev) => ({
+ ...prev,
+ [userId]: {
+ username: info.username,
+ avatar: info.image,
+ },
+ }));
+ }
+ } catch {
+ setUserInfoMap((prev) => ({
+ ...prev,
+ [userId]: {
+ username: `用户${userId}`,
+ avatar: null,
+ },
+ }));
+ }
+ };
+
+ const loadFriends = async (userId) => {
+ setRefreshing(true);
+ try {
+ const res = await getFriendsByUserId(userId);
+ const list = res.data || [];
+ setFriends(list);
+ list.forEach(f => fetchUserInfo(getFriendUserId(f)));
+ } catch {
+ message.error('加载好友失败,请稍后重试');
+ }
+ setRefreshing(false);
+ };
+
+ const loadPendingRequests = async (userId) => {
+ setPendingLoading(true);
+ try {
+ const res = await getPendingRequests(userId);
+ const list = res.data || [];
+ setPendingRequests(list);
+ list.forEach(req => {
+ const otherId = req.friend1 === currentUserId ? req.friend2 : req.friend1;
+ fetchUserInfo(otherId);
+ });
+ } catch {
+ message.error('加载好友申请失败');
+ }
+ setPendingLoading(false);
+ };
+
+ const handleAddFriend = async () => {
+ if (!friendName.trim()) return message.warning('请输入好友用户名');
+
+ setLoading(true);
+ try {
+ const res = await axios.get(`http://localhost:8080/user/getUserid?username=${friendName.trim()}`);
+ const newFriendId = res.data?.data;
+ if (!newFriendId) {
+ message.error('未找到该用户名对应的用户');
+ setLoading(false);
+ return;
+ }
+
+ if (newFriendId === currentUserId) {
+ message.warning('不能添加自己为好友');
+ setLoading(false);
+ return;
+ }
+
+ const isAlreadyFriend = friends.some(f =>
+ (f.friend1 === currentUserId && f.friend2 === newFriendId) ||
+ (f.friend1 === newFriendId && f.friend2 === currentUserId)
+ );
+ if (isAlreadyFriend) {
+ message.warning('该用户已是您的好友');
+ setLoading(false);
+ return;
+ }
+
+ const result = await addFriend({ friend1: currentUserId, friend2: newFriendId });
+ if (result.data) {
+ message.success('好友请求已发送');
+ setFriendName('');
+ loadPendingRequests(currentUserId);
+ } else {
+ message.error('添加失败');
+ }
+ } catch {
+ message.error('添加好友失败,请稍后重试');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleDelete = async (friend1, friend2) => {
+ setLoading(true);
+ try {
+ const res = await deleteFriend(friend1, friend2);
+ if (res.data) {
+ message.success('删除成功');
+ loadFriends(currentUserId);
+ } else {
+ message.error('删除失败');
+ }
+ } catch {
+ message.error('删除好友失败');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleAccept = async (friend1, friend2) => {
+ setPendingLoading(true);
+ try {
+ const res = await acceptFriend(friend1, friend2);
+ if (res.data) {
+ message.success('已同意好友请求');
+ refreshData();
+ } else {
+ message.error('操作失败');
+ }
+ } catch {
+ message.error('同意失败');
+ } finally {
+ setPendingLoading(false);
+ }
+ };
+
+ const handleReject = async (friend1, friend2) => {
+ setPendingLoading(true);
+ try {
+ const res = await rejectFriend(friend1, friend2);
+ if (res.data) {
+ message.info('已拒绝好友请求');
+ loadPendingRequests(currentUserId);
+ } else {
+ message.error('操作失败');
+ }
+ } catch {
+ message.error('拒绝失败');
+ } finally {
+ setPendingLoading(false);
+ }
+ };
+
+ const getFriendUserId = (f) => f.friend1 === currentUserId ? f.friend2 : f.friend1;
+
+ const renderUserMeta = (userId, timeLabel) => {
+ const user = userInfoMap[userId] || {};
+ return {
+ avatar: <Avatar src={user.avatar} />,
+ title: user.username ? `${user.username}(ID: ${userId})` : `用户ID:${userId}`,
+ description: timeLabel,
+ };
+ };
+
+ return (
+ <div style={{ maxWidth: 700, margin: 'auto', padding: 24 }}>
+ <Title level={3} style={{ textAlign: 'center', marginBottom: 24 }}>
+ 好友管理
+ </Title>
+
+ <Space style={{ marginBottom: 24 }} align="start">
+ <Input
+ placeholder="输入好友用户名"
+ value={friendName}
+ onChange={(e) => setFriendName(e.target.value)}
+ style={{ width: 220 }}
+ allowClear
+ prefix={<UserAddOutlined />}
+ />
+ <Button
+ type="primary"
+ loading={loading}
+ onClick={handleAddFriend}
+ disabled={!friendName.trim()}
+ >
+ 添加好友
+ </Button>
+ </Space>
+
+ <Divider />
+
+ <Title level={4}>好友申请</Title>
+ <Spin spinning={pendingLoading}>
+ {pendingRequests.length === 0 ? (
+ <Text type="secondary">暂无好友申请</Text>
+ ) : (
+ <List
+ itemLayout="horizontal"
+ dataSource={pendingRequests}
+ renderItem={(item) => {
+ const otherId = item.friend1 === currentUserId ? item.friend2 : item.friend1;
+ return (
+ <List.Item
+ actions={[
+ <Button
+ key="accept"
+ type="primary"
+ icon={<CheckOutlined />}
+ onClick={() => handleAccept(item.friend1, item.friend2)}
+ loading={pendingLoading}
+ size="small"
+ >
+ 同意
+ </Button>,
+ <Popconfirm
+ key="reject"
+ title="确定拒绝该好友请求?"
+ onConfirm={() => handleReject(item.friend1, item.friend2)}
+ okText="确认"
+ cancelText="取消"
+ >
+ <Button
+ danger
+ icon={<CloseOutlined />}
+ loading={pendingLoading}
+ size="small"
+ >
+ 拒绝
+ </Button>
+ </Popconfirm>,
+ ]}
+ >
+ <List.Item.Meta {...renderUserMeta(otherId, `申请时间:${new Date(item.requestTime).toLocaleString()}`)} />
+ </List.Item>
+ );
+ }}
+ />
+ )}
+ </Spin>
+
+ <Divider />
+
+ <Space align="center" style={{ marginBottom: 12, justifyContent: 'space-between', width: '100%' }}>
+ <Title level={4} style={{ margin: 0 }}>
+ 我的好友列表
+ </Title>
+ <Button
+ icon={<ReloadOutlined />}
+ onClick={() => refreshData()}
+ loading={refreshing || pendingLoading}
+ type="link"
+ >
+ 刷新
+ </Button>
+ </Space>
+ <Spin spinning={refreshing}>
+ {friends.length === 0 ? (
+ <Text type="secondary">暂无好友</Text>
+ ) : (
+ <List
+ itemLayout="horizontal"
+ dataSource={friends}
+ renderItem={(f) => {
+ const friendUserId = getFriendUserId(f);
+ return (
+ <List.Item
+ onClick={() =>
+ onSelectRelation({
+ relationid: f.relationid,
+ friendId: friendUserId,
+ })
+ }
+ style={{ cursor: 'pointer' }}
+ actions={[
+ <Popconfirm
+ title="确定删除该好友?"
+ onConfirm={(e) => {
+ e.stopPropagation();
+ handleDelete(f.friend1, f.friend2);
+ }}
+ okText="确认"
+ cancelText="取消"
+ key="delete"
+ >
+ <Button
+ danger
+ icon={<DeleteOutlined />}
+ loading={loading}
+ size="small"
+ >
+ 删除
+ </Button>
+ </Popconfirm>,
+ ]}
+ >
+ <List.Item.Meta
+ {...renderUserMeta(friendUserId, `添加时间:${new Date(f.requestTime).toLocaleString()}`)}
+ />
+ </List.Item>
+ );
+ }}
+ />
+ )}
+ </Spin>
+ </div>
+ );
+};
+
+export default FriendManager;