From e265bcc28df3896943f2b000313de7642db0f573 Mon Sep 17 00:00:00 2001 From: molong Date: Wed, 11 Feb 2026 17:13:18 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Exports/DepartmentExport.php | 12 +- app/Exports/PermissionExport.php | 85 +++++ app/Exports/RoleExport.php | 67 ++++ app/Exports/UserExport.php | 4 +- .../Controllers/Auth/Admin/Permission.php | 59 +++- app/Http/Controllers/Auth/Admin/Role.php | 59 +++- app/Imports/PermissionImport.php | 166 ++++++++++ app/Imports/RoleImport.php | 129 ++++++++ app/Models/Auth/Department.php | 3 +- app/Models/Auth/Permission.php | 3 +- app/Models/Auth/Role.php | 3 +- app/Models/Auth/User.php | 3 +- app/Models/System/City.php | 2 + app/Models/System/Config.php | 3 +- app/Models/System/Dictionary.php | 3 +- app/Models/System/DictionaryItem.php | 2 + app/Models/System/Log.php | 2 + app/Models/System/Task.php | 3 +- app/Services/Auth/ImportExportService.php | 164 ++++++++++ app/Support/Regex.php | 230 ++++++++++++++ app/Support/Time.php | 298 ++++++++++++++++++ app/Traits/ModelTrait.php | 43 +++ config/jwt.php | 2 +- resources/admin/src/api/auth.js | 34 ++ .../src/pages/auth/departments/index.vue | 113 ++----- .../src/pages/auth/permissions/index.vue | 156 +++++++-- .../admin/src/pages/auth/roles/index.vue | 84 ++++- .../admin/src/pages/auth/users/index.vue | 84 ++--- 28 files changed, 1661 insertions(+), 155 deletions(-) create mode 100644 app/Exports/PermissionExport.php create mode 100644 app/Exports/RoleExport.php create mode 100644 app/Imports/PermissionImport.php create mode 100644 app/Imports/RoleImport.php create mode 100644 app/Support/Regex.php create mode 100644 app/Support/Time.php create mode 100644 app/Traits/ModelTrait.php diff --git a/app/Exports/DepartmentExport.php b/app/Exports/DepartmentExport.php index 0934741..f65f283 100644 --- a/app/Exports/DepartmentExport.php +++ b/app/Exports/DepartmentExport.php @@ -53,15 +53,21 @@ class DepartmentExport implements FromCollection, WithHeadings, WithMapping, Sho */ public function map($department): array { + $parentName = ''; + if ($department->parent_id) { + $parent = Department::find($department->parent_id); + $parentName = $parent ? $parent->name : ''; + } + return [ $department->id, $department->name, - $department->parent_id ? Department::find($department->parent_id)?->name : '', + $parentName, $department->leader, $department->phone, - $department->sort, + (int)$department->sort, $department->status == 1 ? '启用' : '禁用', - $department->created_at ? $department->created_at->toDateTimeString() : '', + $department->created_at ? (string)$department->created_at : '', ]; } } diff --git a/app/Exports/PermissionExport.php b/app/Exports/PermissionExport.php new file mode 100644 index 0000000..03e38f9 --- /dev/null +++ b/app/Exports/PermissionExport.php @@ -0,0 +1,85 @@ +permissionIds = $permissionIds; + } + + /** + * 获取数据集合 + */ + public function collection() + { + $query = Permission::query(); + + if (!empty($this->permissionIds)) { + $query->whereIn('id', $this->permissionIds); + } + + return $query->orderBy('sort')->get(); + } + + /** + * 设置表头 + */ + public function headings(): array + { + return [ + 'ID', + '权限标题', + '权限编码', + '权限类型', + '父级ID', + '路由路径', + '前端组件', + '排序', + '状态', + '创建时间', + ]; + } + + /** + * 映射数据 + */ + public function map($permission): array + { + return [ + $permission->id, + $permission->title, + $permission->name, + $this->getTypeName($permission->type), + $permission->parent_id ?: 0, + $permission->path, + $permission->component, + (int)$permission->sort, + $permission->status == 1 ? '启用' : '禁用', + $permission->created_at ? (string)$permission->created_at : '', + ]; + } + + /** + * 获取类型名称 + */ + protected function getTypeName($type): string + { + $types = [ + 'menu' => '菜单', + 'api' => 'API接口', + 'button' => '按钮', + 'url' => '链接', + ]; + return $types[$type] ?? $type; + } +} diff --git a/app/Exports/RoleExport.php b/app/Exports/RoleExport.php new file mode 100644 index 0000000..45bfeec --- /dev/null +++ b/app/Exports/RoleExport.php @@ -0,0 +1,67 @@ +roleIds = $roleIds; + } + + /** + * 获取数据集合 + */ + public function collection() + { + $query = Role::with(['permissions']); + + if (!empty($this->roleIds)) { + $query->whereIn('id', $this->roleIds); + } + + return $query->get(); + } + + /** + * 设置表头 + */ + public function headings(): array + { + return [ + 'ID', + '角色名称', + '角色编码', + '描述', + '权限', + '排序', + '状态', + '创建时间', + ]; + } + + /** + * 映射数据 + */ + public function map($role): array + { + return [ + $role->id, + $role->name, + $role->code, + $role->description, + $role->permissions->pluck('title')->implode(','), + (int)$role->sort, + $role->status == 1 ? '启用' : '禁用', + $role->created_at ? (string)$role->created_at : '', + ]; + } +} diff --git a/app/Exports/UserExport.php b/app/Exports/UserExport.php index e803dd7..dc97cbc 100644 --- a/app/Exports/UserExport.php +++ b/app/Exports/UserExport.php @@ -64,8 +64,8 @@ class UserExport implements FromCollection, WithHeadings, WithMapping, ShouldAut $user->department ? $user->department->name : '', $user->roles->pluck('name')->implode(','), $user->status == 1 ? '启用' : '禁用', - $user->last_login_at ? $user->last_login_at->toDateTimeString() : '', - $user->created_at ? $user->created_at->toDateTimeString() : '', + $user->last_login_at ? (string)$user->last_login_at : '', + $user->created_at ? (string)$user->created_at : '', ]; } } diff --git a/app/Http/Controllers/Auth/Admin/Permission.php b/app/Http/Controllers/Auth/Admin/Permission.php index 44701c0..0582a9d 100644 --- a/app/Http/Controllers/Auth/Admin/Permission.php +++ b/app/Http/Controllers/Auth/Admin/Permission.php @@ -4,15 +4,20 @@ namespace App\Http\Controllers\Auth\Admin; use App\Http\Controllers\Controller; use App\Services\Auth\PermissionService; +use App\Services\Auth\ImportExportService; use Illuminate\Http\Request; class Permission extends Controller { protected $permissionService; + protected $importExportService; - public function __construct(PermissionService $permissionService) - { + public function __construct( + PermissionService $permissionService, + ImportExportService $importExportService + ) { $this->permissionService = $permissionService; + $this->importExportService = $importExportService; } /** @@ -177,4 +182,54 @@ class Permission extends Controller 'data' => ['count' => $count], ]); } + + /** + * 导出权限 + */ + public function export(Request $request) + { + $validated = $request->validate([ + 'ids' => 'nullable|array', + 'ids.*' => 'integer', + ]); + + $filename = $this->importExportService->exportPermissions($validated['ids'] ?? []); + + $filePath = $this->importExportService->getExportFilePath($filename); + + return response()->download($filePath, $filename)->deleteFileAfterSend(); + } + + /** + * 导入权限 + */ + public function import(Request $request) + { + $validated = $request->validate([ + 'file' => 'required|file|mimes:xlsx,xls', + ]); + + $file = $request->file('file'); + $realPath = $file->getRealPath(); + $filename = $file->getClientOriginalName(); + + $result = $this->importExportService->importPermissions($filename, $realPath); + + return response()->json([ + 'code' => 200, + 'message' => "导入完成,成功 {$result['success_count']} 条,失败 {$result['error_count']} 条", + 'data' => $result, + ]); + } + + /** + * 下载权限导入模板 + */ + public function downloadTemplate() + { + $filename = $this->importExportService->downloadPermissionTemplate(); + $filePath = $this->importExportService->getExportFilePath($filename); + + return response()->download($filePath, $filename)->deleteFileAfterSend(); + } } diff --git a/app/Http/Controllers/Auth/Admin/Role.php b/app/Http/Controllers/Auth/Admin/Role.php index 3e1474b..0d44740 100644 --- a/app/Http/Controllers/Auth/Admin/Role.php +++ b/app/Http/Controllers/Auth/Admin/Role.php @@ -4,15 +4,20 @@ namespace App\Http\Controllers\Auth\Admin; use App\Http\Controllers\Controller; use App\Services\Auth\RoleService; +use App\Services\Auth\ImportExportService; use Illuminate\Http\Request; class Role extends Controller { protected $roleService; + protected $importExportService; - public function __construct(RoleService $roleService) - { + public function __construct( + RoleService $roleService, + ImportExportService $importExportService + ) { $this->roleService = $roleService; + $this->importExportService = $importExportService; } /** @@ -237,4 +242,54 @@ class Role extends Controller 'data' => $result, ]); } + + /** + * 导出角色 + */ + public function export(Request $request) + { + $validated = $request->validate([ + 'ids' => 'nullable|array', + 'ids.*' => 'integer', + ]); + + $filename = $this->importExportService->exportRoles($validated['ids'] ?? []); + + $filePath = $this->importExportService->getExportFilePath($filename); + + return response()->download($filePath, $filename)->deleteFileAfterSend(); + } + + /** + * 导入角色 + */ + public function import(Request $request) + { + $validated = $request->validate([ + 'file' => 'required|file|mimes:xlsx,xls', + ]); + + $file = $request->file('file'); + $realPath = $file->getRealPath(); + $filename = $file->getClientOriginalName(); + + $result = $this->importExportService->importRoles($filename, $realPath); + + return response()->json([ + 'code' => 200, + 'message' => "导入完成,成功 {$result['success_count']} 条,失败 {$result['error_count']} 条", + 'data' => $result, + ]); + } + + /** + * 下载角色导入模板 + */ + public function downloadTemplate() + { + $filename = $this->importExportService->downloadRoleTemplate(); + $filePath = $this->importExportService->getExportFilePath($filename); + + return response()->download($filePath, $filename)->deleteFileAfterSend(); + } } diff --git a/app/Imports/PermissionImport.php b/app/Imports/PermissionImport.php new file mode 100644 index 0000000..096a0ff --- /dev/null +++ b/app/Imports/PermissionImport.php @@ -0,0 +1,166 @@ + $row) { + try { + // 跳过空行 + if (empty($row['权限标题'])) { + continue; + } + + // 检查权限编码是否已存在 + $exists = Permission::where('name', $row['权限编码'])->exists(); + if ($exists) { + $this->addError($index + 2, '权限编码已存在'); + continue; + } + + // 查找父级权限 + $parentId = null; + if (!empty($row['父级ID']) && $row['父级ID'] != 0) { + $parent = Permission::find($row['父级ID']); + if (!$parent) { + $this->addError($index + 2, '父级权限不存在'); + continue; + } + $parentId = $parent->id; + } + + // 解析类型 + $type = $this->parseType($row['权限类型']); + + // 解析元数据 + $meta = null; + if ($type === 'menu' && !empty($row['元数据'])) { + // 元数据格式: icon:Setting,hidden:false,keepAlive:false + $meta = $this->parseMeta($row['元数据']); + } + + // 创建权限 + Permission::create([ + 'title' => $row['权限标题'], + 'name' => $row['权限编码'], + 'type' => $type, + 'parent_id' => $parentId, + 'path' => $row['路由路径'] ?? null, + 'component' => $row['前端组件'] ?? null, + 'meta' => $meta, + 'sort' => $row['排序'] ?? 0, + 'status' => 1, + ]); + + $this->successCount++; + } catch (\Exception $e) { + $this->addError($index + 2, $e->getMessage()); + } + } + } + + /** + * 验证规则 + */ + public function rules(): array + { + return [ + '权限标题' => 'required|string|max:50', + '权限编码' => 'required|string|max:100', + '权限类型' => 'required|in:菜单,API接口,按钮,链接,menu,api,button,url', + '父级ID' => 'nullable|integer|min:0', + '路由路径' => 'nullable|string|max:200', + '前端组件' => 'nullable|string|max:200', + '排序' => 'nullable|integer|min:0', + ]; + } + + /** + * 自定义验证消息 + */ + public function customValidationMessages(): array + { + return [ + '权限标题.required' => '权限标题不能为空', + '权限编码.required' => '权限编码不能为空', + '权限类型.required' => '权限类型不能为空', + ]; + } + + /** + * 解析类型 + */ + protected function parseType($type): string + { + $typeMap = [ + '菜单' => 'menu', + 'API接口' => 'api', + '接口' => 'api', + '按钮' => 'button', + '链接' => 'url', + ]; + + return $typeMap[$type] ?? $type; + } + + /** + * 解析元数据 + */ + protected function parseMeta($metaString): ?string + { + if (empty($metaString)) { + return null; + } + + // 简单解析,实际项目中可能需要更复杂的解析逻辑 + return $metaString; + } + + /** + * 添加错误 + */ + protected function addError(int $row, string $message): void + { + $this->errorCount++; + $this->errors[] = "第 {$row} 行: {$message}"; + } + + /** + * 获取成功数量 + */ + public function getSuccessCount(): int + { + return $this->successCount; + } + + /** + * 获取错误数量 + */ + public function getErrorCount(): int + { + return $this->errorCount; + } + + /** + * 获取错误信息 + */ + public function getErrors(): array + { + return $this->errors; + } +} diff --git a/app/Imports/RoleImport.php b/app/Imports/RoleImport.php new file mode 100644 index 0000000..6d8f799 --- /dev/null +++ b/app/Imports/RoleImport.php @@ -0,0 +1,129 @@ + $row) { + try { + // 跳过空行 + if (empty($row['角色名称'])) { + continue; + } + + // 检查角色编码是否已存在 + $exists = Role::where('code', $row['角色编码'])->exists(); + if ($exists) { + $this->addError($index + 2, '角色编码已存在'); + continue; + } + + // 查找权限 + $permissionIds = []; + if (!empty($row['权限(多个用逗号分隔)'])) { + $permissionNames = array_map('trim', explode(',', $row['权限(多个用逗号分隔)'])); + $permissions = Permission::whereIn('title', $permissionNames)->get(); + + if ($permissions->count() != count($permissionNames)) { + $existingNames = $permissions->pluck('title')->toArray(); + $notFound = array_diff($permissionNames, $existingNames); + $this->addError($index + 2, '权限不存在: ' . implode(', ', $notFound)); + continue; + } + $permissionIds = $permissions->pluck('id')->toArray(); + } + + // 创建角色 + $role = Role::create([ + 'name' => $row['角色名称'], + 'code' => $row['角色编码'], + 'description' => $row['描述'] ?? null, + 'sort' => $row['排序'] ?? 0, + 'status' => 1, + ]); + + // 分配权限 + if (!empty($permissionIds)) { + $role->permissions()->attach($permissionIds); + } + + $this->successCount++; + } catch (\Exception $e) { + $this->addError($index + 2, $e->getMessage()); + } + } + } + + /** + * 验证规则 + */ + public function rules(): array + { + return [ + '角色名称' => 'required|string|max:50', + '角色编码' => 'required|string|max:50', + '描述' => 'nullable|string|max:200', + '排序' => 'nullable|integer|min:0', + ]; + } + + /** + * 自定义验证消息 + */ + public function customValidationMessages(): array + { + return [ + '角色名称.required' => '角色名称不能为空', + '角色编码.required' => '角色编码不能为空', + ]; + } + + /** + * 添加错误 + */ + protected function addError(int $row, string $message): void + { + $this->errorCount++; + $this->errors[] = "第 {$row} 行: {$message}"; + } + + /** + * 获取成功数量 + */ + public function getSuccessCount(): int + { + return $this->successCount; + } + + /** + * 获取错误数量 + */ + public function getErrorCount(): int + { + return $this->errorCount; + } + + /** + * 获取错误信息 + */ + public function getErrors(): array + { + return $this->errors; + } +} diff --git a/app/Models/Auth/Department.php b/app/Models/Auth/Department.php index e725aa5..3515f5d 100644 --- a/app/Models/Auth/Department.php +++ b/app/Models/Auth/Department.php @@ -2,13 +2,14 @@ namespace App\Models\Auth; +use App\Traits\ModelTrait; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; class Department extends Model { - use SoftDeletes; + use ModelTrait, SoftDeletes; protected $table = 'auth_departments'; diff --git a/app/Models/Auth/Permission.php b/app/Models/Auth/Permission.php index f187650..8c9aef7 100644 --- a/app/Models/Auth/Permission.php +++ b/app/Models/Auth/Permission.php @@ -2,13 +2,14 @@ namespace App\Models\Auth; +use App\Traits\ModelTrait; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\SoftDeletes; class Permission extends Model { - use SoftDeletes; + use ModelTrait, SoftDeletes; protected $table = 'auth_permissions'; diff --git a/app/Models/Auth/Role.php b/app/Models/Auth/Role.php index 7ed958d..74626c7 100644 --- a/app/Models/Auth/Role.php +++ b/app/Models/Auth/Role.php @@ -2,13 +2,14 @@ namespace App\Models\Auth; +use App\Traits\ModelTrait; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\SoftDeletes; class Role extends Model { - use SoftDeletes; + use ModelTrait, SoftDeletes; protected $table = 'auth_roles'; diff --git a/app/Models/Auth/User.php b/app/Models/Auth/User.php index 2c1f39b..85a13d5 100644 --- a/app/Models/Auth/User.php +++ b/app/Models/Auth/User.php @@ -2,6 +2,7 @@ namespace App\Models\Auth; +use App\Traits\ModelTrait; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; @@ -11,7 +12,7 @@ use Tymon\JWTAuth\Contracts\JWTSubject; class User extends Authenticatable implements JWTSubject { - use SoftDeletes; + use ModelTrait, SoftDeletes; protected $table = 'auth_users'; diff --git a/app/Models/System/City.php b/app/Models/System/City.php index 318512c..6e1d40d 100644 --- a/app/Models/System/City.php +++ b/app/Models/System/City.php @@ -2,11 +2,13 @@ namespace App\Models\System; +use App\Traits\ModelTrait; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; class City extends Model { + use ModelTrait; protected $table = 'system_cities'; protected $fillable = [ diff --git a/app/Models/System/Config.php b/app/Models/System/Config.php index f5d5008..03dedc4 100644 --- a/app/Models/System/Config.php +++ b/app/Models/System/Config.php @@ -2,12 +2,13 @@ namespace App\Models\System; +use App\Traits\ModelTrait; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; class Config extends Model { - use SoftDeletes; + use ModelTrait, SoftDeletes; protected $table = 'system_configs'; diff --git a/app/Models/System/Dictionary.php b/app/Models/System/Dictionary.php index 98be2c5..b052e83 100644 --- a/app/Models/System/Dictionary.php +++ b/app/Models/System/Dictionary.php @@ -2,13 +2,14 @@ namespace App\Models\System; +use App\Traits\ModelTrait; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; class Dictionary extends Model { - use SoftDeletes; + use ModelTrait, SoftDeletes; protected $table = 'system_dictionaries'; diff --git a/app/Models/System/DictionaryItem.php b/app/Models/System/DictionaryItem.php index fe2db1a..0a2cbfa 100644 --- a/app/Models/System/DictionaryItem.php +++ b/app/Models/System/DictionaryItem.php @@ -2,11 +2,13 @@ namespace App\Models\System; +use App\Traits\ModelTrait; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; class DictionaryItem extends Model { + use ModelTrait; protected $table = 'system_dictionary_items'; protected $fillable = [ diff --git a/app/Models/System/Log.php b/app/Models/System/Log.php index 1805257..99350a2 100644 --- a/app/Models/System/Log.php +++ b/app/Models/System/Log.php @@ -2,11 +2,13 @@ namespace App\Models\System; +use App\Traits\ModelTrait; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; class Log extends Model { + use ModelTrait; protected $table = 'system_logs'; protected $fillable = [ diff --git a/app/Models/System/Task.php b/app/Models/System/Task.php index b78da8b..37b9f24 100644 --- a/app/Models/System/Task.php +++ b/app/Models/System/Task.php @@ -2,12 +2,13 @@ namespace App\Models\System; +use App\Traits\ModelTrait; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; class Task extends Model { - use SoftDeletes; + use ModelTrait, SoftDeletes; protected $table = 'system_tasks'; diff --git a/app/Services/Auth/ImportExportService.php b/app/Services/Auth/ImportExportService.php index 4ea3372..17a3372 100644 --- a/app/Services/Auth/ImportExportService.php +++ b/app/Services/Auth/ImportExportService.php @@ -10,8 +10,12 @@ use Illuminate\Validation\ValidationException; use Maatwebsite\Excel\Facades\Excel; use App\Exports\UserExport; use App\Exports\DepartmentExport; +use App\Exports\RoleExport; +use App\Exports\PermissionExport; use App\Imports\UserImport; use App\Imports\DepartmentImport; +use App\Imports\RoleImport; +use App\Imports\PermissionImport; class ImportExportService { @@ -187,6 +191,166 @@ class ImportExportService return storage_path('app/exports/' . $filename); } + /** + * 下载角色导入模板 + */ + public function downloadRoleTemplate(): string + { + $filename = 'role_import_template_' . date('YmdHis') . '.xlsx'; + + // 确保目录存在 + if (!is_dir(storage_path('app/exports'))) { + mkdir(storage_path('app/exports'), 0755, true); + } + + $templateData = [ + [ + '角色名称*', + '角色编码*', + '描述', + '权限(多个用逗号分隔)', + '排序', + ], + [ + '测试角色', + 'test_role', + '这是一个测试角色', + '查看用户,编辑用户', + '1', + ], + ]; + + Excel::store(new \App\Exports\GenericExport($templateData), 'exports/' . $filename); + + return $filename; + } + + /** + * 导出角色数据 + */ + public function exportRoles(array $roleIds = []): string + { + $filename = 'roles_export_' . date('YmdHis') . '.xlsx'; + + // 确保目录存在 + if (!is_dir(storage_path('app/exports'))) { + mkdir(storage_path('app/exports'), 0755, true); + } + + Excel::store(new RoleExport($roleIds), 'exports/' . $filename); + + return $filename; + } + + /** + * 导入角色数据 + */ + public function importRoles(string $filePath, string $realPath): array + { + if (!file_exists($realPath)) { + throw ValidationException::withMessages([ + 'file' => ['文件不存在'], + ]); + } + + $import = new RoleImport(); + Excel::import($import, $realPath); + + // 删除临时文件 + if (file_exists($realPath)) { + unlink($realPath); + } + + return [ + 'success_count' => $import->getSuccessCount(), + 'error_count' => $import->getErrorCount(), + 'errors' => $import->getErrors(), + ]; + } + + /** + * 下载权限导入模板 + */ + public function downloadPermissionTemplate(): string + { + $filename = 'permission_import_template_' . date('YmdHis') . '.xlsx'; + + // 确保目录存在 + if (!is_dir(storage_path('app/exports'))) { + mkdir(storage_path('app/exports'), 0755, true); + } + + $templateData = [ + [ + '权限标题*', + '权限编码*', + '权限类型*', + '父级ID', + '路由路径', + '前端组件', + '元数据', + '排序', + ], + [ + '系统管理', + 'system', + '菜单', + '0', + '/system', + null, + 'icon:Setting', + '1', + ], + ]; + + Excel::store(new \App\Exports\GenericExport($templateData), 'exports/' . $filename); + + return $filename; + } + + /** + * 导出权限数据 + */ + public function exportPermissions(array $permissionIds = []): string + { + $filename = 'permissions_export_' . date('YmdHis') . '.xlsx'; + + // 确保目录存在 + if (!is_dir(storage_path('app/exports'))) { + mkdir(storage_path('app/exports'), 0755, true); + } + + Excel::store(new PermissionExport($permissionIds), 'exports/' . $filename); + + return $filename; + } + + /** + * 导入权限数据 + */ + public function importPermissions(string $filePath, string $realPath): array + { + if (!file_exists($realPath)) { + throw ValidationException::withMessages([ + 'file' => ['文件不存在'], + ]); + } + + $import = new PermissionImport(); + Excel::import($import, $realPath); + + // 删除临时文件 + if (file_exists($realPath)) { + unlink($realPath); + } + + return [ + 'success_count' => $import->getSuccessCount(), + 'error_count' => $import->getErrorCount(), + 'errors' => $import->getErrors(), + ]; + } + /** * 删除导出文件 */ diff --git a/app/Support/Regex.php b/app/Support/Regex.php new file mode 100644 index 0000000..8d565e9 --- /dev/null +++ b/app/Support/Regex.php @@ -0,0 +1,230 @@ + +// +---------------------------------------------------------------------- +namespace App\Support; + +class Regex { + + + /** + * 验证用户名 + * + * @param string $value 验证的值 + * @param int $minLen 最小长度 + * @param int $maxLen 最大长度 + * @param string $type 验证类型,默认‘ALL’,EN.验证英文,CN.验证中文,ALL.验证中文和英文 + * @return bool + */ + public static function isUsername($value, $minLen = 2, $maxLen = 48, $type = 'ALL') + { + if (empty ($value)) { + return false; + } + + switch ($type) { + case 'EN' : + $match = '/^[_\w\d]{' . $minLen . ',' . $maxLen . '}$/iu'; + break; + case 'CN' : + $match = '/^[_\x{4e00}-\x{9fa5}\d]{' . $minLen . ',' . $maxLen . '}$/iu'; + break; + default : + $match = '/^[_\w\d\x{4e00}-\x{9fa5}]{' . $minLen . ',' . $maxLen . '}$/iu'; + } + + return preg_match($match, $value) !== 0; + } + + /** + * 验证密码 + * + * @param string $value 验证的值 + * @param int $minLen 最小长度 + * @param int $maxLen 最大长度 + * @return bool + */ + public static function isPassword($value, $minLen = 6, $maxLen = 16) + { + $value = trim($value); + if (empty ($value)) { + return false; + } + + $match = '/^[\\~!@#$%^&*()-_=+|{}\[\],.?\/:;\'\"\d\w]{' . $minLen . ',' . $maxLen . '}$/'; + + return preg_match($match, $value) !== 0; + } + + /** + * 验证eamil + * + * @param string $value 验证的值 + * @return bool + */ + public static function isEmail($value) + { + $value = trim($value); + if (empty ($value)) { + return false; + } + + $match = '/^[\w\d]+[\w\d-.]*@[\w\d-.]+\.[\w\d]{2,10}$/i'; + + return preg_match($match, $value) !== 0; + } + + /** + * 验证电话号码 + * + * @param string $value 验证的值 + * @return bool + */ + public static function isTelephone($value) + { + $value = trim($value); + if (empty ($value)) { + return false; + } + + $match = '/^0[0-9]{2,3}[-]?\d{7,8}$/'; + + return preg_match($match, $value) !== 0; + } + + /** + * 验证手机 + * + * @param string $value 验证的值 + * @return bool + */ + public static function isMobile($value) + { + $value = trim($value); + if (empty ($value)) { + return false; + } + + $match = '/^[(86)|0]?(1\d{10})$/'; + + return preg_match($match, $value) !== 0; + } + + /** + * 验证邮政编码 + * + * @param string $value 验证的值 + * @return bool + */ + public static function isPostCode($value) + { + $value = trim($value); + if (empty ($value)) { + return false; + } + + $match = '/\d{6}/'; + + return preg_match($match, $value) !== 0; + } + + /** + * 验证IP + * + * @param string $value 验证的值 + * @return boolean + */ + public static function isIp($value) + { + $value = trim($value); + if (empty ($value)) { + return false; + } + + $match = '/^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])' . + '\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)' . + '\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)' . + '\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])$/'; + + return preg_match($match, $value) !== 0; + } + + /** + * 验证身份证号码 + * + * @param string $value 验证的值 + * @return boolean + */ + public static function isIDCard($value) + { + $value = trim($value); + if (empty ($value)) { + return false; + } + + if (strlen($value) > 18) { + return false; + } + + $match = '/^\d{6}((1[89])|(2\d))\d{2}((0\d)|(1[0-2]))((3[01])|([0-2]\d))\d{3}(\d|X)$/i'; + + return preg_match($match, $value) !== 0; + } + + /** + * 验证URL + * + * @param string $value 验证的值 + * @return boolean + */ + public static function isUrl($value) + { + $value = strtolower(trim($value)); + if (empty ($value)) { + return false; + } + $match = '/^(http:\/\/)?(https:\/\/)?([\w\d-]+\.)+[\w-]+(\/[\d\w-.\/?%&=]*)?$/'; + + return preg_match($match, $value) !== 0; + } + + /** + * 是否有数字 + * 说明:如果字符串中含有非法字符返回假,没有返回真 + * + * @param string $value 验证的值 + * @return int + */ + public static function hasNumber($value) + { + return preg_match("/[0-9]/", $value) != false; + } + + /** + * 是否含有英文 + * 说明:如果字符串中含有非法字符返回假,没有返回真 + * + * @param string $value 验证的值 + * @return bool + */ + public static function hasEnglish($value) + { + return preg_match("/[a-zA-Z]/", $value) != false; + } + + /** + * 是否有中文 + * 说明:如果字符串中含有非法字符返回假,没有返回真 + * + * @param string $value 验证的值 + * @return bool + */ + public static function hasChinese($value) + { + return preg_match("/[\x7f-\xff]/", $value) != false; + } +} diff --git a/app/Support/Time.php b/app/Support/Time.php new file mode 100644 index 0000000..981113d --- /dev/null +++ b/app/Support/Time.php @@ -0,0 +1,298 @@ + +// +---------------------------------------------------------------------- +namespace App\Support; + +use Illuminate\Support\Carbon; + +class Time { + + /** + * 返回今日开始和结束的时间戳 + * + * @return array + */ + public static function today($data = '') { + [$y, $m, $d] = explode('-', $data ? $data : date('Y-m-d')); + + return [ + mktime(0, 0, 0, $m, $d, $y), + mktime(23, 59, 59, $m, $d, $y), + ]; + } + + /** + * 返回昨日开始和结束的时间戳 + * + * @return array + */ + public static function yesterday() { + $yesterday = date('d') - 1; + + return [ + mktime(0, 0, 0, date('m'), $yesterday, date('Y')), + mktime(23, 59, 59, date('m'), $yesterday, date('Y')), + ]; + } + + /** + * 返回本周开始和结束的时间戳 + * + * @return array + */ + public static function week() { + [$y, $m, $d, $w] = explode('-', date('Y-m-d-w')); + if ($w == 0) $w = 7; //修正周日的问题 + + return [ + mktime(0, 0, 0, $m, $d - $w + 1, $y), mktime(23, 59, 59, $m, $d - $w + 7, $y), + ]; + } + + /** + * 返回上周开始和结束的时间戳 + * + * @return array + */ + public static function lastWeek() { + $timestamp = time(); + + return [ + strtotime(date('Y-m-d', strtotime("last week Monday", $timestamp))), + strtotime(date('Y-m-d', strtotime("last week Sunday", $timestamp))) + 24 * 3600 - 1, + ]; + } + + /** + * 返回本月开始和结束的时间戳 + * + * @return array + */ + public static function month($data = '') { + $res = []; + $data = $data ? $data : date('Y-m-d'); + $nextMoth = date('Y-m-d', strtotime($data . ' +1 month')); + + return [ + mktime(0, 0, 0, date('m', strtotime($data)), 1, date('Y', strtotime($data))), + mktime(0, 0, 0, date('m', strtotime($nextMoth)), 1, date('Y', strtotime($nextMoth))), + ]; + } + + /** + * 返回上个月开始和结束的时间戳 + * + * @return array + */ + public static function lastMonth() { + $y = date('Y'); + $m = date('m'); + $begin = mktime(0, 0, 0, $m - 1, 1, $y); + $end = mktime(23, 59, 59, $m - 1, date('t', $begin), $y); + + return [$begin, $end]; + } + + /** + * 返回今年开始和结束的时间戳 + * + * @return array + */ + public static function year() { + $y = date('Y'); + + return [ + mktime(0, 0, 0, 1, 1, $y), + mktime(23, 59, 59, 12, 31, $y), + ]; + } + + /** + * 返回去年开始和结束的时间戳 + * + * @return array + */ + public static function lastYear() { + $year = date('Y') - 1; + + return [ + mktime(0, 0, 0, 1, 1, $year), + mktime(23, 59, 59, 12, 31, $year), + ]; + } + + /** + * 获取几天前零点到现在/昨日结束的时间戳 + * + * @param int $day 天数 + * @param bool $now 返回现在或者昨天结束时间戳 + * @return array + */ + public static function dayToNow($day = 1, $now = true) { + $end = time(); + if (!$now) { + [$foo, $end] = self::yesterday(); + } + + return [ + mktime(0, 0, 0, date('m'), date('d') - $day, date('Y')), + $end, + ]; + } + + /** + * 返回几天前的时间戳 + * + * @param int $day + * @return int + */ + public static function daysAgo($day = 1) { + $nowTime = time(); + + return $nowTime - self::daysToSecond($day); + } + + /** + * 返回几天后的时间戳 + * + * @param int $day + * @return int + */ + public static function daysAfter($day = 1) { + $nowTime = time(); + + return $nowTime + self::daysToSecond($day); + } + + /** + * 天数转换成秒数 + * + * @param int $day + * @return int + */ + public static function daysToSecond($day = 1) { + return $day * 86400; + } + + /** + * 周数转换成秒数 + * + * @param int $week + * @return int + */ + public static function weekToSecond($week = 1) { + return self::daysToSecond() * 7 * $week; + } + + /** + * 获取毫秒级别的时间戳 + */ + public static function getMillisecond() { + $time = explode(" ", microtime()); + $time = $time[1] . ($time[0] * 1000); + $time2 = explode(".", $time); + $time = $time2[0]; + + return $time; + } + + /** + * 获取相对时间 + * + * @param int $timeStamp + * @return string + */ + public static function formatRelative($timeStamp) { + $currentTime = time(); + + // 判断传入时间戳是否早于当前时间戳 + $isEarly = $timeStamp <= $currentTime; + + // 获取两个时间戳差值 + $diff = abs($currentTime - $timeStamp); + + $dirStr = $isEarly ? '前' : '后'; + + if ($diff < 60) { // 一分钟之内 + $resStr = $diff . '秒' . $dirStr; + } elseif ($diff >= 60 && $diff < 3600) { // 多于59秒,少于等于59分钟59秒 + $resStr = floor($diff / 60) . '分钟' . $dirStr; + } elseif ($diff >= 3600 && $diff < 86400) { // 多于59分钟59秒,少于等于23小时59分钟59秒 + $resStr = floor($diff / 3600) . '小时' . $dirStr; + } elseif ($diff >= 86400 && $diff < 2623860) { // 多于23小时59分钟59秒,少于等于29天59分钟59秒 + $resStr = floor($diff / 86400) . '天' . $dirStr; + } elseif ($diff >= 2623860 && $diff <= 31567860 && $isEarly) { // 多于29天59分钟59秒,少于364天23小时59分钟59秒,且传入的时间戳早于当前 + $resStr = date('m-d H:i', $timeStamp); + } else { + $resStr = date('Y-m-d', $timeStamp); + } + + return $resStr; + } + + /** + * 范围日期转换时间戳 + * + * @param string $rangeDatetime + * @param int $maxRange 最大时间间隔 + * @param string $delimiter + * @return array + */ + public static function parseRange($rangeDatetime, $maxRange = 0, $delimiter = ' - ') { + $rangeDatetime = explode($delimiter, $rangeDatetime, 2); + $rangeDatetime[0] = strtotime($rangeDatetime[0]); + $rangeDatetime[1] = isset($rangeDatetime[1]) ? strtotime($rangeDatetime[1]) : time(); + $rangeDatetime = [ + min($rangeDatetime[0], $rangeDatetime[1]), + max($rangeDatetime[0], $rangeDatetime[1]), + ]; + + // 如果结束时间小于或等于开始时间 直接返回null +// if ($rangeDatetime[1] < $rangeDatetime[0]) { +// return null; +// } + + // 如果大于最大时间间隔 则用结束时间减去最大时间间隔获得开始时间 + if ($maxRange > 0 && $rangeDatetime[1] - $rangeDatetime[0] > $maxRange) { + $rangeDatetime[0] = $rangeDatetime[1] - $maxRange; + } + + return $rangeDatetime; + } + + /** + * 获取指定时间范围内的日期数组 + * @param int $startTime + * @param int $endTime + * @return \Carbon\CarbonPeriod + */ + public static function daysUntilOfTimestamp($startTime, $endTime) { + $startTime = Carbon::createFromTimestamp($startTime); + $endTime = Carbon::createFromTimestamp($endTime); + + return $startTime->daysUntil($endTime); + } + + /** + * 时间排序 + * + * @param array $times + * @return array + */ + public static function sort($times) { + usort($times, function ($com1, $com2) { + $com1 = strtotime($com1); + $com2 = strtotime($com2); + + return $com1 < $com2 ? -1 : 1; + }); + + return $times; + } + +} diff --git a/app/Traits/ModelTrait.php b/app/Traits/ModelTrait.php new file mode 100644 index 0000000..0872cb3 --- /dev/null +++ b/app/Traits/ModelTrait.php @@ -0,0 +1,43 @@ + +// +---------------------------------------------------------------------- +namespace App\Traits; + +use Illuminate\Support\Facades\Schema; + +trait ModelTrait { + + protected function casts(): array { + return [ + 'created_at' => 'datetime:Y-m-d H:i:s', + 'updated_at' => 'datetime:Y-m-d H:i:s', + 'deleted_at' => 'datetime:Y-m-d H:i:s', + ]; + } + + protected function serializeDate($data){ + return $data->timezone('Asia/Shanghai')->format('Y-m-d H:i:s'); + } + /** + * 过滤移除非当前表的字段参数 + * + * @param array $params + * + * @return array + */ + public function setFilterFields(array $params) : array { + $fields = Schema::getColumnListing($this->getTable()); + foreach ($params as $key => $param) { + if ( !in_array($key, $fields) ) unset($params[$key]); + } + // 同时过滤时间戳字段【时间戳只允许自动更改,不允许手动设置】 + if ( $this->timestamps === true && isset($params[self::CREATED_AT]) ) unset($params[self::CREATED_AT]); + if ( $this->timestamps === true && isset($params[self::UPDATED_AT]) ) unset($params[self::UPDATED_AT]); + return $params; + } +} diff --git a/config/jwt.php b/config/jwt.php index 81460f3..02e353e 100644 --- a/config/jwt.php +++ b/config/jwt.php @@ -101,7 +101,7 @@ return [ | */ - 'ttl' => env('JWT_TTL', 60 * 60 * 24 * 7), + 'ttl' => (int) env('JWT_TTL', 60 * 60 * 24 * 7), /* |-------------------------------------------------------------------------- diff --git a/resources/admin/src/api/auth.js b/resources/admin/src/api/auth.js index eb4badd..97956e6 100644 --- a/resources/admin/src/api/auth.js +++ b/resources/admin/src/api/auth.js @@ -178,6 +178,23 @@ export default { return await request.post('roles/batch-copy', params) }, }, + export: { + post: async function (params) { + return await request.post('roles/export', params, { responseType: 'blob' }) + }, + }, + import: { + post: async function (formData) { + return await request.post('roles/import', formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }) + }, + }, + downloadTemplate: { + get: async function () { + return await request.get('roles/download-template', { responseType: 'blob' }) + }, + }, }, // 权限管理 @@ -227,6 +244,23 @@ export default { return await request.post('permissions/batch-status', params) }, }, + export: { + post: async function (params) { + return await request.post('permissions/export', params, { responseType: 'blob' }) + }, + }, + import: { + post: async function (formData) { + return await request.post('permissions/import', formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }) + }, + }, + downloadTemplate: { + get: async function () { + return await request.get('permissions/download-template', { responseType: 'blob' }) + }, + }, }, // 部门管理 diff --git a/resources/admin/src/pages/auth/departments/index.vue b/resources/admin/src/pages/auth/departments/index.vue index d106147..418fbe3 100644 --- a/resources/admin/src/pages/auth/departments/index.vue +++ b/resources/admin/src/pages/auth/departments/index.vue @@ -104,34 +104,22 @@ - - - - - - - 下载导入模板 - - - - - 选择文件 - - - - + + + + + diff --git a/resources/admin/src/pages/auth/users/index.vue b/resources/admin/src/pages/auth/users/index.vue index b9ec834..4e354b3 100644 --- a/resources/admin/src/pages/auth/users/index.vue +++ b/resources/admin/src/pages/auth/users/index.vue @@ -126,6 +126,15 @@ + + + + + +