功能说明
- 聊天室样式排版
- 下拉刷新功能在React版本聊天区域实现
- 在初始状态和新内容发送时在React版本实现滚动条置底功能
- 发送内容或刷新页面时去更新数据,没有轮询或者socket来实时更新
简单看下效果
功能开发
样式实现
通过css伪类给聊天框添加尖角
<div className={style.chatRoom}>
<div className={style.chatContent}>
{chatList?.map((chatItem: allType) => {
if (chatItem?.speaker == 'e') {
return (
<div className={style.chatLeft}>
<img src={(showLeft == 'e' ? chatItem?.expertImgUrl : chatItem?.userImgUrl) ?? avater} alt="" />
<div className={style.info}>
<div className={style.name}>{showLeft == 'e' ? chatItem?.expertName : chatItem?.userName}</div>
<div className={style.textCon}>
<div className={style.text}>{chatItem?.content}</div>
</div>
</div>
</div>
)
} else {
return (
<div className={style.chatRight}>
<img src={(showLeft == 'e' ? chatItem?.userImgUrl : chatItem?.expertImgUrl) ?? avater} alt="" />
<div className={style.info}>
<div className={style.name}>{showLeft == 'e' ? chatItem?.userName : chatItem?.expertName}</div>
<div className={style.textCon}>
<div className={style.text}>{chatItem?.content}</div>
</div>
</div>
</div>
)
}
})}
</div>
<div className={style.inputArea}>
<Input placeholder="请输入内容" size="large" />
<Button size="large" type="primary">发送</Button>
</div>
</div>
.chatRoom{
padding: 14px;
margin-top: 20px;
border: 1px solid #dfefff;
border-radius: 4px;
background: rgba(239, 247, 260, .5);
.chatContent{
height: 500px;
overflow: auto;
.chatLeft{
display: flex;
margin-bottom: 20px;
.text::before {
content: "";
position: absolute;
top: 10px;
bottom: 0;
left: -6.3px;
border: 1px solid #EEEEEE;
border-radius: 3px;
width: 10px;
height: 10px;
background: #fff;
transform: rotate(45deg);
border-right: transparent;
border-top: transparent;
}
}
.chatRight{
display: flex;
flex-direction: row-reverse;
margin-bottom: 20px;
.info{
text-align: right;
}
.text::after {
content: "";
position: absolute;
top: 10px;
bottom: 0;
right: -6.3px;
border: 1px solid #EEEEEE;
border-radius: 3px;
width: 10px;
height: 10px;
background: #fff;
transform: rotate(45deg);
border-left: transparent;
border-bottom: transparent;
}
}
img{
width: 45px;
height: 45px;
border-radius: 50%;
}
.info{
margin: 0 12px;
.name{
font-size: 16px;
}
.textCon{
position: relative;
.text{
position: relative;
display: inline-block;
max-width: 350px;
padding: 8px 12px;
border: 1px solid #EEEEEE;
border-radius: 5px;
background: #FFFFFF;
}
}
}
}
.inputArea{
position: relative;
button{
position: absolute;
right: 0;
}
}
}
实现滚动条置底 (默认状态以及发送消息时)
页面初始化后,根据接口返回值,判断当前页码是否为1,将聊天室的scrollTop值设置为scrollHeight来实现置底;
发送成功后,若当前为第一页,直接获取最新数据,否则,将分页重置到第一页,再去获取,以触发置底设置
import React, {useRef, useState, useEffect} from "react"
const ChatRoom = () => {
const [chatList, setChatList] = useState<allType[]>([])
const [pageNo, setPageNo] = useState<number>(1)
const [inputVal, setInputVal] = useState<string>('')
const content = useRef<HTMLDivElement>(null)
const sendMessage = async () => {
await addChat({
content: inputVal
})
setInputVal('')
if(pageNo == 1){
// 发送时若为第一页,无法触发update,需手动获取数据
getChatList()
}else{
// 发送消息时,若当前页不是第一页,手动设置,以触发滚动条置底设置
setPageNo(1)
}
}
const getChatList = async () => {
const res = await fentchChatList({
pageNo,
pageSize: 10
})
let listObj = []
if(res.obj.pageNum != 1){
listObj = [
...res.obj.list,
...chatList
]
}else{
listObj = res.obj.list
}
setChatList(listObj)
// 当pageNum为第一页时,设置scrollTop,使滚动条置底
if(content && content.current && res.obj.pageNum == 1){
content.current.scrollTop = content.current?.scrollHeight
}
}
useEffect(() => {
getChatList()
}, [pageNo])
return (
<div className={style.chatRoom}>
<div
className={style.chatContent}
ref={content}
>内容见样式实现</div>
<div className={style.inputArea}>
<Input placeholder="请输入内容" size="large" value={inputVal} onChange={changeInput} />
<Button size="large" type="primary" onClick={sendMessage}>发送</Button>
</div>
</div>
)
}
下拉加载功能
scrollTop为0时,判断是否还有新数据,若有则去获取并拼接在上一页数据之前,否则文字提示已为全部内容
import React, {useRef, useState, useEffect} from "react"
const ChatRoom = () => {
const [chatList, setChatList] = useState<allType[]>([])
const [pageNo, setPageNo] = useState<number>(1)
const [inputVal, setInputVal] = useState<string>('')
// 记录当前是否还有历史聊天内容,避免无意义的调用
const [hasMore, setHasMore] = useState<boolean>(true)
// 加载数据时显示loading效果
const [chatLoding, setChatLoading] = useState(false)
const content = useRef<HTMLDivElement>(null)
const sendMessage = async () => {
await addChat({
content: inputVal
})
setInputVal('')
if(pageNo == 1){
getChatList()
}else{
setPageNo(1)
// 发送消息是 重置setHasMore数据
setHasMore(true)
}
}
const getChatList = async () => {
// 打开loading
setChatLoading(true)
const res = await fentchChatList({
pageNo,
pageSize: 10
})
let listObj = []
if(res.obj.pageNum != 1){
listObj = [
...res.obj.list,
...chatList
]
}else{
listObj = res.obj.list
}
setChatList(listObj)
if(content && content.current && res.obj.pageNum == 1){
content.current.scrollTop = content.current?.scrollHeight
}
// 根据接口返回的数据 设置是否还有历史数据
setHasMore(res.obj.hasNextPage)
//关闭loading
setChatLoading(false)
}
useEffect(() => {
// 监听聊天区域的scroll事件
content?.current?.addEventListener('scroll', () => {
// 触发时,判断滚动条是否在页面顶部,去获取历史数据
if(content?.current?.scrollTop == 0){
// 这里使用回调形式修改pageNo,因为方法在update时不会更新,直接设置拿不到最新的state
setPageNo((page) => {
return page + 1
})
}
})
}, [])
useEffect(() => {
getChatList()
}, [pageNo])
return (
<div className={style.chatRoom}>
<div
className={style.chatContent}
ref={content}
>
{!hasMore && pageNo != 1 && <div className={style.chatLoading}>没有更多内容了</div>}
{chatLoding && <div className={style.chatLoading}><Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} /></div>}
内容见样式实现
</div>
<div className={style.inputArea}>内容见样式实现</div>
</div>
)
}
至此,功能基本实现。