127 lines
4.3 KiB
JavaScript
127 lines
4.3 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import { checkinService } from '../services/api';
|
|
import CheckinForm from '../components/CheckinForm';
|
|
import CheckinList from '../components/CheckinList';
|
|
import MapView from '../components/MapView';
|
|
|
|
const HomePage = ({ user }) => {
|
|
const [checkins, setCheckins] = useState([]);
|
|
const [stats, setStats] = useState(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [view, setView] = useState('list'); // 'list' or 'map'
|
|
|
|
useEffect(() => {
|
|
loadCheckins();
|
|
loadStats();
|
|
}, []);
|
|
|
|
const loadCheckins = async () => {
|
|
try {
|
|
const response = await checkinService.getAll({ limit: 100 });
|
|
setCheckins(response.data.checkins);
|
|
} catch (error) {
|
|
console.error('加载打卡记录失败:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const loadStats = async () => {
|
|
try {
|
|
const response = await checkinService.getStats();
|
|
setStats(response.data.stats);
|
|
} catch (error) {
|
|
console.error('加载统计信息失败:', error);
|
|
}
|
|
};
|
|
|
|
const handleCheckinSuccess = (newCheckin) => {
|
|
setCheckins([newCheckin, ...checkins]);
|
|
loadStats();
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-50">
|
|
{/* 头部 */}
|
|
<header className="bg-white shadow-sm">
|
|
<div className="max-w-7xl mx-auto px-4 py-4 sm:px-6 lg:px-8">
|
|
<div className="flex justify-between items-center">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-gray-900">📍 Footprint</h1>
|
|
<p className="text-sm text-gray-600">你好, {user.username}</p>
|
|
</div>
|
|
<div className="flex items-center gap-4">
|
|
{stats && (
|
|
<div className="text-right text-sm">
|
|
<p className="text-gray-600">
|
|
总打卡: <span className="font-semibold">{stats.total_checkins}</span>
|
|
</p>
|
|
<p className="text-gray-600">
|
|
打卡天数: <span className="font-semibold">{stats.total_days}</span>
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
{/* 主内容 */}
|
|
<main className="max-w-7xl mx-auto px-4 py-8 sm:px-6 lg:px-8">
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
{/* 左侧:打卡表单 */}
|
|
<div className="lg:col-span-1">
|
|
<CheckinForm onSuccess={handleCheckinSuccess} />
|
|
</div>
|
|
|
|
{/* 右侧:打卡记录或地图 */}
|
|
<div className="lg:col-span-2">
|
|
<div className="bg-white rounded-lg shadow-sm p-6">
|
|
{/* 视图切换 */}
|
|
<div className="flex justify-between items-center mb-6">
|
|
<h2 className="text-xl font-semibold text-gray-900">我的足迹</h2>
|
|
<div className="flex gap-2">
|
|
<button
|
|
onClick={() => setView('list')}
|
|
className={`px-4 py-2 rounded-lg transition-colors ${
|
|
view === 'list'
|
|
? 'bg-blue-600 text-white'
|
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
}`}
|
|
>
|
|
列表
|
|
</button>
|
|
<button
|
|
onClick={() => setView('map')}
|
|
className={`px-4 py-2 rounded-lg transition-colors ${
|
|
view === 'map'
|
|
? 'bg-blue-600 text-white'
|
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
}`}
|
|
>
|
|
地图
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 内容区域 */}
|
|
{loading ? (
|
|
<div className="text-center py-12">
|
|
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
|
<p className="mt-2 text-gray-600">加载中...</p>
|
|
</div>
|
|
) : view === 'list' ? (
|
|
<CheckinList checkins={checkins} onUpdate={loadCheckins} />
|
|
) : (
|
|
<MapView checkins={checkins} />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default HomePage;
|