Files
juwan-frontend/app/(order)/dispute/[id]/page.tsx
T

268 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { AlertTriangle, ArrowLeft, Clock, FileText, Upload, X } from "lucide-react"
import Image from "next/image"
import Link from "next/link"
import { useRouter, useSearchParams } from "next/navigation"
import { type ChangeEvent, use, useEffect, useRef, 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"
const disputeStatusLabels: Record<string, string> = {
open: "已提交",
reviewing: "审核中",
resolved: "已解决",
appealed: "申诉中",
}
export default function DisputePage({ params }: { params: Promise<{ id: string }> }) {
const { id } = use(params)
const router = useRouter()
const searchParams = useSearchParams()
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)
const [files, setFiles] = useState<string[]>([])
const fileInputRef = useRef<HTMLInputElement>(null)
const filesRef = useRef<string[]>([])
useEffect(() => {
filesRef.current = files
}, [files])
useEffect(
() => () => {
filesRef.current.forEach((url) => {
URL.revokeObjectURL(url)
})
},
[],
)
const handleFileSelect = (event: ChangeEvent<HTMLInputElement>) => {
const selectedFiles = event.target.files
if (!selectedFiles?.length) return
setFiles((prev) => {
const remaining = 5 - prev.length
if (remaining <= 0) return prev
const nextUrls = Array.from(selectedFiles)
.slice(0, remaining)
.map((file) => URL.createObjectURL(file))
return [...prev, ...nextUrls]
})
event.target.value = ""
}
const handleRemoveFile = (index: number) => {
setFiles((prev) => {
const removed = prev[index]
if (removed) URL.revokeObjectURL(removed)
return prev.filter((_, currentIndex) => currentIndex !== index)
})
}
const handleSubmit = () => {
setSubmitted(true)
router.replace(`/dispute/${id}?submitted=1`)
}
const showSubmitted = submitted || searchParams.get("submitted") === "1"
if (!order) {
return (
<div className="container mx-auto py-8 px-4 text-center text-muted-foreground">
</div>
)
}
if (existingDispute) {
return (
<div className="container mx-auto py-8 px-4 max-w-lg">
<Link
href={`/order/${id}`}
className="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground mb-4"
>
<ArrowLeft className="h-4 w-4" />
</Link>
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle></CardTitle>
<Badge variant="outline">{disputeStatusLabels[existingDispute.status]}</Badge>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<div className="flex items-center gap-2 text-sm">
<FileText className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground">:</span>
{existingDispute.initiatorName}
</div>
<div className="flex items-center gap-2 text-sm">
<Clock className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground">:</span>
{new Date(existingDispute.createdAt).toLocaleString("zh-CN")}
</div>
</div>
<Separator />
<div>
<Label className="text-muted-foreground"></Label>
<p className="mt-1 text-sm">{existingDispute.reason}</p>
</div>
{existingDispute.evidence.length > 0 && (
<div>
<Label className="text-muted-foreground"></Label>
<div className="mt-1 flex gap-2">
{existingDispute.evidence.map((url) => (
<div
key={url}
className="relative h-20 w-20 rounded border overflow-hidden bg-muted"
>
<Image src={url} alt="证据截图" fill className="object-cover" />
</div>
))}
</div>
</div>
)}
{existingDispute.result && (
<>
<Separator />
<div>
<Label className="text-muted-foreground"></Label>
<p className="mt-1 text-sm font-medium">
{existingDispute.result === "full_refund"
? "全额退款"
: existingDispute.result === "full_payment"
? "全额支付给打手"
: "部分退款"}
</p>
</div>
</>
)}
</CardContent>
</Card>
</div>
)
}
if (showSubmitted) {
return (
<div className="container mx-auto py-8 px-4 max-w-lg text-center space-y-4">
<AlertTriangle className="h-12 w-12 mx-auto text-yellow-500" />
<h2 className="text-xl font-bold"></h2>
<p className="text-sm text-muted-foreground"> 3 </p>
<Link href={`/order/${id}`} className="text-sm text-primary hover:underline">
</Link>
</div>
)
}
return (
<div className="container mx-auto py-8 px-4 max-w-lg">
<Link
href={`/order/${id}`}
className="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground mb-4"
>
<ArrowLeft className="h-4 w-4" />
</Link>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<AlertTriangle className="h-5 w-5 text-yellow-500" />
</CardTitle>
<p className="text-sm text-muted-foreground">
{order.service.title} · {order.playerName}
</p>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="dispute-reason"></Label>
<Textarea
id="dispute-reason"
placeholder="请详细描述你遇到的问题..."
value={reason}
onChange={(e) => setReason(e.target.value)}
rows={4}
/>
</div>
<div className="space-y-2">
<Label></Label>
<input
ref={fileInputRef}
type="file"
accept="image/*"
multiple
className="hidden"
onChange={handleFileSelect}
/>
<button
type="button"
className="w-full border-2 border-dashed rounded-md p-6 text-center text-sm text-muted-foreground hover:border-primary/50 hover:text-foreground transition-colors disabled:opacity-50"
onClick={() => fileInputRef.current?.click()}
disabled={files.length >= 5}
>
<div className="flex flex-col items-center gap-2">
<Upload className="h-5 w-5" />
<span>5</span>
</div>
</button>
{files.length > 0 && (
<div className="grid grid-cols-5 gap-2">
{files.map((fileUrl, index) => (
<div
key={fileUrl}
className="relative h-16 w-16 rounded border overflow-hidden bg-muted"
>
<Image
src={fileUrl}
alt={`证据截图 ${index + 1}`}
fill
unoptimized
className="object-cover"
/>
<button
type="button"
className="absolute right-0 top-0 rounded-bl bg-background/90 p-0.5"
onClick={() => handleRemoveFile(index)}
>
<X className="h-3 w-3" />
</button>
</div>
))}
</div>
)}
<div className="text-xs text-muted-foreground">5</div>
</div>
<div className="rounded-md bg-muted/50 p-3 text-xs text-muted-foreground space-y-1">
<p>· </p>
<p>· </p>
<p>· 3 </p>
<p>· </p>
</div>
<Button className="w-full" disabled={!reason.trim()} onClick={handleSubmit}>
</Button>
</CardContent>
</Card>
</div>
)
}