<template>
|
|
<view class="mt32 question__view" :class="[props.mode]">
|
|
<view class="size-28 mb20 question">
|
|
{{ props.data.title }}
|
|
<text class="question-type" v-if="props.data.typeAnswer == 0">
|
|
{{ isMultipleChoice ? '(多选题)' : '(单选题)' }}
|
|
</text>
|
|
<text class="question-type" v-else-if="props.data.typeAnswer == 1">
|
|
(填空题)
|
|
</text>
|
|
<!-- {{ `${props.index + 1}、${props.data.title}` }} -->
|
|
</view>
|
|
<template v-if="props.mode === 'edit'">
|
|
<template v-if="props.data.typeAnswer == 0">
|
|
<view class="size-28 option" v-for="(option, oIdx) in props.data.options"
|
|
:key="`${props.index}-option-${oIdx}`"
|
|
:class="[isOptionSelected(option.id) ? 'is-selected' : '']"
|
|
@click="onChange(option.id)">
|
|
{{ option.title }}
|
|
</view>
|
|
</template>
|
|
<template v-else>
|
|
<view class="textarea">
|
|
<textarea v-model="value" :placeholder="`请输入您的答案,不得低于${props.data.numberWords || 0}个字`" :rows="10"
|
|
@blur="onChange($event.detail.value)" :maxlength="2000"></textarea>
|
|
</view>
|
|
</template>
|
|
</template>
|
|
<template v-else>
|
|
<template v-if="props.data.typeAnswer == 0">
|
|
<view class="size-28 option" v-for="(option, oIdx) in props.data.options"
|
|
:key="`${props.index}-option-${oIdx}`" :class="[
|
|
option.isTrue ? 'is-correct' : '',
|
|
isDisplayOptionSelected(option.id) && !option.isTrue ? 'is-error' : '',
|
|
isDisplayOptionSelected(option.id) && option.isTrue ? 'is-correct-selected' : ''
|
|
]">
|
|
{{ option.title }}
|
|
<!-- 正确答案标识 -->
|
|
<view class="icon icon-correct" v-if="option.isTrue">
|
|
<up-icon name="checkmark" color="#05C160" size="35rpx"></up-icon>
|
|
</view>
|
|
<!-- 错误选择标识 -->
|
|
<view class="icon icon-error" v-if="isDisplayOptionSelected(option.id) && !option.isTrue">
|
|
<up-icon name="close" color="#FF2A2A" size="35rpx"></up-icon>
|
|
</view>
|
|
<!-- 用户选择标识和正确答案标识 -->
|
|
<view class="answer-tags" v-if="isDisplayOptionSelected(option.id) || option.isTrue">
|
|
<view class="user-choice-tag" v-if="isDisplayOptionSelected(option.id)">
|
|
我的选择
|
|
</view>
|
|
<view class="correct-answer-tag" v-if="option.isTrue">
|
|
正确答案
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
<template v-else>
|
|
<view class="textarea">
|
|
<view class="user-answer-label">我的答案:</view>
|
|
<view class="user-answer">{{ props.data.value || '未作答' }}</view>
|
|
<view class="correct-answer-label" v-if="props.data.answer">正确答案:</view>
|
|
<view class="highlight" v-if="props.data.answer">{{ props.data.answer }}</view>
|
|
</view>
|
|
</template>
|
|
</template>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import {
|
|
computed,
|
|
watch
|
|
} from 'vue'
|
|
import {
|
|
addBaseAnswer,
|
|
addTrainAnswer
|
|
} from '@/api/examination'
|
|
import {
|
|
store
|
|
} from '@/store'
|
|
|
|
const userId = computed(() => {
|
|
return store.state.user.userInfo.userId
|
|
})
|
|
|
|
const props = defineProps({
|
|
index: {
|
|
type: Number,
|
|
default: null,
|
|
},
|
|
data: {
|
|
type: Object,
|
|
default () {
|
|
return {}
|
|
}
|
|
},
|
|
modelValue: {
|
|
type: [String, Number, Array],
|
|
default: null,
|
|
},
|
|
mode: {
|
|
type: String,
|
|
default: 'edit', // edit | display
|
|
},
|
|
type: {
|
|
type: String,
|
|
default: null, // '基本' | '培训'
|
|
}
|
|
})
|
|
|
|
const min = 700
|
|
|
|
const emit = defineEmits(['update:modelValue'])
|
|
|
|
// 判断是否是多选题
|
|
const isMultipleChoice = computed(() => {
|
|
if (!props.data.options || !props.data.options.length) return false
|
|
const correctAnswers = props.data.options.filter(option => option.isTrue === 1 || option.isTrue === true)
|
|
return correctAnswers.length > 1
|
|
})
|
|
|
|
const value = computed({
|
|
set(val) {
|
|
emit('update:modelValue', val)
|
|
},
|
|
get() {
|
|
if (isMultipleChoice.value) {
|
|
// 多选题,确保返回数组
|
|
return Array.isArray(props.modelValue) ? props.modelValue : (props.modelValue ? [props.modelValue] : [])
|
|
} else {
|
|
// 单选题,返回单个值
|
|
return Array.isArray(props.modelValue) ? props.modelValue[0] : props.modelValue
|
|
}
|
|
}
|
|
})
|
|
|
|
// 判断选项是否被选中
|
|
const isOptionSelected = (optionId) => {
|
|
if (isMultipleChoice.value) {
|
|
return Array.isArray(value.value) && value.value.includes(optionId)
|
|
} else {
|
|
return value.value === optionId
|
|
}
|
|
}
|
|
|
|
// 显示模式下判断选项是否被用户选中
|
|
const isDisplayOptionSelected = (optionId) => {
|
|
if (props.mode !== 'display') return false
|
|
|
|
// 方法1: 从 props.data.value 获取用户答案(直接传入的答案)
|
|
if (props.data.value !== undefined && props.data.value !== null) {
|
|
if (Array.isArray(props.data.value)) {
|
|
return props.data.value.includes(optionId)
|
|
} else {
|
|
return props.data.value === optionId
|
|
}
|
|
}
|
|
|
|
// 方法2: 从 userAnswerBaseList 获取用户答案(从后端获取的答案数据)
|
|
if (props.data.userAnswerBaseList && props.data.userAnswerBaseList.length > 0) {
|
|
return props.data.userAnswerBaseList.some(answer => answer.answerId === optionId)
|
|
}
|
|
|
|
// 方法3: 从 modelValue 获取答案(当前组件的值)
|
|
if (props.modelValue !== undefined && props.modelValue !== null) {
|
|
if (Array.isArray(props.modelValue)) {
|
|
return props.modelValue.includes(optionId)
|
|
} else {
|
|
return props.modelValue === optionId
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
const onChange = (val) => {
|
|
if (props.data.typeAnswer == 1) {
|
|
// 填空题处理
|
|
value.value = val
|
|
} else if (props.data.typeAnswer == 0) {
|
|
// 选择题处理
|
|
if (isMultipleChoice.value) {
|
|
// 多选题处理
|
|
let currentValue = Array.isArray(value.value) ? [...value.value] : []
|
|
const index = currentValue.indexOf(val)
|
|
|
|
if (index > -1) {
|
|
// 已选中,取消选择
|
|
currentValue.splice(index, 1)
|
|
} else {
|
|
// 未选中,添加选择
|
|
currentValue.push(val)
|
|
}
|
|
|
|
value.value = currentValue
|
|
} else {
|
|
// 单选题处理
|
|
value.value = val
|
|
}
|
|
}
|
|
|
|
// const data = {
|
|
// userId: userId.value,
|
|
// questionId: props.data.id,
|
|
// }
|
|
|
|
// if (props.type === '基本') {
|
|
// data.answerId = val
|
|
// addBaseAnswer(data)
|
|
// } else if (props.type === '培训') {
|
|
// data.answer = val
|
|
// addTrainAnswer(data)
|
|
// }
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.question {
|
|
color: #000000;
|
|
|
|
.question-type {
|
|
color: #FFBF60;
|
|
font-size: 22rpx;
|
|
margin-left: 10rpx;
|
|
}
|
|
}
|
|
|
|
.option {
|
|
background-color: #F3F3F3;
|
|
color: #707070;
|
|
line-height: 37rpx;
|
|
padding: 23rpx;
|
|
border-radius: 28rpx;
|
|
|
|
position: relative;
|
|
|
|
&+& {
|
|
margin-top: 20rpx;
|
|
}
|
|
|
|
.icon {
|
|
position: absolute;
|
|
right: 45rpx;
|
|
bottom: 23rpx;
|
|
display: none;
|
|
|
|
}
|
|
}
|
|
|
|
.textarea {
|
|
background-color: #F3F3F3;
|
|
padding: 23rpx;
|
|
border-radius: 16rpx;
|
|
|
|
.highlight {
|
|
color: #FF2A2A;
|
|
font-size: 28rpx;
|
|
}
|
|
}
|
|
|
|
.question__view.edit {
|
|
.option.is-selected {
|
|
background-color: rgba($color: #FFBF60, $alpha: 0.22);
|
|
color: #FFBF60;
|
|
}
|
|
}
|
|
|
|
.question__view.display {
|
|
.option {
|
|
&.is-correct {
|
|
background-color: rgba($color: #05C160, $alpha: 0.08);
|
|
color: #05C160;
|
|
|
|
.icon-correct {
|
|
display: block;
|
|
}
|
|
}
|
|
|
|
&.is-error {
|
|
background-color: rgba($color: #FFEBCE, $alpha: 0.36);
|
|
color: #FF2A2A;
|
|
|
|
.icon-error {
|
|
display: block;
|
|
}
|
|
}
|
|
|
|
&.is-correct-selected {
|
|
background-color: rgba($color: #05C160, $alpha: 0.15);
|
|
color: #05C160;
|
|
border: 2px solid #05C160;
|
|
}
|
|
}
|
|
|
|
.answer-tags {
|
|
margin-top: 10rpx;
|
|
display: flex;
|
|
gap: 10rpx;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.user-choice-tag {
|
|
background-color: #FFBF60;
|
|
color: white;
|
|
font-size: 20rpx;
|
|
padding: 6rpx 12rpx;
|
|
border-radius: 12rpx;
|
|
display: inline-block;
|
|
}
|
|
|
|
.correct-answer-tag {
|
|
background-color: #05C160;
|
|
color: white;
|
|
font-size: 20rpx;
|
|
padding: 6rpx 12rpx;
|
|
border-radius: 12rpx;
|
|
display: inline-block;
|
|
}
|
|
}
|
|
|
|
.textarea {
|
|
.user-answer-label, .correct-answer-label {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
margin-bottom: 10rpx;
|
|
}
|
|
|
|
.user-answer {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
margin-bottom: 15rpx;
|
|
padding: 10rpx;
|
|
background-color: rgba($color: #FFBF60, $alpha: 0.1);
|
|
border-radius: 8rpx;
|
|
}
|
|
|
|
.highlight {
|
|
color: #05C160;
|
|
font-size: 28rpx;
|
|
font-weight: bold;
|
|
padding: 10rpx;
|
|
background-color: rgba($color: #05C160, $alpha: 0.1);
|
|
border-radius: 8rpx;
|
|
}
|
|
}
|
|
</style>
|