Browse Source

feat: create AppShell component and refactor layouts for improved structure

Introduce a new AppShell component to encapsulate the main application layout, including header, footer, and menu items. Refactor existing layouts (default and not-login) to utilize the new AppShell component, enhancing code reusability and maintainability. Update the blank layout for a simplified structure. Modify index, login, and register pages for consistent styling and improved user experience.
feat/auth-access-control
npmrun 2 days ago
parent
commit
02e23050d5
  1. 83
      app/components/AppShell.vue
  2. 7
      app/layouts/blank.vue
  3. 80
      app/layouts/default.vue
  4. 76
      app/layouts/not-login.vue
  5. 61
      app/pages/index/index.vue
  6. 25
      app/pages/login/index.vue
  7. 25
      app/pages/register/index.vue

83
app/components/AppShell.vue

@ -0,0 +1,83 @@
<script setup lang="ts">
withDefaults(
defineProps<{
showAuthActions?: boolean
}>(),
{
showAuthActions: false,
},
)
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',
},
],
},
],
]
</script>
<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>
<div v-if="showAuthActions" class="flex items-center gap-2">
<UButton color="neutral" variant="ghost" to="/login" label="登录" />
<UButton color="neutral" variant="outline" to="/register" label="注册" />
</div>
</UContainer>
</header>
<main class="flex-1">
<UContainer class="py-8">
<slot />
</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>

7
app/layouts/blank.vue

@ -0,0 +1,7 @@
<template>
<UApp>
<div class="min-h-screen bg-gradient-to-br from-neutral-50 via-white to-neutral-100 text-default">
<slot />
</div>
</UApp>
</template>

80
app/layouts/default.vue

@ -1,77 +1,5 @@
<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>
<div class="flex items-center gap-2">
<UButton color="neutral" variant="ghost" to="/login" label="登录" />
<UButton color="neutral" variant="outline" to="/register" label="注册" />
</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: "/",
},
],
},
],
]
</script>
<AppShell>
<slot />
</AppShell>
</template>

76
app/layouts/not-login.vue

@ -1,73 +1,5 @@
<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: "/",
},
],
},
],
]
</script>
<AppShell show-auth-actions>
<slot />
</AppShell>
</template>

61
app/pages/index/index.vue

