2025-08-16 16:42:40 +08:00
|
|
|
|
import type { Ref } from 'vue'
|
|
|
|
|
|
import type {
|
|
|
|
|
|
CreateMarkingTaskRecordRequest,
|
|
|
|
|
|
StudentMarkingQuestionResponse,
|
|
|
|
|
|
} from '@/service/types'
|
|
|
|
|
|
import { useQuery } from '@tanstack/vue-query'
|
|
|
|
|
|
import { computed, inject, provide, readonly, ref, watch } from 'vue'
|
|
|
|
|
|
import {
|
|
|
|
|
|
markingTaskRecordsBatchUsingPost,
|
|
|
|
|
|
markingTaskRecordsUsingPost,
|
2025-09-18 23:20:42 +08:00
|
|
|
|
markingTasksProblemRecordUsingPost,
|
|
|
|
|
|
markingTasksQuestionQuestionIdTaskTaskIdAverageScoreComparisonUsingGet,
|
|
|
|
|
|
markingTasksTaskIdQuestionUsingGet,
|
|
|
|
|
|
} from '@/service'
|
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>
|
|
|
|
|
|
isLandscape?: Ref<boolean>
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function createMarkingData(options: UseMarkingDataOptions) {
|
|
|
|
|
|
const { taskId, questionId, isLandscape } = options
|
|
|
|
|
|
|
|
|
|
|
|
// 基础数据
|
|
|
|
|
|
const questionData = ref<StudentMarkingQuestionResponse[]>([])
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 获取题目数据
|
|
|
|
|
|
const {
|
|
|
|
|
|
data: questionResponse,
|
|
|
|
|
|
isLoading: isQuestionLoading,
|
|
|
|
|
|
error: questionError,
|
|
|
|
|
|
refetch: refetchQuestion,
|
|
|
|
|
|
} = useQuery({
|
|
|
|
|
|
queryKey: computed(() => ['marking-question', taskId.value]),
|
|
|
|
|
|
queryFn: async () => {
|
2025-09-18 23:20:42 +08:00
|
|
|
|
return await markingTasksTaskIdQuestionUsingGet({
|
|
|
|
|
|
params: { task_id: taskId.value },
|
|
|
|
|
|
})
|
2025-08-16 16:42:40 +08:00
|
|
|
|
},
|
|
|
|
|
|
enabled: computed(() => !!taskId.value),
|
|
|
|
|
|
})
|
|
|
|
|
|
// 获取平均分对比
|
|
|
|
|
|
const {
|
|
|
|
|
|
data: avgScoreData,
|
|
|
|
|
|
refetch: refetchAvgScore,
|
|
|
|
|
|
} = useQuery({
|
|
|
|
|
|
queryKey: computed(() => ['marking-avg-score', questionId.value, taskId.value]),
|
|
|
|
|
|
queryFn: () => markingTasksQuestionQuestionIdTaskTaskIdAverageScoreComparisonUsingGet({
|
|
|
|
|
|
params: {
|
|
|
|
|
|
question_id: questionId.value,
|
|
|
|
|
|
task_id: taskId.value,
|
|
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
enabled: computed(() => !!questionId.value && !!taskId.value),
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 处理题目数据
|
|
|
|
|
|
const processQuestionData = () => {
|
|
|
|
|
|
if (!questionResponse.value)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
let questions: StudentMarkingQuestionResponse[] = []
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 监听数据变化
|
|
|
|
|
|
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-09-18 23:20:42 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果是问题卷,只提交问题卷记录,不提交正常阅卷记录
|
|
|
|
|
|
if (currentData.isProblem) {
|
|
|
|
|
|
const response = await markingTasksProblemRecordUsingPost({
|
|
|
|
|
|
body: {
|
|
|
|
|
|
scan_info_id: question.scan_info_id!,
|
|
|
|
|
|
task_id: taskId.value,
|
|
|
|
|
|
problem_type: currentData.problemType!,
|
|
|
|
|
|
description: currentData.problemRemark,
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
if (response) {
|
|
|
|
|
|
isSubmitted.value = true
|
|
|
|
|
|
// 重新获取下一题
|
|
|
|
|
|
await refetchQuestion()
|
|
|
|
|
|
processQuestionData()
|
|
|
|
|
|
return response
|
|
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 正常阅卷记录提交
|
2025-08-16 16:42:40 +08:00
|
|
|
|
const submitData: CreateMarkingTaskRecordRequest = {
|
|
|
|
|
|
scan_info_id: question.scan_info_id!,
|
|
|
|
|
|
question_id: questionId.value,
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const response = await markingTaskRecordsUsingPost({
|
|
|
|
|
|
body: submitData,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (response) {
|
|
|
|
|
|
isSubmitted.value = true
|
|
|
|
|
|
// 重新获取下一题
|
|
|
|
|
|
await refetchQuestion()
|
|
|
|
|
|
processQuestionData()
|
|
|
|
|
|
return response
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (error) {
|
|
|
|
|
|
console.error('提交阅卷记录失败:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 批量提交记录
|
|
|
|
|
|
const submitBatchRecords = async () => {
|
|
|
|
|
|
try {
|
2025-09-18 23:20:42 +08:00
|
|
|
|
// 构建所有提交数据
|
|
|
|
|
|
const allSubmitData: CreateMarkingTaskRecordRequest[] = currentMarkingSubmitData.value.map(
|
2025-08-16 16:42:40 +08:00
|
|
|
|
(item, index) => {
|
|
|
|
|
|
const question = questionData.value[index]
|
|
|
|
|
|
return {
|
|
|
|
|
|
scan_info_id: question.scan_info_id!,
|
|
|
|
|
|
question_id: questionId.value,
|
|
|
|
|
|
task_id: taskId.value,
|
|
|
|
|
|
duration: getMarkingTime(),
|
|
|
|
|
|
score: item.score,
|
|
|
|
|
|
remark: JSON.stringify(item.remark),
|
|
|
|
|
|
page_mode: 'single',
|
|
|
|
|
|
is_excellent: item.isExcellent ? 1 : 0,
|
|
|
|
|
|
is_model: item.isTypical ? 1 : 0,
|
|
|
|
|
|
is_problem: item.isProblem ? 1 : 0,
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-09-18 23:20:42 +08:00
|
|
|
|
// 过滤出非问题卷的数据进行正常提交
|
|
|
|
|
|
const normalSubmitData = allSubmitData.filter(item => !item.is_problem)
|
2025-08-16 16:42:40 +08:00
|
|
|
|
|
2025-09-18 23:20:42 +08:00
|
|
|
|
// 提交正常阅卷记录
|
|
|
|
|
|
const response = normalSubmitData.length > 0
|
|
|
|
|
|
? await markingTaskRecordsBatchUsingPost({
|
|
|
|
|
|
body: {
|
|
|
|
|
|
batch_data: normalSubmitData,
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
: null
|
|
|
|
|
|
|
|
|
|
|
|
// 处理问题卷
|
|
|
|
|
|
const problemItems = currentMarkingSubmitData.value
|
|
|
|
|
|
.map((item, index) => ({ item, index }))
|
|
|
|
|
|
.filter(({ item }) => item.isProblem)
|
|
|
|
|
|
|
|
|
|
|
|
if (problemItems.length > 0) {
|
|
|
|
|
|
console.log('检测到问题卷,开始处理问题卷记录...')
|
|
|
|
|
|
|
|
|
|
|
|
const problemPromises = problemItems.map(({ item, index }) => {
|
|
|
|
|
|
const question = questionData.value[index]
|
|
|
|
|
|
return markingTasksProblemRecordUsingPost({
|
|
|
|
|
|
body: {
|
|
|
|
|
|
scan_info_id: question.scan_info_id!,
|
|
|
|
|
|
task_id: taskId.value,
|
|
|
|
|
|
problem_type: item.problemType!,
|
|
|
|
|
|
description: item.problemRemark,
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
await Promise.all(problemPromises)
|
|
|
|
|
|
console.log('问题卷记录处理完成')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response || problemItems.length > 0) {
|
2025-08-16 16:42:40 +08:00
|
|
|
|
isSubmitted.value = true
|
|
|
|
|
|
// 重新获取下一批题目
|
|
|
|
|
|
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(),
|
|
|
|
|
|
])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
// 数据状态
|
|
|
|
|
|
questionData,
|
|
|
|
|
|
currentMarkingSubmitData,
|
|
|
|
|
|
currentScore,
|
|
|
|
|
|
firstNotScoredIndex,
|
|
|
|
|
|
isLoading: computed(() => isQuestionLoading.value || isLoading.value),
|
|
|
|
|
|
isSubmitted: readonly(isSubmitted),
|
|
|
|
|
|
mode,
|
|
|
|
|
|
isLandscape: isLandscape || ref(false),
|
|
|
|
|
|
|
|
|
|
|
|
// 平均分数据
|
|
|
|
|
|
avgScoreData: computed(() => avgScoreData.value),
|
|
|
|
|
|
|
|
|
|
|
|
// 错误状态
|
|
|
|
|
|
questionError,
|
|
|
|
|
|
|
|
|
|
|
|
// 方法
|
|
|
|
|
|
submitRecord,
|
|
|
|
|
|
submitSingleRecord,
|
|
|
|
|
|
submitBatchRecords,
|
|
|
|
|
|
getMarkingTime,
|
|
|
|
|
|
reload,
|
|
|
|
|
|
refetchQuestion,
|
|
|
|
|
|
refetchAvgScore,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|