diff --git a/src/pages/News/Detail.css b/src/pages/News/Detail.css new file mode 100644 index 0000000..c7f3032 --- /dev/null +++ b/src/pages/News/Detail.css @@ -0,0 +1,173 @@ +/* 资讯详情页样式 */ + +.news-detail-page { + min-height: 100vh; + background: #fafaf8; +} + +/* 面包屑导航 */ +.page-breadcrumb { + background: #ffffff; + padding: 20px 0; + border-bottom: 1px solid #f0f0f0; +} + +.page-breadcrumb .ant-breadcrumb { + font-size: 14px; +} + +.page-breadcrumb .ant-breadcrumb a { + color: #666666; + transition: color 0.3s; +} + +.page-breadcrumb .ant-breadcrumb a:hover { + color: #c8363d; +} + +/* 封面图 */ +.detail-cover { + position: relative; + width: 100%; + height: 400px; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + overflow: hidden; +} + +.cover-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(to bottom, transparent 0%, rgba(0, 0, 0, 0.3) 100%); +} + +/* 详情内容 */ +.detail-content { + background: #fafaf8; +} + +.detail-main { + max-width: 900px; + margin: 0 auto; + background: #ffffff; + padding: 48px 64px; + border-radius: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); +} + +/* 头部信息 */ +.detail-header { + margin-bottom: 32px; +} + +.category-tag { + font-size: 14px; + padding: 4px 16px; + margin-bottom: 16px; +} + +.detail-title { + font-size: 36px !important; + font-weight: 700 !important; + color: #1a1a1a !important; + line-height: 1.4 !important; + margin-bottom: 12px !important; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.detail-subtitle { + font-size: 18px !important; + color: #666666 !important; + margin-bottom: 24px !important; +} + +.detail-meta { + padding: 20px 0; + border-top: 1px solid #f0f0f0; + border-bottom: 1px solid #f0f0f0; +} + +.detail-meta .meta-item { + display: inline-flex; + align-items: center; + gap: 6px; + color: #666666; +} + +.detail-meta .meta-item .anticon { + color: #999999; + font-size: 14px; +} + +/* 摘要 */ +.detail-summary { + margin: 32px 0; + padding: 24px; + background: #f8f9fa; + border-left: 4px solid #c8363d; + border-radius: 4px; +} + +.summary-text { + font-size: 16px !important; + color: #333333 !important; + line-height: 1.8 !important; + margin: 0 !important; +} + +/* 正文内容 */ +.detail-body { + margin: 32px 0; +} + +.body-text { + font-size: 17px !important; + color: #333333 !important; + line-height: 1.9 !important; + text-align: justify; + white-space: pre-wrap; +} + +/* 标签 */ +.detail-tags { + margin-top: 40px; + padding-top: 24px; + border-top: 1px solid #f0f0f0; +} + +.detail-tags .ant-tag { + font-size: 13px; + padding: 4px 12px; + border-radius: 4px; +} + +/* 响应式 */ +@media (max-width: 768px) { + .detail-cover { + height: 250px; + } + + .detail-main { + padding: 32px 24px; + } + + .detail-title { + font-size: 28px !important; + } + + .detail-subtitle { + font-size: 16px !important; + } + + .detail-meta .ant-space { + width: 100%; + } + + .body-text { + font-size: 16px !important; + } +} diff --git a/src/pages/News/Detail.tsx b/src/pages/News/Detail.tsx new file mode 100644 index 0000000..77f4ad5 --- /dev/null +++ b/src/pages/News/Detail.tsx @@ -0,0 +1,169 @@ +/** + * 资讯详情页 + */ + +import React, { useEffect, useState } from 'react' +import { useParams, Link } from 'react-router-dom' +import { Breadcrumb, Spin, Empty, Tag, Space, Typography, Divider } from 'antd' +import { + HomeOutlined, + EyeOutlined, + HeartOutlined, + CalendarOutlined, + UserOutlined, +} from '@ant-design/icons' +import { getNewsById } from '@services/api' +import type { NewsArticle } from '@/types' +import './Detail.css' + +const { Title, Paragraph, Text } = Typography + +const categoryLabels: Record = { + exhibition: '展览', + activity: '活动', + policy: '政策', + research: '研究', + story: '故事', +} + +const categoryColors: Record = { + exhibition: 'purple', + activity: 'blue', + policy: 'red', + research: 'green', + story: 'orange', +} + +const NewsDetail: React.FC = () => { + const { id } = useParams<{ id: string }>() + const [data, setData] = useState(null) + const [loading, setLoading] = useState(true) + + useEffect(() => { + if (id) { + fetchData(id) + } + }, [id]) + + const fetchData = async (newsId: string) => { + setLoading(true) + try { + const result = await getNewsById(newsId) + setData(result) + } catch (error) { + console.error('Failed to fetch news detail:', error) + } finally { + setLoading(false) + } + } + + if (loading) { + return ( +
+ +
+ ) + } + + if (!data) { + return ( +
+ +
+ ) + } + + return ( +
+ {/* 面包屑导航 */} +
+
+ + + + 首页 + + + + 活动资讯 + + {data.title} + +
+
+ + {/* 封面图 */} + {data.cover && ( +
+
+
+ )} + + {/* 详情内容 */} +
+
+
+ {/* 头部信息 */} +
+ + {categoryLabels[data.category]} + + + {data.title} + + {data.subtitle && ( + {data.subtitle} + )} +
+ + + + 作者:{data.author} + + + + 发布时间:{data.publishDate} + + + + {data.viewCount.toLocaleString()} 浏览 + + + + {data.likeCount.toLocaleString()} 点赞 + + +
+
+ + + + {/* 摘要 */} +
+ {data.summary} +
+ + {/* 正文内容 */} +
+ {data.content} +
+ + {/* 标签 */} + {data.tags && data.tags.length > 0 && ( +
+ + 标签: + {data.tags.map((tag) => ( + {tag} + ))} + +
+ )} +
+
+
+
+ ) +} + +export default NewsDetail diff --git a/src/pages/News/EventDetail.css b/src/pages/News/EventDetail.css new file mode 100644 index 0000000..436ce99 --- /dev/null +++ b/src/pages/News/EventDetail.css @@ -0,0 +1,285 @@ +/* 活动详情页样式 */ + +.event-detail-page { + min-height: 100vh; + background: #fafaf8; +} + +/* 面包屑导航 */ +.page-breadcrumb { + background: #ffffff; + padding: 20px 0; + border-bottom: 1px solid #f0f0f0; +} + +.page-breadcrumb .ant-breadcrumb { + font-size: 14px; +} + +.page-breadcrumb .ant-breadcrumb a { + color: #666666; + transition: color 0.3s; +} + +.page-breadcrumb .ant-breadcrumb a:hover { + color: #c8363d; +} + +/* 封面图 */ +.event-detail-cover { + position: relative; + width: 100%; + height: 450px; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + overflow: hidden; +} + +.cover-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(to bottom, transparent 0%, rgba(0, 0, 0, 0.4) 100%); +} + +.cover-badge { + position: absolute; + top: 24px; + right: 24px; + z-index: 1; +} + +.status-badge { + font-size: 16px; + padding: 8px 20px; + border-radius: 20px; + font-weight: 600; +} + +/* 详情内容 */ +.event-detail-content { + background: #fafaf8; +} + +/* 主要内容区域 */ +.event-detail-main { + background: #ffffff; + padding: 40px; + border-radius: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); +} + +/* 头部信息 */ +.event-detail-header { + margin-bottom: 32px; +} + +.type-tag { + font-size: 14px; + padding: 4px 16px; + margin-bottom: 16px; +} + +.event-detail-title { + font-size: 36px !important; + font-weight: 700 !important; + color: #1a1a1a !important; + line-height: 1.4 !important; + margin-bottom: 24px !important; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.event-detail-meta { + padding: 20px 0; + border-top: 1px solid #f0f0f0; + border-bottom: 1px solid #f0f0f0; +} + +.event-detail-meta .meta-item { + display: inline-flex; + align-items: center; + gap: 8px; + color: #666666; +} + +.event-detail-meta .meta-item .anticon { + color: #c8363d; + font-size: 16px; +} + +/* 活动描述 */ +.event-detail-description { + margin: 32px 0; +} + +.event-detail-description h3 { + font-size: 24px; + color: #1a1a1a; + margin-bottom: 20px; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.description-text { + font-size: 17px !important; + color: #333333 !important; + line-height: 1.9 !important; + text-align: justify; + white-space: pre-wrap; +} + +/* 标签 */ +.event-detail-tags { + margin-top: 32px; + padding-top: 24px; + border-top: 1px solid #f0f0f0; +} + +.event-detail-tags .ant-tag { + font-size: 13px; + padding: 4px 12px; + border-radius: 4px; +} + +/* 侧边栏 */ +.event-sidebar { + position: sticky; + top: 20px; +} + +/* 报名信息卡片 */ +.enrollment-card { + margin-bottom: 24px; + border-radius: 12px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); + overflow: hidden; +} + +.price-info { + display: flex; + align-items: center; + gap: 16px; + padding: 8px 0; +} + +.price-icon { + font-size: 32px; + color: #c8363d; +} + +.price-value { + margin-top: 4px; +} + +.enrollment-info { + padding-top: 8px; +} + +.info-row { + display: flex; + align-items: center; + gap: 8px; + font-size: 15px; +} + +.info-row .anticon { + color: #c8363d; + font-size: 18px; +} + +.info-row > span:nth-child(2) { + flex: 1; +} + +.capacity-progress { + width: 100%; + height: 8px; + background: #f0f0f0; + border-radius: 4px; + overflow: hidden; + margin-top: 4px; +} + +.progress-bar { + height: 100%; + transition: width 0.3s ease; + border-radius: 4px; +} + +/* 组织方信息卡片 */ +.organizer-card { + border-radius: 12px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); +} + +.organizer-card h4 { + font-size: 18px; + color: #1a1a1a; + margin-bottom: 0; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.organizer-info { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 0; +} + +.contact-item { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; +} + +.contact-item .anticon { + color: #999999; + font-size: 16px; +} + +/* 响应式 */ +@media (max-width: 992px) { + .event-sidebar { + position: static; + margin-top: 32px; + } +} + +@media (max-width: 768px) { + .event-detail-cover { + height: 280px; + } + + .cover-badge { + top: 16px; + right: 16px; + } + + .status-badge { + font-size: 14px; + padding: 6px 16px; + } + + .event-detail-main { + padding: 24px; + } + + .event-detail-title { + font-size: 28px !important; + } + + .event-detail-meta .ant-space { + width: 100%; + } + + .description-text { + font-size: 16px !important; + } + + .event-detail-description h3 { + font-size: 20px; + } +} diff --git a/src/pages/News/EventDetail.tsx b/src/pages/News/EventDetail.tsx new file mode 100644 index 0000000..52a743b --- /dev/null +++ b/src/pages/News/EventDetail.tsx @@ -0,0 +1,341 @@ +/** + * 活动详情页 + */ + +import React, { useEffect, useState } from 'react' +import { useParams, Link } from 'react-router-dom' +import { + Breadcrumb, + Spin, + Empty, + Tag, + Space, + Typography, + Divider, + Button, + Card, + Row, + Col, +} from 'antd' +import { + HomeOutlined, + EyeOutlined, + CalendarOutlined, + ClockCircleOutlined, + EnvironmentOutlined, + UserOutlined, + DollarOutlined, + TeamOutlined, + PhoneOutlined, + MailOutlined, +} from '@ant-design/icons' +import { getEventById } from '@services/api' +import type { Event } from '@/types' +import './EventDetail.css' + +const { Title, Paragraph, Text } = Typography + +const typeLabels: Record = { + exhibition: '展览', + workshop: '工作坊', + performance: '演出', + lecture: '讲座', + festival: '节日', +} + +const typeColors: Record = { + exhibition: 'purple', + workshop: 'blue', + performance: 'red', + lecture: 'green', + festival: 'orange', +} + +const statusLabels: Record = { + upcoming: '即将开始', + ongoing: '进行中', + finished: '已结束', + cancelled: '已取消', +} + +const statusColors: Record = { + upcoming: 'blue', + ongoing: 'green', + finished: 'default', + cancelled: 'red', +} + +const EventDetail: React.FC = () => { + const { id } = useParams<{ id: string }>() + const [data, setData] = useState(null) + const [loading, setLoading] = useState(true) + + useEffect(() => { + if (id) { + fetchData(id) + } + }, [id]) + + const fetchData = async (eventId: string) => { + setLoading(true) + try { + const result = await getEventById(eventId) + setData(result) + } catch (error) { + console.error('Failed to fetch event detail:', error) + } finally { + setLoading(false) + } + } + + const handleEnroll = () => { + // TODO: 实现报名逻辑 + console.log('报名活动:', id) + } + + if (loading) { + return ( +
+ +
+ ) + } + + if (!data) { + return ( +
+ +
+ ) + } + + const isEnrollable = + data.status === 'upcoming' && + (!data.capacity || data.enrolled < data.capacity) + + const isFull = data.capacity && data.enrolled >= data.capacity + + return ( +
+ {/* 面包屑导航 */} +
+
+ + + + 首页 + + + + 活动资讯 + + {data.title} + +
+
+ + {/* 封面图 */} + {data.cover && ( +
+
+
+ + {statusLabels[data.status]} + +
+
+ )} + + {/* 详情内容 */} +
+
+ + {/* 左侧主要内容 */} + +
+ {/* 头部信息 */} +
+ + {typeLabels[data.type]} + + + {data.title} + + +
+ + + + + {data.startDate} + {data.startDate !== data.endDate && ` ~ ${data.endDate}`} + + + {data.startTime && ( + + + + {data.startTime} - {data.endTime} + + + )} + + + {data.location} + + + + {data.viewCount.toLocaleString()} 浏览 + + +
+
+ + + + {/* 活动描述 */} +
+ 活动介绍 + {data.description} +
+ + {/* 标签 */} + {data.tags && data.tags.length > 0 && ( +
+ + 标签: + {data.tags.map((tag) => ( + {tag} + ))} + +
+ )} +
+ + + {/* 右侧信息栏 */} + +
+ {/* 报名信息卡片 */} + +
+ +
+ + 活动费用 + +
+ {data.isFree ? ( + + 免费 + + ) : ( + + ¥{data.price} + + )} +
+
+
+ + + +
+ +
+ + 报名人数 + + {data.enrolled} / {data.capacity || '不限'} + +
+ + {data.capacity && ( +
+
+
+ )} + + {isFull && ( + + 名额已满 + + )} + + {data.status === 'finished' && ( + + 活动已结束 + + )} + + {data.status === 'cancelled' && ( + + 活动已取消 + + )} + + +
+
+
+ + {/* 组织方信息 */} + {data.organizer && ( + + 主办方 + + +
+ + + {data.organizer} + +
+ {data.contactInfo && (data.contactInfo.phone || data.contactInfo.email) && ( + <> + {data.contactInfo.phone && ( +
+ + {data.contactInfo.phone} +
+ )} + {data.contactInfo.email && ( +
+ + {data.contactInfo.email} +
+ )} + + )} +
+
+ )} +
+ +
+
+
+
+ ) +} + +export default EventDetail diff --git a/src/pages/News/index.css b/src/pages/News/index.css new file mode 100644 index 0000000..0fac7f9 --- /dev/null +++ b/src/pages/News/index.css @@ -0,0 +1,111 @@ +/* 活动资讯页面样式 */ + +.news-page { + min-height: 100vh; + background: #fafaf8; +} + +.page-header { + background: linear-gradient(135deg, #c8363d 0%, #8b252b 100%); + padding: 80px 0 60px; + text-align: center; + color: #ffffff; +} + +.page-header h1 { + font-size: 42px; + font-weight: 700; + color: #ffffff; + margin-bottom: 16px; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.page-header p { + font-size: 18px; + color: rgba(255, 255, 255, 0.9); + margin: 0; +} + +.page-content { + background: #fafaf8; +} + +/* Tabs样式 */ +.news-tabs { + background: #ffffff; + padding: 24px; + border-radius: 12px; + margin-bottom: 32px; +} + +.news-tabs .ant-tabs-nav { + margin-bottom: 24px; +} + +.news-tabs .ant-tabs-tab { + font-size: 16px; + font-weight: 500; + padding: 12px 24px; +} + +.news-tabs .ant-tabs-tab .anticon { + margin-right: 8px; +} + +/* 筛选区域 */ +.filter-section { + background: #f5f5f5; + padding: 20px 24px; + border-radius: 8px; + margin-bottom: 24px; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 12px; +} + +.filter-label { + font-size: 14px; + color: #666666; + font-weight: 500; +} + +.filter-section .ant-select { + min-width: 120px; +} + +/* 加载容器 */ +.loading-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 400px; +} + +/* 响应式 */ +@media (max-width: 768px) { + .page-header { + padding: 60px 0 40px; + } + + .page-header h1 { + font-size: 32px; + } + + .page-header p { + font-size: 16px; + } + + .news-tabs { + padding: 16px; + } + + .news-tabs .ant-tabs-tab { + font-size: 14px; + padding: 10px 16px; + } + + .filter-section { + padding: 16px; + } +} diff --git a/src/pages/News/index.tsx b/src/pages/News/index.tsx new file mode 100644 index 0000000..618174f --- /dev/null +++ b/src/pages/News/index.tsx @@ -0,0 +1,252 @@ +/** + * 活动资讯列表页 + */ + +import React, { useEffect, useState } from 'react' +import { Row, Col, Spin, Empty, Tabs, Select, Space } from 'antd' +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 './index.css' + +const { TabPane } = Tabs + +// 资讯分类选项 +const newsCategoryOptions = [ + { label: '全部', value: '' }, + { label: '展览', value: 'exhibition' }, + { label: '活动', value: 'activity' }, + { label: '政策', value: 'policy' }, + { label: '研究', value: 'research' }, + { label: '故事', value: 'story' }, +] + +// 活动类型选项 +const eventTypeOptions = [ + { label: '全部', value: '' }, + { label: '展览', value: 'exhibition' }, + { label: '工作坊', value: 'workshop' }, + { label: '演出', value: 'performance' }, + { label: '讲座', value: 'lecture' }, + { label: '节日', value: 'festival' }, +] + +// 活动状态选项 +const eventStatusOptions = [ + { label: '全部', value: '' }, + { label: '即将开始', value: 'upcoming' }, + { label: '进行中', value: 'ongoing' }, + { label: '已结束', value: 'finished' }, +] + +const NewsPage: React.FC = () => { + const [activeTab, setActiveTab] = useState('news') + const [newsData, setNewsData] = useState | null>(null) + const [eventsData, setEventsData] = useState | null>(null) + const [loading, setLoading] = useState(true) + + // 资讯筛选条件 + const [newsCategory, setNewsCategory] = useState('') + const [newsPage, setNewsPage] = useState(1) + const [newsPageSize, setNewsPageSize] = useState(12) + + // 活动筛选条件 + const [eventType, setEventType] = useState('') + const [eventStatus, setEventStatus] = useState('') + const [eventPage, setEventPage] = useState(1) + const [eventPageSize, setEventPageSize] = useState(12) + + // 获取资讯列表 + const fetchNews = async () => { + setLoading(true) + try { + const result = await getNewsList({ + category: (newsCategory || undefined) as any, + page: newsPage, + pageSize: newsPageSize, + }) + setNewsData(result) + } catch (error) { + console.error('Failed to fetch news:', error) + } finally { + setLoading(false) + } + } + + // 获取活动列表 + const fetchEvents = async () => { + setLoading(true) + try { + const result = await getEventList({ + type: eventType || undefined, + status: eventStatus || undefined, + page: eventPage, + pageSize: eventPageSize, + }) + setEventsData(result) + } catch (error) { + console.error('Failed to fetch events:', error) + } finally { + setLoading(false) + } + } + + useEffect(() => { + if (activeTab === 'news') { + fetchNews() + } else { + fetchEvents() + } + }, [activeTab, newsCategory, newsPage, newsPageSize, eventType, eventStatus, eventPage, eventPageSize]) + + const handleTabChange = (key: string) => { + setActiveTab(key) + } + + const handleNewsPageChange = (page: number, size: number) => { + if (size === newsPageSize) { + setNewsPage(page) + } else { + setNewsPage(1) + setNewsPageSize(size) + } + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + + const handleEventPageChange = (page: number, size: number) => { + if (size === eventPageSize) { + setEventPage(page) + } else { + setEventPage(1) + setEventPageSize(size) + } + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + + return ( +
+
+
+

活动资讯

+

了解最新的非遗活动与资讯动态

+
+
+ +
+
+ + + + 资讯动态 + + } + key="news" + > + {/* 资讯筛选 */} +
+ + 分类筛选: + + 活动状态: +