首页 > 其他分享 >【Next.js 项目实战系列】07-分配 Issue 给用户

【Next.js 项目实战系列】07-分配 Issue 给用户

时间:2024-10-17 13:51:48浏览次数:10  
标签:const 07 app Next return AssigneeSelect import js id

原文链接

CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话,给我的点个star,关注一下吧 

上一篇【Next.js 项目实战系列】06-身份验证

分配 Issue 给用户

本节代码链接

Select Button

# /app/issues/[id]/AssigneeSelect.tsx

"use client";
import { Select } from "@radix-ui/themes";

const AssigneeSelect = () => {
  return (
    <Select.Root>
      <Select.Trigger placeholder="Assign..." />
      <Select.Content>
        <Select.Group>
          <Select.Label>Suggestions</Select.Label>
          <Select.Item value="1">Castamere</Select.Item>
        </Select.Group>
      </Select.Content>
    </Select.Root>
  );
};
export default AssigneeSelect;

效果如下

Select Button

获取所有用户

本节代码链接

构建 API

# /app/api/users.tsx

import { NextRequest, NextResponse } from "next/server";
import prisma from "@/prisma/client";

export async function GET(reques: NextRequest) {
  const users = await prisma.user.findMany({ orderBy: { name: "asc" } });
  return NextResponse.json(users);
}

客户端获取数据

# /app/issues/[id]/AssigneeSelect.tsx

"use client";
import { User } from "@prisma/client";
import { Select } from "@radix-ui/themes";
import axios from "axios";
import { useEffect, useState } from "react";

const AssigneeSelect = () => {
  const [users, setUsers] = useState<User[]>([]);

  useEffect(() => {
    const getUsers = async () => {
      const { data } = await axios.get<User[]>("/api/users");
      setUsers(data);
    };
    getUsers();
  }, []);
  return (
    <Select.Root>
      <Select.Trigger placeholder="Assign..." />
      <Select.Content>
        <Select.Group>
          <Select.Label>Suggestions</Select.Label>
          {users.map((user) => (
            <Select.Item value={user.id} key={user.id}>
              {user.name}
            </Select.Item>
          ))}
        </Select.Group>
      </Select.Content>
    </Select.Root>
  );
};
export default AssigneeSelect;

React-Query

配置 React-Query

本节代码链接

使用如下命令安装 React-Query

npm i @tanstack/react-query

安装好后,在 /app 目录下创建 QueryClientProvider.tsx

# /app/QueryClientProvider.tsx

"use client";
import {
  QueryClient,
  QueryClientProvider as ReactQueryClientProvider,
} from "@tanstack/react-query";
import { PropsWithChildren } from "react";

const queryClient = new QueryClient();

const QueryClientProvider = ({ children }: PropsWithChildren) => {
  return (
    <ReactQueryClientProvider client={queryClient}>
      {children}
    </ReactQueryClientProvider>
  );
};
export default QueryClientProvider;

然后在 layout 中将 body 内所有内容用 QueryClientProvider 包起来

# /app/layout.tsx

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <QueryClientProvider>
          <AuthProvider>
            <Theme appearance="light" accentColor="violet">
              <NavBar />
              <main className="p-5">
                <Container>{children}</Container>
              </main>
            </Theme>
          </AuthProvider>
        </QueryClientProvider>
      </body>
    </html>
  );
}

使用 React-Query

本节代码链接

首先,在 "/app/issues/[id]/Assign" 中去掉之前的 useEffect 和 useState,之后参照下面修改

# /app/issues/[id]/AssigneeSelect.tsx

  ...
+ import { useQuery } from "@tanstack/react-query";
+ import { Skeleton } from "@/app/components";

  const AssigneeSelect = () => {
+   const {
+     data: users,
+     error,
+     isLoading,
+   } = useQuery<User[]>({
      // 用于缓存的 key,在不同地方调用 useQuery 若 key 一样则不会重复获取
+     queryKey: ["users"],
      // 用于获取数据的函数
+     queryFn: () => axios.get<User[]>("/api/users").then((res) => res.data),
      // 数据缓存多久
+     staleTime: 60 * 1000,
      // 最多重复获取几次
+     retry: 3,
+   });
+   if (error) return null;
+   if (isLoading) return <Skeleton />;
    ...
  };
  export default AssigneeSelect;

完整代码(非 git diff 版)

# /app/issues/[id]/AssigneeSelect.tsx

"use client";
import { User } from "@prisma/client";
import { Select } from "@radix-ui/themes";
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import { Skeleton } from "@/app/components";

