This commit is contained in:
zhangjianjun 2026-03-23 16:37:35 +08:00
parent 7b1139f868
commit 3f5380f9c4
19 changed files with 236 additions and 41 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,7 +1,7 @@
.article {
width: 100%;
min-height: 100vh;
padding: 120px 100px;
padding: 120px auto;
.articleHeaderLine {
height: 100px;

View File

@ -2,7 +2,7 @@
width: 100%;
/* height: 1080px; */
height: 100vh;
padding: 100px 260px;
padding: 100px auto;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
@ -44,7 +44,7 @@
justify-content: center;
gap: auto;
border-top: 1px solid rgba(255,255,255,0.5);
padding: 0 200px;
padding: 0 auto;
}
.bottomTabsSectionContentTab {

View File

@ -1,7 +1,7 @@
.honorGrids {
width: 100%;
padding: 100px 300px;
background: rgba(216,216,216,0.5);
padding: 100px 0;
background: rgba(216, 216, 216, 0.5);
.honorGridsTitle {
font-family: Source Han Sans, Source Han Sans;
@ -9,11 +9,15 @@
font-size: 40px;
color: #222222;
line-height: 50px;
margin-bottom: 100px;
text-align: center;
width: 1320px;
margin: 0 auto;
margin-bottom: 100px;
}
.honorGridsItems {
width: 1320px;
margin: 0 auto;
/* 4*n */
display: grid;
grid-template-columns: repeat(4, 1fr);
@ -23,16 +27,42 @@
.honorGridsItem {
height: 116px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: 1px solid #666;
position: relative;
padding: 0 50px;
text-align: center;
.honorGridsTitle {
font-weight: 400;
font-size: 16px;
color: #222222;
}
.honorGridsItemLargeTitle {
font-weight: 600;
font-size: 20px;
color: #222222;
line-height: 30px;
}
.honorGridsItemBgleft,
.honorGridsItemBgright {
width: 70px;
height: 116px;
position: absolute;
background-size: cover;
background-position: left;
background-repeat: no-repeat;
}
.honorGridsItemBgleft {
left: 0;
}
.honorGridsItemBgright {
right: 0;
}
}
}
}

View File

@ -3,10 +3,11 @@ import styles from './index.module.css';
type Data = {
title: string;
items: {
largeTitle: string;
title: string;
}[];
}
export default function HonorGrids({ data }: {data: Data}) {
export default function HonorGrids({ data }: { data: Data }) {
return (
<section className={styles.honorGrids}>
<div className={styles.honorGridsTitle}>{data.title}</div>
@ -14,7 +15,14 @@ export default function HonorGrids({ data }: {data: Data}) {
<div className={styles.honorGridsItems}>
{data.items.map((item) => (
<div className={styles.honorGridsItem} key={item.title}>
<div className={styles.honorGridsItemBgleft}
style={{ backgroundImage: "url(/images/icons/honor-left.png)" }}
></div>
<div className={styles.honorGridsItemLargeTitle}>{item.largeTitle}</div>
<div className={styles.honorGridsItemTitle}>{item.title}</div>
<div className={styles.honorGridsItemBgright}
style={{ backgroundImage: "url(/images/icons/honor-right.png)" }}
></div>
</div>
))}
</div>

View File

@ -65,6 +65,10 @@ export default function RowAccordion({ data, placement='bottom' }: Props) {
}
}, [isInView]);
const getToPath = (link: { text: string; path: string }) => {
return link.path.includes("{id}") ? link.path.replace("{id}", link.text) : link.path;
}
return (
<div ref={containerRef} className={styles.rowAccordion}>
<div className={styles.rowAccordionBgContainer}>
@ -115,7 +119,7 @@ export default function RowAccordion({ data, placement='bottom' }: Props) {
{item.links && (
<div className={styles.contentItemLinks}>
{item.links?.map((link) => (
<Link key={link.text} to={link.path}>{link.text}</Link>
<Link key={link.text} to={getToPath(link)}>{link.text}</Link>
))}
</div>
)}

View File

@ -9,13 +9,13 @@
.topTabsTabs {
position: relative;
/* height: 60px; */
height: 60px;
font-weight: 400;
font-size: 24px;
line-height: 34px;
border-bottom: 1px solid #D5D8DC;
width: 100%;
overflow: hidden;
overflow-y: hidden;
}
.topTabsNavBtn {
@ -80,8 +80,8 @@
}
.topTabsTabItem {
height: 90px;
line-height: 90px;
/* height: 90px;
line-height: 90px; */
flex-shrink: 0;
cursor: pointer;
color: #222222;

View File

@ -35,6 +35,7 @@ html {
body {
margin: 0;
min-width: 1320px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;

View File

@ -99,6 +99,12 @@
font-size: 1rem;
color: #fff;
margin-top: 3.125rem;
transition: all 0.3s ease-in-out;
li {
list-style: disc;
margin: 10px 0;
margin-left: 20px;
}
}
.sectionFounder {
@ -139,6 +145,11 @@
.founderPhotoContent {
padding-top: 6.25rem;
padding-right: 6.25rem;
li {
list-style: disc;
margin: 8px 0;
margin-left: 20px;
}
}
.founderPhotoContent p {

View File

@ -37,7 +37,7 @@ export default function AboutFounder() {
<div className={styles.imageOverlayTitle}>
<span>{item.title}</span>
</div>
<div className={styles.imageOverlayDesc}>{item.content}</div>
<div className={styles.imageOverlayDesc} dangerouslySetInnerHTML={{ __html: item.content ?? "" }}></div>
</div>
</div>
))}

View File

@ -9,7 +9,8 @@
);
min-height: 100vh;
width: 100%;
padding: 6.25rem 18.75rem;
padding: 6.25rem auto;
padding-bottom: 100px;
display: flex;
flex-direction: column;
}
@ -86,12 +87,30 @@
line-height: 1.6;
color: #333;
margin: 0;
max-width: 33.75rem;
margin-top: 1.875rem;
max-width: 600px;
margin-top: 20px;
list-style: disc;
margin-left: 20px;
}
.timelineItem.left .desc {
text-align: right;
list-style: none;
margin-left: 0;
margin-right: 20px;
}
.timelineItem.left li {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.timelineItem.left li.desc::after {
content: "•";
display: block;
margin-left: 0.5em;
}
.dotWrapper {

View File

@ -107,7 +107,11 @@ function TimelineItemRow({
{!isRight && (
<div onMouseEnter={onMouseEnter}>
<span className={styles.year}>{item.year}</span>
<p className={styles.desc}>{item.content}</p>
{
item.content.split('\n').map((line, index) => (
<li key={index} className={styles.desc}>{line}</li>
))
}
</div>
)}
</div>
@ -119,7 +123,11 @@ function TimelineItemRow({
{isRight && (
<div onMouseEnter={onMouseEnter}>
<span className={styles.year}>{item.year}</span>
<p className={styles.desc}>{item.content}</p>
{
item.content.split('\n').map((line, index) => (
<li key={index} className={styles.desc}>{line}</li>
))
}
</div>
)}
</div>

View File

@ -139,6 +139,25 @@
background-position: center;
background-repeat: no-repeat;
padding: 100px 260px;
position: relative;
}
.featuresHeroBg {
position: absolute;
inset: 0;
z-index: -1;
overflow: hidden;
pointer-events: none;
}
.featuresHeroBgLayer {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
transition: opacity 1s cubic-bezier(0.4, 0, 0.2, 1);
will-change: opacity;
}
.featuresHeroContent {
@ -167,11 +186,28 @@
}
.featuresHeroTabs {
position: relative;
border-top: 1px solid rgba(255, 255, 255, 0.5);
}
.featuresHeroTabIndicator {
position: absolute;
top: -1px;
left: 0;
height: 2px;
background: #ffffff;
pointer-events: none;
transition:
transform 0.35s cubic-bezier(0.4, 0, 0.2, 1),
width 0.35s cubic-bezier(0.4, 0, 0.2, 1),
opacity 0.2s ease;
}
.featuresHeroTabRow {
display: flex;
flex-direction: row;
justify-content: center;
gap: 200px;
border-top: 1px solid rgba(255, 255, 255, 0.5);
}
.featuresHeroTab {
@ -183,11 +219,6 @@
cursor: pointer;
}
.featuresHeroTab.active {
border-top: 2px solid #FFFFFF;
}
.propertyServices {
height: 100vh;
color: #fff;

View File

@ -1,4 +1,4 @@
import { useState } from "react";
import { useLayoutEffect, useRef, useState } from "react";
import { Link } from "react-router-dom";
import { useStore } from "@/store";
import styles from "./CommercialGroup.module.css";
@ -17,17 +17,71 @@ function PlaceholderImage() {
export default function BusinessCommercialGroup() {
const appConfig = useStore((s) => s.appConfig);
const data = appConfig?.business?.commercialGroup;
const section3Data = data?.section3Data;
const [in77ImgError, setIn77ImgError] = useState(false);
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;
const section3Data = data.section3Data;
return (
<div>
@ -41,7 +95,7 @@ export default function BusinessCommercialGroup() {
{section1Data && <ParagraphSection data={section1Data} />}
<Section
background={section2Data.backgroundImage }
background={section2Data.backgroundImage}
className={styles.twoColSection}
>
<TopTabs className={styles.twoColSectionTabs} data={section2Data} activeIndex={activeTabIndex} setActiveIndex={setActiveTabIndex} />
@ -72,26 +126,54 @@ export default function BusinessCommercialGroup() {
<section
className={styles.featuresHero}
style={{
backgroundImage: `url(${section3Data.tabItems[activeFeaturesTabIndex]?.backgroundImage}), ${FALLBACK_GRADIENT}`,
}}
>
<div className={styles.featuresHeroBg} aria-hidden>
<div
className={styles.featuresHeroBgLayer}
style={{
zIndex: featuresBg.showA ? 2 : 1,
opacity: featuresBg.showA ? 1 : 0,
backgroundImage: `url(${featuresBg.a}), ${FALLBACK_GRADIENT}`,
}}
/>
<div
className={styles.featuresHeroBgLayer}
style={{
zIndex: featuresBg.showA ? 1 : 2,
opacity: featuresBg.showA ? 0 : 1,
backgroundImage: `url(${featuresBg.b}), ${FALLBACK_GRADIENT}`,
}}
/>
</div>
<SectionTitle color="#fff" title={section3Data.title} subcontent={section3Data.content} />
<div className={styles.featuresHeroContent}>
<div className={styles.featuresHeroContentTitle}>{section3Data.tabItems[activeFeaturesTabIndex]?.tabName}</div>
<div className={styles.featuresHeroContentDesc}>{section3Data.tabItems[activeFeaturesTabIndex]?.content}</div>
</div>
<div className={styles.featuresHeroTabs}>
<div ref={featuresHeroTabsRef} className={styles.featuresHeroTabs}>
<div
className={styles.featuresHeroTabIndicator}
style={{
transform: `translateX(${featuresTabIndicator.x}px)`,
width: featuresTabIndicator.width,
opacity: featuresTabIndicator.width > 0 ? 1 : 0,
}}
/>
<div className={styles.featuresHeroTabRow}>
{section3Data.tabItems.map((item: { tabName: string }, i: number) => (
<div
key={i}
className={`${styles.featuresHeroTab} ${activeFeaturesTabIndex === i ? styles.active : ""}`}
ref={(el) => {
featuresTabItemRefs.current[i] = el;
}}
className={styles.featuresHeroTab}
onClick={() => setActiveFeaturesTabIndex(i)}
>
<span>{item.tabName}</span>
</div>
))}
</div>
</div>
</section>
<section

View File

@ -68,6 +68,7 @@ export default function InvestGroup() {
background={section4Data.backgroundImage}
maskBackground="rgba(20,53,92,0.1)"
className={styles.industryFoster}
height="100vh"
>
<div className={styles.industryFosterMask}>
<div className={styles.industryFosterMaskTitle}>{section4Data.title}</div>