10 Admin Overview & Orders

10 Admin Overview & Orders
type
Post
status
Published
date
Feb 8, 2026
slug
nextjs-shopping-platform-010
summary
tags
Next.js
category
编程学习
icon
password
😀

1. Section Intro

So now we are ready to start creating the admin area. This will allow users with the admin role to manage products, orders and customers. 现在我们已经准备好开始创建管理后台区域。这将允许具有管理员角色的用户管理产品、订单和客户。
It will have it's own layout much like the user area. There will be an admin menu here. 它将拥有自己的布局,类似于用户区域。这里会有一个管理员菜单。
We will have an admin overview page with things like total revenue, recent orders, number of products and customers and even a cool chart for sales data by month. So we need to create the action and the UI for that. 我们将有一个管理员概览页面,显示总收入、最近的订单、产品和客户数量,甚至还有一个按月显示销售数据的酷炫图表。所以我们需要为此创建操作和 UI。
We're going to use a library called Recharts to show the monthly sales in a bar chart. 我们将使用一个名为 Recharts 的库来在柱状图中显示月度销售额。
Then we want to do the orders page where all orders will be listed out for the admin. 然后我们要做订单页面,管理员可以在其中查看所有订单列表。
We want to add the ability to delete orders. We'll be using a nice alert dialog from ShadCN UI. 我们想添加删除订单的功能。我们将使用来自 ShadCN UI 的一个漂亮的警告对话框。
Then we want admins to be able to update COD orders to paid and delivered. 然后我们希望管理员能够将 COD(货到付款)订单更新为已付款和已送达状态。

2. Admin Menu & Layout 管理员菜单和布局

Now we are going to get into the admin area functionality. Let's start with the admin menu and layout. 现在我们要开始实现管理后台功能。让我们从管理员菜单和布局开始。
Create a new file at app/admin/layout.tsx and add the following code: 在 app/admin/layout.tsx 创建一个新文件,并添加以下代码:
This is very similar to the user layout. There are just different links and a search input. This layout will apply to any pages that are under the /admin route. 这与用户布局非常相似。只是有不同的链接和一个搜索输入框。这个布局将应用于 /admin 路由下的任何页面。
Let's create the admin overview page. Create a new file at app/admin/overview/page.tsx and add the following code for now: 让我们创建管理员概览页面。在 app/admin/overview/page.tsx 创建一个新文件,暂时添加以下代码:
Now go to http://localhost:3000/admin/overview and you should see the dashboard text. 现在访问 http://localhost:3000/admin/overview,你应该能看到 Dashboard 文本。

Add Menu Item To User Button 向用户按钮添加菜单项

We need a way to get here. Let's open the components/shared/header/user-button.tsx file and add the following code right above the <DropdownMenuItem> for the sign out: 我们需要一种方式来进入这里。让我们打开 components/shared/header/user-button.tsx 文件,在登出的 <DropdownMenuItem> 上方添加以下代码:
We are showing the link only if the logged in user is an admin. If you have been following along and are logged in with the admin@example.com email you should see it. If not, change the role value of whatever user you are logged in with to admin. 只有当登录用户是管理员时,我们才显示这个链接。如果你一直跟着做,并且使用 admin@example.com 邮箱登录,你应该能看到它。如果没有,将你登录的用户的 role 值改为 admin

Add role to Next Auth User Type 向 Next Auth 用户类型添加 role

Right now, this will throw a TypeScript error because by default the Next Auth session user type only has a name, email and id. If you want to extend it, we need to create a new file in the types folder at types/next-auth.d.ts. The d is because it's a TypeScript definitions file. 现在,这会抛出 TypeScript 错误,因为默认情况下 Next Auth 会话用户类型只有 name、email 和 id。如果你想扩展它,我们需要在 types 文件夹中创建一个新文件 types/next-auth.d.tsd 是因为它是一个 TypeScript 定义文件。
This tells TypeScript that the User object (and therefore session.user) includes the role property. 这告诉 TypeScript 用户对象(因此 session.user)包含 role 属性。
using declare module augments existing types without changing the original module, ensuring compatibility with updates to the module. 使用 declare module 可以在不更改原始模块的情况下扩展现有类型,确保与模块更新的兼容性。
When you want to extend the session object with custom fields (like role), using DefaultSession ensures that you start with the base structure and build upon it. This avoids overwriting the existing properties. 当你想用自定义字段(如 role)扩展会话对象时,使用 DefaultSession 确保你从基础结构开始并在此基础上构建。这避免了覆盖现有属性。

Admin Main Menu 管理员主导航菜单

