从零开始用react + tailwindcss + express + mongodb实现一个聊天程序(十) 收发消息

发布于:2025-03-05 ⋅ 阅读:(11) ⋅ 点赞:(0)

1.聊天框

首先我们完善前端的消息输入框

components下面新建MessageInput组件


import { useState,useRef } from "react"
import {X,Image,Send} from "lucide-react"

import { useChatStore } from "../store/useChatStore"
import toast from "react-hot-toast"
const MessageInput = () => {
    const [text, setText] = useState("")
    const [imagePreview, setImagePreview] = useState(null) // 预览的图片
    const fileInputRef = useRef(null) // 文件输入框
    const {sendMessages} = useChatStore(); // 发送消息

    const handleImageChange =(e) => {
        const file = e.target.files[0]
        if(!file.type.startsWith("image/")) {
            toast.error("请选择图片文件")
            return
        }
        const reader = new FileReader()
        reader.onload = () => {
            setImagePreview(reader.result)
        }
        reader.readAsDataURL(file)
    }
    const removeImage = () => {
        setImagePreview(null)
        if(fileInputRef.current)  fileInputRef.current.value = ""
    }
    const handleSendMessage = async(e) => {
        e.preventDefault()
        if(!text.trim() && !imagePreview) return
        try {
            await sendMessages({text:text.trim(),image:imagePreview})
            toast.success("发送消息成功")
            setText("")
            setImagePreview(null)
            if(fileInputRef.current)  fileInputRef.current.value = ""
        } catch (error) {
            console.log(error)
            toast.error("发送消息失败:",error.message)
        }
    }
  return (
    <div className="p-4 w-full">
       {imagePreview && (
         <div className="mb-3 flex items-center gap-2">

            <div className="relative">
                <img 
                    src={imagePreview} 
                    alt="Preview"
                    className="size-20 object-cover rounded-lg border border-zinc-700"
                />
                <button
                    onClick={removeImage}
                    className="absolute -top-1.5 -right-1.5 size-5 rounded-full bg-base-300
                    flex items-center justify-center"
                    type="button"
                >
                    <X className="size-3"/>
                </button>
            </div>
         </div>
       )}

       {/* 消息输入 */}
       <form onSubmit={handleSendMessage} className="flex items-center gap-2">
          <div className="flex-1 flex gap-2">
            <input
                type="text"
                className="w-full input input-bordered rounded-lg input-sm sm:input-md"
                placeholder="输入消息"
                value={text}
                onChange={(e) => setText(e.target.value)}
            />
            <input 
                type="file"
                accept="image/*"
                className="hidden"
                ref={fileInputRef}
                onChange={handleImageChange}
            />
            <button
                type="button"
                className={`hidden sm:flex btn btn-circle
                ${imagePreview?"text-emerald-500":"text-zinc-400"}`}
                onClick={() => fileInputRef.current.click()}
            >
                <Image size={20} />
            </button>
          </div>

           <button
            type="submit"
            className="btn btn-sm btn-circle"
            disabled={!imagePreview && !text.trim()}
           >
                <Send size={22}/>
           </button>
       </form>
    </div>
  )
}

export default MessageInput

聊天的气泡我们参考daisyUi的 chat-start chat-end 效果如下

ChatBox.jsx页面代码


import {useEffect} 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} = useChatStore()
  const {authUser} = useAuthStore()
  useEffect(()=>{
    getMessages(selectedUser._id)

  },[selectedUser._id, getMessages])

  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'}`}
             >
              <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

 这是我们在左侧发送消息  右侧用户就能收到消息了 并且自己发送的消息在右侧 收到的消息在左侧  正是用了 chat-start chat-end 这2个class

我们的判断逻辑是

// 消息的发送者id和当前用户id一致,则显示在右侧,否则显示在左侧

className={`chat ${message.senderId===authUser._id ? 'chat-end' : 'chat-start'}`}

思考一个问题 目前我们发送消息给对方 只有刷新页面才能收到新的消息 这是不符合要求的 所以我们引入socket.io 实现实时 的 收发消息 功能。

下篇继续。。。 


网站公告

今日签到

点亮在社区的每一天
去签到