feat(portal-shell): error-book + diagnostic + analytics 模块 5 页迁移(教师域 §9.1 B2)

§9.1 line 637-639 教师域:
- /shell/teacher/error-book (列表,1 页)
- /shell/teacher/diagnostic (列表) + /class/[classId] (详情) — 2 页
- /shell/teacher/analytics (概览) + /[studentId] (详情) — 2 页
契约:
- error-book  真实 errorBookItems/errorBookStats
- diagnostic  真实 diagnosticReports 列表 + 详情  MSW
- analytics 🟡 真实 learningTrend/studentWeakness + 概览  MSW

新增文件(24 个):
- src/lib/api/{error-book,diagnostic,analytics}.ts (hooks)
- src/lib/api/operations/{error-book,diagnostic,analytics}.graphql.ts (documents)
- src/features/teacher/{error-book,diagnostic,analytics}/ (clients + transformations + tests)
- src/app/shell/teacher/{error-book,diagnostic,analytics}/ (5 page.tsx + 3 loading + 3 error)

修改文件(7 个):
- src/mocks/graphql-data.ts (7 handler cases)
- src/messages/{zh-CN,en}.json (errorBook/diagnostic/analytics i18n)
- src/lib/api/{index,operations/index}.ts (导出)
- src/shared/lib/route-permissions.ts (3 EXACT + 3 PREFIX 路由权限)
- scripts/check-page-count.ts (baseline 53 → 58)

DoD 验收(§11.3 11 项):
- typecheck 0 errors
- lint 0 errors
- vitest 734 tests passed
- lint:tokens 0 errors
- check:pages 58 PASS
- route-permissions 已声明
- 三态齐备
- @contract-pending + MSW 兜底(仅对 schema 不存在的字段)
- i18n zh-CN + en 同步

设计决策:
- 类型命名冲突解决:ErrorBookItem → ErrorBookEntry;ErrorBookStats → TeacherErrorBookStats;
  KnowledgePointErrorStats → ErrorBookKpStats;useErrorBookStats → useTeacherErrorBookStats
  (避免与 dashboard.ts/student.ts 同名类型冲突)
- analytics TrendPoint/WeakPoint 形状相同,从 dashboard.ts import 复用
- error-book/analytics 复用 CLASS_READ/CLASS_MANAGE(无专用教师权限点);
  diagnostic 复用 DIAGNOSTIC_READ/DIAGNOSTIC_MANAGE

关联:ARCHITECTURE.md §5.3 / §5.4 / §5.5 / §9.1 / §10 P2 / §11.3 / §11.4
契约工单:docs/architecture/issues/contracts/core-edu_contract.md
This commit is contained in:
SpecialX
2026-07-22 23:50:30 +08:00
parent 80cc1d2461
commit 5a9f652943
35 changed files with 4207 additions and 8 deletions

View File

