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

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 组件实现题目详情的弹窗展示,并实现题目的切换功能。希望本文对您有所帮助!

### Vant WeApp `van-popup` 组件使用指南 #### 创建弹出层基础结构 为了实现一个基本的弹窗功能,在页面中定义两个部分:触发按钮或其他元素以及实际显示的内容区域。通过绑定数据模型来控制可见状态。 ```html <template> <!-- 触发器 --> <button type="default" @click="toggleShow">点击打开</button> <!-- 弹出框本身 --> <van-popup :show="isPopupVisible" position="center"> 这里放置要展示的信息... </van-popup> </template> ``` #### 控制显隐逻辑 利用 Vue 的响应式特性管理弹窗的状态变化,通常会设置一个布尔类型的变量用于追踪当前是否应该渲染该组件。 ```javascript <script setup lang="ts"> import { ref } from 'vue'; const isPopupVisible = ref(false); function toggleShow() { isPopupVisible.value = !isPopupVisible.value; } </script> ``` #### 自定义样式与位置参数配置 除了默认居中的布局外,还可以调整其他属性来自定义外观和行为,比如改变方向(`position`)、宽度高度(`width`, `height`)等。 ```html <!-- 设置底部滑动进入的效果 --> <van-popup :show="true" position="bottom" round closeable> ... </van-popup> ``` #### 处理交互事件 当涉及到更复杂的场景时,可以监听特定的动作并作出相应处理,例如确认提交表单或是关闭对话框。 ```html <!-- 添加回调函数 --> <van-popup ... @close="handleCloseEvent"> <div>...</div> </van-popup> ``` ```javascript // 定义事件处理器 methods: { handleCloseEvent(event) { console.log('用户已关闭弹窗'); } } ``` 以上就是针对 Vant WeApp 中 `van-popup` 组件的基础介绍及其常见应用场景下的实践案例[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值