【Next.js 项目实战系列】03-查看 Issue

原文链接

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

上一篇【Next.js 项目实战系列】02-创建 Issue

查看 Issue

展示 Issue​

本节代码链接

首先使用 prisma 获取所有的 issues,然后添加一个 Radix UI 中的 Table 组件

# /app/issues/page.tsximport { Button, Table, TableColumnHeaderCell } from "@radix-ui/themes";import Link from "next/link";
+ import prisma from "@/prisma/client";const IssuesPage = async () => {
+   const issues = await prisma.issue.findMany();return (<div><div className="mb-5"><Button><Link href="/issues/new">New Issue</Link></Button></div>{/* Radix UI 中的 Table 组件 */}
+       <Table.Root variant="surface">
+         <Table.Header>
+           <Table.Row>
+             <TableColumnHeaderCell>Issue</TableColumnHeaderCell>
+             <TableColumnHeaderCell>Status</TableColumnHeaderCell>
+             <TableColumnHeaderCell>Created</TableColumnHeaderCell>
+           </Table.Row>
+         </Table.Header>
+         <Table.Body>
+           {issues.map((issue) => (
+             <Table.Row key={issue.id}>
+               <Table.Cell>{issue.title}</Table.Cell>
+               <Table.Cell>{issue.status}</Table.Cell>
+               <Table.Cell>{issue.createdAt.toDateString()}</Table.Cell>
+             </Table.Row>
+           ))}
+         </Table.Body>
+       </Table.Root></div>);};export default IssuesPage;

然后我们可以给不同的列添加显示选项,以适配不同的屏幕大小

# /app/issues/page.tsx...
const IssuesPage = async () => {...return (<div>...<Table.Root variant="surface"><Table.Header><Table.Row><TableColumnHeaderCell>Issue</TableColumnHeaderCell><TableColumnHeaderCell className="hidden md:table-cell">Status</TableColumnHeaderCell><TableColumnHeaderCell className="hidden md:table-cell">Created</TableColumnHeaderCell></Table.Row></Table.Header><Table.Body>{issues.map((issue) => (<Table.Row key={issue.id}><Table.Cell>{issue.title}<div className="block md:hidden">{issue.status}</div></Table.Cell><Table.Cell className="hidden md:table-cell">{issue.status}</Table.Cell><Table.Cell className="hidden md:table-cell">{issue.createdAt.toDateString()}</Table.Cell></Table.Row>))}</Table.Body></Table.Root></div>);
};
export default IssuesPage;

显示效果如下

Mobile View

Tablet View

制作 Badge​

本节代码链接

在 Prisma 中添加的 Model 会自动为我们生成 Type ,方便拿来做 Interface

Prisma Type

这里有一些技巧。首先,对于一些固定值的映射(比如这里 Issue 状态对 Badge 颜色/内容的映射),我们可以使用一个 Record 来记录,其本质为一个键值对,我们可以使用 < > 来定义键和值的数据类型

# /app/components/IssueStatusBadge.tsximport { Status } from "@prisma/client";
import { Badge } from "@radix-ui/themes";const statusMap: Record<Status,{ label: string; color: "green" | "violet" | "red" }
> = {OPEN: { label: "Open", color: "green" },IN_PROGRESS: { label: "In Progress", color: "violet" },CLOSED: { label: "Closed", color: "red" },
};const IssueStatusBadge = ({ status }: { status: Status }) => {return (<Badge color={statusMap[status].color}>{statusMap[status].label}</Badge>);
};
export default IssueStatusBadge;

最终效果如下

Issue Status Badge

Loading Skeleton​

本节代码链接

本节我们想要实现一个如下的加载动画

Loading Skeleton

安装 delay 包用于模拟网速较慢情况,react-loading-skeleton 包用于添加骨架动画

npm i delay
npm i react-loading-skeleton

首先,我们应该在页面中把不需要加载的部分(指不需要从外部获取数据的部分,一些写死的 Text, Link, Button 之类的)封装起来,

# /app/issues/IssueAction.tsximport { Button } from "@radix-ui/themes";
import Link from "next/link";const IssueActions = () => {return (<div className="mb-5"><Button><Link href="/issues/new">New Issue</Link></Button></div>);
};
export default IssueActions;

然后在 page.tsx 同目录下创建 loading.tsx (注意文件名必须是这个,大小写也不能改)。将 page.tsx 中 return 的内容都复制到里面,把需要加载的字段换为 Skeleton 标签即可

