2024年蓝桥杯Web开发【大学组:模拟题二】15届

1.相不相等

小蓝想要自己开发一套用于 JS 单元测试的基础 API,先从编写一个用于检验两个数据值是否相等的函数开始吧,但是此时的小蓝却犯了难,聪明的你快来帮帮他吧~

1.1 题目要求

请你编写一个名为 expectFn 的函数,用于帮助开发人员测试他们的代码。它可以通过参数 val 接受任何值,并返回一个对象,该对象包含下面两个函数:

  • toBe(val):接受另一个值并在两个值相等( === )时返回 true 。如果它们不相等,则返回 “Not Equal” 。
  • notToBe(val):接受另一个值并在两个值不相等( !== )时返回 true 。如果它们相等,则返回 “Equal” 。

示例如下:

// 示例 1:
输入:console.log(expectFn(5).toBe(5))
输出:true
解释:5 === 5 因此该表达式返回 true// 示例 2:
输入:console.log(expectFn(5).toBe(null))
输出:"Not Equal"
解释:5 !== null 因此抛出错误 "Not Equal".

// 示例 3:
输入:console.log(expectFn(5).notToBe(5))
输出:"Equal"
解释:5 === 5 因此抛出错误 "Equal".

// 示例 4:
输入:console.log(expectFn(5).notToBe(null))
输出:true
解释:5 !== null 因此该表达式返回 true.

1.2 题目分析

题目要求写的很清楚了,按照其要求做判断条件即可

1.3 源代码

var expectFn = function(val) {
  // TODO
  return {
    toBe: (value) => {
      return value === val ? true : 'Not Equal'
    },
    notToBe: (value) => {
      return value !== val ? true : 'Equal'
    }
  }
}

2.三行情书

小蓝准备向小红表白,于是他在网上下单了三行情书网页程序,作为店主的你,快来帮他完成基本结构吧!

2.1 题目要求

请完善 style.css 的 TODO 部分,具体要求如下:

  1. 让第一行标题即 .content span 标签中的文字单行显示,多余溢出显示省略号。
  2. 请使用 -webkit-box 语法使得下面的文字即 .content p 标签里的内容显示三行,多余溢出显示省略号。

完成后,页面效果如下:

完成效果

2.2 题目分析

这里主要是如果写过案例的话,应该就知道怎么写,需要注意的是span元素是单行元素,要想达到效果,需要将其变为块级元素。

2.3 源代码

span {
    font-size: 20px;
    color: #837362;
    /* TODO:补充下面的代码 */
    /* 单行显示,多余溢出省略号 */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: block;
    /* 使得溢出部分显示省略号 */
}
 
p {
    color: #837362;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    /* 显示的行数 */
    overflow: hidden;
}

3.电影院在线订票

随着人们生活水平的日益提升,电影院成为了越来越多的人休闲娱乐,周末放松的好去处。最近又有众多大片在院线上线,正好有小蓝想看的影片。小蓝赶紧邀约朋友们在线上平台订票。

3.1 题目要求

请在 js/script.js 文件中补全代码,最终实现订票功能。具体需求如下:

  1. 实现异步数据读取和渲染功能,使用axios请求./data.json(必须使用该路径请求,否则可能会请求不到数据)的数据。
    • 将电影名字 name 渲染到 id 为 movie-name 的节点中。
    • 将电影售价 price 渲染到 id 为 movie-price 的节点中。
    • 将座位信息 seats 渲染到 id 为 seat-area 的节点中,二维数组中每一个子数组代表一行,0 代表没有被他人占位,1 代表已经被订购。

每一行生成的 DOM 节点格式如下:

<!-- 每一行节点都使用 row class 包裹 -->
<div class="row">
  <!-- 每个座位都具有 seat 的 class -->
  <div class="seat"></div>
  <!-- 如果座位被占了,则相应设置 occupied class -->
  <div class="seat occupied"></div>
  <!-- ...... -->
</div>

完成后的页面布局效果如下:

完成效果

  1. 实现座位选择和总价计算的功能:
    • 被别人订过的座位不能再被选择。
    • 座位被选中后,状态更新,为相应的座位添加选中样式(即 selected),并更新已选择的座位数和总价。
    • 自己所选择的座位可以被取消,并更新已选择的座位数和总价。

完成后的效果见文件夹下面的 gif 图,图片名称为 effect.gif(提示:可以通过 VS Code 或者浏览器预览 gif 图片):

交互效果

3.2 题目分析

  • 第一点是使用axios发送请求获取到数据,这个不会的可以参考一下axios官网的案例学习一下
  • 第二个是根据拿到的数据渲染html的结构,这个的就是创建对应的html结构,最后添加到对应的节点。
  • 第三个是实现作为选择的功能,实现思路就是给所有的座位都绑定一个点击事件,通过事件对象身上是否被选中来替换类名,然后就是渲染数量。

3.3 源代码

/* TODO: 
      1. 完成数据请求,生成电影名,价格以及座位情况
      2. 绑定点击事件,实现订票功能
 */
 
let data = {}
axios
  .get('../data.json')
  .then((res) => {
    console.log(res)
    data = res.data
    movieNameNode.innerHTML = data.name
    moviePriceNode.innerHTML = data.price
    //创建节点渲染数据
    data.seats.forEach((item) => {
      let row = document.createElement('div')
      row.className = 'row'
      item.forEach((item) => {
        let seat = document.createElement('div')
        seat.className = 'seat'
        row.appendChild(seat)
        if (item) {
          seat.classList.add('occupied')
        }
      })
      seatAreaNode.appendChild(row)
    })
  })
  .catch((err) => {
    console.log(err)
  })
 
// 获取座位区域节点
const seatAreaNode = document.getElementById('seat-area')
// 获取电影名节点
const movieNameNode = document.getElementById('movie-name')
// 获取电影票价节点
const moviePriceNode = document.getElementById('movie-price')
// 获取已订电影票数量节点
const count = document.getElementById('count')
// 获取已订电影票总价节点
const total = document.getElementById('total')
 
// 获取所有座位节点
const seatNodes = document.querySelectorAll('.seat')
// 初始化已选择座位数和总价
let selectedSeatsCount = 0
let totalPrice = 0
 
// 监听座位点击事件
seatAreaNode.addEventListener('click', (event) => {
  const clickedSeat = event.target
 
  // 检查是否点击的是座位
  if (clickedSeat.classList.contains('seat') && !clickedSeat.classList.contains('occupied')) {
    // 切换座位的选中状态
    clickedSeat.classList.toggle('selected')
 
    // 更新已选择座位数和总价
    if (clickedSeat.classList.contains('selected')) {
      selectedSeatsCount++
      totalPrice += data.price
    } else {
      selectedSeatsCount--
      totalPrice -= data.price
    }
 
    // 更新显示
    updateDisplay()
  }
})
 
