yintai-company-home/src/components/banner.tsx

137 lines
4.6 KiB
TypeScript

import { Link, useLocation } from "react-router-dom";
import { Swiper, SwiperSlide } from "swiper/react";
import { Autoplay, EffectFade } from "swiper/modules";
import "swiper/css";
import "swiper/css/effect-fade";
import styles from "./Banner.module.css";
import { useMemo } from "react";
import { useStore } from "@/store";
const FALLBACK_GRADIENT = "linear-gradient(135deg, #1a2a4a 0%, #2d4a7c 100%)";
export type BannerConfig = {
title?: string;
content?: string;
subtitle?: string;
largeContent?: string;
titleSize?: "large" | "medium" | string;
showBreadcrumb?: boolean;
backgroundImage?: string | string[];
};
type Props = {
title: string;
subtitle?: string;
desc?: string;
content?: string;
largedesc?: string;
showBreadcrumb?: boolean;
titleSize?: "large" | "medium" | string;
backgroundImage: string | string[];
};
export default function Banner({
title,
subtitle,
desc,
content,
largedesc,
showBreadcrumb,
titleSize = "large",
backgroundImage,
}: Props) {
const appConfig = useStore((s) => s.appConfig);
const header = appConfig?.header;
const navItems = header?.navItems ?? [];
const location = useLocation();
const images = Array.isArray(backgroundImage) ? backgroundImage : [backgroundImage];
const isCarousel = images.length > 1;
const descText = desc ?? content;
const breadcrumbItems = useMemo(() => {
const segments = location.pathname.split("/").filter((s) => s !== "");
if (segments.length === 0) {
return [{ label: "首页", to: "/" }];
}
const paths: string[] = [];
for (let i = 0; i < segments.length; i++) {
paths.push((paths[i - 1] ?? "") + "/" + segments[i]);
}
const getLabelByPath = (path: string): string => {
if (path === "/") return navItems.find((n) => n.index)?.label ?? "首页";
const top = navItems.find((n) => n.path === path);
if (top) return top.label;
for (const item of navItems) {
const child = item.children?.find((c) => c.path === path);
if (child) return child.label;
}
const last = path.split("/").pop() ?? path;
return last;
};
const items = paths.map((path) => ({
label: getLabelByPath(path),
to: path,
}));
items.unshift({
label: navItems.find((n) => n.index)?.label ?? "首页",
to: "/",
});
return items;
}, [location.pathname, navItems]);
const heroContent = (
<div className={styles.heroContent} style={{ gap: "30px" }}>
<h1 className={`${styles.heroTitle} ${titleSize === "medium" ? styles.heroTitleMedium : ""}`}>{title}</h1>
{subtitle && <h2 className={styles.heroSubtitle}>{subtitle}</h2>}
{descText && <p className={styles.heroDesc}>{descText}</p>}
{largedesc && <p className={styles.heroLargeDesc}>{largedesc}</p>}
<div className={styles.breadcrumb}>
{showBreadcrumb &&
(breadcrumbItems ?? []).map((item, i) => (
<span key={i}>
{i > 0 && <span>{" > "}</span>}
{item.to ? <Link to={item.to}>{item.label}</Link> : <span>{item.label}</span>}
</span>
))}
</div>
</div>
);
return (
<section
className={styles.hero}
style={
isCarousel
? undefined
: { backgroundImage: `url(${images[0]}), ${FALLBACK_GRADIENT}` }
}
>
{isCarousel && (
<Swiper
className={styles.bgSwiper}
modules={[Autoplay, EffectFade]}
effect="slide"
autoplay={{ delay: 3000, disableOnInteraction: false }}
allowTouchMove={false}
slidesPerView={1}
loop={true}
>
{images.map((img, i) => (
<SwiperSlide key={i}>
<div
className={styles.bgSlide}
style={{
backgroundImage: `url(${img}), ${FALLBACK_GRADIENT}`,
}}
/>
</SwiperSlide>
))}
</Swiper>
)}
<div className={styles.heroOverlay} />
{heroContent}
</section>
);
}