242 lines
8.7 KiB
TypeScript
242 lines
8.7 KiB
TypeScript
import {
|
|
AlertTriangle,
|
|
ArrowLeft,
|
|
CheckCircle,
|
|
Clock,
|
|
MessageSquare,
|
|
RefreshCw,
|
|
Star,
|
|
} from "lucide-react"
|
|
import Link from "next/link"
|
|
import { notFound } from "next/navigation"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Separator } from "@/components/ui/separator"
|
|
import { mockChatSessions, mockOrders, mockReviews } from "@/lib/mock-data"
|
|
import type { OrderStatus } from "@/lib/types"
|
|
|
|
const statusLabels: Record<OrderStatus, string> = {
|
|
pending_payment: "待支付",
|
|
pending_accept: "待接单",
|
|
in_progress: "进行中",
|
|
pending_close: "待结单",
|
|
pending_review: "待评价",
|
|
disputed: "争议中",
|
|
completed: "已完成",
|
|
cancelled: "已取消",
|
|
}
|
|
|
|
const statusSteps: OrderStatus[] = [
|
|
"pending_payment",
|
|
"pending_accept",
|
|
"in_progress",
|
|
"pending_close",
|
|
"pending_review",
|
|
"completed",
|
|
]
|
|
|
|
export default async function OrderDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
|
const { id } = await params
|
|
const order = mockOrders.find((o) => o.id === id)
|
|
if (!order) notFound()
|
|
|
|
const reviews = mockReviews.filter((r) => r.orderId === id)
|
|
const chatSession = mockChatSessions.find((s) => s.orderId === id)
|
|
const currentStepIndex = statusSteps.indexOf(order.status)
|
|
|
|
return (
|
|
<div className="container mx-auto py-8 px-4 max-w-3xl">
|
|
<Link
|
|
href="/orders"
|
|
className="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground mb-4"
|
|
>
|
|
<ArrowLeft className="h-4 w-4" />
|
|
返回订单列表
|
|
</Link>
|
|
|
|
<div className="flex items-center justify-between mb-6">
|
|
<h1 className="text-2xl font-bold">订单详情</h1>
|
|
<Badge variant="outline">{statusLabels[order.status]}</Badge>
|
|
</div>
|
|
|
|
{order.status !== "disputed" && order.status !== "cancelled" && (
|
|
<Card className="mb-6">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-center justify-between">
|
|
{statusSteps.map((step, i) => {
|
|
const isActive = i <= currentStepIndex
|
|
const isCurrent = i === currentStepIndex
|
|
return (
|
|
<div key={step} className="flex flex-col items-center gap-1 flex-1">
|
|
<div
|
|
className={`h-8 w-8 rounded-full flex items-center justify-center text-xs font-medium ${
|
|
isCurrent
|
|
? "bg-primary text-primary-foreground"
|
|
: isActive
|
|
? "bg-primary/20 text-primary"
|
|
: "bg-muted text-muted-foreground"
|
|
}`}
|
|
>
|
|
{isActive ? <CheckCircle className="h-4 w-4" /> : i + 1}
|
|
</div>
|
|
<span className="text-[10px] text-muted-foreground text-center">
|
|
{statusLabels[step]}
|
|
</span>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
<Card className="mb-6">
|
|
<CardHeader>
|
|
<CardTitle className="text-base">服务信息</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-muted-foreground">服务</span>
|
|
<span>{order.service.title}</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-muted-foreground">游戏</span>
|
|
<span>{order.service.gameName}</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-muted-foreground">单价</span>
|
|
<span>
|
|
¥{order.service.price}/{order.service.unit}
|
|
</span>
|
|
</div>
|
|
<Separator />
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-muted-foreground">打手</span>
|
|
<Link href={`/player/${order.playerId}`} className="text-primary hover:underline">
|
|
{order.playerName}
|
|
</Link>
|
|
</div>
|
|
{order.shopName && (
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-muted-foreground">店铺</span>
|
|
<Link href={`/shop/${order.shopId}`} className="text-primary hover:underline">
|
|
{order.shopName}
|
|
</Link>
|
|
</div>
|
|
)}
|
|
<Separator />
|
|
<div className="flex justify-between text-sm font-medium">
|
|
<span>总价</span>
|
|
<span className="text-lg">¥{order.totalPrice}</span>
|
|
</div>
|
|
{order.note && (
|
|
<div className="text-sm">
|
|
<span className="text-muted-foreground">备注: </span>
|
|
{order.note}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="mb-6">
|
|
<CardHeader>
|
|
<CardTitle className="text-base">时间线</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-2 text-sm">
|
|
<div className="flex items-center gap-2">
|
|
<Clock className="h-3.5 w-3.5 text-muted-foreground" />
|
|
<span className="text-muted-foreground">下单时间:</span>
|
|
{new Date(order.createdAt).toLocaleString("zh-CN")}
|
|
</div>
|
|
{order.acceptedAt && (
|
|
<div className="flex items-center gap-2">
|
|
<CheckCircle className="h-3.5 w-3.5 text-green-500" />
|
|
<span className="text-muted-foreground">接单时间:</span>
|
|
{new Date(order.acceptedAt).toLocaleString("zh-CN")}
|
|
</div>
|
|
)}
|
|
{order.closedAt && (
|
|
<div className="flex items-center gap-2">
|
|
<CheckCircle className="h-3.5 w-3.5 text-green-500" />
|
|
<span className="text-muted-foreground">结单时间:</span>
|
|
{new Date(order.closedAt).toLocaleString("zh-CN")}
|
|
</div>
|
|
)}
|
|
{order.completedAt && (
|
|
<div className="flex items-center gap-2">
|
|
<CheckCircle className="h-3.5 w-3.5 text-green-500" />
|
|
<span className="text-muted-foreground">完成时间:</span>
|
|
{new Date(order.completedAt).toLocaleString("zh-CN")}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{reviews.length > 0 && (
|
|
<Card className="mb-6">
|
|
<CardHeader>
|
|
<CardTitle className="text-base">评价</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{reviews.map((review) => (
|
|
<div key={review.id} className="space-y-1">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-sm font-medium">{review.fromUserName}</span>
|
|
<div className="flex">
|
|
{[1, 2, 3, 4, 5].map((star) => (
|
|
<Star
|
|
key={`star-${star}`}
|
|
className={`h-3.5 w-3.5 ${star <= review.rating ? "fill-yellow-400 text-yellow-400" : "text-muted"}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
{review.content && (
|
|
<p className="text-sm text-muted-foreground">{review.content}</p>
|
|
)}
|
|
<p className="text-xs text-muted-foreground">
|
|
{new Date(review.createdAt).toLocaleDateString("zh-CN")}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
<div className="flex gap-2 flex-wrap">
|
|
{(order.status === "in_progress" || order.status === "pending_close") && chatSession && (
|
|
<Button asChild>
|
|
<Link href={`/chat/${chatSession.id}`}>
|
|
<MessageSquare className="mr-1 h-4 w-4" />
|
|
聊天
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
{order.status === "pending_review" && (
|
|
<Button asChild>
|
|
<Link href={`/review/${order.id}`}>
|
|
<Star className="mr-1 h-4 w-4" />
|
|
评价
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
{["in_progress", "pending_close"].includes(order.status) && (
|
|
<Button variant="destructive" asChild>
|
|
<Link href={`/dispute/${order.id}`}>
|
|
<AlertTriangle className="mr-1 h-4 w-4" />
|
|
发起争议
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
{order.status === "completed" && (
|
|
<Button variant="outline">
|
|
<RefreshCw className="mr-1 h-4 w-4" />
|
|
再来一单
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|