记录前端菜鸟的日常——实现类似学习通的答题界面

一、总体介绍

1.先来看一下答题界面

题目前会展示当前题目类型和题目分数以及当前题数,左下角可以查看当前答题情况。后端返回的数据是一个大数组,每一项包含题目、题目类型、所有答案选项(最多四个)、题目code、题目分值、正确答案。

只有最后一页和作答情况面板里展示交卷按钮,第一页的上一题按钮不可点,未作答完就给出提示,最后提交试卷展示分数。并且支持左右滑动切换题目。

2.先来缕一下整体思路

从后端拿到的数据questionList我们分为三个小数组,并给每一题都配置一个checked属性,记录当前题目有没有作答。

在第一张图里展示的题目就是用的questionList,第二张图使用的是三个小数组,小数组点击通过遍历唯一值questionCode定位到大数组中。(第一张图不使用小数组:a.需要展示当前题号/总题目数 b.后端返回的数据就是单选=>多选=>判断排列的 c.三个小数组每次都得从0开始,作答情况面板不好实现点击定位。)

响应式变量stemNum来记录当前作答题目是questionList中的哪一道,radio数组来记录当前的作答情况。

除此之外就是左右滑动、点击提交加防抖节流等等细节问题。

二、代码

1.先来看一下模版

复制代码
 <div class="answerExam" @touchstart.passive="handleTouchStart" @touchmove.passive="handleTouchMove"
      @touchend.passive="handleTouchEnd">
      <div class="topic-con">
        <div class="stem">
          <div>
            <span class="type">{{
              questionList[stemNum]?.questionType == '1'
                ? "单选题"
                : questionList[stemNum]?.questionType == '2'
                  ? "多选题"
                  : "判断题"
            }}</span>
            <span class="score">
              {{ questionList[stemNum]?.questionType == '2' ? `每题至少两个答案,全部选对为${questionList[stemNum]?.questionScore}分` :
                `每题${questionList[stemNum]?.questionScore}分` }}
            </span>
          </div>
          <div>
            <span class="num">{{ stemNum + 1 }}</span><span class="sum">/{{ questionList.length }}</span>
          </div>
        </div>
        <div class="single" v-if="questionList[stemNum]?.questionType == '1'">
          <p>
            {{ questionList[stemNum].questionTitle }}
          </p>
          <van-radio-group style="padding: 12px;" @change="select()" v-model="radio[stemNum]">
            <van-radio name="A">
              <template #icon="props">
                <span :class="props.checked ? 'checked' : 'nochecked'">A</span>
              </template>
              {{ questionList[stemNum].optionA }}</van-radio>
            <van-radio name="B">
              <template #icon="props">
                <span :class="props.checked ? 'checked' : 'nochecked'">B</span>
              </template>
              {{ questionList[stemNum].optionB }}
            </van-radio>
            <van-radio name="C">
              <template #icon="props">
                <span :class="props.checked ? 'checked' : 'nochecked'">C</span>
              </template>
              {{ questionList[stemNum].optionC }}
            </van-radio>
            <van-radio name="D">
              <template #icon="props">
                <span :class="props.checked ? 'checked' : 'nochecked'">D</span>
              </template>
              {{ questionList[stemNum].optionD }}
            </van-radio>
          </van-radio-group>
        </div>
        <div class="multiple" v-if="questionList[stemNum]?.questionType == '2'">
          <p>{{ questionList[stemNum].questionTitle }}</p>
          <van-checkbox-group @change="select()" v-model="radio[stemNum]">
            <van-checkbox name="A">
              <template #icon="props">
                <span :class="props.checked ? 'checked' : 'nochecked'">A</span>
              </template>
              {{ questionList[stemNum].optionA }}
            </van-checkbox>
            <van-checkbox name="B">
              <template #icon="props">
                <span :class="props.checked ? 'checked' : 'nochecked'">B</span>
              </template>
              {{ questionList[stemNum].optionB }}
            </van-checkbox>
            <van-checkbox name="C">
              <template #icon="props">
                <span :class="props.checked ? 'checked' : 'nochecked'">C</span>
              </template>
              {{ questionList[stemNum].optionC }}
            </van-checkbox>
            <van-checkbox name="D">
              <template #icon="props">
                <span :class="props.checked ? 'checked' : 'nochecked'">D</span>
              </template>
              {{ questionList[stemNum].optionD }}
            </van-checkbox>
          </van-checkbox-group>
        </div>
        <div class="judge" v-if="questionList[stemNum]?.questionType == '3'">
          <p>{{ questionList[stemNum].questionTitle }}</p>
          <van-radio-group v-model="radio[stemNum]" @change="select()">
            <van-radio name="A">
              <template #icon="props">
                <span :class="props.checked ? 'checked' : 'nochecked'"></span>
              </template>
              {{ questionList[stemNum].optionA }}
            </van-radio>
            <van-radio name="B">
              <template #icon="props">
                <span :class="props.checked ? 'checked1' : 'nochecked1'"></span>
              </template>
              {{ questionList[stemNum].optionB }}
            </van-radio>
          </van-radio-group>
        </div>
      </div>
      <div class="botm-con">
        <div class="condition" @click="response">
          <van-icon name="description" size="20px" />作答情况
        </div>
        <van-button round class="prev-btn" @click="goPrev" :disabled="stemNum === 0"
          :class="{ disabled: stemNum === 0 }">
          上一题
        </van-button>
        <!-- 下一题/交卷按钮 -->
        <van-button v-if="stemNum < questionList.length - 1" round type="info" @click="goNext">
          下一题
        </van-button>

        <van-button v-else round type="info" @click="handin">
          交卷
        </van-button>
      </div>
      <van-action-sheet v-model:show="show" title="作答情况">
        <div class="content">
          <div class="sin-c">
            <p>单选题</p>
            <div :class="item.checked ? 'card' : 'nocard'" v-for="(item, index) in singglequest" :key="index"
              @click="clickstem(item.questionCode)">
              {{ index + 1 }}
            </div>
          </div>
          <div class="sin-c" v-if="multiplequest.length !== 0">
            <p>多选题</p>
            <div class="card-con">
              <div :class="item1.checked ? 'card' : 'nocard'" v-for="(item1, index1) in multiplequest" :key="index1"
                @click="clickstem(item1.questionCode)">
                {{ index1 + 1 }}
              </div>
            </div>
          </div>
          <div class="sin-c" v-if="judgequest.length !== 0">
            <p>判断题</p>
            <div class="card-con">
              <div :class="item2.checked ? 'card' : 'nocard'" v-for="(item2, index2) in judgequest" :key="index2"
                @click="clickstem(item2.questionCode)">
                {{ index2 + 1 }}
              </div>
            </div>
          </div>
          <div class="zuoda">
            <van-button round class="cardBtn" @click="handin">交卷</van-button>
          </div>
        </div>
      </van-action-sheet>
      <van-dialog v-model:show="showScore" confirmButtonColor='#E12D22' @confirm="scoreConfirm" @cancel="scoreCancel"
        confirmButtonText="确定">
        <div class="score-con">
          <img class="jiaojuan" src="../../assets/scoreIcon.png" alt="">
          <span class="jjspan">交卷成功</span>
          <div><span class="scorenum">{{ examScore }}</span> <span>分</span></div>
        </div>
      </van-dialog>
    </div>

