落地页
调整页面默认高度
在进行接下来的内容之前,让我们先来修改一个next.js框架的默认设置。
为了了解前后差别,我们先给app目录下面的page.tsx
中的外层div
添加一个h-full bg-black
的样式:
import { Button } from "@/components/ui/button";
export default function Home() {
return (
<div className="flex space-x-2 m-4 h-full bg-slate-200">
<Button variant="default">default</Button>
<Button variant="destructive">destructive</Button>
<Button variant="outline">outline</Button>
<Button variant="secondary">secondary</Button>
<Button variant="ghost">ghost</Button>
<Button variant="link">link</Button>
<Button variant="theme">Theme</Button>
</div>
);
}
然后来看下效果:
可以看到,尽管我们给最外层div
设置了h-full
高度占满屏幕,它还是只作用到了div里面内容的高度。
现在我们在app下面的globals.css
中增加一段代码,就可以让高度撑满整个屏幕了:
@tailwind base;
@tailwind components;
@tailwind utilities;
html,
body,
:root {
height: 100%;
}
body {
font-family: Arial, Helvetica, sans-serif;
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
/* 原来的其他代码... */
这时候再看首页的效果,就是这样了:
创建一个落地页
现在,让我们将app目录下的page.tsx
文件删掉,然后创建一个(marketing)
目录,用来放我们的落地页相关文件。由于我们这个项目是以用户鉴权为主的,所以我们就简单让AI来帮我们生成一个落地页,再稍微改一下内容即可。
这个落地页主要包含navbar、hero、features、stats四个组件,我们先在(marketing)
目录下创建_components
目录,用于存放与落地页相关的组件,然后在_components
目录下分别创建四个组件:
导航栏Navbar组件
import { Button } from "@/components/ui/button";
import Link from "next/link";
export function Navbar() {
return (
<header className="border-b">
<div className="container mx-auto px-4">
<div className="flex h-16 items-center justify-between">
<div className="flex gap-6 items-center">
<Link href="/" className="font-bold text-xl">
DevDocLib
</Link>
</div>
<div className="flex items-center gap-4">
<Button variant="ghost" asChild>
<Link href="/sign-in">登录</Link>
</Button>
<Button asChild>
<Link href="/sign-up">注册</Link>
</Button>
</div>
</div>
</div>
</header>
);
}
头部Hero组件
import { Button } from "@/components/ui/button";
import { ArrowRight } from "lucide-react";
import Link from "next/link";
export const Hero = () => {
return (
<section className="flex flex-col items-center justify-center px-4 py-20 text-center space-y-8">
<h1 className="text-6xl font-bold tracking-tight">
完整用户鉴权功能<br />
</h1>
<h1 className="text-6xl font-bold tracking-tight">
快速启动你的Next.js项目
</h1>
<p className="text-xl text-muted-foreground max-w-2xl">
使用最新的技术栈快速开发安全、可扩展的应用程序,包含完整的用户认证和数据库集成
</p>
<div className="flex gap-4">
<Button variant="theme" size="lg" asChild>
<Link href="/sign-up">
立即开始 <ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
</section>
)
}
特性features组件
import { CheckCircle } from "lucide-react";
const features = [
{
title: "Next.js 14 & TypeScript",
description: "使用最新的Next.js 14和TypeScript,确保类型安全和最佳性能",
icon: CheckCircle,
},
{
title: "Auth.js认证系统",
description: "集成Auth.js实现完整的用户认证流程,支持多种登录方式",
icon: CheckCircle,
},
{
title: "Shadcn UI组件库",
description: "使用shadcn/ui和Tailwind CSS构建美观且响应式的界面",
icon: CheckCircle,
},
{
title: "Prisma ORM数据库集成",
description: "通过Prisma ORM实现类型安全的数据库操作",
icon: CheckCircle,
},
{
title: "Server action",
description: "使用Next.js server action实现用户验证功能",
icon: CheckCircle,
},
{
title: "阿里云OSS图像存储",
description: "使用阿里云OSS实现用户头像存储变更功能",
icon: CheckCircle,
},
];
export const Features = () => {
return (
<section className="py-20 bg-slate-50">
<div className="container mx-auto px-4">
<h2 className="text-3xl font-bold text-center mb-12">技术特性</h2>
<div className="grid md:grid-cols-3 gap-8">
{features.map((feature, index) => (
<div key={index} className="p-6 bg-white rounded-lg shadow-sm">
<feature.icon className="h-12 w-12 text-primary mb-4" />
<h3 className="text-xl font-semibold mb-2">{feature.title}</h3>
<p className="text-muted-foreground">{feature.description}</p>
</div>
))}
</div>
</div>
</section>
);
}
统计stats组件
export const Stats = () => {
return (
<section className="py-20">
<div className="container mx-auto px-4">
<div className="grid md:grid-cols-3 gap-8 text-center">
<div>
<div className="text-4xl font-bold text-primary">100%</div>
<p className="text-muted-foreground">类型安全</p>
</div>
<div>
<div className="text-4xl font-bold text-primary">现代化</div>
<p className="text-muted-foreground">技术栈</p>
</div>
<div>
<div className="text-4xl font-bold text-primary">快速</div>
<p className="text-muted-foreground">开发体验</p>
</div>
</div>
</div>
</section>
);
}
创建MarketingLayout
实践中,一般会把navbar组件放在layout中,其他部分则放在page中作为main标签里面的内容,所以我们需要在(marketing)
目录下创建一个layout.tsx
:
import { Navbar } from "./_components/navbar";
export default function MarketingLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<>
<Navbar />
<main>
{children}
</main>
</>
);
}
在Marketing page中引入各个组件
然后在page.tsx
中引入我们前面创建的Hero、Features和Stats组件:
import { Hero } from "./_components/hero";
import { Features } from "./_components/features";
import { Stats } from "./_components/stats";
export default function Home() {
return (
<div className="flex flex-col min-h-screen">
<Hero />
<Features />
<Stats />
</div>
);
}
项目目录结构
现在,我们项目的app目录是这样的:
- favicon.ico
- globals.css
- layout.tsx
这时候访问首页 http://localhost:3000 ,看到的应该是这个效果:
设置项目主题色
颜色稍微有点素,其实我们仔细看ai给我们生成的代码,里面有一些text-primary
之类的样式是没有生效的,这是一些主题设置。那如何给我们的网站设置主题色呢?
我们可以去shadcn的官网,找到theme,然后选择一个颜色,复制对应的代码到app目录下的globals.css中即可。这里我们选择这个蓝色:
然后点击Copy code
,复制这些代码:
然后找到app目录下的globals.css文件,替换掉里面原来的@layer base
设置:
@tailwind base;
@tailwind components;
@tailwind utilities;
html,
body,
:root {
height: 100%;
}
body {
font-family: Arial, Helvetica, sans-serif;
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.75rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 224.3 76.3% 48%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
现在,落地页就变成了这样:
小标题和一些图标的颜色就变成我们设置的蓝色主题色了。但是还存在一个问题,就是我们最开始自定义的theme button颜色跟主题色不一致了,不用担心,我们再看一下button组件中的default
样式代码使用的就是primary
主题色,所以我们现在就可以把自定义的theme样式删掉了:
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
// theme: "bg-indigo-500 text-white hover:bg-indigo-700" 删掉这一行
},
然后同样删掉navbar和hero两个组件中设置的theme variant即可。
Navbar组件:
import { Button } from "@/components/ui/button";
import Link from "next/link";
export function Navbar() {
return (
<header className="border-b">
<div className="container mx-auto px-4">
<div className="flex h-16 items-center justify-between">
<div className="flex gap-6 items-center">
<Link href="/" className="font-bold text-xl">
DevDocLib
</Link>
</div>
<div className="flex items-center gap-4">
<Button variant="ghost" asChild>
<Link href="/sign-in">登录</Link>
</Button>
<Button asChild>
<Link href="/sign-up">注册</Link>
</Button>
</div>
</div>
</div>
</header>
);
}
Hero组件:
import { Button } from "@/components/ui/button";
import { ArrowRight } from "lucide-react";
import Link from "next/link";
export const Hero = () => {
return (
<section className="flex flex-col items-center justify-center px-4 py-20 text-center space-y-8">
<h1 className="text-6xl font-bold tracking-tight">
完整用户鉴权功能<br />
</h1>
<h1 className="text-6xl font-bold tracking-tight">
快速启动你的Next.js项目
</h1>
<p className="text-xl text-muted-foreground max-w-2xl">
使用最新的技术栈快速开发安全、可扩展的应用程序,包含完整的用户认证和数据库集成
</p>
<div className="flex gap-4">
<Button size="lg" asChild>
<Link href="/sign-up">
立即开始 <ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
</section>
)
}
现在,整体颜色就统一了:
添加背景渐变色
由于白色背景显得有些空,我们再在layout中中的main标签中添加一个浅色的渐变背景:
import { Navbar } from "./_components/navbar";
export default function MarketingLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<>
<Navbar />
<main className="pt-20 bg-gradient-to-r from-blue-100/50 via-indigo-100/50 to-violet-50/50">
{children}
</main>
</>
);
}
再优化下navbar的样式,增加一个半透明和固定到顶部的效果:
import { Button } from "@/components/ui/button";
import Link from "next/link";
export function Navbar() {
return (
<header className="fixed top-0 w-full h-20 px-4 md:px-8 py-2.5 bg-white/75 backdrop-blur-sm border-b border-gray-200/20 flex items-center z-50">
<div className="container mx-auto px-4">
<div className="flex h-16 items-center justify-between">
<div className="flex gap-6 items-center">
<Link href="/" className="font-bold text-xl">
DevDocLib
</Link>
</div>
<div className="flex items-center gap-4">
<Button variant="ghost" asChild>
<Link href="/sign-in">登录</Link>
</Button>
<Button asChild>
<Link href="/sign-up">注册</Link>
</Button>
</div>
</div>
</div>
</header>
);
}
这时候再看下首页,就好看多了:
好了,落地页到此为止。接下来,我们就要开始来实现项目的注册登录功能了。细心的朋友应该发现了,在我们的落地页上,有两个注册按钮,一个是navbar组件中的“注册”按钮,还有一个是hero组件中的“立即开始”按钮,这两个按钮都已经被添加了跳转到注册页(sign-up page)的链接。接下来,就让我们先来实现一下注册页面。