xlx_teacher_app/src/components/marking/composables/useMarkingData.ts

283 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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'
import { useUserId } from '@/composables/useUserId'
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<number>
questionId: Ref<number>
examId?: Ref<number>
subjectId?: Ref<number>
isLandscape?: Ref<boolean>
taskType?: Ref<string>
}
function createMarkingData(options: UseMarkingDataOptions) {
const { taskId, questionId, examId, subjectId, isLandscape, taskType } = options
// 基础数据
const questionData = ref<ExamStudentMarkingQuestionResponse[]>([])
const currentMarkingSubmitData = ref<MarkingSubmitData[]>([])
const isLoading = ref(false)
const isSubmitted = ref(false)
const markingStartTime = ref<number>(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: ['marking-question', taskId, useUserId()],
queryFn: async () => examMarkingTaskApi.questionDetail(taskId.value),
enabled: computed(() => !!taskId.value),
gcTime: 0,
})
// 获取题目列表数据用于Layout显示
const {
data: questionsListData,
refetch: refetchQuestionsList,
} = useQuery({
queryKey: ['marking-questions-list', examId, subjectId, useUserId()],
queryFn: async () => examMarkingTaskApi.byQuestionList({
exam_id: examId!.value!,
exam_subject_id: subjectId!.value!,
}) as unknown as Promise<ExamQuestionWithTasksResponse[]>,
enabled: computed(() => !!examId?.value && !!subjectId?.value),
placeholderData: [],
})
// 获取平均分对比
const {
data: avgScoreData,
refetch: refetchAvgScore,
} = useQuery({
queryKey: ['marking-avg-score', questionId, taskId, useUserId()],
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<typeof createMarkingData>
const markingDataKey = Symbol('markingData')
export function useMarkingData() {
const markingData = inject<MarkingData>(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
}