"use client" import { ArrowLeft, CheckCircle, Clock, Star } from "lucide-react" import Link from "next/link" import { use, useEffect, useMemo, useState } from "react" import OrderActions from "@/components/order-actions" import { Badge } from "@/components/ui/badge" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Separator } from "@/components/ui/separator" import { statusLabels } from "@/lib/constants" import type { OrderStatus } from "@/lib/types" import { useChatStore } from "@/store/chat" import { useOrderStore } from "@/store/orders" import { useReviewStore } from "@/store/reviews" const normalStatusSteps: OrderStatus[] = [ "pending_payment", "pending_accept", "in_progress", "pending_close", "pending_review", "completed", ] const disputedStatusSteps: OrderStatus[] = [ "pending_payment", "pending_accept", "in_progress", "pending_close", "disputed", ] const cancelledStatusSteps: OrderStatus[] = ["pending_payment", "pending_accept", "cancelled"] export default function OrderDetailPage({ params }: { params: Promise<{ id: string }> }) { const { id } = use(params) const order = useOrderStore((state) => state.orders.find((item) => item.id === id)) const sessions = useChatStore((state) => state.sessions) const ensureOrderSession = useChatStore((state) => state.ensureOrderSession) const allReviews = useReviewStore((state) => state.reviews) // Filtering is deferred to useMemo after reading the raw store array. // Zustand v5 compares selector outputs by reference stability. // Returning a fresh filtered array from the selector can re-trigger updates // and loop under useSyncExternalStore (pmndrs/zustand#1936, #3155). const reviews = useMemo(() => allReviews.filter((item) => item.orderId === id), [allReviews, id]) const [nowTs, setNowTs] = useState(Date.now()) useEffect(() => { if (!order) return if (order.status === "pending_payment" || order.status === "cancelled") return ensureOrderSession(order) }, [order, ensureOrderSession]) useEffect(() => { if (!order) return if (order.status !== "pending_accept" && order.status !== "pending_close") return const timer = setInterval(() => { setNowTs(Date.now()) }, 1000) return () => clearInterval(timer) }, [order]) if (!order) { return (
订单不存在
) } const chatSession = sessions.find((session) => session.type === "order" && session.orderId === id) const statusSteps = order.status === "disputed" ? disputedStatusSteps : order.status === "cancelled" ? cancelledStatusSteps : normalStatusSteps const currentStepIndex = statusSteps.indexOf(order.status) const timeoutHint = (() => { if (order.status !== "pending_accept" && order.status !== "pending_close") return null const base = order.status === "pending_accept" ? new Date(order.createdAt).getTime() : new Date(order.closedAt ?? order.createdAt).getTime() const remainSeconds = Math.max(0, 30 - Math.floor((nowTs - base) / 1000)) return order.status === "pending_accept" ? `若 30 秒内无人接单,订单将自动取消(剩余 ${remainSeconds} 秒)` : `若 30 秒内未确认,订单将自动进入待评价(剩余 ${remainSeconds} 秒)` })() return (
返回订单列表

订单详情

{statusLabels[order.status]}
{statusSteps.map((step, i) => { const isActive = i <= currentStepIndex const isCurrent = i === currentStepIndex return (
{isActive ? : i + 1}
{statusLabels[step]}
) })}
{timeoutHint && ( {timeoutHint} )} 服务信息
服务 {order.service.title}
游戏 {order.service.gameName}
单价 ¥{order.service.price}/{order.service.unit}
打手 {order.playerName}
{order.shopName && (
店铺 {order.shopName}
)}
总价 ¥{order.totalPrice}
{order.note && (
备注: {order.note}
)}
时间线
下单时间: {new Date(order.createdAt).toLocaleString("zh-CN")}
{order.acceptedAt && (
接单时间: {new Date(order.acceptedAt).toLocaleString("zh-CN")}
)} {order.closedAt && (
结单时间: {new Date(order.closedAt).toLocaleString("zh-CN")}
)} {order.completedAt && (
完成时间: {new Date(order.completedAt).toLocaleString("zh-CN")}
)}
{reviews.length > 0 && ( 评价 {reviews.map((review) => (
{review.fromUserName}
{[1, 2, 3, 4, 5].map((star) => ( ))}
{review.sealed ? (

评价已提交,待揭晓

) : ( review.content && (

{review.content}

) )}

{new Date(review.createdAt).toLocaleDateString("zh-CN")}

))}
)}
) }