feat(dashboard): add classroom/attendance/income stat cards and trend charts
- Add second row of stat cards: classroom count, occupancy rate, attendance rate, monthly income - Add ECharts line charts for attendance trend (30-day) and income trend (6-month) - All fields sourced from existing GET /dashboard/stats response (Task 7.1 contract)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Row, Col, Card, Statistic, DatePicker, Spin, Grid } from 'antd';
|
||||
import { TeamOutlined, HomeOutlined, DollarOutlined, CheckCircleOutlined } from '@ant-design/icons';
|
||||
import { TeamOutlined, HomeOutlined, DollarOutlined, CheckCircleOutlined, BankOutlined, PercentageOutlined, UserSwitchOutlined } from '@ant-design/icons';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
import dayjs from 'dayjs';
|
||||
import api from '../../api';
|
||||
@@ -156,6 +156,49 @@ const DashboardPage: React.FC = () => {
|
||||
],
|
||||
};
|
||||
|
||||
// 考勤趋势折线图
|
||||
const attendanceLineOption = {
|
||||
tooltip: { trigger: 'axis' },
|
||||
grid: { left: 50, right: 20, bottom: 30, top: 10 },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: (stats?.attendanceTrend || []).map((d: { date: string }) => d.date),
|
||||
axisLabel: { rotate: 45, fontSize: 10 },
|
||||
},
|
||||
yAxis: { type: 'value', min: 0, max: 100, axisLabel: { formatter: '{value}%' } },
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
data: (stats?.attendanceTrend || []).map((d: { rate: string }) => parseFloat(d.rate) || 0),
|
||||
smooth: true,
|
||||
lineStyle: { color: '#007AFF', width: 2 },
|
||||
itemStyle: { color: '#007AFF' },
|
||||
areaStyle: { color: 'rgba(0,122,255,0.1)' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 收入趋势折线图
|
||||
const incomeLineOption = {
|
||||
tooltip: { trigger: 'axis', valueFormatter: (v: number) => `¥${v.toLocaleString()}` },
|
||||
grid: { left: 70, right: 20, bottom: 30, top: 10 },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: (stats?.incomeTrend || []).map((d: { month: string }) => d.month),
|
||||
},
|
||||
yAxis: { type: 'value', axisLabel: { formatter: (v: number) => `¥${(v / 10000).toFixed(0)}万` } },
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
data: (stats?.incomeTrend || []).map((d: { amount: number }) => d.amount),
|
||||
smooth: true,
|
||||
lineStyle: { color: '#34C759', width: 2 },
|
||||
itemStyle: { color: '#34C759' },
|
||||
areaStyle: { color: 'rgba(52,199,89,0.1)' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (loading && !stats)
|
||||
return <Spin size="large" style={{ display: 'block', margin: '100px auto' }} />;
|
||||
|
||||
@@ -217,6 +260,44 @@ const DashboardPage: React.FC = () => {
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||
<Col xs={12} sm={12} md={6}>
|
||||
<Card>
|
||||
<Statistic title="教室总数" value={stats?.classroomCount || 0} prefix={<BankOutlined />} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={12} md={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="教室占用率"
|
||||
value={stats?.classroomOccupancyRate || 0}
|
||||
suffix="%"
|
||||
prefix={<PercentageOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={12} md={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="今日出勤率"
|
||||
value={stats?.todayAttendanceRate || 0}
|
||||
suffix="%"
|
||||
prefix={<UserSwitchOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={12} md={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="本月收入"
|
||||
value={stats?.monthlyIncome || 0}
|
||||
precision={2}
|
||||
prefix="¥"
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Card title="入住时间线(甘特图)" style={{ marginBottom: 24 }}>
|
||||
{ganttData.length > 0 ? (
|
||||
<ReactECharts
|
||||
@@ -228,6 +309,27 @@ const DashboardPage: React.FC = () => {
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||
<Col xs={24} sm={12}>
|
||||
<Card title="考勤趋势(近30天)">
|
||||
{(stats?.attendanceTrend || []).length > 0 ? (
|
||||
<ReactECharts option={attendanceLineOption} style={{ width: '100%', height: isMobile ? 250 : 300 }} />
|
||||
) : (
|
||||
<div style={{ textAlign: 'center', padding: 40, color: '#999' }}>暂无考勤数据</div>
|
||||
)}
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12}>
|
||||
<Card title="收入趋势(近6个月)">
|
||||
{(stats?.incomeTrend || []).length > 0 ? (
|
||||
<ReactECharts option={incomeLineOption} style={{ width: '100%', height: isMobile ? 250 : 300 }} />
|
||||
) : (
|
||||
<div style={{ textAlign: 'center', padding: 40, color: '#999' }}>暂无收入数据</div>
|
||||
)}
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col xs={24} sm={12}>
|
||||
<Card title="费用类型分布">
|
||||
|
||||
Reference in New Issue
Block a user