Files
quant-speed_page/src/components/TeamSection.js
jeremygan2021 d41066f32e 1
2026-02-13 09:55:41 +08:00

325 lines
12 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, useMemo, useRef, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
const TeamSection = ({ isActive }) => {
const members = useMemo(() => ([
{
id: 'agan',
name: '阿甘',
role: '创始人',
bio: '从0到1推动产品与战略落地专注AI与工业融合。',
avatar: 'https://tangledup-ai-staging.oss-cn-shanghai.aliyuncs.com/web/quant-speed_page/public/team_image/agan.png',
portrait: 'https://tangledup-ai-staging.oss-cn-shanghai.aliyuncs.com/web/quant-speed_page/public/team_image/agan.png',
brandLogo: '/asset/logo-bai.png',
education: ['本科(待补充)', '硕士(待补充)'],
experience: [
'负责公司整体战略与产品路线',
'推动跨部门协作与商业落地'
],
skills: ['战略规划', '产品设计', 'AI应用', '工业融合']
},
{
id: 'liwei',
name: '立伟',
role: '市场运营总监',
bio: '增长策略与品牌叙事负责人,连接产品与客户价值。',
avatar: 'https://tangledup-ai-staging.oss-cn-shanghai.aliyuncs.com/web/quant-speed_page/public/team_image/liwei.png',
portrait: 'https://tangledup-ai-staging.oss-cn-shanghai.aliyuncs.com/web/quant-speed_page/public/team_image/liwei.png',
brandLogo: '/asset/logo-bai.png',
education: ['本科(待补充)'],
experience: [
'规划年度增长与渠道策略',
'打造品牌故事与市场活动'
],
skills: ['增长策略', '品牌建设', '整合营销', '渠道拓展']
},
{
id: 'shuangji',
name: '爽吉',
role: '硬件工程师 & 硬件部门主管',
bio: '负责高可靠硬件架构设计与产线可制造性优化。',
avatar: 'https://tangledup-ai-staging.oss-cn-shanghai.aliyuncs.com/web/quant-speed_page/public/team_image/shuangji.png',
portrait: 'https://tangledup-ai-staging.oss-cn-shanghai.aliyuncs.com/web/quant-speed_page/public/team_image/shuangji.png',
brandLogo: '/asset/logo-bai.png',
education: ['本科(待补充)'],
experience: [
'主导多款硬件平台架构设计',
'建立DFM/DFT标准并提升良率'
],
skills: ['硬件架构', '嵌入式', 'DFM/DFT', '可靠性']
},
{
id: 'laoxv',
name: '老许',
role: '工业设计工程师 & 结构件工程师',
bio: '负责高可靠硬件架构设计与产线可制造性优化。',
avatar: 'https://tangledup-ai-staging.oss-cn-shanghai.aliyuncs.com/web/quant-speed_page/public/team_image/laoxv.png',
portrait: 'https://tangledup-ai-staging.oss-cn-shanghai.aliyuncs.com/web/quant-speed_page/public/team_image/laoxv.png',
brandLogo: '/asset/logo-bai.png',
education: ['本科(待补充)'],
experience: [
'主导多款硬件平台架构设计',
'建立DFM/DFT标准并提升良率'
],
skills: ['硬件架构', '嵌入式', 'DFM/DFT', '可靠性']
}
]), []);
const [index, setIndex] = useState(0);
const trackRef = useRef(null);
const firstCardRef = useRef(null);
const [slideStep, setSlideStep] = useState(0);
const CARD_GAP = 24; // 与样式中的间距保持一致
const [selected, setSelected] = useState(null);
useEffect(() => {
const measure = () => {
if (!firstCardRef.current) return;
const rect = firstCardRef.current.getBoundingClientRect();
const step = Math.round(rect.width + CARD_GAP);
setSlideStep(step);
};
measure();
window.addEventListener('resize', measure);
return () => window.removeEventListener('resize', measure);
}, []);
const goPrev = () => setIndex((prev) => (prev - 1 + members.length) % members.length);
const goNext = () => setIndex((prev) => (prev + 1) % members.length);
const handleDragEnd = (_e, info) => {
const threshold = 80;
if (info.offset.x < -threshold) {
goNext();
} else if (info.offset.x > threshold) {
goPrev();
}
};
const openDetail = (member) => setSelected(member);
const closeDetail = () => setSelected(null);
return (
<div className="team-section-minimal">
<motion.div
className="team-content"
initial={{ opacity: 0, y: 60 }}
animate={isActive ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 1.2, delay: 0.3 }}
>
<motion.h1
className="team-main-title"
initial={{ opacity: 0, y: 40 }}
animate={isActive ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.8, delay: 0.6 }}
>
量迹AI
<br />
<span className="highlight-text">2025</span>
<br />
Team
</motion.h1>
<motion.div
className="team-quote"
initial={{ opacity: 0, x: -20 }}
animate={isActive ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.8, delay: 1 }}
>
<div className="quote-line"></div>
<p>
我们的团队汇聚了行业顶尖人才
<br />
拥有深厚的技术背景和丰富的
<br />
创新经验致力于推动AI技术发展
</p>
</motion.div>
</motion.div>
<motion.div
className="team-visual"
initial={{ opacity: 0, scale: 0.9 }}
animate={isActive ? { opacity: 1, scale: 1 } : {}}
transition={{ duration: 1.5, delay: 0.5 }}
>
<div className="team-visual-container">
<motion.div
className="team-visual-element-1"
animate={isActive ? {
rotateZ: [0, 180, 360],
scale: [1, 1.2, 1]
} : {}}
transition={{
duration: 18,
repeat: Infinity,
ease: "easeInOut"
}}
/>
<motion.div
className="team-visual-element-2"
animate={isActive ? {
rotateY: [0, 180, 360],
x: [0, 20, 0]
} : {}}
transition={{
duration: 12,
repeat: Infinity,
ease: "linear"
}}
/>
</div>
</motion.div>
{/* 科技感人员滑动卡片 */}
<motion.div
className="team-slider"
initial={{ opacity: 0, y: 30 }}
animate={isActive ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.9, delay: 0.6 }}
>
<div className="team-slider-viewport">
<motion.div
ref={trackRef}
className="team-slider-track"
drag="x"
dragConstraints={{ left: 0, right: 0 }}
onDragEnd={handleDragEnd}
animate={{ x: -index * slideStep }}
transition={{ type: 'spring', stiffness: 120, damping: 18 }}
>
{members.map((m, i) => (
<div
key={m.id}
ref={i === 0 ? firstCardRef : undefined}
className="team-card"
onClick={() => openDetail(m)}
>
<div className="card-bg-grid" />
<div className="card-scanline" />
<div className="card-glow" />
<img src={m.avatar} alt={m.name} className="member-photo" />
<div className="member-info">
<div className="member-name">{m.name}</div>
<div className="member-role">{m.role}</div>
<div className="member-bio-mini">{m.bio}</div>
</div>
</div>
))}
</motion.div>
<button className="slider-btn prev" onClick={goPrev} aria-label="prev">
<span></span>
</button>
<button className="slider-btn next" onClick={goNext} aria-label="next">
<span></span>
</button>
<div className="slider-dots">
{members.map((m, i) => (
<button
key={m.id}
className={`dot ${i === index ? 'active' : ''}`}
onClick={() => setIndex(i)}
aria-label={`go-to-${m.id}`}
/>
))}
</div>
</div>
</motion.div>
{/* 详情面板(桌面端右侧 / 移动端底部弹层) */}
<AnimatePresence>
{selected && (
<>
<motion.div
className="team-detail-mask"
initial={{ opacity: 0 }}
animate={{ opacity: 0.6 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
onClick={closeDetail}
/>
<motion.div
className="team-detail-panel"
initial={{ opacity: 0, x: 40 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 40 }}
transition={{ type: 'spring', stiffness: 180, damping: 20 }}
>
<button className="detail-close" onClick={closeDetail} aria-label="close">×</button>
<div className="detail-header">
<img src={selected.brandLogo} alt="brand" className="detail-brand" />
<div className="detail-meta">
<div className="detail-name">{selected.name}</div>
<div className="detail-role">{selected.role}</div>
</div>
</div>
<img src={selected.portrait || selected.avatar} alt={selected.name} className="detail-portrait" />
<div className="detail-body detail-scroller">
{selected.bio && (
<div className="detail-card">
<h5>简介</h5>
<p>{selected.bio}</p>
</div>
)}
{selected.education && selected.education.length > 0 && (
<div className="detail-card">
<h5>毕业院校</h5>
<ul className="detail-list">
{selected.education.map((e, i) => (
<li key={i}>{e}</li>
))}
</ul>
</div>
)}
{selected.experience && selected.experience.length > 0 && (
<div className="detail-card">
<h5>过往经历</h5>
<ul className="detail-list">
{selected.experience.map((e, i) => (
<li key={i}>{e}</li>
))}
</ul>
</div>
)}
{selected.skills && selected.skills.length > 0 && (
<div className="detail-card">
<h5>擅长技能</h5>
<div className="chip-grid">
{selected.skills.map((s, i) => (
<span className="chip" key={i}>{s}</span>
))}
</div>
</div>
)}
</div>
</motion.div>
</>
)}
</AnimatePresence>
{/* 进度指示器 */}
<motion.div
className="team-progress"
initial={{ opacity: 0, y: 20 }}
animate={isActive ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.8, delay: 1.5 }}
>
<div className="progress-bar">
<motion.div
className="progress-fill"
initial={{ width: "0%" }}
animate={isActive ? { width: "67%" } : {}}
transition={{ duration: 2, delay: 2 }}
></motion.div>
</div>
<div className="progress-info">
<span className="progress-time">00:12</span>
<span className="progress-total">00:18</span>
</div>
</motion.div>
</div>
);
};
export default TeamSection;