创建项目
运行如下命令
npx create-next-app next-create
创建项目中出现的各种提示直接走默认的就行,一直回车就行了
创建完成后进入到项目运行localhost:3000访问页面,如果和我下面页面一样就是创建项目成功了
整理项目
- 将app/globals.css里面的样式都删除,只留下最上面三行即可
- 将app/layout.js的类名添加一个补充,换成 className={
${inter.className} h-screen
} 相当于给了高度100vh撑满
页面与布局
将app/Layout.js进行改造
import {
Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({
subsets: ["latin"] });
export const metadata = {
title: "Create Next App", // 网站标题
description: "Generated by create next app", // 描述信息
};
export default function RootLayout({
children }) {
return (
<html lang="en">
<body className={
inter.className}>
app下面的layout
{
children}</body>
</html>
);
}
新建app/user/Layout.js存入以下内容
export default function userLayout({
children }) {
return (
<section>
user下面的layout
{
children}
</section>
);
}
访问localhost:3000
访问localhost:3000/user
通过对比可以发现,app下面的就是公共的根样式,下面每个Layout.js都会继承到,然后每个文件夹下都可以定义当前路由页面的样式
路由创建
静态路由
在app下面新建demo文件夹,在里面新建page.js和layout.js,内容如下
// page.js
export default function(){
return <div>12121</div>
}
// layout.js
export default function({
children}){
return <div>我是demo的母版 <br/>{
children}</div>
}
访问:http://localhost:3000/demo
效果图
说明:app下面每个文件夹名称都是一个路由,每个文件夹下面的page.js就是代表当前文件夹的页面,每个文件夹又都有自己的layout.js(layout.js不是必须的,页面内不写这个文件也没关系),里面默认的参数children就是当前页面的元素,也可以说layout.js就相当于当前页面的母版,每个路由都有自己的layout.js但是app下面的layout.js是全局公共的
如果demo下面还有list这个页面,则在demo文件夹内list文件夹,然后在里面创建page.js文件即可,在里面写内容后,页面上面直接访问localhost:3000/demo/list即可
动态路由
当我们访问localhost:3000/demo/list/1或者localhost:3000/demo/list/2这种动态路由怎么实现呢???
这就要用到动态参数了
在app/demo/list下面新建[id]文件夹,这种文件夹名字是[]包裹的就是动态参数了
例如我们的[id]文件夹里面创建page.js内容如下
export default function({
params}){
return <div>动态参数的值为{
params.id}</div>
}
当访问http://localhost:3000/demo/list/2001时,页面效果如下
上面有个弊端就是只支持一级动态参数,如果希望多级的话可以将[id]文件名换成[…id]这样就是可以匹配到后面所有参数,访问localhost:3000/demo/list/2/3/4/5
效果图
路由组
项目下新建三个路径文件
- app/(marketing)/about/page.js
- app/(marketing)/bolg/page.js
- app/(marketing)/(shop)/acconut/page.js
在每个page.js里面随便写点内容,访问以下路径
- localhost:3000/about
- localhost:3000/bolg
- localhost:3000/account
可以发现都能被访问到,总结规律就是文件夹名字带括号的相当于可以忽略了
路由组不参与url的设定的
个人感觉唯一作用是用于设置共同的Layout.js
创建如下两个Layout.js文件
- app/(marketing)/Layout.js
- app/(marketing)/(shop)/Layout.js
在这两个里面添加如下代码
export default function userLayout({
children }) {
return (
<section>
marketing下面的layout
{
children}
</section>
);
}
export default function userLayout({
children }) {
return (
<section>
marketing下面shop的layout
{
children}
</section>
);
}
运行后会发现,marking的Layout,js被它里面所有文件所共用,shop里面的Layout.js被shop里面的文件所共用,因为这个案例shop在marking里面的,因此shop里面的文件也共用marking里面的样式,这就是路由组,按照上面传统的方式建路由,需要每个文件单独设置自己的Layout.js,使用路由组可以达到复用性
路由跳转和传参以及接收参数
跳转和传参
这里跳转的路径为app/list/[id]/page.js
'use client';
import Link from "next/link";
import {
useRouter } from "next/navigation";
function Home({
items, time }) {
const router = useRouter()
return (
<div className="Home">
<Link href={
'/list/1'}>跳转到xiaoji页面</Link>
<br></br>
<Link href={
'/list/1?name=萧寂'}>跳转到xiaoji页面(带参数)</Link>
<hr></hr>
<button onClick={
()=>{
router.push('/list/1')}}>跳转到xiaoji页面</button>
<br></br>
<button onClick={
()=>{
router.push('/list/1?name=萧寂')}}>跳转到xiaoji页面(第一种带参数)</button>
<button onClick={
()=>{
router.push({
pathname:'/list/1',
query:{
name:'萧寂'
}
})}}>跳转到xiaoji页面(第二种带参数)</button>
</div>
);
}
export default Home;
接收参数
app/list/[id]/page.js里面代码如下
export default async function({
params,searchParams}){
await new Promise((resolve) => setTimeout(resolve, 2000));
return <div>动态参数的值为{
params.id},query动态的搜索参数为{
searchParams.name}</div>
}
当我在页面输入地址localhost:3000/list/3?name=xiaoji,效果如下:
Loadding加载和流的处理
1.在app下面新建loading.js组件
export default function(){
return <div className={
"text-2xl text-pink-400"}>Loading...</div>
}
2.修改app/Layout.js
import {
Inter } from "next/font/google";
import {
Suspense } from "react";
import Loading from './loading'; // 引入app/loading.js
import "./globals.css";
const inter = Inter({
subsets: ["latin"] });
export const metadata = {
title: "Create Next App", // 网站标题
description: "Generated by create next app", // 描述信息
};
export default function RootLayout({
children }) {
return (
<html lang="en">
<body className={
inter.className}>
<Suspense fallback={
<Loading></Loading>}> // 2.使用SusPense将页面包裹
app下面的layout
{
children}
</Suspense>
</body>
</html>
);
}
子组件代码
这里使用了async/await模拟了一下异步,这是个细节,因为上面的loadding效果如果要出来的话,页面数据必须要是有异步效果,因为我没注意到这点,费了点时间才搞明白
export default async function Posts() {
await new Promise((resolve) => setTimeout(resolve, 2000));
return <div>1111</div>;
}
注意:将Lodding放到app/Layout.js里面包裹的话,则针对所有页面生效,如果某个页面有不一样的loading效果的话,则需要在当前文件夹里面的Layout.js去单独引入对应的loading.js,可以在当前文件夹里面创建个loading.js,这样的话loading.js的样式仅仅作用于当前文件夹下的所有页面
import {
Suspense } from "react";
export default function userLayout({
children }) {
return (
<section>
// 这个loading效果仅作用于当前文件夹下面的所有页面
<Suspense fallback={
<div className={
"text-2xl text-pink-400"}>Loading...</div>}>
user下面的layout
{
children}
</Suspense>
</section>
);
}
注意:必须是渲染的页面内有异步操作(如async/await)才会有Loading.js效果
错误处理
新建app/error.js,放入以下内容
'use client'
export default function({
error,reset}){
return (
<div>
<h2>我是全局的错误样式处理</h2>
<button onClick={
()=>reset()}>重试一下</button>
</div>
)
}
也可以对每个页面单独定义路由样式,只需要在目标页面的文件夹内新建error.js,放入以下内容即可
例如我在app/user/error.js内加入以下内容
'use client'
export default function({
error,reset}){
return (
<div>
<h2>app/user 页面内有错误啦!!!</h2>
<button onClick={
()=>reset()}>重试一下</button>
</div>
)
}
例如我们在目标的user页面加入一些错误信息
export default function Posts() {
console.log('a',a); // 这里没有a变量,因此这里会报错
return <div>1111</div>;
}
当我们在浏览器访问localhost:3000/user就会报出以下错误
当我们访问其他页面有错误信息时,但是没有给那个页面单独定义错误样式,则会触发全局的错误样式
例如访问: localhost:3000/about
链接和导航
修改下app/page.js内容如下
'use client';
import Link from "next/link";
import {
useRouter } from "next/navigation";
export default function Home() {
const router = useRouter()
return (
<>
<h1 className="text-4xl text-orange-600">Hello Name</h1>
<br></br>
<Link href={
"/user"}>跳转到user路由</Link>
<br></br>
<button onClick={
()=>{
router.push('/user')}}>点击跳转/user</button>
</>
);
}
滚动到新路由的指定位置处,相当于锚点链接
<Link href="/dashboard#settings">Settings</Link>
// Output
<a href="/dashboard#settings">Settings</a>
平行路由(Parallel Routes)
在app下面新建@home和@setting文件夹,里面都新建一个page.js文件,在里面写一点页面
在app/Layout.js里面改为如下页面代码
import {
Inter } from "next/font/google";
import {
Suspense } from "react";
import Loading from './loading';
import "./globals.css";
const inter = Inter({
subsets: ["latin"] });
export const metadata = {
title: "Create Next App", // 网站标题
description: "Generated by create next app", // 描述信息
};
export default function RootLayout({
children,home,setting }) {
return (
<html lang="en">
<body className={
inter.className}>
<Suspense fallback={
<Loading></Loading>}>
app下面的layout
{
home}
{
children}
{
setting}
</Suspense>
</body>
</html>
);
}
可以发现组件效果已经出来了
这里有个注意点,使用了平行路由后,这个文件夹内不要创建别的路由文件夹了,因为就算创建了后面的路径也会报错一堆404,我不知道是正常情况还是异常情况,本人就这样理解的,因此把所有的需要用到平行路由的界面我都归类为最终页面了
组件化渲染
下面是可以应用到我们页面里面的组件的案例
在app平级处创建components/frame/index.js,放入以下内容
import Image from "next/image";
export default function({
photo}){
console.log('photo',photo);
return <>
<Image src={
photo.src} alt="" width={
600} height={
600} className={
'w-full object-cover aspect-square col-span-2 w-28'}></Image>
</>
}
在app里面新建photo/page.js文件,插入以下内容
import Photo from "@/components/frame" // 引入组件
export default function(){
const photo = {
src:'https://take-saas.oss-cn-hangzhou.aliyuncs.com/wechat_applets/coach/bgcimg/bgc-13.png'}
// 给组件传值
return <Photo photo={
photo}></Photo>
}
注意:这里可能会报错图片问题(网络图片需要加一下白名单才能正常加载,如下在next.config.mjs里面进行配置)
/** @type {import('next').NextConfig} */
const nextConfig = {
images:{
domains:['take-saas.oss-cn-hangzhou.aliyuncs.com'] // 这里是存放域名白名单处
}
};
export default nextConfig;
然后就可以看到图片正常加载了
定义404页面
在app下面新建not-found.js,放入以下内容
export default function(){
return <div className={
"text-2xl text-pink-400"}>访问页面不存在...</div>
}
当页面访问一个不存在的页面路由时,页面显示效果如下
自定义页面网站标题(有利于SEO)
静态设置meta标签
在任意page.js内都按照下面这样写就行
export const metadata = {
title: "我是list页面", // 页面标题
description: "list页面描述", // 页面描述
keywords:"list页面,nextjs开发,测试页面" // 关键词
};
export default function(){
return <div>list</div>
}
只要在页面内部导出metadata 即可,里面写好网站title和描述信息,这是一种约束,nextjs默认你导出这个就是设置title和描述的
效果图
动态设置meta标签
例如我在app/list/[id]/page.js加入以下代码
export async function generateMetadata({
params,searchParams}){
// 这个方法是异步方法
return {
title:`这是动态参数:${
params.id}-${
searchParams.name}`, // 这个id其实就是动态路由的动态参数,跟下面的页面接收到的params值一样
}
}
export default async function({
params,searchParams}){
await new Promise((resolve) => setTimeout(resolve, 2000));
return <div>动态参数的值为{
params.id},query动态的搜索参数为{
searchParams.name}</div>
}
在页面输入地址localhost:3000/list/3?name=xiaoji,效果如下:
编写Api接口
静态路径
在app下面新建api文件夹,在里面新建goods文件夹在里面再次新建route.js文件,放入以下内容
主意api文件夹一定是要放到app文件夹下面的,并且接口文件都是route.js命名跟page.js一样是约定名称,固定的
import {
NextRequest,NextResponse } from "next/server";
export const GET = ()=>{
return NextResponse.json({
succes:true,
errorMessage:"获取数据",
data:{
}
})
}
重启项目访问http://localhost:3000/api/goods,效果如下
动态路径
在app下面新建api文件夹,在里面新建goods文件夹,再新建[id]文件夹,在里面再次新建route.js文件,放入以下内容
import {
NextRequest,NextResponse } from "next/server";
export const GET = (req,{
params})=>{
return NextResponse.json({
succes:true,
errorMessage:"获取单条记录"+params.id,
data:{
}
})
}
浏览器访问:http://localhost:3000/api/goods/123
发送接口请求
注意:在app文件夹下面所有文件夹名字前面带_的都不会被自动解析为路由
- 在app/list下面新建_components作为组件文件夹,在里面新建list.js写客户端组件进行发请求,代码如下:
"use client"
import {
useEffect,useState } from "react"
export default function(){
const [data,setData] = useState([])
useEffect(()=>{
fetch("api/goods").then((res)=>res.json()).then((res)=>{
setData(res.data)
})
},[])
return <div>
<ul>
{
data.map((item,index)=>{
return <li key={
item.id}>{
item.name}</li>
})
}
</ul>
</div>
}
- 在app/list/page.js里面引入组件
import List from "./_components/list";
export default function(){
return <List></List>
}
- 访问http://localhost:3000/list,效果图如下
数据库引入
在前端使用这个用于操作数据库的库https://www.prisma.io/
1.安装库
yarn add prisma --save-dev
// 下面这个安装官网没有,但是我报了一个错误是因为没有这个模块导致的,如果跟我一样报错的话就手动安装一下
yarn add @prisma/client
2.初始化数据库(sqlite数据库)
npx prisma init --datasource-provider sqlite
3.在schema.prisma文件内新增一个model模型,也就是表
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
// 新增如下
model Goods{
id String @id @unique @default(uuid())
name String
desc String @default("")
content String @default("")
createAt DateTime @default(now()) @map("create_at") // @map起别名,默认当前时间
updateAt DateTime @updatedAt @map("update_at")
@@map("products") // 表名
}
4.生成数据库(项目下终端执行命令)
npx prisma db push
5.这时候就会看到prisma下面多了一个dev.db数据库文件了
6.使用数据库软件navicat打开这个数据库就可以发现刚刚我们新建的表
7.连接数据库(在prisma官网搜Solution这个关键词,下面这段代码即可,也可以直接复制我的也是一样的)
app同级位置新建文件db.js放入以下内容
import {
PrismaClient } from '@prisma/client'
const prismaClientSingleton = () => {
return new PrismaClient()
}
const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()
export default prisma
if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma
8.将刚刚的/api/good/route.js文件做修改,做两个接口,新增和修改,代码如下
import {
NextRequest,NextResponse } from "next/server";
import prisma from '@/db'
export const GET = async ()=>{
// 查询数据,根据时间倒序排序
// 创建的模型名称小写的(goods)
const data =await prisma.goods.findMany({
orderBy:{
createAt:"desc",
}
})
return NextResponse.json({
succes:true,
successMessage:"获取数据成功",
data
})
}
// 新增数据
export const POST = async (req)=>{
const data = await req.json() // 获取请求体中传递的json数据
await prisma.goods.create({
data,
})
return NextResponse.json({
succes:true,
successMessage:"创建数据成功",
data:{
}
})
}
9:使用apiFox等工具添加数据(post请求,端口号3000)
接口地址为:http:localhost:3000/api/goods
参数:点击body->json->输入{name:‘萧寂’}
发请求:点击send
10:数据创建成功后刷新下数据库,使用数据库软件查看数据
11:在浏览器访问上面发送接口请求的那个页面,看看是否显示了数据
访问:http:localhost:3000/list
可以发现,数据确实被加进来了,至此添加和查询数据的接口准备完毕
项目阶段(做个管理后台)
引入Antd组件库
官方网站
安装
yarn add antd
随便找个页面放入antd组件查看页面是否有效果,我这里在app/page.js放入以下内容
import React from 'react';
import {
DatePicker } from 'antd';
const App = () => {
return <DatePicker />;
};
export default App;
浏览器访问:http://localhost:3000/
效果图
这样就代表组件库已经引入进来了
但是如果发现样式和antd样式不一致,就是tailwindcss和其样式冲突了,我们需要在tailwind.config.js进行配置
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
corePlugins:{
preflight:false // 新增,页面就正常了
}
};
项目结构搭建和配置
在app下面新建admin文件夹
在admin下面新建_components/AntdAdmin.js组件作为管理系统主页面容器,内容如下(这个后台管理布局直接从antd组件库的layout直接复制粘贴来的)
"use client";
import {
Button, Layout, Menu, theme } from "antd";
import "antd/dist/reset.css";
import {
MenuFoldOutlined,
MenuUnfoldOutlined,
UploadOutlined,
UserOutlined,
VideoCameraOutlined,
} from "@ant-design/icons";
import {
useState } from "react";
const {
Header, Sider, Content } = Layout;
export default function ({
children }) {
const [collapsed, setCollapsed] = useState(false);
const {
token: {
colorBgContainer, borderRadiusLG },
} = theme.useToken();
return (
// 设置语言包
<>
<Layout style={
{
height:'100vh'}}>
<Sider trigger={
null} collapsible collapsed