feat: restore learning feedback and knowledge capabilities

This commit is contained in:
2026-07-18 00:50:11 +08:00
parent 876a23a123
commit 6bdc86e3fc
152 changed files with 30337 additions and 953 deletions
+151
View File
@@ -0,0 +1,151 @@
import type { ExamDetail, ExamResultItem, ExamSubmitResult, ExamSummary, SupervisorExamDraft } from '@/types/api';
import { apiRequest } from './api';
type RawExamQuestion = {
id: number;
questionType: string;
stem: string;
options?: string[];
correctAnswers?: string[];
score: number;
explanation?: string;
sortOrder?: number;
};
type RawExamSummary = {
id: number;
title: string;
projectCode?: string;
positionCode?: string;
status: string;
totalScore: number;
passScore: number;
dueDate?: string | null;
questionCount: number;
targetCount: number;
submissionCount: number;
bestScore?: number | null;
passed?: boolean | null;
};
type RawExamDetail = Omit<RawExamSummary, 'questionCount' | 'targetCount' | 'submissionCount' | 'bestScore' | 'passed'> & {
questions: RawExamQuestion[];
targets: ExamDetail['targets'];
};
const normalizeSummary = (item: RawExamSummary): ExamSummary => ({
...item,
passingScore: item.passScore
});
const normalizeDetail = (item: RawExamDetail): ExamDetail => ({
...item,
passingScore: item.passScore,
questionCount: item.questions?.length || 0,
targetCount: item.targets?.length || 0,
submissionCount: 0,
bestScore: null,
passed: null,
questions: (item.questions || []).map((question) => ({
id: question.id,
format: question.questionType,
stem: question.stem,
options: question.options || [],
score: question.score,
answer: question.correctAnswers || [],
explanation: question.explanation || ''
})),
targets: item.targets || []
});
const serializeDraft = (draft: SupervisorExamDraft) => ({
title: draft.title,
projectCode: draft.projectCode,
positionCode: draft.positionCode,
passScore: draft.passScore,
dueDate: draft.dueDate,
targets: draft.targets,
questions: draft.questions.map((question, index) => ({
id: question.id > 0 && question.id < 1_000_000_000 ? question.id : null,
questionType: question.format,
stem: question.stem,
options: question.options,
correctAnswers: Array.isArray(question.answer) ? question.answer : [String(question.answer || '')],
score: question.score,
explanation: question.explanation || '',
sortOrder: index + 1
}))
});
export const listMyExams = async () =>
(await apiRequest<RawExamSummary[]>({
url: '/api/aihr/mobile/exams',
method: 'GET',
timeout: 15000
})).map(normalizeSummary);
export const getMyExam = async (id: number) =>
normalizeDetail(await apiRequest<RawExamDetail>({
url: `/api/aihr/mobile/exams/${id}`,
method: 'GET',
timeout: 15000
}));
export const submitMyExam = (id: number, answers: Record<string, string | string[]>, requestId: string) =>
apiRequest<ExamSubmitResult>({
url: `/api/aihr/mobile/exams/${id}/submit`,
method: 'POST',
data: {
requestId,
answers: Object.entries(answers).map(([questionId, value]) => ({
questionId: Number(questionId),
answers: Array.isArray(value) ? value : [String(value)]
}))
},
timeout: 30000
});
export const listSupervisorExams = async () =>
(await apiRequest<RawExamSummary[]>({
url: '/api/aihr/mobile/exams/supervisor',
method: 'GET',
timeout: 15000
})).map(normalizeSummary);
export const getSupervisorExam = async (id: number) =>
normalizeDetail(await apiRequest<RawExamDetail>({
url: `/api/aihr/mobile/exams/supervisor/${id}`,
method: 'GET',
timeout: 15000
}));
export const saveSupervisorExam = async (draft: SupervisorExamDraft) =>
normalizeDetail(await apiRequest<RawExamDetail>({
url: '/api/aihr/mobile/exams/supervisor',
method: 'POST',
data: serializeDraft(draft),
timeout: 30000
}));
export const updateSupervisorExam = async (id: number, draft: SupervisorExamDraft) =>
normalizeDetail(await apiRequest<RawExamDetail>({
url: `/api/aihr/mobile/exams/supervisor/${id}`,
method: 'PUT',
data: serializeDraft(draft),
timeout: 30000
}));
export const publishSupervisorExam = async (id: number, requestId: string) =>
normalizeDetail(await apiRequest<RawExamDetail>({
url: `/api/aihr/mobile/exams/supervisor/${id}/publish`,
method: 'POST',
data: { requestId },
timeout: 30000
}));
export const getSupervisorExamResults = (id: number) =>
apiRequest<ExamResultItem[]>({
url: `/api/aihr/mobile/exams/supervisor/${id}/results`,
method: 'GET',
timeout: 15000
});