这节来实现下好友/群聊页面:



现在首页是这样的:

需要在 / 下添加一个二级路由:

{
path: '/',
element: <Menu/>,
children: [
{
path: '/',
element: <Friendship/>
},
{
path: '/group',
element: <Group/>
},
{
path: 'chat',
element: <Chat/>
},
{
path: 'collection',
element: <Collection/>
},
{
path: 'notification',
element: <Notification/>
}
]
}
然后分别实现这几个组件:
src/pages/Menu/index.tsx
import { Outlet, useLocation } from "react-router-dom";
import { Menu as AntdMenu, MenuProps } from "antd";
import "./menu.css";
import { MenuClickEventHandler } from "rc-menu/lib/interface";
import { router } from "../../main";
const items: MenuProps["items"] = [
{
key: "1",
label: "好友",
},
{
key: "2",
label: "群聊",
},
{
key: "3",
label: "聊天",
},
{
key: "4",
label: "收藏",
},
{
key: "5",
label: "通知",
},
];
const handleMenuItemClick: MenuClickEventHandler = (info) => {
let path = "";
switch (info.key) {
case "1":
path = "/";
break;
case "2":
path = "/group";
break;
case "3":
path = "/chat";
break;
case "4":
path = "/collection";
break;
case "5":
path = "/notification";
break;
}
router.navigate(path);
};
export function Menu() {
const location = useLocation();
function getSelectedKeys() {
if (location.pathname === "/group") {
return ["2"];
} else if (location.pathname === "/chat") {
return ["3"];
} else if (location.pathname === "/collection") {
return ["4"];
} else if (location.pathname === "/notification") {
return ["5"];
} else {
return ["1"];
}
}
return (
<div id="menu-container">
<div className="menu-area">
<AntdMenu
defaultSelectedKeys={getSelectedKeys()}
items={items}
onClick={handleMenuItemClick}
/>
</div>
<div className="content-area">
<Outlet></Outlet>
</div>
</div>
);
}
引入 antd 的 Menu 实现菜单。
渲染的时候根据 useLocation 拿到的 pathname 来设置选中的菜单项。
点击菜单项的时候用 router.push 修改路径。
这里用到的 router 需要在 index.tsx 导出:

menu.css 如下:
#menu-container {
display: flex;
flex-direction: row;
}
#menu-container .menu-area {
width: 200px;
}
然后创建 src/pages/Friendship/index.tsx
export function Friendship() {
return <div>Friendship</div>;
}
src/pages/Group/index.tsx
export function Group() {
return <div>Group</div>;
}
src/pages/Chat/index.tsx
export function Chat() {
return <div>Chat</div>;
}
src/pages/Collection/index.tsx
export function Collection() {
return <div>Collection</div>;
}
src/pages/Notification/index.tsx
export function Notification() {
return <div>Notification</div>;
}
在 index.tsx 里导入这些组件后,我们跑起来看看:
npm run dev
点击菜单项的路由切换,以及刷新选中对应菜单项,都没问题。

然后来写下好友页面:

import { Badge, Button, Form, Input, Popconfirm, Table, message } from "antd";
import { useCallback, useEffect, useMemo, useState } from "react";
import "./index.css";
import { ColumnsType } from "antd/es/table";
import { useForm } from "antd/es/form/Form";
interface SearchFriend {
name: string;
}
interface FriendshipSearchResult {
id: number;
username: string;
nickName: string;
headPic: string;
email: string;
}
export function Friendship() {
const [friendshipResult, setFriendshipResult] = useState<
Array<FriendshipSearchResult>
>([]);
const columns: ColumnsType<FriendshipSearchResult> = useMemo(
() => [
{
title: "昵称",
dataIndex: "nickName",
},
{
title: "头像",
dataIndex: "headPic",
render: (_, record) => (
<div>
<img src={record.headPic} />
</div>
),
},
{
title: "邮箱",
dataIndex: "email",
},
{
title: "操作",
render: (_, record) => (
<div>
<a href="#">聊天</a>
</div>
),
},
],
[]
);
const searchFriend = async (values: SearchFriend) => {};
const [form] = useForm();
useEffect(() => {
searchFriend({
name: form.getFieldValue("name"),
});
}, []);
return (
<div id="friendship-container">
<div className="friendship-form">
<Form
form={form}
onFinish={searchFriend}
name="search"
layout="inline"
colon={false}>
<Form.Item label="名称" name="name">
<Input />
</Form.Item>
<Form.Item label=" ">
<Button type="primary" htmlType="submit">
搜索
</Button>
</Form.Item>
</Form>
</div>
<div className="friendship-table">
<Table
columns={columns}
dataSource={friendshipResult}
style={{ width: "1000px" }}
/>
</div>
</div>
);
}
上面是 form、下面是 table。
调用搜索接口来搜索列表数据,然后设置到 table 的 dataSource。
css 部分如下:
#friendship-container {
padding: 20px;
}
#friendship-container .friendship-form {
margin-bottom: 30px;
}
看下效果:

