Files
tra-app/src/components/examination/subject.vue
T
2025-08-27 21:04:34 +08:00

405 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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 ? '#0066FF' : '#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="right_text" @click="toggleExpand()">展开</view>
<uv-icon class="arrow-icon" name="arrow-right" color="#0066FF"></uv-icon>
</template>
</view>
</view>
<view class="question_text">
{{ subject_data.qnsContent }}
</view>
<view :style="props.mode === 'mistake' ? animationStyle : {}" class="collapse-wrapper" 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"
:activeVoiceId="subject_data.qnsId"
@buutton_click="questionBuuttonClick"
></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="muCheckbox('answer')"
:hairline="false"
>
确定
</uv-button>
</view>
<view
class="analysis_div"
v-if="!clearResult && ['study', 'mistake', 'settlement', 'practice'].includes(props.mode)"
>
<view class="analysis_title">
<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="analysis_note">题目解析{{ subject_data?.analyContent }}</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick, computed, getCurrentInstance } from 'vue';
import radioSubjectItem from './radio-subject-item.vue';
import questionVue from './question.vue';
import { markIcon, feedbackIcon } from '@/common/imgSvg';
import { SUBJECT_TYPE } from '@/enum.js';
// 多选的确定按钮的样式
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
return ['examination', 'practice', 'mistake', 'study', 'settlement'].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 animationStyle = ref({
height: '0px',
overflow: 'hidden',
transition: 'height 0.3s ease-in-out'
});
const modelValue = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
});
// result.anserResult
let answerText = props.item?.my_answer || props.item.anserResult || [];
// item_answer 是答题结果,可以是对象 数组 字符串类型
const item_answer = ref(typeof answerText === 'string' ? answerText.split(',') : answerText);
const feedback_click = () => {
emit('subject_click', 'feedback', props.item);
};
const instance = getCurrentInstance(); // 获取当前组件实例
// 切换展开/折叠状态(兼容H5和App)
const toggleExpand = async () => {
console.log('切换展开/折叠状态');
const query = uni.createSelectorQuery().in(instance);
query
.select('.collapse-content')
.boundingClientRect((rect) => {
console.log('rect', rect);
})
.exec();
};
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 questionBuuttonClick = (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: item_answer.value,
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);
if (!['study'].includes(props.mode)) {
muCheckbox('choose');
}
} else if (props.item.qnsTyp === 'SN') {
// 单选
// 单选插入,并且移除其他
item_answer.value[0] = analysisVo.itemId;
muCheckbox('choose');
} else if (props.item.qnsTyp === 'MU') {
// 多选题
// 多选直接插入
item_answer.value.push(analysisVo.itemId);
if (!['study'].includes(props.mode)) {
muCheckbox('choose');
}
} else if (props.item.qnsTyp === 'JD') {
// 判断题
// 判断插入,并且移除其他
item_answer.value = [analysisVo.itemId];
muCheckbox('choose');
}
modelValue.value = item_answer.value;
};
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(() => {
// // 创建动画实例
// animation = uni.createAnimation({
// duration: 300,
// timingFunction: 'ease-in-out',
// delay: 0
// });
});
</script>
<style scoped lang="scss">
.collapse-wrapper {
// overflow: hidden; /* 关键:隐藏超出部分 */
// height: 0; /* 初始高度为0 */
// transition: height 0.3s ease-in-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;
}
}
.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 28rpx 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;
}
}
.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>