New nav group with four pages against the Exc admin API: - /exc/demands — status/mode filters, detail sheet with related services, quotes and dispatch rounds - /exc/dispatches — status/round filters - /exc/orders — status/payment filters, detail sheet with items and 状态流转 audit trail - /exc/providers — status/verified filters, detail sheet with dispatch settings and status logs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
198 lines
7.1 KiB
TypeScript
198 lines
7.1 KiB
TypeScript
import { createFileRoute } from '@tanstack/react-router'
|
||
import { useQuery } from '@tanstack/react-query'
|
||
import { useState } from 'react'
|
||
import type { ColumnDef } from '@tanstack/react-table'
|
||
import type { ExcOrder } from '@/api/types'
|
||
import { fetchExcOrder, fetchExcOrders } from '@/api/modules/exc'
|
||
import { useListPage } from '@/lib/list-page'
|
||
import { DataTable } from '@/components/data-table/data-table'
|
||
import { PageHeader } from '@/components/page-header'
|
||
import { DetailSheet } from '@/features/studio/detail-sheet'
|
||
import {
|
||
ORDER_STATUS_LABELS,
|
||
OrderStatusBadge,
|
||
formatAmount,
|
||
formatTime,
|
||
} from '@/features/exc/labels'
|
||
import { Badge } from '@/components/ui/badge'
|
||
import { Button } from '@/components/ui/button'
|
||
import {
|
||
Select,
|
||
SelectContent,
|
||
SelectItem,
|
||
SelectTrigger,
|
||
SelectValue,
|
||
} from '@/components/ui/select'
|
||
|
||
export const Route = createFileRoute('/_authed/exc/orders')({
|
||
component: ExcOrdersPage,
|
||
})
|
||
|
||
function ExcOrdersPage() {
|
||
const [status, setStatus] = useState<string>('all')
|
||
const [payment, setPayment] = useState<string>('all')
|
||
const [detailId, setDetailId] = useState<number | null>(null)
|
||
|
||
const filters: Record<string, string> = {}
|
||
if (status !== 'all') filters.status = status
|
||
if (payment !== 'all') filters.payment_status = payment
|
||
|
||
const list = useListPage('exc-orders', fetchExcOrders, filters)
|
||
|
||
const detail = useQuery({
|
||
queryKey: ['exc-order', detailId],
|
||
queryFn: () => fetchExcOrder(detailId as number),
|
||
enabled: detailId !== null,
|
||
})
|
||
const order = detail.data?.data
|
||
|
||
const columns: ColumnDef<ExcOrder>[] = [
|
||
{
|
||
header: '订单',
|
||
cell: ({ row }) => (
|
||
<Button
|
||
variant="link"
|
||
className="h-auto max-w-64 justify-start p-0 text-left"
|
||
onClick={() => setDetailId(row.original.id)}
|
||
>
|
||
<div className="min-w-0">
|
||
<div className="truncate font-medium">{row.original.title ?? '—'}</div>
|
||
<code className="font-mono text-xs text-muted-foreground">#{row.original.id}</code>
|
||
</div>
|
||
</Button>
|
||
),
|
||
},
|
||
{
|
||
header: '状态',
|
||
cell: ({ row }) => <OrderStatusBadge status={row.original.status} />,
|
||
},
|
||
{
|
||
header: '支付',
|
||
cell: ({ row }) =>
|
||
row.original.payment_status === 'PAID' ? (
|
||
<Badge className="bg-emerald-500/12 text-emerald-700 dark:text-emerald-400">已支付</Badge>
|
||
) : (
|
||
<Badge variant="secondary">未支付</Badge>
|
||
),
|
||
},
|
||
{
|
||
header: '买家 / 服务商',
|
||
cell: ({ row }) => (
|
||
<div className="text-sm">
|
||
<div>{row.original.buyer?.display_name ?? '—'}</div>
|
||
<div className="text-muted-foreground">{row.original.provider?.display_name ?? '—'}</div>
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
header: '金额',
|
||
cell: ({ row }) =>
|
||
formatAmount(
|
||
row.original.actual_total_amount ?? row.original.total_amount,
|
||
row.original.currency_code,
|
||
),
|
||
},
|
||
{
|
||
header: '平台费',
|
||
cell: ({ row }) => formatAmount(row.original.platform_fee_amount, row.original.currency_code),
|
||
},
|
||
{
|
||
header: '结算',
|
||
cell: ({ row }) =>
|
||
row.original.settled_at
|
||
? `已结算 ${formatTime(row.original.settled_at)}`
|
||
: row.original.escrow_released_at
|
||
? '已放款'
|
||
: row.original.escrow_release_at
|
||
? `待放款 ${formatTime(row.original.escrow_release_at)}`
|
||
: '—',
|
||
},
|
||
{
|
||
header: '下单时间',
|
||
cell: ({ row }) => formatTime(row.original.created_at),
|
||
},
|
||
]
|
||
|
||
return (
|
||
<div>
|
||
<PageHeader
|
||
title="订单监控"
|
||
description="服务订单、支付与结算进度(只读)"
|
||
actions={
|
||
<div className="flex items-center gap-2">
|
||
<Select value={status} onValueChange={setStatus}>
|
||
<SelectTrigger size="sm" className="w-28">
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="all">全部状态</SelectItem>
|
||
{Object.entries(ORDER_STATUS_LABELS).map(([value, label]) => (
|
||
<SelectItem key={value} value={value}>
|
||
{label}
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
<Select value={payment} onValueChange={setPayment}>
|
||
<SelectTrigger size="sm" className="w-24">
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="all">全部支付</SelectItem>
|
||
<SelectItem value="UNPAID">未支付</SelectItem>
|
||
<SelectItem value="PAID">已支付</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
}
|
||
/>
|
||
<DataTable
|
||
columns={columns}
|
||
data={list.rows}
|
||
pagination={list.pagination}
|
||
loading={list.loading}
|
||
emptyText="没有订单记录"
|
||
onPageChange={list.setPage}
|
||
onPageSizeChange={list.setPageSize}
|
||
/>
|
||
<DetailSheet
|
||
open={detailId !== null}
|
||
onOpenChange={(open) => !open && setDetailId(null)}
|
||
title={order?.title ?? `订单 #${detailId ?? ''}`}
|
||
description={order ? `状态 ${ORDER_STATUS_LABELS[order.status] ?? order.status}` : undefined}
|
||
fields={[
|
||
{ label: '买家', value: order?.buyer?.display_name ?? '—' },
|
||
{ label: '服务商', value: order?.provider?.display_name ?? '—' },
|
||
{ label: '订单金额', value: formatAmount(order?.total_amount, order?.currency_code) },
|
||
{ label: '实付金额', value: formatAmount(order?.actual_total_amount, order?.currency_code) },
|
||
{ label: '平台费', value: formatAmount(order?.platform_fee_amount, order?.currency_code) },
|
||
{ label: '服务商净额', value: formatAmount(order?.provider_net_amount, order?.currency_code) },
|
||
{ label: '抽成比例', value: order?.commission_rate != null ? `${(order.commission_rate * 100).toFixed(1)}%` : '—' },
|
||
{ label: '支付单号', value: order?.pay_order_no ?? '—' },
|
||
{ label: '支付渠道', value: order?.paid_channel ?? '—' },
|
||
{ label: '支付时间', value: formatTime(order?.paid_at) },
|
||
{ label: '结算时间', value: formatTime(order?.settled_at) },
|
||
{ label: '放款时间', value: formatTime(order?.escrow_released_at) },
|
||
{ label: '买家备注', value: order?.buyer_remarks ?? '—' },
|
||
]}
|
||
jsonBlocks={[
|
||
{ label: `订单项(${order?.items?.length ?? 0})`, value: order?.items?.length ? order.items : null },
|
||
{
|
||
label: '状态流转',
|
||
value: order?.transactions?.length
|
||
? order.transactions.map((t) => ({
|
||
from: t.previous_status,
|
||
to: t.new_status,
|
||
by: t.changed_by,
|
||
by_type: t.changed_by_type,
|
||
reason: t.reason,
|
||
at: t.changed_at,
|
||
}))
|
||
: null,
|
||
},
|
||
]}
|
||
/>
|
||
</div>
|
||
)
|
||
}
|