This commit is contained in:
698
frontend/src/pages/Home.jsx
Normal file
698
frontend/src/pages/Home.jsx
Normal file
@@ -0,0 +1,698 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { Card, Row, Col, Tag, Button, Spin, Typography, Carousel } from 'antd';
|
||||
import { RocketOutlined, RightOutlined, LeftOutlined } from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { motion } from 'framer-motion';
|
||||
import { getConfigs, getHomePageConfig, getCompetitions, getActivities } from '../api';
|
||||
import './Home.css';
|
||||
|
||||
const { Title, Paragraph } = Typography;
|
||||
|
||||
// 获取完整图片 URL
|
||||
const getImageUrl = (url) => {
|
||||
if (!url) return '';
|
||||
if (url.startsWith('http') || url.startsWith('//')) return url;
|
||||
return `http://localhost:8876${url}`;
|
||||
};
|
||||
|
||||
const Home = () => {
|
||||
const [products, setProducts] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [typedText, setTypedText] = useState('');
|
||||
const [isTypingComplete, setIsTypingComplete] = useState(false);
|
||||
const [currentSlide, setCurrentSlide] = useState(0);
|
||||
const [currentSlide2, setCurrentSlide2] = useState(0);
|
||||
const [currentSlide3, setCurrentSlide3] = useState(0);
|
||||
const [homeConfig, setHomeConfig] = useState(null);
|
||||
const [carousel1Items, setCarousel1Items] = useState([]);
|
||||
const [competitions, setCompetitions] = useState([]);
|
||||
const [activities, setActivities] = useState([]);
|
||||
const fullText = "未来已来 AI 核心驱动";
|
||||
const defaultMainTitle = '"创赢未来"云南2026创业大赛';
|
||||
const [mainTitleText, setMainTitleText] = useState(defaultMainTitle);
|
||||
const navigate = useNavigate();
|
||||
const carouselRef = useRef(null);
|
||||
const carouselRef2 = useRef(null);
|
||||
const carouselRef3 = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchProducts();
|
||||
fetchHomePageConfig();
|
||||
fetchCompetitions();
|
||||
fetchActivities();
|
||||
}, []);
|
||||
|
||||
// 主标题打字机效果 - 在获取到配置后执行
|
||||
useEffect(() => {
|
||||
const title = homeConfig?.main_title || defaultMainTitle;
|
||||
setMainTitleText(title);
|
||||
setTypedText('');
|
||||
setIsTypingComplete(false);
|
||||
|
||||
let mainIndex = 0;
|
||||
const mainTypingInterval = setInterval(() => {
|
||||
mainIndex++;
|
||||
setTypedText(title.slice(0, mainIndex));
|
||||
if (mainIndex >= title.length) {
|
||||
clearInterval(mainTypingInterval);
|
||||
setIsTypingComplete(true);
|
||||
}
|
||||
}, 120);
|
||||
|
||||
return () => clearInterval(mainTypingInterval);
|
||||
}, [homeConfig?.main_title]);
|
||||
|
||||
const fetchProducts = async () => {
|
||||
try {
|
||||
const response = await getConfigs();
|
||||
setProducts(response.data);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch products:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchHomePageConfig = async () => {
|
||||
try {
|
||||
const response = await getHomePageConfig();
|
||||
const data = response.data;
|
||||
setHomeConfig(data);
|
||||
setCarousel1Items(data.carousel1_items || []);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch homepage config:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchCompetitions = async () => {
|
||||
try {
|
||||
const response = await getCompetitions({ page: 1, page_size: 10 });
|
||||
setCompetitions(response.data.results || []);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch competitions:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchActivities = async () => {
|
||||
try {
|
||||
const response = await getActivities();
|
||||
const data = Array.isArray(response.data) ? response.data : (response.data?.results || []);
|
||||
setActivities(data);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch activities:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const cardVariants = {
|
||||
hidden: { opacity: 0, y: 50 },
|
||||
visible: (i) => ({
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
delay: i * 0.1,
|
||||
duration: 0.5,
|
||||
type: "spring",
|
||||
stiffness: 100
|
||||
}
|
||||
}),
|
||||
hover: {
|
||||
scale: 1.05,
|
||||
rotateX: 5,
|
||||
rotateY: 5,
|
||||
transition: { duration: 0.3 }
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', height: '50vh' }}>
|
||||
<Spin size="large" />
|
||||
<div style={{ marginTop: 20, color: '#00b96b' }}>加载中...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ textAlign: 'center', marginBottom: 80 }}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
style={{ marginBottom: 50 }}
|
||||
>
|
||||
<Title level={1} style={{
|
||||
color: '#fff',
|
||||
fontSize: 'clamp(2.5rem, 6vw, 5rem)',
|
||||
marginBottom: 30,
|
||||
minHeight: '80px'
|
||||
}}>
|
||||
<span className="gold-text">{typedText}</span>
|
||||
{!isTypingComplete && <span className="cursor-blink-gold">|</span>}
|
||||
</Title>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.8, delay: 0.3 }}
|
||||
>
|
||||
<img
|
||||
src={getImageUrl(homeConfig?.display_banner) || '/shouye.png'}
|
||||
alt="首页Banner"
|
||||
style={{
|
||||
width: '100%',
|
||||
maxWidth: 1200,
|
||||
height: 'auto',
|
||||
borderRadius: 12,
|
||||
boxShadow: '0 8px 32px rgba(24, 144, 255, 0.3)'
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
{/* 轮播图 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.5 }}
|
||||
style={{ marginTop: 40, maxWidth: 1200, marginLeft: 'auto', marginRight: 'auto' }}
|
||||
>
|
||||
{/* 轮播图主体 */}
|
||||
<div style={{
|
||||
position: 'relative',
|
||||
borderRadius: 16,
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 8px 32px rgba(24, 144, 255, 0.2)',
|
||||
}}>
|
||||
<Carousel
|
||||
ref={carouselRef}
|
||||
autoplay
|
||||
dots={false}
|
||||
beforeChange={(_, next) => setCurrentSlide(next)}
|
||||
>
|
||||
{(carousel1Items.length > 0 ? carousel1Items : []).map((image, index) => (
|
||||
<div key={index}>
|
||||
<div style={{
|
||||
height: 450,
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
background: 'linear-gradient(135deg, #1890ff 0%, #69c0ff 100%)',
|
||||
}}>
|
||||
<img
|
||||
src={getImageUrl(image.display_image) || image.image_url}
|
||||
alt={image.title}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover'
|
||||
}}
|
||||
onError={(e) => {
|
||||
e.target.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
{/* 渐变遮罩 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'linear-gradient(to bottom, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.6) 100%)',
|
||||
}} />
|
||||
{/* 标题区域 - 图片上方 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 40,
|
||||
left: 40,
|
||||
textAlign: 'left',
|
||||
}}>
|
||||
<h2 style={{ fontSize: 36, fontWeight: 'bold', color: '#fff', margin: 0, textShadow: '2px 2px 4px rgba(0,0,0,0.3)' }}>
|
||||
{image.title}
|
||||
</h2>
|
||||
<p style={{ fontSize: 18, color: 'rgba(255,255,255,0.9)', margin: '8px 0 0 0', textShadow: '1px 1px 2px rgba(0,0,0,0.3)' }}>
|
||||
{image.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
{/* 底部信息 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
padding: '24px 40px',
|
||||
background: 'linear-gradient(to top, rgba(0,0,0,0.8) 0%, transparent 100%)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12 }}>
|
||||
<Tag style={{
|
||||
background: image.status_color,
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
fontSize: 14,
|
||||
padding: '4px 16px',
|
||||
borderRadius: 20,
|
||||
}}>
|
||||
{image.status}
|
||||
</Tag>
|
||||
<span style={{ color: 'rgba(255,255,255,0.8)', fontSize: 14 }}>{image.location}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<div>
|
||||
<h3 style={{ color: '#fff', fontSize: 24, fontWeight: 'bold', margin: 0 }}>{image.title}</h3>
|
||||
<p style={{ color: 'rgba(255,255,255,0.8)', fontSize: 14, margin: '8px 0 0 0' }}>
|
||||
<span style={{ marginRight: 20 }}>📅 {image.date}</span>
|
||||
<span>📍 {image.location}</span>
|
||||
</p>
|
||||
</div>
|
||||
<Button type="primary" size="large" style={{ borderRadius: 24, paddingLeft: 24, paddingRight: 24 }} onClick={() => navigate('/competitions')}>
|
||||
立即报名
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Carousel>
|
||||
|
||||
{/* 自定义分页指示器 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: 100,
|
||||
right: 40,
|
||||
display: 'flex',
|
||||
gap: 8,
|
||||
}}>
|
||||
{(carousel1Items.length > 0 ? carousel1Items : []).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => carouselRef.current?.goTo(index)}
|
||||
style={{
|
||||
width: currentSlide === index ? 32 : 10,
|
||||
height: 10,
|
||||
borderRadius: 5,
|
||||
background: currentSlide === index ? '#fff' : 'rgba(255,255,255,0.4)',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* 赛事中心轮播图 */}
|
||||
{competitions.length > 0 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.5 }}
|
||||
style={{ marginTop: 40, maxWidth: 1200, marginLeft: 'auto', marginRight: 'auto' }}
|
||||
>
|
||||
{/* 标题区域 */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: 24, justifyContent: 'space-between' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
|
||||
<span style={{ fontSize: 32, fontWeight: 'bold', color: '#fff', paddingLeft: 16, borderLeft: '4px solid #1890ff' }}>赛事中心</span>
|
||||
<span style={{ fontSize: 16, color: 'rgba(255,255,255,0.6)', letterSpacing: 2 }}>COMPETITIONS</span>
|
||||
</div>
|
||||
{/* 箭头导航 */}
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<div
|
||||
onClick={() => carouselRef3.current?.prev()}
|
||||
onMouseEnter={(e) => { e.currentTarget.style.background = '#1890ff'; e.currentTarget.querySelector('svg').style.color = '#fff'; }}
|
||||
onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; e.currentTarget.querySelector('svg').style.color = '#1890ff'; }}
|
||||
style={{
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: '50%',
|
||||
background: 'transparent',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: 'pointer',
|
||||
border: '2px solid #1890ff',
|
||||
transition: 'all 0.3s'
|
||||
}}
|
||||
>
|
||||
<LeftOutlined style={{ fontSize: 18, color: '#1890ff', transition: 'color 0.3s' }} />
|
||||
</div>
|
||||
<div
|
||||
onClick={() => carouselRef3.current?.next()}
|
||||
onMouseEnter={(e) => { e.currentTarget.style.background = '#1890ff'; e.currentTarget.querySelector('svg').style.color = '#fff'; }}
|
||||
onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; e.currentTarget.querySelector('svg').style.color = '#1890ff'; }}
|
||||
style={{
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: '50%',
|
||||
background: 'transparent',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: 'pointer',
|
||||
border: '2px solid #1890ff',
|
||||
transition: 'all 0.3s'
|
||||
}}
|
||||
>
|
||||
<RightOutlined style={{ fontSize: 18, color: '#1890ff', transition: 'color 0.3s' }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 轮播图主体 */}
|
||||
<div style={{
|
||||
position: 'relative',
|
||||
borderRadius: 16,
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 8px 32px rgba(24, 144, 255, 0.2)',
|
||||
}}>
|
||||
<Carousel
|
||||
ref={carouselRef3}
|
||||
autoplay
|
||||
dots={false}
|
||||
beforeChange={(_, next) => setCurrentSlide3(next)}
|
||||
>
|
||||
{competitions.map((competition, index) => (
|
||||
<div key={competition.id}>
|
||||
<div style={{
|
||||
height: 450,
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
background: 'linear-gradient(135deg, #1890ff 0%, #69c0ff 100%)',
|
||||
}}>
|
||||
<img
|
||||
src={getImageUrl(competition.display_cover_image) || competition.cover_image}
|
||||
alt={competition.title}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover'
|
||||
}}
|
||||
onError={(e) => {
|
||||
e.target.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
{/* 渐变遮罩 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'linear-gradient(to bottom, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.6) 100%)',
|
||||
}} />
|
||||
{/* 标题区域 - 图片上方 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 40,
|
||||
left: 40,
|
||||
textAlign: 'left',
|
||||
}}>
|
||||
<h2 style={{ fontSize: 36, fontWeight: 'bold', color: '#fff', margin: 0, textShadow: '2px 2px 4px rgba(0,0,0,0.3)' }}>
|
||||
{competition.title}
|
||||
</h2>
|
||||
</div>
|
||||
{/* 底部信息 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
padding: '24px 40px',
|
||||
background: 'linear-gradient(to top, rgba(0,0,0,0.8) 0%, transparent 100%)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12 }}>
|
||||
<Tag color={competition.status === 'registration' ? 'green' : 'default'} style={{ fontSize: 14, padding: '4px 16px', borderRadius: 20 }}>
|
||||
{competition.status_display}
|
||||
</Tag>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<div>
|
||||
<h3 style={{ color: '#fff', fontSize: 24, fontWeight: 'bold', margin: 0 }}>{competition.title}</h3>
|
||||
<p style={{ color: 'rgba(255,255,255,0.8)', fontSize: 14, margin: '8px 0 0 0' }}>
|
||||
<span style={{ marginRight: 20 }}>📅 {competition.start_time?.split('T')[0]} ~ {competition.end_time?.split('T')[0]}</span>
|
||||
</p>
|
||||
</div>
|
||||
<Button type="primary" size="large" style={{ borderRadius: 24, paddingLeft: 24, paddingRight: 24 }} onClick={() => navigate(`/competitions/${competition.id}`)}>
|
||||
查看详情
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Carousel>
|
||||
|
||||
{/* 自定义分页指示器 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: 100,
|
||||
right: 40,
|
||||
display: 'flex',
|
||||
gap: 8,
|
||||
}}>
|
||||
{competitions.map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => carouselRef3.current?.goTo(index)}
|
||||
style={{
|
||||
width: currentSlide3 === index ? 32 : 10,
|
||||
height: 10,
|
||||
borderRadius: 5,
|
||||
background: currentSlide3 === index ? '#fff' : 'rgba(255,255,255,0.4)',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* 系列活动轮播图 */}
|
||||
{activities.length > 0 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.7 }}
|
||||
style={{ marginTop: 40, maxWidth: 1200, marginLeft: 'auto', marginRight: 'auto' }}
|
||||
>
|
||||
{/* 标题区域 */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: 24, justifyContent: 'space-between' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
|
||||
<span style={{ fontSize: 32, fontWeight: 'bold', color: '#fff', paddingLeft: 16, borderLeft: '4px solid #1890ff' }}>系列活动</span>
|
||||
<span style={{ fontSize: 16, color: 'rgba(255,255,255,0.6)', letterSpacing: 2 }}>ACTIVITIES</span>
|
||||
</div>
|
||||
{/* 箭头导航 */}
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<div
|
||||
onClick={() => carouselRef2.current?.prev()}
|
||||
onMouseEnter={(e) => { e.currentTarget.style.background = '#1890ff'; e.currentTarget.querySelector('svg').style.color = '#fff'; }}
|
||||
onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; e.currentTarget.querySelector('svg').style.color = '#1890ff'; }}
|
||||
style={{
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: '50%',
|
||||
background: 'transparent',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: 'pointer',
|
||||
border: '2px solid #1890ff',
|
||||
transition: 'all 0.3s'
|
||||
}}
|
||||
>
|
||||
<LeftOutlined style={{ fontSize: 18, color: '#1890ff', transition: 'color 0.3s' }} />
|
||||
</div>
|
||||
<div
|
||||
onClick={() => carouselRef2.current?.next()}
|
||||
onMouseEnter={(e) => { e.currentTarget.style.background = '#1890ff'; e.currentTarget.querySelector('svg').style.color = '#fff'; }}
|
||||
onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; e.currentTarget.querySelector('svg').style.color = '#1890ff'; }}
|
||||
style={{
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: '50%',
|
||||
background: 'transparent',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: 'pointer',
|
||||
border: '2px solid #1890ff',
|
||||
transition: 'all 0.3s'
|
||||
}}
|
||||
>
|
||||
<RightOutlined style={{ fontSize: 18, color: '#1890ff', transition: 'color 0.3s' }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 轮播图主体 */}
|
||||
<div style={{
|
||||
position: 'relative',
|
||||
borderRadius: 16,
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 8px 32px rgba(24, 144, 255, 0.2)',
|
||||
}}>
|
||||
<Carousel
|
||||
ref={carouselRef2}
|
||||
autoplay
|
||||
dots={false}
|
||||
beforeChange={(_, next) => setCurrentSlide2(next)}
|
||||
>
|
||||
{activities.map((activity, index) => (
|
||||
<div key={activity.id}>
|
||||
<div style={{
|
||||
height: 450,
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
background: 'linear-gradient(135deg, #1890ff 0%, #69c0ff 100%)',
|
||||
}}>
|
||||
<img
|
||||
src={getImageUrl(activity.display_banner_url || activity.banner_url || activity.cover_image)}
|
||||
alt={activity.title}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover'
|
||||
}}
|
||||
onError={(e) => {
|
||||
e.target.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
{/* 渐变遮罩 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'linear-gradient(to bottom, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.6) 100%)',
|
||||
}} />
|
||||
{/* 标题区域 - 图片上方 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 40,
|
||||
left: 40,
|
||||
textAlign: 'left',
|
||||
}}>
|
||||
<h2 style={{ fontSize: 36, fontWeight: 'bold', color: '#fff', margin: 0, textShadow: '2px 2px 4px rgba(0,0,0,0.3)' }}>
|
||||
{activity.title}
|
||||
</h2>
|
||||
{activity.subtitle && (
|
||||
<p style={{ fontSize: 18, color: 'rgba(255,255,255,0.9)', margin: '8px 0 0 0', textShadow: '1px 1px 2px rgba(0,0,0,0.3)' }}>
|
||||
{activity.subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{/* 底部信息 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
padding: '24px 40px',
|
||||
background: 'linear-gradient(to top, rgba(0,0,0,0.8) 0%, transparent 100%)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12 }}>
|
||||
<Tag style={{
|
||||
background: activity.status_color || '#1890ff',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
fontSize: 14,
|
||||
padding: '4px 16px',
|
||||
borderRadius: 20,
|
||||
}}>
|
||||
{activity.status || activity.status_display || '进行中'}
|
||||
</Tag>
|
||||
<span style={{ color: 'rgba(255,255,255,0.8)', fontSize: 14 }}>{activity.location}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<div>
|
||||
<h3 style={{ color: '#fff', fontSize: 24, fontWeight: 'bold', margin: 0 }}>{activity.title}</h3>
|
||||
<p style={{ color: 'rgba(255,255,255,0.8)', fontSize: 14, margin: '8px 0 0 0' }}>
|
||||
<span style={{ marginRight: 20 }}>📅 {activity.date || activity.start_time?.split('T')[0]}</span>
|
||||
{activity.location && <span>📍 {activity.location}</span>}
|
||||
</p>
|
||||
</div>
|
||||
<Button type="primary" size="large" style={{ borderRadius: 24, paddingLeft: 24, paddingRight: 24 }} onClick={() => navigate('/activities')}>
|
||||
查看详情
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Carousel>
|
||||
|
||||
{/* 自定义分页指示器 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: 100,
|
||||
right: 40,
|
||||
display: 'flex',
|
||||
gap: 8,
|
||||
}}>
|
||||
{activities.map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => carouselRef2.current?.goTo(index)}
|
||||
style={{
|
||||
width: currentSlide2 === index ? 32 : 10,
|
||||
height: 10,
|
||||
borderRadius: 5,
|
||||
background: currentSlide2 === index ? '#fff' : 'rgba(255,255,255,0.4)',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 主办单位信息 */}
|
||||
<div style={{ maxWidth: 1200, margin: '40px auto 0', padding: '0 24px', textAlign: 'center' }}>
|
||||
<div style={{
|
||||
background: 'rgba(255, 255, 255, 0.1)',
|
||||
borderRadius: 16,
|
||||
padding: '24px 40px',
|
||||
backdropFilter: 'blur(10px)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.2)',
|
||||
}}>
|
||||
<p style={{
|
||||
fontSize: 18,
|
||||
color: '#fff',
|
||||
margin: 0,
|
||||
lineHeight: 2,
|
||||
}}>
|
||||
<span style={{ fontWeight: 'bold', marginRight: 8 }}>主办单位:</span>{homeConfig?.organizer || '云南省人力资源和社会保障厅'}
|
||||
<span style={{ margin: '0 20px' }}>|</span>
|
||||
<span style={{ fontWeight: 'bold', marginRight: 8 }}>承办单位:</span>{homeConfig?.undertaker || '云南省就业局'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
.cursor-blink {
|
||||
animation: blink 1s step-end infinite;
|
||||
}
|
||||
.cursor-blink-gold {
|
||||
animation: blink-gold 1s step-end infinite;
|
||||
color: #FFD700;
|
||||
}
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
@keyframes blink-gold {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
.gold-text {
|
||||
color: #FFD700;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
Reference in New Issue
Block a user