2026-08-31 14:49:28 +08:00
|
|
|
|
import { CheckCircle, Clock, Gift, Tag, Ticket, XCircle } from "lucide-react"
|
2026-09-08 16:19:40 +08:00
|
|
|
|
import { useMemo } from "react"
|
|
|
|
|
|
import { fetchMyCoupons } from "@/api/coupon"
|
2026-07-30 13:55:28 +08:00
|
|
|
|
import Page from "@/components/page"
|
2026-08-31 14:49:28 +08:00
|
|
|
|
import { Badge } from "@/components/ui/badge"
|
|
|
|
|
|
import { Card, CardContent } from "@/components/ui/card"
|
|
|
|
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
2026-09-08 16:19:40 +08:00
|
|
|
|
import { useList } from "@/lib/hooks/useList"
|
|
|
|
|
|
import type { MyCouponItem } from "@/lib/models/coupon"
|
|
|
|
|
|
import { formatDate } from "@/lib/utils"
|
2026-08-31 14:49:28 +08:00
|
|
|
|
|
2026-09-08 16:19:40 +08:00
|
|
|
|
const COUPON_TYPE_DISCOUNT = 1
|
2026-08-31 14:49:28 +08:00
|
|
|
|
|
2026-09-08 16:19:40 +08:00
|
|
|
|
function CouponCard({ item }: { item: MyCouponItem }) {
|
|
|
|
|
|
const isDiscount = item.Coupon.CouponType === COUPON_TYPE_DISCOUNT
|
|
|
|
|
|
const isUsed = item.IsUsed
|
2026-07-30 13:55:28 +08:00
|
|
|
|
|
2026-09-08 16:19:40 +08:00
|
|
|
|
return (
|
|
|
|
|
|
<Card
|
|
|
|
|
|
className={`relative overflow-hidden transition-shadow hover:shadow-md ${isUsed ? "opacity-60" : ""}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<CardContent className="p-4">
|
|
|
|
|
|
<div className="flex items-start gap-4">
|
|
|
|
|
|
<div className="flex min-w-20 flex-col items-center justify-center rounded-lg bg-primary/10 p-3">
|
|
|
|
|
|
{isDiscount ? (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<span className="text-2xl font-bold text-primary">
|
|
|
|
|
|
{item.Coupon.CouponValue}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="text-xs text-muted-foreground">折</span>
|
|
|
|
|
|
</>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<span className="text-xs text-muted-foreground">¥</span>
|
|
|
|
|
|
<span className="text-2xl font-bold text-primary">
|
|
|
|
|
|
{item.Coupon.CouponValue}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2026-08-31 14:49:28 +08:00
|
|
|
|
|
2026-09-08 16:19:40 +08:00
|
|
|
|
<div className="flex-1 space-y-1">
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<h4 className="font-medium">{item.Coupon.Name}</h4>
|
|
|
|
|
|
{isUsed ? (
|
|
|
|
|
|
<Badge variant="secondary" className="text-xs">
|
|
|
|
|
|
<CheckCircle className="mr-1 h-3 w-3" />
|
|
|
|
|
|
已使用
|
|
|
|
|
|
</Badge>
|
|
|
|
|
|
) : item.IsExpired ? (
|
|
|
|
|
|
<Badge variant="secondary" className="text-xs">
|
|
|
|
|
|
<XCircle className="mr-1 h-3 w-3" />
|
|
|
|
|
|
已过期
|
|
|
|
|
|
</Badge>
|
2026-08-31 14:49:28 +08:00
|
|
|
|
) : (
|
2026-09-08 16:19:40 +08:00
|
|
|
|
<Badge variant="default" className="text-xs">
|
|
|
|
|
|
<Ticket className="mr-1 h-3 w-3" />
|
|
|
|
|
|
可使用
|
|
|
|
|
|
</Badge>
|
2026-08-31 14:49:28 +08:00
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-09-08 16:19:40 +08:00
|
|
|
|
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-muted-foreground">
|
|
|
|
|
|
<span className="flex items-center gap-1">
|
|
|
|
|
|
<Tag className="h-3 w-3" />
|
|
|
|
|
|
{item.Coupon.AllowMinAmount > 0
|
|
|
|
|
|
? `满${item.Coupon.AllowMinAmount}元可用`
|
|
|
|
|
|
: "无限制"}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="flex items-center gap-1">
|
|
|
|
|
|
<Clock className="h-3 w-3" />
|
|
|
|
|
|
{formatDate(item.Orgin.StartTime)} -{" "}
|
|
|
|
|
|
{formatDate(item.Orgin.EndTime)}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="flex items-center gap-1">
|
|
|
|
|
|
<Gift className="h-3 w-3" />
|
|
|
|
|
|
{item.Orgin.Remark}
|
|
|
|
|
|
</span>
|
2026-08-31 14:49:28 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-09-08 16:19:40 +08:00
|
|
|
|
</div>
|
2026-08-31 14:49:28 +08:00
|
|
|
|
|
2026-09-08 16:19:40 +08:00
|
|
|
|
{isUsed && (
|
|
|
|
|
|
<div className="absolute right-4 top-4">
|
|
|
|
|
|
<XCircle className="h-6 w-6 text-muted-foreground/50" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</CardContent>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default function CouponPage() {
|
|
|
|
|
|
const { data: coupons, status } = useList(fetchMyCoupons)
|
|
|
|
|
|
|
|
|
|
|
|
const availableCoupons = useMemo(
|
|
|
|
|
|
() => coupons.filter(item => !item.IsUsed && !item.IsExpired),
|
|
|
|
|
|
[coupons],
|
|
|
|
|
|
)
|
|
|
|
|
|
const usedCoupons = useMemo(
|
|
|
|
|
|
() => coupons.filter(item => item.IsUsed),
|
|
|
|
|
|
[coupons],
|
|
|
|
|
|
)
|
2026-08-31 14:49:28 +08:00
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Page>
|
|
|
|
|
|
<div className="bg-white rounded-lg p-4">
|
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<h3 className="text-lg font-semibold">我的优惠券</h3>
|
|
|
|
|
|
<Badge variant="secondary" className="text-sm">
|
2026-09-08 16:19:40 +08:00
|
|
|
|
共 {coupons.length} 张
|
2026-08-31 14:49:28 +08:00
|
|
|
|
</Badge>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<Tabs defaultValue="available" className="w-full">
|
|
|
|
|
|
<TabsList className="grid w-full max-w-md grid-cols-2">
|
|
|
|
|
|
<TabsTrigger value="available">
|
|
|
|
|
|
可使用 ({availableCoupons.length})
|
|
|
|
|
|
</TabsTrigger>
|
|
|
|
|
|
<TabsTrigger value="used">
|
|
|
|
|
|
已使用 ({usedCoupons.length})
|
|
|
|
|
|
</TabsTrigger>
|
|
|
|
|
|
</TabsList>
|
|
|
|
|
|
|
|
|
|
|
|
<TabsContent value="available" className="mt-4">
|
2026-09-08 16:19:40 +08:00
|
|
|
|
{status === "load" ? (
|
|
|
|
|
|
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
|
|
|
|
|
<p>加载中...</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : status === "fail" ? (
|
|
|
|
|
|
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
|
|
|
|
|
<XCircle className="mb-4 h-12 w-12 opacity-50" />
|
|
|
|
|
|
<p>加载失败,请刷新重试</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : availableCoupons.length === 0 ? (
|
2026-08-31 14:49:28 +08:00
|
|
|
|
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
|
|
|
|
|
<Ticket className="mb-4 h-12 w-12 opacity-50" />
|
|
|
|
|
|
<p>暂无可用优惠券</p>
|
|
|
|
|
|
<p className="text-sm">快去参与活动领取吧</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
2026-09-08 16:19:40 +08:00
|
|
|
|
{availableCoupons.map(item => (
|
|
|
|
|
|
<CouponCard key={item.Orgin.Id} item={item} />
|
|
|
|
|
|
))}
|
2026-08-31 14:49:28 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</TabsContent>
|
|
|
|
|
|
|
|
|
|
|
|
<TabsContent value="used" className="mt-4">
|
|
|
|
|
|
{usedCoupons.length === 0 ? (
|
|
|
|
|
|
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
|
|
|
|
|
<CheckCircle className="mb-4 h-12 w-12 opacity-50" />
|
|
|
|
|
|
<p>暂无已使用的优惠券</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
2026-09-08 16:19:40 +08:00
|
|
|
|
{usedCoupons.map(item => (
|
|
|
|
|
|
<CouponCard key={item.Orgin.Id} item={item} />
|
|
|
|
|
|
))}
|
2026-08-31 14:49:28 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</TabsContent>
|
|
|
|
|
|
</Tabs>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Page>
|
|
|
|
|
|
)
|
2026-07-30 13:55:28 +08:00
|
|
|
|
}
|