23模拟赛3
1 网页PPT¶
function switchPage() {
// TODO: 请补充该函数,实现根据activeIndex切换页面的功能,并且在到达最后一页或第一页时给相应的按钮添加disable类
const allSection=document.querySelectorAll('.container>section')
allSection.forEach(item=>item.style.display='none')
allSection[activeIndex].style.display='block'
document.querySelector('.page').innerText=`${activeIndex+1} / ${sectionsCount}`
if(activeIndex===4){
document.querySelector('.btn.right').classList.add('disable')
}else if(activeIndex===0){
document.querySelector('.btn.left').classList.add('disable')
}
else{
document.querySelector('.btn.disable')?.classList.remove('disable')
}
}
2 西游记之西天取经¶
animation: a4 0.8s steps(8) infinite;
3 商品销量和销售额实时展示看板¶
charData.xAxis.data=Object.keys(result.data.countObj)
charData.series[0].data = Object.values(result.data.saleObj);
charData.series[1].data = Object.values(result.data.countObj);
yAxis: [
{
type: "value",
name: "销售额",
position: "left",
},
{
type: "value",
name: "销量",
position: "right",
},
],
4. 蓝桥校园卡¶
if (!/^[\u4e00-\u9fa5]{2,4}$/.test(studentName.value)) {
formGroups[0].classList.add("has-error");
document.querySelector("#vail_name").style.display = "block";
return;
}
if (!/^\d{1,12}$/.test(studentId.value)) {
formGroups[1].classList.add("has-error");
document.querySelector("#vail_studentId").style.display = "block";
return;
}
item[0].innerText = studentName.value;
item[1].innerText = studentId.value;
item[2].innerText = college.value;
5.会员权益领取¶
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>会员权益领取中心</title>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<div class="head">
<div class="card"></div>
<div class="pic"></div>
<div class="name">小蓝同学</div>
<div class="during">已加入蓝桥云课 300 天</div>
<div class="member">已加入蓝桥云课 300 天</div>
<div class="vip"></div>
</div>
<div class="content">
<div class="title">会员权益升级</div>
<img src="./images/left.png" alt="" class="left">
<img src="./images/right.png" alt="" class="right">
<table class="quanyi">
<tr>
<td>会员专属特权</td>
<td> <img src="./images/gaojivip.png" alt=""> <span>高级会员</span></td>
<td> <img src="./images/vip.png" alt=""> <span>标准会员</span></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</table>
</div>
</body>
</html>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
position: relative;
height: 1558px;
width: 1440px;
margin: 0 auto;
}
.head {
background-image: url(../images/top_banner.png);
width: 1440px;
height: 240px;
margin: 0 auto;
position: relative;
}
.head .card {
background-image: linear-gradient(180deg, #191720 0%, #080810 100%);
border-radius: 4px;
width: 290px;
height: 176px;
position: absolute;
left: 1000px;
top: 32px;
}
.head .name {
font-family: PingFangSC-Regular;
font-size: 20px;
color: #FFFFFF;
letter-spacing: 0;
line-height: 20px;
font-weight: 400;
position: absolute;
top: 62px;
left: 1094px;
width: 80px;
height: 20px;
}
.head .during {
font-family: PingFangSC-Regular;
font-size: 14px;
color: #FFFFFF;
letter-spacing: 0;
line-height: 14px;
font-weight: 400;
position: absolute;
top: 95px;
left: 1093px;
width: 174px;
height: 14px;
}
.head .member {
font-family: PingFangSC-Regular;
font-size: 12px;
color: #C6C6C6;
letter-spacing: 0;
line-height: 12px;
font-weight: 400;
position: absolute;
left: 1093px;
top: 121px;
width: 154px;
height: 12px;
}
.head .pic {
background-image: url(../images/touxiang.png);
position: absolute;
left: 1024px;
top: 73px;
height: 50px;
width: 50px;
}
.head .vip {
background-image: url(../images/vip.png);
position: absolute;
left: 1055px;
top: 104px;
height: 19px;
width: 19px;
}
.content {
width: 1440px;
height: 1318px;
margin: 0 auto;
background: #FCFDFF;
}
.title {
font-family: PingFangSC-Medium;
font-size: 30px;
color: #232A33;
letter-spacing: 0;
text-align: center;
line-height: 30px;
font-weight: 500;
position: absolute;
left: 630px;
top: 300px;
width: 180px;
height: 30px;
}
.content img.left {
position: absolute;
left: 525px;
top: 307.72px;
width: 89px;
height: 15.56px;
}
.content img.right {
position: absolute;
left: 826px;
top: 307px;
width: 89.63px;
height: 15.56px;
}
.content .quanyi {
background: #FFFFFF;
box-shadow: 0px 2px 10px 0px rgba(229, 235, 241, 1);
border-radius: 10px;
position: absolute;
left: 150px;
top: 370px;
width: 760px;
height: 704px;
}
.quanyi tr {
width: 760px;
height: 64px;
vertical-align: center;
}
.quanyi tr:nth-child(2n) {
background: #F7F8FA !important;
}
tr td:nth-child(1) {
padding-left: 40px;
}
.quanyi tr:nth-child(1) td{
font-family: PingFangSC-Medium;
font-size: 16px;
color: #333333;
letter-spacing: 0;
line-height: 16px;
font-weight: 500;
}
.quanyi tr:nth-child(1) td:nth-child(2) span{
width: 64px;
height: 16px;
line-height: 16px;
vertical-align: center;
}
6.心愿便利贴¶
rules: {
// TODO 待补充验证的代码
name: [
{ required: true, message: "请输入姓名", trigger: "blur" },
{min: 2, max: 4,message: "长度在 3 到 5 个字符",trigger: "blur"},
],
content: [
{ required: true, message: "请输入许愿内容", trigger: "blur" },
{min: 1, max: 30,message: "长度在 3 到 5 个字符",trigger: "blur"}],
},
7.布局切换¶
8.封装Promisiefy函数¶
const promisefy = (fn) => {
// 既然需要时一个函数,就在最外面套上一层函数
return (path, type) => {
return new Promise((res, rej) => {
fn(path, type, (err, contrast) => {
if (err) rej(err);
else res(contrast);
});
});
};
};
const readFileSync = promisefy(fs.readFile);
readFileSync(textPath, "utf8")
.then((res) => {
console.log(res);
})
.catch((err) => {});
- 既然需要是一个函数,就在最外面套上一层函数
9.趣购¶
<!-- TODO: 补充拖拽事件,请不要改动任何 id 属性 -->
<template>
<div class="container">
<div class="good-list">
<div
v-for="good in goods"
draggable="true"
@dragstart="drag($event, good)"
:key="good.name"
class="good"
>
<img :src="good.cover" draggable="false" />
<span>{{ good.name }}</span>
<span>¥{{ good.price }}</span>
</div>
</div>
<!--默认会传一个event过来, 函数有参数就要手动传, 不然event不会传过来-->
<div
id="trolley"
class="trolley"
@drop="drop($event)"
@dragover="stop($event)"
>
<span id="bought" class="bought" v-if="bought.length !== 0">{{
len
}}</span>
<img src="./images/trolley.jpeg" />
</div>
<div class="result">
<div>
购物车商品:<span id="goods">{{ goodsDetail }}</span>
</div>
<div>
购物车商品总计:<span id="total">{{ totalPrice }}</span>
</div>
</div>
</div>
</template>
<script>
module.exports = {
data() {
return {
goods: [
{
name: "畅销书",
price: 30,
cover: "./images/book.jpeg",
},
{
name: "收纳箱",
price: 60,
cover: "./images/box.jpeg",
},
{
name: "纸巾",
price: 20,
cover: "./images/paper.jpeg",
},
{
name: "电视",
price: 1000,
cover: "./images/tv.jpg",
},
],
bought: [],
len: 0,
details: "",
price: 0,
};
},
// TODO: 请补充实现代码
computed: {
totalPrice() {
return this.price;
},
goodsDetail() {
return this.details;
},
},
methods: {
drag(e, parm) {
e.dataTransfer.setData("name", parm.name);
e.dataTransfer.setData("price", parm.price);
},
stop(e) {
e.stopPropagation();
e.preventDefault();
},
drop(e) {
let name = e.dataTransfer.getData("name");
let price = e.dataTransfer.getData("price");
this.add(name, price);
},
add(name, price) {
let isHave = false;
this.bought.forEach((element) => {
if (element.name == name) {
isHave = true;
element.num++;
}
});
if (!isHave) {
this.bought.push({ name, price, num: 1 });
}
this.len = 0;
this.price = 0;
this.details = "";
this.bought.map((e) => {
this.len += e.num;
this.price += e.price * e.num;
this.details += e.name + "*" + e.num + " ";
});
},
},
};
</script>
<div v-for="good in goods" :key="good.name" class="good" draggable @dragstart="dragstart($event, good)">
<div id="trolley" class="trolley" @dragover.prevent="" @drop="drop">
<span id="bought" class="bought" v-if="bought.length !== 0">{{
bought.reduce((prev, curr) => prev + curr.num, 0)
}}</span>
<img src="./images/trolley.jpeg" />
</div>
totalPrice() {
return this.bought.reduce((prev, curr) => prev + curr.price * curr.num, 0);
},
goodsDetail() {
return this.bought.reduce((prev, curr) => prev + curr.name + '*' + curr.num + ' ', "").trim();
},
dragstart(e, good) {
e.dataTransfer.setData("key", JSON.stringify(good));
},
drop(e) {
let data = e.dataTransfer.getData("key");
data = JSON.parse(data);
let tar = this.bought.find((item) => item.name === data.name);
if (tar) {
tar.num++;
} else {
data.num = 1;
this.bought.push(data);
}
},
@click.prevent="onClick"
vue使用.prevent
阻止默认事件- 原生js中,使用
e.prenevtDefault()
e.dataTransfer.setData("key", JSON.stringify(good));
只能存储字符串
10.分页组件¶
https://www.lanqiao.cn/problems/2427/learning/?contest_id=84
/**
* @description ajax 请求,通过传递的 currentPage, pageSize 获取到当前页和总页数的数据
* @param {string} url 请求地址,必填
* @param {string} method 请求方式,可选参数,默认为 get
* @param {string} data 请求体数据,可选参数
* @param {number} currentPage 当前页数,必填
* @param {number} pageSize 每页显示条目个数,必填
* @return {object} {data,total} data为data.json中data数组的部分数据,total为data.json中total的值
* */
async function ajax({ url, method = "get", data, query: { currentPage, pageSize } }) {
// TODO:根据函数参数 `query` 对象 `currentPage, pageSize` 获得当前页的数据
let result = {
data: [],
total: 0
}
let tmp = await axios[method](url, data);
tmp = tmp.data.data;
result.total = tmp.length;
result.data = tmp.splice((currentPage - 1) * pageSize, pageSize);
return result;
}
class Pagination {
/**
* @param {Element} root
* @param {number} pageSize 每页显示条目个数
* @param {number} total 总条目数
* @param {number} pagerCount 设置最大页码按钮数。 页码按钮的数量,当总页数超过该值时会折叠
* @param {number} currentPage 当前页数,默认为第一页
* @param {function} currentChange current-page 改变时触发
*/
constructor(root, { pageSize, total, pagerCount, currentPage }, { currentChange }) {
if (pagerCount % 2 == 0) {
pagerCount--;
}
this.root = root;
this.pageSize = pageSize || 10;
this.total = total;
this.pagerCount = pagerCount || 7;
this.currentPage = currentPage || 1;
this.currentChange = currentChange;
this.totalPages = Math.ceil(total / pageSize);
this.initPagination();
}
/**
* @description 初始化分页组件,首次创建和 this.currentPage 改变时调用
* */
initPagination() {
const indexArr = createPaginationIndexArr(this.currentPage, this.totalPages, this.pagerCount);
document.querySelector("#index-arr").innerText = JSON.stringify(indexArr);
this.renderPagination(indexArr);
this.initEvents();
this.currentChange(this.currentPage);
}
/**
* @description 根据序号数组生成分页组件的字符串模板通过 innerHTML 挂载在 root 元素内
* @param {Array} indexArr 分页数组 indexArr
* @return {String} 分页组件的字符串模板
*/
renderPagination(indexArr) {
let template = '';
// TODO:根据 indexArr 数组生成分页组件的字符串模板 template
let n = indexArr.map(item => item == this.currentPage ? `<li class="number active">${item}</li>` : `<li class="number">${item}</li>`)
if (indexArr[1] != 2) n.splice(1, 0, `<li class="number more">...</li>`)
if (indexArr[indexArr.length - 2] + 1 != this.totalPages) n.splice(-1, 0, `<li class="number more">...</li>`)
template = n.join('')
this.root.innerHTML = `
<div class="pagination">
<div class="btn btn-left" id="btn-prev"><</div>
<ul class="pager">${template} </ul>
<div class="btn btn-right" id="btn-next">></div>
</div>`;
}
/**
* @description 事件绑定,改变 this.currentPage 的值,值在 1 到 this.totalPages 之间
**/
initEvents() {
this.root.querySelector("#btn-prev").addEventListener('click', () => {
if (this.currentPage > 1) {
this.currentPage--;
this.initPagination();
}
})
this.root.querySelector("#btn-next").addEventListener('click', () => {
if (this.currentPage < this.totalPages) {
this.currentPage++;
this.initPagination();
}
})
this.root.querySelector(".pager").addEventListener('click', (e) => {
if (e.target.nodeName.toLowerCase() === 'li') {
if (this.currentPage === e.target.innerText) return;
if (e.target.classList.contains('more')) return;
this.currentPage = Number(e.target.innerText)
}
this.initPagination();
});
}
}
const paginationConfigObj = { pageSize: 10, total: 100, pagerCount: 5 };
const root = document.querySelector(".pagination-container");
async function renderContent(currentPage) {
document.querySelector("#current-page").innerText = currentPage;
const { data, total } = await ajax({ url: "./js/data.json", method: "get", query: { currentPage, ...paginationConfigObj } });
document.querySelector("#ajax-data").innerText = JSON.stringify(data);
document.querySelector("#ajax-total").innerText = JSON.stringify(total);
const contentEle = document.querySelector('.content');
let template = data.reduce((prev, cur) =>
prev + `
<li class="item" data-index="${cur.id}">
<h4 class="title">${cur.title}</h4>
<div class="item-right">
评论数:<span class="replay-count">${cur.replayCount}</span>
/
点击数:<span class="click-count">${cur.clickCount}</span>
</div>
</li>`
, "");
contentEle.innerHTML = template;
}
new Pagination(root, paginationConfigObj, { currentChange: renderContent });
/**
* @description ajax 请求,通过传递的 currentPage, pageSize 获取到当前页和总页数的数据
* @param {string} url 请求地址,必填
* @param {string} method 请求方式,可选参数,默认为 get
* @param {string} data 请求体数据,可选参数
* @param {number} currentPage 当前页数,必填
* @param {number} pageSize 每页显示条目个数,必填
* @return {object} {data,total} data为data.json中data数组的部分数据,total为data.json中total的值
* */
async function ajax({
url,
method = "get",
data,
query: { currentPage, pageSize },
}) {
// TODO:根据函数参数 `query` 对象 `currentPage, pageSize` 获得当前页的数据
let result = {
data: [],
total: 0,
};
let res = await axios(url);
currentPage--;
result.data = res.data.data.slice(
currentPage * pageSize,
currentPage * pageSize + pageSize
);
result.total = pageSize;
return result;
}
class Pagination {
/**
* @param {Element} root
* @param {number} pageSize 每页显示条目个数
* @param {number} total 总条目数
* @param {number} pagerCount 设置最大页码按钮数。 页码按钮的数量,当总页数超过该值时会折叠
* @param {number} currentPage 当前页数,默认为第一页
* @param {function} currentChange current-page 改变时触发
*/
constructor(
root,
{ pageSize, total, pagerCount, currentPage },
{ currentChange }
) {
if (pagerCount % 2 == 0) {
pagerCount--;
}
this.root = root;
this.pageSize = pageSize || 10;
this.total = total;
this.pagerCount = pagerCount || 7;
this.currentPage = currentPage || 1;
this.currentChange = currentChange;
this.totalPages = Math.ceil(total / pageSize);
this.initPagination();
}
/**
* @description 初始化分页组件,首次创建和 this.currentPage 改变时调用
* */
initPagination() {
const indexArr = createPaginationIndexArr(
this.currentPage,
this.totalPages,
this.pagerCount
);
document.querySelector("#index-arr").innerText = JSON.stringify(indexArr);
this.renderPagination(indexArr);
this.initEvents();
this.currentChange(this.currentPage);
}
/**
* @description 根据序号数组生成分页组件的字符串模板通过 innerHTML 挂载在 root 元素内
* @param {Array} indexArr 分页数组 indexArr
* @return {String} 分页组件的字符串模板
*/
renderPagination(indexArr) {
let template = "";
// debugger;
// TODO:根据 indexArr 数组生成分页组件的字符串模板 template
let totalPages = this.totalPages,
pagerCount = this.pagerCount,
currentPage = this.currentPage;
if (totalPages <= pagerCount) {
while (totalPages--)
template += `<li class="number ${
currentPage == totalPages + 1 ? "active" : ""
}">${totalPages + 1}</li>`;
} else {
if (currentPage <= 3) {
for (let i = 0; i < 4; i++) {
template += `<li class="number ${
currentPage == i + 1 ? "active" : ""
}">${i + 1}</li>`;
}
template += `<li class="number more">...</li> <li class="number ">10</li>`;
} else if (currentPage >= totalPages - 2) {
template += `<li class="number ">1</li> <li class="number more">...</li>`;
for (let i = totalPages - 3; i <= totalPages; i++)
template += `<li class="number ${
currentPage == i ? "active" : ""
}">${i}</li>`;
} else {
template += `<li class="number ">1</li> <li class="number more">...</li>`;
for (let i = currentPage - 1; i <= currentPage + 1; i++)
template += `<li class="number ${
currentPage == i ? "active" : ""
}">${i}</li>`;
template += `<li class="number more">...</li> <li class="number ">10</li>`;
}
}
document.querySelector(".pager").innerHTML = template;
// 重新渲染了.pager,导致之前得到的dom对象无法使用
// this.root.innerHTML = `
// <div class="pagination">
// <div class="btn btn-left" id="btn-prev"><</div>
// <ul class="pager">${template} </ul>
// <div class="btn btn-right" id="btn-next">></div>
// </div>`;
}
/**
* @description 事件绑定,改变 this.currentPage 的值,值在 1 到 this.totalPages 之间
**/
initEvents() {
this.root.querySelector("#btn-prev").addEventListener("click", () => {
// TODO:"<" 按钮的点击事件, 点击时 this.currentPage - 1
// debugger
if (this.currentPage > 1) {
this.currentPage--;
this.currentChange(this.currentPage);
this.renderPagination();
}
});
this.root.querySelector("#btn-next").addEventListener("click", () => {
// TODO:">" 按钮的点击事件, 点击时 this.currentPage + 1
if (this.currentPage < this.totalPages) {
this.currentPage++;
this.currentChange(this.currentPage);
this.renderPagination();
console.log(this.currentPage);
}
});
this.root.querySelector(".pager").addEventListener("click", (e) => {
if (e.target.nodeName.toLowerCase() === "li") {
if (this.currentPage === e.target.innerText) return;
if (e.target.classList.contains("more")) return;
this.currentPage = Number(e.target.innerText);
}
this.initPagination();
});
}
}
const paginationConfigObj = { pageSize: 10, total: 100, pagerCount: 5 };
const root = document.querySelector(".pagination-container");
async function renderContent(currentPage) {
document.querySelector("#current-page").innerText = currentPage;
const { data, total } = await ajax({
url: "./js/data.json",
method: "get",
query: { currentPage, ...paginationConfigObj },
});
document.querySelector("#ajax-data").innerText = JSON.stringify(data);
document.querySelector("#ajax-total").innerText = JSON.stringify(total);
const contentEle = document.querySelector(".content");
let template = data.reduce(
(prev, cur) =>
prev +
`
<li class="item" data-index="${cur.id}">
<h4 class="title">${cur.title}</h4>
<div class="item-right">
评论数:<span class="replay-count">${cur.replayCount}</span>
/
点击数:<span class="click-count">${cur.clickCount}</span>
</div>
</li>`,
""
);
contentEle.innerHTML = template;
}
new Pagination(root, paginationConfigObj, { currentChange: renderContent });
const createPaginationIndexArr = (currentPage, totalPages, pagerCount) => {
let indexArr = [];
// TODO:根据传参生成分页数组 indexArr
if (totalPages <= pagerCount) {
while (totalPages--) indexArr.unshift(totalPages++);
}else{
if(currentPage<=3){
indexArr.push(1,2,3,4, totalPages)
}else if(currentPage>=totalPages-2){
indexArr.push(1,totalPages-3,totalPages-2,totalPages-1, totalPages)
}else{
indexArr.push(1,currentPage-1,currentPage,currentPage+1, totalPages)
}
}
return indexArr;
};
官方题解¶
网页 PPT¶
-
考察: JQuery (选择器,显示隐藏等常用 API)
-
答案:
// 缓存 $("section") 的 jQuery 选择器,以提高性能
let $sections = $("section");
// 隐藏所有 section 元素,显示 activeIndex 对应的元素
$sections.hide();
$sections.eq(activeIndex).show();
// 记录 section 元素的个数
let len = $sections.length;
// 如果 activeIndex 是 0,给左侧按钮添加 disable 类,否则删除
$(".left").toggleClass("disable", activeIndex === 0);
// 如果 activeIndex 是 len-1,给右侧按钮添加 disable 类,否则删除
$(".right").toggleClass("disable", activeIndex === len - 1);
// 显示当前页数和总页数,使用字符串插值来构造 HTML 内容
$(".page").html(`${activeIndex + 1}/${len}`);
- 知识点解析:
.toggleClass()
是 jQuery 提供的一个方法,它用于在元素上切换一个或多个类名。这个方法有两个参数:
className
:表示要切换的类名。可以是一个或多个类名,用空格分隔。当该类名存在时,它将被删除;否则它将被添加。
state
:表示一个布尔值,用于决定是添加还是删除指定的类名。如果这个值为 true,类名将被添加;如果是 false,类名将被删除。如果这个参数被省略,那么类名将被添加或删除,取决于它是否存在。
在 $(".left").toggleClass("disable", activeIndex === 0)
这一行中,第一个参数是 disable
类名,它将被添加或删除。第二个参数是一个表达式 activeIndex === 0
,当它的值为 true
时,类名将被添加,否则将被删除。因此,这行代码的作用是:
如果 activeIndex
等于 0,给 .left
元素添加 disable
类。
如果 activeIndex
不等于 0,删除 .left
元素上的 disable
类。
这个方法非常方便,可以用于快速地在元素上添加或删除类名。
// $(".left").toggleClass("disable", activeIndex === 0)`等价于
if (activeIndex == 0) {
$(".left").addClass("disable");
} else {
$(".left").removeClass("disable");
}
西游记之西天取经¶
-
考察点 :CSS3 常用属性
-
答案:
<style>
.actor:nth-child(x) {
/* ...省略代码 */
/* TODO 填空 */
animation: a2 0.8s steps(8) infinite;
}
<style>
-
知识点解析 这是一个 CSS3 动画属性
animation
的值,它包含以下几个部分: -
a2
:表示要应用的动画名称,这个名称需要通过 @keyframes 规则来定义。 -
0.8s
:表示动画持续的时间,即 0.8 秒。 -
steps(8)
:表示动画执行的步数,这里使用steps
函数来指定。steps(8)
表示动画会分为 8步进行,每一步持续相同的时间。 -
infinite
:表示动画将无限循环,直到被停止 -
拓展学习: step()、steps()
商品销量和销售额实时展示看板¶
- 考察 echarts (数据修改,正式比赛:常用 API -> 提供)
答案:
let data = result.data;
// 销售额
if (data?.saleObj) {
let [x, y] = [Object.keys(data.saleObj), Object.values(data.saleObj)];
charData.xAxis.data = x;
charData.series[0].data = y;
}
// 销量
if (data?.countObj) {
let y = Object.values(data.countObj);
charData.series[1].data = y;
}
- 知识点解析
data?.saleObj
是 ECMAScript 2020 引入的一种可选链语法。
它允许您访问对象的属性,而不会在对象为 nullish(null
或 undefined
)时引发错误。如果对象为 nullish
,则表达式将计算为 undefined
。
因此,如果 data
是 nullish
,则 data?.saleObj
将计算为 undefined
。如果 data
不是 nullish
,则将尝试访问 saleObj
属性。如果 saleObj
存在于 data
对象上,则返回它的值,否则返回 undefined
。
以下是一个示例以说明这个语法:
const data = null;
console.log(data?.saleObj); // undefined
const data2 = { saleObj: { price: 10 } };
console.log(data2?.saleObj?.price); // 10
const data3 = { };
console.log(data3?.saleObj?.price); // undefined
心愿便利贴¶
-
考察点: element-ui (表单验证 )
-
答案:
<div class="card" :class="item.css" v-for="(item,index) in wishList" :key="index">
<div>
<script>
const app = new Vue({
// ......
rules: {
// TODO 待补充验证的代码
name:[
{ required: true, message: '请输入姓名', trigger: 'blur' },
{ min: 2, max: 4, message: '长度在 2 到 4 个字符', trigger: 'blur' }
],
content:[
{ required: true, message: '请输入内容', trigger: 'blur' },
{ min: 1, max: 30, message: '长度在 1 到 30 个字符', trigger: 'blur' }
]
}
// ......
})
</script>
消失的 Token¶
-
考察点:Vue(Vuex 的基本使用)
-
答案:
var app = new Vue({
el: '#app',
data() { },
computed: {
welcome() {
return store.getters.welcome
},
username() {
// 第一处修改
return store.getters['user/username']
},
token() {
// 第二处修改
return store.getters['user/token']
}
},
methods: {
// 回车/点击确认的回调事件
login(username) {
// 第三处修改
username && store.commit('user/login', { username, token: 'sxgWKnLADfS8hUxbiMWyb' })
username && store.commit('say', '登录成功,欢迎你回来!')
}
}
})
- 知识点解析
namespaced: true
是在 Vuex 模块中的配置选项之一,它表示将该模块开启命名空间模式。
在 Vuex 中,模块系统可以将 store
分割成小模块,每个模块都有自己的状态、mutation
、action
和 getter
等。命名空间模式是一种使模块更加封装的方法,它可以确保模块内部的 mutation
和 getter
只能访问该模块内部的状态,而不是整个应用程序的状态。
使用 namespaced: true
配置选项,可以为该模块启用命名空间模式,这意味着在访问该模块的 mutation
和 getter
时,需要通过模块名来限定。例如,在一个名为 user
的 Vuex 模块中,如果启用了命名空间模式,那么在该模块内部的 mutation
中,要访问该模块的 state
,就需要使用 state
参数的形式,即 context.state
,而在调用该模块的 mutation
或 getter
时,需要指定该模块的名称,例如:store.commit('user/login')
。
命名空间模式可以有效地避免模块之间的状态冲突,提高代码的可维护性和可读性。但是,在使用命名空间模式时,需要注意模块之间的依赖关系,以及在模块之间传递参数时的命名方式等问题。
封装 Promisefy 函数 (大学组)¶
-
考察点:ES6 promise、fs基本理解
-
答案:
function promisify(fn) {
return function (...args) {
return new Promise((resolve, reject) => {
fn(...args, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
};
}
下面是函数的详细解释:
function promisify(fn)
- promisify 函数使用一个回调函数作为参数,它将被转换为返回 Promise 的函数。return function (...args)
- promisify 返回一个新函数,这个新函数接受任意数量的参数并将它们传递给原始函数。return new Promise((resolve, reject) => { ... }
- promisify 返回的函数创建一个新的 Promise,当原始函数完成时将被解析。fn(...args, (err, result) => { ... })
- promisify 执行原始函数并传递最后一个参数作为回调函数。回调函数期望两个参数:err
和result
。if (err) { reject(err); } else { resolve(result); }
- 当原始函数完成时,回调函数将被执行。如果存在错误,则 Promise 将被拒绝为该错误;否则,Promise 将被解析为返回结果。
/**
* 首先可以看到这个promisefy函数是怎么用的,参数是一个函数
* 调用promisefy函数的结果被存到readFileSync里,
* 而readFileSync在后面可以看到直接接个小括号传两个参了
* 并且还能.then和.catch
* 所以调用这个promisefy返回的是一个参数为(textPath, 'utf8')的函数
* 并且这个函数的调用结果还是个promise对象,
* 不然不是promise对象那来的then方法
* 那这个promise对象里面怎么判断读取文件成功或失败呢
* 学过node就知道了
*/
return (textPath,type) =>
new Promise((resolve, reject) => {
fn(textPath,type,(err, contrast) => {
if(err) reject(err)
else resolve(contrast)
})
})
虚拟滚动¶
- 考察点 :vue
- 答案:
<template>
<div id="virtual-list" class="virtual-list" ref="list" @scroll="onScroll">
<!--用于撑起滚动区域高度的空白元素-->
<div id="scroll-container" :style="{ height: totalHeight + 'px' }"></div>
<!--展示列表的ul元素-->
<ul
id="list"
class="list"
ref="list"
:style="{ transform: dynamicTranslate }"
>
<!--用v-for循环展示列表数据-->
<li
v-for="item in showingList"
:key="item"
:style="{ height: itemHeight + 'px', lineHeight: itemHeight + 'px' }"
>
{{ item }}
</li>
</ul>
</div>
</template>
<script>
module.exports = {
data() {
return {
itemHeight: 60, // 每一项的高度
length: 10, // 初始展示的项数
buffer: 5, // 预加载的项数
list: [], // 数据列表
totalHeight: 0, // 数据列表总高度
scrollTop: 0, // 滚动条的位置
start: 0, // 开始展示的项的索引
};
},
computed: {
showingList() {
return this.list.slice(
Math.max(0, this.start - this.buffer), // 起始索引(加上缓存)
Math.min(this.start + this.buffer + this.length, this.list.length) // 结束索引(加上缓存和可见范围)
);
},
dynamicTranslate() {
const belowOffset = this.buffer * this.itemHeight; // 预加载项的总高度
if (this.scrollTop <= belowOffset) { // 如果滚动距离小于等于预加载项的总高度,不需要滚动
return `translate3d(0,0,0)`;
}
const startOffset =
this.scrollTop - (this.scrollTop % this.itemHeight) - belowOffset; // 计算开始滚动的位置
return `translate3d(0,${startOffset}px,0)`;
},
},
methods: {
onScroll() { // 监听滚动事件
this.scrollTop = this.$refs.list.scrollTop; // 获取滚动条位置
this.start = Math.floor(this.scrollTop / this.itemHeight); // 计算开始展示的索引
},
},
mounted() { // 在组件挂载后获取数据
axios.get("./data.json").then(({ data }) => {
this.list = data; // 将数据存入列表
this.totalHeight = this.itemHeight * this.list.length; // 计算数据列表总高度
this.start = 0; // 初始化开始展示的索引
this.scrollTop = 0; // 初始化滚动条位置
});
},
};
</script>
- 知识点解析
computed
计算属性
computed
计算属性用于根据已有的数据计算出新的数据,并且只有在相关数据发生变化时才会重新计算。在这段代码中,computed
计算属性主要用于计算列表的展示内容和滚动的位置。
showingList:根据
start、
length和
buffer计算出当前需要展示的数据列表。
dynamicTranslate`:根据滚动条的位置和预加载项的总高度计算出需要滚动的位置,以实现虚拟滚动
虚拟列表的实现原理:
虚拟列表通过只渲染可见范围内的数据项,而不是渲染整个列表来提高性能。在这段代码中,虚拟列表的实现原理如下:
- 通过计算出开始展示的项的索引和需要展示的项数,计算出当前需要展示的数据列表。
- 通过计算出滚动条的位置和预加载项的总高度,计算出需要滚动的位置。
- 将滚动位置应用到列表容器上,从而实现虚拟滚动。