前言
写项目时,经常需要用到分页器。虽然分页器在网页中占地面积很小,但是功能真的很重要!它对庞大的数据进行分页展示,点击上一页、下一页或者具体的页码可以实现跳转,还可以改变页面展示的数据量。作为一个十分常用的组件,应该把它封装起来以供使用,上次封装了底部切换栏,那么这次就封装一个难度更高的分页器吧。
注:这是我写的项目里一个小功能:展示所有商品,所以以下代码中会有相关商品的数据。
功能分析
-
改变页码展示不同数据:
分页器监听到页码的变化,通知父组件并传去新页码,父组件接收到新页码后重新发请求获取数据。
-
改变页面数据量大小,展示不同数据:
分页器监听到数据量的变化,通知父组件并传去新的数据量,父组件接收到新数据量后重新发请求获取数据。
-
组件之间通信
可以看出过程中分页器需要向父组件传递数据,而且分页器需要先从父组件获取所需数据,才能监测到对应变化。
综上,我采用的通信方式是:
父 --> 子:props传参子 --> 父:组件的自定义事件
步骤:
封装分页器
1、在components文件夹下新建vue组件Pagination
2、搭建结构和样式
指路Element-ui:element.eleme.io/#/zh-CN/com…
引入Element-ui组件库中的Pagination分页器,有很多的样式可以选,这里我们选用完整功能的分页器。
把结构代码复制过来
<el-pagination
@size-change="handleSizeChange" // 绑定回调:改变页面展示的数据量大小
@current-change="handleCurrentChange" // 绑定回调: 改变页码
:current-page="currentPage4" // 当前页码(样式高亮)
:page-sizes="[100, 200, 300, 400]" // 可选择的数量大小
:page-size="100" // 当前数据量大小
layout="total, sizes, prev, pager, next, jumper" // 分页器布局
:total="400" // 所有数据的数量
>
</el-pagination>
<script>
export default {
methods: {
// 改变页面数据量
handleSizeChange(val) {
console.log(`每页 ${val} 条`);
},
// 改变页码
handleCurrentChange(val) {
console.log(`当前页: ${val}`);
}
},
data() {
return {
currentPage1: 5,
currentPage2: 5,
currentPage3: 5,
currentPage4: 4
};
}
}
</script>
复制以后,再看看结构上的参数分别表示什么意思。我们可以按需使用Element-ui给定的参数、属性和回调事件。
阅读和使用官方文档的能力是一个好程序员的必备能力。
3、判断分页器需要哪些数据
显而易见,从结构上看,我们需要以下参数:
| 参数 | 备注 |
|---|---|
| total | 所有数据总量 |
| pageSize | 当前页面数据量 |
| currentPage | 当前页码 |
props接收父组件传过来的数据,指定每个参数的类型并给予默认值
props: {
total: {
type: Number,
default: 10,
},
pageSize: {
type: Number,
default: 7,
},
currentPage:{
type: Number,
default: 1,
}
},
4、改变时,在回调函数触发自定义事件
父组件中定义两个自定义事件,分别对应改变页码和改变页面数据量
methods: {
// 改页面数据量
handleSizeChange(val) {
// val值就是新的页面数据量
// 父组件传过来的自定义事件名:changePageSize
// 自定义事件其实是一个函数,这里函数需要参数(当前页面数据量,当前页码)
this.$emit("changePageSize", val, this.currentPage);
},
// 改变页码
handleCurrentChange(val) {
// 同理
this.$emit("changePageNo", val, this.pageSize);
},
},
父组件使用
1、引入子组件并注册
import Pagination from "@/components/Pagination";
export default {
components: { Pagination },
data() {
return {
goodsList: [],
total: 0,
pageSize: 1,
pageNo: 1,
currentPage: 1,
};
},
}
2、定义传输的数据和自定义事件的方法函数
// 获取数据
async getData(pageNo = 1, pageSize = 4) {
console.log(pageNo, pageSize);
await this.$api.getAllGoods(pageNo, pageSize).then((res) => {
this.goodsList = res.data;
this.total = res.total;
this.pageSize = parseInt(res.pageSize);
this.pageNo = parseInt(res.pageNo);
this.currentPage = this.pageNo;
this.allData = res.allData
});
},
// 改变页码,重发请求
changePageNo(pageNo, pageSize) {
this.currentPage = pageNo;
this.getData(pageNo, pageSize);
},
// 改变页面数据量,重发请求
changePageSize(pageSize, pageNo) {
this.currentPage = 1;
this.getData(pageNo, pageSize);
},
3、使用组件,传递数据
<Pagination
:total="total"
:pageSize="pageSize"
:pageNo="pageNo"
:currentPage="currentPage"
@changePageNo="changePageNo"
@changePageSize="changePageSize"
/>
效果展示
技能升级:单组件复用分页器
这里还可以小小地做个技能升级。
不止展示所有商品需要用到分页器,同一个页面里的搜索商品功能也有需求,对于搜索出来的结果,我们也是需要做分页处理的。那么就是一个组件的两个功能都需要使用复用组件。
其实要实现复用,一点也不难,只要定义一个变量来区分两种功能状态,然后在发请求时根据不同状态派发不同请求,就可以了。
注:
这里搜索成功返回的是所有数据,也就是后端没有进行分页处理。
这种情况下需要前端自己进行分页处理--根据页码和页面数据量切割数组。
但在实际开发中,分页处理都是由后端来处理的,这里只是假设。
1、定义用于区分的变量
keyword: "",
type:'all' , // 默认状态是展示所有商品,搜索状态下为'search'
searchList: [], // 搜索返回的所有数据
allData:[],
// 搜索关键字 发请求
async goSearch() {
if (this.keyword.trim()) {
await this.$api
.searchGoods({ searchParams: this.keyword.trim() })
.then((res) => {
// console.log(res);
if (res.code == 200) {
this.searchList = res.data;
this.total = res.total; // 现在是获取回来所有数据,前端数组切割展示
this.type = "search";
this.changePageNo(1, 4); // 刚获取搜索数据回来,当前页码是1,初始页码大小是4
} else {
// 无数据处理
this.type = "all";
(this.total = 0), (this.pageNo = 1);
this.goodsList = [];
}
});
} else {
this.getData(1, this.pageSize);
return;
}
},
2、修改自定义事件
// 改变页码,重发请求
changePageNo(pageNo, pageSize) {
this.currentPage = pageNo;
if (this.type == "all") {
this.getData(pageNo, pageSize);
}
// 如果是所有商品,就调用方法发请求;如果是搜索,就切割数组改变当前展示的商品列表
else {
this.goodsList = this.searchList.slice(
(pageNo - 1) * pageSize,
pageNo * pageSize
);
}
},
// 改变页面大小,重发请求
changePageSize(pageSize, pageNo) {
// this.currentPage = 1;
if (this.type == "all") {
this.getData(pageNo, pageSize);
} else {
this.goodsList = this.searchList.slice(
(pageNo - 1) * pageSize,
pageNo * pageSize
);
}
},
有没有注意到,这里完全不用修改Pagination子组件噢。
所有代码
<template>
<div id="showGoods">
<!-- 头部 -->
<header>
<el-input
v-model="keyword"
placeholder="输入关键字进行搜索……"
@change="goSearch"
></el-input>
<el-button type="primary" icon="el-icon-search">搜索</el-button>
<el-button type="primary">添加商品</el-button>
<el-button type="primary">弹窗添加</el-button>
</header>
<!-- 表格 -->
<main>
<el-table
:data="goodsList"
border
height='420'
align="center"
header-align="center"
>
<el-table-column type="selection"> </el-table-column>
<el-table-column label="商品ID" prop="id" width="70"> </el-table-column>
<el-table-column label="商品名称" prop="name"> </el-table-column>
<el-table-column label="商品价格" prop="price"> </el-table-column>
<el-table-column label="商品数量" prop="num"> </el-table-column>
<el-table-column label="规格类目" prop="category" width="100">
</el-table-column>
<el-table-column label="商品图片" prop="imgUrl" width="100">
</el-table-column>
<el-table-column label="商品卖点" prop="sellPoint" width="100">
</el-table-column>
<el-table-column label="商品描述" prop="desc" width="130">
</el-table-column>
<el-table-column label="商品操作" width="180">
<template slot-scope="scope">
<el-button size="mini"
>编辑</el-button
>
<el-button
size="mini"
type="danger"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
</main>
<!-- 分页 -->
<Pagination
:total="total"
:pageSize="pageSize"
:pageNo="pageNo"
:currentPage="currentPage"
@changePageNo="changePageNo"
@changePageSize="changePageSize"
/>
</div>
</template>
<script>
import Pagination from "@/components/Pagination";
import { Message } from 'element-ui';
export default {
name: "ShowGoods",
components: { Pagination },
data() {
return {
keyword: "",
goodsList: [],
total: 0,
pageSize: 1,
pageNo: 1,
type: "all", // search 为搜索分页
searchList: [], //搜索的结果
currentPage: 1,
allData:[],
};
},
created() {
this.getData();
},
methods: {
// 获取数据
async getData(pageNo = 1, pageSize = 4) {
console.log(pageNo, pageSize);
await this.$api.getAllGoods(pageNo, pageSize).then((res) => {
this.goodsList = res.data;
this.total = res.total;
this.pageSize = parseInt(res.pageSize);
this.pageNo = parseInt(res.pageNo);
this.currentPage = this.pageNo;
this.type = "all";
this.allData = res.allData
});
},
// 改变页码,重发请求
changePageNo(pageNo, pageSize) {
this.currentPage = pageNo;
if (this.type == "all") {
this.getData(pageNo, pageSize);
}
else {
this.goodsList = this.searchList.slice(
(pageNo - 1) * pageSize,
pageNo * pageSize
);
}
},
// 改变页面大小,重发请求
changePageSize(pageSize, pageNo) {
this.currentPage = 1;
if (this.type == "all") {
this.getData(pageNo, pageSize);
} else {
this.goodsList = this.searchList.slice(
(pageNo - 1) * pageSize,
pageNo * pageSize
);
}
},
// 搜索商品
async goSearch() {
// 根据关键字发请求 搜索
if (this.keyword.trim()) {
await this.$api
.searchGoods({ searchParams: this.keyword.trim() })
.then((res) => {
// console.log(res);
if (res.code == 200) {
this.searchList = res.data;
this.total = res.total; // 现在是获取回来所有数据,前端数组切割展示
this.type = "search";
this.changePageNo(1, 4);
} else {
// 无数据处理
this.type = "all";
(this.total = 0), (this.pageNo = 1);
this.goodsList = [];
}
});
} else {
this.getData(1, this.pageSize);
return;
}
},
},
};
</script>
<style scoped lang="scss">
header {
display: flex;
align-items: center;
::v-deep .el-input {
width: 500px;
}
::v-deep .el-button {
margin-left: 20px;
font-size: 16px;
}
}
main {
margin: 15px 0;
::v-deep .el-button--mini {
font-size: 15px;
}
}
</style>