提交错题本

This commit is contained in:
田岩
2025-08-30 10:20:16 +08:00
parent 2ee3099e6d
commit c4841aaa78
30 changed files with 1494 additions and 570 deletions
+44 -26
View File
@@ -3,20 +3,25 @@
<view class="line">
<text class="tr">维度评分</text>
<text class="td">{{ dimension_all_score }}</text>
<view class="td_success_error_img">
<image :src="select_status_icon" class="img"></image>
</view>
</view>
<view class="line" v-for="item in evalSettingVos">
<text class="tr">{{ item.gradeDimens }}</text>
<text class="td">{{ item.anlyGrade }}</text>
<view class="your_answer" v-if="item.dimensCode==='01'">
<view class="your_answer" v-if="item.dimensCode === '01'">
<rich-text :nodes="highlightedText" class="content" />
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick, computed, toRaw, watch } from 'vue';
import { optionErrorIcon, optionSuccessIcon } from '@/common/imgSvg';
const emit = defineEmits(['click_option']);
const props = defineProps({
value: {
@@ -37,27 +42,30 @@
});
const evalSettingVos = computed(() => props.value.evalSettingVos);
const status = computed(() => (props.judgeResult ? 'success' : 'error'));
const keywords = computed(() => {
if (!props.value.relKeyword) return [];
return props.value.relKeyword.split(',').map(key => key.trim());
const select_status_icon = computed(() => {
if (props.judgeResult) {
return optionSuccessIcon('#15B859');
} else {
return optionErrorIcon();
}
});
const keywords = computed(() => {
if (!props.value.relKeyword) return [];
return props.value.relKeyword.split(',').map((key) => key.trim());
});
const highlightedText = computed(() => {
// 如果没有关键词,直接返回原文本
if (!keywords.value.length) return props.answerText;
// 构建正则表达式,匹配所有关键词(不区分大小写)
// 对关键词进行转义,避免特殊字符影响正则
const escapedKeywords = keywords.value.map(keyword =>
keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
);
// 创建正则表达式,g表示全局匹配,i表示不区分大小写
const regex = new RegExp(`(${escapedKeywords.join('|')})`, 'gi');
// 替换匹配到的关键词,用span包裹并添加高亮样式
return props.answerText.replace(regex, (match) =>
`<span class="highlight">${match}</span>`
);
// 如果没有关键词,直接返回原文本
if (!keywords.value.length) return '你的答案:' + props.answerText;
// 构建正则表达式,匹配所有关键词(不区分大小写)
// 对关键词进行转义,避免特殊字符影响正则
const escapedKeywords = keywords.value.map((keyword) => keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
// 创建正则表达式,g表示全局匹配,i表示不区分大小写
const regex = new RegExp(`(${escapedKeywords.join('|')})`, 'gi');
// 替换匹配到的关键词,用span包裹并添加高亮样式
return '你的答案:' + props.answerText.replace(regex, (match) => `<span class="highlight">${match}</span>`);
});
const dimension_all_score = computed(() => {
return (
@@ -77,7 +85,6 @@
</script>
<style scoped lang="scss">
.success {
background-color: #e7f8ed;
.td {
@@ -96,12 +103,12 @@
color: #f5212d;
}
}
.your_answer{
.your_answer {
:deep(.highlight) {
text-decoration: underline;
}
}
.analysis_div {
margin-top: 30rpx;
@@ -109,6 +116,7 @@
padding: 22rpx 15rpx 15rpx 34rpx;
.line {
margin: 12rpx 0;
position: relative;
}
.tr {
font-weight: 600;
@@ -119,13 +127,23 @@
font-weight: 600;
font-size: 30rpx;
}
.your_answer{
.your_answer {
font-weight: 500;
font-size: 30rpx;
color: #333333;
line-height: 40rpx;
line-height: 46rpx;
text-align: left;
margin-top: 12rpx;
}
.td_success_error_img {
position: absolute;
right: 0;
top: 0;
.img {
width: 30rpx;
height: 30rpx;
padding-right: 20rpx;
}
}
}
</style>
+27 -7
View File
@@ -32,10 +32,18 @@
</view>
<view class="text-btn" v-if="!textShow" @click.stop="translateVoice()"></view>
<view class="fl1"></view>
<view v-show="!props.isPreview && props.button_again_show" class="btns-cont-button-text" @click.stop="buutton_click('again')">
<view
v-show="!props.isPreview && props.button_again_show"
class="btns-cont-button-text"
@click.stop="buutton_click('again')"
>
重答
</view>
<view v-show="!props.isPreview && props.button_complete_show" class="btns-cont-button-text" @click.stop="buutton_click('complete')">
<view
v-show="!props.isPreview && props.button_complete_show"
class="btns-cont-button-text"
@click.stop="buutton_click('complete')"
>
完成
</view>
</view>
@@ -49,8 +57,16 @@
</view>
<view class="btns-cont" v-show="!props.isPreview">
<view class="fl1"></view>
<view v-if="props.button_again_show" class="btns-cont-button-text" @click.stop="buutton_click('again')">重答</view>
<view v-if="props.button_complete_show" class="btns-cont-button-text" @click.stop="buutton_click('complete')">完成</view>
<view v-if="props.button_again_show" class="btns-cont-button-text" @click.stop="buutton_click('again')">
重答
</view>
<view
v-if="props.button_complete_show"
class="btns-cont-button-text"
@click.stop="buutton_click('complete')"
>
完成
</view>
</view>
</view>
<view class="talking-info talking-info-loading" v-else-if="status === '00'">
@@ -62,7 +78,7 @@
</template>
<script setup>
import { computed, unref, ref, toRefs, watch, onMounted } from 'vue';
import { computed, unref, ref, toRefs, watch, onMounted, nextTick } from 'vue';
import { onUnload } from '@dcloudio/uni-app';
import { useAudioStore } from '@/store/audioStore';
const props = defineProps({
@@ -105,9 +121,8 @@
// 是否显示重答按钮
default: true,
type: Boolean
},
}
});
console.log('props.isPreview', props.isPreview);
const audioStore = useAudioStore();
const { dataInfo } = toRefs(props);
const status = computed(() => props.status);
@@ -126,6 +141,11 @@
// 按钮点击
const buutton_click = (type) => {
if (type === 'again') {
nextTick(() => {
textShow.value = true;
});
}
emit('buutton_click', type, props.dataInfo);
};
+86 -22
View File
@@ -16,16 +16,28 @@
<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>
<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="props.mode === 'mistake' ? animationStyle : {}" class="collapse-wrapper" ref="wrapperRef">
<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'">
@@ -95,7 +107,7 @@
</view>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick, computed, getCurrentInstance } from 'vue';
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';
@@ -149,11 +161,10 @@
const wrapperRef = ref(null);
const contentRef = ref(null);
const judgeResultStatus = computed(() => props.item.judgeResult === 'P');
const animationStyle = ref({
height: '0px',
overflow: 'hidden',
transition: 'height 0.3s ease-in-out'
});
// 不是错题本就默认不折叠,错题本默认折叠
const animationState = ref(props.mode !== 'mistake' ? true : false);
const animationHeight = ref(props.mode !== 'mistake' ? '' : '0px');
const modelValue = computed({
get: () => props.modelValue,
@@ -172,14 +183,21 @@
// 切换展开/折叠状态(兼容H5和App)
const toggleExpand = async () => {
console.log('切换展开/折叠状态');
const query = uni.createSelectorQuery().in(instance);
query
.select('.collapse-content')
.boundingClientRect((rect) => {
console.log('rect', rect);
})
.exec();
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,二者功能一致,名称不同
@@ -204,6 +222,7 @@
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(
() =>
@@ -285,16 +304,54 @@
answerItems: subject_data.value.itemVos.filter((t) => item_answer.value.includes(t.itemId))
});
};
// 初始化动画
onMounted(() => {});
</script>
<style scoped lang="scss">
.collapse-wrapper {
// overflow: hidden; /* 关键:隐藏超出部分 */
.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-in-out;
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 {
@@ -310,6 +367,13 @@
padding: 4rpx;
}
}
.expand_button_div {
display: flex;
align-items: center;
width: 90rpx;
justify-content: space-between;
}
.option_div {
padding-top: 20rpx;
}