diff --git a/app/Admin/Menu.php b/app/Admin/Menu.php new file mode 100644 index 0000000..9354977 --- /dev/null +++ b/app/Admin/Menu.php @@ -0,0 +1,194 @@ +userMenus() + ->push(...array_map(fn($item) => $this->formatItem($item), $this->menus)) + ->sortBy('order') + ->values() + ->toArray(); + + return array_merge($this->list2Menu($menus), $this->extra()); + } + + private function userMenus() + { + /** @var \App\Models\AdminUser */ + $user = Admin::user(); + + return $user->allMenus(); + } + + private function list2Menu($list, $parentId = 0, $parentName = ''): array + { + $data = []; + foreach ($list as $key => $item) { + if ($item['parent_id'] == $parentId) { + $idStr = "[{$item['id']}]"; + $_temp = [ + 'name' => $parentName ? $parentName . '-' . $idStr : $idStr, + 'path' => $item['url'], + 'component' => data_get($item, 'component') ?? 'amis', + 'is_home' => $item['is_home'], + 'is_full' => $item['is_full'] ?? 0, + 'is_link' => $item['url_type'] == Admin::adminMenuModel()::TYPE_LINK, + 'meta' => [ + 'title' => $item['title'], + 'icon' => $item['icon'] ?? '-', + 'hide' => $item['visible'] == 0, + 'order' => $item['order'], + ], + ]; + + $children = $this->list2Menu($list, (int)$item['id'], $_temp['name']); + + if (!empty($children)) { + $_temp['component'] = 'amis'; + $_temp['children'] = $children; + } + + $data[] = $_temp; + if (!in_array($_temp['path'], Admin::config('admin.route.without_extra_routes'))) { + array_push($data, ...$this->generateRoute($_temp)); + } + unset($list[$key]); + } + } + return $data; + } + + private function generateRoute($item): array + { + $url = $item['path'] ?? ''; + $url = preg_replace('/\?.*/', '', $url); + + if (!$url || array_key_exists('children', $item)) { + return []; + } + + $menu = fn($action, $path) => [ + 'name' => $item['name'] . '-' . $action, + 'path' => $url . $path, + 'component' => 'amis', + 'meta' => [ + 'hide' => true, + 'icon' => Arr::get($item, 'meta.icon'), + 'title' => Arr::get($item, 'meta.title') . ' - ' . __('admin.' . $action), + ], + ]; + + return [ + $menu('create', '/create'), + $menu('show', '/:id'), + $menu('edit', '/:id/edit'), + ]; + } + + public function add($menus) + { + $this->menus = array_merge($this->menus, $menus); + + return $this; + } + + private function formatItem($item) + { + return array_merge([ + 'title' => '', + 'url' => '', + 'url_type' => 1, + 'icon' => '', + 'parent_id' => 0, + 'id' => 999, + 'is_home' => 0, + 'visible' => 1, + 'order' => 99, + ], $item); + } + + /** + * 额外菜单 + * + * @return array|array[] + */ + private function extra() + { + $extraMenus = [ + [ + 'name' => 'user_setting', + 'path' => '/user_setting', + 'component' => 'amis', + 'meta' => [ + 'hide' => true, + 'title' => __('admin.user_setting'), + 'icon' => 'material-symbols:manage-accounts', + 'singleLayout' => 'basic', + ], + ], + ]; + + if (Admin::config('admin.show_development_tools')) { + $extraMenus = array_merge($extraMenus, $this->devToolMenus()); + } + + return $extraMenus; + } + + /** + * 开发者工具菜单 + * + * @return array[] + */ + private function devToolMenus() + { + return [ + [ + 'name' => 'dev_tools', + 'path' => '/dev_tools', + 'component' => 'amis', + 'meta' => [ + 'title' => __('admin.developer'), + 'icon' => 'fluent:window-dev-tools-20-regular', + ], + 'children' => [ + [ + 'name' => 'dev_tools_extensions', + 'path' => '/dev_tools/extensions', + 'component' => 'amis', + 'meta' => [ + 'title' => __('admin.extensions.menu'), + 'icon' => 'ion:extension-puzzle-outline', + ], + ], + [ + 'name' => 'dev_tools_code_generator', + 'path' => '/dev_tools/code_generator', + 'component' => 'amis', + 'meta' => [ + 'title' => __('admin.code_generator'), + 'icon' => 'ic:baseline-code', + ], + ], + [ + 'name' => 'dev_tools_editor', + 'path' => '/dev_tools/editor', + 'component' => 'editor', + 'meta' => [ + 'title' => __('admin.visual_editor'), + 'icon' => 'mdi:monitor-edit', + ], + ], + ], + ], + ]; + } +} diff --git a/app/Models/AdminRole.php b/app/Models/AdminRole.php new file mode 100644 index 0000000..cc225ce --- /dev/null +++ b/app/Models/AdminRole.php @@ -0,0 +1,25 @@ +menus()->detach(); + $model->permissions()->detach(); + }); + } + + public function menus(): BelongsToMany + { + return $this->belongsToMany(Admin::adminMenuModel()::class, 'admin_role_menus', 'role_id', 'menu_id'); + } +} diff --git a/app/Models/AdminUser.php b/app/Models/AdminUser.php new file mode 100644 index 0000000..aa14759 --- /dev/null +++ b/app/Models/AdminUser.php @@ -0,0 +1,56 @@ +belongsToMany(Admin::adminRoleModel(), 'admin_role_users', 'user_id', 'role_id')->withTimestamps(); + } + + public function allMenus(): Collection + { + $model = Admin::adminMenuModel(); + $allMenus = $model::all()->keyBy($this->getKeyName()); + + if ($this->isAdministrator()) { + return $allMenus; + } + + /** @var \Illuminate\Database\Eloquent\Collection */ + $roleMenus = $this->roles + ->pluck('menus') + ->flatten() + ->keyBy($this->getKeyName()); + + $allRoleMenus = $roleMenus->collect(); + + foreach ($roleMenus as $roleMenu) { + if (is_null($roleMenu->parent_id) || $allRoleMenus->has($roleMenu->parent_id)) { + continue; + } + + $parent = $allMenus->get($roleMenu->parent_id); + + while ($parent) { + $allRoleMenus->put($parent->id, $parent); + + if (is_null($parent->parent_id)) { + break; + } + + $parent = $allMenus->get($parent->parent_id); + }; + } + + unset($allMenus, $roleMenus); + + return $allRoleMenus; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index f782b96..b3132bc 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -11,8 +11,7 @@ class AppServiceProvider extends ServiceProvider */ public function register(): void { - // - + $this->app->singleton('admin.menu', \App\Admin\Menu::class); } /** diff --git a/config/admin.php b/config/admin.php index 00a8f49..3a3cde3 100644 --- a/config/admin.php +++ b/config/admin.php @@ -45,7 +45,7 @@ return [ 'providers' => [ 'admin' => [ 'driver' => 'eloquent', - 'model' => \Slowlyo\OwlAdmin\Models\AdminUser::class, + 'model' => \App\Models\AdminUser::class, ], ], 'except' => [ @@ -108,8 +108,8 @@ return [ ], 'models' => [ - 'admin_user' => \Slowlyo\OwlAdmin\Models\AdminUser::class, - 'admin_role' => \Slowlyo\OwlAdmin\Models\AdminRole::class, + 'admin_user' => \App\Models\AdminUser::class, + 'admin_role' => \App\Models\AdminRole::class, 'admin_menu' => \Slowlyo\OwlAdmin\Models\AdminMenu::class, 'admin_permission' => \Slowlyo\OwlAdmin\Models\AdminPermission::class, ], diff --git a/database/migrations/2024_03_22_122703_add_slug_to_admin_menus_table.php b/database/migrations/2024_03_22_122703_add_slug_to_admin_menus_table.php new file mode 100644 index 0000000..5a316ea --- /dev/null +++ b/database/migrations/2024_03_22_122703_add_slug_to_admin_menus_table.php @@ -0,0 +1,28 @@ +string('slug')->unique()->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('admin_menus', function (Blueprint $table) { + $table->dropColumn(['slug']); + }); + } +}; diff --git a/database/migrations/2024_03_22_165608_delete_name_unique_index_to_admin_permissions_table.php b/database/migrations/2024_03_22_165608_delete_name_unique_index_to_admin_permissions_table.php new file mode 100644 index 0000000..1647ac0 --- /dev/null +++ b/database/migrations/2024_03_22_165608_delete_name_unique_index_to_admin_permissions_table.php @@ -0,0 +1,28 @@ +dropUnique(['name']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('admin_permissions', function (Blueprint $table) { + $table->unique(['name']); + }); + } +}; diff --git a/database/migrations/2024_03_22_165830_change_slug_column_to_admin_permissions_table.php b/database/migrations/2024_03_22_165830_change_slug_column_to_admin_permissions_table.php new file mode 100644 index 0000000..a825e47 --- /dev/null +++ b/database/migrations/2024_03_22_165830_change_slug_column_to_admin_permissions_table.php @@ -0,0 +1,28 @@ +string('slug')->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('admin_permissions', function (Blueprint $table) { + $table->string('slug', 50)->change(); + }); + } +}; diff --git a/database/migrations/2024_03_22_205128_create_admin_role_menus_table.php b/database/migrations/2024_03_22_205128_create_admin_role_menus_table.php new file mode 100644 index 0000000..0c7f285 --- /dev/null +++ b/database/migrations/2024_03_22_205128_create_admin_role_menus_table.php @@ -0,0 +1,28 @@ +integer('role_id'); + $table->integer('menu_id'); + $table->index(['role_id', 'menu_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('admin_role_menus'); + } +}; diff --git a/database/seeders/AdminPermissionSeeder.php b/database/seeders/AdminPermissionSeeder.php new file mode 100644 index 0000000..6ab2169 --- /dev/null +++ b/database/seeders/AdminPermissionSeeder.php @@ -0,0 +1,168 @@ + [ + 'name' => '主页', + 'icon' => 'line-md:home-twotone-alt', + 'uri' => '/', + 'children' => [], + ], + + /* + |-------------------------------------------------------------------------- + | 系统管理 + |-------------------------------------------------------------------------- + */ + 'system' => [ + 'name' => '系统管理', + 'icon' => 'material-symbols:settings-outline', + 'uri' => '', + 'children' => [ + 'admin_users' => [ + 'name' => '账号管理', + 'icon' => 'ph:user-gear', + 'uri' => '/system/admin_users', + 'resource' => ['list', 'create', 'update', 'delete'], + 'children' => ['change_password' => '修改密码'], + ], + 'admin_roles' => [ + 'name' => '角色管理', + 'icon' => 'carbon:user-role', + 'uri' => '/system/admin_roles', + 'resource' => ['list', 'create', 'update', 'delete'], + 'children' => [ + 'set_menus' => '设置菜单', + 'set_permissions' => '设置权限', + ], + ], + 'admin_permissions' => [ + 'name' => '权限管理', + 'icon' => 'fluent-mdl2:permissions', + 'uri' => '/system/admin_permissions', + 'resource' => ['list', 'create', 'update', 'delete'], + 'children' => [], + ], + 'admin_menus' => [ + 'name' => '菜单管理', + 'icon' => 'ant-design:menu-unfold-outlined', + 'uri' => '/system/admin_menus', + 'resource' => ['list', 'create', 'update', 'delete'], + 'children' => [], + ], + 'settings' => [ + 'name' => '系统设置', + 'icon' => 'akar-icons:settings-horizontal', + 'uri' => '/system/settings', + 'resource' => true, + 'children' => [], + ], + 'keywords' => [ + 'name' => '数据字典', + 'icon' => 'ph:codesandbox-logo-light', + 'uri' => '/system/keywords', + 'resource' => ['list', 'create', 'update', 'delete'], + 'children' => [], + ], + ], + ], + ]; + + $this->handleAdminMenus($data); + $this->handleAdminPermissions($data); + } + + public function handleAdminMenus(array $data, ?AdminMenu $parent = null): void + { + foreach ($data as $slug => $node) { + if (! is_array($node) || ! array_key_exists('uri', $node)) { + continue; + } + + /** @var \Slowlyo\OwlAdmin\Models\AdminMenu */ + $menu = AdminMenu::updateOrCreate([ + 'slug' => ($parent->slug ?? 'admin').'.'.$slug, + ], [ + 'parent_id' => $parent->id ?? 0, + 'order' => $node['order'] ?? 0, + 'title' => $node['name'], + 'icon' => $node['icon'], + 'url' => $node['uri'], + 'url_type' => $node['uri_type'] ?? 1, + 'visible' => $node['visible'] ?? 1, + 'is_home' => $node['is_home'] ?? 0, + 'is_full' => $node['is_full'] ?? 0, + ]); + + $this->handleAdminMenus($node['children'] ?? [], $menu); + } + } + + protected function handleAdminPermissions(array $data, ?AdminPermission $parent = null) + { + foreach ($data as $slug => $node) { + $permission = AdminPermission::updateOrCreate([ + 'slug' => ($parent->slug ?? 'admin') . '.' . $slug, + ], [ + 'parent_id' => $parent->id ?? 0, + 'name' => is_array($node) ? $node['name'] : $node, + ]); + + if (! is_array($node)) { + continue; + } + + // 资源路由权限 + if (array_key_exists('resource', $node)) { + $resourceAbilities = []; + + if (is_array($node['resource'])) { + $resourceAbilities = $node['resource']; + } elseif ($node['resource'] === true) { + $resourceAbilities = array_keys($this->resourceAbilityMap()); + } + + foreach ($resourceAbilities as $resourceAbility) { + AdminPermission::updateOrCreate([ + 'slug' => $permission->slug . '.' . $resourceAbility, + ], [ + 'parent_id' => $permission->id, + 'name' => $this->resourceAbilityMap()[$resourceAbility], + ]); + } + } + + $this->handleAdminPermissions($node['children'] ?? [], $permission); + } + } + + protected function resourceAbilityMap(): array + { + return [ + 'list' => '列表', + 'create' => '新增', + 'update' => '编辑', + 'delete' => '删除', + 'view' => '查看', + ]; + } +} diff --git a/database/seeders/AdminSeeder.php b/database/seeders/AdminSeeder.php index 615a6de..5aa9475 100644 --- a/database/seeders/AdminSeeder.php +++ b/database/seeders/AdminSeeder.php @@ -15,6 +15,7 @@ class AdminSeeder extends Seeder { $this->call([ KeywordSeeder::class, + AdminPermissionSeeder::class, ]); } }