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

336 lines
10 KiB
TypeScript
Raw Normal View History

2025-08-16 16:42:40 +08:00
import type { Ref } from 'vue'
2025-11-04 22:29:47 +08:00
import type { ExamBatchCreateMarkingTaskRecordRequest, ExamCreateMarkingTaskRecordRequest, ExamQuestionWithTasksResponse, ExamSetProblemRecordRequest, ExamStudentMarkingQuestionResponse } from '@/api'
import { useQuery } from '@tanstack/vue-query'
2025-08-16 16:42:40 +08:00
import { computed, inject, provide, readonly, ref, watch } from 'vue'
2025-09-19 21:39:54 +08:00
import { examMarkingTaskApi } from '@/api'
2025-11-04 22:29:47 +08:00
import { getMarkingContext } from '@/composables/marking/MarkingContext'
import { useMarkingHistory } from '@/composables/marking/useMarkingHistory'
2025-10-12 23:07:15 +08:00
import { useUserId } from '@/composables/useUserId'
2025-08-16 16:42:40 +08:00
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>
2025-09-25 22:53:50 +08:00
examId?: Ref<number>
subjectId?: Ref<number>
2025-08-16 16:42:40 +08:00
isLandscape?: Ref<boolean>
2025-09-25 22:53:50 +08:00
taskType?: Ref<string>
2025-08-16 16:42:40 +08:00
}
function createMarkingData(options: UseMarkingDataOptions) {
2025-09-25 22:53:50 +08:00
const { taskId, questionId, examId, subjectId, isLandscape, taskType } = options
2025-08-16 16:42:40 +08:00
2025-11-04 22:29:47 +08:00
// 获取阅卷上下文和历史管理
const markingContext = getMarkingContext()
const markingHistory = useMarkingHistory()
2025-08-16 16:42:40 +08:00
// 基础数据
2025-09-19 21:39:54 +08:00
const questionData = ref<ExamStudentMarkingQuestionResponse[]>([])
2025-08-16 16:42:40 +08:00
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: () => {
return currentMarkingSubmitData.value[firstNotScoredIndex.value]?.score || -1
},
set: (value) => {
if (currentMarkingSubmitData.value[firstNotScoredIndex.value]) {
currentMarkingSubmitData.value[firstNotScoredIndex.value].score = value
}
},
})
2025-11-04 22:29:47 +08:00
// 获取题目数据(使用上下文提供者)
2025-08-16 16:42:40 +08:00
const {
data: questionResponse,
isLoading: isQuestionLoading,
error: questionError,
refetch: refetchQuestion,
} = useQuery({
2025-10-12 23:07:15 +08:00
queryKey: ['marking-question', taskId, useUserId()],
2025-11-04 22:29:47 +08:00
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)
},
2025-08-16 16:42:40 +08:00
enabled: computed(() => !!taskId.value),
2025-09-25 22:53:50 +08:00
gcTime: 0,
})
// 获取题目列表数据用于Layout显示
const {
data: questionsListData,
refetch: refetchQuestionsList,
} = useQuery({
2025-10-12 23:07:15 +08:00
queryKey: ['marking-questions-list', examId, subjectId, useUserId()],
2025-09-25 22:53:50 +08:00
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: [],
2025-08-16 16:42:40 +08:00
})
2025-09-25 22:53:50 +08:00
2025-08-16 16:42:40 +08:00
// 获取平均分对比
const {
data: avgScoreData,
refetch: refetchAvgScore,
} = useQuery({
2025-10-12 23:07:15 +08:00
queryKey: ['marking-avg-score', questionId, taskId, useUserId()],
2025-09-19 21:39:54 +08:00
queryFn: async () => examMarkingTaskApi.questionTaskAverageScoreComparisonDetail(
questionId.value,
taskId.value,
),
2025-08-16 16:42:40 +08:00
enabled: computed(() => !!questionId.value && !!taskId.value),
})
// 处理题目数据
const processQuestionData = () => {
if (!questionResponse.value)
return
2025-09-19 21:39:54 +08:00
let questions: ExamStudentMarkingQuestionResponse[] = []
2025-08-16 16:42:40 +08:00
2025-09-18 23:20:42 +08:00
// 复制数据但不添加 score 字段到原类型中
2025-08-16 16:42:40 +08:00
questions = [questionResponse.value]
questionData.value = questions
// 初始化提交数据
currentMarkingSubmitData.value = questions.map(() => ({
score: -1,
remark: '',
status: 'draft' as const,
2025-09-18 23:20:42 +08:00
isExcellent: false,
isTypical: false,
isProblem: false,
2025-08-16 16:42:40 +08:00
}))
// 设置开始时间
if (questions.length > 0) {
markingStartTime.value = Date.now()
}
}
2025-09-25 22:53:50 +08:00
// 当前任务信息(用于获取总数)
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
})
2025-08-16 16:42:40 +08:00
// 监听数据变化
watch(() => questionResponse.value, processQuestionData, { immediate: true })
// 计算阅卷用时
const getMarkingTime = (): number => {
return markingStartTime.value > 0
? Math.floor((Date.now() - markingStartTime.value) / 1000)
: 0
}
// 提交单个记录
2025-08-16 18:30:38 +08:00
const submitSingleRecord = async (data?: MarkingSubmitData, index: number = 0) => {
2025-08-16 16:42:40 +08:00
try {
const question = questionData.value[index]
if (!question)
throw new Error('题目数据不存在')
2025-08-16 18:30:38 +08:00
const currentData = data || currentMarkingSubmitData.value[index]
2025-11-04 22:29:47 +08:00
const isHistoryMode = markingHistory.isViewingHistory.value
2025-09-18 23:20:42 +08:00
// 如果是问题卷,只提交问题卷记录,不提交正常阅卷记录
if (currentData.isProblem) {
2025-09-19 21:39:54 +08:00
const problemRequest: ExamSetProblemRecordRequest = {
id: question.id!,
task_id: taskId.value,
problem_type: currentData.problemType! as any,
problem_addition: currentData.problemRemark,
}
2025-11-04 22:29:47 +08:00
if (markingContext.dataProvider.createProblemRecord) {
await markingContext.dataProvider.createProblemRecord(problemRequest as any)
}
isSubmitted.value = true
// 如果是历史模式,刷新历史记录
if (isHistoryMode) {
await markingHistory.forceRefreshHistory()
}
else {
2025-11-14 21:55:13 +08:00
currentTaskInfo.value.marked_quantity += 1
2025-09-18 23:20:42 +08:00
// 重新获取下一题
2025-09-25 22:53:50 +08:00
questionData.value = []
2025-09-18 23:20:42 +08:00
await refetchQuestion()
processQuestionData()
}
2025-11-04 22:29:47 +08:00
2025-09-18 23:20:42 +08:00
return
}
// 正常阅卷记录提交
2025-09-19 21:39:54 +08:00
const submitData: ExamCreateMarkingTaskRecordRequest = {
id: question.id,
2025-08-16 16:42:40 +08:00
scan_info_id: question.scan_info_id!,
question_id: questionId.value,
2025-11-04 22:29:47 +08:00
record_id: 0,
2025-08-16 16:42:40 +08:00
task_id: taskId.value,
duration: getMarkingTime(),
2025-08-16 18:30:38 +08:00
score: currentData.score,
remark: JSON.stringify(currentData.remark),
2025-08-16 16:42:40 +08:00
page_mode: 'single',
2025-08-16 18:30:38 +08:00
is_excellent: currentData.isExcellent ? 1 : 0,
is_model: currentData.isTypical ? 1 : 0,
2025-09-18 23:20:42 +08:00
is_problem: 0, // 问题卷已经在上面处理了这里必须是0
2025-08-16 16:42:40 +08:00
}
2025-09-19 21:39:54 +08:00
const batchRequest: ExamBatchCreateMarkingTaskRecordRequest = {
batch_data: [submitData],
}
2025-11-04 22:29:47 +08:00
// 使用上下文提供者提交
const response = await markingContext.dataProvider.submitRecords(batchRequest)
2025-08-16 16:42:40 +08:00
if (response) {
isSubmitted.value = true
2025-11-04 22:29:47 +08:00
// 更新历史记录总数
if (!isHistoryMode && response.success_count > 0) {
markingHistory.incrementHistoryCount(response.success_count)
}
// 更新已阅数量
if (currentTaskInfo.value) {
currentTaskInfo.value.marked_quantity = (currentTaskInfo.value.marked_quantity || 0) + 1
}
// 如果是历史模式,刷新历史记录
if (isHistoryMode) {
await markingHistory.forceRefreshHistory()
}
else {
// 重新获取下一题
questionData.value = []
await refetchQuestion()
processQuestionData()
}
2025-08-16 16:42:40 +08:00
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(),
2025-09-25 22:53:50 +08:00
refetchQuestionsList(),
2025-08-16 16:42:40 +08:00
])
}
return {
// 数据状态
questionData,
2025-09-25 22:53:50 +08:00
questionsList: computed(() => questionsListData.value || []), // 题目列表数据
2025-08-16 16:42:40 +08:00
currentMarkingSubmitData,
currentScore,
firstNotScoredIndex,
2025-09-25 22:53:50 +08:00
totalQuestions, // 总题目数量
currentTaskInfo,
2025-08-16 16:42:40 +08:00
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,
2025-09-25 22:53:50 +08:00
refetchQuestionsList,
2025-08-16 16:42:40 +08:00
}
}
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
}