feat(ui): refine public discovery pages
This commit is contained in:
+51
-73
@@ -5,6 +5,7 @@ import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { EmptyState } from "@/components/ui/empty-state"
|
||||
import { IconInput } from "@/components/ui/icon-input"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet"
|
||||
import { StatusBadge } from "@/components/ui/status-badge"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { listGames } from "@/lib/api"
|
||||
import { searchCatalog } from "@/lib/api/search"
|
||||
@@ -33,18 +35,7 @@ import { GameIcon } from "@/lib/game-icons"
|
||||
import type { SearchResponse, SearchResultItem, SearchSort } from "@/lib/search/types"
|
||||
import type { Game, Player } from "@/lib/types"
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
CheckCircle2,
|
||||
Clock,
|
||||
Filter,
|
||||
Gamepad2,
|
||||
Search,
|
||||
SlidersHorizontal,
|
||||
Star,
|
||||
Store,
|
||||
User,
|
||||
XCircle,
|
||||
} from "lucide-react"
|
||||
import { Filter, Gamepad2, Search, SlidersHorizontal, Star, Store, User } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { useRouter, useSearchParams } from "next/navigation"
|
||||
import { Suspense, useCallback, useEffect, useState } from "react"
|
||||
@@ -70,34 +61,6 @@ function resetPagination(params: URLSearchParams) {
|
||||
params.set("offset", "0")
|
||||
}
|
||||
|
||||
function StatusBadge({ status }: { status: Player["status"] }) {
|
||||
switch (status) {
|
||||
case "available":
|
||||
return (
|
||||
<Badge variant="outline" className="bg-green-50 text-green-700 border-green-200 gap-1">
|
||||
<CheckCircle2 className="w-3 h-3" />
|
||||
可接单
|
||||
</Badge>
|
||||
)
|
||||
case "busy":
|
||||
return (
|
||||
<Badge variant="outline" className="bg-yellow-50 text-yellow-700 border-yellow-200 gap-1">
|
||||
<Clock className="w-3 h-3" />
|
||||
忙碌中
|
||||
</Badge>
|
||||
)
|
||||
case "offline":
|
||||
return (
|
||||
<Badge variant="outline" className="bg-gray-50 text-gray-500 border-gray-200 gap-1">
|
||||
<XCircle className="w-3 h-3" />
|
||||
离线
|
||||
</Badge>
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function PlayerCard({ player }: { player: Player }) {
|
||||
const minPrice =
|
||||
!player.services || player.services.length === 0
|
||||
@@ -111,7 +74,7 @@ function PlayerCard({ player }: { player: Player }) {
|
||||
|
||||
return (
|
||||
<Link href={`/player/${player.id}`} className="block h-full">
|
||||
<Card className="h-full overflow-hidden flex flex-col p-0 gap-0">
|
||||
<Card className="h-full overflow-hidden flex flex-col p-0 gap-0 shadow-sm border-border/80">
|
||||
<CardHeader className="p-4 pb-2 flex flex-row items-start justify-between space-y-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar className="h-12 w-12 border-2 border-background shadow-sm">
|
||||
@@ -122,26 +85,43 @@ function PlayerCard({ player }: { player: Player }) {
|
||||
<h3 className="font-semibold text-base leading-none mb-1">{player.user.nickname}</h3>
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
{player.shopName ? (
|
||||
<span className="flex items-center gap-0.5 text-blue-600 bg-blue-50 px-1.5 py-0.5 rounded-full">
|
||||
<Badge variant="info" className="gap-0.5 px-1.5 py-0.5 rounded-full font-normal">
|
||||
<Store className="w-3 h-3" />
|
||||
{player.shopName}
|
||||
</span>
|
||||
</Badge>
|
||||
) : (
|
||||
<span className="flex items-center gap-0.5 text-gray-600 bg-gray-100 px-1.5 py-0.5 rounded-full">
|
||||
<Badge
|
||||
variant="neutral"
|
||||
className="gap-0.5 px-1.5 py-0.5 rounded-full font-normal"
|
||||
>
|
||||
<User className="w-3 h-3" />
|
||||
个人
|
||||
</span>
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<StatusBadge status={player.status} />
|
||||
<StatusBadge
|
||||
status={
|
||||
player.status === "available"
|
||||
? "success"
|
||||
: player.status === "busy"
|
||||
? "warning"
|
||||
: "neutral"
|
||||
}
|
||||
>
|
||||
{player.status === "available"
|
||||
? "可接单"
|
||||
: player.status === "busy"
|
||||
? "忙碌中"
|
||||
: "离线"}
|
||||
</StatusBadge>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="p-4 pt-2 flex-grow">
|
||||
<div className="flex items-center gap-4 mb-3 text-sm">
|
||||
<div className="flex items-center gap-1 text-yellow-500 font-medium">
|
||||
<Star className="w-4 h-4 fill-current" />
|
||||
<div className="flex items-center gap-1 text-warning font-medium">
|
||||
<Star className="w-4 h-4 fill-warning text-warning" />
|
||||
{player.rating.toFixed(1)}
|
||||
</div>
|
||||
<div className="text-muted-foreground">接单 {player.totalOrders}</div>
|
||||
@@ -194,7 +174,7 @@ type ShopSearchItem = Extract<SearchResultItem, { type: "shop" }>
|
||||
function ShopCard({ item }: { item: ShopSearchItem }) {
|
||||
return (
|
||||
<Link href={`/shop/${item.shop.id}`} className="block h-full">
|
||||
<Card className="h-full overflow-hidden flex flex-col p-0 gap-0">
|
||||
<Card className="h-full overflow-hidden flex flex-col p-0 gap-0 shadow-sm border-border/80">
|
||||
<CardHeader className="p-4 pb-2 flex flex-row items-start justify-between space-y-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar className="h-12 w-12 border-2 border-background shadow-sm">
|
||||
@@ -204,20 +184,22 @@ function ShopCard({ item }: { item: ShopSearchItem }) {
|
||||
<div>
|
||||
<h3 className="font-semibold text-base leading-none mb-1">{item.shop.name}</h3>
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<span className="flex items-center gap-0.5 text-blue-600 bg-blue-50 px-1.5 py-0.5 rounded-full">
|
||||
<Badge variant="info" className="gap-0.5 px-1.5 py-0.5 rounded-full font-normal">
|
||||
<Store className="w-3 h-3" />
|
||||
店铺
|
||||
</span>
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<StatusBadge status={item.hasAvailable ? "available" : "busy"} />
|
||||
<StatusBadge status={item.hasAvailable ? "success" : "warning"}>
|
||||
{item.hasAvailable ? "可接单" : "忙碌中"}
|
||||
</StatusBadge>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="p-4 pt-2 flex-grow">
|
||||
<div className="flex items-center gap-4 mb-3 text-sm">
|
||||
<div className="flex items-center gap-1 text-yellow-500 font-medium">
|
||||
<Star className="w-4 h-4 fill-current" />
|
||||
<div className="flex items-center gap-1 text-warning font-medium">
|
||||
<Star className="w-4 h-4 fill-warning text-warning" />
|
||||
{item.shop.rating}
|
||||
</div>
|
||||
<div className="text-muted-foreground">接单 {item.shop.totalOrders}</div>
|
||||
@@ -585,7 +567,7 @@ function SearchPageContent() {
|
||||
icon={<Search />}
|
||||
type="search"
|
||||
placeholder="搜索陪玩、游戏、标签..."
|
||||
className="border-border bg-card shadow-card transition-shadow focus:shadow-card-hover"
|
||||
className="border-border bg-card shadow-sm transition-shadow focus-visible:shadow-md"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
@@ -730,24 +712,20 @@ function SearchPageContent() {
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-20 bg-muted/30 rounded-lg border border-dashed">
|
||||
<div className="mx-auto w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-4">
|
||||
<Search className="w-6 h-6 text-muted-foreground" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium">未找到相关陪玩</h3>
|
||||
<p className="text-muted-foreground mt-1 max-w-sm mx-auto">
|
||||
{error ? "请求失败,请稍后重试" : "尝试调整筛选条件或更换搜索关键词"}
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="mt-4 rounded-full"
|
||||
onClick={() => {
|
||||
clearAllFilters()
|
||||
}}
|
||||
>
|
||||
清除所有筛选
|
||||
</Button>
|
||||
</div>
|
||||
<EmptyState
|
||||
icon={Search}
|
||||
title="未找到相关陪玩"
|
||||
description={error ? "请求失败,请稍后重试" : "尝试调整筛选条件或更换搜索关键词"}
|
||||
action={
|
||||
<Button
|
||||
variant="outline"
|
||||
className="rounded-full"
|
||||
onClick={() => clearAllFilters()}
|
||||
>
|
||||
清除所有筛选
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{canLoadMore && (
|
||||
|
||||
Reference in New Issue
Block a user