feat: 阅卷质量

This commit is contained in:
小柚 2025-08-19 20:46:10 +08:00
parent ef1acb07d1
commit 613606716c
5 changed files with 233 additions and 294 deletions

View File

@ -1,12 +1,19 @@
<!-- 题目选择组件 --> <!-- 题目选择组件 -->
<script lang="ts" setup> <script lang="ts" setup>
import { DictCode, useDict } from '@/composables/useDict'
import { computed } from 'vue' import { computed } from 'vue'
import { DictCode, useDict } from '@/composables/useDict'
defineOptions({ defineOptions({
name: 'QuestionTabs', name: 'QuestionTabs',
}) })
const props = withDefaults(defineProps<Props>(), {
loading: false,
questionTabs: () => [],
})
const emit = defineEmits<Emits>()
// //
export interface QuestionTabItem { export interface QuestionTabItem {
/** 题目ID */ /** 题目ID */
@ -14,7 +21,7 @@ export interface QuestionTabItem {
/** 题目名称 */ /** 题目名称 */
name: string name: string
/** 评价方法 */ /** 评价方法 */
type: string evaluate_method: string
/** 大题号 */ /** 大题号 */
questionMajor?: string questionMajor?: string
/** 小题号 */ /** 小题号 */
@ -39,24 +46,14 @@ interface Props {
loading?: boolean loading?: boolean
/** 错误状态 */ /** 错误状态 */
error?: any error?: any
/** 是否显示题目详情 */
showDetails?: boolean
} }
const props = withDefaults(defineProps<Props>(), {
loading: false,
questionTabs: () => [],
showDetails: true,
})
// Emits // Emits
interface Emits { interface Emits {
(e: 'change', questionId: number): void (e: 'change', questionId: number): void
(e: 'retry'): void (e: 'retry'): void
} }
const emit = defineEmits<Emits>()
const { getDictOptionsAndGetLabel } = useDict() const { getDictOptionsAndGetLabel } = useDict()
const [, getEvaluateMethodLabel] = getDictOptionsAndGetLabel(DictCode.EVALUATE_METHOD) const [, getEvaluateMethodLabel] = getDictOptionsAndGetLabel(DictCode.EVALUATE_METHOD)
@ -109,47 +106,8 @@ function handleTabChange(tabInfo: { index: number, name: string }) {
v-for="question in questionTabs" v-for="question in questionTabs"
:key="question.id" :key="question.id"
:name="`question-${question.id}`" :name="`question-${question.id}`"
:title="`${question.name} (${getEvaluateMethodLabel(question.type)})`" :title="`${question.name} (${getEvaluateMethodLabel(question.evaluate_method)})`"
> />
<!-- Tab 内容显示当前选中题目的详情 -->
<view v-if="showDetails && currentQuestion" class="p-4">
<!-- 题目详情 -->
<view class="mb-4">
<text class="mb-2 block text-lg font-semibold">题目详情</text>
<!-- 分数统计 -->
<view class="grid grid-cols-4 gap-3">
<view class="rounded-xl bg-blue-50 p-3 text-center">
<text class="block text-lg font-semibold text-blue-600">
{{ currentQuestion.totalScore || 0 }}
</text>
<text class="block text-xs text-gray-500">总分</text>
</view>
<view class="rounded-xl bg-green-50 p-3 text-center">
<text class="block text-lg font-semibold text-green-600">
{{ currentQuestion.averageScore?.toFixed(1) || 0 }}
</text>
<text class="block text-xs text-gray-500">平均分</text>
</view>
<view class="rounded-xl bg-orange-50 p-3 text-center">
<text class="block text-lg font-semibold text-orange-600">
{{ currentQuestion.maxScore || 0 }}
</text>
<text class="block text-xs text-gray-500">最高分</text>
</view>
<view class="rounded-xl bg-red-50 p-3 text-center">
<text class="block text-lg font-semibold text-red-600">
{{ currentQuestion.minScore || 0 }}
</text>
<text class="block text-xs text-gray-500">最低分</text>
</view>
</view>
</view>
</view>
</wd-tab>
</wd-tabs> </wd-tabs>
</view> </view>

View File

@ -92,10 +92,7 @@
"path": "pages/marking/grading", "path": "pages/marking/grading",
"type": "page", "type": "page",
"style": { "style": {
"navigationStyle": "custom", "navigationStyle": "custom"
"enablePullDownRefresh": false,
"disableScroll": true,
"bounce": "none"
} }
}, },
{ {

View File

@ -27,9 +27,6 @@ const questionId = ref<number>()
// ID // ID
const expandedSchools = ref<Set<number>>(new Set()) const expandedSchools = ref<Set<number>>(new Set())
const { getDictOptionsAndGetLabel } = useDict()
const [, getEvaluateMethodLabel] = getDictOptionsAndGetLabel(DictCode.EVALUATE_METHOD)
// //
onLoad((options) => { onLoad((options) => {
if (options.examSubjectId) { if (options.examSubjectId) {
@ -68,8 +65,8 @@ const questionTabs = computed(() => {
const questions = allProgressResponse.value?.questions || [] const questions = allProgressResponse.value?.questions || []
return questions.map(question => ({ return questions.map(question => ({
id: question.question_id!, id: question.question_id!,
name: `题目${question.question_major}${question.question_minor ? `.${question.question_minor}` : ''}`, name: `${question.question_major}${question.question_minor ? `.${question.question_minor}` : ''}`,
type: question.evaluate_method || '单评', evaluate_method: question.evaluate_method,
questionMajor: question.question_major, questionMajor: question.question_major,
questionMinor: question.question_minor, questionMinor: question.question_minor,
})) }))
@ -89,7 +86,7 @@ const {
error: errorQuestion, error: errorQuestion,
refetch: refetchQuestion, refetch: refetchQuestion,
} = useQuery({ } = useQuery({
queryKey: ['question-marking-progress', questionId], queryKey: computed(() => ['question-marking-progress', questionId.value]),
queryFn: () => markingProgressQuestionUsingGet({ queryFn: () => markingProgressQuestionUsingGet({
params: { params: {
question_id: questionId.value!, question_id: questionId.value!,
@ -145,6 +142,9 @@ function getTaskTypeDisplayName(taskType: string) {
} }
} }
const { getDictOptionsAndGetLabel } = useDict()
const [, getTaskTypeLabel] = getDictOptionsAndGetLabel(DictCode.TASK_TYPE)
// //
function handleRefresh() { function handleRefresh() {
refetchAll() refetchAll()
@ -170,7 +170,6 @@ const error = computed(() => errorAll.value || errorQuestion.value)
:active-question-id="questionId" :active-question-id="questionId"
:loading="isLoadingAll" :loading="isLoadingAll"
:error="errorAll" :error="errorAll"
:show-details="false"
@change="handleQuestionChange" @change="handleQuestionChange"
@retry="handleRetry" @retry="handleRetry"
/> />
@ -200,32 +199,35 @@ const error = computed(() => errorAll.value || errorQuestion.value)
class="space-y-3" class="space-y-3"
> >
<!-- 任务类型标题 --> <!-- 任务类型标题 -->
<view class="mb-4"> <view class="mb-4 rounded-md bg-white p-4 shadow-sm">
<text class="mb-2 block text-lg font-semibold"> <view class="mb-4 flex items-center justify-between">
{{ getTaskTypeDisplayName(taskProgress.task_type || '') }} <text class="block text-base font-semibold">
{{ `${getTaskTypeLabel(taskProgress.task_type || '')}进度` }}
</text> </text>
<view class="flex items-center gap-2">
<!-- 整体进度 --> <view
<view class="mb-2 flex items-center justify-between"> class="rounded-full bg-blue-100 px-2 py-1 text-xs"
<text class="text-sm text-gray-600"> >
已阅{{ taskProgress.marked_quantity || 0 }} 已阅{{ taskProgress.marked_quantity || 0 }}
</view>
<view
class="rounded-full bg-orange-100 px-2 py-1 text-xs"
>
待阅{{ (taskProgress.total_quantity || 0) - (taskProgress.marked_quantity || 0) }} 待阅{{ (taskProgress.total_quantity || 0) - (taskProgress.marked_quantity || 0) }}
</text> </view>
<text class="text-sm text-blue-600 font-medium"> </view>
{{ getProgressPercentage(taskProgress.marked_quantity, taskProgress.total_quantity) }}%
</text>
</view> </view>
<!-- 进度条 --> <!-- 进度条 -->
<view class="h-2 w-full rounded-full bg-gray-200"> <view class="flex items-center justify-between gap-2">
<view class="w-full flex items-center justify-between rounded-full bg-gray-200">
<view <view
class="h-2 rounded-full bg-blue-500 transition-all duration-300" class="h-4 rounded-full bg-blue-500 transition-all duration-300"
:style="{ width: `${getProgressPercentage(taskProgress.marked_quantity, taskProgress.total_quantity)}%` }" :style="{ width: `${getProgressPercentage(taskProgress.marked_quantity, taskProgress.total_quantity)}%` }"
/> />
</view> </view>
<view class="mt-1 flex justify-center"> <text class="text-sm text-blue-600 font-medium">
<text class="text-xs text-gray-500"> {{ getProgressPercentage(taskProgress.marked_quantity, taskProgress.total_quantity) }}%
{{ taskProgress.marked_quantity || 0 }}/{{ taskProgress.total_quantity || 0 }}
</text> </text>
</view> </view>
</view> </view>

View File

@ -8,18 +8,12 @@
</route> </route>
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { GetQuestionMarkingQualityResponse, MarkingQualityResponse, QuestionMarkingQualityTeacher } from '@/service/types'
GetQuestionMarkingQualityResponse,
MarkingQualityResponse,
QuestionMarkingQualitySchool,
QuestionMarkingQualityTeacher,
} from '@/service/types'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { useQuery } from '@tanstack/vue-query' import { useQuery } from '@tanstack/vue-query'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import QuestionTabs from '@/components/marking/QuestionTabs.vue' import QuestionTabs from '@/components/marking/QuestionTabs.vue'
import { DictCode, useDict } from '@/composables/useDict' import { markingQualityQuestionUsingGet, markingQualityUsingGet } from '@/service/yuejuanzhiliang'
import { markingQualityQuestionQualityUsingGet, markingQualityUsingGet } from '@/service/yuejuanzhiliang'
defineOptions({ defineOptions({
name: 'MarkingQualityPage', name: 'MarkingQualityPage',
@ -29,11 +23,8 @@ defineOptions({
const examSubjectId = ref<number>() const examSubjectId = ref<number>()
const questionId = ref<number>() const questionId = ref<number>()
// // /
const activeTeacherType = ref<'first' | 'final'>('first') const activeEvaluateType = ref<'initial' | 'final'>('initial')
const { getDictOptionsAndGetLabel } = useDict()
const [, getEvaluateMethodLabel] = getDictOptionsAndGetLabel(DictCode.EVALUATE_METHOD)
// //
onLoad((options) => { onLoad((options) => {
@ -45,12 +36,12 @@ onLoad((options) => {
} }
}) })
// //
const { const {
data: qualityData, data: allQualityData,
isLoading: isLoadingQuality, isLoading: isLoadingAll,
error: errorQuality, error: errorAll,
refetch: refetchQuality, refetch: refetchAll,
} = useQuery({ } = useQuery({
queryKey: ['marking-quality', examSubjectId], queryKey: ['marking-quality', examSubjectId],
queryFn: () => markingQualityUsingGet({ queryFn: () => markingQualityUsingGet({
@ -63,135 +54,129 @@ const {
gcTime: 1000 * 60 * 30, gcTime: 1000 * 60 * 30,
}) })
// //
const qualityResponse = computed<MarkingQualityResponse | undefined>(() => { const allQualityResponse = computed<MarkingQualityResponse>(() => {
return qualityData.value return allQualityData.value
}) })
// //
const { const questionTabs = computed(() => {
data: allQuestionsData, const questions = allQualityResponse.value?.subjective_item?.question_items || []
isLoading: isLoadingQuestions, return questions.map(question => ({
error: errorQuestions, id: question.question_id!,
refetch: refetchQuestions, name: `${question.question_major}${question.question_minor ? `.${question.question_minor}` : ''}`,
} = useQuery({ evaluate_method: question.evaluate_method,
queryKey: ['marking-quality-questions', examSubjectId], questionMajor: question.question_major,
queryFn: async () => { questionMinor: question.question_minor,
if (!qualityResponse.value?.subjective_item?.question_items) averageScore: question.average_score,
return [] totalScore: question.total_score,
}))
})
// //
const promises = qualityResponse.value.subjective_item.question_items.map(item => watch(questionTabs, (newTabs) => {
markingQualityQuestionQualityUsingGet({ if (newTabs.length > 0 && !questionId.value) {
questionId.value = newTabs[0].id
}
}, { immediate: true })
//
const {
data: questionQualityData,
isLoading: isLoadingQuestion,
error: errorQuestion,
refetch: refetchQuestion,
} = useQuery({
queryKey: computed(() => ['question-marking-quality', questionId.value]),
queryFn: () => markingQualityQuestionUsingGet({
params: { params: {
question_id: item.question_id!, question_id: questionId.value!,
}, },
}), }),
) enabled: computed(() => !!questionId.value),
return Promise.all(promises)
},
enabled: computed(() => !!qualityResponse.value?.subjective_item?.question_items?.length),
staleTime: 1000 * 60 * 5, staleTime: 1000 * 60 * 5,
gcTime: 1000 * 60 * 30, gcTime: 1000 * 60 * 30,
}) })
// //
const questionsData = computed<GetQuestionMarkingQualityResponse[]>(() => { const questionQualityResponse = computed<GetQuestionMarkingQualityResponse | undefined>(() => {
return allQuestionsData.value || [] return questionQualityData.value
}) })
// QuestionTabs //
const questionTabsData = computed(() => { const currentQuestion = computed(() => {
return questionsData.value.map(question => ({ return questionTabs.value.find(q => q.id === questionId.value)
id: question.question_id!,
name: `题目${question.question_major}${question.question_minor ? `.${question.question_minor}` : ''}`,
type: question.evaluate_method || '单评',
questionMajor: question.question_major,
questionMinor: question.question_minor,
totalScore: question.total_score,
averageScore: question.average_score,
maxScore: question.max_score,
minScore: question.min_score,
}))
}) })
// //
const currentQuestionData = computed<GetQuestionMarkingQualityResponse | undefined>(() => { const initialTeachers = computed<QuestionMarkingQualityTeacher[]>(() => {
if (!questionsData.value.length) return questionQualityResponse.value?.initial_teachers || []
return undefined
if (questionId.value) {
return questionsData.value.find(q => q.question_id === questionId.value)
}
//
return questionsData.value[0]
}) })
// //
watch(questionsData, (newData) => { const finalTeachers = computed<QuestionMarkingQualityTeacher[]>(() => {
if (newData.length > 0 && !questionId.value) { return questionQualityResponse.value?.final_teachers || []
questionId.value = newData[0].question_id
}
}, { immediate: true })
//
const schoolGroups = computed<QuestionMarkingQualitySchool[]>(() => {
return currentQuestionData.value?.school_groups || []
}) })
// //
const shouldShowFinalTeacher = computed(() => { const currentTeachers = computed<QuestionMarkingQualityTeacher[]>(() => {
const evaluateMethod = currentQuestionData.value?.evaluate_method return activeEvaluateType.value === 'initial' ? initialTeachers.value : finalTeachers.value
return evaluateMethod === '双评' || evaluateMethod === '多评'
}) })
// //
function getCurrentTypeTeachers(school: QuestionMarkingQualitySchool): QuestionMarkingQualityTeacher[] { const hasDoubleEvaluation = computed(() => {
if (!school.teachers) return initialTeachers.value.length > 0 && finalTeachers.value.length > 0
return [] })
// API
// API
//
return school.teachers
}
// //
function handleQuestionChange(newQuestionId: number) { function handleQuestionChange(newQuestionId: number) {
questionId.value = newQuestionId questionId.value = newQuestionId
// useQuery queryKey
} }
// //
function switchTeacherType(type: 'first' | 'final') { function switchEvaluateType(type: 'initial' | 'final') {
activeTeacherType.value = type activeEvaluateType.value = type
} }
// //
function handleRefresh() { function handleRefresh() {
refetchQuality() refetchAll()
refetchQuestions() refetchQuestion()
}
function handleRetry() {
refetchAll()
} }
// //
const isLoading = computed(() => isLoadingQuality.value || isLoadingQuestions.value) const isLoading = computed(() => isLoadingAll.value || isLoadingQuestion.value)
// //
const error = computed(() => errorQuality.value || errorQuestions.value) const error = computed(() => errorAll.value || errorQuestion.value)
// //
function formatAverageTime(seconds?: number): string { function getTeacherLevelColor(score: number = 0, maxScore: number = 100) {
if (!seconds) const percentage = (score / maxScore) * 100
return '-' if (percentage >= 90)
return 'text-green-600'
const minutes = Math.floor(seconds / 60) if (percentage >= 80)
const remainingSeconds = Math.floor(seconds % 60) return 'text-blue-600'
if (percentage >= 70)
if (minutes > 0) { return 'text-orange-600'
return `${minutes}${remainingSeconds}` return 'text-red-600'
} }
return `${remainingSeconds}`
//
function formatScore(score: number = 0) {
return score.toFixed(1)
}
//
function formatTime(seconds: number = 0) {
const minutes = Math.floor(seconds / 60)
const remainingSeconds = seconds % 60
return `${minutes}${remainingSeconds}`
} }
</script> </script>
@ -199,15 +184,15 @@ function formatAverageTime(seconds?: number): string {
<view class="marking-quality-page"> <view class="marking-quality-page">
<!-- 题目选择组件 --> <!-- 题目选择组件 -->
<QuestionTabs <QuestionTabs
:question-tabs="questionTabsData" :question-tabs="questionTabs"
:active-question-id="questionId" :active-question-id="questionId"
:loading="isLoadingQuestions" :loading="isLoadingAll"
:error="errorQuestions" :error="errorAll"
@change="handleQuestionChange" @change="handleQuestionChange"
@retry="refetchQuestions" @retry="handleRetry"
/> />
<!-- 主体内容区域 --> <!-- 质量内容区域 -->
<view class="p-4"> <view class="p-4">
<!-- 加载状态 --> <!-- 加载状态 -->
<view v-if="isLoading" class="py-12 text-center"> <view v-if="isLoading" class="py-12 text-center">
@ -225,110 +210,107 @@ function formatAverageTime(seconds?: number): string {
<!-- 质量数据 --> <!-- 质量数据 -->
<view v-else class="space-y-4"> <view v-else class="space-y-4">
<!-- 初评老师/终评老师切换 --> <!-- 题目详情 -->
<view v-if="shouldShowFinalTeacher" class="rounded-2xl bg-white p-4"> <view class="mb-4 rounded-md bg-white p-4 shadow-sm">
<view class="flex"> <text class="mb-2 block text-base font-semibold">题目详情</text>
<wd-button
:type="activeTeacherType === 'first' ? 'primary' : 'default'" <view class="flex items-center justify-between gap-2">
size="small" <view
class="mr-2" class="rounded-full bg-blue-100 px-3 py-1 text-xs"
@click="switchTeacherType('first')"
> >
初评老师 总分{{ questionQualityResponse?.total_score || 0 }}
</wd-button> </view>
<wd-button <view
:type="activeTeacherType === 'final' ? 'primary' : 'default'" class="rounded-full bg-orange-100 px-3 py-1 text-xs"
size="small"
@click="switchTeacherType('final')"
> >
终评老师 平均分{{ questionQualityResponse?.average_score || 0 }}
</wd-button> </view>
<view
class="rounded-full bg-red-100 px-3 py-1 text-xs"
>
最高分{{ questionQualityResponse?.max_score || 0 }}
</view>
<view
class="rounded-full bg-green-100 px-3 py-1 text-xs"
>
最低分{{ questionQualityResponse?.min_score || 0 }}
</view>
</view> </view>
</view> </view>
<!-- 阅卷质量表格 --> <!-- 评选类型切换仅在有双评时显示 -->
<view class="rounded-2xl bg-white p-4"> <view v-if="hasDoubleEvaluation" class="border border-gray-100 rounded-2xl bg-white p-4 shadow-sm">
<!-- 表头 --> <view class="flex space-x-4">
<view class="mb-4"> <view
class="flex-1 cursor-pointer rounded-lg py-2 text-center transition-colors"
:class="activeEvaluateType === 'initial' ? 'bg-blue-100 text-blue-600' : 'bg-gray-100 text-gray-600'"
@click="switchEvaluateType('initial')"
>
<text class="font-medium">初评老师</text>
</view>
<view
class="flex-1 cursor-pointer rounded-lg py-2 text-center transition-colors"
:class="activeEvaluateType === 'final' ? 'bg-blue-100 text-blue-600' : 'bg-gray-100 text-gray-600'"
@click="switchEvaluateType('final')"
>
<text class="font-medium">终评老师</text>
</view>
</view>
</view>
<!-- 老师质量列表 -->
<view v-if="currentTeachers.length > 0" class="space-y-3">
<view class="mb-3">
<text class="text-lg font-semibold"> <text class="text-lg font-semibold">
{{ shouldShowFinalTeacher && activeTeacherType === 'final' ? '终评老师' : '初评老师' }} {{ hasDoubleEvaluation ? (activeEvaluateType === 'initial' ? '初评老师' : '终评老师') : '阅卷老师' }}
</text> </text>
</view> </view>
<!-- 表头行 -->
<view class="grid grid-cols-6 mb-3 gap-2 border-b border-gray-100 pb-3">
<text class="text-sm text-gray-600 font-medium">姓名</text>
<text class="text-sm text-gray-600 font-medium">阅卷量</text>
<text class="text-sm text-gray-600 font-medium">问题卷</text>
<text class="text-sm text-gray-600 font-medium">平均分</text>
<text class="text-sm text-gray-600 font-medium">平均用时</text>
<text class="text-sm text-gray-600 font-medium">标准差</text>
</view>
<!-- 学校列表 -->
<view class="space-y-4">
<view <view
v-for="school in schoolGroups" v-for="(teacher, index) in currentTeachers"
:key="school.school_id" :key="`${teacher.teacher_id}-${index}`"
class="space-y-2" class="border border-gray-100 rounded-2xl bg-white p-4 shadow-sm"
> >
<!-- 学校名称 --> <!-- 老师基本信息 -->
<view class="py-2"> <view class="mb-3 flex items-center justify-between">
<text class="text-base text-gray-800 font-medium"> <view class="flex items-center">
{{ school.school_name }} <view class="mr-3 h-3 w-3 rounded-full" :class="getTeacherLevelColor(teacher.average_score || 0, currentQuestion?.totalScore || 100)" />
<view>
<text class="text-base font-medium">{{ teacher.teacher_name || '未知老师' }}</text>
<text v-if="teacher.phone" class="block text-sm text-gray-500">{{ teacher.phone }}</text>
</view>
</view>
<view class="text-right">
<text class="block text-lg font-bold" :class="getTeacherLevelColor(teacher.average_score || 0, currentQuestion?.totalScore || 100)">
{{ formatScore(teacher.average_score || 0) }}
</text> </text>
<text class="text-sm text-gray-500">平均分</text>
</view>
</view> </view>
<!-- 老师列表 --> <!-- 老师详细统计 -->
<view class="space-y-2"> <view class="grid grid-cols-3 gap-4 border-t border-gray-100 pt-3">
<view <view class="text-center">
v-for="teacher in getCurrentTypeTeachers(school)" <text class="block text-base text-gray-700 font-semibold">{{ teacher.marking_count || 0 }}</text>
:key="teacher.teacher_id" <text class="text-xs text-gray-500">阅卷数</text>
class="grid grid-cols-6 gap-2 rounded-lg bg-gray-50 p-3"
>
<!-- 姓名 -->
<view class="flex items-center">
<text class="text-sm">{{ teacher.teacher_name }}</text>
</view> </view>
<view class="text-center">
<!-- 阅卷量 --> <text class="block text-base text-gray-700 font-semibold">{{ formatTime(teacher.average_time || 0) }}</text>
<view class="flex items-center"> <text class="text-xs text-gray-500">平均用时</text>
<text class="text-sm">{{ teacher.marking_count || 0 }}</text>
</view> </view>
<view class="text-center">
<!-- 问题卷 --> <text class="block text-base text-red-600 font-semibold">{{ teacher.problem_num || 0 }}</text>
<view class="flex items-center"> <text class="text-xs text-gray-500">问题卷</text>
<text class="text-sm">{{ teacher.problem_num || 0 }}</text>
</view> </view>
<!-- 平均分 -->
<view class="flex items-center">
<text class="text-sm">{{ teacher.average_score?.toFixed(1) || '-' }}</text>
</view>
<!-- 平均用时 -->
<view class="flex items-center">
<text class="text-sm">{{ formatAverageTime(teacher.average_time) }}</text>
</view>
<!-- 标准差 -->
<view class="flex items-center">
<text class="text-sm">{{ teacher.standard_dev?.toFixed(2) || '-' }}</text>
</view> </view>
</view> </view>
</view> </view>
<!-- 没有老师数据 --> <!-- 暂无数据 -->
<view v-if="getCurrentTypeTeachers(school).length === 0" class="py-4 text-center"> <view v-else class="py-12 text-center">
<text class="text-sm text-gray-500">暂无老师数据</text> <text class="text-sm text-gray-500">
</view> {{ hasDoubleEvaluation ? `暂无${activeEvaluateType === 'initial' ? '初评' : '终评'}老师数据` : '暂无老师数据' }}
</view> </text>
</view>
<!-- 没有学校数据 -->
<view v-if="schoolGroups.length === 0" class="py-8 text-center">
<text class="text-sm text-gray-500">暂无阅卷质量数据</text>
</view>
</view> </view>
</view> </view>
</view> </view>

View File

@ -115,20 +115,20 @@ export async function markingQualityExportTeacherDetailUsingGet({
}); });
} }
/** 获取题目阅卷质量 根据question_id获取指定题目的阅卷质量统计包含大题、小题、评价方法、总分数、平均分数等详细信息 GET /marking-quality/question-quality */ /** 获取题目阅卷质量 根据question_id获取指定题目的阅卷质量统计包含大题、小题、评价方法、总分数、平均分数等详细信息 GET /marking-quality/question */
export async function markingQualityQuestionQualityUsingGet({ export async function markingQualityQuestionUsingGet({
params, params,
options, options,
}: { }: {
// 叠加生成的Param类型 (非body参数openapi默认没有生成对象) // 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
params: API.markingQualityQuestionQualityUsingGetParams; params: API.markingQualityQuestionUsingGetParams;
options?: CustomRequestOptions; options?: CustomRequestOptions;
}) { }) {
return request< return request<
API.Response & { API.Response & {
data?: API.GetQuestionMarkingQualityResponse; data?: API.GetQuestionMarkingQualityResponse;
} }
>('/marking-quality/question-quality', { >('/marking-quality/question', {
method: 'GET', method: 'GET',
params: { params: {
...params, ...params,