feat(exc): 专长交易 read-only monitoring pages
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 <[email protected]>
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user