挨个遍历questionList,最上面展示题目类型、每题分数、当前题数,单选和判断使用van-radio-group;多选是van-checkbox-group,van-checkbox,都使用自定义图标。

上一题、下一题、交卷按钮根据当前题数stemNum和questionList.length进行比较控制显隐和能否点击。

作答弹窗遍历的是三个小数组,每一题的checked控制样式(这里有一个小问题,多选题只选择一项会被认为没有答过此题,可以根据需求来定)

2.逻辑代码

(1)大数组拆分成三个小数组

复制代码
const dividequestion = () => {
  questionList.value.forEach((item, index) => {
    if (item.questionType == "1") {
      item.checked = 0
      singglequest.value.push(item);
    } else if (item.questionType == "2") {
      item.checked = 0
      multiplequest.value.push(item);
    } else if (item.questionType == "3") {
      item.checked = 0
      judgequest.value.push(item);
    }
  });
}

(2)左右滑动和点击按钮切换题目

复制代码
const handleTouchStart = () => {
  startX.value = event.touches[0].clientX;
}
const handleTouchMove = (event) => {
  // 可以在这里实时监控滑动
}
const handleTouchEnd = () => {
  endX.value = event.changedTouches[0].clientX;
  const deltaX = endX.value - startX.value;
  if (Math.abs(deltaX) > 15) {
    if (deltaX > 0) {
      if (stemNum.value > 0) {
        stemNum.value--;
      }
      // 向右滑动
    } else {
      if (stemNum.value < questionList.value.length - 1) {
        stemNum.value++;
      }

    }
  }
}
// 导航功能
const goPrev = debounce(() => {
  if (stemNum.value > 0) {
    stemNum.value--
  }
}, 300)
const goNext = debounce(() => {
  if (stemNum.value < questionList.value.length - 1) {
    stemNum.value++
  }
}, 300)

记录起止滑动的坐标,相减超过一定范围(这里定的是15px)才判断为切换题目,并且需要注意边界问题,然后就可以直接改变stemNum进行切换了。

(3)当选择了某个选项触发的事件