@@ -0,0 +1,245 @@
"use client";
/**
* Analytics domain APIARCHITECTURE.md §5.1 / §5.3 / §9.1 教师域学情分析模块)
*
* 契约状态:混合(🟡 部分真实 + 部分契约待补)
* - learningTrend: ✅ 真实schema 已就绪LearningTrend无参数
* - studentWeakness: ✅ 真实schema 已就绪StudentWeakness无参数
* - analyticsOverview: ❌ schema 无此聚合根字段 → MSW 兜底(@contract-pending
* - studentAnalytics(studentId): ❌ schema 无此根字段 → MSW 兜底(@contract-pending
*
* Schema 缺陷处理data-ana 子图):
* - LearningTrend.points 在 schema 中是单数 TrendPoint 类型(应为列表)
* - StudentWeakness.weak_points 在 schema 中是单数 WeakPoint 类型(应为列表)
* - lib/api 层 TypeScript 类型按业务语义定义为数组MSW 返回数组形状
*
* 契约工单docs/architecture/issues/contracts/data-ana_contract.md#analytics
* 关联ARCHITECTURE.md §5.3 契约纪律 / §5.4 MSW 兜底 / §5.5 后端已就绪查询 / §9.1 / §11.4
*/
import type { FetchPolicy } from "@apollo/client";
import { useWidgetQuery } from "../useWidgetQuery";
import {
GET_ANALYTICS_OVERVIEW_DOC,
GET_LEARNING_TREND_DOC,
GET_STUDENT_ANALYTICS_DOC,
GET_STUDENT_WEAKNESS_DOC,
} from "./operations/analytics.graphql";
import type { TrendPoint, WeakPoint } from "./dashboard";
import type { UseQueryResult } from "./types";
// ===== 数据类型(对齐 data-ana 子图snake_case=====
// TrendPoint / WeakPoint 复用 dashboard.ts 的定义(同 schema 字段,避免 barrel 导出冲突)
/** 学习趋势(对齐 schema LearningTrendpoints 运行时按列表处理) */
export interface LearningTrend {
student_id: string;
points: TrendPoint[];
}
/** 学生薄弱点(对齐 schema StudentWeaknessweak_points 运行时按列表处理) */
export interface StudentWeakness {
student_id: string;
weak_points: WeakPoint[];
}
/** 总览趋势点(@contract-pending 扩展,含 avg_score */
export interface OverviewTrendPoint {
date: string;
avg_score: number;
}
/** 总览薄弱知识点(@contract-pending 扩展) */
export interface OverviewWeakPoint {
knowledge_point_id: string;
title: string;
error_count: number;
mastery: number;
}
/** 班级学情分解(@contract-pending 扩展) */
export interface AnalyticsClassBreakdown {
class_id: string;
class_name: string;
avg_score: number;
student_count: number;
at_risk_count: number;
}
/** 学情分析总览(@contract-pending 聚合数据) */
export interface AnalyticsOverview {
total_students: number;
avg_score: number;
avg_mastery: number;
at_risk_count: number;
weak_points: OverviewWeakPoint[];
trends: OverviewTrendPoint[];
class_breakdown: AnalyticsClassBreakdown[];
}
/** 学生近期考试(@contract-pending 扩展) */
export interface StudentRecentExam {
exam_id: string;
exam_title: string;
score: number;
total_score: number;
date: string;
}
/** 单生学情分析详情(@contract-pending 聚合数据) */
export interface StudentAnalytics {
student_id: string;
student_name: string;
student_no: string;
class_id: string;
class_name: string;
avg_score: number;
class_rank: number;
total_students: number;
weak_points: WeakPoint[];
trends: TrendPoint[];
recent_exams: StudentRecentExam[];
}
// ===== 响应类型 =====
/** learningTrend 查询响应 */
interface LearningTrendResponse {
learningTrend: LearningTrend | null;
}
/** studentWeakness 查询响应 */
interface StudentWeaknessResponse {
studentWeakness: StudentWeakness | null;
}
/** analyticsOverview 查询响应(@contract-pending */
interface AnalyticsOverviewResponse {
analyticsOverview: AnalyticsOverview | null;
}
/** studentAnalytics 查询响应(@contract-pending */
interface StudentAnalyticsResponse {
studentAnalytics: StudentAnalytics | null;
}
// ===== 查询选项 =====
export interface AnalyticsQueryOptions {
enabled?: boolean;
pollInterval?: number;
fetchPolicy?: FetchPolicy;
}
// ===== Hooks =====
/**
* 查询学习趋势(✅ 真实 schemalearningTrend
*
* 关联ARCHITECTURE.md §5.5 后端已就绪查询 / §9.1
*/
export function useLearningTrend(
options?: AnalyticsQueryOptions,
): UseQueryResult<LearningTrend | null> {
const result = useWidgetQuery<LearningTrendResponse>(
GET_LEARNING_TREND_DOC,
{},
{
enabled: options?.enabled ?? true,
fetchPolicy: options?.fetchPolicy,
pollInterval: options?.pollInterval,
},
);
return {
data: result.data?.learningTrend ?? null,
loading: result.loading,
error: result.error,
refetch: result.refetch,
};
}
/**
* 查询学生薄弱知识点(✅ 真实 schemastudentWeakness
*
* 关联ARCHITECTURE.md §5.5 后端已就绪查询 / §9.1
*/
export function useStudentWeakness(
options?: AnalyticsQueryOptions,
): UseQueryResult<StudentWeakness | null> {
const result = useWidgetQuery<StudentWeaknessResponse>(
GET_STUDENT_WEAKNESS_DOC,
{},
{
enabled: options?.enabled ?? true,
fetchPolicy: options?.fetchPolicy,
pollInterval: options?.pollInterval,
},
);
return {
data: result.data?.studentWeakness ?? null,
loading: result.loading,
error: result.error,
refetch: result.refetch,
};
}
/**
* 查询学情分析总览(@contract-pendingMSW 兜底)。
*
* schema 无 analyticsOverview 根字段,由 MSW handlers 返回 mock 数据。
* 用于 /shell/teacher/analytics 总览页。
*
* 关联ARCHITECTURE.md §5.4 / §9.1 总览页 / §11.4 契约工单
*/
export function useAnalyticsOverview(
options?: AnalyticsQueryOptions,
): UseQueryResult<AnalyticsOverview | null> {
const result = useWidgetQuery<AnalyticsOverviewResponse>(
GET_ANALYTICS_OVERVIEW_DOC,
{},
{
enabled: options?.enabled ?? true,
fetchPolicy: options?.fetchPolicy,
pollInterval: options?.pollInterval,
},
);
return {
data: result.data?.analyticsOverview ?? null,
loading: result.loading,
error: result.error,
refetch: result.refetch,
};
}
/**
* 按 studentId 查询单生学情分析(@contract-pendingMSW 兜底)。
*
* schema 无 studentAnalytics(studentId) 根字段,由 MSW handlers 返回 mock 数据。
* 用于 /shell/teacher/analytics/[studentId] 学生详情页。
*
* 关联ARCHITECTURE.md §5.4 / §9.1 详情页 / §11.4 契约工单
*/
export function useStudentAnalytics(
studentId: string,
options?: AnalyticsQueryOptions,
): UseQueryResult<StudentAnalytics | null> {
const result = useWidgetQuery<
StudentAnalyticsResponse,
{ studentId: string }
>(
GET_STUDENT_ANALYTICS_DOC,
{ studentId },
{
...options,
enabled: options?.enabled ?? studentId.length > 0,
},
);
return {
data: result.data?.studentAnalytics ?? null,
loading: result.loading,
error: result.error,
refetch: result.refetch,
};
}

