feat(teacher-portal): 完整实现 teacher-portal 微前端

包含 settings/students/api、graphql、mocks、ui-tokens 设计令牌等
This commit is contained in:
SpecialX
2026-07-10 19:10:20 +08:00
parent 14d836beb0
commit 1eacd1ed87
48 changed files with 3897 additions and 1056 deletions

View File

@@ -1,4 +1,10 @@
// 认证工具token 存储 + 路由保护
/**
* 认证工具token 存储 + 路由保护
*
* F12 裁决P2 用 localStorageaccessToken + userInfoP6 迁移 httpOnly cookie
* 维护者ai13teacher-portal
* 关联02-architecture-design.md §2.1 会话状态
*/
const TOKEN_KEY = "edu_access_token";
const USER_KEY = "edu_user_info";
@@ -48,12 +54,17 @@ export function isAuthenticated(): boolean {
return getToken() !== null;
}
// 调用 Gateway 登录接口
/**
* 调用 Gateway 登录接口
*
* 非 GraphQL认证前提MF 依赖认证F12 裁决)
* 路径POST /api/auth/login经 next.config.js rewrites 代理到 api-gateway
*/
export async function login(
email: string,
password: string,
): Promise<{ user: UserInfo; token: string }> {
const res = await fetch("/api/v1/iam/login", {
const res = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),

View File

@@ -0,0 +1,302 @@
/**
* GraphQL client + queries/mutationsARB-001 P2 第一版 schema
*
* 维护者ai13teacher-portal
* 关联coord.md §1 ARB-001、02-architecture-design.md §4
*
* schema 来源packages/shared-ts/contracts/graphql/teacher-bff.graphqlai03 维护)
* P2 第一版 Query 范围ARB-001 §1.2dashboard / viewports / me / classes / class5 个)
* P2 不包含 MutationP3+ 扩展)
*
* 错误处理ARB-001 §1.3
* - BFF 始终返回 ActionState 信封success/errors/data
* - GraphQL errors[].extensions.code = BFF_TEACHER_*G14 裁决)
* - 前端按前缀路由 i18n keyF11: 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 单例
*
* Shellteacher-portal调用此函数创建 client通过 GraphQLProvider 注入给所有 Remote。
* token 从 localStorage 读取F12: P2 用 localStorageP6 迁移 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 QueryARB-001 §1.25 个 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/MutationMSW 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
}
}
`;
/** 更新用户信息 MutationP3 扩展MSW mock */
export const UpdateUserMutation = gql`
mutation UpdateUser($input: UpdateUserInput!) {
updateUser(input: $input) {
id
email
name
roles
dataScope
}
}
`;