feat(admin): 新增后台财务看板与财务明细
This commit is contained in:
@@ -93,6 +93,11 @@ const AdminDisputesPage = lazy(() =>
|
||||
default: module.AdminDisputesPage,
|
||||
}))
|
||||
)
|
||||
const AdminFinancePage = lazy(() =>
|
||||
import('@/features/admin/AdminFinancePage').then(module => ({
|
||||
default: module.AdminFinancePage,
|
||||
}))
|
||||
)
|
||||
const AdminLayout = lazy(() =>
|
||||
import('@/layouts/AdminLayout').then(module => ({ default: module.AdminLayout }))
|
||||
)
|
||||
@@ -101,7 +106,6 @@ const AdminSectionPlaceholder = lazy(() =>
|
||||
)
|
||||
|
||||
const adminSections: Array<{ path: string; title: string; description: string }> = [
|
||||
{ path: 'finance', title: '财务看板', description: '查看收入、退款、结算与资金趋势。' },
|
||||
{ path: 'distribution/sites', title: '分销站点', description: '管理分站、规则、结算与站点钱包。' },
|
||||
{ path: 'announcements', title: '公告管理', description: '维护主站与分站公告。' },
|
||||
{ path: 'system-configs', title: '系统配置', description: '维护首页、发布、订单与支付配置。' },
|
||||
@@ -144,6 +148,7 @@ export function AppRouter() {
|
||||
<Route path="users" element={<AdminUsersPage />} />
|
||||
<Route path="withdrawals" element={<AdminWithdrawalsPage />} />
|
||||
<Route path="disputes" element={<AdminDisputesPage />} />
|
||||
<Route path="finance" element={<AdminFinancePage />} />
|
||||
{adminSections.map(section => (
|
||||
<Route
|
||||
key={section.path}
|
||||
|
||||
@@ -0,0 +1,534 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { keepPreviousData, useQuery } from '@tanstack/react-query'
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
DatePicker,
|
||||
Input,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Statistic,
|
||||
Tag,
|
||||
Typography,
|
||||
type TableColumnsType,
|
||||
} from 'antd'
|
||||
import {
|
||||
AccountBookOutlined,
|
||||
BankOutlined,
|
||||
ReloadOutlined,
|
||||
RiseOutlined,
|
||||
WalletOutlined,
|
||||
WarningOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import dayjs, { type Dayjs } from 'dayjs'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import { fetchFinanceDashboard, fetchFinanceDetails } from '@/features/admin/api'
|
||||
import { AdminPageHeader } from '@/features/admin/components/AdminPageHeader'
|
||||
import { DataTable } from '@/features/admin/components/DataTable'
|
||||
import { FilterBar, FilterField } from '@/features/admin/components/FilterBar'
|
||||
import { StatusTag } from '@/features/admin/components/StatusTag'
|
||||
import { financeStatusMap, orderStatusMap, readStatusMeta } from '@/features/admin/labels'
|
||||
import type { FinanceDaily, FinanceDetail } from '@/features/admin/types'
|
||||
import { formatDateTime, formatMoney, formatText } from '@/shared/utils/format'
|
||||
|
||||
type DateType = 'settled' | 'created' | 'offline_settled'
|
||||
|
||||
const dateTypeOptions = [
|
||||
{ value: 'settled', label: '按结算时间' },
|
||||
{ value: 'created', label: '按下单时间' },
|
||||
{ value: 'offline_settled', label: '按线下结算时间' },
|
||||
]
|
||||
|
||||
const dailyColumns: TableColumnsType<FinanceDaily> = [
|
||||
{ title: '日期', dataIndex: 'date', width: 120, fixed: 'left' },
|
||||
{
|
||||
title: '流水',
|
||||
dataIndex: 'total_flow_amount_cent',
|
||||
width: 130,
|
||||
align: 'right',
|
||||
render: value => formatMoney(value),
|
||||
},
|
||||
{
|
||||
title: '退款',
|
||||
dataIndex: 'total_refund_amount_cent',
|
||||
width: 120,
|
||||
align: 'right',
|
||||
render: value => formatMoney(value),
|
||||
},
|
||||
{
|
||||
title: '渠道净额',
|
||||
dataIndex: 'channel_net_amount_cent',
|
||||
width: 130,
|
||||
align: 'right',
|
||||
render: value => formatMoney(value),
|
||||
},
|
||||
{
|
||||
title: '平台收入',
|
||||
dataIndex: 'platform_income_amount_cent',
|
||||
width: 130,
|
||||
align: 'right',
|
||||
render: value => <span className="admin-money">{formatMoney(value)}</span>,
|
||||
},
|
||||
{
|
||||
title: '号主应得',
|
||||
dataIndex: 'owner_should_income_amount_cent',
|
||||
width: 130,
|
||||
align: 'right',
|
||||
render: value => formatMoney(value),
|
||||
},
|
||||
{
|
||||
title: '已结算订单',
|
||||
dataIndex: 'settled_order_count',
|
||||
width: 110,
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
title: '支付订单',
|
||||
dataIndex: 'paid_order_count',
|
||||
width: 100,
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
title: '运营支出',
|
||||
dataIndex: 'operating_expense_amount_cent',
|
||||
width: 130,
|
||||
align: 'right',
|
||||
render: value => formatMoney(value),
|
||||
},
|
||||
{
|
||||
title: '已打款',
|
||||
dataIndex: 'disbursement_paid_amount_cent',
|
||||
width: 130,
|
||||
align: 'right',
|
||||
render: value => formatMoney(value),
|
||||
},
|
||||
]
|
||||
|
||||
function buildDetailColumns(onOpenOrder: (orderNo: string) => void): TableColumnsType<FinanceDetail> {
|
||||
return [
|
||||
{
|
||||
title: '订单',
|
||||
dataIndex: 'order_no',
|
||||
width: 200,
|
||||
fixed: 'left',
|
||||
render: (_, row) => (
|
||||
<div className="admin-stack">
|
||||
<button type="button" className="admin-order-link" onClick={() => onOpenOrder(row.order_no)}>
|
||||
{row.order_no}
|
||||
</button>
|
||||
<StatusTag meta={readStatusMeta(orderStatusMap, row.order_status)} />
|
||||
<span>结算 {formatText(row.settlement_status)}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '用户',
|
||||
key: 'users',
|
||||
width: 170,
|
||||
render: (_, row) => (
|
||||
<div className="admin-stack">
|
||||
<span>租客 {formatText(row.renter_phone)}</span>
|
||||
<span>号主 {formatText(row.owner_phone)}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '订单金额',
|
||||
key: 'order_amount',
|
||||
width: 140,
|
||||
align: 'right',
|
||||
render: (_, row) => (
|
||||
<div className="admin-amount">
|
||||
<strong>{formatMoney(row.order_rent_amount_cent)}</strong>
|
||||
<span>押金 {formatMoney(row.order_deposit_amount_cent)}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '结账',
|
||||
key: 'checkout',
|
||||
width: 160,
|
||||
align: 'right',
|
||||
render: (_, row) => (
|
||||
<div className="admin-amount">
|
||||
<span>租金 {formatMoney(row.checkout_rent_amount_cent)}</span>
|
||||
<span>号主 {formatMoney(row.checkout_owner_income_cent)}</span>
|
||||
<span>平台费 {formatMoney(row.checkout_platform_fee_cent)}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '支付 / 退款',
|
||||
key: 'payment',
|
||||
width: 160,
|
||||
align: 'right',
|
||||
render: (_, row) => (
|
||||
<div className="admin-amount">
|
||||
<strong>{formatMoney(row.paid_amount_cent)}</strong>
|
||||
<span>退款 {formatMoney(row.refunded_amount_cent)}</span>
|
||||
<span>渠道净额 {formatMoney(row.channel_net_amount_cent)}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '财务状态',
|
||||
dataIndex: 'finance_status',
|
||||
width: 130,
|
||||
render: value => <StatusTag meta={readStatusMeta(financeStatusMap, value)} />,
|
||||
},
|
||||
{
|
||||
title: '结算时间',
|
||||
dataIndex: 'settled_at',
|
||||
width: 170,
|
||||
render: value => formatDateTime(value),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 100,
|
||||
fixed: 'right',
|
||||
render: (_, row) => (
|
||||
<Button type="link" size="small" onClick={() => onOpenOrder(row.order_no)}>
|
||||
查看订单
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export function AdminFinancePage() {
|
||||
const navigate = useNavigate()
|
||||
const [range, setRange] = useState<[Dayjs, Dayjs]>([
|
||||
dayjs().subtract(6, 'day'),
|
||||
dayjs(),
|
||||
])
|
||||
const [keyword, setKeyword] = useState('')
|
||||
const [appliedKeyword, setAppliedKeyword] = useState('')
|
||||
const [dateType, setDateType] = useState<DateType>('settled')
|
||||
const [page, setPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
|
||||
const startDate = range[0].format('YYYY-MM-DD')
|
||||
const endDate = range[1].format('YYYY-MM-DD')
|
||||
|
||||
const dashboard = useQuery({
|
||||
queryKey: ['finance-dashboard', startDate, endDate],
|
||||
queryFn: () => fetchFinanceDashboard({ start_date: startDate, end_date: endDate }),
|
||||
staleTime: 60_000,
|
||||
})
|
||||
|
||||
const details = useQuery({
|
||||
queryKey: ['finance-details', startDate, endDate, appliedKeyword, dateType, page, pageSize],
|
||||
queryFn: () =>
|
||||
fetchFinanceDetails({
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
order_no: appliedKeyword,
|
||||
date_type: dateType,
|
||||
page,
|
||||
page_size: pageSize,
|
||||
}),
|
||||
placeholderData: keepPreviousData,
|
||||
})
|
||||
|
||||
const detailColumns = useMemo(
|
||||
() => buildDetailColumns(orderNo => navigate(`/orders?keyword=${encodeURIComponent(orderNo)}`)),
|
||||
[navigate]
|
||||
)
|
||||
|
||||
const summary = dashboard.data?.summary
|
||||
const disbursement = dashboard.data?.disbursement_summary
|
||||
const pickup = dashboard.data?.pickup_summary
|
||||
const mohong = dashboard.data?.mohong_summary
|
||||
const expense = dashboard.data?.operating_expense_summary
|
||||
const daily = dashboard.data?.daily_items ?? []
|
||||
|
||||
const applyDetailFilters = () => {
|
||||
setAppliedKeyword(keyword.trim())
|
||||
setPage(1)
|
||||
}
|
||||
|
||||
const resetDetailFilters = () => {
|
||||
setKeyword('')
|
||||
setAppliedKeyword('')
|
||||
setPage(1)
|
||||
}
|
||||
|
||||
const errorText =
|
||||
dashboard.error instanceof Error ? dashboard.error.message : '请稍后重试或重新登录后台。'
|
||||
|
||||
return (
|
||||
<div className="admin-page">
|
||||
<AdminPageHeader
|
||||
eyebrow="JIEYOU OPS · 财务"
|
||||
title="财务看板"
|
||||
description="按时间区间核对流水、退款、平台收入、号主结算与出款压力。"
|
||||
meta={
|
||||
<Space size={8} wrap>
|
||||
<Tag color="geekblue">
|
||||
{startDate} ~ {endDate}
|
||||
</Tag>
|
||||
{dashboard.isFetching ? <Tag color="processing">正在更新</Tag> : null}
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
<Space wrap>
|
||||
<DatePicker.RangePicker
|
||||
value={range}
|
||||
allowClear={false}
|
||||
onChange={value => {
|
||||
if (value?.[0] && value[1]) {
|
||||
setRange([value[0], value[1]])
|
||||
setPage(1)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={() => {
|
||||
void dashboard.refetch()
|
||||
void details.refetch()
|
||||
}}
|
||||
loading={dashboard.isFetching}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
|
||||
{dashboard.isError ? (
|
||||
<Alert
|
||||
type="error"
|
||||
showIcon
|
||||
className="admin-table-alert"
|
||||
message="财务数据没有加载出来"
|
||||
description={errorText}
|
||||
action={
|
||||
<Button size="small" onClick={() => void dashboard.refetch()}>
|
||||
重试
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col xs={24} sm={12} xl={6}>
|
||||
<Card className="admin-kpi-card">
|
||||
<div className="admin-kpi-card__icon">
|
||||
<AccountBookOutlined />
|
||||
</div>
|
||||
<Statistic title="总流水" value={formatMoney(summary?.total_flow_amount_cent ?? 0)} />
|
||||
<span className="admin-kpi-card__foot">
|
||||
支付 {summary?.successful_pay_count ?? 0} 笔 · 退款{' '}
|
||||
{summary?.successful_refund_count ?? 0} 笔
|
||||
</span>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} xl={6}>
|
||||
<Card className="admin-kpi-card">
|
||||
<div className="admin-kpi-card__icon">
|
||||
<WalletOutlined />
|
||||
</div>
|
||||
<Statistic title="总退款" value={formatMoney(summary?.total_refund_amount_cent ?? 0)} />
|
||||
<span className="admin-kpi-card__foot">
|
||||
待退款 {formatMoney(summary?.pending_refund_amount_cent ?? 0)} ·{' '}
|
||||
{summary?.pending_refund_count ?? 0} 笔
|
||||
</span>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} xl={6}>
|
||||
<Card className="admin-kpi-card admin-kpi-card--accent">
|
||||
<div className="admin-kpi-card__icon">
|
||||
<RiseOutlined />
|
||||
</div>
|
||||
<Statistic
|
||||
title="平台收入"
|
||||
value={formatMoney(summary?.platform_income_amount_cent ?? 0)}
|
||||
/>
|
||||
<span className="admin-kpi-card__foot">
|
||||
订单 {formatMoney(summary?.normal_order_profit_amount_cent ?? 0)} · 提号{' '}
|
||||
{formatMoney(summary?.pickup_profit_amount_cent ?? 0)}
|
||||
</span>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} xl={6}>
|
||||
<Card className="admin-kpi-card">
|
||||
<div className="admin-kpi-card__icon">
|
||||
<BankOutlined />
|
||||
</div>
|
||||
<Statistic
|
||||
title="号主应得"
|
||||
value={formatMoney(summary?.owner_should_income_amount_cent ?? 0)}
|
||||
/>
|
||||
<span className="admin-kpi-card__foot">
|
||||
钱包入账 {formatMoney(summary?.owner_wallet_income_amount_cent ?? 0)}
|
||||
</span>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} xl={6}>
|
||||
<Card className="admin-kpi-card">
|
||||
<div className="admin-kpi-card__icon">
|
||||
<WalletOutlined />
|
||||
</div>
|
||||
<Statistic
|
||||
title="渠道净额"
|
||||
value={formatMoney(summary?.channel_net_amount_cent ?? 0)}
|
||||
/>
|
||||
<span className="admin-kpi-card__foot">
|
||||
已结算订单 {summary?.settled_order_count ?? 0} 笔
|
||||
</span>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} xl={6}>
|
||||
<Card className="admin-kpi-card">
|
||||
<div className="admin-kpi-card__icon">
|
||||
<WarningOutlined />
|
||||
</div>
|
||||
<Statistic title="资金异常" value={summary?.financial_exception_count ?? 0} />
|
||||
<span className="admin-kpi-card__foot">
|
||||
结算差异 {formatMoney(summary?.settlement_diff_amount_cent ?? 0)}
|
||||
</span>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} xl={6}>
|
||||
<Card className="admin-kpi-card">
|
||||
<div className="admin-kpi-card__icon">
|
||||
<BankOutlined />
|
||||
</div>
|
||||
<Statistic
|
||||
title="待打款"
|
||||
value={formatMoney(disbursement?.pending_payment_amount_cent ?? 0)}
|
||||
/>
|
||||
<span className="admin-kpi-card__foot">
|
||||
{disbursement?.pending_payment_count ?? 0} 笔 · 已打款{' '}
|
||||
{formatMoney(disbursement?.paid_amount_cent ?? 0)}
|
||||
</span>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} xl={6}>
|
||||
<Card className="admin-kpi-card">
|
||||
<div className="admin-kpi-card__icon">
|
||||
<WarningOutlined />
|
||||
</div>
|
||||
<Statistic
|
||||
title="提现待审"
|
||||
value={formatMoney(disbursement?.withdrawal_review_amount_cent ?? 0)}
|
||||
/>
|
||||
<span className="admin-kpi-card__foot">
|
||||
{disbursement?.withdrawal_review_count ?? 0} 笔 · 待打款{' '}
|
||||
{formatMoney(disbursement?.withdrawal_pending_amount_cent ?? 0)}
|
||||
</span>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={[16, 16]} className="admin-finance-mini">
|
||||
<Col xs={24} sm={12} xl={6}>
|
||||
<Card className="admin-pending-card">
|
||||
<div>
|
||||
<span className="admin-pending-card__label">待结算订单</span>
|
||||
<strong>{summary?.pending_settle_order_count ?? 0}</strong>
|
||||
</div>
|
||||
<span>待线下结算 {formatMoney(summary?.offline_settlement_pending_amount_cent ?? 0)}</span>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} xl={6}>
|
||||
<Card className="admin-pending-card">
|
||||
<div>
|
||||
<span className="admin-pending-card__label">线下提号</span>
|
||||
<strong>{pickup?.completed_count ?? 0}</strong>
|
||||
</div>
|
||||
<span>
|
||||
利润 {formatMoney(pickup?.profit_amount_cent ?? 0)} · 待结{' '}
|
||||
{formatMoney(pickup?.pending_amount_cent ?? 0)}
|
||||
</span>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} xl={6}>
|
||||
<Card className="admin-pending-card">
|
||||
<div>
|
||||
<span className="admin-pending-card__label">撞车商城净额</span>
|
||||
<strong>{formatMoney(mohong?.net_amount_cent ?? 0)}</strong>
|
||||
</div>
|
||||
<span>
|
||||
流水 {formatMoney(mohong?.flow_amount_cent ?? 0)} · 退款{' '}
|
||||
{formatMoney(mohong?.refund_amount_cent ?? 0)}
|
||||
</span>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} xl={6}>
|
||||
<Card className="admin-pending-card">
|
||||
<div>
|
||||
<span className="admin-pending-card__label">运营支出</span>
|
||||
<strong>{formatMoney(expense?.amount_cent ?? 0)}</strong>
|
||||
</div>
|
||||
<span>{expense?.count ?? 0} 笔已登记支出</span>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<DataTable<FinanceDaily>
|
||||
className="admin-finance-daily"
|
||||
title={<span>每日趋势</span>}
|
||||
extra={<Typography.Text type="secondary">数据更新于 {formatDateTime(dashboard.data?.generated_at)}</Typography.Text>}
|
||||
rowKey="date"
|
||||
columns={dailyColumns}
|
||||
dataSource={daily}
|
||||
loading={dashboard.isFetching}
|
||||
emptyDescription="所选区间没有财务数据"
|
||||
/>
|
||||
|
||||
<FilterBar
|
||||
onSearch={applyDetailFilters}
|
||||
onReset={appliedKeyword ? resetDetailFilters : undefined}
|
||||
loading={details.isFetching}
|
||||
extra={
|
||||
<Select
|
||||
value={dateType}
|
||||
style={{ width: 160 }}
|
||||
options={dateTypeOptions}
|
||||
onChange={value => {
|
||||
setDateType(value)
|
||||
setPage(1)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<FilterField label="订单号" width={280}>
|
||||
<Input
|
||||
allowClear
|
||||
value={keyword}
|
||||
placeholder="按订单号精确查询"
|
||||
onChange={event => setKeyword(event.target.value)}
|
||||
/>
|
||||
</FilterField>
|
||||
</FilterBar>
|
||||
|
||||
<DataTable<FinanceDetail>
|
||||
title={<span>财务明细</span>}
|
||||
extra={
|
||||
<Typography.Text type="secondary">
|
||||
{details.data ? `共 ${details.data.total} 条` : '加载中'}
|
||||
</Typography.Text>
|
||||
}
|
||||
rowKey="order_id"
|
||||
columns={detailColumns}
|
||||
dataSource={details.data?.items ?? []}
|
||||
loading={details.isFetching}
|
||||
total={details.data?.total ?? 0}
|
||||
page={details.data?.page ?? page}
|
||||
pageSize={details.data?.page_size ?? pageSize}
|
||||
onPageChange={(nextPage, nextPageSize) => {
|
||||
setPage(nextPageSize !== pageSize ? 1 : nextPage)
|
||||
setPageSize(nextPageSize)
|
||||
}}
|
||||
emptyDescription="所选区间没有财务明细"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -20,6 +20,8 @@ import type {
|
||||
AdminWithdrawalPage,
|
||||
AdminWithdrawalQuery,
|
||||
DashboardData,
|
||||
FinanceDashboard,
|
||||
FinanceDetailPage,
|
||||
} from '@/features/admin/types'
|
||||
|
||||
export async function fetchAdminCaptcha() {
|
||||
@@ -311,3 +313,35 @@ export async function arbitrateAdminDispute(
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export interface FinanceRangeQuery {
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
}
|
||||
|
||||
export async function fetchFinanceDashboard(range: FinanceRangeQuery) {
|
||||
const { data } = await adminClient.get<ApiResponse<FinanceDashboard>>('/admin/finance/dashboard', {
|
||||
params: compactParams(range),
|
||||
})
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchFinanceDetails(
|
||||
query: FinanceRangeQuery & {
|
||||
order_no?: string
|
||||
date_type?: 'created' | 'settled' | 'offline_settled'
|
||||
page?: number
|
||||
page_size?: number
|
||||
}
|
||||
) {
|
||||
const { data } = await adminClient.get<ApiResponse<Partial<FinanceDetailPage>>>(
|
||||
'/admin/finance/details',
|
||||
{ params: compactParams(query) }
|
||||
)
|
||||
return {
|
||||
items: data.data.items ?? [],
|
||||
total: data.data.total ?? 0,
|
||||
page: data.data.page ?? query.page ?? 1,
|
||||
page_size: data.data.page_size ?? query.page_size ?? 20,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,3 +189,11 @@ export const withdrawalStatusMap: Record<string, StatusMeta> = {
|
||||
}
|
||||
|
||||
export const withdrawalStatusOptions = toOptions(withdrawalStatusMap)
|
||||
|
||||
export const financeStatusMap: Record<string, StatusMeta> = {
|
||||
normal: { label: '正常', tone: 'success' },
|
||||
refund_pending: { label: '退款中', tone: 'blue' },
|
||||
refund_failed: { label: '退款失败', tone: 'red' },
|
||||
settlement_diff: { label: '结算差异', tone: 'orange' },
|
||||
offline_settlement_pending: { label: '待线下结算', tone: 'gold' },
|
||||
}
|
||||
|
||||
@@ -404,3 +404,132 @@ export interface AdminDisputePage {
|
||||
page: number
|
||||
page_size: number
|
||||
}
|
||||
|
||||
export interface FinanceSummary {
|
||||
total_flow_amount_cent: number
|
||||
total_refund_amount_cent: number
|
||||
pending_refund_amount_cent: number
|
||||
channel_net_amount_cent: number
|
||||
platform_income_amount_cent: number
|
||||
normal_order_profit_amount_cent: number
|
||||
pickup_profit_amount_cent: number
|
||||
owner_should_income_amount_cent: number
|
||||
owner_wallet_income_amount_cent: number
|
||||
owner_effective_settlement_amount_cent: number
|
||||
offline_settlement_amount_cent: number
|
||||
offline_settlement_pending_amount_cent: number
|
||||
settlement_diff_amount_cent: number
|
||||
estimated_income_amount_cent: number
|
||||
successful_pay_count: number
|
||||
successful_refund_count: number
|
||||
pending_refund_count: number
|
||||
settled_order_count: number
|
||||
pending_settle_order_count: number
|
||||
offline_settlement_pending_count: number
|
||||
financial_exception_count: number
|
||||
}
|
||||
|
||||
export interface FinanceDaily {
|
||||
date: string
|
||||
total_flow_amount_cent: number
|
||||
total_refund_amount_cent: number
|
||||
platform_income_amount_cent: number
|
||||
channel_net_amount_cent: number
|
||||
owner_should_income_amount_cent: number
|
||||
settled_order_count: number
|
||||
paid_order_count: number
|
||||
operating_expense_amount_cent: number
|
||||
disbursement_paid_amount_cent: number
|
||||
}
|
||||
|
||||
export interface FinancePickupSummary {
|
||||
settled_amount_cent: number
|
||||
profit_amount_cent: number
|
||||
completed_count: number
|
||||
in_progress_count: number
|
||||
pending_amount_cent: number
|
||||
pending_count: number
|
||||
}
|
||||
|
||||
export interface FinanceMohongSummary {
|
||||
flow_amount_cent: number
|
||||
refund_amount_cent: number
|
||||
net_amount_cent: number
|
||||
paid_order_count: number
|
||||
}
|
||||
|
||||
export interface FinanceDisbursementSummary {
|
||||
paid_amount_cent: number
|
||||
paid_count: number
|
||||
pending_payment_amount_cent: number
|
||||
pending_payment_count: number
|
||||
withdrawal_amount_cent: number
|
||||
withdrawal_fee_amount_cent: number
|
||||
withdrawal_paid_amount_cent: number
|
||||
withdrawal_paid_count: number
|
||||
withdrawal_pending_amount_cent: number
|
||||
withdrawal_pending_count: number
|
||||
withdrawal_review_amount_cent: number
|
||||
withdrawal_review_count: number
|
||||
manual_paid_amount_cent: number
|
||||
manual_paid_count: number
|
||||
}
|
||||
|
||||
export interface FinanceOperatingExpenseSummary {
|
||||
amount_cent: number
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface FinanceDashboard {
|
||||
summary: FinanceSummary
|
||||
daily_items: FinanceDaily[]
|
||||
pickup_summary: FinancePickupSummary
|
||||
mohong_summary: FinanceMohongSummary
|
||||
disbursement_summary: FinanceDisbursementSummary
|
||||
operating_expense_summary: FinanceOperatingExpenseSummary
|
||||
generated_at: string
|
||||
}
|
||||
|
||||
export interface FinanceDetail {
|
||||
order_id: number
|
||||
order_no: string
|
||||
order_status: string
|
||||
settlement_status: string
|
||||
refund_status: string
|
||||
handoff_mode: string
|
||||
settlement_mode: string
|
||||
offline_settlement_status: string
|
||||
offline_settlement_amount_cent: number
|
||||
renter_id: number
|
||||
renter_phone: string
|
||||
renter_nickname: string
|
||||
owner_id: number
|
||||
owner_phone: string
|
||||
owner_nickname: string
|
||||
order_rent_amount_cent: number
|
||||
order_deposit_amount_cent: number
|
||||
checkout_rent_amount_cent: number
|
||||
checkout_renter_refund_cent: number
|
||||
checkout_owner_income_cent: number
|
||||
checkout_platform_fee_cent: number
|
||||
owner_wallet_income_amount_cent: number
|
||||
owner_effective_settlement_amount_cent: number
|
||||
paid_amount_cent: number
|
||||
refunded_amount_cent: number
|
||||
refunding_amount_cent: number
|
||||
failed_refund_amount_cent: number
|
||||
channel_net_amount_cent: number
|
||||
platform_net_amount_cent: number
|
||||
settlement_diff_amount_cent: number
|
||||
finance_status: string
|
||||
created_at: string
|
||||
settled_at?: string | null
|
||||
offline_settled_at?: string | null
|
||||
}
|
||||
|
||||
export interface FinanceDetailPage {
|
||||
items: FinanceDetail[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ const allNavGroups: NavGroup[] = [
|
||||
label: '财务',
|
||||
icon: <AccountBookOutlined />,
|
||||
children: [
|
||||
{ key: '/finance', label: '财务看板', permission: 'finance:view' },
|
||||
{ key: '/finance', label: '财务看板', permission: 'wallet:view' },
|
||||
{ key: '/withdrawals', label: '提现审核', permission: 'withdrawal:list' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1403,6 +1403,15 @@ a {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.admin-finance-mini {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.admin-finance-mini .admin-pending-card > .ant-card-body > span {
|
||||
color: var(--ink-muted);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.admin-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user