systemMenuService.ts 5.5 KB
import { createApiClient } from "../lib/apiClient";
import type {
  PagedResultDto,
  RbacMenuTreeNode,
  SystemMenuDto,
  SystemMenuGetListInput,
  SystemMenuUpsertInput,
} from "../types/systemMenu";

const api = createApiClient({
  getToken: () => {
    try {
      return localStorage.getItem("access_token") ?? localStorage.getItem("token") ?? null;
    } catch {
      return null;
    }
  },
});

// Swagger: /api/app/rbac-menu
const PATH = "/rbac-menu";

/** 兼容后端 PascalCase 字段 */
export function normalizeSystemMenuDto(row: unknown): SystemMenuDto {
  if (!row || typeof row !== "object") return { id: "" };
  const r = row as Record<string, unknown>;
  return {
    id: String(r.id ?? r.Id ?? ""),
    orderNum: (r.orderNum ?? r.OrderNum) as number | null | undefined,
    state: (r.state ?? r.State) as boolean | null | undefined,
    menuName: (r.menuName ?? r.MenuName) as string | null | undefined,
    routerName: (r.routerName ?? r.RouterName) as string | null | undefined,
    menuType: (r.menuType ?? r.MenuType) as number | null | undefined,
    permissionCode: (r.permissionCode ?? r.PermissionCode) as string | null | undefined,
    parentId: (r.parentId ?? r.ParentId) as string | null | undefined,
    menuIcon: (r.menuIcon ?? r.MenuIcon) as string | null | undefined,
    routeUrl: (r.routeUrl ?? r.RouteUrl) as string | null | undefined,
    link: (r.link ?? r.Link) as string | null | undefined,
    isCache: (r.isCache ?? r.IsCache) as boolean | null | undefined,
    isShow: (r.isShow ?? r.IsShow) as boolean | null | undefined,
    remark: (r.remark ?? r.Remark) as string | null | undefined,
    component: (r.component ?? r.Component) as string | null | undefined,
    menuSource: (r.menuSource ?? r.MenuSource) as number | null | undefined,
    query: (r.query ?? r.Query) as string | null | undefined,
    concurrencyStamp: (r.concurrencyStamp ?? r.ConcurrencyStamp) as string | null | undefined,
  };
}

function toPagedMenus(raw: unknown): PagedResultDto<SystemMenuDto> {
  if (Array.isArray(raw)) {
    return {
      items: raw.map(normalizeSystemMenuDto),
      totalCount: raw.length,
    };
  }
  const o = raw as Record<string, unknown>;
  const itemsRaw = (o.items ?? o.Items ?? []) as unknown[];
  const total =
    typeof o.totalCount === "number"
      ? o.totalCount
      : typeof o.TotalCount === "number"
        ? (o.TotalCount as number)
        : itemsRaw.length;
  return {
    items: itemsRaw.map(normalizeSystemMenuDto),
    totalCount: total,
  };
}

export async function getSystemMenus(
  input: SystemMenuGetListInput,
  signal?: AbortSignal,
): Promise<PagedResultDto<SystemMenuDto>> {
  const raw = await api.requestJson<unknown>({
    path: PATH,
    method: "GET",
    query: {
      SkipCount: input.skipCount,
      MaxResultCount: input.maxResultCount,
      Sorting: input.sorting,
      Keyword: input.keyword,
    },
    signal,
  });
  return toPagedMenus(raw);
}

function normalizeRbacMenuTreeNode(row: unknown): RbacMenuTreeNode {
  const base = normalizeSystemMenuDto(row);
  const r = row as Record<string, unknown> | null | undefined;
  const childrenRaw = (r?.children ?? (r as any)?.Children) as unknown;
  const children = Array.isArray(childrenRaw) ? childrenRaw.map(normalizeRbacMenuTreeNode) : undefined;
  return {
    ...base,
    children,
  };
}

function toMenuTree(raw: unknown): RbacMenuTreeNode[] {
  if (Array.isArray(raw)) return raw.map(normalizeRbacMenuTreeNode);
  if (!raw || typeof raw !== "object") return [];
  const o = raw as Record<string, unknown>;
  const items = (o.items ?? o.Items ?? o.data ?? (o as any).Data) as unknown;
  if (Array.isArray(items)) return items.map(normalizeRbacMenuTreeNode);
  return [];
}

/**
 * Swagger: GET /api/app/rbac-menu/tree (no params)
 */
export async function getRbacMenuTree(signal?: AbortSignal): Promise<RbacMenuTreeNode[]> {
  const raw = await api.requestJson<unknown>({
    path: `${PATH}/tree`,
    method: "GET",
    signal,
  });
  return toMenuTree(raw);
}

/**
 * 父级下拉:所有目录类型菜单(menuType === 0),多页合并。
 */
export async function getDirectoryMenusForParentSelect(signal?: AbortSignal): Promise<SystemMenuDto[]> {
  const byId = new Map<string, SystemMenuDto>();
  let page = 1;
  const size = 500;
  for (;;) {
    const res = await getSystemMenus({ skipCount: page, maxResultCount: size }, signal);
    const items = res.items ?? [];
    for (const m of items) {
      if (m.menuType !== 0 || !m.id) continue;
      if (!byId.has(m.id)) byId.set(m.id, m);
    }
    if (items.length < size) break;
    page += 1;
    if (page > 100) break;
  }
  return Array.from(byId.values()).sort((a, b) => (a.orderNum ?? 0) - (b.orderNum ?? 0));
}

export async function createSystemMenu(input: SystemMenuUpsertInput): Promise<SystemMenuDto> {
  const raw = await api.requestJson<unknown>({
    path: PATH,
    method: "POST",
    body: input,
  });
  return normalizeSystemMenuDto(raw);
}

export async function updateSystemMenu(id: string, input: SystemMenuUpsertInput): Promise<SystemMenuDto> {
  const raw = await api.requestJson<unknown>({
    path: `${PATH}/${encodeURIComponent(id)}`,
    method: "PUT",
    body: input,
  });
  return normalizeSystemMenuDto(raw);
}

/**
 * Swagger: DELETE /api/app/rbac-menu,Body 为 ID 数组
 */
export async function deleteSystemMenus(ids: string[]): Promise<void> {
  if (!ids.length) return;
  await api.requestJson<unknown>({
    path: PATH,
    method: "DELETE",
    body: ids,
  });
}

export async function deleteSystemMenu(id: string): Promise<void> {
  await deleteSystemMenus([id]);
}