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:
2026-07-11 14:54:58 +08:00
co-authored by Claude Fable 5
parent f5a7249969
commit ddf07bbf58
10 changed files with 2211 additions and 2 deletions
+197
View File
@@ -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>
)
}