控制台架構
建議採用「側邊導航 + 頂部工具列 + 主內容區」的標準後台資訊架構,並把核心模組做成可重用的列表與詳情頁。
- 側邊導航:Dashboard、用戶、訂單、積分、設定(支援展開/收合)
- 頂部 Header:搜尋、通知、快速操作(例如匯出、建立優惠)
- 主內容:表格(DataTable)與圖表(銷售趨勢、今日訂單、總營收)
UI 設計原則(響應式 + 可操作性)
- 佈局:固定側邊欄,內容區使用 responsive grid;支援暗黑模式
- 元件:DataTable(排序/篩選/分頁)、Chart(趨勢)、Toast(回饋)
- 電商專屬:訂單狀態 Badge、用戶角色下拉、積分調整表單、退款/出貨操作
- UI 庫方向:Tailwind + shadcn/ui + icon 套件(加速 DataTable、Dialog、Dropdown)
權限與安全(RBAC + Middleware + Server Actions)
後台安全的核心是「所有資料讀寫都在 server-side 驗證」。前端只負責呈現與送出操作,不應持有敏感邏輯。
- 路由層:middleware 保護
/admin/*,未授權導向/unauthorized或後台登入頁 - 資料層:API / Server Actions 內再次驗證角色(避免繞過)
- 角色:至少區分 USER / ADMIN;可擴充 SUPERADMIN、readonly 等
程式碼片段:middleware 保護 /admin
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(req: NextRequest) {
const token = req.cookies.get('admin-token');
if (req.nextUrl.pathname.startsWith('/admin') && !token) {
return NextResponse.redirect(new URL('/admin/login', req.url));
}
return NextResponse.next();
}
export const config = { matcher: '/admin/:path*' };
程式碼片段:Server Action 內驗證 admin
'use server';
import { revalidatePath } from 'next/cache';
export async function updateOrderStatus(formData: FormData) {
// const session = await auth();
// if (session?.user?.role !== 'ADMIN') throw new Error('無權限');
const id = formData.get('id');
const status = formData.get('status');
// await prisma.order.update({ where: { id }, data: { status } });
revalidatePath('/admin/orders');
}
API 端點設計(App Router Route Handlers)
可用 Route Handlers 做 RESTful API(適合第三方整合與工具調用),同時把管理操作盡量用 Server Actions 直接綁表單,減少額外 API 往返。
程式碼片段:端點目錄(示意)
app/api/admin/orders/route.ts // GET /api/admin/orders?page=1&status=pending
app/api/admin/users/[id]/route.ts // GET/PUT/DELETE /api/admin/users/123
app/api/admin/points/route.ts // POST /api/admin/points { userId: '123', points: 100 }
實作範例:用戶管理頁
用戶管理建議以 DataTable 為核心:欄位包含名稱、Email、角色、活躍狀態、動作(編輯/刪除)。更新角色建議走 Server Action,並在 server-side 驗證 admin。
程式碼片段:app/admin/users/page.tsx(示意)
import { DataTable } from '@/components/admin/data-table';
import { columns } from './columns';
export default async function UsersPage({ searchParams }: { searchParams: { page?: string } }) {
// const session = await auth();
// if (session?.user?.role !== 'ADMIN') redirect('/unauthorized');
const users = await db.user.findMany({ /* 查詢 */ });
return <DataTable columns={columns} data={users} />;
}
訂單管理:列表 / 篩選 / 狀態更新 / 通知
訂單管理聚焦在:列表檢視、狀態更新、細節編輯與通知整合。建議支援分頁、狀態篩選、日期範圍、客戶搜尋,以及匯出 CSV/Excel。
API(列表與更新)示意
程式碼片段:GET /api/admin/orders(分頁 + 篩選)
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url);
const page = parseInt(searchParams.get('page') || '1');
const status = searchParams.get('status');
const limit = 20;
const orders = await prisma.order.findMany({
where: status ? { status } : {},
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: 'desc' },
});
return Response.json({ orders });
}
程式碼片段:PATCH /api/admin/orders/[id](更新狀態 + 觸發通知)
export async function PATCH(req: NextRequest, { params }) {
const data = await req.json(); // { status: 'shipped', tracking: 'ABC123' }
const order = await prisma.order.update({
where: { id: params.id },
data,
include: { user: true },
});
// await sendOrderUpdateEmail(order);
return Response.json(order);
}
Server Actions(推薦:避免多餘 API)
程式碼片段:updateOrderStatus(revalidatePath)
'use server';
import { revalidatePath } from 'next/cache';
export async function updateOrderStatus(id: string, newStatus: string) {
// await prisma.order.update({ where: { id }, data: { status: newStatus } });
// await sendStatusEmail(order, newStatus);
revalidatePath('/admin/orders');
}
程式碼片段:app/admin/orders/page.tsx(管理頁骨架)
import { DataTable } from '@/components/ui/data-table';
import { columns } from './columns';
import { getOrders } from './actions';
export default async function OrdersPage({ searchParams }) {
const orders = await getOrders(searchParams);
return (
<div className="space-y-4">
<div className="flex justify-between">
<h1>訂單管理</h1>
<Button>匯出CSV</Button>
</div>
<DataTable columns={columns} data={orders} />
</div>
);
}
| 操作 | API 方法 | 功能 |
|---|---|---|
| 列表查詢 | GET /api/admin/orders | 分頁 / 篩選 |
| 更新狀態 | PATCH /api/admin/orders/[id] | 出貨 / 取消 + 通知 |
| 刪除 | DELETE /api/admin/orders/[id] | 軟刪除 |
| 匯入/匯出 | POST /api/admin/orders/bulk | CSV 批量 |
檔案結構(建議)
程式碼片段:Admin + API 檔案結構
app/
├── api/
│ └── admin/
│ └── orders/
│ ├── route.ts // GET 列表
│ ├── [id]/
│ │ ├── route.ts // GET/PATCH/DELETE 單筆
│ │ └── page.tsx // 細節頁(選用)
│ └── actions.ts // Server Actions
├── admin/
│ └── orders/
│ ├── page.tsx // 管理頁
│ └── columns.ts // DataTable 欄位
└── middleware.ts // 權限檢查
標準訂單狀態流程
後台更新狀態時,建議只在狀態「真的變更」時才觸發通知;並把每次變更記錄成稽核 log(adminId、時間、舊狀態、新狀態、備註)。
- Pending:新訂單,待付款確認
- Processing:已收款 / 揀貨中
- Shipped:已出貨(可附追蹤碼)
- Delivered:已送達
- Complete:歸檔
- Cancelled / Refunded:取消 / 退款
程式碼片段:PATCH 更新狀態時比對 old/new(示意)
export async function PATCH(req: NextRequest, { params }) {
const data = await req.json();
const order = await prisma.order.update({ where: { id: params.id }, data });
// if (data.status !== order.status) {
// await sendStatusEmail(order, data.status);
// }
// revalidatePath('/admin/orders');
return NextResponse.json(order);
}
使用與最佳實務
- 權限:API 內做 authAdmin 檢查,前端顯示只是輔助
- 快取:列表查詢可用 revalidatePath 或設定 revalidate 秒數
- 輸入驗證:用 schema 驗證(例如 Zod),避免髒資料
- 錯誤監控:把後台的關鍵錯誤上報(例如 Sentry),並保留操作 log
- 效能:分頁、索引(orderId、status、createdAt)、避免一次查出大量關聯資料
開始專案
如果你希望把電商後台(訂單/用戶/積分)做成可擴充的控制台,我們可以先以 MVP 後台(訂單列表 + 狀態更新 + RBAC)上線,再逐步補上圖表、批次匯入匯出與更細的權限。