From 6257ce5c7b1ee9c0c48cedfbf9e2d51579b9c018 Mon Sep 17 00:00:00 2001 From: Leo <98382335+gaoziman@users.noreply.github.com> Date: Thu, 9 Oct 2025 23:47:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=A0=B8=E5=BF=83=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 首页:轮播图、特色项目、传承人展示、最新资讯等 - 非遗项目页面:列表页和详情页,支持筛选和排序 - 传承人页面:列表页和详情页,展示个人作品和技艺 - 关于页面:核心价值观、使命愿景展示 - 搜索页面:全站搜索功能 - 数据可视化页面:统计图表展示 - 用户中心:登录、注册、个人信息管理 --- src/pages/About/index.css | 202 ++++++++++++ src/pages/About/index.tsx | 123 ++++++++ src/pages/Data/index.css | 414 ++++++++++++++++++++++++ src/pages/Data/index.tsx | 254 +++++++++++++++ src/pages/Heritage/Detail.css | 126 ++++++++ src/pages/Heritage/Detail.tsx | 178 +++++++++++ src/pages/Heritage/List.css | 161 ++++++++++ src/pages/Heritage/List.tsx | 287 +++++++++++++++++ src/pages/Home/index.css | 558 +++++++++++++++++++++++++++++++++ src/pages/Home/index.tsx | 312 ++++++++++++++++++ src/pages/Inheritor/Detail.css | 276 ++++++++++++++++ src/pages/Inheritor/Detail.tsx | 366 +++++++++++++++++++++ src/pages/Inheritors/List.css | 58 ++++ src/pages/Inheritors/List.tsx | 89 ++++++ src/pages/Search/index.css | 133 ++++++++ src/pages/Search/index.tsx | 266 ++++++++++++++++ src/pages/User/Center.css | 172 ++++++++++ src/pages/User/Center.tsx | 173 ++++++++++ src/pages/User/Login.css | 180 +++++++++++ src/pages/User/Login.tsx | 107 +++++++ src/pages/User/Register.css | 146 +++++++++ src/pages/User/Register.tsx | 167 ++++++++++ 22 files changed, 4748 insertions(+) create mode 100644 src/pages/About/index.css create mode 100644 src/pages/About/index.tsx create mode 100644 src/pages/Data/index.css create mode 100644 src/pages/Data/index.tsx create mode 100644 src/pages/Heritage/Detail.css create mode 100644 src/pages/Heritage/Detail.tsx create mode 100644 src/pages/Heritage/List.css create mode 100644 src/pages/Heritage/List.tsx create mode 100644 src/pages/Home/index.css create mode 100644 src/pages/Home/index.tsx create mode 100644 src/pages/Inheritor/Detail.css create mode 100644 src/pages/Inheritor/Detail.tsx create mode 100644 src/pages/Inheritors/List.css create mode 100644 src/pages/Inheritors/List.tsx create mode 100644 src/pages/Search/index.css create mode 100644 src/pages/Search/index.tsx create mode 100644 src/pages/User/Center.css create mode 100644 src/pages/User/Center.tsx create mode 100644 src/pages/User/Login.css create mode 100644 src/pages/User/Login.tsx create mode 100644 src/pages/User/Register.css create mode 100644 src/pages/User/Register.tsx diff --git a/src/pages/About/index.css b/src/pages/About/index.css new file mode 100644 index 0000000..f9c58d5 --- /dev/null +++ b/src/pages/About/index.css @@ -0,0 +1,202 @@ +/* 关于我们页面样式 */ + +.about-page { + min-height: 100vh; +} + +.page-header { + background: linear-gradient(135deg, #4a5f7f 0%, #2a3f5f 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; +} + +.about-content { + background: #ffffff; +} + +.mission-section { + background: #ffffff; +} + +.values-section { + background: linear-gradient(135deg, #fff9f0 0%, #f5f0e8 100%); +} + +.value-card { + position: relative; + border-radius: 16px; + padding: 40px 32px; + background: #ffffff; + border: 2px solid #f0f0f0; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + height: 100%; + overflow: hidden; + text-align: center; +} + +.value-card:hover { + transform: translateY(-8px); + border-color: var(--gradient-end); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12); +} + +/* 定义各卡片的渐变色 */ +.value-card[data-color='red'] { + --gradient-start: #ff6b6b; + --gradient-end: #c8363d; + --icon-bg: rgba(200, 54, 61, 0.1); + --icon-color: #c8363d; +} + +.value-card[data-color='gold'] { + --gradient-start: #ffd89b; + --gradient-end: #d4a574; + --icon-bg: rgba(212, 165, 116, 0.1); + --icon-color: #d4a574; +} + +.value-card[data-color='green'] { + --gradient-start: #52b788; + --gradient-end: #2a5e4d; + --icon-bg: rgba(42, 94, 77, 0.1); + --icon-color: #2a5e4d; +} + +.value-card[data-color='blue'] { + --gradient-start: #7b9fd4; + --gradient-end: #4a5f7f; + --icon-bg: rgba(74, 95, 127, 0.1); + --icon-color: #4a5f7f; +} + +/* 卡片头部 */ +.value-card-header { + margin-bottom: 24px; +} + +/* 图标容器 */ +.value-icon-circle { + position: relative; + width: 90px; + height: 90px; + margin: 0 auto; + background: var(--icon-bg); + border-radius: 20px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + z-index: 2; +} + +.value-card:hover .value-icon-circle { + transform: scale(1.1); + background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end)); +} + +.value-icon-circle .anticon { + font-size: 42px; + color: var(--icon-color); + transition: all 0.4s ease; +} + +.value-card:hover .value-icon-circle .anticon { + color: #ffffff; + transform: scale(1.1); +} + +/* 文字区域 */ +.value-card-body { + position: relative; + z-index: 2; +} + +.value-card-body h4 { + font-size: 22px; + font-weight: 600; + color: #2c2c2c; + margin-bottom: 12px; + font-family: 'Noto Serif SC', 'Songti SC', serif; + letter-spacing: 2px; + transition: color 0.3s ease; +} + +.value-card:hover .value-card-body h4 { + background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.value-card-body .ant-typography { + font-size: 14px; + color: #666666; + line-height: 1.8; + margin: 0; +} + +.contact-section { + background: #ffffff; +} + +.contact-section .ant-card { + border-radius: 12px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); +} + +.contact-section .ant-typography { + font-size: 15px; + line-height: 2; + color: #666666; +} + +/* 响应式 */ +@media (max-width: 768px) { + .page-header { + padding: 60px 0 40px; + } + + .page-header h1 { + font-size: 32px; + } + + .page-header p { + font-size: 16px; + } + + .value-card { + padding: 32px 24px; + } + + .value-icon-circle { + width: 75px; + height: 75px; + } + + .value-icon-circle .anticon { + font-size: 36px; + } + + .value-card-body h4 { + font-size: 20px; + } + + .value-card-body .ant-typography { + font-size: 13px; + } +} diff --git a/src/pages/About/index.tsx b/src/pages/About/index.tsx new file mode 100644 index 0000000..7924400 --- /dev/null +++ b/src/pages/About/index.tsx @@ -0,0 +1,123 @@ +/** + * 关于我们页面 + */ + +import React from 'react' +import { Row, Col, Typography, Card } from 'antd' +import { HeartOutlined, BulbOutlined, GlobalOutlined, StarOutlined } from '@ant-design/icons' +import './index.css' + +const { Title, Paragraph } = Typography + +const About: React.FC = () => { + return ( +
+
+
+

关于我们

+

让千年技艺重焕新生,用数字科技守护文化根脉

+
+
+ +
+
+
+
+ 我们的使命 + + 非遗传承平台致力于中国非物质文化遗产的数字化保护、传承与推广。 + 我们通过现代科技手段,让传统文化在当代社会焕发新的生命力, + 让更多人了解、学习和传承这些珍贵的文化瑰宝。 + +
+
+
+ +
+
+
+ 核心价值观 +
+ + +
+
+
+ +
+
+
+ 传承 + 保护和传承中华民族优秀传统文化 +
+
+ + +
+
+
+ +
+
+
+ 创新 + 用现代科技赋能传统文化传承 +
+
+ + +
+
+
+ +
+
+
+ 开放 + 打造开放共享的文化传承平台 +
+
+ + +
+
+
+ +
+
+
+ 卓越 + 追求卓越品质,打造精品服务 +
+
+ +
+
+
+ +
+
+
+ 联系我们 + + 如有任何问题或建议,欢迎随时与我们联系 + +
+ + 地址:北京市朝阳区文化创意产业园 + 电话:400-123-4567 + 邮箱:heritage@example.com + + 工作时间:周一至周五 9:00-18:00 + + +
+
+
+
+
+
+ ) +} + +export default About diff --git a/src/pages/Data/index.css b/src/pages/Data/index.css new file mode 100644 index 0000000..f4325f6 --- /dev/null +++ b/src/pages/Data/index.css @@ -0,0 +1,414 @@ +/* 数据可视化页样式 */ + +.data-page { + min-height: 100vh; + background: #fafaf8; +} + +.page-header { + background: linear-gradient(135deg, #3d5a80 0%, #2a4a6a 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; +} + +/* 总体统计 */ +.stats-overview { + margin-bottom: 32px; +} + +.stats-overview .ant-card { + border-radius: 12px; + transition: all 0.3s ease; +} + +.stats-overview .ant-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); +} + +.stats-overview .ant-statistic-title { + font-size: 14px; + color: #999999; + margin-bottom: 12px; +} + +.stats-overview .ant-statistic-content { + font-size: 32px; + font-weight: 700; +} + +/* 数据卡片 */ +.data-card { + border-radius: 12px; + margin-bottom: 24px; +} + +.data-card .ant-card-head { + border-bottom: 2px solid #f0ebe3; +} + +.data-card .ant-card-head-title { + font-size: 18px; + font-weight: 600; + color: #2c2c2c; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +/* 省份分布图 */ +.province-chart { + display: flex; + flex-direction: column; + gap: 16px; +} + +.province-item { + display: grid; + grid-template-columns: 40px 100px 1fr 60px; + align-items: center; + gap: 16px; +} + +.province-rank { + width: 32px; + height: 32px; + background: linear-gradient(135deg, #d4a574 0%, #c8a060 100%); + color: #ffffff; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 14px; +} + +.province-name { + font-size: 14px; + font-weight: 500; + color: #2c2c2c; +} + +.province-bar-wrapper { + background: #f5f0e8; + height: 24px; + border-radius: 12px; + overflow: hidden; +} + +.province-bar { + height: 100%; + background: linear-gradient(90deg, #d4a574 0%, #c8a060 100%); + transition: width 0.6s ease; +} + +.province-count { + font-size: 16px; + font-weight: 600; + color: #2c2c2c; + text-align: right; +} + +/* 类别分布图 */ +.category-chart { + display: flex; + flex-direction: column; + gap: 20px; +} + +.category-item { + display: flex; + flex-direction: column; + gap: 8px; +} + +.category-info { + display: flex; + justify-content: space-between; + align-items: center; +} + +.category-count { + font-size: 14px; + font-weight: 600; + color: #666666; +} + +/* 级别分布图 */ +.level-chart { + display: flex; + flex-direction: column; + gap: 16px; +} + +.level-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px; + background: #fafaf8; + border-radius: 8px; +} + +.level-info { + display: flex; + flex-direction: column; + gap: 4px; +} + +.level-name { + font-size: 14px; + font-weight: 500; + color: #2c2c2c; +} + +.level-count { + font-size: 13px; + color: #999999; +} + +.level-percentage { + font-size: 24px; + font-weight: 700; + color: #d4a574; +} + +/* 年龄分布图 */ +.age-chart { + display: flex; + flex-direction: column; + gap: 20px; +} + +.age-item { + display: flex; + align-items: center; + gap: 16px; +} + +.age-range { + width: 100px; + font-size: 14px; + font-weight: 500; + color: #2c2c2c; +} + +.age-bar-wrapper { + flex: 1; + background: #f5f0e8; + height: 32px; + border-radius: 16px; + overflow: hidden; +} + +.age-bar { + height: 100%; + background: linear-gradient(90deg, #c8363d 0%, #a82f35 100%); + display: flex; + align-items: center; + justify-content: flex-end; + padding: 0 12px; + transition: width 0.6s ease; +} + +.age-count { + color: #ffffff; + font-weight: 600; + font-size: 14px; +} + +/* 性别分布图 */ +.gender-chart { + padding: 20px 0; +} + +.gender-stats { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 24px; +} + +.gender-item { + text-align: center; + padding: 32px 20px; + background: #fafaf8; + border-radius: 12px; + transition: all 0.3s ease; +} + +.gender-item:hover { + transform: translateY(-4px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); +} + +.gender-item.male { + border: 2px solid #3d5a80; +} + +.gender-item.female { + border: 2px solid #c8363d; +} + +.gender-icon { + font-size: 48px; + margin-bottom: 16px; +} + +.gender-label { + font-size: 14px; + color: #999999; + margin-bottom: 8px; +} + +.gender-count { + font-size: 32px; + font-weight: 700; + color: #2c2c2c; + margin-bottom: 8px; +} + +.gender-percentage { + font-size: 18px; + font-weight: 600; + color: #d4a574; +} + +/* 课程排行 */ +.course-chart { + display: flex; + flex-direction: column; + gap: 16px; +} + +.course-item { + display: grid; + grid-template-columns: 40px 1fr auto; + align-items: center; + gap: 20px; + padding: 16px; + background: #fafaf8; + border-radius: 8px; + transition: all 0.3s ease; +} + +.course-item:hover { + background: #f5f0e8; + transform: translateX(4px); +} + +.course-rank { + width: 36px; + height: 36px; + background: linear-gradient(135deg, #c8363d 0%, #a82f35 100%); + color: #ffffff; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + font-size: 16px; +} + +.course-info { + flex: 1; +} + +.course-title { + font-size: 15px; + font-weight: 600; + color: #2c2c2c; + margin-bottom: 6px; +} + +.course-meta { + display: flex; + gap: 16px; + font-size: 13px; + color: #999999; +} + +.course-instructor::before { + content: '👨‍🏫 '; +} + +.course-enroll { + text-align: right; +} + +.enroll-count { + font-size: 24px; + font-weight: 700; + color: #d4a574; + line-height: 1; + margin-bottom: 4px; +} + +.enroll-label { + font-size: 12px; + color: #999999; +} + +/* 响应式 */ +@media (max-width: 768px) { + .page-header { + padding: 60px 0 40px; + } + + .page-header h1 { + font-size: 32px; + } + + .province-item { + grid-template-columns: 32px 80px 1fr 50px; + gap: 12px; + } + + .gender-stats { + grid-template-columns: 1fr; + } + + .course-item { + grid-template-columns: 1fr; + gap: 12px; + } + + .course-enroll { + text-align: left; + } +} + +@media (max-width: 576px) { + .stats-overview .ant-statistic-content { + font-size: 24px; + } + + .province-item { + grid-template-columns: 1fr; + } + + .province-bar-wrapper { + order: 3; + } + + .age-item { + flex-direction: column; + align-items: stretch; + } + + .age-range { + width: 100%; + } +} diff --git a/src/pages/Data/index.tsx b/src/pages/Data/index.tsx new file mode 100644 index 0000000..7d4b258 --- /dev/null +++ b/src/pages/Data/index.tsx @@ -0,0 +1,254 @@ +/** + * 数据可视化页 + */ + +import React, { useEffect, useState } from 'react' +import { Row, Col, Card, Statistic, Progress, Tag } from 'antd' +import { + GlobalOutlined, + UserOutlined, +} from '@ant-design/icons' +import * as mockData from '@services/mockData' +import './index.css' + +interface ProvinceStats { + name: string + count: number + percentage: number +} + +interface CategoryStats { + name: string + count: number + color: string +} + +const DataVisualization: React.FC = () => { + const [provinceStats, setProvinceStats] = useState([]) + const [categoryStats, setCategoryStats] = useState([]) + const [levelStats, setLevelStats] = useState([]) + + useEffect(() => { + calculateStats() + }, []) + + const calculateStats = () => { + // 计算省份统计 + const provinceMap = new Map() + mockData.mockHeritageItems.forEach((item) => { + provinceMap.set(item.province, (provinceMap.get(item.province) || 0) + 1) + }) + + const totalItems = mockData.mockHeritageItems.length + const provinces: ProvinceStats[] = Array.from(provinceMap.entries()) + .map(([name, count]) => ({ + name, + count, + percentage: Math.round((count / totalItems) * 100), + })) + .sort((a, b) => b.count - a.count) + .slice(0, 10) + + setProvinceStats(provinces) + + // 计算类别统计 + const categoryMap = new Map() + mockData.mockHeritageItems.forEach((item) => { + categoryMap.set(item.category, (categoryMap.get(item.category) || 0) + 1) + }) + + const colors = ['#c8363d', '#d4a574', '#2a5e40', '#3d5a80', '#8b4513'] + const categories: CategoryStats[] = Array.from(categoryMap.entries()) + .map(([name, count], index) => ({ + name, + count, + color: colors[index % colors.length], + })) + .sort((a, b) => b.count - a.count) + + setCategoryStats(categories) + + // 计算级别统计 + const levelMap = new Map() + mockData.mockHeritageItems.forEach((item) => { + levelMap.set(item.level, (levelMap.get(item.level) || 0) + 1) + }) + + const levelLabels: Record = { + world: '世界级', + national: '国家级', + provincial: '省级', + municipal: '市级', + county: '县级', + } + + const levels = Array.from(levelMap.entries()) + .map(([key, count]) => ({ + name: levelLabels[key] || key, + count, + percentage: Math.round((count / totalItems) * 100), + })) + .sort((a, b) => b.count - a.count) + + setLevelStats(levels) + } + + return ( +
+
+
+

数据可视化

+

非遗文化传承数据统计与分析

+
+
+ +
+
+ {/* 总体统计 */} + + + + } + valueStyle={{ color: '#c8363d' }} + /> + + + + + } + valueStyle={{ color: '#d4a574' }} + /> + + + + + {/* 省份分布 */} + +
+ {provinceStats.map((item, index) => ( +
+
{index + 1}
+
{item.name}
+
+
+
+
{item.count}
+
+ ))} +
+ + + + {/* 类别分布 */} + + +
+ {categoryStats.map((item) => ( +
+
+ {item.name} + {item.count} 项 +
+ +
+ ))} +
+
+ + + {/* 级别分布 */} + + +
+ {levelStats.map((item, index) => ( +
+
+ {item.name} + {item.count} 项 +
+
{item.percentage}%
+
+ ))} +
+
+ +
+ + {/* 传承人统计 */} + + + +
+ {[ + { range: '30岁以下', count: mockData.mockInheritors.filter(i => i.age < 30).length }, + { range: '30-50岁', count: mockData.mockInheritors.filter(i => i.age >= 30 && i.age <= 50).length }, + { range: '50-70岁', count: mockData.mockInheritors.filter(i => i.age > 50 && i.age <= 70).length }, + { range: '70岁以上', count: mockData.mockInheritors.filter(i => i.age > 70).length }, + ].map((item) => ( +
+
{item.range}
+
+
+ {item.count} +
+
+
+ ))} +
+
+ + + + +
+
+
+
👨
+
男性
+
+ {mockData.mockInheritors.filter(i => i.gender === 'male').length} +
+
+ {Math.round((mockData.mockInheritors.filter(i => i.gender === 'male').length / mockData.mockInheritors.length) * 100)}% +
+
+
+
👩
+
女性
+
+ {mockData.mockInheritors.filter(i => i.gender === 'female').length} +
+
+ {Math.round((mockData.mockInheritors.filter(i => i.gender === 'female').length / mockData.mockInheritors.length) * 100)}% +
+
+
+
+
+ +
+
+
+
+ ) +} + +export default DataVisualization diff --git a/src/pages/Heritage/Detail.css b/src/pages/Heritage/Detail.css new file mode 100644 index 0000000..946b9cd --- /dev/null +++ b/src/pages/Heritage/Detail.css @@ -0,0 +1,126 @@ +/* 非遗项目详情页样式 */ + +.heritage-detail-page { + min-height: 100vh; + background: #fafaf8; +} + +.page-breadcrumb { + padding: 24px 0; + background: #ffffff; + border-bottom: 1px solid #f0ebe3; +} + +.detail-content { + background: #fafaf8; +} + +.detail-header { + margin-bottom: 32px; +} + +.detail-title { + font-size: 32px; + font-weight: 700; + color: #2c2c2c; + margin-bottom: 16px; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.detail-meta { + display: flex; + align-items: center; + gap: 24px; + font-size: 14px; + color: #666666; +} + +.detail-meta .anticon { + margin-right: 6px; + color: #d4a574; +} + +.detail-gallery { + margin-bottom: 32px; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 16px; +} + +.detail-gallery .ant-image { + border-radius: 8px; + overflow: hidden; +} + +.detail-tabs { + margin-bottom: 40px; +} + +.detail-text { + font-size: 15px; + line-height: 1.8; + color: #666666; +} + +/* 侧边栏 */ +.detail-sidebar { + position: sticky; + top: 80px; +} + +.sidebar-actions { + margin-bottom: 24px; +} + +.sidebar-actions button { + width: 100%; +} + +.sidebar-info, +.sidebar-tags { + background: #ffffff; + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; +} + +.sidebar-info h3, +.sidebar-tags h3 { + font-size: 16px; + font-weight: 600; + margin-bottom: 16px; + color: #2c2c2c; +} + +.info-item { + display: flex; + padding: 12px 0; + border-bottom: 1px solid #f0ebe3; +} + +.info-item:last-child { + border-bottom: none; +} + +.info-label { + width: 80px; + color: #999999; + font-size: 14px; +} + +.info-value { + flex: 1; + color: #2c2c2c; + font-size: 14px; + font-weight: 500; +} + +@media (max-width: 992px) { + .detail-sidebar { + position: static; + } + + .detail-title { + font-size: 24px; + } +} diff --git a/src/pages/Heritage/Detail.tsx b/src/pages/Heritage/Detail.tsx new file mode 100644 index 0000000..a960f49 --- /dev/null +++ b/src/pages/Heritage/Detail.tsx @@ -0,0 +1,178 @@ +/** + * 非遗项目详情页 + */ + +import React, { useEffect, useState } from 'react' +import { useParams } from 'react-router-dom' +import { Row, Col, Tag, Breadcrumb, Image, Tabs, Spin, Empty, Space } from 'antd' +import { HomeOutlined, EnvironmentOutlined, EyeOutlined, HeartOutlined } from '@ant-design/icons' +import { Link } from 'react-router-dom' +import type { HeritageItem } from '@types/index' +import { getHeritageById } from '@services/api' +import FavoriteButton from '@components/FavoriteButton' +import CommentSection from '@components/CommentSection' +import './Detail.css' + +const { TabPane } = Tabs + +const levelLabels: Record = { + world: '世界级', + national: '国家级', + provincial: '省级', + municipal: '市级', + county: '县级', +} + +const HeritageDetail: 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 (heritageId: string) => { + setLoading(true) + try { + const result = await getHeritageById(heritageId) + setData(result) + } catch (error) { + console.error('Failed to fetch heritage detail:', error) + } finally { + setLoading(false) + } + } + + if (loading) { + return ( +
+ +
+ ) + } + + if (!data) { + return ( +
+ +
+ ) + } + + return ( +
+
+
+ + + 首页 + + + 非遗项目 + + {data.name} + +
+
+ +
+
+ + + {/* 头部信息 */} +
+

{data.name}

+
+ {levelLabels[data.level]} + {data.province} + {data.viewCount.toLocaleString()} + {data.likeCount.toLocaleString()} +
+
+ + {/* 图片画廊 */} +
+ + {data.images?.map((img, index) => ( + + ))} + +
+ + {/* 详细内容 */} + + +
+

{data.description}

+
+
+ +
+

{data.history}

+
+
+ +
+

{data.skills}

+
+
+ +
+

{data.significance}

+
+
+
+ + {/* 评论区 */} + + + + +
+
+ +
+ +
+

基本信息

+
+ 分类: + {data.category} +
+
+ 级别: + {levelLabels[data.level]} +
+
+ 地区: + {data.province} +
+
+ 状态: + {data.status === 'active' ? '正常传承' : '濒危'} +
+
+ + {data.tags.length > 0 && ( +
+

相关标签

+ + {data.tags.map((tag) => ( + {tag} + ))} + +
+ )} +
+ +
+
+
+
+ ) +} + +export default HeritageDetail diff --git a/src/pages/Heritage/List.css b/src/pages/Heritage/List.css new file mode 100644 index 0000000..ed7f536 --- /dev/null +++ b/src/pages/Heritage/List.css @@ -0,0 +1,161 @@ +/* 非遗项目列表页样式 */ + +.heritage-list-page { + min-height: 100vh; +} + +.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: #ffffff; +} + +.loading-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 400px; +} + +.pagination-container { + display: flex; + justify-content: center; + margin-top: 48px; +} + +/* 筛选器区域样式 */ +.filter-section { + background: #f5f0e8; + border-radius: 12px; + padding: 24px; + margin-bottom: 32px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); +} + +.filter-controls { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 12px; +} + +.filter-controls .ant-input-affix-wrapper { + height: 40px !important; + border-radius: 8px; + font-size: 14px; + padding: 4px 11px; + display: flex; + align-items: center; +} + +.filter-controls .ant-input-affix-wrapper .ant-input { + height: 32px; + font-size: 14px; +} + +.filter-controls .ant-input-affix-wrapper .ant-input-prefix { + margin-right: 8px; +} + +.filter-controls .ant-select { + font-size: 14px; +} + +.filter-controls .ant-select-selector { + height: 40px !important; + border-radius: 8px !important; + display: flex; + align-items: center; +} + +.filter-controls .ant-btn { + height: 40px; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + padding: 0 20px; +} + +/* 筛选结果提示 */ +.filter-result-info { + background: #ffffff; + border-radius: 8px; + padding: 12px 16px; + border-left: 3px solid #c8363d; +} + +.filter-label { + color: #666666; + font-size: 14px; + font-weight: 500; +} + +.filter-tag { + display: inline-block; + background: #fff9f0; + color: #c8363d; + padding: 4px 12px; + border-radius: 6px; + font-size: 13px; + font-weight: 400; + border: 1px solid rgba(200, 54, 61, 0.2); +} + +/* 响应式 */ +@media (max-width: 768px) { + .page-header { + padding: 60px 0 40px; + } + + .page-header h1 { + font-size: 32px; + } + + .page-header p { + font-size: 16px; + } + + .filter-section { + padding: 16px; + margin-bottom: 24px; + } + + .filter-controls { + flex-direction: column; + gap: 12px; + } + + .filter-controls .ant-input, + .filter-controls .ant-select, + .filter-controls .ant-btn { + width: 100% !important; + } + + .filter-result-info { + padding: 10px 12px; + } + + .filter-label, + .filter-tag { + font-size: 12px; + } +} diff --git a/src/pages/Heritage/List.tsx b/src/pages/Heritage/List.tsx new file mode 100644 index 0000000..d4e207f --- /dev/null +++ b/src/pages/Heritage/List.tsx @@ -0,0 +1,287 @@ +/** + * 非遗项目列表页 + */ + +import React, { useEffect, useState } from 'react' +import { Row, Col, Spin, Empty, Input, Select, Space, Button } from 'antd' +import { SearchOutlined, FilterOutlined, ReloadOutlined } from '@ant-design/icons' +import HeritageCard from '@components/HeritageCard' +import CustomPagination from '@components/CustomPagination' +import { getHeritageList } from '@services/api' +import type { HeritageItem, PaginationResult, HeritageCategory } from '@types/index' +import './List.css' + +// 分类选项 +const categoryOptions = [ + { label: '全部分类', value: '' }, + { label: '民间文学', value: 'folk-literature' }, + { label: '传统音乐', value: 'traditional-music' }, + { label: '传统舞蹈', value: 'traditional-dance' }, + { label: '传统戏剧', value: 'traditional-opera' }, + { label: '曲艺', value: 'folk-art' }, + { label: '传统体育、游艺与杂技', value: 'sports-acrobatics' }, + { label: '传统技艺', value: 'traditional-craft' }, + { label: '传统医药', value: 'traditional-medicine' }, + { label: '民俗', value: 'folk-custom' }, + { label: '传统美术', value: 'traditional-art' }, +] + +// 常用标签选项 +const tagOptions = [ + '传统技艺', + '传统美术', + '传统音乐', + '传统戏剧', + '民俗', + '金属工艺', + '刺绣', + '陶瓷', + '古琴', + '书法', + '造纸', + '宫廷艺术', + '江南文化', + '文人艺术', + '文房四宝', +] + +const HeritageList: React.FC = () => { + const [data, setData] = useState | null>(null) + const [loading, setLoading] = useState(true) + const [currentPage, setCurrentPage] = useState(1) + const [pageSize, setPageSize] = useState(12) + + // 筛选条件状态 + const [searchKeyword, setSearchKeyword] = useState('') + const [selectedCategory, setSelectedCategory] = useState('') + const [selectedTags, setSelectedTags] = useState([]) + + // 实际应用的筛选条件(用于提交后生效) + const [appliedFilters, setAppliedFilters] = useState({ + keyword: '', + category: '', + tags: [] as string[], + }) + + const fetchData = async () => { + setLoading(true) + try { + // 获取所有数据(设置一个很大的 pageSize) + const result = await getHeritageList({ page: 1, pageSize: 1000 }) + setData(result) + } catch (error) { + console.error('Failed to fetch heritage list:', error) + } finally { + setLoading(false) + } + } + + useEffect(() => { + fetchData() + }, []) + + const handlePageChange = (page: number, size: number) => { + // 只改变页码,不改变 pageSize + if (size === pageSize) { + setCurrentPage(page) + } else { + // pageSize 改变时,重置到第一页 + setCurrentPage(1) + setPageSize(size) + } + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + + const handleShowSizeChange = (current: number, size: number) => { + setCurrentPage(1) + setPageSize(size) + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + + // 应用筛选条件 + const handleApplyFilters = () => { + setAppliedFilters({ + keyword: searchKeyword, + category: selectedCategory, + tags: selectedTags, + }) + setCurrentPage(1) + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + + // 重置筛选条件 + const handleResetFilters = () => { + setSearchKeyword('') + setSelectedCategory('') + setSelectedTags([]) + setAppliedFilters({ + keyword: '', + category: '', + tags: [], + }) + setCurrentPage(1) + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + + // 客户端筛选和分页数据 + const getFilteredData = () => { + if (!data) return null + + let filteredItems = [...data.data] + + // 按名字搜索 + if (appliedFilters.keyword) { + const keyword = appliedFilters.keyword.toLowerCase() + filteredItems = filteredItems.filter( + (item) => + item.name.toLowerCase().includes(keyword) || + item.description.toLowerCase().includes(keyword) + ) + } + + // 按分类筛选 + if (appliedFilters.category) { + filteredItems = filteredItems.filter((item) => item.category === appliedFilters.category) + } + + // 按标签筛选 + if (appliedFilters.tags.length > 0) { + filteredItems = filteredItems.filter((item) => + appliedFilters.tags.some((tag) => item.tags.includes(tag)) + ) + } + + // 客户端分页 + const total = filteredItems.length + const totalPages = Math.ceil(total / pageSize) + const start = (currentPage - 1) * pageSize + const end = start + pageSize + const paginatedItems = filteredItems.slice(start, end) + + return { + data: paginatedItems, + total: total, + page: currentPage, + pageSize: pageSize, + totalPages: totalPages, + } + } + + const filteredData = getFilteredData() + + return ( +
+
+
+