View File

@@ -0,0 +1,155 @@
"use client";
/**
* Diagnostic domain APIARCHITECTURE.md §5.1 / §5.3 / §9.1 教师域诊断报告模块)
*
* 契约状态:混合
* - diagnosticReports: ✅ 真实schema 已就绪,[DiagnosticReportList!]!,无参数)
* - diagnosticReport(classId): ❌ schema 无此字段 → MSW 兜底(@contract-pending
*
* Schema 缺陷处理data-ana 子图):
* - DiagnosticReportList.reports 在 schema 中是单数 DiagnosticReport 类型(应为列表)
* - lib/api 层 TypeScript 类型按业务语义定义为数组MSW 返回数组形状
*
* 契约工单docs/architecture/issues/contracts/data-ana_contract.md#diagnostic
* 关联ARCHITECTURE.md §5.3 契约纪律 / §5.4 MSW 兜底 / §9.1 / §11.4
*/
import type { FetchPolicy } from "@apollo/client";
import { useWidgetQuery } from "../useWidgetQuery";
import {
GET_DIAGNOSTIC_REPORT_DOC,
GET_DIAGNOSTIC_REPORTS_DOC,
} from "./operations/diagnostic.graphql";
import type { UseQueryResult } from "./types";
// ===== 数据类型(对齐 data-ana 子图snake_case=====
/** 诊断报告状态枚举 */
export type DiagnosticReportStatus =
"DRAFT" | "GENERATED" | "PUBLISHED" | "ARCHIVED";
/** 诊断报告基础信息(对齐 schema DiagnosticReport */
export interface DiagnosticReport {
report_id: string;
student_id: string;
report_type: string;
title: string;
summary: string;
generated_at: string;
status: string;
}
/** 班级诊断报告详情(@contract-pending 扩展字段MSW 返回) */
export interface DiagnosticReportDetail extends DiagnosticReport {
class_id: string;
class_name: string;
student_count: number;
avg_score: number;
weak_points: DiagnosticWeakPoint[];
recommendations: string[];
}
/** 诊断薄弱知识点 */
export interface DiagnosticWeakPoint {
knowledge_point_id: string;
title: string;
mastery: number;
error_count: number;
}
/** 诊断报告列表项(展平后供页面使用,带上聚合来源 student_id */
export interface DiagnosticReportListItem extends DiagnosticReport {
student_id: string;
}
// ===== 响应类型 =====
/**
* diagnosticReports 查询响应。
* schema: diagnosticReports: [DiagnosticReportList!]!
* DiagnosticReportList.reports 在 schema 中是单数运行时按列表处理MSW 返回数组)
*/
interface DiagnosticReportsResponse {
diagnosticReports: Array<{
student_id: string;
reports: DiagnosticReport[];
total: number;
}>;
}
/** diagnosticReport(classId) 查询响应(@contract-pending */
interface DiagnosticReportResponse {
diagnosticReport: DiagnosticReportDetail | null;
}
// ===== 查询选项 =====
export interface DiagnosticQueryOptions {
enabled?: boolean;
pollInterval?: number;
fetchPolicy?: FetchPolicy;
}
// ===== Hooks =====
/**
* 查询诊断报告列表(✅ 真实 schemadiagnosticReports
*
* diagnosticReports 返回 [DiagnosticReportList!]!(按 student 聚合),
* 本 hook 展平为 DiagnosticReportListItem[] 供列表页渲染。
*
* 关联ARCHITECTURE.md §5.5 后端已就绪查询 / §9.1 列表页
*/
export function useDiagnosticReports(
options?: DiagnosticQueryOptions,
): UseQueryResult<{ items: DiagnosticReportListItem[]; total: number }> {
const result = useWidgetQuery<DiagnosticReportsResponse>(
GET_DIAGNOSTIC_REPORTS_DOC,
{},
{
enabled: options?.enabled ?? true,
fetchPolicy: options?.fetchPolicy,
pollInterval: options?.pollInterval,
},
);
const raw = result.data?.diagnosticReports ?? [];
const items: DiagnosticReportListItem[] = raw.flatMap((group) =>
group.reports.map((r) => ({ ...r, student_id: group.student_id })),
);
const total = raw.reduce((sum, g) => sum + g.total, 0);
return {
data: { items, total },
loading: result.loading,
error: result.error,
refetch: result.refetch,
};
}
/**
* 按 classId 查询班级诊断报告详情(@contract-pendingMSW 兜底)。
*
* schema 无 diagnosticReport(classId) 根字段,由 MSW handlers 返回 mock 数据。
* 用于 /shell/teacher/diagnostic/class/[classId] 班级诊断详情页。
*
* 关联ARCHITECTURE.md §5.4 / §9.1 详情页 / §11.4 契约工单
*/
export function useDiagnosticReport(
classId: string,
options?: DiagnosticQueryOptions,
): UseQueryResult<DiagnosticReportDetail | null> {
const result = useWidgetQuery<DiagnosticReportResponse, { classId: string }>(
GET_DIAGNOSTIC_REPORT_DOC,
{ classId },
{
...options,
enabled: options?.enabled ?? classId.length > 0,
},
);
return {
data: result.data?.diagnosticReport ?? null,
loading: result.loading,
error: result.error,
refetch: result.refetch,
};
}

