创赢未来评分系统 - 初始化提交(移除大文件)

This commit is contained in:
爽哒哒
2026-03-18 22:28:45 +08:00
commit f26d35da66
315 changed files with 36043 additions and 0 deletions

592
frontend/src/pages/Home.jsx Normal file
View File

@@ -0,0 +1,592 @@
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;