first commit
This commit is contained in:
250
src/components/Footer.tsx
Normal file
250
src/components/Footer.tsx
Normal file
@@ -0,0 +1,250 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { motion } from 'framer-motion';
|
||||
import {
|
||||
Building2,
|
||||
Phone,
|
||||
Mail,
|
||||
MapPin,
|
||||
Clock,
|
||||
MessageCircle,
|
||||
Share2,
|
||||
Linkedin as LinkedinIcon,
|
||||
ArrowRight,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
COMPANY_INFO,
|
||||
FOOTER_LINKS,
|
||||
SOCIAL_MEDIA,
|
||||
} from '../lib/constants';
|
||||
|
||||
/**
|
||||
* Footer 组件 - 企业官网页脚
|
||||
*/
|
||||
|
||||
// 动画变体配置
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.1,
|
||||
delayChildren: 0.2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: { duration: 0.5 },
|
||||
},
|
||||
};
|
||||
|
||||
// 图标映射
|
||||
const iconMap: Record<string, React.ComponentType<{ size?: number; className?: string }>> = {
|
||||
Wechat: MessageCircle,
|
||||
Weibo: Share2,
|
||||
Linkedin: LinkedinIcon,
|
||||
};
|
||||
|
||||
/**
|
||||
* 联系方式项组件
|
||||
*/
|
||||
const ContactItem: React.FC<{
|
||||
icon: React.ComponentType<{ size?: number; className?: string }>;
|
||||
title: string;
|
||||
content: string;
|
||||
}> = ({ icon: Icon, title, content }) => (
|
||||
<div className="flex items-start gap-3">
|
||||
<Icon size={18} className="text-accent mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="text-xs text-gray-400 mb-0.5">{title}</p>
|
||||
<p className="text-sm text-gray-200">{content}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
/**
|
||||
* 链接列组件
|
||||
*/
|
||||
const LinkColumn: React.FC<{
|
||||
title: string;
|
||||
links: Array<{ label: string; path: string }>;
|
||||
}> = ({ title, links }) => (
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-white uppercase tracking-wider mb-4">
|
||||
{title}
|
||||
</h3>
|
||||
<ul className="space-y-3">
|
||||
{links.map((link) => (
|
||||
<li key={link.path}>
|
||||
<Link
|
||||
to={link.path}
|
||||
className="flex items-center gap-2 text-sm text-gray-300 hover:text-accent transition-colors duration-200 group"
|
||||
>
|
||||
<ArrowRight
|
||||
size={14}
|
||||
className="opacity-0 -ml-4 group-hover:opacity-100 group-hover:ml-0 transition-all duration-200"
|
||||
/>
|
||||
<span className="group-hover:translate-x-1 transition-transform duration-200">
|
||||
{link.label}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
/**
|
||||
* Footer 组件
|
||||
*/
|
||||
export const Footer: React.FC = () => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer className="bg-primary-dark text-white" role="contentinfo">
|
||||
{/* 主内容区域 */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 lg:py-16">
|
||||
<motion.div
|
||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-8 lg:gap-12"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
{/* 企业信息 */}
|
||||
<motion.div className="lg:col-span-2 space-y-6" variants={itemVariants}>
|
||||
{/* Logo */}
|
||||
<Link to="/" className="flex items-center gap-3 group" aria-label="返回首页">
|
||||
<div className="flex items-center justify-center w-12 h-12 rounded-lg bg-accent text-primary-dark">
|
||||
<Building2 size={28} />
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-xl font-bold text-white group-hover:text-accent transition-colors">
|
||||
示例集团
|
||||
</span>
|
||||
<p className="text-xs text-gray-400">Chengyu Group</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{/* 企业简介 */}
|
||||
<p className="text-sm text-gray-300 leading-relaxed max-w-sm">
|
||||
{COMPANY_INFO.description}
|
||||
</p>
|
||||
|
||||
{/* 联系方式 */}
|
||||
<div className="space-y-3 pt-2">
|
||||
<ContactItem
|
||||
icon={MapPin}
|
||||
title="总部地址"
|
||||
content={COMPANY_INFO.headquarters}
|
||||
/>
|
||||
<ContactItem
|
||||
icon={Phone}
|
||||
title="服务热线"
|
||||
content={COMPANY_INFO.phone}
|
||||
/>
|
||||
<ContactItem
|
||||
icon={Mail}
|
||||
title="商务邮箱"
|
||||
content={COMPANY_INFO.email}
|
||||
/>
|
||||
<ContactItem
|
||||
icon={Clock}
|
||||
title="工作时间"
|
||||
content={COMPANY_INFO.workingHours}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* 产品服务 */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<LinkColumn title="产品服务" links={FOOTER_LINKS.products} />
|
||||
</motion.div>
|
||||
|
||||
{/* 公司信息 */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<LinkColumn title="关于我们" links={FOOTER_LINKS.company} />
|
||||
</motion.div>
|
||||
|
||||
{/* 社交媒体 */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<h3 className="text-sm font-semibold text-white uppercase tracking-wider mb-4">
|
||||
关注我们
|
||||
</h3>
|
||||
<p className="text-xs text-gray-400 mb-4">
|
||||
了解更多企业动态
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{SOCIAL_MEDIA.map((social) => {
|
||||
const Icon = iconMap[social.icon] || MessageCircle;
|
||||
return (
|
||||
<motion.a
|
||||
key={social.id}
|
||||
href={social.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 px-3 py-2 bg-white/10 rounded-lg text-sm text-gray-300 hover:bg-accent hover:text-primary-dark transition-all duration-200"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
aria-label={social.label}
|
||||
>
|
||||
<Icon size={18} />
|
||||
<span className="hidden sm:inline">{social.label}</span>
|
||||
</motion.a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* 底部版权栏 */}
|
||||
<div className="border-t border-white/10">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
|
||||
{/* 版权信息 */}
|
||||
<motion.p
|
||||
className="text-sm text-gray-400"
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
© {currentYear} {COMPANY_INFO.fullName} 版权所有
|
||||
</motion.p>
|
||||
|
||||
{/* 备案和链接 */}
|
||||
<motion.div
|
||||
className="flex flex-wrap items-center justify-center gap-4 text-sm text-gray-400"
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.1 }}
|
||||
>
|
||||
<span>京ICP备XXXXXXXX号</span>
|
||||
<span className="hidden sm:inline">|</span>
|
||||
<Link
|
||||
to="/privacy"
|
||||
className="hover:text-accent transition-colors duration-200"
|
||||
>
|
||||
隐私政策
|
||||
</Link>
|
||||
<span className="hidden sm:inline">|</span>
|
||||
<Link
|
||||
to="/terms"
|
||||
className="hover:text-accent transition-colors duration-200"
|
||||
>
|
||||
使用条款
|
||||
</Link>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
296
src/components/Header.tsx
Normal file
296
src/components/Header.tsx
Normal file
@@ -0,0 +1,296 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Menu, X, Building2, Phone, Mail, Search, ChevronDown } from 'lucide-react';
|
||||
import { NAVIGATION_MENU } from '../lib/constants';
|
||||
|
||||
/**
|
||||
* Header 组件 - 企业官网导航栏
|
||||
*/
|
||||
export const Header: React.FC = () => {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
const location = useLocation();
|
||||
|
||||
// 监听滚动事件
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 20);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
// 关闭移动端菜单 - 使用回调避免直接 setState
|
||||
const closeMobileMenu = useCallback(() => {
|
||||
setIsMobileMenuOpen(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
closeMobileMenu();
|
||||
}, [location.pathname, closeMobileMenu]);
|
||||
|
||||
// 检查当前路径是否为活动状态
|
||||
const isActive = (path: string): boolean => {
|
||||
if (path === '/') {
|
||||
return location.pathname === '/';
|
||||
}
|
||||
return location.pathname.startsWith(path);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 顶部信息栏 - 玻璃效果 */}
|
||||
<motion.div
|
||||
className={`fixed top-0 left-0 right-0 z-50 text-white text-xs transition-all duration-300 border-b border-white/10 ${
|
||||
isScrolled ? 'h-0 opacity-0 overflow-hidden' : 'h-10'
|
||||
}`}
|
||||
style={{
|
||||
background: 'linear-gradient(to right, rgba(20, 56, 93, 0.85), rgba(33, 93, 155, 0.85))',
|
||||
backdropFilter: 'blur(12px) saturate(150%)',
|
||||
WebkitBackdropFilter: 'blur(12px) saturate(150%)',
|
||||
}}
|
||||
initial={{ y: -40 }}
|
||||
animate={{ y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-full flex items-center justify-between">
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<Phone size={14} />
|
||||
<span>400-123-4567</span>
|
||||
</div>
|
||||
<div className="hidden sm:flex items-center gap-2">
|
||||
<Mail size={14} />
|
||||
<span>contact@chengyu.com</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden md:flex items-center gap-4">
|
||||
<span className="text-white/80">欢迎来到示例集团官网</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-white/60">|</span>
|
||||
<button className="hover:text-accent transition-colors">简体中文</button>
|
||||
<span className="text-white/60">/</span>
|
||||
<button className="hover:text-accent transition-colors">English</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* 主导航栏 */}
|
||||
<motion.header
|
||||
className={`fixed left-0 right-0 z-50 transition-all duration-300 ${
|
||||
isScrolled
|
||||
? 'top-0 bg-white/80 backdrop-blur-xl backdrop-saturate-150 shadow-lg border-b border-white/20'
|
||||
: 'top-10 bg-white/60 backdrop-blur-lg backdrop-saturate-120 shadow-md border-b border-white/30'
|
||||
}`}
|
||||
initial={{ y: -100 }}
|
||||
animate={{ y: 0 }}
|
||||
transition={{ duration: 0.3, delay: 0.1 }}
|
||||
style={{
|
||||
background: isScrolled
|
||||
? 'rgba(255, 255, 255, 0.8)'
|
||||
: 'rgba(255, 255, 255, 0.6)',
|
||||
backdropFilter: isScrolled
|
||||
? 'blur(20px) saturate(150%)'
|
||||
: 'blur(16px) saturate(120%)',
|
||||
WebkitBackdropFilter: isScrolled
|
||||
? 'blur(20px) saturate(150%)'
|
||||
: 'blur(16px) saturate(120%)',
|
||||
}}
|
||||
>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-[80px]">
|
||||
{/* Logo 区域 - 增强版 */}
|
||||
<Link
|
||||
to="/"
|
||||
className="flex items-center gap-3 group relative"
|
||||
aria-label="返回首页"
|
||||
>
|
||||
<motion.div
|
||||
className="relative flex items-center justify-center w-12 h-12 rounded-xl bg-gradient-to-br from-primary to-primary-light text-white shadow-lg"
|
||||
whileHover={{ scale: 1.05, rotate: 5 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<Building2 size={26} />
|
||||
{/* 光晕效果 */}
|
||||
<div className="absolute inset-0 rounded-xl bg-gradient-to-br from-primary to-primary-light opacity-20 blur-xl group-hover:opacity-40 transition-opacity" />
|
||||
</motion.div>
|
||||
<div className="hidden sm:block">
|
||||
<span className="text-xl font-bold bg-gradient-to-r from-primary-dark to-primary bg-clip-text text-transparent group-hover:from-primary group-hover:to-primary-light transition-all">
|
||||
示例集团
|
||||
</span>
|
||||
<p className="text-xs text-gray-500 -mt-1 font-medium tracking-wider">CHENGYU GROUP</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{/* 桌面端导航菜单 - 增强版 */}
|
||||
<nav
|
||||
className="hidden lg:flex items-center gap-1"
|
||||
role="navigation"
|
||||
aria-label="主导航"
|
||||
>
|
||||
{NAVIGATION_MENU.map((item, index) => (
|
||||
<motion.div
|
||||
key={item.id}
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
>
|
||||
<Link
|
||||
to={item.path}
|
||||
className={`group relative px-5 py-3 text-sm font-medium rounded-xl transition-all duration-300 flex items-center gap-2 ${
|
||||
isActive(item.path)
|
||||
? 'text-primary bg-primary/5'
|
||||
: 'text-gray-700 hover:text-primary hover:bg-primary/5'
|
||||
}`}
|
||||
aria-current={isActive(item.path) ? 'page' : undefined}
|
||||
>
|
||||
<span className="relative z-10">{item.label}</span>
|
||||
|
||||
{/* 悬停背景效果 */}
|
||||
<motion.div
|
||||
className="absolute inset-0 rounded-xl bg-gradient-to-r from-primary/10 to-accent/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300"
|
||||
initial={false}
|
||||
/>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* 桌面端操作区域 */}
|
||||
<div className="hidden lg:flex items-center gap-3">
|
||||
{/* 搜索按钮 */}
|
||||
<motion.button
|
||||
className="p-2.5 rounded-lg text-gray-600 hover:text-primary hover:bg-primary/5 transition-all"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
aria-label="搜索"
|
||||
>
|
||||
<Search size={20} />
|
||||
</motion.button>
|
||||
|
||||
{/* 联系电话按钮 */}
|
||||
<motion.a
|
||||
href="tel:400-123-4567"
|
||||
className="flex items-center gap-2 px-4 py-2.5 text-sm font-medium text-primary border-2 border-primary rounded-lg hover:bg-primary hover:text-white transition-all"
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
<Phone size={16} />
|
||||
<span className="hidden xl:inline">400-123-4567</span>
|
||||
</motion.a>
|
||||
|
||||
{/* CTA 按钮 - 渐变风格 */}
|
||||
<motion.button
|
||||
className="relative px-6 py-2.5 text-sm font-medium text-white rounded-lg overflow-hidden group"
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-primary to-primary-light" />
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-accent to-primary opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||
<span className="relative z-10 flex items-center gap-2">
|
||||
立即咨询
|
||||
<motion.span
|
||||
animate={{ x: [0, 3, 0] }}
|
||||
transition={{ repeat: Infinity, duration: 1.5 }}
|
||||
>
|
||||
→
|
||||
</motion.span>
|
||||
</span>
|
||||
</motion.button>
|
||||
</div>
|
||||
|
||||
{/* 移动端菜单按钮 */}
|
||||
<motion.button
|
||||
className="lg:hidden p-2.5 rounded-xl text-gray-600 hover:bg-primary/5 hover:text-primary transition-all"
|
||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
aria-label={isMobileMenuOpen ? '关闭菜单' : '打开菜单'}
|
||||
aria-expanded={isMobileMenuOpen}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<motion.div
|
||||
animate={{ rotate: isMobileMenuOpen ? 90 : 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
{isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
||||
</motion.div>
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 移动端菜单 - 玻璃效果版 */}
|
||||
<AnimatePresence>
|
||||
{isMobileMenuOpen && (
|
||||
<motion.nav
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="lg:hidden overflow-hidden shadow-xl border-b border-white/20"
|
||||
style={{
|
||||
background: 'rgba(255, 255, 255, 0.85)',
|
||||
backdropFilter: 'blur(20px) saturate(150%)',
|
||||
WebkitBackdropFilter: 'blur(20px) saturate(150%)',
|
||||
}}
|
||||
role="navigation"
|
||||
aria-label="移动端导航"
|
||||
>
|
||||
<div className="px-4 py-6 space-y-3">
|
||||
{NAVIGATION_MENU.map((item, index) => (
|
||||
<motion.div
|
||||
key={item.id}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
>
|
||||
<Link
|
||||
to={item.path}
|
||||
className={`block px-5 py-3.5 rounded-xl text-base font-medium transition-all duration-200 ${
|
||||
isActive(item.path)
|
||||
? 'bg-gradient-to-r from-primary/15 to-accent/15 text-primary shadow-sm backdrop-blur-sm'
|
||||
: 'text-gray-700 hover:bg-white/30 active:scale-95 backdrop-blur-sm'
|
||||
}`}
|
||||
aria-current={isActive(item.path) ? 'page' : undefined}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span>{item.label}</span>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
|
||||
{/* 移动端联系方式 */}
|
||||
<motion.div
|
||||
className="pt-4 mt-2 border-t border-white/20 space-y-3"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
>
|
||||
<a
|
||||
href="tel:400-123-4567"
|
||||
className="flex items-center gap-3 px-5 py-3 rounded-xl text-gray-700 hover:bg-white/30 transition-all backdrop-blur-sm"
|
||||
>
|
||||
<Phone size={18} className="text-primary" />
|
||||
<span className="text-sm font-medium">400-123-4567</span>
|
||||
</a>
|
||||
|
||||
<motion.button
|
||||
className="w-full px-5 py-3.5 text-base font-medium text-white rounded-xl bg-gradient-to-r from-primary to-primary-light shadow-lg shadow-primary/30 backdrop-blur-sm"
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
立即咨询
|
||||
</motion.button>
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.nav>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</motion.header>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
296
src/components/Hero.tsx
Normal file
296
src/components/Hero.tsx
Normal file
@@ -0,0 +1,296 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { motion, useInView, useAnimation } from 'framer-motion';
|
||||
import { ArrowDown, TrendingUp, Users, Building2, Award } from 'lucide-react';
|
||||
import { COMPANY_INFO, COMPANY_STATS } from '../lib/constants';
|
||||
|
||||
/**
|
||||
* 数字递增动画组件
|
||||
*/
|
||||
const CountUpNumber: React.FC<{
|
||||
value: string;
|
||||
suffix?: string;
|
||||
duration?: number;
|
||||
}> = ({ value, suffix = '', duration = 2 }) => {
|
||||
const ref = useRef<HTMLSpanElement>(null);
|
||||
const isInView = useInView(ref, { once: true });
|
||||
const controls = useAnimation();
|
||||
|
||||
useEffect(() => {
|
||||
if (isInView && ref.current) {
|
||||
const numValue = parseInt(value.replace(/[^0-9]/g, ''));
|
||||
const hasPlus = value.includes('+');
|
||||
const hasYi = value.includes('亿');
|
||||
|
||||
controls.start({
|
||||
opacity: [0, 1],
|
||||
scale: [0.5, 1.2, 1],
|
||||
});
|
||||
|
||||
// 数字递增动画
|
||||
let current = 0;
|
||||
const increment = numValue / (duration * 60);
|
||||
const timer = setInterval(() => {
|
||||
current += increment;
|
||||
if (current >= numValue) {
|
||||
current = numValue;
|
||||
clearInterval(timer);
|
||||
}
|
||||
let display = Math.floor(current);
|
||||
if (hasYi) {
|
||||
display = Math.floor(current);
|
||||
}
|
||||
ref.current.textContent = display.toLocaleString() + (hasPlus ? '+' : '') + (hasYi ? '亿' : suffix);
|
||||
}, 1000 / 60);
|
||||
|
||||
return () => clearInterval(timer);
|
||||
}
|
||||
}, [isInView, value, suffix, duration, controls]);
|
||||
|
||||
return (
|
||||
<motion.span
|
||||
ref={ref}
|
||||
className="text-3xl md:text-4xl font-bold text-accent"
|
||||
initial={{ opacity: 0, scale: 0.5 }}
|
||||
animate={isInView ? { opacity: 1, scale: 1 } : {}}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
0
|
||||
</motion.span>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 统计数据项组件
|
||||
*/
|
||||
const StatItem: React.FC<{
|
||||
icon: React.ComponentType<{ size?: number; className?: string }>;
|
||||
label: string;
|
||||
value: string;
|
||||
suffix?: string;
|
||||
}> = ({ icon: Icon, label, value, suffix }) => (
|
||||
<motion.div
|
||||
className="flex items-center gap-4 p-4 bg-white/10 rounded-xl backdrop-blur-sm"
|
||||
whileHover={{ scale: 1.02, backgroundColor: 'rgba(255,255,255,0.15)' }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<div className="flex items-center justify-center w-12 h-12 rounded-lg bg-accent/20">
|
||||
<Icon size={24} className="text-accent" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-baseline gap-1">
|
||||
<CountUpNumber value={value} suffix={suffix} />
|
||||
</div>
|
||||
<p className="text-sm text-gray-300">{label}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
/**
|
||||
* Hero 组件 - 首页大图区域
|
||||
*/
|
||||
export const Hero: React.FC = () => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const statsRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// 滚动到指定区域
|
||||
const scrollToSection = (sectionId: string) => {
|
||||
const element = document.getElementById(sectionId);
|
||||
if (element) {
|
||||
const offset = 80;
|
||||
const elementPosition = element.getBoundingClientRect().top;
|
||||
const offsetPosition = elementPosition + window.pageYOffset - offset;
|
||||
|
||||
window.scrollTo({
|
||||
top: offsetPosition,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section
|
||||
ref={containerRef}
|
||||
className="relative min-h-screen flex items-center overflow-hidden"
|
||||
role="banner"
|
||||
>
|
||||
{/* 背景层 */}
|
||||
<div className="absolute inset-0 z-0">
|
||||
{/* 图片背景 */}
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||
style={{ backgroundImage: 'url(/images/hero-building.jpg)' }}
|
||||
/>
|
||||
{/* 渐变遮罩 - 确保文字可读性 */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-primary-dark/90 via-primary/80 to-primary/70" />
|
||||
{/* 装饰性图案 */}
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div className="absolute top-0 right-0 w-[800px] h-[800px] rounded-full bg-white/5 blur-3xl -translate-y-1/2 translate-x-1/2" />
|
||||
<div className="absolute bottom-0 left-0 w-[600px] h-[600px] rounded-full bg-accent/10 blur-3xl translate-y-1/2 -translate-x-1/2" />
|
||||
</div>
|
||||
{/* 网格图案 */}
|
||||
<div
|
||||
className="absolute inset-0 opacity-5"
|
||||
style={{
|
||||
backgroundImage: `linear-gradient(rgba(255,255,255,0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255,255,255,0.1) 1px, transparent 1px)`,
|
||||
backgroundSize: '50px 50px',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 主内容区域 */}
|
||||
<div className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20">
|
||||
<div className="grid lg:grid-cols-2 gap-12 lg:gap-16 items-center">
|
||||
{/* 左侧内容 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8, ease: 'easeOut' }}
|
||||
>
|
||||
{/* 企业标语 */}
|
||||
<motion.h1
|
||||
className="text-4xl sm:text-5xl lg:text-6xl font-bold text-white leading-tight"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2, duration: 0.6 }}
|
||||
>
|
||||
{COMPANY_INFO.slogan}
|
||||
</motion.h1>
|
||||
|
||||
{/* 企业名称 */}
|
||||
<motion.p
|
||||
className="mt-6 text-2xl sm:text-3xl font-semibold text-accent"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.4, duration: 0.6 }}
|
||||
>
|
||||
{COMPANY_INFO.fullName}
|
||||
</motion.p>
|
||||
|
||||
{/* 企业简介 */}
|
||||
<motion.p
|
||||
className="mt-6 text-lg text-gray-200 leading-relaxed max-w-xl"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.6, duration: 0.6 }}
|
||||
>
|
||||
{COMPANY_INFO.description}
|
||||
</motion.p>
|
||||
|
||||
{/* CTA 按钮组 */}
|
||||
<motion.div
|
||||
className="mt-8 flex flex-wrap gap-4"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.8, duration: 0.6 }}
|
||||
>
|
||||
<motion.button
|
||||
onClick={() => scrollToSection('about')}
|
||||
className="inline-flex items-center gap-2 px-8 py-4 bg-accent text-primary-dark font-semibold rounded-lg shadow-lg shadow-accent/25 hover:shadow-xl hover:shadow-accent/30 transition-all duration-300"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
了解更多
|
||||
<ArrowDown size={20} />
|
||||
</motion.button>
|
||||
<motion.button
|
||||
onClick={() => scrollToSection('contact')}
|
||||
className="inline-flex items-center gap-2 px-8 py-4 border-2 border-white/30 text-white font-semibold rounded-lg hover:bg-white/10 transition-all duration-300"
|
||||
whileHover={{ scale: 1.05, borderColor: 'rgba(255,255,255,0.5)' }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
联系我们
|
||||
</motion.button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* 右侧统计数据 */}
|
||||
<motion.div
|
||||
ref={statsRef}
|
||||
className="space-y-4"
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.4, duration: 0.8, ease: 'easeOut' }}
|
||||
>
|
||||
<motion.h2
|
||||
className="text-xl font-semibold text-white mb-6"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.6 }}
|
||||
>
|
||||
核心数据
|
||||
</motion.h2>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{/* 成立年限 */}
|
||||
<StatItem
|
||||
icon={Award}
|
||||
label={COMPANY_STATS[0].label}
|
||||
value={COMPANY_STATS[0].value}
|
||||
suffix={COMPANY_STATS[0].suffix}
|
||||
/>
|
||||
{/* 员工数量 */}
|
||||
<StatItem
|
||||
icon={Users}
|
||||
label={COMPANY_STATS[1].label}
|
||||
value={COMPANY_STATS[1].value}
|
||||
suffix={COMPANY_STATS[1].suffix}
|
||||
/>
|
||||
{/* 服务客户 */}
|
||||
<StatItem
|
||||
icon={Building2}
|
||||
label={COMPANY_STATS[2].label}
|
||||
value={COMPANY_STATS[2].value}
|
||||
suffix={COMPANY_STATS[2].suffix}
|
||||
/>
|
||||
{/* 管理资产 */}
|
||||
<StatItem
|
||||
icon={TrendingUp}
|
||||
label={COMPANY_STATS[3].label}
|
||||
value={COMPANY_STATS[3].value}
|
||||
suffix={COMPANY_STATS[3].suffix}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 额外说明 */}
|
||||
<motion.p
|
||||
className="mt-6 text-sm text-gray-400 text-center"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1 }}
|
||||
>
|
||||
数据截至 2025 年 12 月
|
||||
</motion.p>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部装饰 */}
|
||||
<motion.div
|
||||
className="absolute bottom-0 left-0 right-0 h-20 bg-gradient-to-t from-background to-transparent"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1.2 }}
|
||||
/>
|
||||
|
||||
{/* 滚动提示 */}
|
||||
<motion.div
|
||||
className="absolute bottom-8 left-1/2 -translate-x-1/2"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1, y: [0, 10, 0] }}
|
||||
transition={{ delay: 1.5, duration: 2, repeat: Infinity }}
|
||||
>
|
||||
<button
|
||||
onClick={() => scrollToSection('about')}
|
||||
className="flex flex-col items-center gap-2 text-white/60 hover:text-accent transition-colors duration-300"
|
||||
aria-label="向下滚动"
|
||||
>
|
||||
<span className="text-xs uppercase tracking-wider">Scroll</span>
|
||||
<ArrowDown size={20} />
|
||||
</button>
|
||||
</motion.div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Hero;
|
||||
188
src/components/Home/AboutSection.tsx
Normal file
188
src/components/Home/AboutSection.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Target, Eye, Heart, Award, ArrowRight } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
/**
|
||||
* AboutSection 组件 - 关于我们简介区域
|
||||
*/
|
||||
|
||||
const values = [
|
||||
{
|
||||
icon: Heart,
|
||||
title: '诚信为本',
|
||||
description: '坚守诚信底线,建立长期信任关系',
|
||||
},
|
||||
{
|
||||
icon: Target,
|
||||
title: '创新驱动',
|
||||
description: '持续创新,保持行业领先优势',
|
||||
},
|
||||
{
|
||||
icon: Award,
|
||||
title: '品质至上',
|
||||
description: '追求卓越,提供高品质服务',
|
||||
},
|
||||
];
|
||||
|
||||
export const AboutSection: React.FC = () => {
|
||||
return (
|
||||
<section
|
||||
id="about"
|
||||
className="py-20 lg:py-28 bg-background"
|
||||
aria-labelledby="about-heading"
|
||||
>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* 标题区域 */}
|
||||
<motion.div
|
||||
className="text-center mb-16"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<h2
|
||||
id="about-heading"
|
||||
className="text-3xl md:text-4xl font-bold text-primary-dark"
|
||||
>
|
||||
关于示例集团
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-gray-600 max-w-3xl mx-auto">
|
||||
示例集团成立于 2010 年,是一家集科技研发、金融服务、产业投资于一体的综合性企业集团
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* 主要内容区域 */}
|
||||
<div className="grid lg:grid-cols-2 gap-12 lg:gap-16 items-center">
|
||||
{/* 左侧内容 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<h3 className="text-2xl font-semibold text-primary-dark mb-6">
|
||||
示例集团 · 稳健前行,携手共赢
|
||||
</h3>
|
||||
<div className="space-y-6 text-gray-600 leading-relaxed">
|
||||
<p>
|
||||
示例集团总部位于北京,经过十余年的稳健发展,已形成了以金融服务、科技研发、产业投资为核心的业务体系。集团秉承"诚信、创新、共赢"的核心价值观,致力于为客户创造最大价值。
|
||||
</p>
|
||||
<p>
|
||||
我们拥有一支经验丰富、专业高效的管理团队,汇聚了金融、科技、投资等领域的优秀人才。通过持续的业务创新和服务优化,示例集团已成功为超过 1000 家企业客户提供专业服务。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 使命与愿景 */}
|
||||
<div className="mt-8 grid sm:grid-cols-2 gap-6">
|
||||
<div className="p-5 bg-white rounded-xl shadow-sm border border-gray-100">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="p-2 bg-primary/10 rounded-lg">
|
||||
<Eye size={20} className="text-primary" />
|
||||
</div>
|
||||
<h4 className="font-semibold text-primary-dark">我们的愿景</h4>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600">
|
||||
成为具有国际影响力的综合性企业集团
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-5 bg-white rounded-xl shadow-sm border border-gray-100">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="p-2 bg-accent/20 rounded-lg">
|
||||
<Target size={20} className="text-accent" />
|
||||
</div>
|
||||
<h4 className="font-semibold text-primary-dark">我们的使命</h4>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600">
|
||||
为客户创造价值,为社会贡献力量
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 更多按钮 */}
|
||||
<motion.div className="mt-8" whileHover={{ x: 5 }}>
|
||||
<Link
|
||||
to="/about"
|
||||
className="inline-flex items-center gap-2 text-primary font-medium hover:text-primary-light transition-colors"
|
||||
>
|
||||
了解更多关于我们
|
||||
<ArrowRight size={18} />
|
||||
</Link>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* 右侧图片/装饰 */}
|
||||
<motion.div
|
||||
className="relative"
|
||||
initial={{ opacity: 0, x: 30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
>
|
||||
{/* 主图片区域 */}
|
||||
<div className="aspect-[4/3] rounded-2xl overflow-hidden shadow-xl">
|
||||
<img
|
||||
src="/images/about-office.jpg"
|
||||
alt="示例集团办公室环境"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
{/* 叠加装饰层 */}
|
||||
<div className="absolute inset-0 bg-gradient-to-tr from-primary/20 to-transparent" />
|
||||
</div>
|
||||
|
||||
{/* 装饰性元素 */}
|
||||
<motion.div
|
||||
className="absolute -bottom-6 -left-6 w-32 h-32 bg-accent/20 rounded-2xl -z-10"
|
||||
initial={{ scale: 0 }}
|
||||
whileInView={{ scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.4, duration: 0.5 }}
|
||||
/>
|
||||
<motion.div
|
||||
className="absolute -top-6 -right-6 w-24 h-24 bg-primary/10 rounded-2xl -z-10"
|
||||
initial={{ scale: 0 }}
|
||||
whileInView={{ scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.5, duration: 0.5 }}
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* 核心价值观 */}
|
||||
<motion.div
|
||||
className="mt-20"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<h3 className="text-2xl font-semibold text-center text-primary-dark mb-10">
|
||||
核心价值观
|
||||
</h3>
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{values.map((value, index) => (
|
||||
<motion.div
|
||||
key={value.title}
|
||||
className="text-center p-8 bg-white rounded-2xl shadow-sm border border-gray-100 hover:shadow-md transition-shadow duration-300"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: index * 0.1, duration: 0.5 }}
|
||||
whileHover={{ y: -5 }}
|
||||
>
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-gradient-to-br from-primary/10 to-accent/10 mb-5">
|
||||
<value.icon size={32} className="text-primary" />
|
||||
</div>
|
||||
<h4 className="text-xl font-semibold text-primary-dark mb-3">
|
||||
{value.title}
|
||||
</h4>
|
||||
<p className="text-gray-600">{value.description}</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutSection;
|
||||
183
src/components/Home/NewsSection.tsx
Normal file
183
src/components/Home/NewsSection.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Calendar, ArrowRight } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { formatDate } from '../../lib/utils';
|
||||
|
||||
/**
|
||||
* NewsSection 组件 - 最新动态区域
|
||||
*/
|
||||
|
||||
// 模拟新闻数据
|
||||
const newsItems = [
|
||||
{
|
||||
id: 1,
|
||||
category: 'company',
|
||||
title: '示例集团荣获"2025年度优秀企业"称号',
|
||||
excerpt: '在近日举办的年度企业评选活动中,示例集团凭借其卓越的经营业绩和社会责任表现,荣获"2025年度优秀企业"称号。',
|
||||
date: '2025-12-20',
|
||||
image: '/images/news-award.jpg',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
category: 'industry',
|
||||
title: '金融科技创新论坛圆满落幕,示例集团分享行业洞察',
|
||||
excerpt: '示例集团受邀参加金融科技创新论坛,与行业专家共同探讨金融科技发展趋势,分享公司在数字化转型方面的实践经验。',
|
||||
date: '2025-12-15',
|
||||
image: '/images/news-tech.jpg',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
category: 'achievement',
|
||||
title: '示例集团完成新一轮战略融资,估值突破百亿',
|
||||
excerpt: '示例集团宣布完成新一轮战略融资,本轮融资由知名投资机构领投,估值突破百亿元人民币,标志着公司发展进入新阶段。',
|
||||
date: '2025-12-10',
|
||||
image: '/images/news-company.jpg',
|
||||
},
|
||||
];
|
||||
|
||||
// 新闻分类映射
|
||||
const categoryMap: Record<string, { label: string; color: string }> = {
|
||||
company: { label: '公司动态', color: 'bg-primary/10 text-primary' },
|
||||
industry: { label: '行业资讯', color: 'bg-accent/20 text-accent-dark' },
|
||||
achievement: { label: '荣誉资质', color: 'bg-green-100 text-green-700' },
|
||||
};
|
||||
|
||||
/**
|
||||
* 新闻卡片组件
|
||||
*/
|
||||
const NewsCard: React.FC<{
|
||||
news: typeof newsItems[0];
|
||||
index: number;
|
||||
}> = ({ news, index }) => {
|
||||
const category = categoryMap[news.category] || categoryMap.company;
|
||||
|
||||
return (
|
||||
<motion.article
|
||||
className="group bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden hover:shadow-lg transition-shadow duration-300"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: index * 0.1, duration: 0.5 }}
|
||||
whileHover={{ y: -5 }}
|
||||
>
|
||||
{/* 图片区域 */}
|
||||
<div className="aspect-[16/9] bg-gradient-to-br from-primary/5 to-primary-light/10 relative overflow-hidden">
|
||||
{news.image ? (
|
||||
<img
|
||||
src={news.image}
|
||||
alt=""
|
||||
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-primary/20">
|
||||
<svg
|
||||
className="w-16 h-16 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1}
|
||||
d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
{/* 分类标签 */}
|
||||
<span
|
||||
className={`absolute top-4 left-4 px-3 py-1 text-xs font-medium rounded-full ${category.color}`}
|
||||
>
|
||||
{category.label}
|
||||
</span>
|
||||
{/* 图片遮罩 */}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/30 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||
</div>
|
||||
|
||||
{/* 内容区域 */}
|
||||
<div className="p-6">
|
||||
{/* 日期 */}
|
||||
<div className="flex items-center gap-2 text-sm text-gray-500 mb-3">
|
||||
<Calendar size={14} />
|
||||
<time dateTime={news.date}>{formatDate(news.date, 'YYYY年MM月DD日')}</time>
|
||||
</div>
|
||||
|
||||
{/* 标题 */}
|
||||
<h3 className="text-lg font-semibold text-primary-dark mb-3 line-clamp-2 group-hover:text-primary transition-colors">
|
||||
{news.title}
|
||||
</h3>
|
||||
|
||||
{/* 摘要 */}
|
||||
<p className="text-gray-600 text-sm leading-relaxed line-clamp-3 mb-4">
|
||||
{news.excerpt}
|
||||
</p>
|
||||
|
||||
{/* 了解更多链接 */}
|
||||
<Link
|
||||
to={`/news/${news.id}`}
|
||||
className="inline-flex items-center gap-2 text-sm font-medium text-primary group-hover:text-primary-light transition-colors"
|
||||
>
|
||||
阅读全文
|
||||
<motion.span
|
||||
initial={{ x: 0 }}
|
||||
whileHover={{ x: 5 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<ArrowRight size={16} />
|
||||
</motion.span>
|
||||
</Link>
|
||||
</div>
|
||||
</motion.article>
|
||||
);
|
||||
};
|
||||
|
||||
export const NewsSection: React.FC = () => {
|
||||
return (
|
||||
<section
|
||||
id="news"
|
||||
className="py-20 lg:py-28 bg-background"
|
||||
aria-labelledby="news-heading"
|
||||
>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* 标题区域 */}
|
||||
<motion.div
|
||||
className="flex flex-col md:flex-row md:items-end md:justify-between gap-4 mb-12"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<div>
|
||||
<h2
|
||||
id="news-heading"
|
||||
className="text-3xl md:text-4xl font-bold text-primary-dark"
|
||||
>
|
||||
新闻资讯
|
||||
</h2>
|
||||
<p className="mt-2 text-lg text-gray-600">
|
||||
了解示例集团最新动态
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
to="/news"
|
||||
className="inline-flex items-center gap-2 text-primary font-medium hover:text-primary-light transition-colors"
|
||||
>
|
||||
查看更多新闻
|
||||
<ArrowRight size={18} />
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
{/* 新闻卡片网格 */}
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{newsItems.map((news, index) => (
|
||||
<NewsCard key={news.id} news={news} index={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewsSection;
|
||||
135
src/components/Home/ServicesSection.tsx
Normal file
135
src/components/Home/ServicesSection.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { TrendingUp, Cpu, Building2, Briefcase, ArrowRight } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { SERVICES } from '../../lib/constants';
|
||||
|
||||
/**
|
||||
* ServicesSection 组件 - 核心业务展示区域
|
||||
*/
|
||||
|
||||
// 业务卡片组件
|
||||
const ServiceCard: React.FC<{
|
||||
service: typeof SERVICES[0];
|
||||
index: number;
|
||||
}> = ({ service, index }) => {
|
||||
const iconMap: Record<string, React.ComponentType<{ size?: number; className?: string }>> = {
|
||||
TrendingUp,
|
||||
Cpu,
|
||||
Building2,
|
||||
Briefcase,
|
||||
};
|
||||
|
||||
const Icon = iconMap[service.icon] || TrendingUp;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="group relative p-8 bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: index * 0.1, duration: 0.5 }}
|
||||
whileHover={{ y: -8, shadow: '0 20px 40px rgba(0,0,0,0.1)' }}
|
||||
>
|
||||
{/* 背景装饰 */}
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-br from-primary/5 to-accent/5 rounded-full -translate-y-1/2 translate-x-1/2 group-hover:scale-150 transition-transform duration-500" />
|
||||
|
||||
{/* 图标 */}
|
||||
<div className="relative z-10 inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-gradient-to-br from-primary to-primary-light text-white mb-6 group-hover:scale-110 transition-transform duration-300">
|
||||
<Icon size={32} />
|
||||
</div>
|
||||
|
||||
{/* 内容 */}
|
||||
<h3 className="relative z-10 text-xl font-semibold text-primary-dark mb-4">
|
||||
{service.title}
|
||||
</h3>
|
||||
<p className="relative z-10 text-gray-600 leading-relaxed mb-6">
|
||||
{service.description}
|
||||
</p>
|
||||
|
||||
{/* 特性列表 */}
|
||||
<ul className="relative z-10 space-y-3 mb-8">
|
||||
{service.features.map((feature) => (
|
||||
<li key={feature} className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-accent" />
|
||||
{feature}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{/* 了解更多链接 */}
|
||||
<Link
|
||||
to={`/services/${service.id}`}
|
||||
className="relative z-10 inline-flex items-center gap-2 text-primary font-medium group-hover:text-primary-light transition-colors"
|
||||
>
|
||||
了解更多
|
||||
<motion.span
|
||||
initial={{ x: 0 }}
|
||||
whileHover={{ x: 5 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<ArrowRight size={18} />
|
||||
</motion.span>
|
||||
</Link>
|
||||
|
||||
{/* 悬停边框效果 */}
|
||||
<div className="absolute inset-0 border-2 border-transparent group-hover:border-accent/30 rounded-2xl transition-colors duration-300" />
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ServicesSection: React.FC = () => {
|
||||
return (
|
||||
<section
|
||||
id="services"
|
||||
className="py-20 lg:py-28 bg-white"
|
||||
aria-labelledby="services-heading"
|
||||
>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* 标题区域 */}
|
||||
<motion.div
|
||||
className="text-center mb-16"
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<h2
|
||||
id="services-heading"
|
||||
className="text-3xl md:text-4xl font-bold text-primary-dark"
|
||||
>
|
||||
核心业务
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-gray-600 max-w-3xl mx-auto">
|
||||
我们提供全方位的专业服务,帮助客户实现商业目标
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* 业务卡片网格 */}
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8">
|
||||
{SERVICES.map((service, index) => (
|
||||
<ServiceCard key={service.id} service={service} index={index} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 查看全部服务 */}
|
||||
<motion.div
|
||||
className="mt-12 text-center"
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.4, duration: 0.6 }}
|
||||
>
|
||||
<Link
|
||||
to="/services"
|
||||
className="inline-flex items-center gap-2 px-8 py-4 bg-primary text-white font-medium rounded-lg hover:bg-primary-light transition-colors shadow-lg shadow-primary/25"
|
||||
>
|
||||
查看全部服务
|
||||
<ArrowRight size={20} />
|
||||
</Link>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServicesSection;
|
||||
41
src/components/PostCard.tsx
Normal file
41
src/components/PostCard.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react'
|
||||
|
||||
interface PostCardProps {
|
||||
title: string
|
||||
excerpt: string
|
||||
category?: string
|
||||
date: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
export const PostCard: React.FC<PostCardProps> = ({
|
||||
title,
|
||||
excerpt,
|
||||
category,
|
||||
date,
|
||||
onClick,
|
||||
}) => {
|
||||
return (
|
||||
<article
|
||||
className="border border-gray-200 rounded-lg p-6 shadow-sm hover:shadow-md transition-shadow cursor-pointer bg-white"
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
{category && (
|
||||
<span className="px-2 py-1 text-xs font-medium bg-blue-100 text-blue-700 rounded-full">
|
||||
{category}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-sm text-gray-500">{date}</span>
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-2">{title}</h2>
|
||||
<p className="text-gray-600 line-clamp-3">{excerpt}</p>
|
||||
<div className="mt-4 text-blue-600 text-sm font-medium flex items-center gap-1">
|
||||
阅读更多
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
19
src/components/PostCardSkeleton.tsx
Normal file
19
src/components/PostCardSkeleton.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
|
||||
export const PostCardSkeleton: React.FC = () => {
|
||||
return (
|
||||
<div className="border border-gray-200 rounded-lg p-6 shadow-sm bg-white animate-pulse">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="w-16 h-5 bg-gray-200 rounded-full"></div>
|
||||
<div className="w-20 h-4 bg-gray-200 rounded"></div>
|
||||
</div>
|
||||
<div className="h-6 bg-gray-200 rounded w-3/4 mb-3"></div>
|
||||
<div className="space-y-2">
|
||||
<div className="h-4 bg-gray-200 rounded w-full"></div>
|
||||
<div className="h-4 bg-gray-200 rounded w-5/6"></div>
|
||||
<div className="h-4 bg-gray-200 rounded w-4/6"></div>
|
||||
</div>
|
||||
<div className="mt-4 h-4 bg-gray-200 rounded w-20"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user