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) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+
👨
+
男性
+
+ {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.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
+ />
+
+ {/* 分类选择 */}
+
+
+ {/* 筛选结果提示 */}
+ {(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}
+
+
+
+
+
+ ))}
+
+
+
+
+ {/* 精选非遗项目 */}
+
+
+
+
+ {featuredHeritage.map((item) => (
+
+
+
+ ))}
+
+
+
+
+ {/* 代表性传承人 */}
+
+
+
+
代表性传承人
+
致敬守护文化根脉的匠人大师
+
+
+ {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.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.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.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="密码" />
+
+
+
+
+
+ 忘记密码?
+
+
+
+
+
+
+
+
+
+
其他登录方式
+
+
+ } className="social-btn wechat">
+ 微信登录
+
+ } className="social-btn alipay">
+ 支付宝登录
+
+
+
+
+ 还没有账号?立即注册
+
+
+
+
+
+
传承非遗文化
+
探索中华文化瑰宝,传承千年技艺精髓
+
+

+
+
+
+
+
+ )
+}
+
+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