View File

@@ -0,0 +1,151 @@
"use client";
/**
* Error Book domain APIARCHITECTURE.md §5.1 / §5.3 / §9.1 教师域错题本模块)
*
* 契约状态:✅ 全真实schema 已就绪)
* - errorBookItems: [ErrorBookList!]!(无参数)
* - errorBookStats: ErrorBookStats无参数复用 dashboard.graphql 的 GET_ERROR_BOOK_STATS_DOC
*
* Schema 缺陷处理data-ana 子图):
* - ErrorBookList.items 在 schema 中是单数 ErrorBookItem 类型(应为列表)
* - ErrorBookStats.by_knowledge_point 在 schema 中是单数 KnowledgePointErrorStats应为列表
* - lib/api 层 TypeScript 类型按业务语义定义为数组MSW 返回数组形状
* - 后端补齐 schema 后类型自然对齐
*
* 契约工单docs/architecture/issues/contracts/data-ana_contract.md#error-book
* 关联ARCHITECTURE.md §5.3 契约纪律 / §5.5 后端已就绪查询 / §9.1 / §11.4
*/
import type { FetchPolicy } from "@apollo/client";
import { useWidgetQuery } from "../useWidgetQuery";
import { GET_ERROR_BOOK_STATS_DOC } from "./operations/dashboard.graphql";
import { GET_ERROR_BOOK_ITEMS_DOC } from "./operations/error-book.graphql";
import type { UseQueryResult } from "./types";
// ===== 数据类型(对齐 data-ana 子图snake_case=====
// 注:类型名加 Teacher 前缀避免与 dashboard.ts / student.ts 的同名类型 barrel 导出冲突
/** 错题条目(对齐 schema ErrorBookItem教师域视图 */
export interface ErrorBookEntry {
question_id: string;
knowledge_point_id: string;
knowledge_point_title: string;
error_count: number;
last_error_time: string;
content: string;
}
/** 知识点错误统计(对齐 schema KnowledgePointErrorStats教师域视图 */
export interface ErrorBookKpStats {
knowledge_point_id: string;
title: string;
error_count: number;
question_count: number;
error_rate: number;
}
/** 错题本统计(对齐 schema ErrorBookStats教师域视图by_knowledge_point 运行时按列表处理) */
export interface TeacherErrorBookStats {
student_id: string;
total_error_questions: number;
total_error_count: number;
by_knowledge_point: ErrorBookKpStats[];
recent_7d_errors: number;
}
/** 错题本列表项(展平后供页面使用,每条带上 student_id */
export interface ErrorBookListItem extends ErrorBookEntry {
student_id: string;
}
// ===== 响应类型 =====
/**
* errorBookItems 查询响应。
* schema: errorBookItems: [ErrorBookList!]!
* ErrorBookList.items 在 schema 中是单数运行时按列表处理MSW 返回数组)
*/
interface ErrorBookItemsResponse {
errorBookItems: Array<{
student_id: string;
items: ErrorBookEntry[];
total: number;
}>;
}
/** errorBookStats 查询响应 */
interface ErrorBookStatsResponse {
errorBookStats: TeacherErrorBookStats | null;
}
// ===== 查询选项 =====
export interface ErrorBookQueryOptions {
enabled?: boolean;
pollInterval?: number;
fetchPolicy?: FetchPolicy;
}
// ===== Hooks =====
/**
* 查询错题本条目列表(✅ 真实 schemaerrorBookItems
*
* errorBookItems 返回 [ErrorBookList!]!(按 student 聚合),
* 本 hook 展平为 ErrorBookListItem[],每条带上 student_id 便于列表渲染。
*
* 关联ARCHITECTURE.md §5.5 后端已就绪查询 / §9.1 列表页
*/
export function useErrorBookItems(
options?: ErrorBookQueryOptions,
): UseQueryResult<{ items: ErrorBookListItem[]; total: number }> {
const result = useWidgetQuery<ErrorBookItemsResponse>(
GET_ERROR_BOOK_ITEMS_DOC,
{},
{
enabled: options?.enabled ?? true,
fetchPolicy: options?.fetchPolicy,
pollInterval: options?.pollInterval,
},
);
const raw = result.data?.errorBookItems ?? [];
const items: ErrorBookListItem[] = raw.flatMap((group) =>
group.items.map((item) => ({ ...item, student_id: group.student_id })),
);
const total = raw.reduce((sum, g) => sum + g.total, 0);
return {
data: { items, total },
loading: result.loading,
error: result.error,
refetch: result.refetch,
};
}
/**
* 查询错题本统计(✅ 真实 schemaerrorBookStats
*
* 复用 dashboard.graphql.ts 的 GET_ERROR_BOOK_STATS_DOC同一 schema 字段)。
* 用于错题本列表页顶部统计卡片区。
*
* 关联ARCHITECTURE.md §5.5 后端已就绪查询 / §9.1 列表页
*/
export function useTeacherErrorBookStats(
options?: ErrorBookQueryOptions,
): UseQueryResult<TeacherErrorBookStats | null> {
const result = useWidgetQuery<ErrorBookStatsResponse>(
GET_ERROR_BOOK_STATS_DOC,
{},
{
enabled: options?.enabled ?? true,
fetchPolicy: options?.fetchPolicy,
pollInterval: options?.pollInterval,
},
);
return {
data: result.data?.errorBookStats ?? null,
loading: result.loading,
error: result.error,
refetch: result.refetch,
};
}

