From 545435d0955b91841d69b8d14ee2eeab25bf1b69 Mon Sep 17 00:00:00 2001 From: Eamon <17516219072@163.com> Date: Wed, 1 Apr 2026 13:14:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0navigation=E9=87=8C=E7=9A=84?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6=20&=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E8=AE=A4=E9=A2=86=E6=9F=A5=E8=AF=A2=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/actions/user.ts | 4 + src/app/(root)/appbar.tsx | 1 + src/app/(root)/navigation.tsx | 212 +++++++++++++++++------- src/app/(root)/user/page.tsx | 169 +++++++------------ src/components/data-table/component.tsx | 8 +- src/lib/scopes.ts | 82 ++++----- 6 files changed, 261 insertions(+), 215 deletions(-) diff --git a/src/actions/user.ts b/src/actions/user.ts index 3ed5037..298da28 100644 --- a/src/actions/user.ts +++ b/src/actions/user.ts @@ -18,3 +18,7 @@ export async function bindAdmin(params: { user_id: params.id, }) } + +export async function getPageUser(params: object) { + return callByUser("/api/admin/user/get", params) +} diff --git a/src/app/(root)/appbar.tsx b/src/app/(root)/appbar.tsx index 69f42b6..e408016 100644 --- a/src/app/(root)/appbar.tsx +++ b/src/app/(root)/appbar.tsx @@ -112,6 +112,7 @@ export default function Appbar(props: { admin: Admin }) { admin: "管理员", permissions: "权限列表", discount: "折扣管理", + statistics: "数据统计", } return labels[path] || path diff --git a/src/app/(root)/navigation.tsx b/src/app/(root)/navigation.tsx index 4c204c1..227a2bc 100644 --- a/src/app/(root)/navigation.tsx +++ b/src/app/(root)/navigation.tsx @@ -23,6 +23,7 @@ import Link from "next/link" import { usePathname } from "next/navigation" import { createContext, type ReactNode, useContext, useState } from "react" import { twJoin } from "tailwind-merge" +import { Auth } from "@/components/auth" import { Button } from "@/components/ui/button" import { ScrollArea } from "@/components/ui/scroll-area" import { Separator } from "@/components/ui/separator" @@ -32,6 +33,21 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip" +import { + ScopeAdminRead, + ScopeAdminRoleRead, + ScopeBatchRead, + ScopeBillRead, + ScopeChannelRead, + ScopeCouponRead, + ScopeDiscountRead, + ScopePermissionRead, + ScopeProductRead, + ScopeResourceRead, + ScopeTradeRead, + ScopeUserRead, + ScopeUserReadOne, +} from "@/lib/scopes" // Navigation Context interface NavigationContextType { @@ -78,13 +94,16 @@ interface NavItemProps { href: string icon: LucideIcon label: string + requiredScope?: string } -function NavItem({ href, icon: Icon, label }: NavItemProps) { +function NavItem({ href, icon: Icon, label, requiredScope }: NavItemProps) { + // console.log(requiredScope, "requiredScope") + const { collapsed, isActive } = useNavigation() const active = isActive(href) - const linkContent = ( + let linkContent = ( - - {linkContent} - -

{label}

