From 22a98cd23a7f5fb8d3e86ed509b175dbf6978486 Mon Sep 17 00:00:00 2001 From: zhangjianjun Date: Tue, 24 Mar 2026 16:44:41 +0800 Subject: [PATCH] save --- package.json | 1 + src/Routes.tsx | 6 +- src/components/ScrollReveal/index.tsx | 2 +- .../layout/BottomTabsSection/BottomTabs.tsx | 60 ++++++++- .../layout/BottomTabsSection/index.module.css | 43 ++++++- .../layout/BottomTabsSection/index.tsx | 120 ++++++++++++++++-- src/components/layout/ColumnXGrids/index.tsx | 3 + src/components/layout/HonorGrids/index.tsx | 5 +- .../layout/JobPage/index.module.css | 1 + src/components/layout/StatsRow/StatsRow.tsx | 21 ++- src/pages/Business/CommercialGroup.tsx | 58 +-------- src/pages/Business/InvestGroup.tsx | 7 + src/pages/Business/RuijingGroup.tsx | 3 + src/pages/Join/Campus.tsx | 10 +- src/pages/Join/CampusDetail.tsx | 2 +- src/pages/Join/Culture.tsx | 5 + src/pages/News/Media.tsx | 5 +- src/pages/News/NewsPublic.tsx | 3 + src/pages/Social/Foundation.module.css | 3 +- src/pages/Social/Foundation.tsx | 51 ++++++-- src/pages/Social/Sustainability.tsx | 12 +- src/utils/request.ts | 5 + 22 files changed, 316 insertions(+), 110 deletions(-) diff --git a/package.json b/package.json index 3c71abf..f616e4f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/Routes.tsx b/src/Routes.tsx index 866bb6a..b22900e 100644 --- a/src/Routes.tsx +++ b/src/Routes.tsx @@ -252,11 +252,7 @@ const routes = createBrowserRouter([ { path: "join/campus/detail/:id", element: ( - - - - - + ), }, // 其它 diff --git a/src/components/ScrollReveal/index.tsx b/src/components/ScrollReveal/index.tsx index 15c3e6b..3225d94 100644 --- a/src/components/ScrollReveal/index.tsx +++ b/src/components/ScrollReveal/index.tsx @@ -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]; diff --git a/src/components/layout/BottomTabsSection/BottomTabs.tsx b/src/components/layout/BottomTabsSection/BottomTabs.tsx index c9914f9..5d297ed 100644 --- a/src/components/layout/BottomTabsSection/BottomTabs.tsx +++ b/src/components/layout/BottomTabsSection/BottomTabs.tsx @@ -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(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 ( -
- {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([]); + 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 (
{localNewsList?.map((item: any, index: number) => ( -
-
+ ))}
)} - {section4Data && ( + {section3Data && (
- {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) => (
-
  • - {item.fileName} +
  • window.open(item.path, '_blank')}> + {item.name}
  • ))} @@ -110,9 +137,11 @@ export default function Foundation() {
    {section4Data.items?.map((item: any, index: number) => ( +
    logo
    +
    ))}
    diff --git a/src/pages/Social/Sustainability.tsx b/src/pages/Social/Sustainability.tsx index 1d58154..452b0b5 100644 --- a/src/pages/Social/Sustainability.tsx +++ b/src/pages/Social/Sustainability.tsx @@ -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() {

    {localNewsItems?.map((item: any, index: number) => ( -
    + -
    + ))}
    @@ -154,7 +156,7 @@ export default function Sustainability() { >
    {localReportItems?.map((item: any, index: number) => ( -
    @@ -168,7 +170,7 @@ export default function Sustainability() {
    {item.name}
    -
    + ))}