关闭公开注册并优化仪表盘

This commit is contained in:
yml2213
2026-07-31 09:39:18 +08:00
parent fe71276e62
commit f29f3eb841
11 changed files with 439 additions and 198 deletions
-2
View File
@@ -4,7 +4,6 @@ import zhCN from 'antd/locale/zh_CN'
import { AuthProvider, useAuth } from './store/auth'
import MainLayout from './layouts/MainLayout'
import Login from './pages/Login'
import Register from './pages/Register'
import Dashboard from './pages/Dashboard'
import OpenApiDocs from './pages/OpenApiDocs'
import ApiDebugger from './pages/ApiDebugger'
@@ -27,7 +26,6 @@ function AppRoutes() {
return (
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route
path="/"
element={
-4
View File
@@ -23,10 +23,6 @@ export const authApi = {
const res = await request.post('/auth/login', { username, password })
return res.data.data as LoginResult
},
register: async (data: { username: string; password: string; nickname?: string }) => {
const res = await request.post('/auth/register', data)
return res.data.data as User
},
profile: async () => {
const res = await request.get('/auth/profile')
return res.data.data as User
+181 -40
View File
@@ -1,15 +1,77 @@
import { useEffect, useState } from 'react'
import { Card, Col, Row, Statistic, Typography, Spin, message } from 'antd'
import { useEffect, useState, type ReactNode } from 'react'
import { Card, Col, Progress, Row, Space, Statistic, Typography, Spin, message } from 'antd'
import {
ShopOutlined,
ShoppingOutlined,
ApiOutlined,
CheckCircleOutlined,
ClockCircleOutlined,
CloseCircleOutlined,
DatabaseOutlined,
DollarOutlined,
PercentageOutlined,
ClockCircleOutlined,
ShopOutlined,
ShoppingOutlined,
TeamOutlined,
WalletOutlined,
} from '@ant-design/icons'
import { dashboardApi } from '../api'
import type { DashboardStats } from '../types'
const numberText = (value?: number) => (value ?? 0).toLocaleString('zh-CN')
function ratio(part: number, total: number) {
if (total <= 0) return 0
return Math.round((part / total) * 100)
}
function MetricCard({
title,
value,
icon,
suffix,
color,
}: {
title: string
value: number
icon: ReactNode
suffix?: string
color?: string
}) {
return (
<Card>
<Statistic
title={title}
value={value}
formatter={() => numberText(value)}
prefix={icon}
suffix={suffix}
valueStyle={color ? { color } : undefined}
/>
</Card>
)
}
function StatusLine({
label,
value,
total,
color,
}: {
label: string
value: number
total: number
color: string
}) {
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>
)
}
export default function Dashboard() {
const [stats, setStats] = useState<DashboardStats | null>(null)
const [loading, setLoading] = useState(true)
@@ -32,53 +94,132 @@ export default function Dashboard() {
return (
<div>
<Typography.Title level={4} style={{ marginTop: 0 }}>
</Typography.Title>
<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>
<Row gutter={[16, 16]}>
<Col xs={24} sm={12} lg={8}>
<Card>
<Statistic title="商户商品" value={stats?.product_count ?? 0} prefix={<ShoppingOutlined />} />
</Card>
<MetricCard
title={stats?.scope === 'platform' ? '可售商品' : '商户商品'}
value={stats?.active_product_count ?? 0}
icon={<ShoppingOutlined />}
/>
</Col>
<Col xs={24} sm={12} lg={8}>
<Card>
<Statistic title="平台商户" value={stats?.merchant_count ?? 0} prefix={<ShopOutlined />} />
</Card>
<MetricCard
title={stats?.scope === 'platform' ? '启用商户' : '当前商户'}
value={stats?.active_merchant_count ?? 0}
icon={<ShopOutlined />}
/>
</Col>
<Col xs={24} sm={12} lg={8}>
<Card>
<Statistic title="订单总数" value={stats?.order_count ?? 0} prefix={<ShoppingOutlined />} />
</Card>
<MetricCard title="成员账号" value={stats?.user_count ?? 0} icon={<TeamOutlined />} />
</Col>
<Col xs={24} sm={12} lg={8}>
<Card>
<Statistic
title="成交积分"
value={stats?.total_sales ?? 0}
prefix={<DollarOutlined />}
suffix="积分"
/>
</Card>
<MetricCard title="订单总数" value={stats?.order_count ?? 0} icon={<DatabaseOutlined />} />
</Col>
<Col xs={24} sm={12} lg={8}>
<Card>
<Statistic
title="平台手续费"
value={stats?.total_fees ?? 0}
prefix={<PercentageOutlined />}
suffix="积分"
/>
</Card>
<MetricCard title="今日订单" value={stats?.today_order_count ?? 0} icon={<ClockCircleOutlined />} />
</Col>
<Col xs={24} sm={12} lg={8}>
<Card>
<Statistic
title="待处理订单"
value={stats?.pending_order_count ?? 0}
prefix={<ClockCircleOutlined />}
valueStyle={{ color: (stats?.pending_order_count ?? 0) > 0 ? '#cf1322' : undefined }}
/>
<MetricCard
title="待履约订单"
value={stats?.pending_order_count ?? 0}
icon={<ClockCircleOutlined />}
color={(stats?.pending_order_count ?? 0) > 0 ? '#cf1322' : undefined}
/>
</Col>
</Row>
<Typography.Title level={5} style={{ marginTop: 24 }}>
</Typography.Title>
<Row gutter={[16, 16]}>
<Col xs={24} sm={12} lg={6}>
<MetricCard title="成交积分" value={stats?.total_sales ?? 0} icon={<DollarOutlined />} suffix="积分" />
</Col>
<Col xs={24} sm={12} lg={6}>
<MetricCard title="今日成交" value={stats?.today_sales ?? 0} icon={<DollarOutlined />} suffix="积分" />
</Col>
<Col xs={24} sm={12} lg={6}>
<MetricCard title="平台手续费" value={stats?.total_fees ?? 0} icon={<PercentageOutlined />} suffix="积分" />
</Col>
<Col xs={24} sm={12} lg={6}>
<MetricCard title="可用余额" value={stats?.wallet_available_balance ?? 0} icon={<WalletOutlined />} suffix="积分" />
</Col>
</Row>
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
<Col xs={24} lg={14}>
<Card title="订单状态">
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
<StatusLine
label="已成功"
value={stats?.succeeded_order_count ?? 0}
total={stats?.order_count ?? 0}
color="#52c41a"
/>
<StatusLine
label="待履约"
value={stats?.pending_order_count ?? 0}
total={stats?.order_count ?? 0}
color="#faad14"
/>
<StatusLine
label="履约中"
value={stats?.processing_order_count ?? 0}
total={stats?.order_count ?? 0}
color="#1677ff"
/>
<StatusLine
label="已失败"
value={stats?.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>
</Col>
<Col xs={24} lg={10}>
<Card title="接口与回调">
<Row gutter={[16, 16]}>
<Col span={12}>
<Statistic
title="API 客户端"
value={stats?.active_api_client_count ?? 0}
prefix={<ApiOutlined />}
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 />} />
</Col>
<Col span={12}>
<Statistic
title="失败回调"
value={stats?.failed_callback_count ?? 0}
prefix={<CloseCircleOutlined />}
valueStyle={(stats?.failed_callback_count ?? 0) > 0 ? { color: '#cf1322' } : undefined}
/>
</Col>
</Row>
</Card>
</Col>
</Row>
+4 -6
View File
@@ -1,5 +1,5 @@
import { useState } from 'react'
import { useNavigate, Link } from 'react-router-dom'
import { useNavigate } from 'react-router-dom'
import { Card, Form, Input, Button, Typography, message, Space } from 'antd'
import { UserOutlined, LockOutlined } from '@ant-design/icons'
import { useAuth } from '../store/auth'
@@ -53,11 +53,9 @@ export default function Login() {
</Button>
</Form.Item>
</Form>
<div style={{ textAlign: 'center' }}>
<Typography.Text type="secondary">
<Link to="/register"></Link>
</Typography.Text>
</div>
<Typography.Paragraph type="secondary" style={{ fontSize: 12, marginBottom: 0, textAlign: 'center' }}>
</Typography.Paragraph>
<Typography.Paragraph type="secondary" style={{ fontSize: 12, marginBottom: 0, textAlign: 'center' }}>
admin / admin123
</Typography.Paragraph>
-71
View File
@@ -1,71 +0,0 @@
import { useState } from 'react'
import { useNavigate, Link } from 'react-router-dom'
import { Card, Form, Input, Button, Typography, message, Space } from 'antd'
import { UserOutlined, LockOutlined } from '@ant-design/icons'
import { authApi } from '../api'
export default function Register() {
const navigate = useNavigate()
const [loading, setLoading] = useState(false)
const onFinish = async (values: {
username: string
password: string
nickname?: string
}) => {
setLoading(true)
try {
await authApi.register(values)
message.success('注册成功,请登录')
navigate('/login')
} catch (e) {
message.error(e instanceof Error ? e.message : '注册失败')
} finally {
setLoading(false)
}
}
return (
<div
style={{
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)',
}}
>
<Card style={{ width: 400, boxShadow: '0 8px 32px rgba(0,0,0,0.3)' }}>
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
<div style={{ textAlign: 'center' }}>
<Typography.Title level={3} style={{ marginBottom: 4 }}>
</Typography.Title>
<Typography.Text type="secondary"></Typography.Text>
</div>
<Form layout="vertical" onFinish={onFinish}>
<Form.Item name="username" rules={[{ required: true, min: 3, message: '用户名至少3位' }]}>
<Input prefix={<UserOutlined />} placeholder="用户名" size="large" />
</Form.Item>
<Form.Item name="nickname">
<Input prefix={<UserOutlined />} placeholder="昵称(可选)" size="large" />
</Form.Item>
<Form.Item name="password" rules={[{ required: true, min: 6, message: '密码至少6位' }]}>
<Input.Password prefix={<LockOutlined />} placeholder="密码" size="large" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" loading={loading} block size="large">
</Button>
</Form.Item>
</Form>
<div style={{ textAlign: 'center' }}>
<Typography.Text type="secondary">
<Link to="/login"></Link>
</Typography.Text>
</div>
</Space>
</Card>
</div>
)
}
+19
View File
@@ -158,12 +158,31 @@ export interface CallbackCredential {
}
export interface DashboardStats {
scope: 'platform' | 'merchant'
catalog_product_count: number
product_count: number
active_product_count: number
merchant_count: number
active_merchant_count: number
user_count: number
order_count: number
today_order_count: number
total_sales: number
today_sales: number
total_fees: number
today_fees: number
pending_order_count: number
processing_order_count: number
succeeded_order_count: number
failed_order_count: number
cancelled_order_count: number
wallet_available_balance: number
wallet_frozen_balance: number
api_client_count: number
active_api_client_count: number
callback_subscription_count: number
pending_callback_count: number
failed_callback_count: number
}
export interface PageResult<T> {