feat: 完善活动资讯页面功能

- 集成评论区到详情页
- 添加活动报名功能
- 优化资讯卡片展示
- 改进活动详情页布局和交互
This commit is contained in:
Leo 2025-10-13 21:44:00 +08:00
parent 02b4a1eeec
commit 8f603837a1
2 changed files with 130 additions and 112 deletions

View File

@ -4,39 +4,40 @@
import React, { useEffect, useState } from 'react'
import { useParams, Link } from 'react-router-dom'
import { Breadcrumb, Spin, Empty, Tag, Space, Typography, Divider } from 'antd'
import { Breadcrumb, Spin, Empty, Tag, Space, Typography, Divider, Card } from 'antd'
import {
HomeOutlined,
EyeOutlined,
HeartOutlined,
LikeOutlined,
CalendarOutlined,
UserOutlined,
} from '@ant-design/icons'
import { getNewsById } from '@services/api'
import type { NewsArticle } from '@/types'
import dayjs from 'dayjs'
import { getNewsDetail } from '@services/newsApi'
import FavoriteButton from '@components/FavoriteButton'
import LikeButton from '@components/LikeButton'
import CommentSection from '@components/CommentSection'
import type { NewsDetail } from '@/types'
import './Detail.css'
const { Title, Paragraph, Text } = Typography
// 后端分类映射news、activity、notice
const categoryLabels: Record<string, string> = {
exhibition: '展览',
news: '新闻',
activity: '活动',
policy: '政策',
research: '研究',
story: '故事',
notice: '通知',
}
const categoryColors: Record<string, string> = {
exhibition: 'purple',
activity: 'blue',
policy: 'red',
research: 'green',
story: 'orange',
news: 'blue',
activity: 'green',
notice: 'red',
}
const NewsDetail: React.FC = () => {
const NewsDetailPage: React.FC = () => {
const { id } = useParams<{ id: string }>()
const [data, setData] = useState<NewsArticle | null>(null)
const [data, setData] = useState<NewsDetail | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
@ -48,7 +49,7 @@ const NewsDetail: React.FC = () => {
const fetchData = async (newsId: string) => {
setLoading(true)
try {
const result = await getNewsById(newsId)
const result = await getNewsDetail(newsId)
setData(result)
} catch (error) {
console.error('Failed to fetch news detail:', error)
@ -73,6 +74,9 @@ const NewsDetail: React.FC = () => {
)
}
// 处理标签字符串
const tagArray = data?.tags ? data.tags.split(',').filter(tag => tag.trim()) : []
return (
<div className="news-detail-page">
{/* 面包屑导航 */}
@ -93,8 +97,8 @@ const NewsDetail: React.FC = () => {
</div>
{/* 封面图 */}
{data.cover && (
<div className="detail-cover" style={{ backgroundImage: `url(${data.cover})` }}>
{data.coverImage && (
<div className="detail-cover" style={{ backgroundImage: `url(${data.coverImage})` }}>
<div className="cover-overlay"></div>
</div>
)}
@ -102,63 +106,96 @@ const NewsDetail: React.FC = () => {
{/* 详情内容 */}
<div className="detail-content section-spacing-sm">
<div className="container">
<div className="detail-main">
{/* 头部信息 */}
<div className="detail-header">
<Tag color={categoryColors[data.category]} className="category-tag">
{categoryLabels[data.category]}
</Tag>
<Title level={1} className="detail-title">
{data.title}
</Title>
{data.subtitle && (
<Paragraph className="detail-subtitle">{data.subtitle}</Paragraph>
<div className="detail-layout" style={{ display: 'flex', gap: '24px' }}>
{/* 主要内容 */}
<div className="detail-main" style={{ flex: 1 }}>
{/* 头部信息 */}
<div className="detail-header">
<Tag color={categoryColors[data.category]} className="category-tag">
{categoryLabels[data.category] || data.category}
</Tag>
<Title level={1} className="detail-title">
{data.title}
</Title>
<div className="detail-meta">
<Space size="large" wrap>
<span className="meta-item">
<UserOutlined />
<Text>{data.author}</Text>
</span>
<span className="meta-item">
<Text>{data.source}</Text>
</span>
<span className="meta-item">
<CalendarOutlined />
<Text>{dayjs(data.publishTime).format('YYYY-MM-DD HH:mm')}</Text>
</span>
<span className="meta-item">
<EyeOutlined />
<Text>{data.viewCount.toLocaleString()} </Text>
</span>
<span className="meta-item">
<LikeOutlined />
<Text>{data.likeCount.toLocaleString()} </Text>
</span>
</Space>
</div>
</div>
<Divider />
{/* 摘要 */}
<div className="detail-summary">
<Paragraph className="summary-text">{data.summary}</Paragraph>
</div>
{/* 正文内容 - 使用 dangerouslySetInnerHTML 渲染 HTML */}
<div
className="detail-body"
dangerouslySetInnerHTML={{ __html: data.content }}
/>
{/* 标签 */}
{tagArray.length > 0 && (
<div className="detail-tags" style={{ marginTop: '24px' }}>
<Space size="small" wrap>
<Text type="secondary"></Text>
{tagArray.map((tag, index) => (
<Tag key={`${tag}-${index}`}>{tag}</Tag>
))}
</Space>
</div>
)}
<div className="detail-meta">
<Space size="large" wrap>
<span className="meta-item">
<UserOutlined />
<Text>{data.author}</Text>
</span>
<span className="meta-item">
<CalendarOutlined />
<Text>{data.publishDate}</Text>
</span>
<span className="meta-item">
<EyeOutlined />
<Text>{data.viewCount.toLocaleString()} </Text>
</span>
<span className="meta-item">
<HeartOutlined />
<Text>{data.likeCount.toLocaleString()} </Text>
</span>
</Space>
<Divider />
{/* 评论区 */}
<div className="detail-comments">
<CommentSection targetType="news" targetId={data.id} />
</div>
</div>
<Divider />
{/* 摘要 */}
<div className="detail-summary">
<Paragraph className="summary-text">{data.summary}</Paragraph>
</div>
{/* 正文内容 */}
<div className="detail-body">
<Paragraph className="body-text">{data.content}</Paragraph>
</div>
{/* 标签 */}
{data.tags && data.tags.length > 0 && (
<div className="detail-tags">
<Space size="small" wrap>
<Text type="secondary"></Text>
{data.tags.map((tag) => (
<Tag key={tag}>{tag}</Tag>
))}
{/* 侧边栏 */}
<div className="detail-sidebar" style={{ width: '280px' }}>
{/* 操作按钮 */}
<Card className="sidebar-card" style={{ marginBottom: 24 }}>
<Space direction="vertical" style={{ width: '100%' }} size="middle">
<FavoriteButton
targetType="news"
targetId={data.id}
size="large"
showText
/>
<LikeButton
targetType="news"
targetId={data.id}
size="large"
showText
initialCount={data.likeCount}
/>
</Space>
</div>
)}
</Card>
</div>
</div>
</div>
</div>
@ -166,4 +203,4 @@ const NewsDetail: React.FC = () => {
)
}
export default NewsDetail
export default NewsDetailPage

View File

@ -8,54 +8,43 @@ import { FileTextOutlined, CalendarOutlined } from '@ant-design/icons'
import NewsCard from '@components/NewsCard'
import EventCard from '@components/EventCard'
import CustomPagination from '@components/CustomPagination'
import { getNewsList, getEventList } from '@services/api'
import type { NewsArticle, Event, PaginationResult } from '@/types'
import { getNewsList } from '@services/newsApi'
import { getEventList } from '@services/eventApi'
import type { NewsListItem, EventListItem, IPage, NewsCategory, EventStatus } from '@/types'
import './index.css'
const { TabPane } = Tabs
// 资讯分类选项
// 资讯分类选项(匹配后端 category 字段news、activity、notice
const newsCategoryOptions = [
{ label: '全部', value: '' },
{ label: '展览', value: 'exhibition' },
{ label: '活动', value: 'activity' },
{ label: '政策', value: 'policy' },
{ label: '研究', value: 'research' },
{ label: '故事', value: 'story' },
{ label: '新闻资讯', value: 'news' },
{ label: '活动动态', value: 'activity' },
{ label: '通知公告', value: 'notice' },
]
// 活动类型选项
const eventTypeOptions = [
{ label: '全部', value: '' },
{ label: '展览', value: 'exhibition' },
{ label: '工作坊', value: 'workshop' },
{ label: '演出', value: 'performance' },
{ label: '讲座', value: 'lecture' },
{ label: '节日', value: 'festival' },
]
// 活动状态选项
// 活动状态选项(匹配后端 status 字段upcoming、ongoing、finished、cancelled
const eventStatusOptions = [
{ label: '全部', value: '' },
{ label: '即将开始', value: 'upcoming' },
{ label: '进行中', value: 'ongoing' },
{ label: '已结束', value: 'finished' },
{ label: '已取消', value: 'cancelled' },
]
const NewsPage: React.FC = () => {
const [activeTab, setActiveTab] = useState('news')
const [newsData, setNewsData] = useState<PaginationResult<NewsArticle> | null>(null)
const [eventsData, setEventsData] = useState<PaginationResult<Event> | null>(null)
const [newsData, setNewsData] = useState<IPage<NewsListItem> | null>(null)
const [eventsData, setEventsData] = useState<IPage<EventListItem> | null>(null)
const [loading, setLoading] = useState(true)
// 资讯筛选条件
const [newsCategory, setNewsCategory] = useState('')
const [newsCategory, setNewsCategory] = useState<NewsCategory | ''>('')
const [newsPage, setNewsPage] = useState(1)
const [newsPageSize, setNewsPageSize] = useState(12)
// 活动筛选条件
const [eventType, setEventType] = useState('')
const [eventStatus, setEventStatus] = useState('')
const [eventStatus, setEventStatus] = useState<EventStatus | ''>('')
const [eventPage, setEventPage] = useState(1)
const [eventPageSize, setEventPageSize] = useState(12)
@ -64,9 +53,9 @@ const NewsPage: React.FC = () => {
setLoading(true)
try {
const result = await getNewsList({
category: (newsCategory || undefined) as any,
page: newsPage,
pageNum: newsPage,
pageSize: newsPageSize,
category: newsCategory || undefined,
})
setNewsData(result)
} catch (error) {
@ -81,10 +70,9 @@ const NewsPage: React.FC = () => {
setLoading(true)
try {
const result = await getEventList({
type: eventType || undefined,
status: eventStatus || undefined,
page: eventPage,
pageNum: eventPage,
pageSize: eventPageSize,
status: eventStatus || undefined,
})
setEventsData(result)
} catch (error) {
@ -100,7 +88,7 @@ const NewsPage: React.FC = () => {
} else {
fetchEvents()
}
}, [activeTab, newsCategory, newsPage, newsPageSize, eventType, eventStatus, eventPage, eventPageSize])
}, [activeTab, newsCategory, newsPage, newsPageSize, eventStatus, eventPage, eventPageSize])
const handleTabChange = (key: string) => {
setActiveTab(key)
@ -165,10 +153,10 @@ const NewsPage: React.FC = () => {
<div className="loading-container">
<Spin size="large" />
</div>
) : newsData && newsData.data.length > 0 ? (
) : newsData && newsData.records.length > 0 ? (
<>
<Row gutter={[24, 24]}>
{newsData.data.map((item) => (
{newsData.records.map((item) => (
<Col key={item.id} xs={24} sm={12} md={8} lg={6}>
<NewsCard item={item} />
</Col>
@ -199,13 +187,6 @@ const NewsPage: React.FC = () => {
{/* 活动筛选 */}
<div className="filter-section">
<Space size="middle" wrap>
<span className="filter-label"></span>
<Select
value={eventType}
onChange={setEventType}
style={{ width: 150 }}
options={eventTypeOptions}
/>
<span className="filter-label"></span>
<Select
value={eventStatus}
@ -221,10 +202,10 @@ const NewsPage: React.FC = () => {
<div className="loading-container">
<Spin size="large" />
</div>
) : eventsData && eventsData.data.length > 0 ? (
) : eventsData && eventsData.records.length > 0 ? (
<>
<Row gutter={[24, 24]}>
{eventsData.data.map((item) => (
{eventsData.records.map((item) => (
<Col key={item.id} xs={24} sm={12} md={8} lg={6}>
<EventCard item={item} />
</Col>