205 lines
8.6 KiB
TypeScript
205 lines
8.6 KiB
TypeScript
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>
|
||
);
|
||
} |