diff --git a/Server/application/superadmin/controller/Administrator.php b/Server/application/superadmin/controller/Administrator.php index 1509e487..cab67ad5 100644 --- a/Server/application/superadmin/controller/Administrator.php +++ b/Server/application/superadmin/controller/Administrator.php @@ -95,7 +95,7 @@ class Administrator extends Controller 'status' => $admin->status, 'authId' => $admin->authId, 'roleName' => $this->getRoleName($admin->authId), - 'createdAt' => date('Y-m-d', $admin->createTime), + 'createdAt' => $admin->createTime, 'lastLogin' => !empty($admin->lastLoginTime) ? date('Y-m-d H:i', $admin->lastLoginTime) : '从未登录', 'permissions' => $this->getPermissions($admin->authId) ]; diff --git a/SuperAdmin/app/dashboard/admins/[id]/edit/page.tsx b/SuperAdmin/app/dashboard/admins/[id]/edit/page.tsx index 7cc8de27..d112b107 100644 --- a/SuperAdmin/app/dashboard/admins/[id]/edit/page.tsx +++ b/SuperAdmin/app/dashboard/admins/[id]/edit/page.tsx @@ -2,29 +2,34 @@ import type React from "react" -import { useState } from "react" +import { useState, useEffect } from "react" import { useRouter } from "next/navigation" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" -import { ArrowLeft } from "lucide-react" +import { ArrowLeft, Loader2 } from "lucide-react" import Link from "next/link" import { Checkbox } from "@/components/ui/checkbox" +import { getAdministratorDetail, AdministratorDetail } from "@/lib/admin-api" +import { useToast } from "@/components/ui/use-toast" -// Sample admin data for editing -const adminData = { - id: "2", - username: "admin_li", - name: "李管理", - permissions: ["project_management", "customer_pool"], -} +// 权限 ID 到前端权限键的映射 +const permissionMapping: Record = { + 1: ["project_management", "customer_pool", "admin_management"], // 超级管理员 + 2: ["project_management", "customer_pool"], // 项目管理员 + 3: ["customer_pool"], // 客户管理员 + 4: [], // 普通管理员 +}; export default function EditAdminPage({ params }: { params: { id: string } }) { const router = useRouter() + const { toast } = useToast() + const [isLoading, setIsLoading] = useState(true) const [isSubmitting, setIsSubmitting] = useState(false) - const [username, setUsername] = useState(adminData.username) - const [name, setName] = useState(adminData.name) + const [adminInfo, setAdminInfo] = useState(null) + const [username, setUsername] = useState("") + const [name, setName] = useState("") const permissions = [ { id: "project_management", label: "项目管理" }, @@ -32,7 +37,42 @@ export default function EditAdminPage({ params }: { params: { id: string } }) { { id: "admin_management", label: "管理员权限" }, ] - const [selectedPermissions, setSelectedPermissions] = useState(adminData.permissions) + const [selectedPermissions, setSelectedPermissions] = useState([]) + + // 加载管理员详情 + useEffect(() => { + const fetchAdminDetail = async () => { + setIsLoading(true) + try { + const response = await getAdministratorDetail(params.id) + if (response.code === 200 && response.data) { + setAdminInfo(response.data) + // 设置表单数据 + setUsername(response.data.username) + setName(response.data.name) + // 根据 authId 设置权限 + setSelectedPermissions(permissionMapping[response.data.authId] || []) + } else { + toast({ + title: "获取管理员详情失败", + description: response.msg || "请稍后重试", + variant: "destructive", + }) + } + } catch (error) { + console.error("获取管理员详情出错:", error) + toast({ + title: "获取管理员详情失败", + description: "请检查网络连接后重试", + variant: "destructive", + }) + } finally { + setIsLoading(false) + } + } + + fetchAdminDetail() + }, [params.id]) const togglePermission = (permissionId: string) => { setSelectedPermissions((prev) => @@ -51,6 +91,17 @@ export default function EditAdminPage({ params }: { params: { id: string } }) { }, 1500) } + if (isLoading) { + return ( +
+
+ +

加载管理员详情中...

