xlx_teacher_app/src/pages/marking/grading.vue

370 lines
9.9 KiB
Vue
Raw Normal View History

2025-08-16 16:42:40 +08:00
<!-- 阅卷页面 -->
<route lang="jsonc">
{
"style": {
"navigationStyle": "custom"
}
}
</route>
<script lang="ts" setup>
2025-09-25 22:53:50 +08:00
import { useQueryClient } from '@tanstack/vue-query'
import { whenever } from '@vueuse/core'
2025-08-16 16:42:40 +08:00
import { computed, onMounted, onUnmounted, ref } from 'vue'
2025-08-16 18:30:38 +08:00
import AnswerDialog from '@/components/marking/components/dialog/AnswerDialog.vue'
import AvgScoreDialog from '@/components/marking/components/dialog/AvgScoreDialog.vue'
2025-11-04 22:29:47 +08:00
import FullscreenImageDialog from '@/components/marking/components/dialog/FullscreenImageDialog.vue'
2025-08-16 16:42:40 +08:00
import ScoreSettingsDialog from '@/components/marking/components/dialog/ScoreSettingsDialog.vue'
import QuickScorePanel from '@/components/marking/components/QuickScorePanel.vue'
import MarkingImageViewerNew from '@/components/marking/components/renderer/MarkingImageViewerNew.vue'
import { provideMarkingData } from '@/components/marking/composables/useMarkingData'
import MarkingLayout from '@/components/marking/MarkingLayout.vue'
2025-11-04 22:29:47 +08:00
import { DefaultMarkingDataProvider, DefaultMarkingHistoryProvider, provideMarkingContext } from '@/composables/marking/MarkingContext'
import { provideMarkingHistory } from '@/composables/marking/useMarkingHistory'
import { provideMarkingNavigation } from '@/composables/marking/useMarkingNavigation'
2025-08-16 16:42:40 +08:00
defineOptions({
name: 'GradingPage',
})
// 获取路由参数
const examId = ref<number>()
const subjectId = ref<number>()
const questionId = ref<number>()
const taskId = ref<number>()
const evaluationType = ref<'single' | 'double'>('single')
2025-09-25 22:53:50 +08:00
const taskType = ref<'initial' | 'final' | 'arbitration'>('initial')
2025-08-16 16:42:40 +08:00
// 屏幕方向状态
const isLandscape = ref(false)
2025-11-04 22:29:47 +08:00
// 图片缩放比例
const imageScale = ref(1.0)
// 提供阅卷上下文(使用默认实现)
provideMarkingContext({
dataProvider: new DefaultMarkingDataProvider(),
historyProvider: new DefaultMarkingHistoryProvider(),
isHistory: false,
defaultPosition: 'last',
})
// 提供历史记录管理
provideMarkingHistory({ taskId })
// 提供导航管理
const markingNavigation = provideMarkingNavigation({ taskId })
const {
currentIndex: navCurrentIndex,
totalCount: navTotalCount,
isViewingHistory,
canGoPrev,
canGoNext,
goToPrevQuestion,
goToNextQuestion,
initNavigation,
createSwipeHandler,
} = markingNavigation
// 提供数据管理
2025-08-16 16:42:40 +08:00
const markingData = provideMarkingData({
taskId,
questionId,
2025-09-25 22:53:50 +08:00
examId,
subjectId,
2025-08-16 16:42:40 +08:00
isLandscape,
2025-09-25 22:53:50 +08:00
taskType,
2025-08-16 16:42:40 +08:00
})
2025-09-25 22:53:50 +08:00
const { questionData: questions, questionsList, totalQuestions: totalQuestionsCount } = markingData
2025-08-16 16:42:40 +08:00
2025-11-04 22:29:47 +08:00
// 全屏弹窗状态
const showFullscreenImage = ref(false)
2025-08-16 16:42:40 +08:00
const currentQuestionIndex = ref(0)
2025-09-25 22:53:50 +08:00
const totalQuestions = computed(() => totalQuestionsCount.value)
2025-08-16 16:42:40 +08:00
// 分数数据
const myScore = computed(() => {
const data = markingData.avgScoreData.value
return data?.teacher_score.average_score || 0
})
const avgScore = computed(() => {
const data = markingData.avgScoreData.value
return data?.all_teachers_score.average_score || 0
})
// 页面加载时获取参数
onLoad((options) => {
examId.value = Number(options.examId)
subjectId.value = Number(options.subjectId)
questionId.value = Number(options.questionId)
taskId.value = Number(options.taskId) // 从路由参数获取taskId
evaluationType.value = options.type as 'single' | 'double' || 'single'
2025-09-25 22:53:50 +08:00
taskType.value = options.taskType
2025-08-16 16:42:40 +08:00
})
// 监听屏幕方向变化
function handleOrientationChange() {
// #ifdef H5
const orientation = window.screen.orientation?.angle || 0
isLandscape.value = orientation === 90 || orientation === 270
// #endif
// #ifdef APP-PLUS
// plus.screen.lockOrientation('portrait-primary')
// #endif
}
2025-11-04 22:29:47 +08:00
onMounted(async () => {
2025-08-16 16:42:40 +08:00
// #ifdef H5
window.addEventListener('orientationchange', handleOrientationChange)
handleOrientationChange()
// #endif
2025-11-04 22:29:47 +08:00
// 初始化导航
await initNavigation()
2025-08-16 16:42:40 +08:00
})
onUnmounted(() => {
// #ifdef H5
window.removeEventListener('orientationchange', handleOrientationChange)
// #endif
})
// 切换屏幕方向
function toggleOrientation() {
// #ifdef APP-PLUS
if (isLandscape.value) {
plus.screen.lockOrientation('portrait-primary')
}
else {
plus.screen.lockOrientation('landscape-primary')
}
// #endif
isLandscape.value = !isLandscape.value
}
2025-11-04 22:29:47 +08:00
// 打开全屏图片弹窗
2025-08-16 16:42:40 +08:00
function toggleFullscreen() {
2025-11-04 22:29:47 +08:00
showFullscreenImage.value = true
2025-08-16 16:42:40 +08:00
}
// 返回上一页
function goBack() {
uni.navigateBack()
}
// 题目选择
function selectQuestion(index: number) {
currentQuestionIndex.value = index
2025-09-25 22:53:50 +08:00
questions.value = []
2025-08-16 16:42:40 +08:00
}
2025-08-16 18:30:38 +08:00
// 弹窗状态
2025-08-16 16:42:40 +08:00
const showScoreSettings = ref(false)
2025-08-16 18:30:38 +08:00
const showAvgScore = ref(false)
const showAnswer = ref(false)
2025-08-16 16:42:40 +08:00
// 打分设置
function openScoreSettings() {
showScoreSettings.value = true
}
// 打分设置确认
function handleScoreSettingsConfirm(data: { stepSize: number, commonScores: number[] }) {
console.log('打分设置已确认:', data)
uni.showToast({
title: '设置已保存',
icon: 'success',
})
}
// 查看均分
function viewAvgScore() {
2025-08-16 18:30:38 +08:00
showAvgScore.value = true
2025-08-16 16:42:40 +08:00
}
// 查看答案
function viewAnswer() {
2025-08-16 18:30:38 +08:00
showAnswer.value = true
2025-08-16 16:42:40 +08:00
}
// 当前题目信息
2025-09-25 22:53:50 +08:00
const currentQuestions = computed(() => questionsList.value[currentQuestionIndex.value])
2025-11-04 22:29:47 +08:00
// 如果在历史查看模式,使用历史记录数据,否则使用当前题目数据
const currentQuestion = computed(() => {
if (isViewingHistory.value && markingNavigation.currentHistoryQuestion.value) {
// 历史模式:返回历史记录
return markingNavigation.currentHistoryQuestion.value
}
// 正常模式:返回当前题目
return questions.value[0]
})
2025-09-25 22:53:50 +08:00
const currentTask = computed(() => currentQuestions.value?.tasks?.[taskType.value])
2025-11-04 22:29:47 +08:00
// 当前答题卡图片列表
const currentImageUrls = computed(() => currentQuestion.value?.image_urls || [])
2025-09-27 23:24:48 +08:00
let isFirst = true
whenever(questionsList, () => {
if (isFirst) {
isFirst = false
currentQuestionIndex.value = questionsList.value.findIndex(q => q.question_id === questionId.value)
}
})
2025-09-25 22:53:50 +08:00
const queryClient = useQueryClient()
whenever(currentTask, (task, oldTask) => {
taskId.value = task?.id
questionId.value = task?.question_id
queryClient.invalidateQueries({ queryKey: ['marking-question', oldTask?.id] })
queryClient.invalidateQueries({ queryKey: ['marking-question', task.id] })
})
2025-08-16 16:42:40 +08:00
// 快捷打分选择
function handleQuickScoreSelect(score: number) {
console.log('选择分数:', score)
}
2025-11-04 22:29:47 +08:00
// 创建手势处理器
const swipeHandler = createSwipeHandler()
// 历史查看模式提示
const historyModeText = computed(() => {
if (isViewingHistory.value) {
return `${navCurrentIndex.value}/${totalQuestionsCount.value}`
}
return ''
})
// 上一题
async function handlePrevQuestion() {
await goToPrevQuestion()
}
// 下一题
async function handleNextQuestion() {
await goToNextQuestion()
}
2025-08-16 16:42:40 +08:00
</script>
<template>
2025-11-04 22:29:47 +08:00
<div
class="relative h-screen w-100vw flex flex-col touch-pan-x touch-pan-y overflow-hidden overscroll-none pt-safe"
@touchstart="swipeHandler.onTouchStart"
@touchend="swipeHandler.onTouchEnd"
>
2025-08-16 16:42:40 +08:00
<MarkingLayout
:is-landscape="isLandscape"
2025-11-04 22:29:47 +08:00
:is-fullscreen="false"
2025-08-16 16:42:40 +08:00
:current-question-index="currentQuestionIndex"
2025-09-25 22:53:50 +08:00
:current-task-submit="currentTask?.marked_quantity || 0"
2025-08-16 16:42:40 +08:00
:total-questions="totalQuestions"
2025-09-25 22:53:50 +08:00
:questions="questionsList"
2025-08-16 16:42:40 +08:00
:my-score="myScore"
:avg-score="avgScore"
2025-11-04 22:29:47 +08:00
:is-viewing-history="isViewingHistory"
:can-go-prev="canGoPrev"
:can-go-next="canGoNext"
:history-mode-text="historyModeText"
:task-id="taskId"
2025-08-16 16:42:40 +08:00
@go-back="goBack"
@select-question="selectQuestion"
@open-score-settings="openScoreSettings"
@view-avg-score="viewAvgScore"
@view-answer="viewAnswer"
@toggle-orientation="toggleOrientation"
@toggle-fullscreen="toggleFullscreen"
2025-11-04 22:29:47 +08:00
@prev-question="handlePrevQuestion"
@next-question="handleNextQuestion"
2025-08-16 16:42:40 +08:00
>
<template #content>
<MarkingImageViewerNew
v-if="questions[0]?.image_urls?.length"
2025-11-04 22:29:47 +08:00
v-model:scale="imageScale"
:question-data="[currentQuestion]"
2025-08-16 16:42:40 +08:00
:image-size="100"
/>
2025-09-19 21:39:54 +08:00
<!-- 快捷打分面板 - 固定定位在右侧 -->
<QuickScorePanel
2025-11-04 22:29:47 +08:00
v-if="currentQuestion && !isViewingHistory"
2025-09-19 21:39:54 +08:00
:is-landscape="isLandscape"
:full-score="currentQuestion.full_score"
@score-selected="handleQuickScoreSelect"
/>
2025-08-16 16:42:40 +08:00
</template>
</MarkingLayout>
<!-- 打分设置弹窗 -->
<ScoreSettingsDialog
v-model="showScoreSettings"
2025-09-27 23:24:48 +08:00
:full-score="currentQuestions?.full_score"
2025-08-16 16:42:40 +08:00
@confirm="handleScoreSettingsConfirm"
/>
2025-08-16 18:30:38 +08:00
<!-- 查看均分弹窗 -->
<AvgScoreDialog
v-model="showAvgScore"
:my-score="myScore"
:avg-score="avgScore"
2025-09-27 23:24:48 +08:00
:question-title="`${currentQuestions?.question_major}.${currentQuestions?.question_minor}`"
:full-score="currentQuestions?.full_score"
2025-08-16 18:30:38 +08:00
:avg-score-data="markingData.avgScoreData.value"
/>
<!-- 查看答案弹窗 -->
<AnswerDialog
2025-09-19 21:39:54 +08:00
v-if="currentQuestion"
2025-08-16 18:30:38 +08:00
v-model="showAnswer"
:question-title="`${currentQuestion?.question_major}.${currentQuestion?.question_minor}`"
:full-score="currentQuestion?.full_score"
2025-11-04 22:29:47 +08:00
:standard-answer="currentQuestion?.standard_answer || ''"
/>
<!-- 全屏图片弹窗 -->
<FullscreenImageDialog
v-model="showFullscreenImage"
:image-urls="currentImageUrls"
2025-08-16 18:30:38 +08:00
/>
2025-08-16 16:42:40 +08:00
</div>
</template>
2025-08-16 20:12:10 +08:00
<style scoped>
/* 禁用橡皮筋效果 */
.h-screen {
position: fixed;
overscroll-behavior: none;
-webkit-overflow-scrolling: touch;
}
/* H5端额外的橡皮筋禁用 */
/* #ifdef H5 */
body,
html {
overscroll-behavior: none;
overflow: hidden;
position: fixed;
width: 100%;
height: 100%;
}
/* #endif */
/* 微信小程序端橡皮筋禁用 */
/* #ifdef MP-WEIXIN */
page {
overflow: hidden;
overscroll-behavior: none;
}
/* #endif */
/* APP端橡皮筋禁用 */
/* #ifdef APP-PLUS */
page {
overflow: hidden;
}
/* #endif */
</style>