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

news2024/11/25 2:34:44

原文链接

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,关注一下吧 

下一篇讲数据处理

下一篇【Next.js 项目实战系列】08-数据处理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2220236.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java】正则表达式详解

目录 引言 一、基本概念 1.1 元字符 1.2 预定义字符类 1.3 边界匹配符 1.4 数量标识符 1.5 捕获与非捕获分组 二、Java中的正则表达式支持 三、正则表达式的使用示例 3.1 匹配字符串 3.2 替换字符串 3.3 分割字符串 3.4 使用Pattern和Matcher 3.5 捕获组和后向…

【电商项目】1分布式基础篇

1 项目简介 1.2 项目架构图 1.2.1 项目微服务架构图 1.2.2 微服务划分图 2 分布式基础概念 3 Linux系统环境搭建 查看网络IP和网关 linux网络环境配置 补充P123&#xff08;修改linux网络设置&开启root密码访问&#xff09; 设置主机名和hosts映射 主机名解析过程分析&…

金九银十互联网大厂Java高频面试题(2024最新含答案)

2024 年的互联网行业竞争越来越严峻&#xff0c;面试也是越来越难&#xff0c;一直以来我都想整理一套完美的面试宝典&#xff0c;奈何难抽出时间&#xff0c;这套 1200道的 Java 面试手册我整理了整整 1 个月&#xff0c;上传到 Git 上目前 star 数达到了 30K 这套互联网 Jav…

冲击美团!已成功 OC

这是一位训练营学员的美团面经&#xff0c;目前已经 OC 。 在此之前他已经拿到了不少公司的offer&#xff0c;但是都达不到他的预期&#xff0c;美团给的待遇就非常不错&#xff0c;大厂不愧是大厂&#xff0c;就是不知道工作强度如何。 他经历了一共三场面试&#xff0c;一面…

实现省略号查看详情样式

1.期望实现效果 2.目前实现效果 3.实现代码 1.wxml<view class"desc-text"><view class"show-more">查看详情 >></view><!-- <rich-text nodes"{{富文本接口数据内容 }}"></rich-text> --><text&…

python反爬

1.无限debug无法f12 关闭掉 Deactivate Breakpoints

【闲谈程序设计例三则:抛弃传统单步进初级阶段,用推导归纳出来的规律写代码,进入进阶阶段,人类自性的高级活动。】2024-10-21

闲谈程序设计三则&#xff1a;抛弃传统单步进&#xff0c;用推导归纳出来的规律写代码。 本论坛常见新学提问都是一些入门级别的问题&#xff0c;近来AI活跃抢答&#xff0c;然而&#xff0c;对于有些问题AI可以说是答非所问&#xff0c;令人哭笑不得&#xff0c;而AI能回答的…

MacOS安装BurpSuite

文章目录 一、下载地址二、下载注册机三、安装教程四、启动burpsuit五、免责声明 一、下载地址 https://portswigger-cdn.net/burp/releases/download?productpro&version2024.7.1&typeMacOsx二、下载注册机 https://github.com/NepoloHebo/BurpSuite-BurpLoaderKey…

B站协议登录到实现各种功能完整代码(专栏总结)

B站协议登录、点赞、收藏、转发实现及代码 关注、动态转发实现动态抽奖实现及代码 直播预约抽奖实现及代码 本文为本专栏的总结文章 一、扫码登录 请求获取二维码包&#xff0c;得到二维码链接和qrcode_key参数之后&#xff0c;利用qrcode_key循环GET请求登录状态包即可&#x…

【word】页眉横线无法取消

小伙伴们日常想在页眉里加横线&#xff0c;直接双击页眉&#xff0c;然后在页眉横线里选择自己喜欢的横线样式就可以了。 但今天我遇到的这个比较奇特&#xff0c;有些页有这个横线&#xff0c;有些页没有&#xff0c;就很奇怪。 最后排查完&#xff0c;发现是只有标题2的页…

