feat(plop): 生成的模块自动应用权限控制
后端 Controller:
- 添加 PermissionGuard 守卫
- 每个接口添加 @RequirePermission 装饰器
- 权限格式: {module}:create/read/update/delete
前端 Table:
- 使用 PermissionGuard 组件进行权限控制
- 新建按钮根据 create 权限显示/隐藏
- 编辑/删除/恢复按钮根据对应权限显示/隐藏
- 整个操作菜单根据 update/delete 权限显示/隐藏
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,15 +42,18 @@ import { JwtAuthGuard } from '@/auth/guards/jwt-auth.guard';
|
||||
{{#unless hasQueryDto}}
|
||||
import { PaginationQueryDto } from '@/common/crud/dto/pagination.dto';
|
||||
{{/unless}}
|
||||
import { RequirePermission } from '@/permission/decorators/require-permission.decorator';
|
||||
import { PermissionGuard } from '@/permission/guards/permission.guard';
|
||||
|
||||
@ApiTags('{{chineseName}}')
|
||||
@Controller('{{kebabCase pluralName}}')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(JwtAuthGuard, PermissionGuard)
|
||||
@ApiBearerAuth()
|
||||
export class {{pascalCase name}}Controller {
|
||||
constructor(private readonly {{camelCase name}}Service: {{pascalCase name}}Service) {}
|
||||
|
||||
@Post()
|
||||
@RequirePermission('{{kebabCase name}}:create')
|
||||
@ApiOperation({ summary: '创建{{chineseName}}' })
|
||||
@ApiCreatedResponse({ type: {{pascalCase name}}ResponseDto, description: '创建成功' })
|
||||
create(@Body() dto: Create{{pascalCase name}}Dto) {
|
||||
@@ -58,6 +61,7 @@ export class {{pascalCase name}}Controller {
|
||||
}
|
||||
|
||||
@Get()
|
||||
@RequirePermission('{{kebabCase name}}:read')
|
||||
@ApiOperation({ summary: '获取所有{{chineseName}}(分页)' })
|
||||
@ApiOkResponse({ type: Paginated{{pascalCase name}}ResponseDto, description: '{{chineseName}}列表' })
|
||||
{{#if hasQueryDto}}
|
||||
@@ -91,6 +95,7 @@ export class {{pascalCase name}}Controller {
|
||||
|
||||
{{#if softDelete}}
|
||||
@Get('deleted')
|
||||
@RequirePermission('{{kebabCase name}}:read')
|
||||
@ApiOperation({ summary: '获取已删除的{{chineseName}}列表(分页)' })
|
||||
@ApiOkResponse({ type: Paginated{{pascalCase name}}ResponseDto, description: '已删除{{chineseName}}列表' })
|
||||
{{#if hasQueryDto}}
|
||||
@@ -116,6 +121,7 @@ export class {{pascalCase name}}Controller {
|
||||
|
||||
{{/if}}
|
||||
@Get(':id')
|
||||
@RequirePermission('{{kebabCase name}}:read')
|
||||
@ApiOperation({ summary: '根据 ID 获取{{chineseName}}' })
|
||||
{{#if needsDetailDto}}
|
||||
@ApiOkResponse({ type: {{pascalCase name}}DetailResponseDto, description: '{{chineseName}}详情' })
|
||||
@@ -135,6 +141,7 @@ export class {{pascalCase name}}Controller {
|
||||
{{/if}}
|
||||
|
||||
@{{#if (eq serviceType 'CrudService')}}Patch{{else}}Put{{/if}}(':id')
|
||||
@RequirePermission('{{kebabCase name}}:update')
|
||||
@ApiOperation({ summary: '更新{{chineseName}}信息' })
|
||||
@ApiOkResponse({ type: {{pascalCase name}}ResponseDto, description: '更新后的{{chineseName}}信息' })
|
||||
update(@Param('id') id: string, @Body() dto: Update{{pascalCase name}}Dto) {
|
||||
@@ -142,6 +149,7 @@ export class {{pascalCase name}}Controller {
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@RequirePermission('{{kebabCase name}}:delete')
|
||||
@ApiOperation({ summary: '删除{{chineseName}}' })
|
||||
@ApiOkResponse({ description: '删除成功' })
|
||||
delete(@Param('id') id: string) {
|
||||
@@ -150,6 +158,7 @@ export class {{pascalCase name}}Controller {
|
||||
|
||||
{{#if softDelete}}
|
||||
@{{#if (eq serviceType 'CrudService')}}Patch{{else}}Put{{/if}}(':id/restore')
|
||||
@RequirePermission('{{kebabCase name}}:update')
|
||||
@ApiOperation({ summary: '恢复已删除的{{chineseName}}' })
|
||||
@ApiOkResponse({ type: {{pascalCase name}}ResponseDto, description: '恢复后的{{chineseName}}信息' })
|
||||
restore(@Param('id') id: string) {
|
||||
@@ -160,6 +169,7 @@ export class {{pascalCase name}}Controller {
|
||||
{{#each manyToMany}}
|
||||
|
||||
@Get(':id/{{name}}')
|
||||
@RequirePermission('{{kebabCase ../name}}:read')
|
||||
@ApiOperation({ summary: '获取{{../chineseName}}的{{name}}列表' })
|
||||
@ApiOkResponse({ type: [{{targetModel}}BriefDto], description: '{{name}}列表' })
|
||||
get{{pascalCase name}}(@Param('id') id: string) {
|
||||
@@ -167,6 +177,7 @@ export class {{pascalCase name}}Controller {
|
||||
}
|
||||
|
||||
@Put(':id/{{name}}')
|
||||
@RequirePermission('{{kebabCase ../name}}:update')
|
||||
@ApiOperation({ summary: '分配{{../chineseName}}的{{name}}' })
|
||||
@ApiOkResponse({ type: {{pascalCase ../name}}DetailResponseDto, description: '分配成功' })
|
||||
assign{{pascalCase name}}(@Param('id') id: string, @Body() dto: Assign{{pascalCase name}}Dto) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { toast } from 'sonner';
|
||||
import { {{pascalCase name}}CreateDialog } from './{{pascalCase name}}CreateDialog';
|
||||
import { {{pascalCase name}}EditDialog } from './{{pascalCase name}}EditDialog';
|
||||
|
||||
import { PermissionGuard } from '@/components/permission/PermissionGuard';
|
||||
import {
|
||||
DataTable,
|
||||
DataTableColumnHeader,
|
||||
@@ -70,28 +71,52 @@ function {{pascalCase name}}Actions({
|
||||
onEdit,
|
||||
}: {{pascalCase name}}ActionsProps) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">打开菜单</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>操作</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<PermissionGuard permission={['{{kebabCase name}}:update', '{{kebabCase name}}:delete']}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">打开菜单</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>操作</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{{#if softDelete}}
|
||||
{isDeleted ? (
|
||||
<DropdownMenuItem onClick={() => onRestore({{camelCase name}}.id)}>
|
||||
<RotateCcw className="mr-2 h-4 w-4" />
|
||||
恢复
|
||||
</DropdownMenuItem>
|
||||
) : (
|
||||
<>
|
||||
{isDeleted ? (
|
||||
<PermissionGuard permission="{{kebabCase name}}:update">
|
||||
<DropdownMenuItem onClick={() => onRestore({{camelCase name}}.id)}>
|
||||
<RotateCcw className="mr-2 h-4 w-4" />
|
||||
恢复
|
||||
</DropdownMenuItem>
|
||||
</PermissionGuard>
|
||||
) : (
|
||||
<>
|
||||
<PermissionGuard permission="{{kebabCase name}}:update">
|
||||
<DropdownMenuItem onClick={() => onEdit({{camelCase name}})}>
|
||||
<Pencil className="mr-2 h-4 w-4" />
|
||||
编辑
|
||||
</DropdownMenuItem>
|
||||
</PermissionGuard>
|
||||
<PermissionGuard permission="{{kebabCase name}}:delete">
|
||||
<DropdownMenuItem
|
||||
onClick={() => onDelete({{camelCase name}}.id)}
|
||||
className="text-destructive"
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
删除
|
||||
</DropdownMenuItem>
|
||||
</PermissionGuard>
|
||||
</>
|
||||
)}
|
||||
{{else}}
|
||||
<PermissionGuard permission="{{kebabCase name}}:update">
|
||||
<DropdownMenuItem onClick={() => onEdit({{camelCase name}})}>
|
||||
<Pencil className="mr-2 h-4 w-4" />
|
||||
编辑
|
||||
</DropdownMenuItem>
|
||||
</PermissionGuard>
|
||||
<PermissionGuard permission="{{kebabCase name}}:delete">
|
||||
<DropdownMenuItem
|
||||
onClick={() => onDelete({{camelCase name}}.id)}
|
||||
className="text-destructive"
|
||||
@@ -99,23 +124,11 @@ function {{pascalCase name}}Actions({
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
删除
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
{{else}}
|
||||
<DropdownMenuItem onClick={() => onEdit({{camelCase name}})}>
|
||||
<Pencil className="mr-2 h-4 w-4" />
|
||||
编辑
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => onDelete({{camelCase name}}.id)}
|
||||
className="text-destructive"
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
删除
|
||||
</DropdownMenuItem>
|
||||
</PermissionGuard>
|
||||
{{/if}}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</PermissionGuard>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -352,10 +365,12 @@ export function {{pascalCase pluralName}}Table() {
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => setCreateDialogOpen(true)}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
新建{{chineseName}}
|
||||
</Button>
|
||||
<PermissionGuard permission="{{kebabCase name}}:create">
|
||||
<Button size="sm" onClick={() => setCreateDialogOpen(true)}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
新建{{chineseName}}
|
||||
</Button>
|
||||
</PermissionGuard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user