58 lines
1.6 KiB
TypeScript
58 lines
1.6 KiB
TypeScript
'use client';
|
|
|
|
import { useRouter } from 'next/navigation';
|
|
import { useEffect, useState } from 'react';
|
|
import { useAdminStore } from '@/store/adminStore';
|
|
import { tokenStorage } from '@/lib/api';
|
|
import { Loader2 } from 'lucide-react';
|
|
|
|
interface AdminHeaderProps {
|
|
title: string;
|
|
description?: string;
|
|
actions?: React.ReactNode;
|
|
}
|
|
|
|
export function AdminHeader({ title, description, actions }: AdminHeaderProps) {
|
|
const router = useRouter();
|
|
const token = useAdminStore((s) => s.token);
|
|
const [checking, setChecking] = useState(true);
|
|
|
|
useEffect(() => {
|
|
// 客户端兜底:如果 store 中没 token 但 localStorage 有,刷新一下
|
|
const lsToken = tokenStorage.get();
|
|
if (!token && lsToken) {
|
|
useAdminStore.setState({
|
|
token: lsToken,
|
|
admin: useAdminStore.getState().admin,
|
|
});
|
|
}
|
|
setChecking(false);
|
|
}, [token]);
|
|
|
|
useEffect(() => {
|
|
if (!checking && !token) {
|
|
router.replace('/admin/login');
|
|
}
|
|
}, [checking, token, router]);
|
|
|
|
if (checking || !token) {
|
|
return (
|
|
<div className="flex h-40 items-center justify-center text-gray-400">
|
|
<Loader2 className="mr-2 h-5 w-5 animate-spin" /> 正在校验登录状态…
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<header className="mb-6 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
<div>
|
|
<h1 className="text-xl font-semibold text-gray-900">{title}</h1>
|
|
{description && (
|
|
<p className="mt-1 text-sm text-gray-500">{description}</p>
|
|
)}
|
|
</div>
|
|
{actions && <div className="flex items-center gap-2">{actions}</div>}
|
|
</header>
|
|
);
|
|
}
|