diff --git a/design/prototype/icons.js b/design/prototype/icons.js index 5904581..470444a 100644 --- a/design/prototype/icons.js +++ b/design/prototype/icons.js @@ -57,6 +57,7 @@ 'compass': '', 'chart-no-axes-column': '', 'chart': '', + 'home': '', 'map-pin': '', 'laptop': '', 'monitor-smartphone': '', diff --git a/tools/check-l1-sync.mjs b/tools/check-l1-sync.mjs index 3f045d1..5a56301 100644 --- a/tools/check-l1-sync.mjs +++ b/tools/check-l1-sync.mjs @@ -73,7 +73,8 @@ function checkWebTokenSync(label, webPath) { // usercenter: export const LUCIDE: Record = { 'name': '', ... } const ucJs = read('web/usercenter/components/icons.tsx'); const ucBody = ucJs.match(/LUCIDE\s*:[^=]*=\s*\{([\s\S]*?)\n\};/)?.[1] ?? ucJs.match(/LUCIDE\s*=\s*\{([\s\S]*?)\n\};/)?.[1] ?? ''; - for (const [, id, body] of ucBody.matchAll(/'([^']+)'\s*:\s*'([^']*)'/g)) { + // 键可能带引号('refresh-cw')或裸标识符(home)—— 两种都匹配(裸键此前被漏检)。 + for (const [, id, body] of ucBody.matchAll(/['"]?([\w-]+)['"]?\s*:\s*'([^']*)'/g)) { if (!(id in protoIcons)) { problems.push(`[icons] usercenter 图标「${id}」不在原型 sprite —— 先登记 design/prototype/icons.js 再用`); } else if (protoIcons[id] !== body) { diff --git a/web/usercenter/components/UserCenter.tsx b/web/usercenter/components/UserCenter.tsx index 5e8df3a..7bda309 100644 --- a/web/usercenter/components/UserCenter.tsx +++ b/web/usercenter/components/UserCenter.tsx @@ -152,6 +152,10 @@ export default function UserCenter() { {!mobile && } {mobile &&
} + + + {!mobile && t('backHome')} + diff --git a/web/usercenter/components/icons.tsx b/web/usercenter/components/icons.tsx index 99db11d..cb6c3f2 100644 --- a/web/usercenter/components/icons.tsx +++ b/web/usercenter/components/icons.tsx @@ -25,6 +25,7 @@ export const LUCIDE: Record = { 'shopping-bag': '', 'log-out': '', 'external-link': '', + home: '', gift: '', smartphone: '', lock: '', diff --git a/web/usercenter/lib/i18n.ts b/web/usercenter/lib/i18n.ts index 6fda3a8..b7f17de 100644 --- a/web/usercenter/lib/i18n.ts +++ b/web/usercenter/lib/i18n.ts @@ -12,6 +12,7 @@ export const STRINGS: Record = { navInvite: { zh: '邀请返利', en: 'Referral', ja: '紹介', ko: '추천', ru: 'Рефералы', es: 'Referidos' }, navSettings: { zh: '设置', en: 'Settings', ja: '設定', ko: '설정', ru: 'Настройки', es: 'Ajustes' }, signOut: { zh: '退出', en: 'Sign out', ja: 'ログアウト', ko: '로그아웃', ru: 'Выйти', es: 'Cerrar sesión' }, + backHome: { zh: '返回主页', en: 'Home', ja: 'ホーム', ko: '홈', ru: 'На главную', es: 'Inicio' }, /* login */ loginTitle: { zh: '登录用户中心', en: 'Log in to your account', ja: 'アカウントにログイン', ko: '계정에 로그인', ru: 'Вход в аккаунт', es: 'Inicia sesión en tu cuenta' }, diff --git a/web/website/src/components/Header.jsx b/web/website/src/components/Header.jsx index 3848d52..cdf9dc2 100644 --- a/web/website/src/components/Header.jsx +++ b/web/website/src/components/Header.jsx @@ -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 = {} }) { ))}
-
+
{langOpen && ( -
+
{langs.map(([code, label]) => { const on = code === lang; return ( - + {label} ); @@ -105,7 +112,11 @@ export default function Header({ lang = 'zh', t = {} }) {
)}
- {t.login} + {loggedIn ? ( + {t.center} + ) : ( + {t.login} + )} {t.get} diff --git a/web/website/src/i18n/strings.ts b/web/website/src/i18n/strings.ts index 7630648..42d0909 100644 --- a/web/website/src/i18n/strings.ts +++ b/web/website/src/i18n/strings.ts @@ -38,6 +38,7 @@ export const STRINGS: Record> = { 'nav.docs': { zh: '文档', en: 'Docs', ja: 'ドキュメント', ko: '문서', ru: 'Документация', es: 'Documentación' }, 'nav.blog': { zh: 'Blog', en: 'Blog', ja: 'ブログ', ko: '블로그', ru: 'Блог', es: 'Blog' }, 'nav.login': { zh: '登录', en: 'Log in', ja: 'ログイン', ko: '로그인', ru: 'Войти', es: 'Iniciar sesión' }, + 'nav.center': { zh: '用户中心', en: 'Account', ja: 'アカウント', ko: '계정', ru: 'Личный кабинет', es: 'Mi cuenta' }, 'nav.get': { zh: '立即下载', en: 'Get the app', ja: 'アプリを入手', ko: '앱 받기', ru: 'Получить приложение', es: 'Obtener la app' }, 'hero.eyebrow': { zh: '极速 · 稳定 · 省心', en: 'Fast · Stable · Effortless', ja: '高速 · 安定 · 快適', ko: '빠름 · 안정 · 간편', ru: 'Быстро · Стабильно · Без забот', es: 'Rápido · Estable · Sin complicaciones' }, diff --git a/web/website/src/layouts/Site.astro b/web/website/src/layouts/Site.astro index 8441d93..50b0fc4 100644 --- a/web/website/src/layouts/Site.astro +++ b/web/website/src/layouts/Site.astro @@ -56,6 +56,7 @@ const headerT = { docs: t('nav.docs'), blog: t('nav.blog'), login: t('nav.login'), + center: t('nav.center'), get: t('nav.get'), suBtn: t('su.btn'), }; diff --git a/web/website/src/styles/website.css b/web/website/src/styles/website.css index 2e7ce30..2d821b7 100644 --- a/web/website/src/styles/website.css +++ b/web/website/src/styles/website.css @@ -42,6 +42,17 @@ img,svg{display:block} .linklogin{font-size:14.5px;font-weight:600;color:var(--fg1);cursor:pointer} .linklogin:hover{color:var(--accent)} +/* ---------- language dropdown ---------- */ +.langwrap{position:relative;display:inline-block} +.langsel{display:inline-flex;align-items:center;gap:6px;border:1.5px solid var(--border-strong);border-radius:var(--radius-full);padding:8px 14px;background:var(--surface);color:var(--fg1);font-family:var(--font-sans);font-size:14px;font-weight:600;cursor:pointer;transition:border-color var(--dur-fast) var(--ease-out),color var(--dur-fast) var(--ease-out)} +.langsel:hover{border-color:var(--accent);color:var(--accent)} +.langsel .caret{width:11px;height:11px;transition:transform 140ms var(--ease-out)} +.langsel[aria-expanded="true"] .caret{transform:rotate(180deg)} +.langmenu{position:absolute;top:calc(100% + 6px);right:0;min-width:160px;display:flex;flex-direction:column;gap:1px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:var(--shadow-lg);padding:5px;z-index:60} +.langmenu a{text-align:left;text-decoration:none;font-family:var(--font-sans);font-size:13px;font-weight:500;color:var(--fg1);background:transparent;padding:8px 11px;border-radius:var(--radius-sm);white-space:nowrap;transition:background var(--dur-fast) var(--ease-out)} +.langmenu a:hover{background:var(--bg-subtle)} +.langmenu a[aria-selected="true"]{color:var(--accent);background:var(--accent-subtle);font-weight:700} + /* mobile nav */ .menu-btn{display:none;border:none;background:transparent;cursor:pointer;padding:7px;border-radius:var(--radius-sm);color:var(--fg1)} .menu-btn svg{width:22px;height:22px}