diff --git a/.gitignore b/.gitignore index a547bf3..b8c4685 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ dist dist-ssr *.local +# 设计参考稿 / 切片源文件,正式素材已整理进 public/assets +images/ + # Editor directories and files .vscode/* !.vscode/extensions.json diff --git a/public/assets/img/intl/README.md b/public/assets/img/intl/README.md new file mode 100644 index 0000000..4d7beb0 --- /dev/null +++ b/public/assets/img/intl/README.md @@ -0,0 +1,18 @@ +# 国际站切片素材(来自 images/*_slices.zip) + +| 文件 | 来源 | 用途 | +|------|------|------| +| tab-report.png | 首页1 The_Rating... | 首页 Tab1 图标 | +| tab-ai.png | 首页1 Frame | 首页 Tab2 图标 | +| tab-method.png | 首页1 STAR_Multi... | 首页 Tab3 图标 | +| report-cover.png | 首页1 占位图 | 报告封面 / Research 卡头 | +| feature-ai.png | 首页2 背景2_ | AI Tab 右侧插画 | +| feature-method.png | 首页3 背景3 | Methodology Tab 右侧插画 | +| bg-coop-light.png | 首页1 背景1 | Cooperation 页浅色区背景 | +| bg-coop-dark.png | cooperation-悬浮 背景6 | 首页 Cooperation 深色区底纹 | +| bg-mission.png | people 背景4 | People Mission & Vision 左图 | +| icon-join/meeting/partner/contact.png | 首页1 加入/会议/合作/联系 | Cooperation 卡片图标 | +| icon-improve/training.png | cooperation-悬浮 改进/发展 | Cooperation 额外图标 | +| icon-more.png | 首页1 ic_右-后 | more 箭头 | + +Hero「Guidelines Worth Applying」使用原站图:`/assets/img/carousel/jishitang.png` diff --git a/public/assets/img/intl/bg-coop-dark.png b/public/assets/img/intl/bg-coop-dark.png new file mode 100644 index 0000000..be77f87 Binary files /dev/null and b/public/assets/img/intl/bg-coop-dark.png differ diff --git a/public/assets/img/intl/bg-coop-light.png b/public/assets/img/intl/bg-coop-light.png new file mode 100644 index 0000000..3f994f5 Binary files /dev/null and b/public/assets/img/intl/bg-coop-light.png differ diff --git a/public/assets/img/intl/bg-mission.png b/public/assets/img/intl/bg-mission.png new file mode 100644 index 0000000..b0ea6f0 Binary files /dev/null and b/public/assets/img/intl/bg-mission.png differ diff --git a/public/assets/img/intl/feature-ai.png b/public/assets/img/intl/feature-ai.png new file mode 100644 index 0000000..3d8cc34 Binary files /dev/null and b/public/assets/img/intl/feature-ai.png differ diff --git a/public/assets/img/intl/feature-method.png b/public/assets/img/intl/feature-method.png new file mode 100644 index 0000000..f6363fb Binary files /dev/null and b/public/assets/img/intl/feature-method.png differ diff --git a/public/assets/img/intl/icon-contact.png b/public/assets/img/intl/icon-contact.png new file mode 100644 index 0000000..8c14bee Binary files /dev/null and b/public/assets/img/intl/icon-contact.png differ diff --git a/public/assets/img/intl/icon-improve.png b/public/assets/img/intl/icon-improve.png new file mode 100644 index 0000000..f239b4e Binary files /dev/null and b/public/assets/img/intl/icon-improve.png differ diff --git a/public/assets/img/intl/icon-join.png b/public/assets/img/intl/icon-join.png new file mode 100644 index 0000000..eefe919 Binary files /dev/null and b/public/assets/img/intl/icon-join.png differ diff --git a/public/assets/img/intl/icon-meeting.png b/public/assets/img/intl/icon-meeting.png new file mode 100644 index 0000000..d799822 Binary files /dev/null and b/public/assets/img/intl/icon-meeting.png differ diff --git a/public/assets/img/intl/icon-more.png b/public/assets/img/intl/icon-more.png new file mode 100644 index 0000000..b2d2c7c Binary files /dev/null and b/public/assets/img/intl/icon-more.png differ diff --git a/public/assets/img/intl/icon-partner.png b/public/assets/img/intl/icon-partner.png new file mode 100644 index 0000000..f664567 Binary files /dev/null and b/public/assets/img/intl/icon-partner.png differ diff --git a/public/assets/img/intl/icon-training.png b/public/assets/img/intl/icon-training.png new file mode 100644 index 0000000..eaf3b8b Binary files /dev/null and b/public/assets/img/intl/icon-training.png differ diff --git a/public/assets/img/intl/report-cover.png b/public/assets/img/intl/report-cover.png new file mode 100644 index 0000000..929c986 Binary files /dev/null and b/public/assets/img/intl/report-cover.png differ diff --git a/public/assets/img/intl/tab-ai.png b/public/assets/img/intl/tab-ai.png new file mode 100644 index 0000000..627fc05 Binary files /dev/null and b/public/assets/img/intl/tab-ai.png differ diff --git a/public/assets/img/intl/tab-method.png b/public/assets/img/intl/tab-method.png new file mode 100644 index 0000000..741bb04 Binary files /dev/null and b/public/assets/img/intl/tab-method.png differ diff --git a/public/assets/img/intl/tab-report.png b/public/assets/img/intl/tab-report.png new file mode 100644 index 0000000..293ce6e Binary files /dev/null and b/public/assets/img/intl/tab-report.png differ diff --git a/public/assets/img/logo-en-white.png b/public/assets/img/logo-en-white.png new file mode 100644 index 0000000..d689014 Binary files /dev/null and b/public/assets/img/logo-en-white.png differ diff --git a/public/assets/img/logo-star-mark-white.png b/public/assets/img/logo-star-mark-white.png new file mode 100644 index 0000000..06130fc Binary files /dev/null and b/public/assets/img/logo-star-mark-white.png differ diff --git a/public/assets/img/logo-star-mark.png b/public/assets/img/logo-star-mark.png new file mode 100644 index 0000000..f2a56d2 Binary files /dev/null and b/public/assets/img/logo-star-mark.png differ diff --git a/src/components/site/CoopCardIcon.jsx b/src/components/site/CoopCardIcon.jsx new file mode 100644 index 0000000..ebe09ea --- /dev/null +++ b/src/components/site/CoopCardIcon.jsx @@ -0,0 +1,23 @@ +import { cn } from "@/lib/utils"; + +/** + * Cooperation 卡片图标:用 mask 上色,随父级 currentColor 变化(含 hover)。 + */ +export default function CoopCardIcon({ src, className }) { + return ( + + ); +} diff --git a/src/components/site/PlaceholderMedia.jsx b/src/components/site/PlaceholderMedia.jsx new file mode 100644 index 0000000..ef1a045 --- /dev/null +++ b/src/components/site/PlaceholderMedia.jsx @@ -0,0 +1,62 @@ +import { cn } from "@/lib/utils"; + +/** + * 通用占位图:用渐变色块代替正式摄影/插画素材。 + */ +export function PlaceholderMedia({ + label = "Placeholder", + className, + tone = "blue", +}) { + const tones = { + blue: "from-[#2b4a9a] via-[#4a6fd0] to-[#9bb6f0]", + soft: "from-[#c5d6f5] via-[#e8f1ff] to-[#f7faff]", + warm: "from-[#1a2748] via-[#3d4f7a] to-[#c9a06a]", + teal: "from-[#1e4a6e] via-[#3a7ca5] to-[#8ec5e0]", + paper: "from-[#3a61c2] via-[#5b7fd4] to-[#d6e2ff]", + }; + + return ( +
+
+ );
}
export default function PublicLayout() {
@@ -68,34 +86,9 @@ export default function PublicLayout() {
const dispatch = useDispatch();
const memberToken = useSelector((s) => s.member?.token);
const memberProfile = useSelector((s) => s.member?.profile);
- const [keyword, setKeyword] = useState("");
- const [openMenu, setOpenMenu] = useState(null);
const [mobileOpen, setMobileOpen] = useState(false);
- const [columns, setColumns] = useState(null);
- const navRef = useRef(null);
- const itemRefs = useRef(new Map());
- const [indicator, setIndicator] = useState({
- left: 0,
- width: 0,
- visible: false,
- });
- const [indicatorReady, setIndicatorReady] = useState(false);
-
- useEffect(() => {
- let alive = true;
- contentApi
- .listColumns({ limit: 200 })
- .then((res) => {
- if (!alive) return;
- setColumns(res.results || []);
- })
- .catch(() => {
- if (alive) setColumns([]);
- });
- return () => {
- alive = false;
- };
- }, []);
+ const [openMenu, setOpenMenu] = useState(null);
+ const [settingsOpen, setSettingsOpen] = useState(false);
useEffect(() => {
if (memberToken && !memberProfile) {
@@ -103,249 +96,57 @@ export default function PublicLayout() {
}
}, [memberToken, memberProfile, dispatch]);
- const topNav = useMemo(() => {
- if (!columns) return fallbackTopNav(t);
- const tree = buildColumnNavTree(columns, 1);
- return tree.length ? tree : fallbackTopNav(t);
- }, [columns, t]);
-
- const footerNav = useMemo(() => {
- if (!columns) return fallbackFooterNav(t);
- const tree = buildColumnNavTree(columns, 3);
- return tree.length ? tree : fallbackFooterNav(t);
- }, [columns, t]);
-
- const hotWords = useMemo(
- () =>
- i18n.language === "zh"
- ? ["影像", "指南", "共识"]
- : ["imaging", "guideline", "consensus"],
- [i18n.language],
- );
-
- const onSearch = () => {
- const q = keyword.trim();
- if (q) navigate(`/search?q=${encodeURIComponent(q)}`);
- };
-
- const isItemActive = (item) => {
- if (
- item.children?.some((c) =>
- navActive(location.pathname, location.search, c.to),
- )
- ) {
- return true;
- }
- return navActive(location.pathname, location.search, item.to);
- };
-
- const activeNavId = useMemo(() => {
- const active = topNav.find((item) => isItemActive(item));
- return active?.id ?? null;
- }, [topNav, location.pathname, location.search]);
-
- const setItemRef = (id) => (el) => {
- if (el) itemRefs.current.set(id, el);
- else itemRefs.current.delete(id);
- };
-
- const updateIndicator = useCallback(() => {
- const navEl = navRef.current;
- if (!navEl || activeNavId == null) {
- setIndicator((prev) => ({ ...prev, width: 0, visible: false }));
- return;
- }
- const el = itemRefs.current.get(activeNavId);
- if (!el) {
- setIndicator((prev) => ({ ...prev, width: 0, visible: false }));
- return;
- }
- const navRect = navEl.getBoundingClientRect();
- const rect = el.getBoundingClientRect();
- const inset = 10;
- setIndicator({
- left: rect.left - navRect.left + inset,
- width: Math.max(0, rect.width - inset * 2),
- visible: true,
- });
- setIndicatorReady(true);
- }, [activeNavId]);
-
- useLayoutEffect(() => {
- updateIndicator();
- }, [updateIndicator, topNav, i18n.language]);
-
useEffect(() => {
- const onResize = () => updateIndicator();
- window.addEventListener("resize", onResize);
- return () => window.removeEventListener("resize", onResize);
- }, [updateIndicator]);
+ setMobileOpen(false);
+ setOpenMenu(null);
+ setSettingsOpen(false);
+ }, [location.pathname, location.search]);
- // 桌面主导航(指示条由共享元素滑动,不在单项上画)
- const linkClass = (active) =>
- cn(
- "inline-flex h-10 items-center px-5 text-base no-underline transition-colors",
- "text-(--star-primary) hover:bg-(--star-nav-hover)",
- active ? "font-bold" : "font-semibold",
- );
-
- // 移动端一级项:左侧指示条
- const mobileLinkClass = (active) =>
- cn(
- "relative flex h-10 items-center px-5 text-base no-underline transition-colors",
- "text-(--star-primary)",
- active
- ? "bg-(--star-nav-hover) font-bold before:absolute before:top-2 before:bottom-2 before:left-0 before:w-0.5 before:rounded-full before:bg-(--star-nav-active-bar)"
- : "font-semibold hover:bg-(--star-nav-hover)",
- );
-
- const dropdownItemClass = (active) =>
- cn(
- "relative block px-5 py-2.5 text-base no-underline transition-colors",
- active
- ? "bg-(--star-nav-hover) font-bold text-(--star-primary) before:absolute before:top-1.5 before:bottom-1.5 before:left-0 before:w-0.5 before:rounded-full before:bg-(--star-nav-active-bar)"
- : "font-medium text-[#333] hover:bg-(--star-nav-dropdown-hover) hover:text-(--star-primary)",
- );
+ const topNav = useMemo(() => INTL_TOP_NAV, []);
return (