This commit is contained in:
2025-08-28 00:55:35 +08:00
commit 410f54a65e
93 changed files with 9863 additions and 0 deletions

View File

@ -0,0 +1,171 @@
import React, { useEffect, useMemo, useState } from 'react'
import { Card, Col, Progress, Row, Statistic, Table, Tag, Typography } from 'antd'
import api from '../utils/axios'
import { formatDateTime } from '../utils/datetime'
import PageHeader from '../components/PageHeader'
interface UserItem { id: number; username: string; nickname?: string; status?: number; created_at?: string }
export default function Dashboard() {
const [loading, setLoading] = useState(false)
const [userTotal, setUserTotal] = useState(0)
const [roleTotal, setRoleTotal] = useState(0)
const [deptTotal, setDeptTotal] = useState(0)
const [menuTotal, setMenuTotal] = useState(0)
const [userSample, setUserSample] = useState([] as UserItem[])
useEffect(() => {
const fetchAll = async () => {
setLoading(true)
try {
const [usersRes, rolesRes, deptsRes, menusRes, usersSampleRes] = await Promise.all([
api.get('/users', { params: { page: 1, page_size: 1 } }),
api.get('/roles', { params: { page: 1, page_size: 1 } }),
api.get('/departments', { params: { keyword: '' } }),
api.get('/menus'),
api.get('/users', { params: { page: 1, page_size: 200 } }),
])
if (usersRes.data?.code === 0) setUserTotal(Number(usersRes.data?.data?.total || 0))
if (rolesRes.data?.code === 0) setRoleTotal(Number(rolesRes.data?.data?.total || 0))
if (deptsRes.data?.code === 0) setDeptTotal(Array.isArray(deptsRes.data?.data) ? deptsRes.data.data.length : 0)
if (menusRes.data?.code === 0) setMenuTotal(Array.isArray(menusRes.data?.data) ? menusRes.data.data.length : 0)
if (usersSampleRes.data?.code === 0) setUserSample(Array.isArray(usersSampleRes.data?.data?.items) ? usersSampleRes.data.data.items : [])
} catch (e) {
// ignore on dashboard
} finally { setLoading(false) }
}
fetchAll()
}, [])
// 用户状态分布(基于 sample 数据近似统计)
const statusDist = useMemo(() => {
const enabled = userSample.reduce((acc, u) => acc + (u.status === 1 ? 1 : 0), 0)
const total = userSample.length || 1
const percentEnabled = Math.round((enabled / total) * 100)
return { enabled, disabled: total - enabled, percentEnabled }
}, [userSample])
// 近7天新增用户基于 sample 的 created_at 统计)
const last7Days = useMemo(() => {
const today = new Date()
const days: string[] = []
for (let i = 6; i >= 0; i--) {
const d = new Date(today)
d.setDate(today.getDate() - i)
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const da = String(d.getDate()).padStart(2, '0')
days.push(`${y}-${m}-${da}`)
}
const counter: Record<string, number> = Object.fromEntries(days.map(d => [d, 0]))
userSample.forEach(u => {
if (!u.created_at) return
const dt = new Date(u.created_at)
if (Number.isNaN(dt.getTime())) return
const y = dt.getFullYear()
const m = String(dt.getMonth() + 1).padStart(2, '0')
const da = String(dt.getDate()).padStart(2, '0')
const key = `${y}-${m}-${da}`
if (key in counter) counter[key] += 1
})
return days.map(d => ({ date: d, value: counter[d] || 0 }))
}, [userSample])
const userColumns = useMemo(() => [
{ title: 'ID', dataIndex: 'id', width: 80 },
{ title: '用户名', dataIndex: 'username' },
{ title: '昵称', dataIndex: 'nickname' },
{ title: '状态', dataIndex: 'status', render: (v: number) => v === 1 ? <Tag color="green"></Tag> : <Tag></Tag> },
{ title: '创建时间', dataIndex: 'created_at', render: (v: any) => formatDateTime(v) },
], [])
const recentUsers = useMemo(() => {
const withTime = userSample.filter(u => !!u.created_at)
withTime.sort((a, b) => (new Date(b.created_at!).getTime() - new Date(a.created_at!).getTime()))
return withTime.slice(0, 8)
}, [userSample])
const maxDaily = Math.max(...last7Days.map(d => d.value), 1)
return (
<div>
<PageHeader items={["首页"]} title="首页" />
<Row gutter={[16, 16]}>
<Col xs={12} sm={12} md={6}>
<Card loading={loading}>
<Statistic title="用户总数" value={userTotal} />
</Card>
</Col>
<Col xs={12} sm={12} md={6}>
<Card loading={loading}>
<Statistic title="角色总数" value={roleTotal} />
</Card>
</Col>
<Col xs={12} sm={12} md={6}>
<Card loading={loading}>
<Statistic title="部门总数" value={deptTotal} />
</Card>
</Col>
<Col xs={12} sm={12} md={6}>
<Card loading={loading}>
<Statistic title="菜单总数" value={menuTotal} />
</Card>
</Col>
</Row>
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
<Col xs={24} md={12}>
<Card title="用户状态分布(样本)" loading={loading}>
<Row gutter={24}>
<Col span={12} style={{ textAlign: 'center' }}>
<Progress type="dashboard" percent={statusDist.percentEnabled} />
<div style={{ marginTop: 8 }}></div>
</Col>
<Col span={12}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<div><Tag color="green">{statusDist.enabled}</Tag></div>
<div><Tag>{statusDist.disabled}</Tag></div>
<div>{userSample.length}</div>
</div>
</Col>
</Row>
</Card>
</Col>
<Col xs={24} md={12}>
<Card title="近7天新增用户样本" loading={loading}>
<div style={{ height: 180, display: 'flex', alignItems: 'flex-end', gap: 8 }}>
{last7Days.map(d => (
<div key={d.date} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<div style={{ width: 20, background: '#1677ff', height: Math.max(4, (d.value / maxDaily) * 140) }} />
<div style={{ marginTop: 6, fontSize: 12 }}>{d.value}</div>
<div style={{ marginTop: 2, fontSize: 12, color: '#999' }}>{d.date.slice(5)}</div>
</div>
))}
</div>
</Card>
</Col>
</Row>
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
<Col span={24}>
<Card title="最近注册用户(样本)">
<Table<UserItem>
rowKey="id"
size="small"
pagination={false}
dataSource={recentUsers}
columns={userColumns as any}
/>
</Card>
</Col>
</Row>
<Card style={{ marginTop: 16 }}>
<Typography.Paragraph>
使 Udmin
</Typography.Paragraph>
</Card>
</div>
)
}