// 更新显示函数
function updateDisplay() {
  count.textContent = selectedSeatsCount
  total.textContent = totalPrice 
}

4.老虎坤

小蓝开发了一款老虎机游戏。用户点击开始按钮后,页面上的三列图片开始随机滚动。当最后停下来的三张图片相同时,用户即可获得奖励。让我们一起来帮助小蓝完善这个老虎机应用吧!

4.1 题目要求

找到 js/index.js 中的 GetResult 函数,完成此函数,实现以下目标:

点击开始后,可以通过 GetResult的三个参数 r1r2r3 计算出滚动后每一列图片的停留位置。当最后停留的图片都相同时,意味着玩家中了大奖!文字框(class = textPanel)显示“恭喜你,中奖了”,否则显示:“很遗憾,未中奖”。

参数介绍:r1r2r3 表示的是三列元素下的 li 的最后停留位置,分别对应第一列(id=sevenFirst)、第二列(id=sevenSecond)、第三列(id=sevenThird)。以第一列为例,最终显示的元素是 sevenFirst 下的第 rli 元素。请使用显示的 li 元素的 data-point 属性判断三张图片是否相同。当 data-point 属性对应的值相同时,说明这三张图片相同。

在完成之后,请点击“开始”按钮,以下是未中奖和中奖的效果:

未中奖效果

中奖效果

4.2 题目分析

这一题很明确的考察点就是获取元素身上的属性,需要用到getAttribute()方法,通过获取到的属性对比是否一样,如果三个相同则中奖。

4.3 源代码

if (sevenFirst.children[r1 - 1].getAttribute('data-point') == sevenSecond.children[r2 - 1].getAttribute('data-point') 
&& sevenFirst.children[r1 - 1].getAttribute('data-point') == sevenThird.children[r3 - 1].getAttribute('data-point')) {
      textPanel.innerHTML = '恭喜你,中奖了'
    } else {
      textPanel.innerHTML = '很遗憾,未中奖'
    }

5.星际通讯

为了解密外星人的密文消息,科学家们创建了一个名为"星际通讯翻译器"的程序。这个程序可以将外星人的密文翻译成人类可以理解的语言。翻译器使用了一个称为"密文编码表"的参考表,其中包含了一系列密文和对应的人类语言翻译。

5.1 题目要求

完善 index.js 中的 translate 函数,完善其中的 TODO 部分:

translate 函数接收一个字符串参数 alienMessage,其中包含一系列外星人的密文。函数根据特定的翻译规则将密文翻译成人类语言,并返回翻译后的结果字符串。外星人密文翻译规则存放在 codonTable 变量中。

注意:翻译后的结果字符串之间不能有空格

特殊条件:

  • 密文如果为空,直接返回空字符串。
  • 如果密文任意一处无法翻译或遇到找不到对应翻译的密文,则返回字符串无效密语
  • 如果密文中出现了特殊密文对应的翻译结果是 stop,则停止翻译,返回之前已翻译的结果(不包括对应 stop 的密文)

以下为提供部分测试用例,通过测试用例不代表通过全部测试,请确保代码的通用性:

测试用例输入字符串预期输出
1IIXIIIXIV人类你好交个朋友
2VIIIIIXIV哈喽你好交个朋友
3 (只翻译 stop 之前的密语)IIXIIIXXIXIV人类你好
4 (只翻译 stop 之前的密语)IIXXXIIIIXIV人类
5IIXxIIIXIV无效密语
6ax4无效密语
7''‘’

5.2 题目分析

这个题目就是js的切割 遍历

5.3 源代码

// 密语规则
const codonTable = {
  'IIX': '人类',
  'VII': '哈喽',
  'III': '你好',
  'IXI': '赞',
  'XVI': '嗨',
  'CUV': '打击',
  'XII': '夜晚',
  'IVI': '我',
  'XIC': '想',
  'XIV': '交个朋友',
  'VIX': '月亮',
  'XCI': '代码',
  'XIX': '祈福',
  'XVI': '和',
  'XXI': 'stop',
};

/**
 * @param {string} alienMessage 外星人的密文
 * @return {string}  翻译结果
 */
const translate = (alienMessage) => {
  let str = ''
  // TODO:待补充代码
  if (!alienMessage) return str
  const arr = []
  for(let i = 0; i < alienMessage.length; i+=3){
    arr.push(alienMessage.slice(i, i + 3))
  }

  for(let i =0; i < arr.length; i++) {
    if(!codonTable[arr[i]]) { 
      str = '无效密语'
      break
    }
    if( arr[i] === 'XXI' ) {
      break
    }
    str += codonTable[arr[i]]
  }
  return str

}
// 请注意:以下代码用于检测,请勿删除。
try{
module.exports = translate;
}catch(e){}

6.蓝桥杯排位

今年的蓝桥排位赛非常热闹,全国的大学生们争先恐后参加,人气满满!小蓝想知道每个省份的参赛热度,特意做出了全国备赛地图备赛荣耀战力榜数据可视化大屏。可是数据怎么也调不对,导致大屏显示数据有误,快来帮帮小蓝处理数据,让大屏正确的运行起来吧!

6.1 题目要求

请在 js/index.js 文件中补全代码,具体需求如下:

  1. 完成数据请求(数据来源 ./mock/map.json),map.json 中存放的数据为省市对应的学校数据,使用到的字段介绍如下:
参数类型说明
namestring省/市名称
school_countnumber加入备赛的学校数量
valuenumber热度值
school_powerobject战力存放对象,其中 name 为学校名称,power 为战力值
  1. 根据请求的数据正确完成左侧热力地图。
  2. 右侧战力榜中柱形图中,根据 power 字段的值对所有学校进行排序,取出排在前 10 名的学校,从左到右降序排列。
  • 完成后运行起来,效果如下:

完成效果

6.2 题目分析

根据要求获取数据,然后替换options中的配置即可

6.3 源代码

