diff --git a/src/App.tsx b/src/App.tsx index ed5b500..8af0149 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,7 +4,8 @@ import "./App.css" import routes from "@/Routes"; import { useCallback, useEffect, useState } from "react"; import appApi from "@/api/app"; -import { useStore, type I18nData } from "@/store"; +import { useStore } from "@/store"; +import type { I18nData, SupportLocale } from "@/type"; function AppRoutes() { return useRoutes(routes); @@ -36,7 +37,12 @@ function App() { const data = res.data as I18nData; useStore.getState().setAppConfig(data); const locale = useStore.getState().locale; - const config = data[locale] ?? data.zhCN; + const config = data[locale] ?? data["zh-CN"]; + const supportLocales: SupportLocale[] = [ + { key: "zh-CN", label: "中文" }, + { key: "en-US", label: "English" }, + ]; + useStore.getState().setSupportLocales(supportLocales); initState(config); } catch (error) { console.log(error); diff --git a/src/api/mockData.ts b/src/api/mockData.ts index b13a80a..c1f2ba5 100644 --- a/src/api/mockData.ts +++ b/src/api/mockData.ts @@ -1,14 +1,12 @@ /** * 接口返回数据结构 * mockData: { - * zhCN: {...}, - * en: {...} + * "zh-CN": {...}, + * "en-US": {...} * } */ -export type NavChild = { path: string; label: string }; -export type NavItem = { path: string; label: string; children?: NavChild[]; index?: boolean }; -export type LocaleKey = "zhCN" | "en"; +import type { NavChild, NavItem } from "@/type"; const zhCN = { companyName: "银泰", @@ -72,10 +70,6 @@ const zhCN = { ], }, ] as NavItem[], - langMenuItems: [ - { key: "zhCN", label: "中文" }, - { key: "en", label: "English" }, - ], }, footer: { @@ -309,7 +303,44 @@ const zhCN = { backgroundImage: '', sideImage: "/images/bg-invest-group.png", content: "银泰集团在社会责任方面有着丰富的经验和深厚的实力,致力于打造高品质的商业空间,引领现代消费体验。", - } + }, + section3Data: { + title: "社会职务", + backgroundImage: '/images/bg-overview.png', + columns: [ + [ // 第 1 列 + { title: '第十一、十三届全国政协委员' }, + { title: '第十三届全国政协提案委员会委员' }, + { title: '第十四、十五、十六届中国致公党中央常委' }, + { title: '第一届浙商总会执行会长' }, + ], + [ // 第 2 列 + { title: '第一届甬商总会会长' }, + { title: '北京浙江企业商会终身名誉会长' }, + { title: '西安市人民政府经济顾问' }, + { title: '中国企业家俱乐部理事' }, + ], + [ // 第 3 列 + { title: '桃花源生态保护基金会执行主席' }, + { title: '银泰公益基金会创始人兼荣誉理事长' }, + { title: '中国宋庆龄基金会第六届理事会理事' }, + { title: '爱佑慈善基金会发起理事' }, + { title: '致福慈善基金会副理事长' }, + ], + ] + }, + section4Data: { + title: "荣誉奖项", + backgroundImage: '', + items: [ + { year: '2020年', children: ["2015年度“影响·2015中国公益100人”", "2015年度“中国社会十大推动者”"]}, + { year: '2019年', children: ["2015年度“影响·2015中国公益100人”", "2015年度“中国社会十大推动者”"]}, + { year: '2018年', children: ["2015年度“影响·2015中国公益100人”", "2015年度“中国社会十大推动者”"]}, + { year: '2017年', children: ["2015年度“影响·2015中国公益100人”", "2015年度“中国社会十大推动者”"]}, + { year: '2016年', children: ["2015年度“影响·2015中国公益100人”", "2015年度“中国社会十大推动者”"]}, + { year: '2015年', children: ["2015年度“影响·2015中国公益100人”", "2015年度“中国社会十大推动者”"]}, + ], + }, }, }, @@ -975,10 +1006,6 @@ const en: typeof zhCN = { ], }, ] as NavItem[], - langMenuItems: [ - { key: "zhCN", label: "中文" }, - { key: "en", label: "English" }, - ], }, footer: { ...zhCN.footer, @@ -1002,5 +1029,5 @@ const en: typeof zhCN = { }; export type LocaleConfig = typeof zhCN; -const mockData = { zhCN, en }; +const mockData = { "zh-CN": zhCN, "en-US": en }; export default mockData; diff --git a/src/components/banner.tsx b/src/components/banner.tsx index 3d0441e..290fa84 100644 --- a/src/components/banner.tsx +++ b/src/components/banner.tsx @@ -6,19 +6,11 @@ import "swiper/css/effect-fade"; import styles from "./Banner.module.css"; import { useMemo } from "react"; import { useStore } from "@/store"; +import type { BannerConfig } from "@/type"; const FALLBACK_GRADIENT = "linear-gradient(135deg, #1a2a4a 0%, #2d4a7c 100%)"; - -export type BannerConfig = { - title?: string; - content?: string; - subtitle?: string; - largeContent?: string; - titleSize?: "large" | "medium" | string; - showBreadcrumb?: boolean; - backgroundImage?: string | string[]; -}; +export type { BannerConfig } from "@/type"; type Props = { title: string; diff --git a/src/components/layout/RowAccordion/index.module.css b/src/components/layout/RowAccordion/index.module.css index 5dad5fa..f746635 100644 --- a/src/components/layout/RowAccordion/index.module.css +++ b/src/components/layout/RowAccordion/index.module.css @@ -86,6 +86,7 @@ margin-bottom: 6px; display: flex; align-items: center; + white-space: nowrap; } .contentItemSubtitle { font-size: 20px; @@ -93,6 +94,7 @@ } .contentItemContentWrapper { + box-sizing: border-box; height: 0; opacity: 0; transition: all var(--duration) ease-in-out; @@ -100,11 +102,12 @@ display: flex; flex-direction: column; justify-content: space-between; - padding: 60px 0; + /* padding: 60px 0; */ + margin-top: 20px; } .contentItemContent { font-size: 16px; - margin-top: 40px; + /* margin-top: 40px; */ } .contentItemLinks { diff --git a/src/layouts/Footer.tsx b/src/layouts/Footer.tsx index d7f2f10..bbf9199 100644 --- a/src/layouts/Footer.tsx +++ b/src/layouts/Footer.tsx @@ -73,7 +73,7 @@ function FooterLower({
{lowerLinks.map((link, index) => ( < > - + {link.label} {index !== lowerLinks.length - 1 && ·} diff --git a/src/layouts/Header.module.css b/src/layouts/Header.module.css index 360de75..f94de8d 100644 --- a/src/layouts/Header.module.css +++ b/src/layouts/Header.module.css @@ -153,6 +153,7 @@ display: inline-flex; flex-direction: column; text-align: center; + transform: translateX(-50%); } .dropPanelLink { diff --git a/src/layouts/Header.tsx b/src/layouts/Header.tsx index 82b095a..ae9017e 100644 --- a/src/layouts/Header.tsx +++ b/src/layouts/Header.tsx @@ -3,29 +3,29 @@ import { Dropdown } from "antd"; import styles from "./Header.module.css"; import { useEffect, useMemo, useState } from "react"; import { useStore } from "@/store"; -import type { NavChild } from "@/api/mockData"; -import type { LocaleKey } from "@/store"; +import type { NavChild, LocaleKey, SupportLocale } from "@/type"; const DEFAULT_NAV_ITEMS: { path: string; label: string; children?: NavChild[] }[] = []; -const DEFAULT_LANG_ITEMS = [{ key: "zh", label: "中文" }, { key: "en", label: "English" }]; export default function Header() { const location = useLocation(); const appConfig = useStore((s) => s.appConfig); const locale = useStore((s) => s.locale); const setLocale = useStore((s) => s.setLocale); + const supportLocales = useStore((s) => s.supportLocales); const navItems = appConfig?.header?.navItems?.filter((item) => !item.index) ?? DEFAULT_NAV_ITEMS; - const langMenuItems = appConfig?.header?.langMenuItems ?? DEFAULT_LANG_ITEMS; + const langMenuItems: SupportLocale[] = supportLocales || []; const logo = appConfig?.logo ?? "/images/logo.png"; const [activeNav, setActiveNav] = useState(""); const [showDropPanel, setShowDropPanel] = useState(false); const [hoverElLeft, setHoverElLeft] = useState(0); const handleNavEnter = (e: any, path: string) => { - // left + 元素宽度的一半 const left = e.target.offsetLeft; - setHoverElLeft(left); + // 计算元素宽度 + const width = e.target.offsetWidth; + setHoverElLeft(left + width / 2); setActiveNav(path); setShowDropPanel(true); } @@ -87,9 +87,9 @@ export default function Header() { ({ + items: langMenuItems.map((item: SupportLocale) => ({ ...item, - onClick: () => setLocale(item.key as LocaleKey), + onClick: () => setLocale(item.key), })), }} placement="bottomRight" @@ -97,7 +97,7 @@ export default function Header() { > diff --git a/src/pages/About/Founder.module.css b/src/pages/About/Founder.module.css index f9a1885..51a696a 100644 --- a/src/pages/About/Founder.module.css +++ b/src/pages/About/Founder.module.css @@ -1,5 +1,5 @@ .section { - background: rgba(255,255,255,0.6); + background: rgba(255, 255, 255, 0.6); border-radius: 0; min-height: 100vh; width: 100%; @@ -26,38 +26,45 @@ aspect-ratio: 680 / 800; } } -.images img{ + +.images img { width: 100%; aspect-ratio: 680 / 800; - background: rgba(0,0,0,0.3); + background: rgba(0, 0, 0, 0.3); object-fit: cover; } .images .imageItem { position: relative; overflow: hidden; + .imageOverlay { background: transparent; } } + .images .imageItem:hover .imageOverlay { top: 0; - background: rgba(20,53,92,0.8); + background: rgba(20, 53, 92, 0.8); + .imageOverlayDesc { opacity: 1; } + .imageOverlayTitle span { border-bottom: 3px solid #FFFFFF; } } + .images .imageMask { position: absolute; bottom: 0; left: 0; width: 100%; height: 100%; - background: rgba(0,0,0,0.3); + background: rgba(0, 0, 0, 0.3); } + .images .imageOverlay { position: absolute; top: 78%; @@ -67,33 +74,38 @@ color: #fff; padding: 3.75rem 2.5rem; transition: top 0.3s ease-in-out; + .imageOverlayDesc { opacity: 0; } } + .images .imageOverlayTitle { font-size: 1.5rem; font-weight: 500; height: 4.375rem; color: #fff; text-align: center; - border-bottom: 1px solid rgba(255,255,255,0.3); + border-bottom: 1px solid rgba(255, 255, 255, 0.3); } + .images .imageOverlayTitle span { display: inline-block; height: 4.4375rem; border-bottom: 3px solid #14355C; } + .images .imageOverlayDesc { font-size: 1rem; color: #fff; margin-top: 3.125rem; } -.sectionFounder{ +.sectionFounder { padding: 6.25rem 0 9.375rem; min-height: 100vh; } + .founderIntroduction h2 { font-size: 2.5rem; font-weight: 700; @@ -101,6 +113,7 @@ margin-bottom: 3.125rem; text-align: center; } + .founderIntroduction p { font-size: 1.125rem; line-height: 1.5; @@ -109,22 +122,25 @@ letter-spacing: 0.05em; text-align: center; } -.founderPhoto{ + +.founderPhoto { display: grid; grid-template-columns: 1fr 1fr; gap: 6.25rem; margin-top: 6.25rem; } -.founderPhoto img{ +.founderPhoto img { flex: 1; width: 100%; aspect-ratio: 680 / 800; } -.founderPhotoContent{ + +.founderPhotoContent { padding-top: 6.25rem; padding-right: 6.25rem; } + .founderPhotoContent p { font-size: 1rem; line-height: 1.5; @@ -133,6 +149,28 @@ letter-spacing: 0.05em; } +.section3Content { + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-auto-rows: auto; + align-items: start; + gap: 16px; +} + +.section3Item { + width: 100%; + height: 100%; + color: #fff; + + li { + font-weight: 500; + font-size: 16px; + color: #FFFFFF; + line-height: 22px; + list-style: disc; + } +} + @media (max-width: 768px) { .section { padding: 2rem 1rem; diff --git a/src/pages/About/Founder.tsx b/src/pages/About/Founder.tsx index 91c2a9c..0bd1b3f 100644 --- a/src/pages/About/Founder.tsx +++ b/src/pages/About/Founder.tsx @@ -2,6 +2,7 @@ import Banner from "@/components/Banner"; import styles from "./Founder.module.css"; import ParagraphSection from "@/components/layout/ParagraphSection"; import { useStore } from "@/store"; +import Section from "@/components/layout/Section"; export default function AboutFounder() { const appConfig = useStore((s) => s.appConfig); @@ -10,6 +11,8 @@ export default function AboutFounder() { const banner = founder?.banner; const section1Data = founder?.section1Data; const section2Data = founder?.section2Data; + const section3Data = founder?.section3Data; + const section4Data = founder?.section4Data; if (!founder) return null; @@ -63,6 +66,26 @@ export default function AboutFounder() {
)} + + {/* 社会职务 */} +
+
+ {Array.from({ + length: Math.max(0, ...(section3Data?.columns?.map((c) => c.length) ?? [0])), + }).flatMap((_, rowIndex) => + (section3Data?.columns ?? []).map((colItems, colIndex) => ( +
+ {colItems[rowIndex] ?
  • {colItems[rowIndex].title}
  • : null} +
    + )) + )} +
    +
    + + {/* 荣誉奖项 */} +
    + +
    ); } diff --git a/src/pages/Business/CommercialGroup.tsx b/src/pages/Business/CommercialGroup.tsx index 7b362d9..8fc1353 100644 --- a/src/pages/Business/CommercialGroup.tsx +++ b/src/pages/Business/CommercialGroup.tsx @@ -60,7 +60,7 @@ export default function BusinessCommercialGroup() { ) : ( in77 杭州湖滨银泰 setIn77ImgError(true)} style={{ width: "100%", height: "800px" }} /> diff --git a/src/store/index.ts b/src/store/index.ts index 98855b7..abfa81b 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -2,27 +2,36 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; import type mockData from "@/api/mockData"; +import type { LocaleKey, SupportLocale } from "@/type"; -export type AppConfig = typeof mockData extends { zhCN: infer Z } ? Z : never; -export type I18nData = { zhCN: AppConfig; en: AppConfig }; -export type LocaleKey = "zhCN" | "en"; +export type AppConfig = typeof mockData extends { "zh-CN": infer Z } ? Z : never; +export type I18nData = { "zh-CN": AppConfig; "en-US": AppConfig }; + +/** 根据 navigator.language 映射到支持的 LocaleKey */ +function getLocaleFromNavigator(): LocaleKey { + const lang = navigator.language as LocaleKey; + return lang; +} interface StoreState { locale: LocaleKey; i18nData: I18nData | null; appConfig: AppConfig | null; + supportLocales: SupportLocale[]; token: string | null; setLocale: (locale: LocaleKey) => void; setAppConfig: (data: I18nData) => void; setToken: (token: string | null) => void; + setSupportLocales: (locales: SupportLocale[]) => void; } export const useStore = create()( persist( (set) => ({ - locale: "zhCN", + locale: 'zh-CN', i18nData: null, appConfig: null, + supportLocales: [], token: null, setLocale: (locale) => set((state) => ({ @@ -32,21 +41,28 @@ export const useStore = create()( setAppConfig: (data) => set((state) => ({ i18nData: data, - appConfig: data[state.locale] ?? null, + appConfig: data[state.locale] ?? data['en-US'] ?? data['zh-CN'] ?? null, })), setToken: (token) => set({ token }), + setSupportLocales: (locales: SupportLocale[]) => set({ supportLocales: locales }), }), { name: "yintai-store", version: 1, migrate: (persisted: unknown) => { const p = persisted as Record; - if (p?.i18nData || !p?.appConfig) return p; + const validLocales: LocaleKey[] = ["zh-CN", "en-US"]; + const locale = validLocales.includes(p?.locale as LocaleKey) + ? (p.locale as LocaleKey) + : getLocaleFromNavigator(); + if (p?.i18nData || !p?.appConfig) { + return { ...p, locale }; + } const legacy = p.appConfig as AppConfig; return { ...p, - locale: (p.locale as LocaleKey) ?? "zhCN", - i18nData: { zhCN: legacy, en: legacy }, + locale, + i18nData: { "zh-CN": legacy, "en-US": legacy }, appConfig: legacy, }; }, diff --git a/src/type/index.ts b/src/type/index.ts new file mode 100644 index 0000000..49859d2 --- /dev/null +++ b/src/type/index.ts @@ -0,0 +1,25 @@ +/** + * 项目类型定义集中管理 + */ + +// === 导航 & 路由 === +export type NavChild = { path: string; label: string }; +export type NavItem = { path: string; label: string; children?: NavChild[]; index?: boolean }; + +// === 国际化 === +export type LocaleKey = "zh-CN" | "en-US"; +export type SupportLocale = { key: LocaleKey; label: string }; + +// === 页面配置 === +export type BannerConfig = { + title?: string; + content?: string; + subtitle?: string; + largeContent?: string; + titleSize?: "large" | "medium" | string; + showBreadcrumb?: boolean; + backgroundImage?: string | string[]; +}; + +// === 从 store 导出(依赖 mockData)=== +export type { AppConfig, I18nData } from "@/store";