前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用nextjs进行CRUD开发

使用nextjs进行CRUD开发

原创
作者头像
程序员库里
发布2024-05-15 17:31:47
1030
发布2024-05-15 17:31:47
举报
文章被收录于专栏:全栈学习全栈学习

前言

创建项目

使用nextjs官方提供的脚手架创建一个项目模版

代码语言:bash
复制
npx create-next-app@latest next-crud --use-npm --example "https://github.com/vercel/next-learn/tree/main/dashboard/starter-example"
项目结构
项目结构

启动项目

代码语言:bash
复制
pnpm dev

添加全局样式

1.在app目录下创建global.css文件,并写入以下全局样式代码:

代码语言:css
复制
:root {
  --max-width: 1100px;
  --border-radius: 12px;
  --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
    'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
    'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;

  --foreground-rgb: 0, 0, 0;
  --background-start-rgb: 214, 219, 220;
  --background-end-rgb: 255, 255, 255;

  --primary-glow: conic-gradient(
    from 180deg at 50% 50%,
    #16abff33 0deg,
    #0885ff33 55deg,
    #54d6ff33 120deg,
    #0071ff33 160deg,
    transparent 360deg
  );
  --secondary-glow: radial-gradient(
    rgba(255, 255, 255, 1),
    rgba(255, 255, 255, 0)
  );

  --tile-start-rgb: 239, 245, 249;
  --tile-end-rgb: 228, 232, 233;
  --tile-border: conic-gradient(
    #00000080,
    #00000040,
    #00000030,
    #00000020,
    #00000010,
    #00000010,
    #00000080
  );

  --callout-rgb: 238, 240, 241;
  --callout-border-rgb: 172, 175, 176;
  --card-rgb: 180, 185, 188;
  --card-border-rgb: 131, 134, 135;
}

@media (prefers-color-scheme: dark) {
  :root {
    --foreground-rgb: 255, 255, 255;
    --background-start-rgb: 0, 0, 0;
    --background-end-rgb: 0, 0, 0;

    --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
    --secondary-glow: linear-gradient(
      to bottom right,
      rgba(1, 65, 255, 0),
      rgba(1, 65, 255, 0),
      rgba(1, 65, 255, 0.3)
    );

    --tile-start-rgb: 2, 13, 46;
    --tile-end-rgb: 2, 5, 19;
    --tile-border: conic-gradient(
      #ffffff80,
      #ffffff40,
      #ffffff30,
      #ffffff20,
      #ffffff10,
      #ffffff10,
      #ffffff80
    );

    --callout-rgb: 20, 20, 20;
    --callout-border-rgb: 108, 108, 108;
    --card-rgb: 100, 100, 100;
    --card-border-rgb: 200, 200, 200;
  }
}

* {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}

html,
body {
  max-width: 100vw;
  overflow-x: hidden;
}

body {
  color: rgb(var(--foreground-rgb));
  background: linear-gradient(
      to bottom,
      transparent,
      rgb(var(--background-end-rgb))
    )
    rgb(var(--background-start-rgb));
}

a {
  color: inherit;
  text-decoration: none;
}

@media (prefers-color-scheme: dark) {
  html {
    color-scheme: dark;
  }
}

2.在app/layout.tsx中引入

代码语言:typescript
复制
import '@/app/ui/global.css';
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={`${inter.className} antialiased`}>{children}</body>
    </html>
  );
}

字体和图像

设置全局字体

1.新建app/ui/fonts.ts,引入字体并导出

代码语言:typescript
复制
import { Inter, Lusitana } from 'next/font/google';

export const inter = Inter({ subsets: ['latin'] });

export const lusitana = Lusitana({
  weight: ['400', '700'],
  subsets: ['latin'],
});

2.将字体添加到 /app/layout.tsx 中的 <body> 元素:

代码语言:typescript
复制
import '@/app/ui/global.css';
import { inter } from '@/app/ui/fonts';
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={`${inter.className} antialiased`}>{children}</body>
    </html>
  );
}

可以看到设置的全局字体了

全局字体
全局字体

设置图像

在nextjs中可以使用next/image设置图像。

1.将图片文件放到 public文件夹下

2.使用next/image组件,引入

代码语言:typescript
复制
import Image from 'next/image';

<Image        
    src="/hero-desktop.png"        
    width={1000}        
    height={760}        
    className="hidden md:block"        
    alt="Screenshots of the dashboard project showing desktop version"      
/>

创建布局和页面

嵌套路由

