第十三届蓝桥杯大赛Web应用与开发国赛职业院校组真题解析

232 阅读3分钟

第十三届蓝桥杯大赛Web国赛专科组赛题解析

本文是第十三届蓝桥杯大赛Web国赛职业院校组赛题解析

01 传送门 (5 分)

通过获取 html 标签的 scrollTop 的值,判断区间给对应元素添加 active

$(window).scroll(function ($event) {
  // 页面滚动到指定范围,对应的侧边按钮字体变色
  // TODO:请补充代码实现功能
  // 获取当前滚动条位置
  const h = document.querySelector('html').scrollTop
  // 获取导航标签
  const $children = $('#lift').children()
  if (h < 960) {
    setActive($children.eq(0))
  } else if (h >= 960 && h < 1920) {
    setActive($children.eq(2))
  } else if (h >= 1920) {
    setActive($children.eq(4))
  }
})

/**
 * 设置导航高亮
 * @param {\} $e 需要设置高亮的元素
 */
function setActive($e) {
  $e.addClass('active-color').siblings('a').removeClass('active-color')
}

/**
 * @param {Object} scrollTopVal:到达指定位置需要滚动的高度
 * 点击按钮,滚动到指定位置
 */
function toFunction(scrollTopVal) {
  // TODO:请补充代码实现功能
  document.querySelector('html').scrollTop = scrollTopVal
}

02 新鲜的蔬菜 (5 分)

主要考察 flex ,相信熟悉 flex 小伙伴马上就能做出来

/* TODO:待补充代码 */
#box1{
  display: flex;
  justify-content: center;
  align-items: center;
}

#box2{
  display: flex;
  justify-content: space-between;
}
#box2 span:last-child{
  align-self: end;
}

#box3{
  display: flex;
}
#box3 span:nth-of-type(2){
  align-self: center;
}
#box3 span:last-child{
  align-self: end;
}

03 小兔子找胡萝下(10 分)

这里主要是通过 active 类来确定 小兔子的位置,找到小兔子的位置后就没啥难点了。

// 定义常量 active
const active = 'active'
// 获取提示语句容器
const $result = $('.result')
// 获取输入框
const $input = $('input[type=number]')

// TODO:游戏开始
function start() {
  console.log(document.querySelector('#start'))
  document.querySelector('#start').style.display = 'none'
  document.querySelector('#move').style.display = 'block'
}

// TODO:重置游戏
function reset(e) {
  $('.container>div').eq(0).addClass(active).siblings().removeClass(active)
  $result.text('')
  $input.val('')
  $('#reset').css('display', 'none')
  $('#start').css('display', 'block')
}

// TODO:移动
function move() {
  const step = Number($input.val())
  // 如果输入的数字不正确直接 return
  if (![1, 2].includes(step)) return $result.text('输入的步数不正确,请重新输入。')
  // 获取所有草坪div
  const $div = $('.container>div')
  // 遍历草坪 div 查询当前兔子位置
  for (const div of $div) {
    // 如果当前 div 有 active 样式,则说明兔子在此位置
    if ($(div).hasClass(active)) {
      // 移除当前 div 的 active 样式
      $(div).removeClass(active)
      // 计算兔子前进坐标
      const index = $(div).index() + step
      // 给对应坐标的 div 添加 active 样式
      $div.eq(index).addClass(active)
      // 踩到炸弹提示
      if (index === 12) resetState('哎呀!兔子踩到炸弹了,游戏结束!')
      // 找到胡萝卜提示
      if (index === 23) resetState('小兔子吃到胡萝卜啦,游戏获胜!')
      break
    }
  }
}

/**
 * 隐藏移动按钮
 * 显示重置按钮
 * 根据参数输出提示
 * @param {*} tip 提示语句
 */
function resetState(tip) {
  $('#move').css('display', 'none')
  $('#reset').css('display', 'block')
  $result.text(tip)
}

04 猜硬币 (10 分)

主要考察正则和随机数

// 将输入的值中 1-9 组成一个数组
function findNum(input_values) {
  // TODO:待补充代码
  const arr = new Set(input_values.match(/\d/g))
  return Array.from(arr).map(v => Number(v))
}

