336 lines
10 KiB
TypeScript
336 lines
10 KiB
TypeScript
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
|
||
}
|