前言
很早之前写了一篇《vue + element-ui二次封装Table组件,实现表格内容的完全自定义》的文章,这篇文章仅仅阐述了vue2基于element-ui如何实现一个通用化的表格,而没有写如何更优美的去获取表格数据以及分页。
由于项目一直使用react,最近才有项目使用到vue3,那借着这个机会,给大家分享一下实现一个完整的带分页的表格的二次封装。
用到的第三方库
由于我在react的项目中习惯于用ahooks的第三方库,所以vue3项目中也找到一个实现方式及用法类似的库v-e-hooks-plus,在实现useTable hook的方法里会用到其提供的useRequest(http请求的方法)。
vue-hooks-plus: https://inhiblab-core.gitee.io/docs/hooks/en/useRequest/plugins/fetchsing/index.html
表格组件封装
<script setup>
const props = defineProps({
columns: Array,
tableData: Array,
pagination: {
type: Boolean,
default: true,
}, // 是否需要分页
showCheckBox: Boolean, // 是否展示多选框
loading: Boolean,
paginationData: {
type: Object,
default: () => {
return {
current: 1,
total: 0
}
}
}
});
const emits = defineEmits(["handleCurrentChange", "selectRows"]);
// 改变页数
const handleCurrentChange = (value) => {
emits('handleCurrentChange', value)
};
// 多选
const handleSelectionChange = (value) => {
emits("selectRows", value);
};
</script>
<template>
<div class="table-container">
<el-table :data="tableData" v-loading="loading" scrollbar-always-on flexible element-loading-text="加载中..." size="large" style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" v-if="showCheckBox" />
<template v-for="column in columns" :key="column.prop">
<el-table-column :prop="column.prop" :label="column.title" :width="column.width" min-width="160" :fixed="column.fixed">
<template #default="scope">
<slot v-if="column.slot" :name="column.prop" :row="scope.row" />
<span v-else>{{ scope.row[column.prop] ?? "-" }}</span>
</template>
</el-table-column>
</template>
</el-table>
<div class="pagination-container" v-if="pagination && tableData?.length > 0">
<el-pagination background layout="total, prev, pager, next" :page-sizes="[10, 20, 30, 40]"
:current-page="paginationData.current" :total="paginationData.total" @current-change="handleCurrentChange" />
</div>
</div>
</template>
表格组件的使用
<script setup>
import * as api from "@/services/index";
import Search from "./components/search.vue";
import { useTable } from '@/hooks/useTable'
const columns = [
{ prop: "action", title: "操作", width: 120, slot: true },
{ prop: "column1", title: "列名1" },
{ prop: "column2", title: "列名2" },
{ prop: "column3", title: "列名3" },
{ prop: "column4", title: "列名4" },
{ prop: "column5", title: "列名5"},
];
const { searchParam, handleCurrentChange, tableData, pagination, search, reset, loading } = useTable(api.getApnList)
// 搜索
const handleSearch = (params) => {
searchParam.value = params;
search()
};
</script>
<template>
<div>
<Search @handleSearch="handleSearch" :reset="reset" />
<div class="searchTable-wrap">
<c-table :columns="columns" :tableData="tableData" :loading="loading" :paginationData="pagination"
@handleCurrentChange="(value) => handleCurrentChange(value)">
<template #action="record">
<a>删除</a>
</template>
</c-table>
</div>
</div>
</template>
useTable hook方法实现
import {
reactive,
onMounted,
computed,
toRefs
} from 'vue'
import {
useRequest
} from 'vue-hooks-plus'
/**
* @description table 页面表格操作方法封装
* @param {Function} api 获取表格数据 api 方法(必传)
* @param {Object} initParam 获取数据初始化参数(非必传,默认为{})
* @param {Boolean} isPageable 是否有分页(非必传,默认为true)
* @param {Boolean} manual 是否手动触发(非必传,默认为false)
* @param {Function} dataCallBack 对后台返回的数据进行处理的方法(非必传)
* */
export const useTable = (api, initParam = {}, isPageable = true, manual = false, dataCallBack) => {
/**
* 1.获取表格数据的方法getTableData
* 2.查询条件变量searchParam
* 3.分页查询条件变量pagination
* 4.分页pageSize变更的方法handleSizeChange()
* 5.分页currentPage变更的方法handleCurrentChange()
* 6.查询方法search()
* 7.初始化参数的变量searchInitParam
*/
const state = reactive({
// 表格数据
tableData: [],
// 分页数据
pagination: {
// 当前页数
current: 1,
// 每页显示条数
pageSize: 10,
// 总条数
total: 0
},
// 查询参数(只包括查询)
searchParam: {},
// 初始化默认的查询参数
searchInitParam: {},
// 总参数(包含分页和查询参数)
totalParam: {},
// 是否加载中
loading: false,
// 接口返回的所有内容
responseData: null
});
const pageParam = computed({
get: () => {
return {
current: state.pagination.current,
pageSize: state.pagination.pageSize
};
}
});
onMounted(() => {
!manual && reset();
});
// 调用接口获取表格数据
const getData = useRequest(api, {
manual: true
})
const getTableData = async() => {
try {
//合并查询参数
Object.assign(state.totalParam, isPageable ? pageParam.value : {}, initParam)
state.loading = true
let data = await getData.runAsync(state.totalParam);
state.responseData = data;
const {
current,
pageSize,
total,
list
} = data; // 接口返回内容的数据格式
//回调处理,此处可以满足对特殊数据的处理
dataCallBack && (data = dataCallBack(data));
//根据是否分页,进行赋值
state.tableData = isPageable ? list : data;
//对分页参数进行更新
isPageable && updatePagination({
current,
pageSize,
total
});
} catch (err) {
} finally {
state.loading = false
}
}
const updatePagination = (resPageable) => {
Object.assign(state.pagination, resPageable);
};
const updatedTotalParam = () => {
state.totalParam = {};
// 处理查询参数,可以给查询参数加自定义前缀操作
let nowSearchParam = {};
// 防止手动清空输入框携带参数(这里可以自定义查询参数前缀)
for (let key in state.searchParam) {
// * 某些情况下参数为 false/0 也应该携带参数
if (state.searchParam[key] || state.searchParam[key] === false || state.searchParam[key] === 0) {
nowSearchParam[key] = state.searchParam[key];
}
}
Object.assign(state.totalParam, nowSearchParam, isPageable ? pageParam.value : {});
};
const search = () => {
state.pagination.current = 1;
updatedTotalParam();
getTableData();
};
const reset = () => {
state.searchParam = {};
// 重置搜索表单的时,如果有默认搜索参数,则重置默认的搜索参数
Object.keys(state.searchInitParam).forEach(key => {
state.searchParam[key] = state.searchInitParam[key];
});
search()
};
const handleSizeChange = (val) => {
state.pagination.current = 1;
state.pagination.pageSize = val;
getTableData();
};
const handleCurrentChange = (val) => {
state.pagination.current = val;
getTableData();
};
return {
...toRefs(state),
getTableData,
handleCurrentChange,
handleSizeChange,
search,
reset
}
}
题外话
本人空闲时间做了一个计算房贷的微信小程序,有需求,有兴趣的可以扫码看看