5 changed files with 195 additions and 0 deletions
@ -0,0 +1,8 @@ |
|||
export default defineNuxtRouteMiddleware(async (to) => { |
|||
const fetch = useRequestFetch(); |
|||
try { |
|||
await fetch("/api/me", { credentials: "include" }); |
|||
} catch { |
|||
return navigateTo({ path: "/login", query: { redirect: to.fullPath } }); |
|||
} |
|||
}); |
|||
@ -0,0 +1,9 @@ |
|||
export default defineNuxtRouteMiddleware(async () => { |
|||
const fetch = useRequestFetch(); |
|||
try { |
|||
await fetch("/api/me", { credentials: "include" }); |
|||
return navigateTo("/me"); |
|||
} catch { |
|||
return; |
|||
} |
|||
}); |
|||
@ -0,0 +1,41 @@ |
|||
<script setup lang="ts"> |
|||
definePageMeta({ middleware: "guest" }); |
|||
|
|||
const email = ref(""); |
|||
const password = ref(""); |
|||
const errorMsg = ref(""); |
|||
|
|||
async function onSubmit() { |
|||
errorMsg.value = ""; |
|||
try { |
|||
await $fetch("/api/auth/login", { |
|||
method: "POST", |
|||
body: { email: email.value, password: password.value }, |
|||
credentials: "include", |
|||
}); |
|||
await navigateTo("/me"); |
|||
} catch (e: unknown) { |
|||
const err = e as { data?: { error?: { message?: string } } }; |
|||
errorMsg.value = err.data?.error?.message ?? "登录失败"; |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<div style="max-width: 360px; margin: 2rem auto; font-family: system-ui"> |
|||
<h1>登录</h1> |
|||
<form @submit.prevent="onSubmit"> |
|||
<label> |
|||
邮箱 |
|||
<input v-model="email" type="email" required style="display: block; width: 100%; margin: 0.25rem 0 1rem" /> |
|||
</label> |
|||
<label> |
|||
密码 |
|||
<input v-model="password" type="password" minlength="8" required style="display: block; width: 100%; margin: 0.25rem 0 1rem" /> |
|||
</label> |
|||
<p v-if="errorMsg" style="color: crimson">{{ errorMsg }}</p> |
|||
<button type="submit">登录</button> |
|||
</form> |
|||
<p><NuxtLink to="/register">没有账号?注册</NuxtLink></p> |
|||
</div> |
|||
</template> |
|||
@ -0,0 +1,81 @@ |
|||
<script setup lang="ts"> |
|||
definePageMeta({ middleware: "auth" }); |
|||
|
|||
type User = { |
|||
id: number; |
|||
name: string; |
|||
age: number; |
|||
email: string; |
|||
emailVerified: boolean; |
|||
}; |
|||
|
|||
const fetch = useRequestFetch(); |
|||
const { data, refresh } = await useAsyncData("me", () => |
|||
fetch<{ user: User }>("/api/me", { credentials: "include" }), |
|||
); |
|||
|
|||
const name = ref(data.value?.user.name ?? ""); |
|||
const age = ref(data.value?.user.age ?? 0); |
|||
const patchError = ref(""); |
|||
|
|||
watch( |
|||
() => data.value?.user, |
|||
(u) => { |
|||
if (u) { |
|||
name.value = u.name; |
|||
age.value = u.age; |
|||
} |
|||
}, |
|||
{ immediate: true }, |
|||
); |
|||
|
|||
async function saveProfile() { |
|||
patchError.value = ""; |
|||
try { |
|||
await $fetch("/api/me", { |
|||
method: "PATCH", |
|||
body: { name: name.value, age: age.value }, |
|||
credentials: "include", |
|||
}); |
|||
await refresh(); |
|||
} catch (e: unknown) { |
|||
const err = e as { statusCode?: number; data?: { error?: { message?: string } } }; |
|||
if (err.statusCode === 403) { |
|||
patchError.value = "需先完成邮箱验证后才能修改资料。"; |
|||
} else { |
|||
patchError.value = err.data?.error?.message ?? "保存失败"; |
|||
} |
|||
} |
|||
} |
|||
|
|||
async function logout() { |
|||
await $fetch("/api/auth/logout", { method: "POST", credentials: "include" }); |
|||
await navigateTo("/login"); |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<div style="max-width: 420px; margin: 2rem auto; font-family: system-ui"> |
|||
<h1>个人资料</h1> |
|||
<p v-if="data?.user"> |
|||
邮箱:{{ data.user.email }} |
|||
<span v-if="data.user.emailVerified">(已验证)</span> |
|||
<span v-else>(未验证)</span> |
|||
</p> |
|||
<p v-if="patchError" style="color: darkorange">{{ patchError }}</p> |
|||
<form @submit.prevent="saveProfile"> |
|||
<label> |
|||
姓名 |
|||
<input v-model="name" type="text" required style="display: block; width: 100%; margin: 0.25rem 0 1rem" /> |
|||
</label> |
|||
<label> |
|||
年龄 |
|||
<input v-model.number="age" type="number" min="1" max="150" required style="display: block; width: 100%; margin: 0.25rem 0 1rem" /> |
|||
</label> |
|||
<button type="submit">保存</button> |
|||
</form> |
|||
<p style="margin-top: 2rem"> |
|||
<button type="button" @click="logout">退出登录</button> |
|||
</p> |
|||
</div> |
|||
</template> |
|||
@ -0,0 +1,56 @@ |
|||
<script setup lang="ts"> |
|||
definePageMeta({ middleware: "guest" }); |
|||
|
|||
const email = ref(""); |
|||
const password = ref(""); |
|||
const name = ref(""); |
|||
const age = ref(20); |
|||
const errorMsg = ref(""); |
|||
|
|||
async function onSubmit() { |
|||
errorMsg.value = ""; |
|||
try { |
|||
await $fetch("/api/auth/register", { |
|||
method: "POST", |
|||
body: { |
|||
email: email.value, |
|||
password: password.value, |
|||
name: name.value, |
|||
age: Number(age.value), |
|||
}, |
|||
credentials: "include", |
|||
}); |
|||
await navigateTo("/me"); |
|||
} catch (e: unknown) { |
|||
const err = e as { data?: { error?: { message?: string } } }; |
|||
errorMsg.value = err.data?.error?.message ?? "注册失败"; |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<div style="max-width: 360px; margin: 2rem auto; font-family: system-ui"> |
|||
<h1>注册</h1> |
|||
<form @submit.prevent="onSubmit"> |
|||
<label> |
|||
邮箱 |
|||
<input v-model="email" type="email" required style="display: block; width: 100%; margin: 0.25rem 0 1rem" /> |
|||
</label> |
|||
<label> |
|||
密码(至少 8 位) |
|||
<input v-model="password" type="password" minlength="8" required style="display: block; width: 100%; margin: 0.25rem 0 1rem" /> |
|||
</label> |
|||
<label> |
|||
姓名 |
|||
<input v-model="name" type="text" required style="display: block; width: 100%; margin: 0.25rem 0 1rem" /> |
|||
</label> |
|||
<label> |
|||
年龄 |
|||
<input v-model.number="age" type="number" min="1" max="150" required style="display: block; width: 100%; margin: 0.25rem 0 1rem" /> |
|||
</label> |
|||
<p v-if="errorMsg" style="color: crimson">{{ errorMsg }}</p> |
|||
<button type="submit">注册并登录</button> |
|||
</form> |
|||
<p><NuxtLink to="/login">已有账号?登录</NuxtLink></p> |
|||
</div> |
|||
</template> |
|||
Loading…
Reference in new issue