非遗项目

+

探索中华民族丰富多彩的非物质文化遗产

+
+
+ +
+
+ {/* 筛选器区域 */} +
+ + + {/* 搜索框 */} + } + value={searchKeyword} + onChange={(e) => setSearchKeyword(e.target.value)} + onPressEnter={handleApplyFilters} + style={{ width: 280 }} + allowClear + /> + + {/* 分类选择 */} + ({ label: tag, value: tag }))} + maxTagCount="responsive" + allowClear + /> + + {/* 操作按钮 */} + + + + + {/* 筛选结果提示 */} + {(appliedFilters.keyword || appliedFilters.category || appliedFilters.tags.length > 0) && ( +
+ + 当前筛选: + {appliedFilters.keyword && ( + 关键词: {appliedFilters.keyword} + )} + {appliedFilters.category && ( + + 分类:{' '} + {categoryOptions.find((opt) => opt.value === appliedFilters.category) + ?.label} + + )} + {appliedFilters.tags.map((tag) => ( + + 标签: {tag} + + ))} + +
+ )} +
+
+ + {/* 内容区域 */} + {loading ? ( +
+ +
+ ) : filteredData && filteredData.data.length > 0 ? ( + <> + + {filteredData.data.map((item) => ( + + + + ))} + + + + ) : ( + + )} +
+
+
+ ) +} + +export default HeritageList diff --git a/src/pages/Home/index.css b/src/pages/Home/index.css new file mode 100644 index 0000000..89b13ef --- /dev/null +++ b/src/pages/Home/index.css @@ -0,0 +1,558 @@ +/* 首页样式 */ + +.home-page { + width: 100%; +} + +/* ===== Hero Section ===== */ +.hero-section { + position: relative; + width: 100%; + height: 600px; + overflow: hidden; +} + +.hero-carousel { + height: 100%; +} + +.hero-carousel .slick-slide { + height: 600px; +} + +.hero-slide { + position: relative; + width: 100%; + height: 600px; + display: flex !important; + align-items: center; + justify-content: center; +} + +.hero-background { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center; + animation: ken-burns 20s ease-in-out infinite alternate; +} + +@keyframes ken-burns { + 0% { + transform: scale(1); + } + 100% { + transform: scale(1.1); + } +} + +.hero-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient( + 135deg, + rgba(200, 54, 61, 0.7) 0%, + rgba(139, 37, 43, 0.8) 100% + ); +} + +.hero-content { + position: relative; + z-index: 2; + width: 100%; + text-align: center; + color: #ffffff; +} + +.hero-title { + font-size: 56px; + font-weight: 700; + color: #ffffff; + margin-bottom: 16px; + font-family: 'Noto Serif SC', 'Songti SC', serif; + text-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + animation-delay: 0.2s; +} + +.hero-subtitle { + font-size: 24px; + color: rgba(255, 255, 255, 0.95); + margin-bottom: 40px; + font-weight: 300; + letter-spacing: 2px; + text-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + animation-delay: 0.4s; +} + +.hero-actions { + animation-delay: 0.6s; +} + +.hero-actions .ant-btn { + height: 48px; + padding: 0 40px; + font-size: 16px; + border-radius: 24px; +} + +.hero-actions .ant-btn-primary { + background: #ffffff; + color: #c8363d; + border: none; + font-weight: 600; +} + +.hero-actions .ant-btn-primary:hover { + background: rgba(255, 255, 255, 0.9); + transform: translateY(-2px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); +} + +.hero-actions .ant-btn:not(.ant-btn-primary) { + background: transparent; + color: #ffffff; + border: 2px solid #ffffff; +} + +.hero-actions .ant-btn:not(.ant-btn-primary):hover { + background: rgba(255, 255, 255, 0.1); + border-color: #ffffff; + transform: translateY(-2px); +} + +/* ===== Values Section ===== */ +.values-section { + background: linear-gradient(180deg, #ffffff 0%, #fafaf8 100%); + position: relative; +} + +.value-card { + text-align: center; + padding: 48px 32px; + background: #ffffff; + border-radius: 16px; + border: 2px solid #f0ebe3; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} + +.value-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: linear-gradient(90deg, var(--icon-color, #C8363D) 0%, transparent 100%); + opacity: 0; + transition: opacity 0.4s ease; +} + +.value-card:hover::before { + opacity: 1; +} + +.value-card:hover { + transform: translateY(-12px); + border-color: var(--icon-color, #C8363D); + box-shadow: 0 20px 48px rgba(0, 0, 0, 0.12); +} + +.value-icon-wrapper { + position: relative; + display: inline-block; + margin-bottom: 28px; +} + +.value-icon { + width: 100px; + height: 100px; + margin: 0 auto; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, var(--icon-color, #C8363D), rgba(var(--icon-color-rgb, 200, 54, 61), 0.7)); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); + transform: rotate(45deg); + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; +} + +.value-card:hover .value-icon { + transform: rotate(45deg) scale(1.1); + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15); +} + +.icon-text { + font-size: 42px; + font-weight: 700; + color: #ffffff; + transform: rotate(-45deg); + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.value-decoration { + position: absolute; + top: -10px; + right: -10px; + width: 30px; + height: 30px; + border: 3px solid var(--icon-color, #C8363D); + border-radius: 50%; + opacity: 0; + transform: scale(0.5); + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +.value-card:hover .value-decoration { + opacity: 1; + transform: scale(1); +} + +.value-card h3 { + font-size: 22px; + font-weight: 600; + color: #2c2c2c; + margin-bottom: 16px; + font-family: 'Noto Serif SC', 'Songti SC', serif; + letter-spacing: 2px; +} + +.value-divider { + width: 60px; + height: 3px; + background: linear-gradient(90deg, var(--icon-color, #C8363D) 0%, transparent 100%); + margin: 0 auto 16px; + border-radius: 2px; +} + +.value-card p { + font-size: 15px; + color: #666666; + line-height: 1.8; + margin: 0; +} + +/* ===== Categories Section ===== */ +.categories-section { + background: #ffffff; +} + +.category-card { + display: block; + padding: 32px 24px; + background: var(--category-bg, #ffffff); + border: 2px solid transparent; + border-radius: 16px; + text-decoration: none; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + min-height: 140px; + position: relative; + overflow: hidden; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06); +} + +.category-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 4px; + height: 100%; + background: var(--category-color, #C8363D); + transform: scaleY(0); + transform-origin: top; + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +.category-card:hover::before { + transform: scaleY(1); +} + +.category-card:hover { + transform: translateY(-8px); + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12); + border-color: var(--category-color, #C8363D); +} + +.category-content { + position: relative; + z-index: 2; +} + +.category-title { + font-size: 20px; + font-weight: 700; + color: #2c2c2c; + margin-bottom: 8px; + font-family: 'Noto Serif SC', 'Songti SC', serif; + letter-spacing: 2px; + transition: all 0.3s ease; +} + +.category-card:hover .category-title { + color: var(--category-color, #C8363D); + transform: translateX(4px); +} + +.category-desc { + font-size: 14px; + color: #666666; + font-family: 'Noto Serif SC', 'Songti SC', serif; + letter-spacing: 1px; + transition: all 0.3s ease; +} + +.category-card:hover .category-desc { + color: #333333; +} + +.category-corner { + position: absolute; + top: 12px; + right: 12px; + width: 40px; + height: 40px; + border-top: 3px solid var(--category-color, #C8363D); + border-right: 3px solid var(--category-color, #C8363D); + opacity: 0; + transform: translate(10px, -10px); + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + z-index: 1; +} + +.category-card:hover .category-corner { + opacity: 0.3; + transform: translate(0, 0); +} + +.category-pattern { + position: absolute; + bottom: -20px; + right: -20px; + width: 100px; + height: 100px; + background: radial-gradient(circle, var(--category-color, #C8363D) 0%, transparent 70%); + opacity: 0; + transform: scale(0.5); + transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); + z-index: 0; +} + +.category-card:hover .category-pattern { + opacity: 0.1; + transform: scale(1); +} + +/* ===== Section Header ===== */ +.section-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 40px; +} + +.section-header.text-center { + flex-direction: column; + text-align: center; +} + +.section-header h2 { + font-size: 32px; + font-weight: 600; + color: #2c2c2c; + margin: 0 !important; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.section-header p { + font-size: 16px; + color: #666666; + margin: 8px 0 0 0 !important; +} + +.section-header a { + color: #c8363d; + font-size: 16px; + font-weight: 500; + text-decoration: none; + transition: all 0.3s ease; +} + +.section-header a:hover { + color: #a82e34; + transform: translateX(4px); +} + +/* ===== Featured Section ===== */ +.featured-section { + background: #ffffff; +} + +/* ===== Inheritors Section ===== */ +.inheritors-section { + background: linear-gradient(135deg, #fff9f0 0%, #f5f0e8 100%); +} + +/* ===== Statistics Section ===== */ +.statistics-section { + background: #ffffff; +} + +.stat-card { + text-align: center; + padding: 32px 24px; + background: #f5f0e8; + border-radius: 12px; + transition: all 0.3s ease; +} + +.stat-card:hover { + transform: translateY(-4px); + background: #ffffff; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); +} + +.stat-card .ant-statistic { + margin: 0; +} + +.stat-card .ant-statistic-title { + font-size: 14px; + color: #666666; + margin-bottom: 8px; +} + +.stat-card .ant-statistic-content { + font-size: 32px; + font-weight: 700; + color: #c8363d; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.stat-card .anticon { + font-size: 24px; + color: #d4a574; + margin-right: 8px; +} + +/* ===== CTA Section ===== */ +.cta-section { + background: linear-gradient(135deg, #c8363d 0%, #8b252b 100%); + position: relative; + overflow: hidden; +} + +.cta-section .ant-btn-default.ant-btn-background-ghost { + border-color: #ffffff; + color: #ffffff; +} + +.cta-section .ant-btn-default.ant-btn-background-ghost:hover { + background: #ffffff !important; + border-color: #ffffff !important; + color: #c8363d !important; +} + +.cta-section .ant-btn:not(.ant-btn-background-ghost):hover { + background: rgba(255, 255, 255, 0.9) !important; + transform: translateY(-2px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); +} + +/* ===== 响应式 ===== */ +@media (max-width: 992px) { + .hero-section, + .hero-carousel, + .hero-carousel .slick-slide, + .hero-slide { + height: 500px; + } + + .hero-title { + font-size: 42px; + } + + .hero-subtitle { + font-size: 20px; + } + + .section-header h2 { + font-size: 28px; + } +} + +@media (max-width: 768px) { + .hero-section, + .hero-carousel, + .hero-carousel .slick-slide, + .hero-slide { + height: 400px; + } + + .hero-title { + font-size: 32px; + } + + .hero-subtitle { + font-size: 16px; + } + + .hero-actions .ant-btn { + height: 40px; + padding: 0 24px; + font-size: 14px; + } + + .section-header { + flex-direction: column; + align-items: flex-start; + gap: 16px; + } + + .section-header h2 { + font-size: 24px; + } + + .value-card { + padding: 36px 24px; + } + + .value-icon { + width: 80px; + height: 80px; + } + + .icon-text { + font-size: 36px; + } + + .value-card h3 { + font-size: 20px; + } + + .stat-card .ant-statistic-content { + font-size: 28px; + } + + .category-card { + padding: 24px 20px; + min-height: 120px; + } + + .category-title { + font-size: 18px; + } + + .category-desc { + font-size: 13px; + } +} diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx new file mode 100644 index 0000000..96d88e5 --- /dev/null +++ b/src/pages/Home/index.tsx @@ -0,0 +1,312 @@ +/** + * 首页组件 + */ + +import React, { useEffect, useState } from 'react' +import { Row, Col, Typography, Button, Statistic, Space, Carousel } from 'antd' +import { + ArrowRightOutlined, + GlobalOutlined, + TrophyOutlined, + TeamOutlined, + EnvironmentOutlined, +} from '@ant-design/icons' +import { Link } from 'react-router-dom' +import HeritageCard from '@components/HeritageCard' +import InheritorCard from '@components/InheritorCard' +import { + getStatistics, + getFeaturedHeritage, + getFeaturedInheritors, +} from '@services/api' +import type { HeritageItem, Inheritor, Statistics } from '@types/index' +import './index.css' + +const { Title, Paragraph } = Typography + +const categories = [ + { key: 'traditional-craft', label: '传统技艺', desc: '千年匠心', color: '#C8363D', bgGradient: 'linear-gradient(135deg, #FFEAE7 0%, #FFD6D1 100%)' }, + { key: 'traditional-art', label: '传统美术', desc: '丹青妙笔', color: '#D4A574', bgGradient: 'linear-gradient(135deg, #FFF4E6 0%, #FFE7C8 100%)' }, + { key: 'traditional-music', label: '传统音乐', desc: '余音绕梁', color: '#2A5E4D', bgGradient: 'linear-gradient(135deg, #E6F4F1 0%, #CCE8E1 100%)' }, + { key: 'traditional-opera', label: '传统戏剧', desc: '唱念做打', color: '#4A5F7F', bgGradient: 'linear-gradient(135deg, #E8EBF3 0%, #D1D7E8 100%)' }, + { key: 'folk-custom', label: '民俗', desc: '礼仪风尚', color: '#C8363D', bgGradient: 'linear-gradient(135deg, #FFEAE7 0%, #FFD6D1 100%)' }, + { key: 'traditional-medicine', label: '传统医药', desc: '岐黄之术', color: '#2A5E4D', bgGradient: 'linear-gradient(135deg, #E6F4F1 0%, #CCE8E1 100%)' }, + { key: 'folk-literature', label: '民间文学', desc: '口传心授', color: '#D4A574', bgGradient: 'linear-gradient(135deg, #FFF4E6 0%, #FFE7C8 100%)' }, + { key: 'traditional-dance', label: '传统舞蹈', desc: '翩若惊鸿', color: '#4A5F7F', bgGradient: 'linear-gradient(135deg, #E8EBF3 0%, #D1D7E8 100%)' }, +] + +const heroSlides = [ + { + image: 'https://images.unsplash.com/photo-1610701596007-11502861dcfa?w=1600', + title: '传承千年技艺', + subtitle: '守护中华文化根脉', + }, + { + image: 'https://images.unsplash.com/photo-1617038260897-41a1f14a8ca0?w=1600', + title: '匠心独运', + subtitle: '让非遗文化重焕新生', + }, + { + image: 'https://images.unsplash.com/photo-1544947950-fa07a98d237f?w=1600', + title: '文化自信', + subtitle: '弘扬中华优秀传统文化', + }, +] + +const Home: React.FC = () => { + const [statistics, setStatistics] = useState(null) + const [featuredHeritage, setFeaturedHeritage] = useState([]) + const [featuredInheritors, setFeaturedInheritors] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const fetchData = async () => { + try { + const [stats, heritage, inheritors] = await Promise.all([ + getStatistics(), + getFeaturedHeritage(6), + getFeaturedInheritors(4), + ]) + setStatistics(stats) + setFeaturedHeritage(heritage) + setFeaturedInheritors(inheritors) + } catch (error) { + console.error('Failed to fetch data:', error) + } finally { + setLoading(false) + } + } + + fetchData() + }, []) + + return ( +
+ {/* Hero Banner */} +
+ + {heroSlides.map((slide, index) => ( +
+
+
+
+
+

{slide.title}

+

{slide.subtitle}

+ + + + +
+
+
+ ))} + +
+ + {/* 核心价值 */} +
+
+ + +
+
+
+
+
+
+
+

传承千年

+
+

保护和传承中华民族优秀传统文化,让历史技艺得以延续

+
+ + +
+
+
+
+
+
+
+

匠心独运

+
+

弘扬工匠精神,展现非遗项目的精湛技艺和艺术价值

+
+ + +
+
+
+
+
+
+
+

活态传承

+
+

创新传承方式,让非遗文化在当代社会焕发新的生命力

+
+ +
+
+
+ + {/* 分类导航 */} +
+
+
+ 非遗分类 + 探索丰富多彩的中华非物质文化遗产 +
+ + {categories.map((category) => ( + + +
+
{category.label}
+
{category.desc}
+
+
+
+ + + ))} +
+
+
+ + {/* 精选非遗项目 */} + + + {/* 代表性传承人 */} +
+
+
+ 代表性传承人 + 致敬守护文化根脉的匠人大师 +
+ + {featuredInheritors.map((item) => ( + + + + ))} + +
+ + + +
+
+
+ + {/* 统计数据 */} + {statistics && ( +
+
+ + +
+ } + suffix="项" + /> +
+ + +
+ } + suffix="位" + /> +
+ + +
+ } + suffix="项" + /> +
+ + +
+ } + suffix="个" + /> +
+ +
+
+
+ )} + + {/* CTA 区块 */} +
+
+ + 加入我们,共同守护文化遗产 + + + 无论您是传承人、爱好者还是研究者,都可以参与到非遗保护与传承中来 + + + + + + + + + +
+
+
+ ) +} + +export default Home diff --git a/src/pages/Inheritor/Detail.css b/src/pages/Inheritor/Detail.css new file mode 100644 index 0000000..d57dfd7 --- /dev/null +++ b/src/pages/Inheritor/Detail.css @@ -0,0 +1,276 @@ +/* 传承人详情页样式 */ + +.inheritor-detail-page { + min-height: 100vh; + background: #fafaf8; +} + +.page-breadcrumb { + padding: 24px 0; + background: #ffffff; + border-bottom: 1px solid #f0ebe3; +} + +.detail-content { + background: #fafaf8; +} + +/* 传承人信息头部 */ +.detail-header { + margin-bottom: 32px; + background: #ffffff; + padding: 32px; + border-radius: 12px; +} + +.inheritor-profile { + display: flex; + gap: 24px; + align-items: flex-start; +} + +.inheritor-avatar { + flex-shrink: 0; +} + +.inheritor-info { + flex: 1; +} + +.detail-title { + font-size: 32px; + font-weight: 700; + color: #2c2c2c; + margin-bottom: 8px; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.inheritor-title { + font-size: 16px; + color: #d4a574; + font-weight: 500; + margin-bottom: 16px; +} + +.detail-meta { + display: flex; + align-items: center; + gap: 16px; + font-size: 14px; + color: #666666; + margin-bottom: 12px; + flex-wrap: wrap; +} + +.detail-meta .anticon { + margin-right: 6px; + color: #d4a574; +} + +.detail-stats { + display: flex; + gap: 24px; + font-size: 14px; + color: #666666; + padding-top: 16px; + border-top: 1px solid #f0ebe3; +} + +.detail-stats .anticon { + margin-right: 6px; + color: #d4a574; +} + +/* 详情卡片区块 */ +.detail-section { + margin-bottom: 32px; +} + +/* 作品展示 */ +.detail-gallery { + margin-bottom: 32px; + background: #ffffff; + padding: 32px; + border-radius: 12px; +} + +.detail-gallery h2 { + font-size: 20px; + font-weight: 600; + color: #2c2c2c; + margin-bottom: 24px; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.detail-gallery .ant-image-preview-group { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 16px; +} + +.detail-gallery .ant-image { + border-radius: 8px; + overflow: hidden; +} + +/* 详细内容标签页 */ +.detail-tabs { + margin-bottom: 40px; + background: #ffffff; + border-radius: 12px; + padding: 24px; +} + +.detail-text { + font-size: 15px; + line-height: 1.8; + color: #666666; +} + +.detail-text p { + margin-bottom: 16px; +} + +/* 获奖记录 */ +.awards-section { + margin-top: 32px; + padding-top: 32px; + border-top: 1px solid #f0ebe3; +} + +.awards-section h3 { + font-size: 18px; + font-weight: 600; + color: #2c2c2c; + margin-bottom: 16px; +} + +.awards-list { + list-style: none; + padding: 0; +} + +.awards-list li { + padding: 12px 0; + border-bottom: 1px solid #f5f0e8; + font-size: 14px; + color: #666666; +} + +.awards-list li:last-child { + border-bottom: none; +} + +/* 侧边栏 */ +.detail-sidebar { + position: sticky; + top: 80px; +} + +.sidebar-actions { + margin-bottom: 24px; +} + +.sidebar-actions button { + width: 100%; +} + +.sidebar-info { + background: #ffffff; + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; +} + +.sidebar-info h3 { + font-size: 16px; + font-weight: 600; + margin-bottom: 16px; + color: #2c2c2c; +} + +.info-item { + display: flex; + padding: 12px 0; + border-bottom: 1px solid #f0ebe3; +} + +.info-item:last-child { + border-bottom: none; +} + +.info-label { + width: 80px; + color: #999999; + font-size: 14px; +} + +.info-value { + flex: 1; + color: #2c2c2c; + font-size: 14px; + font-weight: 500; +} + +.sidebar-card { + border-radius: 12px; + margin-bottom: 24px; +} + +.sidebar-card .ant-card-head { + border-bottom: 1px solid #f0ebe3; +} + +.sidebar-card .ant-card-head-title { + font-size: 16px; + font-weight: 600; + color: #2c2c2c; +} + +/* 响应式 */ +@media (max-width: 992px) { + .detail-sidebar { + position: static; + } + + .detail-title { + font-size: 24px; + } + + .inheritor-profile { + flex-direction: column; + align-items: center; + text-align: center; + } + + .detail-meta { + justify-content: center; + } + + .detail-stats { + justify-content: center; + } +} + +@media (max-width: 576px) { + .detail-header { + padding: 20px; + } + + .detail-gallery { + padding: 20px; + } + + .inheritor-avatar { + width: 80px !important; + height: 80px !important; + } + + .detail-title { + font-size: 20px; + } + + .detail-gallery .ant-image-preview-group { + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + } +} diff --git a/src/pages/Inheritor/Detail.tsx b/src/pages/Inheritor/Detail.tsx new file mode 100644 index 0000000..2c7572b --- /dev/null +++ b/src/pages/Inheritor/Detail.tsx @@ -0,0 +1,366 @@ +/** + * 传承人详情页 + */ + +import React, { useEffect, useState } from 'react' +import { useParams } from 'react-router-dom' +import { Row, Col, Tag, Breadcrumb, Image, Tabs, Spin, Empty, Avatar, Card, Typography } from 'antd' +import { + HomeOutlined, + EnvironmentOutlined, + EyeOutlined, + TrophyOutlined, + UserOutlined, + CalendarOutlined, + ManOutlined, + WomanOutlined, + TeamOutlined, + PlayCircleOutlined, +} from '@ant-design/icons' +import { Link } from 'react-router-dom' +import type { Inheritor } from '@types/index' +import { getInheritorById } from '@services/api' +import './Detail.css' + +const { Title, Paragraph, Text } = Typography + +const levelLabels: Record = { + national: '国家级', + provincial: '省级', + municipal: '市级', +} + +const InheritorDetail: 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 (inheritorId: string) => { + setLoading(true) + try { + const result = await getInheritorById(inheritorId) + setData(result) + } catch (error) { + console.error('Failed to fetch inheritor detail:', error) + } finally { + setLoading(false) + } + } + + if (loading) { + return ( +
+ +
+ ) + } + + if (!data) { + return ( +
+ +
+ ) + } + + const age = new Date().getFullYear() - data.birthYear + + return ( +
+ {/* 面包屑导航 */} +
+
+ + + + 首页 + + + + 传承人 + + {data.name} + +
+
+ + {/* 封面图 */} + {data.coverImage && ( +
+
+
+ )} + + {/* 详情内容 */} +
+
+ + + {/* 头部信息 */} +
+
+ } className="inheritor-avatar" /> +
+ + {data.name} + + {data.title} +
+ {levelLabels[data.level]}传承人 + + {data.province} {data.city && `· ${data.city}`} + + + {data.gender === 'male' ? : } + {data.gender === 'male' ? '男' : '女'} + + + {age}岁 + +
+
+ + {data.viewCount.toLocaleString()} 浏览 + + + {data.followers.toLocaleString()} 关注 + +
+
+
+
+ + {/* 个人简介 */} + + + {data.bio} + + + + {/* 技艺特点 */} + + + {data.masterSkills} + + + + {/* 成就荣誉 */} + {data.achievements && data.achievements.length > 0 && ( + +
+ {data.achievements.map((achievement) => ( +
+
+ + + {achievement.title} + +
+ + {achievement.date} + + + {achievement.description} + + {achievement.images && achievement.images.length > 0 && ( + +
+ {achievement.images.map((img, idx) => ( + + ))} +
+
+ )} +
+ ))} +
+
+ )} + + {/* 获奖记录 */} + {data.awards && data.awards.length > 0 && ( + +
+ {data.awards.map((award) => ( +
+ +
+ {award.name} + + {award.organization} · {award.year}年 · {award.level} + +
+
+ ))} +
+
+ )} + + {/* 代表作品 */} + {data.works && data.works.length > 0 && ( + +
+ {data.works.map((work) => ( +
+ + + {work.name} + + + {work.description} + + + 创作于 {work.year}年 + +
+ ))} +
+
+ )} + + {/* 相关视频 */} + {data.videos && data.videos.length > 0 && ( + +
+ {data.videos.map((video) => ( +
+
+ +
+ + {video.title} + + {video.description && ( + + {video.description} + + )} +
+ + {video.viewCount.toLocaleString()} + + {Math.floor(video.duration / 60)}:{(video.duration % 60).toString().padStart(2, '0')} +
+
+ ))} +
+
+ )} + + + {/* 侧边栏 */} + +
+ {/* 基本信息 */} + +
+ 姓名: + {data.name} +
+
+ 性别: + {data.gender === 'male' ? '男' : '女'} +
+
+ 年龄: + {age}岁 +
+
+ 级别: + {levelLabels[data.level]}传承人 +
+
+ 地区: + + {data.province} + {data.city && ` · ${data.city}`} + +
+
+ + {/* 联系方式 */} + {data.contactInfo && ( + + {data.contactInfo.phone && ( +
+ 电话: + {data.contactInfo.phone} +
+ )} + {data.contactInfo.email && ( +
+ 邮箱: + {data.contactInfo.email} +
+ )} + {data.contactInfo.address && ( +
+ 地址: + {data.contactInfo.address} +
+ )} + {data.contactInfo.website && ( + + )} +
+ )} + + {/* 社交媒体 */} + {data.socialMedia && ( + + {data.socialMedia.weibo && ( +
+ 微博: + {data.socialMedia.weibo} +
+ )} + {data.socialMedia.wechat && ( +
+ 微信: + {data.socialMedia.wechat} +
+ )} + {data.socialMedia.douyin && ( +
+ 抖音: + {data.socialMedia.douyin} +
+ )} + {data.socialMedia.bilibili && ( +
+ B站: + {data.socialMedia.bilibili} +
+ )} +
+ )} + + {/* 技艺传承 */} + + + 如果您对该传承人的技艺感兴趣,可以通过联系方式咨询学习事宜,共同传承非遗文化。 + + +
+ +
+
+
+
+ ) +} + +export default InheritorDetail diff --git a/src/pages/Inheritors/List.css b/src/pages/Inheritors/List.css new file mode 100644 index 0000000..c24dbd0 --- /dev/null +++ b/src/pages/Inheritors/List.css @@ -0,0 +1,58 @@ +/* 传承人列表页样式 */ + +.inheritors-list-page { + min-height: 100vh; +} + +.page-header { + background: linear-gradient(135deg, #2a5e4d 0%, #1a4a3a 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: #ffffff; +} + +.loading-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 400px; +} + +.pagination-container { + display: flex; + justify-content: center; + margin-top: 48px; +} + +/* 响应式 */ +@media (max-width: 768px) { + .page-header { + padding: 60px 0 40px; + } + + .page-header h1 { + font-size: 32px; + } + + .page-header p { + font-size: 16px; + } +} diff --git a/src/pages/Inheritors/List.tsx b/src/pages/Inheritors/List.tsx new file mode 100644 index 0000000..e685c29 --- /dev/null +++ b/src/pages/Inheritors/List.tsx @@ -0,0 +1,89 @@ +/** + * 传承人列表页 + */ + +import React, { useEffect, useState } from 'react' +import { Row, Col, Spin, Empty } from 'antd' +import InheritorCard from '@components/InheritorCard' +import CustomPagination from '@components/CustomPagination' +import { getInheritorList } from '@services/api' +import type { Inheritor, PaginationResult } from '@types/index' +import './List.css' + +const InheritorsList: React.FC = () => { + const [data, setData] = useState | null>(null) + const [loading, setLoading] = useState(true) + const [currentPage, setCurrentPage] = useState(1) + const [pageSize, setPageSize] = useState(12) + + const fetchData = async (page: number, size: number) => { + setLoading(true) + try { + const result = await getInheritorList({ page, pageSize: size }) + setData(result) + } catch (error) { + console.error('Failed to fetch inheritors list:', error) + } finally { + setLoading(false) + } + } + + useEffect(() => { + fetchData(currentPage, pageSize) + }, [currentPage, pageSize]) + + const handlePageChange = (page: number, size: number) => { + setCurrentPage(page) + setPageSize(size) + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + + const handleShowSizeChange = (current: number, size: number) => { + setCurrentPage(1) + setPageSize(size) + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + + return ( +
+
+
+

代表性传承人

+

致敬守护文化根脉的匠人大师

+
+
+ +
+
+ {loading ? ( +
+ +
+ ) : data && data.data.length > 0 ? ( + <> + + {data.data.map((item) => ( + + + + ))} + + + + ) : ( + + )} +
+
+
+ ) +} + +export default InheritorsList diff --git a/src/pages/Search/index.css b/src/pages/Search/index.css new file mode 100644 index 0000000..ad02906 --- /dev/null +++ b/src/pages/Search/index.css @@ -0,0 +1,133 @@ +/* 搜索页样式 */ + +.search-page { + min-height: 100vh; + background: #fafaf8; +} + +.search-header { + background: linear-gradient(135deg, #d4a574 0%, #c8a060 100%); + padding: 80px 0 60px; + text-align: center; + color: #ffffff; +} + +.search-header h1 { + font-size: 42px; + font-weight: 700; + color: #ffffff; + margin-bottom: 32px; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.search-input { + max-width: 600px; + margin: 0 auto 24px; +} + +.search-input .ant-input-wrapper { + border-radius: 24px; + overflow: hidden; +} + +.search-input input { + border: none; + font-size: 16px; + padding: 12px 24px; +} + +.search-input .ant-btn { + height: 48px; + border: none; + background: #c8363d; + color: #ffffff; + font-weight: 600; +} + +.search-input .ant-btn:hover { + background: #a82f35; +} + +.search-stats { + color: rgba(255, 255, 255, 0.9); + font-size: 16px; + margin: 0; +} + +.search-stats strong { + color: #ffffff; + font-weight: 700; + font-size: 20px; + margin: 0 8px; +} + +/* 搜索结果 */ +.search-content { + background: #fafaf8; +} + +.search-content .ant-tabs { + background: #ffffff; + border-radius: 12px; + padding: 24px; +} + +.result-section { + margin-bottom: 48px; +} + +.result-section:last-child { + margin-bottom: 0; +} + +.result-section h2 { + font-size: 20px; + font-weight: 600; + color: #2c2c2c; + margin-bottom: 24px; + padding-bottom: 12px; + border-bottom: 2px solid #d4a574; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.card-cover { + position: relative; + width: 100%; + padding-top: 66.67%; + overflow: hidden; + background: #f5f0e8; +} + +.card-cover img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; +} + +/* 响应式 */ +@media (max-width: 768px) { + .search-header { + padding: 60px 0 40px; + } + + .search-header h1 { + font-size: 32px; + } + + .search-input { + max-width: 100%; + } +} + +@media (max-width: 576px) { + .search-header h1 { + font-size: 28px; + } + + .result-section h2 { + font-size: 18px; + } +} diff --git a/src/pages/Search/index.tsx b/src/pages/Search/index.tsx new file mode 100644 index 0000000..9e17bbf --- /dev/null +++ b/src/pages/Search/index.tsx @@ -0,0 +1,266 @@ +/** + * 搜索页 + */ + +import React, { useEffect, useState } from 'react' +import { useSearchParams, Link } from 'react-router-dom' +import { Input, Tabs, List, Card, Tag, Empty, Spin, Row, Col, Avatar } from 'antd' +import { + SearchOutlined, + EnvironmentOutlined, + UserOutlined, + BookOutlined, + FileTextOutlined, +} from '@ant-design/icons' +import type { SearchResults } from '@types/index' +import { searchAll } from '@services/api' +import './index.css' + +const { Search } = Input +const { TabPane } = Tabs + +const SearchPage: React.FC = () => { + const [searchParams, setSearchParams] = useSearchParams() + const keyword = searchParams.get('q') || '' + const [searchKeyword, setSearchKeyword] = useState(keyword) + const [results, setResults] = useState(null) + const [loading, setLoading] = useState(false) + const [activeTab, setActiveTab] = useState('all') + + useEffect(() => { + if (keyword) { + setSearchKeyword(keyword) + handleSearch(keyword) + } + }, [keyword]) + + const handleSearch = async (value: string) => { + if (!value.trim()) return + + setLoading(true) + try { + const result = await searchAll(value) + setResults(result) + setSearchParams({ q: value }) + } catch (error) { + console.error('Search failed:', error) + } finally { + setLoading(false) + } + } + + const getTotalCount = () => { + if (!results) return 0 + return ( + results.heritages.length + + results.inheritors.length + + results.news.length + ) + } + + return ( +
+
+
+

搜索

+ } + size="large" + value={searchKeyword} + onChange={(e) => setSearchKeyword(e.target.value)} + onSearch={handleSearch} + className="search-input" + /> + {results && ( +

+ 为您找到 {getTotalCount()} 条相关结果 +

+ )} +
+
+ +
+
+ {loading ? ( +
+ +
+ ) : results ? ( + + + {getTotalCount() > 0 ? ( +
+ {/* 非遗项目 */} + {results.heritages.length > 0 && ( +
+

非遗项目 ({results.heritages.length})

+ + {results.heritages.slice(0, 3).map((item) => ( + + + + {item.name} +
+ } + > + + {item.level} + {item.category} +
+ } + /> + + + + ))} + +
+ )} + + {/* 传承人 */} + {results.inheritors.length > 0 && ( +
+

传承人 ({results.inheritors.length})

+ ( + + } + title={{item.name}} + description={ +
+

{item.title}

+
+ {item.category} + {item.level} + + {item.province} + +
+
+ } + /> +
+ )} + /> +
+ )} + + {/* 新闻 */} + {results.news.length > 0 && ( +
+

新闻资讯 ({results.news.length})

+ ( + + } + title={{item.title}} + description={item.summary} + /> + + )} + /> +
+ )} +
+ ) : ( + + )} + + + + {results.heritages.length > 0 ? ( + + {results.heritages.map((item) => ( + + + + {item.name} +
+ } + > + + {item.level} + {item.category} +
+ } + /> + + + + ))} + + ) : ( + + )} + + + + {results.inheritors.length > 0 ? ( + ( + + } + title={{item.name}} + description={ +
+

{item.title}

+
+ {item.category} + {item.level} + + {item.province} + +
+
+ } + /> +
+ )} + /> + ) : ( + + )} +
+ + ) : ( + + )} + + + + ) +} + +export default SearchPage diff --git a/src/pages/User/Center.css b/src/pages/User/Center.css new file mode 100644 index 0000000..1b8e0be --- /dev/null +++ b/src/pages/User/Center.css @@ -0,0 +1,172 @@ +/* 用户中心页样式 */ + +.user-center-page { + min-height: 100vh; + background: #fafaf8; +} + +.page-header { + background: linear-gradient(135deg, #d4a574 0%, #c8a060 100%); + padding: 60px 0; + color: #ffffff; +} + +.user-profile { + text-align: center; +} + +.user-profile h2 { + font-size: 24px; + font-weight: 600; + color: #ffffff; + margin: 16px 0 8px; +} + +.user-email { + font-size: 14px; + color: rgba(255, 255, 255, 0.8); + margin-bottom: 20px; +} + +.user-actions { + display: flex; + gap: 12px; + justify-content: center; +} + +.user-actions button { + border-radius: 20px; + background: rgba(255, 255, 255, 0.2); + border: 1px solid rgba(255, 255, 255, 0.3); + color: #ffffff; + backdrop-filter: blur(10px); +} + +.user-actions button:hover { + background: rgba(255, 255, 255, 0.3); + border-color: rgba(255, 255, 255, 0.4); + color: #ffffff; +} + +.user-stats { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 24px; + background: rgba(255, 255, 255, 0.15); + padding: 32px; + border-radius: 12px; + backdrop-filter: blur(10px); +} + +.user-stats .ant-statistic { + text-align: center; +} + +.user-stats .ant-statistic-title { + color: rgba(255, 255, 255, 0.9); + font-size: 14px; +} + +.user-stats .ant-statistic-content { + color: #ffffff; + font-size: 28px; + font-weight: 700; +} + +.user-stats .anticon { + color: rgba(255, 255, 255, 0.8); +} + +/* 页面内容 */ +.page-content { + background: #fafaf8; +} + +.page-content .ant-tabs { + background: #ffffff; + padding: 24px; + border-radius: 12px; +} + +/* 卡片封面 */ +.card-cover { + position: relative; + width: 100%; + padding-top: 66.67%; + overflow: hidden; + background: #f5f0e8; +} + +.card-cover img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; +} + +/* 成就网格 */ +.achievements-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 24px; +} + +.achievement-card { + text-align: center; + padding: 32px 16px; + border-radius: 12px; + transition: all 0.3s ease; +} + +.achievement-card.locked { + opacity: 0.5; + filter: grayscale(100%); +} + +.achievement-card:not(.locked):hover { + transform: translateY(-4px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); +} + +.achievement-icon { + font-size: 48px; + color: #d4a574; + margin-bottom: 16px; +} + +.achievement-card h3 { + font-size: 16px; + font-weight: 600; + color: #2c2c2c; + margin-bottom: 8px; +} + +.achievement-card p { + font-size: 13px; + color: #999999; + margin: 0; +} + +/* 响应式 */ +@media (max-width: 992px) { + .user-stats { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 576px) { + .page-header { + padding: 40px 0; + } + + .user-stats { + grid-template-columns: 1fr; + padding: 20px; + } + + .achievements-grid { + grid-template-columns: 1fr; + } +} diff --git a/src/pages/User/Center.tsx b/src/pages/User/Center.tsx new file mode 100644 index 0000000..c3a0bcc --- /dev/null +++ b/src/pages/User/Center.tsx @@ -0,0 +1,173 @@ +/** + * 用户中心页 + */ + +import React, { useEffect, useState } from 'react' +import { Tabs, Card, Avatar, Button, List, Empty, Tag, Row, Col, Statistic } from 'antd' +import { + UserOutlined, + HeartOutlined, + BookOutlined, + CommentOutlined, + EditOutlined, + TrophyOutlined, + ClockCircleOutlined, +} from '@ant-design/icons' +import { Link, useNavigate } from 'react-router-dom' +import { useUserStore } from '@store/useUserStore' +import type { HeritageItem } from '@types/index' +import { getHeritageById } from '@services/api' +import './Center.css' + +const { TabPane } = Tabs + +const UserCenter: React.FC = () => { + const { user, isAuthenticated, logout } = useUserStore() + const navigate = useNavigate() + const [favorites, setFavorites] = useState([]) + const [loading, setLoading] = useState(false) + + useEffect(() => { + if (!isAuthenticated) { + navigate('/login') + return + } + fetchUserData() + }, [isAuthenticated]) + + const fetchUserData = async () => { + if (!user) return + + setLoading(true) + try { + // 获取收藏的非遗项目 + const favoriteItems = await Promise.all( + user.favorites.slice(0, 6).map((id) => getHeritageById(id)) + ) + setFavorites(favoriteItems) + } catch (error) { + console.error('Failed to fetch user data:', error) + } finally { + setLoading(false) + } + } + + const handleLogout = () => { + logout() + navigate('/') + } + + if (!user) { + return ( +
+ +
+ ) + } + + return ( +
+
+
+ + +
+ } /> +

