diff --git a/app/Filament/Resources/UserResource.php b/app/Filament/Resources/UserResource.php index ce837e7..9dc1073 100644 --- a/app/Filament/Resources/UserResource.php +++ b/app/Filament/Resources/UserResource.php @@ -31,32 +31,103 @@ class UserResource extends Resource { return $form ->schema([ - Forms\Components\TextInput::make('name') - ->label('用户名称') - ->required() - ->maxLength(255) - ->placeholder('请输入用户名称'), - Forms\Components\TextInput::make('email') - ->label('邮箱') - ->email() - ->required() - ->maxLength(255) - ->placeholder('请输入邮箱地址'), - Forms\Components\TextInput::make('password') - ->label('密码') - ->password() - ->required(fn (string $context): bool => $context === 'create') - ->dehydrated(fn ($state) => filled($state)) - ->minLength(8) - ->placeholder('请输入密码(至少8位)') - ->helperText('编辑时留空表示不修改密码'), - Forms\Components\Select::make('groups') - ->label('所属分组') - ->multiple() - ->relationship('groups', 'name') - ->preload() - ->placeholder('请选择用户所属的分组') - ->helperText('用户可以属于多个分组'), + Forms\Components\Section::make('基本信息') + ->schema([ + Forms\Components\TextInput::make('name') + ->label('用户名称') + ->required() + ->maxLength(255) + ->placeholder('请输入用户名称'), + Forms\Components\TextInput::make('email') + ->label('邮箱') + ->email() + ->required() + ->maxLength(255) + ->placeholder('请输入邮箱地址'), + Forms\Components\TextInput::make('password') + ->label('密码') + ->password() + ->required(fn (string $context): bool => $context === 'create') + ->dehydrated(fn ($state) => filled($state)) + ->minLength(8) + ->placeholder('请输入密码(至少8位)') + ->helperText('编辑时留空表示不修改密码'), + ]) + ->columns(2), + + Forms\Components\Section::make('分组与角色') + ->schema([ + Forms\Components\Select::make('groups') + ->label('所属分组') + ->multiple() + ->relationship('groups', 'name') + ->preload() + ->placeholder('请选择用户所属的分组') + ->helperText('用户可以属于多个分组'), + Forms\Components\Select::make('roles') + ->label('角色') + ->multiple() + ->relationship('roles', 'name') + ->preload() + ->placeholder('请选择用户角色') + ->helperText('角色决定用户的基础权限') + ->searchable(), + ]) + ->columns(2), + + Forms\Components\Section::make('直接权限') + ->description('为用户分配额外的权限,这些权限会叠加到角色权限之上') + ->schema([ + Forms\Components\CheckboxList::make('permissions') + ->label('权限列表') + ->relationship('permissions', 'name') + ->columns(3) + ->gridDirection('row') + ->options(function () { + return \Spatie\Permission\Models\Permission::all() + ->groupBy(function ($permission) { + return explode('.', $permission->name)[0]; + }) + ->map(function ($permissions, $module) { + $moduleNames = [ + 'document' => '文档管理', + 'sop' => 'SOP模板', + 'terminal' => '终端管理', + 'user' => '用户管理', + 'role' => '角色管理', + 'group' => '分组管理', + 'system' => '系统设置', + 'activity' => '操作日志', + ]; + return $permissions->pluck('name', 'name') + ->mapWithKeys(function ($name) use ($moduleNames) { + $parts = explode('.', $name); + $module = $parts[0]; + $action = $parts[1] ?? ''; + $actionNames = [ + 'view' => '查看', + 'create' => '创建', + 'update' => '编辑', + 'delete' => '删除', + 'export' => '导出', + 'import' => '导入', + 'publish' => '发布', + 'archive' => '归档', + 'download' => '下载', + 'sync' => '同步', + ]; + $label = ($moduleNames[$module] ?? $module) . ' - ' . ($actionNames[$action] ?? $action); + return [$name => $label]; + }); + }) + ->flatten() + ->toArray(); + }) + ->searchable() + ->bulkToggleable(), + ]) + ->collapsible() + ->collapsed(), ]); } @@ -75,11 +146,33 @@ class UserResource extends Resource ->label('邮箱') ->searchable() ->sortable(), + Tables\Columns\TextColumn::make('roles.name') + ->label('角色') + ->badge() + ->color(fn (string $state): string => match ($state) { + 'super-admin' => 'danger', + 'admin' => 'warning', + 'user' => 'success', + default => 'gray', + }) + ->formatStateUsing(fn (string $state): string => match ($state) { + 'super-admin' => '超级管理员', + 'admin' => '管理员', + 'user' => '普通用户', + default => $state, + }) + ->searchable() + ->toggleable(), Tables\Columns\TextColumn::make('groups.name') ->label('所属分组') ->badge() ->searchable() ->toggleable(), + Tables\Columns\TextColumn::make('permissions_count') + ->label('权限数量') + ->counts('permissions') + ->sortable() + ->toggleable(), Tables\Columns\TextColumn::make('created_at') ->label('创建时间') ->dateTime('Y-m-d H:i:s') @@ -92,7 +185,11 @@ class UserResource extends Resource ->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ - // + Tables\Filters\SelectFilter::make('roles') + ->label('角色') + ->relationship('roles', 'name') + ->multiple() + ->preload(), ]) ->actions([ Tables\Actions\ViewAction::make() @@ -100,12 +197,35 @@ class UserResource extends Resource Tables\Actions\EditAction::make() ->label('编辑'), Tables\Actions\DeleteAction::make() - ->label('删除'), + ->label('删除') + ->before(function (Tables\Actions\DeleteAction $action, User $record) { + // 防止删除超级管理员 + if ($record->isSuperAdmin()) { + \Filament\Notifications\Notification::make() + ->danger() + ->title('无法删除') + ->body('不能删除超级管理员账户') + ->send(); + $action->cancel(); + } + }), ]) ->bulkActions([ Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make() - ->label('批量删除'), + ->label('批量删除') + ->before(function (Tables\Actions\DeleteBulkAction $action, $records) { + // 检查是否包含超级管理员 + $hasSuperAdmin = $records->contains(fn ($record) => $record->isSuperAdmin()); + if ($hasSuperAdmin) { + \Filament\Notifications\Notification::make() + ->danger() + ->title('无法删除') + ->body('选中的用户中包含超级管理员,无法批量删除') + ->send(); + $action->cancel(); + } + }), ]), ]) ->defaultSort('created_at', 'desc'); @@ -123,6 +243,7 @@ class UserResource extends Resource return [ 'index' => Pages\ListUsers::route('/'), 'create' => Pages\CreateUser::route('/create'), + 'view' => Pages\ViewUser::route('/{record}'), 'edit' => Pages\EditUser::route('/{record}/edit'), ]; } diff --git a/app/Filament/Resources/UserResource/Pages/ViewUser.php b/app/Filament/Resources/UserResource/Pages/ViewUser.php new file mode 100644 index 0000000..5ad5d2f --- /dev/null +++ b/app/Filament/Resources/UserResource/Pages/ViewUser.php @@ -0,0 +1,163 @@ +label('编辑'), + ]; + } + + public function infolist(Infolist $infolist): Infolist + { + return $infolist + ->schema([ + Infolists\Components\Section::make('基本信息') + ->schema([ + Infolists\Components\TextEntry::make('name') + ->label('用户名称'), + Infolists\Components\TextEntry::make('email') + ->label('邮箱'), + Infolists\Components\TextEntry::make('created_at') + ->label('创建时间') + ->dateTime('Y-m-d H:i:s'), + Infolists\Components\TextEntry::make('updated_at') + ->label('更新时间') + ->dateTime('Y-m-d H:i:s'), + ]) + ->columns(2), + + Infolists\Components\Section::make('角色信息') + ->schema([ + Infolists\Components\TextEntry::make('roles.name') + ->label('已分配角色') + ->badge() + ->color(fn (string $state): string => match ($state) { + 'super-admin' => 'danger', + 'admin' => 'warning', + 'user' => 'success', + default => 'gray', + }) + ->formatStateUsing(fn (string $state): string => match ($state) { + 'super-admin' => '超级管理员', + 'admin' => '管理员', + 'user' => '普通用户', + default => $state, + }) + ->placeholder('未分配角色'), + ]), + + Infolists\Components\Section::make('分组信息') + ->schema([ + Infolists\Components\TextEntry::make('groups.name') + ->label('所属分组') + ->badge() + ->placeholder('未加入任何分组'), + ]), + + Infolists\Components\Section::make('权限详情') + ->description('显示用户拥有的所有权限(包括角色权限和直接权限)') + ->schema([ + Infolists\Components\TextEntry::make('all_permissions') + ->label('所有权限') + ->state(function ($record) { + // 获取所有权限(角色权限 + 直接权限) + $permissions = $record->getAllPermissions(); + + if ($permissions->isEmpty()) { + return '无权限'; + } + + // 按模块分组 + $grouped = $permissions->groupBy(function ($permission) { + return explode('.', $permission->name)[0]; + }); + + $moduleNames = [ + 'document' => '文档管理', + 'sop' => 'SOP模板', + 'terminal' => '终端管理', + 'user' => '用户管理', + 'role' => '角色管理', + 'group' => '分组管理', + 'system' => '系统设置', + 'activity' => '操作日志', + ]; + + $actionNames = [ + 'view' => '查看', + 'create' => '创建', + 'update' => '编辑', + 'delete' => '删除', + 'export' => '导出', + 'import' => '导入', + 'publish' => '发布', + 'archive' => '归档', + 'download' => '下载', + 'sync' => '同步', + ]; + + $result = []; + foreach ($grouped as $module => $perms) { + $moduleName = $moduleNames[$module] ?? $module; + $actions = $perms->map(function ($perm) use ($actionNames) { + $parts = explode('.', $perm->name); + $action = $parts[1] ?? ''; + return $actionNames[$action] ?? $action; + })->join('、'); + $result[] = "{$moduleName}:{$actions}"; + } + + return implode("\n", $result); + }) + ->columnSpanFull() + ->placeholder('无权限'), + + Infolists\Components\TextEntry::make('direct_permissions') + ->label('直接权限') + ->state(function ($record) { + $permissions = $record->permissions; + + if ($permissions->isEmpty()) { + return '无直接权限'; + } + + $actionNames = [ + 'view' => '查看', + 'create' => '创建', + 'update' => '编辑', + 'delete' => '删除', + 'export' => '导出', + 'import' => '导入', + 'publish' => '发布', + 'archive' => '归档', + 'download' => '下载', + 'sync' => '同步', + ]; + + return $permissions->map(function ($perm) use ($actionNames) { + $parts = explode('.', $perm->name); + $action = $parts[1] ?? ''; + return $actionNames[$action] ?? $action; + })->join('、'); + }) + ->badge() + ->color('info') + ->columnSpanFull() + ->placeholder('无直接权限'), + ]), + ]); + } +} diff --git a/app/Policies/UserPolicy.php b/app/Policies/UserPolicy.php new file mode 100644 index 0000000..de8aa1c --- /dev/null +++ b/app/Policies/UserPolicy.php @@ -0,0 +1,74 @@ +can('user.view'); + } + + /** + * 查看单个用户 + */ + public function view(User $user, User $model): bool + { + return $user->can('user.view'); + } + + /** + * 创建用户 + */ + public function create(User $user): bool + { + return $user->can('user.create'); + } + + /** + * 更新用户 + */ + public function update(User $user, User $model): bool + { + // 超级管理员只能由超级管理员编辑 + if ($model->isSuperAdmin() && !$user->isSuperAdmin()) { + return false; + } + + return $user->can('user.update'); + } + + /** + * 删除用户 + */ + public function delete(User $user, User $model): bool + { + // 不能删除超级管理员 + if ($model->isSuperAdmin()) { + return false; + } + + // 不能删除自己 + if ($user->id === $model->id) { + return false; + } + + return $user->can('user.delete'); + } + + /** + * 批量删除用户 + */ + public function deleteAny(User $user): bool + { + return $user->can('user.delete'); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index d5856bf..2c5f168 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -36,5 +36,6 @@ class AppServiceProvider extends ServiceProvider Gate::policy(\App\Models\Terminal::class, \App\Policies\TerminalPolicy::class); Gate::policy(SopTemplate::class, SopTemplatePolicy::class); Gate::policy(\Spatie\Permission\Models\Role::class, \App\Policies\RolePolicy::class); + Gate::policy(\App\Models\User::class, \App\Policies\UserPolicy::class); } }