const { createApp, ref, onMounted } = Vue
const app = createApp({
  setup() {
    const chartsData = ref([])
    onMounted(() => {
      // TODO:待补充代码 请求数据,并正确渲染柱形图和地图
      axios
        .get('../mock/map.json')
        .then((res) => {
          chartsData.value = res.data
          showChartBar()
          showChinaMap()
        })
        .catch((err) => {
          console.log(err)
        })
    })
    // 展示柱状图
    const showChartBar = () => {
      const myChart = echarts.init(document.getElementById('chart'))
 
      let data = chartsData.value.map((item, index) => {
        return item.school_power
      })
      console.log(data)
      let result = data.flat(1).sort((a, z) => {
        return z.power - a.power
      })
      let arr = result.slice(0, 10)
      let school = arr.map((item) => {
        return item.name
      })
      let power = arr.map((item) => {
        return item.power
      })
      console.log(school)
      console.log(power)
      // 指定配置和数据
      const option = {
        xAxis: {
          type: 'category',
          axisLabel: { interval: 0, rotate: 40 },
          // TODO:待修改  柱状图 x 轴数据 -> 前 10 学校名称
          data: school
        },
        grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          containLabel: true
        },
        yAxis: {
          type: 'value',
          boundaryGap: [0, 0.01]
        },
        series: [
          {
            // TODO:待修改   柱状图 y 轴数据->学校战力值
            data: power,
            type: 'bar',
            showBackground: true,
            backgroundStyle: {
              color: 'rgba(180, 180, 180, 0.2)'
            },
            itemStyle: {
              color: '#8c7ae6'
            }
          }
        ]
      }
 
      // 把配置给实例对象
      myChart.setOption(option)
      // 根据浏览器大小切换图表尺寸
      window.addEventListener('resize', function () {
        myChart.resize()
      })
    }
 
    // 展示地图
    const showChinaMap = () => {
      const chinaMap = echarts.init(document.getElementById('chinaMap'))
 
      // 进行相关配置
      const mapOption = {
        tooltip: [
          {
            backgroundColor: '#fff',
            subtext: 'aaa',
            borderColor: '#ccc',
            padding: 15,
            formatter: (params) => {
              return params.name + '热度值:' + params.value + '<br>' + params.data.school_count + '所学校已加入备赛'
            },
            textStyle: {
              fontSize: 18,
              fontWeight: 'bold',
              color: '#464646'
            },
            subtextStyle: {
              fontSize: 12,
              color: '#6E7079'
            }
          }
        ],
        geo: {
          // 这个是重点配置区
          map: 'china', // 表示中国地图
          label: {
            normal: {
              show: false // 是否显示对应地名
            }
          },
          itemStyle: {
            normal: {
              borderColor: 'rgb(38,63,168)',
              borderWidth: '0.4',
              areaColor: '#fff'
            },
            emphasis: {
              //鼠标移入的效果
              areaColor: 'rgb(255,158,0)',
              shadowOffsetX: 0,
              shadowOffsetY: 0,
              shadowBlur: 20,
              borderWidth: 0,
              shadowColor: 'rgba(0, 0, 0, 0.5)'
            }
          }
        },
        visualMap: {
          show: true,
          left: 'center',
          top: 'bottom',
          type: 'piecewise',
          align: 'bottom',
          orient: 'horizontal',
          pieces: [
            {
              gte: 80000,
              color: 'rgb(140,122,230)'
            },
            {
              min: 50000,
              max: 79999,
              color: 'rgba(140,122,230,.8)'
            },
            {
              min: 30000,
              max: 49999,
              color: 'rgba(140,122,230,.6)'
            },
            {
              min: 10000,
              max: 29999,
              color: 'rgba(140,122,230,.4)'
            },
            {
              min: 1,
              max: 9999,
              color: 'rgba(140,122,230,.2)'
            }
          ],
          textStyle: {
            color: '#000',
            fontSize: '11px'
          }
        },
        series: [
          {
            type: 'map',
            geoIndex: 0,
            // TODO:待修改 地图对应数据
            data: chartsData.value.map((item) => {
              return {
                name: item.name,
                school_count: item.school_count,
                value: item.value
              }
            })
          }
        ]
      }
 
      // 把配置给实例对象
      chinaMap.setOption(mapOption)
    }
 
    return {
      chartsData,
      showChartBar,
      showChinaMap
    }
  }
})
 
app.mount('#app')

7.拼出一个未来

在这个任务中,你将进入拼图游戏的世界,一个迷人的益智娱乐领域。拼图游戏一直以来都是受欢迎的休闲选择,要求玩家通过调整碎片位置,将图像拼凑成完整画面。在这个编程挑战中,你将体验到拼图游戏的魅力,通过编写代码实现交互逻辑,使拼图块能够在虚拟环境中重新排列,恰如其分地还原图像。通过这个任务,你将不仅提升编程技能,还能够理解游戏开发中的交互原理,为玩家创造出一个有趣的、具有挑战性的游戏体验。

7.1 题目要求

完善 js/index.js 的 TODO 部分,实现以下目标:

  1. 将拖动的拼图块与目标拼图块的图片进行交换,这包括交换图片的 src 属性和 data-id 属性。待补充代码的 drop 函数中现有的两个变量解释如下:draggedPiece:代表被拖动的拼图块的图片元素的父元素。this:代表当前目标位置的拼图块的图片元素父元素。

拼图成功后的 DOM 如下,图片 srcaltdata-id 均按照 1-9 顺序排列

在这里插入图片描述

  1. 显示/隐藏成功消息:拼图成功则设置成功消息元素(id=success-message)的 class 名为 show,否则该元素的 class 名为 hide。(注意:成功消息元素同时有且只能有一个 class

完成后效果如下:在这里插入图片描述

7.2 题目分析

7.3 源代码

// 声明一个数组,包含了所有的拼图块数据
var puzzlePieces = [
  { src: './images/img1.png', id: 1 },
  { src: './images/img2.png', id: 2 },
  { src: './images/img3.png', id: 3 },
  { src: './images/img4.png', id: 4 },
  { src: './images/img5.png', id: 5 },
  { src: './images/img6.png', id: 6 },
  { src: './images/img7.png', id: 7 },
  { src: './images/img8.png', id: 8 },
  { src: './images/img9.png', id: 9 }
]
 
// 定义一个打乱数组的函数
function shuffleArray(array) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1))
    ;[array[i], array[j]] = [array[j], array[i]]
  }
  return array
}
 
// 使用定义的函数打乱拼图块数组
puzzlePieces = shuffleArray(puzzlePieces)
 
// 获取拼图容器元素
var container = document.getElementById('puzzle-container')
 
// 遍历拼图块数据数组
puzzlePieces.forEach(function (pieceData) {
  // 创建一个新的拼图块元素
  var piece = document.createElement('div')
  piece.classList.add('puzzle-piece')
  piece.setAttribute('draggable', 'true')
 
  // 创建一个新的图片元素
  var image = document.createElement('img')
  image.src = pieceData.src
  image.dataset.id = pieceData.id
 
  // 将图片元素添加到拼图块元素中
  piece.appendChild(image)
 
  // 将拼图块元素添加到父容器元素中
  container.appendChild(piece)
})
 