{user.username}

+

{user.email}

+
+ + +
+
+ + +
+ } + /> + } + /> +
+ +
+
+
+ +
+
+ + + {favorites.length > 0 ? ( + + {favorites.map((item) => ( + + + + {item.name} +
+ } + > + + {item.level} + {item.category} +
+ } + /> + + + + ))} + + ) : ( + + )} + + + + + + + +
+ + +

文化探索者

+

收藏第一个非遗项目

+
+ + +

热心评论家

+

发表10条评论

+
+ + +

非遗达人

+

收藏50个非遗项目

+
+ + +

文化使者

+

分享5次非遗知识

+
+
+
+ +
+ + + ) +} + +export default UserCenter diff --git a/src/pages/User/Login.css b/src/pages/User/Login.css new file mode 100644 index 0000000..1067a90 --- /dev/null +++ b/src/pages/User/Login.css @@ -0,0 +1,180 @@ +/* 用户登录页样式 */ + +.login-page { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #f5f0e8 0%, #ffffff 100%); + padding: 40px 20px; +} + +.login-container { + width: 100%; + max-width: 1000px; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0; + background: #ffffff; + border-radius: 16px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08); + overflow: hidden; +} + +.login-card { + padding: 60px 50px; +} + +.login-header { + text-align: center; + margin-bottom: 40px; +} + +.login-header h1 { + font-size: 28px; + font-weight: 700; + color: #2c2c2c; + margin-bottom: 8px; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.login-header p { + font-size: 14px; + color: #999999; + margin: 0; +} + +.login-options { + display: flex; + justify-content: flex-end; +} + +.forgot-link { + color: #d4a574; + font-size: 14px; +} + +.forgot-link:hover { + color: #c8a060; +} + +.social-login { + display: flex; + gap: 12px; + margin-bottom: 24px; +} + +.social-btn { + flex: 1; + border-radius: 8px; +} + +.social-btn.wechat { + color: #07c160; + border-color: #07c160; +} + +.social-btn.wechat:hover { + color: #ffffff; + background: #07c160; + border-color: #07c160; +} + +.social-btn.alipay { + color: #1677ff; + border-color: #1677ff; +} + +.social-btn.alipay:hover { + color: #ffffff; + background: #1677ff; + border-color: #1677ff; +} + +.register-link { + text-align: center; + font-size: 14px; + color: #666666; +} + +.register-link a { + color: #d4a574; + font-weight: 500; + margin-left: 8px; +} + +.register-link a:hover { + color: #c8a060; +} + +/* 插图区域 */ +.login-illustration { + background: linear-gradient(135deg, #d4a574 0%, #c8a060 100%); + padding: 60px 50px; + display: flex; + align-items: center; + justify-content: center; + color: #ffffff; +} + +.illustration-content { + text-align: center; +} + +.illustration-content h2 { + font-size: 32px; + font-weight: 700; + margin-bottom: 16px; + color: #ffffff; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.illustration-content p { + font-size: 16px; + color: rgba(255, 255, 255, 0.9); + margin-bottom: 40px; +} + +.illustration-image { + width: 100%; + max-width: 300px; + margin: 0 auto; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); +} + +.illustration-image img { + width: 100%; + height: auto; + display: block; +} + +/* 响应式 */ +@media (max-width: 992px) { + .login-container { + grid-template-columns: 1fr; + } + + .login-illustration { + display: none; + } + + .login-card { + padding: 40px 30px; + } +} + +@media (max-width: 576px) { + .login-card { + padding: 30px 20px; + } + + .login-header h1 { + font-size: 24px; + } + + .social-login { + flex-direction: column; + } +} diff --git a/src/pages/User/Login.tsx b/src/pages/User/Login.tsx new file mode 100644 index 0000000..dd746a3 --- /dev/null +++ b/src/pages/User/Login.tsx @@ -0,0 +1,107 @@ +/** + * 用户登录页 + */ + +import React, { useState } from 'react' +import { Form, Input, Button, message, Divider } from 'antd' +import { UserOutlined, LockOutlined, WechatOutlined, AlipayOutlined } from '@ant-design/icons' +import { Link, useNavigate } from 'react-router-dom' +import { useUserStore } from '@store/useUserStore' +import './Login.css' + +const Login: React.FC = () => { + const [loading, setLoading] = useState(false) + const navigate = useNavigate() + const login = useUserStore((state) => state.login) + + const onFinish = async (values: { username: string; password: string }) => { + setLoading(true) + try { + await login(values.username, values.password) + message.success('登录成功') + navigate('/') + } catch (error) { + message.error('登录失败,请检查用户名或密码') + } finally { + setLoading(false) + } + } + + return ( +
+
+
+
+

欢迎回来

+

登录以继续您的非遗文化之旅

+
+ +
+ + } placeholder="用户名" /> + + + + } placeholder="密码" /> + + + +
+ + 忘记密码? + +
+
+ + + + +
+ + 其他登录方式 + +
+ + +
+ +
+ 还没有账号?立即注册 +
+
+ +
+
+

