生产管理系统前端 开发中心配置系统 所有页面

This commit is contained in:
2025-10-21 18:04:39 +08:00
parent 4a5d278d89
commit 9afc680833
185 changed files with 13677 additions and 4487 deletions

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}