H5 中 van-popup 的使用以及题目的切换

news2024/12/20 2:41:18

H5 中 van-popup 的使用以及题目的切换

在移动端开发中,弹窗组件是一个常见的需求。vant 是一个轻量、可靠的移动端 Vue 组件库,其中的 van-popup 组件可以方便地实现弹窗效果。本文将介绍如何使用 van-popup 实现题目详情的弹窗展示,并实现题目的切换功能。

关键点总结

  1. 引入 van-popup 组件

    • 在 Vue 项目中引入 vant 组件库,并使用 van-popup 组件实现弹窗效果。
    • import { createApp } from 'vue'
      import Vant from 'vant'
      
      const app = createApp(App)
      app.use(Vant)
      app.mount('#app')
  2. 弹窗内容的条件渲染

    • 根据不同的题目类型(如互动题和练习题),在弹窗中显示不同的内容。
  3. 题目详情的展示

    • 使用 computed 属性计算当前题目的详情,并在弹窗中展示题目的相关信息。
  4. 题目的切换

    • 通过按钮实现题目的上一题和下一题的切换,并更新当前题目的索引。
代码示例

以下是实现上述功能的关键代码片段:

questions.vue---子组件

<template>
  <van-popup v-model:show="localVisible" position="bottom" round :style="{ height: '80%' }" @close="close">
    <div v-if="type === 'interactive'">
      <div class="picker-header">
        <div class="picker-title">
          题目详情
          <button @click="close" class="close-button">X</button>
        </div>
        <div class="picker-info">
          <div class="left-info">
            <span class="number">第{{ currentQuestion.serial_number }}题</span>
            <span class="status">{{ getStatusText(currentQuestion.status) }}</span>
          </div>
          <div class="right-info">
            <button v-if="!isFirstQuestion" @click="prevQuestion">
              <van-icon name="arrow-left" />
            </button>
            <span>{{ currentQuestion.serial_number }}/{{ questions.length }}</span>
            <button v-if="!isLastQuestion" @click="nextQuestion">
              <van-icon name="arrow" />
            </button>
          </div>
        </div>
      </div>
      <div class="picker-content">
        <div class="section-title">课件页面</div>
        <iframe :src="currentQuestion.previewUrl" frameborder="0"></iframe>
        <div class="use-duration">
          我的用时:
          <span class="time-number">{{ formattedDuration.minutes }}</span>分 <span class="time-number">{{
            formattedDuration.seconds }}</span>秒
        </div>
      </div>
    </div>
    <div v-else-if="type === 'practice'">
      //  其他内容
    </div>
  </van-popup>
</template>

<script setup>
import { defineProps, defineEmits, computed, ref, watch } from 'vue'
import { Popup } from 'vant'

const props = defineProps({
  visible: Boolean,
  questions: {
    type: Array,
    required: true,
  },
  currentQuestionIndex: {
    type: Number,
    required: true,
  },
  type: {
    type: String,
    required: true,
  },
})

const emits = defineEmits(['close', 'changeQuestion'])

const localVisible = ref(props.visible)

watch(
  () => props.visible,
  newVal => {
    localVisible.value = newVal
  },
)

const currentQuestion = computed(() => {
  const question = props.questions[props.currentQuestionIndex] || {}
  if (props.type === 'practice' && !question.serial_number) {
    question.serial_number = props.currentQuestionIndex + 1
  }
  return question
})

const getStatusText = status => {
  switch (status) {
    case 1:
      return '正确'
    case 2:
      return '错误'
    case 3:
      return '半对半错'
    default:
      return '未作答'
  }
}

const formatDuration = duration => {
  const minutes = String(Math.floor(duration / 60)).padStart(2, '0')
  const seconds = String(duration % 60).padStart(2, '0')
  return { minutes, seconds }
}

const formattedDuration = computed(() => formatDuration(currentQuestion.value.use_duration))

const isFirstQuestion = computed(() => props.currentQuestionIndex === 0)
const isLastQuestion = computed(() => props.currentQuestionIndex === props.questions.length - 1)

const prevQuestion = () => {
  if (!isFirstQuestion.value) {
    emits('changeQuestion', props.currentQuestionIndex - 1)
  }
}

