创建组件和样式文件
跟之前先创建如图所示的目录结构,然后我们在 packages/components/components.js 导出
export { TButton } from "./button";
export { TMessage } from "./message";
export { TTree } from "./tree";
export { TSteps } from "./steps";
export { TStep } from "./step";
export { TPagination } from "./pagination";
pagination 需要你传入的总数 total,还有每一个页的数量,这两个是必须的,然后通过这两个就可以得知你当前的数据需要多少页。
pagination.js
export const PaginationProps = {
total: {
type: Number,
default: 0,
},
pageSize: {
type: Number,
default: 10,
},
};
pagination.vue
<template>
<div class="t-pagination">
<ul class="t-pagination-list">
<li class="t-pagination-pre t-icon icon-arrow-left-bold t-pagination-item"></li>
<li v-for="page in pageCount" :key="page" class="t-pagination-item">{{ page }}</li>
<li class="t-pagination-next t-icon icon-arrow-right-bold t-pagination-item"></li>
</ul>
</div>
</template>
<script setup>
import { PaginationProps } from "./pagination";
import { computed } from "vue";
defineOptions({
name: "t-pagination",
});
const props = defineProps(PaginationProps);
// 根据总数和每页数量计算出页数
const pageCount = computed(() =>
[...Array(Math.ceil(props.total / props.pageSize)).keys()].map((i) => i + 1)
);
</script>
pagination.less
@paginationHeight: 30px;
.t-pagination {
.t-pagination-list {
padding: 0;
margin: 0;
list-style: none;
display: flex;
align-items: center;
align-items: center;
.t-pagination-item {
padding: 0;
margin: 0;
list-style: none;
width: 28px;
height: @paginationHeight;
line-height: @paginationHeight;
text-align: center;
border-radius: 4px;
cursor: pointer;
}
.t-pagination-item:hover {
color: var(--t-primary);
}
}
}
<t-pagination :total="100" />
我们看一下效果
分页展示
由于我们分页展示的时候,并不是所有的页码都会展示出来,所以我们需要根据当前页码和页数来展示页码,我们这里采用以下规则:
我们先设定显示按钮的最大个数为 7
- 如果页数小于 7,则展示所有页码
- 如果页数大于 7
- 当前页码如果小于 4,则展示 1 2 3 4 5 6 ... 总页数
- 当前页码在最后 4 个页码内,则展示 1 ... 总页数-5 总页数-4 总页数-3 总页数-2 总页数-1 总页数
举个例子:
-
如果有 8 页,当前页面是 4,则显示 1 2 3 4 5 6 ... 8
-
如果当前页面是 5,则显示 1 ... 3 4 5 6 7 8
-
如果有 20 页,当前页面是 10,则显示 1 ... 8 9 10 11 12 ... 20
那我们可以将页码分为三部分,首页,尾页,中间页,中间页最多只有 5 页,省略显示遵循上述规则。
思路已经清楚了,我们就来实现一下
<template>
<div class="t-pagination">
<ul class="t-pagination-list">
<li class="t-pagination-pre t-icon icon-arrow-left-bold t-pagination-item"></li>
<li
class="t-pagination-item"
:class="{ 't-pagination-item__active': firstPage === currentPage }"
@click="handleChangeCurrentPage(firstPage)"
>
{{ firstPage }}
</li>
<li class="t-pagination-item t-icon icon-elipsis" v-if="showFrontEllipsis"></li>
<li
v-for="page in centerPages"
:key="page"
class="t-pagination-item"
:class="{ 't-pagination-item__active': page === currentPage }"
@click="handleChangeCurrentPage(page)"
>
{{ page }}
</li>
<li class="t-pagination-item t-icon icon-elipsis" v-if="showEndEllipsis"></li>
<li
class="t-pagination-item"
:class="{ 't-pagination-item__active': lastPage === currentPage }"
@click="handleChangeCurrentPage(lastPage)"
>
{{ lastPage }}
</li>
<li class="t-pagination-next t-icon icon-arrow-right-bold t-pagination-item"></li>
</ul>
</div>
</template>
<script setup>
import { PaginationProps } from "./pagination";
import { computed, ref } from "vue";
defineOptions({
name: "t-pagination",
});
const props = defineProps(PaginationProps);
// 根据总数和每页数量计算出页数
const pageCount = computed(() =>
[...Array(Math.ceil(props.total / props.pageSize)).keys()].map((i) => i + 1)
);
const currentPage = ref(5); // 当前页面
const showFrontEllipsis = ref(false); // 是否显示前面的省略号
const showEndEllipsis = ref(false); // 是否显示后面的省略号
const firstPage = 1; // 第一页
const lastPage = computed(() => pageCount.value.length); // 最后一页
// 中间的页码
const centerPages = computed(() => {
const center = pageCount.value.slice(1, pageCount.value.length - 1);
if (pageCount.value.length > 7) {
if (currentPage.value === 1) {
showFrontEllipsis.value = false;
showEndEllipsis.value = true;
return center.slice(0, 5);
} else if (currentPage.value === lastPage.value) {
showFrontEllipsis.value = true;
showEndEllipsis.value = false;
return center.slice(-5);
} else if (center.indexOf(currentPage.value) < 3) {
showFrontEllipsis.value = false;
showEndEllipsis.value = true;
return center.slice(0, 5);
} else if (center.indexOf(currentPage.value) > center.length - 4) {
showFrontEllipsis.value = true;
showEndEllipsis.value = false;
return center.slice(-5);
} else {
showFrontEllipsis.value = true;
showEndEllipsis.value = true;
const center = [
currentPage.value - 2,
currentPage.value - 1,
currentPage.value,
currentPage.value + 1,
currentPage.value + 2,
];
return center;
}
} else {
return center;
}
});
const handleChangeCurrentPage = (page) => {
currentPage.value = page;
};
</script>
pagination.less
.t-pagination {
// ...
.t-pagination-item__active {
font-weight: bold;
color: var(--t-primary);
}
}
没有问题,我们将 currentPage 的值改为双向绑定的,并且切换页码的时候发出一个事件
pagination.js
export const PaginationProps = {
total: {
type: Number,
default: 0,
},
pageSize: {
type: Number,
default: 10,
},
currentPage: {
type: Number,
default: 1,
},
};
export const PaginationEmits = ["update:current-page", "current-change"];
我们将组件内部定义的 currentPage 变量删除,然后改为从 props 中获取,且点击页面的时候发出事件
const handleChangeCurrentPage = (page) => {
emit("update:current-page", page);
};
这下我们就可以这么使用了
<t-pagination :total="100" v-model:current-page="currentPage" />
页码交互
上面我们实现的功能虽然满足基本使用,但是页码特别多的时候我们想找到我们想要的页码就会操作特变慢,这时候有两个方案:
- 鼠标移入 ... 按钮的时候变为快进快退按钮,点击的时候可以快速跳转,每次最多跳 5 页
- 可以画一个输入框,让用户直接输入页码,然后回车跳转
页码快速前进后退
我们先来实现第一种方案。
首先,我们需要将我们的 ... 按钮要设置一个交互,左边的省略按钮鼠标 hover 的时候显示后退的 icon,后面的省略按钮 hover 的时候显示前进的 icon。我们可以将 icon 的名称设置为变量,然后通过鼠标的移入和移出事件来控制显示什么图标。
<template>
<div class="t-pagination">
<ul class="t-pagination-list">
<li class="t-pagination-pre t-icon icon-arrow-left-bold t-pagination-item"></li>
<li
class="t-pagination-item"
:class="{ 't-pagination-item__active': firstPage === currentPage }"
>
{{ firstPage }}
</li>
<li
:class="`t-pagination-item t-icon ${frontIcon}`"
v-if="showFrontEllipsis"
@mouseenter="handleMouseOver('left')"
@mouseleave="handleMouseLeave('left')"
></li>
<li
v-for="page in centerPages"
:key="page"
class="t-pagination-item"
:class="{ 't-pagination-item__active': page === currentPage }"
@click="handleChangeCurrentPage(page)"
>
{{ page }}
</li>
<li
:class="`t-pagination-item t-icon ${endIcon}`"
v-if="showEndEllipsis"
@mouseenter="handleMouseOver('right')"
@mouseleave="handleMouseLeave('right')"
></li>
<li
class="t-pagination-item"
:class="{ 't-pagination-item__active': lastPage === currentPage }"
>
{{ lastPage }}
</li>
<li class="t-pagination-next t-icon icon-arrow-right-bold t-pagination-item"></li>
</ul>
</div>
</template>
<script setup>
// ...
const frontIcon = ref("icon-elipsis");
const endIcon = ref("icon-elipsis");
const handleMouseOver = (direction) => {
if (direction === "left") {
frontIcon.value = "icon-arrow-double-left";
} else {
endIcon.value = "icon-arrow-double-right";
}
};
const handleMouseLeave = (direction) => {
if (direction === "left") {
frontIcon.value = "icon-elipsis";
} else {
endIcon.value = "icon-elipsis";
}
};
</script>
接下来我们要实现点击的时候可以快速跳转,每次最多跳 5 页。
<template>
<!-- ... -->
<li
:class="`t-pagination-item t-icon ${frontIcon}`"
v-if="showFrontEllipsis"
@mouseenter="handleMouseOver('left')"
@mouseleave="handleMouseLeave('left')"
@click="handlePageGo('retreat')"
></li>
<!-- ... -->
<li
:class="`t-pagination-item t-icon ${endIcon}`"
v-if="showEndEllipsis"
@mouseenter="handleMouseOver('right')"
@mouseleave="handleMouseLeave('right')"
@click="handlePageGo('forward')"
></li>
<!-- ... -->
</template>
<script setup>
// ..
const frontIcon = ref("icon-elipsis");
const endIcon = ref("icon-elipsis");
// 中间的页码
const centerPages = computed(() => {
const center = pageCount.value.slice(1, pageCount.value.length - 1);
if (pageCount.value.length > 7) {
if (props.currentPage === 1) {
showFrontEllipsis.value = false;
showEndEllipsis.value = true;
frontIcon.value = "icon-elipsis";
return center.slice(0, 5);
} else if (props.currentPage === lastPage.value) {
showFrontEllipsis.value = true;
showEndEllipsis.value = false;
endIcon.value = "icon-elipsis";
return center.slice(-5);
} else if (center.indexOf(props.currentPage) < 3) {
showFrontEllipsis.value = false;
showEndEllipsis.value = true;
frontIcon.value = "icon-elipsis";
return center.slice(0, 5);
} else if (center.indexOf(props.currentPage) > center.length - 4) {
showFrontEllipsis.value = true;
showEndEllipsis.value = false;
endIcon.value = "icon-elipsis";
return center.slice(-5);
} else {
showFrontEllipsis.value = true;
showEndEllipsis.value = true;
const center = [
props.currentPage - 2,
props.currentPage - 1,
props.currentPage,
props.currentPage + 1,
props.currentPage + 2,
];
return center;
}
} else {
return center;
}
});
const handleMouseOver = (direction) => {
if (direction === "left") {
frontIcon.value = "icon-arrow-double-left";
} else {
endIcon.value = "icon-arrow-double-right";
}
};
const handleMouseLeave = (direction) => {
if (direction === "left") {
frontIcon.value = "icon-elipsis";
} else {
endIcon.value = "icon-elipsis";
}
};
const handlePageGo = (direction) => {
if (direction === "forward") {
emit(
"update:current-page",
props.currentPage + 5 > lastPage.value ? lastPage.value : props.currentPage + 5
);
} else {
emit(
"update:current-page",
props.currentPage - 5 < firstPage ? firstPage : props.currentPage - 5
);
}
};
</script>
✨ 注意上面的
centerPages我加了一些图标名称的控制,因为在控制显示隐藏的时候会触发不到mouseleave时间,所有我们需要手动给重置一下图标
页码左右切换
这下这个快速切换功能我们也实现了,我们发现左右的切换按钮没有完成,接下来我们先完善一下两边的切换交互,这个交互规则很简单:
- 点击左边的向左 < 的按钮,当前页面倒退一页
- 点击右边的向右 > 的按钮,当前页面前进一页
- 若当前页面为首页,则向左 < 的按钮置灰且 hover 显示禁用
- 若当前页面为最后一页,则向右 > 的按钮置灰且 hover 显示禁用
前进和后退我们直接复用一下 handlePageGo 方法,我们稍微修改一下
const handlePageGo = (direction, num) => {
if (
(direction === "forward" && props.currentPage === lastPage.value) ||
(direction === "backward" && props.currentPage === firstPage)
)
return;
if (direction === "forward") {
emit(
"update:current-page",
props.currentPage + num > lastPage.value ? lastPage.value : props.currentPage + num
);
} else {
emit(
"update:current-page",
props.currentPage - num < firstPage ? firstPage : props.currentPage - num
);
}
};
然后我们就可以直接使用了
<template>
<div class="t-pagination">
<ul class="t-pagination-list">
<li
class="t-pagination-pre t-icon icon-arrow-left-bold t-pagination-item"
@click="handlePageGo('retreat', 1)"
></li>
<li
class="t-pagination-item"
:class="{ 't-pagination-item__active': firstPage === currentPage }"
@click="handleChangeCurrentPage(firstPage)"
>
{{ firstPage }}
</li>
<li
:class="`t-pagination-item t-icon ${frontIcon}`"
v-if="showFrontEllipsis"
@mouseenter="handleMouseOver('left')"
@mouseleave="handleMouseLeave('left')"
@click="handlePageGo('retreat', 5)"
></li>
<li
v-for="page in centerPages"
:key="page"
class="t-pagination-item"
:class="{ 't-pagination-item__active': page === currentPage }"
@click="handleChangeCurrentPage(page)"
>
{{ page }}
</li>
<li
:class="`t-pagination-item t-icon ${endIcon}`"
v-if="showEndEllipsis"
@mouseenter="handleMouseOver('right')"
@mouseleave="handleMouseLeave('right')"
@click="handlePageGo('forward', 5)"
></li>
<li
class="t-pagination-item"
:class="{ 't-pagination-item__active': lastPage === currentPage }"
@click="handleChangeCurrentPage(lastPage)"
>
{{ lastPage }}
</li>
<li
class="t-pagination-next t-icon icon-arrow-right-bold t-pagination-item"
@click="handlePageGo('forward', 1)"
></li>
</ul>
</div>
</template>
接下来是样式
<!-- ... -->
<li
class="t-pagination-pre t-icon icon-arrow-left-bold t-pagination-item"
:style="{
cursor: firstPage === currentPage ? 'not-allowed' : 'pointer',
color: firstPage === currentPage ? '#ccc' : null,
}"
@click="handlePageGo('retreat', 1)"
></li>
<!-- ... -->
<li
class="t-pagination-next t-icon icon-arrow-right-bold t-pagination-item"
:style="{
cursor: lastPage === currentPage ? 'not-allowed' : 'pointer',
color: lastPage === currentPage ? '#ccc' : null,
}"
@click="handlePageGo('forward', 1)"
></li>
<!-- ... -->
页码直达
好了,接下来我们来做直接切换到用户所要到达的页码,只需要画一个输入框,然后做一个回车事件即可
<div class="t-pagination">
<!-- ... -->
<div class="t-pagination__jump">
<span>跳转至</span>
<input type="number" v-model="goToNum" class="t-pagination__editor" />
</div>
</div>
@paginationHeight: 30px;
.t-pagination {
// ...
.t-pagination__jump {
display: flex;
align-items: center;
gap: 8px;
.t-pagination__editor {
padding: 0 6px;
width: 42px;
height: @paginationHeight;
border-radius: 4px;
border: 1px solid var(--t-border-color);
font-size: 14px;
color: var(--t-text-color);
}
input[type="number"] {
-moz-appearance: textfield;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
}
}
<template>
<div class="t-pagination">
<!-- ... -->
<div class="t-pagination__jump">
<span>跳转至</span>
<input
type="number"
v-model="goToNum"
class="t-pagination__editor"
@keydown.enter="handleGoToPage"
/>
</div>
</div>
</template>
<script setup>
// ...
const goToNum = ref(null);
const handleGoToPage = () => {
if (goToNum.value) {
const targetPageNum =
goToNum.value < 0 ? 1 : goToNum.value > lastPage.value ? lastPage.value : goToNum.value;
goToNum.value = targetPageNum;
emit("update:current-page", targetPageNum);
}
};
</script>
我们设置一个属性 showJump 来控制是否显示跳转功能
pagination.js
export const PaginationProps = {
total: {
type: Number,
default: 0,
},
pageSize: {
type: Number,
default: 10,
},
currentPage: {
type: Number,
default: 1,
},
showJump: {
type: Boolean,
default: false,
},
};
export const PaginationEmits = ["update:current-page", "current-change"];
到这我们的Pagination组件就算基本实现了,后面会继续更新,大家持续关注。
✨ 本专栏源码地址