优化后台UI与开放文档排版设计
This commit is contained in:
@@ -1,20 +1,25 @@
|
||||
import { useEffect, useState, type ReactNode } from 'react'
|
||||
import { Card, Col, Progress, Row, Space, Statistic, Typography, Spin, message } from 'antd'
|
||||
import { Card, Col, Progress, Row, Space, Statistic, Typography, Spin, message, Tag } from 'antd'
|
||||
import {
|
||||
ApiOutlined,
|
||||
CheckCircleOutlined,
|
||||
ClockCircleOutlined,
|
||||
CloseCircleOutlined,
|
||||
CodeOutlined,
|
||||
DatabaseOutlined,
|
||||
DollarOutlined,
|
||||
PercentageOutlined,
|
||||
FileTextOutlined,
|
||||
PlusOutlined,
|
||||
ShopOutlined,
|
||||
ShoppingOutlined,
|
||||
TeamOutlined,
|
||||
WalletOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { dashboardApi } from '../api'
|
||||
import type { DashboardStats } from '../types'
|
||||
import { PageHeader } from '../components/PageHeader'
|
||||
import { useAuth } from '../store/auth'
|
||||
|
||||
const numberText = (value?: number) => (value ?? 0).toLocaleString('zh-CN')
|
||||
|
||||
@@ -23,51 +28,74 @@ function ratio(part: number, total: number) {
|
||||
return Math.round((part / total) * 100)
|
||||
}
|
||||
|
||||
function MetricCard({
|
||||
function ModernMetricCard({
|
||||
title,
|
||||
value,
|
||||
icon,
|
||||
suffix,
|
||||
color,
|
||||
unit = '',
|
||||
iconTheme = '',
|
||||
footerText,
|
||||
footerValue,
|
||||
}: {
|
||||
title: string
|
||||
value: number
|
||||
icon: ReactNode
|
||||
suffix?: string
|
||||
color?: string
|
||||
unit?: string
|
||||
iconTheme?: string
|
||||
footerText?: string
|
||||
footerValue?: string | number
|
||||
}) {
|
||||
return (
|
||||
<Card>
|
||||
<Statistic
|
||||
title={title}
|
||||
value={value}
|
||||
formatter={() => numberText(value)}
|
||||
prefix={icon}
|
||||
suffix={suffix}
|
||||
valueStyle={color ? { color } : undefined}
|
||||
/>
|
||||
</Card>
|
||||
<div className="metric-card-box">
|
||||
<div className="metric-card-header">
|
||||
<span className="metric-card-title">{title}</span>
|
||||
<div className={`metric-card-icon ${iconTheme}`}>{icon}</div>
|
||||
</div>
|
||||
<div className="metric-card-value">
|
||||
{numberText(value)}
|
||||
{unit && <span style={{ fontSize: 13, fontWeight: 500, marginLeft: 4, color: '#64748b' }}>{unit}</span>}
|
||||
</div>
|
||||
{footerText && (
|
||||
<div className="metric-card-footer">
|
||||
<span>{footerText}</span>
|
||||
<span style={{ fontWeight: 600, color: '#0f172a' }}>{footerValue}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function StatusLine({
|
||||
function CustomStatusLine({
|
||||
label,
|
||||
value,
|
||||
total,
|
||||
color,
|
||||
tagClass,
|
||||
}: {
|
||||
label: string
|
||||
value: number
|
||||
total: number
|
||||
color: string
|
||||
tagClass: string
|
||||
}) {
|
||||
const percent = ratio(value, total)
|
||||
return (
|
||||
<div>
|
||||
<Space style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||
<Typography.Text>{label}</Typography.Text>
|
||||
<Typography.Text strong>{numberText(value)}</Typography.Text>
|
||||
</Space>
|
||||
<Progress percent={ratio(value, total)} strokeColor={color} showInfo={false} />
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 6 }}>
|
||||
<div className={`status-tag ${tagClass}`}>
|
||||
<span className="status-dot"></span>
|
||||
{label}
|
||||
</div>
|
||||
<Space size="small">
|
||||
<Typography.Text strong style={{ fontSize: 15, color: '#0f172a' }}>
|
||||
{numberText(value)} 单
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
({percent}%)
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
</div>
|
||||
<Progress percent={percent} strokeColor={color} showInfo={false} size="small" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -75,6 +103,8 @@ function StatusLine({
|
||||
export default function Dashboard() {
|
||||
const [stats, setStats] = useState<DashboardStats | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const { user, isAdmin } = useAuth()
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
dashboardApi
|
||||
@@ -86,137 +116,238 @@ export default function Dashboard() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ textAlign: 'center', padding: 80 }}>
|
||||
<Spin size="large" />
|
||||
<div style={{ textAlign: 'center', padding: '100px 0' }}>
|
||||
<Spin size="large" tip="正在加载仪表盘数据..." />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Space style={{ width: '100%', justifyContent: 'space-between', marginBottom: 16 }} align="start">
|
||||
<div>
|
||||
<Typography.Title level={4} style={{ marginTop: 0, marginBottom: 4 }}>
|
||||
数据概览
|
||||
</Typography.Title>
|
||||
<Typography.Text type="secondary">
|
||||
{stats?.scope === 'platform' ? '平台全局运营数据' : '当前商户运营数据'}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</Space>
|
||||
<PageHeader
|
||||
title="运营工作台"
|
||||
subtitle={stats?.scope === 'platform' ? '平台全局运营数据与发货概览' : '当前商户运营数据与极速发货概览'}
|
||||
breadcrumbs={[{ title: '工作台' }]}
|
||||
extra={
|
||||
<Tag color="blue" style={{ padding: '4px 12px', borderRadius: 20, fontSize: 12, fontWeight: 600 }}>
|
||||
{stats?.scope === 'platform' ? '全局视图' : '商户视图'}
|
||||
</Tag>
|
||||
}
|
||||
/>
|
||||
|
||||
<Row gutter={[16, 16]}>
|
||||
{/* 顶部欢迎卡片 & 快捷 LaunchPad */}
|
||||
<div className="dash-welcome-card">
|
||||
<h2 className="dash-welcome-title">
|
||||
你好,{user?.nickname || user?.username} 👋
|
||||
</h2>
|
||||
<p className="dash-welcome-sub">
|
||||
欢迎回到 Skin Hub 游戏皮肤分销管理平台。以下是系统的最新业务状态与发货订单追踪数据。
|
||||
</p>
|
||||
<div className="dash-quick-launch">
|
||||
<button className="dash-quick-btn" type="button" onClick={() => navigate('/merchant-orders')}>
|
||||
<PlusOutlined /> 发货订单管理
|
||||
</button>
|
||||
<button className="dash-quick-btn" type="button" onClick={() => navigate('/merchant-wallet')}>
|
||||
<WalletOutlined /> 积分流水查看
|
||||
</button>
|
||||
{isAdmin && (
|
||||
<>
|
||||
<button className="dash-quick-btn" type="button" onClick={() => navigate('/platform-merchants')}>
|
||||
<ShopOutlined /> 平台商户管理
|
||||
</button>
|
||||
<button className="dash-quick-btn" type="button" onClick={() => navigate('/open-api')}>
|
||||
<FileTextOutlined /> 开放接口文档
|
||||
</button>
|
||||
<button className="dash-quick-btn" type="button" onClick={() => navigate('/api-debug')}>
|
||||
<CodeOutlined /> 在线 API 调试
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 第一组:核心业务指标 */}
|
||||
<Typography.Title level={5} style={{ marginBottom: 14, fontWeight: 700, color: '#1e293b' }}>
|
||||
基础数据指标
|
||||
</Typography.Title>
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||
<Col xs={24} sm={12} lg={8}>
|
||||
<MetricCard
|
||||
title={stats?.scope === 'platform' ? '可售商品' : '商户商品'}
|
||||
<ModernMetricCard
|
||||
title={stats?.scope === 'platform' ? '平台可售商品' : '商户上架商品'}
|
||||
value={stats?.active_product_count ?? 0}
|
||||
icon={<ShoppingOutlined />}
|
||||
iconTheme=""
|
||||
footerText="活跃商品库"
|
||||
footerValue="已上线"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} lg={8}>
|
||||
<MetricCard
|
||||
title={stats?.scope === 'platform' ? '启用商户' : '当前商户'}
|
||||
<ModernMetricCard
|
||||
title={stats?.scope === 'platform' ? '平台启用商户' : '当前合作商户'}
|
||||
value={stats?.active_merchant_count ?? 0}
|
||||
icon={<ShopOutlined />}
|
||||
iconTheme="metric-card-icon--purple"
|
||||
footerText="商户状态"
|
||||
footerValue="正常运行"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} lg={8}>
|
||||
<MetricCard title="成员账号" value={stats?.user_count ?? 0} icon={<TeamOutlined />} />
|
||||
<ModernMetricCard
|
||||
title="平台成员账号"
|
||||
value={stats?.user_count ?? 0}
|
||||
icon={<TeamOutlined />}
|
||||
iconTheme="metric-card-icon--emerald"
|
||||
footerText="团队成员"
|
||||
footerValue="活跃"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} lg={8}>
|
||||
<MetricCard title="订单总数" value={stats?.order_count ?? 0} icon={<DatabaseOutlined />} />
|
||||
<ModernMetricCard
|
||||
title="累计订单总数"
|
||||
value={stats?.order_count ?? 0}
|
||||
icon={<DatabaseOutlined />}
|
||||
iconTheme="metric-card-icon--amber"
|
||||
footerText="今日新增订单"
|
||||
footerValue={`${stats?.today_order_count ?? 0} 单`}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} lg={8}>
|
||||
<MetricCard title="今日订单" value={stats?.today_order_count ?? 0} icon={<ClockCircleOutlined />} />
|
||||
<ModernMetricCard
|
||||
title="今日订单总量"
|
||||
value={stats?.today_order_count ?? 0}
|
||||
icon={<ClockCircleOutlined />}
|
||||
iconTheme=""
|
||||
footerText="占比全量"
|
||||
footerValue={`${ratio(stats?.today_order_count ?? 0, stats?.order_count ?? 0)}%`}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} lg={8}>
|
||||
<MetricCard
|
||||
title="待发货订单"
|
||||
<ModernMetricCard
|
||||
title="待处理/待发货订单"
|
||||
value={stats?.paid_order_count ?? 0}
|
||||
icon={<ClockCircleOutlined />}
|
||||
color={(stats?.paid_order_count ?? 0) > 0 ? '#cf1322' : undefined}
|
||||
iconTheme={(stats?.paid_order_count ?? 0) > 0 ? 'metric-card-icon--rose' : ''}
|
||||
footerText="需及时发货"
|
||||
footerValue={(stats?.paid_order_count ?? 0) > 0 ? '需处理' : '无积压'}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Typography.Title level={5} style={{ marginTop: 24 }}>
|
||||
积分与费用
|
||||
{/* 第二组:积分与资金监控 */}
|
||||
<Typography.Title level={5} style={{ marginBottom: 14, fontWeight: 700, color: '#1e293b' }}>
|
||||
积分变动与账户额度
|
||||
</Typography.Title>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||
<Col xs={24} sm={12} lg={6}>
|
||||
<MetricCard title="成交积分" value={stats?.total_sales ?? 0} icon={<DollarOutlined />} suffix="积分" />
|
||||
<ModernMetricCard
|
||||
title="累计成交积分"
|
||||
value={stats?.total_sales ?? 0}
|
||||
unit="积分"
|
||||
icon={<DollarOutlined />}
|
||||
iconTheme="metric-card-icon--emerald"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} lg={6}>
|
||||
<MetricCard title="今日成交" value={stats?.today_sales ?? 0} icon={<DollarOutlined />} suffix="积分" />
|
||||
<ModernMetricCard
|
||||
title="今日成交积分"
|
||||
value={stats?.today_sales ?? 0}
|
||||
unit="积分"
|
||||
icon={<DollarOutlined />}
|
||||
iconTheme="metric-card-icon--amber"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} lg={6}>
|
||||
<MetricCard title="平台手续费" value={stats?.total_fees ?? 0} icon={<PercentageOutlined />} suffix="积分" />
|
||||
<ModernMetricCard
|
||||
title="平台扣费/手续费"
|
||||
value={stats?.total_fees ?? 0}
|
||||
unit="积分"
|
||||
icon={<WalletOutlined />}
|
||||
iconTheme="metric-card-icon--purple"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} lg={6}>
|
||||
<MetricCard title="可用余额" value={stats?.wallet_available_balance ?? 0} icon={<WalletOutlined />} suffix="积分" />
|
||||
<ModernMetricCard
|
||||
title="账户可用余额"
|
||||
value={stats?.wallet_available_balance ?? 0}
|
||||
unit="积分"
|
||||
icon={<WalletOutlined />}
|
||||
iconTheme=""
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
|
||||
{/* 第三组:订单发货分布与 API 监控 */}
|
||||
<Row gutter={[20, 20]}>
|
||||
<Col xs={24} lg={14}>
|
||||
<Card title="订单状态">
|
||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||
<StatusLine
|
||||
label="待发货"
|
||||
value={stats?.paid_order_count ?? 0}
|
||||
total={stats?.order_count ?? 0}
|
||||
color="#1677ff"
|
||||
/>
|
||||
<StatusLine
|
||||
label="发货中"
|
||||
value={stats?.delivering_order_count ?? 0}
|
||||
total={stats?.order_count ?? 0}
|
||||
color="#13c2c2"
|
||||
/>
|
||||
<StatusLine
|
||||
label="已交付"
|
||||
value={stats?.delivered_order_count ?? 0}
|
||||
total={stats?.order_count ?? 0}
|
||||
color="#52c41a"
|
||||
/>
|
||||
<StatusLine
|
||||
label="发货失败"
|
||||
value={stats?.ship_failed_order_count ?? 0}
|
||||
total={stats?.order_count ?? 0}
|
||||
color="#ff4d4f"
|
||||
/>
|
||||
<StatusLine
|
||||
label="已取消"
|
||||
value={stats?.cancelled_order_count ?? 0}
|
||||
total={stats?.order_count ?? 0}
|
||||
color="#8c8c8c"
|
||||
/>
|
||||
</Space>
|
||||
<Card title="发货订单状态占比分布" style={{ height: '100%' }}>
|
||||
<CustomStatusLine
|
||||
label="待发货 (paid)"
|
||||
value={stats?.paid_order_count ?? 0}
|
||||
total={stats?.order_count ?? 0}
|
||||
color="#2563eb"
|
||||
tagClass="status-tag--blue"
|
||||
/>
|
||||
<CustomStatusLine
|
||||
label="发货中 (delivering)"
|
||||
value={stats?.delivering_order_count ?? 0}
|
||||
total={stats?.order_count ?? 0}
|
||||
color="#0891b2"
|
||||
tagClass="status-tag--cyan"
|
||||
/>
|
||||
<CustomStatusLine
|
||||
label="已交付 (delivered)"
|
||||
value={stats?.delivered_order_count ?? 0}
|
||||
total={stats?.order_count ?? 0}
|
||||
color="#16a34a"
|
||||
tagClass="status-tag--green"
|
||||
/>
|
||||
<CustomStatusLine
|
||||
label="发货失败 (ship_failed)"
|
||||
value={stats?.ship_failed_order_count ?? 0}
|
||||
total={stats?.order_count ?? 0}
|
||||
color="#dc2626"
|
||||
tagClass="status-tag--red"
|
||||
/>
|
||||
<CustomStatusLine
|
||||
label="已取消 (cancelled)"
|
||||
value={stats?.cancelled_order_count ?? 0}
|
||||
total={stats?.order_count ?? 0}
|
||||
color="#64748b"
|
||||
tagClass="status-tag--gray"
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} lg={10}>
|
||||
<Card title="接口与回调">
|
||||
<Row gutter={[16, 16]}>
|
||||
<Card title="接口密钥与 Webhook 回调监控" style={{ height: '100%' }}>
|
||||
<Row gutter={[16, 20]}>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title="API 密钥"
|
||||
title="API 密钥 (活跃/总量)"
|
||||
value={stats?.active_api_client_count ?? 0}
|
||||
prefix={<ApiOutlined />}
|
||||
prefix={<ApiOutlined style={{ color: '#2563eb' }} />}
|
||||
suffix={`/ ${numberText(stats?.api_client_count)}`}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic title="回调订阅" value={stats?.callback_subscription_count ?? 0} prefix={<CheckCircleOutlined />} />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic title="待投递回调" value={stats?.pending_callback_count ?? 0} prefix={<ClockCircleOutlined />} />
|
||||
<Statistic
|
||||
title="Webhook 回调订阅"
|
||||
value={stats?.callback_subscription_count ?? 0}
|
||||
prefix={<CheckCircleOutlined style={{ color: '#16a34a' }} />}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title="失败回调"
|
||||
title="待投递回调队列"
|
||||
value={stats?.pending_callback_count ?? 0}
|
||||
prefix={<ClockCircleOutlined style={{ color: '#d97706' }} />}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title="投递失败回调"
|
||||
value={stats?.failed_callback_count ?? 0}
|
||||
prefix={<CloseCircleOutlined />}
|
||||
valueStyle={(stats?.failed_callback_count ?? 0) > 0 ? { color: '#cf1322' } : undefined}
|
||||
prefix={<CloseCircleOutlined style={{ color: '#dc2626' }} />}
|
||||
valueStyle={(stats?.failed_callback_count ?? 0) > 0 ? { color: '#dc2626' } : undefined}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
Reference in New Issue
Block a user