复制代码
const select = () => {
  getnoSelect()
}
const getnoSelect = () => {
  questionList.value.forEach((item, index) => {
    if (radio.value[index] && item.questionType == '1') {
      singglequest.value.forEach((item1, index1) => {
        if (item1.questionCode == item.questionCode) {
          item1.checked = 1
        }
      })
    } else if (radio.value[index] && item.questionType == '2' && radio.value[index].length > 1) {
      multiplequest.value.forEach((item2, index2) => {
        if (item2.questionCode == item.questionCode) {
          item2.checked = 1
        }
      })
    } else if (radio.value[index] && item.questionType == '3') {
      judgequest.value.forEach((item3, index3) => {
        if (item3.questionCode == item.questionCode) {
          item3.checked = 1
        }
      })
    } else {
      multiplequest.value.forEach((item2, index2) => {
        if (item2.questionCode == item.questionCode) {
          item2.checked = 0
        }
      })
    }
  });
  console.log(questionList.value, multiplequest.value, judgequest.value, '选择情况')
}

这个函数是先遍历的questionList,然后判断是否有值、再看类型,再遍历对应的小数组将用户当前选择的那项设置为被选择了(多选只选一项不算),不过我试过了,就算不再遍历里面的小数组直接修改当前item.checked也是对的(引用数据类型指向同一地址),代码如下:

复制代码
questionList.value.forEach((item, index) => {
    if (radio.value[index] && item.questionType == '1') {
      // singglequest.value.forEach((item1, index1) => {
      //   if (item1.questionCode == item.questionCode) {
      item.checked = 1
      console.log(item, 'item1被改变喽')
      // }
      // })
    } else if (radio.value[index] && item.questionType == '2' && radio.value[index].length > 1) {
      // multiplequest.value.forEach((item2, index2) => {
      //   if (item2.questionCode == item.questionCode) {
      item.checked = 1
      console.log(item, 'item2被改变喽')
      //   }
      // })
    } else if (radio.value[index] && item.questionType == '3') {
      // judgequest.value.forEach((item3, index3) => {
      //   if (item3.questionCode == item.questionCode) {
      item.checked = 1
      console.log(item, 'item3被改变喽')
      //   }
      // })
    } else {
      // multiplequest.value.forEach((item2, index2) => {
      //   if (item2.questionCode == item.questionCode) {
      item.checked = 0
      console.log(item, 'item2只选了一个')
      //   }
      // })
    }
  });

在点击弹窗出来的时候也要调用一次这个函数,保证数据实时有效

(4)点击弹窗面板上的题号跳转

复制代码
const clickstem = (code) => {
  questionList.value.forEach((item, index) => {
    if (item.questionCode == code) {
      stemNum.value = index
    }
  })
  show.value = false
}

这里用的是唯一值questionCode找到大数组中的所在位置,然后赋值给stemNum

(5)交卷

这里就只展示一部分内容,我采用的结构是每一道题都将questionCode、abcd四个选项(选则1,否则0)这种结构传给后端

复制代码
studentAnswerList.value = []
  for (let i = 0; i < questionList.value.length; i++) {
    let list = {
      questionCode: questionList.value[i].questionCode,
      selectedA: "0",
      selectedB: "0",
      selectedC: "0",
      selectedD: "0",
    };
    if (radio.value[i]) {
      if (questionList.value[i].questionType == '2') {
        radio.value[i].forEach((item, index) => {
          list[`selected${item}`] = "1";
        });
      } else {
        list[`selected${radio.value[i]}`] = "1";
      }
      studentAnswerList.value.push(list);
    } else {
      studentAnswerList.value.push(list);
    }
  }

0: "A"

1: "A"

10: (2) ['A', 'B']

radio数组输出是这种结构的

样式这里就不再放了哈,大家根据自己的项目去设置

完结~

相关推荐
山土成旧客2 小时前
【Python学习打卡-Day33】你好,PyTorch!从“自动挡”到“手动挡”的深度学习之旅
python·深度学习·学习
强子感冒了3 小时前
Java集合框架深度学习:从Iterable到ArrayList的完整继承体系
java·笔记·学习
来不及辣哎呀3 小时前
学习Java第六十二天——Hot 100-09-438. 找到字符串中所有字母异位词
java·开发语言·学习
鸿途优学-UU教育3 小时前
2025搜狐教育年度盛典|UU教育CEO彭普杰:成人学习不止于知识传递,科技赋能背后更需温度守护
科技·学习
后端小张3 小时前
【TextIn大模型加速器 + 火山引擎】TextIn大模型加速器与火山引擎协同构建智能文档处理新范式
人工智能·学习·数据挖掘·langchain·tensorflow·gpt-3·火山引擎
yuhaiqun19894 小时前
发现前端性能瓶颈的巧妙方法:建立“现象归因→分析定位→优化验证”的闭环思维
前端·经验分享·笔记·python·学习·课程设计·学习方法
d111111111d4 小时前
使用STM32 HAL库配置ADC单次转换模式详解
笔记·stm32·单片机·嵌入式硬件·学习
·present·4 小时前
射频网课学习第0章(绪论)
学习
DYS_房东的猫4 小时前
学习总结笔记三:让网站“活”起来——处理静态文件、表单验证与用户登录(第3章实战版)
笔记·学习·golang