303 lines
6.4 KiB
TypeScript
303 lines
6.4 KiB
TypeScript
/**
|
||
* GraphQL client + queries/mutations(ARB-001 P2 第一版 schema)
|
||
*
|
||
* 维护者:ai13(teacher-portal)
|
||
* 关联:coord.md §1 ARB-001、02-architecture-design.md §4
|
||
*
|
||
* schema 来源:packages/shared-ts/contracts/graphql/teacher-bff.graphql(ai03 维护)
|
||
* P2 第一版 Query 范围(ARB-001 §1.2):dashboard / viewports / me / classes / class(5 个)
|
||
* P2 不包含 Mutation(P3+ 扩展)
|
||
*
|
||
* 错误处理(ARB-001 §1.3):
|
||
* - BFF 始终返回 ActionState 信封(success/errors/data)
|
||
* - GraphQL errors[].extensions.code = BFF_TEACHER_*(G14 裁决)
|
||
* - 前端按前缀路由 i18n key(F11: error.teacher_bff.<code_snake>)
|
||
*/
|
||
|
||
import { createClient, cacheExchange, fetchExchange } from "urql";
|
||
import { gql } from "urql";
|
||
import type { Client } from "urql";
|
||
|
||
// ============ Types(对齐 ARB-001 §1.2 schema) ============
|
||
|
||
export type DataScope =
|
||
"SELF" | "CLASS" | "GRADE" | "SCHOOL" | "DISTRICT" | "ALL";
|
||
|
||
export type ExamStatus =
|
||
"DRAFT" | "PUBLISHED" | "IN_PROGRESS" | "GRADING" | "SCORED" | "ARCHIVED";
|
||
|
||
export type SubmissionStatus = "NOT_SUBMITTED" | "SUBMITTED" | "GRADED";
|
||
|
||
export interface User {
|
||
id: string;
|
||
email: string;
|
||
name: string;
|
||
roles: string[];
|
||
dataScope: DataScope;
|
||
}
|
||
|
||
export interface ViewportItem {
|
||
key: string;
|
||
label: string;
|
||
route: string;
|
||
icon: string | null;
|
||
sortOrder: string;
|
||
requiredPermission: string | null;
|
||
}
|
||
|
||
export interface Class {
|
||
id: string;
|
||
name: string;
|
||
gradeId: string;
|
||
studentCount: number;
|
||
}
|
||
|
||
export interface DashboardStats {
|
||
totalExams: number;
|
||
pendingGrading: number;
|
||
todayHomework: number;
|
||
}
|
||
|
||
export interface DashboardData {
|
||
user: User;
|
||
classes: Class[];
|
||
viewports: ViewportItem[];
|
||
stats: DashboardStats;
|
||
}
|
||
|
||
// ============ urql client 单例(总裁 §2.17 方案 A) ============
|
||
|
||
/**
|
||
* 创建 urql client 单例
|
||
*
|
||
* Shell(teacher-portal)调用此函数创建 client,通过 GraphQLProvider 注入给所有 Remote。
|
||
* token 从 localStorage 读取(F12: P2 用 localStorage,P6 迁移 httpOnly cookie)。
|
||
*/
|
||
export function createGraphQLClient(): Client {
|
||
const url =
|
||
process.env.NEXT_PUBLIC_TEACHER_BFF_GRAPHQL_URL ||
|
||
"/api/v1/teacher/graphql";
|
||
|
||
return createClient({
|
||
url,
|
||
fetchOptions: () => {
|
||
const headers: Record<string, string> = {
|
||
"Content-Type": "application/json",
|
||
};
|
||
|
||
// F12: P2 token 存 localStorage
|
||
if (typeof window !== "undefined") {
|
||
const token = window.localStorage.getItem("edu_access_token");
|
||
if (token) {
|
||
headers["Authorization"] = `Bearer ${token}`;
|
||
}
|
||
}
|
||
|
||
return { headers };
|
||
},
|
||
exchanges: [cacheExchange, fetchExchange],
|
||
});
|
||
}
|
||
|
||
// ============ P2 Query(ARB-001 §1.2,5 个 Must Have) ============
|
||
|
||
/** 仪表盘聚合查询(并行:iam.me + iam.viewports + iam.permissions + classes.byTeacher) */
|
||
export const DashboardQuery = gql`
|
||
query Dashboard {
|
||
dashboard {
|
||
user {
|
||
id
|
||
email
|
||
name
|
||
roles
|
||
dataScope
|
||
}
|
||
classes {
|
||
id
|
||
name
|
||
gradeId
|
||
studentCount
|
||
}
|
||
viewports {
|
||
key
|
||
label
|
||
route
|
||
icon
|
||
sortOrder
|
||
requiredPermission
|
||
}
|
||
stats {
|
||
totalExams
|
||
pendingGrading
|
||
todayHomework
|
||
}
|
||
}
|
||
}
|
||
`;
|
||
|
||
/** 视口配置查询(导航) */
|
||
export const ViewportsQuery = gql`
|
||
query Viewports {
|
||
viewports {
|
||
key
|
||
label
|
||
route
|
||
icon
|
||
sortOrder
|
||
requiredPermission
|
||
}
|
||
}
|
||
`;
|
||
|
||
/** 当前用户信息查询 */
|
||
export const MeQuery = gql`
|
||
query Me {
|
||
me {
|
||
id
|
||
email
|
||
name
|
||
roles
|
||
dataScope
|
||
}
|
||
}
|
||
`;
|
||
|
||
/** 班级列表查询(按教师 dataScope 过滤) */
|
||
export const ClassesQuery = gql`
|
||
query Classes {
|
||
classes {
|
||
id
|
||
name
|
||
gradeId
|
||
studentCount
|
||
}
|
||
}
|
||
`;
|
||
|
||
/** 班级详情查询 */
|
||
export const ClassQuery = gql`
|
||
query Class($id: ID!) {
|
||
class(id: $id) {
|
||
id
|
||
name
|
||
gradeId
|
||
studentCount
|
||
}
|
||
}
|
||
`;
|
||
|
||
// ============ P3 Query/Mutation(MSW mock,待 teacher-bff P3+ 实现) ============
|
||
// 以下查询对应 workline.md §3 P3-P5 交付物,schema 尚未在 ARB-001 P2 第一版中定义。
|
||
// 开发期通过 MSW 拦截返回 mock 响应;上游就绪后由 ai03 在 teacher-bff.graphql 扩展。
|
||
|
||
/** 学生信息(iam 数据,经 teacher-bff 聚合) */
|
||
export interface Student {
|
||
id: string;
|
||
name: string;
|
||
email: string;
|
||
avatarUrl: string | null;
|
||
}
|
||
|
||
/** 班级学生名单查询(P3 扩展,MSW mock) */
|
||
export const ClassStudentsQuery = gql`
|
||
query ClassStudents($classId: ID!) {
|
||
classStudents(classId: $classId) {
|
||
id
|
||
name
|
||
email
|
||
avatarUrl
|
||
}
|
||
}
|
||
`;
|
||
|
||
/** 考试项(core-edu 域) */
|
||
export interface ExamItem {
|
||
id: string;
|
||
classId: string;
|
||
title: string;
|
||
description: string | null;
|
||
examDate: string;
|
||
duration: number;
|
||
totalScore: number;
|
||
status: ExamStatus;
|
||
}
|
||
|
||
/** 班级考试列表查询(P3 扩展,MSW mock) */
|
||
export const ClassExamsQuery = gql`
|
||
query ClassExams($classId: ID!) {
|
||
classExams(classId: $classId) {
|
||
id
|
||
classId
|
||
title
|
||
description
|
||
examDate
|
||
duration
|
||
totalScore
|
||
status
|
||
}
|
||
}
|
||
`;
|
||
|
||
/** 作业项(core-edu 域) */
|
||
export interface HomeworkItem {
|
||
id: string;
|
||
classId: string;
|
||
title: string;
|
||
description: string | null;
|
||
dueDate: string;
|
||
status: SubmissionStatus;
|
||
}
|
||
|
||
/** 班级作业列表查询(P3 扩展,MSW mock) */
|
||
export const ClassHomeworkQuery = gql`
|
||
query ClassHomework($classId: ID!) {
|
||
classHomework(classId: $classId) {
|
||
id
|
||
classId
|
||
title
|
||
description
|
||
dueDate
|
||
status
|
||
}
|
||
}
|
||
`;
|
||
|
||
/** 成绩项(core-edu 域) */
|
||
export interface GradeItem {
|
||
id: string;
|
||
studentId: string;
|
||
studentName: string;
|
||
examId: string | null;
|
||
homeworkId: string | null;
|
||
score: number;
|
||
feedback: string | null;
|
||
}
|
||
|
||
/** 考试成绩列表查询(P3 扩展,MSW mock) */
|
||
export const ExamGradesQuery = gql`
|
||
query ExamGrades($examId: ID!) {
|
||
examGrades(examId: $examId) {
|
||
id
|
||
studentId
|
||
studentName
|
||
examId
|
||
homeworkId
|
||
score
|
||
feedback
|
||
}
|
||
}
|
||
`;
|
||
|
||
/** 更新用户信息 Mutation(P3 扩展,MSW mock) */
|
||
export const UpdateUserMutation = gql`
|
||
mutation UpdateUser($input: UpdateUserInput!) {
|
||
updateUser(input: $input) {
|
||
id
|
||
email
|
||
name
|
||
roles
|
||
dataScope
|
||
}
|
||
}
|
||
`;
|