532 lines
14 KiB
Vue
532 lines
14 KiB
Vue
<template>
|
||
<view class="item">
|
||
<view class="top">
|
||
<view class="type">{{ subject_type_text }}</view>
|
||
<view class="score" v-if="['settlement', 'examination'].includes(props.mode)">{{ item.tgtGrade }}分</view>
|
||
<view class="fl1"></view>
|
||
<view class="right">
|
||
<!-- 反馈中心 -->
|
||
<view class="mark_and_feedback_div" v-if="['examination', 'practice'].includes(props.mode)">
|
||
<image
|
||
v-if="props.mode === 'examination'"
|
||
:src="markIcon(props.item.mark ? '#e8b531' : '#ABABAB')"
|
||
class="img"
|
||
@click="mark_click()"
|
||
></image>
|
||
<image :src="feedbackIcon()" class="img" @click="feedback_click()"></image>
|
||
</view>
|
||
<template v-if="['mistake'].includes(props.mode)">
|
||
<view class="expand_button_div" @click="toggleExpand()">
|
||
<!-- <view class="right_text">{{animationState?'收起':'展开'}}</view> -->
|
||
|
||
<view class="toggle-container">
|
||
<view class="toggle-text" :class="{ active: !animationState }">展开</view>
|
||
<view class="toggle-text" :class="{ active: animationState }">收起</view>
|
||
</view>
|
||
|
||
<uv-icon
|
||
class="click_text_icon"
|
||
name="arrow-right"
|
||
:class="{ click_text_icon_action: animationState }"
|
||
color="#0066FF"
|
||
></uv-icon>
|
||
</view>
|
||
</template>
|
||
</view>
|
||
</view>
|
||
<view class="question_text">
|
||
{{ subject_data.qnsContent }}
|
||
</view>
|
||
<view :style="{ height: animationHeight }" :class="{ collapse_wrapper: props.mode === 'mistake' }" ref="wrapperRef">
|
||
<view class="collapse-content" ref="contentRef">
|
||
<view class="option_div">
|
||
<template v-if="qnsTyp === 'ES'">
|
||
<questionVue
|
||
:status="subject_data.es_status"
|
||
:dataInfo="props.item?.my_answer"
|
||
:isPreview="props.isPreview"
|
||
:activeVoiceId="subject_data.qnsId"
|
||
@buutton_click="questionButtonClick"
|
||
:button_complete_show="props.mode === 'practice'"
|
||
></questionVue>
|
||
</template>
|
||
<template v-else>
|
||
<radioSubjectItem
|
||
v-for="(analysisVo, index) in subject_data.itemVos"
|
||
@click_option="click_option"
|
||
:key="index"
|
||
:analysisVo="analysisVo"
|
||
:my_answer="item_answer"
|
||
></radioSubjectItem>
|
||
</template>
|
||
</view>
|
||
<view
|
||
class="option_div_button"
|
||
v-if="qnsTyp === 'MU' && ['practice', 'study'].includes(props.mode) && !props.isPreview && clearResult"
|
||
>
|
||
<uv-button
|
||
type="primary"
|
||
class="send_evaluate"
|
||
:custom-style="customStyle"
|
||
:throttleTime="300"
|
||
@click="muBtnClick"
|
||
:hairline="false"
|
||
>
|
||
确定
|
||
</uv-button>
|
||
</view>
|
||
|
||
<view
|
||
class="analysis_div"
|
||
v-if="
|
||
!clearResult &&
|
||
['study', 'mistake', 'settlement', 'practice', 'practice_record'].includes(props.mode)
|
||
"
|
||
>
|
||
<view class="analysis_title" v-if="qnsTyp !== 'ES'">
|
||
<view class="success_answer">正确答案:{{ correctResultLabel.join('') }}</view>
|
||
<view class="your_answer">
|
||
你的答案:
|
||
<text :class="right_or_wrong_class ? 'success' : 'error'">{{ myResultLabel.join('') }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="reference_answer" v-if="qnsTyp === 'ES'">
|
||
<view class="reference_answer_title">你的答案:</view>
|
||
{{ subject_data.anserResult ?? '' }}
|
||
</view>
|
||
<esAnalysis
|
||
v-if="qnsTyp === 'ES'"
|
||
:value="subject_data.itemVos[0]"
|
||
:judgeResult="judgeResultStatus"
|
||
></esAnalysis>
|
||
|
||
<!-- 问答题专属参考答案 -->
|
||
<view class="reference_answer" v-if="qnsTyp === 'ES'">
|
||
<view class="reference_answer_title">参考答案:</view>
|
||
{{ subject_data.itemVos[0]?.itemAnswer ?? '' }}
|
||
</view>
|
||
<view class="analysis_note">试题解析:{{ subject_data?.analyContent }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
<script setup>
|
||
import { ref, reactive, onMounted, nextTick, computed, getCurrentInstance, watch } from 'vue';
|
||
import radioSubjectItem from './radio-subject-item.vue';
|
||
import esAnalysis from './es-analysis.vue';
|
||
import questionVue from './question.vue';
|
||
import { markIcon, feedbackIcon } from '@/common/imgSvg';
|
||
import { SUBJECT_TYPE } from '@/enum.js';
|
||
import common from '@/common/common';
|
||
|
||
// 多选的确定按钮的样式
|
||
const customStyle = {
|
||
borderRadius: '16rpx',
|
||
width: '292rpx',
|
||
backgroundColor: '#0066FF',
|
||
border: '1rpx solid rgb(12, 123, 245)'
|
||
};
|
||
|
||
const emit = defineEmits(['subject_click', 'update:modelValue']);
|
||
const props = defineProps({
|
||
modelValue: {
|
||
type: [Array, Object],
|
||
default: () => []
|
||
},
|
||
item: {
|
||
type: Object,
|
||
required: true
|
||
},
|
||
mode: {
|
||
type: String,
|
||
required: true,
|
||
validator: (value) => {
|
||
// 考试: examination
|
||
// 练习: practice
|
||
// 错题本: mistake
|
||
// 学习: study
|
||
// 考试结算页 settlement
|
||
// 练习记录页 practice_record
|
||
return ['examination', 'practice', 'mistake', 'study', 'settlement', 'practice_record'].includes(value);
|
||
}
|
||
},
|
||
// 是否预览模式
|
||
isPreview: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
// 是否清除正确答案
|
||
clearResult: {
|
||
type: Boolean,
|
||
default: true
|
||
}
|
||
});
|
||
|
||
// 状态管理
|
||
const isExpanded = ref(false);
|
||
const wrapperRef = ref(null);
|
||
const contentRef = ref(null);
|
||
const judgeResultStatus = computed(() => props.item.judgeResult === 'P');
|
||
|
||
// 不是错题本就默认不折叠,错题本默认折叠
|
||
const animationState = ref(props.mode !== 'mistake' ? true : false);
|
||
const animationHeight = ref(props.mode !== 'mistake' ? '' : '0px');
|
||
|
||
const modelValue = computed({
|
||
get: () => props.modelValue,
|
||
set: (val) => emit('update:modelValue', val)
|
||
});
|
||
|
||
let answerText = props.item?.my_answer || props.item.anserResult || [];
|
||
// item_answer 是答题结果,可以是对象 数组 字符串类型
|
||
const item_answer = ref(typeof answerText === 'string' ? answerText.split(',') : answerText);
|
||
|
||
watch(
|
||
() => props.modelValue,
|
||
(newVal) => {
|
||
console.log('modelValue', newVal);
|
||
item_answer.value = typeof newVal === 'string' ? newVal.split(',') : newVal;
|
||
}
|
||
);
|
||
watch(
|
||
item_answer,
|
||
(newVal) => {
|
||
console.log('item_answer', newVal);
|
||
emit('update:modelValue', newVal);
|
||
},
|
||
{ deep: true }
|
||
);
|
||
|
||
const feedback_click = () => {
|
||
emit('subject_click', 'feedback', props.item);
|
||
};
|
||
|
||
const instance = getCurrentInstance(); // 获取当前组件实例
|
||
|
||
// 切换展开/折叠状态(兼容H5和App)
|
||
const toggleExpand = async () => {
|
||
console.log('切换展开/折叠状态', animationHeight.value, animationState.value);
|
||
if (!animationState.value) {
|
||
animationState.value = true;
|
||
const query = uni.createSelectorQuery().in(instance);
|
||
query
|
||
.select('.collapse-content')
|
||
.boundingClientRect((rect) => {
|
||
animationHeight.value = `${rect.height}px`;
|
||
console.log('rect', rect);
|
||
})
|
||
.exec();
|
||
} else {
|
||
animationState.value = false;
|
||
animationHeight.value = '0px';
|
||
}
|
||
};
|
||
const queryRect = () => {
|
||
// 组件内部一般用this.$uvGetRect,对外的为getRect,二者功能一致,名称不同
|
||
return new Promise((resolve) => {
|
||
this.$uvGetRect(`#${this.elId}`).then((size) => {
|
||
resolve(size);
|
||
});
|
||
});
|
||
};
|
||
const mark_click = () => {
|
||
emit('subject_click', 'mark', props.item);
|
||
};
|
||
// 单选 SN, 多选 MU,判断题 JD,简答 ES
|
||
const qnsTyp = computed(() => props.item.qnsTyp);
|
||
// 正确答案选项
|
||
const correctResultLabel = computed(() =>
|
||
subject_data.value.itemVos.filter((res) => res.correctFlag === 'R').map((res) => res.itemNo)
|
||
);
|
||
// 我的答案选项
|
||
const myResultLabel = computed(() => {
|
||
if (!props.item.anserResult) return ['无'];
|
||
const anserResultArray = props.item.anserResult.split(',');
|
||
return subject_data.value.itemVos.filter((res) => anserResultArray.includes(res.itemId)).map((res) => res.itemNo);
|
||
});
|
||
|
||
// 判断选项是否完全正确
|
||
const right_or_wrong_class = computed(
|
||
() =>
|
||
correctResultLabel.value.length === myResultLabel.value.length &&
|
||
myResultLabel.value.every((res) => correctResultLabel.value.includes(res))
|
||
);
|
||
|
||
const subject_data = computed({
|
||
get: () => {
|
||
return {
|
||
qnsId: props.item.qnsId,
|
||
qnsContent: props.item.qnsContent,
|
||
qnsTyp: props.item.qnsTyp,
|
||
es_status: props.item.es_status,
|
||
analyContent: props.item.analyContent,
|
||
anserResult: props.item.anserResult,
|
||
itemVos: (props.item.itemVos ?? []).map((res) => ({
|
||
...res,
|
||
correctFlag: props.clearResult ? '' : res.correctFlag
|
||
}))
|
||
};
|
||
},
|
||
set: (val) => {}
|
||
});
|
||
|
||
// 语音播放组件小按钮
|
||
const questionButtonClick = (type, submitObj) => {
|
||
console.log('type', type, submitObj);
|
||
// 判断点击的是什么
|
||
if (type === 'complete') {
|
||
// 完成
|
||
} else if (type === 'again') {
|
||
// 重答
|
||
}
|
||
emit('subject_click', type, {
|
||
qnsTyp: props.item.qnsTyp,
|
||
qnsId: props.item.qnsId,
|
||
answer: submitObj,
|
||
answerItems: []
|
||
});
|
||
};
|
||
|
||
// 题类型
|
||
const subject_type_text = computed(() => SUBJECT_TYPE.find((res) => qnsTyp.value === res.value)?.label || '');
|
||
// 点击答题
|
||
const click_option = (analysisVo) => {
|
||
if (props.isPreview || !props.clearResult) return;
|
||
|
||
// 无论什么类型如果表里已经有了,再次点击就是弹出
|
||
if (item_answer.value.includes(analysisVo.itemId)) {
|
||
item_answer.value = item_answer.value.filter((id) => id !== analysisVo.itemId);
|
||
// modelValue.value = item_answer.value;
|
||
if (!['study'].includes(props.mode)) {
|
||
muCheckbox('choose');
|
||
}
|
||
} else if (props.item.qnsTyp === 'SN') {
|
||
// 单选
|
||
// 单选插入,并且移除其他
|
||
item_answer.value[0] = analysisVo.itemId;
|
||
// modelValue.value = item_answer.value;
|
||
muCheckbox('choose');
|
||
} else if (props.item.qnsTyp === 'MU') {
|
||
// 多选题
|
||
// 多选直接插入
|
||
item_answer.value.push(analysisVo.itemId);
|
||
// modelValue.value = item_answer.value;
|
||
if (!['study'].includes(props.mode)) {
|
||
muCheckbox('choose');
|
||
}
|
||
} else if (props.item.qnsTyp === 'JD') {
|
||
// 判断题
|
||
// 判断插入,并且移除其他
|
||
item_answer.value = [analysisVo.itemId];
|
||
// modelValue.value = item_answer.value;
|
||
muCheckbox('choose');
|
||
}
|
||
};
|
||
|
||
const muBtnClick = () => {
|
||
if (item_answer.value.length === 0) {
|
||
common.msg('请至少选择一个选项!');
|
||
return;
|
||
}
|
||
muCheckbox('answer');
|
||
};
|
||
|
||
const muCheckbox = (type = 'answer') => {
|
||
emit('subject_click', type, {
|
||
qnsTyp: props.item.qnsTyp,
|
||
qnsId: props.item.qnsId,
|
||
answer: item_answer.value,
|
||
answerItems: subject_data.value.itemVos.filter((t) => item_answer.value.includes(t.itemId))
|
||
});
|
||
};
|
||
|
||
// 初始化动画
|
||
onMounted(() => {});
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.expand_button_div {
|
||
/* 相对定位,让两个文本重叠 */
|
||
// display: inline-block;
|
||
// cursor: pointer;
|
||
}
|
||
.toggle-container {
|
||
position: relative;
|
||
height: 42rpx;
|
||
width: 60rpx;
|
||
font-size: 30rpx;
|
||
line-height: 42rpx;
|
||
}
|
||
.toggle-text {
|
||
/* 绝对定位使文本重叠 */
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
/* 初始状态:非激活文本透明且轻微下移 */
|
||
opacity: 0;
|
||
// transform: translateY(15rpx);
|
||
/* 添加过渡动画 */
|
||
transition: all 0.2s ease-out;
|
||
/* 确保文本占据空间一致 */
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* 激活状态:文本可见且位置正常 */
|
||
.toggle-text.active {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
z-index: 1; /* 确保激活文本在上方 */
|
||
}
|
||
|
||
.collapse_wrapper {
|
||
overflow: hidden; /* 关键:隐藏超出部分 */
|
||
// height: 0; /* 初始高度为0 */
|
||
transition: height 0.3s ease-out;
|
||
}
|
||
.click_text_icon_action {
|
||
transform: rotate(-90deg);
|
||
}
|
||
.click_text_icon {
|
||
transition: transform 0.3s ease-out;
|
||
}
|
||
|
||
.mark_and_feedback_div {
|
||
display: flex;
|
||
height: 100%;
|
||
justify-content: end;
|
||
align-items: center;
|
||
|
||
.img {
|
||
width: 38rpx;
|
||
height: 38rpx;
|
||
margin-left: 4rpx;
|
||
padding: 4rpx;
|
||
}
|
||
}
|
||
.expand_button_div {
|
||
display: flex;
|
||
align-items: center;
|
||
width: 90rpx;
|
||
|
||
justify-content: space-between;
|
||
}
|
||
.option_div {
|
||
padding-top: 20rpx;
|
||
}
|
||
.option_div_button {
|
||
width: 100%;
|
||
height: 84rpx;
|
||
padding-top: 30rpx;
|
||
// background-color: red;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
.analysis_div {
|
||
.analysis_title {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin: 32rpx 0 0 0;
|
||
|
||
.success_answer {
|
||
}
|
||
|
||
.your_answer {
|
||
.success {
|
||
color: #15b859;
|
||
}
|
||
.error {
|
||
color: #f5212d;
|
||
}
|
||
}
|
||
}
|
||
|
||
.analysis_note {
|
||
font-family: PingFangSC;
|
||
font-weight: 500;
|
||
font-size: 30rpx;
|
||
color: #666666;
|
||
line-height: 48rpx;
|
||
text-align: left;
|
||
font-style: normal;
|
||
margin-top: 28rpx;
|
||
}
|
||
.reference_answer {
|
||
.reference_answer_title {
|
||
font-weight: 700;
|
||
font-size: 30rpx;
|
||
color: #333333;
|
||
text-align: left;
|
||
line-height: 30rpx;
|
||
margin: 30rpx 0;
|
||
}
|
||
font-weight: 400;
|
||
font-size: 30rpx;
|
||
color: #666666;
|
||
line-height: 48rpx;
|
||
text-align: left;
|
||
}
|
||
}
|
||
.item {
|
||
// height: 170rpx;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding-bottom: 40rpx;
|
||
|
||
.top {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.type {
|
||
width: 108rpx;
|
||
height: 46rpx;
|
||
background: #267eff;
|
||
border-radius: 8rpx;
|
||
font-family: PingFangSC;
|
||
font-weight: 500;
|
||
font-size: 25rpx;
|
||
color: #ffffff;
|
||
text-align: center;
|
||
line-height: 46rpx;
|
||
}
|
||
|
||
.score {
|
||
margin-left: 24rpx;
|
||
font-family: ArialMT;
|
||
font-size: 30rpx;
|
||
color: #333333;
|
||
text-align: left;
|
||
}
|
||
|
||
.right {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.right_text {
|
||
font-family: PingFangSC;
|
||
font-weight: 400;
|
||
font-size: 28rpx;
|
||
color: #333333;
|
||
line-height: 40px;
|
||
text-align: left;
|
||
font-style: normal;
|
||
}
|
||
}
|
||
}
|
||
|
||
.question_text {
|
||
font-family: PingFangSC;
|
||
font-weight: 500;
|
||
font-size: 30rpx;
|
||
color: #333333;
|
||
line-height: 48rpx;
|
||
text-align: left;
|
||
font-style: normal;
|
||
margin-top: 20rpx;
|
||
}
|
||
}
|
||
</style>
|