Files
juwan-frontend/app/(order)/chat/[id]/page.tsx
T

186 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { ArrowLeft, ImagePlus, Lock, Send } from "lucide-react"
import Image from "next/image"
import Link from "next/link"
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"
import { Input } from "@/components/ui/input"
import { ScrollArea } from "@/components/ui/scroll-area"
import { cn } from "@/lib/utils"
import { useAuthStore } from "@/store/auth"
import { useChatStore } from "@/store/chat"
export default function ChatDetailPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = use(params)
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) {
return (
<div className="container mx-auto py-8 px-4 text-center text-muted-foreground">
</div>
)
}
const userId = user?.id ?? session.participants[0].id
const other = session.participants.find((p) => p.id !== userId) ?? session.participants[1]
return (
<div className="flex flex-col h-[calc(100vh-3.5rem)]">
<div className="border-b px-4 py-3 flex items-center gap-3">
<Link href="/chat" className="text-muted-foreground hover:text-foreground">
<ArrowLeft className="h-5 w-5" />
</Link>
<Avatar className="h-8 w-8">
<AvatarImage src={other.avatar} />
<AvatarFallback>{other.name[0]}</AvatarFallback>
</Avatar>
<div>
<span className="text-sm font-medium">{other.name}</span>
<div className="flex items-center gap-1">
<Badge variant="outline" className="text-[10px] px-1.5 py-0">
{session.type === "order" ? "订单会话" : "咨询会话"}
</Badge>
{session.readonly && (
<span className="text-[10px] text-muted-foreground flex items-center gap-0.5">
<Lock className="h-3 w-3" />
</span>
)}
</div>
</div>
</div>
<ScrollArea className="flex-1 p-4">
<div className="space-y-4 max-w-2xl mx-auto">
{messages.map((msg) => {
if (msg.type === "system") {
return (
<div key={msg.id} className="text-center">
<span className="text-xs text-muted-foreground bg-muted px-2 py-1 rounded">
{msg.content}
</span>
</div>
)
}
const isMine = msg.senderId === userId
return (
<div key={msg.id} className={cn("flex gap-2", isMine && "flex-row-reverse")}>
<Avatar className="h-8 w-8 shrink-0">
<AvatarImage src={msg.senderAvatar} />
<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",
isMine ? "bg-primary text-primary-foreground" : "bg-muted",
)}
>
{msg.content}
</p>
)}
<p className="text-[10px] text-muted-foreground mt-1">
{new Date(msg.createdAt).toLocaleTimeString("zh-CN", {
hour: "2-digit",
minute: "2-digit",
})}
</p>
</div>
</div>
)
})}
</div>
</ScrollArea>
{!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) => {
e.preventDefault()
const text = input.trim()
if (!text) return
const sender = session.participants.find((participant) => participant.id === userId)
sendTextMessage(
session.id,
{
id: userId,
name: sender?.name ?? user?.nickname ?? "",
avatar: sender?.avatar ?? user?.avatar ?? "",
},
text,
)
setInput("")
}}
>
<Input
value={input}
onChange={(e) => setInput(e.target.value)}
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>
</form>
</div>
) : (
<div className="border-t p-4 text-center text-sm text-muted-foreground">
<Lock className="h-4 w-4 inline mr-1" />
</div>
)}
</div>
)
}