feat(portal-shell): extract domain API layer and migrate 31 widgets
Task 4-10 of portal-shell data abstraction plan (M1-M2). Add 7 domain API modules under src/lib/api/ (parent/admin/teacher/ student/universal/sidebar/topbar), each exposing semantic hooks that wrap useWidgetQuery/useWidgetMutation and return flattened domain models. Widget code now imports from @/lib/api instead of inlining gql literals. - 31 widgets migrated (gql literal count in widgets: 0) - 7 test files (85 cases, all passing) - topbar.useNotifications renamed to useNotificationBell to avoid barrel export collision with universal.useNotifications - typecheck + lint (0 errors) + test (85/85) verified
This commit is contained in:
135
apps/portal-shell/src/lib/api/topbar.ts
Normal file
135
apps/portal-shell/src/lib/api/topbar.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Topbar domain API
|
||||
*
|
||||
* 涵盖:useNotificationBell、useCurrentUser、useGlobalSearch
|
||||
*
|
||||
* widget 通过 `import { useNotificationBell } from "@/lib/api/topbar"` 调用,
|
||||
* 不再各自内嵌 gql/接口/手写类型。
|
||||
*
|
||||
* 注意:useNotificationBell 与 universal.useNotifications(分页通知列表)语义不同,
|
||||
* 前者是 topbar 铃铛 widget 专用(按 limit 取最近 N 条),后者是分页查询。
|
||||
*
|
||||
* 关联:spec §2.2
|
||||
*/
|
||||
import {
|
||||
GET_NOTIFICATIONS_DOC,
|
||||
GET_CURRENT_USER_DOC,
|
||||
SEARCH_DOC,
|
||||
} from "@/lib/api/operations/topbar.graphql";
|
||||
import { useWidgetQuery } from "@/lib/useWidgetQuery";
|
||||
import type { UseQueryResult } from "./types";
|
||||
|
||||
// ===== 领域模型类型 =====
|
||||
|
||||
export interface NotificationItem {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface CurrentUser {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
role: string;
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
}
|
||||
|
||||
// ===== 内部 Query 类型 =====
|
||||
|
||||
interface NotificationsQueryData {
|
||||
notifications: NotificationItem[];
|
||||
}
|
||||
|
||||
type NotificationsQueryVars = {
|
||||
limit: number;
|
||||
};
|
||||
|
||||
interface CurrentUserQueryData {
|
||||
me: CurrentUser | null;
|
||||
}
|
||||
|
||||
type CurrentUserQueryVars = Record<string, never>;
|
||||
|
||||
interface SearchQueryData {
|
||||
search: SearchResult[];
|
||||
}
|
||||
|
||||
type SearchQueryVars = {
|
||||
keyword: string;
|
||||
limit: number;
|
||||
};
|
||||
|
||||
// ===== Hooks =====
|
||||
|
||||
/**
|
||||
* 查询通知列表(topbar 铃铛 widget 专用,按 limit 取最近 N 条)。
|
||||
*
|
||||
* 关联:portal-shell spec §5.6 统一 Hook
|
||||
*/
|
||||
export function useNotificationBell(
|
||||
limit: number,
|
||||
): UseQueryResult<NotificationItem[]> {
|
||||
const result = useWidgetQuery<NotificationsQueryData, NotificationsQueryVars>(
|
||||
GET_NOTIFICATIONS_DOC,
|
||||
{ limit },
|
||||
);
|
||||
|
||||
return {
|
||||
data: result.data?.notifications,
|
||||
loading: result.loading,
|
||||
error: result.error,
|
||||
refetch: result.refetch,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前登录用户(iam 子图 me 字段)。
|
||||
*
|
||||
* 关联:portal-shell spec §5.6 统一 Hook
|
||||
*/
|
||||
export function useCurrentUser(): UseQueryResult<CurrentUser | null> {
|
||||
const result = useWidgetQuery<CurrentUserQueryData, CurrentUserQueryVars>(
|
||||
GET_CURRENT_USER_DOC,
|
||||
{},
|
||||
);
|
||||
|
||||
return {
|
||||
data: result.data?.me,
|
||||
loading: result.loading,
|
||||
error: result.error,
|
||||
refetch: result.refetch,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局搜索。
|
||||
*
|
||||
* `keyword` 为空时不发起查询(enabled: false),避免无效请求。
|
||||
*
|
||||
* 关联:portal-shell spec §5.6 统一 Hook
|
||||
*/
|
||||
export function useGlobalSearch(
|
||||
keyword: string,
|
||||
limit: number,
|
||||
): UseQueryResult<SearchResult[]> {
|
||||
const result = useWidgetQuery<SearchQueryData, SearchQueryVars>(
|
||||
SEARCH_DOC,
|
||||
{ keyword, limit },
|
||||
{ enabled: keyword.length > 0 },
|
||||
);
|
||||
|
||||
return {
|
||||
data: result.data?.search,
|
||||
loading: result.loading,
|
||||
error: result.error,
|
||||
refetch: result.refetch,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user