上节实现了好友、群聊的列表,这节来实现添加好友功能
添加 src/pages/Friendship/AddFriendModal.tsx
import { Button, Form, Input, InputNumber, Modal, message } from "antd";
import { useForm } from "antd/es/form/Form";
import TextArea from "antd/es/input/TextArea";
import { useState } from "react";
interface AddFriendModalProps {
isOpen: boolean;
handleClose: Function
}
const layout = {
labelCol: { span: 6 },
wrapperCol: { span: 18 }
}
export interface AddFriend {
username: string;
reason: string;
}
export function AddFriendModal(props: AddFriendModalProps) {
const [form] = useForm<AddFriend>();
const handleOk = async function() {
}
return <Modal
title="添加好友"
open={props.isOpen}
onOk={handleOk}
onCancel={() => props.handleClose()}
okText={'发送好友请求'}
cancelText={'取消'}
>
<Form
form={form}
colon={false}
{...layout}
>
<Form.Item
label="用户名"
name="username"
rules={[
{ required: true, message: '请输入用户名!' },
]}
>
<Input />
</Form.Item>
<Form.Item
label="添加理由"
name="reason"
rules={[
{ required: true, message: '请输入添加理由!' },
]}
>
<TextArea />
</Form.Item>
</Form>
</Modal>
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
然后在 Friednship/index.tsx 里引入下:
添加一个 state 控制弹窗显示隐藏,然后加一个按钮,点击的时候设置 state 为 true,关闭弹窗的时候设置 state 为 false
const [isAddFriendModalOpen, setAddFriendModalOpen] = useState(false);
<Form.Item label=" ">
<Button type="primary" style={{background: 'green'}} onClick={() => setAddFriendModalOpen(true)}>
添加好友
</Button>
</Form.Item>
2
3
4
5
<AddFriendModal isOpen={isAddFriendModalOpen} handleClose={() => {
setAddFriendModalOpen(false)
}}/>
2
3
测试下:
然后调用下添加好友的接口。
之前是通过 id 来添加的好友:
现在要改一下:
import { IsNotEmpty } from "class-validator";
export class FriendAddDto {
@IsNotEmpty({
message: "添加好友的 username 不能为空"
})
username: string;
reason: string;
}
2
3
4
5
6
7
8
9
10
11
然后改下 service 的实现:
async add(friendAddDto: FriendAddDto, userId: number) {
const friend = await this.prismaService.user.findUnique({
where: {
username: friendAddDto.username
}
});
if(!friend) {
throw new BadRequestException('要添加的 username 不存在');
}
if(friend.id === userId) {
throw new BadRequestException('不能添加自己为好友');
}
const found = await this.prismaService.friendship.findMany({
where: {
userId,
friendId: friend.id
}
})
if(found.length) {
throw new BadRequestException('该好友已经添加过');
}
return await this.prismaService.friendRequest.create({
data: {
fromUserId: userId,
toUserId: friend.id,
reason: friendAddDto.reason,
status: 0
}
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
先根据 username 查询 user,如果不存在就返回错误,提示 username 不存在。
如果添加的是自己,返回错误,提示不能添加自己为好友。
如果已经添加过,返回错误,提示已经添加。
否则,创建好友申请。
在页面调用下:
interfaces 加一下这个接口
export async function friendAdd(data: AddFriend) {
return axiosInstance.post('/friendship/add', data);
}
2
3
组件里调用下:
const handleOk = async function() {
await form.validateFields();
const values = form.getFieldsValue();
try{
const res = await friendAdd(values);
if(res.status === 201 || res.status === 200) {
message.success('好友申请已发送');
form.resetFields();
props.handleClose();
}
} catch(e: any){
message.error(e.response?.data?.message || '系统繁忙,请稍后再试');
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
试下效果:
提示好友申请已发送。
其中 hong 提示已经是好友了,我们查一下:
/friendship/list
确实。
然后查一下新的好友请求:
已经有了。
然后我们写一下通知页面:
之前的好友请求列表接口有点问题:
其实用户发出的好友请求、发给用户的好友请求,都应该展示出来。
并且接口应该顺带把用户信息也给查出来返回。
我们完善下:
async list(userId: number) {
const fromMeRequest = await this.prismaService.friendRequest.findMany({
where: {
fromUserId: userId
}
})
const toMeRequest = await this.prismaService.friendRequest.findMany({
where: {
toUserId: userId
}
})
const res = {
toMe: [],
fromMe: []
}
for (let i = 0; i < fromMeRequest.length; i++) {
const user = await this.prismaService.user.findUnique({
where: {
id: fromMeRequest[i].toUserId
},
select: {
id: true,
username: true,
nickName: true,
email: true,
headPic: true,
createTime: true
}
})
res.fromMe.push({
...fromMeRequest[i],
toUser: user
})
}
for (let i = 0; i < toMeRequest.length; i++) {
const user = await this.prismaService.user.findUnique({
where: {
id: toMeRequest[i].fromUserId
},
select: {
id: true,
username: true,
nickName: true,
email: true,
headPic: true,
createTime: true
}
})
res.toMe.push({
...toMeRequest[i],
fromUser: user
})
}
return res;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
分别查询 fromUserId、toUsrId 为 userId 的好友请求,然后把其中的 user 查出来返回。
测试下:
因为现在还没有发送给当前用户的好友请求。
我们在界面发送一个:
再查询就有了:
然后我们在页面把这个显示下就行:
src/pages/Notification.tsx
import { Button, Form, Input, Popconfirm, Table, Tabs, TabsProps, message } from "antd";
import { useForm } from "antd/es/form/Form";
import './index.css';
export function Notification() {
const [form ] = useForm();
const onChange = (key: string) => {
console.log(key);
};
const items: TabsProps['items'] = [
{
key: '1',
label: '我发出的',
children: '发给我的',
},
{
key: '2',
label: '我发出的',
children: '我发出的',
}
];
return <div id="notification-container">
<div className="notification-list">
<Tabs defaultActiveKey="1" items={items} onChange={onChange} />
</div>
</div>
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
css
#notification-container {
padding: 20px;
}
2
3
然后在 interfaces 添加下接口:
export async function friendRequestList() {
return axiosInstance.get('/friendship/request_list');
}
2
3
在页面调用下:
import { Button, Form, Input, Popconfirm, Table, Tabs, TabsProps, message } from "antd";
import { useForm } from "antd/es/form/Form";
import './index.css';
import { useEffect, useState } from "react";
import { friendRequestList } from "../../interfaces";
interface User {
id: number;
headPic: string;
nickName: string;
email: string;
captcha: string;
}
interface FriendRequest {
id: number
fromUserId: number
toUserId: number
reason: string
createTime: Date
fromUser: User
toUser: User
status: number
}
export function Notification() {
const [form ] = useForm();
const [fromMe, setFromMe] = useState<Array<FriendRequest>>([]);
const [toMe, setToMe] = useState<Array<FriendRequest>>([]);
async function queryFriendRequestList() {
try{
const res = await friendRequestList();
if(res.status === 201 || res.status === 200) {
setFromMe(res.data.fromMe.map((item: FriendRequest) => {
return {
...item,
key: item.id
}
}));
setToMe(res.data.toMe.map((item: FriendRequest) => {
return {
...item,
key: item.id
}
}));
}
} catch(e: any){
message.error(e.response?.data?.message || '系统繁忙,请稍后再试');
}
}
useEffect(() => {
queryFriendRequestList();
}, []);
const onChange = (key: string) => {
console.log(key);
};
const items: TabsProps['items'] = [
{
key: '1',
label: '我发出的',
children: <div style={{width: 1000}}>
{JSON.stringify(fromMe)}
</div>
},
{
key: '2',
label: '发给我的',
children: <div style={{width: 1000}}>
{JSON.stringify(toMe)}
</div>
}
];
return <div id="notification-container">
<div className="notification-list">
<Tabs defaultActiveKey="1" items={items} onChange={onChange} />
</div>
</div>
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
请求下接口,设置到 fromMe、toMe 的 state:
然后在 tab 内容展示下:
看下效果:
数据请求成功。
我们用 table 展示下就好了:
import { Button, Form, Input, Popconfirm, Table, Tabs, TabsProps, message } from "antd";
import { useForm } from "antd/es/form/Form";
import './index.css';
import { useEffect, useMemo, useState } from "react";
import { friendRequestList } from "../../interfaces";
import { ColumnsType } from "antd/es/table";
interface User {
id: number;
headPic: string;
nickName: string;
email: string;
captcha: string;
}
interface FriendRequest {
id: number
fromUserId: number
toUserId: number
reason: string
createTime: Date
fromUser: User
toUser: User
status: number
}
export function Notification() {
const [form ] = useForm();
const [fromMe, setFromMe] = useState<Array<FriendRequest>>([]);
const [toMe, setToMe] = useState<Array<FriendRequest>>([]);
async function queryFriendRequestList() {
try{
const res = await friendRequestList();
if(res.status === 201 || res.status === 200) {
setFromMe(res.data.fromMe.map((item: FriendRequest) => {
return {
...item,
key: item.id
}
}));
setToMe(res.data.toMe.map((item: FriendRequest) => {
return {
...item,
key: item.id
}
}));
}
} catch(e: any){
message.error(e.response?.data?.message || '系统繁忙,请稍后再试');
}
}
useEffect(() => {
queryFriendRequestList();
}, []);
const onChange = (key: string) => {
console.log(key);
};
const toMeColumns: ColumnsType<FriendRequest> = [
{
title: '用户',
render: (_, record) => {
return <div>
<img src={record.fromUser.headPic} width={30} height={30}/>
{' ' + record.fromUser.nickName + ' 请求加你为好友'}
</div>
}
},
{
title: '请求时间',
render: (_, record) => {
return new Date(record.createTime).toLocaleString()
}
},
{
title: '操作',
render: (_, record) => (
<div>
<a href="#">同意</a><br/>
<a href="#">拒绝</a>
</div>
)
}
]
const fromMeColumns: ColumnsType<FriendRequest> = [
{
title: '用户',
render: (_, record) => {
return <div>
{' 请求添加好友 ' + record.toUser.nickName}
<img src={record.toUser.headPic} width={30} height={30}/>
</div>
}
},
{
title: '请求时间',
render: (_, record) => {
return new Date(record.createTime).toLocaleString()
}
},
{
title: '状态',
render: (_, record) => {
const map: Record<string, any> = {
0: '申请中',
1: '已通过',
2: '已拒绝'
}
return <div>
{map[record.status]}
</div>
}
}
]
const items: TabsProps['items'] = [
{
key: '1',
label: '发给我的',
children: <div style={{width: 1000}}>
<Table columns={toMeColumns} dataSource={toMe} style={{width: '1000px'}}/>
</div>
},
{
key: '2',
label: '我发出的',
children: <div style={{width: 1000}}>
<Table columns={fromMeColumns} dataSource={fromMe} style={{width: '1000px'}}/>
</div>
}
];
return <div id="notification-container">
<div className="notification-list">
<Tabs defaultActiveKey="1" items={items} onChange={onChange} />
</div>
</div>
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
看下效果:
没啥问题。
然后加一下同意和拒绝的接口调用:
export async function agreeFriendRequest(id: number) {
return axiosInstance.get(`/friendship/agree/${id}`);
}
export async function rejectFriendRequest(id: number) {
return axiosInstance.get(`/friendship/reject/${id}`);
}
2
3
4
5
6
7
然后页面上调用下:
{
title: '操作',
render: (_, record) => {
if(record.status === 0) {
return <div>
<a href="#" onClick={() => agree(record.fromUserId)}>同意</a><br/>
<a href="#" onClick={() => reject(record.fromUserId)}>拒绝</a>
</div>
} else {
const map: Record<string, any> = {
1: '已通过',
2: '已拒绝'
}
return <div>
{map[record.status]}
</div>
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
async function agree(id: number) {
try{
const res = await agreeFriendRequest(id);
if(res.status === 201 || res.status === 200) {
message.success('操作成功');
queryFriendRequestList();
}
} catch(e: any){
message.error(e.response?.data?.message || '系统繁忙,请稍后再试');
}
}
async function reject(id: number) {
try{
const res = await rejectFriendRequest(id);
if(res.status === 201 || res.status === 200) {
message.success('操作成功');
queryFriendRequestList();
}
} catch(e: any){
message.error(e.response?.data?.message || '系统繁忙,请稍后再试');
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
试下效果:
同意后再看下好友列表:
多了小强这个好友。
我们好像忘记展示 reason 了,补一下:
最后,我们整体测试下添加好友的功能:
首先登录一个用户的账号,给 guang 发送好友请求:
然后登录 guang 的账号:
同意之后,就可以在好友列表看到这个好友了。
# 总结
这节我们实现了添加好友。
首先点击添加好友按钮的时候会有个弹窗,输入 username 和理由之后,会发送一个好友请求。
在通知页面分别展示发给我的和我发出的好友请求,对方点击同意后,就会成为好友了。
这样添加好友、好友请求的功能就完成了。