Next.js 使用文件系统路由,其中文件夹用于创建嵌套路由。每个文件夹代表一个映射到 URL 段的路由段。

路由系统
路由系统

1.nextjs默认 app/page.tsx 是根路由

2.新建 app/dashboard/page.tsx文件

代码语言:typescript
复制
export default function Page() {
  return <p>Dashboard Page</p>;
}

3.访问http://localhost:3000/dashboard 就是dashboard路由对应的页面了

创建布局

共享导航:在app/dashboard下面创建layout.tsx

代码语言:typescript
复制
import SideNav from '@/app/ui/dashboard/sidenav';

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex h-screen flex-col md:flex-row md:overflow-hidden">
      <div className="w-full flex-none md:w-64">
        <SideNav />
      </div>
      <div className="flex-grow p-6 md:overflow-y-auto md:p-12">{children}</div>
    </div>
  );
}

这样/dashboard下面的都会共享同一个布局,即同一个 SideNav 左侧导航

访问:http://localhost:3000/dashboard 查看效果

使用nextjs导航,当组件更新的时候,布局不会重新渲染

页面导航

现在导航切换使用a标签,点击会重新加载页面,使用Link标签替换

代码语言:typescript
复制
// /app/ui/dashboard/nav-links.tsx

import {
  UserGroupIcon,
  HomeIcon,
  DocumentDuplicateIcon,
} from '@heroicons/react/24/outline';
import Link from 'next/link';

// Map of links to display in the side navigation.
// Depending on the size of the application, this would be stored in a database.
const links = [
  { name: 'Home', href: '/dashboard', icon: HomeIcon },
  {
    name: 'Invoices',
    href: '/dashboard/invoices',
    icon: DocumentDuplicateIcon,
  },
  { name: 'Customers', href: '/dashboard/customers', icon: UserGroupIcon },
];

export default function NavLinks() {
  return (
    <>
      {links.map((link) => {
        const LinkIcon = link.icon;
        return (
          <Link
            key={link.name}
            href={link.href}
            className="flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3"
          >
            <LinkIcon className="w-6" />
            <p className="hidden md:block">{link.name}</p>
          </Link>
        );
      })}
    </>
  );
}

高亮当前链接

1.使用next/navigation提供的usePathname()

由于 usePathname() 是一个钩子,因此需要将 nav-links.tsx 转换为客户端组件。将 React 的 "use client" 指令添加到文件顶部,然后从 next/navigation 导入 usePathname() :

代码语言:typescript
复制
'use client';
 
import {
  UserGroupIcon,
  HomeIcon,
  InboxIcon,
} from '@heroicons/react/24/outline';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
 
// ...

2.获取当前pathname

代码语言:typescript
复制
const pathname = usePathname();

3.当 link.href 与 pathname 匹配时,链接以蓝色文本和浅蓝色背景显示。

代码语言:typescript
复制
className={clsx(
              'flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3',
              {
                'bg-sky-100 text-blue-600': pathname === link.href,
              },
            )}

创建数据库

1.创建github项目,推送代码

2.创建一个vercel帐号

3.在vercel中部署github项目

4.部署完成:https://next-crud-two-psi.vercel.app/dashboard

5.选择storage:Connect Store → Create New → Postgres → Continue.

6.获取数据库相关数据

7.将上面数据粘贴到.env中

8.安装postgres

代码语言:bash
复制
npm i @vercel/postgres

9.运行脚本创建数据表,把本地数据导入到vercel 数据库中

代码语言:bash
复制
"seed": "node -r dotenv/config ./scripts/seed.js"

10.可以从vercel数据库看到数据

可以查询数据:

安装Antd

代码语言:bash
复制
npm install antd --save

将 antd 首屏样式按需抽离并植入到 HTML 中,以避免页面闪动的情况。

1.安装 @ant-design/nextjs-registry

代码语言:bash
复制
npm install @ant-design/nextjs-registry --save

2.在 app/layout.tsx 中使用

代码语言:typescript
复制
import '@/app/ui/global.css';
import { inter } from '@/app/ui/fonts';
import { AntdRegistry } from '@ant-design/nextjs-registry';
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={`${inter.className} antialiased`}>
        <AntdRegistry>{children}</AntdRegistry>
      </body>
    </html>
  );
}

CRUD操作

查询数据

1.编写查询数据库方法

代码语言:typescript
复制
export async function fetchLatestInvoices() {
  noStore();
  try {
    const data = await sql<LatestInvoiceRaw>`
    SELECT  * FROM customers;`;

    return data.rows;
  } catch (error) {
    console.error('Database Error:', error);
    throw new Error('Failed to fetch the latest invoices.');
  }
}

