关闭公开注册并优化仪表盘
This commit is contained in:
@@ -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={
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user