save
This commit is contained in:
parent
b527ad343b
commit
22a98cd23a
|
|
@ -59,6 +59,7 @@
|
|||
"react": "^19.1.0",
|
||||
"react-activation": "^0.13.4",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
"react-countup": "^6.5.3",
|
||||
"react-dev-utils": "^12.0.1",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-refresh": "^0.11.0",
|
||||
|
|
|
|||
|
|
@ -252,11 +252,7 @@ const routes = createBrowserRouter([
|
|||
{
|
||||
path: "join/campus/detail/:id",
|
||||
element: (
|
||||
<KeepAlive id="join-campus-detail">
|
||||
<Suspense fallback={null}>
|
||||
<JoinCampusDetail />
|
||||
</Suspense>
|
||||
</KeepAlive>
|
||||
<JoinCampusDetail />
|
||||
),
|
||||
},
|
||||
// 其它
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export default function ScrollReveal({
|
|||
once = true,
|
||||
amount = 0.5,
|
||||
delay = 0,
|
||||
duration = 0.7,
|
||||
duration = 0.9,
|
||||
as: Component = "div",
|
||||
}: ScrollRevealProps) {
|
||||
const variant = ANIMATION_VARIANTS[preset];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState } from "react";
|
||||
import { useLayoutEffect, useRef, useState } from "react";
|
||||
import styles from "./index.module.css";
|
||||
|
||||
type TabItems = {
|
||||
|
|
@ -7,14 +7,60 @@ type TabItems = {
|
|||
}[]
|
||||
|
||||
export default function BottomTabs({ tabItems, activeIndex, setActiveIndex }: { tabItems: TabItems, activeIndex: number, setActiveIndex: (index: number) => void }) {
|
||||
const tabsRef = useRef<HTMLDivElement>(null);
|
||||
const tabItemRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||
const [tabIndicator, setTabIndicator] = useState({ x: 0, width: 0 });
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!tabItems?.length) return;
|
||||
const root = tabsRef.current;
|
||||
if (!root) return;
|
||||
|
||||
const updateIndicator = () => {
|
||||
const tab = tabItemRefs.current[activeIndex];
|
||||
if (!tab) return;
|
||||
const rootRect = root.getBoundingClientRect();
|
||||
const tabRect = tab.getBoundingClientRect();
|
||||
setTabIndicator({
|
||||
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);
|
||||
};
|
||||
}, [activeIndex, tabItems]);
|
||||
|
||||
return (
|
||||
<div className={styles.bottomTabsSectionContentTabs}>
|
||||
{tabItems.map((item, i) => (
|
||||
<div key={i} className={`${styles.bottomTabsSectionContentTab} ${activeIndex === i ? styles.active : ''}`} onClick={() => setActiveIndex(i)}>
|
||||
<span>{item.tabName}</span>
|
||||
</div>
|
||||
))}
|
||||
<div ref={tabsRef} className={styles.bottomTabsSectionTabsWrap}>
|
||||
<div
|
||||
className={styles.bottomTabsSectionTabIndicator}
|
||||
style={{
|
||||
transform: `translateX(${tabIndicator.x}px)`,
|
||||
width: tabIndicator.width,
|
||||
opacity: tabIndicator.width > 0 ? 1 : 0,
|
||||
}}
|
||||
/>
|
||||
<div className={styles.bottomTabsSectionContentTabs}>
|
||||
{tabItems.map((item, i) => (
|
||||
<div
|
||||
key={i}
|
||||
ref={(el) => {
|
||||
tabItemRefs.current[i] = el;
|
||||
}}
|
||||
className={styles.bottomTabsSectionContentTab}
|
||||
onClick={() => setActiveIndex(i)}
|
||||
>
|
||||
<span>{item.tabName}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
.bottomTabsSection {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
/* height: 1080px; */
|
||||
height: 100vh;
|
||||
padding: 100px auto;
|
||||
padding: 100px 0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
|
|
@ -10,8 +12,26 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bottomTabsSectionBg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
|
||||
}
|
||||
|
||||
.bottomTabsSectionBgLayer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
/* Features Section */
|
||||
.bottomTabsSectionContent {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: calc(100% - 520px);
|
||||
margin: 0 auto;
|
||||
height: 500px;
|
||||
|
|
@ -40,16 +60,30 @@
|
|||
}
|
||||
|
||||
.bottomTabsSectionContentTabs {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
justify-content: space-evenly;
|
||||
gap: auto;
|
||||
border-top: 1px solid rgba(255,255,255,0.5);
|
||||
padding: 0 auto;
|
||||
}
|
||||
|
||||
.bottomTabsSectionTabsWrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bottomTabsSectionTabIndicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
background-color: #fff;
|
||||
transition: transform 0.3s ease, width 0.3s ease, opacity 0.3s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.bottomTabsSectionContentTab {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
color: #FFFFFF;
|
||||
|
|
@ -62,7 +96,4 @@
|
|||
line-height: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
.bottomTabsSectionContentTab.active span {
|
||||
border-top: 2px solid #FFFFFF;
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import styles from './index.module.css';
|
||||
import SectionTitle from '../SectionTitle';
|
||||
import { useState } from 'react';
|
||||
import BottomTabs from './BottomTabs';
|
||||
import { useLayoutEffect, useRef, useState } from 'react';
|
||||
import ScrollReveal from '@/components/ScrollReveal';
|
||||
|
||||
type Data = {
|
||||
title: string;
|
||||
|
|
@ -9,25 +9,129 @@ type Data = {
|
|||
tabName: string;
|
||||
contentTitle: string;
|
||||
contentText: string;
|
||||
/** 以 mockData 为准 */
|
||||
content?: string;
|
||||
backgroundImage?: string;
|
||||
image?: string;
|
||||
}[]
|
||||
}
|
||||
|
||||
const FALLBACK_GRADIENT = "linear-gradient(135deg, #1a2a4a 0%, #2d4a7c 100%)";
|
||||
|
||||
export default function BottomTabsSection({ data }: { data: Data }) {
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const bgImg = data.tabItems[activeIndex]?.backgroundImage ?? data.tabItems[activeIndex]?.image ?? "";
|
||||
const tabsRef = useRef<HTMLDivElement>(null);
|
||||
const tabItemRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||
const [tabIndicator, setTabIndicator] = useState({ x: 0, width: 0 });
|
||||
|
||||
const firstBg = data.tabItems?.[0]?.backgroundImage ?? data.tabItems?.[0]?.image ?? "";
|
||||
const [bgState, setBgState] = useState({
|
||||
a: firstBg,
|
||||
b: firstBg,
|
||||
showA: true,
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!data.tabItems?.length) return;
|
||||
const next = data.tabItems[activeIndex]?.backgroundImage ?? data.tabItems[activeIndex]?.image ?? "";
|
||||
setBgState((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 };
|
||||
});
|
||||
}, [activeIndex, data.tabItems]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!data.tabItems?.length) return;
|
||||
const root = tabsRef.current;
|
||||
if (!root) return;
|
||||
|
||||
const updateIndicator = () => {
|
||||
const tab = tabItemRefs.current[activeIndex];
|
||||
if (!tab) return;
|
||||
const rootRect = root.getBoundingClientRect();
|
||||
const tabRect = tab.getBoundingClientRect();
|
||||
setTabIndicator({
|
||||
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);
|
||||
};
|
||||
}, [activeIndex, data.tabItems]);
|
||||
|
||||
return (
|
||||
<div className={styles.bottomTabsSection} style={{ backgroundImage: bgImg ? `url(${bgImg})` : undefined }}>
|
||||
<div className={styles.bottomTabsSection}>
|
||||
<div className={styles.bottomTabsSectionBg} aria-hidden>
|
||||
<div
|
||||
className={styles.bottomTabsSectionBgLayer}
|
||||
style={{
|
||||
zIndex: bgState.showA ? 2 : 1,
|
||||
opacity: bgState.showA ? 1 : 0,
|
||||
backgroundImage: `url(${bgState.a}), ${FALLBACK_GRADIENT}`,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className={styles.bottomTabsSectionBgLayer}
|
||||
style={{
|
||||
zIndex: bgState.showA ? 1 : 2,
|
||||
opacity: bgState.showA ? 0 : 1,
|
||||
backgroundImage: `url(${bgState.b}), ${FALLBACK_GRADIENT}`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<SectionTitle title={data.title} color="#fff" />
|
||||
<div className={styles.bottomTabsSectionContent}>
|
||||
<div className={styles.bottomTabsSectionContentContent}>
|
||||
<div className={styles.contentTitle}>{data.tabItems[activeIndex].contentTitle}</div>
|
||||
<div className={styles.contentText}>{data.tabItems[activeIndex].contentText}</div>
|
||||
<ScrollReveal preset="slideUp" amount={0.2} delay={0.5}>
|
||||
<div className={styles.contentTitle}>{
|
||||
data.tabItems[activeIndex].contentTitle ||
|
||||
data.tabItems[activeIndex].tabName
|
||||
}</div>
|
||||
</ScrollReveal>
|
||||
<ScrollReveal preset="slideUp" amount={0.2} delay={0.7}>
|
||||
<div className={styles.contentText}>{
|
||||
data.tabItems[activeIndex].contentText
|
||||
|| (data.tabItems[activeIndex]?.content)
|
||||
}</div>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
<div ref={tabsRef} className={styles.bottomTabsSectionTabsWrap}>
|
||||
<div
|
||||
className={styles.bottomTabsSectionTabIndicator}
|
||||
style={{
|
||||
transform: `translateX(${tabIndicator.x}px)`,
|
||||
width: tabIndicator.width,
|
||||
opacity: tabIndicator.width > 0 ? 1 : 0,
|
||||
}}
|
||||
/>
|
||||
<div className={styles.bottomTabsSectionContentTabs}>
|
||||
{data.tabItems.map((item, i) => (
|
||||
<div
|
||||
key={i}
|
||||
ref={(el) => {
|
||||
tabItemRefs.current[i] = el;
|
||||
}}
|
||||
className={styles.bottomTabsSectionContentTab}
|
||||
onClick={() => setActiveIndex(i)}
|
||||
>
|
||||
<span>{item.tabName}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<BottomTabs tabItems={data.tabItems} activeIndex={activeIndex} setActiveIndex={setActiveIndex} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import ScrollReveal from "@/components/ScrollReveal";
|
||||
import styles from "./index.module.css";
|
||||
|
||||
type Props = {
|
||||
|
|
@ -12,6 +13,7 @@ export default function ColumnXGrids({ items }: Props) {
|
|||
return (
|
||||
<div className={styles.columnXGrids}>
|
||||
{items.map((item, index) => (
|
||||
<ScrollReveal preset="slideUp" amount={0.2} delay={(index % 3) * 0.5}>
|
||||
<div key={index} className={styles.columnXGridsItem}>
|
||||
<div className={styles.bg} style={{ backgroundImage: `url(${item.backgroundImage})` }} />
|
||||
<div className={styles.mask}></div>
|
||||
|
|
@ -20,6 +22,7 @@ export default function ColumnXGrids({ items }: Props) {
|
|||
<div className={styles.columnXGridsItemContent}>{item.content}</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import ScrollReveal from '@/components/ScrollReveal';
|
||||
import styles from './index.module.css';
|
||||
|
||||
type Data = {
|
||||
|
|
@ -13,7 +14,8 @@ export default function HonorGrids({ data }: { data: Data }) {
|
|||
<div className={styles.honorGridsTitle}>{data.title}</div>
|
||||
|
||||
<div className={styles.honorGridsItems}>
|
||||
{data.items.map((item) => (
|
||||
{data.items.map((item, index) => (
|
||||
<ScrollReveal preset="slideUp" amount={0.2} delay={index * 0.2}>
|
||||
<div className={styles.honorGridsItem} key={item.title}>
|
||||
<div className={styles.honorGridsItemBgleft}
|
||||
style={{ backgroundImage: "url(/images/icons/honor-left.png)" }}
|
||||
|
|
@ -24,6 +26,7 @@ export default function HonorGrids({ data }: { data: Data }) {
|
|||
style={{ backgroundImage: "url(/images/icons/honor-right.png)" }}
|
||||
></div>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -65,5 +65,6 @@
|
|||
text-align: left;
|
||||
font-style: normal;
|
||||
text-transform: none;
|
||||
white-space: pre-line;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import styles from "./StatsRow.module.css";
|
||||
|
||||
import CountUp from "react-countup";
|
||||
type Data = {
|
||||
num: string;
|
||||
label: string;
|
||||
|
|
@ -22,7 +22,24 @@ export default function StatsRow({ data, color }: Props) {
|
|||
<div key={i} className={styles.statItem}>
|
||||
<div>
|
||||
<span className={`${styles.statNum} font-num`}>
|
||||
{item.num.split(' ')[0] + ' '}
|
||||
{(() => {
|
||||
const numberPart = item.num.split(" ")[0] ?? "";
|
||||
const parsed = Number(numberPart.replace(/,/g, ""));
|
||||
if (Number.isNaN(parsed)) return `${numberPart} `;
|
||||
return (
|
||||
|
||||
<CountUp
|
||||
end={parsed}
|
||||
duration={2}
|
||||
separator=","
|
||||
decimals={Number.isInteger(parsed) ? 0 : 2}
|
||||
preserveValue={false}
|
||||
enableScrollSpy
|
||||
scrollSpyOnce
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
{" "}
|
||||
</span>
|
||||
<span className={`${styles.statNumDesc}`}>
|
||||
{item.num.split(' ')[1]}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ 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%)";
|
||||
|
||||
|
|
@ -127,61 +128,8 @@ export default function BusinessCommercialGroup() {
|
|||
</div>
|
||||
</Section>
|
||||
|
||||
<section
|
||||
className={styles.featuresHero}
|
||||
>
|
||||
<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}>
|
||||
<ScrollReveal preset="slideUp" >
|
||||
<div className={styles.featuresHeroContentTitle}>{section3Data.tabItems[activeFeaturesTabIndex]?.tabName}</div>
|
||||
</ScrollReveal>
|
||||
<ScrollReveal preset="slideUp" >
|
||||
<div className={styles.featuresHeroContentDesc}>{section3Data.tabItems[activeFeaturesTabIndex]?.content}</div>
|
||||
</ScrollReveal>
|
||||
</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>
|
||||
|
||||
{section3Data && <BottomTabsSection data={section3Data} />}
|
||||
|
||||
<section
|
||||
className={`${styles.twoColSection} ${styles.propertyServices}`}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import SectionTitle from "@/components/layout/SectionTitle";
|
|||
import Section from "@/components/layout/Section";
|
||||
import { useStore } from "@/store";
|
||||
import styles from "./InvestGroup.module.css";
|
||||
import ScrollReveal from "@/components/ScrollReveal";
|
||||
|
||||
export default function InvestGroup() {
|
||||
const appConfig = useStore((s) => s.appConfig);
|
||||
|
|
@ -34,10 +35,12 @@ export default function InvestGroup() {
|
|||
<div className={styles.investGroupContent}>
|
||||
<div className={styles.investGroupContentItems}>
|
||||
{section2Data.items?.map((item: { title: string; content: string }, index: number) => (
|
||||
<ScrollReveal preset="slideUp" amount={0.2} delay={index * 0.5}>
|
||||
<div className={styles.investGroupContentItem} key={index}>
|
||||
<div className={styles.investGroupContentItemTitle}>{item.title}</div>
|
||||
<div className={styles.investGroupContentItemContent}>{item.content}</div>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -49,6 +52,7 @@ export default function InvestGroup() {
|
|||
<SectionTitle title={section3Data.title} />
|
||||
<div className={styles.equityInvestmentItems}>
|
||||
{section3Data.items?.map((item: { logo: string; title: string }, index: number) => (
|
||||
<ScrollReveal preset="slideUp" amount={0.2} delay={index * 0.5}>
|
||||
<div className={styles.equityInvestmentItem} key={index}>
|
||||
<img
|
||||
src={item.logo}
|
||||
|
|
@ -57,6 +61,7 @@ export default function InvestGroup() {
|
|||
/>
|
||||
<div className={styles.equityInvestmentItemTitle}>{item.title}</div>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -70,10 +75,12 @@ export default function InvestGroup() {
|
|||
className={styles.industryFoster}
|
||||
height="100vh"
|
||||
>
|
||||
<ScrollReveal preset="slideUp" amount={0.2}>
|
||||
<div className={styles.industryFosterMask}>
|
||||
<div className={styles.industryFosterMaskTitle}>{section4Data.title}</div>
|
||||
<div className={styles.industryFosterMaskContent}>{section4Data.content}</div>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
</Section>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import Section from "@/components/layout/Section";
|
|||
import StatsRow from "@/components/layout/StatsRow/StatsRow";
|
||||
import { useStore } from "@/store";
|
||||
import styles from "./RuijingGroup.module.css";
|
||||
import ScrollReveal from "@/components/ScrollReveal";
|
||||
|
||||
export default function RuijingGroup() {
|
||||
const appConfig = useStore((s) => s.appConfig);
|
||||
|
|
@ -50,7 +51,9 @@ export default function RuijingGroup() {
|
|||
background="/images/bg-overview.png"
|
||||
maskBackground="rgba(2,17,48,0.5)"
|
||||
>
|
||||
<ScrollReveal preset="slideUp" amount={0.2} delay={0.5}>
|
||||
<div className={styles.businessFeatureContent}>{section3Data.content}</div>
|
||||
</ScrollReveal>
|
||||
{section3Data.statsData && (
|
||||
<StatsRow data={section3Data.statsData} color="#fff" />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export default function JoinCampus() {
|
|||
const [businessPlateOptions, setBusinessPlateOptions] = useState<SelectOption[]>([]);
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
const [size] = useState(2 * supportLocales.length);
|
||||
const [size] = useState(5 * supportLocales.length);
|
||||
const [total, setTotal] = useState(1000);
|
||||
|
||||
|
||||
|
|
@ -79,9 +79,9 @@ export default function JoinCampus() {
|
|||
categoryList?.filter((item: any) => item.type === 'job_area').map((item: any) => ({ label: item.name, value: String(item.id) })) ?? [];
|
||||
const businessPlateOptions: SelectOption[] =
|
||||
categoryList?.filter((item: any) => item.type === 'job_unit').map((item: any) => ({ label: item.name, value: String(item.id) })) ?? [];
|
||||
setJobTypeOptions(jobTypeOptions);
|
||||
setBusinessAreaOptions(businessAreaOptions);
|
||||
setBusinessPlateOptions(businessPlateOptions);
|
||||
setJobTypeOptions([{ label: '全部', value: '' }, ...jobTypeOptions]);
|
||||
setBusinessAreaOptions([{ label: '全部', value: '' }, ...businessAreaOptions]);
|
||||
setBusinessPlateOptions([{ label: '全部', value: '' }, ...businessPlateOptions]);
|
||||
}, [categoryList]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -140,7 +140,7 @@ export default function JoinCampus() {
|
|||
<div key={index} className={styles.jobItemLabel}> • {label}</div>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.jobItemContent}>{item.content}</div>
|
||||
<div className={styles.jobItemContent}>工作职责:{item.content}</div>
|
||||
</div></Link>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export default function CampusDetail() {
|
|||
getJobDetail()
|
||||
}, [])
|
||||
return (
|
||||
<div>
|
||||
<div style={{ minHeight: '100vh' }}>
|
||||
{
|
||||
localJobDetail &&
|
||||
<JobPage data={localJobDetail} />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import styles from "./Culture.module.css";
|
||||
import Banner, { type BannerConfig } from "@/components/Banner";
|
||||
import Section from "@/components/layout/Section";
|
||||
import ScrollReveal from "@/components/ScrollReveal";
|
||||
import { useStore } from "@/store";
|
||||
|
||||
export default function Culture() {
|
||||
|
|
@ -30,6 +31,7 @@ export default function Culture() {
|
|||
>
|
||||
<div className={styles.cultureItems}>
|
||||
{section1Data.items?.map((item: any, index: number) => (
|
||||
<ScrollReveal preset="slideUp" amount={0.2} delay={index * 0.5} key={index} className={styles.cultureItem}>
|
||||
<div
|
||||
key={index}
|
||||
className={styles.cultureItem}
|
||||
|
|
@ -41,6 +43,7 @@ export default function Culture() {
|
|||
<div className={styles.cultureItemContent}>{item.content}</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
|
@ -54,6 +57,7 @@ export default function Culture() {
|
|||
>
|
||||
<div className={styles.valuesItems}>
|
||||
{section2Data.items?.map((item: { title: string; content?: string; icon?: string }, index: number) => (
|
||||
<ScrollReveal preset="slideUp" amount={0.2} delay={index * 0.5} key={index} className={styles.cultureItem}>
|
||||
<div key={index} className={styles.valuesItem}>
|
||||
<div
|
||||
className={styles.valuesItemIcon}
|
||||
|
|
@ -62,6 +66,7 @@ export default function Culture() {
|
|||
<div className={styles.valuesItemTitle}>{item.title}</div>
|
||||
<div className={styles.valuesItemContent}>{item.content}</div>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import styles from "./Media.module.css";
|
||||
import Banner, { type BannerConfig } from "@/components/Banner";
|
||||
import Section from "@/components/layout/Section";
|
||||
import ScrollReveal from "@/components/ScrollReveal";
|
||||
import { useStore } from "@/store";
|
||||
|
||||
export default function Media() {
|
||||
|
|
@ -25,10 +26,10 @@ export default function Media() {
|
|||
<Section background="/images/bg-overview.png" maskBackground="rgba(1,11,72,0.8)">
|
||||
<div className={styles.mediaItems}>
|
||||
{items.map((item: { title: string; content?: string }, index: number) => (
|
||||
<div key={index} className={styles.mediaItem}>
|
||||
<ScrollReveal preset="slideUp" amount={0.2} delay={index * 0.5} key={index} className={styles.mediaItem}>
|
||||
<div className={styles.mediaItemTitle}>{item.title}</div>
|
||||
<div className={styles.mediaItemContent}>{item.content}</div>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import Pagination from "@/components/Pagination";
|
|||
import { Link } from "react-router-dom";
|
||||
import { useStore } from "@/store";
|
||||
import appApi from "@/api/app";
|
||||
import ScrollReveal from "@/components/ScrollReveal";
|
||||
|
||||
type NewsItem = {
|
||||
id: number;
|
||||
|
|
@ -90,6 +91,7 @@ export default function NewsPublic() {
|
|||
|
||||
<div className={styles.newList}>
|
||||
{localNewsList.map((item, index) => (
|
||||
<ScrollReveal preset="slideUp" delay={(index % 3) * 0.5} key={index}>
|
||||
<Link
|
||||
key={index}
|
||||
to={`/news/detail/${item.id}`}
|
||||
|
|
@ -121,6 +123,7 @@ export default function NewsPublic() {
|
|||
<div className={styles.newItemCreateTime}>{item.createTime}</div>
|
||||
</div>
|
||||
</Link>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@
|
|||
|
||||
/* 信息公开 */
|
||||
.informationPublicDataContent {
|
||||
min-height: 500px;
|
||||
min-height: 450px;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Banner, { type BannerConfig } from "@/components/Banner";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import ParagraphSection from "@/components/layout/ParagraphSection";
|
||||
import Section from "@/components/layout/Section";
|
||||
import AnimateTopCard from "@/components/layout/AnimateTopCard";
|
||||
|
|
@ -8,6 +8,7 @@ import { useStore } from "@/store";
|
|||
import styles from "./Foundation.module.css";
|
||||
import TopTabsSection from "@/components/layout/TopTabsSection";
|
||||
import appApi from "@/api/app";
|
||||
import ScrollReveal from "@/components/ScrollReveal";
|
||||
export default function Foundation() {
|
||||
const appConfig = useStore((s) => s.appConfig);
|
||||
const data = appConfig?.social?.foundation;
|
||||
|
|
@ -26,16 +27,16 @@ export default function Foundation() {
|
|||
const localNewsList = useMemo(() => {
|
||||
return newsList.filter((item: any) => item.lang.toLowerCase() === locale.split('-')[0]);
|
||||
}, [newsList, locale])
|
||||
useEffect(() => {
|
||||
const getNewsList = useCallback(() => {
|
||||
appApi.getNewsList({ page: 1, size: 1000, sort: "create_time DESC",
|
||||
category_id: String(categoryList?.find((item: any) => item.name.includes('【可持续发展】社会责任案例集'))?.id ?? ''),
|
||||
category_id: String(categoryList?.find((item: any) => item.name.includes('【银泰公益基金会】公益传播'))?.id ?? ''),
|
||||
}).then((res:any) => {
|
||||
setNewsList(res.data.items.map((item:any) => {
|
||||
return {
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
content: item.content,
|
||||
path: item.path,
|
||||
path: '/news/detail/' + item.id,
|
||||
image: item.covers_show === 'image' ? item.covers.image[0] : '',
|
||||
video: item.covers_show === 'video' ? item.covers.video[0] : '',
|
||||
lang: item.lang,
|
||||
|
|
@ -45,6 +46,27 @@ export default function Foundation() {
|
|||
});
|
||||
}, [])
|
||||
|
||||
|
||||
const [fileList, setFileList] = useState<any[]>([]);
|
||||
const localFileList = useMemo(() => {
|
||||
return fileList.filter((item: any) => item.lang.toLowerCase() === locale.split('-')[0]);
|
||||
}, [fileList, locale])
|
||||
|
||||
const getFileList = useCallback(async () => {
|
||||
const res = await appApi.getDocList({
|
||||
page: 1,
|
||||
size: 1000,
|
||||
// category_id: String(categoryList?.find((item: any) => item.name.includes('社会责任报告'))?.id ?? '')
|
||||
})
|
||||
const items = res.data.items.filter((item:any) => item.category_name.includes("信息公开"))
|
||||
console.log("------items-----", items)
|
||||
setFileList(items)
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
getNewsList()
|
||||
getFileList()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Banner
|
||||
|
|
@ -68,31 +90,36 @@ export default function Foundation() {
|
|||
>
|
||||
<div className={styles.publicWelfareDataItems}>
|
||||
{localNewsList?.map((item: any, index: number) => (
|
||||
<div
|
||||
<ScrollReveal preset="slideUp" amount={0.2} delay={(index % 2) * 0.5}
|
||||
key={index}
|
||||
className={styles.publicWelfareDataItem}
|
||||
>
|
||||
<AnimateTopCard data={item} />
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ marginBottom: "50px" }} />
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{section4Data && (
|
||||
{section3Data && (
|
||||
<Section
|
||||
title={section4Data.title}
|
||||
title={section3Data.title}
|
||||
titleColor="#fff"
|
||||
background={section3Data.backgroundImage ?? "/images/bg-overview.png"}
|
||||
maskBackground="rgba(255,255,255,0.1)"
|
||||
height="760px"
|
||||
>
|
||||
<div className={styles.informationPublicDataContent}>
|
||||
<div className={styles.informationPublicDataItems}>
|
||||
{section3Data.tabItems?.[activeIndex]?.fileItems?.map((item: any, index: number) => (
|
||||
{localFileList
|
||||
.filter((item:any) => item.category_name.includes(
|
||||
section3Data.tabItems?.[activeIndex]?.tabName ?? ''
|
||||
))
|
||||
.map((item: any, index: number) => (
|
||||
<div key={index} className={styles.informationPublicDataItem}>
|
||||
<li>
|
||||
<span>{item.fileName}</span>
|
||||
<li onClick={() => window.open(item.path, '_blank')}>
|
||||
<span>{item.name}</span>
|
||||
</li>
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -110,9 +137,11 @@ export default function Foundation() {
|
|||
<Section title={section4Data.title} background="" maskBackground="#F7FBFF">
|
||||
<div className={styles.partnerItems}>
|
||||
{section4Data.items?.map((item: any, index: number) => (
|
||||
<ScrollReveal preset="slideUp" amount={0.2} delay={index * 0.5} key={index}>
|
||||
<div key={index} className={styles.partnerItem}>
|
||||
<img src={item.logo} alt="logo" />
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import AnimateTopCard from "@/components/layout/AnimateTopCard";
|
|||
import { useStore } from "@/store";
|
||||
import styles from "./Sustainability.module.css";
|
||||
import appApi from "@/api/app";
|
||||
import ScrollReveal from "@/components/ScrollReveal";
|
||||
|
||||
type NewsItem = {
|
||||
id: number;
|
||||
|
|
@ -135,12 +136,13 @@ export default function Sustainability() {
|
|||
</p>
|
||||
<div className={styles.socialResponsibilityCaseDataItems}>
|
||||
{localNewsItems?.map((item: any, index: number) => (
|
||||
<div
|
||||
key={index}
|
||||
<ScrollReveal preset="slideUp" amount={0.2} delay={(index % 2) * 0.5}
|
||||
key={index}
|
||||
className={styles.socialResponsibilityCaseDataItem}
|
||||
>
|
||||
|
||||
<AnimateTopCard data={item} />
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
|
@ -154,7 +156,7 @@ export default function Sustainability() {
|
|||
>
|
||||
<div className={styles.socialResponsibilityReportData}>
|
||||
{localReportItems?.map((item: any, index: number) => (
|
||||
<div
|
||||
<ScrollReveal preset="slideUp" amount={0.2} delay={(index % 4) * 0.5}
|
||||
key={index}
|
||||
className={styles.socialResponsibilityReportItem}
|
||||
>
|
||||
|
|
@ -168,7 +170,7 @@ export default function Sustainability() {
|
|||
<div className={styles.socialResponsibilityReportItemTitle}>
|
||||
{item.name}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -122,6 +122,11 @@ instance.interceptors.response.use(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(response.data.code === -1) {
|
||||
Toast('系统发生异常', "error");
|
||||
throw Error(response.data.message);
|
||||
}
|
||||
}
|
||||
return response.data;
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue