save
This commit is contained in:
parent
7b1139f868
commit
3f5380f9c4
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 |
|
|
@ -1,7 +1,7 @@
|
|||
.article {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
padding: 120px 100px;
|
||||
padding: 120px auto;
|
||||
|
||||
.articleHeaderLine {
|
||||
height: 100px;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,25 +126,53 @@ 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}>
|
||||
{section3Data.tabItems.map((item: { tabName: string }, i: number) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`${styles.featuresHeroTab} ${activeFeaturesTabIndex === i ? styles.active : ""}`}
|
||||
onClick={() => setActiveFeaturesTabIndex(i)}
|
||||
>
|
||||
<span>{item.tabName}</span>
|
||||
</div>
|
||||
))}
|
||||
<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}
|
||||
ref={(el) => {
|
||||
featuresTabItemRefs.current[i] = el;
|
||||
}}
|
||||
className={styles.featuresHeroTab}
|
||||
onClick={() => setActiveFeaturesTabIndex(i)}
|
||||
>
|
||||
<span>{item.tabName}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue