生产管理系统前端 开发中心配置系统 所有页面
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Role, RoleType } from '../types';
|
||||
|
||||
interface RoleDetailDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
selectedRole: Role | null;
|
||||
}
|
||||
|
||||
export function RoleDetailDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
selectedRole
|
||||
}: RoleDetailDialogProps) {
|
||||
const getRoleTypeBadge = (type: RoleType) => {
|
||||
return type === 'system' ? (
|
||||
<Badge className="bg-blue-100 text-blue-700">系统角色</Badge>
|
||||
) : (
|
||||
<Badge className="bg-green-100 text-green-700">自定义</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
return status === 'active' ? (
|
||||
<Badge className="bg-green-100 text-green-700">启用</Badge>
|
||||
) : (
|
||||
<Badge className="bg-gray-100 text-gray-700">禁用</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
if (!selectedRole) return null;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>角色详情</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
查看角色的详细信息和权限
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label>角色名称</Label>
|
||||
<div className="mt-1">{selectedRole.name}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>角色代码</Label>
|
||||
<div className="mt-1">{selectedRole.code}</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Label>角色描述</Label>
|
||||
<div className="mt-1">{selectedRole.description || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>角色类型</Label>
|
||||
<div className="mt-1">{getRoleTypeBadge(selectedRole.type)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>状态</Label>
|
||||
<div className="mt-1">{getStatusBadge(selectedRole.status)}</div>
|
||||
</div>
|
||||
{selectedRole.defaultHomePage && (
|
||||
<div className="col-span-2">
|
||||
<Label>默认首页</Label>
|
||||
<div className="mt-1">{selectedRole.defaultHomePage}</div>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Label>菜单数量</Label>
|
||||
<div className="mt-1">
|
||||
{selectedRole.menuIds.includes('*') ? '全部' : selectedRole.menuIds.length}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>权限数量</Label>
|
||||
<div className="mt-1">
|
||||
{selectedRole.permissionIds.includes('*') ? '全部' : selectedRole.permissionIds.length}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>创建时间</Label>
|
||||
<div className="mt-1">
|
||||
{new Date(selectedRole.createdAt).toLocaleString('zh-CN')}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>更新时间</Label>
|
||||
<div className="mt-1">
|
||||
{new Date(selectedRole.updatedAt).toLocaleString('zh-CN')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
关闭
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Dialog, DialogContent, DialogDescription, 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 { Textarea } from '@/components/ui/textarea';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { Role, RoleFormData, allSystemMenus } from '../types';
|
||||
|
||||
interface RoleFormDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
editingRole: Role | null;
|
||||
formData: RoleFormData;
|
||||
onFormDataChange: (data: RoleFormData) => void;
|
||||
onSave: () => void;
|
||||
}
|
||||
|
||||
export function RoleFormDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
editingRole,
|
||||
formData,
|
||||
onFormDataChange,
|
||||
onSave
|
||||
}: RoleFormDialogProps) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{editingRole ? '编辑角色' : '添加角色'}</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
{editingRole ? '编辑角色信息' : '添加新角色'}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<ScrollArea className="max-h-[calc(90vh-180px)]">
|
||||
<div className="space-y-6 pr-4">
|
||||
{/* 基本信息 */}
|
||||
<div className="space-y-4">
|
||||
<h4 className="text-green-800">基本信息</h4>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label htmlFor="name">角色名称 *</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={formData.name || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, name: e.target.value })}
|
||||
placeholder="如:数据分析员"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="code">角色代码 *</Label>
|
||||
<Input
|
||||
id="code"
|
||||
value={formData.code || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, code: e.target.value })}
|
||||
placeholder="如:data_analyst"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="description">角色描述</Label>
|
||||
<Textarea
|
||||
id="description"
|
||||
value={formData.description || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, description: e.target.value })}
|
||||
rows={3}
|
||||
placeholder="描述角色的职责和权限范围..."
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="defaultHomePage">默认首页</Label>
|
||||
<Input
|
||||
id="defaultHomePage"
|
||||
value={formData.defaultHomePage || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, defaultHomePage: e.target.value })}
|
||||
placeholder="/machinery/monitoring/location"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 菜单与操作权限 */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-green-800">菜单与操作权限</h4>
|
||||
<p className="text-xs text-muted-foreground">选择菜单后可配置该菜单下的操作权限</p>
|
||||
</div>
|
||||
<Card className="p-4 bg-gray-50">
|
||||
<div className="space-y-6">
|
||||
{allSystemMenus.map((system) => (
|
||||
<div key={system.id} className="space-y-3">
|
||||
{/* 系统级别 */}
|
||||
<div className="flex items-center gap-2 pb-2 border-b border-green-200">
|
||||
<Checkbox
|
||||
id={`system-${system.id}`}
|
||||
checked={
|
||||
formData.menuIds?.includes('*') ||
|
||||
(system.menus.every(menu =>
|
||||
formData.menuIds?.includes(menu.id)
|
||||
) && system.menus.length > 0)
|
||||
}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
// 选中所有一级菜单
|
||||
const allMenuIds = system.menus.map(m => m.id);
|
||||
onFormDataChange({
|
||||
...formData,
|
||||
menuIds: Array.from(new Set([...(formData.menuIds || []), ...allMenuIds])),
|
||||
});
|
||||
} else {
|
||||
// 取消选中所有一级菜单
|
||||
const menuIdsToRemove = system.menus.map(m => m.id);
|
||||
onFormDataChange({
|
||||
...formData,
|
||||
menuIds: (formData.menuIds || []).filter(id => !menuIdsToRemove.includes(id)),
|
||||
});
|
||||
}
|
||||
}}
|
||||
disabled={formData.menuIds?.includes('*')}
|
||||
/>
|
||||
<Label htmlFor={`system-${system.id}`} className="cursor-pointer text-green-700">
|
||||
{system.label}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
{/* 一级菜单 */}
|
||||
<div className="ml-6 space-y-4">
|
||||
{system.menus.map((menu) => (
|
||||
<div key={menu.id} className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id={`menu-${menu.id}`}
|
||||
checked={formData.menuIds?.includes(menu.id) || formData.menuIds?.includes('*')}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
onFormDataChange({
|
||||
...formData,
|
||||
menuIds: [...(formData.menuIds || []), menu.id],
|
||||
});
|
||||
} else {
|
||||
// 取消菜单时,同时取消该菜单下的所有权限
|
||||
const childrenIds = menu.children?.map(c => c.id) || [];
|
||||
const permissionsToRemove = menu.children?.flatMap(c =>
|
||||
c.operations.map(op => op.id)
|
||||
) || [];
|
||||
|
||||
onFormDataChange({
|
||||
...formData,
|
||||
menuIds: (formData.menuIds || []).filter(id =>
|
||||
id !== menu.id && !childrenIds.includes(id)
|
||||
),
|
||||
permissionIds: (formData.permissionIds || []).filter(id =>
|
||||
!permissionsToRemove.includes(id)
|
||||
),
|
||||
});
|
||||
}
|
||||
}}
|
||||
disabled={formData.menuIds?.includes('*')}
|
||||
/>
|
||||
<Label htmlFor={`menu-${menu.id}`} className="cursor-pointer">
|
||||
{menu.label}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
{/* 二级菜单及其操作权限 */}
|
||||
{menu.children && (formData.menuIds?.includes(menu.id) || formData.menuIds?.includes('*')) && (
|
||||
<div className="ml-6 space-y-3 pl-4 border-l-2 border-green-100">
|
||||
{menu.children.map((child) => (
|
||||
<div key={child.id} className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<ChevronRight className="w-3 h-3 text-green-600" />
|
||||
<span className="text-sm text-muted-foreground">{child.label}</span>
|
||||
</div>
|
||||
<div className="ml-5 flex flex-wrap gap-3">
|
||||
{child.operations.map((operation) => (
|
||||
<div key={operation.id} className="flex items-center gap-1.5">
|
||||
<Checkbox
|
||||
id={`permission-${operation.id}`}
|
||||
checked={
|
||||
formData.permissionIds?.includes(operation.id) ||
|
||||
formData.permissionIds?.includes('*')
|
||||
}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
onFormDataChange({
|
||||
...formData,
|
||||
permissionIds: [...(formData.permissionIds || []), operation.id],
|
||||
});
|
||||
} else {
|
||||
onFormDataChange({
|
||||
...formData,
|
||||
permissionIds: (formData.permissionIds || []).filter(id => id !== operation.id),
|
||||
});
|
||||
}
|
||||
}}
|
||||
disabled={formData.permissionIds?.includes('*')}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`permission-${operation.id}`}
|
||||
className="text-xs cursor-pointer text-foreground"
|
||||
>
|
||||
{operation.label}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={onSave}>保存</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Eye, Edit, Trash2, Shield } from 'lucide-react';
|
||||
import { Role, RoleType } from '../types';
|
||||
|
||||
interface RoleListProps {
|
||||
roles: Role[];
|
||||
onViewDetail: (role: Role) => void;
|
||||
onEdit: (role: Role) => void;
|
||||
onDelete: (id: string) => void;
|
||||
}
|
||||
|
||||
export function RoleList({
|
||||
roles,
|
||||
onViewDetail,
|
||||
onEdit,
|
||||
onDelete
|
||||
}: RoleListProps) {
|
||||
const getRoleTypeBadge = (type: RoleType) => {
|
||||
return type === 'system' ? (
|
||||
<Badge className="bg-blue-100 text-blue-700">系统角色</Badge>
|
||||
) : (
|
||||
<Badge className="bg-green-100 text-green-700">自定义</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
return status === 'active' ? (
|
||||
<Badge className="bg-green-100 text-green-700">启用</Badge>
|
||||
) : (
|
||||
<Badge className="bg-gray-100 text-gray-700">禁用</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>角色名称</TableHead>
|
||||
<TableHead>角色代码</TableHead>
|
||||
<TableHead>角色描述</TableHead>
|
||||
<TableHead>类型</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>创建时间</TableHead>
|
||||
<TableHead>操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{roles.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7} className="text-center text-muted-foreground py-8">
|
||||
暂无数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
roles.map((role) => (
|
||||
<TableRow key={role.id}>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield className="w-4 h-4 text-muted-foreground" />
|
||||
{role.name}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground">{role.code}</TableCell>
|
||||
<TableCell className="text-muted-foreground max-w-xs truncate">
|
||||
{role.description}
|
||||
</TableCell>
|
||||
<TableCell>{getRoleTypeBadge(role.type)}</TableCell>
|
||||
<TableCell>{getStatusBadge(role.status)}</TableCell>
|
||||
<TableCell className="text-muted-foreground">
|
||||
{new Date(role.createdAt).toLocaleDateString('zh-CN')}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onViewDetail(role)}
|
||||
>
|
||||
<Eye className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onEdit(role)}
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
</Button>
|
||||
{role.type === 'custom' && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onDelete(role.id)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 text-destructive" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Plus } from 'lucide-react';
|
||||
|
||||
interface RoleManagementHeaderProps {
|
||||
onAddRole: () => void;
|
||||
}
|
||||
|
||||
export function RoleManagementHeader({ onAddRole }: RoleManagementHeaderProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-green-800">角色管理</h2>
|
||||
<p className="text-muted-foreground">基于RBAC的角色访问控制管理</p>
|
||||
</div>
|
||||
<Button onClick={onAddRole}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
添加角色
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Card } from '@/components/ui/card';
|
||||
|
||||
export function RoleManagementInstructions() {
|
||||
return (
|
||||
<Card className="p-4 bg-blue-50 border-blue-200">
|
||||
<h4 className="text-blue-900 mb-2">角色管理说明</h4>
|
||||
<ul className="space-y-1 text-sm text-blue-800">
|
||||
<li>• 系统角色是预定义的角色,不能删除,只能修改描述和权限</li>
|
||||
<li>• 自定义角色可根据业务需求灵活创建</li>
|
||||
<li>• 每个角色可配置可访问的菜单和功能权限</li>
|
||||
<li>• 用户通过分配角色获得相应的系统访问权限</li>
|
||||
<li>• 建议按照职能划分角色,便于权限管理</li>
|
||||
</ul>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { RoleManagementStats, Role } from '../types';
|
||||
|
||||
interface RoleManagementStatsCardsProps {
|
||||
roles: Role[];
|
||||
}
|
||||
|
||||
export function RoleManagementStatsCards({ roles }: RoleManagementStatsCardsProps) {
|
||||
const stats: RoleManagementStats[] = [
|
||||
{
|
||||
label: '总角色数',
|
||||
value: roles.length,
|
||||
color: 'text-blue-600',
|
||||
bg: 'bg-blue-100',
|
||||
},
|
||||
{
|
||||
label: '系统角色',
|
||||
value: roles.filter(r => r.type === 'system').length,
|
||||
color: 'text-purple-600',
|
||||
bg: 'bg-purple-100',
|
||||
},
|
||||
{
|
||||
label: '自定义角色',
|
||||
value: roles.filter(r => r.type === 'custom').length,
|
||||
color: 'text-green-600',
|
||||
bg: 'bg-green-100',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{stats.map((stat, index) => (
|
||||
<Card key={index} className="p-4">
|
||||
<div className="text-sm text-muted-foreground">{stat.label}</div>
|
||||
<div className={`mt-2 ${stat.color} text-2xl font-semibold`}>{stat.value}</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Search } from 'lucide-react';
|
||||
|
||||
interface RoleSearchProps {
|
||||
searchKeyword: string;
|
||||
onSearchChange: (keyword: string) => void;
|
||||
}
|
||||
|
||||
export function RoleSearch({ searchKeyword, onSearchChange }: RoleSearchProps) {
|
||||
return (
|
||||
<Card className="p-4">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="搜索角色名称、代码、描述..."
|
||||
value={searchKeyword}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user