View File

@@ -26,5 +26,8 @@ export * from "./students";
export * from "./student";
export * from "./course-plans";
export * from "./elective";
export * from "./error-book";
export * from "./diagnostic";
export * from "./analytics";
export * from "./parent";
export * from "./admin";

View File

@@ -0,0 +1,118 @@
// Analytics domain GraphQL documents (ARCHITECTURE.md §5.3 契约纪律 / §9.1)
//
// 拆分原则:
// - learningTrend✅ combined-schema 中真实存在learningTrend: LearningTrend
// - studentWeakness✅ combined-schema 中真实存在studentWeakness: StudentWeakness
// - analyticsOverview❌ schema 无此聚合根字段 → MSW 兜底(@contract-pending
// - studentAnalytics(studentId):❌ schema 无此根字段 → MSW 兜底(@contract-pending
//
// Schema 缺陷说明data-ana 子图):
// - LearningTrend.points 在 schema 中是单数 TrendPoint 类型(应为列表)
// - StudentWeakness.weak_points 在 schema 中是单数 WeakPoint 类型(应为列表)
// - operations 按单数对象语法查询lib/api 层运行时按列表处理MSW 返回数组)
//
// 契约工单docs/architecture/issues/contracts/data-ana_contract.md#analytics
// 关联ARCHITECTURE.md §5.3 / §5.4 / §9.1 / §11.4
import { gql } from "@apollo/client";
// ── 真实查询learningTrend无参数──────────────────────────
// schema: learningTrend: LearningTrend
// LearningTrend { student_id, points: TrendPoint }
// 字段全部 snake_case 对齐 data-ana 子图
export const GET_LEARNING_TREND_DOC = gql`
query GetLearningTrend {
learningTrend {
student_id
points {
date
score
}
}
}
`;
// ── 真实查询studentWeakness无参数────────────────────────
// schema: studentWeakness: StudentWeakness
// StudentWeakness { student_id, weak_points: WeakPoint }
// 字段全部 snake_case 对齐 data-ana 子图
export const GET_STUDENT_WEAKNESS_DOC = gql`
query GetStudentWeakness {
studentWeakness {
student_id
weak_points {
knowledge_point_id
title
mastery
error_count
}
}
}
`;
// ── 假契约查询(@contract-pending─────────────────────────────
// 学情分析总览schema 无 analyticsOverview 根字段 → MSW 兜底
// 用于 /shell/teacher/analytics 总览页(聚合班级学情数据)
// 契约工单data-ana_contract.md#analytics-overview
export const GET_ANALYTICS_OVERVIEW_DOC = gql`
query GetAnalyticsOverview {
analyticsOverview {
total_students
avg_score
avg_mastery
at_risk_count
weak_points {
knowledge_point_id
title
error_count
mastery
}
trends {
date
avg_score
}
class_breakdown {
class_id
class_name
avg_score
student_count
at_risk_count
}
}
}
`;
// ── 假契约查询(@contract-pending─────────────────────────────
// 单生学情分析schema 无 studentAnalytics(studentId) 根字段 → MSW 兜底
// 用于 /shell/teacher/analytics/[studentId] 学生详情页
// 契约工单data-ana_contract.md#student-analytics
export const GET_STUDENT_ANALYTICS_DOC = gql`
query GetStudentAnalytics($studentId: ID!) {
studentAnalytics(studentId: $studentId) {
student_id
student_name
student_no
class_id
class_name
avg_score
class_rank
total_students
weak_points {
knowledge_point_id
title
mastery
error_count
}
trends {
date
score
}
recent_exams {
exam_id
exam_title
score
total_score
date
}
}
}
`;