# /app/issues/loading.tsximport { Table, TableColumnHeaderCell } from "@radix-ui/themes";// import Skeleton
+ import Skeleton from "react-loading-skeleton";
+ import "react-loading-skeleton/dist/skeleton.css";import IssueActions from "./IssueActions";const LoadingIssuesPage = () => {// 显示 5 行 skeleton
+   const issues = [1, 2, 3, 4, 5];return (...<Table.Body>{issues.map((issue) => ({/* 将所有需要数据的字段换为 <Skeleton />即可 */}
-         <Table.Row key={issue.id}>
+         <Table.Row key={issue}><Table.Cell>
-             {issue.title}
+             <Skeleton /><div className="block md:hidden">
-               <IssueStatusBadge status={issue.status} />
+               <Skeleton /></div></Table.Cell><Table.Cell className="hidden md:table-cell">
-             <IssueStatusBadge status={issue.status} />
+             <Skeleton /></Table.Cell><Table.Cell className="hidden md:table-cell">
-             {issue.createdAt.toDateString()}
+             <Skeleton /></Table.Cell></Table.Row>))}</Table.Body>...);};export default LoadingIssuesPage;

我们可以在 page.tsx 中添加一个 delay(2000) 来模拟

# /app/issues/page.tsx...
const IssuesPage = async () => {const issues = await prisma.issue.findMany();await delay(2000);...

Issue Detail Page​

本节代码链接

首先创建一个页面用于展示 Issue 细节 /app/issues/[id]/page.tsx

# /app/issues/[id]/page.tsximport prisma from "@/prisma/client";
import { notFound } from "next/navigation";
interface Props {params: { id: string };
}
const IssueDeatilPage = async ({ params }: Props) => {// 判断 url 中的 id 是不是 number,比如 'issues/abc' 就直接404if (typeof params.id !== "number") notFound();// 获取 issueconst issue = await prisma.issue.findUnique({where: { id: parseInt(params.id) },});// 如果 issue 不存在,也404if (!issue) notFound();return (<div><p>{issue.title}</p><p>{issue.description}</p><p>{issue.status}</p><p>{issue.createdAt.toDateString()}</p></div>);
};
export default IssueDeatilPage;

然后在 /app/issues/page.tsx 中渲染表格时,添加一个 Link,用于跳转到 detail 页面

# /app/issues/page.tsxconst IssuesPage = async () => {return (...<Table.Cell>
+       <Link href={`/issues/${issue.id}`}>{issue.title}</Link><div className="block md:hidden"><IssueStatusBadge status={issue.status} /></div></Table.Cell>...);};

最后,我们应该为 "/app/issues" 下的每一个 page 都提供一个 loading.tsx,否则刚刚的 "/app/issues/loading.tsx" 会应用到 "/app/issues" 下的所有页面

添加样式​

本节代码链接

此处大量使用了 Radix UI 中的组件

# /app/issues/[id]/page.tsxconst IssueDeatilPage = async ({ params }: Props) => {...return (<div><Heading as="h2">{issue.title}</Heading><Flex gap="3" my="5"><IssueStatusBadge status={issue.status}></IssueStatusBadge><Text>{issue.createdAt.toDateString()}</Text></Flex><Card>{issue.description}</Card></div>);
};

效果如下

Style Detail Page

MarkDown 渲染​

本节代码链接

首先,安装以下两个 package

npm i react-markdown
npm install -D @tailwindcss/typography

在 "/app/issues/[id]/page.tsx" 中,将 issue.description 用 ReactMarkdown 组件包起来即可

# /app/issues/[id]/page.tsxconst IssueDeatilPage = async ({ params }: Props) => {...return (...
-     <Card>{issue.description}</Card>
+     <Card className="prose">
+       <ReactMarkdown>{issue.description}</ReactMarkdown>
+     </Card>...);};

在 Tailwind 中,默认 h1, h2, ul, ol, strong 这些标签都是无样式的,我们需要手动进行设置。刚刚安装的 tailwindcss/typography 就是这个作用。首先,在 tailwind.config.ts 中,添加 plugin,然后在需要用到这些样式的 container 的 className 中添加 prose 即可

# tailwind.config.tsimport type { Config } from "tailwindcss";const config: Config = {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))",},},},// 在这里添加 @tailwindcss/typography"
+   plugins: [require("@tailwindcss/typography")],};export default config;

最终实现效果如下

Markdown Preview

本节代码链接

我们想要同时应用 Next.js 中 Link 的客户端导航功能,和 Radix UI 中 Link 的样式,就可以进行如下改装,后期直接使用即可

# /app/components/link.tsximport NextLink from "next/link";
import { Link as RadixLink } from "@radix-ui/themes";interface Props {href: string;children: string;
}const Link = ({ href, children }: Props) => {return (<NextLink href={href} passHref legacyBehavior><RadixLink>{children}</RadixLink></NextLink>);
};
export default Link;

效果如下,其中 Link 的颜色会随着 Theme 的改变而改变

Link

Loading Skeletons​

本节代码链接

