1
This commit is contained in:
@@ -0,0 +1,396 @@
|
||||
<template>
|
||||
<hr-page page-bg="#F3F4F6" status-bg="#FFFFFF" :nav-title="'顺序练习 · 第 ' + (cur + 1) + ' 题'"
|
||||
@right-click="onMarkCurrent">
|
||||
<template v-slot:right>
|
||||
<x-icon :name="isMarked ? 'bookmark-fill' : 'bookmark-line'" :color="isMarked ? '#F59E0B' : '#6B7280'"
|
||||
font-size="19"></x-icon>
|
||||
</template>
|
||||
|
||||
<view class="hr-body">
|
||||
|
||||
<!-- ============ 进度 ============ -->
|
||||
<view class="hr-row">
|
||||
<view style="flex:1">
|
||||
<hr-progress :percent="pct"></hr-progress>
|
||||
</view>
|
||||
<text class="qz-prog">{{ cur + 1 }} / {{ total }}</text>
|
||||
</view>
|
||||
|
||||
<!-- ============ 题卡 ============ -->
|
||||
<hr-card style="margin-top:10px;padding:16px 14px 16px 14px">
|
||||
<text class="qz-stem">{{ q.stem }}</text>
|
||||
|
||||
<view class="qz-opts">
|
||||
<view v-for="(o, i) in q.options" :key="i" :class="optClass(i)" @click="choose(i)">
|
||||
<view :class="optKeyClass(i)">
|
||||
<text :class="optKeyTextClass(i)">{{ o.key }}</text>
|
||||
</view>
|
||||
<text class="qz-opt-t">{{ o.text }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="qz-acts">
|
||||
<view class="hr-btn hr-btn-ghost qz-act" @click="onMarkCurrent">
|
||||
<x-icon :name="isMarked ? 'bookmark-fill' : 'bookmark-line'"
|
||||
:color="isMarked ? '#F59E0B' : '#1F2937'" font-size="16"></x-icon>
|
||||
<text class="qz-act-t">{{ isMarked ? '已标记' : '标记本题' }}</text>
|
||||
</view>
|
||||
<view class="hr-btn hr-btn-ghost qz-act qz-act-last" @click="openCard">
|
||||
<x-icon name="layout-grid-line" color="#1F2937" font-size="16"></x-icon>
|
||||
<text class="qz-act-t">答题卡</text>
|
||||
</view>
|
||||
</view>
|
||||
</hr-card>
|
||||
|
||||
<hr-tip style="margin-top:10px" theme="info" icon="ℹ"
|
||||
text="答错的题目会自动进入错题本,可在课题功能页统一复习。"></hr-tip>
|
||||
|
||||
<!-- 作答反馈 -->
|
||||
<view v-if="showAnswer" class="qz-feedback" :class="isRight ? 'qz-feedback-ok' : 'qz-feedback-err'">
|
||||
<view class="qz-fb-ic" :class="isRight ? 'qz-fb-ic-ok' : 'qz-fb-ic-err'">
|
||||
<text class="qz-fb-ic-t">{{ isRight ? '✓' : '✕' }}</text>
|
||||
</view>
|
||||
<view class="hr-flex1">
|
||||
<text class="qz-fb-b">{{ isRight ? '回答正确' : '回答错误,已加入错题本' }}</text>
|
||||
<text class="qz-fb-s">正确答案:{{ rightAnswer }}</text>
|
||||
<text class="qz-fb-s">{{ q.analysis }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view style="height:14px"></view>
|
||||
</view>
|
||||
|
||||
<template v-slot:footer>
|
||||
<view class="hr-fixedbar">
|
||||
<view class="hr-btn hr-btn-ghost qz-foot-prev" @click="prev">
|
||||
<text class="qz-foot-t">上一题</text>
|
||||
</view>
|
||||
<view class="hr-btn hr-btn-primary" style="flex:1" @click="next">
|
||||
<text class="qz-foot-t2">{{ isLast ? '交卷并查看成绩' : '下一题' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</hr-page>
|
||||
</template>
|
||||
|
||||
<script lang="uts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { quizQuestions } from '@/mock/index.uts'
|
||||
import { quizState, setAnswer, toggleMark, answeredCount } from '@/utils/quiz.uts'
|
||||
import { relaunchTo, navTo } from '@/utils/nav.uts'
|
||||
import { HrQuestion } from '@/mock/types.uts'
|
||||
|
||||
/** s03 答题界面 */
|
||||
const total = quizQuestions.length
|
||||
const cur = ref(0)
|
||||
const refreshKey = ref(0)
|
||||
|
||||
const q = computed(() : HrQuestion => {
|
||||
return quizQuestions[cur.value]
|
||||
})
|
||||
|
||||
const selected = computed(() : number => {
|
||||
const k = refreshKey.value
|
||||
return quizState.answers[cur.value]
|
||||
})
|
||||
|
||||
const isMarked = computed(() : boolean => {
|
||||
const k = refreshKey.value
|
||||
return quizState.marked[cur.value]
|
||||
})
|
||||
|
||||
const isLast = computed(() : boolean => {
|
||||
return cur.value == total - 1
|
||||
})
|
||||
|
||||
const pct = computed(() : number => {
|
||||
return Math.round((cur.value + 1) * 100 / total)
|
||||
})
|
||||
|
||||
const showAnswer = computed(() : boolean => {
|
||||
const k = refreshKey.value
|
||||
return quizState.answers[cur.value] >= 0
|
||||
})
|
||||
|
||||
const isRight = computed(() : boolean => {
|
||||
const k = refreshKey.value
|
||||
return quizState.answers[cur.value] == q.value.answer
|
||||
})
|
||||
|
||||
const rightAnswer = computed(() : string => {
|
||||
const o = q.value.options[q.value.answer]
|
||||
return `${o.key}. ${o.text}`
|
||||
})
|
||||
|
||||
// uni-app x 页面显示生命周期:从答题卡返回时同步当前题号与作答状态
|
||||
onPageShow(() => {
|
||||
cur.value = quizState.current
|
||||
refreshKey.value = refreshKey.value + 1
|
||||
})
|
||||
|
||||
function optClass(i : number) : string {
|
||||
if (selected.value == i) {
|
||||
if (i == q.value.answer) { return 'qz-opt qz-opt-ok' }
|
||||
return 'qz-opt qz-opt-sel'
|
||||
}
|
||||
if (showAnswer.value && i == q.value.answer) { return 'qz-opt qz-opt-ok' }
|
||||
return 'qz-opt'
|
||||
}
|
||||
|
||||
function optKeyClass(i : number) : string {
|
||||
if (selected.value == i) {
|
||||
if (i == q.value.answer) { return 'qz-ok qz-ok-ok' }
|
||||
return 'qz-ok qz-ok-sel'
|
||||
}
|
||||
if (showAnswer.value && i == q.value.answer) { return 'qz-ok qz-ok-ok' }
|
||||
return 'qz-ok'
|
||||
}
|
||||
|
||||
function optKeyTextClass(i : number) : string {
|
||||
if (selected.value == i || (showAnswer.value && i == q.value.answer)) {
|
||||
return 'qz-ok-t qz-ok-t-on'
|
||||
}
|
||||
return 'qz-ok-t'
|
||||
}
|
||||
|
||||
function choose(i : number) : void {
|
||||
if (selected.value >= 0) {
|
||||
uni.showToast({ title: '本题已作答', icon: 'none' })
|
||||
return
|
||||
}
|
||||
setAnswer(cur.value, i)
|
||||
refreshKey.value = refreshKey.value + 1
|
||||
if (i != q.value.answer) {
|
||||
// 答错自动进错题本(mock 阶段仅提示)
|
||||
uni.showToast({ title: '已加入错题本', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
function onMarkCurrent() : void {
|
||||
toggleMark(cur.value)
|
||||
refreshKey.value = refreshKey.value + 1
|
||||
uni.showToast({ title: isMarked.value ? '已标记本题' : '已取消标记', icon: 'none' })
|
||||
}
|
||||
|
||||
function prev() : void {
|
||||
if (cur.value == 0) {
|
||||
uni.showToast({ title: '已是第一题', icon: 'none' })
|
||||
return
|
||||
}
|
||||
cur.value = cur.value - 1
|
||||
quizState.current = cur.value
|
||||
refreshKey.value = refreshKey.value + 1
|
||||
}
|
||||
|
||||
// ⚠ submit 被 next() 调用,必须先定义(UTS 不提升函数声明)
|
||||
function submit() : void {
|
||||
const done = answeredCount()
|
||||
uni.showModal({
|
||||
title: '交卷确认',
|
||||
content: `已作答 ${done} / ${total} 题${done < total ? ',仍有题目未作答' : ''}。交卷后无法修改。`,
|
||||
cancelText: '继续答题',
|
||||
confirmText: '交卷',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
quizState.submitted = true
|
||||
uni.showToast({ title: '已交卷 · 得分 92', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
relaunchTo('/pages/study/exams')
|
||||
}, 700)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function next() : void {
|
||||
if (isLast.value) {
|
||||
submit()
|
||||
return
|
||||
}
|
||||
cur.value = cur.value + 1
|
||||
quizState.current = cur.value
|
||||
refreshKey.value = refreshKey.value + 1
|
||||
}
|
||||
|
||||
function openCard() : void {
|
||||
quizState.current = cur.value
|
||||
navTo('/pages/course/answer-card')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.qz-prog {
|
||||
font-size: 25.8rpx;
|
||||
font-weight: bold;
|
||||
color: #6B7280;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.qz-stem {
|
||||
font-size: 33.6rpx;
|
||||
font-weight: bold;
|
||||
color: #1F2937;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.qz-opts {
|
||||
flex-direction: column;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
|
||||
.qz-opt {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
min-height: 112rpx;
|
||||
padding: 20rpx 26rpx 20rpx 26rpx;
|
||||
border: 3rpx solid #E5E7EB;
|
||||
border-radius: 24rpx;
|
||||
background-color: #FFFFFF;
|
||||
margin-bottom: 18rpx;
|
||||
}
|
||||
|
||||
.qz-opt-sel {
|
||||
border: 3rpx solid #2563EB;
|
||||
background-color: #EFF6FF;
|
||||
}
|
||||
|
||||
.qz-opt-ok {
|
||||
border: 3rpx solid #10B981;
|
||||
background-color: #ECFDF5;
|
||||
}
|
||||
|
||||
.qz-ok {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
border-radius: 26rpx;
|
||||
border: 3rpx solid #E5E7EB;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 22rpx;
|
||||
}
|
||||
|
||||
.qz-ok-sel {
|
||||
background-color: #2563EB;
|
||||
border: 3rpx solid #2563EB;
|
||||
}
|
||||
|
||||
.qz-ok-ok {
|
||||
background-color: #10B981;
|
||||
border: 3rpx solid #10B981;
|
||||
}
|
||||
|
||||
.qz-ok-t {
|
||||
font-size: 26.8rpx;
|
||||
font-weight: bold;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.qz-ok-t-on {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.qz-opt-t {
|
||||
flex: 1;
|
||||
font-size: 30.2rpx;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.qz-acts {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
|
||||
.qz-act {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.qz-act-last {
|
||||
margin-right: 0rpx;
|
||||
}
|
||||
|
||||
.qz-act-t {
|
||||
font-size: 30.2rpx;
|
||||
font-weight: bold;
|
||||
color: #1F2937;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.qz-feedback {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
margin-top: 20rpx;
|
||||
padding: 22rpx 24rpx 22rpx 24rpx;
|
||||
border-radius: 22rpx;
|
||||
}
|
||||
|
||||
.qz-feedback-ok {
|
||||
background-color: #ECFDF5;
|
||||
border: 2rpx solid #A7F3D0;
|
||||
}
|
||||
|
||||
.qz-feedback-err {
|
||||
background-color: #FEF2F2;
|
||||
border: 2rpx solid #FECACA;
|
||||
}
|
||||
|
||||
.qz-fb-ic {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.qz-fb-ic-ok {
|
||||
background-color: #10B981;
|
||||
}
|
||||
|
||||
.qz-fb-ic-err {
|
||||
background-color: #DC2626;
|
||||
}
|
||||
|
||||
.qz-fb-ic-t {
|
||||
font-size: 29.2rpx;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.qz-fb-b {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.qz-fb-s {
|
||||
font-size: 24.6rpx;
|
||||
color: #6B7280;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.qz-foot-prev {
|
||||
flex: 0 0 220rpx;
|
||||
margin-right: 18rpx;
|
||||
}
|
||||
|
||||
.qz-foot-t {
|
||||
font-size: 31.4rpx;
|
||||
font-weight: bold;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.qz-foot-t2 {
|
||||
font-size: 32.4rpx;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user