feat(web): 修语言下拉漂移(class 化)+ 主页登录态用户中心按钮 + 用户中心返回主页
ci-pangolin / Lint — shellcheck (pull_request) Successful in 10s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 29s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 32s
ci-pangolin / Cleartext Scan — Android 禁明文 (pull_request) Successful in 21s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 24s
ci-pangolin / Flutter — analyze + test (pull_request) Failing after 21s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 10s
ci-pangolin / DS-flow — 原型/跨端同源/代码色单源闸 (pull_request) Successful in 8s
ci-pangolin / Go — build + test (pull_request) Successful in 10s
Deploy Site / deploy-site (push) Successful in 2m33s
ci-pangolin / Go — integration (mysql/redis testcontainers) (pull_request) Failing after 4m33s
ci-pangolin / Golden — 视觉回归 (全量:components/auth/desktop/tablet) (pull_request) Successful in 19s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Failing after 13m16s

#1 官网语言下拉漂移:根因=官网 CSP 无 unsafe-inline,Header.jsx 下拉全用内联
style= 被浏览器拦→菜单飘视口右边。修:搬进 website.css 的 .langwrap/.langsel
/.langmenu/.langmenu a(全走 var(--token),right:0 锚按钮下方,min-width 160
加宽),Header.jsx 删净内联 style。

#2 主页登录态右上角「用户中心」按钮:Header.jsx 读同源 localStorage
'pg_uc_refresh',有则 .linklogin 位显「用户中心」(→ /user/)、无则「Log in」。
i18n nav.center 6 语言。

#3 用户中心「返回主页」按钮:UserCenter.tsx 顶栏加 <a href="/">(home 图标),
i18n backHome 6 语言。

顺带堵同源闸漏洞:check-l1-sync ③ 原只匹配带引号图标键('refresh-cw'),漏了
裸标识符键(home),导致新加的 home 未被校验就通过。修解析器匹配裸键;并按
ds-flow 把 home 登记进 design/prototype/icons.js。同源闸复跑绿(裸键现全受检)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-07-08 09:16:13 +08:00
parent c9e266b89a
commit f37f1f3b59
9 changed files with 41 additions and 9 deletions
+19 -8
View File
@@ -30,8 +30,18 @@ export default function Header({ lang = 'zh', t = {} }) {
const [open, setOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
const [langOpen, setLangOpen] = useState(false);
const [loggedIn, setLoggedIn] = useState(false);
const langRef = useRef(null);
// 与用户中心同源:登录后 localStorage 存 pg_uc_refresh → 显示「用户中心」入口。
useEffect(() => {
try {
setLoggedIn(!!localStorage.getItem('pg_uc_refresh'));
} catch {
setLoggedIn(false);
}
}, []);
useEffect(() => {
if (!langOpen) return;
const onDoc = (e) => { if (langRef.current && !langRef.current.contains(e.target)) setLangOpen(false); };
@@ -75,7 +85,7 @@ export default function Header({ lang = 'zh', t = {} }) {
))}
</nav>
<div class="right">
<div ref={langRef} style={{ position: 'relative', display: 'inline-block' }}>
<div ref={langRef} class="langwrap">
<button
type="button"
class="langsel"
@@ -83,21 +93,18 @@ export default function Header({ lang = 'zh', t = {} }) {
aria-haspopup="listbox"
aria-expanded={langOpen}
aria-label="Language"
style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}
>
<span>{(langs.find(([c]) => c === lang) || ['', 'English'])[1]}</span>
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" aria-hidden="true"
style={{ transform: langOpen ? 'rotate(180deg)' : 'none', transition: 'transform 140ms' }}>
<svg class="caret" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M6 9l6 6 6-6" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
{langOpen && (
<div role="listbox" style={{ position: 'absolute', top: 'calc(100% + 6px)', right: 0, minWidth: 132, background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 12, boxShadow: '0 10px 30px rgba(20, 12, 6, 0.22)', padding: 5, zIndex: 60, display: 'flex', flexDirection: 'column', gap: 1 }}>
<div class="langmenu" role="listbox">
{langs.map(([code, label]) => {
const on = code === lang;
return (
<a key={code} role="option" aria-selected={on} href={langHref(code)}
style={{ textAlign: 'left', textDecoration: 'none', background: on ? 'var(--accent-subtle, var(--bg-subtle))' : 'transparent', color: on ? 'var(--accent)' : 'var(--fg1)', fontWeight: on ? 700 : 500, fontFamily: 'var(--font-sans)', fontSize: 13, padding: '8px 11px', borderRadius: 8, whiteSpace: 'nowrap' }}>
<a key={code} role="option" aria-selected={on} href={langHref(code)}>
{label}
</a>
);
@@ -105,7 +112,11 @@ export default function Header({ lang = 'zh', t = {} }) {
</div>
)}
</div>
<a class="linklogin" href={SITE.usercenter}>{t.login}</a>
{loggedIn ? (
<a class="linklogin" href={SITE.usercenter}>{t.center}</a>
) : (
<a class="linklogin" href={SITE.usercenter}>{t.login}</a>
)}
<a class="btn btn-primary" href="#download">
<Download />
<span>{t.get}</span>