【Next.js 项目实战系列】07-分配 Issue 给用户
原文链接
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;
效果如下

获取所有用户
本节代码链接
构建 API
# /app/api/users.tsximport { 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.tsxexport 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.prismamodel Issue {id Int @id @default(autoincrement())title String @db.VarChar(255)description String @db.Textstatus 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? @uniqueemailVerified DateTime?image String?accounts Account[]sessions Session[]
+ assignedIssues Issue[]}
更新修改 Issue API
本节代码链接
首先,添加一个新的 zod schema,其中 title, description, assignedToUserId 都设置为了 optional
# validationSchema.tsimport { 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.RootdefaultValue={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;
效果如下

CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话,给我的库点个star,关注一下吧
下一篇讲数据处理
下一篇【Next.js 项目实战系列】08-数据处理
相关文章:
【Next.js 项目实战系列】07-分配 Issue 给用户
原文链接 CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话,给我的库点个star,关注一下吧 上一篇【Next.js 项目实战系列】06-身份验证 分配 Issue 给用户 本节代码链接 Select Button # /app/issues/[i…...
Web,RESTful API 在微服务中的作用是什么?
大家好,我是锋哥。今天分享关于【Web,RESTful API 在微服务中的作用是什么?】面试题?希望对大家有帮助; Web,RESTful API 在微服务中的作用是什么? 在微服务架构中,Web 和 RESTful …...
Ajax:跨域、防抖和节流、HTTP协议
在善意的“双向奔赴”中,每个普通人都如星辰,微小但释放着自己的光芒,交织成灿烂的星河 文章目录 跨域防抖和节流HTTP协议HTP状态码以及代表意义错误代码的影响移动的小天使 跨域 同源策略 概念:协议,域名,…...
数据结构(8.2_2)—希尔排序
希尔排序的定义: 第一趟:先将在排序表中根据增量di分别将数组元素分别插入各个子表 ,在进行排序 代码实现: 算法性能分析 稳定性:不稳定! 适用性:仅适用于顺序表,不适用于链表 总…...
Netty笔记
本笔记是看了黑马的Netty进行总结的。想要更详细的可以去看视频 学习netty之前要先打好NIO的基础,可以先去看我的另一篇文章 一、概述 不想看的可以直接跳过 Netty 的地位 Netty 在 Java 网络应用框架中的地位就好比:Spring 框架在 JavaEE 开发中的地位…...
管道燃气监管系统
一、建设目标 建立一个高效、智能、安全的管道燃气监管系统,实现对管道燃气的实时监测、风险预警、事故应急处理和数据分析,确保燃气供应的安全稳定,提高燃气管理的效率和水平。 二、系统架构 感知层 安装压力传感器、流量传感器、温度传感…...
Python语法结构(三)(Python Syntax Structure III)
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…...
08_Linux文件查找技巧:locate、find 和 grep 命令详解
系列文章导航:01_Linux基础操作CentOS7学习笔记-CSDN博客 文章目录 1. locate命令2. grep命令3. find命令 在Linux系统中,文件查找是一项常见的任务。本文将详细介绍三种强大的文件查找命令:locate、find 和 grep,以及它们的使用…...
JAVA 实验六
一: (1) 运行以上尟序并尣以上尟序尜尢一行尥码添加注解,将尟序保存尣e601.java (2) 以上尟序尣类变量是哪一个变量,类尠尞是哪一个尠尞,请找出类变量和类尠尞被使用尜语…...
电脑查不到IP地址是什么原因?怎么解决
在日常使用电脑的过程中,有时会遇到无法查询到电脑IP地址的情况,这可能会影响到网络的正常使用。本文将探讨电脑查不到IP地址的可能原因,并提供相应的解决方案。 一、原因分析 网络连接问题:首先,网络连接不稳定或…...
Axure重要元件三——中继器修改数据
亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢! 课程主题:中继器修改数据 主要内容:显示编辑内容、表格赋值、修改数据 应用场景:更新行、表单数据行修改 案例展示: 正文…...
应用层——电子邮件、MIME、简单网络管理协议SNMP
电子邮件 电子邮件系统采用三个主要构件组成:用户代理、邮件服务器、电子邮件所需的协议 我们可以简单的认为邮件服务器中有很多邮箱,还有用来缓存再转发邮件的缓存,发送方使用用户代理通过邮件发送协议。例如SMTP将邮件发送给发送方。 邮件服…...
我与C语言二周目邂逅vlog——8.编译和链接
C语言中的编译和链接过程详细总结 1. 概述 C 语言是一种经典的系统级编程语言,其开发过程包括多个阶段,其中最关键的就是编译和链接过程。编译和链接的理解对于掌握 C 语言程序的构建至关重要。在本篇文章中,我们将深入讲解 C 语言的编译和…...
Views Page 视图页面
下图中显示的 Views 页面允许自定义网格级别及其相应的 View。 Views (视图) 页面包含两个主要部分: 关卡设计师;请注意,其他设计器页面为在关卡设计器中选择的 View 提供设置;Properties (属性) 窗口&…...
Win10 IDEA远程连接HBase
Win10 IDEA远程连接HBase Win10 IDEA连接虚拟机中的Hadoop(HDFS) 关闭Hadoop和Hbase 如果已经关闭不需要走这一步 cd /usr/local/hbase bin/stop-hbase.sh cd /usr/local/hadoop ./sbin/stop-dfs.sh获取虚拟机的ip 虚拟机终端输入 ip a关闭虚拟机…...
1.centos 镜像
centos 它有官网的下载地址:https://vault.centos.org/ 选择想要的版本,我选择 centos7.8 进入到镜像目录 isos 选择 x86_64 选择想要的版本,我选择 CentOS-7-x86_64-DVD-2003.iso 安装就正常安装就行。我选择虚拟机安装。这个参考&…...
electron 操作 cookie
前言:在 Electron 中操作 Cookie 可以使用electron模块提供的session对象来实现。 一、获取 Cookie 通过defaultSession获取默认会话对象,然后调用cookies.get方法并传入要获取 Cookie 的 URL 地址,以获取该 URL 对应的 Cookie。 const el…...
黑马软件测试第一篇_Linux
Linux 操作系统 说明: 所有硬件设备组装完成后的第⼀一层软件, 能够使⽤用户使⽤用硬件设备的软件 即为操作系统 常见分类 桌⾯面操作系统: Windows/macOS/Linux移动端操作系统: Android(安卓)/iOS(苹果)服务器器操作系统: Linux/Windows Server嵌⼊入式操作系统: Android(底…...
npm run dev 启动前端项目的原理
在一个使用 Vite 构建工具的 Vue 项目中,当你运行 npm run dev 时,实际执行的命令是 vite。为了理解这一过程,我们需要了解几个关键点: package.json 文件中的 scripts 字段: "scripts": {"dev": "vite&…...
【2024年SCI一区新算法:黑翅鸢优化算法 】分布式电网故障定位
1 场景介绍 使用10节点网络 2 故障设置 分为单重故障和两重故障 %% 2 故障设置 %% 1)单重故障 I[1,-1,0,0,-1,-1,0,0,-1,-1]; % 区段1故障 节点状态实际编码(是否流过故障电流) % I[1,1,0,0,-1,-1,0,0,-1,-1]; % 区段2故障 % I[…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