Now we will create the main menu for the admin area just like we did with the user area. Create a file at app/admin/main-nav.tsx and add the following code: 现在我们将为管理后台区域创建主导航菜单,就像我们对用户区域所做的那样。在 app/admin/main-nav.tsx 创建一个文件,并添加以下代码:
This is very similar to the user main nav. We are just using a different set of links. 这与用户主导航非常相似。我们只是使用了一组不同的链接。
Now we need to add the main nav to the layout. Open the app/admin/layout.tsx file and import the MainNav component and replace the comment: 现在我们需要将主导航添加到布局中。打开 app/admin/layout.tsx 文件,导入 MainNav 组件并替换注释:

3. Get Order Summary 获取订单摘要

Now we want to get a summart of the orders that have been made on the site. Let's create a new action in the lib/actions/order.actions.ts file: 现在我们要获取网站上已下订单的摘要。让我们在 lib/actions/order.actions.ts 文件中创建一个新的操作:
Now just add the SalesDataType above this function: 现在只需在这个函数上方添加 SalesDataType
Let's go over this function step by step. 让我们一步一步地讲解这个函数。
First, we get the total number of orders, products, and users. 首先,我们获取订单、产品和用户的总数。
Then we use the aggregate method to calculate the total sales by summing up the totalPrice field across all orders. 然后我们使用 aggregate 方法通过汇总所有订单的 totalPrice 字段来计算总销售额。
Then we retrieve monthly sales data, grouped by month and year (formatted as MM/YY). We do this by using the $queryRaw method to execute a raw SQL query. 然后我们检索按月分组的销售数据(格式为 MM/YY)。我们通过使用 $queryRaw 方法执行原始 SQL 查询来实现这一点。
We need to convert the Decimal type from Prisma to a number. 我们需要将 Prisma 的 Decimal 类型转换为 number。
Finally, we fetch the latest 6 orders, sorted by creation date in descending order. We include the user's name for each order. 最后,我们获取最新的 6 个订单,按创建日期降序排序。我们为每个订单包含用户名。
We return all the gathered data in a single object. The salesData is of type salesDataType which is defined above the function. 我们将所有收集到的数据作为一个对象返回。salesData 的类型是 salesDataType,它在函数上方定义。
Next, I want to use this data to display some charts using the Rechart library. Let's do that in the next lesson. 接下来,我想使用这些数据通过 Rechart 库显示一些图表。让我们在下一课中实现这一点。

4. Admin Overview Display Data 管理员概览显示数据

Overview Page With Recharts 使用 Recharts 的概览页面
Now we are going to take the data that we got in the action function and display it on the overview page. 现在我们要将在操作函数中获取的数据显示在概览页面上。
open the app/admin/overview/page.tsx file and add all of the imports: 打开 app/admin/overview/page.tsx 文件并添加所有导入:
We need to create the formatNumber utility function, which is simple. Open the lib/utils.ts file and add the following code: 我们需要创建 formatNumber 工具函数,这很简单。打开 lib/utils.ts 文件并添加以下代码:
All this does is format a number to a string with commas using the Intl.NumberFormat class. 这所做的只是使用 Intl.NumberFormat 类将数字格式化为带逗号的字符串。
Now, back in the app/admin/overview/page.tsx file, add the following code: 现在,回到 app/admin/overview/page.tsx 文件,添加以下代码:
We are getting the session and making sure the user is admin. We are also getting the order summary and displaying the data on the page. I just put a comment where the chart will go for now. Let's add that next. 我们获取会话并确保用户是管理员。我们还获取订单摘要并在页面上显示数据。我现在只在图表位置放了一个注释。让我们在下一步添加它。

5. Monthly Sales Chart 月度销售图表

Now we are going to create the monthly sales chart on the admin overview page. 现在我们将在管理员概览页面上创建月度销售图表。
First, we need to install Recharts. Open a terminal and type the following: 首先,我们需要安装 Recharts。打开终端并输入以下内容:
Now create a file at app/admin/overview/charts.tsx and add the following code: 现在在 app/admin/overview/charts.tsx 中创建一个文件并添加以下代码:
What we are doing is taking in the sales data from the action function and passing it to the chart. We are also using the ResponsiveContainer component to make the chart responsive. We are using the BarChart component to create the chart. We are using the XAxis and YAxis components to create the x and y axis. Finally, we are using the Bar component to create the bars in the chart. 我们正在做的是从 action 函数中获取销售数据并将其传递给图表。我们还使用了 ResponsiveContainer 组件来使图表具有响应性。我们使用 BarChart 组件来创建图表。我们使用 XAxisYAxis 组件来创建 X 轴和 Y 轴。最后,我们使用 Bar 组件在图表中创建柱状图。

