diff --git a/app/(main)/player/[id]/page.tsx b/app/(main)/player/[id]/page.tsx
index 0160e88..041ed00 100644
--- a/app/(main)/player/[id]/page.tsx
+++ b/app/(main)/player/[id]/page.tsx
@@ -1,8 +1,211 @@
-export default function PlayerDetailPage({ params: _params }: { params: Promise<{ id: string }> }) {
+import { CheckCircle, Clock, MapPin, MessageSquare, ShoppingBag, Star } from "lucide-react"
+import Link from "next/link"
+import { notFound } from "next/navigation"
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card"
+import { Separator } from "@/components/ui/separator"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { mockPlayers, mockReviews, mockServices } from "@/lib/mock-data"
+
+export default async function PlayerDetailPage({ params }: { params: Promise<{ id: string }> }) {
+ const { id } = await params
+ const player = mockPlayers.find((p) => p.id === id)
+
+ if (!player) {
+ notFound()
+ }
+
+ const playerReviews = mockReviews.filter((r) => r.toUserId === player.id)
+ const playerServices =
+ player.services && player.services.length > 0
+ ? player.services
+ : mockServices.filter((s) => s.playerId === player.id)
+
return (
-
-
打手详情
-
评分、服务列表、评价、所属店铺
+
+
+
+
+
+ {player.user.nickname[0]}
+
+
+
+
+
+
+
+ {player.user.nickname}
+
+ {player.status === "available" ? "可接单" : "忙碌中"}
+
+
+
+
+
+ {player.rating}
+
+
+
接单 {player.totalOrders}
+
+
完成率 {(player.completionRate * 100).toFixed(0)}%
+
+
+
+ {player.shopId && (
+
+
+
+ 所属店铺: {player.shopName}
+
+
+ )}
+
+
+
+
+ {player.user.bio || "这个打手很懒,什么都没写~"}
+
+
+
+
+ {player.tags.map((tag) => (
+
+ {tag}
+
+ ))}
+
+
+
+
+
+
+
+ 服务列表 ({playerServices.length})
+
+
+ 评价 ({playerReviews.length})
+
+
+
+
+
+ {playerServices.map((service) => (
+
+
+
+
{service.gameName}
+
+ ¥{service.price}{" "}
+
+ / {service.unit}
+
+
+
+ {service.title}
+
+ {service.description}
+
+
+
+ {service.rankRange && (
+
+
+ 段位: {service.rankRange}
+
+ )}
+
+
+
+ {service.availability.map((time) => (
+ {time}
+ ))}
+
+
+
+
+ 立即下单
+
+
+ ))}
+ {playerServices.length === 0 && (
+
+ )}
+
+
+
+
+
+ {playerReviews.length > 0 ? (
+ playerReviews.map((review) => (
+
+
+
+
+
+ {review.fromUserName[0]}
+
+
+
+
+
{review.fromUserName}
+
+ {new Date(review.createdAt).toLocaleDateString()}
+
+
+
+ {[1, 2, 3, 4, 5].map((star) => (
+
+ ))}
+
+
+
{review.content}
+ {review.sealed && (
+
+
+ 平台认证评价
+
+ )}
+
+
+
+
+ ))
+ ) : (
+
+ )}
+
+
+
)
}
diff --git a/app/(main)/search/page.tsx b/app/(main)/search/page.tsx
index 622582c..c321440 100644
--- a/app/(main)/search/page.tsx
+++ b/app/(main)/search/page.tsx
@@ -1,8 +1,575 @@
-export default function SearchPage() {
+"use client"
+
+import {
+ CheckCircle2,
+ Clock,
+ Filter,
+ Gamepad2,
+ Search,
+ SlidersHorizontal,
+ Star,
+ Store,
+ User,
+ XCircle,
+} from "lucide-react"
+import Link from "next/link"
+import { useRouter, useSearchParams } from "next/navigation"
+import { Suspense, useEffect, useMemo, useState } from "react"
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
+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 { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select"
+import { Separator } from "@/components/ui/separator"
+import {
+ Sheet,
+ SheetClose,
+ SheetContent,
+ SheetDescription,
+ SheetFooter,
+ SheetHeader,
+ SheetTitle,
+ SheetTrigger,
+} from "@/components/ui/sheet"
+import { Switch } from "@/components/ui/switch"
+import { mockGames, mockPlayers } from "@/lib/mock-data"
+import type { Player } from "@/lib/types"
+import { cn } from "@/lib/utils"
+
+function StatusBadge({ status }: { status: Player["status"] }) {
+ switch (status) {
+ case "available":
+ return (
+
+
+ 可接单
+
+ )
+ case "busy":
+ return (
+
+
+ 忙碌中
+
+ )
+ case "offline":
+ return (
+
+
+ 离线
+
+ )
+ default:
+ return null
+ }
+}
+
+function PlayerCard({ player }: { player: Player }) {
+ const minPrice = useMemo(() => {
+ if (!player.services || player.services.length === 0) return 0
+ return Math.min(...player.services.map((s) => s.price))
+ }, [player.services])
+
+ const unit = useMemo(() => {
+ if (!player.services || player.services.length === 0) return "局"
+ const cheapestService = player.services.reduce((prev, curr) =>
+ prev.price < curr.price ? prev : curr,
+ )
+ return cheapestService.unit
+ }, [player.services])
+
return (
-
-
搜索
-
搜索打手、店铺和服务
+
+
+
+
+
+
+ {player.user.nickname.slice(0, 2)}
+
+
+
{player.user.nickname}
+
+ {player.shopName ? (
+
+
+ {player.shopName}
+
+ ) : (
+
+
+ 个人
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {player.rating.toFixed(1)}
+
+
接单 {player.totalOrders}
+
+
+
+ {player.games.slice(0, 3).map((game) => (
+
+ {game}
+
+ ))}
+ {player.games.length > 3 && (
+
+ +{player.games.length - 3}
+
+ )}
+
+
+
+ {player.tags.slice(0, 2).map((tag) => (
+
+ {tag}
+
+ ))}
+
+
+
+
+
+
+
+ 起
+ ¥{minPrice}
+ /{unit}
+
+
+ 查看详情
+
+
+
+
+ )
+}
+
+interface FilterProps {
+ selectedGames: string[]
+ onGameChange: (game: string, checked: boolean) => void
+ priceRange: { min: string; max: string }
+ onPriceChange: (type: "min" | "max", value: string) => void
+ onlyOnline: boolean
+ onOnlineChange: (checked: boolean) => void
+ minRating: string
+ onRatingChange: (value: string) => void
+ className?: string
+}
+
+function FilterSection({
+ selectedGames,
+ onGameChange,
+ priceRange,
+ onPriceChange,
+ onlyOnline,
+ onOnlineChange,
+ minRating,
+ onRatingChange,
+ className,
+}: FilterProps) {
+ return (
+
+
+
+
+ 游戏类型
+
+
+ {mockGames.map((game) => (
+
+ onGameChange(game.name, checked as boolean)}
+ />
+
+ {game.icon}
+ {game.name}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+ 只看在线
+
+
+
+
+
+
+
+
+
+ 最低评分
+
+
+
+
+
+
+ 不限
+ 4.0分以上
+ 4.5分以上
+ 4.8分以上
+ 5.0分满分
+
+
+
)
}
+
+function SearchPageContent() {
+ const searchParams = useSearchParams()
+ const router = useRouter()
+
+ const [searchQuery, setSearchQuery] = useState(searchParams.get("q") || "")
+ const [selectedGames, setSelectedGames] = useState
([])
+ const [priceRange, setPriceRange] = useState<{ min: string; max: string }>({ min: "", max: "" })
+ const [onlyOnline, setOnlyOnline] = useState(false)
+ const [minRating, setMinRating] = useState("0")
+ const [sortBy, setSortBy] = useState("composite")
+ const [visibleCount, setVisibleCount] = useState(12)
+
+ useEffect(() => {
+ const q = searchParams.get("q")
+ if (q !== null && q !== searchQuery) {
+ setSearchQuery(q)
+ }
+ }, [searchParams, searchQuery])
+
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ const params = new URLSearchParams(searchParams.toString())
+ if (searchQuery) {
+ params.set("q", searchQuery)
+ } else {
+ params.delete("q")
+ }
+ router.replace(`/search?${params.toString()}`, { scroll: false })
+ }, 500)
+ return () => clearTimeout(timer)
+ }, [searchQuery, router, searchParams])
+
+ const handleGameChange = (game: string, checked: boolean) => {
+ setSelectedGames((prev) => (checked ? [...prev, game] : prev.filter((g) => g !== game)))
+ }
+
+ const handlePriceChange = (type: "min" | "max", value: string) => {
+ setPriceRange((prev) => ({ ...prev, [type]: value }))
+ }
+
+ const filteredPlayers = useMemo(() => {
+ return mockPlayers.filter((player) => {
+ if (searchQuery) {
+ const query = searchQuery.toLowerCase()
+ const matchName = player.user.nickname.toLowerCase().includes(query)
+ const matchTags = player.tags.some((tag) => tag.toLowerCase().includes(query))
+ const matchGames = player.games.some((game) => game.toLowerCase().includes(query))
+ if (!matchName && !matchTags && !matchGames) return false
+ }
+
+ if (selectedGames.length > 0) {
+ const hasGame = player.games.some((game) => selectedGames.includes(game))
+ if (!hasGame) return false
+ }
+
+ const minP = priceRange.min ? Number(priceRange.min) : 0
+ const maxP = priceRange.max ? Number(priceRange.max) : Infinity
+ const playerMinPrice = Math.min(...(player.services?.map((s) => s.price) || [0]))
+
+ if (playerMinPrice < minP) return false
+ if (priceRange.max && playerMinPrice > maxP) return false
+
+ if (onlyOnline && player.status !== "available") return false
+
+ if (player.rating < Number(minRating)) return false
+
+ return true
+ })
+ }, [searchQuery, selectedGames, priceRange, onlyOnline, minRating])
+
+ const sortedPlayers = useMemo(() => {
+ return [...filteredPlayers].sort((a, b) => {
+ switch (sortBy) {
+ case "rating":
+ return b.rating - a.rating
+ case "price_asc": {
+ const priceA = Math.min(...(a.services?.map((s) => s.price) || [0]))
+ const priceB = Math.min(...(b.services?.map((s) => s.price) || [0]))
+ return priceA - priceB
+ }
+ case "price_desc": {
+ const priceA = Math.min(...(a.services?.map((s) => s.price) || [0]))
+ const priceB = Math.min(...(b.services?.map((s) => s.price) || [0]))
+ return priceB - priceA
+ }
+ case "orders":
+ return b.totalOrders - a.totalOrders
+ case "composite": {
+ const scoreA = a.rating * Math.log10(a.totalOrders + 1)
+ const scoreB = b.rating * Math.log10(b.totalOrders + 1)
+ return scoreB - scoreA
+ }
+ default:
+ return 0
+ }
+ })
+ }, [filteredPlayers, sortBy])
+
+ const displayedPlayers = sortedPlayers.slice(0, visibleCount)
+
+ return (
+
+
+
+
寻找陪玩
+
找到最适合你的游戏伙伴,一起畅玩游戏世界
+
+
+
+
+
+ setSearchQuery(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+ 筛选条件
+ 调整筛选条件以找到更匹配的陪玩
+
+
+
+
+
+
+ 查看 {filteredPlayers.length} 个结果
+
+
+
+
+
+
+
+
+
+
+
+
+
筛选
+ {(selectedGames.length > 0 ||
+ priceRange.min ||
+ priceRange.max ||
+ onlyOnline ||
+ minRating !== "0") && (
+ {
+ setSelectedGames([])
+ setPriceRange({ min: "", max: "" })
+ setOnlyOnline(false)
+ setMinRating("0")
+ }}
+ >
+ 重置
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ setSortBy("composite")}
+ className="whitespace-nowrap"
+ >
+ 综合排序
+
+ setSortBy("rating")}
+ className="whitespace-nowrap"
+ >
+ 评分最高
+
+ setSortBy("orders")}
+ className="whitespace-nowrap"
+ >
+ 接单最多
+
+ setSortBy(sortBy === "price_asc" ? "price_desc" : "price_asc")}
+ className="whitespace-nowrap gap-1"
+ >
+ 价格
+
+
+
+
+ 共找到 {filteredPlayers.length} 位陪玩
+
+
+
+ {displayedPlayers.length > 0 ? (
+
+ {displayedPlayers.map((player) => (
+
+ ))}
+
+ ) : (
+
+
+
+
+
未找到相关陪玩
+
+ 尝试调整筛选条件或更换搜索关键词
+
+
{
+ setSearchQuery("")
+ setSelectedGames([])
+ setPriceRange({ min: "", max: "" })
+ setOnlyOnline(false)
+ setMinRating("0")
+ }}
+ >
+ 清除所有筛选
+
+
+ )}
+
+ {filteredPlayers.length > visibleCount && (
+
+ setVisibleCount((prev) => prev + 12)}
+ >
+ 加载更多
+
+
+ )}
+
+
+
+ )
+}
+
+export default function SearchPage() {
+ return (
+
+
+
+ }
+ >
+
+
+ )
+}
diff --git a/app/(main)/shop/[id]/page.tsx b/app/(main)/shop/[id]/page.tsx
index eea668a..1d3db30 100644
--- a/app/(main)/shop/[id]/page.tsx
+++ b/app/(main)/shop/[id]/page.tsx
@@ -1,8 +1,273 @@
-export default function ShopDetailPage({ params: _params }: { params: Promise<{ id: string }> }) {
+import { Gamepad2, Megaphone, ShoppingBag, Star, Users } from "lucide-react"
+import Image from "next/image"
+import Link from "next/link"
+import { notFound } from "next/navigation"
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
+import { Badge } from "@/components/ui/badge"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Separator } from "@/components/ui/separator"
+import { mockPlayers, mockReviews, mockServices, mockShops } from "@/lib/mock-data"
+
+interface PageProps {
+ params: Promise<{ id: string }>
+}
+
+export default async function ShopPage({ params }: PageProps) {
+ const { id } = await params
+ const shop = mockShops.find((s) => s.id === id)
+
+ if (!shop) {
+ notFound()
+ }
+
+ const shopPlayers = mockPlayers.filter((p) => p.shopId === shop.id)
+ const playerIds = shopPlayers.map((p) => p.id)
+ const shopServices = mockServices.filter((s) => playerIds.includes(s.playerId))
+ const shopReviews = mockReviews.filter((r) => playerIds.includes(r.toUserId))
+
+ const sortedSections = [...shop.templateConfig.sections]
+ .filter((s) => s.enabled)
+ .sort((a, b) => a.order - b.order)
+
return (
-
-
店铺详情
-
店铺主页、服务列表、打手列表
+
+ {sortedSections.map((section) => {
+ switch (section.type) {
+ case "banner":
+ return (
+
+ {shop.banner ? (
+
+ ) : (
+
+ )}
+
+
+ {shop.name}
+
+
+
+ )
+
+ case "intro":
+ return (
+
+
+
+
+
+
{shop.name}
+
+ {shop.commissionType === "percentage"
+ ? `抽成 ${shop.commissionValue}%`
+ : `固定抽成 ${shop.commissionValue}`}
+
+
+
{shop.description}
+
+
+
+ {shop.rating}
+ 评分
+
+
+
+
+ {shop.totalOrders}
+ 总订单
+
+
+
+
+ {shop.playerCount}
+ 陪玩师
+
+
+
+
+
+
+ {shop.owner.nickname[0]}
+
+
+
店长:{shop.owner.nickname}
+
+ {shop.owner.bio || "暂无介绍"}
+
+
+
+
+
+
+ )
+
+ case "announcements":
+ if (shop.announcements.length === 0) return null
+ return (
+
+
+
+
+ 店铺公告
+
+
+
+
+ {shop.announcements.map((announcement) => (
+
+
+ {announcement}
+
+ ))}
+
+
+
+ )
+
+ case "services":
+ if (shopServices.length === 0) return null
+ return (
+
+
+
+ 热门服务
+
+
+ {shopServices.map((service) => (
+
+
+
+
{service.gameName}
+
+ ¥{service.price}
+ /{service.unit}
+
+
+ {service.title}
+
+
+
+ {service.description}
+
+ {service.rankRange && (
+
+ 段位:{service.rankRange}
+
+ )}
+
+
+ ))}
+
+
+ )
+
+ case "players":
+ if (shopPlayers.length === 0) return null
+ return (
+
+
+
+ 明星陪玩
+
+
+ {shopPlayers.map((player) => (
+
+
+
+
+
+
+ {player.user.nickname[0]}
+
+
+
+
+
{player.user.nickname}
+
+
+ {player.rating}
+ •
+ {player.totalOrders}单
+
+
+
+ {player.games.slice(0, 3).map((game) => (
+
+ {game}
+
+ ))}
+
+
+
+
+ ))}
+
+
+ )
+
+ case "reviews":
+ if (shopReviews.length === 0) return null
+ return (
+
+
+
+ 最新评价
+
+
+ {shopReviews.map((review) => (
+
+
+
+
+
+ {review.fromUserName[0]}
+
+
+
+
+
{review.fromUserName}
+
+ {[1, 2, 3, 4, 5].map((star) => (
+
+ ))}
+
+
+
+ {new Date(review.createdAt).toLocaleDateString()}
+
+
+
{review.content}
+
+
+
+
+ ))}
+
+
+ )
+
+ default:
+ return null
+ }
+ })}
)
}
diff --git a/app/(order)/chat/[id]/page.tsx b/app/(order)/chat/[id]/page.tsx
index 94af656..1793815 100644
--- a/app/(order)/chat/[id]/page.tsx
+++ b/app/(order)/chat/[id]/page.tsx
@@ -1,8 +1,126 @@
-export default function ChatDetailPage({ params: _params }: { params: Promise<{ id: string }> }) {
+"use client"
+
+import { ArrowLeft, Lock, Send } from "lucide-react"
+import Link from "next/link"
+import { use, useState } from "react"
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { ScrollArea } from "@/components/ui/scroll-area"
+import { mockChatMessages, mockChatSessions } from "@/lib/mock-data"
+import { cn } from "@/lib/utils"
+
+export default function ChatDetailPage({ params }: { params: Promise<{ id: string }> }) {
+ const { id } = use(params)
+ const session = mockChatSessions.find((s) => s.id === id)
+ const messages = mockChatMessages.filter((m) => m.sessionId === id)
+ const [input, setInput] = useState("")
+
+ if (!session) {
+ return (
+
+ 会话不存在
+
+ )
+ }
+
+ const other = session.participants[1]
+ const currentUserId = session.participants[0].id
+
return (
-
-
聊天
-
与打手沟通
+
+
+
+
+
+
+
+ {other.name[0]}
+
+
+
{other.name}
+
+
+ {session.type === "order" ? "订单会话" : "咨询会话"}
+
+ {session.readonly && (
+
+
+ 只读
+
+ )}
+
+
+
+
+
+
+ {messages.map((msg) => {
+ if (msg.type === "system") {
+ return (
+
+
+ {msg.content}
+
+
+ )
+ }
+ const isMine = msg.senderId === currentUserId
+ return (
+
+
+
+ {msg.senderName[0]}
+
+
+
+ {msg.content}
+
+
+ {new Date(msg.createdAt).toLocaleTimeString("zh-CN", {
+ hour: "2-digit",
+ minute: "2-digit",
+ })}
+
+
+
+ )
+ })}
+
+
+
+ {!session.readonly ? (
+
+
+
+ ) : (
+
+
+ 订单已关闭,会话为只读状态
+
+ )}
)
}
diff --git a/app/(order)/chat/page.tsx b/app/(order)/chat/page.tsx
index 38d9a73..134c654 100644
--- a/app/(order)/chat/page.tsx
+++ b/app/(order)/chat/page.tsx
@@ -1,8 +1,61 @@
+import { Lock, MessageSquare } from "lucide-react"
+import Link from "next/link"
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
+import { Badge } from "@/components/ui/badge"
+import { Card, CardContent } from "@/components/ui/card"
+import { mockChatSessions } from "@/lib/mock-data"
+
export default function ChatListPage() {
return (
-
-
消息
-
咨询会话和订单会话
+
+
消息
+
+
+ {mockChatSessions.map((session) => {
+ const other = session.participants[1]
+ return (
+
+
+
+
+
+ {other.name[0]}
+
+
+
+ {other.name}
+
+ {session.type === "order" ? "订单" : "咨询"}
+
+ {session.readonly && }
+
+
{session.lastMessage}
+
+
+ {session.lastMessageAt && (
+
+ {new Date(session.lastMessageAt).toLocaleDateString("zh-CN")}
+
+ )}
+ {session.unreadCount > 0 && (
+
+ {session.unreadCount}
+
+ )}
+
+
+
+
+ )
+ })}
+
+ {mockChatSessions.length === 0 && (
+
+
+ 暂无消息
+
+ )}
+
)
}
diff --git a/app/(order)/dispute/[id]/page.tsx b/app/(order)/dispute/[id]/page.tsx
index 1d1f1ea..79d05ce 100644
--- a/app/(order)/dispute/[id]/page.tsx
+++ b/app/(order)/dispute/[id]/page.tsx
@@ -1,8 +1,176 @@
-export default function DisputePage({ params: _params }: { params: Promise<{ id: string }> }) {
+"use client"
+
+import { AlertTriangle, ArrowLeft, Clock, FileText } from "lucide-react"
+import Link from "next/link"
+import { use, useState } from "react"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Label } from "@/components/ui/label"
+import { Separator } from "@/components/ui/separator"
+import { Textarea } from "@/components/ui/textarea"
+import { mockDisputes, mockOrders } from "@/lib/mock-data"
+
+const disputeStatusLabels: Record
= {
+ open: "已提交",
+ reviewing: "审核中",
+ resolved: "已解决",
+ appealed: "申诉中",
+}
+
+export default function DisputePage({ params }: { params: Promise<{ id: string }> }) {
+ const { id } = use(params)
+ const order = mockOrders.find((o) => o.id === id)
+ const existingDispute = mockDisputes.find((d) => d.orderId === id)
+ const [reason, setReason] = useState("")
+ const [submitted, setSubmitted] = useState(false)
+
+ if (!order) {
+ return (
+
+ 订单不存在
+
+ )
+ }
+
+ if (existingDispute) {
+ return (
+
+
+
+ 返回订单
+
+
+
+
+
+ 争议详情
+ {disputeStatusLabels[existingDispute.status]}
+
+
+
+
+
+
+ 发起人:
+ {existingDispute.initiatorName}
+
+
+
+ 提交时间:
+ {new Date(existingDispute.createdAt).toLocaleString("zh-CN")}
+
+
+
+
+
争议原因
+
{existingDispute.reason}
+
+ {existingDispute.evidence.length > 0 && (
+
+
证据截图
+
+ {existingDispute.evidence.map((url) => (
+
+ 截图
+
+ ))}
+
+
+ )}
+ {existingDispute.result && (
+ <>
+
+
+
仲裁结果
+
+ {existingDispute.result === "full_refund"
+ ? "全额退款"
+ : existingDispute.result === "full_payment"
+ ? "全额支付给打手"
+ : "部分退款"}
+
+
+ >
+ )}
+
+
+
+ )
+ }
+
+ if (submitted) {
+ return (
+
+
+
争议已提交
+
+ 平台将在 3 个工作日内审核你的争议申请,期间聊天会话仍可使用。
+
+
+ 返回订单
+
+
+ )
+ }
+
return (
-
-
争议仲裁
-
提交争议说明和证据
+
+
+
+ 返回订单
+
+
+
+
+
+
+ 发起争议
+
+
+ {order.service.title} · {order.playerName}
+
+
+
+
+ 争议原因
+
+
+
+
上传证据截图
+
+ 点击或拖拽上传截图(最多5张)
+
+
+
+
+
· 提交争议后,订单资金将继续托管
+
· 聊天记录将作为证据保留
+
· 平台将在 3 个工作日内审核
+
· 对仲裁结果不满可申诉一次
+
+
+ setSubmitted(true)}>
+ 提交争议
+
+
+
)
}
diff --git a/app/(order)/order/[id]/page.tsx b/app/(order)/order/[id]/page.tsx
index e02b098..7f8705f 100644
--- a/app/(order)/order/[id]/page.tsx
+++ b/app/(order)/order/[id]/page.tsx
@@ -1,8 +1,240 @@
-export default function OrderDetailPage({ params: _params }: { params: Promise<{ id: string }> }) {
+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 { mockOrders, mockReviews } from "@/lib/mock-data"
+import type { OrderStatus } from "@/lib/types"
+
+const statusLabels: Record
= {
+ 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 currentStepIndex = statusSteps.indexOf(order.status)
+
return (
-
-
订单详情
-
订单状态、聊天、评价、争议
+
+
+
+ 返回订单列表
+
+
+
+
订单详情
+ {statusLabels[order.status]}
+
+
+ {order.status !== "disputed" && order.status !== "cancelled" && (
+
+
+
+ {statusSteps.map((step, i) => {
+ const isActive = i <= currentStepIndex
+ const isCurrent = i === currentStepIndex
+ return (
+
+
+ {isActive ? : i + 1}
+
+
+ {statusLabels[step]}
+
+
+ )
+ })}
+
+
+
+ )}
+
+
+
+ 服务信息
+
+
+
+ 服务
+ {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.content && (
+
{review.content}
+ )}
+
+ {new Date(review.createdAt).toLocaleDateString("zh-CN")}
+
+
+ ))}
+
+
+ )}
+
+
+ {(order.status === "in_progress" || order.status === "pending_close") && (
+
+
+
+ 聊天
+
+
+ )}
+ {order.status === "pending_review" && (
+
+
+
+ 评价
+
+
+ )}
+ {["in_progress", "pending_close"].includes(order.status) && (
+
+
+
+ 发起争议
+
+
+ )}
+ {order.status === "completed" && (
+
+
+ 再来一单
+
+ )}
+
)
}
diff --git a/app/(order)/orders/page.tsx b/app/(order)/orders/page.tsx
index 81df8f7..487ab90 100644
--- a/app/(order)/orders/page.tsx
+++ b/app/(order)/orders/page.tsx
@@ -1,8 +1,131 @@
+"use client"
+
+import { Clock, MessageSquare, RefreshCw } from "lucide-react"
+import Link from "next/link"
+import { useState } from "react"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { mockOrders } from "@/lib/mock-data"
+import type { OrderStatus } from "@/lib/types"
+import { cn } from "@/lib/utils"
+import { useAuthStore } from "@/store/auth"
+
+const statusLabels: Record
= {
+ pending_payment: "待支付",
+ pending_accept: "待接单",
+ in_progress: "进行中",
+ pending_close: "待结单",
+ pending_review: "待评价",
+ disputed: "争议中",
+ completed: "已完成",
+ cancelled: "已取消",
+}
+
+const statusColors: Record = {
+ pending_payment: "bg-yellow-100 text-yellow-800",
+ pending_accept: "bg-blue-100 text-blue-800",
+ in_progress: "bg-green-100 text-green-800",
+ pending_close: "bg-orange-100 text-orange-800",
+ pending_review: "bg-purple-100 text-purple-800",
+ disputed: "bg-red-100 text-red-800",
+ completed: "bg-gray-100 text-gray-800",
+ cancelled: "bg-gray-100 text-gray-500",
+}
+
+type TabFilter = "all" | "active" | "completed" | "disputed"
+
export default function OrderListPage() {
+ const [tab, setTab] = useState("all")
+ const { currentRole } = useAuthStore()
+
+ const filtered = mockOrders.filter((order) => {
+ if (tab === "active")
+ return ["pending_accept", "in_progress", "pending_close", "pending_review"].includes(
+ order.status,
+ )
+ if (tab === "completed") return order.status === "completed" || order.status === "cancelled"
+ if (tab === "disputed") return order.status === "disputed"
+ return true
+ })
+
return (
-
我的订单
-
查看和管理订单
+
+
我的订单
+
+ {currentRole === "consumer"
+ ? "消费者视角"
+ : currentRole === "player"
+ ? "打手视角"
+ : "店主视角"}
+
+
+
+
setTab(v as TabFilter)}>
+
+ 全部
+ 进行中
+ 已完成
+ 争议
+
+
+
+ {filtered.length === 0 ? (
+ 暂无订单
+ ) : (
+ filtered.map((order) => (
+
+
+
+ {order.service.title}
+
+ {statusLabels[order.status]}
+
+
+
+ {currentRole === "consumer"
+ ? `打手: ${order.playerName}`
+ : `消费者: ${order.consumerName}`}
+ {order.shopName && ` · ${order.shopName}`}
+
+
+
+
+
+ ¥{order.totalPrice}
+
+
+ {new Date(order.createdAt).toLocaleDateString("zh-CN")}
+
+
+
+ {order.status === "in_progress" && (
+
+
+
+ 聊天
+
+
+ )}
+ {order.status === "completed" && (
+
+
+ 再来一单
+
+ )}
+
+ 查看详情
+
+
+
+
+
+ ))
+ )}
+
+
)
}
diff --git a/app/(order)/review/[id]/page.tsx b/app/(order)/review/[id]/page.tsx
index 3aac99e..247226a 100644
--- a/app/(order)/review/[id]/page.tsx
+++ b/app/(order)/review/[id]/page.tsx
@@ -1,8 +1,110 @@
-export default function ReviewPage({ params: _params }: { params: Promise<{ id: string }> }) {
+"use client"
+
+import { ArrowLeft, Lock, Star } from "lucide-react"
+import Link from "next/link"
+import { use, useState } from "react"
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Label } from "@/components/ui/label"
+import { Textarea } from "@/components/ui/textarea"
+import { mockOrders } from "@/lib/mock-data"
+
+export default function ReviewPage({ params }: { params: Promise<{ id: string }> }) {
+ const { id } = use(params)
+ const order = mockOrders.find((o) => o.id === id)
+ const [rating, setRating] = useState(0)
+ const [hoverRating, setHoverRating] = useState(0)
+ const [content, setContent] = useState("")
+ const [submitted, setSubmitted] = useState(false)
+
+ if (!order) {
+ return (
+
+ 订单不存在
+
+ )
+ }
+
+ if (submitted) {
+ return (
+
+
+
评价已提交
+
+ 你的评价已密封保存,待对方也提交评价后将同时揭晓
+
+
+ 返回订单
+
+
+ )
+ }
+
return (
-
-
评价
-
对本次服务进行评价
+
+
+
+ 返回订单
+
+
+
+
+ 评价服务
+
+ {order.service.title} · {order.playerName}
+
+
+
+
+
评分
+
+ {[1, 2, 3, 4, 5].map((star) => (
+ setRating(star)}
+ onMouseEnter={() => setHoverRating(star)}
+ onMouseLeave={() => setHoverRating(0)}
+ className="p-0.5"
+ >
+
+
+ ))}
+
+
+
+
+ 评价内容(可选)
+
+
+
+
+
+ 评价采用密封机制:你的评价将在双方都提交后同时揭晓,确保评价的真实性和公正性。
+
+
+
+ setSubmitted(true)}>
+ 提交评价
+
+
+
)
}
diff --git a/next-env.d.ts b/next-env.d.ts
index 7a70f65..9edff1c 100644
--- a/next-env.d.ts
+++ b/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/types/routes.d.ts"
+import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.