feat(frontend+backend): 实现SSR登录态优化与字体自托管
1. 后端调整refresh cookie路径与注释,完善认证cookie处理逻辑 2. 前端全局layout新增next/font自托管字体,消除FOUT闪动 3. 实现SSR直出登录态与未读通知数,避免页面加载闪动 4. 新增中间件处理access token静默轮转,保障SSR登录态 5. 优化登录、改密流程,添加页面刷新同步登录态 6. 重构通知铃铛组件支持初始未读数传入
This commit is contained in:
@@ -17,19 +17,33 @@ import {
|
||||
Bell,
|
||||
Home,
|
||||
} from "lucide-react";
|
||||
import { apiMe, apiLogout, type User } from "@/lib/api";
|
||||
import { apiLogout, type User } from "@/lib/api";
|
||||
import NotificationBell from "./NotificationBell";
|
||||
import Avatar from "./Avatar";
|
||||
|
||||
export default function Header() {
|
||||
export default function Header({
|
||||
initialUser,
|
||||
initialUnread = 0,
|
||||
initialTheme = "light",
|
||||
}: {
|
||||
initialUser: User | null;
|
||||
initialUnread?: number;
|
||||
initialTheme?: "light" | "dark";
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [theme, setTheme] = useState<"light" | "dark">("light");
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [theme, setTheme] = useState<"light" | "dark">(initialTheme);
|
||||
// 登录态以 SSR 直出为初始值,F5 无切换闪动;router.refresh() 后随 props 校正
|
||||
const [user, setUser] = useState<User | null>(initialUser);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// 登录/登出触发 router.refresh() 后,服务端 layout 重新取态并下发新 props
|
||||
useEffect(() => {
|
||||
setUser(initialUser);
|
||||
}, [initialUser]);
|
||||
|
||||
const handleSearch = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const q = searchQuery.trim();
|
||||
@@ -45,9 +59,6 @@ export default function Header() {
|
||||
document.documentElement.classList.toggle("dark", savedTheme === "dark");
|
||||
setTheme(savedTheme);
|
||||
setSearchQuery(new URLSearchParams(window.location.search).get("q")?.trim() || "");
|
||||
apiMe()
|
||||
.then((res) => setUser(res.user || null))
|
||||
.catch(() => setUser(null));
|
||||
}, [pathname]);
|
||||
|
||||
// "/" 快速聚焦搜索
|
||||
@@ -173,7 +184,7 @@ export default function Header() {
|
||||
<button onClick={toggleTheme} className={iconBtn} style={iconBtnStyle} aria-label="切换主题">
|
||||
{theme === "dark" ? <Sun size={17} /> : <Moon size={17} />}
|
||||
</button>
|
||||
{user && <NotificationBell />}
|
||||
{user && <NotificationBell initialUnread={initialUnread} />}
|
||||
{user ? (
|
||||
<>
|
||||
<Link href="/compose" className="btn btn-primary btn-sm ml-1">
|
||||
@@ -210,7 +221,7 @@ export default function Header() {
|
||||
|
||||
{/* 移动端按钮 */}
|
||||
<div className="flex items-center gap-2 lg:hidden">
|
||||
{user && <NotificationBell />}
|
||||
{user && <NotificationBell initialUnread={initialUnread} />}
|
||||
<button
|
||||
onClick={() => setMenuOpen(!menuOpen)}
|
||||
className="w-10 h-10 flex items-center justify-center rounded-full"
|
||||
|
||||
Reference in New Issue
Block a user