# /app/issues/[id]/loading.tsximport IssueStatusBadge from "@/app/components/IssueStatusBadge";
import { Box, Card, Flex, Heading } from "@radix-ui/themes";
import ReactMarkdown from "react-markdown";
import Skeleton from "react-loading-skeleton";
import "react-loading-skeleton/dist/skeleton.css";const LoadingIssueDetailPage = () => {return (<Box className="max-w-xl"><Skeleton /><Flex gap="3" my="5"><Skeleton width="5rem" /><Skeleton width="8rem" /></Flex><Card className="prose"><Skeleton count={3} /></Card></Box>);
};
export default LoadingIssueDetailPage;
# /app/issues/new/loading.tsximport { Box } from "@radix-ui/themes";
import Skeleton from "react-loading-skeleton";
import "react-loading-skeleton/dist/skeleton.css";const LoadingNewIssuePage = () => {return (<Box className="max-w-xl"><Skeleton /><Skeleton height="20rem" /></Box>);
};
export default LoadingNewIssuePage;

显示效果如下

Detail Loading Skeleton

New Loading Skeleton

动态导入(关闭 SSR)​

本节代码链接

SSR(Server Side Render) 相关内容可参考组件的渲染

# /app/issues/new/page.tsx- import SimpleMDE from "react-simplemde-editor";
+ import dynamic from "next/dynamic";+ const SimpleMDE = dynamic(() => import("react-simplemde-editor"), {
+   ssr: false,
+ });

整理 imports​

本节代码链接

我们可以在 Components 文件夹下添加 index.ts,将该文件夹下所有组件都注册到其中

# /app/components/index.tsexport { default as Link } from "./Link";
export { default as ErrorMessage } from "./ErrorMessage";
export { default as IssueStatusBadge } from "./IssueStatusBadge";
export { default as Spinner } from "./Spinner";
export { default as Skeleton } from "./Skeleton";

然后在其他页面,直接使用以下 import 语句即可

import { ErrorMessage, Spinner } from "@/app/components";

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

下一篇讲修改 Issue

下一篇【Next.js 项目实战系列】04-修改 Issue​​​​​​​

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【QT】常用控件(二)

个人主页~ 常用控件&#xff08;一&#xff09;~ 常用控件 三、按钮类控件1、Push Buttonwidget.hwidget.cpp 2、Radio Button3、Check Box 四、显示类控件1、label 三、按钮类控件 1、Push Button QPushButton继承自QAbstractButton&#xff0c;它是所有按钮的父类 我们从…

线下陪玩导游系统软件源码,家政预约服务源码(h5+小程序+app)

游戏陪玩系统源码陪玩小程序源码搭建基于PHP&#xff0b;MySQL陪玩系统app源码陪玩系统定制开发服务、成品陪玩系统源码 系统基于Nginx或者Apache PHP7.3 数据库mysql5.6 前端为uniapp-vue2.0 后端为thinkphp6 有域名授权加密&#xff0c;其他开源可二开 演示源码下载 开…

Zookeeper快速入门:部署服务、基本概念与操作

文章目录 一、部署服务1.下载与安装2.查看并修改配置文件3.启动 二、基本概念与操作1.节点类型特性总结使用场景示例查看节点查看节点数据 2.文件系统层次结构3.watcher 一、部署服务 1.下载与安装 下载&#xff1a; 一定要下载编译后的文件&#xff0c;后缀为bin.tar.gz w…

如何匿名浏览网站,保护在线隐私?

在现如今的网络世界&#xff0c;在线隐私已不复存在。你总是被跟踪&#xff0c;即使你使用隐身模式也无济于事。隐身模式会阻止浏览器保存你的浏览历史记录。但它并不能阻止你的互联网服务提供商 (ISP)、雇主、学校、图书馆或你访问的网站看到你在网上做什么。 更有不法分子在未…

CVE-2015-4852 Weblogic T3 反序列化分析

0x01 前言 看到很多师傅的面经里面都有提到 Weblogic 这一个漏洞&#xff0c;最近正好有一些闲暇时间&#xff0c;可以看一看。 因为环境上总是有一些小问题&#xff0c;所以会在本地和云服务器切换着调试0x02 环境搭建 • 太坑了&#xff0c;我的建议是用本地搭建的方法&…

【C语言】一维数组应用Fibonacci数列

Fibonacci数&#xff08;斐波那契数列&#xff09; 前两项为1&#xff0c;从第三项开始&#xff0c;每一项为前两项的和。可以知道连续三项的关系&#xff1a;f[i]f[i-1]f[i-2] 使用数组进行存储&#xff0c;十分方便。可以知道前n项的fibonacci数。 #include <stdio.h>…

ios局域网访问主机Xcode配置

前景&#xff1a; 公司业务是做智能家居&#xff0c;所有设备通过主机控制&#xff0c;目前有个产品需求是&#xff0c;在没有外网的情况下依然能够通过局域网控制主机的设备。 IOS开发需要做的&#xff1a; 除了业务代码之外&#xff0c;前提还要配置访问局域网功能。有以下…

