字节面试官:既然你啥都会,那你实现一个分页器吧

5,858 阅读3分钟

前言

银五金六, 你申哥我又回来了,我一直以为自己狂的所有面试都能通过。所以才有了下面的一幕。。。这次是一个字节跳动的二面,晚上8点开始,首先是一段自我介绍,我叫申哥,21年来自XX学院,熟悉vue2,vue3源码,各种组件库源码,webpack源码,一阵狂吹之后,首先让我写了一个列表,样式类似下面这种:

image.png 我天,字节跳动就考这么so easy 的东西? 这还不进大厂?三下五除,我便写出来了。之后是一个分页选择器,我写着写着就把自己写哭了,太tmd难了!!

需求

大概让我实现一个100页,显示第一页和最后一页,有省略号的分页器,效果如下:

1652925693031

准备阶段

楼主目前主要使用vue项目,所以只讲vue实现不过你是react用户大体思路可以参考一下

  1. element UI 分页器 Demo 地址 element.eleme.cn/#/zh-CN/com…
  2. element ui 源码 地址: github.com/ElemeFE/ele…
  3. 随便找一个vue项目或者 使用 vue create App 命令自己用脚手架搭建一个项目,方便使用vue项目

分页器如何使用

image-20220519102523040

  <el-pagination
       :current-page="currentPage4"
       :page-size="100"
       :total="400">
     </el-pagination>

pagination分页器的属性比较多,我们就不全都实现了,只是实现它的核心部分,就是中间的切换页码的按钮

主要使用的属性

属性名含义
pageSize每页显示条目个数
total总条目数
pageCount总页数
pagerCount页码按钮的数量,当总页数超过该值时会折叠
currentPage当前页数
disabled是否禁用

实现分页器

步骤一: 分析分页器结构

image-20220519112347386

步骤二: 首页和尾页

第一页和尾页一定会显示, onPagerClick 事件之所以写在Ul上,是利用了事件委托,使当点击某个li的时候都会触发onPagerClick,为了简化内容,css写在最后

image-20220519110548985

 <template>
     <ul @click="onPagerClick" class="el-pager">
         <!-- 首页 -->
         <li
             :class="{ active: currentPage === 1, disabled }"
             v-if="pageCount > 0"
             class="number"
         >
             1
         </li>
         <!-- 最后一页 -->
         <li
             :class="{ active: currentPage === pageCount, disabled }"
             class="number"
             v-if="pageCount > 1"
         >
             {{ pageCount }}
         </li>
     </ul>
 </template>

步骤三: 前省略号和后面省略号显示判断

pageCount 总页数, pagerCount 页码按钮的数量,当总页数超过该值时会折叠,即pageCount > pagerCount

  // 当前页大于按钮数量的一半,左面的省略号显示
 // 这里举个例子,比如最多展示7个按钮(pagerCount为7),则只要当前页大于3的时候,左面的省略号显示
 if (currentPage > (pagerCount + 1) / 2) {
    showPrevMore = true
 }

image-20220523101953282

  // 当前页比中间页码小的时候,右面的省略号一定显示
 if (currentPage < pageCount - halfPagerCount) {
    showNextMore = true
 }

image-20220523101607104

// 页码数量 > 页码按钮的数量,当总页数超过该值时会折叠
// 此时需要显示折叠按钮
/* 处理省略号-start */
if (pageCount > pagerCount) {
    // 当前页大于按钮数量的一半,左面的省略号显示
    // 这里举个例子,比如最多展示7个按钮(pagerCount为7),则只要当前页大于3的时候,左面的省略号显示
    if (currentPage > (pagerCount + 1) / 2) {
        showPrevMore = true
    } // 当前页比中间页码小的时候,右面的省略号一定显示
    if (currentPage < pageCount - halfPagerCount) {
        showNextMore = true
    }
}

步骤四:省略号中间分页按钮

 /* 处理剩余显示的按钮-start */
 // array数组为要显示的页码数字的集合
 const array = []
 // 当前页在页码的右半部分
 // 只显示左面的省略号,则array为...8, 9, 10,直到最大页码数
 if (showPrevMore && !showNextMore) {
     const startPage = pageCount - (pagerCount - 2)
     for (let i = startPage; i < pageCount; i++) {
         array.push(i)
     }
     //
     // 只显示右面的省略号 当前页在页码的左半部分
 } else if (!showPrevMore && showNextMore) {
     // 这种情况比较简单2, 3, 4...
     for (let i = 2; i < pagerCount; i++) {
         array.push(i)
     }
     // 左右两边省略号都显示
 } else if (showPrevMore && showNextMore) {
     // Math.floor(pagerCount / 2)  的意思是取中间分页按钮的左右一边, -1 是因为要减去第一页和最后一页
     const offset = Math.floor(pagerCount / 2) - 1
     for (let i = currentPage - offset; i <= currentPage + offset; i++) {
         array.push(i)
     }
 } else {
     // 没有省略号,则依次显示分页按钮
     for (let i = 2; i < pageCount; i++) {
         array.push(i)
     }
 }
 ​

