- {tabItems.map((item, i) => (
-
setActiveIndex(i)}>
- {item.tabName}
-
- ))}
+
+
0 ? 1 : 0,
+ }}
+ />
+
+ {tabItems.map((item, i) => (
+
{
+ tabItemRefs.current[i] = el;
+ }}
+ className={styles.bottomTabsSectionContentTab}
+ onClick={() => setActiveIndex(i)}
+ >
+ {item.tabName}
+
+ ))}
+
)
}
\ No newline at end of file
diff --git a/src/components/layout/BottomTabsSection/index.module.css b/src/components/layout/BottomTabsSection/index.module.css
index ce9596c..8a111cb 100644
--- a/src/components/layout/BottomTabsSection/index.module.css
+++ b/src/components/layout/BottomTabsSection/index.module.css
@@ -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;
}
\ No newline at end of file
diff --git a/src/components/layout/BottomTabsSection/index.tsx b/src/components/layout/BottomTabsSection/index.tsx
index 911ae12..09b400a 100644
--- a/src/components/layout/BottomTabsSection/index.tsx
+++ b/src/components/layout/BottomTabsSection/index.tsx
@@ -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
(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 (
-
+
+
-
{data.tabItems[activeIndex].contentTitle}
-
{data.tabItems[activeIndex].contentText}
+
+ {
+ data.tabItems[activeIndex].contentTitle ||
+ data.tabItems[activeIndex].tabName
+ }
+
+
+ {
+ data.tabItems[activeIndex].contentText
+ || (data.tabItems[activeIndex]?.content)
+ }
+
+
+
+
0 ? 1 : 0,
+ }}
+ />
+
+ {data.tabItems.map((item, i) => (
+
{
+ tabItemRefs.current[i] = el;
+ }}
+ className={styles.bottomTabsSectionContentTab}
+ onClick={() => setActiveIndex(i)}
+ >
+ {item.tabName}
+
+ ))}
+
-
)
diff --git a/src/components/layout/ColumnXGrids/index.tsx b/src/components/layout/ColumnXGrids/index.tsx
index 38064da..035cb8e 100644
--- a/src/components/layout/ColumnXGrids/index.tsx
+++ b/src/components/layout/ColumnXGrids/index.tsx
@@ -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 (
{items.map((item, index) => (
+
@@ -20,6 +22,7 @@ export default function ColumnXGrids({ items }: Props) {
{item.content}
+
))}
)
diff --git a/src/components/layout/HonorGrids/index.tsx b/src/components/layout/HonorGrids/index.tsx
index 4907469..b6d3db5 100644
--- a/src/components/layout/HonorGrids/index.tsx
+++ b/src/components/layout/HonorGrids/index.tsx
@@ -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 }) {
{data.title}
- {data.items.map((item) => (
+ {data.items.map((item, index) => (
+
+
))}
diff --git a/src/components/layout/JobPage/index.module.css b/src/components/layout/JobPage/index.module.css
index 570b187..dc0d9a6 100644
--- a/src/components/layout/JobPage/index.module.css
+++ b/src/components/layout/JobPage/index.module.css
@@ -65,5 +65,6 @@
text-align: left;
font-style: normal;
text-transform: none;
+ white-space: pre-line;
}
}
\ No newline at end of file
diff --git a/src/components/layout/StatsRow/StatsRow.tsx b/src/components/layout/StatsRow/StatsRow.tsx
index 00d0b7b..3571d83 100644
--- a/src/components/layout/StatsRow/StatsRow.tsx
+++ b/src/components/layout/StatsRow/StatsRow.tsx
@@ -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) {
- {item.num.split(' ')[0] + ' '}
+ {(() => {
+ const numberPart = item.num.split(" ")[0] ?? "";
+ const parsed = Number(numberPart.replace(/,/g, ""));
+ if (Number.isNaN(parsed)) return `${numberPart} `;
+ return (
+
+
+ );
+ })()}
+ {" "}
{item.num.split(' ')[1]}
diff --git a/src/pages/Business/CommercialGroup.tsx b/src/pages/Business/CommercialGroup.tsx
index b06268a..eab2087 100644
--- a/src/pages/Business/CommercialGroup.tsx
+++ b/src/pages/Business/CommercialGroup.tsx
@@ -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() {
-
-
-
-
-
- {section3Data.tabItems[activeFeaturesTabIndex]?.tabName}
-
-
- {section3Data.tabItems[activeFeaturesTabIndex]?.content}
-
-
-
-
0 ? 1 : 0,
- }}
- />
-
- {section3Data.tabItems.map((item: { tabName: string }, i: number) => (
-
{
- featuresTabItemRefs.current[i] = el;
- }}
- className={styles.featuresHeroTab}
- onClick={() => setActiveFeaturesTabIndex(i)}
- >
- {item.tabName}
-
- ))}
-
-
-
+
+ {section3Data &&
}
s.appConfig);
@@ -34,10 +35,12 @@ export default function InvestGroup() {
{section2Data.items?.map((item: { title: string; content: string }, index: number) => (
+
{item.title}
{item.content}
+
))}
@@ -49,6 +52,7 @@ export default function InvestGroup() {
{section3Data.items?.map((item: { logo: string; title: string }, index: number) => (
+
{item.title}
+
))}
@@ -70,10 +75,12 @@ export default function InvestGroup() {
className={styles.industryFoster}
height="100vh"
>
+
{section4Data.title}
{section4Data.content}
+
)}
diff --git a/src/pages/Business/RuijingGroup.tsx b/src/pages/Business/RuijingGroup.tsx
index c6b583b..414e06f 100644
--- a/src/pages/Business/RuijingGroup.tsx
+++ b/src/pages/Business/RuijingGroup.tsx
@@ -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)"
>
+
{section3Data.content}
+
{section3Data.statsData && (
)}
diff --git a/src/pages/Join/Campus.tsx b/src/pages/Join/Campus.tsx
index 541285f..bd0bce9 100644
--- a/src/pages/Join/Campus.tsx
+++ b/src/pages/Join/Campus.tsx
@@ -40,7 +40,7 @@ export default function JoinCampus() {
const [businessPlateOptions, setBusinessPlateOptions] = useState([]);
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() {
• {label}
))}
-
{item.content}
+
工作职责:{item.content}
))}
diff --git a/src/pages/Join/CampusDetail.tsx b/src/pages/Join/CampusDetail.tsx
index 410b14e..188288b 100644
--- a/src/pages/Join/CampusDetail.tsx
+++ b/src/pages/Join/CampusDetail.tsx
@@ -36,7 +36,7 @@ export default function CampusDetail() {
getJobDetail()
}, [])
return (
-
+
{
localJobDetail &&
diff --git a/src/pages/Join/Culture.tsx b/src/pages/Join/Culture.tsx
index 9c89366..0f48a6d 100644
--- a/src/pages/Join/Culture.tsx
+++ b/src/pages/Join/Culture.tsx
@@ -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() {
>
{section1Data.items?.map((item: any, index: number) => (
+
{item.content}
+
))}
@@ -54,6 +57,7 @@ export default function Culture() {
>
{section2Data.items?.map((item: { title: string; content?: string; icon?: string }, index: number) => (
+
{item.title}
{item.content}
+
))}
diff --git a/src/pages/News/Media.tsx b/src/pages/News/Media.tsx
index 5d0fce6..d7126f5 100644
--- a/src/pages/News/Media.tsx
+++ b/src/pages/News/Media.tsx
@@ -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() {
{items.map((item: { title: string; content?: string }, index: number) => (
-
+
{item.title}
{item.content}
-
+
))}
diff --git a/src/pages/News/NewsPublic.tsx b/src/pages/News/NewsPublic.tsx
index 890ce72..46ca379 100644
--- a/src/pages/News/NewsPublic.tsx
+++ b/src/pages/News/NewsPublic.tsx
@@ -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() {
{localNewsList.map((item, index) => (
+
{item.createTime}
+
))}
diff --git a/src/pages/Social/Foundation.module.css b/src/pages/Social/Foundation.module.css
index ffa3413..9db28b4 100644
--- a/src/pages/Social/Foundation.module.css
+++ b/src/pages/Social/Foundation.module.css
@@ -22,7 +22,8 @@
/* 信息公开 */
.informationPublicDataContent {
- min-height: 500px;
+ min-height: 450px;
+ overflow: auto;
display: flex;
flex-direction: column;
justify-content: space-between;
diff --git a/src/pages/Social/Foundation.tsx b/src/pages/Social/Foundation.tsx
index dbb1370..53f8530 100644
--- a/src/pages/Social/Foundation.tsx
+++ b/src/pages/Social/Foundation.tsx
@@ -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