1、 ChatBox组件
目前我们收到新的消息 不会滚动到视野当中
修改chatbox组件
// 最后一条消息的ref
const lastMessageRef = useRef(null)
// 滚动到最新消息
useEffect(()=>{
if(lastMessageRef.current && messages) {
lastMessageRef.current.scrollIntoView({behavior: 'smooth'})
}
})
javascript
import {useEffect,useRef} from "react"
import { useChatStore } from "../store/useChatStore"
import { useAuthStore } from "../store/useAuthStore"
import {formatMessageTime} from "@/lib/util"
import ChatHeader from "./ChatHeader"
import MessageInput from "./MessageInput"
const ChatBox = () => {
const {messages, getMessages, isMessagesLoading, selectedUser, subscribeToMessages, unsubscribeFromMessages} = useChatStore()
const {authUser} = useAuthStore()
// 最后一条消息的ref
const lastMessageRef = useRef(null)
useEffect(()=>{
getMessages(selectedUser._id)
// 开始订阅消息
subscribeToMessages()
return () => unsubscribeFromMessages()
},[selectedUser._id, getMessages,subscribeToMessages, unsubscribeFromMessages])
// 滚动到最新消息
useEffect(()=>{
if(lastMessageRef.current && messages) {
lastMessageRef.current.scrollIntoView({behavior: 'smooth'})
}
})
if(isMessagesLoading) return <div>Loading...</div>
return (
<div className="flex-1 flex flex-col overflow-auto">
{/* 聊天框头部 */}
<ChatHeader/>
{/* 聊天消息 */}
<div className="flex-1 overflow-auto p-4 space-y-4">
{messages.map((message)=> (
<div
key={message._id}
// 消息的发送者id和当前用户id一致,则显示在右侧,否则显示在左侧
className={`chat ${message.senderId===authUser._id ? 'chat-end' : 'chat-start'}`}
ref={lastMessageRef}
>
<div className="chat-image avatar">
<div className="size-10 rounded-full border">
<img
src={message.senderId === authUser._id ? authUser.profilePic || 'http://via.placeholder.com/150' : selectedUser.profilePic}
alt=""
/>
</div>
</div>
<div className="chat-header mb-1">
<time className="text-xs opacity-50 ml-1">{formatMessageTime(message.createdAt)}</time>
</div>
{/* flex-col 图片和文字上下排列 */}
<div className="chat-bubble flex flex-col">
{message.image && (
<img
src={message.image}
alt=""
className="sm:max-w-[200px] rounded-md mb-2"
/>
)}
{message.text && <p>{message.text}</p>}
</div>
</div>
))}
</div>
{/* 消息输入 */}
<MessageInput/>
</div>
)
}
export default ChatBox
2、Sidebar组件
展示在线人员
javascript
import { useEffect,useState} from "react"
import { useChatStore } from "../store/useChatStore"
import { useAuthStore } from "../store/useAuthStore"
import {User} from "lucide-react"
const Sidebar = () => {
const {getUsers,users,selectedUser, setSelectedUser,isUsersLoading} = useChatStore()
const {onlineUsers} = useAuthStore()
const [showOnlyOnlineUsers, setShowOnlyOnlineUsers] = useState(false)
// const onlineUsers = [];
// 过滤在线用户
const filterOnlineUsers = showOnlyOnlineUsers ? users.filter((user) => onlineUsers.includes(user._id)) :users
useEffect(() => {
getUsers()
},[getUsers])
if(isUsersLoading) return <div>Loading...</div>
return (
<aside className="h-full w-20 lg:w-72 border-r border-base-300 flex flex-col transition-all duration-200">
<div className="border-b border-base-300 w-full p-5">
<div className="flex items-center gap-2">
<User className="size-6" />
<span className="font-medium hidden lg:block">联系人</span>
</div>
{/* 在线人员过滤 */}
<div className="mt-3 hidden lg:flex items-center gap-2">
<label className="cursor-pointer flex items-center gap-2">
<input
type="checkbox"
checked={showOnlyOnlineUsers}
onChange={(e) => setShowOnlyOnlineUsers(e.target.checked)}
className="checkbox checkbox-sm"
/>
<span className="text-sm">只显示在线</span>
</label>
<span className="text-xs text-zinc-500">({onlineUsers.length -1}在线)</span>
</div>
</div>
<div className="overflow-y-auto w-full py-3">
{filterOnlineUsers.map((user) =>(
<button
key={user._id}
onClick={() => setSelectedUser(user)}
className={`w-full p-3 flex items-center gap-3
hover:bg-base-300 transition-colors
${selectedUser?._id===user._id}?"bg-base-300 ring-l ring-base-300":""
`}
>
<div className="relative mx-auto lg:mx-0">
<img
src={user.profilePic || "https://picsum.photos/200" }
alt={user.userName}
className="size-12 object-cover rounded-full"
/>
{onlineUsers.includes(user._id) &&(
<span className="absolute bottom-0 right-0 bg-green-500 size-4 rounded-full ring-2 ring-zinc-900">
</span>
)}
</div>
{/* 用户信息 只在大屏显示 */}
<div className="hidden lg:block text-left min-w-0">
<div className="font-medium truncate">{user.userName}</div>
<div className="text-sm text-zinc-400">
{onlineUsers.includes(user._id) ? "在线" : "离线"}
</div>
</div>
</button>
))}
{filterOnlineUsers.length === 0 && (
<div className="text-center text-zinc-500 py-4">无在线用户</div>
)}
</div>
</aside>
)
}
export default Sidebar
3、ChatHeader组件
javascript
import { useChatStore } from "../store/useChatStore";
import { useAuthStore } from "../store/useAuthStore";
import { X } from "lucide-react";
const ChatHeader = () => {
const {selectedUser,setSelectedUser} = useChatStore();
const {onlineUsers} = useAuthStore()
return (
<div className="p-2.5 border-b border-base-300">
<div className="flex items-center justify-between">
<div className="flex items-center">
{/* 头像 */}
<div className="avatar">
<div className="size-10 rounded-full relative">
<img src={selectedUser.profilePic || "https://picsum.photos/200"} alt={selectedUser.userName} />
</div>
</div>
{/* 用户信息 */}
<div>
<h3 className="font-medium">{selectedUser.userName}</h3>
<p className="text-sm text-base-content/70">
{onlineUsers.includes(selectedUser._id) ? "在线" : "离线"}
</p>
</div>
</div>
{/* 关闭按钮 */}
<button onClick={()=>setSelectedUser(null)}>
<X/>
</button>
</div>
</div>
)
}
export default ChatHeader
这下页面体验就更好了!