This commit is contained in:
zhangjianjun 2026-03-19 16:36:29 +08:00
parent 5cab4c00eb
commit 4ca125387e
26 changed files with 205 additions and 119 deletions

View File

Before

Width:  |  Height:  |  Size: 670 B

After

Width:  |  Height:  |  Size: 670 B

View File

Before

Width:  |  Height:  |  Size: 680 B

After

Width:  |  Height:  |  Size: 680 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="92" height="55" viewBox="0 0 92 55"><defs><pattern x="0" y="0" width="92" height="55" patternUnits="userSpaceOnUse" id="master_svg0_94_11479"><image x="0" y="0" width="92" height="55" xlink:href="data:image/png;base64,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"/></pattern></defs><rect x="0" y="0" width="92" height="55" rx="0" fill="url(#master_svg0_94_11479)" fill-opacity="1"/></svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -138,3 +138,7 @@ body {
} }
} }
} }
.logo-dark {
filter: brightness(0) saturate(100%) invert(16%) sepia(25%) saturate(4500%) hue-rotate(223deg) brightness(80%) contrast(85%);
}

View File

@ -17,9 +17,9 @@ function App() {
const locale = useStore((s) => s.locale); const locale = useStore((s) => s.locale);
const appConfig = useStore((s) => s.appConfig); const appConfig = useStore((s) => s.appConfig);
const initState = useCallback((data: Record<string, unknown>) => { const initState = useCallback((data: any) => {
const company = data?.company as { config?: { favicon?: string; shortName?: string } } | undefined; const company = data?.__global__.company;
const { favicon, shortName } = company?.config || {}; const { favicon, shortName } = company || {};
if (favicon) { if (favicon) {
const favor: any = document.querySelector("link[rel*='icon']") || document.createElement('link'); const favor: any = document.querySelector("link[rel*='icon']") || document.createElement('link');
favor.type = 'image/x-icon'; favor.type = 'image/x-icon';

View File

@ -94,7 +94,7 @@ export default function YearPicker({
{value != null ? `${value}` : placeholder} {value != null ? `${value}` : placeholder}
</span> </span>
<div className={styles.icons}> <div className={styles.icons}>
<img className={`${styles.arrow} ${open ? styles.arrowOpen : ""}`} src="/images/icon-arrow-down.png" alt="arrow-down" style={{ width: "24px", height: "24px" }} /> <img className={`${styles.arrow} ${open ? styles.arrowOpen : ""}`} src="/images/icons/icon-arrow-down.png" alt="arrow-down" style={{ width: "24px", height: "24px" }} />
</div> </div>
</div> </div>

View File

@ -33,8 +33,7 @@ export default function Banner({
backgroundImage, backgroundImage,
}: Props) { }: Props) {
const appConfig = useStore((s) => s.appConfig); const appConfig = useStore((s) => s.appConfig);
const header = appConfig?.header; const navItems = appConfig?.navItems ?? [];
const navItems = header?.navItems ?? [];
const location = useLocation(); const location = useLocation();
const images = Array.isArray(backgroundImage) ? backgroundImage : [backgroundImage]; const images = Array.isArray(backgroundImage) ? backgroundImage : [backgroundImage];
@ -61,7 +60,7 @@ export default function Banner({
if (child) return child.label; if (child) return child.label;
} }
const last = path.split("/").pop() ?? path; const last = path.split("/").pop() ?? path;
return last; return title;
}; };
const items = paths.map((path) => ({ const items = paths.map((path) => ({
label: getLabelByPath(path), label: getLabelByPath(path),

View File

@ -13,7 +13,7 @@ export default function Article({ data }: { data: Data }) {
<div className={styles.article}> <div className={styles.article}>
<div className={styles.articleHeaderLine}> <div className={styles.articleHeaderLine}>
<div className={styles.articleHeaderLineBack} onClick={() => window.history.back()}> <div className={styles.articleHeaderLineBack} onClick={() => window.history.back()}>
<img src="/images/icon-arrowleft.png" alt="arrowleft" style={{ width: "20px", height: "20px" }} /> <img src="/images/icons/icon-arrowleft.png" alt="arrowleft" style={{ width: "20px", height: "20px" }} />
<span></span> <span></span>
</div> </div>
</div> </div>

View File

@ -18,7 +18,7 @@ export default function JobPage({ data }: { data: Data }) {
<div className={styles.jobPage}> <div className={styles.jobPage}>
<div className={styles.jobPageHeaderLine}> <div className={styles.jobPageHeaderLine}>
<div className={styles.jobPageHeaderLineBack} onClick={() => window.history.back()}> <div className={styles.jobPageHeaderLineBack} onClick={() => window.history.back()}>
<img src="/images/icon-arrowleft.png" alt="arrowleft" style={{ width: "20px", height: "20px" }} /> <img src="/images/icons/icon-arrowleft.png" alt="arrowleft" style={{ width: "20px", height: "20px" }} />
<span></span> <span></span>
</div> </div>
</div> </div>

View File

@ -61,7 +61,7 @@ export default function RowAccordion({ data, placement='bottom' }: Props) {
useEffect(() => { useEffect(() => {
if (isInView && containerRef.current) { if (isInView && containerRef.current) {
const rect = containerRef.current.getBoundingClientRect(); const rect = containerRef.current.getBoundingClientRect();
smoothScrollTo(window.scrollY + rect.top, 1200); smoothScrollTo(window.scrollY + rect.top, 800);
} }
}, [isInView]); }, [isInView]);
@ -99,7 +99,7 @@ export default function RowAccordion({ data, placement='bottom' }: Props) {
variants={contentItemVariants} variants={contentItemVariants}
transition={{ transition={{
duration: 0.6, duration: 0.6,
delay: 0.5 + index * 0.12, delay: 0.3 + index * 0.12,
ease: [0.25, 0.46, 0.45, 0.94], ease: [0.25, 0.46, 0.45, 0.94],
}} }}
> >

View File

@ -1,10 +1,11 @@
import { useCallback, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { Swiper, SwiperSlide } from "swiper/react"; import { Swiper, SwiperSlide } from "swiper/react";
import type { Swiper as SwiperType } from "swiper"; import type { Swiper as SwiperType } from "swiper";
import "swiper/css"; import "swiper/css";
import SectionTitle from "../SectionTitle"; import SectionTitle from "../SectionTitle";
import styles from "./index.module.css"; import styles from "./index.module.css";
import { RightOutlined, LeftOutlined } from "@ant-design/icons"; import { RightOutlined, LeftOutlined } from "@ant-design/icons";
import { useLocation } from "react-router-dom";
type Data = { type Data = {
title: string; title: string;
cardItems: { cardItems: {
@ -18,15 +19,30 @@ type Data = {
} }
export default function SwiperCardSection({ data }: { data: Data }) { export default function SwiperCardSection({ data }: { data: Data }) {
const location = useLocation();
const hash = location.hash;
const id = decodeURIComponent(hash.replace('#', ''));
const [swiperRef, setSwiperRef] = useState<SwiperType | null>(null); const [swiperRef, setSwiperRef] = useState<SwiperType | null>(null);
const [activeIndex, setActiveIndex] = useState(0); const [activeIndex, setActiveIndex] = useState(0);
const onSwiperChange = useCallback((e: any) => { const onSwiperChange = useCallback((e: any) => {
setActiveIndex(e.activeIndex); setActiveIndex(e.activeIndex);
}, []) }, [])
useEffect(() => {
if (id && data.cardItems && swiperRef) {
const index = data.cardItems.findIndex((item) => item.title === id);
if (index !== -1) {
setTimeout(() => {
swiperRef.slideTo(index);
}, 500);
}
}
}, [id, data.cardItems, swiperRef])
return ( return (
<section className={styles.swiperCardSection}> <section className={styles.swiperCardSection} id={id}>
<SectionTitle title={data.title} /> <SectionTitle title={data.title} />
<div className={styles.carouselWrapper}> <div className={styles.carouselWrapper} >
{activeIndex > 0 && ( {activeIndex > 0 && (
<button <button
type="button" type="button"

View File

@ -1,6 +1,7 @@
import { useState } from 'react'; import { useEffect, useState } from 'react';
import styles from './index.module.css'; import styles from './index.module.css';
import TopTabs from './TopTabs'; import TopTabs from './TopTabs';
import { useLocation } from 'react-router-dom';
type Data = { type Data = {
tabItems: { tabItems: {
@ -19,9 +20,24 @@ type Data = {
} }
export default function TopTabsSection({ data, className }: { data: Data, className?: string }) { export default function TopTabsSection({ data, className }: { data: Data, className?: string }) {
const location = useLocation();
const hash = location.hash;
const id = decodeURIComponent(hash.replace('#', ''));
const [activeIndex, setActiveIndex] = useState(0); const [activeIndex, setActiveIndex] = useState(0);
useEffect(() => {
if (id && data.tabItems) {
setTimeout(() => {
const index = data.tabItems.findIndex((item) => item.tabName === id);
console.log('index', index, id, data.tabItems)
if (index !== -1) {
setActiveIndex(index);
}
}, 300)
}
}, [id, data.tabItems])
return ( return (
<section className={`${styles.topTabsSection} ${className}`} style={{ backgroundImage: `url(${data.backgroundImage})` }}> <section id={id} className={`${styles.topTabsSection} ${className}`} style={{ backgroundImage: `url(${data.backgroundImage})` }}>
<TopTabs data={data} activeIndex={activeIndex} setActiveIndex={setActiveIndex} /> <TopTabs data={data} activeIndex={activeIndex} setActiveIndex={setActiveIndex} />
<div className={styles.topTabsContent}> <div className={styles.topTabsContent}>
<div className={styles.topTabsContentLeft}> <div className={styles.topTabsContentLeft}>

View File

@ -6,8 +6,20 @@ const useHashScroll = () => {
useEffect(() => { useEffect(() => {
if (location.hash) { if (location.hash) {
const element = document.querySelector(location.hash); setTimeout(() => {
element?.scrollIntoView({ behavior: "smooth" }); const element = document.querySelector(decodeURIComponent(location.hash));
if (element) {
const header = document.querySelector("header");
const headerHeight = header?.getBoundingClientRect().height ?? 7.5 * 16;
const elementTop = element.getBoundingClientRect().top + window.scrollY;
const offsetPosition = elementTop - headerHeight;
window.scrollTo({
top: offsetPosition,
behavior: "smooth",
});
}
})
} else { } else {
window.scrollTo({ window.scrollTo({
top: 0, top: 0,

View File

@ -42,11 +42,13 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
gap: 1rem; gap: 40px;
position: absolute; position: absolute;
right: 3.75rem; right: 3.75rem;
} }
.footerLogo { .footerLogo {
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 600; font-weight: 600;

View File

@ -5,15 +5,17 @@ import type { NavItem, NavChild } from "@/type";
export default function Footer() { export default function Footer() {
const appConfig = useStore((s) => s.appConfig); const appConfig = useStore((s) => s.appConfig);
const footer = appConfig?.footer; const footer = appConfig?.__global__?.footer;
const header = appConfig?.header;
const footerColumns = header?.navItems?.filter((item: NavItem) => item.path !== "/") ?? []; const footerColumns = appConfig?.navItems?.filter((item: NavItem) => item.path !== "/") ?? [];
const lowerLinks = footer?.lowerLinks ?? []; const lowerLinks = footer?.lowerLinks?.items ?? [];
const socialIcons = footer?.socialIcons ?? []; const socialIcons = footer?.socialIcons?.items ?? [];
const copyright = footer?.copyright ?? "版权声明©2001-{year} | 中国银泰投资有限公司"; const copyright = footer?.copyright ?? "版权声明©2001-{year}";
const companyName = appConfig?.__global__.company?.companyName;
const icpNumber = footer?.icpNumber ?? "京ICP备05026114号-1"; const icpNumber = footer?.icpNumber ?? "京ICP备05026114号-1";
const logo = appConfig?.__global__.company?.logo;
return ( return (
<footer className={`layout-footer ${styles.footer}`}> <footer className={`layout-footer ${styles.footer}`}>
<div className={styles.footerUpper}> <div className={styles.footerUpper}>
@ -34,14 +36,21 @@ export default function Footer() {
</div> </div>
))} ))}
<div className={styles.footerRight}> <div className={styles.footerRight}>
{socialIcons.map((icon: { src: string; alt: string }) => ( {socialIcons.filter((item:any) => item.show).map((icon: any) => (
<Link to={icon.link} target="_blank" key={icon.alt}>
<img <img
key={icon.alt}
src={icon.src} src={icon.src}
alt={icon.alt} alt={icon.alt}
style={{ width: "26px", height: "26px" }} style={{ width: "26px", height: "26px" }}
/> />
</Link>
))} ))}
<img
src={logo}
alt="logo"
style={{ width: "92px", height: "56px" }}
className="logo-dark"
/>
</div> </div>
</div> </div>
</div> </div>
@ -49,6 +58,7 @@ export default function Footer() {
<FooterLower <FooterLower
lowerLinks={lowerLinks} lowerLinks={lowerLinks}
copyright={copyright.replace("{year}", String(new Date().getFullYear()))} copyright={copyright.replace("{year}", String(new Date().getFullYear()))}
companyName={companyName}
icpNumber={icpNumber} icpNumber={icpNumber}
/> />
</footer> </footer>
@ -58,17 +68,19 @@ export default function Footer() {
function FooterLower({ function FooterLower({
lowerLinks, lowerLinks,
copyright, copyright,
companyName,
icpNumber, icpNumber,
}: { }: {
lowerLinks: { path: string; label: string }[]; lowerLinks: { path: string; label: string }[];
copyright: string; copyright: string;
companyName: string;
icpNumber: string; icpNumber: string;
}) { }) {
return ( return (
<div className={styles.footerLower}> <div className={styles.footerLower}>
<div className={`header-row ${styles.footerLowerInner}`}> <div className={`header-row ${styles.footerLowerInner}`}>
<div style={{ display: "flex", flexDirection: "row", gap: "50px" }}> <div style={{ display: "flex", flexDirection: "row", gap: "50px" }}>
<span>{copyright}</span> <span>{`${copyright} | ${companyName}`}</span>
<span>{icpNumber}</span> <span>{icpNumber}</span>
</div> </div>
<div className={styles.footerLowerLinks}> <div className={styles.footerLowerLinks}>

View File

@ -14,22 +14,41 @@
font-style: normal; font-style: normal;
text-transform: none; text-transform: none;
transition: background 0.3s ease; /* transition: all 0.7s ease-in-out;
transition-delay: 0.3s; */
}
.header::before {
content: '';
display: block;
position: absolute;
top: 0;
width: 100%;
height: 0%;
background: #fff;
transition: height 0.7s ease-in-out;
}
.showDropPanel.header::before {
height: 350%;
}
.whiteMode.header::before {
height: 100%;
} }
.whiteMode.header { .whiteMode.header {
background: #fff; /* background: rgba(255, 255, 255, 1); */
box-shadow: 0 0 10px 0 rgba(255, 255, 255, 0.1); box-shadow: 0 0 10px 0 rgba(255, 255, 255, 0.1);
border-bottom: 1px solid #eee;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2); box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2);
transition-delay: none;
} }
.whiteMode .navLink { .whiteMode .navLink, .showDropPanel .navLink {
color: #222222; color: #222222;
} }
.whiteMode { .whiteMode, .showDropPanel {
.searchBtn { .searchBtn {
color: #222222; color: #222222;
} }
@ -49,6 +68,7 @@
} }
.headerInner { .headerInner {
position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
@ -58,14 +78,35 @@
.headerInner::after { .headerInner::after {
position: absolute; position: absolute;
bottom: 0; top: 100%;
left: 3.75rem; left: 0;
right: 0;
content: ''; content: '';
display: block; display: block;
width: calc(100% - 7.5rem); width: 95%;
margin: 0 auto;
box-sizing: border-box; box-sizing: border-box;
height: 1px; height: 1px;
background: #FFFFFF; background: #FFFFFF;
transform: scaleX(0);
transform-origin: 90% 0; /* 交点:左侧 85%,右侧 15%,从此点向左右展开 */
transition: transform 1.5s ease-in-out;
}
.animate {
&.headerInner::after {
transform: scaleX(1);
}
.crossYline {
height: 100%;
}
}
.whiteMode, .showDropPanel {
.headerInner::after {
transform: scaleX(0);
}
.crossYline {
height: 0%;
}
} }
.logo { .logo {
@ -121,9 +162,11 @@
.crossYline { .crossYline {
width: 1px; width: 1px;
height: 100%; height: 0%;
background: #FFFFFF; background: #FFFFFF;
transition: height 0.3s ease-in-out; align-self: flex-end; /* 交点在最底部,从此点向上展开 */
transform-origin: center bottom; /* 缩放从底部中心点展开 */
transition: height 1s ease-in-out;
} }
.langTrigger { .langTrigger {
@ -154,15 +197,16 @@
left: 0; left: 0;
width: 100%; width: 100%;
height: 0; height: 0;
background: rgba(255, 255, 255, 0.9); /* background: rgba(255, 255, 255, 0.9); */
/* box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); */ /* box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); */
padding: 0; padding: 0;
z-index: 1000; z-index: 1000;
transition: height 0.9s ease-in-out; overflow: hidden;
transition: height 0.5s ease-in-out, padding-top 0.5s ease-in-out;
&.visible { &.visible {
height: 23.75rem; height: 23.75rem;
padding: 1.25rem; padding-top: 1.25rem;
} }
} }

View File

@ -4,7 +4,7 @@ import styles from "./Header.module.css";
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { useStore } from "@/store"; import { useStore } from "@/store";
import type { NavChild, NavItem, LocaleKey, SupportLocale } from "@/type"; import type { NavChild, NavItem, LocaleKey, SupportLocale } from "@/type";
import ScrollReveal from "@/components/ScrollReveal";
const DEFAULT_NAV_ITEMS: { path: string; label: string; children?: NavChild[] }[] = []; const DEFAULT_NAV_ITEMS: { path: string; label: string; children?: NavChild[] }[] = [];
@ -15,9 +15,9 @@ export default function Header() {
const setLocale = useStore((s) => s.setLocale); const setLocale = useStore((s) => s.setLocale);
const supportLocales = useStore((s) => s.supportLocales); const supportLocales = useStore((s) => s.supportLocales);
const navItems = appConfig?.header?.navItems?.filter((item: NavItem) => item.path !== "/") ?? DEFAULT_NAV_ITEMS; const navItems = appConfig?.navItems?.filter((item: NavItem) => item.path !== "/") ?? DEFAULT_NAV_ITEMS;
const langMenuItems: SupportLocale[] = supportLocales || []; const langMenuItems: SupportLocale[] = supportLocales || [];
const logo = appConfig?.company?.logo ?? "/images/logo.png"; const logo = appConfig?.__global__.company?.logo;
const [activeNav, setActiveNav] = useState(""); const [activeNav, setActiveNav] = useState("");
const [showDropPanel, setShowDropPanel] = useState(false); const [showDropPanel, setShowDropPanel] = useState(false);
@ -57,13 +57,26 @@ export default function Header() {
return () => window.removeEventListener("scroll", handleScroll); return () => window.removeEventListener("scroll", handleScroll);
}, []); }, []);
// 动画
const [animateHeader, setAnimateHeader] = useState(false);
useEffect(() => {
setTimeout(() => {
setAnimateHeader(true)
}, 500);
}, [])
return ( return (
<header className={`${styles.header} ${(showDropPanel || showWhiteMode) ? styles.whiteMode : ""}`} <header className={`${styles.header}
${showWhiteMode && styles.whiteMode}
${showDropPanel && styles.showDropPanel}
`}
onMouseLeave={() => setShowDropPanel(false)} onMouseLeave={() => setShowDropPanel(false)}
> >
<div className={`header-row ${styles.headerInner}`}> <div className={`header-row ${styles.headerInner} ${animateHeader ? styles.animate : ""}`}>
<Link to="/" className={styles.logo}> <Link to="/" className={styles.logo}>
<img src={logo} alt="logo" style={{ width: "5.75rem", height: "3.4375rem" }} /> <img src={logo} alt="logo" style={{ width: "5.75rem", height: "3.4375rem" }}
className={(showDropPanel || showWhiteMode) ? "logo-dark" : ""}
/>
</Link> </Link>
<div className={styles.headerRight}> <div className={styles.headerRight}>
<nav> <nav>
@ -125,7 +138,6 @@ function DropPanel({ items, left, onLinkClick, show }: {
clearTimeout(timeout); clearTimeout(timeout);
} }
}, [show]); }, [show]);
if (!visible) return null;
return ( return (
<div id="drop-panel" className={`${styles.dropPanel} ${visible ? styles.visible : ""}`} style={{ paddingLeft: left}}> <div id="drop-panel" className={`${styles.dropPanel} ${visible ? styles.visible : ""}`} style={{ paddingLeft: left}}>
<div className={styles.dropPanelContent}> <div className={styles.dropPanelContent}>

View File

@ -2,8 +2,9 @@ import { Outlet, useLocation } from "react-router-dom";
import { useEffect } from "react"; import { useEffect } from "react";
import Header from "./Header"; import Header from "./Header";
import Footer from "./Footer"; import Footer from "./Footer";
import useHashScroll from "@/hooks/useHashScroll";
export default function MainLayout() { export default function MainLayout() {
useHashScroll()
const { pathname } = useLocation(); const { pathname } = useLocation();
useEffect(() => { useEffect(() => {

View File

@ -71,15 +71,13 @@ export default function AboutFounder() {
{section3Data && ( {section3Data && (
<Section title={section3Data?.title} titleColor="#fff" background={section3Data?.backgroundImage}> <Section title={section3Data?.title} titleColor="#fff" background={section3Data?.backgroundImage}>
<div className={styles.section3Content}> <div className={styles.section3Content}>
{Array.from({ {
length: Math.max(0, ...(section3Data?.columns?.map((c: { title?: string }[]) => c.length) ?? [0])), section3Data?.items.map((item: { title?: string }, index: number) => (
}).flatMap((_, rowIndex) => <div key={index} className={styles.section3Item}>
(section3Data?.columns ?? []).map((colItems: { title?: string }[], colIndex: number) => ( {item.title ? <li>{item.title}</li> : null}
<div key={`${rowIndex}-${colIndex}`} className={styles.section3Item}>
{colItems[rowIndex] ? <li>{colItems[rowIndex].title}</li> : null}
</div> </div>
)) ))
)} }
</div> </div>
</Section> </Section>
)} )}

View File

@ -29,7 +29,7 @@ export default function InvestGroup() {
{section1Data && <ParagraphSection data={section1Data} />} {section1Data && <ParagraphSection data={section1Data} />}
{section2Data && ( {section2Data && (
<section className={styles.investGroup}> <section className={styles.investGroup} id={section2Data.title}>
<SectionTitle title={section2Data.title} color="#fff" /> <SectionTitle title={section2Data.title} color="#fff" />
<div className={styles.investGroupContent}> <div className={styles.investGroupContent}>
<div className={styles.investGroupContentItems}> <div className={styles.investGroupContentItems}>
@ -45,7 +45,7 @@ export default function InvestGroup() {
)} )}
{section3Data && ( {section3Data && (
<section className={styles.equityInvestment}> <section className={styles.equityInvestment} id={section3Data.title}>
<SectionTitle title={section3Data.title} /> <SectionTitle title={section3Data.title} />
<div className={styles.equityInvestmentItems}> <div className={styles.equityInvestmentItems}>
{section3Data.items?.map((item: { logo: string; title: string }, index: number) => ( {section3Data.items?.map((item: { logo: string; title: string }, index: number) => (
@ -63,6 +63,7 @@ export default function InvestGroup() {
)} )}
{section4Data && ( {section4Data && (
<div id={section4Data.title}>
<Section <Section
background={section4Data.backgroundImage} background={section4Data.backgroundImage}
maskBackground="rgba(20,53,92,0.1)" maskBackground="rgba(20,53,92,0.1)"
@ -73,6 +74,7 @@ export default function InvestGroup() {
<div className={styles.industryFosterMaskContent}>{section4Data.content}</div> <div className={styles.industryFosterMaskContent}>{section4Data.content}</div>
</div> </div>
</Section> </Section>
</div>
)} )}
</div> </div>
); );

View File

@ -354,7 +354,7 @@
color: #666; color: #666;
margin: 0 0 0.5rem; margin: 0 0 0.5rem;
line-height: 1.6; line-height: 1.6;
height: 75px; height: 70px;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;

View File

@ -101,7 +101,7 @@ function News() {
const handleVideoPause = () => { const handleVideoPause = () => {
if (videoRef.current) { if (videoRef.current) {
videoRef.current.pause(); videoRef.current.pause();
videoRef.current.currentTime = 0; // videoRef.current.currentTime = 0;
} }
}; };
@ -160,7 +160,7 @@ function News() {
} }
<p className={styles.newsFeaturedCaption}> <p className={styles.newsFeaturedCaption}>
{localNewsData[activeNewsIndex]?.title}
</p> </p>
</div> </div>
</div> </div>
@ -172,7 +172,7 @@ function News() {
<h3 className={styles.newsItemTitle}> <h3 className={styles.newsItemTitle}>
{item.title} {item.title}
</h3> </h3>
<p className={styles.newsItemSnippet}>{item.snippet}</p> <p className={styles.newsItemSnippet} dangerouslySetInnerHTML={{ __html: item.snippet }} ></p>
<div className={styles.newsItemDateWrap}> <div className={styles.newsItemDateWrap}>
<span className={styles.newsItemDate}>{item.date}</span> <span className={styles.newsItemDate}>{item.date}</span>
<ArrowRightOutlined className={styles.newsItemArrowIcon} style={{ fontSize: '12px' }} /> <ArrowRightOutlined className={styles.newsItemArrowIcon} style={{ fontSize: '12px' }} />

View File

@ -25,7 +25,7 @@ export default function CampusDetail() {
content: item.description, content: item.description,
requirement: item.requirement, requirement: item.requirement,
lang: item.lang, lang: item.lang,
contact: item.contact, contact: item.contract_info,
} }
}); });
setJobDetail(items); setJobDetail(items);

View File

@ -47,7 +47,7 @@ export default function SiteMap() {
<div className={styles.siteMapItemChildChildren}> <div className={styles.siteMapItemChildChildren}>
{child.children?.map((childChild) => ( {child.children?.map((childChild) => (
<Link <Link
to={childChild.path} to={childChild.path.replace('{id}', childChild.label )}
className={styles.siteMapItemChildChild} className={styles.siteMapItemChildChild}
key={childChild.label} key={childChild.label}
> >

View File

@ -52,7 +52,7 @@ function resolveKeyPath(
if (page === "/") { if (page === "/") {
if (tags === "menu") return "home"; if (tags === "menu") return "home";
if (name === "全局配置") return "__global__"; if (tags === "global") return "__global__";
return null; return null;
} }
@ -123,34 +123,6 @@ function buildNavItems(
}); });
} }
const DEFAULT_FOOTER_ZH = {
lowerLinks: [
{ path: "/contact-us", label: "联系我们" },
{ path: "/site-map", label: "网站地图" },
{ path: "/terms-of-use", label: "使用条款" },
{ path: "/privacy-policy", label: "隐私保护" },
{ path: "/audit-report", label: "审计举报" },
],
socialIcons: [
{ src: "/images/icon-weixin.png", alt: "weixin" },
{ src: "/images/icon-weibo.png", alt: "weibo" },
{ src: "/images/logo.png", alt: "logo" },
],
copyright: "版权声明©2001-{year} | 中国银泰投资有限公司",
icpNumber: "京ICP备05026114号-1",
};
const DEFAULT_FOOTER_EN = {
...DEFAULT_FOOTER_ZH,
lowerLinks: [
{ path: "/contact-us", label: "Contact" },
{ path: "/site-map", label: "Site Map" },
{ path: "/terms-of-use", label: "Terms of Use" },
{ path: "/privacy-policy", label: "Privacy Policy" },
{ path: "/audit-report", label: "Audit Report" },
],
copyright: "Copyright ©2001-{year} | China Yintai Investment Co., Ltd.",
};
/** /**
* Restructure global config to match the expected layout: * Restructure global config to match the expected layout:
@ -193,6 +165,7 @@ export function parsePageConfig(items: RawPageItem[]): {
const zhNavItems = buildNavItems(menuItems, "ZH", parsedContentMap); const zhNavItems = buildNavItems(menuItems, "ZH", parsedContentMap);
const enNavItems = buildNavItems(menuItems, "EN", parsedContentMap); const enNavItems = buildNavItems(menuItems, "EN", parsedContentMap);
const zhConfig: Record<string, any> = {}; const zhConfig: Record<string, any> = {};
const enConfig: Record<string, any> = {}; const enConfig: Record<string, any> = {};
@ -220,12 +193,8 @@ export function parsePageConfig(items: RawPageItem[]): {
} }
} }
zhConfig.header = { navItems: zhNavItems }; zhConfig.navItems = zhNavItems;
zhConfig.footer = { ...DEFAULT_FOOTER_ZH }; enConfig.navItems = enNavItems;
enConfig.header = { navItems: enNavItems };
enConfig.footer = { ...DEFAULT_FOOTER_EN };
deepFallback(enConfig, zhConfig); deepFallback(enConfig, zhConfig);
return { "zh-CN": zhConfig, "en-US": enConfig }; return { "zh-CN": zhConfig, "en-US": enConfig };