在 sveltekit 中,我想/home
对两种类型的用户使用类似的路线,但具有不同的内容:
- 如果用户是管理员,则应显示用户列表
- 如果用户不是管理员,则应显示任务列表
这意味着这两个结果应该使用不同的+page.ts
函数load
来获取页面数据。
整个东西是一个 SPA,因此 sveltekit 的服务器部分未被使用。
这可能吗?
PS. 这与这个问题正好相反
在 sveltekit 中,我想/home
对两种类型的用户使用类似的路线,但具有不同的内容:
这意味着这两个结果应该使用不同的+page.ts
函数load
来获取页面数据。
整个东西是一个 SPA,因此 sveltekit 的服务器部分未被使用。
这可能吗?
PS. 这与这个问题正好相反
我正在尝试使用 SvelteKit 开发一个将部署在 one.com 上的网站。由于这将是一个静态网站,因此我使用@sveltejs/adapter-static
。
我面临的问题是关于使用适配器静态预渲染 API 端点。
代表该问题的代码在这里:
https://stackblitz.com/edit/node-ewi2yl?file=src%2Froutes%2Fapi%2Fupload%2F%2Bserver.js
在 中routes/api/upload/+server.js
,我尝试了该prerender
选项。但部署后我的尝试均未奏效。
/* - In DEV mode, runtime error "Error: Cannot prerender endpoints that have mutative methods".
- Build fails with error "Error: Cannot prerender a +server file with POST, PATCH, PUT, or DELETE (/api/upload)".
*/
export const prerender = true;
/* - In DEV mode, it works.
- Build does not fail. But after deploying, the endpoint /api/upload is missing - 404 Not Found.
*/
export const prerender = false;
似乎POST
端点不会预渲染。尽管我的端点不会改变页面。这POST
只是为了将数据放入 SQLite 数据库中(上面的代码中未显示 - 可能不相关)。
在 Google 上几乎没有找到Error: Cannot prerender a +server file with POST, PATCH, PUT, or DELETE
!
有人知道如何处理这种情况吗?
我已经教授了有关组件的多个 props 的教程,但我想在 中使用它+layout
。
我希望可以在不同的路由子级(一些文件)中{@render}
上层布局指定的(+layout.svelte
,不是一个组件)插入多个不同的组件,+page.svelte
就像svelte4的多名插槽一样。
我目前使用下面的代码来解决需求,但直觉认为我的方法不好,很迂回,而且我不需要在页面加载后动态改变插入的组件。现在我使用额外的代码通过上下文方法传递这些代码片段,有没有更好的方法来简化它们?
<!-- +layout.svelte -->
<script lang="ts">
import { setContext, createRawSnippet } from 'svelte';
import { writable } from 'svelte/store';
const dummySnippet = (text: string) =>
createRawSnippet(() => {
return { render: () => text };
});
let { children } = $props();
let slotLeft = writable(dummySnippet('LEFT'));
let slotCenter = writable(dummySnippet('CENTER'));
let slotRight = writable(dummySnippet('RIGHT'));
setContext('LayoutSlot', { slotLeft, slotCenter, slotRight });
</script>
<winbox class="flex flex-row h-full w-full overflow-hidden">
<listbox class="w-[400px]">
{@render $slotLeft()}
</listbox>
<div class="flex-1 flex flex-col border-x">
{@render $slotCenter()}
</div>
<div class="w-[350px]">
{@render $slotRight()}
</div>
</winbox>
{@render children()}
<!-- +page.svelte -->
<script lang="ts">
import { onMount, getContext, type Snippet } from 'svelte';
import type { Writable } from 'svelte/store';
let {
slotLeft,
slotCenter,
slotRight
}: {
slotLeft: Writable<Snippet>;
slotCenter: Writable<Snippet>;
slotRight: Writable<Snippet>;
} = getContext('LayoutSlot');
onMount(() => {
$slotLeft = menuLeft;
$slotCenter = mainContent;
$slotRight = menuRight;
});
</script>
{#snippet menuLeft()}
<p>Left Menu Pending</p>
{/snippet}
{#snippet mainContent()}
<p>Center Content Pending</p>
{/snippet}
{#snippet menuRight()}
<p>Right Menu Pending</p>
{/snippet}
<children>TEST</children>
<!-- .... other option snippets from other routing-->
<!-- {#snippet foo1()} -->
<!-- {#snippet foo2()} -->
希望有人能给我提供比我当前的方法来简化代码更好的想法,也许应该有一种直接的方法children
在+page.svelte
内部 html 区域传递道具(而不仅仅是默认道具),但我找不到它。
考虑以下代码:
<script>
let input;
const foo = () => {
let html = input.innerHTML;
// Do something with HTML
}
</script>
<button on:click={foo}>Foo</button>
{#if edit}
<div bind:this={input}></div>
{/if}
我需要获取 div 的 DOM 元素。然后在按钮运行的函数中使用它。我该怎么做?bind:this
仅在 中有效onMount
,但我无法foo
在 内定义onMount
。
理解这一点innerHTML
,或者具体的方法/属性并不重要。我只需要在按下按钮时调用的函数中使用它的 DOM 元素。
我有一组 id,我想为每个 id 显示一个按钮,单击该按钮时会将 id 提交到 page.server.ts 中的操作。这应该很简单,但我绞尽脑汁想让它工作。
最小可重复示例
例子.schemas.ts
import { z } from "zod";
export const exampleSchema = z.object({
exampleId: z.string().length(24),
});
export type ExampleSchema = typeof exampleSchema;
例如-component.svelte
<script lang="ts">
import { exampleSchema, type ExampleSchema } from "./example.schema";
import {
type SuperValidated,
type Infer,
superForm,
} from "sveltekit-superforms";
import SuperDebug from 'sveltekit-superforms';
import { zodClient } from "sveltekit-superforms/adapters";
export let data: SuperValidated<Infer<ExampleSchema>>;
const form = superForm(data, {
validators: zodClient(exampleSchema),
});
const { form: formData, enhance, message } = form;
let exampleArray = ["507f1f77bcf86cd799439011","507f1f77bcf86cd799439012","507f1f77bcf86cd799439013"]
</script>
<form method="POST" action="?/exampleAction" use:enhance>
{#each exampleArray as item}
<input type="hidden" name="exampleId" value="{item}"/>
<button type="submit" >{item}</button><br />
{/each}
</form>
<SuperDebug data={formData} />
+页面.服务器.ts
import type { PageServerLoad, Actions } from "./$types";
import { superValidate } from "sveltekit-superforms";
import { exampleSchema } from "$lib/components/custom/example.schema";
import { zod } from "sveltekit-superforms/adapters";
export const load: PageServerLoad = async () => {
return {
exampleForm: await superValidate(zod(exampleSchema)),
};
};
export const actions: Actions = {
exampleAction: async ({ request }) => {
const formData = await request.formData();
const exampleId = formData.get('exampleId');
// Process the ID as needed
console.log('Submitted ID:', exampleId);
return { success: true };
}
};
+页面.svelte
<script lang="ts">
import type { PageData, ActionData } from './$types';
import Example from "$lib/components/custom/example-component.svelte";
export let data: PageData;
</script>
<Example data={data.exampleForm} />
SuperDebug 总是显示 exampleId 为空,并且验证总是失败,看起来我的操作根本没有运行。
我的应用将用户偏好存储在本地存储中,我正尝试将这些值作为上下文提供给我的应用。我陷入困境,因为我不明白如何在 SvelteKit 中从应用最高级别的异步函数设置上下文。
我创建了一个默认为具有默认值的对象的存储(本地存储尚未加载):
export const localStoragePreferences = writable(new LocalStoragePreferences());
queryLocalStoragePreferences
现在,我有一个返回本地存储的真实值的异步函数。
我尝试在根目录中设置它layout.ts
:
export async function load() {
const localStoragePreferences = await queryLocalStoragePreferences();
setContext('localStoragePreferences', localStoragePreferences);
}
由于错误,此操作失败Function called outside component initialization
。
根据对类似问题的回答,我应该在高级组件中声明 setContext,然后在低级组件中使用我的异步值更新上下文。在 SvelteKit 中哪里可以执行此操作?
LayoutServerLoad
在我的 SvelteKit 网站中,我在函数内部向后端发出请求+layout.server.ts
。即当使用查询参数打开网站时?affiliate_id=bla
,我想将其发送到后端:
import type { LayoutServerLoad } from "./$types";
import { keyBy } from "$lib/utils";
import { backendUrl } from "$lib/config";
export const trailingSlash = "always";
export const load: LayoutServerLoad = async ({ locals, url, fetch }) => {
const affiliate_id = url.searchParams.get("affiliate_id");
if (affiliate_id) {
const headers = {
"Content-Type": "application/json",
"X-CSRFToken": locals.csrfToken,
};
fetch(`${backendUrl}/shop/basket/`, { method: "POST", credentials: "include", headers, body: JSON.stringify({ affiliate_id }) })
.catch(error => {
console.error("Error storing affiliate_id: ", error);
});
}
return {
// I'm returning a bunch of data here
};
};
问题是后端响应包含set-cookie
标头,并且 cookie 没有存储在浏览器中。
当我将load
函数更改为仅返回affiliate_id
:
export const load: LayoutServerLoad = async ({ locals, url }) => {
return {
affiliateId: url.searchParams.get("affiliate_id"),
// and more data
};
};
然后在里面发出请求+layout.svelte
,那么一切就正常工作了。
<script lang="ts">
import type { PageData } from "./$types";
import { onMount } from "svelte";
export let data: PageData;
const { affiliateId } = data;
onMount(async () => {
if (affiliateId) {
// Do the request here
}
});
</script>
<slot />
现在,响应中的 cookie 已正常存储在浏览器中。
为什么在函数中做同样的请求时,cookie没有被存储呢load
?
我已关注:https://supabase.com/docs/guides/getting-started/tutorials/with-sveltekit ?language=ts 并验证一切运行正常并且所有文件都位于正确的位置/具有正确的内容。
该应用程序运行并更新/与 SUPABASE 通信。但是,当您使用电子邮件/密码正确登录时,它只会停留在 authui 屏幕上。
没有重定向到/account。仅当刷新页面/转到任何端点时,它才能成功通过身份验证。端点在未登录时也会重定向回身份验证用户界面。
我在这里没有得到什么?
Supabase SQL 编辑器
-- Create a table for public profiles
create table profiles (
id uuid references auth.users not null primary key,
updated_at timestamp with time zone,
username text unique,
full_name text,
avatar_url text,
website text,
constraint username_length check (char_length(username) >= 3)
);
-- Set up Row Level Security (RLS)
-- See https://supabase.com/docs/guides/auth/row-level-security for more details.
alter table profiles
enable row level security;
create policy "Public profiles are viewable by everyone." on profiles
for select using (true);
create policy "Users can insert their own profile." on profiles
for insert with check (auth.uid() = id);
create policy "Users can update own profile." on profiles
for update using (auth.uid() = id);
-- This trigger automatically creates a profile entry when a new user signs up via Supabase Auth.
-- See https://supabase.com/docs/guides/auth/managing-user-data#using-triggers for more details.
create function public.handle_new_user()
returns trigger as $$
begin
insert into public.profiles (id, full_name, avatar_url)
values (new.id, new.raw_user_meta_data->>'full_name', new.raw_user_meta_data->>'avatar_url');
return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on auth.users
for each row execute procedure public.handle_new_user();
-- Set up Storage!
insert into storage.buckets (id, name)
values ('avatars', 'avatars');
-- Set up access controls for storage.
-- See https://supabase.com/docs/guides/storage/security/access-control#policy-examples for more details.
create policy "Avatar images are publicly accessible." on storage.objects
for select using (bucket_id = 'avatars');
create policy "Anyone can upload an avatar." on storage.objects
for insert with check (bucket_id = 'avatars');
create policy "Anyone can update their own avatar." on storage.objects
for update using (auth.uid() = owner) with check (bucket_id = 'avatars');
初始化精简应用程序
npm create svelte@latest supabase-sveltekit
cd supabase-sveltekit
npm install
定义的.ENV
PUBLIC_SUPABASE_URL="YOUR_SUPABASE_URL"
PUBLIC_SUPABASE_ANON_KEY="YOUR_SUPABASE_KEY"
安装:npm install @supabase/ssr @supabase/supabase-js
// src/hooks.server.ts
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
import { createServerClient } from '@supabase/ssr'
import type { Handle } from '@sveltejs/kit'
export const handle: Handle = async ({ event, resolve }) => {
event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
cookies: {
get: (key) => event.cookies.get(key),
/**
* Note: You have to add the `path` variable to the
* set and remove method due to sveltekit's cookie API
* requiring this to be set, setting the path to `/`
* will replicate previous/standard behaviour (https://kit.svelte.dev/docs/types#public-types-cookies)
*/
set: (key, value, options) => {
event.cookies.set(key, value, { ...options, path: '/' })
},
remove: (key, options) => {
event.cookies.delete(key, { ...options, path: '/' })
},
},
})
/**
* A convenience helper so we can just call await getSession() instead const { data: { session } } = await supabase.auth.getSession()
*/
event.locals.getSession = async () => {
const {
data: { session },
} = await event.locals.supabase.auth.getSession()
return session
}
return resolve(event, {
filterSerializedResponseHeaders(name) {
return name === 'content-range'
},
})
}
// src/app.d.ts
import { SupabaseClient, Session } from '@supabase/supabase-js'
declare global {
namespace App {
interface Locals {
supabase: SupabaseClient
getSession(): Promise<Session | null>
}
interface PageData {
session: Session | null
}
// interface Error {}
// interface Platform {}
}
}
// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types'
export const load: LayoutServerLoad = async ({ locals: { getSession } }) => {
return {
session: await getSession(),
}
}
// src/routes/+layout.ts
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
import type { LayoutLoad } from './$types'
import { createBrowserClient, isBrowser, parse } from '@supabase/ssr'
export const load: LayoutLoad = async ({ fetch, data, depends }) => {
depends('supabase:auth')
const supabase = createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
global: {
fetch,
},
cookies: {
get(key) {
if (!isBrowser()) {
return JSON.stringify(data.session)
}
const cookie = parse(document.cookie)
return cookie[key]
},
},
})
const {
data: { session },
} = await supabase.auth.getSession()
return { supabase, session }
}
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import '../styles.css'
import { invalidate } from '$app/navigation'
import { onMount } from 'svelte'
export let data
let { supabase, session } = data
$: ({ supabase, session } = data)
onMount(() => {
const { data } = supabase.auth.onAuthStateChange((event, _session) => {
if (_session?.expires_at !== session?.expires_at) {
invalidate('supabase:auth')
}
})
return () => data.subscription.unsubscribe()
})
</script>
<svelte:head>
<title>User Management</title>
</svelte:head>
<div class="container" style="padding: 50px 0 100px 0">
<slot />
</div>
npm install @supabase/auth-ui-svelte @supabase/auth-ui-shared
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { Auth } from '@supabase/auth-ui-svelte'
import { ThemeSupa } from '@supabase/auth-ui-shared'
export let data
</script>
<svelte:head>
<title>User Management</title>
</svelte:head>
<div class="row flex-center flex">
<div class="col-6 form-widget">
<Auth
supabaseClient={data.supabase}
view="magic_link"
redirectTo={`${data.url}/auth/callback`}
showLinks={false}
appearance={{ theme: ThemeSupa, style: { input: 'color: #fff' } }}
/>
</div>
</div>
// src/routes/auth/callback/+server.ts
import { redirect } from '@sveltejs/kit'
export const GET = async ({ url, locals: { supabase } }) => {
const code = url.searchParams.get('code')
if (code) {
await supabase.auth.exchangeCodeForSession(code)
}
throw redirect(303, '/account')
}
<!-- src/routes/account/+page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms';
import type { SubmitFunction } from '@sveltejs/kit';
export let data
export let form
let { session, supabase, profile } = data
$: ({ session, supabase, profile } = data)
let profileForm: HTMLFormElement
let loading = false
let fullName: string = profile?.full_name ?? ''
let username: string = profile?.username ?? ''
let website: string = profile?.website ?? ''
let avatarUrl: string = profile?.avatar_url ?? ''
const handleSubmit: SubmitFunction = () => {
loading = true
return async () => {
loading = false
}
}
const handleSignOut: SubmitFunction = () => {
loading = true
return async ({ update }) => {
loading = false
update()
}
}
</script>
<div class="form-widget">
<form
class="form-widget"
method="post"
action="?/update"
use:enhance={handleSubmit}
bind:this={profileForm}
>
<div>
<label for="email">Email</label>
<input id="email" type="text" value={session.user.email} disabled />
</div>
<div>
<label for="fullName">Full Name</label>
<input id="fullName" name="fullName" type="text" value={form?.fullName ?? fullName} />
</div>
<div>
<label for="username">Username</label>
<input id="username" name="username" type="text" value={form?.username ?? username} />
</div>
<div>
<label for="website">Website</label>
<input id="website" name="website" type="url" value={form?.website ?? website} />
</div>
<div>
<input
type="submit"
class="button block primary"
value={loading ? 'Loading...' : 'Update'}
disabled={loading}
/>
</div>
</form>
<form method="post" action="?/signout" use:enhance={handleSignOut}>
<div>
<button class="button block" disabled={loading}>Sign Out</button>
</div>
</form>
</div>
import { fail, redirect } from '@sveltejs/kit'
import type { Actions, PageServerLoad } from './$types'
export const load: PageServerLoad = async ({ locals: { supabase, getSession } }) => {
const session = await getSession()
if (!session) {
throw redirect(303, '/')
}
const { data: profile } = await supabase
.from('profiles')
.select(`username, full_name, website, avatar_url`)
.eq('id', session.user.id)
.single()
return { session, profile }
}
export const actions: Actions = {
update: async ({ request, locals: { supabase, getSession } }) => {
const formData = await request.formData()
const fullName = formData.get('fullName') as string
const username = formData.get('username') as string
const website = formData.get('website') as string
const avatarUrl = formData.get('avatarUrl') as string
const session = await getSession()
const { error } = await supabase.from('profiles').upsert({
id: session?.user.id,
full_name: fullName,
username,
website,
avatar_url: avatarUrl,
updated_at: new Date(),
})
if (error) {
return fail(500, {
fullName,
username,
website,
avatarUrl,
})
}
return {
fullName,
username,
website,
avatarUrl,
}
},
signout: async ({ locals: { supabase, getSession } }) => {
const session = await getSession()
if (session) {
await supabase.auth.signOut()
throw redirect(303, '/')
}
},
}
并启动 npm run dev -- --open --host
我需要一个全局变量来存储当前用户状态和令牌。但我不明白在最新版本的 SvelteKit 中这是如何完成的。如何使其从应用程序的所有组件中可见
美好的一天/晚上!我已经处理这个问题几天了,移动各个部分以尝试隔离并解决问题。我很感激您在这里提供的任何见解。
以下是发生的事情的顺序:
+page.server.ts
:初始负载按预期工作+page.svelte
:data:PageData
已正确填充+page.server.ts
:Action
按预期执行并工作+page.server.ts
:再次加载新数据+page.svelte
: 根本不重新加载这是我的预期:
-+page.svelte
重新加载,因为page.server.ts
返回了新数据
分析:
- 起初,我认为可能hooks.server.ts
是我的问题造成的,所以我删除了这些功能,但问题仍然存在。
- 似乎不需要invalidate
任何东西,因为表单update
正在工作并且page.server.ts
正在按预期重新加载。
// src/routes/admin/manage-roles/+page.server.ts
// (...) import statements all working: db, schemas, types, etc...
export const load: PageServerLoad = async ({ locals }) => {
try {
const allRoles = await db.select().from(roles)
return {
allRoles
};
} catch (error) {
console.error("Error fetching roles:", error);
return fail(500, { error: error.message });
}
};
export const actions: Actions = {
new: async ({ request }) => {
const form = await superValidate(request, roleSchema);
if (!form.valid) {
return fail(400, {
form
});
}
try {
const id = uuidv4();
const result = await db.insert(roles).values({
id,
name: form?.data?.name
});
} catch (error) {
console.error("Error creating role:", error);
return fail(500, { error: error.message });
}
return { success: true };
}
};
<script lang="ts">
// (...) import statements all working: db, schemas, components, etc...
export let data: PageData;
const { allRoles } = data;
const roleStore = writable(allRoles);
console.log("allRoles?", allRoles);
const isNewDialogOpen = writable(false);
const isSubmittingForm = writable(false);
const table = createTable(roleStore, {
sort: addSortBy({ disableMultiSort: true }),
page: addPagination(),
filter: addTableFilter({
fn: ({ filterValue, value }) => value.includes(filterValue)
}),
select: addSelectedRows(),
hide: addHiddenColumns()
});
// (...) custom table control events, etc... all working.
</script>
<!-- New Role Dialog -->
<Dialog
isOpen={$isNewDialogOpen}
onOpenChange={(open) => {
$isNewDialogOpen = open;
}}
title="New Role"
description="Add a new role to assign to users.">
<div slot="body">
<Form
action="?/new"
onBeforeSubmit={() => ($isSubmittingForm = true)}
on:successAfterUpdate={async () => {
$isSubmittingForm = false;
$isNewDialogOpen = false;
}}>
<div class="grid gap-2">
<div class="grid gap-1">
<Label class="sr-only" for="name">Role Name</Label>
<Input id="name" name="name" placeholder="Admin" required />
</div>
<div class="flex w-full justify-end">
<Button type="submit" class="w-32" disabled={$isSubmittingForm}>Create Role</Button>
</div>
</div>
</Form>
</div>
<div slot="footer" />
</Dialog>
<!-- Roles -->
<div class="flex flex-col px-10">
<div class="flex-1 mb-6">
<div class="flex items-center justify-between mb-2">
<h2 class="text-2xl font-bold tracking-tight mb-4">
<Users class="header-icon" />Manage Roles
</h2>
<div class="flex items-center space-x-2">
<Button size="sm" on:click={() => ($isNewDialogOpen = true)}>
<Plus class="mr-2 h-4 w-4" />
New
</Button>
</div>
</div>
<ManageNav active="roles" />
</div>
<!-- (...) The rest of the table/UI -->
import { auth } from "$lib/server/lucia";
import type { Handle, HandleServerError } from "@sveltejs/kit";
export const handleError: HandleServerError = async ({ error, event }) => {
const errorId = crypto.randomUUID();
event.locals.error = error?.toString() || undefined;
event.locals.errorStackTrace = error?.stack || undefined;
event.locals.errorId = errorId;
console.error(`Error ID: ${errorId}`, error)
return {
message: "An unexpected error occurred.",
errorId
};
};
export const handle: Handle = async ({ event, resolve }) => {
event.locals.auth = auth.handleRequest(event)
return await resolve(event)
};
感谢您花时间考虑我的情况。我想这里所有的集体努力都会对那些在这种情况下/未来与人工智能聊天的人有益。
祝大家度过美好的一天。干杯!
🎩🌹⚡️✨
我了解到一切依赖的东西都data
必须是反应性的,现在一切都进展顺利。(见下面的答案)。
export let data: PageData;
const roleStore = writable([]);
$: if (data?.allRoles) roleStore.set(data.allRoles);