layouts
添加聊天界面布局pages
目录添加聊天界面├── src
│ ├── layouts
│ │ ├── layouts
│ │ ├── ChatLayout.jsx # 聊天布局
│ │ ├── ChatLayout.less # 聊天布局样式
│ ├── pages
│ │ ├── Im
│ │ │ ├── chat
│ │ │ │ ├── index.jsx # 聊天界面
│ │ │ │ ├── idnex.less # 聊天界面样式
│ │ │ ├── index.jsx # 聊天初始化界面
config/routes.js
{
path: '/im',
name: 'im',
icon: 'wechat', // 显示图标
routes: [
{
path: '/im', // 访问路由地址
component: '../layouts/ChatLayout', // 聊天界面使用聊天布局
routes: [
{
path: '/im', // 初始化界面
component: './Im',
},
{
path: '/im/chat', // 具体聊天界面显示
name: 'chat',
component: './Im/chat', // 对应组件
},
],
},
],
},
locales/zh-CN/menu.js
'menu.im': '即时通讯',
'menu.chat': '聊天',
dva 不熟悉可以查看介绍使用 React 常用开发框架
models
目录下创建 chat.js
const ChatModel = {
namespace: "chat",
state: {
// 当前聊天对象
chatUserInfo: {},
// 输入聊天信息
content: "",
// 聊天详情
messageList: [
{
msgId: "7751131831315698898",
sendId: "775113183131074580",
content: "你吃饭了吗?",
},
{
msgId: "7751133655565656565",
sendId: "801901013480247300",
content: "黄河之水天上来。",
},
],
// 最近聊天列表
chatRecordList: [
{
msgId: "775113183131069599",
sendId: "801901013480247300",
receiveId: "775113183131074580",
type: "1",
chatType: "1",
sendTime: "2021-03-09",
content: "黄河之水天上来。",
},
],
},
effects: {},
reducers: {
// 当前和那个聊天
chatCurrentUser(state, { payload: { chatUserInfo } }) {
return {
...state,
chatUserInfo,
};
},
// 刷新聊天列表
refreshChatList(state, { payload: { messageList } }) {
return {
...state,
messageList: messageList,
};
},
// 输入消息改变时
chatInputMessageChange(state, { payload: { message } }) {
return {
...state,
content: message,
};
},
},
subscriptions: {
setup({ history }) {
history.listen(({ pathname, search }) => {});
},
},
};
export default ChatModel;
import { Link } from "umi";
import { connect } from "dva";
import ChatRecordItem from "@/components/ChatRecordItem";
import styles from "./ChatLayout.less";
const ChatLayout = ({ dispatch, children, chat }) => {
const { chatUserInfo, chatRecordList } = chat;
// 点击切换当前聊天对象
// model/chat 中定义 `chat/chatCurrentUser`
const onChangeChatCurrentUser = (item) => {
dispatch({
type: "chat/chatCurrentUser",
payload: {
chatUserInfo: item,
},
});
};
return (
<div className={styles["chat-layout-container"]}>
<div className={styles["chat-message-list"]}>
{chatRecordList.map((item) => {
return (
<Link to="/im/chat" key={item.msgId}>
<ChatRecordItem
{...item}
// 选中聊天对象
selected={item.sendId === chatUserInfo.sendId}
onClick={() => onChangeChatCurrentUser(item)}
/>
</Link>
);
})}
</div>
<div className={styles["chat-message-content"]}>{children}</div>
</div>
);
};
export default connect(({ chat }) => ({
chat,
}))(ChatLayout);
import { Card, Empty } from "antd";
export default () => (
);
antd.design-pro Layout
定义route.js
中使用
config/routes.js
ChatRecordItem
为封装的组件,显示每一天最近聊天记录initUserList
为 mock 数据,存放 mock 用户信息(用户名称、头像)
export const initUserList = [
{
userId: "801901013480247300",
userName: "李白",
avatar:
"https://gitee.com/shizidada/moose-resource/raw/master/blog/default-avatar.png",
},
];
后期对接 SpringBoot 集成 netty-socketio
classnames
模块可以构建使用 className 添加样式
import { Avatar, Divider } from "antd";
import { initUserList } from "@/mock/userList";
import cls from "classnames";
import styles from "./index.less";
const ChatRecordItem = ({ sendId, sendTime, content, selected, onClick }) => {
const [userInfo] = initUserList.filter((item) => item.userId === sendId);
if (!userInfo) return null;
let selectedClassName = styles["chat-record-item-selected"];
return (
{userInfo.userName || ""}
{sendTime}
{content}
);
};
export default ChatRecordItem;
代码地址
https://gitee.com/shizidada/moose-react-learn/blob/master/src/pages/Im/chat/index.jsx
{
messageList.map((item, index) => {
return item.sendId !== userId ? (
{/* receiver */}
{toUserName}
{item.content}
) : (
{/* sender */}
{item.content}
{userName}
);
});
}
可以对显示发送的消息和接收的消息进行组件化封装
dispatch({
type: "chat/chatInputMessageChange",
payload: {
message: e.target.value,
},
})
}
/>
{/* for test */}
{/* */}
const onSendMessage = () => {
if (!receiveId) {
message.error("请选择聊天对象");
return;
}
if (!content) {
return;
}
let messageTemplate = {
type: "MS:TEXT",
chatType: "CT:SINGLE",
content,
sendId: userId,
receiveId: receiveId,
};
const temp = messageList.concat();
temp.push(messageTemplate);
dispatch({
type: "chat/refreshChatList",
payload: {
messageList: temp,
},
});
dispatch({
type: "chat/chatInputMessageChange",
payload: {
message: null,
},
});
};
react-scroll
模块实现react-scroll
npm install react-scroll --save
添加 react-scroll
代码
import { Element } from "react-scroll";
react-scroll
APIimport { scroller } from "react-scroll";
// scrollId 为 Element name 定义
scroller.scrollTo(`scrollId`, {
duration: 800,
delay: 0,
smooth: true,
containerId: `containerId`,
offset: 50,
});
react-scroll
APIimport { scroller } from "react-scroll";
export const scrollToBottom = (scrollId, containerId) => {
scroller.scrollTo(scrollId, {
duration: 800,
delay: 0,
smooth: true,
containerId: containerId,
offset: 50,
});
};
更多
react-scroll
API 参考react-scroll
模块文档 https://www.npmjs.com/package/react-scroll
scrollToBottom
scrollToBottom
const onMessageScroll = () => {
scrollToBottom("bottomElement", "chatItems");
};
// 发送消息
...
dispatch({
type: 'chat/chatInputMessageChange',
payload: {
message: null,
},
});
onMessageScroll(); // 调用 scrollToBottom
};
....
// 进入页面
useEffect(() => {
// 没有选中聊天对象,返回 聊天初始化界面
if (!receiveId) {
history.replace({ pathname: '/im' });
return;
}
onMessageScroll();
return () => {};
}, []);