{
+ return [
+ {
+ title: '订单',
+ dataIndex: 'order_no',
+ width: 220,
+ fixed: 'left',
+ render: (_, row) => (
+
+ {canView ? (
+
+ ) : (
+ {row.order_no}
+ )}
+ {row.title}
+ 商品 {formatText(row.listing_no)}
+
+ ),
+ },
+ {
+ title: '交易双方',
+ key: 'parties',
+ width: 180,
+ render: (_, row) => (
+
+ 租客 {formatText(row.renter_phone)}
+ 号主 {formatText(row.owner_phone)}
+
+ ),
+ },
+ {
+ title: '退款金额',
+ dataIndex: 'refund_amount_cent',
+ width: 140,
+ align: 'right',
+ render: (_, row) => (
+
+ {formatMoney(row.refund_amount_cent)}
+ 订单 {formatMoney(row.display_amount_cent)}
+ 押金 {formatMoney(row.deposit_amount_cent)}
+
+ ),
+ },
+ {
+ title: '订单状态',
+ key: 'status',
+ width: 170,
+ render: (_, row) => (
+
+
+
+
+ ),
+ },
+ {
+ title: '申请时间',
+ dataIndex: 'updated_at',
+ width: 170,
+ render: value => formatDateTime(value),
+ },
+ {
+ title: '操作',
+ key: 'actions',
+ width: 190,
+ fixed: 'right',
+ render: (_, row) => (
+
+ {canView ? (
+ } onClick={() => onDetail(row)}>
+ 详情
+
+ ) : null}
+ {canClose ? (
+ <>
+
+
+ >
+ ) : null}
+
+ ),
+ },
+ ]
+}
+
+export function AdminRefundReviewPage() {
+ const { message, modal } = App.useApp()
+ const queryClient = useQueryClient()
+ const permissions = useAdminSessionStore(state => state.admin?.permissions)
+ const can = (code: string) => Boolean(permissions?.includes('*') || permissions?.includes(code))
+ const canClose = can('order:close')
+ const canView = can('order:view')
+ const [detailId, setDetailId] = useState(null)
+ const [detailOpen, setDetailOpen] = useState(false)
+ const [rejectOrder, setRejectOrder] = useState(null)
+ const [rejectMode, setRejectMode] = useState('restore')
+ const [submitting, setSubmitting] = useState(false)
+
+ const query = useQuery({
+ queryKey: ['admin-refund-pending'],
+ queryFn: fetchPendingRefundOrders,
+ })
+ const items = query.data ?? []
+
+ const refreshAll = async () => {
+ await query.refetch()
+ await queryClient.invalidateQueries({ queryKey: ['admin-orders'] })
+ await queryClient.invalidateQueries({ queryKey: ['admin-dashboard'] })
+ }
+
+ const openDetail = (row: AdminOrderDetail) => {
+ setDetailId(row.id)
+ setDetailOpen(true)
+ }
+
+ const approve = (row: AdminOrderDetail) => {
+ modal.confirm({
+ title: '通过退款申请?',
+ content: `订单 ${row.order_no} 将原路退款 ${formatMoney(row.refund_amount_cent)},订单随后取消。`,
+ okText: '通过退款',
+ cancelText: '再核对一下',
+ okButtonProps: { danger: true },
+ onOk: async () => {
+ try {
+ await approveAdminRefund(row.id)
+ message.success('退款已通过并发起')
+ await refreshAll()
+ } catch (error) {
+ message.error(error instanceof Error ? error.message : '退款审核失败')
+ }
+ },
+ })
+ }
+
+ const openReject = (row: AdminOrderDetail) => {
+ setRejectOrder(row)
+ setRejectMode('restore')
+ }
+
+ const submitReject = async () => {
+ if (!rejectOrder) return
+ setSubmitting(true)
+ try {
+ await rejectAdminRefund(rejectOrder.id, rejectMode)
+ message.success(rejectMode === 'restore' ? '退款已驳回,订单恢复待交接' : '退款已驳回,订单已关闭')
+ setRejectOrder(null)
+ await refreshAll()
+ } catch (error) {
+ message.error(error instanceof Error ? error.message : '驳回退款失败')
+ } finally {
+ setSubmitting(false)
+ }
+ }
+
+ const columns = buildColumns(openDetail, approve, openReject, canClose, canView)
+ const errorText = query.error instanceof Error ? query.error.message : '请稍后重试或重新登录后台。'
+
+ return (
+
+
+ 待审核 {items.length} 单
+ {query.isFetching ? 正在更新 : null}
+
+ }
+ extra={
+ } onClick={() => void query.refetch()} loading={query.isFetching}>
+ 刷新队列
+
+ }
+ />
+
+ {!canClose ? (
+
+ ) : null}
+
+ {query.isError ? (
+ void query.refetch()}>
+ 重试
+
+ }
+ />
+ ) : null}
+
+
+ title={待审核退款}
+ extra={
+
+ {query.isPending ? '加载中' : `共 ${items.length} 单`}
+
+ }
+ rowKey="id"
+ columns={columns}
+ dataSource={items}
+ loading={query.isFetching}
+ emptyDescription="当前没有待审核退款"
+ onRowClick={canView ? row => openDetail(row) : undefined}
+ />
+
+ {canView ? (
+ setDetailOpen(false)}
+ />
+ ) : null}
+
+ void submitReject()}
+ onCancel={() => {
+ if (!submitting) setRejectOrder(null)
+ }}
+ >
+
+ 订单 {rejectOrder?.order_no},退款金额 {formatMoney(rejectOrder?.refund_amount_cent)}。
+ 请选择驳回后的订单处理方式。
+
+ setRejectMode(event.target.value as RejectMode)}
+ style={{ display: 'flex', flexDirection: 'column', gap: 12 }}
+ >
+
+ 恢复订单至待交接
+
+ 仅驳回退款,订单继续流转,商品保持占用。
+
+
+
+ 驳回退款并关闭订单
+
+ 关闭订单并归档商品,后续不可再操作。
+
+
+
+
+
+ )
+}
diff --git a/frontend/src/features/admin/api.ts b/frontend/src/features/admin/api.ts
index 07efe08..4454daa 100644
--- a/frontend/src/features/admin/api.ts
+++ b/frontend/src/features/admin/api.ts
@@ -217,3 +217,18 @@ export async function adjustAdminUserWallet(
)
return data.data
}
+
+export async function fetchPendingRefundOrders() {
+ const { data } = await adminClient.get>(
+ '/admin/orders/refund-pending'
+ )
+ return data.data.items ?? []
+}
+
+export async function approveAdminRefund(id: number | string) {
+ await adminClient.post(`/admin/orders/${id}/refund/approve`)
+}
+
+export async function rejectAdminRefund(id: number | string, action: 'restore' | 'close') {
+ await adminClient.post(`/admin/orders/${id}/refund/reject`, { action })
+}