第十五届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组(详细分析解答)
目录
1.动态的Tab栏
1.1 题目要求
1.2 题目分析
1.3 源代码
2.地球环游
2.1 题目要求
2.2 题目分析
2.3 源代码
3.迷惑的this
3.1 题目要求
3.2 题目分析
3.3 源代码
4.魔法失灵了
4.1 题目要求
4.2 题目分析
4.3 源代码
5.燃烧你的卡路里
5.1 题目要求
5.2 题目分析
5.3 源代码
6.司龄统计
6.1 题目要求
6.2 题目分析
6.3 源代码
7.不翼而飞的余额
7.1 题目要求
7.2 题目分析
7.3 源代码
8.个性化推荐
8.1 题目要求
8.2 题目分析
8.3 源代码
9.贪吃蛇
9.1 题目要求
9.2 题目分析
9.3 源代码
10.自定义表单验证
10.1 题目要求
10.2 题目分析
10.3 源代码
小结
代码地址:第15届蓝桥杯Web模拟题-大学组-题目与解析
最近看到新一届的蓝桥杯也是开始报名了,而且还准备了模拟题供大家练习,我花了点时间写了一下,跟大家分享一下我的解题思路。
我的小tips,因为web蓝桥杯通常 补全代码的形式,所以题目较多,我们不一定要阅读所有代码,直接看题目的要求即可,题目中会明确告诉我们哪里需要加代码或者是修改代码。(特别是前面的简单题目)
1.动态的Tab栏
1.1 题目要求
请在 style.css
文件中补全代码。
当用户向下滚动的高度没有超过标题栏(即 .heading
元素)的高度时,保持 Tab 栏在其原有的位置。当滚动高度超过标题栏的高度时,固定显示 Tab 栏在网页顶部。
1.2 题目分析
第一题比较简单,就是让我们在style.css中添加代码实现定位的效果,想到定位我们脑子里应该想到的是:position,没错就是用它,如果你忘记了它有哪些用法,请看下面的表格:
static | 元素处于正常文档流中,不受 top, right, bottom, left 影响。 |
relative | 元素相对于其正常位置进行定位。 元素仍保持正常文档流,但会对其进行相对调整。 |
absolute | 元素相对于最近的已定位祖先元素进行定位。 如果没有已定位的祖先元素,则相对于最初的包含块(通常是 |
fixed | 元素相对于视口进行定位,即使页面滚动,元素位置也不会改变。 |
sticky | 元素根据用户的滚动位置在父元素中定位。在元素在屏幕中可见时,它的位置是相对定位的,当页面滚动超出范围时,它的位置是固定的。 |
当我们知道各个属性的作用后,在根据题目需要达到的效果,很明显这里要使用的是:sticky
1.3 源代码
/* TODO: 请在此补充代码实现tab栏动态固定 */
position: sticky;
top: 0;
2.地球环游
2.1 题目要求
找到 css/style.csss
文件中的 TODO 部分,完成以下目标:
给 .earth-con
元素添加动画,设置如下:
- 动画名称:
orbit
- 动画时间: 36.5 秒
- 速度函数:线性
- 播放方式:无限循环
2.2 题目分析
这一题只要你知道css动画的使用方法,简直就是送分,所有动画的要求都已经给你了,而且还标注出来了动画的名称,直接在对应的css部分添加动画代码即可。
2.3 源代码
/* TODO:待补充代码,添加动画 */
animation: orbit 36.5s linear infinite;
3.迷惑的this
3.1 题目要求
完善 js/index.js
中的 handle
函数中的 TODO 部分,实现以下功能:
为输入框(即 this.inputEl
)绑定 input
事件,当输入框的值发生变化时,调用已经提供的 handleInput
方法进行搜索处理,注意 handleInput
方法调用时的 this
指向应为 search
对象本身。
3.2 题目分析
根据题目的名称可以看出来这一题应该是与this相关的问题,js文件中需要用到的dom元素和函数都已经给我们写好了,只需要我们给input绑定一个input事件,我们相当然的就会写出这样的代码:
handle() {
// TODO:待补充代码
this.inputEl.addEventListener('input', this.handleInput(this))
},
但是运行发现不对,达不到效果,错误的原因就是忽略了this的指向问题,当你使用addEventListener
将一个函数作为事件处理程序附加到元素时,该函数会失去其原始上下文,函数内部的this
将指向触发事件的元素。我们可以打印此时的this看看:
可以看到此时的this指向已经变成了input这个dom元素了,他的身上并没有hanleInput()这个方法,此时我们就要使用bind来修改this的指向。在面向对象中bind是很重要的,关乎到整个class对象的this问题。
3.3 源代码
handle() {
// TODO:待补充代码
//使用bind来纠正 this的指向
this.inputEl.addEventListener('input', this.handleInput.bind(this))
},
4.魔法失灵了
4.1 题目要求
找到 index.html
中 TODO 部分,正确修复代码使 data
对象恢复响应式特性,即点击页面上的 - 与 + 按钮可以改变 value
的值。正确实现后效果如下:
4.2 题目分析
这个题目主要是按钮事件失灵了,如果不仔细看可能一时半会看不出来问题,其实就是
//TODO:待修复代码
let { value }={ ...data }
这行代码将value从data中结构出来后,value就失去了响应式效果了,而且仔细看的话,可以看到题目中引入了toRefs,但是没有使用,toRefs的作用就是让结构出来的元素不失去响应式,在日常开发中主要是为了简化代码,如果不结构,在html中就要使用这种:data.value的形式渲染数据。
4.3 源代码
//TODO:待修复代码
let { value } = toRefs(data) //结构赋值将value失去的响应式
//利用toRefs 恢复响应式
5.燃烧你的卡路里
5.1 题目要求
- 点击“定制方案”按钮后,弹出侧滑页面,所使用的组件为
el-drawer
- 封装
sortItem
函数,按照食物属性名称将早餐/午餐/晚餐的数组数据从大到小排序,然后找到排序后的数组中第一个不大于对应摄入量上限的食材对象,并将其返回。
5.2 题目分析
第一个要求很简单,就是在el-drawer上绑定它的开关属性,在触发按钮事件时将其改成 true即可
第二个要求涉及到的是排序以及遍历,排序我们直接使用 sort对属性名称进行排序即可,然后再遍历查找到对应的元素进行比较返回对应的值即可。
5.3 源代码
第一处:
第二处:
这里提供二种方法,如果对js的array方法熟练的可以直接使用方法,不熟练的可以遍历
const sortItem = (arr, pro, compare) => {
// TODO 根据 compare 匹配食材对象后返回这个对象
// 按照食物属性名称将早餐/午餐/晚餐的数组数据从大到小排序,
//然后找到排序后的数组中第一个不大于对应摄入量上限的食材对象,并将其返回。
//第一种
const sortedArr = arr.sort((a, b) => b[pro] - a[pro]);
const optimalItem = sortedArr.find(item => item[pro] < compare);
return optimalItem;
//第二种
arr.sort((a, b) => b[pro] - a[pro])
for (const item of arr) {
if (item[pro] < compare) {
return item
}
}
}
6.司龄统计
6.1 题目要求
- 找到
groupByAge
函数,groupByAge
接收一个参数,参数为数组对象,age
代表司龄,name
代表人名,返回一个根据司龄进行分组的对象,key
是司龄,value
是一个数组,其中包含了所有当前司龄的人的对象。 - 正确设置 ECharts 图表中的 x 轴和 y 轴数据,x 轴表示司龄,从小到大排序,y 轴表示司龄对应的人数个数。如 1 年司龄的有 4 人,则 y 轴对应的数据为 4。
6.2 题目分析
这个题目首先第一步就是要对数据进行重新改装,将数据按照age进行分组改装
要实现其实比较简单,直接遍历插入即可
第二步是要设置Echarts的X轴数据和Y轴数据,x轴的数据是司龄,也就是age,但是有个要求是要进行升序排序,这里又用到了sort排序,y轴的数据是人数,也就是对应的age数组的长度,因为前面已经进行了排序,我们直接遍历返回数组长度就行。
6.3 源代码
// 按照年龄分组的函数
const groupByAge = (peoples) => {
// TODO:待补充代码,按照年龄进行分组
//按照age进行排序
const data = {}
peoples.forEach((item) => {
//判断data中是否存在
if (!data[item.age]) {
data[item.age] = []
}
data[item.age].push(item)
})
return data
}
// TODO: 设置 Echars X 轴数据 xAxisData 和 Y 轴数据 seriesData
xAxisData.value = Object.keys(groupedPeople.value).sort((a, b) => a - b)
seriesData.value = Object.values(groupedPeople.value).map((item) => {
return item.length
})
7.不翼而飞的余额
7.1 题目要求
- 找到
index.html
中的 TODO 部分,为项目配置history
模式路由,浏览器中访问/
的时候显示WalletPage
组件,访问/deposit
的时候显示DepositPage
组件。 - 找到
DepositPage.js
中的 TODO 部分,在DepositPage
页面中的 (id = deposit-balance
)元素正确显示钱包余额(store
中的balance
)。 - 完善
js/store.js
和component/DepositPage.js
中的 TODO 部分,在DepositPage
页面中,在输入框(input
)输入数字(只考虑正整数),点击 “Deposit” 按钮(button
)后,余额 = 现在的余额 + 输入框中输入的金额,在DepositPage
页面正确显示钱包余额(两个页面的余额相同)。初始余额为 23。
7.2 题目分析
- 第一步就是配置路由,如果不会的建议学习一下vue-router:https://router.vuejs.org/zh/guide/essentials/history-mode.html,这里二种模式都是可以使用的,按照要求配置即可,没什么难的。
- 第二步是再DepositPage组件中修改balance的数据,需要保持组件间的数据统一性,这里使用了Pinia仓库,不过这里不知道pinia也没事,这里唯一要注意的是数据的正确性,题目要求是要正整数,所以做一个判断即可,最后加上后清空input框。
7.3 源代码
第一步:
const { createRouter, createWebHistory } = VueRouter; // TODO:待补充代码,在此引入路由相关 API
const app = createApp({});
app.use(createPinia());
const router = createRouter({
// TODO:待补充代码,为项目配置 history 模式的路由
history: createWebHistory(),
routes: [
{
path: '/',
component: WalletPage
},
{
path: '/deposit',
component: DepositPage
}
]
})
第二步:
第三步:
setup() {
const depositAmount = Vue.ref() // 输入框中的的值->存款金额
const store = useMoneyStore() // 引入 store
// TODO:待补充代码,完善点击存款按钮事件
function deposit() {
if (!isNaN(depositAmount.value) && depositAmount.value >= 0) {
store.balance += depositAmount.value
depositAmount.value = null
}
}
return {
deposit,
depositAmount,
store
}
}
8.个性化推荐
8.1 题目要求
启动服务后,如果没有勾选则相应对应的内容,有勾选相应其他的相关内容。
- 如果用户勾选了标签,则返回对应的标签的查询信息以及相关推荐,比如用户选择了标签
Javascript
,除了查询tag
为Javascript
的内容,也需要查询Javascript
的相关推荐标签HTML5
,CSS3
的内容(即relevance
字段对应的数据),一并返回。 - 注意:不再需要进一步查询相关推荐的相关推荐,即不需要查询
HTML5
的相关推荐标签的内容。多个标签之间的相关标签可能重复,需要对查询内容进行去重,比如选择了标签Javascript
和CSS3
,最终的去重结果应该是标签Javascript
、CSS3
、HTML5
的内容。
这里主要的要求就是提交一个表单过来,根据表单的数据展示对应的文本,不过还需要注意的是需要查询其中的relevance连带进来,比如:
表单数据有一个Python,那么Java也要一起携带返回相应。注意标红的字,HTML5是要忽略的!
8.2 题目分析
初看题目,我们要写的地方都在js文件中,如果会nodejs的人,应该能读懂代码,我简单解释一下,(如果能看懂可以跳过)这里就是启动服务在8080端口,如果访问的是:‘/’,则会读取index.html文件相应给客户端,而在index.html中有一个表单,它的提交地址是:‘/customized’,所以当submit按钮单击后就会跳到此页面,并携带这些数据。
此时我们需要相应对应的页面给客户端,这里就需要进行判判表单元素有没有数据了,如果没有则相应一段提示文本,否则相应对应的提示,这里的提示还算简单,主要看怎么根据对应的数据解析出所有需要的推荐数据。
8.3 源代码
// TODO: 补充个性化页面处理代码
//1.读取文件
const customizedPage = fs.readFileSync(path.join(__dirname, '../customized.html'), {
encoding: 'utf8'
})
let indexHtml = ''
if (interested.length === 0) {
indexHtml = customizedPage.replace('<body></body>', `<body><div class="unselect">你还未选择任何感兴趣的标签!</div></body>`)
} else {
//2.首先查出所有的tag以及附带的tag (将interested转为数组)
let tags = Array.isArray(interested) ? interested : [interested]
tags.forEach((item) => {
const found = data.find((foundData) => {
return foundData.tag == item
})
//查出来后再去对比relevance
if (found && found.tag != 'HTML5') {
//小坑,
found.relevance.forEach((relevance) => {
if (!tags.includes(relevance)) {
tags.push(relevance)
}
})
}
})
let html = ''
tags.forEach((item) => {
const found = data.find((foundData) => {
return foundData.tag == item
})
if (found) {
html += `
<div class="interest">
<div class="tag">${found.tag}</div>
<div>${found.content}</div>
</div>`
}
})
indexHtml = customizedPage.replace('<body></body>', `<body>${html}</body>`)
}
res.writeHead(200, { 'Content-Type': 'text/html' })
res.write(indexHtml)
res.end()
9.贪吃蛇
9.1 题目要求
根据当前蛇的移动方向(this.direction
)以及蛇身块的大小(this.size
),计算新的蛇头位置,更新蛇身坐标数组 (this.snakeBody
),即可实现蛇的正确移动。蛇的移动和增加蛇的长度代码已提供。
9.2 题目分析
这一题乍一看挺难的,代码很长,但其实通过观察发现,其实大多数代码都已经写好了,我们只需要拿到蛇头的方向对它的坐标进行替换就行,后面身子部分啥的坐标都不用管。所以我们只要拿到蛇头的坐标,判断其目前的方向,坐标加上自身的大小(size)即可。
9.3 源代码
// 移动蛇的头部
nextStep() {
// TODO:待补充代码
const snake = this.snakeBody
const head = { ...snake[0] }
switch (this.direction) {
case 'right':
head.left += this.size
break
case 'down':
head.top += this.size
break
case 'left':
head.left -= this.size
break
case 'up':
head.top -= this.size
break
}
this.snakeBody.unshift(head) // 在头部插入新坐标
this.snakeBody.pop() // 删除尾部坐标
this.render() // 重新渲染蛇身体
}
10.自定义表单验证
10.1 题目要求
-
完成
FormInput.js
中的 TODO 部分,当输入框(class= form-input
) 的值变化时,触发事件更新index.html
中组件(form-input
)的v-model
值。(调试 tips:考生可以在点击按钮时通过打印formData
的值进行查看)。 -
完成
js/util.js
中的is_email
函数,参数是邮箱地址,是合法邮箱返回true
,否则返回false
。(邮箱的正则校验) - 完成
components/FormValidator.js
中通用表单验证函数validateForm
中的 TODO 部分。如果表单验证通过,则Promise
为resolve(true)
,否则为resolve(false)
(此部分代码已提供)。index.html
中定义的formRules
对象对应字段的表单值验证失败时,使用validateForm
函数中提供的errors
对象,在对应字段中存储错误信息。若某个字段对应的错误信息为多个时,将按照验证规则数组的顺序优先显示,即只显示第一个错误信息。函数使用的数据通过props.rules
(字段名和对应的验证规则) 和props.formData
(表单数据的键值对) 进行获取。
10.2 题目分析
- 第一问,改变父组件中的数据,我们首先应该想到的是自定义事件,子传父嘛,但是通过观察后发现父组件好像并没有定义自定义事件,而且题目要求我们只修改子组件,然后通过观察可以知道子组件引入了emit,而父组件使用了v-model对表单进行了绑定,此时我们就可以使用v-model来进行通信,如果对如果使用v-model来通信不了解的,可以查看这篇文章,里面列举了vue3组件间的通信方式:https://blog.csdn.net/m0_64642443/article/details/131787640
- 第二问是邮箱正则没啥好说的,不会的可以先去学习一下正则怎么写喔。
- 第三问:题目要求中也写出来了数据从,props.rules和props.formData中拿,我们拿到数据对其进行验证然后返回相应的错误信息就行(message)。
10.3 源代码
第一问:
// TODO:目标 1 当输入框的值变化时,触发 input 事件更新父组件的 v-model 值
watch(inputValue, (newValue) => {
emit('update:value', newValue)
})
// TODO:end
第二问:
const is_email = (val) => {
// TODO:目标 2 待补充代码
const emailRegex = /^[a-zA-Z0-9]+@[a-zA-Z0-9]+.[a-zA-Z]{2,4}$/
return emailRegex.test(val)
}
第三问:
// TODO:目标 3 编写通用的表单验证规则,并将错误信息放置到 errors 对象中
for (const field in props.rules) {
const fieldRules = props.rules[field]
for (const rule of fieldRules) {
if (rule.required && !props.formData[field]) {
errors.value[field] = rule.message
break
}
if (rule.type && !validateByType(rule.type, props.formData[field])) {
errors.value[field] = rule.message
break
}
if (rule.min && props.formData[field].length < rule.min) {
errors.value[field] = rule.message
break
}
if (rule.max && props.formData[field].length > rule.max) {
errors.value[field] = rule.message
break
}
}
}
// 添加对 nickname 字段的非空判断
if (props.rules.nickname && !props.formData.nickname) {
errors.value.nickname = '昵称不能为空'
}
// TODO:END
小结
总的来说这一次的题目不算太难吧,大家觉得呢,大多数题目还是考的是大家对语法的熟练度已经js代码的编写,所以蓝桥杯对基础的考察还是占比比较大的,大家要想在蓝桥杯中取得一个好的成绩,需把握好基础知识。