Showing The Chart 显示图表

Now go to the app/admin/overview/page.tsx file and import the Charts component: 现在转到 app/admin/overview/page.tsx 文件并导入 Charts 组件:
Now add it in the card and pass in the sales data: 现在将其添加到卡片中并传入销售数据:
Your dashboard should look like this: 你的仪表板应该如下所示:
notion image
Let's move on the the admin orders. 让我们继续处理管理员订单。

6. Protecting Admin Routes

Admin Route Guard 管理员路由守卫
Right now, any user can access the admin screens by typing the URL in the browser. We need to add a route guard to protect the admin screens from unauthorized users. 目前,任何用户都可以通过在浏览器中输入 URL 来访问管理员界面。我们需要添加一个路由守卫来保护管理员界面免受未授权用户的访问。
Create a new file at app/lib/auth-guard.ts and add the following code: 在 app/lib/auth-guard.ts 中创建一个新文件并添加以下代码:
We are just checking if the user is an admin and redirecting to the unauthorized page if they are not. 我们只是检查用户是否是管理员,如果不是,则重定向到未授权页面。

Create Unauthrorized Page 创建未授权页面

Now let's create the unauthorized page. Create a file at app/unauthorized/page.tsx and add the following: 现在让我们创建未授权页面。在 app/unauthorized/page.tsx 中创建一个文件并添加以下内容:

Protect Admin Routes 保护管理员路由

Now we need to bring in the requireAdmin function and use it to protect the admin routes. You may be tempted to use it in the admin layout rather than the individual pages, however, it isn't recommended. When you first go to the page it runs and renders the entire page but if you go to another admin page, you're essentially just fetching a server component and not getting a hard refresh with a new HTML page and the layout will not re-render. 现在我们需要引入 requireAdmin 函数并使用它来保护管理员路由。你可能会想在管理员布局中使用它,而不是在各个页面中使用,但这并不推荐。当你第一次访问页面时,它会运行并渲染整个页面,但如果你访问另一个管理员页面,你实际上只是在获取一个服务器组件,而不会获得带有新 HTML 页面的硬刷新,布局也不会重新渲染。
So what we're going to do is use the requireAdmin function in each admin page to protect it. You just need to require it and call it at the top of the function. 所以我们要做的是在每个管理员页面中使用 requireAdmin 函数来保护它。你只需要引入它并在函数顶部调用它即可。
Open app/admin/overview/page.tsx and add the following: 打开 app/admin/overview/page.tsx 并添加以下内容:
Then call the function at the top of the function: 然后在函数顶部调用该函数:
You want to do the same forall admin pages. I know you may have not created these pages yet, however I had to go back and add this lesson, so just be sure to call requireAdmin at the top of the function for the following pages when you create them: 你需要对所有管理员页面做同样的操作。我知道你可能还没有创建这些页面,但我不得不回过头来添加这一课,所以当你创建以下页面时,确保在函数顶部调用 requireAdmin
  • app/admin/users/page.tsx
  • app/admin/users/[id]/page.tsx
  • app/admin/products/page.tsx
  • app/admin/products/[id]/page.tsx
  • app/admin/products/create/page.tsx
  • app/admin/orders/page.tsx
  • app/admin/overview/page.tsx

7. Admin Orders Page & Action

Get Orders For Admin 获取管理员订单
We have our admin section with the overview and some charts. Now we want the orders section to work. Let's start by creating the action to get all orders. 我们已经有了包含概览和一些图表的管理后台。现在我们希望订单部分能够正常工作。让我们从创建获取所有订单的 action 开始。
Open the lib/actions/orders.actions.ts file and add the following code: 打开 lib/actions/orders.actions.ts 文件并添加以下代码:
We are using pagination for the order list, so we take in a limit that is set to the PAGE_SIZE constant and a page number. We then use the Prisma findMany method to get all orders. We also include the user name in the order so we can display it in the order list. We also get the total number of orders so we can calculate the total number of pages. 我们为订单列表使用了分页功能,因此我们接收一个设置为 PAGE_SIZE 常量的 limit 参数和一个页码。然后我们使用 Prisma 的 findMany 方法获取所有订单。我们还在订单中包含了用户名,以便在订单列表中显示。我们还获取了订单的总数,以便计算总页数。
Now let's create the page where we will display the orders. Create a new file called pages/admin/orders/page.tsx and add the following code: 现在让我们创建用于显示订单的页面。创建一个名为 pages/admin/orders/page.tsx 的新文件并添加以下代码:
We are getting the page from the search params and passing it to the getAllOrders action. We are also checking if the user is an admin. If not we throw an error. We are also logging the orders to the console. 我们从搜索参数中获取 page 并将其传递给 getAllOrders action。我们还会检查用户是否是管理员。如果不是,我们会抛出一个错误。我们还将订单记录到控制台。

