Next.js项目实践(九)– 仪表板

本文实现仪表板功能,包括三个组件:顶部是问题的概要,下面是图表,右侧是最新的问题。


构建最新问题组件

先构建最新问题组件,在app目录下新建LastestIssues.tsx。

使用prisma获取最新5个问题,并获取分配的用户,注意Prisma中使用的是include属性关联。

用Card包装Table,然后map分别显示各行,每行显示带链接的标题和分配的用户头像。Card显示一个标题信息Lastest Issues,并根据页面效果调整相应样式。

import prisma from "@/prisma/client";
import { Avatar, Card, Flex, Heading, Table } from "@radix-ui/themes";
import Link from "next/link";
import React from "react";
import { IssueStatusBadge } from "./components";
​
const LastestIssues = async () => {
  const issues = await prisma.issue.findMany({
    orderBy: { createAt: "desc" },
    take: 5,
    include: {
      assignedToUser: true,
    },
  });
  return (
    <Card>
      <Heading size="4" mb="4">
        Lastest Issues
      </Heading>
      <Table.Root>
        <Table.Body>
          {issues.map((issue) => (
            <Table.Row key={issue.id}>
              <Table.Cell>
                <Flex justify={"between"}>
                  <Flex direction={"column"} align="start" gap="2">
                    <Link href={`/issues/${issue.id}`}>{issue.title}</Link>
                    <IssueStatusBadge status={issue.status} />
                  </Flex>
                  {issue.assignedToUser && (
                    <Avatar
                      src={issue.assignedToUser.image!}
                      fallback="?"
                      size={"2"}
                      radius="full"
                    />
                  )}
                </Flex>
              </Table.Cell>
            </Table.Row>
          ))}
        </Table.Body>
      </Table.Root>
    </Card>
  );
};
​
export default LastestIssues;
​

将LastestIssues组件在首页(/app/page.tsx)调用并查看效果。

import LastestIssues from "./LastestIssues";
​
export default function Home() {
  return <LastestIssues />;
}
​

提交Git,命名:Build the lastestIssue component.


构建问题概要组件

再来创建问题概要组件,在app目录下新建IssueSummary组件。

使用3个卡片水平排列显示各个状态的问题数量。组件需要3个传递数据:open, inProgress, closed。创建数组containers,元素是3个对象,各个对象的属性有label,value,status。通过调用containers的map方法显示各个卡片,并适当进行样式化。

import { Status } from "@prisma/client";
import { Card, Flex, Text } from "@radix-ui/themes";
import Link from "next/link";
​
interface Props {
  open: number;
  inProgress: number;
  closed: number;
}
const IssueSummary = ({ open, inProgress, closed }: Props) => {
  const containers: { label: string; value: number; status: Status }[] = [
    { label: "Open Issues", value: open, status: "OPEN" },
    { label: "In-progress Issues", value: inProgress, status: "IN_PROGRESS" },
    { label: "Closed Issues", value: closed, status: "CLOSED" },
  ];
  return (
    <Flex gap={"4"}>
      {containers.map((container) => (
        <Card>
          <Flex direction={"column"} gap="1">
            <Link
              className="text-sm font-medium"
              href={`/issues?status=${container.status}`}
            >
              {container.label}
            </Link>
            <Text size="5" className="font-bold">
              {container.value}
            </Text>
          </Flex>
        </Card>
      ))}
    </Flex>
  );
};
​
export default IssueSummary;
​

在主页app/page.tsx中查看。

从数据库获取各状态的问题的数量,再调用IssueSummary。

import prisma from "@/prisma/client";
import IssueSummary from "./IssueSummary";
​
export default async function Home() {
  const open = await prisma.issue.count({ where: { status: "OPEN" } });
  const inProgress = await prisma.issue.count({
    where: { status: "IN_PROGRESS" },
  });
  const closed = await prisma.issue.count({ where: { status: "CLOSED" } });
​
  return <IssueSummary open={open} inProgress={inProgress} closed={closed} />;
}
​

查看页面,还不错。

提交Git,命名:Build the IssueSummary component.


构建条形图组件

现在来创建图表组件,我们使用Recharts,官网是:

https://recharts.org

安装:npm i recharts@2.8.0

在app下新建IssueChart.tsx文件,我们要创建的是条形图,使用的组件是BarChart,使用ResponsiveContainer包裹。为保持统一,外层再用Card包装。

与IssueSummary组件一样,IssueChart组件接受open, inProgress, closed的数量,然后进行展示。根据官网的示例,非常简单。Bar的样式使用了style属性,同时调用fill为 var(–accent-9),这是为了与主题theme一致,当主题变换了之后,Bar的颜色也会变。 “–accent-9”可以通过谷歌浏览器开发工具的查看元素功能获取,这里不详述。

"use client";
import { Card } from "@radix-ui/themes";
import React from "react";
import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis } from "recharts";
​
interface Props {
  open: number;
  inProgress: number;
  closed: number;
}
​
const IssueChart = ({ open, inProgress, closed }: Props) => {
  const data = [
    { label: "Open", value: open },
    { label: "In progress", value: inProgress },
    { label: "Closed", value: closed },
  ];
  return (
    <Card>
      <ResponsiveContainer width="100%" height={300}>
        <BarChart data={data}>
          <XAxis dataKey="label" />
          <YAxis />
          <Bar
            dataKey="value"
            barSize={60}
            style={{ fill: "var(--accent-9)" }}
          />
        </BarChart>
      </ResponsiveContainer>
    </Card>
  );
};
​
export default IssueChart;
​

同样的,app/page.tsx调用查看效果。

提交Git,命名:Build the IssueChart component.


布置仪表板

仪表板的组件已建好,我们来布置。

app/page.tsx

import prisma from "@/prisma/client";
import { Flex, Grid } from "@radix-ui/themes";
import IssueChart from "./IssueChart";
import IssueSummary from "./IssueSummary";
import LastestIssues from "./LastestIssues";
​
export default async function Home() {
  const open = await prisma.issue.count({ where: { status: "OPEN" } });
  const inProgress = await prisma.issue.count({
    where: { status: "IN_PROGRESS" },
  });
  const closed = await prisma.issue.count({ where: { status: "CLOSED" } });
​
  return (
    <Grid columns={{ initial: "1", md: "2" }} gap={"5"}>
      <Flex direction={"column"} gap={"5"}>
        <IssueSummary open={open} inProgress={inProgress} closed={closed} />
        <IssueChart open={open} inProgress={inProgress} closed={closed} />
      </Flex>
      <LastestIssues />
    </Grid>
  );
}
​

形成的响应式页面。大屏和小屏分别显示如下。


小结

本文实现了仪表板功能,分别创建三个组件:顶部的问题概要组件,下面的图表组件,右侧的最新问题组件,并将这些组件组合形成页面。

下一篇是本系列的最后一篇,部署Next.js的生产环境。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注