feat: 阅卷质量
This commit is contained in:
parent
ef1acb07d1
commit
613606716c
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue