feat(team-admin): TeamAdminLayout 加消息中心铃铛 + 主题切换 — 跟超管侧栏对齐
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m8s

之前团管侧栏 footer 只有头像 + 退出,缺这两个常用按钮。
观察者团管访问 /admin/assets 走 AdminLayout 是有这俩的,
团管在 /team/* 反而没有,体验不一致。

照搬 AdminLayout 同款实现:
- 消息中心铃铛:60s 轮询 + visibilitychange 立即拉,有未读右上角红点
- 主题切换:深色/浅色切换,月亮/太阳 SVG

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
seaislee1209 2026-05-18 18:22:24 +08:00
parent dccb4cb5e1
commit ab790fbe65

View File

@ -1,6 +1,8 @@
import { NavLink, Outlet, useNavigate } from 'react-router-dom';
import { useAuthStore } from '../store/auth';
import { useState } from 'react';
import { useThemeStore } from '../store/theme';
import { useNotificationStore } from '../store/notification';
import { useState, useEffect } from 'react';
import logoImg from '../assets/logo_32.png';
import styles from './AdminLayout.module.css';
@ -14,9 +16,23 @@ const navItems = [
export function TeamAdminLayout() {
const user = useAuthStore((s) => s.user);
const logout = useAuthStore((s) => s.logout);
const theme = useThemeStore((s) => s.theme);
const toggleTheme = useThemeStore((s) => s.toggleTheme);
const unreadCount = useNotificationStore((s) => s.unreadCount);
const fetchUnreadCount = useNotificationStore((s) => s.fetchUnreadCount);
const navigate = useNavigate();
const [collapsed, setCollapsed] = useState(false);
// 60s 轮询未读数 + tab 重新可见时立即拉一次(和 AdminLayout 一致)
useEffect(() => {
if (!user) return;
fetchUnreadCount();
const tick = setInterval(fetchUnreadCount, 60_000);
const onVis = () => { if (!document.hidden) fetchUnreadCount(); };
document.addEventListener('visibilitychange', onVis);
return () => { clearInterval(tick); document.removeEventListener('visibilitychange', onVis); };
}, [user, fetchUnreadCount]);
const handleLogout = () => {
logout();
navigate('/login', { replace: true });
@ -80,6 +96,50 @@ export function TeamAdminLayout() {
</nav>
<div className={styles.sidebarFooter}>
{/* 消息中心铃铛 — 有未读时右上角红点 */}
<button
className={styles.themeToggle}
onClick={() => navigate('/notifications')}
title={unreadCount > 0 ? `${unreadCount} 条未读消息` : '消息中心'}
aria-label="消息中心"
style={{ position: 'relative' }}
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
<path d="M13.73 21a2 2 0 0 1-3.46 0" />
</svg>
{!collapsed && <span>{unreadCount > 0 ? ` (${unreadCount})` : ''}</span>}
{unreadCount > 0 && (
<span style={{
position: 'absolute',
top: 6, left: collapsed ? 22 : 22,
width: 8, height: 8, borderRadius: '50%',
background: 'var(--color-danger)',
boxShadow: '0 0 0 2px var(--color-bg-sidebar)',
}} />
)}
</button>
{/* 主题切换 — 月亮/太阳 SVG */}
<button
className={styles.themeToggle}
onClick={toggleTheme}
title={theme === 'dark' ? '切换到浅色主题' : '切换到深色主题'}
aria-label={theme === 'dark' ? '切换到浅色主题' : '切换到深色主题'}
>
{theme === 'dark' ? (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
</svg>
) : (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" />
</svg>
)}
{!collapsed && <span>{theme === 'dark' ? '浅色' : '深色'}</span>}
</button>
<div className={styles.userInfo}>
<div className={styles.userAvatar}>{user?.username.charAt(0).toUpperCase()}</div>
{!collapsed && (