vue实现单选做题效果详解
最近在做一个答题项目,废了好多时间,经过折腾和在大佬的指导下,终于清晰的了解逻辑,如果你也遇到此类问题,不妨先看看我的思路,看完点个赞,那是继续跟新的动力
此案例是使用swiper 以及vue 来完成的。
需求分析,当点击了正确答案,选项背景色成指定的颜色,并且有一个打勾的效果,不能在选择其他选项,错误选项背景色改成对应的颜色,打上一个小叉,答对了自动切换到下一个题目,点击选项显示对应的答案及其解析。
主要涉及的知识点,vue 动态绑定class ,条件渲染,话不多说,首先先来看看效果
先详解在,在上源码,
先看布局,这里可以自己根据需求来布局
<div v-cloak id='app' class="recommendPage">
<div class="swiper-container">
<div class="swiper-wrapper">
<div class="swiper-slide" v-for="(item, index) in tmList" :key="index">
<div class="box">
<van-divider class="qes-num"> {{ index + 1 }}/{{tmList.length}}</van-divider>
<p class="title">{{item.title}}</p>
<div class="anlist">
<ul>
<li :class="{
'success-active': item.userAnswer === subIndex,
'error-active':item.userAnswer!==item.correct && item.userAnswer === subIndex}"
v-for="(sub, subIndex) in item.answer" :key="sub.id" class="lis"
@click="chechan(item,sub, index,subIndex)">
{{ sub }}
</li>
</ul>
</div>
<div v-if="isShowAnswer===index||item.userAnswer!==-1">
<div class="mar20">
正确答案: <span class="redcol"> {{ item.answer[item.correct] }}</span>
</div>
<div class="mar20 pa20">解析: {{ item.content }}</div>
</div>
</div>
</div>
</div>
</div>
再看逻辑
<script>
const app = new Vue({
el: "#app",
data() {
return {
swiper: {},
isShowAnswer: -1,// 控制是否显示解析
isActive: -1,
tmList: [
{
id: 1,
title: '1、1947年12月,毛泽东在《目前形势和我们的任务》中总结出十大军事原则,其核心是( )',
correct: 0,
answer: [
'A.集中优势兵力,各个歼灭敌人',
'B.诱敌深入,积极防御',
'C.多种作战形式,适时转变',
'D.避其主力,打其虚弱'
],
userAnswer: -1,
content: "毛泽东于1947年12月25日在中共中央召开的会议上所做《目前形势和我们的任务》的报告中正式提出十大军事原则。十大军事原则是一个有机的整体,其核心是强调集中优势兵力打歼灭战",
},
{
id: 2,
title: '2、中国宋代哲学家朱熹提出“理在事先”,把“天理”说成是世界万物的本原。这是一种( )',
correct: 1,
answer: ['A.主观唯心主义观点', 'B.客观唯心主义观点', 'C.朴素唯物主义观点', 'D.形而上学唯物主义观点'],
userAnswer: -1,
content: "主观唯心主义认为物质是某种主观精神的产物,客观唯心主义将物质归结为某种客观精神的产物。结合题干可得,“理在事先”这种“天理”观属于客观唯心主义。",
},
{
id: 3,
title: '3、毛泽东在《新民主主义论》中提出的新民主主义文化纲领是( )',
correct: 2,
answer: [
'A.为抗战服务',
'B.为工农兵服务',
'C.民族的、科学的、大众的文化',
'D.古为今用,洋为中用'
],
userAnswer: -1,
content: "新民主主义的文化就是“无产阶级领导的,人民大众的,反帝反封建的文化”。这种文化是无产阶级领导的民族的、科学的、大众的文化。",
},
{
id: 4,
title: '4、下列选项中,强调外因在事物发展中起着重要作用的是( )',
correct: 2,
answer: [
'A.出淤泥而不染',
'B.威武不能屈,贫贱不能移',
'C.近朱者赤,近墨者黑',
'D.常在河边走,就是不湿鞋'
],
userAnswer: -1,
content: "在因果联系中,内因是事物变化的根据,外因是事物变化的条件。“近朱者赤,近墨者黑”强调的是外因在事物变化发展中的重要作用。",
}
],
}
},
methods: {
chechan(v1, v2, index, index1) {
if (v1.userAnswer !== -1) return // 代表已经答过了
this.isActive = index1//选中当前选项
this.isShowAnswer = index //选完显示答案和解析
v1.userAnswer = index1; //此处非常关键,已经改变了 tmList数组
if (index1 === v1.correct) {
this.nextto();
}
console.log("点击的答案" + v1, "正确答案" + `${v2}`, "第" + `${index + 1}` + "题", `点击了第${index + 1}题的第${index1 + 1}答案`);
//判断最后一个 此判断只适用于最后一个题目的答案 和没一道题的 选项唯一 根据自己的业务判断最后一个题
let clicklast = v1.content;
let arrlast = this.tmList[this.tmList.length-1].content;
if(clicklast==arrlast){
alert("恭喜你完成答题")
}
},
nextto() {
//获取当前索引
let thisindex = this.swiper.activeIndex;
this.swiper.slideTo(thisindex + 1, 1000, false);
},
},
mounted() {
let that = this;
that.swiper = new Swiper('.swiper-container', {
initialSlide: 0, //默认显示
autoplay:false,//等同于以下设置
on: {
touchStart: function () {
// alert(this.activeIndex);//切换结束时,告诉我现在是第几个slide
},
},
})
},
created() {
}
});
</script>
isShowAnswer===index||item.userAnswer!==-1
首先isShowAnswer 默认值为-1.index就是当前题目的下标,只有当isShowAnswer和下标相等的时候,就显示,所以在点击事件中只需要将isShowAnswer 与当前题目的下标相等即可。this.isShowAnswer = index ,显然这样是可以显示对应的题目的解析答案,当我们切下一个题目的时候,在滑动回来上一题,刚才显示的答案又被影藏了,显然是下标不等了,观察数据结构,每一个题目中都有一个userAnswer: -1, 的字段,那么我们点击选项的时候,我们就将这个值变成点击的答案的下标,v1.userAnswer = index1; 往上自己了解这一句,这一句非常关键,这里的参数v1 也就是题目的每一条数据,神奇的是,你可以通过打印,如果你这样赋值之后,加入我们点击的是第一道题目的第一个选项,题目数组中的第一条数据将变成
{
id: 1,
title: '1、1947年12月,毛泽东在《目前形势和我们的任务》中总结出十大军事原则,其核心是( )',
correct: 0,
answer: [
'A.集中优势兵力,各个歼灭敌人',
'B.诱敌深入,积极防御',
'C.多种作战形式,适时转变',
'D.避其主力,打其虚弱'
],
userAnswer: 0,
content: "毛泽东于1947年12月25日在中共中央召开的会议上所做《目前形势和我们的任务》的报告中正式提出十大军事原则。十大军事原则是一个有机的整体,其核心是强调集中优势兵力打歼灭战",
userAnswer: 0,
所以我们在加上一个条件 ||item.userAnswer!==-1 ,这样当我们做过题目之后,每条数据的userAnswer 字段经等于点击答案的下标,不在是-1,从而只要我们答题过后,切换回来,刚才显示的答案依然还是显示出来。
- 2.再看正确答案标色
我们给每一个答案都动态绑定一个一个样式
:class="{
'success-active': item.userAnswer === subIndex,
'error-active':item.userAnswer!==item.correct && item.userAnswer === subIndex}"
v-for="(sub, subIndex) in item.answer" :
从字面上不难看出,一个是正确的样式,一个是错误的样式。
item.userAnswer === subIndex
从点击事件中不难看出 v1.userAnswer = index1; 点击了哪个答案就让他 添加上success-active 的样式,那有的小伙伴想。万一我点击的是错误的答案呢,不要着急,仔细看error-active 它还需要&& 一个条件才成立item.userAnswer!==item.correct 从数据结构中不难看出
{
id: 1,
title: '1、1947年12月,毛泽东在《目前形势和我们的任务》中总结出十大军事原则,其核心是( )',
correct: 0,
answer: [
'A.集中优势兵力,各个歼灭敌人',
'B.诱敌深入,积极防御',
'C.多种作战形式,适时转变',
'D.避其主力,打其虚弱'
],
userAnswer: -1,
content: "毛泽东于1947年12月25日在中共中央召开的会议上所做《目前形势和我们的任务》的报告中正式提出十大军事原则。十大军事原则是一个有机的整体,其核心是强调集中优势兵力打歼灭战",
},
correct字段标识正确答案的选项,0就是`answe`r中的第一项,那表示第一个就是正确答案
所以取值正确答案即为 正确答案: <span class="redcol"> {{ item.answer[item.correct] }}</span>
比如第一题 ,当我们本来正确答案是A,假如我蛮点击了B,那此时 userAnswer 是不是等于了1 ,1不等于correct 是不是代表我们选择错误的答案。所以只要保证item.userAnswer!==item.correct && item.userAnswer === subIndex 说明我们点击的就是错误的。
最后再看,
if (v1.userAnswer !== -1) return
本来每一个数组中的每一条数据userAnswer 都是1-只要点击过后,userAnswer 就等于点击的答案下标,所以下一次点击的时候,表示这个题目已点击过了,直接return 不在往下执行。
最后说一下打勾打叉。其实就是条件添加上对用的class 之后,我们给当前类添加一个伪类样式即可。
.anlist .success-active::before{
position: absolute;
content: "?";
top: 8px;
left: 2px;
font-size: 25px;
color: #80e4ba;
}
.anlist .error-active::before{
position: absolute;
content: "×";
top: 8px;
left: 2px;
font-size: 35px;
color: #80e4ba;
}
是不是没有想象中的那么复杂。
不是到这里就完了,一些小伙伴会问。我们后端返回的数据结构不是这样的呀,里面就没有userAnswer=-1的字段呀 ,那这个就很简单了,拿到数据遍历。像每一条数据中增加这样的一个字段,那不就完事了。然后还有一种可能,correct 正确答案给的不是下标那怎么办,举个例子
{
id: 3,
title: '3、毛泽东在《新民主主义论》中提出的新民主主义文化纲领是( )',
correct: 2,
answer: [
'A.为抗战服务',
'B.为工农兵服务',
'C.民族的、科学的、大众的文化',
'D.古为今用,洋为中用'
],
userAnswer: -1,
content: "新民主主义的文化就是“无产阶级领导的,人民大众的,反帝反封建的文化”。这种文化是无产阶级领导的民族的、科学的、大众的文化。",
},
此题目不难看出正确答案就是C 如果后端给的数据书correct:'C.民族的、科学的、大众的文化' 那也简单,拿correct 去answer 数组中匹配,找到对应的下标即可。
总一,万变不离其中,你难道后台数据,将你的数据格式处理成上面的格式完事,无非就是遍历,追加字段。匹配一系列问题,差不多都是数组对象的简单玩法
重要事情说三遍,写了我一个小时,做个好人
**点赞** **点赞** **点赞**
*唯一跟新的动力*
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
<link rel="stylesheet" href="https://unpkg.com/swiper@6.8.4/swiper-bundle.min.css">
<script src="https://unpkg.com/swiper@6.8.4/swiper-bundle.min.js"> </script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vant@2.12/lib/index.css"/>
<script src="https://cdn.jsdelivr.net/npm/vant@2.12/lib/vant.min.js"></script>
<title>单选做题</title>
<style>
* {
padding: 0;
margin: 0;
}
[v-cloak]{
display: none
}
#app{
width: 100%;
height: 100%;
overflow: scroll;
}
ul li {
list-style: none;
}
/* swiper */
.swiper-container {
width: 100vw;
height: 100vh;
background: #fff;
}
.swiper-container .swiper-slide {
width: 100%;
height: 100%;
background: #fff;
color: #000;
font-size: 16px;
}
/* swiper */
.box {
width: 94%;
margin: 0 auto;
}
.qes-num {
margin-left: 8px;
}
.anlist {
margin-top: 15px;
}
.anlist ul li {
padding: 10px 0px;
background: #f6f6f6;
margin: 5px;
border-radius: 7px;
text-indent: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.anlist .success-active {
background: #021a02ad;
color: #fff;
position: relative;
}
.anlist .success-active::before{
position: absolute;
content: "?";
top: 8px;
left: 2px;
font-size: 25px;
color: #80e4ba;
}
.anlist .error-active {
background: #c15a5a;
color: #fff;
position: relative;
}
.anlist .error-active::before{
position: absolute;
content: "×";
top: 8px;
left: 2px;
font-size: 35px;
color: #80e4ba;
}
.van-radio {
margin: 10px 0px;
}
.redcol {
color: red;
}
.mar20 {
margin: 20px auto;
}
.pa20{
background: #7c937014;
padding: 10px;
border-radius: 10px;
font-size: 14px;
color: #999;
line-height: 28px;
}
</style>
</head>
<body>
<div v-cloak id='app' class="recommendPage">
<div class="swiper-container">
<div class="swiper-wrapper">
<div class="swiper-slide" v-for="(item, index) in tmList" :key="index">
<div class="box">
<van-divider class="qes-num"> {{ index + 1 }}/{{tmList.length}}</van-divider>
<p class="title">{{item.title}}</p>
<div class="anlist">
<ul>
<li :class="{
'success-active': item.userAnswer === subIndex,
'error-active':item.userAnswer!==item.correct && item.userAnswer === subIndex}"
v-for="(sub, subIndex) in item.answer" :key="sub.id" class="lis"
@click="chechan(item,sub, index,subIndex)">
{{ sub }}
</li>
</ul>
</div>
<div v-if="isShowAnswer===index||item.userAnswer!==-1">
<div class="mar20">
正确答案: <span class="redcol"> {{ item.answer[item.correct] }}</span>
</div>
<div class="mar20 pa20">解析: {{ item.content }}</div>
</div>
</div>
</div>
</div>
</div>
</body>
<script>
const app = new Vue({
el: "#app",
data() {
return {
swiper: {},
isShowAnswer: -1,// 控制是否显示解析
isActive: -1,
tmList: [
{
id: 1,
title: '1、1947年12月,毛泽东在《目前形势和我们的任务》中总结出十大军事原则,其核心是( )',
correct: 0,
answer: [
'A.集中优势兵力,各个歼灭敌人',
'B.诱敌深入,积极防御',
'C.多种作战形式,适时转变',
'D.避其主力,打其虚弱'
],
userAnswer: -1,
content: "毛泽东于1947年12月25日在中共中央召开的会议上所做《目前形势和我们的任务》的报告中正式提出十大军事原则。十大军事原则是一个有机的整体,其核心是强调集中优势兵力打歼灭战",
},
{
id: 2,
title: '2、中国宋代哲学家朱熹提出“理在事先”,把“天理”说成是世界万物的本原。这是一种( )',
correct: 1,
answer: ['A.主观唯心主义观点', 'B.客观唯心主义观点', 'C.朴素唯物主义观点', 'D.形而上学唯物主义观点'],
userAnswer: -1,
content: "主观唯心主义认为物质是某种主观精神的产物,客观唯心主义将物质归结为某种客观精神的产物。结合题干可得,“理在事先”这种“天理”观属于客观唯心主义。",
},
{
id: 3,
title: '3、毛泽东在《新民主主义论》中提出的新民主主义文化纲领是( )',
correct: 2,
answer: [
'A.为抗战服务',
'B.为工农兵服务',
'C.民族的、科学的、大众的文化',
'D.古为今用,洋为中用'
],
userAnswer: -1,
content: "新民主主义的文化就是“无产阶级领导的,人民大众的,反帝反封建的文化”。这种文化是无产阶级领导的民族的、科学的、大众的文化。",
},
{
id: 4,
title: '4、下列选项中,强调外因在事物发展中起着重要作用的是( )',
correct: 2,
answer: [
'A.出淤泥而不染',
'B.威武不能屈,贫贱不能移',
'C.近朱者赤,近墨者黑',
'D.常在河边走,就是不湿鞋'
],
userAnswer: -1,
content: "在因果联系中,内因是事物变化的根据,外因是事物变化的条件。“近朱者赤,近墨者黑”强调的是外因在事物变化发展中的重要作用。",
}
],
}
},
methods: {
chechan(v1, v2, index, index1) {
if (v1.userAnswer !== -1) return // 代表已经答过了
this.isActive = index1//选中当前选项
this.isShowAnswer = index //选完显示答案和解析
v1.userAnswer = index1; //此处非常关键,已经改变了 tmList数组
if (index1 === v1.correct) {
this.nextto();
}
console.log("点击的答案" + v1, "正确答案" + `${v2}`, "第" + `${index + 1}` + "题", `点击了第${index + 1}题的第${index1 + 1}答案`);
//判断最后一个 此判断只适用于最后一个题目的答案 和没一道题的 选项唯一 根据自己的业务判断最后一个题
let clicklast = v1.content;
let arrlast = this.tmList[this.tmList.length-1].content;
if(clicklast==arrlast){
alert("恭喜你完成答题")
}
},
nextto() {
//获取当前索引
let thisindex = this.swiper.activeIndex;
this.swiper.slideTo(thisindex + 1, 1000, false);
},
},
mounted() {
let that = this;
that.swiper = new Swiper('.swiper-container', {
initialSlide: 0, //默认显示
autoplay:false,//等同于以下设置
on: {
touchStart: function () {
// alert(this.activeIndex);//切换结束时,告诉我现在是第几个slide
},
},
})
},
created() {
}
});
</script>
</html>
|