const nextQuestion = () => {
  if (!isLastQuestion.value) {
    emits('changeQuestion', props.currentQuestionIndex + 1)
  }
}

const close = () => {
  emits('close')
}
</script>

<style lang="less" scoped>
.picker-header {
  padding: 10px;
}

.picker-title {
  font-size: 18px;
  font-weight: bold;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
  color: #000;
  margin-top: 10px;
  display: flex;
  width: 100%;

  .close-button {
    background: none;
    border: none;
    font-size: 16px;
    margin-left: auto;
    color: #a9aeb8;
    cursor: pointer;
  }
}

.picker-info {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 10px 0 10px;
}

.left-info {
  display: flex;
  flex-direction: row;

  .number {
    margin-right: 20px;
    font-size: 16px;
    font-weight: 500;
  }

  .status {
    font-size: 16px;
    font-weight: 500;
    color: #1f70ff;
  }
}

.right-info {
  display: flex;
  position: absolute;
  right: 10px;
  color: #a9aeb8;

  .right-icon {
    width: 28px;
    height: 28px;
  }
}

.right-info button {
  background: none;
  border: none;
  font-size: 16px;
  cursor: pointer;
  margin: 0 5px;
}

.picker-content {
  padding: 10px 20px 0 20px;
}

.section-title {
  font-size: 16px;
  font-family: PingFangSC-Regular, PingFang SC;
  font-weight: 400;
  color: #2b2f38;
}

iframe {
  width: 100%;
  height: 300px;
  border: none;
  margin-bottom: 10px;
}

.use-duration {
  font-size: 16px;
  color: #2b2f38;
}

.time-number {
  font-weight: bold;
  color: #0074fc;
  font-size: 24px;
}

.van-popup {
  height: 50%;
  z-index: 99999;
}

.practice-content {
  padding: 0px 20px 0 20px;
}
</style>

courseDetail.vue---父组件

// template关键代码
<div v-for="(item, index) in period.interactive_performance.list" :key="index" :class="[
              'performance-item',
              getStatusClass(item.status),
              { selected: selectedQuestion === index },
            ]" @click="selectQuestion(index, period.interactive_performance.list, 'interactive')">
              <span :class="getQuestionTextClass(item.status, selectedQuestion === index)">{{
                item.serial_number
                }}</span>
            </div>

<div v-for="(item, index) in period.practice_detail.list" :key="index" :class="[
              'practice-item',
              getStatusClass(item.status),
              { selected: selectedPracticeQuestion === index },
            ]" @click="selectPracticeQuestion(index, period.practice_detail.list, 'practice')">
              <div class="question-number">
                <span>{{ index + 1 }}</span>
              </div>
</div>

<QuestionDetail :visible="showQuestionDetail" :questions="currentQuestions" :type="currentType"
      :currentQuestionIndex="currentQuestionIndex" @close="closeQuestionDetail" @changeQuestion="changeQuestion" />

// script关键代码
const selectQuestion = (index, questions, type) => {
  selectedQuestion.value = index
  currentQuestions.value = questions
  currentType.value = type
  currentQuestionIndex.value = index
  showQuestionDetail.value = true
}

const selectPracticeQuestion = (index, questions, type) => {
  selectedPracticeQuestion.value = index
  currentQuestions.value = questions
  currentQuestionIndex.value = index
  // 设置 serial_number 属性
  currentQuestions.value.forEach((question, idx) => {
    question.serial_number = idx + 1
  })
  currentType.value = type
  showQuestionDetail.value = true
}
const changeQuestion = index => {
  currentQuestionIndex.value = index
}

数据结构

关键点解析
  1. 引入 van-popup 组件

    • 在模板中使用 <van-popup> 标签,并通过 v-model:show 绑定弹窗的显示状态。
    • 设置 position="bottom" 和 round 属性,使弹窗从底部弹出并带有圆角。
  2. 弹窗内容的条件渲染

    • 使用 v-if 和 v-else-if 根据 type 属性的值渲染不同的内容。
    • 当 type 为 interactive 时,显示互动题的详情;当 type 为 practice 时,显示练习题的详情。
  3. 题目详情的展示

    • 使用 computed 属性计算当前题目的详情,并在弹窗中展示题目的相关信息。
    • 通过 currentQuestion 计算属性获取当前题目的详细信息。
  4. 题目的切换

    • 通过按钮实现题目的上一题和下一题的切换,并更新当前题目的索引。
    • 使用 isFirstQuestion 和 isLastQuestion 计算属性判断当前题目是否为第一题或最后一题,以控制按钮的显示和隐藏。

