123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- import React, { useState, useEffect } from 'react';
- import { Link } from 'react-router-dom';
- import { Row, Col, Card, Statistic, List, Tag, Button, Spin, Empty } from 'antd';
- import {
- CheckCircleOutlined,
- ClockCircleOutlined,
- ExclamationCircleOutlined,
- ScheduleOutlined,
- RobotOutlined
- } from '@ant-design/icons';
- import axios from 'axios';
- import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js';
- import { Doughnut } from 'react-chartjs-2';
- import MoodSelector from '../components/MoodSelector';
- ChartJS.register(ArcElement, Tooltip, Legend);
- const Dashboard = () => {
- const [loading, setLoading] = useState(true);
- const [stats, setStats] = useState({
- totalTasks: 0,
- completedTasks: 0,
- pendingTasks: 0,
- importanceCounts: [],
- });
- const [recentTasks, setRecentTasks] = useState([]);
- const [schedules, setSchedules] = useState([]);
- const [todayMood, setTodayMood] = useState(null);
- useEffect(() => {
- const fetchDashboardData = async () => {
- try {
- setLoading(true);
-
- // 获取任务数据
- const tasksRes = await axios.get('/api/tasks');
- const tasks = tasksRes.data;
-
- // 获取时间安排数据
- const schedulesRes = await axios.get('/api/schedules');
- const allSchedules = schedulesRes.data;
-
- // 获取心情数据
- const moodsRes = await axios.get('/api/moods');
- const moods = moodsRes.data;
-
- // 计算统计数据
- const completedTasks = tasks.filter(task => task.completed).length;
- const pendingTasks = tasks.filter(task => !task.completed).length;
-
- // 按重要性分组
- const importanceCounts = Array(5).fill(0);
- tasks.forEach(task => {
- if (task.importance >= 1 && task.importance <= 5) {
- importanceCounts[task.importance - 1]++;
- }
- });
-
- // 获取最近任务(按创建日期排序)
- const sortedTasks = [...tasks].sort((a, b) =>
- new Date(b.created_date) - new Date(a.created_date)
- ).slice(0, 5);
-
- // 当前星期几(1-5对应周一到周五)
- const today = new Date().getDay();
- const dayOfWeek = today === 0 ? 5 : today === 6 ? 5 : today;
-
- // 过滤出今天的时间安排
- const todaySchedules = allSchedules.filter(
- schedule => schedule.day_of_week === dayOfWeek
- ).sort((a, b) => a.time_slot.localeCompare(b.time_slot));
-
- // 查找今天的心情
- const todayStr = new Date().toISOString().split('T')[0];
- const todayMoodRecord = moods.find(mood => mood.date === todayStr);
-
- setStats({
- totalTasks: tasks.length,
- completedTasks,
- pendingTasks,
- importanceCounts,
- });
- setRecentTasks(sortedTasks);
- setSchedules(todaySchedules);
- setTodayMood(todayMoodRecord?.mood_value || null);
-
- } catch (error) {
- console.error('获取仪表盘数据失败:', error);
- } finally {
- setLoading(false);
- }
- };
- fetchDashboardData();
- }, []);
- // 饼图数据
- const chartData = {
- labels: ['非常低', '低', '中等', '高', '非常高'],
- datasets: [
- {
- data: stats.importanceCounts,
- backgroundColor: [
- '#e5e7eb',
- '#bfdbfe',
- '#c7d2fe',
- '#fef3c7',
- '#fee2e2',
- ],
- borderColor: [
- '#d1d5db',
- '#93c5fd',
- '#a5b4fc',
- '#fde68a',
- '#fca5a5',
- ],
- borderWidth: 1,
- },
- ],
- };
- // 获取重要性标签
- const getImportanceTag = (importance) => {
- const colors = ['default', 'blue', 'purple', 'warning', 'error'];
- const labels = ['非常低', '低', '中等', '高', '非常高'];
-
- return (
- <Tag color={colors[importance - 1] || 'default'}>
- {labels[importance - 1] || '未知'}
- </Tag>
- );
- };
- // 心情选择回调
- const handleMoodSelect = async (value) => {
- try {
- await axios.post('/api/moods', { mood_value: value });
- setTodayMood(value);
- } catch (error) {
- console.error('记录心情失败:', error);
- }
- };
- if (loading) {
- return (
- <div className="flex items-center justify-center h-64">
- <Spin size="large" />
- </div>
- );
- }
- return (
- <div className="fade-in">
- <h1 className="text-2xl font-bold mb-6">仪表盘</h1>
-
- {/* 统计卡片 */}
- <Row gutter={[16, 16]}>
- <Col xs={24} sm={8}>
- <Card className="hover-card">
- <Statistic
- title="总任务数"
- value={stats.totalTasks}
- prefix={<ScheduleOutlined />}
- />
- </Card>
- </Col>
- <Col xs={24} sm={8}>
- <Card className="hover-card">
- <Statistic
- title="已完成任务"
- value={stats.completedTasks}
- valueStyle={{ color: '#3f8600' }}
- prefix={<CheckCircleOutlined />}
- />
- </Card>
- </Col>
- <Col xs={24} sm={8}>
- <Card className="hover-card">
- <Statistic
- title="待完成任务"
- value={stats.pendingTasks}
- valueStyle={{ color: '#cf1322' }}
- prefix={<ClockCircleOutlined />}
- />
- </Card>
- </Col>
- </Row>
-
- {/* 今日心情 */}
- <Card title="今日心情" className="mt-6 hover-card">
- <MoodSelector value={todayMood} onChange={handleMoodSelect} />
- </Card>
-
- <Row gutter={[16, 16]} className="mt-6">
- {/* 最近任务 */}
- <Col xs={24} md={16}>
- <Card
- title="最近任务"
- className="hover-card"
- extra={<Link to="/tasks">查看全部</Link>}
- >
- {recentTasks.length > 0 ? (
- <List
- dataSource={recentTasks}
- renderItem={task => (
- <List.Item className="slide-in">
- <List.Item.Meta
- title={
- <div className="flex items-center">
- <span className={task.completed ? 'line-through text-gray-500' : ''}>
- {task.name}
- </span>
- <div className="ml-2">
- {getImportanceTag(task.importance)}
- {task.completed &&
- <Tag color="success\" className="ml-1">已完成</Tag>
- }
- </div>
- </div>
- }
- description={
- <div>
- <p className="text-gray-500">
- 预计完成时间: {task.expected_completion_date}
- </p>
- {task.notes && <p className="text-gray-500 truncate">{task.notes}</p>}
- </div>
- }
- />
- </List.Item>
- )}
- />
- ) : (
- <Empty description="暂无任务" />
- )}
- </Card>
- </Col>
-
- {/* 任务重要性分布 */}
- <Col xs={24} md={8}>
- <Card title="任务重要性分布" className="hover-card h-full">
- {stats.totalTasks > 0 ? (
- <div className="flex justify-center">
- <div style={{ width: 320, height: 320, margin: '0 auto' }}>
- <Doughnut data={chartData} width={320} height={320} />
- </div>
- </div>
- ) : (
- <Empty description="暂无数据" />
- )}
- </Card>
- </Col>
- </Row>
-
- {/* 今日安排 */}
- <Card
- title="今日安排"
- className="mt-6 hover-card"
- extra={<Link to="/schedule">查看全部</Link>}
- >
- {schedules.length > 0 ? (
- <List
- dataSource={schedules}
- renderItem={schedule => (
- <List.Item className="slide-in">
- <List.Item.Meta
- title={
- <div className="flex items-center">
- <span className={schedule.completed ? 'line-through text-gray-500' : ''}>
- {schedule.time_slot} - {schedule.activity}
- </span>
- {schedule.completed &&
- <Tag color="success" className="ml-2">已完成</Tag>
- }
- </div>
- }
- />
- </List.Item>
- )}
- />
- ) : (
- <Empty description="今日无安排" />
- )}
- </Card>
-
- {/* AI助手链接 */}
- <div className="mt-6 flex justify-center">
- <Link to="/ai-chat">
- <Button type="primary" icon={<RobotOutlined />} size="large">
- 咨询AI助手
- </Button>
- </Link>
- </div>
- </div>
- );
- };
- export default Dashboard;
|