// 获取所有的拼图块元素,并转换为数组
const puzzleArray = Array.from(document.querySelectorAll('.puzzle-piece'))
 
// 获取成功消息元素
const successMessage = document.getElementById('success-message')
 
// 为每个拼图块元素添加拖拽事件监听器
puzzleArray.forEach((piece) => {
  piece.addEventListener('dragstart', dragStart)
  piece.addEventListener('dragover', dragOver)
  piece.addEventListener('drop', drop)
})
 
// 声明一个变量用来保存正在拖动的拼图块
let draggedPiece = null
 
// 定义开始拖动事件的处理函数
function dragStart(event) {
  draggedPiece = this
  event.dataTransfer.setData('text/plain', null)
}
 
// 定义在拖动过程中的处理函数,阻止默认行为
function dragOver(event) {
  event.preventDefault()
}
 
// 定义拖放事件的处理函数
function drop(event) {
  // 检查是否拖动的拼图块不是当前目标拼图块
  // draggedPiece 被拖动的拼图块元素。this 目标位置的拼图块元素。
  let num = 0
  if (draggedPiece !== this) {
    // TODO:待补充代码
    // 交换图片的 src 属性和 data-id 属性
    let tempSrc = draggedPiece.querySelector('img').src
    let tempDataId = draggedPiece.querySelector('img').dataset.id
 
    draggedPiece.querySelector('img').src = this.querySelector('img').src
    draggedPiece.querySelector('img').dataset.id = this.querySelector('img').dataset.id
 
    this.querySelector('img').src = tempSrc
    this.querySelector('img').dataset.id = tempDataId
 
    // 检查是否拼图成功
    puzzleArray.forEach((item, index) => {
      if (parseInt(item.children[0].getAttribute('data-id')) === index + 1) {
        num++
      }
    })
    if (num === 9) {
      successMessage.classList.remove('hide')
      successMessage.classList.add('show')
    } else {
      successMessage.classList.remove('show')
      successMessage.classList.add('hide')
    }
    // 重置正在拖动的拼图块
    draggedPiece = null
  }
}
// 定义拖放事件的处理函数
function drop(event) {
    // 检查是否拖动的拼图块不是当前目标拼图块
    // draggedPiece 被拖动的拼图块元素。this 目标位置的拼图块元素。
    if (draggedPiece !== this) {
       // TODO:待补充代码 
       console.log(draggedPiece.children[0]);
       console.log(this.children[0]);
       let src = draggedPiece.children[0].src 
       draggedPiece.children[0].src = this.children[0].src
       this.children[0].src = src
       console.log(src);
       let id = draggedPiece.children[0].dataset.id 
       draggedPiece.children[0].dataset.id = this.children[0].dataset.id
       this.children[0].dataset.id = id
       console.log(puzzleArray);
       let flag = puzzleArray.every((item, index) => {
        return index+1 == item.children[0].dataset.id
       })
       if(flag){
        successMessage.classList.remove('hide')
        successMessage.classList.add('show')
       }else{
        successMessage.classList.add('hide')
        successMessage.classList.remove('show')
       }
    }

    // 重置正在拖动的拼图块
    draggedPiece = null;
}

8.超能英雄联盟

当虚拟与现实交织,地球陷入巨大危机。一群源自不同虚拟世界的超级英雄,突然现身现实。原本只在游戏和漫画中存在的英雄们,现在踏足真实世界。

在跨时空的能量风暴中,你获得神秘力量,可以召唤虚拟世界的英雄。面对现实威胁,你集结英雄加入队伍,赋予他们真实存在。科学家发明“英雄链接器”,你从列表中选择队员,将能力融入现实,共同保卫家园。团结合作,寻找修复虚拟世界之法,最终打败黑暗势力,恢复平衡。现在,请你集结一支强大的队伍,以应对地球危机!

8.1 题目要求

请在 js/store.jscomponents 文件夹下两个组件中补全代码,最终实现可以从英雄列表中选择英雄,建立一支属于自己的队伍的功能。

具体需求如下:

  1. 完成可选英雄的渲染

    所有英雄的信息存放于 js/heroes.json 中,请在 components/HeroList.js 组件中使用 axios 发送请求(请求地址写死为 ./js/heroes.json ,以免影响检测通过)获取到英雄信息,将其保存至 js/store.jsheroes 数组中,并将所有英雄渲染在“可选英雄”列表中(每条英雄信息是一个 li 标签,需要生成对应英雄数量的 li 标签作为子元素插入到 .hero-list ul 中)。

    js/heroes.json 中英雄信息的解释: js/heroes.json 中存放了每个英雄的信息,以下述信息为例,其中 id 是序号,name 是英雄姓名,ability 是英雄的能力,strength 是英雄的强度。

    { id: 1, name: "绯红女巫", ability: "混沌魔法", strength: 98 }
    

    用于展示可选英雄的 li 标签的 DOM 结构如下:

    <li class="hero-item">
        <span>name</span>
        <span>ability</span>
        <span>strength</span>
        <button>添加至队伍/已添加</button>
    </li>
    

最终完成效果如下:

图片描述

  1. 实现“添加英雄”功能

    在需求 1 实现的基础上,当点击可选英雄左侧的“添加至队伍”按钮时,该按钮文本变为“已添加”,按钮变为禁用状态(按钮的 disabled 属性变为 false),并且将当前被选择的英雄的信息添加至页面右侧“我的队伍”列表中(英雄信息是一个 li 标签,生成对应结构的 li 标签作为子元素插入到 .team-list ul 中)。

    用于展示已选英雄的 li 标签的 DOM 结构如下:

    <li class="team-item">
        <span>name</span>
        <span>strength</span>
        <button>移除</button>
    </li>
    
  2. 实现“移除英雄”功能

    在需求 2 实现的基础上,当点击已选英雄左侧“移除”按钮(.sort-button)时,该英雄从“我的队伍”列表中被移除,“可选英雄”列表中该英雄左侧“已添加”按钮文本变回“添加至队伍”,且按钮变为可用状态(按钮的 disabled 属性变为 true)。

  3. 实现“按实力排序”功能

    在需求 2 实现的基础上,当点击“按实力排序”按钮(.sort-button)时,将“我的队伍”中的英雄按照英雄强度降序排列。

  4. 实现实时显示队伍战斗力功能

    在需求 2 、3 实现的基础上,.total-strength 中应实时显示当前队伍的战斗力,即“我的队伍”中所有英雄的强度之和。

