import { ReloadOutlined, SearchOutlined } from '@ant-design/icons' import { useQuery } from '@tanstack/react-query' import { Alert, App, Button, Card, Col, Form, Row, Select, Space, Statistic, Tabs, Tag, Typography, } from 'antd' import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useState } from 'react' import { AdminRangePicker } from '@/components/admin/AdminDatePicker' import PageHeader from '@/components/admin/PageHeader' import FinanceSubNav from '@/components/admin/FinanceSubNav' import dayjs from '@/lib/dayjs' import { fetchAdminSalesDailyFacts, fetchAdminSalesProfitOverview, rebuildAdminSalesDailyFacts, } from '@/services/admin' import type { AdminSalesDailyFact } from '@/types/admin/finance' import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination' import { hasAdminPermission } from '@/utils/admin-auth' import { formatMoney } from './panels/shared' type FilterForm = { dateRange?: unknown; provider?: string; fulfillmentMode?: string } const FULFILLMENT_MODE_OPTIONS = [ { value: '', label: '全部履约方式' }, { value: 'work_order', label: '接单平台' }, { value: 'affiliate_dash', label: '自动发货' }, { value: 'legacy_platform', label: '历史平台' }, { value: 'manual', label: '人工客服' }, ] const FULFILLMENT_MODE_LABEL: Record = { work_order: '接单平台', affiliate_dash: '自动发货', legacy_platform: '历史平台', manual: '人工客服', } const COST_STAGE_LABEL: Record = { missing: { label: '无成本', color: 'red' }, partial: { label: '部分缺失', color: 'orange' }, estimated: { label: '预估成本', color: 'blue' }, actual: { label: '实际成本', color: 'green' }, } export default function AdminSalesProfitPage() { const { message } = App.useApp() const [form] = Form.useForm() const canConfig = hasAdminPermission('finance.post') const defaultDateFrom = dayjs().subtract(29, 'day').format('YYYY-MM-DD') const defaultDateTo = dayjs().format('YYYY-MM-DD') const [filters, setFilters] = useState<{ dateFrom: string dateTo: string provider: string fulfillmentMode: string }>({ dateFrom: defaultDateFrom, dateTo: defaultDateTo, provider: '', fulfillmentMode: '' }) const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE) const [rebuilding, setRebuilding] = useState(false) const overviewQuery = useQuery({ queryKey: ['admin-sales-profit-overview', filters], queryFn: () => fetchAdminSalesProfitOverview(filters), }) const factsQuery = useQuery({ queryKey: ['admin-sales-daily-facts', filters, page, pageSize], queryFn: () => fetchAdminSalesDailyFacts({ ...filters, page, pageSize }), }) const overview = overviewQuery.data?.data const facts = factsQuery.data?.data function applyFilters(values: FilterForm) { const range = Array.isArray(values.dateRange) ? values.dateRange : [] setPage(1) setFilters({ dateFrom: range[0] ? dayjs(range[0]).format('YYYY-MM-DD') : '', dateTo: range[1] ? dayjs(range[1]).format('YYYY-MM-DD') : '', provider: values.provider || '', fulfillmentMode: values.fulfillmentMode || '', }) } function resetFilters() { form.resetFields() setPage(1) setFilters({ dateFrom: defaultDateFrom, dateTo: defaultDateTo, provider: '', fulfillmentMode: '', }) } async function rebuildFacts() { setRebuilding(true) try { const response = await rebuildAdminSalesDailyFacts({ dateFrom: filters.dateFrom || undefined, dateTo: filters.dateTo || undefined, }) message.success( `销售事实已重算:${response.data.insertedCount} 行,范围 ${response.data.range.fromDate} ~ ${response.data.range.toDate}`, ) await Promise.all([overviewQuery.refetch(), factsQuery.refetch()]) } catch (error) { message.error(error instanceof Error ? error.message : '重算销售事实失败') } finally { setRebuilding(false) } } const summary = overview?.overview const dailyColumns: TableColumnsType['daily'][number]> = [ { title: '日期', dataIndex: 'statDate', width: 120 }, { title: '订单项', dataIndex: 'itemCount', width: 90 }, { title: '销售收入', dataIndex: 'grossRevenueFen', render: formatMoney }, { title: '退款', dataIndex: 'refundAmountFen', render: formatMoney }, { title: '成本', dataIndex: 'costAmountFen', render: formatMoney }, { title: '缺失成本', dataIndex: 'costMissingCount', width: 100 }, { title: '毛利', dataIndex: 'grossProfitFen', render: (value: number) => ( = 0 ? 'success' : 'danger'}> {formatMoney(value)} ), }, ] const productColumns: TableColumnsType['byProduct'][number]> = [ { title: '标准商品', dataIndex: 'canonicalName', width: 320, ellipsis: true, render: (value: string, row) => ( {value} {row.canonicalProductId ? ( {row.canonicalSku} ) : ( 未归一 )} ), }, { title: '订单项', dataIndex: 'itemCount', width: 90 }, { title: '销售收入', dataIndex: 'grossRevenueFen', render: formatMoney }, { title: '成本', dataIndex: 'costAmountFen', render: formatMoney }, { title: '缺失成本', dataIndex: 'costMissingCount', width: 100 }, { title: '毛利', dataIndex: 'grossProfitFen', render: (value: number) => ( = 0 ? 'success' : 'danger'}> {formatMoney(value)} ), }, ] const fulfillmentModeColumns: TableColumnsType< NonNullable['byFulfillmentMode'][number] > = [ { title: '履约方式', dataIndex: 'fulfillmentMode', render: (value: string) => FULFILLMENT_MODE_LABEL[value] || value, }, { title: '订单项', dataIndex: 'itemCount', width: 90 }, { title: '销售收入', dataIndex: 'grossRevenueFen', render: formatMoney }, { title: '成本', dataIndex: 'costAmountFen', render: formatMoney }, { title: '缺失成本', dataIndex: 'costMissingCount', width: 100 }, { title: '毛利', dataIndex: 'grossProfitFen', render: (value: number) => ( = 0 ? 'success' : 'danger'}> {formatMoney(value)} ), }, ] const factColumns: TableColumnsType = [ { title: '日期', dataIndex: 'statDate', width: 110 }, { title: '来源', dataIndex: 'provider', width: 120 }, { title: '店铺', dataIndex: 'shopId', width: 140, ellipsis: true, render: (value) => value || '全局', }, { title: '商品', dataIndex: 'canonicalName', width: 260, ellipsis: true, render: (value: string, row) => row.canonicalProductId ? value : 未归一, }, { title: '履约方式', dataIndex: 'fulfillmentMode', width: 100, render: (value: string) => FULFILLMENT_MODE_LABEL[value] || value, }, { title: '订单项', dataIndex: 'itemCount', width: 80 }, { title: '销售收入', dataIndex: 'grossRevenueFen', width: 120, render: formatMoney }, { title: '成本', dataIndex: 'costAmountFen', width: 120, render: formatMoney }, { title: '成本状态', dataIndex: 'costStage', width: 110, render: (value: string) => { const meta = COST_STAGE_LABEL[value] || { label: value, color: 'default' } return {meta.label} }, }, { title: '毛利', dataIndex: 'grossProfitFen', width: 130, render: (value: number) => ( = 0 ? 'success' : 'danger'}> {formatMoney(value)} ), }, ] return (
form={form} layout="inline" className="filter-form" onFinish={applyFilters} initialValues={{ dateRange: [dayjs().subtract(29, 'day'), dayjs()], provider: '', fulfillmentMode: '', }} > {canConfig ? ( ) : null} {overviewQuery.error ? ( ) : null} {summary && summary.costMissingCount > 0 ? ( ) : null} {summary && summary.unmappedRevenuePct > 0 ? ( ) : null} {summary && summary.ordersWithoutItems > 0 ? ( ) : null} formatMoney(summary?.grossRevenueFen || 0)} /> formatMoney(summary?.netRevenueFen || 0)} /> formatMoney(summary?.costAmountFen || 0)} /> = 0 ? '#3f8600' : '#cf1322' }} formatter={() => formatMoney(summary?.grossProfitFen || 0)} /> ), }, { key: 'product', label: '商品', children: ( `${row.canonicalProductId || 'unmapped'}`} size="small" columns={productColumns} dataSource={overview?.byProduct || []} loading={overviewQuery.isLoading} pagination={false} scroll={{ x: 900 }} /> ), }, { key: 'fulfillment-mode', label: '履约方式', children: ( ), }, { key: 'facts', label: '事实明细', children: ( `共 ${total} 条事实`, onChange: (nextPage, nextPageSize) => { setPage(nextPage) setPageSize(nextPageSize) }, })} scroll={{ x: 1400 }} /> ), }, ]} />
) }