2.在page.tsx中获取数据

代码语言:typescript
复制
data = await fetchLatestInvoices();

3.获取data后进行渲染

代码语言:typescript
复制
  <div className="bg-white px-6">
          {data.map((invoice, i) => (
            <div
              key={invoice.id}
              className={clsx(
                'flex flex-row items-center justify-between py-4',
                {
                  'border-t': i !== 0,
                },
              )}
            >
              <div className="flex items-center">
                <Image
                  src={invoice.image_url}
                  alt={`${invoice.name}'s profile picture`}
                  className="mr-4 rounded-full"
                  width={32}
                  height={32}
                />
                <div className="min-w-0">
                  <p className="truncate text-sm font-semibold md:text-base">
                    {invoice.name}
                  </p>
                  <p className="hidden text-sm text-gray-500 sm:block">
                    {invoice.email}
                  </p>
                </div>
              </div>
              <Delete invoice={invoice} handleDelete={handleDelete} />
              <p
                className={`${lusitana.className} truncate text-sm font-medium md:text-base`}
              >
                {invoice.amount}
              </p>
            </div>
          ))}
        </div>

新增数据

1.熟悉编写ui组件,一个输入框和一个按钮,用户在输入框输入内容,点击按钮调用插入数据的方法

编写客户端组件Add

代码语言:typescript
复制
'use client';
import React, { useState } from 'react';
import { Button, Input } from 'antd';

interface IProps {
  handleAdd: (value: string) => void;
}

const Add = (props: IProps) => {
  const { handleAdd } = props;
  const [value, setValue] = useState('');
  const handleClick = () => {
    handleAdd && handleAdd(value);
  };
  return (
    <>
      <Input onChange={(e) => setValue(e.target.value)} />
      <Button onClick={handleClick}>添加</Button>
    </>
  );
};
export default Add;

2.编写插入数据方法

代码语言:typescript
复制
export async function insertInvoices(name: string) {
  noStore();
  try {
    const data = await sql<LatestInvoiceRaw>`
    INSERT INTO customers (name, email, image_url)
VALUES (${name},'email','/customers/balazs-orban.png');`;

    return data.rows;
  } catch (error) {
    console.error('Database Error:', error);
    throw new Error('Failed to fetch the latest invoices.');
  }
}

3.在page.tsx中引入Add组件

代码语言:typescript
复制
   <Add handleAdd={handleAdd} />

3.编写handleAdd方法,这里注意因为page.tsx是服务端组件,所以在handleAdd方法中指明是服务端

代码语言:typescript
复制
  const handleDelete = async (record: any) => {
    'use server';
    await deleteInvoices(record.id);
  };

删除操作

1.首先编写客户端删除按钮组件

代码语言:typescript
复制
'use client';
import React from 'react';
import { Button } from 'antd';

interface IProps {
  handleDelete: (invoice: any) => void;
  invoice: any;
}

const Delete = (props: IProps) => {
  const { handleDelete, invoice } = props;
  const handleClick = () => {
    console.log('aa');
    handleDelete && handleDelete(invoice);
  };
  return (
    <>
      <Button onClick={handleClick}>删除</Button>
    </>
  );
};
export default Delete;

2.在page.tsx中引入删除按钮组件

代码语言:typescript
复制
 <Delete invoice={invoice} handleDelete={handleDelete} /> 3.编写删除数据操作export async function deleteInvoices(id: string) {
 noStore();
 try {
   const data = await sql<LatestInvoiceRaw>`
   DELETE FROM customers
   WHERE id=${id};`;

   return data.rows;
 } catch (error) {
   console.error('Database Error:', error);
   throw new Error('Failed to fetch the latest invoices.');
 }
}

4.最后编写删除操作

代码语言:typescript
复制
  const handleDelete = async (record: any) => {
    'use server';
    await deleteInvoices(record.id);
  };

这样就完成了crud操作。

部署

将代码提交到github上,使用vercel会自动部署

体验地址

https://nextjs-dashboard-one-chi-69.vercel.app/dashboard

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 创建项目
  • 添加全局样式
  • 字体和图像
    • 设置全局字体
      • 设置图像
      • 创建布局和页面
        • 嵌套路由
          • 创建布局
            • 页面导航
              • 高亮当前链接
              • 创建数据库
              • 安装Antd
              • CRUD操作
                • 查询数据
                  • 新增数据
                    • 删除操作
                    • 部署
                    • 体验地址
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档