// 将 1-9 中三个不重复的随机数放入数组中,并返回这个数组
let randomCoin = () => {
  // TODO:待补充代码
  const ran = new Set()
  while (true) {
    ran.add(Math.floor(Math.random() * 9) + 1)
    if (ran.size === 3) break
  }
  return Array.from(ran)
}

05 用什么来做计算 B(15 分)

这题主要是使用 jseval() 方法来进行表达式的计算

// TODO:请补充代码
const formula = document.querySelector('#formula') // 式子
const result = document.querySelector('#result') // 结果
// 给 AC 绑定事件
document.querySelector('#reset').onclick = () => {
  formula.value = ''
  result.value = ''
}
// 给 = 绑定事件
document.querySelector('#equal').onclick = () => {
  let formulaStr = formula.value
  formulaStr = formulaStr.replace('x', '*')
  formulaStr = formulaStr.replace('÷', '/')
  result.value = eval(formulaStr)
}
// 给 √ 绑定事件
document.querySelector('#sqrt').onclick = () => {
  const num = formula.value
  result.value = num < 0 ? 'NaN' : Math.pow(num, 2)
}
// 遍历按钮绑定事件
document.querySelectorAll('.calc-row div').forEach(e => {
  const keyword = e.innerText
  if (['AC', '=', '√'].includes(keyword)) return
  e.onclick = () => (formula.value += keyword)
})

06 开学礼物大放送(15 分)

此题为布局题,略...

07 阅读吧 (20 分)

主要考察 vue,熟悉 vue 的话毫无难度,主要在于细心。

<body>
  <div id="app">
    <!-- TODO:请在下面实现需求 -->
    <!-- 设置按钮图标 -->
    <a class="iconfont icon-setting" @click="show(true)"></a>
    <!-- 头部工具栏 -->
    <transition name="fade">
      <div class="header" v-show="showHeader">
        <ul class="tools">
          <li class="container">
            <div class="left">阅读主题</div>
            <!-- 设置主题的圆形色块 -->
            <div class="right" id="setBG">
              <a style="background-color: #f6edd4" :class="isSelected(0)" @click="changeBgColor"></a>
              <a style="background-color: #ebf4ea" :class="isSelected(1)" @click="changeBgColor"></a>
              <a style="background-color: #e9f2f5" :class="isSelected(2)" @click="changeBgColor"></a>
              <a style="background-color: #f6e8e4" :class="isSelected(3)" @click="changeBgColor"></a>
              <a style="background-color: #000000" :class="isSelected(4)" @click="changeBgColor"></a>
            </div>
          </li>
          <li class="container">
            <div class="left">字体大小</div>
            <!-- 设置字体大小的按钮 -->
            <div class="set-font">
              <a class="prev" @click="decrease">A-</a><b></b> <span class="lang">{{fontSize}}</span><b></b>
              <a class="next" @click="increase">A+</a>
            </div>
          </li>
          <li class="container" @click="show(false)">
            <!-- 关闭 x 图标 -->
            <a class="iconfont icon-close"></a>
          </li>
        </ul>
      </div>
    </transition>

    <!-- 阅读区 -->
    <p class="text-content" :style="{
          'background-color': bgColor,
          'color': color,
          'font-size': fontSize + 'px',
          'line-height': lineHeight + 'px',
    }">
      ...
      <label>————— 文章摘自著名科幻小说《三体》</label>
    </p>
  </div>
  <script type="text/javascript" src="./js/vue.js"></script>
  <script type="text/javascript">
    // TODO:请在下面实现需求
    new Vue({
      // 注意:请勿修改 data 选项中已提供的数据!!!
      el: "#app",
      data: {
        bgList: ["#f6edd4", "#ebf4ea", "#e9f2f5", "#F6E8E4", "#000000"], // 阅读主题色列表(与设置主题的圆形色块一一对应)
        showHeader: true,
        bgColor: '#f6edd4',
        fontSize: 18,
        lineHeight: 28,
        selected: 0,
      },
      computed: {
        color () {
          return this.bgColor === this.bgList[4] ? '#ffffff' : '#333333'
        },
      },
      methods: {
        show (isShow) { this.showHeader = isShow },
        isSelected (i) {
          return { 'iconfont icon-selected': i === this.selected }
        },
        changeBgColor ($e) {
          const a = document.querySelectorAll('#setBG a')
          a.forEach((e, i) => (e === $e.target) ? this.selected = i : 0)
          this.bgColor = this.bgList[this.selected]
        },
        increase () {
          if (this.fontSize === 48) return
          this.fontSize += 2
          this.lineHeight = this.fontSize + 10
        },
        decrease () {
          if (this.fontSize === 12) return
          this.fontSize -= 2
          this.lineHeight = this.fontSize + 10
        }
      }
    });
  </script>
