| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- "use client";
- import React, {useState, useEffect, useMemo} from 'react';
- import './NewsPage.css';
- import Link from "next/link";
- // 1. 抽离类型定义,提升可维护性
- interface Article {
- id: string;
- newsUrl: string;
- newsName: string;
- newsDesc: string;
- newsAuthor?: string; // 可选字段
- content: string;
- newsCategory: string; // 补充分类字段(原逻辑依赖)
- }
- // 2. 定义Props类型,避免隐式any
- interface NewsItemProps {
- news: Article,
- key?: string
- }
- interface NewsListProps {
- newsList: Article[];
- }
- interface NewsPageProps {
- newsList: Article[];
- }
- // 3. 抽离常量,便于统一维护
- const CATEGORY_MAP = Object.freeze({
- company: '公司新闻',
- industry: '行内新闻'
- } as const); // 常量断言,锁定类型
- // 4. 新闻列表项组件(纯组件,优化性能)
- const NewsItem: React.FC<NewsItemProps> = ({news: article, key}) => {
- // 环境变量默认值兜底,避免undefined
- const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || '';
- // 图片地址处理抽离,提升可读性
- const coverImageUrl = article.newsUrl ? `${BASE_URL}${article.newsUrl}` : '';
- return (
- <div className="news-item">
- <div className="news-cover">
- <img
- src={coverImageUrl}
- alt={article.newsName} // 补充有意义的alt属性,优化可访问性
- loading="lazy"
- onError={(e) => {
- // 图片加载失败兜底,提升用户体验
- (e.target as HTMLImageElement).src = '/default-news-cover.png';
- }}
- />
- </div>
- <div className="news-content-wrapper">
- <h3 className="news-title">{article.newsName}</h3>
- <div className="news-meta">
- <span className="news-date">{article.newsDesc}</span>
- {/* 可选字段兜底,避免空值展示 */}
- <span className="news-author">作者:{article.newsAuthor || '未知'}</span>
- </div>
- <p className="news-content">{article.content}</p>
- </div>
- <Link
- href={`/news/${article.id}`}
- className="text-blue-500 hover:underline mt-4 self-start text-sm sm:text-base"
- aria-label={`查看${article.newsName}详情`} // 优化可访问性
- >
- 了解更多 >
- </Link>
- </div>
- );
- };
- // 5. 新闻列表组件(添加空状态优化)
- const NewsList: React.FC<NewsListProps> = ({newsList}) => {
- // 空数组判断优化,避免length判断出错
- if (!Array.isArray(newsList) || newsList.length === 0) {
- return <div className="no-news">暂无相关新闻</div>;
- }
- return (
- <div className="news-list">
- {newsList.map((news) => (
- <NewsItem
- key={news.id}
- news={news}
- />
- ))}
- </div>
- );
- };
- // 6. 主新闻页面组件
- const NewsPage: React.FC<NewsPageProps> = ({newsList = []}) => {
- // 类型守卫,确保newsList是数组类型
- const safeNewsList = useMemo(() =>
- Array.isArray(newsList) ? newsList : [],
- [newsList]);
- const [activeTab, setActiveTab] = useState<keyof typeof CATEGORY_MAP>('company'); // 严格类型约束
- const [filteredNews, setFilteredNews] = useState<Article[]>([]);
- const [isLoading, setIsLoading] = useState(true);
- // 7. 优化筛选逻辑:使用useMemo减少重复计算
- useEffect(() => {
- setIsLoading(true);
- const timer = setTimeout(() => {
- const targetCategory = CATEGORY_MAP[activeTab];
- const filtered = safeNewsList.filter(
- (news) => news.newsCategory === targetCategory
- );
- setFilteredNews(filtered);
- setIsLoading(false);
- }, 300);
- return () => clearTimeout(timer);
- }, [safeNewsList, activeTab]);
- // 8. 抽离标签切换逻辑,简化代码
- const handleTabChange = (tab: keyof typeof CATEGORY_MAP) => {
- setIsLoading(true);
- setActiveTab(tab);
- };
- return (
- <div className="news-page">
- <div className="news-tabs">
- <button
- className={`tab-btn ${activeTab === 'company' ? 'active' : ''}`}
- onClick={() => handleTabChange('company')}
- aria-pressed={activeTab === 'company'} // 优化可访问性
- >
- 公司新闻
- </button>
- <button
- className={`tab-btn ${activeTab === 'industry' ? 'active' : ''}`}
- onClick={() => handleTabChange('industry')}
- aria-pressed={activeTab === 'industry'}
- >
- 行内新闻
- </button>
- </div>
- {isLoading && <div className="loading">加载中...</div>}
- {!isLoading && <NewsList newsList={filteredNews}/>}
- </div>
- );
- };
- export default NewsPage;
|