4 changed files with 190 additions and 29 deletions
@ -0,0 +1,73 @@ |
|||||
|
<template> |
||||
|
<UApp> |
||||
|
<div class="min-h-screen bg-default text-default flex flex-col"> |
||||
|
<header class="border-b border-default"> |
||||
|
<UContainer class="h-14 flex items-center justify-between"> |
||||
|
<div class="flex items-center gap-2"> |
||||
|
<NuxtLink to="/" class="font-semibold tracking-tight"> |
||||
|
BigHouse |
||||
|
</NuxtLink> |
||||
|
<UDropdownMenu :items="menuItems" :content="{ align: 'end' }"> |
||||
|
<UButton color="neutral" variant="ghost" label="菜单" icon="i-lucide-menu" /> |
||||
|
</UDropdownMenu> |
||||
|
</div> |
||||
|
</UContainer> |
||||
|
</header> |
||||
|
|
||||
|
<main class="flex-1"> |
||||
|
<UContainer class="py-8"> |
||||
|
<NuxtPage /> |
||||
|
</UContainer> |
||||
|
</main> |
||||
|
|
||||
|
<footer class="border-t border-default"> |
||||
|
<UContainer class="h-12 flex items-center text-sm text-muted"> |
||||
|
Built with Nuxt + Nuxt UI & BigHouse |
||||
|
</UContainer> |
||||
|
</footer> |
||||
|
</div> |
||||
|
</UApp> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
const menuItems = [ |
||||
|
[ |
||||
|
{ |
||||
|
label: "首页", |
||||
|
icon: "i-lucide-house", |
||||
|
to: "/", |
||||
|
}, |
||||
|
{ |
||||
|
label: "文档", |
||||
|
icon: "i-lucide-book-open", |
||||
|
children: [ |
||||
|
{ |
||||
|
label: "Nuxt", |
||||
|
to: "https://nuxt.com/docs", |
||||
|
target: "_blank", |
||||
|
}, |
||||
|
{ |
||||
|
label: "Nuxt UI", |
||||
|
to: "https://ui.nuxt.com/getting-started", |
||||
|
target: "_blank", |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
label: "示例", |
||||
|
icon: "i-lucide-layout-grid", |
||||
|
children: [ |
||||
|
{ |
||||
|
label: "Hello API", |
||||
|
to: "/api/hello", |
||||
|
target: "_blank", |
||||
|
}, |
||||
|
{ |
||||
|
label: "首页页面", |
||||
|
to: "/index", |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
], |
||||
|
] |
||||
|
</script> |
||||
@ -0,0 +1,113 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { FormError, FormSubmitEvent } from '@nuxt/ui' |
||||
|
|
||||
|
definePageMeta({ |
||||
|
title: '登录', |
||||
|
layout: 'not-login', |
||||
|
}) |
||||
|
|
||||
|
type LoginFormState = { |
||||
|
email: string |
||||
|
password: string |
||||
|
} |
||||
|
|
||||
|
const state = reactive<LoginFormState>({ |
||||
|
email: '', |
||||
|
password: '', |
||||
|
}) |
||||
|
|
||||
|
const loading = ref(false) |
||||
|
const resultType = ref<'success' | 'error' | ''>('') |
||||
|
const resultMessage = ref('') |
||||
|
|
||||
|
const validate = (formState: LoginFormState): FormError[] => { |
||||
|
const errors: FormError[] = [] |
||||
|
|
||||
|
if (!formState.email) { |
||||
|
errors.push({ name: 'email', message: '请输入邮箱' }) |
||||
|
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formState.email)) { |
||||
|
errors.push({ name: 'email', message: '邮箱格式不正确' }) |
||||
|
} |
||||
|
|
||||
|
if (!formState.password) { |
||||
|
errors.push({ name: 'password', message: '请输入密码' }) |
||||
|
} else if (formState.password.length < 6) { |
||||
|
errors.push({ name: 'password', message: '密码至少 6 位' }) |
||||
|
} |
||||
|
|
||||
|
return errors |
||||
|
} |
||||
|
|
||||
|
const onSubmit = async (_event: FormSubmitEvent<LoginFormState>) => { |
||||
|
resultType.value = '' |
||||
|
resultMessage.value = '' |
||||
|
loading.value = true |
||||
|
|
||||
|
try { |
||||
|
await new Promise((resolve) => setTimeout(resolve, 800)) |
||||
|
resultType.value = 'success' |
||||
|
resultMessage.value = `登录成功,欢迎 ${state.email}` |
||||
|
} catch { |
||||
|
resultType.value = 'error' |
||||
|
resultMessage.value = '登录失败,请稍后重试' |
||||
|
} finally { |
||||
|
loading.value = false |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div class="max-w-md mx-auto py-10"> |
||||
|
<UCard> |
||||
|
<template #header> |
||||
|
<div class="space-y-1"> |
||||
|
<h1 class="text-xl font-semibold">欢迎登录</h1> |
||||
|
<p class="text-sm text-muted">请输入邮箱和密码继续</p> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<UForm :state="state" :validate="validate" class="space-y-4" @submit="onSubmit"> |
||||
|
<UFormField label="邮箱" name="email" required> |
||||
|
<UInput |
||||
|
v-model="state.email" |
||||
|
type="email" |
||||
|
placeholder="you@example.com" |
||||
|
autocomplete="email" |
||||
|
class="w-full" |
||||
|
/> |
||||
|
</UFormField> |
||||
|
|
||||
|
<UFormField label="密码" name="password" required> |
||||
|
<UInput |
||||
|
v-model="state.password" |
||||
|
type="password" |
||||
|
placeholder="请输入密码" |
||||
|
autocomplete="current-password" |
||||
|
class="w-full" |
||||
|
/> |
||||
|
</UFormField> |
||||
|
|
||||
|
<UButton type="submit" block :loading="loading"> |
||||
|
立即登录 |
||||
|
</UButton> |
||||
|
</UForm> |
||||
|
|
||||
|
<UAlert |
||||
|
v-if="resultType" |
||||
|
:color="resultType === 'success' ? 'success' : 'error'" |
||||
|
:title="resultType === 'success' ? '操作成功' : '操作失败'" |
||||
|
:description="resultMessage" |
||||
|
class="mt-4" |
||||
|
/> |
||||
|
|
||||
|
<div class="mt-4 flex items-center justify-between text-sm"> |
||||
|
<NuxtLink to="/register" class="text-primary hover:underline"> |
||||
|
去注册 |
||||
|
</NuxtLink> |
||||
|
<NuxtLink to="/forgot-password" class="text-muted hover:underline"> |
||||
|
忘记密码? |
||||
|
</NuxtLink> |
||||
|
</div> |
||||
|
</UCard> |
||||
|
</div> |
||||
|
</template> |
||||
Loading…
Reference in new issue