Files
Scoring-System/frontend/src/pages/Home.jsx

593 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useState, useRef } from 'react';
import { Card, Row, Col, Tag, Button, Spin, Typography, Carousel } from 'antd';
import { RocketOutlined, RightOutlined, LeftOutlined, RightCircleOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import { motion } from 'framer-motion';
import { getConfigs, getHomePageConfig } from '../api';
import ActivityList from '../components/activity/ActivityList';
import './Home.css';
const { Title, Paragraph } = Typography;
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 [homeConfig, setHomeConfig] = useState(null);
const [carousel1Items, setCarousel1Items] = useState([]);
const [carousel2Items, setCarousel2Items] = useState([]);
const fullText = "未来已来 AI 核心驱动";
const navigate = useNavigate();
const carouselRef = useRef(null);
const carouselRef2 = useRef(null);
useEffect(() => {
fetchProducts();
fetchHomePageConfig();
let i = 0;
const typingInterval = setInterval(() => {
i++;
setTypedText(fullText.slice(0, i));
if (i >= fullText.length) {
clearInterval(typingInterval);
setIsTypingComplete(true);
}
}, 150);
return () => clearInterval(typingInterval);
}, []);
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 || []);
setCarousel2Items(data.carousel2_items || []);
} catch (error) {
console.error('Failed to fetch homepage config:', 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: 60 }}>
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.8 }}
>
<img
src={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: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.3 }}
style={{ marginTop: 30 }}
>
<h1 style={{
fontSize: '42px',
fontWeight: 'bold',
color: '#fff',
margin: 0,
letterSpacing: '4px'
}}>
{homeConfig?.main_title || '"创赢未来"云南2026创业大赛'}
</h1>
</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={{ 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' }}>{homeConfig?.carousel1_title || '"创赢未来"云南2026创业大赛'}</span>
<span style={{ fontSize: 16, color: 'rgba(255,255,255,0.6)', letterSpacing: 2 }}>EVENTS</span>
</div>
{/* 箭头导航 */}
<div style={{ display: 'flex', gap: 12 }}>
<div
onClick={() => carouselRef.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={() => carouselRef.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={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={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>
{/* 第二个轮播图 */}
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.6 }}
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' }}>{homeConfig?.carousel2_title || '"七彩云南创业福地"创业主题系列活动'}</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)}
>
{(carousel2Items.length > 0 ? carousel2Items : []).map((image, index) => (
<div key={index}>
<div style={{
height: 450,
position: 'relative',
overflow: 'hidden',
background: 'linear-gradient(135deg, #1890ff 0%, #69c0ff 100%)',
}}>
<img
src={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,
}}>
{(carousel2Items.length > 0 ? carousel2Items : []).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: '1200px', margin: '0 auto', padding: '0 24px' }}>
<ActivityList />
</div>
<div className="product-scroll-container">
<Row gutter={[24, 24]} wrap={false}>
{products.map((product, index) => (
<Col key={product.id} flex="0 0 320px">
<motion.div
custom={index}
initial="hidden"
animate="visible"
whileHover="hover"
variants={cardVariants}
style={{ perspective: 1000 }}
>
<Card
className="tech-card glass-panel"
variant="borderless"
cover={
<div style={{
height: 200,
background: 'linear-gradient(135deg, rgba(31,31,31,0.8), rgba(42,42,42,0.8))',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
color: '#444',
borderBottom: '1px solid rgba(255,255,255,0.05)',
overflow: 'hidden'
}}>
{product.static_image_url ? (
<img
src={product.static_image_url}
alt={product.name}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
) : (
<motion.div
animate={{ y: [0, -10, 0] }}
transition={{ repeat: Infinity, duration: 3, ease: "easeInOut" }}
>
<RocketOutlined style={{ fontSize: 60, color: '#00b96b' }} />
</motion.div>
)}
</div>
}
onClick={() => navigate(`/product/${product.id}`)}
>
<div className="tech-card-title neon-text-blue">{product.name}</div>
<div style={{ marginBottom: 10, height: 40, overflow: 'hidden', color: '#bbb' }}>
{product.description}
</div>
<div style={{ marginBottom: 15, display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
<Tag color="cyan" style={{ background: 'rgba(0,255,255,0.1)', border: '1px solid cyan', margin: 0 }}>{product.chip_type}</Tag>
{product.has_camera && <Tag color="blue" style={{ background: 'rgba(0,0,255,0.1)', border: '1px solid blue', margin: 0 }}>Camera</Tag>}
{product.has_microphone && <Tag color="purple" style={{ background: 'rgba(114,46,209,0.1)', border: '1px solid #722ed1', margin: 0 }}>Mic</Tag>}
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div className="tech-price neon-text-green">¥{product.price}</div>
<Button type="primary" shape="circle" icon={<RightOutlined />} />
</div>
</Card>
</motion.div>
</Col>
))}
</Row>
</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;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
`}</style>
</div>
);
};
export default Home;