最终完成效果如下:

图片描述

说明:

  • 页面中共有 3 个组件,分别是 app 根组件,hero 组件和 team 组件,其中后两个组件一直作为 app 根组件的子组件出现。而上文中对“可选英雄列表”和“我的队伍列表”的描述分别指 hero 组件与 team 组件。
  • 游戏中的人物数据会有很多界面/组件共享,所以需要存储在 pinia 状态管理器中,减少了程序花销也避免了同步问题。

8.2 题目分析

8.3 源代码

HeroList:

// TODO:补全代码,实现目标效果
const HeroList = {
  template: `
  <div class="hero-list">
    <h2>可选英雄</h2>
    <ul>
      <li class="hero-item" v-for="(item,index) in store.heroes" :key="item.id">
        <span>{{item.name}}</span>
        <span>{{item.ability}}</span>
        <span>{{item.strength}}</span>
        <button @click=store.add(item.id) :disabled="item.btn">{{ item.btn ?  '已添加' : '添加至队伍' }}</button>
      </li>
    </ul>
  </div>
  `,
  setup() {
    //第一步获取数据
    const store = useHeroStore()
    axios
      .get('./js/heroes.json')
      .then((res) => {
        store.heroes = res.data
      })
      .catch((err) => {
        console.log(err)
      })
    return {
      store
    }
  }
}
// TODOEnd

TeamList:

// TODO:补全代码,实现目标效果
const TeamList = {
  template: `
  <div class="team-list">
      <h2>我的队伍</h2>
      <ul>
        <li class="team-item" v-for="(item,index) in store.team" :key="item.id">
          <span>{{item.name}}</span>
          <span>{{item.strength}}</span>
          <button @click=store.removeHero(item.id)>移除</button>
        </li>
      </ul>
      <button class="sort-button" @click=store.sort>按实力排序</button>
      <p class="total-strength">当前队伍战斗力:{{store.totalStrength}} </p>
  </div>
  `,
  setup() {
    const store = useHeroStore()
    return {
      store
    }
  }
}
// TODOEnd

store.js:

const { defineStore } = Pinia
const { ref } = Vue
 
const useHeroStore = defineStore('hero', {
  state: () => ({
    heroes: [], //英雄列表
    team: [] // 队伍列表
  }),
  // TODO:补全代码,实现目标效果
  getters: {
    //计算出战力总和strength
    totalStrength() {
      return this.team.reduce((total, hero) => {
        return total + hero.strength
      }, 0)
    }
  },
  actions: {
    add(id) {
      this.heroes[id - 1].btn = true
      this.team.push(this.heroes[id - 1])
    },
    removeHero(id) {
      this.heroes[id - 1].btn = false
      //移出team中的元素
      this.team = this.team.filter((hero) => hero.id !== id)
    },
    sort() {
      //按照实力排序strength
      this.team.sort((a, b) => {
        return b.strength - a.strength
      })
    }
  }
  // TODOEnd
})

9.实时展示权限日志

在实际开发中,我们可能会遇到记录用户操作的场景,我们需要将用户的操作以日志的形式记录下来,便于进行数据监控。

本题通过 Node.js 、Vue 和 Axios 来帮助考生实现这个效果,需要在已提供的基础项目中使用上述知识点来完善代码,最终实现需求中的具体功能。

9.1 题目要求

请在 js/node.jsjs/index.js 文件中补全 TODO 部分的代码,最终实现以下需求:

一、请在 js/node.js 文件中补全代码,完善服务器的相应业务功能。

代码中已经搭建好了一个本地服务器,端口固定为 8080 ,直接在终端运行下面的命令即可启动服务。

node js/node.js

服务器做出的响应需要调用已经实现的 send 方法,参数可参见注释,响应的状态码 code 值为 0 。如未调用或者参数传递有问题,会导致检测不通过。

在读取文件数据时,建议使用绝对路径读取,本题已经在基础项目中给出相应文件的绝对路径,考生自行判断使用绝对路径或相对路径读取文件,本题对路径没有严格要求,能正确读取文件数据即可。

目标 1: 处理服务器的 GET 请求,请求地址为 /users 。仅需考虑如何读取 data.json 文件中的用户权限数据。文件数据读取完毕后,请调用封装好的 send 方法将文件数据响应给客户端。

目标 2: 处理服务器的 PUT 请求,请求地址为 /editUser 。仅需考虑如何根据已经获取到的请求体 body 修改目录中 data.json 文件数据对应的用户权限,请严格按照 data.json 文件中的对象格式修改,如不按要求则本需求检测不通过。文件数据修改完毕后,需要调用已实现的 send 方法将最新的文件数据响应给客户端。

本需求的对象格式为:

{
    id: string, //用户的id
    name: string, //用户姓名
    power: boolean //用户的登录权限 true(允许用户登录)||false(禁止用户登录)
}

目标 3: 处理服务器的 POST 请求,请求地址为 /logger 。需考虑如何将已经获取到的请求体 body 转化成满足要求的对象(见下方示例,id 属性值可以使用代码中提供的 getLoggerId 方法获取唯一 id 也可以自行给定,需要保证该属性值是唯一的)并在该对象转化成 JSON 格式的末尾添加换行符如不添加换行符会导致检测不通过)然后添加到目录中 logger.json 文件数据的末尾。文件数据添加完毕后,请调用已实现的 send 方法将最新一条日志数据响应给客户端。

本需求要求的对象格式如下:

   {
     id: string, //日志的id
     msg: string, //日志信息(请求体数据)
     time: string, //创建日志的当前时间
  }

二、请在 js/index.js 文件中补全代码,页面结构已经给出,请参考 index.html 文件,考生仅需实现请求数据渲染页面的相应功能。

js/index.js 文件中已经实现了 parseRes 方法,该方法用于解析服务器响应的数据,参数为响应对象。如果不调用该方法进行解析响应数据,可能会导致检测不通过

目标 4: 请在 js/index.js 文件的 setup 方法中补全代码,实现获取用户权限数据并重新渲染用户权限列表功能。

  • 在页面挂载前,向服务器请求用户权限数据,请求地址为 /users ,请求方法为 GET ,获取到响应后需调用已实现的 parseRes 方法解析响应数据。

  • 将解析后的结果赋值给对应的响应式数据 userList,用户权限列表渲染效果如下:

    初始用户权限列表渲染

