/** * 账号选择器(顶栏下拉):像邮箱 app 的账号切换。 * * 列表:`全部邮箱`(≥2 个可用账号时才出现——只有一个账号时它是噪声)、 * 每个账号(带未选中点/选中勾)、`管理账号…`。 * * # 为什么"全部邮箱"在只有一个可用账号时不显示 * * 聚合的语义是"合并多个账号的收件箱"。只有一个账号时它与该账号等价, * 留着只会让人以为漏了谁。少于两个可用账号时选择器直接退化成静态标题。 */ import { useEffect, useRef, useState } from 'react'; import { AGGREGATE_ID, isUsableAccount } from '../lib/accounts'; import { useAccountStore } from '../stores/accountStore'; import { useMailStore } from '../stores/mailStore'; import { useUIStore } from '../stores/uiStore'; import { ChevronRightIcon } from './icons'; export default function AccountSwitcher() { const accounts = useAccountStore(s => s.accounts); const activeId = useAccountStore(s => s.activeId); const setActive = useAccountStore(s => s.setActive); const fetchInbox = useMailStore(s => s.fetchInbox); const setViewMode = useUIStore(s => s.setViewMode); const [open, setOpen] = useState(false); const boxRef = useRef(null); const usable = accounts.filter(isUsableAccount); const active = usable.find(a => a.id === activeId) ?? null; const aggregateAvailable = usable.length >= 2; // 点外面 / 按 Esc 关下拉:不关的话它会盖住列表且没有明显出口 useEffect(() => { if (!open) return; const onDown = (e: MouseEvent) => { if (boxRef.current && !boxRef.current.contains(e.target as Node)) setOpen(false); }; const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') setOpen(false); }; document.addEventListener('mousedown', onDown); document.addEventListener('keydown', onKey); return () => { document.removeEventListener('mousedown', onDown); document.removeEventListener('keydown', onKey); }; }, [open]); // 没有账号(首次运行 / 全被删掉)时不渲染:那时界面该显示登录, // 由 LoginPage 负责,这里画一个"未命名账号"的下拉只会添乱 if (usable.length === 0) return null; const label = activeId === AGGREGATE_ID && aggregateAvailable ? '全部邮箱' : (active?.displayName ?? '全部邮箱'); const pick = async (id: string) => { setOpen(false); await setActive(id); // 切账号后**必须重取**:收件箱是按账号返回的,不重取就会看到上一个账号的信。 // 这条在聚合⇄单账号之间同样成立(聚合要并发问多个账号)。 await fetchInbox('all'); }; const title = aggregateAvailable ? label : active?.displayName; return (
{open && (
{aggregateAvailable && ( )}
{usable.map(a => ( ))}
)}
); }