传承非遗文化

+

探索中华文化瑰宝,传承千年技艺精髓

+
+ 传统文化 +
+
+
+
+
+ ) +} + +export default Login diff --git a/src/pages/User/Register.css b/src/pages/User/Register.css new file mode 100644 index 0000000..97e9e4d --- /dev/null +++ b/src/pages/User/Register.css @@ -0,0 +1,146 @@ +/* 用户注册页样式 */ + +.register-page { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #f5f0e8 0%, #ffffff 100%); + padding: 40px 20px; +} + +.register-container { + width: 100%; + max-width: 1000px; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0; + background: #ffffff; + border-radius: 16px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08); + overflow: hidden; +} + +.register-card { + padding: 60px 50px; +} + +.register-header { + text-align: center; + margin-bottom: 40px; +} + +.register-header h1 { + font-size: 28px; + font-weight: 700; + color: #2c2c2c; + margin-bottom: 8px; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.register-header p { + font-size: 14px; + color: #999999; + margin: 0; +} + +.login-link { + text-align: center; + font-size: 14px; + color: #666666; +} + +.login-link a { + color: #d4a574; + font-weight: 500; + margin-left: 8px; +} + +.login-link a:hover { + color: #c8a060; +} + +/* 插图区域 */ +.register-illustration { + background: linear-gradient(135deg, #c8363d 0%, #a82f35 100%); + padding: 60px 50px; + display: flex; + align-items: center; + justify-content: center; + color: #ffffff; +} + +.illustration-content { + text-align: center; +} + +.illustration-content h2 { + font-size: 32px; + font-weight: 700; + margin-bottom: 16px; + color: #ffffff; + font-family: 'Noto Serif SC', 'Songti SC', serif; +} + +.illustration-content > p { + font-size: 16px; + color: rgba(255, 255, 255, 0.9); + margin-bottom: 40px; +} + +.feature-list { + display: flex; + flex-direction: column; + gap: 24px; +} + +.feature-item { + text-align: left; + background: rgba(255, 255, 255, 0.1); + padding: 20px; + border-radius: 12px; + backdrop-filter: blur(10px); +} + +.feature-icon { + font-size: 32px; + margin-bottom: 12px; +} + +.feature-item h3 { + font-size: 18px; + font-weight: 600; + color: #ffffff; + margin-bottom: 8px; +} + +.feature-item p { + font-size: 14px; + color: rgba(255, 255, 255, 0.8); + margin: 0; +} + +/* 响应式 */ +@media (max-width: 992px) { + .register-container { + grid-template-columns: 1fr; + } + + .register-illustration { + display: none; + } + + .register-card { + padding: 40px 30px; + } +} + +@media (max-width: 576px) { + .register-card { + padding: 30px 20px; + } + + .register-header h1 { + font-size: 24px; + } +} diff --git a/src/pages/User/Register.tsx b/src/pages/User/Register.tsx new file mode 100644 index 0000000..9127051 --- /dev/null +++ b/src/pages/User/Register.tsx @@ -0,0 +1,167 @@ +/** + * 用户注册页 + */ + +import React, { useState } from 'react' +import { Form, Input, Button, message, Checkbox } from 'antd' +import { UserOutlined, LockOutlined, MailOutlined, PhoneOutlined } from '@ant-design/icons' +import { Link, useNavigate } from 'react-router-dom' +import { useUserStore } from '@store/useUserStore' +import './Register.css' + +const Register: React.FC = () => { + const [loading, setLoading] = useState(false) + const navigate = useNavigate() + const register = useUserStore((state) => state.register) + + const onFinish = async (values: { + username: string + email: string + phone: string + password: string + agree: boolean + }) => { + if (!values.agree) { + message.warning('请阅读并同意用户协议和隐私政策') + return + } + + setLoading(true) + try { + await register(values.username, values.email, values.password) + message.success('注册成功,欢迎加入非遗文化传承平台') + navigate('/') + } catch (error) { + message.error('注册失败,请稍后重试') + } finally { + setLoading(false) + } + } + + return ( +
+
+
+
+

加入我们

+

开启您的非遗文化传承之旅

+
+ +
+ + } placeholder="用户名" /> + + + + } placeholder="邮箱" /> + + + + } placeholder="手机号" /> + + + + } placeholder="密码" /> + + + ({ + validator(_, value) { + if (!value || getFieldValue('password') === value) { + return Promise.resolve() + } + return Promise.reject(new Error('两次输入的密码不一致')) + }, + }), + ]} + > + } placeholder="确认密码" /> + + + + + 我已阅读并同意 + 用户协议 + 和 + 隐私政策 + + + + + + +
+ +
+ 已有账号?立即登录 +
+
+ +
+
+

探索非遗之美

+

与千万用户一起,传承中华优秀传统文化

+
+
+
📚
+

在线学习

+

海量非遗课程,随时随地学习

+
+
+
👥
+

名师指导

+

跟随传承人学习传统技艺

+
+
+
🏆
+

获得认证

+

完成学习获得专业证书

+
+
+
+
+
+
+ ) +} + +export default Register