Browse Source

feat: update layout to reflect new branding and add login page

feat/auth-access-control
npmrun 2 days ago
parent
commit
a2778b6fb3
  1. 4
      app/layouts/default.vue
  2. 73
      app/layouts/not-login.vue
  3. 29
      app/pages/index/index.vue
  4. 113
      app/pages/login/index.vue

4
app/layouts/default.vue

@ -5,7 +5,7 @@
<UContainer class="h-14 flex items-center justify-between"> <UContainer class="h-14 flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<NuxtLink to="/" class="font-semibold tracking-tight"> <NuxtLink to="/" class="font-semibold tracking-tight">
Nuxt4 Demo BigHouse
</NuxtLink> </NuxtLink>
<UDropdownMenu :items="menuItems" :content="{ align: 'end' }"> <UDropdownMenu :items="menuItems" :content="{ align: 'end' }">
<UButton color="neutral" variant="ghost" label="菜单" icon="i-lucide-menu" /> <UButton color="neutral" variant="ghost" label="菜单" icon="i-lucide-menu" />
@ -25,7 +25,7 @@
<footer class="border-t border-default"> <footer class="border-t border-default">
<UContainer class="h-12 flex items-center text-sm text-muted"> <UContainer class="h-12 flex items-center text-sm text-muted">
Built with Nuxt + Nuxt UI Built with Nuxt + Nuxt UI & BigHouse
</UContainer> </UContainer>
</footer> </footer>
</div> </div>

73
app/layouts/not-login.vue

@ -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>

29
app/pages/index/index.vue

@ -5,32 +5,7 @@ const userCount = computed(() => data.value?.users?.length ?? 0)
</script> </script>
<template> <template>
<div>
<h1>Person Panel</h1> sad
<UAlert title="Heads up!" />
<p v-if="pending">加载中...</p>
<div v-else-if="error">
<p>接口请求失败{{ error.message }}</p>
<button type="button" @click="refresh()">重试</button>
</div> </div>
<section v-else>
<p>hello: {{ data?.hello }}</p>
<p>users count: {{ userCount }}</p>
<button type="button" @click="refresh()">刷新数据</button>
<div v-if="Array.isArray(data?.users)">
<ul>
<li v-for="user in data.users" :key="user.id">
<span v-if="user.name">姓名{{ user.name }}</span>
<span v-if="user.email" style="margin-left: 1em;">邮箱{{ user.email }}</span>
<span v-if="user.age !== undefined" style="margin-left: 1em;">年龄{{ user.age }}</span>
</li>
</ul>
</div>
<div v-else>
<p>暂无用户信息</p>
</div>
</section>
</template> </template>

113
app/pages/login/index.vue

@ -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…
Cancel
Save