- 新增 Prisma + SQLite 数据库模型 (Category, Content, Media, User 等) - 新增 Admin 管理后台 (认证、内容管理、媒体管理) - 新增 CMS API 路由 (CRUD, 草稿/发布, 重新验证) - 新增 CMS 内容版本的历史归档页面 - 新增 components/cms 内容渲染组件 - 新增 components/admin 管理后台 UI 组件 - 更新 Contact API 路由
353 lines
15 KiB
TypeScript
353 lines
15 KiB
TypeScript
'use client';
|
|
|
|
import { useRef, useState, useEffect } from 'react';
|
|
import { motion, useInView } from 'framer-motion';
|
|
import { ScrollReveal } from '@/components/ui/scroll-reveal';
|
|
import { ArrowRight, Shield, Building2, Users, Code, Target, Layers, BookOpen, Sparkles } from 'lucide-react';
|
|
import { SectionLabel, EASE_OUT } from '@/components/ui/page-decoration';
|
|
import { useReducedMotion } from '@/hooks/use-reduced-motion';
|
|
|
|
const TEAM_STRENGTHS = [
|
|
{
|
|
icon: Shield,
|
|
number: '01',
|
|
title: '12 年+ 行业深耕',
|
|
description: '核心团队长期从事技术咨询、企业数字化等领域,服务覆盖金融、制造、零售、政务、农业等多个行业。',
|
|
},
|
|
{
|
|
icon: Building2,
|
|
number: '02',
|
|
title: '大型 IT 企业背景',
|
|
description: '开发团队成员来自多个大型传统 IT 企业,具备扎实的工程能力、规范化的交付流程和严格的质量意识。',
|
|
},
|
|
{
|
|
icon: Users,
|
|
number: '03',
|
|
title: '复合型技术团队',
|
|
description: '既懂技术又懂业务,能够深入理解客户的真实场景和痛点,提供真正可落地的解决方案。',
|
|
},
|
|
{
|
|
icon: Code,
|
|
number: '04',
|
|
title: '全栈技术能力',
|
|
description: '从前端到后端、从云原生到数据智能、从移动端到物联网——应对各种复杂技术挑战。',
|
|
},
|
|
{
|
|
icon: Target,
|
|
number: '05',
|
|
title: '结果导向交付',
|
|
description: '不以"项目上线"为终点,以"客户业务是否真正改善"为衡量标准。每一个交付成果都追求可量化的业务价值。',
|
|
},
|
|
];
|
|
|
|
const TEAM_CULTURE = [
|
|
{
|
|
icon: Layers,
|
|
number: '01',
|
|
title: '扁平化协作',
|
|
description: '没有冗长的汇报链条,每个人都有机会直接参与决策。信息透明,沟通高效。',
|
|
},
|
|
{
|
|
icon: BookOpen,
|
|
number: '02',
|
|
title: '持续学习',
|
|
description: '技术日新月异,我们保持对新技术的好奇。内部分享、技术沙龙、外部培训——学习是日常的一部分。',
|
|
},
|
|
{
|
|
icon: Sparkles,
|
|
number: '03',
|
|
title: '客户成功',
|
|
description: '我们的成就感来自客户的成功。客户的业务增长,就是我们最好的成绩单。',
|
|
},
|
|
];
|
|
|
|
const STATS = [
|
|
{ value: 12, suffix: '+', label: '年行业经验' },
|
|
{ value: 10, suffix: '+', label: '核心成员' },
|
|
{ value: 5, suffix: '+', label: '行业覆盖' },
|
|
];
|
|
|
|
function useCountUp(target: number, start: boolean, duration: number = 2000) {
|
|
const [count, setCount] = useState(0);
|
|
const prefersReducedMotion = useReducedMotion();
|
|
|
|
useEffect(() => {
|
|
if (!start || prefersReducedMotion) {
|
|
setCount(target);
|
|
return;
|
|
}
|
|
|
|
const startTime = performance.now();
|
|
let animationId: number;
|
|
|
|
const animate = (currentTime: number) => {
|
|
const elapsed = currentTime - startTime;
|
|
const progress = Math.min(elapsed / duration, 1);
|
|
const easeOutQuart = 1 - Math.pow(1 - progress, 4);
|
|
setCount(Math.floor(target * easeOutQuart));
|
|
|
|
if (progress < 1) {
|
|
animationId = requestAnimationFrame(animate);
|
|
} else {
|
|
setCount(target);
|
|
}
|
|
};
|
|
|
|
animationId = requestAnimationFrame(animate);
|
|
return () => cancelAnimationFrame(animationId);
|
|
}, [target, start, duration, prefersReducedMotion]);
|
|
|
|
return count;
|
|
}
|
|
|
|
function StatItem({ value, suffix, label, start, delay }: { value: number; suffix: string; label: string; start: boolean; delay: number }) {
|
|
const count = useCountUp(value, start, 2000);
|
|
const shouldReduceMotion = useReducedMotion();
|
|
|
|
return (
|
|
<motion.div
|
|
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
|
animate={start ? { opacity: 1, y: 0 } : {}}
|
|
transition={{ delay, duration: 0.6, ease: EASE_OUT }}
|
|
className="text-center"
|
|
>
|
|
<div className="text-4xl sm:text-5xl font-bold text-ink tracking-tight leading-none mb-2">
|
|
{count}<span className="text-brand">{suffix}</span>
|
|
</div>
|
|
<div className="text-sm text-text-muted tracking-wide">{label}</div>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
function StrengthCard({ strength, index }: { strength: typeof TEAM_STRENGTHS[0]; index: number }) {
|
|
const Icon = strength.icon;
|
|
const shouldReduceMotion = useReducedMotion();
|
|
|
|
return (
|
|
<motion.div
|
|
key={strength.title}
|
|
className="group relative border border-border-primary hover:border-brand/30 bg-white transition-all duration-500 overflow-hidden"
|
|
initial={shouldReduceMotion ? {} : { opacity: 0, y: 30 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true, margin: '-80px' }}
|
|
transition={{ duration: 0.6, delay: index * 0.08, ease: EASE_OUT }}
|
|
whileHover={{ y: -6 }}
|
|
>
|
|
<div className="absolute top-0 left-0 bottom-0 w-1 bg-brand scale-y-0 group-hover:scale-y-100 transition-transform duration-500 origin-top" />
|
|
|
|
<div className="p-8 sm:p-10">
|
|
<div className="flex items-center gap-4 mb-6">
|
|
<div className="w-12 h-12 flex items-center justify-center border border-brand/30 bg-brand/[0.08] text-brand">
|
|
<Icon className="w-5 h-5" />
|
|
</div>
|
|
<span className="text-4xl font-bold text-text-muted/10 font-mono">{strength.number}</span>
|
|
</div>
|
|
|
|
<h3 className="text-xl font-bold text-ink mb-3">{strength.title}</h3>
|
|
<p className="text-text-secondary text-sm leading-relaxed">{strength.description}</p>
|
|
</div>
|
|
|
|
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-brand/40 via-brand/10 to-transparent scale-x-0 group-hover:scale-x-100 transition-transform duration-700 origin-left" />
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
function CultureCard({ culture, index }: { culture: typeof TEAM_CULTURE[0]; index: number }) {
|
|
const Icon = culture.icon;
|
|
const shouldReduceMotion = useReducedMotion();
|
|
|
|
return (
|
|
<motion.div
|
|
key={culture.title}
|
|
className="group relative border border-border-primary hover:border-brand/30 bg-white transition-all duration-500 text-center overflow-hidden"
|
|
initial={shouldReduceMotion ? {} : { opacity: 0, y: 30 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true, margin: '-80px' }}
|
|
transition={{ duration: 0.6, delay: index * 0.1, ease: EASE_OUT }}
|
|
whileHover={{ y: -8 }}
|
|
>
|
|
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-700 origin-left" />
|
|
|
|
<div className="p-8 sm:p-10 lg:p-12">
|
|
<div className="w-16 h-16 mx-auto mb-6 flex items-center justify-center border border-brand/30 bg-brand/[0.08] text-brand">
|
|
<Icon className="w-7 h-7" />
|
|
</div>
|
|
|
|
<span className="block text-6xl font-bold text-text-muted/10 font-mono mb-4">{culture.number}</span>
|
|
|
|
<h3 className="text-2xl font-bold text-ink mb-4">{culture.title}</h3>
|
|
<p className="text-text-secondary leading-relaxed">{culture.description}</p>
|
|
</div>
|
|
|
|
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-brand/20 to-transparent scale-x-0 group-hover:scale-x-100 transition-transform duration-700 origin-center" />
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
export default function TeamContentV2() {
|
|
const statsRef = useRef(null);
|
|
const isInView = useInView(statsRef, { once: true, margin: '-100px' });
|
|
|
|
return (
|
|
<div className="min-h-screen bg-white text-ink overflow-x-hidden">
|
|
{/* Hero Section */}
|
|
<section className="relative min-h-[85vh] flex items-center overflow-hidden bg-white">
|
|
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-28 sm:py-36 md:py-44 lg:py-56">
|
|
<div className="max-w-4xl">
|
|
<ScrollReveal>
|
|
<SectionLabel>Our Team</SectionLabel>
|
|
</ScrollReveal>
|
|
|
|
<ScrollReveal delay={0.1}>
|
|
<h1 className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl font-bold leading-[0.92] tracking-tighter mb-10 sm:mb-12 md:mb-16">
|
|
既懂技术又懂业务的
|
|
<span className="block mt-4 text-brand">复合型团队</span>
|
|
</h1>
|
|
</ScrollReveal>
|
|
|
|
<ScrollReveal delay={0.2}>
|
|
<p className="text-lg md:text-xl text-text-secondary max-w-2xl leading-relaxed mb-12">
|
|
我们的成员不只是写代码的工程师——我们是能理解您业务场景、能和您一起想清楚问题的合作伙伴。
|
|
</p>
|
|
</ScrollReveal>
|
|
|
|
<ScrollReveal delay={0.3}>
|
|
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6 mb-16">
|
|
<a
|
|
href="/contact"
|
|
className="group inline-flex items-center justify-center gap-3 px-12 py-6 font-semibold text-base bg-brand text-white transition-all duration-300 hover:bg-brand/90"
|
|
>
|
|
与我们交流
|
|
<ArrowRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-1" />
|
|
</a>
|
|
<a
|
|
href="/about"
|
|
className="inline-flex items-center justify-center gap-3 px-12 py-6 font-semibold text-base text-ink border border-border-primary hover:border-border-secondary hover:bg-bg-secondary transition-all duration-300"
|
|
>
|
|
了解公司
|
|
</a>
|
|
</div>
|
|
</ScrollReveal>
|
|
|
|
<div ref={statsRef} className="grid grid-cols-3 gap-6 sm:gap-8 md:gap-12 max-w-2xl">
|
|
{STATS.map((stat, idx) => (
|
|
<StatItem
|
|
key={stat.label}
|
|
value={stat.value}
|
|
suffix={stat.suffix}
|
|
label={stat.label}
|
|
start={isInView}
|
|
delay={idx * 0.15}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Team Strengths Section */}
|
|
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-bg-secondary">
|
|
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
|
|
<div className="absolute bottom-0 left-0 right-0 h-px bg-border-primary" />
|
|
|
|
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
|
<ScrollReveal className="text-center max-w-3xl mx-auto mb-16 sm:mb-20">
|
|
<SectionLabel className="justify-center">Team Strengths</SectionLabel>
|
|
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight leading-tight text-ink">
|
|
我们的底气,从何而来
|
|
</h2>
|
|
<p className="mt-6 text-lg text-text-secondary leading-relaxed">
|
|
不是自吹自擂,而是多年实战积累的真实能力
|
|
</p>
|
|
</ScrollReveal>
|
|
|
|
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8">
|
|
{TEAM_STRENGTHS.map((strength, idx) => (
|
|
<StrengthCard key={strength.title} strength={strength} index={idx} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Team About Section */}
|
|
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-white">
|
|
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
|
<ScrollReveal>
|
|
<div className="max-w-4xl mx-auto p-8 sm:p-10 lg:p-16 border border-border-primary bg-bg-secondary/50">
|
|
<h2 className="text-2xl sm:text-3xl font-bold mb-8 text-center text-ink">关于我们的团队</h2>
|
|
<div className="space-y-6 max-w-3xl mx-auto text-center">
|
|
<p className="text-text-secondary leading-relaxed text-base sm:text-lg">
|
|
我们的核心团队长期从事<span className="text-brand font-semibold">技术咨询</span>、<span className="text-brand font-semibold">企业数字化</span>等行业,拥有 12 年以上的深厚积累。
|
|
</p>
|
|
<p className="text-text-secondary leading-relaxed text-base sm:text-lg">
|
|
开发团队成员来自于多个<span className="text-brand font-semibold">大型传统 IT 企业</span>,具备扎实的工程能力和规范化的交付经验。
|
|
</p>
|
|
<p className="text-text-secondary leading-relaxed text-base sm:text-lg">
|
|
我们相信,优秀的技术咨询不仅需要过硬的技术能力,更需要深入理解客户的业务场景和真实需求。
|
|
每一位成员都是既懂技术又懂业务的复合型人才。
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</ScrollReveal>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Team Culture Section */}
|
|
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-bg-secondary">
|
|
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
|
|
<div className="absolute bottom-0 left-0 right-0 h-px bg-border-primary" />
|
|
|
|
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
|
<ScrollReveal className="text-center max-w-3xl mx-auto mb-16 sm:mb-20">
|
|
<SectionLabel className="justify-center">Team Culture</SectionLabel>
|
|
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight leading-tight text-ink">
|
|
我们怎样工作
|
|
</h2>
|
|
<p className="mt-6 text-lg text-text-secondary leading-relaxed">
|
|
好的团队文化,最终体现在为客户创造的价值上
|
|
</p>
|
|
</ScrollReveal>
|
|
|
|
<div className="grid md:grid-cols-3 gap-8">
|
|
{TEAM_CULTURE.map((culture, idx) => (
|
|
<CultureCard key={culture.title} culture={culture} index={idx} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* CTA Section */}
|
|
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-white">
|
|
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
|
|
<div className="absolute bottom-0 left-0 right-0 h-px bg-border-primary" />
|
|
|
|
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
|
<ScrollReveal className="text-center max-w-3xl mx-auto">
|
|
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight leading-tight text-ink">
|
|
想成为我们的一员?
|
|
</h2>
|
|
<p className="mt-6 text-lg text-text-secondary leading-relaxed">
|
|
我们始终在寻找既懂技术又热爱业务的伙伴。如果您认同我们的理念,欢迎聊聊。
|
|
</p>
|
|
<div className="mt-12 flex flex-col sm:flex-row justify-center gap-4 sm:gap-6">
|
|
<a
|
|
href="/contact"
|
|
className="group inline-flex items-center justify-center gap-3 px-12 py-6 font-semibold text-base bg-brand text-white transition-all duration-300 hover:bg-brand/90"
|
|
>
|
|
联系我们
|
|
<ArrowRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-1" />
|
|
</a>
|
|
<a
|
|
href="/about"
|
|
className="inline-flex items-center justify-center gap-3 px-12 py-6 font-semibold text-base text-ink border border-border-primary hover:border-border-secondary hover:bg-bg-secondary transition-all duration-300"
|
|
>
|
|
了解公司
|
|
</a>
|
|
</div>
|
|
</ScrollReveal>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|