Show Orders Table 显示订单表格

Delete the console and let's show the orders in a table. 删除控制台语句,让我们在表格中显示订单。
Add the following to the return statement: 在 return 语句中添加以下内容:
We are simply showing the orders in a table. We are mapping over the orders to show each one. We are using the formatId utility function to format the order ID. We are using the formatDateTime utility function to format the date and time. We are using the formatCurrency utility function to format the total price. There is a comment where the delete button will go. 我们只是在表格中显示订单。我们遍历订单以显示每个订单。我们使用 formatId 工具函数来格式化订单 ID。我们使用 formatDateTime 工具函数来格式化日期和时间。我们使用 formatCurrency 工具函数来格式化总价。有一个注释标明删除按钮的位置。
Now you should see the orders and if there are more than 10 or whatever you set PAGE_SIZE to, you will see the pagination component. 现在您应该能看到订单,如果订单数量超过 10 个或您设置的 PAGE_SIZE 数量,您将看到分页组件。
Let's work on the delete next. 接下来让我们处理删除功能。

8. Delete Order (Admin) 删除订单(管理员)

Admins can now see all of the orders that have been placed. Now we want to show delete buttons on each order.
管理员现在可以查看所有已下的订单。现在我们希望在每个订单上显示删除按钮。

Delete Action 删除操作

First off, we need an action to delete the order. Open the lib/actions/orders.actions.ts file and add the following function:
首先,我们需要一个删除订单的操作。打开 lib/actions/orders.actions.ts 文件并添加以下函数:
This is very simple. We are using the Prisma delete method to delete the order. We are also using the revalidatePath method to revalidate the path /admin/orders. This will update the orders page after the order has been deleted.
这非常简单。我们使用 Prisma 的 delete 方法来删除订单。我们还使用 revalidatePath 方法来重新验证 /admin/orders 路径。这将在订单删除后更新订单页面。

Delete Dialog Component 删除对话框组件

We are going to create a custom component for the delete dialog, but we will get some help from ShadCN, which has dialog components.
我们将创建一个自定义的删除对话框组件,但会从 ShadCN 获得一些帮助,ShadCN 提供了对话框组件。
Install the following from your terminal:
从终端安装以下内容:
Now let's create a new component at components/shared/delete-dialog.tsx and add the following code:
现在让我们在 components/shared/delete-dialog.tsx 创建一个新组件并添加以下代码:
We are just bringing in all the imports and creating the component function, which takes in an id and an action. The action is a function that will be called when the delete button is clicked. It returns a promise, which returns an object with a success property and a message property.
我们只是导入了所有必要的导入并创建了组件函数,该函数接收一个 id 和一个 action。action 是一个函数,当删除按钮被点击时会被调用。它返回一个 Promise,该 Promise 返回一个带有 success 属性和 message 属性的对象。
We are setting some state for the dialog and using the useTransition hook to handle the transition of the dialog. We are also using the useToast hook to show a toast message after the order has been deleted.
我们正在为对话框设置状态,并使用 useTransition 钩子来处理对话框的过渡。我们还使用 useToast 钩子在订单删除后显示一条 toast 消息。
Open the admin/orders/page.tsx file and import both the delete action and the dialog component:
打开 admin/orders/page.tsx 文件并导入删除操作和对话框组件:
Add the component just under the ending </Button> for the details button and pass in the id and the action:
在详情按钮的结束 </Button> 下方添加组件,并传入 id 和 action:
You should just see the text, "Dialog" in the UI. Now let's go back to the delete-dialog.tsx file and add the following code in the return statement:
您应该只会在 UI 中看到"Dialog"文本。现在让我们回到 delete-dialog.tsx 文件并在 return 语句中添加以下代码:
Now you should see the button, but it still doesn't do anything. Let's add a click handler function right above the return statement:
现在您应该能看到按钮,但它仍然不起任何作用。让我们在 return 语句正上方添加一个点击处理函数:
Now add it to the button:
现在将其添加到按钮上:
We are using the startTransition hook to handle the transition of the dialog. We are also using the useToast hook to show a toast message after the order has been deleted.
我们使用 startTransition 钩子来处理对话框的过渡。我们还使用 useToast 钩子在订单删除后显示一条 toast 消息。
The reason that we are passing in the deleteOrder action instead of just bringing it in is because we may want to use this dialog in other places do run other actions.
我们传入 deleteOrder 操作而不是直接导入它的原因是我们可能希望在其他地方使用此对话框来执行其他操作。
You should now be able to delete an order. Go ahead and try it out.
您现在应该能够删除订单了。去试试吧。

