feat: 实现后台管理的基础布局和认证钩子逻辑
This commit is contained in:
1 parent
8fb1f7da94
commit
69e01bdc32
4 files changed
+125
-1
No files matched your search
Vendored
+5
-1
@@ -12,7 +12,11 @@ declare global {
|
||||
}
|
||||
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
interface Locals {
|
||||
user?: {
|
||||
email: string;
|
||||
}
|
||||
}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
import { dev } from '$app/environment';
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
// 拦截 /admin 路由
|
||||
if (event.url.pathname.startsWith('/admin')) {
|
||||
// 读取 Cloudflare Access 提供的 Header
|
||||
const userEmail = event.request.headers.get('cf-access-authenticated-user-email');
|
||||
|
||||
// 本地开发环境通过 $app/environment 的 dev 变量来判断
|
||||
const isLocalDev = dev;
|
||||
|
||||
if (!userEmail && !isLocalDev) {
|
||||
return new Response('Unauthorized', { status: 401 });
|
||||
}
|
||||
|
||||
// 将用户信息存入 locals,供后续使用
|
||||
event.locals.user = {
|
||||
email: userEmail || 'dev@local.host'
|
||||
};
|
||||
}
|
||||
|
||||
return resolve(event);
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
|
||||
export const load: LayoutServerLoad = async ({ locals }) => {
|
||||
return {
|
||||
user: locals.user
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
let { children } = $props();
|
||||
|
||||
let scrolled = $state(false);
|
||||
|
||||
onMount(() => {
|
||||
const handleScroll = () => {
|
||||
scrolled = window.scrollY > 50;
|
||||
};
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>后台管理 - BlogFlare</title>
|
||||
</svelte:head>
|
||||
|
||||
<header class="header {scrolled ? 'scrolled' : ''}">
|
||||
<div class="container wide">
|
||||
<a href="/admin" class="brand">BlogFlare Admin</a>
|
||||
<nav>
|
||||
<a href="/admin" class={$page.url.pathname === '/admin' ? 'active' : ''}>仪表盘</a>
|
||||
<a href="/admin/posts/new" class={$page.url.pathname === '/admin/posts/new' ? 'active' : ''}>撰写</a>
|
||||
|
||||
<div class="nav-dropdown">
|
||||
<span class="nav-dropdown-trigger {($page.url.pathname.startsWith('/admin/posts') && $page.url.pathname !== '/admin/posts/new') || $page.url.pathname.startsWith('/admin/categories') || $page.url.pathname.startsWith('/admin/tags') ? 'active' : ''}">
|
||||
管理
|
||||
</span>
|
||||
<div class="dropdown-menu">
|
||||
<a href="/admin/posts">文章</a>
|
||||
<a href="/admin/categories">分类</a>
|
||||
<a href="/admin/tags">标签</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="/admin/settings" class={$page.url.pathname.startsWith('/admin/settings') ? 'active' : ''}>设置</a>
|
||||
</nav>
|
||||
<div class="user-info">
|
||||
<a href="/" target="_blank" class="site-link" title="访问前台">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>
|
||||
<span>站点首页</span>
|
||||
</a>
|
||||
<span class="user-email">{$page.data.user?.email || 'Admin'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="pjax-container" style="padding-top: 58px; min-height: calc(100vh - 120px);">
|
||||
<div class="wrapper container wide admin-container">
|
||||
{@render children()}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.admin-container {
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 4rem;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
font-size: 0.9rem;
|
||||
color: var(--header-text);
|
||||
}
|
||||
|
||||
.site-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--header-text);
|
||||
text-decoration: none;
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.85;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.site-link:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user