fischerX/apps/web/src/app/(dashboard)/orders/page.tsx

160 lines
6.0 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 { useState } from 'react';
import { useRouter } from 'next/navigation';
import { useOrders, useCancelOrder } from '@/hooks/use-orders';
import { Order, OrderStatusType } from '@/lib/order-api';
const STATUS_OPTIONS: { label: string; value?: OrderStatusType }[] = [
{ label: '全部' },
{ label: '待支付', value: 'PENDING' },
{ label: '已支付', value: 'PAID' },
{ label: '已发货', value: 'SHIPPED' },
{ label: '已完成', value: 'COMPLETED' },
{ label: '已取消', value: 'CANCELLED' },
{ label: '已退款', value: 'REFUNDED' },
];
const STATUS_LABEL_MAP: Record<OrderStatusType, string> = {
PENDING: '待支付',
PAID: '已支付',
SHIPPED: '已发货',
COMPLETED: '已完成',
CANCELLED: '已取消',
REFUNDED: '已退款',
};
const STATUS_STYLE_MAP: Record<OrderStatusType, string> = {
PENDING: 'bg-yellow-100 text-yellow-800',
PAID: 'bg-blue-100 text-blue-800',
SHIPPED: 'bg-purple-100 text-purple-800',
COMPLETED: 'bg-green-100 text-green-800',
CANCELLED: 'bg-gray-100 text-gray-800',
REFUNDED: 'bg-red-100 text-red-800',
};
export default function OrdersPage() {
const router = useRouter();
const [page, setPage] = useState(1);
const [statusFilter, setStatusFilter] = useState<OrderStatusType | undefined>();
const limit = 20;
const { data: ordersData, isLoading } = useOrders(page, limit, statusFilter);
const cancelOrder = useCancelOrder();
const orders = ordersData?.data?.orders || [];
const pagination = ordersData?.data?.pagination;
const handleCancel = async (id: string) => {
if (!confirm('确定要取消该订单吗?')) return;
try {
await cancelOrder.mutateAsync(id);
} catch {
alert('取消订单失败');
}
};
return (
<div className="p-6 space-y-6">
<h1 className="text-2xl font-bold"></h1>
<div className="flex space-x-2">
{STATUS_OPTIONS.map((opt) => (
<button
key={opt.label}
onClick={() => {
setStatusFilter(opt.value);
setPage(1);
}}
className={`px-3 py-1.5 text-sm rounded-md border ${
statusFilter === opt.value
? 'bg-blue-600 text-white border-blue-600'
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
}`}
>
{opt.label}
</button>
))}
</div>
{isLoading ? (
<div className="text-center py-8 text-gray-500">...</div>
) : orders.length === 0 ? (
<div className="text-center py-8 text-gray-500"></div>
) : (
<div className="border rounded-lg overflow-hidden">
<table className="w-full">
<thead className="bg-gray-50">
<tr>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-600"></th>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-600"></th>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-600"></th>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-600"></th>
<th className="px-4 py-3 text-right text-sm font-medium text-gray-600"></th>
</tr>
</thead>
<tbody className="divide-y">
{orders.map((order: Order) => (
<tr
key={order.id}
className="hover:bg-gray-50 cursor-pointer"
onClick={() => router.push(`/orders/${order.id}`)}
>
<td className="px-4 py-3 text-sm font-medium">{order.orderNo}</td>
<td className="px-4 py-3 text-sm">¥{order.totalAmount.toFixed(2)}</td>
<td className="px-4 py-3">
<span className={`inline-flex px-2 py-1 text-xs rounded-full ${STATUS_STYLE_MAP[order.status as OrderStatusType] || 'bg-gray-100 text-gray-800'}`}>
{STATUS_LABEL_MAP[order.status as OrderStatusType] || order.status}
</span>
</td>
<td className="px-4 py-3 text-sm text-gray-500">
{new Date(order.createdAt).toLocaleString()}
</td>
<td className="px-4 py-3 text-right" onClick={(e) => e.stopPropagation()}>
{(order.status === 'PENDING' || order.status === 'PAID') && (
<button
onClick={() => handleCancel(order.id)}
disabled={cancelOrder.isPending}
className="text-sm text-red-600 hover:underline disabled:opacity-50"
>
</button>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{pagination && pagination.totalPages > 1 && (
<div className="flex justify-between items-center">
<p className="text-sm text-gray-500">
{(pagination.page - 1) * pagination.limit + 1} - {Math.min(pagination.page * pagination.limit, pagination.total)} {pagination.total}
</p>
<div className="flex space-x-2">
<button
onClick={() => setPage(page - 1)}
disabled={page === 1}
className="px-3 py-1 border rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
>
</button>
<span className="px-3 py-1">
{pagination.page} / {pagination.totalPages}
</span>
<button
onClick={() => setPage(page + 1)}
disabled={page === pagination.totalPages}
className="px-3 py-1 border rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
>
</button>
</div>
</div>
)}
</div>
);
}