feat: 阅卷质量
This commit is contained in:
parent
ef1acb07d1
commit
613606716c
|
|
@ -1,12 +1,19 @@
|
|||
<!-- 题目选择组件 -->
|
||||
<script lang="ts" setup>
|
||||
import { DictCode, useDict } from '@/composables/useDict'
|
||||
import { computed } from 'vue'
|
||||
import { DictCode, useDict } from '@/composables/useDict'
|
||||
|
||||
defineOptions({
|
||||
name: 'QuestionTabs',
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
loading: false,
|
||||
questionTabs: () => [],
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
// 通用题目接口
|
||||
export interface QuestionTabItem {
|
||||
/** 题目ID */
|
||||
|
|
@ -14,7 +21,7 @@ export interface QuestionTabItem {
|
|||
/** 题目名称 */
|
||||
name: string
|
||||
/** 评价方法 */
|
||||
type: string
|
||||
evaluate_method: string
|
||||
/** 大题号 */
|
||||
questionMajor?: string
|
||||
/** 小题号 */
|
||||
|
|
@ -39,24 +46,14 @@ interface Props {
|
|||
loading?: boolean
|
||||
/** 错误状态 */
|
||||
error?: any
|
||||
/** 是否显示题目详情 */
|
||||
showDetails?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
loading: false,
|
||||
questionTabs: () => [],
|
||||
showDetails: true,
|
||||
})
|
||||
|
||||
// Emits
|
||||
interface Emits {
|
||||
(e: 'change', questionId: number): void
|
||||
(e: 'retry'): void
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const { getDictOptionsAndGetLabel } = useDict()
|
||||
const [, getEvaluateMethodLabel] = getDictOptionsAndGetLabel(DictCode.EVALUATE_METHOD)
|
||||
|
||||
|
|
@ -109,47 +106,8 @@ function handleTabChange(tabInfo: { index: number, name: string }) {
|
|||
v-for="question in questionTabs"
|
||||
:key="question.id"
|
||||
:name="`question-${question.id}`"
|
||||
:title="`${question.name} (${getEvaluateMethodLabel(question.type)})`"
|
||||
>
|
||||
<!-- 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>
|
||||
:title="`${question.name} (${getEvaluateMethodLabel(question.evaluate_method)})`"
|
||||
/>
|
||||
</wd-tabs>
|
||||
</view>
|
||||
|
||||
|
|
|
|||
|
|
@ -92,10 +92,7 @@
|
|||
"path": "pages/marking/grading",
|
||||
"type": "page",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": false,
|
||||
"disableScroll": true,
|
||||
"bounce": "none"
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,9 +27,6 @@ const questionId = ref<number>()
|
|||
// 展开的学校ID集合
|
||||
const expandedSchools = ref<Set<number>>(new Set())
|
||||
|
||||
const { getDictOptionsAndGetLabel } = useDict()
|
||||
const [, getEvaluateMethodLabel] = getDictOptionsAndGetLabel(DictCode.EVALUATE_METHOD)
|
||||
|
||||
// 页面加载时获取参数
|
||||
onLoad((options) => {
|
||||
if (options.examSubjectId) {
|
||||
|
|
@ -68,8 +65,8 @@ const questionTabs = computed(() => {
|
|||
const questions = allProgressResponse.value?.questions || []
|
||||
return questions.map(question => ({
|
||||
id: question.question_id!,
|
||||
name: `题目${question.question_major}${question.question_minor ? `.${question.question_minor}` : ''}`,
|
||||
type: question.evaluate_method || '单评',
|
||||
name: `${question.question_major}${question.question_minor ? `.${question.question_minor}` : ''}`,
|
||||
evaluate_method: question.evaluate_method,
|
||||
questionMajor: question.question_major,
|
||||
questionMinor: question.question_minor,
|
||||
}))
|
||||
|
|
@ -89,7 +86,7 @@ const {
|
|||
error: errorQuestion,
|
||||
refetch: refetchQuestion,
|
||||
} = useQuery({
|
||||
queryKey: ['question-marking-progress', questionId],
|
||||
queryKey: computed(() => ['question-marking-progress', questionId.value]),
|
||||
queryFn: () => markingProgressQuestionUsingGet({
|
||||
params: {
|
||||
question_id: questionId.value!,
|
||||
|
|
@ -145,6 +142,9 @@ function getTaskTypeDisplayName(taskType: string) {
|
|||
}
|
||||
}
|
||||
|
||||
const { getDictOptionsAndGetLabel } = useDict()
|
||||
const [, getTaskTypeLabel] = getDictOptionsAndGetLabel(DictCode.TASK_TYPE)
|
||||
|
||||
// 刷新数据
|
||||
function handleRefresh() {
|
||||
refetchAll()
|
||||
|
|
@ -170,7 +170,6 @@ const error = computed(() => errorAll.value || errorQuestion.value)
|
|||
:active-question-id="questionId"
|
||||
:loading="isLoadingAll"
|
||||
:error="errorAll"
|
||||
:show-details="false"
|
||||
@change="handleQuestionChange"
|
||||
@retry="handleRetry"
|
||||
/>
|
||||
|
|
@ -200,32 +199,35 @@ const error = computed(() => errorAll.value || errorQuestion.value)
|
|||
class="space-y-3"
|
||||
>
|
||||
<!-- 任务类型标题 -->
|
||||
<view class="mb-4">
|
||||
<text class="mb-2 block text-lg font-semibold">
|
||||
{{ getTaskTypeDisplayName(taskProgress.task_type || '') }}
|
||||
</text>
|
||||
|
||||
<!-- 整体进度 -->
|
||||
<view class="mb-2 flex items-center justify-between">
|
||||
<text class="text-sm text-gray-600">
|
||||
已阅{{ taskProgress.marked_quantity || 0 }}
|
||||
待阅{{ (taskProgress.total_quantity || 0) - (taskProgress.marked_quantity || 0) }}
|
||||
</text>
|
||||
<text class="text-sm text-blue-600 font-medium">
|
||||
{{ getProgressPercentage(taskProgress.marked_quantity, taskProgress.total_quantity) }}%
|
||||
<view class="mb-4 rounded-md bg-white p-4 shadow-sm">
|
||||
<view class="mb-4 flex items-center justify-between">
|
||||
<text class="block text-base font-semibold">
|
||||
{{ `${getTaskTypeLabel(taskProgress.task_type || '')}进度` }}
|
||||
</text>
|
||||
<view class="flex items-center gap-2">
|
||||
<view
|
||||
class="rounded-full bg-blue-100 px-2 py-1 text-xs"
|
||||
>
|
||||
已阅{{ 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) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 进度条 -->
|
||||
<view class="h-2 w-full rounded-full bg-gray-200">
|
||||
<view
|
||||
class="h-2 rounded-full bg-blue-500 transition-all duration-300"
|
||||
:style="{ width: `${getProgressPercentage(taskProgress.marked_quantity, taskProgress.total_quantity)}%` }"
|
||||
/>
|
||||
</view>
|
||||
<view class="mt-1 flex justify-center">
|
||||
<text class="text-xs text-gray-500">
|
||||
{{ taskProgress.marked_quantity || 0 }}/{{ taskProgress.total_quantity || 0 }}
|
||||
<view class="flex items-center justify-between gap-2">
|
||||
<view class="w-full flex items-center justify-between rounded-full bg-gray-200">
|
||||
<view
|
||||
class="h-4 rounded-full bg-blue-500 transition-all duration-300"
|
||||
:style="{ width: `${getProgressPercentage(taskProgress.marked_quantity, taskProgress.total_quantity)}%` }"
|
||||
/>
|
||||
</view>
|
||||
<text class="text-sm text-blue-600 font-medium">
|
||||
{{ getProgressPercentage(taskProgress.marked_quantity, taskProgress.total_quantity) }}%
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
|
|
|||
|
|
@ -8,18 +8,12 @@
|
|||
</route>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
GetQuestionMarkingQualityResponse,
|
||||
MarkingQualityResponse,
|
||||
QuestionMarkingQualitySchool,
|
||||
QuestionMarkingQualityTeacher,
|
||||
} from '@/service/types'
|
||||
import type { GetQuestionMarkingQualityResponse, MarkingQualityResponse, QuestionMarkingQualityTeacher } from '@/service/types'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import QuestionTabs from '@/components/marking/QuestionTabs.vue'
|
||||
import { DictCode, useDict } from '@/composables/useDict'
|
||||
import { markingQualityQuestionQualityUsingGet, markingQualityUsingGet } from '@/service/yuejuanzhiliang'
|
||||
import { markingQualityQuestionUsingGet, markingQualityUsingGet } from '@/service/yuejuanzhiliang'
|
||||
|
||||
defineOptions({
|
||||
name: 'MarkingQualityPage',
|
||||
|
|
@ -29,11 +23,8 @@ defineOptions({
|
|||
const examSubjectId = ref<number>()
|
||||
const questionId = ref<number>()
|
||||
|
||||
// 当前选中的教师类型
|
||||
const activeTeacherType = ref<'first' | 'final'>('first')
|
||||
|
||||
const { getDictOptionsAndGetLabel } = useDict()
|
||||
const [, getEvaluateMethodLabel] = getDictOptionsAndGetLabel(DictCode.EVALUATE_METHOD)
|
||||
// 当前选中的评选类型(初评/终评)
|
||||
const activeEvaluateType = ref<'initial' | 'final'>('initial')
|
||||
|
||||
// 页面加载时获取参数
|
||||
onLoad((options) => {
|
||||
|
|
@ -45,12 +36,12 @@ onLoad((options) => {
|
|||
}
|
||||
})
|
||||
|
||||
// 获取阅卷质量总体数据
|
||||
// 获取所有题目的阅卷质量数据
|
||||
const {
|
||||
data: qualityData,
|
||||
isLoading: isLoadingQuality,
|
||||
error: errorQuality,
|
||||
refetch: refetchQuality,
|
||||
data: allQualityData,
|
||||
isLoading: isLoadingAll,
|
||||
error: errorAll,
|
||||
refetch: refetchAll,
|
||||
} = useQuery({
|
||||
queryKey: ['marking-quality', examSubjectId],
|
||||
queryFn: () => markingQualityUsingGet({
|
||||
|
|
@ -63,135 +54,129 @@ const {
|
|||
gcTime: 1000 * 60 * 30,
|
||||
})
|
||||
|
||||
// 解析质量数据
|
||||
const qualityResponse = computed<MarkingQualityResponse | undefined>(() => {
|
||||
return qualityData.value
|
||||
// 解析所有题目数据
|
||||
const allQualityResponse = computed<MarkingQualityResponse>(() => {
|
||||
return allQualityData.value
|
||||
})
|
||||
|
||||
// 获取所有题目的阅卷质量详情
|
||||
// 题目列表
|
||||
const questionTabs = computed(() => {
|
||||
const questions = allQualityResponse.value?.subjective_item?.question_items || []
|
||||
return questions.map(question => ({
|
||||
id: question.question_id!,
|
||||
name: `${question.question_major}${question.question_minor ? `.${question.question_minor}` : ''}`,
|
||||
evaluate_method: question.evaluate_method,
|
||||
questionMajor: question.question_major,
|
||||
questionMinor: question.question_minor,
|
||||
averageScore: question.average_score,
|
||||
totalScore: question.total_score,
|
||||
}))
|
||||
})
|
||||
|
||||
// 监听题目列表变化,设置默认选中第一个题目
|
||||
watch(questionTabs, (newTabs) => {
|
||||
if (newTabs.length > 0 && !questionId.value) {
|
||||
questionId.value = newTabs[0].id
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 获取具体题目的详细阅卷质量数据
|
||||
const {
|
||||
data: allQuestionsData,
|
||||
isLoading: isLoadingQuestions,
|
||||
error: errorQuestions,
|
||||
refetch: refetchQuestions,
|
||||
data: questionQualityData,
|
||||
isLoading: isLoadingQuestion,
|
||||
error: errorQuestion,
|
||||
refetch: refetchQuestion,
|
||||
} = useQuery({
|
||||
queryKey: ['marking-quality-questions', examSubjectId],
|
||||
queryFn: async () => {
|
||||
if (!qualityResponse.value?.subjective_item?.question_items)
|
||||
return []
|
||||
|
||||
// 获取所有主观题的详细质量数据
|
||||
const promises = qualityResponse.value.subjective_item.question_items.map(item =>
|
||||
markingQualityQuestionQualityUsingGet({
|
||||
params: {
|
||||
question_id: item.question_id!,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
return Promise.all(promises)
|
||||
},
|
||||
enabled: computed(() => !!qualityResponse.value?.subjective_item?.question_items?.length),
|
||||
queryKey: computed(() => ['question-marking-quality', questionId.value]),
|
||||
queryFn: () => markingQualityQuestionUsingGet({
|
||||
params: {
|
||||
question_id: questionId.value!,
|
||||
},
|
||||
}),
|
||||
enabled: computed(() => !!questionId.value),
|
||||
staleTime: 1000 * 60 * 5,
|
||||
gcTime: 1000 * 60 * 30,
|
||||
})
|
||||
|
||||
// 题目列表数据
|
||||
const questionsData = computed<GetQuestionMarkingQualityResponse[]>(() => {
|
||||
return allQuestionsData.value || []
|
||||
// 解析题目详细质量数据
|
||||
const questionQualityResponse = computed<GetQuestionMarkingQualityResponse | undefined>(() => {
|
||||
return questionQualityData.value
|
||||
})
|
||||
|
||||
// 转换为QuestionTabs组件需要的格式
|
||||
const questionTabsData = computed(() => {
|
||||
return questionsData.value.map(question => ({
|
||||
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 currentQuestion = computed(() => {
|
||||
return questionTabs.value.find(q => q.id === questionId.value)
|
||||
})
|
||||
|
||||
// 当前选中的题目数据
|
||||
const currentQuestionData = computed<GetQuestionMarkingQualityResponse | undefined>(() => {
|
||||
if (!questionsData.value.length)
|
||||
return undefined
|
||||
|
||||
if (questionId.value) {
|
||||
return questionsData.value.find(q => q.question_id === questionId.value)
|
||||
}
|
||||
|
||||
// 默认选择第一个题目
|
||||
return questionsData.value[0]
|
||||
// 初评老师列表
|
||||
const initialTeachers = computed<QuestionMarkingQualityTeacher[]>(() => {
|
||||
return questionQualityResponse.value?.initial_teachers || []
|
||||
})
|
||||
|
||||
// 监听题目数据变化,设置默认选中第一个题目
|
||||
watch(questionsData, (newData) => {
|
||||
if (newData.length > 0 && !questionId.value) {
|
||||
questionId.value = newData[0].question_id
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 获取学校数据,根据当前题目筛选
|
||||
const schoolGroups = computed<QuestionMarkingQualitySchool[]>(() => {
|
||||
return currentQuestionData.value?.school_groups || []
|
||||
// 终评老师列表
|
||||
const finalTeachers = computed<QuestionMarkingQualityTeacher[]>(() => {
|
||||
return questionQualityResponse.value?.final_teachers || []
|
||||
})
|
||||
|
||||
// 根据评价方法判断是否显示终评老师
|
||||
const shouldShowFinalTeacher = computed(() => {
|
||||
const evaluateMethod = currentQuestionData.value?.evaluate_method
|
||||
return evaluateMethod === '双评' || evaluateMethod === '多评'
|
||||
// 当前显示的老师列表
|
||||
const currentTeachers = computed<QuestionMarkingQualityTeacher[]>(() => {
|
||||
return activeEvaluateType.value === 'initial' ? initialTeachers.value : finalTeachers.value
|
||||
})
|
||||
|
||||
// 获取当前类型的老师数据
|
||||
function getCurrentTypeTeachers(school: QuestionMarkingQualitySchool): QuestionMarkingQualityTeacher[] {
|
||||
if (!school.teachers)
|
||||
return []
|
||||
|
||||
// 这里需要根据实际API返回的数据结构来判断老师类型
|
||||
// 目前API中没有明确的老师类型字段,可能需要后端补充
|
||||
// 暂时返回所有老师
|
||||
return school.teachers
|
||||
}
|
||||
// 是否有双评(既有初评又有终评)
|
||||
const hasDoubleEvaluation = computed(() => {
|
||||
return initialTeachers.value.length > 0 && finalTeachers.value.length > 0
|
||||
})
|
||||
|
||||
// 切换题目
|
||||
function handleQuestionChange(newQuestionId: number) {
|
||||
questionId.value = newQuestionId
|
||||
// useQuery 会自动根据 queryKey 的变化重新获取数据
|
||||
}
|
||||
|
||||
// 切换老师类型
|
||||
function switchTeacherType(type: 'first' | 'final') {
|
||||
activeTeacherType.value = type
|
||||
// 切换评选类型
|
||||
function switchEvaluateType(type: 'initial' | 'final') {
|
||||
activeEvaluateType.value = type
|
||||
}
|
||||
|
||||
// 刷新数据
|
||||
function handleRefresh() {
|
||||
refetchQuality()
|
||||
refetchQuestions()
|
||||
refetchAll()
|
||||
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 {
|
||||
if (!seconds)
|
||||
return '-'
|
||||
// 获取老师等级标识颜色
|
||||
function getTeacherLevelColor(score: number = 0, maxScore: number = 100) {
|
||||
const percentage = (score / maxScore) * 100
|
||||
if (percentage >= 90)
|
||||
return 'text-green-600'
|
||||
if (percentage >= 80)
|
||||
return 'text-blue-600'
|
||||
if (percentage >= 70)
|
||||
return 'text-orange-600'
|
||||
return 'text-red-600'
|
||||
}
|
||||
|
||||
// 格式化分数显示
|
||||
function formatScore(score: number = 0) {
|
||||
return score.toFixed(1)
|
||||
}
|
||||
|
||||
// 格式化时间显示(秒转分钟)
|
||||
function formatTime(seconds: number = 0) {
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const remainingSeconds = Math.floor(seconds % 60)
|
||||
|
||||
if (minutes > 0) {
|
||||
return `${minutes}分${remainingSeconds}秒`
|
||||
}
|
||||
return `${remainingSeconds}秒`
|
||||
const remainingSeconds = seconds % 60
|
||||
return `${minutes}分${remainingSeconds}秒`
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -199,15 +184,15 @@ function formatAverageTime(seconds?: number): string {
|
|||
<view class="marking-quality-page">
|
||||
<!-- 题目选择组件 -->
|
||||
<QuestionTabs
|
||||
:question-tabs="questionTabsData"
|
||||
:question-tabs="questionTabs"
|
||||
:active-question-id="questionId"
|
||||
:loading="isLoadingQuestions"
|
||||
:error="errorQuestions"
|
||||
:loading="isLoadingAll"
|
||||
:error="errorAll"
|
||||
@change="handleQuestionChange"
|
||||
@retry="refetchQuestions"
|
||||
@retry="handleRetry"
|
||||
/>
|
||||
|
||||
<!-- 主体内容区域 -->
|
||||
<!-- 质量内容区域 -->
|
||||
<view class="p-4">
|
||||
<!-- 加载状态 -->
|
||||
<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-if="shouldShowFinalTeacher" class="rounded-2xl bg-white p-4">
|
||||
<view class="flex">
|
||||
<wd-button
|
||||
:type="activeTeacherType === 'first' ? 'primary' : 'default'"
|
||||
size="small"
|
||||
class="mr-2"
|
||||
@click="switchTeacherType('first')"
|
||||
<!-- 题目详情 -->
|
||||
<view class="mb-4 rounded-md bg-white p-4 shadow-sm">
|
||||
<text class="mb-2 block text-base font-semibold">题目详情</text>
|
||||
|
||||
<view class="flex items-center justify-between gap-2">
|
||||
<view
|
||||
class="rounded-full bg-blue-100 px-3 py-1 text-xs"
|
||||
>
|
||||
初评老师
|
||||
</wd-button>
|
||||
<wd-button
|
||||
:type="activeTeacherType === 'final' ? 'primary' : 'default'"
|
||||
size="small"
|
||||
@click="switchTeacherType('final')"
|
||||
总分{{ questionQualityResponse?.total_score || 0 }}
|
||||
</view>
|
||||
<view
|
||||
class="rounded-full bg-orange-100 px-3 py-1 text-xs"
|
||||
>
|
||||
终评老师
|
||||
</wd-button>
|
||||
平均分{{ questionQualityResponse?.average_score || 0 }}
|
||||
</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 class="rounded-2xl bg-white p-4">
|
||||
<!-- 表头 -->
|
||||
<view class="mb-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="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">
|
||||
{{ shouldShowFinalTeacher && activeTeacherType === 'final' ? '终评老师' : '初评老师' }}
|
||||
{{ hasDoubleEvaluation ? (activeEvaluateType === 'initial' ? '初评老师' : '终评老师') : '阅卷老师' }}
|
||||
</text>
|
||||
</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
|
||||
v-for="school in schoolGroups"
|
||||
:key="school.school_id"
|
||||
class="space-y-2"
|
||||
>
|
||||
<!-- 学校名称 -->
|
||||
<view class="py-2">
|
||||
<text class="text-base text-gray-800 font-medium">
|
||||
{{ school.school_name }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 老师列表 -->
|
||||
<view class="space-y-2">
|
||||
<view
|
||||
v-for="teacher in getCurrentTypeTeachers(school)"
|
||||
:key="teacher.teacher_id"
|
||||
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 class="flex items-center">
|
||||
<text class="text-sm">{{ teacher.marking_count || 0 }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 问题卷 -->
|
||||
<view class="flex items-center">
|
||||
<text class="text-sm">{{ teacher.problem_num || 0 }}</text>
|
||||
</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
|
||||
v-for="(teacher, index) in currentTeachers"
|
||||
:key="`${teacher.teacher_id}-${index}`"
|
||||
class="border border-gray-100 rounded-2xl bg-white p-4 shadow-sm"
|
||||
>
|
||||
<!-- 老师基本信息 -->
|
||||
<view class="mb-3 flex items-center justify-between">
|
||||
<view class="flex items-center">
|
||||
<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 class="text-sm text-gray-500">平均分</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 没有老师数据 -->
|
||||
<view v-if="getCurrentTypeTeachers(school).length === 0" class="py-4 text-center">
|
||||
<text class="text-sm text-gray-500">暂无老师数据</text>
|
||||
<!-- 老师详细统计 -->
|
||||
<view class="grid grid-cols-3 gap-4 border-t border-gray-100 pt-3">
|
||||
<view class="text-center">
|
||||
<text class="block text-base text-gray-700 font-semibold">{{ teacher.marking_count || 0 }}</text>
|
||||
<text class="text-xs text-gray-500">阅卷数</text>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<text class="block text-base text-gray-700 font-semibold">{{ formatTime(teacher.average_time || 0) }}</text>
|
||||
<text class="text-xs text-gray-500">平均用时</text>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<text class="block text-base text-red-600 font-semibold">{{ teacher.problem_num || 0 }}</text>
|
||||
<text class="text-xs text-gray-500">问题卷</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 没有学校数据 -->
|
||||
<view v-if="schoolGroups.length === 0" class="py-8 text-center">
|
||||
<text class="text-sm text-gray-500">暂无阅卷质量数据</text>
|
||||
</view>
|
||||
<!-- 暂无数据 -->
|
||||
<view v-else class="py-12 text-center">
|
||||
<text class="text-sm text-gray-500">
|
||||
{{ hasDoubleEvaluation ? `暂无${activeEvaluateType === 'initial' ? '初评' : '终评'}老师数据` : '暂无老师数据' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
|
|
|||
|
|
@ -115,20 +115,20 @@ export async function markingQualityExportTeacherDetailUsingGet({
|
|||
});
|
||||
}
|
||||
|
||||
/** 获取题目阅卷质量 根据question_id获取指定题目的阅卷质量统计,包含大题、小题、评价方法、总分数、平均分数等详细信息 GET /marking-quality/question-quality */
|
||||
export async function markingQualityQuestionQualityUsingGet({
|
||||
/** 获取题目阅卷质量 根据question_id获取指定题目的阅卷质量统计,包含大题、小题、评价方法、总分数、平均分数等详细信息 GET /marking-quality/question */
|
||||
export async function markingQualityQuestionUsingGet({
|
||||
params,
|
||||
options,
|
||||
}: {
|
||||
// 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
|
||||
params: API.markingQualityQuestionQualityUsingGetParams;
|
||||
params: API.markingQualityQuestionUsingGetParams;
|
||||
options?: CustomRequestOptions;
|
||||
}) {
|
||||
return request<
|
||||
API.Response & {
|
||||
data?: API.GetQuestionMarkingQualityResponse;
|
||||
}
|
||||
>('/marking-quality/question-quality', {
|
||||
>('/marking-quality/question', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
...params,
|
||||
|
|
|
|||
Loading…
Reference in New Issue