增加权限弹窗
This commit is contained in:
parent
95e66480a2
commit
774523696a
@ -99,7 +99,7 @@ const DialogBody = ({
|
|||||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-1 overflow-y-auto px-6 py-4",
|
"flex-1 overflow-y-auto px-6 py-4 pt-6",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -6,9 +6,7 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Loader2 } from 'lucide-react';
|
||||||
import { Separator } from '@/components/ui/separator';
|
|
||||||
import { FileText, Tag, Folder, AlignLeft, Copy, Loader2 } from 'lucide-react';
|
|
||||||
import { useToast } from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import { createDefinition, updateDefinition } from '../service';
|
import { createDefinition, updateDefinition } from '../service';
|
||||||
import { getEnabledCategories } from '../../../Category/service';
|
import { getEnabledCategories } from '../../../Category/service';
|
||||||
@ -42,7 +40,6 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
|
|||||||
key: '',
|
key: '',
|
||||||
categoryId: undefined as number | undefined,
|
categoryId: undefined as number | undefined,
|
||||||
description: '',
|
description: '',
|
||||||
isTemplate: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 加载分类列表和初始化数据
|
// 加载分类列表和初始化数据
|
||||||
@ -58,7 +55,6 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
|
|||||||
key: record.key,
|
key: record.key,
|
||||||
categoryId: record.categoryId,
|
categoryId: record.categoryId,
|
||||||
description: record.description || '',
|
description: record.description || '',
|
||||||
isTemplate: record.isTemplate || false,
|
|
||||||
});
|
});
|
||||||
} else if (mode === 'create') {
|
} else if (mode === 'create') {
|
||||||
resetForm();
|
resetForm();
|
||||||
@ -81,7 +77,6 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
|
|||||||
key: '',
|
key: '',
|
||||||
categoryId: undefined,
|
categoryId: undefined,
|
||||||
description: '',
|
description: '',
|
||||||
isTemplate: false,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -132,7 +127,7 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
|
|||||||
key: formData.key,
|
key: formData.key,
|
||||||
categoryId: formData.categoryId,
|
categoryId: formData.categoryId,
|
||||||
description: formData.description,
|
description: formData.description,
|
||||||
isTemplate: formData.isTemplate,
|
isTemplate: false,
|
||||||
status: 'DRAFT',
|
status: 'DRAFT',
|
||||||
schema: {
|
schema: {
|
||||||
version: '1.0',
|
version: '1.0',
|
||||||
@ -166,7 +161,7 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
|
|||||||
description: formData.description,
|
description: formData.description,
|
||||||
schema: record.schema,
|
schema: record.schema,
|
||||||
status: record.status,
|
status: record.status,
|
||||||
isTemplate: formData.isTemplate,
|
isTemplate: record.isTemplate,
|
||||||
version: record.version, // 乐观锁版本号
|
version: record.version, // 乐观锁版本号
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -204,14 +199,36 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<DialogBody>
|
<DialogBody>
|
||||||
<Separator />
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* 第一行:表单名称 + 表单标识 */}
|
{/* 第一行:分类 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="form-category">
|
||||||
|
表单分类
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={formData.categoryId?.toString() || undefined}
|
||||||
|
onValueChange={(value) => setFormData(prev => ({ ...prev, categoryId: Number(value) }))}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="form-category" className="h-10">
|
||||||
|
<SelectValue placeholder="选择表单所属分类" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{categories.map(cat => (
|
||||||
|
<SelectItem key={cat.id} value={cat.id.toString()}>
|
||||||
|
{cat.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
帮助用户快速找到相关表单
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 第二行:表单名称 + 表单标识 */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="form-name" className="flex items-center gap-2">
|
<Label htmlFor="form-name">
|
||||||
<FileText className="h-4 w-4 text-muted-foreground" />
|
|
||||||
表单名称
|
表单名称
|
||||||
<span className="text-destructive">*</span>
|
<span className="text-destructive">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
@ -228,8 +245,7 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="form-key" className="flex items-center gap-2">
|
<Label htmlFor="form-key">
|
||||||
<Tag className="h-4 w-4 text-muted-foreground" />
|
|
||||||
表单标识
|
表单标识
|
||||||
<span className="text-destructive">*</span>
|
<span className="text-destructive">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
@ -246,58 +262,9 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 第二行:分类 + 设为模板 */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="form-category" className="flex items-center gap-2">
|
|
||||||
<Folder className="h-4 w-4 text-muted-foreground" />
|
|
||||||
表单分类
|
|
||||||
</Label>
|
|
||||||
<Select
|
|
||||||
value={formData.categoryId?.toString() || undefined}
|
|
||||||
onValueChange={(value) => setFormData(prev => ({ ...prev, categoryId: Number(value) }))}
|
|
||||||
>
|
|
||||||
<SelectTrigger id="form-category" className="h-10">
|
|
||||||
<SelectValue placeholder="选择表单所属分类" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{categories.map(cat => (
|
|
||||||
<SelectItem key={cat.id} value={cat.id.toString()}>
|
|
||||||
{cat.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
帮助用户快速找到相关表单
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="form-isTemplate" className="flex items-center gap-2">
|
|
||||||
<Copy className="h-4 w-4 text-muted-foreground" />
|
|
||||||
设为表单模板
|
|
||||||
</Label>
|
|
||||||
<div className="flex items-center h-10 rounded-lg border px-4 bg-muted/30">
|
|
||||||
<div className="flex-1">
|
|
||||||
<span className="text-sm">启用模板功能</span>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
id="form-isTemplate"
|
|
||||||
checked={formData.isTemplate}
|
|
||||||
onCheckedChange={(checked) => setFormData(prev => ({ ...prev, isTemplate: checked }))}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
模板表单可以被其他表单引用和复制
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 第三行:描述 */}
|
{/* 第三行:描述 */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="form-description" className="flex items-center gap-2">
|
<Label htmlFor="form-description">
|
||||||
<AlignLeft className="h-4 w-4 text-muted-foreground" />
|
|
||||||
表单描述
|
表单描述
|
||||||
</Label>
|
</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
|
|||||||
@ -0,0 +1,339 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogBody,
|
||||||
|
DialogFooter,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableHeader,
|
||||||
|
TableBody,
|
||||||
|
TableRow,
|
||||||
|
TableHead,
|
||||||
|
TableCell,
|
||||||
|
} from '@/components/ui/table';
|
||||||
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
|
import { Plus, Edit, Trash2, Loader2 } from 'lucide-react';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { DataTablePagination } from '@/components/ui/pagination';
|
||||||
|
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
||||||
|
import type { MenuResponse } from '../types';
|
||||||
|
import {
|
||||||
|
getPermissions,
|
||||||
|
createPermission,
|
||||||
|
updatePermission,
|
||||||
|
deletePermission,
|
||||||
|
} from '@/pages/System/Permission/List/service';
|
||||||
|
import type {
|
||||||
|
PermissionResponse,
|
||||||
|
PermissionRequest,
|
||||||
|
} from '@/pages/System/Permission/List/types';
|
||||||
|
import PermissionEditDialog, { type FormValues } from './PermissionEditDialog';
|
||||||
|
|
||||||
|
interface PermissionDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
menu: MenuResponse;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PermissionDialog: React.FC<PermissionDialogProps> = ({
|
||||||
|
open,
|
||||||
|
menu,
|
||||||
|
onOpenChange,
|
||||||
|
}) => {
|
||||||
|
const { toast } = useToast();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [permissions, setPermissions] = useState<PermissionResponse[]>([]);
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
|
pageNum: DEFAULT_CURRENT,
|
||||||
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
|
totalElements: 0,
|
||||||
|
});
|
||||||
|
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||||
|
const [editRecord, setEditRecord] = useState<PermissionResponse | undefined>();
|
||||||
|
|
||||||
|
// 加载权限列表
|
||||||
|
const loadPermissions = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await getPermissions({
|
||||||
|
menuId: menu.id,
|
||||||
|
pageNum: pagination.pageNum - 1,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
});
|
||||||
|
if (response) {
|
||||||
|
setPermissions(response.content || []);
|
||||||
|
setPagination({
|
||||||
|
...pagination,
|
||||||
|
totalElements: response.totalElements || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载权限失败:', error);
|
||||||
|
toast({
|
||||||
|
title: '加载失败',
|
||||||
|
description: '获取权限列表失败',
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
// 每次打开都重置到初始状态
|
||||||
|
setPagination({
|
||||||
|
pageNum: DEFAULT_CURRENT,
|
||||||
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
|
totalElements: 0,
|
||||||
|
});
|
||||||
|
setPermissions([]);
|
||||||
|
setEditDialogOpen(false);
|
||||||
|
setEditRecord(undefined);
|
||||||
|
}
|
||||||
|
}, [open, menu.id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
loadPermissions();
|
||||||
|
}
|
||||||
|
}, [open, pagination.pageNum, pagination.pageSize]);
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const handleAdd = () => {
|
||||||
|
setEditRecord(undefined);
|
||||||
|
setEditDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const handleEdit = (record: PermissionResponse) => {
|
||||||
|
setEditRecord(record);
|
||||||
|
setEditDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleFormSubmit = async (formData: FormValues) => {
|
||||||
|
try {
|
||||||
|
const data: PermissionRequest = {
|
||||||
|
menuId: menu.id,
|
||||||
|
code: formData.code,
|
||||||
|
name: formData.name,
|
||||||
|
type: formData.type,
|
||||||
|
sort: formData.sort,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (editRecord) {
|
||||||
|
await updatePermission(editRecord.id, data);
|
||||||
|
toast({
|
||||||
|
title: '更新成功',
|
||||||
|
description: `权限 "${formData.name}" 已更新`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createPermission(data);
|
||||||
|
toast({
|
||||||
|
title: '创建成功',
|
||||||
|
description: `权限 "${formData.name}" 已创建`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setEditDialogOpen(false);
|
||||||
|
|
||||||
|
// 保存后重置分页到第一页
|
||||||
|
if (!editRecord && pagination.pageNum !== 1) {
|
||||||
|
setPagination({ ...pagination, pageNum: 1 });
|
||||||
|
} else {
|
||||||
|
loadPermissions();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
toast({
|
||||||
|
title: editRecord ? '更新失败' : '创建失败',
|
||||||
|
description: error.response?.data?.message || '操作失败',
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const handleDelete = async (record: PermissionResponse) => {
|
||||||
|
if (!confirm(`确定要删除权限 "${record.name}" 吗?`)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deletePermission(record.id);
|
||||||
|
toast({
|
||||||
|
title: '删除成功',
|
||||||
|
description: `权限 "${record.name}" 已删除`,
|
||||||
|
});
|
||||||
|
loadPermissions();
|
||||||
|
} catch (error: any) {
|
||||||
|
toast({
|
||||||
|
title: '删除失败',
|
||||||
|
description: error.response?.data?.message || '删除失败',
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取权限类型Badge
|
||||||
|
const getTypeBadge = (type: string) => {
|
||||||
|
const typeMap: Record<
|
||||||
|
string,
|
||||||
|
{ text: string; variant: 'default' | 'secondary' | 'outline' }
|
||||||
|
> = {
|
||||||
|
MENU: { text: '菜单', variant: 'default' },
|
||||||
|
BUTTON: { text: '按钮', variant: 'secondary' },
|
||||||
|
API: { text: '接口', variant: 'outline' },
|
||||||
|
};
|
||||||
|
const info = typeMap[type] || { text: type, variant: 'outline' };
|
||||||
|
return <Badge variant={info.variant}>{info.text}</Badge>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 分页变化
|
||||||
|
const handlePageChange = (newPage: number) => {
|
||||||
|
setPagination({ ...pagination, pageNum: newPage + 1 });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="max-w-4xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>权限管理:{menu.name}</DialogTitle>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
管理该菜单下的权限点
|
||||||
|
</p>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<DialogBody>
|
||||||
|
{/* 权限列表 */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="font-medium">权限列表</h3>
|
||||||
|
<Button size="sm" onClick={handleAdd}>
|
||||||
|
<Plus className="h-4 w-4 mr-1" />
|
||||||
|
新增权限
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-md border">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead width="200px">权限编码</TableHead>
|
||||||
|
<TableHead width="150px">权限名称</TableHead>
|
||||||
|
<TableHead width="100px">类型</TableHead>
|
||||||
|
<TableHead width="80px" className="text-center">
|
||||||
|
排序
|
||||||
|
</TableHead>
|
||||||
|
<TableHead width="120px" className="text-right">
|
||||||
|
操作
|
||||||
|
</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{loading ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={5} className="h-24 text-center">
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<Loader2 className="h-6 w-6 animate-spin mr-2" />
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
加载中...
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : permissions.length > 0 ? (
|
||||||
|
permissions.map((item) => (
|
||||||
|
<TableRow key={item.id}>
|
||||||
|
<TableCell width="200px">
|
||||||
|
<code className="text-xs bg-muted px-2 py-0.5 rounded">
|
||||||
|
{item.code}
|
||||||
|
</code>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell width="150px" className="font-medium">
|
||||||
|
{item.name}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell width="100px">
|
||||||
|
{getTypeBadge(item.type)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell width="80px" className="text-center">
|
||||||
|
{item.sort}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell width="120px" className="text-right">
|
||||||
|
<div className="flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleEdit(item)}
|
||||||
|
>
|
||||||
|
<Edit className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleDelete(item)}
|
||||||
|
className="text-destructive hover:text-destructive"
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={5} className="h-24 text-center">
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
暂无权限数据
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<div className="flex justify-end border-t border-border bg-muted/40">
|
||||||
|
<DataTablePagination
|
||||||
|
pageIndex={pagination.pageNum - 1}
|
||||||
|
pageSize={pagination.pageSize}
|
||||||
|
pageCount={Math.ceil(pagination.totalElements / pagination.pageSize)}
|
||||||
|
onPageChange={handlePageChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogBody>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
{/* 编辑对话框 */}
|
||||||
|
<PermissionEditDialog
|
||||||
|
open={editDialogOpen}
|
||||||
|
record={editRecord}
|
||||||
|
menuId={menu.id}
|
||||||
|
menuName={menu.name}
|
||||||
|
onOpenChange={setEditDialogOpen}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PermissionDialog;
|
||||||
|
|
||||||
@ -0,0 +1,177 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogFooter,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import * as z from 'zod';
|
||||||
|
import type { PermissionResponse } from '@/pages/System/Permission/List/types';
|
||||||
|
|
||||||
|
interface PermissionEditDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
record?: PermissionResponse;
|
||||||
|
menuId: number;
|
||||||
|
menuName: string;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
onSubmit: (data: FormValues) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
code: z.string().min(1, '请输入权限编码'),
|
||||||
|
name: z.string().min(1, '请输入权限名称'),
|
||||||
|
type: z.string().min(1, '请选择权限类型'),
|
||||||
|
sort: z.number().min(0, '排序必须大于等于0'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FormValues = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
const PermissionEditDialog: React.FC<PermissionEditDialogProps> = ({
|
||||||
|
open,
|
||||||
|
record,
|
||||||
|
menuId,
|
||||||
|
menuName,
|
||||||
|
onOpenChange,
|
||||||
|
onSubmit,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
reset,
|
||||||
|
setValue,
|
||||||
|
watch,
|
||||||
|
} = useForm<FormValues>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
type: 'BUTTON',
|
||||||
|
sort: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedType = watch('type');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
if (record) {
|
||||||
|
// 编辑模式
|
||||||
|
reset({
|
||||||
|
code: record.code,
|
||||||
|
name: record.name,
|
||||||
|
type: record.type,
|
||||||
|
sort: record.sort,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 新建模式
|
||||||
|
reset({
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
type: 'BUTTON',
|
||||||
|
sort: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [open, record, reset]);
|
||||||
|
|
||||||
|
const handleFormSubmit = (data: FormValues) => {
|
||||||
|
onSubmit(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="sm:max-w-[500px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{record ? '编辑权限' : '新建权限'}</DialogTitle>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
菜单:{menuName}
|
||||||
|
</p>
|
||||||
|
</DialogHeader>
|
||||||
|
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||||
|
<div className="grid gap-4 px-6 py-4">
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="code">权限编码 *</Label>
|
||||||
|
<Input
|
||||||
|
id="code"
|
||||||
|
placeholder="例如: user:create"
|
||||||
|
{...register('code')}
|
||||||
|
/>
|
||||||
|
{errors.code && (
|
||||||
|
<p className="text-sm text-destructive">{errors.code.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="name">权限名称 *</Label>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
placeholder="例如: 创建用户"
|
||||||
|
{...register('name')}
|
||||||
|
/>
|
||||||
|
{errors.name && (
|
||||||
|
<p className="text-sm text-destructive">{errors.name.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="type">权限类型 *</Label>
|
||||||
|
<Select
|
||||||
|
value={selectedType}
|
||||||
|
onValueChange={(value) => setValue('type', value)}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="type">
|
||||||
|
<SelectValue placeholder="选择权限类型" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="MENU">菜单权限</SelectItem>
|
||||||
|
<SelectItem value="BUTTON">按钮权限</SelectItem>
|
||||||
|
<SelectItem value="API">接口权限</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
{errors.type && (
|
||||||
|
<p className="text-sm text-destructive">{errors.type.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="sort">排序 *</Label>
|
||||||
|
<Input
|
||||||
|
id="sort"
|
||||||
|
type="number"
|
||||||
|
placeholder="0"
|
||||||
|
{...register('sort', { valueAsNumber: true })}
|
||||||
|
/>
|
||||||
|
{errors.sort && (
|
||||||
|
<p className="text-sm text-destructive">{errors.sort.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" type="button" onClick={() => onOpenChange(false)}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button type="submit">确定</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PermissionEditDialog;
|
||||||
|
|
||||||
@ -7,7 +7,7 @@ import { useToast } from '@/components/ui/use-toast';
|
|||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
import {
|
import {
|
||||||
Loader2, Plus, Edit, Trash2, ChevronRight, ChevronDown,
|
Loader2, Plus, Edit, Trash2, ChevronRight, ChevronDown,
|
||||||
FolderTree, Menu as MenuIcon, EyeOff
|
FolderTree, Menu as MenuIcon, EyeOff, Key
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { setMenus } from '@/store/userSlice';
|
import { setMenus } from '@/store/userSlice';
|
||||||
@ -16,6 +16,7 @@ import type { MenuResponse } from './types';
|
|||||||
import { getIconComponent } from '@/config/icons.tsx';
|
import { getIconComponent } from '@/config/icons.tsx';
|
||||||
import EditDialog from './components/EditDialog';
|
import EditDialog from './components/EditDialog';
|
||||||
import DeleteDialog from './components/DeleteDialog';
|
import DeleteDialog from './components/DeleteDialog';
|
||||||
|
import PermissionDialog from './components/PermissionDialog';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 菜单管理页面
|
* 菜单管理页面
|
||||||
@ -30,6 +31,8 @@ const MenuPage: React.FC = () => {
|
|||||||
const [editRecord, setEditRecord] = useState<MenuResponse>();
|
const [editRecord, setEditRecord] = useState<MenuResponse>();
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const [deleteRecord, setDeleteRecord] = useState<MenuResponse | null>(null);
|
const [deleteRecord, setDeleteRecord] = useState<MenuResponse | null>(null);
|
||||||
|
const [permissionDialogOpen, setPermissionDialogOpen] = useState(false);
|
||||||
|
const [permissionMenu, setPermissionMenu] = useState<MenuResponse | null>(null);
|
||||||
|
|
||||||
// 加载菜单树
|
// 加载菜单树
|
||||||
const loadMenuTree = async () => {
|
const loadMenuTree = async () => {
|
||||||
@ -93,6 +96,12 @@ const MenuPage: React.FC = () => {
|
|||||||
setDeleteDialogOpen(true);
|
setDeleteDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 权限管理
|
||||||
|
const handlePermission = (record: MenuResponse) => {
|
||||||
|
setPermissionMenu(record);
|
||||||
|
setPermissionDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
const confirmDelete = async () => {
|
const confirmDelete = async () => {
|
||||||
if (!deleteRecord) return;
|
if (!deleteRecord) return;
|
||||||
try {
|
try {
|
||||||
@ -151,6 +160,9 @@ const MenuPage: React.FC = () => {
|
|||||||
key={menu.id}
|
key={menu.id}
|
||||||
className={`hover:bg-muted/50 ${menu.hidden ? 'bg-muted/30' : ''}`}
|
className={`hover:bg-muted/50 ${menu.hidden ? 'bg-muted/30' : ''}`}
|
||||||
>
|
>
|
||||||
|
<TableCell width="80px" className="text-muted-foreground">
|
||||||
|
{menu.id}
|
||||||
|
</TableCell>
|
||||||
<TableCell width="250px">
|
<TableCell width="250px">
|
||||||
<div className="flex items-center gap-2" style={{ paddingLeft: `${level * 24}px` }}>
|
<div className="flex items-center gap-2" style={{ paddingLeft: `${level * 24}px` }}>
|
||||||
{hasChildren ? (
|
{hasChildren ? (
|
||||||
@ -214,6 +226,14 @@ const MenuPage: React.FC = () => {
|
|||||||
<TableCell width="100px" className="text-center">{menu.sort}</TableCell>
|
<TableCell width="100px" className="text-center">{menu.sort}</TableCell>
|
||||||
<TableCell width="200px" sticky>
|
<TableCell width="200px" sticky>
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handlePermission(menu)}
|
||||||
|
title="权限"
|
||||||
|
>
|
||||||
|
<Key className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -337,9 +357,10 @@ const MenuPage: React.FC = () => {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
{/* 表格 */}
|
{/* 表格 */}
|
||||||
<div className="rounded-md border">
|
<div className="rounded-md border">
|
||||||
<Table minWidth="1280px">
|
<Table minWidth="1360px">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
<TableHead width="80px">ID</TableHead>
|
||||||
<TableHead width="250px">菜单名称</TableHead>
|
<TableHead width="250px">菜单名称</TableHead>
|
||||||
<TableHead width="80px">图标</TableHead>
|
<TableHead width="80px">图标</TableHead>
|
||||||
<TableHead width="200px">路由地址</TableHead>
|
<TableHead width="200px">路由地址</TableHead>
|
||||||
@ -353,7 +374,7 @@ const MenuPage: React.FC = () => {
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={8} className="h-24 text-center">
|
<TableCell colSpan={9} className="h-24 text-center">
|
||||||
<div className="flex items-center justify-center">
|
<div className="flex items-center justify-center">
|
||||||
<Loader2 className="h-6 w-6 animate-spin mr-2" />
|
<Loader2 className="h-6 w-6 animate-spin mr-2" />
|
||||||
<span className="text-sm text-muted-foreground">加载中...</span>
|
<span className="text-sm text-muted-foreground">加载中...</span>
|
||||||
@ -364,7 +385,7 @@ const MenuPage: React.FC = () => {
|
|||||||
menuTree.map(menu => renderMenuRow(menu))
|
menuTree.map(menu => renderMenuRow(menu))
|
||||||
) : (
|
) : (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={8} className="h-24 text-center">
|
<TableCell colSpan={9} className="h-24 text-center">
|
||||||
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
||||||
<FolderTree className="w-16 h-16 mb-4 text-muted-foreground/50" />
|
<FolderTree className="w-16 h-16 mb-4 text-muted-foreground/50" />
|
||||||
<div className="text-lg font-semibold mb-2">暂无菜单数据</div>
|
<div className="text-lg font-semibold mb-2">暂无菜单数据</div>
|
||||||
@ -398,6 +419,15 @@ const MenuPage: React.FC = () => {
|
|||||||
onOpenChange={setDeleteDialogOpen}
|
onOpenChange={setDeleteDialogOpen}
|
||||||
onConfirm={confirmDelete}
|
onConfirm={confirmDelete}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 权限管理对话框 */}
|
||||||
|
{permissionMenu && (
|
||||||
|
<PermissionDialog
|
||||||
|
open={permissionDialogOpen}
|
||||||
|
menu={permissionMenu}
|
||||||
|
onOpenChange={setPermissionDialogOpen}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
30
frontend/src/pages/System/Permission/List/service.ts
Normal file
30
frontend/src/pages/System/Permission/List/service.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
import type { PermissionResponse, PermissionRequest, PermissionQuery } from './types';
|
||||||
|
import { Page } from '@/types/base';
|
||||||
|
|
||||||
|
const BASE_URL = '/api/v1/permission';
|
||||||
|
|
||||||
|
// 获取权限列表(分页)
|
||||||
|
export const getPermissions = (params?: PermissionQuery) =>
|
||||||
|
request.get<Page<PermissionResponse>>(`${BASE_URL}/page`, { params });
|
||||||
|
|
||||||
|
// 获取权限列表(不分页,按菜单ID查询)
|
||||||
|
export const getPermissionsByMenuId = (menuId: number) =>
|
||||||
|
request.get<PermissionResponse[]>(`${BASE_URL}/list`, { params: { menuId } });
|
||||||
|
|
||||||
|
// 获取单个权限
|
||||||
|
export const getPermission = (id: number) =>
|
||||||
|
request.get<PermissionResponse>(`${BASE_URL}/${id}`);
|
||||||
|
|
||||||
|
// 创建权限
|
||||||
|
export const createPermission = (data: PermissionRequest) =>
|
||||||
|
request.post<PermissionResponse>(`${BASE_URL}/create`, data);
|
||||||
|
|
||||||
|
// 更新权限
|
||||||
|
export const updatePermission = (id: number, data: PermissionRequest) =>
|
||||||
|
request.put<PermissionResponse>(`${BASE_URL}/${id}`, data);
|
||||||
|
|
||||||
|
// 删除权限
|
||||||
|
export const deletePermission = (id: number) =>
|
||||||
|
request.delete(`${BASE_URL}/${id}`);
|
||||||
|
|
||||||
36
frontend/src/pages/System/Permission/List/types.ts
Normal file
36
frontend/src/pages/System/Permission/List/types.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { BaseQuery, BaseRequest, BaseResponse } from '@/types/base';
|
||||||
|
import type { MenuResponse } from '@/pages/System/Menu/List/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限查询参数
|
||||||
|
*/
|
||||||
|
export interface PermissionQuery extends BaseQuery {
|
||||||
|
menuId?: number;
|
||||||
|
code?: string;
|
||||||
|
name?: string;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限请求参数
|
||||||
|
*/
|
||||||
|
export interface PermissionRequest extends BaseRequest {
|
||||||
|
menuId: number;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
sort: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限响应数据
|
||||||
|
*/
|
||||||
|
export interface PermissionResponse extends BaseResponse {
|
||||||
|
menuId: number;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
sort: number;
|
||||||
|
menu?: MenuResponse;
|
||||||
|
}
|
||||||
|
|
||||||
@ -7,7 +7,7 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { DataTablePagination } from '@/components/ui/pagination';
|
import { DataTablePagination } from '@/components/ui/pagination';
|
||||||
import {
|
import {
|
||||||
Loader2, Plus, Search, Edit, Trash2, KeyRound, Tag as TagIcon,
|
Loader2, Plus, Search, Edit, Trash2, KeyRound, Tag as TagIcon,
|
||||||
ShieldCheck, Settings
|
ShieldCheck, Settings, Shield
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useToast } from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import { getRoleList, deleteRole, getRolePermissions, assignPermissions } from './service';
|
import { getRoleList, deleteRole, getRolePermissions, assignPermissions } from './service';
|
||||||
@ -246,11 +246,12 @@ const RolePage: React.FC = () => {
|
|||||||
|
|
||||||
{/* 表格 */}
|
{/* 表格 */}
|
||||||
<div className="rounded-md border">
|
<div className="rounded-md border">
|
||||||
<Table minWidth="1180px">
|
<Table minWidth="1280px">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead width="150px">角色编码</TableHead>
|
<TableHead width="150px">角色编码</TableHead>
|
||||||
<TableHead width="150px">角色名称</TableHead>
|
<TableHead width="150px">角色名称</TableHead>
|
||||||
|
<TableHead width="100px">类型</TableHead>
|
||||||
<TableHead width="200px">标签</TableHead>
|
<TableHead width="200px">标签</TableHead>
|
||||||
<TableHead width="100px">排序</TableHead>
|
<TableHead width="100px">排序</TableHead>
|
||||||
<TableHead width="200px">描述</TableHead>
|
<TableHead width="200px">描述</TableHead>
|
||||||
@ -261,7 +262,7 @@ const RolePage: React.FC = () => {
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={7} className="h-24 text-center">
|
<TableCell colSpan={8} className="h-24 text-center">
|
||||||
<div className="flex items-center justify-center">
|
<div className="flex items-center justify-center">
|
||||||
<Loader2 className="h-6 w-6 animate-spin mr-2" />
|
<Loader2 className="h-6 w-6 animate-spin mr-2" />
|
||||||
<span className="text-sm text-muted-foreground">加载中...</span>
|
<span className="text-sm text-muted-foreground">加载中...</span>
|
||||||
@ -270,13 +271,22 @@ const RolePage: React.FC = () => {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
) : data?.content && data.content.length > 0 ? (
|
) : data?.content && data.content.length > 0 ? (
|
||||||
data.content.map((record) => {
|
data.content.map((record) => {
|
||||||
const isAdmin = record.code === 'admin';
|
|
||||||
return (
|
return (
|
||||||
<TableRow key={record.id} className="hover:bg-muted/50">
|
<TableRow key={record.id} className="hover:bg-muted/50">
|
||||||
<TableCell width="150px" className="font-medium">
|
<TableCell width="150px" className="font-medium">
|
||||||
<code className="text-sm">{record.code}</code>
|
<code className="text-sm">{record.code}</code>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell width="150px">{record.name}</TableCell>
|
<TableCell width="150px">{record.name}</TableCell>
|
||||||
|
<TableCell width="100px">
|
||||||
|
{record.isAdmin ? (
|
||||||
|
<Badge variant="default" className="bg-amber-500 hover:bg-amber-600">
|
||||||
|
<Shield className="h-3 w-3 mr-1" />
|
||||||
|
管理员
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<Badge variant="outline">普通角色</Badge>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
<TableCell width="200px">
|
<TableCell width="200px">
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{record.tags && record.tags.length > 0 ? (
|
{record.tags && record.tags.length > 0 ? (
|
||||||
@ -330,7 +340,7 @@ const RolePage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<TagIcon className="h-4 w-4" />
|
<TagIcon className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
{!isAdmin && (
|
{!record.isAdmin && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -348,7 +358,7 @@ const RolePage: React.FC = () => {
|
|||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={7} className="h-24 text-center">
|
<TableCell colSpan={8} className="h-24 text-center">
|
||||||
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
||||||
<ShieldCheck className="w-16 h-16 mb-4 text-muted-foreground/50" />
|
<ShieldCheck className="w-16 h-16 mb-4 text-muted-foreground/50" />
|
||||||
<div className="text-lg font-semibold mb-2">暂无角色数据</div>
|
<div className="text-lg font-semibold mb-2">暂无角色数据</div>
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export interface RoleRequest extends BaseRequest{
|
|||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
sort?: number;
|
sort?: number;
|
||||||
|
isAdmin?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 角色响应数据
|
// 角色响应数据
|
||||||
@ -35,6 +36,7 @@ export interface RoleResponse extends BaseResponse {
|
|||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
sort: number;
|
sort: number;
|
||||||
|
isAdmin: boolean;
|
||||||
tags?: RoleTagResponse[];
|
tags?: RoleTagResponse[];
|
||||||
createTime: string;
|
createTime: string;
|
||||||
updateTime: string;
|
updateTime: string;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user