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:
245
apps/portal-shell/src/lib/api/analytics.ts
Normal file
245
apps/portal-shell/src/lib/api/analytics.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Analytics domain API(ARCHITECTURE.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 LearningTrend,points 运行时按列表处理) */
|
||||
export interface LearningTrend {
|
||||
student_id: string;
|
||||
points: TrendPoint[];
|
||||
}
|
||||
|
||||
/** 学生薄弱点(对齐 schema StudentWeakness,weak_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 =====
|
||||
|
||||
/**
|
||||
* 查询学习趋势(✅ 真实 schema,learningTrend)。
|
||||
*
|
||||
* 关联: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,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询学生薄弱知识点(✅ 真实 schema,studentWeakness)。
|
||||
*
|
||||
* 关联: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-pending,MSW 兜底)。
|
||||
*
|
||||
* 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-pending,MSW 兜底)。
|
||||
*
|
||||
* 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,
|
||||
};
|
||||
}
|
||||
155
apps/portal-shell/src/lib/api/diagnostic.ts
Normal file
155
apps/portal-shell/src/lib/api/diagnostic.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Diagnostic domain API(ARCHITECTURE.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 =====
|
||||
|
||||
/**
|
||||
* 查询诊断报告列表(✅ 真实 schema,diagnosticReports)。
|
||||
*
|
||||
* 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-pending,MSW 兜底)。
|
||||
*
|
||||
* 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,
|
||||
};
|
||||
}
|
||||
151
apps/portal-shell/src/lib/api/error-book.ts
Normal file
151
apps/portal-shell/src/lib/api/error-book.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Error Book domain API(ARCHITECTURE.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 =====
|
||||
|
||||
/**
|
||||
* 查询错题本条目列表(✅ 真实 schema,errorBookItems)。
|
||||
*
|
||||
* 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,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询错题本统计(✅ 真实 schema,errorBookStats)。
|
||||
*
|
||||
* 复用 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,
|
||||
};
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
118
apps/portal-shell/src/lib/api/operations/analytics.graphql.ts
Normal file
118
apps/portal-shell/src/lib/api/operations/analytics.graphql.ts
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -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 复用。
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user