'dir:overview', 'name' => '概览', 'type' => 'dir', 'children' => [ [ 'code' => 'menu:dashboard', 'name' => '数据概览', 'type' => 'menu', 'path' => '/dashboard', 'icon' => '📊', 'children' => [], ], ], ], [ 'code' => 'dir:org', 'name' => '组织架构', 'type' => 'dir', 'children' => [ [ 'code' => 'menu:users', 'name' => '用户管理', 'type' => 'menu', 'path' => '/users', 'icon' => '👥', 'children' => [ ['code' => 'btn:user:create', 'name' => '新增用户', 'type' => 'btn'], ['code' => 'btn:user:edit', 'name' => '编辑用户', 'type' => 'btn'], ['code' => 'btn:user:reset_password', 'name' => '重置密码', 'type' => 'btn'], ['code' => 'btn:user:delete', 'name' => '删除用户', 'type' => 'btn'], ], ], [ 'code' => 'menu:departments', 'name' => '部门管理', 'type' => 'menu', 'path' => '/departments', 'icon' => '🏢', 'children' => [ ['code' => 'btn:dept:create', 'name' => '新增部门', 'type' => 'btn'], ['code' => 'btn:dept:edit', 'name' => '编辑部门', 'type' => 'btn'], ['code' => 'btn:dept:delete', 'name' => '删除部门', 'type' => 'btn'], ], ], [ 'code' => 'menu:roles', 'name' => '角色管理', 'type' => 'menu', 'path' => '/roles', 'icon' => '🛡️', 'children' => [ ['code' => 'btn:role:create', 'name' => '新增角色', 'type' => 'btn'], ['code' => 'btn:role:edit', 'name' => '编辑角色', 'type' => 'btn'], ['code' => 'btn:role:delete', 'name' => '删除角色', 'type' => 'btn'], ], ], ], ], [ 'code' => 'dir:business', 'name' => '业务数据', 'type' => 'dir', 'children' => [ [ 'code' => 'menu:conversations', 'name' => '会话管理', 'type' => 'menu', 'path' => '/conversations', 'icon' => '💬', 'children' => [ ['code' => 'btn:conv:view_all', 'name' => '查看全部会话', 'type' => 'btn'], ['code' => 'btn:conv:view_subordinate', 'name' => '查看下级部门会话', 'type' => 'btn'], ], ], [ 'code' => 'menu:memberships', 'name' => '会员等级', 'type' => 'menu', 'path' => '/memberships', 'icon' => '⭐', 'children' => [ ['code' => 'btn:membership:create', 'name' => '新增会员等级', 'type' => 'btn'], ['code' => 'btn:membership:edit', 'name' => '编辑会员等级', 'type' => 'btn'], ['code' => 'btn:membership:delete', 'name' => '删除会员等级', 'type' => 'btn'], ], ], ], ], [ 'code' => 'dir:system', 'name' => '系统管理', 'type' => 'dir', 'children' => [ [ 'code' => 'menu:models', 'name' => 'AI 模型', 'type' => 'menu', 'path' => '/models', 'icon' => '🤖', 'children' => [ ['code' => 'btn:model:create', 'name' => '新增模型', 'type' => 'btn'], ['code' => 'btn:model:edit', 'name' => '编辑模型', 'type' => 'btn'], ['code' => 'btn:model:delete', 'name' => '删除模型', 'type' => 'btn'], ['code' => 'btn:model:test', 'name' => '测试连接', 'type' => 'btn'], ], ], [ 'code' => 'menu:permissions', 'name' => '权限管理', 'type' => 'menu', 'path' => '/permissions', 'icon' => '🔑', 'children' => [ ['code' => 'btn:perm:create', 'name' => '新增权限', 'type' => 'btn'], ['code' => 'btn:perm:edit', 'name' => '编辑权限', 'type' => 'btn'], ['code' => 'btn:perm:delete', 'name' => '删除权限', 'type' => 'btn'], ], ], [ 'code' => 'menu:settings', 'name' => '系统设置', 'type' => 'menu', 'path' => '/settings', 'icon' => '🔧', 'children' => [ ['code' => 'btn:settings:save', 'name' => '保存设置', 'type' => 'btn'], ], ], ], ], ]; } public static function tree(): array { try { if (!self::tableReady()) { return self::builtinTree(); } $rows = SysPermission::order('sort_order')->order('id')->select()->toArray(); if (!$rows) { return self::builtinTree(); } return self::buildTreeFromRows($rows); } catch (\Throwable $e) { return self::builtinTree(); } } public static function flatList(): array { try { if (!self::tableReady()) { return self::flattenBuiltin(); } return SysPermission::order('sort_order')->order('id')->select()->toArray(); } catch (\Throwable $e) { return self::flattenBuiltin(); } } private static function tableReady(): bool { static $ready = null; if ($ready !== null) { return $ready; } try { $db = Db::getConfig('database') ?: env('DB_NAME', 'ai_chat'); $rows = Db::query( 'SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? LIMIT 1', [$db, 'sys_permissions'] ); $ready = !empty($rows); } catch (\Throwable $e) { $ready = false; } return $ready; } private static function buildTreeFromRows(array $rows): array { $byParent = []; foreach ($rows as $row) { $pid = $row['parent_id'] ? (int) $row['parent_id'] : 0; $byParent[$pid][] = $row; } $mapNode = function (array $row) use (&$mapNode, $byParent) { $children = []; foreach ($byParent[(int) $row['id']] ?? [] as $child) { $children[] = $mapNode($child); } return [ 'id' => (int) $row['id'], 'code' => $row['code'], 'name' => $row['name'], 'type' => $row['type'], 'path' => $row['path'] ?? '', 'icon' => $row['icon'] ?? '', 'parent_id' => $row['parent_id'] ? (int) $row['parent_id'] : null, 'sort_order' => (int) ($row['sort_order'] ?? 0), 'is_system' => (int) ($row['is_system'] ?? 0), 'children' => $children, ]; }; $tree = []; foreach ($byParent[0] ?? [] as $root) { $tree[] = $mapNode($root); } return $tree; } private static function flattenBuiltin(): array { $list = []; $walk = function (array $nodes, $parentId = null) use (&$walk, &$list) { foreach ($nodes as $i => $node) { $id = count($list) + 1; $list[] = [ 'id' => $id, 'type' => $node['type'], 'code' => $node['code'], 'name' => $node['name'], 'parent_id' => $parentId, 'path' => $node['path'] ?? null, 'icon' => $node['icon'] ?? null, 'sort_order' => $i, 'is_system' => 1, ]; if (!empty($node['children'])) { $walk($node['children'], $id); } } }; $walk(self::builtinTree()); return $list; } public static function emptyPermissions(): array { $perms = [ 'can_access_admin' => false, 'dirs' => [], 'menus' => [], 'buttons' => [], ]; foreach (self::legacyKeys() as $key) { $perms[$key] = false; } return $perms; } public static function fullPermissions(): array { $dirs = []; $menus = []; $buttons = []; foreach (self::tree() as $dir) { $dirs[] = $dir['code']; foreach ($dir['children'] ?? [] as $menu) { $menus[] = $menu['code']; foreach ($menu['children'] ?? [] as $btn) { $buttons[] = $btn['code']; } } } $perms = [ 'can_access_admin' => true, 'dirs' => $dirs, 'menus' => $menus, 'buttons' => $buttons, ]; foreach (self::legacyKeys() as $key) { $perms[$key] = true; } return $perms; } public static function legacyKeys(): array { return [ 'can_manage_users', 'can_manage_roles', 'can_manage_departments', 'can_view_all_conversations', 'can_view_subordinate_conversations', 'can_manage_models', 'can_manage_settings', 'can_manage_memberships', 'can_manage_permissions', ]; } public static function normalize(array $input): array { $base = self::emptyPermissions(); $dirs = array_values(array_unique(array_filter((array) ($input['dirs'] ?? [])))); $menus = array_values(array_unique(array_filter((array) ($input['menus'] ?? [])))); $buttons = array_values(array_unique(array_filter((array) ($input['buttons'] ?? [])))); if (empty($menus) && empty($buttons) && empty($dirs)) { [$dirs, $menus, $buttons] = self::fromLegacy($input); } $perms = array_merge($base, [ 'can_access_admin' => !empty($input['can_access_admin']), 'dirs' => $dirs, 'menus' => $menus, 'buttons' => $buttons, ]); return self::syncLegacyFlags($perms); } public static function syncLegacyFlags(array $perms): array { $menus = $perms['menus'] ?? []; $buttons = $perms['buttons'] ?? []; $perms['can_manage_users'] = in_array('menu:users', $menus, true); $perms['can_manage_roles'] = in_array('menu:roles', $menus, true); $perms['can_manage_departments'] = in_array('menu:departments', $menus, true); $perms['can_manage_models'] = in_array('menu:models', $menus, true); $perms['can_manage_settings'] = in_array('menu:settings', $menus, true); $perms['can_manage_memberships'] = in_array('menu:memberships', $menus, true); $perms['can_manage_permissions'] = in_array('menu:permissions', $menus, true); $perms['can_view_all_conversations'] = in_array('btn:conv:view_all', $buttons, true); $perms['can_view_subordinate_conversations'] = in_array('btn:conv:view_subordinate', $buttons, true); if (in_array('menu:conversations', $menus, true) && empty($perms['can_view_all_conversations']) && empty($perms['can_view_subordinate_conversations'])) { $perms['can_view_subordinate_conversations'] = true; if (!in_array('btn:conv:view_subordinate', $buttons, true)) { $perms['buttons'][] = 'btn:conv:view_subordinate'; } } return $perms; } private static function fromLegacy(array $input): array { $dirs = []; $menus = []; $buttons = []; $map = [ 'can_manage_users' => ['dir:org', 'menu:users', ['btn:user:create', 'btn:user:edit', 'btn:user:reset_password', 'btn:user:delete']], 'can_manage_departments' => ['dir:org', 'menu:departments', ['btn:dept:create', 'btn:dept:edit', 'btn:dept:delete']], 'can_manage_roles' => ['dir:org', 'menu:roles', ['btn:role:create', 'btn:role:edit', 'btn:role:delete']], 'can_manage_memberships' => ['dir:business', 'menu:memberships', ['btn:membership:create', 'btn:membership:edit', 'btn:membership:delete']], 'can_manage_models' => ['dir:system', 'menu:models', ['btn:model:create', 'btn:model:edit', 'btn:model:delete', 'btn:model:test']], 'can_manage_settings' => ['dir:system', 'menu:settings', ['btn:settings:save']], 'can_manage_permissions' => ['dir:system', 'menu:permissions', ['btn:perm:create', 'btn:perm:edit', 'btn:perm:delete']], ]; foreach ($map as $key => [$dir, $menu, $btns]) { if (!empty($input[$key])) { $dirs[] = $dir; $menus[] = $menu; $buttons = array_merge($buttons, $btns); } } if (!empty($input['can_view_all_conversations']) || !empty($input['can_view_subordinate_conversations'])) { $dirs[] = 'dir:business'; $menus[] = 'menu:conversations'; if (!empty($input['can_view_all_conversations'])) { $buttons[] = 'btn:conv:view_all'; } if (!empty($input['can_view_subordinate_conversations'])) { $buttons[] = 'btn:conv:view_subordinate'; } } if (!empty($input['can_access_admin'])) { $dirs[] = 'dir:overview'; $menus[] = 'menu:dashboard'; } return [ array_values(array_unique($dirs)), array_values(array_unique($menus)), array_values(array_unique($buttons)), ]; } public static function hasCode(array $perms, string $code): bool { if ($code === 'can_access_admin') { return !empty($perms['can_access_admin']); } if (str_starts_with($code, 'dir:')) { return in_array($code, $perms['dirs'] ?? [], true); } if (str_starts_with($code, 'menu:')) { return in_array($code, $perms['menus'] ?? [], true); } if (str_starts_with($code, 'btn:')) { return in_array($code, $perms['buttons'] ?? [], true); } return !empty($perms[$code]); } public static function menuMeta(): array { $items = []; foreach (self::tree() as $dir) { foreach ($dir['children'] ?? [] as $menu) { $items[] = [ 'id' => $menu['id'] ?? null, 'code' => $menu['code'], 'name' => $menu['name'], 'path' => $menu['path'] ?? '', 'icon' => $menu['icon'] ?? '', 'dir' => $dir['code'], 'dirName' => $dir['name'], ]; } } return $items; } }