Vue封装复用组件[ Pagination ]分页器

4,499 阅读3分钟

前言

写项目时,经常需要用到分页器。虽然分页器在网页中占地面积很小,但是功能真的很重要!它对庞大的数据进行分页展示,点击上一页、下一页或者具体的页码可以实现跳转,还可以改变页面展示的数据量。作为一个十分常用的组件,应该把它封装起来以供使用,上次封装了底部切换栏,那么这次就封装一个难度更高的分页器吧。

注:这是我写的项目里一个小功能:展示所有商品,所以以下代码中会有相关商品的数据。

功能分析

  • 改变页码展示不同数据:

    分页器监听到页码的变化,通知父组件并传去新页码,父组件接收到新页码后重新发请求获取数据。

  • 改变页面数据量大小,展示不同数据:

    分页器监听到数据量的变化,通知父组件并传去新的数据量,父组件接收到新数据量后重新发请求获取数据。

  • 组件之间通信

    可以看出过程中分页器需要向父组件传递数据,而且分页器需要先从父组件获取所需数据,才能监测到对应变化。

    综上,我采用的通信方式是:

    • 父 --> 子 :props传参
    • 子 --> 父 :组件的自定义事件

步骤:

封装分页器

1、在components文件夹下新建vue组件Pagination

image.png

2、搭建结构和样式

指路Element-ui:element.eleme.io/#/zh-CN/com…

引入Element-ui组件库中的Pagination分页器,有很多的样式可以选,这里我们选用完整功能的分页器。

image.png

把结构代码复制过来

<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"
/>

效果展示

20221017_030415 00_00_00-00_00_30.gif

技能升级:单组件复用分页器

这里还可以小小地做个技能升级。

不止展示所有商品需要用到分页器,同一个页面里的搜索商品功能也有需求,对于搜索出来的结果,我们也是需要做分页处理的。那么就是一个组件的两个功能都需要使用复用组件。

其实要实现复用,一点也不难,只要定义一个变量来区分两种功能状态,然后在发请求时根据不同状态派发不同请求,就可以了。

注:

这里搜索成功返回的是所有数据,也就是后端没有进行分页处理。

这种情况下需要前端自己进行分页处理--根据页码和页面数据量切割数组。

但在实际开发中,分页处理都是由后端来处理的,这里只是假设。

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>

效果展示

20221017_034613 00_00_00-00_00_30.gif