feat(chat): add image messages and enforce readonly sessions
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
}))
|
||||
},
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user