diff --git a/src/App.tsx b/src/App.tsx index 9a09dc1..c08dba7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -31,6 +31,28 @@ function App() { } }, []) + const getCategoryList = useCallback(async () => { + const results = await Promise.allSettled( + ['news', 'job_type', 'job_area', 'job_unit', "file"] + .map(async (type) => { + const res = await appApi.getCategoryList(type); + return res.data.items.map((item:any) => { + return { + ...item, + type + } + }) + }) + ).then((results) => { + return results.map((result:any) => { + return result.value; + }) + }) + + const categoryList = results.flat(Infinity) + useStore.getState().setCategoryList(categoryList); + }, []) + const getAppConfig = useCallback(async () => { try { const res = await appApi.getAppConfig(); @@ -44,6 +66,8 @@ function App() { ]; useStore.getState().setSupportLocales(supportLocales); initState(config); + + await getCategoryList() } catch (error) { console.log(error); } diff --git a/src/api/app.ts b/src/api/app.ts index 92d1d65..4c394bb 100644 --- a/src/api/app.ts +++ b/src/api/app.ts @@ -24,14 +24,11 @@ const app = { } }); }, - getDocList() { + getDocList(params: any) { return requests({ url: "/yt/api/doc", method: "get", - params: { - page: 1, - size: 1000, - } + params: params }); }, // 历程列表 diff --git a/src/components/layout/AnimateTopCard/index.module.css b/src/components/layout/AnimateTopCard/index.module.css index 8608cbf..84f524c 100644 --- a/src/components/layout/AnimateTopCard/index.module.css +++ b/src/components/layout/AnimateTopCard/index.module.css @@ -3,6 +3,17 @@ background-position: center; background-repeat: no-repeat; + .cardImage { + width: 100%; + height: 100%; + object-fit: cover; + position: absolute; + } + .cardVideo { + width: 100%; + height: 100%; + } + .cardMask { position: absolute; width: 100%; @@ -20,6 +31,7 @@ .cardTitle span { left: 30px; transform: translateX(0); + text-align: left; transition-delay: 0s; } } @@ -45,8 +57,6 @@ transition-delay: 0.2s; position: absolute; top: calc(100% - 170px); - - } .cardTitle { @@ -61,11 +71,20 @@ padding: 0 30px; span { + display: inline-block; transition: all 0.3s ease; transition-delay: 0.2s; position: absolute; left: 50%; transform: translateX(-50%); + width: max-content; + max-width: 100%; + box-sizing: border-box; + text-align: center; + + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } } diff --git a/src/components/layout/AnimateTopCard/index.tsx b/src/components/layout/AnimateTopCard/index.tsx index e806159..0317351 100644 --- a/src/components/layout/AnimateTopCard/index.tsx +++ b/src/components/layout/AnimateTopCard/index.tsx @@ -1,25 +1,38 @@ import styles from './index.module.css'; import { Link } from 'react-router-dom'; +import { useStore } from '@/store'; type Data = { title: string; content: string; - backgroundImage: string; + image: string; + video: string; path: string; - moreText: string; + moreText: 'moreText'; } export default function AnimateTopCard({ data }: { data: Data }) { + const appConfig = useStore((s) => s.appConfig); + const others = appConfig?.__global__?.others ?? {}; + return ( -
-
-
+
+ { + data.image ? + {data.title} : +
); } \ No newline at end of file diff --git a/src/layouts/Header.module.css b/src/layouts/Header.module.css index ef455cb..d92ec02 100644 --- a/src/layouts/Header.module.css +++ b/src/layouts/Header.module.css @@ -23,16 +23,19 @@ display: block; position: absolute; top: 0; + left: 0; width: 100%; height: 0%; background: #fff; - transition: height 0.7s ease-in-out; + transition: height 0.5s ease-in-out; } .showDropPanel.header::before { - height: 350%; + height: 100%; + transition: height 0.2s ease-in-out; } + .whiteMode.header::before { height: 100%; } @@ -44,11 +47,25 @@ transition-delay: none; } -.whiteMode .navLink, .showDropPanel .navLink { - color: #222222; +.keepNavDark.header::before { + height: 120px; } -.whiteMode, .showDropPanel { +.whiteMode .navLink, +.showDropPanel .navLink, +.keepNavDark .navLink, +.keepNavDark .langTrigger, +.keepNavDark .searchBtn, +.keepNavDark svg { + color: #222222; + /* transition: color 0.3s ease-in-out; */ +} +.keepNavDark svg path { + fill: #222222; +} + +.whiteMode, +.showDropPanel { .searchBtn { color: #222222; } @@ -89,21 +106,27 @@ height: 1px; background: #FFFFFF; transform: scaleX(0); - transform-origin: 90% 0; /* 交点:左侧 85%,右侧 15%,从此点向左右展开 */ + transform-origin: 90% 0; + /* 交点:左侧 85%,右侧 15%,从此点向左右展开 */ transition: transform 1.5s ease-in-out; } + .animate { &.headerInner::after { transform: scaleX(1); } + .crossYline { height: 100%; } } -.whiteMode, .showDropPanel { + +.whiteMode, +.showDropPanel { .headerInner::after { transform: scaleX(0); } + .crossYline { height: 0%; } @@ -116,6 +139,12 @@ line-height: 1.3; } +.commonMode { + .logo img { + transition: filter 0.5s steps(1); + } +} + .headerRight { display: flex; align-items: center; @@ -152,6 +181,7 @@ opacity: 0.9; } + .actions { display: flex; height: 100%; @@ -164,8 +194,10 @@ width: 1px; height: 0%; background: #FFFFFF; - align-self: flex-end; /* 交点在最底部,从此点向上展开 */ - transform-origin: center bottom; /* 缩放从底部中心点展开 */ + align-self: flex-end; + /* 交点在最底部,从此点向上展开 */ + transform-origin: center bottom; + /* 缩放从底部中心点展开 */ transition: height 1s ease-in-out; } @@ -197,16 +229,18 @@ left: 0; width: 100%; height: 0; - /* background: rgba(255, 255, 255, 0.9); */ - /* box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); */ + background: rgba(255, 255, 255, 0.9); + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); padding: 0; z-index: 1000; overflow: hidden; transition: height 0.5s ease-in-out, padding-top 0.5s ease-in-out; + /* transition-delay: 0.45s; */ &.visible { height: 23.75rem; padding-top: 1.25rem; + } } diff --git a/src/layouts/Header.tsx b/src/layouts/Header.tsx index a0e5945..f009598 100644 --- a/src/layouts/Header.tsx +++ b/src/layouts/Header.tsx @@ -21,6 +21,8 @@ export default function Header() { const [activeNav, setActiveNav] = useState(""); const [showDropPanel, setShowDropPanel] = useState(false); + const [keepNavDark, setKeepNavDark] = useState(false); + const leaveTimerRef = useRef | null>(null); const [hoverElLeft, setHoverElLeft] = useState(0); const handleNavEnter = (e: any, path: string) => { const left = e.target.offsetLeft; @@ -29,6 +31,11 @@ export default function Header() { setHoverElLeft(left + width / 2); setActiveNav(path); setShowDropPanel(true); + setKeepNavDark(true); + if (leaveTimerRef.current) { + clearTimeout(leaveTimerRef.current); + leaveTimerRef.current = null; + } } const activePanelItem = useMemo(() => { @@ -65,12 +72,33 @@ export default function Header() { }, 500); }, []) + const handleHeaderMouseLeave = () => { + setShowDropPanel(false); + if (leaveTimerRef.current) { + clearTimeout(leaveTimerRef.current); + } + leaveTimerRef.current = setTimeout(() => { + setKeepNavDark(false); + leaveTimerRef.current = null; + }, 500); + }; + + const handleHeaderMouseEnter = () => { + if (leaveTimerRef.current) { + clearTimeout(leaveTimerRef.current); + leaveTimerRef.current = null; + } + }; + return (
setShowDropPanel(false)} + onMouseLeave={handleHeaderMouseLeave} + onMouseEnter={handleHeaderMouseEnter} >
@@ -117,7 +145,12 @@ export default function Header() {
setShowDropPanel(false)} + onLinkClick={() => { + setShowDropPanel(false) + setTimeout(() => { + setKeepNavDark(false); + }, 500); + }} show={showDropPanel && activePanelItem.length > 0} />
diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx index 0b87e64..c48ec02 100644 --- a/src/pages/Home/index.tsx +++ b/src/pages/Home/index.tsx @@ -82,6 +82,7 @@ export default function Home() { function News() { const locale = useStore((s) => s.locale); + const categoryList = useStore((s) => s.categoryList); const [newsData, setNewsData] = useState([]); const videoRef = useRef(null); const localNewsData = useMemo(() => { @@ -122,16 +123,10 @@ function News() { setNewsData(data); }); }, []); - const getCategoryList = useCallback(async () => { - const res = await appApi.getCategoryList('news'); - const category_id = res.data.items.find((item: any) => item.name.includes('新闻资讯'))?.id; - return category_id; - }, []); useEffect(() => { - getCategoryList().then((category_id) => { - handleSearch(category_id) - }); + const category_id = categoryList?.find((item: any) => item.name === '【首页】新闻资讯')?.id; + handleSearch(category_id) }, []) return (
diff --git a/src/pages/Join/Campus.tsx b/src/pages/Join/Campus.tsx index 818895d..ae7256c 100644 --- a/src/pages/Join/Campus.tsx +++ b/src/pages/Join/Campus.tsx @@ -16,11 +16,14 @@ type JobItem = { content: string; labels: string[]; lang: string -} +}; + +type SelectOption = { label: string; value: string }; export default function JoinCampus() { const appConfig = useStore((s) => s.appConfig); const supportLocales = useStore((s) => s.supportLocales); + const categoryList = useStore((s) => s.categoryList); const locale = useStore((s) => s.locale); const data = appConfig?.join?.campus; const banner = data?.banner; @@ -29,11 +32,11 @@ export default function JoinCampus() { // 职业类别 业务领域 所属板块 const [jobType, setJobType] = useState(''); - const [jobTypeOptions, setJobTypeOptions] = useState([]); + const [jobTypeOptions, setJobTypeOptions] = useState([]); const [businessArea, setBusinessArea] = useState(''); - const [businessAreaOptions, setBusinessAreaOptions] = useState([]); + const [businessAreaOptions, setBusinessAreaOptions] = useState([]); const [businessPlate, setBusinessPlate] = useState(''); - const [businessPlateOptions, setBusinessPlateOptions] = useState([]); + const [businessPlateOptions, setBusinessPlateOptions] = useState([]); const [page, setPage] = useState(1); const [size] = useState(2 * supportLocales.length); @@ -69,20 +72,16 @@ export default function JoinCampus() { }, 500), []); const getTypes = useCallback(() => { - ['job_type', 'job_area', 'job_unit'].forEach(type => { - appApi.getCategoryList(type).then((res) => { - const items = res.data.items.map((item:any) => ({ label: item.name, value: item.id })); - items.unshift({ label: "全部", value: "" }); - if (type === 'job_type') { - setJobTypeOptions(items); - } else if (type === 'job_area') { - setBusinessAreaOptions(items); - } else if (type === 'job_unit') { - setBusinessPlateOptions(items); - } - }) - }) - }, []); + const jobTypeOptions: SelectOption[] = + categoryList?.filter((item: any) => item.type === 'job_type').map((item: any) => ({ label: item.name, value: String(item.id) })) ?? []; + const businessAreaOptions: SelectOption[] = + 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); + }, [categoryList]); useEffect(() => { refreshData(); diff --git a/src/pages/News/NewsPublic.tsx b/src/pages/News/NewsPublic.tsx index f8328c6..890ce72 100644 --- a/src/pages/News/NewsPublic.tsx +++ b/src/pages/News/NewsPublic.tsx @@ -21,19 +21,20 @@ export default function NewsPublic() { const appConfig = useStore((s) => s.appConfig); const locale = useStore((s) => s.locale); const supportLocales = useStore((s) => s.supportLocales); + const categoryList = useStore((s) => s.categoryList); const data = appConfig?.news?.public; const [page, setPage] = useState(1); const [size] = useState(9 * supportLocales.length); const [total, setTotal] = useState(0); - const categoryIdRef = useRef(''); + const categoryId = String(categoryList?.find((item: any) => item.name.includes('集团发布'))?.id ?? ''); const [newList, setNewList] = useState([]); const videoRefs = useRef<(HTMLVideoElement | null)[]>([]); const [searchValue, setSearchValue] = useState(""); const handleSearch = useCallback(() => { appApi.getNewsList({ page, size, sort: "create_time DESC", title: searchValue, - category_id: categoryIdRef.current ?? '', + category_id: categoryId, }).then((res) => { const data = res.data.items.map((item:any) => { return { @@ -54,19 +55,10 @@ export default function NewsPublic() { return newList.filter(item => item.lang.toLowerCase() === locale.split('-')[0]); }, [newList, locale]); - const getCategoryList = useCallback(async () => { - const res = await appApi.getCategoryList('news'); - const category_id = res.data.items.find((item: any) => item.name.includes('集团发布'))?.id; - categoryIdRef.current = category_id; - return category_id; - }, []); - const banner = data?.banner; useEffect(() => { - getCategoryList().then(() => { - handleSearch(); - }) + handleSearch(); }, [page, size]); if (!data) return null; diff --git a/src/pages/Social/Foundation.tsx b/src/pages/Social/Foundation.tsx index cff521a..dbb1370 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 { useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import ParagraphSection from "@/components/layout/ParagraphSection"; import Section from "@/components/layout/Section"; import AnimateTopCard from "@/components/layout/AnimateTopCard"; @@ -7,20 +7,44 @@ import BottomTabs from "@/components/layout/BottomTabsSection/BottomTabs"; import { useStore } from "@/store"; import styles from "./Foundation.module.css"; import TopTabsSection from "@/components/layout/TopTabsSection"; - +import appApi from "@/api/app"; export default function Foundation() { const appConfig = useStore((s) => s.appConfig); const data = appConfig?.social?.foundation; + const locale = useStore((s) => s.locale) + const categoryList = useStore((s) => s.categoryList) const [activeIndex, setActiveIndex] = useState(0); - if (!data) return null; - const banner = data.banner; const section1Data = data.section1Data; const section2Data = data.section2Data; const section3Data = data.section3Data; const section4Data = data.section4Data; + + const [newsList, setNewsList] = useState([]); + const localNewsList = useMemo(() => { + return newsList.filter((item: any) => item.lang.toLowerCase() === locale.split('-')[0]); + }, [newsList, locale]) + useEffect(() => { + appApi.getNewsList({ page: 1, size: 1000, sort: "create_time DESC", + 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, + image: item.covers_show === 'image' ? item.covers.image[0] : '', + video: item.covers_show === 'video' ? item.covers.video[0] : '', + lang: item.lang, + moreText: 'moreText', + } + })); + }); + }, []) + return (
- {section2Data.items?.map((item: { title: string; content?: string; backgroundImage?: string; path?: string; moreText?: string }, index: number) => ( + {localNewsList?.map((item: any, index: number) => (
- +
))}
diff --git a/src/pages/Social/Sustainability.module.css b/src/pages/Social/Sustainability.module.css index 1f973f7..ff6d43f 100644 --- a/src/pages/Social/Sustainability.module.css +++ b/src/pages/Social/Sustainability.module.css @@ -140,6 +140,7 @@ background-size: cover; background-position: center; background-repeat: no-repeat; + cursor: pointer; } .socialResponsibilityReportItemTitle { diff --git a/src/pages/Social/Sustainability.tsx b/src/pages/Social/Sustainability.tsx index 26e9d37..1d58154 100644 --- a/src/pages/Social/Sustainability.tsx +++ b/src/pages/Social/Sustainability.tsx @@ -1,18 +1,30 @@ import Banner, { type BannerConfig } from "@/components/Banner"; -import { useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import ParagraphSection from "@/components/layout/ParagraphSection"; import ColumnXGrids from "@/components/layout/ColumnXGrids"; import Section from "@/components/layout/Section"; import AnimateTopCard from "@/components/layout/AnimateTopCard"; import { useStore } from "@/store"; import styles from "./Sustainability.module.css"; +import appApi from "@/api/app"; + +type NewsItem = { + id: number; + title: string; + createTime: string; + image: string; + video: string; + lang: "ZH" | "EN" +} + +const LANG_LEN = 2 export default function Sustainability() { const appConfig = useStore((s) => s.appConfig); + const categoryList = useStore((s) => s.categoryList); + const locale = useStore((s) => s.locale); const data = appConfig?.social?.sustainability; - const [sliceIndex, setSliceIndex] = useState(4); - - if (!data) return null; + const others = appConfig?.__global__?.others ?? {}; const banner = data.banner; const section1Data = data.section1Data; @@ -22,6 +34,81 @@ export default function Sustainability() { const columnXGridsData = section2Data; + + // 社会责任案例新闻数据 + const [newsItems, setNewsItems] = useState([]); + const localNewsItems = useMemo(() => { + return newsItems.filter((item: any) => item.lang.toLowerCase() === locale.split('-')[0]); + }, [newsItems, locale]) + + const getNewsList = useCallback(() => { + appApi.getNewsList({ + page: 1, + size: 8, + sort: "create_time DESC", + category_id: String(categoryList?.find((item: any) => item.name.includes('【可持续发展】社会责任案例集'))?.id ?? ''), + }).then((res) => { + const items = res.data.items.map((item: any) => { + return { + id: item.id, + title: item.title, + content: item.content, + createTime: item.create_time, + image: item.covers_show === 'image' ? item.covers.image[0] : '', + video: item.covers_show === 'video' ? item.covers.video[0] : '', + path: `/news/detail/${item.id}`, + lang: item.lang, + moreText: 'moreText', + } + }) + setNewsItems(items as NewsItem[]) + }); + }, []) + + + // 社会责任报告 + type ReportItem = { + id: number; + name: string; + path: string; + cover: string; + } + const [reportItems, setReportItems] = useState([]); + const [page, setPage] = useState(1); + const [size, setSize] = useState(8); + const [total, setTotal] = useState(0); + const localReportItems = useMemo(() => { + return reportItems.filter((item: any) => item.lang.toLowerCase() === locale.split('-')[0]); + }, [reportItems, locale]) + const getReportList = useCallback(async () => { + const res = await appApi.getDocList({ + page, + size, + category_id: String(categoryList?.find((item: any) => item.name.includes('社会责任报告'))?.id ?? '') + }) + setTotal(res.data.total / LANG_LEN) + setReportItems((prev) => { + let items = [...prev, ...res.data.items] + const resItems: any[] = [] + // 去重 id lang 相同 + items.forEach((item: any) => { + if(!resItems.some((i: any) => i.id === item.id && i.lang === item.lang)) { + resItems.push(item) + } + }) + console.log('---resItems', resItems) + return resItems + }) + }, [page, size]) + + useEffect(() => { + getNewsList() + }, []); + + useEffect(() => { + getReportList() + }, [page]) + return (
- {section3Data.items?.map((item: any, index: number) => ( + {localNewsItems?.map((item: any, index: number) => (
@@ -67,32 +153,36 @@ export default function Sustainability() { maskBackground="#F7FBFF" >
- {section4Data.items?.slice(0, sliceIndex).map((item: any, index: number) => ( + {localReportItems?.map((item: any, index: number) => (
{ + window.open(item.path, '_blank') + }} />
- {item.title} + {item.name}
))}
- setSliceIndex( - sliceIndex < (section4Data.items?.length ?? 0) - ? sliceIndex + 4 - : 4 - ) - } + onClick={() => { + if(localReportItems.length < total) { + setPage(page + 1) + } else { + setReportItems(prev => [...prev.slice(0, 8)]) + setPage(1) + } + }} > - {sliceIndex < (section4Data.items?.length ?? 0) ? "了解更多" : "收起"} + {localReportItems.length < total ? "了解更多" : "收起"}
)} diff --git a/src/store/index.ts b/src/store/index.ts index 2c36639..91c38e5 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -15,10 +15,16 @@ interface StoreState { locale: LocaleKey; i18nData: I18nData | null; appConfig: AppConfig | null; + categoryList: { + id: number; + name: string; + type: string; + }[] | null; supportLocales: SupportLocale[]; token: string | null; setLocale: (locale: LocaleKey) => void; setAppConfig: (data: I18nData) => void; + setCategoryList: (list: any[]) => void; setToken: (token: string | null) => void; setSupportLocales: (locales: SupportLocale[]) => void; } @@ -29,6 +35,7 @@ export const useStore = create()( locale: 'zh-CN', i18nData: null, appConfig: null, + categoryList: null, supportLocales: [], token: null, setLocale: (locale) => @@ -41,6 +48,7 @@ export const useStore = create()( i18nData: data, appConfig: data[state.locale] ?? data['en-US'] ?? data['zh-CN'] ?? null, })), + setCategoryList: (list) => set({ categoryList: list }), setToken: (token) => set({ token }), setSupportLocales: (locales: SupportLocale[]) => set({ supportLocales: locales }), }), @@ -62,6 +70,7 @@ export const useStore = create()( locale, i18nData: { "zh-CN": legacy, "en-US": legacy }, appConfig: legacy, + categoryList: p.categoryList, }; }, partialize: (s) => ({ @@ -70,6 +79,7 @@ export const useStore = create()( token: s.token, appConfig: s.i18nData?.[s.locale] ?? s.appConfig, supportLocales: s.supportLocales, + categoryList: s.categoryList, }), } ) diff --git a/src/utils/index.ts b/src/utils/index.ts index b47fb09..72b8e18 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,6 @@ +import { useStore } from "zustand"; +import { LocaleKey } from "@/type"; + // debounce export const debounce = (func: (...args: any[]) => void, delay: number) => { let timeout: NodeJS.Timeout;