You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

296 lines
16 KiB

'use client';
import React, { useState } from "react";
import Image from "next/image";
import { SimpleCarousel } from "./SimpleCarousel";
import type { HeroData } from "../types";
interface HomeHeroCarouselProps {
data: HeroData;
}
export function HomeHeroCarousel({ data }: HomeHeroCarouselProps) {
const { eyebrow, title, subtitle, carousel } = data;
// 跟踪第一张图片是否已加载完成
const [isFirstImageLoaded, setIsFirstImageLoaded] = useState(false);
return (
<section className="relative overflow-hidden bg-[#f5f7fb] pb-14 pt-10 text-[#0f1f39] md:pb-20 md:pt-16">
<div className="absolute inset-0">
<div className="pointer-events-none absolute inset-x-0 top-0 h-[320px] bg-gradient-to-b from-white via-[#f5f7fb] to-transparent opacity-60" />
<div className="pointer-events-none absolute left-[-120px] top-[-140px] h-[360px] w-[360px] rounded-full bg-[radial-gradient(circle,rgba(17,138,244,0.14)_0%,rgba(17,138,244,0)_70%)] blur-3xl opacity-70" />
</div>
<div className="relative mx-auto flex w-full max-w-6xl flex-col items-center gap-6 px-4 text-center md:px-6">
<div className="max-w-3xl space-y-3">
{eyebrow && (
<p className="text-xs font-semibold uppercase tracking-[0.46em] text-[#118af4]">
{eyebrow}
</p>
)}
<h1 className="text-3xl font-semibold leading-tight text-[#0f1f39] md:text-[40px]">
{title}
</h1>
<p className="text-sm leading-relaxed text-[#4b5565] md:text-base">
{subtitle}
</p>
</div>
<SimpleCarousel
items={carousel}
className="mt-4 h-[360px] w-full max-w-5xl rounded-[28px] bg-white/90 shadow-[0_30px_60px_rgba(15,31,57,0.08)] md:h-[450px] lg:h-[500px]"
isReady={isFirstImageLoaded}
renderItem={(item, index) => {
// 处理第一张图片的加载完成事件
const handleImageLoad = () => {
if (index === 0) {
setIsFirstImageLoaded(true);
}
};
// 如果有文字,根据 layout 决定布局方式
if (item.text && item.text.length > 0) {
// 上下布局(上面文字,下面图片)
if (item.layout === "vertical" && item.imageBottom) {
return (
<div className="flex h-full w-full flex-col overflow-hidden rounded-[28px] border border-[rgba(17,138,244,0.12)] bg-gradient-to-br from-[#f0f9ff] to-[#e4f2ff]">
{/* 上面:文字内容 */}
<div className="flex flex-1 flex-col justify-center bg-gradient-to-br from-[#f0f9ff] to-[#e4f2ff] p-5 text-[#0f1f39] md:p-6 lg:p-8">
{item.title && (
<h3 className="mb-2 text-base font-semibold leading-snug text-[#0f1f39] md:mb-3 md:text-lg lg:text-xl break-words">
{item.title}
</h3>
)}
<div className="space-y-2 text-xs leading-relaxed text-[#1f2937] md:space-y-2.5 md:text-sm md:leading-relaxed lg:text-base">
{item.text.map((paragraph, index) => {
// 如果有高亮关键词,渲染带高亮的段落
if (item.highlights && item.highlights.length > 0) {
let parts: (string | JSX.Element)[] = [paragraph];
item.highlights.forEach((highlight, highlightIndex) => {
const newParts: (string | JSX.Element)[] = [];
parts.forEach((part) => {
if (typeof part === 'string') {
const regex = new RegExp(`(${highlight.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
const matches = part.split(regex);
matches.forEach((match, matchIndex) => {
if (matchIndex % 2 === 1) {
newParts.push(
<strong key={`p${index}-h${highlightIndex}-m${matchIndex}`} className="text-[#118af4]">
{match}
</strong>
);
} else if (match) {
newParts.push(match);
}
});
} else {
newParts.push(part);
}
});
parts = newParts;
});
return (
<p key={index}>
{parts}
</p>
);
}
return <p key={index}>{paragraph}</p>;
})}
{/* 统计信息框 */}
{item.stats && (
<div className="mt-3 md:mt-4 p-3 md:p-4 rounded-lg bg-gradient-to-br from-[#e8f4fd] to-[#dbeafe] border border-[rgba(17,138,244,0.15)]">
{item.highlights && item.highlights.length > 0 ? (() => {
let parts: (string | JSX.Element)[] = [item.stats];
item.highlights.forEach((highlight, highlightIndex) => {
const newParts: (string | JSX.Element)[] = [];
parts.forEach((part) => {
if (typeof part === 'string') {
const regex = new RegExp(`(${highlight.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
const matches = part.split(regex);
matches.forEach((match, matchIndex) => {
if (matchIndex % 2 === 1) {
newParts.push(
<strong key={`stats-h${highlightIndex}-m${matchIndex}`} className="text-[#0f1f39]">
{match}
</strong>
);
} else if (match) {
newParts.push(match);
}
});
} else {
newParts.push(part);
}
});
parts = newParts;
});
return <p className="text-xs md:text-sm text-[#1f2937] leading-relaxed">{parts}</p>;
})() : (
<p className="text-xs md:text-sm text-[#1f2937] leading-relaxed">{item.stats}</p>
)}
</div>
)}
</div>
</div>
{/* 下面:图片 */}
<div className="relative flex h-[200px] md:h-[250px] w-full items-center justify-center overflow-hidden bg-gradient-to-br from-[#f0f9ff] to-[#e4f2ff]">
<Image
src={item.imageBottom}
alt={item.alt}
fill
sizes="100vw"
className="object-contain"
priority={item.id === "hero-5"}
onLoad={index === 0 ? handleImageLoad : undefined}
/>
</div>
</div>
);
}
// 左右布局(左边文字,右边图片)
return (
<div className="flex h-full w-full flex-col overflow-hidden rounded-[28px] border border-[rgba(17,138,244,0.12)] bg-gradient-to-br from-[#f0f9ff] to-[#e4f2ff] md:flex-row">
{/* 左边:文字内容 */}
<div className="flex flex-1 flex-col justify-center bg-gradient-to-br from-[#f0f9ff] to-[#e4f2ff] p-5 text-[#0f1f39] md:p-6 lg:p-8">
{item.title && (
<h3 className="mb-2 text-base font-semibold leading-snug text-[#0f1f39] md:mb-3 md:text-lg lg:text-xl break-words">
{item.title}
</h3>
)}
<div className="space-y-2 text-xs leading-relaxed text-[#1f2937] md:space-y-2.5 md:text-sm md:leading-relaxed lg:text-base">
{item.text.map((paragraph, index) => {
// 如果有高亮关键词,渲染带高亮的段落
if (item.highlights && item.highlights.length > 0) {
let parts: (string | JSX.Element)[] = [paragraph];
item.highlights.forEach((highlight, highlightIndex) => {
const newParts: (string | JSX.Element)[] = [];
parts.forEach((part) => {
if (typeof part === 'string') {
const regex = new RegExp(`(${highlight.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
const matches = part.split(regex);
matches.forEach((match, matchIndex) => {
if (matchIndex % 2 === 1) {
newParts.push(
<strong key={`p${index}-h${highlightIndex}-m${matchIndex}`} className="text-[#118af4]">
{match}
</strong>
);
} else if (match) {
newParts.push(match);
}
});
} else {
newParts.push(part);
}
});
parts = newParts;
});
return (
<p key={index}>
{parts}
</p>
);
}
return <p key={index}>{paragraph}</p>;
})}
{/* 统计信息框 */}
{item.stats && (
<div className="mt-3 md:mt-4 p-3 md:p-4 rounded-lg bg-gradient-to-br from-[#e8f4fd] to-[#dbeafe] border border-[rgba(17,138,244,0.15)]">
{item.highlights && item.highlights.length > 0 ? (() => {
let parts: (string | JSX.Element)[] = [item.stats];
item.highlights.forEach((highlight) => {
const newParts: (string | JSX.Element)[] = [];
parts.forEach((part) => {
if (typeof part === 'string') {
const regex = new RegExp(`(${highlight.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
const matches = part.split(regex);
matches.forEach((match, matchIndex) => {
if (matchIndex % 2 === 1) {
newParts.push(
<strong key={`stats-${matchIndex}`} className="text-[#0f1f39]">
{match}
</strong>
);
} else if (match) {
newParts.push(match);
}
});
} else {
newParts.push(part);
}
});
parts = newParts;
});
return <p className="text-xs md:text-sm text-[#1f2937] leading-relaxed">{parts}</p>;
})() : (
<p className="text-xs md:text-sm text-[#1f2937] leading-relaxed">{item.stats}</p>
)}
</div>
)}
</div>
</div>
{/* 右边:图片和 KPI 指标 */}
<div className="flex h-full w-full min-h-0 flex-col items-center justify-between bg-gradient-to-br from-[#f0f9ff] to-[#e4f2ff] p-4 md:w-1/2 md:p-6">
{/* 图片区域 */}
<div className="relative flex flex-1 w-full max-w-[400px] items-center justify-center min-h-0 mb-3 md:mb-4">
<div className="h-full w-full flex items-center justify-center bg-gradient-to-br from-[#f0f9ff] to-[#e4f2ff] rounded-lg relative min-h-0">
<Image
src={item.src}
alt={item.alt}
fill
sizes="(max-width: 768px) 100vw, 400px"
className="object-contain"
style={{
mixBlendMode: 'multiply',
filter: 'contrast(1.1) brightness(1.02)'
}}
priority={item.id === "hero-1"}
onLoad={index === 0 ? handleImageLoad : undefined}
/>
</div>
</div>
{/* KPI 指标卡片 */}
{item.kpis && item.kpis.length > 0 && (
<div className="flex w-full max-w-[400px] gap-2 md:gap-3 flex-shrink-0">
{item.kpis.map((kpi, index) => (
<div
key={index}
className="flex-1 rounded-lg bg-gradient-to-br from-[#e8f4fd] to-[#dbeafe] border border-[rgba(17,138,244,0.15)] p-3 md:p-4 text-center shadow-[0_2px_8px_rgba(17,138,244,0.08)]"
>
<div className="text-lg md:text-xl lg:text-2xl font-bold text-[#0f1f39] mb-1">
{kpi.value}
</div>
<div className="text-xs md:text-sm text-[#4b5565] leading-tight">
{kpi.label}
</div>
</div>
))}
</div>
)}
</div>
</div>
);
}
// 没有文字,使用原来的全屏图片布局
return (
<div className="relative flex h-full w-full items-center justify-center overflow-hidden rounded-[28px] border border-[rgba(17,138,244,0.12)] bg-gradient-to-br from-white via-[#f7faff] to-[#eaf3ff]">
<Image
src={item.src}
alt={item.alt}
fill
sizes="100vw"
className="object-cover"
priority={item.id === "hero-2"}
onLoad={index === 0 ? handleImageLoad : undefined}
/>
</div>
);
}}
keyExtractor={(item) => item.id}
interval={6000}
/>
</div>
</section>
);
}