const AssigneeSelect = () => {
  const {
    data: users,
    error,
    isLoading,
  } = useQuery<User[]>({
    queryKey: ["users"], // 用于缓存的 key,在不同地方调用 useQuery 若 key 一样则不会重复获取
    queryFn: () => axios.get<User[]>("/api/users").then((res) => res.data), // 用于获取数据的函数
    staleTime: 60 * 1000, // 数据缓存多久
    retry: 3, // 最多重复获取几次
  });
  if (error) return null;
  if (isLoading) return <Skeleton />;

  return (
    <Select.Root>
      <Select.Trigger placeholder="Assign..." />
      <Select.Content>
        <Select.Group>
          <Select.Label>Suggestions</Select.Label>
          {users?.map((user) => (
            <Select.Item value={user.id} key={user.id}>
              {user.name}
            </Select.Item>
          ))}
        </Select.Group>
      </Select.Content>
    </Select.Root>
  );
};
export default AssigneeSelect;

Prisma Relation

本节代码链接

我们需要在 Prisma 中的 Issue model 和 User model 创建一个 Relation

# schema.prisma

  model Issue {
    id               Int      @id @default(autoincrement())
    title            String   @db.VarChar(255)
    description      String   @db.Text
    status           Status   @default(OPEN)
    createdAt        DateTime @default(now())
    updatedAt        DateTime @updatedAt()
+   assignedToUserId String?  @db.VarChar(255)
+   assignedToUser   User?    @relation(fields: [assignedToUserId], references: [id])
  }

  model User {
    id             String    @id @default(cuid())
    name           String?
    email          String?   @unique
    emailVerified  DateTime?
    image          String?
    accounts       Account[]
    sessions       Session[]
+   assignedIssues Issue[]
  }

更新修改 Issue API

本节代码链接

首先,添加一个新的 zod schema,其中 title, description, assignedToUserId 都设置为了 optional

# validationSchema.ts

import { z } from "zod";

export const issueSchema = z.object({
  title: z.string().min(1, "Title is required!").max(255),
  description: z.string().min(1, "Description is required!").max(65535),
});

export const patchIssueSchema = z.object({
  title: z.string().min(1, "Title is required!").max(255).optional(),
  description: z.string().min(1, "Description is required!").optional(),
  assignedToUserId: z
    .string()
    .min(1, "AssignedToUserId is required.")
    .max(255)
    .optional()
    .nullable(),
});

然后修改 "/app/api/issues/[id]/route.tsx"

# /app/api/issues/[id]/route.tsx

+ import { patchIssueSchema } from "@/app/validationSchema";
  ...

  export async function PATCH(
    request: NextRequest,
    { params }: { params: { id: string } }
  ) {
    const session = await getServerSession(authOptions);
    if (!session) return NextResponse.json({}, { status: 401 });

    const body = await request.json();
    // 换成 patchIssueSchema
+   const validation = patchIssueSchema.safeParse(body);
    if (!validation.success)
      return NextResponse.json(validation.error.format(), { status: 400 });

    // 直接将 title, description, assignedToUserId 结构出来
+   const { title, description, assignedToUserId } = body;

    // 若 body 中有 assignedToUserId,则判断该用户是否存在
+   if (assignedToUserId) {
+     const user = await prisma.user.findUnique({
+       where: { id: assignedToUserId },
+     });
+     if (!user)
+       return NextResponse.json({ error: "Invalid user" }, { status: 400 });
+   }

    const issue = await prisma.issue.findUnique({
      where: { id: parseInt(params.id) },
    });
    if (!issue)
      return NextResponse.json({ error: "Invalid Issue" }, { status: 404 });

    const updatedIssue = await prisma.issue.update({
      where: { id: issue.id },
+     data: {
+       title,
+       description,
+       assignedToUserId,
+     },
    });

    return NextResponse.json(updatedIssue, { status: 200 });
  }

分配 Issue

本节代码链接

# /app/issues/[id]/AssigneeSelect.tsx

  ...
  const AssigneeSelect = ({ issue }: { issue: Issue }) => {
    ...

    return (
      <Select.Root
        // 设置初始显示值
+       defaultValue={issue.assignedToUserId || ""}
        // 当选择时,使用patch (不需要await)
+       onValueChange={(userId) => {
+         axios.patch("/api/issues/" + issue.id, {
+           assignedToUserId: userId === "Unassign" ? null : userId,
+         });
+       }}
      >
        <Select.Trigger placeholder="Assign..." />
        <Select.Content>
          <Select.Group>
            <Select.Label>Suggestions</Select.Label>
            {/* 添加一个 unassign */}
+           <Select.Item value="Unassign">Unassign</Select.Item>
            {users?.map((user) => (
              <Select.Item value={user.id} key={user.id}>
                {user.name}
              </Select.Item>
            ))}
          </Select.Group>
        </Select.Content>
      </Select.Root>
    );
  };
  export default AssigneeSelect;