步骤五:利用事件冒泡写按钮点击事件

ui
 <ul @click="onPagerClick" class="el-pager">
        <!-- 首页 -->
        <li
            :class="{ active: currentPage === 1, disabled }"
            v-if="pageCount > 0"
            class="number"
        >
js
 methods: {
        onPagerClick(e) {
            const text = e.target.innerText
            if (isNaN(text)) {
            } else {
                const page = Number(text)
                console.log(page)
                this.$emit("change", page)
            }
        },
    },

项目源码

<template>
    <ul @click="onPagerClick" class="el-pager">
        <!-- 首页 -->
        <li
            :class="{ active: currentPage === 1, disabled }"
            v-if="pageCount > 0"
            class="number"
        >
            1
        </li>

        <!-- 上面的更多 -->
        <li
            class="el-icon more btn-quickprev"
            :class="[quickprevIconClass, { disabled }]"
            v-if="showPrevMore"
            @mouseenter="onMouseenter('left')"
            @mouseleave="quickprevIconClass = 'el-icon-more'"
        ></li>
        <!-- 页码 -->
        <li
            v-for="pager in pagers"
            :key="pager"
            :class="{ active: currentPage === pager, disabled }"
            class="number"
        >
            {{ pager }}
        </li>
        <!-- 下面的更多 -->
        <li
            class="el-icon more btn-quicknext"
            :class="[quicknextIconClass, { disabled }]"
            v-if="showNextMore"
            @mouseenter="onMouseenter('right')"
            @mouseleave="quicknextIconClass = 'el-icon-more'"
        ></li>

        <!-- 最后一页 -->
        <li
            :class="{ active: currentPage === pageCount, disabled }"
            class="number"
            v-if="pageCount > 1"
        >
            {{ pageCount }}
        </li>
    </ul>
</template>

<script type="text/babel">
export default {
    name: "ElPager",

    props: {
        currentPage: Number,

        pageCount: Number,

        pagerCount: Number,

        disabled: Boolean,
    },

    watch: {
        showPrevMore(val) {
            if (!val) this.quickprevIconClass = "el-icon-more"
        },

        showNextMore(val) {
            if (!val) this.quicknextIconClass = "el-icon-more"
        },
    },

    methods: {
        onPagerClick(event) {
            const target = event.target
            if (target.tagName === "UL" || this.disabled) {
                return
            }

            let newPage = Number(event.target.textContent)
            const pageCount = this.pageCount
            const currentPage = this.currentPage
            const pagerCountOffset = this.pagerCount - 2

            if (target.className.indexOf("more") !== -1) {
                if (target.className.indexOf("quickprev") !== -1) {
                    newPage = currentPage - pagerCountOffset
                } else if (target.className.indexOf("quicknext") !== -1) {
                    newPage = currentPage + pagerCountOffset
                }
            }

            /* istanbul ignore if */
            if (!isNaN(newPage)) {
                if (newPage < 1) {
                    newPage = 1
                }

                if (newPage > pageCount) {
                    newPage = pageCount
                }
            }

            if (newPage !== currentPage) {
                this.$emit("change", newPage)
            }
        },

        onMouseenter(direction) {
            if (this.disabled) return
            if (direction === "left") {
                this.quickprevIconClass = "el-icon-d-arrow-left"
            } else {
                this.quicknextIconClass = "el-icon-d-arrow-right"
            }
        },
    },

    computed: {
        pagers() {
            const pagerCount = this.pagerCount
            const halfPagerCount = (pagerCount - 1) / 2

            const currentPage = Number(this.currentPage)
            const pageCount = Number(this.pageCount)

            let showPrevMore = false
            let showNextMore = false
            // 页码数量 > 页码按钮的数量,当总页数超过该值时会折叠
            // 此时需要显示折叠按钮
            if (pageCount > pagerCount) {
                if (currentPage > pagerCount - halfPagerCount) {
                    showPrevMore = true
                }

                if (currentPage < pageCount - halfPagerCount) {
                    showNextMore = true
                }
            }

            const array = []

            if (showPrevMore && !showNextMore) {
                const startPage = pageCount - (pagerCount - 2)
                for (let i = startPage; i < pageCount; i++) {
                    array.push(i)
                }
            } else if (!showPrevMore && showNextMore) {
                for (let i = 2; i < pagerCount; i++) {
                    array.push(i)
                }
            } else if (showPrevMore && showNextMore) {
                const offset = Math.floor(pagerCount / 2) - 1
                for (
                    let i = currentPage - offset;
                    i <= currentPage + offset;
                    i++
                ) {
                    array.push(i)
                }
            } else {
                for (let i = 2; i < pageCount; i++) {
                    array.push(i)
                }
            }

            this.showPrevMore = showPrevMore
            this.showNextMore = showNextMore

            return array
        },
    },

    data() {
        return {
            current: null,
            showPrevMore: false,
            showNextMore: false,
            quicknextIconClass: "el-icon-more",
            quickprevIconClass: "el-icon-more",
        }
    },
}
</script>
<style scoped></style>

结语:平时还是要多看组件源码了,多面试,多成长一下吧!