feat(ui): refine public detail pages
This commit is contained in:
@@ -10,10 +10,12 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card"
|
||||
import { EmptyState } from "@/components/ui/empty-state"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { StatusBadge } from "@/components/ui/status-badge"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { getPlayerById, listReviewsByTargetUser, listServicesByPlayer } from "@/lib/api"
|
||||
import { CheckCircle, Clock, MapPin, MessageSquare, ShoppingBag, Star } from "lucide-react"
|
||||
import { Clock, Gamepad2, MapPin, MessageSquare, ShoppingBag, Star } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
@@ -34,7 +36,7 @@ export default async function PlayerDetailPage({ params }: { params: Promise<{ i
|
||||
<div className="container mx-auto py-8 px-4 max-w-5xl">
|
||||
<div className="flex flex-col md:flex-row gap-8 mb-8">
|
||||
<div className="flex-shrink-0">
|
||||
<Avatar className="w-32 h-32 border-4 border-background shadow-card">
|
||||
<Avatar className="w-32 h-32 border border-border shadow-sm">
|
||||
<AvatarImage src={player.user.avatar} alt={player.user.nickname} />
|
||||
<AvatarFallback>{player.user.nickname[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
@@ -45,16 +47,26 @@ export default async function PlayerDetailPage({ params }: { params: Promise<{ i
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold flex items-center gap-3">
|
||||
{player.user.nickname}
|
||||
<Badge
|
||||
variant={player.status === "available" ? "default" : "secondary"}
|
||||
className="text-sm"
|
||||
<StatusBadge
|
||||
status={
|
||||
player.status === "available"
|
||||
? "success"
|
||||
: player.status === "busy"
|
||||
? "warning"
|
||||
: "neutral"
|
||||
}
|
||||
className="text-sm font-normal"
|
||||
>
|
||||
{player.status === "available" ? "可接单" : "忙碌中"}
|
||||
</Badge>
|
||||
{player.status === "available"
|
||||
? "可接单"
|
||||
: player.status === "busy"
|
||||
? "忙碌中"
|
||||
: "离线"}
|
||||
</StatusBadge>
|
||||
</h1>
|
||||
<div className="flex items-center gap-2 mt-2 text-muted-foreground">
|
||||
<div className="flex items-center text-yellow-500">
|
||||
<Star className="w-4 h-4 fill-current" />
|
||||
<div className="flex items-center text-warning">
|
||||
<Star className="w-4 h-4 fill-warning" />
|
||||
<span className="ml-1 font-medium text-foreground">{player.rating}</span>
|
||||
</div>
|
||||
<Separator orientation="vertical" className="h-4" />
|
||||
@@ -75,11 +87,13 @@ export default async function PlayerDetailPage({ params }: { params: Promise<{ i
|
||||
<FavoriteButton initialFavorited={false} targetType="player" targetId={player.id} />
|
||||
</div>
|
||||
|
||||
<div className="bg-muted/50 p-4 rounded-xl">
|
||||
<p className="text-sm leading-relaxed">
|
||||
{player.user.bio || "这个打手很懒,什么都没写~"}
|
||||
</p>
|
||||
</div>
|
||||
{player.user.bio ? (
|
||||
<div className="bg-muted/50 p-4 rounded-xl border border-border/50">
|
||||
<p className="text-sm leading-relaxed">{player.user.bio}</p>
|
||||
</div>
|
||||
) : (
|
||||
<EmptyState className="min-h-[120px] p-6" title="暂无个人简介" />
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{player.tags.map((tag) => (
|
||||
@@ -103,7 +117,9 @@ export default async function PlayerDetailPage({ params }: { params: Promise<{ i
|
||||
<Card key={service.id} className="flex flex-col h-full">
|
||||
<CardHeader>
|
||||
<div className="flex justify-between items-start">
|
||||
<Badge variant="outline">{service.gameName}</Badge>
|
||||
<Badge variant="info" className="font-normal">
|
||||
{service.gameName}
|
||||
</Badge>
|
||||
<div className="text-lg font-bold text-primary">
|
||||
¥{service.price}{" "}
|
||||
<span className="text-sm text-muted-foreground font-normal">
|
||||
@@ -140,8 +156,12 @@ export default async function PlayerDetailPage({ params }: { params: Promise<{ i
|
||||
</Card>
|
||||
))}
|
||||
{playerServices.length === 0 && (
|
||||
<div className="col-span-full text-center py-12 text-muted-foreground">
|
||||
<p>暂无服务</p>
|
||||
<div className="col-span-full">
|
||||
<EmptyState
|
||||
title="暂无服务"
|
||||
description="该陪玩尚未添加任何服务项"
|
||||
icon={Gamepad2}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -166,21 +186,20 @@ export default async function PlayerDetailPage({ params }: { params: Promise<{ i
|
||||
{new Date(review.createdAt).toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center text-yellow-500">
|
||||
<div className="flex items-center text-warning">
|
||||
{[1, 2, 3, 4, 5].map((star) => (
|
||||
<Star
|
||||
key={star}
|
||||
className={`w-4 h-4 ${star <= review.rating ? "fill-current" : "text-muted stroke-muted-foreground"}`}
|
||||
className={`w-4 h-4 ${star <= review.rating ? "fill-warning" : "text-muted stroke-muted-foreground"}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-foreground/90">{review.content}</p>
|
||||
{review.sealed && (
|
||||
<div className="flex items-center gap-1 text-xs text-green-600 bg-green-50 w-fit px-2 py-1 rounded-full">
|
||||
<CheckCircle className="w-3 h-3" />
|
||||
<span>平台认证评价</span>
|
||||
</div>
|
||||
<StatusBadge status="success" className="text-xs font-normal">
|
||||
平台认证评价
|
||||
</StatusBadge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -188,10 +207,7 @@ export default async function PlayerDetailPage({ params }: { params: Promise<{ i
|
||||
</Card>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
<MessageSquare className="w-12 h-12 mx-auto mb-4 opacity-20" />
|
||||
<p>暂无评价</p>
|
||||
</div>
|
||||
<EmptyState title="暂无评价" description="还没有收到任何评价" icon={MessageSquare} />
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
Reference in New Issue
Block a user