InfiniteCloud/components/NewsList.tsx
2025-09-15 14:52:27 +08:00

205 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Link from 'next/link';
import { useState, useMemo } from 'react';
import { NewsItem } from '../lib/content';
import { Search, Filter, Calendar, Tag, ArrowRight, Clock } from 'lucide-react';
export default function NewsList({ news }: { news: NewsItem[] }) {
const [searchTerm, setSearchTerm] = useState('');
const [selectedCategory, setSelectedCategory] = useState('全部');
const [sortBy, setSortBy] = useState('date');
// 获取所有分类
const categories = useMemo(() => {
const allTags = news.flatMap(item => item.tags || []);
const uniqueTags = Array.from(new Set(allTags));
return ['全部', ...uniqueTags];
}, [news]);
// 过滤和排序新闻
const filteredNews = useMemo(() => {
let filtered = news.filter(item => {
const matchesSearch = item.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.summary?.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.tags?.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()));
const matchesCategory = selectedCategory === '全部' || item.tags?.includes(selectedCategory);
return matchesSearch && matchesCategory;
});
// 排序
filtered.sort((a, b) => {
switch (sortBy) {
case 'date':
return new Date(b.date).getTime() - new Date(a.date).getTime();
case 'title':
return a.title.localeCompare(b.title);
default:
return 0;
}
});
return filtered;
}, [news, searchTerm, selectedCategory, sortBy]);
return (
<section className="px-6 py-16 bg-gray-50">
<div className="max-w-screen-xl mx-auto">
{/* 搜索和筛选区域 */}
<div className="bg-white rounded-2xl p-6 shadow-sm mb-8">
<div className="flex flex-col lg:flex-row gap-4 items-center">
{/* 搜索框 */}
<div className="flex-1 relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
placeholder="搜索新闻标题、内容或标签..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-accent focus:border-transparent outline-none"
/>
</div>
{/* 分类筛选 */}
<div className="flex items-center gap-2">
<Filter className="w-5 h-5 text-gray-400" />
<select
value={selectedCategory}
onChange={(e) => setSelectedCategory(e.target.value)}
className="px-4 py-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-accent focus:border-transparent outline-none"
>
{categories.map(category => (
<option key={category} value={category}>{category}</option>
))}
</select>
</div>
{/* 排序选择 */}
<div className="flex items-center gap-2">
<Calendar className="w-5 h-5 text-gray-400" />
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="px-4 py-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-accent focus:border-transparent outline-none"
>
<option value="date"></option>
<option value="title"></option>
</select>
</div>
</div>
{/* 搜索结果统计 */}
<div className="mt-4 pt-4 border-t border-gray-100">
<p className="text-sm text-gray-600">
<span className="font-semibold text-accent">{filteredNews.length}</span>
{searchTerm && (
<span><span className="font-semibold">"{searchTerm}"</span></span>
)}
{selectedCategory !== '全部' && (
<span><span className="font-semibold">{selectedCategory}</span></span>
)}
</p>
</div>
</div>
{/* 新闻列表 */}
{filteredNews.length === 0 ? (
<div className="text-center py-16">
<div className="w-24 h-24 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
<Search className="w-12 h-12 text-gray-400" />
</div>
<h3 className="text-xl font-semibold text-gray-600 mb-2"></h3>
<p className="text-gray-500 mb-6"></p>
<button
onClick={() => {
setSearchTerm('');
setSelectedCategory('全部');
}}
className="px-6 py-3 bg-accent text-white rounded-xl hover:bg-blue-700 transition-colors"
>
</button>
</div>
) : (
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
{filteredNews.map((item, index) => (
<article
key={item.slug}
className="group bg-white rounded-2xl shadow-sm hover:shadow-xl transition-all duration-300 border border-gray-100 hover:border-accent/20 overflow-hidden"
>
<div className="p-6">
{/* 新闻头部 */}
<div className="flex items-start justify-between mb-4">
<div className="flex items-center gap-2">
<Clock className="w-4 h-4 text-gray-400" />
<time className="text-sm text-gray-500">{item.date}</time>
</div>
{index < 3 && (
<span className="bg-gradient-to-r from-accent to-blue-600 text-white text-xs font-semibold px-3 py-1 rounded-full">
</span>
)}
</div>
{/* 新闻标题 */}
<h3 className="text-xl font-semibold text-primary mb-3 group-hover:text-accent transition-colors line-clamp-2">
<Link href={`/news/${item.slug}`} className="hover:underline">
{item.title}
</Link>
</h3>
{/* 新闻摘要 */}
{item.summary && (
<p className="text-gray-600 text-sm line-clamp-3 mb-4 leading-relaxed">
{item.summary}
</p>
)}
{/* 标签 */}
{item.tags && item.tags.length > 0 && (
<div className="flex flex-wrap gap-2 mb-4">
{item.tags.map(tag => (
<span
key={tag}
className="inline-flex items-center gap-1 px-3 py-1 text-xs bg-gray-100 text-gray-600 rounded-full hover:bg-accent hover:text-white transition-colors cursor-pointer"
onClick={() => setSelectedCategory(tag)}
>
<Tag className="w-3 h-3" />
{tag}
</span>
))}
</div>
)}
{/* 阅读更多按钮 */}
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
<Link
href={`/news/${item.slug}`}
className="inline-flex items-center gap-2 text-accent hover:text-blue-700 font-medium text-sm group-hover:gap-3 transition-all"
>
<ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
</Link>
<div className="text-xs text-gray-400">
{Math.ceil(Math.random() * 5) + 1}
</div>
</div>
</div>
</article>
))}
</div>
)}
{/* 加载更多按钮(如果新闻很多的话) */}
{filteredNews.length > 6 && (
<div className="text-center mt-12">
<button className="px-8 py-3 bg-white border border-gray-200 text-primary rounded-xl hover:bg-gray-50 hover:border-accent transition-all duration-300 font-medium">
</button>
</div>
)}
</div>
</section>
);
}