上节实现了好友、群聊的列表,这节来实现添加好友功能
添加 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>
}
然后在 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>
<AddFriendModal isOpen={isAddFriendModalOpen} handleClose={() => {
setAddFriendModalOpen(false)
}}/>
测试下:
然后调用下添加好友的接口。
之前是通过 id 来添加的好友:
现在要改一下:
import { IsNotEmpty } from "class-validator";
export class FriendAddDto {
@IsNotEmpty({
message: "添加好友的 username 不能为空"
})
username: string;
reason: string;
}
然后改下 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
}
})
}
先根据 username 查询 user,如果不存在就返回错误,提示 username 不存在。
如果添加的是自己,返回错误,提示不能添加自己为好友。
如果已经添加过,返回错误,提示已经添加。
否则,创建好友申请。
在页面调用下:
interfaces 加一下这个接口
export async function friendAdd(data: AddFriend) {
return axiosInstance.post('/friendship/add', data);
}
组件里调用下:
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 || '系统繁忙,请稍后再试');
}
}
试下效果:
提示好友申请已发送。
其中 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;
}
分别查询 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>
}
css
#notification-container {
padding: 20px;
}
然后在 interfaces 添加下接口:
export async function friendRequestList() {
return axiosInstance.get('/friendship/request_list');
}
在页面调用下:
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>
}
请求下接口,设置到 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>
}
看下效果:
没啥问题。
然后加一下同意和拒绝的接口调用:
export async function agreeFriendRequest(id: number) {
return axiosInstance.get(`/friendship/agree/${id}`);
}
export async function rejectFriendRequest(id: number) {
return axiosInstance.get(`/friendship/reject/${id}`);
}
然后页面上调用下:
{
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>
}
}
}
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 || '系统繁忙,请稍后再试');
}
}
试下效果:
同意后再看下好友列表:
多了小强这个好友。
我们好像忘记展示 reason 了,补一下:
最后,我们整体测试下添加好友的功能:
首先登录一个用户的账号,给 guang 发送好友请求:
然后登录 guang 的账号:
同意之后,就可以在好友列表看到这个好友了。
总结
这节我们实现了添加好友。
首先点击添加好友按钮的时候会有个弹窗,输入 username 和理由之后,会发送一个好友请求。
在通知页面分别展示发给我的和我发出的好友请求,对方点击同意后,就会成为好友了。
这样添加好友、好友请求的功能就完成了。