+
+
+ ) + } + return (
diff --git a/SuperAdmin/app/layout.tsx b/SuperAdmin/app/layout.tsx index 8e15d431..cf84e55d 100644 --- a/SuperAdmin/app/layout.tsx +++ b/SuperAdmin/app/layout.tsx @@ -3,6 +3,7 @@ import type { Metadata } from "next" import { Inter } from "next/font/google" import "./globals.css" import { ThemeProvider } from "@/components/theme-provider" +import { ToastProvider } from "@/components/ui/use-toast" const inter = Inter({ subsets: ["latin"] }) @@ -21,7 +22,9 @@ export default function RootLayout({ - {children} + + {children} + diff --git a/SuperAdmin/components/ui/toast.tsx b/SuperAdmin/components/ui/toast.tsx index 521b94b0..c6463b30 100644 --- a/SuperAdmin/components/ui/toast.tsx +++ b/SuperAdmin/components/ui/toast.tsx @@ -1,129 +1,61 @@ "use client" import * as React from "react" -import * as ToastPrimitives from "@radix-ui/react-toast" -import { cva, type VariantProps } from "class-variance-authority" import { X } from "lucide-react" import { cn } from "@/lib/utils" -const ToastProvider = ToastPrimitives.Provider - -const ToastViewport = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -ToastViewport.displayName = ToastPrimitives.Viewport.displayName - -const toastVariants = cva( - "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", - { - variants: { - variant: { - default: "border bg-background text-foreground", - destructive: - "destructive group border-destructive bg-destructive text-destructive-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -const Toast = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & - VariantProps ->(({ className, variant, ...props }, ref) => { - return ( - - ) -}) -Toast.displayName = ToastPrimitives.Root.displayName - -const ToastAction = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -ToastAction.displayName = ToastPrimitives.Action.displayName - -const ToastClose = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - -)) -ToastClose.displayName = ToastPrimitives.Close.displayName - -const ToastTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -ToastTitle.displayName = ToastPrimitives.Title.displayName - -const ToastDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -ToastDescription.displayName = ToastPrimitives.Description.displayName - -type ToastProps = React.ComponentPropsWithoutRef - -type ToastActionElement = React.ReactElement - -export { - type ToastProps, - type ToastActionElement, - ToastProvider, - ToastViewport, - Toast, - ToastTitle, - ToastDescription, - ToastClose, - ToastAction, +interface ToastProps extends React.HTMLAttributes { + variant?: "default" | "destructive" | "success" + onDismiss?: () => void + title?: string + description?: string + action?: React.ReactNode +} + +export const ToastProvider = React.Fragment + +export function Toast({ + className, + variant = "default", + onDismiss, + title, + description, + action, + ...props +}: ToastProps) { + const variantStyles = { + default: "bg-background text-foreground", + destructive: "bg-destructive text-destructive-foreground", + success: "bg-green-500 text-white" + } + + return ( +
+
+ {title &&

{title}

} + {description &&

{description}

} +
+ {action} + +
+ ) +} + +export function ToastViewport() { + return ( +
+ ) } diff --git a/SuperAdmin/components/ui/use-toast.tsx b/SuperAdmin/components/ui/use-toast.tsx new file mode 100644 index 00000000..59446f99 --- /dev/null +++ b/SuperAdmin/components/ui/use-toast.tsx @@ -0,0 +1,65 @@ +"use client" + +import { createContext, useContext, useState, useEffect, ReactNode } from "react" +import { Toast, ToastProvider, ToastViewport } from "@/components/ui/toast" + +type ToastProps = { + id: string + title?: string + description?: string + variant?: "default" | "destructive" | "success" + action?: ReactNode +} + +interface ToastContextValue { + toast: (props: Omit) => void + dismiss: (id: string) => void + toasts: ToastProps[] +} + +const ToastContext = createContext(null) + +export function useToast() { + const context = useContext(ToastContext) + if (context === null) { + throw new Error("useToast must be used within a ToastProvider") + } + return context +} + +interface ToastProviderProps { + children: ReactNode +} + +export function ToastProvider({ children }: ToastProviderProps) { + const [toasts, setToasts] = useState([]) + + const toast = (props: Omit) => { + const id = Math.random().toString(36).substring(2, 9) + setToasts((prev) => [...prev, { id, ...props }]) + } + + const dismiss = (id: string) => { + setToasts((prev) => prev.filter((toast) => toast.id !== id)) + } + + // 自动移除 + useEffect(() => { + const timer = setTimeout(() => { + if (toasts.length > 0) { + setToasts((prev) => prev.slice(1)) + } + }, 5000) + return () => clearTimeout(timer) + }, [toasts]) + + return ( + + {children} + + {toasts.map((props) => ( + dismiss(props.id)} /> + ))} + + ) +} \ No newline at end of file diff --git a/SuperAdmin/lib/admin-api.ts b/SuperAdmin/lib/admin-api.ts index 4095a8f5..4d8553b5 100644 --- a/SuperAdmin/lib/admin-api.ts +++ b/SuperAdmin/lib/admin-api.ts @@ -12,6 +12,19 @@ export interface Administrator { permissions: string[]; } +// 管理员详情接口 +export interface AdministratorDetail { + id: number; + username: string; + name: string; + authId: number; + roleName: string; + status: number; + createdAt: string; + lastLogin: string; + permissions: string[]; +} + // 分页响应数据类型 export interface PaginatedResponse { list: T[]; @@ -71,4 +84,36 @@ export async function getAdministrators( data: null }; } +} + +/** + * 获取管理员详情 + * @param id 管理员ID + * @returns 管理员详情 + */ +export async function getAdministratorDetail(id: number | string): Promise> { + const { apiBaseUrl } = getConfig(); + + try { + // 发送请求 + const response = await fetch(`${apiBaseUrl}/administrator/detail/${id}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) { + throw new Error(`请求失败,状态码: ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error('获取管理员详情失败:', error); + return { + code: 500, + msg: '获取管理员详情失败', + data: null + }; + } } \ No newline at end of file