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

336 lines
10 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, ExamQuestionWithTasksResponse, ExamSetProblemRecordRequest, ExamStudentMarkingQuestionResponse } from '@/api'
import type { MarkingNavigation } from '@/composables/marking/useMarkingNavigation'
import { useQuery } from '@tanstack/vue-query'
import { computed, inject, provide, readonly, ref, watch } from 'vue'
import { examMarkingTaskApi } from '@/api'
import { getMarkingContext } from '@/composables/marking/MarkingContext'
import { useMarkingHistory } from '@/composables/marking/useMarkingHistory'
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>
markingNavigation: MarkingNavigation
}
function createMarkingData(options: UseMarkingDataOptions) {
const { taskId, questionId, examId, subjectId, isLandscape, taskType, markingNavigation } = options
// 获取阅卷上下文和历史管理
const markingContext = getMarkingContext()
const markingHistory = useMarkingHistory()
// 基础数据
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 firstNotScoredIndex = computed(() => {
return 0
})
const currentScore = computed({
get: () => {
const score = currentMarkingSubmitData.value[firstNotScoredIndex.value]?.score
return score !== undefined ? 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 () => {
// 如果在历史查看模式,返回历史记录
if (markingHistory.isViewingHistory.value && markingHistory.currentHistoryRecord.value) {
const record = markingHistory.currentHistoryRecord.value
// 将历史记录转换为题目数据格式
return {
id: record.id,
task_id: record.task_id,
question_id: record.question_id,
scan_info_id: record.scan_info_id,
score: record.score,
full_score: record.full_score,
question_major: record.question_major,
question_minor: record.question_minor,
image_urls: record.image_urls,
} as ExamStudentMarkingQuestionResponse
}
// 否则使用上下文提供者获取当前题目
return markingContext.dataProvider.getSingleQuestion(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 currentData = data || currentMarkingSubmitData.value[index]
const isHistoryMode = markingHistory.isViewingHistory.value
// 根据模式选择数据源
const question = isHistoryMode
? markingHistory.currentHistoryRecord.value!
: questionData.value[index]
if (!question)
throw new Error('题目数据不存在')
// 构建提交数据
const submitData: ExamCreateMarkingTaskRecordRequest = {
id: question.id,
scan_info_id: question.scan_info_id!,
question_id: questionId.value,
record_id: 0,
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: currentData.isProblem ? 1 : 0,
}
// 如果是问题卷,不提交正常记录,只提交问题卷记录
const response = !currentData.isProblem
? await markingContext.dataProvider.submitRecords({
is_review: isHistoryMode,
batch_data: [submitData],
})
: null
// 处理问题卷
if (currentData.isProblem) {
const problemRequest: ExamSetProblemRecordRequest = {
id: question.id!,
task_id: taskId.value,
problem_type: currentData.problemType! as any,
problem_addition: currentData.problemRemark,
}
if (markingContext.dataProvider.createProblemRecord) {
await markingContext.dataProvider.createProblemRecord(problemRequest as any)
}
}
if (response) {
if (response.success_count === 0) {
const errorMessage = response.fail_data?.map(item => item.message).join('、') || '提交失败'
throw new Error(errorMessage)
}
// 更新历史记录总数
if (!isHistoryMode && response.success_count > 0) {
markingHistory.incrementHistoryCount(response.success_count)
}
}
isSubmitted.value = true
// 如果是历史模式,刷新历史记录
if (!isHistoryMode) {
// 更新已阅数量
if (currentTaskInfo.value) {
currentTaskInfo.value.marked_quantity = (currentTaskInfo.value.marked_quantity || 0) + 1
}
// 重新获取下一题
questionData.value = []
currentMarkingSubmitData.value = []
markingStartTime.value = 0
await refetchQuestion()
processQuestionData()
}
else {
(question as any).final_score = currentData.score
markingNavigation.goToNextQuestion()
}
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
}