From da590c2939a59af40c015ea30df082d76555ca35 Mon Sep 17 00:00:00 2001 From: zpc Date: Thu, 19 Mar 2026 07:59:16 +0800 Subject: [PATCH] 2121 --- uniapp/pages/assessment/questions/index.vue | 432 ++++++++++++++++---- 1 file changed, 346 insertions(+), 86 deletions(-) diff --git a/uniapp/pages/assessment/questions/index.vue b/uniapp/pages/assessment/questions/index.vue index 730ee94..960f676 100644 --- a/uniapp/pages/assessment/questions/index.vue +++ b/uniapp/pages/assessment/questions/index.vue @@ -3,10 +3,11 @@ * 测评答题页面 * * 功能: - * - 展示所有测评题目和选项 + * - 单题翻页模式展示测评题目 * - 每题10个选项,单选 + * - 底部导航:题目导航、上一题、下一题 + * - 侧边题目导航面板,按题号跳转 * - 提交时检测未答题目 - * - 未答题弹窗提示 */ import { ref, computed } from 'vue' @@ -26,17 +27,20 @@ const questions = ref([]) // 答案数据 { questionId: selectedIndex } const answers = ref({}) +// 当前题目索引 +const currentIndex = ref(0) + // 页面状态 const pageLoading = ref(true) const submitting = ref(false) +// 题目导航弹窗 +const showNavPopup = ref(false) + // 未答题弹窗 const showUnansweredPopup = ref(false) const unansweredQuestions = ref([]) -// scroll-view 滚动目标 -const scrollTarget = ref('') - // 评分标准选项(从后台加载) const scoreOptions = ref([ { score: 1, label: '极弱', desc: '完全不符合' }, @@ -51,30 +55,46 @@ const scoreOptions = ref([ { score: 10, label: '极强', desc: '完全符合' } ]) -/** 已答题数量 */ -const answeredCount = computed(() => Object.keys(answers.value).length) +/** 当前题目 */ +const currentQuestion = computed(() => questions.value[currentIndex.value] || null) + +/** 当前题目ID */ +const currentQuestionId = computed(() => { + if (!currentQuestion.value) return 0 + return currentQuestion.value.id || currentIndex.value + 1 +}) /** 总题目数量 */ const totalCount = computed(() => questions.value.length) +/** 是否第一题 */ +const isFirst = computed(() => currentIndex.value === 0) + +/** 是否最后一题 */ +const isLast = computed(() => currentIndex.value === totalCount.value - 1) + +/** 判断某题是否已答 */ +function isAnswered(index) { + const q = questions.value[index] + const qId = q?.id || index + 1 + return answers.value[qId] !== undefined +} + /** 加载题目列表 */ async function loadQuestions() { pageLoading.value = true try { - // 并行加载题目和评分标准 const [questionsRes, scoreRes] = await Promise.all([ getQuestionList(typeId.value), getScoreOptions(typeId.value) ]) - // 处理题目数据 if (questionsRes && questionsRes.code === 0 && questionsRes.data) { questions.value = questionsRes.data.list || questionsRes.data || [] } else { questions.value = generateMockQuestions() } - // 处理评分标准数据 if (scoreRes && scoreRes.code === 0 && scoreRes.data && scoreRes.data.length > 0) { scoreOptions.value = scoreRes.data.map(item => ({ score: item.score, @@ -82,7 +102,6 @@ async function loadQuestions() { desc: item.description })) } - // 如果加载失败,保留默认值 } catch (error) { console.error('加载数据失败:', error) questions.value = generateMockQuestions() @@ -118,14 +137,43 @@ function isSelected(questionId, scoreIndex) { return answers.value[questionId] === scoreIndex } +/** 上一题 */ +function goPrev() { + if (!isFirst.value) { + currentIndex.value-- + } +} + +/** 下一题 */ +function goNext() { + if (!isLast.value) { + currentIndex.value++ + } +} + +/** 跳转到指定题目(题目导航用) */ +function goToQuestion(index) { + currentIndex.value = index + showNavPopup.value = false +} + +/** 打开题目导航 */ +function openNavPopup() { + showNavPopup.value = true +} + +/** 关闭题目导航 */ +function closeNavPopup() { + showNavPopup.value = false +} + /** 提交答案 */ async function handleSubmit() { - // 检查未答题目 const unanswered = [] questions.value.forEach((q, index) => { const qId = q.id || index + 1 if (answers.value[qId] === undefined) { - unanswered.push(q.questionNo || index + 1) + unanswered.push({ no: q.questionNo || index + 1, index }) } }) @@ -167,14 +215,10 @@ function closeUnansweredPopup() { showUnansweredPopup.value = false } -/** 滚动到未答题目 */ -function scrollToQuestion(questionNo) { +/** 跳转到未答题目 */ +function goToUnanswered(index) { closeUnansweredPopup() - // 先清空再赋值,确保相同 id 也能触发滚动 - scrollTarget.value = '' - setTimeout(() => { - scrollTarget.value = `question-${questionNo}` - }, 50) + currentIndex.value = index } /** 页面加载 */ @@ -197,45 +241,89 @@ onLoad((options) => { 加载题目中... - - - - - - - - {{ question.questionNo || index + 1 }} - {{ question.content }} - + + + + + + 当前题目 + /总题数: + {{ currentIndex + 1 }}/{{ totalCount }} + - - - - - - - 【{{ option.label }}】{{ option.desc }} - + + + {{ currentQuestion.questionNo || currentIndex + 1 }} + {{ currentQuestion.content }} + + + + + + + + 【{{ option.label }}】{{ option.desc }} - + - - - - {{ submitting ? '提交中...' : '提交' }} + + + + + 题目导航 + + + + + 上一题 + + + 下一题 + + + {{ submitting ? '提交中...' : '提交' }} + + + + + + + + + + 题目导航 + + + + + + {{ q.questionNo || idx + 1 }} + + + @@ -250,12 +338,12 @@ onLoad((options) => { 以下题目尚未作答,请完成后再提交: - 第 {{ qNo }} 题 + 第 {{ item.no }} 题 @@ -307,35 +395,51 @@ onLoad((options) => { to { transform: rotate(360deg); } } -// ========== 滚动区域 ========== -.scroll-area { +// ========== 答题区域 ========== +.question-area { flex: 1; - overflow: hidden; + overflow-y: auto; + padding: $spacing-sm $spacing-lg; + display: flex; + align-items: center; } -.scroll-inner { - padding: $spacing-lg; -} - -.questions-card { +.question-card { background-color: $bg-white; - border-radius: $border-radius-xl; + border-radius: 32rpx; padding: $spacing-xl $spacing-lg; + width: 100%; + min-height: 65vh; } -// ========== 单个题目 ========== -.question-block { - margin-bottom: $spacing-xl; +// ========== 进度信息 ========== +.progress-bar { + display: flex; + align-items: center; + margin-bottom: $spacing-lg; - &:last-child { - margin-bottom: 0; + .progress-current { + font-size: $font-size-sm; + color: #FF6B60; + } + + .progress-sep { + font-size: $font-size-sm; + color: $text-placeholder; + } + + .progress-nums { + font-size: $font-size-sm; + color: $text-placeholder; + margin-left: 4rpx; } } +// ========== 题目标题 ========== .question-title { display: flex; align-items: flex-start; - margin-bottom: $spacing-lg; + margin-bottom: $spacing-xl; } .question-no { @@ -406,32 +510,91 @@ onLoad((options) => { line-height: 1.5; } -// ========== 底部固定提交按钮 ========== -.submit-fixed { +// ========== 底部操作栏 ========== +.bottom-bar { flex-shrink: 0; - padding: $spacing-lg $spacing-lg; - padding-bottom: calc(#{$spacing-lg} + env(safe-area-inset-bottom)); - background-color: rgba(255, 234, 231, 1); + display: flex; + align-items: center; + padding: $spacing-md $spacing-lg; + padding-bottom: calc(#{$spacing-md} + env(safe-area-inset-bottom)); + background-color: $bg-white; + border-top: 1rpx solid $border-light; } -.submit-btn { - height: 96rpx; - background-color: #FF6B60; - border-radius: 48rpx; +.nav-btn-left { + display: flex; + align-items: center; + padding: $spacing-sm 0; + + .nav-icon-text { + font-size: 32rpx; + color: #FF6B60; + margin-right: 8rpx; + line-height: 1; + } + + .nav-label { + font-size: $font-size-sm; + color: #FF6B60; + } + + &:active { + opacity: 0.7; + } +} + +.bar-divider { + width: 1rpx; + height: 48rpx; + background-color: $border-color; + margin: 0 $spacing-lg; +} + +.bar-actions { + flex: 1; + display: flex; + align-items: center; + gap: $spacing-md; +} + +.action-btn { + flex: 1; + height: 76rpx; + border-radius: 38rpx; display: flex; align-items: center; justify-content: center; text { - font-size: $font-size-xl; - font-weight: $font-weight-bold; - color: $text-white; + font-size: $font-size-md; + font-weight: $font-weight-medium; } &:active { - opacity: 0.9; + opacity: 0.8; transform: scale(0.98); } +} + +.action-prev { + background-color: #FF6B60; + + text { + color: $text-white; + } + + &.action-disabled { + opacity: 0.4; + pointer-events: none; + } +} + +.action-next { + background-color: #FF6B60; + + text { + color: $text-white; + } &.btn-loading { opacity: 0.7; @@ -439,6 +602,103 @@ onLoad((options) => { } } +// ========== 题目导航侧边弹窗 ========== +.nav-popup-mask { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1000; + display: flex; +} + +.nav-popup-panel { + width: 580rpx; + height: 100%; + background-color: $bg-white; + display: flex; + flex-direction: column; +} + +.nav-popup-header { + display: flex; + align-items: center; + padding: $spacing-xl $spacing-lg; + padding-top: calc(#{$spacing-xl} + env(safe-area-inset-top) + 88rpx); + + .nav-popup-indicator { + width: 8rpx; + height: 36rpx; + background-color: $text-color; + border-radius: 4rpx; + margin-right: $spacing-sm; + } + + .nav-popup-title { + font-size: $font-size-lg; + font-weight: $font-weight-bold; + color: $text-color; + } +} + +.nav-popup-divider { + height: 1rpx; + background: repeating-linear-gradient( + to right, + $border-color 0, + $border-color 8rpx, + transparent 8rpx, + transparent 16rpx + ); + margin: 0 $spacing-lg; +} + +.nav-popup-grid-scroll { + flex: 1; + overflow: hidden; +} + +.nav-popup-grid { + display: flex; + flex-wrap: wrap; + padding: $spacing-lg; + gap: $spacing-md; +} + +.nav-grid-item { + width: 100rpx; + height: 100rpx; + border-radius: $border-radius-md; + background-color: #F0F0F0; + display: flex; + align-items: center; + justify-content: center; + + text { + font-size: $font-size-lg; + color: $text-secondary; + font-weight: $font-weight-medium; + } + + &.nav-grid-answered { + background-color: #FF6B60; + + text { + color: $text-white; + } + } + + &.nav-grid-current { + border: 3rpx solid #FF6B60; + } + + &:active { + opacity: 0.8; + } +} + // ========== 未答题弹窗 ========== .popup-mask { position: fixed; @@ -446,7 +706,7 @@ onLoad((options) => { left: 0; right: 0; bottom: 0; - background-color: $bg-mask; + background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center;