13 changed files with 230 additions and 15 deletions
@ -0,0 +1,41 @@ |
|||||
|
import * as React from 'react'; |
||||
|
import { Slot } from '@radix-ui/react-slot'; |
||||
|
import { cn } from '@/lib/utils'; |
||||
|
|
||||
|
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { |
||||
|
asChild?: boolean; |
||||
|
variant?: 'default' | 'destructive' | 'outline' | 'secondary'; |
||||
|
size?: 'default' | 'sm' | 'lg'; |
||||
|
} |
||||
|
|
||||
|
const buttonVariants = { |
||||
|
default: 'bg-blue-600 text-white hover:bg-blue-700', |
||||
|
destructive: 'bg-red-600 text-white hover:bg-red-700', |
||||
|
outline: 'border border-gray-300 text-gray-700 bg-white hover:bg-gray-50', |
||||
|
secondary: 'bg-gray-100 text-gray-800 hover:bg-gray-200', |
||||
|
}; |
||||
|
|
||||
|
const sizeVariants = { |
||||
|
default: 'px-4 py-2 text-sm', |
||||
|
sm: 'px-3 py-1 text-xs', |
||||
|
lg: 'px-6 py-3 text-base', |
||||
|
}; |
||||
|
|
||||
|
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( |
||||
|
({ className, variant = 'default', size = 'default', asChild = false, ...props }, ref) => { |
||||
|
const Comp = asChild ? Slot : 'button'; |
||||
|
return ( |
||||
|
<Comp |
||||
|
className={cn( |
||||
|
'inline-flex items-center justify-center rounded font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none', |
||||
|
buttonVariants[variant], |
||||
|
sizeVariants[size], |
||||
|
className |
||||
|
)} |
||||
|
ref={ref} |
||||
|
{...props} |
||||
|
/> |
||||
|
); |
||||
|
} |
||||
|
); |
||||
|
Button.displayName = 'Button'; |
@ -0,0 +1,26 @@ |
|||||
|
import * as React from 'react'; |
||||
|
import * as DialogPrimitive from '@radix-ui/react-dialog'; |
||||
|
import { cn } from '@/lib/utils'; |
||||
|
|
||||
|
export const Dialog = DialogPrimitive.Root; |
||||
|
export const DialogTrigger = DialogPrimitive.Trigger; |
||||
|
|
||||
|
export const DialogContent = React.forwardRef< |
||||
|
React.ElementRef<typeof DialogPrimitive.Content>, |
||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> |
||||
|
>(({ className, children, ...props }, ref) => ( |
||||
|
<DialogPrimitive.Portal> |
||||
|
<DialogPrimitive.Overlay className="fixed inset-0 bg-black/30 z-40" /> |
||||
|
<DialogPrimitive.Content |
||||
|
ref={ref} |
||||
|
className={cn( |
||||
|
'fixed left-1/2 top-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2 bg-white p-6 rounded shadow-lg focus:outline-none', |
||||
|
className |
||||
|
)} |
||||
|
{...props} |
||||
|
> |
||||
|
{children} |
||||
|
</DialogPrimitive.Content> |
||||
|
</DialogPrimitive.Portal> |
||||
|
)); |
||||
|
DialogContent.displayName = 'DialogContent'; |
@ -0,0 +1,5 @@ |
|||||
|
import { Outlet } from 'react-router-dom'; |
||||
|
|
||||
|
export default function UserManagePage() { |
||||
|
return <Outlet />; |
||||
|
} |
@ -0,0 +1,43 @@ |
|||||
|
import React, { useState } from 'react'; |
||||
|
import { Button } from '@/components/ui/button'; |
||||
|
|
||||
|
interface RoleFormProps { |
||||
|
onSubmit: (data: { name: string; description?: string }) => void; |
||||
|
} |
||||
|
|
||||
|
export default function RoleForm({ onSubmit }: RoleFormProps) { |
||||
|
const [name, setName] = useState(''); |
||||
|
const [description, setDescription] = useState(''); |
||||
|
|
||||
|
const handleSubmit = (e: React.FormEvent) => { |
||||
|
e.preventDefault(); |
||||
|
onSubmit({ name, description }); |
||||
|
setName(''); |
||||
|
setDescription(''); |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<form onSubmit={handleSubmit} className="space-y-4"> |
||||
|
<div> |
||||
|
<label className="block text-sm font-medium mb-1">角色名</label> |
||||
|
<input |
||||
|
className="w-full border rounded px-3 py-2 focus:outline-none focus:ring focus:border-blue-300" |
||||
|
value={name} |
||||
|
onChange={e => setName(e.target.value)} |
||||
|
required |
||||
|
/> |
||||
|
</div> |
||||
|
<div> |
||||
|
<label className="block text-sm font-medium mb-1">描述</label> |
||||
|
<input |
||||
|
className="w-full border rounded px-3 py-2 focus:outline-none focus:ring focus:border-blue-300" |
||||
|
value={description} |
||||
|
onChange={e => setDescription(e.target.value)} |
||||
|
/> |
||||
|
</div> |
||||
|
<div className="flex justify-end"> |
||||
|
<Button type="submit">保存</Button> |
||||
|
</div> |
||||
|
</form> |
||||
|
); |
||||
|
} |
@ -0,0 +1,45 @@ |
|||||
|
import React from 'react'; |
||||
|
import { Button } from '@/components/ui/button'; |
||||
|
|
||||
|
interface RoleTableProps { |
||||
|
roles: any[]; |
||||
|
loading: boolean; |
||||
|
onDelete: (roleId: string) => void; |
||||
|
} |
||||
|
|
||||
|
export default function RoleTable({ roles, loading, onDelete }: RoleTableProps) { |
||||
|
return ( |
||||
|
<div className="bg-white rounded shadow"> |
||||
|
<table className="min-w-full divide-y divide-gray-200"> |
||||
|
<thead className="bg-gray-50"> |
||||
|
<tr> |
||||
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">角色名</th> |
||||
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">描述</th> |
||||
|
<th className="px-6 py-3"></th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody className="bg-white divide-y divide-gray-200"> |
||||
|
{loading ? ( |
||||
|
<tr> |
||||
|
<td colSpan={3} className="text-center py-6">加载中...</td> |
||||
|
</tr> |
||||
|
) : roles.length === 0 ? ( |
||||
|
<tr> |
||||
|
<td colSpan={3} className="text-center py-6">暂无数据</td> |
||||
|
</tr> |
||||
|
) : ( |
||||
|
roles.map((role) => ( |
||||
|
<tr key={role.id}> |
||||
|
<td className="px-6 py-4 whitespace-nowrap">{role.name}</td> |
||||
|
<td className="px-6 py-4 whitespace-nowrap">{role.description}</td> |
||||
|
<td className="px-6 py-4 whitespace-nowrap text-right"> |
||||
|
<Button variant="destructive" size="sm" onClick={() => onDelete(role.id)}>删除</Button> |
||||
|
</td> |
||||
|
</tr> |
||||
|
)) |
||||
|
)} |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
); |
||||
|
} |
@ -0,0 +1,53 @@ |
|||||
|
import React, { useEffect, useState } from 'react'; |
||||
|
import { Button } from '@/components/ui/button'; |
||||
|
import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog'; |
||||
|
import { getAllRoles, createRole, deleteRole } from '@/services/roleService'; |
||||
|
import RoleTable from './RoleTable'; |
||||
|
import RoleForm from './RoleForm'; |
||||
|
|
||||
|
export default function RolesView() { |
||||
|
const [roles, setRoles] = useState<any[]>([]); |
||||
|
const [loading, setLoading] = useState(false); |
||||
|
const [open, setOpen] = useState(false); |
||||
|
|
||||
|
const fetchRoles = async () => { |
||||
|
setLoading(true); |
||||
|
const res = await getAllRoles(); |
||||
|
setRoles(res.data || []); |
||||
|
setLoading(false); |
||||
|
}; |
||||
|
|
||||
|
useEffect(() => { |
||||
|
fetchRoles(); |
||||
|
}, []); |
||||
|
|
||||
|
const handleCreate = async (data: { name: string; description?: string }) => { |
||||
|
await createRole(data); |
||||
|
setOpen(false); |
||||
|
fetchRoles(); |
||||
|
}; |
||||
|
|
||||
|
const handleDelete = async (roleId: string) => { |
||||
|
await deleteRole(roleId); |
||||
|
fetchRoles(); |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<main className="flex-1 p-4 transition-all duration-300 ease-in-out sm:p-6"> |
||||
|
<div className="w-full space-y-6"> |
||||
|
<div className="flex justify-between items-center mb-4"> |
||||
|
<h1 className="text-2xl font-bold">角色管理</h1> |
||||
|
<Dialog open={open} onOpenChange={setOpen}> |
||||
|
<DialogTrigger asChild> |
||||
|
<Button>新建角色</Button> |
||||
|
</DialogTrigger> |
||||
|
<DialogContent> |
||||
|
<RoleForm onSubmit={handleCreate} /> |
||||
|
</DialogContent> |
||||
|
</Dialog> |
||||
|
</div> |
||||
|
<RoleTable roles={roles} loading={loading} onDelete={handleDelete} /> |
||||
|
</div> |
||||
|
</main> |
||||
|
); |
||||
|
} |
@ -0,0 +1,6 @@ |
|||||
|
import { httpClient } from '@/lib/http-client'; |
||||
|
|
||||
|
export const getAllRoles = () => httpClient.get('/api/Roles/GetAllRoles'); |
||||
|
export const getRole = (roleId: string) => httpClient.get(`/api/Roles/GetRole/${roleId}`); |
||||
|
export const createRole = (data: { name: string; description?: string }) => httpClient.post('/api/Roles/CreateRole', data); |
||||
|
export const deleteRole = (roleId: string) => httpClient.delete(`/api/Roles/DeleteRole/${roleId}`); |
Loading…
Reference in new issue