View File

@@ -0,0 +1,64 @@
// Diagnostic domain GraphQL documents (ARCHITECTURE.md §5.3 契约纪律 / §9.1)
//
// 拆分原则:
// - diagnosticReports✅ combined-schema 中真实存在diagnosticReports: [DiagnosticReportList!]!
// - diagnosticReport(classId):❌ schema 无此根字段 → MSW 兜底(@contract-pending
//
// Schema 缺陷说明data-ana 子图):
// - DiagnosticReportList.reports 在 schema 中是单数 DiagnosticReport 类型(应为列表)
// - operations 按单数对象语法查询lib/api 层运行时按列表处理MSW 返回数组)
//
// 契约工单docs/architecture/issues/contracts/data-ana_contract.md#diagnostic
// 关联ARCHITECTURE.md §5.3 / §5.4 / §9.1 / §11.4
import { gql } from "@apollo/client";
// ── 真实查询diagnosticReports无参数──────────────────────
// schema: diagnosticReports: [DiagnosticReportList!]!
// 每个 DiagnosticReportList { student_id, reports: DiagnosticReport, total }
// 字段全部 snake_case 对齐 data-ana 子图
export const GET_DIAGNOSTIC_REPORTS_DOC = gql`
query GetDiagnosticReports {
diagnosticReports {
student_id
reports {
report_id
student_id
report_type
title
summary
generated_at
status
}
total
}
}
`;
// ── 假契约查询(@contract-pending─────────────────────────────
// 按 classId 单查班级诊断报告schema 无 diagnosticReport(classId) 根字段
// 页面通过 MSW 兜底获取,后端补齐后切换 fetcher 指向真实查询
// 契约工单data-ana_contract.md#diagnostic-class-detail
export const GET_DIAGNOSTIC_REPORT_DOC = gql`
query GetDiagnosticReport($classId: ID!) {
diagnosticReport(classId: $classId) {
report_id
student_id
report_type
title
summary
generated_at
status
class_id
class_name
student_count
avg_score
weak_points {
knowledge_point_id
title
mastery
error_count
}
recommendations
}
}
`;

