feat:[FL-165][角色管理页面查询时合并角色和菜单请求]
将角色管理页面的角色和菜单两个独立API请求合并为单个请求,减少网络开销。 后端改动: - 新增 RolesWithMenusResponse 响应模型 - 新增 list_roles_with_menus 服务函数 - 新增 GET /api/v1/admin/roles-with-menus 接口 前端改动: - 更新 loadData 函数使用新的合并接口 - 减少从两个并发请求改为单个请求 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -14,6 +14,7 @@ from ...schemas.admin import (
|
||||
RoleListResponse,
|
||||
RoleMenuUpdateRequest,
|
||||
RolePublic,
|
||||
RolesWithMenusResponse,
|
||||
SeedDefaultsResponse,
|
||||
RoleUpdateRequest,
|
||||
)
|
||||
@@ -32,6 +33,7 @@ from ...services.legacy_admin_rbac_service import (
|
||||
list_permissions,
|
||||
list_role_menu_ids,
|
||||
list_roles,
|
||||
list_roles_with_menus,
|
||||
replace_role_menus,
|
||||
update_menu,
|
||||
update_role,
|
||||
@@ -50,6 +52,15 @@ def get_roles(
|
||||
return list_roles(db, keyword=keyword)
|
||||
|
||||
|
||||
@router.get("/roles-with-menus", response_model=RolesWithMenusResponse)
|
||||
def get_roles_with_menus(
|
||||
keyword: str | None = Query(default=None),
|
||||
_: CurrentUser = Depends(require_any_permission("role.read", "role.manage")),
|
||||
db: Session = Depends(get_db),
|
||||
) -> RolesWithMenusResponse:
|
||||
return list_roles_with_menus(db, keyword=keyword)
|
||||
|
||||
|
||||
@router.post("/roles", response_model=RolePublic)
|
||||
def create_role_endpoint(
|
||||
payload: RoleCreateRequest,
|
||||
|
||||
@@ -128,4 +128,11 @@ class SeedDefaultsResponse(BaseModel):
|
||||
summary: dict[str, SeedCategorySummary] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class RolesWithMenusResponse(BaseModel):
|
||||
roles: list[RolePublic]
|
||||
roles_total: int
|
||||
menus: list[MenuPublic]
|
||||
menus_total: int
|
||||
|
||||
|
||||
MenuTreeItem.model_rebuild()
|
||||
|
||||
@@ -17,6 +17,7 @@ from ..schemas.admin import (
|
||||
RoleCreateRequest,
|
||||
RoleListResponse,
|
||||
RolePublic,
|
||||
RolesWithMenusResponse,
|
||||
RoleUpdateRequest,
|
||||
)
|
||||
from .audit_service import compose_audit_detail, describe_changed_fields, summarize_values, write_audit_log
|
||||
@@ -462,6 +463,18 @@ def list_menus(db: Session, keyword: str | None = None, status: str | None = Non
|
||||
return MenuListResponse(items=items, total=len(items))
|
||||
|
||||
|
||||
def list_roles_with_menus(db: Session, keyword: str | None = None) -> RolesWithMenusResponse:
|
||||
"""Get roles and menus in a single request to reduce network calls."""
|
||||
roles_response = list_roles(db, keyword=keyword)
|
||||
menus_response = list_menus(db)
|
||||
return RolesWithMenusResponse(
|
||||
roles=roles_response.items,
|
||||
roles_total=roles_response.total,
|
||||
menus=menus_response.items,
|
||||
menus_total=menus_response.total,
|
||||
)
|
||||
|
||||
|
||||
def get_menu_by_id(db: Session, menu_id: str) -> MenuPublic | None:
|
||||
normalized_menu_id = menu_id.strip()
|
||||
if not normalized_menu_id:
|
||||
|
||||
@@ -32,6 +32,13 @@ const AntCard = Card as unknown as ComponentType<CardProps>;
|
||||
|
||||
type MenuListResponse = { items: MenuItem[]; total: number };
|
||||
|
||||
type RolesWithMenusResponse = {
|
||||
roles: RoleItem[];
|
||||
roles_total: number;
|
||||
menus: MenuItem[];
|
||||
menus_total: number;
|
||||
};
|
||||
|
||||
type RoleFormValues = {
|
||||
code: string;
|
||||
name: string;
|
||||
@@ -90,27 +97,20 @@ export default function AdminRolesPage() {
|
||||
setError("");
|
||||
try {
|
||||
const keyword = searchKeyword.trim();
|
||||
const roleUrl = keyword
|
||||
? `/api/v1/admin/roles?keyword=${encodeURIComponent(keyword)}`
|
||||
: "/api/v1/admin/roles";
|
||||
const url = keyword
|
||||
? `/api/v1/admin/roles-with-menus?keyword=${encodeURIComponent(keyword)}`
|
||||
: "/api/v1/admin/roles-with-menus";
|
||||
|
||||
const [roleRes, menuRes] = await Promise.all([
|
||||
fetchWithAuth(roleUrl),
|
||||
fetchWithAuth("/api/v1/admin/menus"),
|
||||
]);
|
||||
const response = await fetchWithAuth(url);
|
||||
|
||||
if (!roleRes.ok) {
|
||||
throw new Error(await readApiError(roleRes));
|
||||
}
|
||||
if (!menuRes.ok) {
|
||||
throw new Error(await readApiError(menuRes));
|
||||
if (!response.ok) {
|
||||
throw new Error(await readApiError(response));
|
||||
}
|
||||
|
||||
const rolePayload = (await roleRes.json()) as RoleListResponse;
|
||||
const menuPayload = (await menuRes.json()) as MenuListResponse;
|
||||
const payload = (await response.json()) as RolesWithMenusResponse;
|
||||
|
||||
setRoles(rolePayload.items);
|
||||
setMenus(menuPayload.items);
|
||||
setRoles(payload.roles);
|
||||
setMenus(payload.menus);
|
||||
} catch (candidate) {
|
||||
setError(candidate instanceof Error ? candidate.message : "角色数据加载失败");
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user