</body>

</html>

08 新增地址 (20 分)

这题考察原生 js ,虽然有点麻烦,但是题目没难度

/**
 * 返回当前选择的option节点以及对应下标
 * @param {*} nodes 需要遍历的option节点
 * @returns 当前节点和下标
 */
function getSelected(nodes) {
  for (const i in nodes) {
    if (nodes[i].selected) return { node: nodes[i], index: Number(i) }
  }
}

// 声明标签数组
const marks = [
  { code: 'home', name: '家' },
  { code: 'school', name: '学校' },
  { code: 'company', name: '公司' },
]
/**
 * 通过选择的标签文本获取对应的class
 * @param {*} mark 标签文本
 * @returns 返回对应的class
 */
function toMarkClass(mark) {
  for (const m of marks) {
    if (m.name === mark) {
      return m.code
    }
  }
}

// 初始化省份下拉列表内容
function provinceInit() {
  var province = document.getElementById('param_province')
  province.length = provinces.length
  for (var i = 0; i < provinces.length; i++) {
    province.options[i].text = provinces[i]
    province.options[i].value = provinces[i]
  }
}

// 选择省份后对应城市下拉列表内容渲染
function provincechange() {
  // TODO:请补充代码实现功能
  // 当前选择省份的下标
  let index = 0
  // 获取所有省份元素
  const P = document.querySelectorAll('#param_province option')
  index = getSelected(P).index
  // 获取城市容器
  const province = document.querySelector('#param_city')
  // 通过城市数量填充对应数量的option
  province.length = citys[index].length
  for (let i = 0; i < citys[index].length; i++) {
    province.options[i].text = citys[index][i]
    province.options[i].value = citys[index][i]
  }
}

/**
 * 为标签绑定单击事件。
 * 事件效果为:
 * 1、鼠标点击该标签后该标签样式显示 class=active;
 * 2、其他已选标签的 active 样式被移除;
 * 3、将选中的标签对应下标(即选择器为 “mark a” 选中的标签对应的下标)更新到 id=param_mark 的隐藏的 input 中。
 */
function addClick() {
  // TODO:请补充代码实现功能
  // 获取所有标签
  const A = document.querySelectorAll('.mark a')
  // 设置隐藏域默认值
  document.querySelector('#param_mark').value = A[0].text
  // 遍历标签并绑定点击事件
  for (const a of A) {
    a.onclick = () => {
      // 移除所有标签高亮
      A.forEach(e => e.classList.remove('active'))
      // 给当前选择的标签添加高亮
      a.classList.add('active')
      // 设置隐藏域值
      document.querySelector('#param_mark').value = a.text
    }
  }
}

/**
 * 解析表单数据
 * @returns 提交的表单数据
 */
function getFormData() {
  const address = document.querySelector('#param_address').value.trim()
  const mark = document.querySelector('#param_mark').value
  const name = document.querySelector('#param_name').value.trim()
  const phone = document.querySelector('#param_phone').value.trim()
  // 获取元素节点
  const PROVINCES = document.querySelectorAll('#param_province option')
  const CITYS = document.querySelectorAll('#param_city option')
  // 获取参数
  const province = getSelected(PROVINCES).node.text
  const city = getSelected(CITYS).node.text
  const markClass = toMarkClass(mark)
  // 拼接地址列表
  const content =
    `
      <li>
        <div class="show-area">
          <label class="${markClass}">${mark}</label>
          <span>${(province, city)}</span>
        </div>
        <div class="show-address">
          <span>${address}</span>
          <a><img src="./images/edit.png" /></a>
        </div>
        <div class="show-info">
          <span>${name}</span>
          <span>${phone}</span>
        </div>
      </li>` + document.querySelector('.address-list').innerHTML
  return { address, name, phone, content }
}

