159 lines
6.5 KiB
TypeScript
159 lines
6.5 KiB
TypeScript
import { useLayoutEffect, useRef, useState } from "react";
|
|
import { Link } from "react-router-dom";
|
|
import { useStore } from "@/store";
|
|
import styles from "./CommercialGroup.module.css";
|
|
import Banner from "@/components/Banner";
|
|
import ParagraphSection from "@/components/layout/ParagraphSection";
|
|
import Section from "@/components/layout/Section";
|
|
import TopTabs from "@/components/layout/TopTabsSection/TopTabs";
|
|
import SectionTitle from "@/components/layout/SectionTitle";
|
|
import ScrollReveal from "@/components/ScrollReveal";
|
|
import BottomTabsSection from "@/components/layout/BottomTabsSection";
|
|
|
|
const FALLBACK_GRADIENT = "linear-gradient(135deg, #1a2a4a 0%, #2d4a7c 100%)";
|
|
|
|
function PlaceholderImage() {
|
|
return <div className={styles.placeholder}>占位图</div>;
|
|
}
|
|
|
|
export default function BusinessCommercialGroup() {
|
|
const appConfig = useStore((s) => s.appConfig);
|
|
const { viewDetail = "查看详情" } = appConfig?.__global__?.others
|
|
const data = appConfig?.business?.commercialGroup;
|
|
const section3Data = data?.section3Data;
|
|
const section4Data = data?.section4Data;
|
|
|
|
const [activeTabIndex, setActiveTabIndex] = useState(0);
|
|
const [activeFeaturesTabIndex, setActiveFeaturesTabIndex] = useState(0);
|
|
|
|
const featuresHeroTabsRef = useRef<HTMLDivElement>(null);
|
|
const featuresTabItemRefs = useRef<(HTMLDivElement | null)[]>([]);
|
|
const [featuresTabIndicator, setFeaturesTabIndicator] = useState({ x: 0, width: 0 });
|
|
|
|
const featuresFirstBg = section3Data?.tabItems?.[0]?.backgroundImage ?? "";
|
|
const [featuresBg, setFeaturesBg] = useState({
|
|
a: featuresFirstBg,
|
|
b: featuresFirstBg,
|
|
showA: true,
|
|
});
|
|
|
|
useLayoutEffect(() => {
|
|
if (!section3Data?.tabItems?.length) return;
|
|
const next = section3Data.tabItems[activeFeaturesTabIndex]?.backgroundImage ?? "";
|
|
setFeaturesBg((prev) => {
|
|
const visible = prev.showA ? prev.a : prev.b;
|
|
if (next === visible) return prev;
|
|
// 数据首帧就绪:直接铺满两层,避免从空图做一次淡入
|
|
if (!visible && next) {
|
|
return { a: next, b: next, showA: true };
|
|
}
|
|
if (prev.showA) {
|
|
return { ...prev, b: next, showA: false };
|
|
}
|
|
return { ...prev, a: next, showA: true };
|
|
});
|
|
}, [activeFeaturesTabIndex, section3Data]);
|
|
|
|
useLayoutEffect(() => {
|
|
if (!section3Data?.tabItems?.length) return;
|
|
const root = featuresHeroTabsRef.current;
|
|
if (!root) return;
|
|
|
|
const updateIndicator = () => {
|
|
const tab = featuresTabItemRefs.current[activeFeaturesTabIndex];
|
|
if (!tab) return;
|
|
const rootRect = root.getBoundingClientRect();
|
|
const tabRect = tab.getBoundingClientRect();
|
|
setFeaturesTabIndicator({
|
|
x: tabRect.left - rootRect.left,
|
|
width: tabRect.width,
|
|
});
|
|
};
|
|
|
|
updateIndicator();
|
|
const ro = new ResizeObserver(updateIndicator);
|
|
ro.observe(root);
|
|
window.addEventListener("resize", updateIndicator);
|
|
return () => {
|
|
ro.disconnect();
|
|
window.removeEventListener("resize", updateIndicator);
|
|
};
|
|
}, [activeFeaturesTabIndex, section3Data?.tabItems]);
|
|
|
|
if (!data) return null;
|
|
|
|
const banner = data.banner;
|
|
const section1Data = data.section1Data;
|
|
const section2Data = data.section2Data;
|
|
|
|
return (
|
|
<div>
|
|
<Banner
|
|
title={banner?.title ?? ""}
|
|
largedesc={banner?.content}
|
|
titleSize={banner?.titleSize as "medium" | "large" | undefined ?? "medium"}
|
|
backgroundImage={banner?.backgroundImage ?? "/images/bg-commercial-group.png"}
|
|
/>
|
|
|
|
{section1Data && <ParagraphSection data={section1Data} />}
|
|
|
|
<Section
|
|
background={section2Data.backgroundImage}
|
|
className={styles.twoColSection}
|
|
>
|
|
<TopTabs className={styles.twoColSectionTabs} data={section2Data} activeIndex={activeTabIndex} setActiveIndex={setActiveTabIndex} />
|
|
<div className={styles.twoColSectionContent}>
|
|
<ScrollReveal
|
|
preset="slideRight"
|
|
duration={1}
|
|
className={styles.twoColImageWrap}
|
|
>
|
|
<div className={styles.twoColImage} style={{
|
|
backgroundImage: `url(${section2Data.tabItems[activeTabIndex]?.sideImage as string})`,
|
|
}}>
|
|
</div>
|
|
</ScrollReveal>
|
|
|
|
<div className={styles.twoColText}>
|
|
<div className={styles.twoColTextContent}>
|
|
<ScrollReveal preset="slideUp" delay={0.5} >
|
|
<p className={styles.twoColDesc} dangerouslySetInnerHTML={{ __html: section2Data.tabItems[activeTabIndex]?.content ?? "" }}></p>
|
|
</ScrollReveal>
|
|
<Link
|
|
to={section2Data.tabItems[activeTabIndex]?.path ?? "#"}
|
|
className={styles.btnPrimary}
|
|
>
|
|
{viewDetail}
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Section>
|
|
|
|
|
|
{section3Data && <BottomTabsSection data={section3Data} />}
|
|
|
|
{
|
|
section4Data.hide ? null : (
|
|
<section
|
|
className={`${styles.twoColSection} ${styles.propertyServices}`}
|
|
style={{
|
|
backgroundImage: `url(${section4Data?.backgroundImage}), ${FALLBACK_GRADIENT}`,
|
|
}}
|
|
>
|
|
<div className={styles.propertyServicesContent}>
|
|
<div className={styles.propertyServicesTitle}>{section4Data?.title}</div>
|
|
<p className={styles.propertyServicesSubtitle}>{section4Data?.content}</p>
|
|
<ScrollReveal preset="slideUp" className={styles.propertyServicesBtn}>
|
|
<Link to={section4Data?.path ?? "#"} >
|
|
{viewDetail}
|
|
</Link>
|
|
</ScrollReveal>
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|
|
</div>
|
|
);
|
|
}
|