feat(chat): add image messages and enforce readonly sessions

This commit is contained in:
zetaloop
2026-02-22 08:17:09 +08:00
parent 5542015abe
commit ea822aaa8d
2 changed files with 87 additions and 10 deletions
+45 -2
View File
@@ -1,8 +1,9 @@
"use client"
import { ArrowLeft, Lock, Send } from "lucide-react"
import { ArrowLeft, ImagePlus, Lock, Send } from "lucide-react"
import Image from "next/image"
import Link from "next/link"
import { use, useState } from "react"
import { use, useRef, useState } from "react"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
@@ -17,7 +18,9 @@ export default function ChatDetailPage({ params }: { params: Promise<{ id: strin
const session = useChatStore((state) => state.sessions.find((item) => item.id === id))
const messages = useChatStore((state) => state.messages.filter((item) => item.sessionId === id))
const sendTextMessage = useChatStore((state) => state.sendTextMessage)
const sendImageMessage = useChatStore((state) => state.sendImageMessage)
const [input, setInput] = useState("")
const imageInputRef = useRef<HTMLInputElement>(null)
const { user } = useAuthStore()
if (!session) {
@@ -77,6 +80,16 @@ export default function ChatDetailPage({ params }: { params: Promise<{ id: strin
<AvatarFallback>{msg.senderName[0]}</AvatarFallback>
</Avatar>
<div className={cn("max-w-[70%]", isMine && "text-right")}>
{msg.type === "image" ? (
<Image
src={msg.content}
alt="聊天图片"
width={256}
height={192}
unoptimized
className="inline-block rounded-lg max-h-48 max-w-64 border"
/>
) : (
<p
className={cn(
"inline-block rounded-lg px-3 py-2 text-sm",
@@ -85,6 +98,7 @@ export default function ChatDetailPage({ params }: { params: Promise<{ id: strin
>
{msg.content}
</p>
)}
<p className="text-[10px] text-muted-foreground mt-1">
{new Date(msg.createdAt).toLocaleTimeString("zh-CN", {
hour: "2-digit",
@@ -100,6 +114,27 @@ export default function ChatDetailPage({ params }: { params: Promise<{ id: strin
{!session.readonly ? (
<div className="border-t p-4">
<input
ref={imageInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={(event) => {
const file = event.target.files?.[0]
if (!file) return
const sender = session.participants.find((participant) => participant.id === userId)
sendImageMessage(
session.id,
{
id: userId,
name: sender?.name ?? user?.nickname ?? "",
avatar: sender?.avatar ?? user?.avatar ?? "",
},
URL.createObjectURL(file),
)
event.target.value = ""
}}
/>
<form
className="flex gap-2 max-w-2xl mx-auto"
onSubmit={(e) => {
@@ -126,6 +161,14 @@ export default function ChatDetailPage({ params }: { params: Promise<{ id: strin
placeholder="输入消息..."
className="flex-1"
/>
<Button
type="button"
size="icon"
variant="outline"
onClick={() => imageInputRef.current?.click()}
>
<ImagePlus className="h-4 w-4" />
</Button>
<Button type="submit" size="icon" disabled={!input.trim()}>
<Send className="h-4 w-4" />
</Button>
+34
View File
@@ -14,6 +14,7 @@ interface ChatState {
messages: ChatMessage[]
ensureOrderSession: (order: Order) => ChatSession
sendTextMessage: (sessionId: string, sender: Sender, content: string) => void
sendImageMessage: (sessionId: string, sender: Sender, imageUrl: string) => void
}
function resolveAvatar(userId: string) {
@@ -74,6 +75,8 @@ export const useChatStore = create<ChatState>((set, get) => ({
sendTextMessage: (sessionId, sender, content) => {
const text = content.trim()
if (!text) return
const session = get().sessions.find((item) => item.id === sessionId)
if (!session || session.readonly) return
const now = new Date().toISOString()
const message: ChatMessage = {
@@ -100,4 +103,35 @@ export const useChatStore = create<ChatState>((set, get) => ({
),
}))
},
sendImageMessage: (sessionId, sender, imageUrl) => {
const content = imageUrl.trim()
if (!content) return
const session = get().sessions.find((item) => item.id === sessionId)
if (!session || session.readonly) return
const now = new Date().toISOString()
const message: ChatMessage = {
id: generateId("msg"),
sessionId,
senderId: sender.id,
senderName: sender.name,
senderAvatar: sender.avatar,
type: "image",
content,
createdAt: now,
}
set((state) => ({
messages: [...state.messages, message],
sessions: state.sessions.map((item) =>
item.id === sessionId
? {
...item,
lastMessage: "[图片]",
lastMessageAt: now,
}
: item,
),
}))
},
}))