feat(ui): refine public detail pages

This commit is contained in:
zetaloop
2026-04-25 20:12:23 +08:00
parent 93b880f932
commit 151fabe8c2
3 changed files with 97 additions and 42 deletions
+42 -26
View File
@@ -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>
{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>
+2 -2
View File
@@ -27,7 +27,7 @@ export default async function PostDetailPage({ params }: { params: Promise<{ id:
</Link>
<Card className="hover:shadow-card-hover">
<Card className="shadow-sm border-border/80">
<CardHeader className="pb-3">
<div className="flex items-center gap-3">
<Avatar className="h-10 w-10">
@@ -76,7 +76,7 @@ export default async function PostDetailPage({ params }: { params: Promise<{ id:
<Link href={`/order/${post.linkedOrderId}`}>
<div className="rounded-lg border bg-muted/30 p-3 text-sm hover:bg-muted/50 transition-colors">
<div className="flex items-center gap-2 mb-1">
<Star className="h-3.5 w-3.5 text-yellow-500" />
<Star className="h-3.5 w-3.5 text-warning fill-warning" />
<span className="font-medium"></span>
</div>
<p className="text-muted-foreground text-xs"></p>
+52 -13
View File
@@ -3,6 +3,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { EmptyState } from "@/components/ui/empty-state"
import { Separator } from "@/components/ui/separator"
import { getShopById, listPlayersByShop, listReviews, listServices } from "@/lib/api"
import { getShopSections } from "@/lib/domain/shop-template"
@@ -61,8 +62,8 @@ export default async function ShopPage({ params }: PageProps) {
<h2 className="text-2xl font-bold">{shop.name}</h2>
<p className="text-muted-foreground">{shop.description}</p>
<div className="flex flex-wrap gap-4 text-sm">
<div className="flex items-center gap-1">
<Star className="w-4 h-4 text-yellow-500 fill-yellow-500" />
<div className="flex items-center gap-1 text-warning">
<Star className="w-4 h-4 fill-warning" />
<span className="font-medium">{shop.rating}</span>
<span className="text-muted-foreground"></span>
</div>
@@ -103,7 +104,16 @@ export default async function ShopPage({ params }: PageProps) {
)
case "announcements":
if (shop.announcements.length === 0) return null
if (shop.announcements.length === 0) {
return (
<EmptyState
key="announcements"
title="暂无公告"
description="店长还没有发布任何公告内容"
icon={Megaphone}
/>
)
}
return (
<Card key="announcements">
<CardHeader>
@@ -126,7 +136,16 @@ export default async function ShopPage({ params }: PageProps) {
)
case "services":
if (shopServices.length === 0) return null
if (shopServices.length === 0) {
return (
<EmptyState
key="services"
title="暂无服务"
description="这家店铺还没上架任何服务"
icon={Gamepad2}
/>
)
}
return (
<div key="services" className="space-y-4">
<h3 className="text-xl font-bold flex items-center gap-2">
@@ -138,7 +157,9 @@ export default async function ShopPage({ params }: PageProps) {
<Card key={service.id} className="flex flex-col h-full">
<CardHeader className="pb-2">
<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-right">
<span className="text-lg font-bold text-primary">¥{service.price}</span>
<span className="text-xs text-muted-foreground">/{service.unit}</span>
@@ -168,7 +189,16 @@ export default async function ShopPage({ params }: PageProps) {
)
case "players":
if (shopPlayers.length === 0) return null
if (shopPlayers.length === 0) {
return (
<EmptyState
key="players"
title="暂无打手"
description="这家店目前还没有招募打手"
icon={Users}
/>
)
}
return (
<div key="players" className="space-y-4">
<h3 className="text-xl font-bold flex items-center gap-2">
@@ -188,18 +218,18 @@ export default async function ShopPage({ params }: PageProps) {
<span
className={`absolute bottom-0 right-0 w-4 h-4 border-2 border-background rounded-full ${
player.status === "available"
? "bg-green-500"
? "bg-success"
: player.status === "busy"
? "bg-yellow-500"
: "bg-gray-500"
? "bg-warning"
: "bg-neutral"
}`}
/>
</div>
<div>
<h4 className="font-bold">{player.user.nickname}</h4>
<div className="flex items-center justify-center gap-1 text-sm text-muted-foreground mt-1">
<Star className="w-3 h-3 fill-yellow-500 text-yellow-500" />
<span>{player.rating}</span>
<Star className="w-3 h-3 fill-warning text-warning" />
<span className="text-foreground">{player.rating}</span>
<span></span>
<span>{player.totalOrders}</span>
</div>
@@ -220,7 +250,16 @@ export default async function ShopPage({ params }: PageProps) {
)
case "reviews":
if (shopReviews.length === 0) return null
if (shopReviews.length === 0) {
return (
<EmptyState
key="reviews"
title="暂无评价"
description="这家店还没收到任何评价"
icon={Star}
/>
)
}
return (
<div key="reviews" className="space-y-4">
<h3 className="text-xl font-bold flex items-center gap-2">
@@ -246,7 +285,7 @@ export default async function ShopPage({ params }: PageProps) {
key={`star-${star}`}
className={`w-3 h-3 ${
star <= review.rating
? "fill-yellow-500 text-yellow-500"
? "fill-warning text-warning"
: "text-muted-foreground"
}`}
/>