目标 5: 请在 js/index.js 文件的 setup 方法中补全代码,实现点击复选框修改对应的用户权限并打印日志信息的功能。

  • 处理代码中提供的 handleChange 事件,获取当前点击的复选框的自定义属性:data-id ,通过 id 查找对应的用户,发送请求,请求方法为 PUT ,请求地址为 /editUser ,请求体(类型是字符串)格式见下方示例。获取响应后调用已实现的 parseRes 方法解析响应数据,重新为对应的响应式数据赋值,达到重新渲染用户权限列表的目的。 请求体格式为:

    {
        data:{
            power: boolean //当前用户的登录权限 值为true||false
        },
        params: {
            id: string //当前用户的id
        }
    }
    
  • 处理代码中提供的 handleChange 事件,发送请求,请求方法为 POST,请求地址为 /logger ,请求体(类型是字符串)格式见下方示例。获取响应后调用已实现的 parseRes 方法解析响应数据,重新为对应的响应式数据赋值,达到重新渲染日志查看区域的目的。本需求还需将最新一条日志显示在该区域的最上面。 请求体格式为:

    {
    data:`超级管理员将用户${用户名}设置为${getPowerText(用户修改后的权限)}权限`
    }
    

实现效果如下:

最终效果

9.2 题目分析

9.3 源代码

node.js:

/**
 * 请完成下面的 TODO 部分,其他代码请勿改动
 */
const fs = require('fs')
const http = require('http')
const path = require('path')
const dataUrl = path.resolve(__dirname, '../data.json')
const loggerUrl = path.resolve(__dirname, '../logger.json')
// 获取唯一的id
function getLoggerId() {
  return Buffer.from(Date.now().toString()).toString('base64') + Math.random().toString(36).substring(2)
}
 
/**
 * 该方法统一了服务器返回的消息格式,并返回给客户端
 * @param {*} res 响应 response
 * @param {*} code 状态码,默认为 0 代表没有错误,如果有错误固定为 404
 * @param {*} msg 错误消息,固定为空字符串即可 ''
 * @param {*} data 响应体,为 js 对象,若 data 为 utf-8 编码时需要使用 eval(data) 处理
 */
function send(res, code, msg, data) {
  const responseObj = {
    code,
    msg,
    data
  }
  const da = JSON.stringify(responseObj)
  res.setHeader('Content-Type', 'application/json;charset=utf-8')
  res.write(da)
  res.end()
}
 
function handleStatic(res, pathName, part) {
  const content = fs.readFileSync(path.resolve(__dirname, pathName))
  let contentType = 'text/html'
  switch (part) {
    case 'css':
      contentType = 'text/css'
      break
    case 'js':
      contentType = 'text/js'
      break
  }
  res.writeHead(200, 'Content-Type', contentType)
  res.write(content)
  res.end()
}
 
const server = http.createServer((req, res) => {
  res.setHeader('Access-Control-Allow-Origin', '*')
  if (req.url === '/') {
    handleStatic(res, '../index.html', '')
  } else if (req.url === '/css/index.css') {
    handleStatic(res, `..${req.url}`, 'css')
  } else if (req.url === '/js/index.js') {
    handleStatic(res, `..${req.url}`, 'js')
  } else if (req.url === '/js/axios.min.js') {
    handleStatic(res, `..${req.url}`, 'js')
  } else if (req.url === '/js/vue3.global.min.js') {
    handleStatic(res, `..${req.url}`, 'js')
  }
 
  if (req.method === 'GET' && req.url === '/users') {
    // TODO 处理获取文件内容的操作
    //读取data.json中的数据
    let fileContent = fs.readFileSync(dataUrl, 'utf-8')
    let data = JSON.parse(fileContent)
    if (fileContent) {
      //将读取到的数据转化为json格式
      //将json格式的数据响应给客户端
      send(res, 0, '', data)
    }
  } else if (req.method === 'PUT' && req.url === '/editUser') {
    let fileContent = fs.readFileSync(dataUrl, 'utf-8')
    let data = JSON.parse(fileContent)
    let body = ''
    req.on('readable', () => {
      let chunk = ''
      if (null !== (chunk = req.read())) {
        body += chunk
      }
    })
    req.on('end', () => {
      if (body) {
        // TODO 处理更改文件数据并将最新的文件数据响应给客户端
        //处理put请求
        let bodyData = JSON.parse(body)
        //修改data.json中的数据
        data.forEach((item) => {
          if (item.id == bodyData.id) {
            item.power = bodyData.power
          }
        })
        //存储文件数据到data.json中
        fs.writeFileSync(dataUrl, JSON.stringify(data))
        send(res, 0, '', data)
      }
    })
  } else if (req.method === 'POST' && req.url === '/logger') {
    let body = ''
    req.on('readable', () => {
      let chunk = ''
      if (null !== (chunk = req.read())) {
        body += chunk
      }
    })
    req.on('end', () => {
      let fileContentLog = fs.readFileSync(loggerUrl, 'utf-8')
      //判断是否有日志
      let dataLog = []
      if (fileContentLog) {
        dataLog = JSON.parse(fileContentLog)
      }
      let fileContentUser = fs.readFileSync(dataUrl, 'utf-8')
      let dataUser = JSON.parse(fileContentUser)
      if (body) {
        // TODO 处理新增日志
        let bodyData = JSON.parse(body)
 
        let dataJson = {
          id: getLoggerId(),
          msg: bodyData.data,
          // 时间格式为:2023/6/6 上午8:10:35
          time: `${getTime()}`
        }
        //存储日志
        dataLog.unshift(dataJson)
        // 并在该对象转化成 JSON 格式的末尾添加换行符(如不添加换行符会导致检测不通过)
        fs.writeFileSync(loggerUrl, JSON.stringify(dataLog, null, 2) + '\n')
        send(res, 0, '', dataJson)
      }
    })
  }
})
 
function getTime() {
  // 获取当前时间
  const currentDate = new Date()
  // 获取年、月、日、时、分、秒
  const year = currentDate.getFullYear()
  const month = currentDate.getMonth() + 1 // 月份是从 0 开始的,所以要加 1
  const day = currentDate.getDate()
  const hours = currentDate.getHours()
  //获取是上午还是下午
  const amPm = hours >= 12 ? '下午' : '上午'
  const minutes = currentDate.getMinutes()
  const seconds = currentDate.getSeconds()
  // 格式化时间
  const formattedTime = `${year}/${month}/${day} ${amPm}${hours}:${minutes}:${seconds}`
  return formattedTime
}
 
server.listen(8080, () => {
  console.log('Server running on port 8080')
})

index.js:

/**
 * 请完成下面的 TODO 部分,其他代码请勿改动
 */
 
// 对响应进行统一处理,如果不调用该函数,可能导致判题出错
// 参数为服务器的响应对象
function parseRes(res) {
  return (res.json && res.json()) || res.data
}
 
