257 lines
9.2 KiB
TypeScript
257 lines
9.2 KiB
TypeScript
import { useEffect, useState, useCallback } from 'react'
|
|
import {
|
|
Card,
|
|
Table,
|
|
Typography,
|
|
Statistic,
|
|
Space,
|
|
Row,
|
|
Col,
|
|
Select,
|
|
Tag,
|
|
Button,
|
|
message,
|
|
} from 'antd'
|
|
import {
|
|
ShoppingCartOutlined,
|
|
DollarCircleOutlined,
|
|
WalletOutlined,
|
|
AppstoreOutlined,
|
|
LineChartOutlined,
|
|
ArrowRightOutlined,
|
|
SkinOutlined,
|
|
} from '@ant-design/icons'
|
|
import { Link } from 'react-router-dom'
|
|
import {
|
|
listOrders,
|
|
listSites,
|
|
fetchDashboardAnalytics,
|
|
formatMoney,
|
|
type Site,
|
|
type DistributionAnalytics,
|
|
type DistributionOrder,
|
|
} from '../api'
|
|
|
|
export default function Dashboard() {
|
|
const [sites, setSites] = useState<Site[]>([])
|
|
const [siteID, setSiteID] = useState<number>()
|
|
const [analytics, setAnalytics] = useState<DistributionAnalytics | null>(null)
|
|
const [recentOrders, setRecentOrders] = useState<DistributionOrder[]>([])
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
const loadData = useCallback(async (sid: number) => {
|
|
setLoading(true)
|
|
try {
|
|
const [anaRes, ordRes] = await Promise.all([
|
|
fetchDashboardAnalytics(sid),
|
|
listOrders({ siteID: sid, pageSize: 8 }),
|
|
])
|
|
setAnalytics(anaRes)
|
|
setRecentOrders(ordRes.items)
|
|
} catch {
|
|
message.error('加载大盘数据失败')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
listSites()
|
|
.then((rows) => {
|
|
setSites(rows)
|
|
if (rows.length > 0) {
|
|
setSiteID(rows[0].id)
|
|
void loadData(rows[0].id)
|
|
}
|
|
})
|
|
.catch(() => message.error('站点加载失败'))
|
|
}, [loadData])
|
|
|
|
const selectedSite = sites.find((s) => s.id === siteID)
|
|
|
|
return (
|
|
<Space direction="vertical" size="large" className="page" style={{ width: '100%' }}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<div>
|
|
<Typography.Title level={3} style={{ marginBottom: 4 }}>
|
|
运营数据大盘
|
|
</Typography.Title>
|
|
<Typography.Text type="secondary">
|
|
当前站点:{selectedSite?.site_name || '默认分销站'} ({selectedSite?.site_code}) · 实时聚合收号、货架、订单与结算收益
|
|
</Typography.Text>
|
|
</div>
|
|
<Space>
|
|
<Select
|
|
placeholder="切换分销网站"
|
|
style={{ width: 220 }}
|
|
value={siteID}
|
|
options={sites.map((site) => ({ label: `${site.site_name} (${site.site_code})`, value: site.id }))}
|
|
onChange={(val) => {
|
|
setSiteID(val)
|
|
void loadData(val)
|
|
}}
|
|
/>
|
|
<Link to="/wallet">
|
|
<Button type="primary" icon={<WalletOutlined />}>
|
|
去提现
|
|
</Button>
|
|
</Link>
|
|
<Link to="/settings">
|
|
<Button icon={<SkinOutlined />}>
|
|
站点装修
|
|
</Button>
|
|
</Link>
|
|
</Space>
|
|
</div>
|
|
|
|
{/* 核心业务指标看板 */}
|
|
<Row gutter={[16, 16]}>
|
|
<Col xs={24} sm={12} md={8} lg={4}>
|
|
<Card bordered={false} style={{ background: '#fafafa' }} loading={loading}>
|
|
<Statistic
|
|
title="今日收号 / 累计收号"
|
|
value={analytics?.today_received_count ?? 0}
|
|
suffix={`/ ${analytics?.total_received_count ?? 0} 个`}
|
|
valueStyle={{ color: '#1677ff', fontWeight: 'bold' }}
|
|
/>
|
|
<div style={{ marginTop: 8 }}>
|
|
<Link to="/receive" style={{ fontSize: 12 }}>查看收号资产 <ArrowRightOutlined /></Link>
|
|
</div>
|
|
</Card>
|
|
</Col>
|
|
<Col xs={24} sm={12} md={8} lg={4}>
|
|
<Card bordered={false} style={{ background: '#fafafa' }} loading={loading}>
|
|
<Statistic
|
|
title="在售可租货源"
|
|
value={analytics?.active_listing_count ?? 0}
|
|
prefix={<AppstoreOutlined style={{ color: '#52c41a' }} />}
|
|
suffix="个"
|
|
valueStyle={{ color: '#52c41a', fontWeight: 'bold' }}
|
|
/>
|
|
<div style={{ marginTop: 8 }}>
|
|
<Link to="/accounts" style={{ fontSize: 12 }}>管理商品货架 <ArrowRightOutlined /></Link>
|
|
</div>
|
|
</Card>
|
|
</Col>
|
|
<Col xs={24} sm={12} md={8} lg={4}>
|
|
<Card bordered={false} style={{ background: '#fafafa' }} loading={loading}>
|
|
<Statistic
|
|
title="今日订单量"
|
|
value={analytics?.today_order_count ?? 0}
|
|
prefix={<ShoppingCartOutlined style={{ color: '#722ed1' }} />}
|
|
suffix="单"
|
|
valueStyle={{ color: '#722ed1', fontWeight: 'bold' }}
|
|
/>
|
|
<div style={{ marginTop: 8 }}>
|
|
<Link to="/sold" style={{ fontSize: 12 }}>查看交易订单 <ArrowRightOutlined /></Link>
|
|
</div>
|
|
</Card>
|
|
</Col>
|
|
<Col xs={24} sm={12} md={8} lg={4}>
|
|
<Card bordered={false} style={{ background: '#fafafa' }} loading={loading}>
|
|
<Statistic
|
|
title="今日成交 GMV"
|
|
value={((analytics?.today_gmv_cent ?? 0) / 100).toFixed(2)}
|
|
suffix="元"
|
|
valueStyle={{ color: '#fa8c16', fontWeight: 'bold' }}
|
|
/>
|
|
<div style={{ marginTop: 8, fontSize: 12, color: '#888' }}>
|
|
今日收益: {formatMoney(analytics?.today_profit_cent)}
|
|
</div>
|
|
</Card>
|
|
</Col>
|
|
<Col xs={24} sm={12} md={8} lg={4}>
|
|
<Card bordered={false} style={{ background: '#fafafa' }} loading={loading}>
|
|
<Statistic
|
|
title="本月预估分润收益"
|
|
value={((analytics?.month_profit_cent ?? 0) / 100).toFixed(2)}
|
|
prefix={<DollarCircleOutlined style={{ color: '#eb2f96' }} />}
|
|
suffix="元"
|
|
valueStyle={{ color: '#eb2f96', fontWeight: 'bold' }}
|
|
/>
|
|
<div style={{ marginTop: 8 }}>
|
|
<Link to="/settlements" style={{ fontSize: 12 }}>结算对账 <ArrowRightOutlined /></Link>
|
|
</div>
|
|
</Card>
|
|
</Col>
|
|
<Col xs={24} sm={12} md={8} lg={4}>
|
|
<Card bordered={false} style={{ background: '#f6ffed', border: '1px solid #b7eb8f' }} loading={loading}>
|
|
<Statistic
|
|
title="钱包可提现余额"
|
|
value={((analytics?.available_balance_cent ?? 0) / 100).toFixed(2)}
|
|
prefix={<WalletOutlined style={{ color: '#52c41a' }} />}
|
|
suffix="元"
|
|
valueStyle={{ color: '#52c41a', fontWeight: 'bold' }}
|
|
/>
|
|
<div style={{ marginTop: 8 }}>
|
|
<Link to="/wallet" style={{ fontSize: 12 }}>立即提现 <ArrowRightOutlined /></Link>
|
|
</div>
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
|
|
{/* 近7天运营走势 */}
|
|
<Card
|
|
title={
|
|
<Space>
|
|
<LineChartOutlined />
|
|
<span>近 7 天交易与收号走势</span>
|
|
</Space>
|
|
}
|
|
loading={loading}
|
|
>
|
|
<Table
|
|
rowKey="date"
|
|
dataSource={analytics?.trend_days ?? []}
|
|
pagination={false}
|
|
columns={[
|
|
{ title: '日期', dataIndex: 'date', width: 140, render: (v: string) => <strong>{v}</strong> },
|
|
{
|
|
title: '收号数量',
|
|
dataIndex: 'received_count',
|
|
render: (v: number) => <Tag color={v > 0 ? 'blue' : 'default'}>{v} 个</Tag>,
|
|
},
|
|
{
|
|
title: '成交订单',
|
|
dataIndex: 'order_count',
|
|
render: (v: number) => <Tag color={v > 0 ? 'purple' : 'default'}>{v} 单</Tag>,
|
|
},
|
|
{
|
|
title: '成交 GMV',
|
|
dataIndex: 'gmv_cent',
|
|
render: (v: number) => <span style={{ color: '#cf1322', fontWeight: 'bold' }}>{formatMoney(v)}</span>,
|
|
},
|
|
{
|
|
title: '预计分成收益',
|
|
dataIndex: 'profit_cent',
|
|
render: (v: number) => <span style={{ color: '#52c41a', fontWeight: 'bold' }}>{formatMoney(v)}</span>,
|
|
},
|
|
]}
|
|
/>
|
|
</Card>
|
|
|
|
{/* 最近交易订单 */}
|
|
<Card
|
|
title="最近分销订单"
|
|
extra={<Link to="/sold">查看全部订单 <ArrowRightOutlined /></Link>}
|
|
loading={loading}
|
|
>
|
|
<Table
|
|
rowKey="order_id"
|
|
dataSource={recentOrders}
|
|
pagination={false}
|
|
columns={[
|
|
{ title: '订单号', dataIndex: 'order_no', render: (v: string) => <Typography.Text copyable>{v}</Typography.Text> },
|
|
{ title: '状态', dataIndex: 'status', render: (v: string) => <Tag>{v}</Tag> },
|
|
{ title: '净支付', dataIndex: 'net_paid_amount_cent', render: (v: number) => formatMoney(v) },
|
|
{ title: '手续费', dataIndex: 'payment_fee_cent', render: (v: number) => formatMoney(v) },
|
|
{ title: '分成基数', dataIndex: 'split_base_amount_cent', render: (v: number) => formatMoney(v) },
|
|
{ title: '结算状态', dataIndex: 'settlement_status', render: (v: string) => <Tag color={v === 'settled' ? 'success' : 'default'}>{v}</Tag> },
|
|
{ title: '创建时间', dataIndex: 'created_at' },
|
|
]}
|
|
/>
|
|
</Card>
|
|
</Space>
|
|
)
|
|
}
|