From ea822aaa8d573b9d0edb7f5936e6f01a3eeab489 Mon Sep 17 00:00:00 2001 From: zetaloop Date: Sun, 22 Feb 2026 08:17:09 +0800 Subject: [PATCH] feat(chat): add image messages and enforce readonly sessions --- app/(order)/chat/[id]/page.tsx | 63 ++++++++++++++++++++++++++++------ store/chat.ts | 34 ++++++++++++++++++ 2 files changed, 87 insertions(+), 10 deletions(-) diff --git a/app/(order)/chat/[id]/page.tsx b/app/(order)/chat/[id]/page.tsx index f44f986..b2578cf 100644 --- a/app/(order)/chat/[id]/page.tsx +++ b/app/(order)/chat/[id]/page.tsx @@ -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(null) const { user } = useAuthStore() if (!session) { @@ -77,14 +80,25 @@ export default function ChatDetailPage({ params }: { params: Promise<{ id: strin {msg.senderName[0]}
-

- {msg.content} -

+ {msg.type === "image" ? ( + 聊天图片 + ) : ( +

+ {msg.content} +

+ )}

{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 ? (

+ { + 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 = "" + }} + />
{ @@ -126,6 +161,14 @@ export default function ChatDetailPage({ params }: { params: Promise<{ id: strin placeholder="输入消息..." className="flex-1" /> + diff --git a/store/chat.ts b/store/chat.ts index f98c6fb..22e3f14 100644 --- a/store/chat.ts +++ b/store/chat.ts @@ -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((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((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, + ), + })) + }, }))