然后我们对接下后端接口:
当时我们的好友列表接口没支持按照昵称搜索,加一下:


return res.filter((item: User) => item.nickName.includes(name));
试下效果:


没啥问题。
然后在前端页面调用下:
在 interfaces 调用下 list 接口:
export async function friendshipList(name?: string) {
return axiosInstance.get(`/friendship/list?name=${name || ""}`);
}
组件里调用下:
const searchFriend = async (values: SearchFriend) => {
try {
const res = await friendshipList(values.name || "");
if (res.status === 201 || res.status === 200) {
setFriendshipResult(
res.data.map((item: FriendshipSearchResult) => {
return {
...item,
key: item.id,
};
})
);
}
} catch (e: any) {
message.error(e.response?.data?.message || "系统繁忙,请稍后再试");
}
};

没啥问题。
然后我们同样的方式写下 Group 组件:
之前的 /chatroom/list 接口也没支持 name 参数,加一下:


测试下:


在 interfaces 加一下:
export async function chatroomList(name: string) {
return axiosInstance.get(`/chatroom/list?name=${name}`);
}
写下 Group 组件:
import { Badge, Button, Form, Input, Popconfirm, Table, message } from "antd";
import { useCallback, useEffect, useMemo, useState } from "react";
import "./index.css";
import { ColumnsType } from "antd/es/table";
import { useForm } from "antd/es/form/Form";
import { chatroomList } from "../../interfaces";
interface SearchGroup {
name: string;
}
interface GroupSearchResult {
id: number;
name: string;
createTime: Date;
}
export function Group() {
const [groupResult, setGroupResult] = useState<Array<GroupSearchResult>>(
[]
);
const columns: ColumnsType<GroupSearchResult> = useMemo(
() => [
{
title: "名称",
dataIndex: "name",
},
{
title: "创建时间",
dataIndex: "createTime",
},
{
title: "操作",
render: (_, record) => (
<div>
<a href="#">聊天</a>
</div>
),
},
],
[]
);
const searchGroup = async (values: SearchGroup) => {
try {
const res = await chatroomList(values.name || "");
if (res.status === 201 || res.status === 200) {
setGroupResult(
res.data.map((item: GroupSearchResult) => {
return {
...item,
key: item.id,
};
})
);
}
} catch (e: any) {
message.error(e.response?.data?.message || "系统繁忙,请稍后再试");
}
};
const [form] = useForm();
useEffect(() => {
searchGroup({
name: form.getFieldValue("name"),
});
}, []);
return (
<div id="group-container">
<div className="group-form">
<Form
form={form}
onFinish={searchGroup}
name="search"
layout="inline"
colon={false}>
<Form.Item label="名称" name="name">
<Input />
</Form.Item>
<Form.Item label=" ">
<Button type="primary" htmlType="submit">
搜索
</Button>
</Form.Item>
</Form>
</div>
<div className="group-table">
<Table
columns={columns}
dataSource={groupResult}
style={{ width: "1000px" }}
/>
</div>
</div>
);
}
还有 css
#group-container {
padding: 20px;
}
#group-container .group-form {
margin-bottom: 30px;
}
测试下:

没啥问题。
总结
这节我们实现了好友和群聊的列表和搜索。
首先我们添加了二级路由,通过 Menu 组件实现了菜单,点击切换不同页面。
然后实现了好友列表和群聊列表,搜索框输入内容,点击搜索调用 list 接口,返回的数据设置到 table。
这样,好友和群聊列表就完成了
