Skip to main content

moregeek program

web技术分享| 基于vue3实现自己的组件库第二章:pagination组件_mb60af473914346的博客-多极客编程

大家好今天的内容是基于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>

效果展示


在这里插入图片描述
在这里插入图片描述

©著作权归作者所有:来自51CTO博客作者anyRTC的原创作品,请联系作者获取转载授权,否则将追究法律责任
web技术分享| 基于vue3实现自己的组件库第二章:Pagination组件
https://blog.51cto.com/u_15232255/5460307

#yyds干货盘点 前端歌谣的刷题之路-第十二题-伪元素_前端歌谣的博客-多极客编程

前言我是歌谣 我有个兄弟 巅峰的时候排名c站总榜19 叫前端小歌谣 曾经我花了三年的时间创作了他 现在我要用五年的时间超越他 今天又是接近兄弟的一天人生难免坎坷 大不了从头再来 歌谣的意志是永恒的 放弃很容易 但是坚持一定很酷 本题目源自于牛客网 微信公众号前端小歌谣题目请给html模块的div元素加一个后伪元素,且后伪元素的宽度和高度都是20px,背景颜色为"rgb(255, 0, 0)"。​编

#yyds干货盘点#js数组扁平化_文本、的博客-多极客编程

数组扁平化(又称数组降维)flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。const test = ["a", ["b", "c"], ["d", ["e", ["f"]], "g"]]// flat不传参数时,默认扁平化一层test.flat()// ["a", "b", "c", "d", ["e", ["f"]], "g"]//

css houdini:用浏览器引擎实现高级css效果_vivo互联网技术的博客-多极客编程

vivo 互联网前端团队-Wei XingHoudini被称之为Magic of styling and layout on the web,看起来十分神秘,但实际上,Houdini并非什么神秘组织或者神奇魔法,它是一系列与CSS引擎相关的浏览器API的总称。一、Houdini 是什么在了解之前,先来看一些Houdini能实现的效果吧:反向的圆角效果(Border-radius):动态的球形背景(

纯css实现四种方式文本反差色效果_南城前端的博客-多极客编程

如上图所示,文字随着界面的交互变化会修改文字的颜色形成反差色,让平平无奇的文字产生了眼前一亮的效果。如果你关注设计和动画效果,这样的效果肯定见过不少,在一些比较注重设计的网站都有类似的交互效果出现。本文将详细解读有哪些方案可以实现文本反差色效果。 动画效果一 文字蓝色背景颜色从左往右延伸至整个元素,文字背景颜色伴随着背景颜色覆盖过程中发生颜色的变化,且两段文字有着不同的颜色。 实现过程 如图所

婚恋交友网站开发社交聊天平台代码分享(一)_it跟班的博客-多极客编程

婚恋交友网站是基于网络平台的广泛性、互通性、娱乐性、经济性、安全性等优点,作为一种新兴的网络交流方式出现在网络上。网络交友较之其它交友方式更加经济、安全、健康。网站是用当今比较流行的网站开发技术PHP语言进行开发,数据库采用免费,小巧,易用的MySQL数据库。下面是我们用的一些源码这个是前端的,会员部分控制器推荐部分class IndexController extends SiteControl

#yyds干货盘点 前端歌谣的刷题之路-第十三题-画一个圆_前端歌谣的博客-多极客编程

前言我是歌谣 我有个兄弟 巅峰的时候排名c站总榜19 叫前端小歌谣 曾经我花了三年的时间创作了他 现在我要用五年的时间超越他 今天又是接近兄弟的一天人生难免坎坷 大不了从头再来 歌谣的意志是永恒的 放弃很容易 但是坚持一定很酷 本题目源自于牛客网 微信公众号前端小歌谣题目请将html模块的div元素设置为一个半径是50px的圆,且边框为1px的黑色实线。要求:1. 圆角属性仅设置一个值2. 圆角属

减少云计算成本的12种编程技巧_wot技术大会的博客-多极客编程

削减云计算成本需要包括开发人员在内整个团队的努力。以下是开发人员在云中运行成本更低的软件的一些技巧。没有什么比看到开发的应用程序得到病毒式传播更能振奋开发团队的精神了。这是一种美妙的感觉,至少在每月的云账单到来之前是这样。一些开发人员认为,管理计算成本是Devops团队的责任。程序员在编写软件之后并将其发布,然而却让他人担心成本问题。没有比这更离谱的了。明智的开发人员知道他们开发的应用程序对企业的

web安全入门-部署堡垒机jumpserver_最爱大苹果的博客-多极客编程

jumpserver简介:JumpServer 是全球首款开源的堡垒机,使用 GNU GPL v3.0 开源协议,是符合 4A 规范的运维安全审计系统。 JumpServer 使用 Python / Django 为主进行开发,遵循 Web 2.0 规范。总结,如果你企业对设备安全性有审计要求,或者说是要通过iso27001的认证。预算又是没有多少的时候,可以考虑使用开源的堡垒机。网站首页我们使用

海康、大华等安防摄像头、nvr、平台通过gb28181接入livegbs流媒体服务实现web无插件直播_mb62c79384e8183的博客-多极客编程

介绍GB28181协议是由公安部牵头实现的安防行业统一的设备接入、流媒体传输的协议。GB28181从2011版开始,目前基本所有的安防设备厂家的摄像头、NVR、视频平台都支持GB28181协议,是各家设备统一接入管理最好的标准。 LiveGBS就是基于GB28181协议实现的可接入海康、大华、华为、科达、宇视等等各家摄像头、NVR、平台,做到统一管理,以及统一视频接入、并实现WEB端无插件直播,同

【机器学习】线性回归实战案例三:股票数据价格区间预测模型(国外+国内数据)_wx62cb940602a10的博客-多极客编程

股票数据价格区间预测模型(国外+国内数据)​​案例三:股票数据价格区间预测模型(国外+国内数据)​​​​2.3.1 模块加载与忽略警告设置​​​​2.3.2 加载数据和数据筛选​​​​2.3.3 探索式数据分析(EDA)​​​​2.3.4 探究字段之间的关联性​​​​2.3.5 特征工程​​​​2.3.6 模型创建与应用​​​​2.3.7 模型对比​​​​2.3.8 预测结果可视化​​​​2.3.

pg基础篇--核心架构_mysql dba攻坚之路的博客-多极客编程

PG进程启动PG时,会先启动一个Postmaster的主进程,还会fork出一些辅助子进程,这些辅助子进程各自负责一部分功能。Logger(系统日志)进程BgWriter(后台写)进程WalWriter(预写式日志)进程PgArch(归档)进程AutoVacuum(系统自动清理)进程PgStat(统计信息收集)进程主进程Postmaster主进程Postmaster是整个数据库实例的总控进程,负责

istio组件mixer介绍_枫叶飘飘的博客-多极客编程

​Mixer是负责提供策略控制和遥测统计的Istio组件。一、Mixer基本概念Mixer处理不同基础设施后端的灵活性是通过使用通用插件模型实现的。每个插件都被称为Adapter,通过通用插件模型可以方便地扩展新的基础设施后端,通过配置可以指定运行时使用的具体适配器。Mixer当前内置了不少Adapter,常见的有Fluentd、Prometheus、Statsd、Redis Quota等。属性是