// 提交信息后,读取并显示在页面中
function saveInfo() {
  // TODO:请补充代码实现功能
  const { address, name, phone, content } = getFormData()
  if (!address || !name || !phone) {
    document.querySelector('.warning-dialog').style.display = 'block'
  } else {
    // 渲染模板数据
    document.querySelector('.address-list').innerHTML = content
    // 设置页面头和模块显隐
    document.getElementById('main_title').innerText = '地址管理'
    document.querySelector('.address-list').style.display = 'block'
    document.querySelector('.address').style.display = 'none'
    document.querySelector('.user-info').style.display = 'none'
  }
}

// 切换新增地址和地址管理的显隐
function back() {
  if (document.getElementById('main_title').innerHTML == '地址管理') {
    document.getElementById('main_title').innerHTML = '新增地址'
    document.querySelector('.address-list').style.display = 'none'
    document.querySelector('.address').style.display = 'block'
    document.querySelector('.user-info').style.display = 'block'
  }
}
// 页面加载后的初始化操作
function init() {
  // 初始化省份下拉列表内容
  provinceInit()
  // 为标签绑定单击事件
  addClick()
}

window.onload = function () {
  // 初始化
  init()
}

09 天气趋势 B(25分)

这题 vue 和 echarts 结合的综合题,主要难点在于对 未来7天 的数据处理,还是有点难度的

<div class="month">
  <ul>
    <!-- TODO:待补充代码 在下面的 li 标签中完成 12个月份 (即 monthList) 的渲染  -->
    <li v-for="(item,key,index) in monthList" @click="toggleMonth(key, index)" :class="active(key)">{{item}}
    </li>
  </ul>
</div>
<div class="chart">
  <!-- TODO:待补充代码  -->
  <!-- curMonth  未来七天和本月 tab 切换,只有当前月才显示 -->
  <div id="curMonth" v-show="tipShow">
    <div class="title">
      <h3>{{typeTitle}}</h3>
      <div class="type">
        <span @click="toggleTip('next')" :class="nextSeven" id="seven">未来7天</span>
        <span @click="toggleTip('month')" :class="currentMonth" id="current">本月</span>
      </div>
    </div>
  </div>
  <div id="chart"></div>
</div>

