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 Error {}
|
||||||
// interface Locals {}
|
interface Locals {
|
||||||
|
user?: {
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
// interface PageData {}
|
// interface PageData {}
|
||||||
// interface PageState {}
|
// 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