跳转至

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">&lt;</div>
    //         <ul class="pager">${template} </ul>
    //         <div class="btn btn-right" id="btn-next">&gt;</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(nullundefined)时引发错误。如果对象为 nullish,则表达式将计算为 undefined

因此,如果 datanullish,则 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 分割成小模块,每个模块都有自己的状态、mutationactiongetter 等。命名空间模式是一种使模块更加封装的方法,它可以确保模块内部的 mutationgetter 只能访问该模块内部的状态,而不是整个应用程序的状态。

使用 namespaced: true 配置选项,可以为该模块启用命名空间模式,这意味着在访问该模块的 mutationgetter 时,需要通过模块名来限定。例如,在一个名为 user 的 Vuex 模块中,如果启用了命名空间模式,那么在该模块内部的 mutation 中,要访问该模块的 state,就需要使用 state 参数的形式,即 context.state,而在调用该模块的 mutationgetter 时,需要指定该模块的名称,例如: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 执行原始函数并传递最后一个参数作为回调函数。回调函数期望两个参数:errresult
  • 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:根据startlengthbuffer计算出当前需要展示的数据列表。

dynamicTranslate`:根据滚动条的位置和预加载项的总高度计算出需要滚动的位置,以实现虚拟滚动

虚拟列表的实现原理:

虚拟列表通过只渲染可见范围内的数据项,而不是渲染整个列表来提高性能。在这段代码中,虚拟列表的实现原理如下:

  • 通过计算出开始展示的项的索引和需要展示的项数,计算出当前需要展示的数据列表。
  • 通过计算出滚动条的位置和预加载项的总高度,计算出需要滚动的位置。
  • 将滚动位置应用到列表容器上,从而实现虚拟滚动。