import type { Ref } from 'vue' import type { ExamBatchCreateMarkingTaskRecordRequest, ExamCreateMarkingTaskRecordRequest, ExamQuestionAverageScoreComparisonResponse, ExamQuestionWithTasksResponse, ExamSetProblemRecordRequest, ExamStudentMarkingQuestionResponse } from '@/api' import { useQuery, useQueryClient } from '@tanstack/vue-query' import { useDebounceFn, useThrottleFn } from '@vueuse/core' import { computed, inject, provide, readonly, ref, watch } from 'vue' import { examMarkingTaskApi } from '@/api' export interface MarkingSubmitData { score: number remark: any status: 'draft' | 'submitted' markingTime?: number isExcellent?: boolean isTypical?: boolean isProblem?: boolean problemType?: string problemRemark?: string } export interface UseMarkingDataOptions { taskId: Ref questionId: Ref examId?: Ref subjectId?: Ref isLandscape?: Ref taskType?: Ref } function createMarkingData(options: UseMarkingDataOptions) { const { taskId, questionId, examId, subjectId, isLandscape, taskType } = options // 基础数据 const questionData = ref([]) const currentMarkingSubmitData = ref([]) const isLoading = ref(false) const isSubmitted = ref(false) const markingStartTime = ref(0) const mode = ref<'single' | 'multi'>('single') const queryClient = useQueryClient() // 当前分数 const firstNotScoredIndex = computed(() => { return 0 }) const currentScore = computed({ get: () => { return currentMarkingSubmitData.value[firstNotScoredIndex.value]?.score || -1 }, set: (value) => { if (currentMarkingSubmitData.value[firstNotScoredIndex.value]) { currentMarkingSubmitData.value[firstNotScoredIndex.value].score = value } }, }) // 获取题目数据 const { data: questionResponse, isLoading: isQuestionLoading, error: questionError, refetch: refetchQuestion, } = useQuery({ queryKey: computed(() => ['marking-question', taskId.value]), queryFn: async () => examMarkingTaskApi.questionDetail(taskId.value), enabled: computed(() => !!taskId.value), gcTime: 0, }) // 获取题目列表数据(用于Layout显示) const { data: questionsListData, refetch: refetchQuestionsList, } = useQuery({ queryKey: computed(() => ['marking-questions-list', examId?.value, subjectId?.value]), queryFn: async () => examMarkingTaskApi.byQuestionList({ exam_id: examId!.value!, exam_subject_id: subjectId!.value!, }) as unknown as Promise, enabled: computed(() => !!examId?.value && !!subjectId?.value), placeholderData: [], }) // 获取平均分对比 const { data: avgScoreData, refetch: refetchAvgScore, } = useQuery({ queryKey: computed(() => ['marking-avg-score', questionId.value, taskId.value]), queryFn: async () => examMarkingTaskApi.questionTaskAverageScoreComparisonDetail( questionId.value, taskId.value, ), enabled: computed(() => !!questionId.value && !!taskId.value), }) // 处理题目数据 const processQuestionData = () => { if (!questionResponse.value) return let questions: ExamStudentMarkingQuestionResponse[] = [] // 复制数据但不添加 score 字段到原类型中 questions = [questionResponse.value] questionData.value = questions // 初始化提交数据 currentMarkingSubmitData.value = questions.map(() => ({ score: -1, remark: '', status: 'draft' as const, isExcellent: false, isTypical: false, isProblem: false, })) // 设置开始时间 if (questions.length > 0) { markingStartTime.value = Date.now() } } // 当前任务信息(用于获取总数) const currentTaskInfo = computed(() => { if (!questionId.value || !taskId.value || !questionsListData.value.length) return null // 找到当前题目 const currentQuestion = questionsListData.value.find(q => q.question_id === questionId.value) if (!currentQuestion) return null return currentQuestion.tasks?.[taskType.value] }) // 总题目数量(从任务中获取) const totalQuestions = computed(() => { return currentTaskInfo.value?.task_quantity || 0 }) // 监听数据变化 watch(() => questionResponse.value, processQuestionData, { immediate: true }) // 计算阅卷用时 const getMarkingTime = (): number => { return markingStartTime.value > 0 ? Math.floor((Date.now() - markingStartTime.value) / 1000) : 0 } // 提交单个记录 const submitSingleRecord = async (data?: MarkingSubmitData, index: number = 0) => { try { const question = questionData.value[index] if (!question) throw new Error('题目数据不存在') const currentData = data || currentMarkingSubmitData.value[index] // 如果是问题卷,只提交问题卷记录,不提交正常阅卷记录 if (currentData.isProblem) { const problemRequest: ExamSetProblemRecordRequest = { id: question.id!, task_id: taskId.value, problem_type: currentData.problemType! as any, problem_addition: currentData.problemRemark, } const response = await examMarkingTaskApi.problemRecordCreate(problemRequest) if (response) { isSubmitted.value = true // 重新获取下一题 questionData.value = [] await refetchQuestion() processQuestionData() return response } return } // 正常阅卷记录提交 const submitData: ExamCreateMarkingTaskRecordRequest = { id: question.id, scan_info_id: question.scan_info_id!, question_id: questionId.value, task_id: taskId.value, duration: getMarkingTime(), score: currentData.score, remark: JSON.stringify(currentData.remark), page_mode: 'single', is_excellent: currentData.isExcellent ? 1 : 0, is_model: currentData.isTypical ? 1 : 0, is_problem: 0, // 问题卷已经在上面处理了,这里必须是0 } const batchRequest: ExamBatchCreateMarkingTaskRecordRequest = { batch_data: [submitData], } const response = await examMarkingTaskApi.batchCreate(batchRequest) if (response) { isSubmitted.value = true currentTaskInfo.value!.marked_quantity = (currentTaskInfo.value!.marked_quantity || 0) + 1 // 重新获取下一题 questionData.value = [] await refetchQuestion() processQuestionData() return response } } catch (error) { console.error('提交阅卷记录失败:', error) throw error } } // 提交记录(根据模式选择单个或批量) const submitRecord = async (data?: MarkingSubmitData, index?: number) => { return await submitSingleRecord(data, index) } // 重新加载数据 const reload = async () => { await Promise.all([ refetchQuestion(), refetchAvgScore(), refetchQuestionsList(), ]) } return { // 数据状态 questionData, questionsList: computed(() => questionsListData.value || []), // 题目列表数据 currentMarkingSubmitData, currentScore, firstNotScoredIndex, totalQuestions, // 总题目数量 currentTaskInfo, isLoading: computed(() => isQuestionLoading.value || isLoading.value), isSubmitted: readonly(isSubmitted), mode, isLandscape: isLandscape || ref(false), // 平均分数据 avgScoreData: computed(() => avgScoreData.value), // 错误状态 questionError, // 方法 submitRecord, submitSingleRecord, getMarkingTime, reload, refetchQuestion, refetchAvgScore, refetchQuestionsList, } } type MarkingData = ReturnType const markingDataKey = Symbol('markingData') export function useMarkingData() { const markingData = inject(markingDataKey) if (!markingData) { throw new Error('Marking data not found') } return markingData } export function provideMarkingData(options: UseMarkingDataOptions) { const markingData = createMarkingData(options) provide(markingDataKey, markingData) return markingData }