205 lines
8.6 KiB
TypeScript
Raw Permalink Normal View History

2025-09-15 14:52:27 +08:00
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>
);
}