1
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
版权声明: 本文为 InfoQ 作者【anyRTC开发者】的原创文章。
原文链接:【http://xie.infoq.cn/article/2d4b621e9032c41c9dad5b1bd】。文章转载请联系作者。
anyRTC开发者
关注
实时交互,万物互联! 2020.08.10 加入
实时交互,万物互联,全球实时互动云服务商领跑者!










评论