const App = {
  setup() {
    const { onMounted } = Vue
    const data = Vue.reactive({
      userList: [], //用户数组
      loggerList: [] //日志数组
    })
    const getPowerText = (power) => {
      return power ? '可以登录' : '禁止登录'
    }
    const handleChange = async (e) => {
      if (e.target.tagName !== 'INPUT') {
        return
      }
      // TODO 处理发送请求修改当前用户的权限并更新一条日志记录
      //处理put请求
      let res = await axios.put(`/editUser`, {
        id: e.target.dataset.id,
        power: e.target.checked
      })
      if (res.status == 200) {
        data.userList = parseRes(res.data)
      } else {
        console.log('修改失败')
      }
      //调用post请求,添加一条修改日志
      //用id找出用户名
      let userName = data.userList.find((item) => item.id == e.target.dataset.id).name
      let postRes = await axios.post('/logger', {
        data: `超级管理员将用户${userName}设置为${getPowerText(e.target.checked)}权限`
      })
      if (postRes.status == 200) {
        //将数据放在数组首
        let a = parseRes(postRes.data)
        data.loggerList.unshift(a)
      } else {
        console.log('添加日志失败')
      }
    }
 
    // TODO 在页面挂载之前请求用户数据并修改对应的响应数据
    //利用axios获取数据
    const getUserData = async () => {
      let res = await axios.get('/users')
      if (res.status == 200) {
        data.userList = res.data.data
      } else {
        getUserData()
      }
    }
    onMounted(() => {
      getUserData()
    })
    return {
      data,
      handleChange,
      getPowerText,
      getUserData
    }
  }
}
const app = Vue.createApp(App)
app.mount(document.querySelector('#app'))

10.账户验证

生活中各种平台的登录方式,除了账号密码登陆外,也有使用手机号登录的选项。现在小蓝想实现一个使用手机号验证码登录的场景,但是小蓝由于学艺不精,关键部分功能不知道如何实现,请你帮助他完成。

10.1 题目要求

请在 index.html 文件中补全代码,最终实现使用手机号发送验证码验证登录的效果

