写点什么

web 技术分享| 基于 vue3 实现自己的组件库第二章:Pagination 组件

作者:anyRTC开发者
  • 2022 年 7 月 11 日
  • 本文字数:10075 字

    阅读完需:约 33 分钟

大家好今天的内容是基于vue3实现自己的组件库系列第二章,本文默认你会安装和创建 vue3 项目,如果不会请参考vue官网

Pagination.vue Template

  • v-select 组件可以先注释掉

  • v-input 组件可以先注释掉


<div class='v-pagination'>    <div v-for='(item, index) in layout' :key='index'>        <div class='v-pagination-select-box' v-if='item === "switch"'>            <v-select             v-model='activePageSize'             :options='pageSizes'            :disabled='disabled'            @change='handleSizeChange'>            </v-select>        </div>        <v-pages        v-if='item === "pages"'        ref='cpages'        :total='total'        :pagerCount='pagerCount'        :pageSize='pageSize'        v-model='cCurrentPage'        :prependText='prependText'        :suffixText='suffixText'        :hideOnSinglePage='hideOnSinglePage'        :disabled='disabled'        :background='background'        @prev-click='handlePrevClick'        @next-click='handleNextClick'        ></v-pages>        <div :class='["v-pagination-input-box", { disabled }]' v-if='item === "jump"'>            <span>前往</span>            <v-input placeholder='' :disabled='disabled' v-model='jumpValue' @input='handleInput' @change='handleChange'></v-input>            <span>页</span>        </div>    </div></div>
复制代码

Pagination.vue Script

import VairPagination from '../../types/pagination';import vPages from './components/Pages';import vSelect from '../Select/Select';import vInput from '../Input/Input';import { ref, computed, watchEffect, watch } from 'vue';export default {    name: 'pagination',    components: {        vPages,        vSelect,        vInput    },    props: VairPagination,    setup (props, ctx) {        const reg = /^\+?[1-9][0-9]*$/;        const activePageSize = ref('');        const oldJumpValue = ref(props.currentPage);        const jumpValue = ref(props.currentPage);        const cCurrentPage = ref(props.currentPage);        const cpages = ref(null);
const pageSizes = computed(() => { if (Array.isArray(props.pageSizes)) { let arr = []; props.pageSizes.forEach((item, index) => { if (!reg.test(item)) { throw new Error('page-sizes the item It has to be an integer greater than or equal to 1'); } arr.push({ label: `${item}条 / 页`, value: index, number: item }); }); return arr; } else { throw new TypeError(`page-sizes wants to receive an array, but received a ${typeof props.pageSizes}`); } });
const handleSizeChange = (option) => { ctx.emit('size-change', option.number); };
const handlePrevClick = (index) => { ctx.emit('prev-click', index); setJumpValue(); };
const handleNextClick = (index) => { ctx.emit('next-click', index); setJumpValue(); };
const handleInput = (value) => { if (!reg.test(value) && value !== '') { jumpValue.value = oldJumpValue.value; } else { oldJumpValue.value = value; } };
const handleChange = (value) => { const max = Math.ceil(props.total / props.pageSize); if (value < 1) { jumpValue.value = 1; oldJumpValue.value = 1; } else if (value > max) { jumpValue.value = max; oldJumpValue.value = max; } cCurrentPage.value = +jumpValue.value; }; const setJumpValue = () => { jumpValue.value = cCurrentPage.value; oldJumpValue.value = cCurrentPage.value; };
watchEffect(() => { activePageSize.value = pageSizes.value[0].label; });
watchEffect(() => { if (!reg.test(props.pagerCount) || props.pagerCount < 7 || props.pagerCount > 21) { throw new TypeError(`pager-count value of can only be an integer greater than or equal to 7 and less than or equal to 21, but received a ${props.pagerCount}`); } });
watchEffect(() => { if (!reg.test(props.pageSize)) { throw new Error('pager-size It has to be an integer greater than or equal to 1'); } });
watch(() => props.pageSize, () => { setJumpValue(); });
watchEffect(() => { if (!reg.test(props.currentPage)) { throw new Error('current-page It has to be an integer greater than or equal to 1'); } else { cCurrentPage.value = props.currentPage; } });
watchEffect(() => { if (typeof props.total !== 'number' || props.total < 0) { throw new Error('total must be a number greater than or equal to 0'); } });
watchEffect(() => { ctx.emit('current-change', cCurrentPage.value); setJumpValue(); });
return { handleSizeChange, handlePrevClick, handleNextClick, handleInput, handleChange, pageSizes, activePageSize, jumpValue, cCurrentPage, cpages } }}
复制代码

