feat(team-admin): TeamAdminLayout 加消息中心铃铛 + 主题切换 — 跟超管侧栏对齐
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m8s
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:
parent
dccb4cb5e1
commit
ab790fbe65
@ -1,6 +1,8 @@
|
|||||||
import { NavLink, Outlet, useNavigate } from 'react-router-dom';
|
import { NavLink, Outlet, useNavigate } from 'react-router-dom';
|
||||||
import { useAuthStore } from '../store/auth';
|
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 logoImg from '../assets/logo_32.png';
|
||||||
import styles from './AdminLayout.module.css';
|
import styles from './AdminLayout.module.css';
|
||||||
|
|
||||||
@ -14,9 +16,23 @@ const navItems = [
|
|||||||
export function TeamAdminLayout() {
|
export function TeamAdminLayout() {
|
||||||
const user = useAuthStore((s) => s.user);
|
const user = useAuthStore((s) => s.user);
|
||||||
const logout = useAuthStore((s) => s.logout);
|
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 navigate = useNavigate();
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
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 = () => {
|
const handleLogout = () => {
|
||||||
logout();
|
logout();
|
||||||
navigate('/login', { replace: true });
|
navigate('/login', { replace: true });
|
||||||
@ -80,6 +96,50 @@ export function TeamAdminLayout() {
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div className={styles.sidebarFooter}>
|
<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.userInfo}>
|
||||||
<div className={styles.userAvatar}>{user?.username.charAt(0).toUpperCase()}</div>
|
<div className={styles.userAvatar}>{user?.username.charAt(0).toUpperCase()}</div>
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user