| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- import React, { useState, useEffect } from 'react';
- import { useNavigate, useLocation } from 'react-router';
- import { HomeAPI } from './api/index.ts';
- import { MessageAPI } from './api/index.ts';
- import {
- HomeIcon,
- UserIcon,
- NewspaperIcon,
- BellIcon
- } from '@heroicons/react/24/outline';
- import { useAuth } from './hooks.tsx';
- import { formatRelativeTime } from './utils.ts';
- import { KnowInfo, UserMessage, MessageType, MessageStatus } from '../share/types.ts';
- // 首页组件
- const HomePage: React.FC = () => {
- const { user } = useAuth();
- const navigate = useNavigate();
- const location = useLocation();
- const [loading, setLoading] = useState(true);
- const [banners, setBanners] = useState<KnowInfo[]>([]);
- const [news, setNews] = useState<KnowInfo[]>([]);
- const [notices, setNotices] = useState<UserMessage[]>([]);
- const [activeTab, setActiveTab] = useState('news');
-
- // 模拟加载数据
- useEffect(() => {
- const fetchData = async () => {
- try {
- // 获取数据
- const [bannersRes, newsRes, messagesRes] = await Promise.all([
- HomeAPI.getBanners(),
- HomeAPI.getNews(),
- MessageAPI.getMessages({ type: MessageType.ANNOUNCE })
- ]);
-
- setBanners(bannersRes.data.map((item: KnowInfo) => ({
- id: item.id,
- title: item.title,
- cover_url: item.cover_url,
- content: item.content,
- category: 'banner',
- created_at: new Date().toISOString(),
- updated_at: new Date().toISOString(),
- sort_order: item.sort_order || 0
- } as KnowInfo)));
- setNews(newsRes.data);
- setNotices(messagesRes.data);
-
- setLoading(false);
- } catch (error) {
- console.error('获取首页数据失败:', error);
- setLoading(false);
- }
- };
-
- fetchData();
- }, []);
-
- // 处理轮播图点击
- const handleBannerClick = (link: string) => {
- navigate(link);
- };
-
- // 处理新闻点击
- const handleNewsClick = (id: number) => {
- navigate(`/news/${id}`);
- };
-
- // 处理通知点击
- const handleNoticeClick = (id: number) => {
- navigate(`/notices/${id}`);
- };
-
- return (
- <div className="pb-16">
- {/* 顶部用户信息 */}
- <div className="bg-blue-600 text-white p-4">
- <div className="flex items-center justify-between">
- <div className="flex items-center space-x-3">
- <div className="w-12 h-12 rounded-full bg-white/20 flex items-center justify-center">
- {user?.avatar ? (
- <img
- src={user.avatar}
- alt={user?.nickname || user?.username || '用户'}
- className="w-10 h-10 rounded-full object-cover"
- />
- ) : (
- <UserIcon className="w-6 h-6" />
- )}
- </div>
- <div>
- <h2 className="text-lg font-medium">
- {user ? `您好,${user.nickname || user.username}` : '您好,游客'}
- </h2>
- <p className="text-sm text-white/80">
- {user ? '欢迎回来' : '请登录体验更多功能'}
- </p>
- </div>
- </div>
-
- <div className="relative">
- <BellIcon className="w-6 h-6" />
- {notices.some(notice => notice.user_status === MessageStatus.UNREAD) && (
- <span className="absolute top-0 right-0 w-2 h-2 bg-red-500 rounded-full"></span>
- )}
- </div>
- </div>
- </div>
-
- {/* 轮播图 */}
- {!loading && banners.length > 0 && (
- <div className="relative w-full h-40 overflow-hidden mt-2">
- <div className="flex transition-transform duration-300"
- style={{ transform: `translateX(-${0 * 100}%)` }}>
- {banners.map((banner) => (
- <div
- key={banner.id}
- className="w-full h-40 flex-shrink-0 relative"
- onClick={() => handleBannerClick(banner.content || '')}
- >
- <img
- src={banner.cover_url || ''}
- alt={banner.title}
- className="w-full h-full object-cover"
- />
- <div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent p-3">
- <h3 className="text-white font-medium">{banner.title}</h3>
- </div>
- </div>
- ))}
- </div>
-
- {/* 指示器 */}
- <div className="absolute bottom-2 left-0 right-0 flex justify-center space-x-1">
- {banners.map((_, index) => (
- <span
- key={index}
- className={`w-2 h-2 rounded-full ${index === 0 ? 'bg-white' : 'bg-white/50'}`}
- ></span>
- ))}
- </div>
- </div>
- )}
-
- {/* 快捷入口 */}
- <div className="grid grid-cols-4 gap-2 p-4 bg-white rounded-lg shadow mt-4 mx-2">
- {[
- { icon: <HomeIcon className="w-6 h-6" />, name: '首页', path: '/' },
- { icon: <NewspaperIcon className="w-6 h-6" />, name: '资讯', path: '/news' },
- { icon: <BellIcon className="w-6 h-6" />, name: '通知', path: '/notices' },
- { icon: <UserIcon className="w-6 h-6" />, name: '我的', path: '/profile' }
- ].map((item, index) => (
- <div
- key={index}
- className="flex flex-col items-center justify-center p-2"
- onClick={() => navigate(item.path)}
- >
- <div className="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 mb-1">
- {item.icon}
- </div>
- <span className="text-sm">{item.name}</span>
- </div>
- ))}
- </div>
-
- {/* 内容标签页 */}
- <div className="mt-4 mx-2">
- <div className="flex border-b border-gray-200">
- <button
- className={`flex-1 py-2 text-center ${activeTab === 'news' ? 'text-blue-600 border-b-2 border-blue-600 font-medium' : 'text-gray-500'}`}
- onClick={() => setActiveTab('news')}
- >
- 最新资讯
- </button>
- <button
- className={`flex-1 py-2 text-center ${activeTab === 'notices' ? 'text-blue-600 border-b-2 border-blue-600 font-medium' : 'text-gray-500'}`}
- onClick={() => setActiveTab('notices')}
- >
- 通知公告
- </button>
- </div>
-
- <div className="mt-2">
- {activeTab === 'news' ? (
- loading ? (
- <div className="flex justify-center p-4">
- <div className="w-6 h-6 border-2 border-gray-200 border-t-blue-600 rounded-full animate-spin"></div>
- </div>
- ) : (
- <div className="space-y-4">
- {news.map((item) => (
- <div
- key={item.id}
- className="bg-white p-3 rounded-lg shadow flex items-start space-x-3"
- onClick={() => handleNewsClick(item.id)}
- >
- {item.cover_url && (
- <img
- src={item.cover_url}
- alt={item.title}
- className="w-20 h-20 object-cover rounded-md flex-shrink-0"
- />
- )}
- <div className={item.cover_url ? '' : 'w-full'}>
- <h3 className="font-medium text-gray-900 line-clamp-2">{item.title}</h3>
- <p className="text-sm text-gray-500 mt-1 line-clamp-2">
- {item.content?.substring(0, 100)}...
- </p>
- <div className="flex justify-between items-center mt-2">
- <span className="text-xs text-blue-600 bg-blue-50 px-2 py-1 rounded-full">
- {item.category}
- </span>
- <span className="text-xs text-gray-400">
- {formatRelativeTime(item.created_at)}
- </span>
- </div>
- </div>
- </div>
- ))}
- </div>
- )
- ) : (
- loading ? (
- <div className="flex justify-center p-4">
- <div className="w-6 h-6 border-2 border-gray-200 border-t-blue-600 rounded-full animate-spin"></div>
- </div>
- ) : (
- <div className="space-y-3">
- {notices.map((item) => (
- <div
- key={item.id}
- className="bg-white p-3 rounded-lg shadow"
- onClick={() => handleNoticeClick(item.id)}
- >
- <div className="flex justify-between items-start">
- <h3 className={`font-medium ${item.user_status === MessageStatus.READ ? 'text-gray-700' : 'text-blue-600'}`}>
- {item.user_status === MessageStatus.UNREAD && (
- <span className="inline-block w-2 h-2 bg-blue-600 rounded-full mr-2"></span>
- )}
- {item.title}
- </h3>
- <span className="text-xs text-gray-400 mt-1">
- {formatRelativeTime(item.created_at)}
- </span>
- </div>
- <p className="text-sm text-gray-500 mt-2 line-clamp-2">{item.content}</p>
- </div>
- ))}
- </div>
- )
- )}
- </div>
- </div>
-
- {/* 底部导航 */}
- <div className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 flex justify-around py-2">
- {[
- { icon: <HomeIcon className="w-6 h-6" />, name: '首页', path: '/' },
- { icon: <NewspaperIcon className="w-6 h-6" />, name: '资讯', path: '/news' },
- { icon: <BellIcon className="w-6 h-6" />, name: '通知', path: '/notices' },
- { icon: <UserIcon className="w-6 h-6" />, name: '我的', path: '/profile' }
- ].map((item, index) => (
- <div
- key={index}
- className="flex flex-col items-center"
- onClick={() => navigate(item.path)}
- >
- <div className={`${location.pathname === item.path ? 'text-blue-600' : 'text-gray-500'}`}>
- {item.icon}
- </div>
- <span className={`text-xs mt-1 ${location.pathname === item.path ? 'text-blue-600' : 'text-gray-500'}`}>
- {item.name}
- </span>
- </div>
- ))}
- </div>
- </div>
- );
- };
- export default HomePage;
|