诺贝尔经济学奖历史名单数据集(1969-2024年)

2024年诺贝尔经济学奖授予了达龙阿西莫格鲁&#xff08;Daron Acemoglu&#xff09;、西蒙约翰逊&#xff08;Simon Johnson&#xff09;和詹姆斯A罗宾逊&#xff08;James A. Robinson&#xff09;&#xff0c;以表彰他们在理解制度如何影响经济发展方面的贡献。&#xff08;“…

Linux 外设驱动 应用 3 串口

3 串口 3.1 串口原理 串行口是计算机一种常用的接口&#xff0c;具有连接线少&#xff0c;通讯简单&#xff0c;得到广泛的使用。常用的串口是 RS- 232-C接口(又称 EIA RS-232-C)它是在 1970 年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同…

Input-Source-Pro:自动切换输入法并提示状态

Input Source Pro 是一款 macOS 上的输入法辅助工具&#xff0c;它可以根据不同应用、不同网站来自动切换输入法&#xff0c;并可以在鼠标周围显示当前输入法状态。 macOS 不像 Windows 那样能保存输入法状态&#xff0c;因此这样的软件还是挺有用的。 ‍ 介绍 官网&#x…

OceanBase中扩容OCP节点step by step

许多用户在开始使用OceanBase时部署OCP&#xff0c;通常选择单节点部署。但随着后续业务规模的不断扩大&#xff0c;会开始担忧单节点OCP在面对故障时可能丧失对集群运维管控的连续性。鉴于此&#xff0c;会将现有的单节点OCP扩展至多节点部署&#xff0c;以此来确保OCP服务的高…

『Mysql集群』Mysql高可用集群之读写分离(二)

前言 主从复制: 解决了Mysql的单点故障问题以及提高MySQL的整体服务性能. 读写分离: 解决的是数据库的读性能问题,分担主库的压力&#xff0c;提高系统的可用性和稳定性。 分库分表: 数据库分表可以解决单表海量数据的查询性能问题&#xff0c;分库可以解决单台数据库的并发…

计算机网络——应用层(DNS域名系统、文件传输协议FTP、远程终端协议TELNET、万维网)

应用层概述 不同网络应用的应用进程之间&#xff0c;还需要用不同的通信规则。因此在运输层协议之上&#xff0c;还需要有应用层协议。 每个应用层协议都是为了解决某一类应用问题&#xff0c;而问题的解决又必须通过位于不同主机中的多个应用进程之间的通信和协同工作来完成。…

网络七层架构

目录标题 网络七层架构从正确认识网络七层架构开始 网络七层架构 简介&#xff1a; 网络七层架构是指ISO/OSI模型&#xff0c;它是国际标准化组织&#xff08;ISO&#xff09;制定的一种用于计算机网络体系结构的参考模型。该模型将计算机网络的功能划分为七个层次&#xff0c…

Spring Boot知识管理:跨平台集成方案

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

【UML】一个UML学习的还不错的几个帖子

https://segmentfault.com/a/1190000042775634 寂然解读设计模式 - UML类图&类的六大关系-阿里云开发者社区

信息抽取数据集处理——RAMS

引言 RAMS数据集&#xff08;RAMS&#xff1a;Richly Annotated Multilingual Schema-guided Event Structure&#xff09;由约翰斯霍普金斯大学于2020年发布&#xff0c;是一个以新闻为基础的事件抽取数据集。它标注了9,124个事件&#xff0c;涵盖了139种不同的事件类型和65种…

客户案例 | Ansys与台积电和微软合作加速光子仿真

Ansys与台积电和微软展开合作&#xff0c;将硅光子器件的仿真和分析速度提高10倍以上 主要亮点 借助使用NVIDIA图形处理单元&#xff08;GPU&#xff09;的Microsoft Azure虚拟机&#xff0c;Ansys Lumerical™ FDTD 3D电磁仿真的光子器件仿真速度实现了10倍提升 凭借Azure云…

k8s系列-Rancher 上操作的k8s容器网络配置总结

Rancher 上操作的k8s容器网络配置总结 要在 Rancher 中配置Spring Boot 应用 ykhd-zhjgyw-xpwfxfjfl 服务&#xff0c;正确的配置方式如下&#xff1a; 1. 应用程序监听端口 在 application.yaml 文件中&#xff0c;配置的应用监听端口是 10001&#xff0c;并且应用的上下文…

C++ | Leetcode C++题解之第485题最大连续1的个数

题目&#xff1a; 题解&#xff1a; class Solution { public:int findMaxConsecutiveOnes(vector<int>& nums) {int maxCount 0, count 0;int n nums.size();for (int i 0; i < n; i) {if (nums[i] 1) {count;} else {maxCount max(maxCount, count);coun…