From 386e0bce906b4bfcb985551a21d5896d0af2db81 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Tue, 7 Jul 2026 22:42:00 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20=E8=AF=AD=E8=A8=80=E4=B8=8B?= =?UTF-8?q?=E6=8B=89=E6=8D=A2=E8=87=AA=E5=AE=9A=E4=B9=89=E8=8F=9C=E5=8D=95?= =?UTF-8?q?(=E5=8E=9F=E7=94=9F=20select=20=E5=BC=B9=E5=B1=82=E8=A2=AB?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=AE=9A=E4=BD=8D/=E6=BC=82=E7=A7=BB)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原生 的弹层由系统定位(macOS 锚在选中项、会"漂移"), +// 改成自控菜单(按钮 + 绝对定位列表),不漂移、样式统一、跨端一致。 export function LangSeg({ lang, setLang }: { lang: Lang; setLang: (l: Lang) => void }) { - // 6 语言用原生下拉(段控横排会挤)。默认 en。 - const langs: [Lang, string][] = [ - ['en', 'English'], ['zh', '中文'], ['ja', '日本語'], - ['ko', '한국어'], ['ru', 'Русский'], ['es', 'Español'], - ]; + const [open, setOpen] = useState(false); + const ref = useRef(null); + useEffect(() => { + if (!open) return; + const onDoc = (e: MouseEvent) => { + if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); + }; + const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') setOpen(false); }; + document.addEventListener('mousedown', onDoc); + document.addEventListener('keydown', onKey); + return () => { + document.removeEventListener('mousedown', onDoc); + document.removeEventListener('keydown', onKey); + }; + }, [open]); + + const current = LANGS.find(([v]) => v === lang)?.[1] ?? String(lang); return ( - +
+ + {open && ( +
+ {LANGS.map(([v, l]) => { + const on = v === lang; + return ( + + ); + })} +
+ )} +
); } diff --git a/web/website/src/components/Header.jsx b/web/website/src/components/Header.jsx index 4123095..3848d52 100644 --- a/web/website/src/components/Header.jsx +++ b/web/website/src/components/Header.jsx @@ -3,7 +3,7 @@ * 迁自 design/ui_kits/website/index.html 的
+ site.js 的菜单/滚动毛玻璃逻辑。 * 语言切换由原型的 JS 文本替换改为「路由跳转」(zh=/, en=/en/),单显不并排(铁律 6)。 */ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { Download, Menu } from 'lucide-react'; import { SITE } from '../config/site'; @@ -29,6 +29,17 @@ function Mark() { export default function Header({ lang = 'zh', t = {} }) { const [open, setOpen] = useState(false); const [scrolled, setScrolled] = useState(false); + const [langOpen, setLangOpen] = useState(false); + const langRef = useRef(null); + + useEffect(() => { + if (!langOpen) return; + const onDoc = (e) => { if (langRef.current && !langRef.current.contains(e.target)) setLangOpen(false); }; + const onKey = (e) => { if (e.key === 'Escape') setLangOpen(false); }; + document.addEventListener('mousedown', onDoc); + document.addEventListener('keydown', onKey); + return () => { document.removeEventListener('mousedown', onDoc); document.removeEventListener('keydown', onKey); }; + }, [langOpen]); useEffect(() => { const onScroll = () => setScrolled(window.scrollY > 8); @@ -64,16 +75,36 @@ export default function Header({ lang = 'zh', t = {} }) { ))}
- +
+ + {langOpen && ( +
+ {langs.map(([code, label]) => { + const on = code === lang; + return ( + + {label} + + ); + })} +
+ )} +
{t.login}