You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用goto实现登录页首屏展示及Supabase身份验证拦截

未登录限制访问首页的两种实现方案(Supabase + SvelteKit)

你的问题核心是没有对首页添加登录验证逻辑,所以未登录用户可以直接访问。下面是两种可行的实现方案:


方案一:服务端全局拦截(推荐,更安全)

通过hooks.server.js在服务端统一验证用户登录状态,未登录时直接重定向到登录页,避免客户端验证被绕过。

步骤:

  1. 在src目录下创建hooks.server.js文件
  2. 编写如下验证逻辑:
// src/hooks.server.js
import { redirect } from '@sveltejs/kit';
import supabase from '$routes/supa'; // 对应你的supabase实例路径

export async function handle({ event, resolve }) {
  // 获取当前用户会话
  const { data: { session } } = await supabase.auth.getSession();

  // 定义无需登录即可访问的页面
  const publicPaths = ['/login', '/signup'];
  
  // 未登录且访问非公开页面时,重定向到登录页
  if (!session && !publicPaths.includes(event.url.pathname)) {
    throw redirect(303, '/login');
  }

  // 已登录则正常处理请求
  const response = await resolve(event);
  return response;
}

注意事项:

  • 确保supabase实例的导入路径正确,和你登录/注册页面中的路径一致
  • 303状态码用于规范重定向逻辑,避免POST请求的重复提交问题
  • 你的登录/注册页面已经通过goto("/")跳转首页,登录成功后会自动进入首页

方案二:客户端弹窗限制(适合展示首页内容但禁止操作)

如果希望用户能看到首页内容但无法操作,可在首页添加登录弹窗,未登录时覆盖页面并禁用交互元素。

修改后的首页代码:

// src/routes/+page.svelte
<script>
    import { onMount } from 'svelte';
    import supabase from '../supa';
    import { goto } from '$app/navigation';

    let totalStudyTime = 0;
    let goalHours = 0;
    let goalMinutes = 0;
    let completed = false;
    let userSession = null;
    let loginEmail = '';
    let loginPassword = '';

    // 检查登录状态
    async function checkAuth() {
        const { data: { session } } = await supabase.auth.getSession();
        userSession = session;
        userSession && loadLocalData();
    }

    // 加载本地存储的学习数据
    function loadLocalData() {
        goalHours = parseInt(localStorage.getItem('goalHours') || '0', 10);
        goalMinutes = parseInt(localStorage.getItem('goalMinutes') || '0', 10);
        const studyTime = parseInt(localStorage.getItem('totalStudyTime') || '0', 10) + parseInt(localStorage.getItem('elapsedTime') || '0', 10);
        totalStudyTime = studyTime;
        setGoal();
    }

    function calculateCompletion(time, goal) {
        const totalTimeMinutes = time / 60;
        completed = totalTimeMinutes >= goal;
    }

    function setGoal() {
        if (!userSession) return;
        const totalGoalMinutes = goalHours * 60 + goalMinutes;
        localStorage.setItem('dailyStudyGoal', totalGoalMinutes);
        localStorage.setItem('goalHours', goalHours);
        localStorage.setItem('goalMinutes', goalMinutes);
        totalStudyTime = parseInt(localStorage.getItem('totalStudyTime') || '0', 10) + parseInt(localStorage.getItem('elapsedTime') || '0', 10);
        calculateCompletion(totalStudyTime, totalGoalMinutes);
    }

    function formatStudyTime(seconds) {
        const days = Math.floor(seconds / (3600 * 24));
        const hours = Math.floor((seconds % (3600 * 24)) / 3600);
        const minutes = Math.floor((seconds % 3600) / 60);
        const remainingSeconds = seconds % 60;

        const formattedDays = days > 0 ? `${days} day${days > 1 ? 's' : ''}` : '';
        const formattedHours = hours > 0 ? `${hours} hour${hours > 1 ? 's' : ''}` : '';
        const formattedMinutes = minutes > 0 ? `${minutes} minute${minutes > 1 ? 's' : ''}` : '';
        const formattedSeconds = remainingSeconds > 0 ? `${remainingSeconds} second${remainingSeconds > 1 ? 's' : ''}` : '';

        const timeArray = [formattedDays, formattedHours, formattedMinutes, formattedSeconds].filter(Boolean);
        return timeArray.join(', ');
    }

    // 登录操作
    async function handleLogin() {
        const { error } = await supabase.auth.signIn({ email: loginEmail, password: loginPassword });
        if (!error) {
            userSession = (await supabase.auth.getSession()).data.session;
            loadLocalData();
        } else {
            alert('登录失败:' + error.message);
        }
    }

    onMount(() => {
        checkAuth();
        // 监听登录状态变化
        supabase.auth.onAuthStateChange((_event, session) => {
            userSession = session;
            session && loadLocalData();
        });
    });
</script>

<!-- 未登录时显示登录弹窗遮罩 -->
{#if !userSession}
<div class="auth-overlay">
    <div class="login-modal">
        <h2>请先登录</h2>
        <input type="email" bind:value={loginEmail} placeholder="邮箱" />
        <input type="password" bind:value={loginPassword} placeholder="密码" />
        <button on:click={handleLogin}>登录</button>
        <p>还没有账号?<button on:click={() => goto('/signup')} class="link-btn">立即注册</button></p>
    </div>
</div>
{/if}

<main>
<h1><b>Welcome to studyclock!</b></h1>
<h3> Start a study session! </h3>
<p>Set your daily goal! <b> {goalHours} hr {goalMinutes} min </b></p> 

<label for="hoursInput">Hours</label>
<input type="number" id="hoursInput" bind:value={goalHours} min="0" disabled={!userSession}>

<label for="minutesInput">Minutes</label>
<input type="number" id="minutesInput" bind:value={goalMinutes} min="0" max="59" disabled={!userSession}>

<button on:click={setGoal} disabled={!userSession}>Set Goal</button>

{#if completed}
<p>Great job! You completed your daily study goal! 🥳</p>
{/if}

<p> Total study time: <b>{formatStudyTime(totalStudyTime)}</b> </p>
</main>

<style>
    main {
        text-align: center;
    }

    label, input, button {
        display: block;
        margin: 8px auto;
        text-align: center;
        width: fit-content;
    }

    input { margin-bottom: 16px; }
    button { margin-top: 16px; }

    /* 登录遮罩样式 */
    .auth-overlay {
        position: fixed;
        top: 0; left: 0;
        width: 100%; height: 100%;
        background: rgba(0,0,0,0.7);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 1000;
    }

    .login-modal {
        background: white;
        padding: 30px;
        border-radius: 15px;
        box-shadow: 0 0 10px rgba(0,0,0,0.3);
        width: 300px;
    }

    .login-modal input {
        width: 100%;
        padding: 8px;
        margin: 10px 0;
        box-sizing: border-box;
    }

    .link-btn {
        background: none;
        border: none;
        color: blue;
        cursor: pointer;
        text-decoration: underline;
    }
</style>

方案特点:

  • 未登录时用户可查看首页内容,但所有交互元素(输入框、按钮)均被禁用
  • 弹窗遮罩强制用户登录,登录后自动加载数据并启用交互
  • 监听Supabase的登录状态变化,实时更新页面状态

内容的提问来源于stack exchange,提问作者Claryssa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 05:37:03