<script>
var vm = new Vue({
  el: "#app",
  data: {
    chart: null, // 图表
    chartOptions: null, // 图表配置项
    typeTitle: "本月天气", // tip标题
    monthList: {...},
    data: [], // 温度数据
    xDate: [], // 日期
    yDate: [], // 温度
    monthActive: 'January', // 月份高亮状态
    tipActive: 'month', // Tip高亮状态
    tipShow: false, // 本月和未来七天模块显隐
    curMonth: new Date().getMonth(), // 本月
  },
  computed: {
    currentMonth () { return this.tipActive === 'month' ? 'active' : '' },
    nextSeven () { return this.tipActive === 'next' ? 'active' : '' }
  },
  mounted: async function () {
    // 获取温度数据
    const response = await axios.get('./js/weather.json')
    this.data = response.data
    // 默认显示一月份的数据
    this.yData = this.data[0][this.monthActive]
    this.xData = this.generateMonth(this.yData.length)
    // 初始化 echarts
    this.$nextTick(() => {
      this.initChart();
    });
  },
  methods: {
    // 切换高亮
    active (key) { return this.monthActive === key ? 'active' : '' },
    // 生成x轴日期
    generateMonth (len) { return Array.from(Array(len).keys(), n => ++n) },
    // 切换月份
    toggleMonth (key, index) {
      // 设置本月tap显隐状态
      this.tipShow = index === this.curMonth
      // 替换数据
      this.yData = this.data[index][key]
      this.xData = this.generateMonth(this.yData.length)
      // 更改高亮状态
      this.monthActive = key
      this.tipActive = 'month'
      this.initChart(); // 重新加载图表
    },
    // 切换本月和未来七天
    toggleTip (key) {
      const monthKey = Object.keys(this.monthList) // 获取所有的月份key
      this.tipActive = key // 切换高亮
      if (key === 'next') {
        this.typeTitle = '未来7天天气' // 切换标题
        let { x, y } = this.analysisCurrentMonth()
        // 判断上月剩余天数是否足够7天
        if (y.length < 7) {
          const len = 7 - y.length // 缺少天数
          const { nx, ny } = this.analysisNextMonth(len, monthKey[this.curMonth + 1])
          x = [...x, ...nx]
          y = [...y, ...ny]
        }
        // x, y轴赋值
        this.yData = y
        this.xData = x
        this.initChart(); // 重新加载图表
      } else {
        this.typeTitle = '本月天气' // 切换标题
        this.toggleMonth(monthKey[this.curMonth], this.curMonth)
      }
    },
    // 解析月份
    analysis ({ data, month, start = 0, end }) {
      const x = [], y = []
      for (i = start; i < end; i++) {
        x.push(`${month}/${i + 1}`)
        y.push(data[i])
      }
      return { x, y }
    },
    // 返回当前月剩余天数
    analysisCurrentMonth () {
      const day = new Date().getDate() // 获取当天日期
      const len = this.yData.length
      const end = day + 6
      return this.analysis({
        data: this.yData,
        month: this.curMonth + 1,
        start: day - 1,
        end: end <= len ? end : len
      })
    },
    // 返回下月需要天数
    analysisNextMonth (len, key) {
      const { x, y } = this.analysis({
        data: this.data[this.curMonth + 1][key],
        end: len,
        month: this.curMonth + 2
      })
      return { nx: x, ny: y }
    },
    initChart () {...},
  },
});
</script>

10 JSON 生成器(25 分)

主要考察正则,难点在于使用正则提取所需参数。

/*
 * @param {*}  左侧输入框输入的值转化成的 js 数据
 * @return {*} 根据传入的数据生成对应的 js 格式数据
 */
let generateData = data => {
  // TODO:待补充代码
  let result = null
  if (Array.isArray(data) && data.length > 1) {
    const obj = parseObj(data[1]) // 解析对象
    const range = parseNum(data[0]) // 获取repeat()中的区间值
    const len = range.length < 2 ? range[0] : random(range[0], range[1]) // 结果数组长度
    result = Array.from(Array(len), o => (o = obj)) // 生成结果数组
  } else {
    result = parseObj(data)
  }
  return result
}

/**
 * 解析传入的对象,将符合规则的值替换
 * @param {*} obj 需要处理的对象
 * @returns 解析完成的的对象
 */
function parseObj(obj) {
  const regBool = new RegExp(/{{bool\(\)}}/)
  const regInteger = new RegExp(/{{integer\(\d+,\s?\d+\)}}/)
  for (const key in obj) {
    // 通过正则判断是否转换为 布尔值
    if (regBool.test(obj[key])) obj[key] = bool()
    // 通过正则判断是否转换为 整数值
    if (regInteger.test(obj[key])) {
      const range = parseNum(obj[key]) // 使用正则匹配数值
      obj[key] = random(range[0], range[1]) // 转换为区间随机数
    }
  }
  return obj
}

/**
 * 提取字符串中所有的数值
 * @param {*} str 需要提取的字符串
 * @returns 提取的数值数组
 */
function parseNum(str) {
  return str.match(/\d+/g).map(e => Number(e))
}

/**
 * 随机生成 布尔值
 * @returns 生成的布尔值
 */
function bool() {
  return random(0, 1) === 0
}

/**
 * 根据传入的值生成对应区间的随机数
 * @param {*} min 最小值(包含)
 * @param {*} max 最大值(包含)
 * @returns 对应区间的随机数
 */
function random(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min
}