WPF入门_02依赖属性

1、依赖属性主要有以下三个优点 1)依赖属性加入了属性变化通知、限制、验证等功能。这样可以使我们更方便地实现应用,同时大大减少了代码量 2)节约内存:在WinForm中,每个UI控件的属性都赋予了初始值,这样每个相同的控件在内存中都会保存一份初始值。而WPF依赖属性很好地…

《向量数据库指南》揭秘:Mlivus Cloud如何赋能GraphRAG应用

嘿,各位向量数据库和AI领域的探索者们,我是你们的老朋友,大禹智库的向量数据库高级研究员王帅旭,也是《向量数据库指南》的作者。今天,咱们来聊聊一个既前沿又实用的话题——检索增强生成(Retrieval Augmented Generation,简称RAG)及其面临的挑战,特别是如何用Mlivus …

前端/node.js锁定依赖版本、锁定依赖的依赖的版本

一、知识前提 version&#xff1a;必须依赖某个具体的版本。如&#xff1a;vue的3.2.0&#xff0c;表示必须安装3.2.0版本。>version&#xff1a;必须大于某个版本。>version&#xff1a;大于或等于某个版本。<version&#xff1a;必须小于某个版本。<version&…

多线程——单例模式

目录 前言 一、设计模式 二、饿汉模式 三、懒汉模式 1.单线程版 2.多线程版 结尾 前言 前面的几篇文章中介绍了多线程编程的基础知识&#xff0c;在本篇文章开始&#xff0c;就会利用前面的多线程编程知识来编写一些代码案例&#xff0c;从而使大家可以更好的理解运用多…

扩散模型对抗蒸馏:ADD 和 Latent-ADD

扩散模型对抗蒸馏&#xff1a;ADD 和 Latent-ADD ADD&#xff08;Adversarial Diffusion Distillation&#xff09;和 Latent-ADD 是 StabilityAI 公司提出的一系列针对 Stable Diffusion 的扩散模型对抗蒸馏方法&#xff0c;通过对抗训练和蒸馏训练来提高扩散模型的采样速度&…

python基于图片内容识别的微信自动发送信息(对其中逻辑修改一些可以改为自动化回复)

1.内容基于python日常生活问题帮助 2.主要框架 import time from datetime import datetimeimport pyperclip import win32api import win32con import os import refrom Image_Content_Text_Recognition import ICTR from screenshot import img 上面是逻辑部分主要框架 i…

【开源免费】基于SpringBoot+Vue.JS在线视频教育平台(JAVA毕业设计)

本文项目编号 T 027 &#xff0c;文末自助获取源码 \color{red}{T027&#xff0c;文末自助获取源码} T027&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 新…

解析 Vue 模板的本质:从语法糖到渲染过程

大家耳熟能详的表述如下&#xff1a;Vue 模板的本质其实是一种 声明式渲染 的形式&#xff0c;它在开发过程中提供了将组件的结构与逻辑分离的便利。 也就是说&#xff0c;模板 template 的存在只是为了让我们以更直观的方式描述界面的结构&#xff0c;然而在运行时&#xff0…

Android Framework AMS(09)service组件分析-3(bindService和unbindService关键流程分析)

该系列文章总纲链接&#xff1a;专题总纲目录 Android Framework 总纲 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;上上一章节主要解读应用层service组件启动的2种方式startService和bindService&#xff0c;以及从APP层到AMS调用之间的打通。上一章节我们关注了s…

北京大学冯惠:与卓越者同行,方能更快的成长 | OceanBase数据库大赛获奖选手访谈

本文邀请2022 OceanBase 数据库大赛的季军&#xff0c;来自北京大学的冯惠同学&#xff0c;与我们分享如何寻找自己的兴趣&#xff1b;在一番经历后&#xff0c;对于产品与研发的职业方向观察&#xff1b;以及如何在学生时期提升个人专业能力&#xff0c;和参加数据库大赛的个人…