@ -19,23 +19,66 @@ async function logout() {
</script>
<template>
<div class="max-w-3xl mx-auto py-10">
<div class="max-w-5xl mx-auto py-8 space-y-6">
<UCard v-if="loggedIn">
<template #header>
<h1 class="text-xl font-semibold">欢迎回来</h1>
<div class="flex items-start justify-between gap-4">
<div>
<h1 class="text-2xl font-semibold">欢迎回来{{ user?.username }}</h1>
<p class="text-sm text-muted mt-1">你已登录可访问站内受保护内容</p>
</div>
<UBadge color="success" variant="subtle">已登录</UBadge>
</div>
</template>
<p class="text-sm text-muted">当前用户{{ user?.username }}</p>
<UButton class="mt-4" color="neutral" :loading="logoutLoading" @click="logout">
退出登录
</UButton>
<div class="grid grid-cols-1 md:grid-cols-3 gap-3">
<UCard>
<p class="text-xs text-muted">状态</p>
<p class="mt-1 font-medium">会话有效</p>
</UCard>
<UCard>
<p class="text-xs text-muted">入口</p>
<p class="mt-1 font-medium">首页 / 配置 / API</p>
</UCard>
<UCard>
<p class="text-xs text-muted">建议</p>
<p class="mt-1 font-medium">可继续扩展个人中心</p>
</UCard>
</div>
<div class="mt-5 flex items-center gap-3">
<UButton color="neutral" :loading="logoutLoading" @click="logout">
退出登录
</UButton>
<UButton to="/api/config/me" target="_blank" variant="outline">
打开受保护接口
</UButton>
</div>
</UCard>
<UCard v-else>
<template #header>
<h1 class="text-xl font-semibold">欢迎来到本站</h1>
<div class="flex items-start justify-between gap-4">
<div>
<h1 class="text-2xl font-semibold">欢迎来到本站</h1>
<p class="text-sm text-muted mt-1">当前为访客模式仅开放首页与认证页面</p>
</div>
<UBadge color="neutral" variant="subtle">未登录</UBadge>
</div>
</template>
<p class="text-sm text-muted">登录后可访问更多受保护内容</p>
<div class="mt-4 flex gap-3">
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<UCard>
<p class="text-xs text-muted">访问策略</p>
<p class="mt-1 font-medium">登录优先白名单放行</p>
</UCard>
<UCard>
<p class="text-xs text-muted">安全能力</p>
<p class="mt-1 font-medium">API 默认鉴权 + 401 统一处理</p>
</UCard>
</div>
<div class="mt-5 flex gap-3">
<UButton to="/login">去登录</UButton>
<UButton to="/register" color="neutral" variant="outline">去注册</UButton>
</div>

25
app/pages/login/index.vue

@ -6,7 +6,7 @@ import { useAuthSession } from '../../composables/useAuthSession'
definePageMeta({
title: '登录',
layout: 'not-login',
layout: 'blank',
})
type LoginFormState = {
@ -89,12 +89,20 @@ const onSubmit = async (_event: FormSubmitEvent<LoginFormState>) => {
</script>
<template>
<div class="max-w-md mx-auto py-10">
<UCard>
<div class="min-h-screen flex items-start justify-center px-4 pt-14 pb-8 md:pt-20">
<div class="w-full max-w-md space-y-4">
<div class="text-center space-y-1">
<NuxtLink to="/" class="inline-flex items-center gap-2 text-sm text-muted hover:text-primary transition-colors">
<UIcon name="i-lucide-arrow-left" class="size-4" />
返回首页
</NuxtLink>
</div>
<UCard class="shadow-lg">
<template #header>
<div class="space-y-1">
<h1 class="text-xl font-semibold">欢迎登录</h1>
<p class="text-sm text-muted">请输入用户名和密码继续</p>
<div class="space-y-1 text-center">
<h1 class="text-2xl font-semibold">欢迎登录</h1>
<p class="text-sm text-muted">请输入用户名和密码继续使用</p>
</div>
</template>
@ -131,11 +139,12 @@ const onSubmit = async (_event: FormSubmitEvent<LoginFormState>) => {
class="mt-4"
/>
<div class="mt-4 flex items-center justify-between text-sm">
<div class="mt-4 flex items-center justify-center text-sm">
<NuxtLink to="/register" class="text-primary hover:underline">
去注册
没有账号去注册
</NuxtLink>
</div>
</UCard>
</div>
</div>
</template>

25
app/pages/register/index.vue

@ -5,7 +5,7 @@ import { normalizeSafeRedirect } from '../../utils/auth-routes'
definePageMeta({
title: '注册',
layout: 'not-login',
layout: 'blank',
})
type RegisterFormState = {
@ -79,12 +79,20 @@ const onSubmit = async (_event: FormSubmitEvent<RegisterFormState>) => {
</script>
<template>
<div class="max-w-md mx-auto py-10">
<UCard>
<div class="min-h-screen flex items-start justify-center px-4 pt-14 pb-8 md:pt-20">
<div class="w-full max-w-md space-y-4">
<div class="text-center space-y-1">
<NuxtLink to="/" class="inline-flex items-center gap-2 text-sm text-muted hover:text-primary transition-colors">
<UIcon name="i-lucide-arrow-left" class="size-4" />
返回首页
</NuxtLink>
</div>
<UCard class="shadow-lg">
<template #header>
<div class="space-y-1">
<h1 class="text-xl font-semibold">创建账号</h1>
<p class="text-sm text-muted">请输入用户名和密码完成注册</p>
<div class="space-y-1 text-center">
<h1 class="text-2xl font-semibold">创建账号</h1>
<p class="text-sm text-muted">用户名 3-20 密码至少 6 </p>
</div>
</template>
@ -121,11 +129,12 @@ const onSubmit = async (_event: FormSubmitEvent<RegisterFormState>) => {
class="mt-4"
/>
<div class="mt-4 flex items-center justify-between text-sm">
<div class="mt-4 flex items-center justify-center text-sm">
<NuxtLink to="/login" class="text-primary hover:underline">
已有账号去登录
已有账号去登录
</NuxtLink>
</div>
</UCard>
</div>
</div>
</template>

Loading…
Cancel
Save