显示 Toast

本节代码链接

使用如下命令安装

npm i react-hot-toast

我们只需要在该组件任意地方添加 <Toaster /> 组件,然后在需要报错的地方调用 toast() 函数即可

# /app/issues/[id]/AssigneeSelect.tsx

+ import toast, { Toaster } from "react-hot-toast";

  const AssigneeSelect = ({ issue }: { issue: Issue }) => {

    return (
      <>
        <Select.Root
          defaultValue={issue.assignedToUserId || ""}
          onValueChange={ (userId) => {
+           axios
+             .patch("/api/issues/" + issue.id, {
+               assignedToUserId: userId === "Unassign" ? null : userId,
+             })
              // 调用 toast.error()即可
+             .catch(() => toast.error("Changes could not be saved!"));
            }
          }
        >
          ...
        </Select.Root>
+       <Toaster />
      </>
    );
  };
  export default AssigneeSelect;

效果如下

Toast

CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话,给我的点个star,关注一下吧 

下一篇讲数据处理

标签:const,07,app,Next,return,AssigneeSelect,import,js,id
From: https://blog.csdn.net/qq_54869075/article/details/143015345

相关文章

  • 基于Node.js+vue钢材销售平台(开题+程序+论文) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容一、选题背景关于钢材销售平台的研究,现有研究主要以传统销售模式为主,对于利用现代信息技术构建专门的钢材销售平台的研究较少。在国内外,传统钢材销售面临着流程繁琐......
  • 基于Node.js+vue个人理财微服务系统(开题+程序+论文) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容一、选题背景关于个人理财微服务系统的研究,现有研究主要集中在传统理财系统的整体架构和功能实现上。在国内外,传统理财系统已经有了较为成熟的开发模式和功能模块,但......
  • 基于Node.js+vue短视频推荐系统(开题+程序+论文) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容一、选题背景随着互联网的迅速发展,短视频行业蓬勃兴起。关于短视频推荐的研究,现有研究主要以用户行为分析和通用推荐算法为主 1。专门针对短视频这一特殊媒体形式,结......
  • 9 个 Node.js 最佳实践原则!
    你好,前端开发爱好者。Node.js是现代应用开发的基石之一,超过630万网站和无数API使用Node.js。Node.js以事件驱动的架构为核心,支持高并发和非阻塞I/O操作,是构建高性能应用的理想选择。虽然它功能强大,但有效管理和扩展Node.js应用需要丰富的经验。今天的文章中总......
  • 基于Node.js+vue高中生心理健康管理系统(开题+程序+论文) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容选题背景高中生心理健康问题一直是教育领域和社会关注的焦点。随着社会竞争的加剧和学业压力的增大,高中生面临的心理压力也日益增加。近年来,国内外关于高中生心理健......
  • 基于Node.js+vue辅导员职责信息管理系统(开题+程序+论文) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容选题背景辅导员职责信息管理系统的设计与开发,旨在提高辅导员工作效率,优化学生管理流程。当前,关于辅导员职责信息管理的研究主要集中在辅导员工作流程的优化、学生信......
  • jsp东哈驾校管理系统的设计与实现dy35m(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表学员,教练,报名登记,退学登记,练车预约,考试预约,报考信息,报考反馈,成绩信息开题报告内容一、项目背景随着汽车保有量的不断增加,驾驶培训行业迎来了巨大的发......
  • jsp订餐管理系统的设计与实现3v4h1--(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表用户,厢房信息,厢房预约,菜品分类,特色美食,员工信息,营业统计开题报告内容一、项目背景随着餐饮行业的数字化转型,订餐管理系统成为提升餐厅运营效率、优化顾......
  • jsp动物园管理系统的设计与实现zoejc程序+源码+数据库+调试部署+开发环境
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表饲养员,后勤人员,动物类型,动物信息,食谱定制,物资信息,物资入库,排班申请,排班申请2,工资信息开题报告内容一、研究背景与意义随着城市化进程的加快和公众对......
  • Nodejs中process.cwd()与__dirname的区别
    Nodejs中process.cwd()与__dirname的区别2018-03-214104版权 简介: 首先,上官方解释。=>process.cwd(): The process.cwd() methodreturns thecurrentworkingdirectoryoftheNode.jsprocess.上面的意思就是,process.cwd()返回的是当前Node.js进程执行时的工作目......