Pagination.vue Props

const VairPagination = {    background: { // 是否开启背景色        type: Boolean,        default: () => {            return false;        }    },    pageSize: { // 每页显示几条数据        type: Number,        default: () => {            return 10;        }    },    total: { // 数据总数量        type: Number,        default: () => {            return 0;        }    },    pagerCount: { // 当总页数超过该值时会开启折叠 最低为 7        type: Number,        default: () => {            return 7;        }    },    pageSizes: { // 每页显示个数选择器的选项        type: Array,        default: () => {            return [10, 20, 30, 40, 50, 100];        }    },    prependText: { // 后退按钮文字        type: String,        default: () => {            return '';        }    },    suffixText: { // 前进按钮文字        type: String,        default: () => {            return '';        }    },    disabled: { // 是否禁用        type: Boolean,        default: () => {            return false;        }    },    hideOnSinglePage: { // 只有一页时是否隐藏        type: Boolean,        default: () => {            return false;        }    },    currentPage: { // 当前页数        type: Number,        default: () => {            return 1;        }    },    layout: { // 组件布局显示顺序        type: Array,        default: () => {            return ['switch', 'pages', 'jump'];        }    },};

// Event // size-change (number) // current-change (index) // prev-click (index) // next-click (index)

export default VairPagination;
复制代码

Pagination.vue Style

<style lang='less' scoped>.v-pagination {    display: flex;    align-items: center;    .v-pagination-select-box, .v-pagination-input-box {        width: 120px;        /deep/.v-select, /deep/.v-input {            min-width: 0;        }        /deep/.v-input {            height: 30px;            .v-input-box, .input {                height: 30px;                .suffix {                    height: 25px;                }            }        }    }    .v-pagination-input-box {        display: flex;        align-items: center;        width: 120px;        /deep/.v-input {            width: 50px;            margin: 0 6px;            .input {                text-indent: 0px;                text-align: center;            }        }        span {            font-size: 14px;        }    }    .disabled {        cursor: not-allowed;        span {            color: #c0c4cc;        }    }}</style>
复制代码

Pages.vue Template

<div class='v-pages' ref='cPages'>    <div :class='["prepend", { prependDisabled }, { disabled }]'         ref='prepend'        @click='handlePrependClick'>        <p v-if='prependText'>{{ prependText }}</p>        <i v-if='!prependText' class='iconfont icon-zuojiantou'></i>    </div>    <ul class='v-pages-ul'>        <li :class='["v-pages-li", { activeLi: modelValue === item }, { disabled }]'             :ref='el => {if (el) liList[index] = el}'            v-for='(item, index) in calculatePagesButtonList' :key='index'            @click='handlePagesLiClick(item)'>            <span :class='[{ color: !background }]' v-if='item !== "suffix" && item !== "prepend"'>{{ item }}</span>            <i  @mouseenter='handleMouseEnter(item)'                @mouseleave='handleMouseLeave(item)'                 @click='handleIClick(item)'                v-else                 :class='["iconfont", "icon-ellipsis2",                { "icon-chevronsrightshuangyoujiantou": doubleRight && item === "suffix" },                { "icon-chevronsleftshuangzuojiantou": doubleLeft && item === "prepend" }]'>            </i>        </li>    </ul>    <div         ref='suffix'        :class='["suffix", { suffixDisabled }, { disabled }]'         @click='handleSuffixClick'>        <p v-if='suffixText'>{{ suffixText }}</p>        <i v-if='!suffixText' class='iconfont icon-youjiantou'></i>    </div></div>
复制代码

Pages.vue Script

import { ref, computed, watchEffect, onMounted } from 'vue';export default {    name: 'pages',    props: {        total: Number,        pagerCount: Number,        pageSize: Number,        prependText: String,        suffixText: String,        disabled: Boolean,        hideOnSinglePage: Boolean,        background: Boolean || String,        modelValue: Number    },    setup (props, ctx) {        const medianButtonList = ref([]);        const prependDisabled = ref(true);        const suffixDisabled = ref(true);        const doubleLeft = ref(false);        const doubleRight = ref(false);        const liList = ref([]);        const prepend = ref(null);        const suffix = ref(null);        const cPages = ref(null);
onMounted(() => { watchEffect(() => { liList.value.forEach(item => { !props.background && (item.style.backgroundColor = 'transparent'); }); !props.background && (prepend.value.style.backgroundColor = 'transparent'); !props.background && (suffix.value.style.backgroundColor = 'transparent'); }); watchEffect(() => { if (calculatePagesButtonList.value.length <= 1 && props.hideOnSinglePage) { cPages.value.style.display = 'none'; } else { cPages.value.style.display = 'flex'; } }); });
const handlePagesLiClick = (index) => { if (props.disabled) return if (typeof index === 'number' && props.modelValue !== index) { ctx.emit('update:modelValue', index); ctx.emit('current-change', index); } };
const handleMouseEnter = (item) => { if (props.disabled) return if (item === 'prepend') { doubleLeft.value = true; } else if (item === 'suffix') { doubleRight.value = true; } };
const handleMouseLeave = (item) => { if (props.disabled) return if (item === 'prepend') { doubleLeft.value = false; } else if (item === 'suffix') { doubleRight.value = false; } };
const handleIClick = (item) => { if (props.disabled) return if (item === 'prepend') { const num = props.modelValue - 3; ctx.emit('update:modelValue', num < 1? 1 : num); } else if (item === 'suffix') { const maxPages = Math.ceil(props.total / props.pageSize); const num = props.modelValue + 3; ctx.emit('update:modelValue', num > maxPages? maxPages : num); } ctx.emit('current-change', props.modelValue); };
const handlePrependClick = () => { if (props.disabled) return if (props.modelValue <= 1) return; ctx.emit('update:modelValue', props.modelValue - 1); ctx.emit('prev-click', props.modelValue); };
const handleSuffixClick = () => { if (props.disabled) return const value = calculatePagesButtonList.value; const item = value[value.length - 1]; if (props.modelValue >= item) return; ctx.emit('update:modelValue', props.modelValue + 1); ctx.emit('next-click', props.modelValue); };
const calculatePagesButtonList = computed(() => { const value = props.modelValue; const maxPages = Math.ceil(props.total / props.pageSize); const bool = (maxPages - props.pagerCount) > 0; const maxCurrentPage = (value - maxPages) > 0? maxPages : value; const pagerCountHalf = Math.ceil((props.pagerCount - 2) / 2); const a = (maxCurrentPage + pagerCountHalf) < maxPages; const b = (maxCurrentPage - pagerCountHalf) <= 2; let pagesButtonList = []; if (bool) { if (b) { for(let i = 1; i < props.pagerCount; i++) { pagesButtonList.push(i); } pagesButtonList.push('suffix'); pagesButtonList.push(maxPages); } else if (a) { pagesButtonList.push(1); pagesButtonList.push('prepend'); for(let i = (maxCurrentPage - pagerCountHalf + 1); i < (maxCurrentPage + pagerCountHalf); i++) { pagesButtonList.push(i); } pagesButtonList.push('suffix'); pagesButtonList.push(maxPages); } else if (!a) { pagesButtonList.push(1); pagesButtonList.push('prepend'); for(let i = (maxPages - props.pagerCount + 2); i <= maxPages; i++) { pagesButtonList.push(i); } } } else { for (let i = 1; i <= maxPages; i++) { pagesButtonList.push(i); } } return pagesButtonList; });
const calculateCurrentPage = () => { const value = calculatePagesButtonList.value; const item = value[value.length - 1]; ctx.emit('update:modelValue', props.modelValue > item? item : props.modelValue); };
watchEffect(() => { calculateCurrentPage(); });
watchEffect(() => { const value = calculatePagesButtonList.value; const item = value[value.length - 1]; prependDisabled.value = props.modelValue === 1; suffixDisabled.value = props.modelValue >= item; });
return { calculatePagesButtonList, medianButtonList, prependDisabled, suffixDisabled, handlePagesLiClick, handlePrependClick, handleSuffixClick, handleMouseEnter, handleMouseLeave, handleIClick, doubleRight, doubleLeft, liList, prepend, cPages, suffix } }}
复制代码

Pages.vue Style

<style lang='less' scoped>.v-pages {    display: flex;    align-items: center;    margin: 0 14px;    .prepend, .suffix {        display: flex;        align-items: center;        justify-content: center;        min-width: 30px;        min-height: 28px;        box-sizing: border-box;        padding: 0 6px;        margin: 0 5px;        background-color:#F4F4F5;        cursor: pointer;        p, i {            font-size: 12px;            color: #333;            font-weight: 600;        }        &:hover {            p, i {                color: #409EFF;            }        }    }    .prependDisabled, .suffixDisabled {        p, i {            color: #CDC9CC !important;        }        cursor: not-allowed;    }    .v-pages-ul {        display: flex;        align-items: center;        .v-pages-li {            margin: 0 5px;            background-color:#F4F4F5;            cursor: pointer;            span, i {                display: block;                min-width: 30px;                box-sizing: border-box;                padding: 0 6px;                line-height: 28px;                text-align: center;                font-size: 12px;                color: #333;                font-weight: 600;            }            &:hover {                span, i {                    color: #409EFF;                }            }        }        .activeLi {            background-color:#409EFF;            span {                color: #fff;            }            .color {                color: #409EFF !important;            }            &:hover {                span {                    color: #fff;                }            }        }    }    .disabled {        p, i, span {            color: #c0c4cc !important;        }        cursor: not-allowed !important;    }}</style>
复制代码

index.js 出口文件中引入组件

// Pagination 分页import Pagination from './components/Pagination/Pagination.vue';import Pages from './components/Pagination/components/Pages.vue';
const Vair = function(Vue) { Vue.component(`v-${Pagination.name}`, Pagination); Vue.component(`v-${Pages.name}`, Pages);}
export default Vair;
复制代码

使用组件

  • 在main.js中引入


import { createApp } from 'vue'; import App from './App.vue'; import Vair from './libs/vair/index.js'; const app = createApp(App); app.use(Vair).mount('#app');
复制代码

App.vue 中调用

<template>    <div>        <v-pagination        :page-sizes='pageSizes'        :pager-count='pagerCount'        :total='total'        :page-size='pageSize'        :current-page='currentPage'        :background='background'        :hideOnSinglePage='false'        :disabled='disabled'        @current-change='handleCurrentChange'        @prev-click='handlePrevClick'        @next-click='handleNextClick'        @size-change='handleSizeChange'        ></v-pagination>    </div></template>
<script>import { ref } from 'vue';export default { setup () { const pageSizes = ref([10, 20, 30, 40, 50, 100]); const pageSize = ref(10); const total = ref(50500); const currentPage = ref(10); const pagerCount = ref(7); const background = ref(true); const prependText = ref('prepre'); const suffixText = ref('nextnext'); const disabled = ref(false);
const handleCurrentChange = (index) => { console.log(index) };
const handlePrevClick = (index) => { console.log('handlePrevClick 触发了', index); };
const handleNextClick = (index) => { console.log('handleNextClick 触发了', index); };
const handleSizeChange = (number) => { pageSize.value = number; };
return { pageSizes, pageSize, total, disabled, currentPage, pagerCount, handleCurrentChange, handlePrevClick, handleNextClick, handleSizeChange, background, prependText, suffixText } }}</script>
<style lang='less' scoped>div { margin-top: 20px;}</style>
复制代码

效果展示



发布于: 刚刚阅读数: 4
用户头像

实时交互,万物互联! 2020.08.10 加入

实时交互,万物互联,全球实时互动云服务商领跑者!

评论

发布
暂无评论
web技术分享| 基于vue3实现自己的组件库第二章:Pagination组件_前端_anyRTC开发者_InfoQ写作社区