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,
|
RoleListResponse,
|
||||||
RoleMenuUpdateRequest,
|
RoleMenuUpdateRequest,
|
||||||
RolePublic,
|
RolePublic,
|
||||||
|
RolesWithMenusResponse,
|
||||||
SeedDefaultsResponse,
|
SeedDefaultsResponse,
|
||||||
RoleUpdateRequest,
|
RoleUpdateRequest,
|
||||||
)
|
)
|
||||||
@@ -32,6 +33,7 @@ from ...services.legacy_admin_rbac_service import (
|
|||||||
list_permissions,
|
list_permissions,
|
||||||
list_role_menu_ids,
|
list_role_menu_ids,
|
||||||
list_roles,
|
list_roles,
|
||||||
|
list_roles_with_menus,
|
||||||
replace_role_menus,
|
replace_role_menus,
|
||||||
update_menu,
|
update_menu,
|
||||||
update_role,
|
update_role,
|
||||||
@@ -50,6 +52,15 @@ def get_roles(
|
|||||||
return list_roles(db, keyword=keyword)
|
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)
|
@router.post("/roles", response_model=RolePublic)
|
||||||
def create_role_endpoint(
|
def create_role_endpoint(
|
||||||
payload: RoleCreateRequest,
|
payload: RoleCreateRequest,
|
||||||
|
|||||||
@@ -128,4 +128,11 @@ class SeedDefaultsResponse(BaseModel):
|
|||||||
summary: dict[str, SeedCategorySummary] = Field(default_factory=dict)
|
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()
|
MenuTreeItem.model_rebuild()
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from ..schemas.admin import (
|
|||||||
RoleCreateRequest,
|
RoleCreateRequest,
|
||||||
RoleListResponse,
|
RoleListResponse,
|
||||||
RolePublic,
|
RolePublic,
|
||||||
|
RolesWithMenusResponse,
|
||||||
RoleUpdateRequest,
|
RoleUpdateRequest,
|
||||||
)
|
)
|
||||||
from .audit_service import compose_audit_detail, describe_changed_fields, summarize_values, write_audit_log
|
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))
|
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:
|
def get_menu_by_id(db: Session, menu_id: str) -> MenuPublic | None:
|
||||||
normalized_menu_id = menu_id.strip()
|
normalized_menu_id = menu_id.strip()
|
||||||
if not normalized_menu_id:
|
if not normalized_menu_id:
|
||||||
|
|||||||
@@ -32,6 +32,13 @@ const AntCard = Card as unknown as ComponentType<CardProps>;
|
|||||||
|
|
||||||
type MenuListResponse = { items: MenuItem[]; total: number };
|
type MenuListResponse = { items: MenuItem[]; total: number };
|
||||||
|
|
||||||
|
type RolesWithMenusResponse = {
|
||||||
|
roles: RoleItem[];
|
||||||
|
roles_total: number;
|
||||||
|
menus: MenuItem[];
|
||||||
|
menus_total: number;
|
||||||
|
};
|
||||||
|
|
||||||
type RoleFormValues = {
|
type RoleFormValues = {
|
||||||
code: string;
|
code: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -90,27 +97,20 @@ export default function AdminRolesPage() {
|
|||||||
setError("");
|
setError("");
|
||||||
try {
|
try {
|
||||||
const keyword = searchKeyword.trim();
|
const keyword = searchKeyword.trim();
|
||||||
const roleUrl = keyword
|
const url = keyword
|
||||||
? `/api/v1/admin/roles?keyword=${encodeURIComponent(keyword)}`
|
? `/api/v1/admin/roles-with-menus?keyword=${encodeURIComponent(keyword)}`
|
||||||
: "/api/v1/admin/roles";
|
: "/api/v1/admin/roles-with-menus";
|
||||||
|
|
||||||
const [roleRes, menuRes] = await Promise.all([
|
const response = await fetchWithAuth(url);
|
||||||
fetchWithAuth(roleUrl),
|
|
||||||
fetchWithAuth("/api/v1/admin/menus"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!roleRes.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(await readApiError(roleRes));
|
throw new Error(await readApiError(response));
|
||||||
}
|
|
||||||
if (!menuRes.ok) {
|
|
||||||
throw new Error(await readApiError(menuRes));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rolePayload = (await roleRes.json()) as RoleListResponse;
|
const payload = (await response.json()) as RolesWithMenusResponse;
|
||||||
const menuPayload = (await menuRes.json()) as MenuListResponse;
|
|
||||||
|
|
||||||
setRoles(rolePayload.items);
|
setRoles(payload.roles);
|
||||||
setMenus(menuPayload.items);
|
setMenus(payload.menus);
|
||||||
} catch (candidate) {
|
} catch (candidate) {
|
||||||
setError(candidate instanceof Error ? candidate.message : "角色数据加载失败");
|
setError(candidate instanceof Error ? candidate.message : "角色数据加载失败");
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user