import React, { useEffect, useState } from 'react';
|
import { Row, Col, Card, Statistic, Button, Table, List, Avatar, Tag, message, Modal } from 'antd';
|
import { useNavigate } from 'react-router-dom';
|
import {
|
UserOutlined,
|
GiftOutlined,
|
CalendarOutlined,
|
TeamOutlined,
|
PlusOutlined,
|
EyeOutlined,
|
EditOutlined,
|
} from '@ant-design/icons';
|
import ReactECharts from 'echarts-for-react';
|
import { statisticsAPI, dashboardAPI } from '../services/api';
|
import dayjs from 'dayjs';
|
|
const Dashboard = () => {
|
const navigate = useNavigate();
|
const [loading, setLoading] = useState(false);
|
const [overviewData, setOverviewData] = useState({
|
totalVolunteers: 0,
|
totalPoints: 0,
|
totalActivities: 0,
|
totalRegistrations: 0,
|
});
|
const [recentActivities, setRecentActivities] = useState([]);
|
const [recentVolunteers, setRecentVolunteers] = useState([]);
|
const [pointsTrendData, setPointsTrendData] = useState({
|
categories: [],
|
totalPoints: [],
|
});
|
const [activityParticipationData, setActivityParticipationData] = useState([]);
|
const [viewModalVisible, setViewModalVisible] = useState(false);
|
const [viewingActivity, setViewingActivity] = useState(null);
|
|
// 获取数据概览
|
const fetchOverviewData = async () => {
|
try {
|
const response = await statisticsAPI.getOverview();
|
if (response.code === 0) {
|
setOverviewData(response.data);
|
}
|
} catch (error) {
|
console.error('获取概览数据失败:', error);
|
message.error('获取概览数据失败');
|
}
|
};
|
|
// 获取最新活动列表
|
const fetchRecentActivities = async () => {
|
try {
|
const response = await dashboardAPI.getRecentActivities(5);
|
if (response.code === 0) {
|
setRecentActivities(response.data);
|
}
|
} catch (error) {
|
console.error('获取最新活动失败:', error);
|
message.error('获取最新活动失败');
|
}
|
};
|
|
// 获取最新志愿者列表
|
const fetchRecentVolunteers = async () => {
|
try {
|
const response = await dashboardAPI.getRecentVolunteers(5);
|
if (response.code === 0) {
|
setRecentVolunteers(response.data);
|
}
|
} catch (error) {
|
console.error('获取最新志愿者失败:', error);
|
message.error('获取最新志愿者失败');
|
}
|
};
|
|
// 获取积分趋势数据
|
const fetchPointsTrend = async () => {
|
try {
|
const response = await statisticsAPI.getPointsTrend('month');
|
if (response.code === 0) {
|
setPointsTrendData(response.data);
|
}
|
} catch (error) {
|
console.error('获取积分趋势失败:', error);
|
message.error('获取积分趋势失败');
|
}
|
};
|
|
// 获取活动参与度数据
|
const fetchActivityParticipation = async () => {
|
try {
|
const response = await statisticsAPI.getActivityParticipation();
|
if (response.code === 0) {
|
setActivityParticipationData(response.data);
|
}
|
} catch (error) {
|
console.error('获取活动参与度失败:', error);
|
message.error('获取活动参与度失败');
|
}
|
};
|
|
// 页面初始化加载数据
|
useEffect(() => {
|
const loadData = async () => {
|
setLoading(true);
|
try {
|
await Promise.all([
|
fetchOverviewData(),
|
fetchRecentActivities(),
|
fetchRecentVolunteers(),
|
fetchPointsTrend(),
|
fetchActivityParticipation(),
|
]);
|
} finally {
|
setLoading(false);
|
}
|
};
|
|
loadData();
|
}, []);
|
|
// 积分趋势图表配置
|
const pointsTrendOption = {
|
title: {
|
text: '积分趋势',
|
left: 'center',
|
},
|
tooltip: {
|
trigger: 'axis',
|
},
|
xAxis: {
|
type: 'category',
|
data: pointsTrendData.categories,
|
},
|
yAxis: {
|
type: 'value',
|
},
|
series: [
|
{
|
name: '积分总数',
|
type: 'line',
|
data: pointsTrendData.totalPoints,
|
smooth: true,
|
itemStyle: {
|
color: '#1890ff',
|
},
|
},
|
],
|
};
|
|
// 活动参与度图表配置
|
const activityParticipationOption = {
|
title: {
|
text: '活动参与度',
|
left: 'center',
|
},
|
tooltip: {
|
trigger: 'item',
|
},
|
series: [
|
{
|
name: '参与情况',
|
type: 'pie',
|
radius: '50%',
|
data: activityParticipationData,
|
emphasis: {
|
itemStyle: {
|
shadowBlur: 10,
|
shadowOffsetX: 0,
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
},
|
},
|
},
|
],
|
};
|
|
// 查看活动详情
|
const handleViewActivity = (activity) => {
|
setViewingActivity(activity);
|
setViewModalVisible(true);
|
};
|
|
const handleModalCancel = () => {
|
setViewModalVisible(false);
|
setViewingActivity(null);
|
};
|
|
// 活动状态转换函数
|
const getActivityStatusText = (status) => {
|
switch (status) {
|
case '1':
|
return '未开始';
|
case '2':
|
return '进行中';
|
case '3':
|
return '已结束';
|
default:
|
return status; // 如果不是数字状态码,直接返回原值
|
}
|
};
|
|
const getStatusColor = (status) => {
|
switch (status) {
|
case '1':
|
case '未开始':
|
return 'default';
|
case '2':
|
case '进行中':
|
return 'processing';
|
case '3':
|
case '已结束':
|
return 'default';
|
case '报名中':
|
return 'warning';
|
case '活跃':
|
return 'success';
|
default:
|
return 'default';
|
}
|
};
|
|
const quickActions = [
|
{
|
title: '发布活动',
|
icon: <PlusOutlined />,
|
color: '#1890ff',
|
onClick: () => navigate('/activities/create'),
|
},
|
{
|
title: '查看志愿者',
|
icon: <TeamOutlined />,
|
color: '#52c41a',
|
onClick: () => navigate('/volunteers'),
|
},
|
{
|
title: '积分查询',
|
icon: <GiftOutlined />,
|
color: '#faad14',
|
onClick: () => navigate('/volunteers/points'),
|
},
|
{
|
title: '数据统计',
|
icon: <CalendarOutlined />,
|
color: '#722ed1',
|
onClick: () => navigate('/statistics/overview'),
|
},
|
];
|
|
return (
|
<div>
|
<div className="page-header">
|
<h1 className="page-title">主控制台</h1>
|
<p className="page-description">欢迎使用志愿者服务后台管理系统</p>
|
</div>
|
|
{/* 数据概览 */}
|
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
<Col xs={24} sm={12} lg={6}>
|
<Card>
|
<Statistic
|
title="志愿者总数"
|
value={overviewData.totalVolunteers}
|
prefix={<UserOutlined />}
|
valueStyle={{ color: '#1890ff' }}
|
/>
|
</Card>
|
</Col>
|
<Col xs={24} sm={12} lg={6}>
|
<Card>
|
<Statistic
|
title="积分总数"
|
value={overviewData.totalPoints}
|
prefix={<GiftOutlined />}
|
valueStyle={{ color: '#52c41a' }}
|
/>
|
</Card>
|
</Col>
|
<Col xs={24} sm={12} lg={6}>
|
<Card>
|
<Statistic
|
title="活动总数"
|
value={overviewData.totalActivities}
|
prefix={<CalendarOutlined />}
|
valueStyle={{ color: '#faad14' }}
|
/>
|
</Card>
|
</Col>
|
<Col xs={24} sm={12} lg={6}>
|
<Card>
|
<Statistic
|
title="报名总数"
|
value={overviewData.totalRegistrations}
|
prefix={<TeamOutlined />}
|
valueStyle={{ color: '#722ed1' }}
|
/>
|
</Card>
|
</Col>
|
</Row>
|
|
{/* 快捷操作 */}
|
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
<Col span={24}>
|
<Card title="快捷操作">
|
<Row gutter={[16, 16]}>
|
{quickActions.map((action, index) => (
|
<Col xs={12} sm={6} key={index}>
|
<Button
|
type="dashed"
|
size="large"
|
icon={action.icon}
|
onClick={action.onClick}
|
style={{
|
width: '100%',
|
height: 80,
|
borderColor: action.color,
|
color: action.color,
|
}}
|
>
|
{action.title}
|
</Button>
|
</Col>
|
))}
|
</Row>
|
</Card>
|
</Col>
|
</Row>
|
|
{/* 图表区域 */}
|
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
<Col xs={24} lg={12}>
|
<Card>
|
<ReactECharts option={pointsTrendOption} style={{ height: 300 }} />
|
</Card>
|
</Col>
|
<Col xs={24} lg={12}>
|
<Card>
|
<ReactECharts option={activityParticipationOption} style={{ height: 300 }} />
|
</Card>
|
</Col>
|
</Row>
|
|
{/* 最新活动和志愿者 */}
|
<Row gutter={[16, 16]}>
|
<Col xs={24} lg={12}>
|
<Card
|
title="最新活动"
|
extra={<Button type="link" onClick={() => navigate('/activities')}>查看全部</Button>}
|
>
|
<List
|
dataSource={recentActivities}
|
renderItem={(item) => (
|
<List.Item
|
actions={[
|
<Button type="link" icon={<EyeOutlined />} onClick={() => handleViewActivity(item)}>
|
查看
|
</Button>
|
]}
|
>
|
<List.Item.Meta
|
title={item.title}
|
description={`${dayjs(item.date).format('YYYY-MM-DD HH:mm')} | 参与人数: ${item.participants}`}
|
/>
|
<Tag color={getStatusColor(item.status)}>{getActivityStatusText(item.status)}</Tag>
|
</List.Item>
|
)}
|
/>
|
</Card>
|
</Col>
|
<Col xs={24} lg={12}>
|
<Card
|
title="最新志愿者"
|
extra={<Button type="link" onClick={() => navigate('/volunteers')}>查看全部</Button>}
|
>
|
<List
|
dataSource={recentVolunteers}
|
renderItem={(item) => (
|
<List.Item
|
actions={[
|
<Button type="link" icon={<EyeOutlined />} onClick={() => navigate(`/volunteers/${item.id}`)}>
|
查看
|
</Button>
|
]}
|
>
|
<List.Item.Meta
|
avatar={<Avatar icon={<UserOutlined />} />}
|
title={item.name}
|
description={`加入时间: ${dayjs(item.joinDate).format('YYYY-MM-DD HH:mm')} | 积分: ${item.points}`}
|
/>
|
<Tag color={getStatusColor(item.status)}>{item.status}</Tag>
|
</List.Item>
|
)}
|
/>
|
</Card>
|
</Col>
|
</Row>
|
|
{/* 查看活动详情弹窗 */}
|
<Modal
|
title="查看活动详情"
|
open={viewModalVisible}
|
onCancel={handleModalCancel}
|
footer={[
|
<Button key="close" onClick={handleModalCancel}>
|
关闭
|
</Button>
|
]}
|
width={800}
|
>
|
{viewingActivity && (
|
<div>
|
<Row gutter={[24, 16]}>
|
<Col span={12}>
|
<div><strong>活动名称:</strong>{viewingActivity.title}</div>
|
</Col>
|
<Col span={12}>
|
<div><strong>活动分类:</strong>{viewingActivity.categoryDesc || '未分类'}</div>
|
</Col>
|
</Row>
|
<Row gutter={[24, 16]}>
|
<Col span={12}>
|
<div><strong>活动开始时间:</strong>{dayjs(viewingActivity.startTime || viewingActivity.date).format('YYYY-MM-DD HH:mm')}</div>
|
</Col>
|
<Col span={12}>
|
<div><strong>活动结束时间:</strong>{dayjs(viewingActivity.endTime).format('YYYY-MM-DD HH:mm')}</div>
|
</Col>
|
</Row>
|
<Row gutter={[24, 16]}>
|
<Col span={12}>
|
<div><strong>活动地点:</strong>{viewingActivity.location || '未设置'}</div>
|
</Col>
|
<Col span={12}>
|
<div><strong>报名截止时间:</strong>{dayjs(viewingActivity.deadline).format('YYYY-MM-DD HH:mm')}</div>
|
</Col>
|
</Row>
|
<Row gutter={[24, 16]}>
|
<Col span={8}>
|
<div><strong>最大志愿者人数:</strong>{viewingActivity.maxParticipants || '未设置'}</div>
|
</Col>
|
<Col span={8}>
|
<div><strong>活动积分:</strong>{viewingActivity.points || 0}</div>
|
</Col>
|
<Col span={8}>
|
<div><strong>状态:</strong>{getActivityStatusText(viewingActivity.status)}</div>
|
</Col>
|
</Row>
|
<Row gutter={[24, 16]}>
|
<Col span={12}>
|
<div><strong>创建时间:</strong>{dayjs(viewingActivity.createTime).format('YYYY-MM-DD HH:mm')}</div>
|
</Col>
|
<Col span={12}>
|
<div><strong>参与人数:</strong>{viewingActivity.participants || 0}</div>
|
</Col>
|
</Row>
|
{viewingActivity.content && (
|
<Row gutter={[24, 16]}>
|
<Col span={24}>
|
<div><strong>活动内容:</strong></div>
|
<div style={{ marginTop: '8px', padding: '12px', backgroundColor: '#f5f5f5', borderRadius: '4px' }}>
|
{viewingActivity.content}
|
</div>
|
</Col>
|
</Row>
|
)}
|
{viewingActivity.cover && (
|
<Row gutter={[24, 16]}>
|
<Col span={24}>
|
<div><strong>活动封面:</strong></div>
|
<div style={{ marginTop: '8px' }}>
|
<img src={viewingActivity.cover} alt="活动封面" style={{ maxWidth: '200px', borderRadius: '4px' }} />
|
</div>
|
</Col>
|
</Row>
|
)}
|
</div>
|
)}
|
</Modal>
|
</div>
|
);
|
};
|
|
export default Dashboard;
|