-
-
- + linkContent = ( + + {linkContent} + +

{label}

+
+
) } - return
  • {linkContent}
  • + if (requiredScope) { + linkContent = ( + +
  • {linkContent}
  • +
    + ) + } + + return linkContent } // NavSeparator Component @@ -129,6 +154,109 @@ function NavSeparator() { ) } +const menuSections: { title: string; items: NavItemProps[] }[] = [ + { + title: "概览", + items: [ + { href: "/", icon: Home, label: "首页" }, + { href: "/statistics", icon: BarChart3, label: "数据统计" }, + ], + }, + { + title: "客户", + items: [ + { + href: "/user", + icon: Users, + label: "客户认领", + requiredScope: ScopeUserReadOne, + }, + { + href: "/cust", + icon: ContactRound, + label: "客户管理", + requiredScope: ScopeUserRead, + }, + { + href: "/trade", + icon: Activity, + label: "交易明细", + requiredScope: ScopeTradeRead, + }, + { + href: "/billing", + icon: DollarSign, + label: "账单详情", + requiredScope: ScopeBillRead, + }, + ], + }, + { + title: "运营", + items: [ + { + href: "/product", + icon: ShoppingBag, + label: "产品管理", + requiredScope: ScopeProductRead, + }, + { + href: "/discount", + icon: SquarePercent, + label: "折扣管理", + requiredScope: ScopeDiscountRead, + }, + { + href: "/coupon", + icon: TicketPercent, + label: "优惠券", + requiredScope: ScopeCouponRead, + }, + { + href: "/resources", + icon: Package, + label: "套餐管理", + requiredScope: ScopeResourceRead, + }, + { + href: "/batch", + icon: ClipboardList, + label: "提取记录", + requiredScope: ScopeBatchRead, + }, + { + href: "/channel", + icon: Code, + label: "IP管理", + requiredScope: ScopeChannelRead, + }, + ], + }, + { + title: "系统", + items: [ + { + href: "/admin", + icon: Shield, + label: "管理员", + requiredScope: ScopeAdminRead, + }, + { + href: "/roles", + icon: KeyRound, + label: "角色列表", + requiredScope: ScopeAdminRoleRead, + }, + { + href: "/permissions", + icon: Shield, + label: "权限列表", + requiredScope: ScopePermissionRead, + }, + ], + }, +] + // Main Navigation Component export default function Navigation() { const [collapsed, setCollapsed] = useState(false) @@ -168,56 +296,16 @@ export default function Navigation() { {/* Navigation Menu */} diff --git a/src/app/(root)/user/page.tsx b/src/app/(root)/user/page.tsx index 0f6b64f..db39d96 100644 --- a/src/app/(root)/user/page.tsx +++ b/src/app/(root)/user/page.tsx @@ -3,9 +3,10 @@ import { zodResolver } from "@hookform/resolvers/zod" import { format } from "date-fns" import { Suspense, useCallback, useState } from "react" import { Controller, useForm } from "react-hook-form" +import { toast } from "sonner" import { z } from "zod" -import { bindAdmin, getPageUsers } from "@/actions/user" -import { DataTable, useDataTable } from "@/components/data-table" +import { bindAdmin, getPageUser } from "@/actions/user" +import { DataTable } from "@/components/data-table" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { @@ -15,66 +16,84 @@ import { FieldLabel, } from "@/components/ui/field" import { Input } from "@/components/ui/input" -import { useFetch } from "@/hooks/data" import type { User } from "@/models/user" -type FilterValues = { +interface UserQueryParams { account?: string name?: string - identified?: boolean - enabled?: boolean - assigned?: boolean } const filterSchema = z.object({ account: z.string().optional(), name: z.string().optional(), - identified: z.string().optional(), - enabled: z.string().optional(), - assigned: z.string().optional(), }) type FormValues = z.infer export default function UserPage() { - const [filters, setFilters] = useState({}) + const [userList, setUserList] = useState([]) + const [loading, setLoading] = useState(false) const { control, handleSubmit, reset } = useForm({ resolver: zodResolver(filterSchema), defaultValues: { account: "", name: "", - identified: "all", - enabled: "all", - assigned: "all", }, }) - const fetchUsers = useCallback( - (page: number, size: number) => getPageUsers({ page, size, ...filters }), - [filters], + const fetchUsers = useCallback(async (filters: UserQueryParams = {}) => { + setLoading(true) + try { + const res = await getPageUser(filters) + console.log(res, "res") + + if (res.success) { + const req = [{ ...res.data }] + setUserList(req) + } else { + toast.error(res.message || "获取用户失败") + setUserList([]) + } + } catch (error) { + const message = error instanceof Error ? error.message : error + toast.error(`获取管理员失败: ${message}`) + } finally { + setLoading(false) + } + }, []) + + const bind = useCallback( + async (id: number) => { + try { + const res = await bindAdmin({ id }) + if (res.success) { + toast.success("用户已认领") + fetchUsers() + } else { + toast.error(res.message || "认领失败") + } + } catch (error) { + const message = error instanceof Error ? error.message : error + toast.error(`认领请求失败: ${message}`) + } + }, + [fetchUsers], ) - const table = useDataTable(fetchUsers) - const bind = useFetch(table, (id: number) => bindAdmin({ id }), { - done: "用户已认领", - fail: "用户认领失败", - }) + const onFilter = handleSubmit((data: FormValues) => { + const params: UserQueryParams = {} - const onFilter = handleSubmit(data => { - const result: FilterValues = {} - - if (data.account) result.account = data.account - if (data.name) result.name = data.name - if (data.identified && data.identified !== "all") - result.identified = data.identified === "1" - if (data.enabled && data.enabled !== "all") - result.enabled = data.enabled === "1" - if (data.assigned && data.assigned !== "all") - result.assigned = data.assigned === "has" - - setFilters(result) - table.pagination.onPageChange(1) + if (data.account?.trim()) params.account = data.account.trim() + if (data.name?.trim()) params.name = data.name.trim() + const hasValidFilter = Object.keys(params).length > 0 + if (hasValidFilter) { + fetchUsers(params) + } else { + setUserList([]) + setLoading(false) + toast.info("请输入筛选条件后再查询") + } }) return ( @@ -110,69 +129,6 @@ export default function UserPage() { )} /> - - {/* ( - - 实名状态 - - {fieldState.error?.message} - - )} - /> - - ( - - 账号状态 - - {fieldState.error?.message} - - )} - /> - - ( - - 认领状态 - - {fieldState.error?.message} - - )} - /> */} @@ -181,15 +137,9 @@ export default function UserPage() { type="button" variant="outline" onClick={() => { - reset({ - account: "", - name: "", - identified: "all", - enabled: "all", - assigned: "all", - }) - setFilters({}) - table.pagination.onPageChange(1) + reset() + setUserList([]) + setLoading(false) }} > 重置 @@ -199,7 +149,8 @@ export default function UserPage() { - {...table} + data={userList || []} + status={loading ? "load" : "done"} columns={[ { header: "ID", accessorKey: "id" }, { header: "账号", accessorKey: "username" }, diff --git a/src/components/data-table/component.tsx b/src/components/data-table/component.tsx index 640900b..6ff9217 100644 --- a/src/components/data-table/component.tsx +++ b/src/components/data-table/component.tsx @@ -23,7 +23,7 @@ export type DataTableProps = { data: T[] status: "load" | "done" | "fail" columns: ColumnDef[] - pagination: PaginationProps + pagination?: PaginationProps classNames?: { root?: string headRow?: string @@ -39,9 +39,9 @@ export function DataTable>( columns: props.columns, getCoreRowModel: getCoreRowModel(), manualPagination: true, - rowCount: props.pagination.total, + rowCount: props.pagination?.total, state: { - pagination: { + pagination: props.pagination && { pageIndex: props.pagination.page, pageSize: props.pagination.size, }, @@ -160,7 +160,7 @@ export function DataTable>( {/* 分页器 */} - + {props.pagination && } ) } diff --git a/src/lib/scopes.ts b/src/lib/scopes.ts index 03b868e..3ff7ba4 100644 --- a/src/lib/scopes.ts +++ b/src/lib/scopes.ts @@ -1,65 +1,67 @@ // 权限 -export const ScopePermission = "permission"; -export const ScopePermissionRead = "permission:read"; // 读取权限列表 -export const ScopePermissionWrite = "permission:write"; // 写入权限 +export const ScopePermission = "permission" +export const ScopePermissionRead = "permission:read" // 读取权限列表 +export const ScopePermissionWrite = "permission:write" // 写入权限 // 管理员角色 -export const ScopeAdminRole = "admin_role"; -export const ScopeAdminRoleRead = "admin_role:read"; // 读取管理员角色列表 -export const ScopeAdminRoleWrite = "admin_role:write"; // 写入管理员角色 +export const ScopeAdminRole = "admin_role" +export const ScopeAdminRoleRead = "admin_role:read" // 读取管理员角色列表 +export const ScopeAdminRoleWrite = "admin_role:write" // 写入管理员角色 // 管理员 -export const ScopeAdmin = "admin"; -export const ScopeAdminRead = "admin:read"; // 读取管理员列表 -export const ScopeAdminWrite = "admin:write"; // 写入管理员 +export const ScopeAdmin = "admin" +export const ScopeAdminRead = "admin:read" // 读取管理员列表 +export const ScopeAdminWrite = "admin:write" // 写入管理员 // 产品 -export const ScopeProduct = "product"; -export const ScopeProductRead = "product:read"; // 读取产品列表 -export const ScopeProductWrite = "product:write"; // 写入产品 +export const ScopeProduct = "product" +export const ScopeProductRead = "product:read" // 读取产品列表 +export const ScopeProductWrite = "product:write" // 写入产品 // 产品套餐 -export const ScopeProductSku = "product_sku"; -export const ScopeProductSkuRead = "product_sku:read"; // 读取产品套餐列表 -export const ScopeProductSkuWrite = "product_sku:write"; // 写入产品套餐 +export const ScopeProductSku = "product_sku" +export const ScopeProductSkuRead = "product_sku:read" // 读取产品套餐列表 +export const ScopeProductSkuWrite = "product_sku:write" // 写入产品套餐 // 折扣 -export const ScopeDiscount = "discount"; -export const ScopeDiscountRead = "discount:read"; // 读取折扣列表 -export const ScopeDiscountWrite = "discount:write"; // 写入折扣 +export const ScopeDiscount = "discount" +export const ScopeDiscountRead = "discount:read" // 读取折扣列表 +export const ScopeDiscountWrite = "discount:write" // 写入折扣 // 用户套餐 -export const ScopeResource = "resource"; -export const ScopeResourceRead = "resource:read"; // 读取用户套餐列表 -export const ScopeResourceWrite = "resource:write"; // 写入用户套餐 +export const ScopeResource = "resource" +export const ScopeResourceRead = "resource:read" // 读取用户套餐列表 +export const ScopeResourceWrite = "resource:write" // 写入用户套餐 // 用户 -export const ScopeUser = "user"; -export const ScopeUserRead = "user:read"; // 读取用户列表 -export const ScopeUserWrite = "user:write"; // 写入用户 -export const ScopeUserWriteBalance = "user:write:balance"; // 写入用户余额 +export const ScopeUser = "user" +export const ScopeUserRead = "user:read" // 读取用户列表 +export const ScopeUserWrite = "user:write" // 写入用户 +export const ScopeUserWriteBalance = "user:write:balance" // 写入用户余额 +export const ScopeUserReadOne = "user:read:one" // 读取单个用户 +export const ScopeUserWriteBind = "user:write:bind" // 认领用户 // 优惠券 -export const ScopeCoupon = "coupon"; -export const ScopeCouponRead = "coupon:read"; // 读取优惠券列表 -export const ScopeCouponWrite = "coupon:write"; // 写入优惠券 +export const ScopeCoupon = "coupon" +export const ScopeCouponRead = "coupon:read" // 读取优惠券列表 +export const ScopeCouponWrite = "coupon:write" // 写入优惠券 // 批次 -export const ScopeBatch = "batch"; -export const ScopeBatchRead = "batch:read"; // 读取批次列表 -export const ScopeBatchWrite = "batch:write"; // 写入批次 +export const ScopeBatch = "batch" +export const ScopeBatchRead = "batch:read" // 读取批次列表 +export const ScopeBatchWrite = "batch:write" // 写入批次 // IP -export const ScopeChannel = "channel"; -export const ScopeChannelRead = "channel:read"; // 读取 IP 列表 -export const ScopeChannelWrite = "channel:write"; // 写入 IP +export const ScopeChannel = "channel" +export const ScopeChannelRead = "channel:read" // 读取 IP 列表 +export const ScopeChannelWrite = "channel:write" // 写入 IP // 交易 -export const ScopeTrade = "trade"; -export const ScopeTradeRead = "trade:read"; // 读取交易列表 -export const ScopeTradeWrite = "trade:write"; // 写入交易 +export const ScopeTrade = "trade" +export const ScopeTradeRead = "trade:read" // 读取交易列表 +export const ScopeTradeWrite = "trade:write" // 写入交易 // 账单 -export const ScopeBill = "bill"; -export const ScopeBillRead = "bill:read"; // 读取账单列表 -export const ScopeBillWrite = "bill:write"; // 写入账单 +export const ScopeBill = "bill" +export const ScopeBillRead = "bill:read" // 读取账单列表 +export const ScopeBillWrite = "bill:write" // 写入账单