大致效果

通过以上关键点的实现,我们可以在移动端应用中使用 van-popup 组件实现题目详情的弹窗展示,并实现题目的切换功能。希望本文对您有所帮助!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2262467.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【第一节】Git的简介和安装

目录 一、git的介绍 二、git 的安装 2.1 Linux 平台安装 2.2 源码安装 2.3 Windows 平台安装 2.4 Mac 平台安装 2.5 Git 配置 2.5.1 配置文件 2.5.2 用户信息配置 2.5.3 文本编辑器配置 2.5.4 差异分析工具配置 2.5.5 查看配置信息 一、git的介绍 Git 是一种开源的…

奇怪的知识又增加了,ESP32下的Lisp编程:ULisp--Lisp for microcontrollers

ESP32下有MicroPython&#xff0c;那么我就在想&#xff0c;有Lisp语言支持吗&#xff1f;答案是果然有&#xff01;有ULisp&#xff0c;专门为MCU设计的Lisp&#xff01; 网址&#xff1a;uLisp - Lisp for microcontrollers 介绍&#xff1a;用于微控制器的 Lisp 适用于 Ar…

决策树的生成与剪枝

决策树的生成与剪枝 决策树的生成生成决策树的过程决策树的生成算法 决策树的剪枝决策树的损失函数决策树的剪枝算法 代码 决策树的生成 生成决策树的过程 为了方便分析描述&#xff0c;我们对上节课中的训练样本进行编号&#xff0c;每个样本加一个ID值&#xff0c;如图所示…

51c嵌入式~单片机~合集2

我自己的原文哦~ https://blog.51cto.com/whaosoft/12362395 一、不同的电平信号的MCU怎么通信&#xff1f; 下面这个“电平转换”电路&#xff0c;理解后令人心情愉快。电路设计其实也可以很有趣。 先说一说这个电路的用途&#xff1a;当两个MCU在不同的工作电压下工作&a…

Kerberos实验

kdc&#xff1a;192.168.72.163 客户端&#xff08;机器账户win10&#xff09;&#xff1a;192.168.72.159 用户&#xff1a;administrator 抓包&#xff1a;开机登录win10&#xff0c;使用administrator域用户凭据登录。 生成 Kerberos 解密文件 抓取 krbtgt 用户和 win1…

AI一键分析小红书对标账号‼️

宝子们&#xff0c;AI小助手近期发现了一款宝藏AI工具&#xff0c;拥有对标账号AI分析功能&#xff0c;只需10秒就能全面掌握对标账号的运营情况&#xff0c;并且可以根据分析结果提供创作方向和灵感&#xff0c;轻松助力1:1复刻起号&#xff01; 功能亮点&#xff1a; &…

大腾智能CAD:国产云原生三维设计新选择

在快速发展的工业设计领域&#xff0c;CAD软件已成为不可或缺的核心工具。它通过强大的建模、分析、优化等功能&#xff0c;不仅显著提升了设计效率与精度&#xff0c;还促进了设计思维的创新与拓展&#xff0c;为产品从概念构想到实体制造的全过程提供了强有力的技术支持。然而…

VMware虚拟机 Ubuntu没有共享文件夹的问题

在虚拟机的Ubuntu系统中&#xff0c;共享文件目录存放在 mnt/hgfs 下面&#xff0c;但是我安装完系统并添加共享文件后发现&#xff0c;在mnt下连/hgfs目录都没有。 注意&#xff1a;使用共享文件目录需要已安装VMtools工具。 添加共享文件目录 一&#xff1a;在超级用户下 可…

OpenGL ES 01 渲染一个四边形