View File

@@ -0,0 +1,38 @@
// Error Book domain GraphQL documents (ARCHITECTURE.md §5.3 契约纪律 / §9.1)
//
// 拆分原则:
// - errorBookItems✅ combined-schema 中真实存在errorBookItems: [ErrorBookList!]!
// - errorBookStats✅ 真实存在,但已由 dashboard.graphql.ts 定义GET_ERROR_BOOK_STATS_DOC
// → 此处不重复定义error-book.ts 直接从 dashboard.graphql 导入复用
//
// Schema 缺陷说明data-ana 子图):
// - ErrorBookList.items 在 schema 中是单数 ErrorBookItem 类型(应为列表)
// - operations 按单数对象语法查询lib/api 层运行时按列表处理MSW 返回数组)
//
// 契约工单docs/architecture/issues/contracts/data-ana_contract.md#error-book
// 关联ARCHITECTURE.md §5.3 / §5.4 / §9.1 / §11.4
import { gql } from "@apollo/client";
// ── 真实查询errorBookItems无参数─────────────────────────
// schema: errorBookItems: [ErrorBookList!]!
// 每个 ErrorBookList { student_id, items: ErrorBookItem, total }
// 字段全部 snake_case 对齐 data-ana 子图
export const GET_ERROR_BOOK_ITEMS_DOC = gql`
query GetErrorBookItems {
errorBookItems {
student_id
items {
question_id
knowledge_point_id
knowledge_point_title
error_count
last_error_time
content
}
total
}
}
`;
// 注GET_ERROR_BOOK_STATS_DOC 已在 dashboard.graphql.ts 定义并 export
// 通过 operations/index.ts 的 `export *` 暴露error-book.ts 直接 import 复用。

View File

@@ -16,5 +16,8 @@ export * from "./students.graphql";
export * from "./student.graphql";
export * from "./course-plans.graphql";
export * from "./elective.graphql";
export * from "./error-book.graphql";
export * from "./diagnostic.graphql";
export * from "./analytics.graphql";
export * from "./parent.graphql";
export * from "./admin.graphql";