具体需求如下:

  1. 实现发送验证码功能

    phone 组件中,当点击下一步按钮时( #btn ),对输入框( #numberInput )中输入的手机号码的有效性以及是否同意下方协议(复选框 #checkbox 是否是选中状态)做检验。当且仅当上述两条件均满足时,在网页右上角弹出成功提示框,标题为“发送成功”,提示内容为“您的验证码为XXXXXX”(XXXXXX是生成的验证码),且组件由 phone 组件跳转至 check 组件;否则弹出失败提示框,标题为“发送失败”,提示内容为“无效的手机号码”或“请先阅读并同意下方协议”,若都不满足,则提示内容为“请先阅读并同意下方协议”。

    发送验证码成功后,在 check 组件中的 .hassend i 标签中显示经过处理的的手机号码(保留前三位和后两位,其余位替换为字符 * )。

说明:

  • 若手机号码是以 18 开头的 11 位数字,则视为有效,否则视为无效。
  • 验证码为随机生成的 6 位数字,由考生自行生成。
  • 注意提示内容必须为上述字眼,不能有错别字或空格,否则影响判分结果。

最终完成效果如下:

图片描述

  1. 实现输入验证码验证的功能

    发送验证码成功后,在 check 组件的 #code-container 中有 6 个 input 标签,分别用于输入验证码的每一位,第一个 input 默认聚焦。在向验证码输入框中输入内容时,鼠标焦点按照下述规则跳转:

    • 除了最后一个 input 标签,在任意 input 标签中输入数字后,鼠标焦点会自动跳转至当前 input 标签的下一个 input 标签。
    • 除了第一个 input 标签,在任意 input 标签中删除数字时,鼠标焦点会自动跳转至当前 input 标签的上一个 input 标签。

    当每个 input 标签均输入过内容时会自动验证输入的验证码是否正确,如果正确,则在页面右上角弹出成功提示框,标题为“验证成功”,提示内容为“欢迎回来”,且组件由 check 组件跳转至 success 组件;如果不正确,则清空 6 个 input 输入框中的值,在右上角弹出失败提示框,标题为“验证失败”,提示内容为“您输入的验证码有误”,

    点击重新发送( #resend )会重新发送验证码,生成的验证码规则及提示内容同需求 1 。

最终完成效果如下:

图片描述

说明:

  • 题中提供了 pinia 状态管理库,考生自行选择是否使用。
  • 若多次生成验证码,则以最后一次生成的验证码为准。
  • 如果本题使用到了定时器,则定时器的等待时间不应超过 20ms ,以免影响检测通过。
  • 页面中共有四个组件,分别是 app 根组件,phone 组件,check 组件,success 组件。在页面使用过程中,后三个组件总会作为 app 根组件的子组件出现,请使用 <component></component> 标签实现后三个组件的跳转切换效果。
  • 本题在右上角弹出的成功或失败提示框是使用 element-plus 中的 Notification 通知 实现的,其 API 如下:
名称说明类型默认
title标题string“”
message提示内容string“”
type提示类型enum( “success” 、“warning”、“info”、“error”、“”)“”
position弹出位置enum( “top-right” 、“top-left”、“bottom-right”、“bottom-left”)“top-right”
duration显示时间,单位为毫秒,值为 0 则不会自动关闭number4500

使用示例:

<template>
  <el-button plain @click="open1"> Success </el-button>
  <el-button plain @click="open2"> Error </el-button>
</template>

<script lang="ts" setup>
import { ElNotification } from 'element-plus'

const open1 = () => {
  ElNotification({
    title: 'Success',
    message: 'This is a success message',
    type: 'success',
  })
}

const open2 = () => {
  ElNotification({
    title: 'Error',
    message: 'This is an error message',
    type: 'error',
  })
}
</script>

10.2 题目分析

10.3 源代码

<!DOCTYPE html>
<html>
 
<head>
  <meta charset="utf-8" />
  <title>账户验证</title>
  <link rel="stylesheet" type="text/css" href="./css/index.css" />
  <link rel="stylesheet" href="./css/element-plus@2.3.7/index.css">
  <script src="./js/vue3.global.js"></script>
  <script src="./css/element-plus@2.3.7/index.full.js"></script>
  <script type="importmap">
    {
      "imports": {
        "vue-demi": "./js/index.mjs",
        "vue": "./js/vue.esm-browser.prod.js",
        "pinia": "./js/pinia.esm-browser.js"
      }
    }
  </script>
  <script src="./js/pinia.esm-browser.js" type="module"></script>
</head>
 
<body>
  <!-- app根组件开始 -->
  <div id="app">
    <div class="header">
      <img class="back-btn" src="images/arrow.png" />
      <span id="main_title">使用手机号登录</span>
      <span class="blank"></span>
    </div>
    <component :is="showName"></component>
  </div>
  <!-- app根组件结束 -->
 
  <!-- phone组件开始 -->
  <template id="phone">
    <div>
      <ul class="phone">
        <span>输入手机号码</span>
        <li>
          <input v-model="phoneVal" type="text" autofocus id="numberInput" />
        </li>
        <li>
          <input v-model="isSure" type="checkbox" name="" id="checkbox" />
          <span>已阅读并同意
            <a href="javascript:;">服务协议</a><a href="javascript:;">隐私保护指引</a>
          </span>
        </li>
        <button id="btn" @click="nextStep">下一步</button>
      </ul>
    </div>
  </template>
  <!-- phone组件结束 -->
 
  <!-- check组件开始 -->
  <template id="check">
    <ul class="number">
      <span>输入短信验证码</span>
      <li class="hassend">已向
        <i>{{ handlePhoneVal }}</i>
        发送验证码
      </li>
      <li class="code-container">
        <input v-for="(item, index) in verificationCodeInput" :key="index" v-model="item" @input="handleInput(index)"
          @keydown="handleKeyDown(index)" class="code" type="number" min="0" max="9" ref="codeInput{{index}}"
          required />
      </li>
      <a href="javascript:;" id="resend" @click="resendCode">重新发送</a>
    </ul>
  </template>
  <!-- check组件结束 -->
 
  <!-- success组件开始 -->
  <template id="success">
    <div class="success">
      <ul>
        <div>验证成功!</div>
        <div>5s后将自动跳转</div>
      </ul>
    </div>
  </template>
  <!-- success组件结束 -->
</body>
 
<script type="module">
  import { createPinia } from 'pinia';
  import { createApp, ref, reactive, provide, inject, onBeforeMount } from 'vue';
  const { ElNotification } = ElementPlus;
 
  const app = createApp({
    setup() {
      const data = reactive({
        showName: 'phone',
      });
 
      const code = ref([]);
      const phoneVal = ref('');
      const createCode = function () {
        let res = '';
        function* _create() {
          let count = 0;
          while (++count <= 6) {
            yield Math.floor(Math.random() * 10);
          }
        }
        for (const iterator of _create()) {
          res += iterator;
        }
        return res;
      };
 
      const handlePhone = (num) => {
        let res = '';
        for (let idx in num) {
          if (idx > 2 && idx < num.length - 2) {
            res += '*';
          } else {
            res += num[idx];
          }
        }
        return res;
      };
 
      provide('code', code);
      provide('phoneVal', phoneVal);
      provide('createCode', createCode);
      provide('data', data);
      provide('handlePhone', handlePhone);
 
      return {
        ...data,
      };
    },
  });
 
  app.use(ElementPlus);
  app.use(createPinia());
 
  app.component('phone', {
    template: '#phone',
    setup() {
      const isSure = ref('');
      const phoneVal = inject('phoneVal');
      const code = inject('code');
      const createCode = inject('createCode');
      const data = inject('data');
 
      function verifyPhone(num) {
        if (num.length !== 11) return false;
        return num[0] === '1' && num[1] === '8';
      }
 
      return {
        isSure,
        phoneVal,
        nextStep() {
          if (!isSure.value)
            return ElNotification({
              title: '发送失败',
              message: '请先阅读并同意下方协议',
              type: 'error',
            });
          if (!verifyPhone(phoneVal.value))
            return ElNotification({
              title: '发送失败',
              message: '无效的手机号码',
              type: 'error',
            });
          code.value = createCode();
          ElNotification({
            title: '发送成功',
            message: '您在验证码为' + code.value,
            type: 'success',
          });
          data.showName = 'check';
        },
      };
    },
  });
 
  app.component('check', {
    template: '#check',
    setup() {
      const phoneVal = inject('phoneVal');
      const handlePhoneVal = inject('handlePhone')(phoneVal.value);
      const data = inject('data');
      const code = inject('code');
      const createCode = inject('createCode');
      const verificationCodeInput = Array(6).fill('');
 
      onBeforeMount(() => {
        setTimeout(() => {
          const oCodeIptList = [...document.getElementsByClassName('code')];
 
          oCodeIptList[0].focus();
 
          oCodeIptList.map((item) => {
            item.oninput = function () {
              if (item.value) {
                item?.nextElementSibling && item?.nextElementSibling.focus();
              } else {
                item?.previousElementSibling && item?.previousElementSibling.focus();
              }
              trackVal();
            };
          });
 
          function trackVal() {
            const val = verificationCodeInput.join('');
            if (val.length === 6) {
              if (val === code.value) {
                ElNotification({
                  title: '验证成功',
                  message: '欢迎回来',
                  type: 'success',
                });
                data.showName = 'success';
              } else {
                ElNotification({
                  title: '验证失败',
                  message: '您输入的验证码有误',
                  type: 'error',
                });
                verificationCodeInput.fill('');
                oCodeIptList[0].focus();
              }
            }
          }
        });
      });
 
      return {
        handlePhoneVal,
        verificationCodeInput,
        handleInput(index) {
          if (index < 5 && verificationCodeInput[index].length === 1) {
            this.$refs[`codeInput${index + 1}`]?.focus();
          } else if (index > 0 && verificationCodeInput[index].length === 0) {
            this.$refs[`codeInput${index - 1}`]?.focus();
          }
          trackVal();
        },
        handleKeyDown(index) {
          if (event.key === 'Backspace' && index > 0) {
            this.$refs[`codeInput${index - 1}`]?.focus();
          }
        },
        trackVal() {
          const val = verificationCodeInput.join('');
          if (val.length === 6) {
            if (val === code.value) {
              ElNotification({
                title: '验证成功',
                message: '欢迎回来',
                type: 'success',
              });
              data.showName = 'success';
            } else {
              ElNotification({
                title: '验证失败',
                message: '您输入的验证码有误',
                type: 'error',
              });
              verificationCodeInput.fill('');
              this.$refs['codeInput0']?.focus();
            }
          }
        },
        resendCode() {
          code.value = createCode();
          ElNotification({
            title: '发送成功',
            message: '您的验证码为' + code.value,
            type: 'success',
          });
        },
      };
    },
  });
 
  app.component('success', {
    template: '#success',
  });
 
  app.mount('#app');
</script>
 
</html>
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值