项目架构 着色器封装 vertex #version 300 es // 接收顶点数据 layout (location 0) in vec3 aPos; // 位置变量的属性位置值为0 layout (location 1) in vec4 aColors; // 位置变量的属性位置值为1 out vec4 vertexColor; // 为片段着色器指定一个颜色输出void main() {gl…

leetcode二叉搜索树部分笔记

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 二叉搜索树 1. 二叉搜索树的最小绝对差2. 二叉搜索树中第 K 小的元素3. 验证二叉搜索树 1. 二叉搜索树的最小绝对差 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中…

推送本地仓库到远程git仓库

目录 推送本地仓库到远程git仓库1.1修改本地仓库用户名1.2 push 命令1.3远程分支查看 推送本地仓库到远程git仓库 删除之前的仓库中的所有内容&#xff0c;从新建库&#xff0c;同时创建一个 A.txt 文件 清空原有的远程仓库内容&#xff0c;重新创建一个新的仓库&#xff0c;…

暂停一下,给Next.js项目配置一下ESLint(Next+tailwind项目)

前提 之前开自己的GitHub项目&#xff0c;想着不是团队项目&#xff0c;偷懒没有配置eslint&#xff0c;后面发现还是不行。eslint的存在可以帮助我们规范代码格式&#xff0c;同时 ctrl s保存立即调整代码格式是真的很爽。 除此之外&#xff0c;团队使用eslint也是好处颇多…

基于微信小程序的小区疫情防控ssm+论文源码调试讲解

第2章 程序开发技术 2.1 Mysql数据库 为了更容易理解Mysql数据库&#xff0c;接下来就对其具备的主要特征进行描述。 &#xff08;1&#xff09;首选Mysql数据库也是为了节省开发资金&#xff0c;因为网络上对Mysql的源码都已进行了公开展示&#xff0c;开发者根据程序开发需…

Win11安装安卓子系统WSA

文章目录 简介一、启用Hyper-V二、安装WSA三、安装APKAPK商店参考文献 简介 WSA&#xff1a;Windows Subsystem For Android 一、启用Hyper-V 控制面板 → 程序和功能 → 启用或关闭 Windows 功能 → 勾选 Hyper-V 二、安装WSA 进入 Microsoft Store&#xff0c;下拉框改为 …

[面试题]--索引用了什么数据结构?有什么特点?

答&#xff1a;使用了B树&#xff1a; 时间复杂度&#xff1a;O(logN),可以有效控制树高 B树特点&#xff1a; 1.叶子节点之间有相互链接的作用&#xff0c;会指向下一个相近的兄弟节点。 MySQL在组织叶子节点使用的是双向链表 2.非叶子节点的值都保存在叶子节点当中 MySQL非叶…

Element plus 下拉框组件选中一个选项后显示的是 value 而不是 label

最近刚进行 Vue3 Element plus 项目实践&#xff0c;在进行表单二次封装的时候&#xff0c;表单元素 select 下拉框组件选中一个选项后显示的是 value 而不是 label&#xff0c;下面上代码&#xff1a; 原来的写法&#xff1a; <el-selectv-if"v.type select"…

bean创建源码

去字节面试&#xff0c;直接让人出门左拐&#xff1a;Bean 生命周期都不知道&#xff01; spring启动创建bean流程 下面就接上了 bean生命周期 doGetBean Object sharedInstance this.getSingleton(beanName); sharedInstance this.getSingleton(beanName, new ObjectF…

【C++】- 掌握STL List类:带你探索双向链表的魅力

文章目录 前言&#xff1a;一.list的介绍及使用1. list的介绍2. list的使用2.1 list的构造2.2 list iterator的使用2.3 list capacity2.4 list element access2.5 list modifiers2.6 list的迭代器失效 二.list的模拟实现1. list的节点2. list的成员变量3.list迭代器相关问题3.1…

泷羽sec学习打卡-brupsuite8伪造IP和爬虫审计

声明 学习视频来自B站UP主 泷羽sec,如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都 与本人无关,切莫逾越法律红线,否则后果自负 关于brupsuite的那些事儿-Brup-FaskIP 伪造IP配置环境brupsuite导入配置1、扩展中先配置python环境2、安…