9. Update Order Action (COD) 更新订单操作(货到付款)

We can purchase products via PayPal and update the orders to paid However, we will have other payment methods such as Cash on Delivery (COD) and Stripe. We also need to mark as delivered. Let's create a new action in the lib/actions/orders.actions.ts file:
我们可以通过PayPal购买产品并将订单更新为已付款状态。然而,我们还会有其他支付方式,例如货到付款(COD)和Stripe。我们还需要标记订单为已送达。让我们在lib/actions/orders.actions.ts文件中创建一个新的操作:
We are just calling the updateOrderToPaid function from the lib/actions/orders.actions.ts file and passing in the orderId. We are also using the revalidatePath method to revalidate the path /order/${orderId}. This will update the order page after the order has been updated.
我们只是调用lib/actions/orders.actions.ts文件中的updateOrderToPaid函数并传入orderId。我们还使用revalidatePath方法来重新验证路径/order/${orderId}。这将在订单更新后更新订单页面。
Now add the following function to the lib/actions/orders.actions.ts file:
现在将以下函数添加到lib/actions/orders.actions.ts文件中:
We are getting the order from the database and checking if it is paid. If it is not paid, we are throwing an error. If it is paid, we are updating the order to delivered and setting the deliveredAt field to the current date. We are also using the revalidatePath method to revalidate the path /order/${orderId}. This will update the order page after the order has been updated.
我们从数据库中获取订单并检查它是否已付款。如果未付款,我们会抛出一个错误。如果已付款,我们会将订单更新为已送达并将deliveredAt字段设置为当前日期。我们还使用revalidatePath方法来重新验证路径/order/${orderId}。这将在订单更新后更新订单页面。
In the next lesson, we will work on the order page.
在下一课中,我们将处理订单页面。

10. Update Order Buttons (COD)

Order Details Page Delivered 订单详情页面已送达
We now need to show the delivered status on the order details page.
我们现在需要在订单详情页面上显示已送达状态。
Open the app/(root)/order/[id]/page.tsx file and add the auth import:
打开app/(root)/order/[id]/page.tsx文件并添加auth导入:
Above the return statement, get the session using the auth function:
在return语句上方,使用auth函数获取会话:
Now pass in a prop of isAdmin:
现在传入一个isAdmin属性:
Now, open the app/(root)/order/[id]/order-details-table.tsx file and import the two actions that we created in the last lesson, which are updateOrderToPaidByCOD and deliverOrder:
现在,打开app/(root)/order/[id]/order-details-table.tsx文件并导入我们在上一课中创建的两个操作,即updateOrderToPaidByCODdeliverOrder
Also import the useTransition hook and Button component:
还要导入useTransition钩子和Button组件:
We need to take in the isAdmin prop from the parent component:
我们需要从父组件接收isAdmin属性:

Admin buttons 管理员按钮

Now we want to have 2 buttons that only admins have access to to update the paid and delivered status of the order. Add the following code to the return statement right below the PayPal button code and right above the ending </CardContent> tag:
现在我们希望有2个只有管理员才能访问的按钮,用于更新订单的付款和送达状态。将以下代码添加到return语句中,正好在PayPal按钮代码下方和结束的</CardContent>标签上方:
Now let's create the MarkAsPaidButton and MarkAsDeliveredButton components.
现在让我们创建MarkAsPaidButtonMarkAsDeliveredButton组件。
Go right above the return statement in this file and add the following code:
转到该文件中return语句的正上方,添加以下代码:
These buttons are just calling the actions that we created in the last lesson.
这些按钮只是调用我们在上一课中创建的操作。
You can test this out by creating an order and then marking it as paid and delivered.
您可以通过创建订单然后将其标记为已付款和已送出来测试这个功能。
If the purchase uses PayPal, it will be marked as paid and delivered automatically. If it uses Cash on Delivery, you will need to mark it as paid and delivered manually.
如果购买使用PayPal,它将自动标记为已付款和已送达。如果使用货到付款,您将需要手动标记为已付款和已送达。

📎 参考文章

  • 一些引用
  • 引用文章
 
💡
欢迎您在底部评论区留言,一起交流~
上一篇
11 Admin Products & Image Upload
下一篇
09 Order History & User Profile
Loading...