学习vue3之useTable封装
在学习vue3中,极致的hook封装,业务与视图分离已经成为趋势。
于是试着封装成适用于自己项目的通用型useTable,视图使用elementPlus的el-table和el-pagination,视图不做过多赘述。
1.开写代码之前需要先理清楚产物
由于项目是后台项目,主要有【查询区域】、【表格】、【分页】三部分组成,根据这三部分,可以梳理出如下产物
1.1 【查询区域】用到的查询参数变量searchParam
1.2 【查询区域】用到的查询方法 search()
1.3 【表格】要用到的数据变量 tableData
1.4 【分页】要用到的变量 pagination
1.5【分页】要用到pageSize变更方法 handleSizeChange()
1.6【分页】要用到currentPage变更方法 handleCurrentChange()
2.梳理出产物后,要能确保能实现上述的产物,接下来梳理入参
2.1 通用型hook,所以要有一个api的入参,确保每个业务都能试用
2.2 查询的时候有些查询条件会有默认值,于是需要一个initParam初始化查询参数的变量
2.3 比如java的mybatisPlus的分页和不分页的接口,返回的内容是不同的,于是加个isPageable参数控制是否需要分页,默认为true
2.4 有时候后端返回的数据,需要二次加工,加多个回调函数dataCallBack(data)
3.代码
export namespace Table {
export interface Pagination {
page: number;
pageSize: number;
total: number;
}
export interface TableStateProps {
tableData: any[];
pagination: Pagination;
searchParam: {
[key: string]: any;
};
searchInitParam: {
[key: string]: any;
};
totalParam: {
[key: string]: any;
};
icon?: {
[key: string]: any;
};
}
}
import { reactive, onMounted, computed, toRefs } from 'vue'
import { Table } from "./interface";
/**
* @description table 页面表格操作方法封装
* @param {Function} api 获取表格数据 api 方法(必传)
* @param {Object} initParam 获取数据初始化参数(非必传,默认为{})
* @param {Boolean} isPageable 是否有分页(非必传,默认为true)
* @param {Function} dataCallBack 对后台返回的数据进行处理的方法(非必传)
* */
export const useTable = (
api: (params: any) => Promise<any>,
initParam: object = {},
isPageable: boolean = true,
dataCallBack?: (data: any) => any
) => {
/**
* 设计思路(首先要知道这个hook要输出的产物是什么?)
* 产物:1.获取表格数据的方法getTableData
* 产物:2.查询条件变量searchParam
* 产物:3.分页查询条件变量pagination
* 产物:4.分页pageSize变更的方法handleSizeChange()
* 产物:5.分页currentPage变更的方法handleCurrentChange()
* 产物:6.查询方法search()
* 产物:7.初始化参数的变量searchInitParam
*/
const state = reactive<Table.TableStateProps>({
// 表格数据
tableData: [],
// 分页数据
pagination: {
// 当前页数
page: 1,
// 每页显示条数
pageSize: 10,
// 总条数
total: 0
},
// 查询参数(只包括查询)
searchParam: {},
// 初始化默认的查询参数
searchInitParam: {},
// 总参数(包含分页和查询参数)
totalParam: {}
});
const pageParam = computed({
get: () => {
return {
page: state.pagination.page,
pageSize: state.pagination.pageSize
};
},
set: (newVal: any) => {
console.log("我是分页更新之后的值", newVal);
}
});
const getTableData = async () => {
try {
//合并查询参数
Object.assign(state.totalParam, isPageable ? pageParam.value : {}, initParam)
console.log(state.totalParam);
let { data } = await api(state.totalParam)
//回调处理,此处可以满足对特殊数据的处理
dataCallBack && (data = dataCallBack(data))
//根据是否分页,进行赋值
state.tableData = isPageable ? data.records : data
//对分页参数进行更新
const { current: page, size: pageSize, total } = data;
isPageable && updatePagination({ page, pageSize, total });
} catch (err) {
}
}
const updatePagination = (resPageable: Table.Pagination) => {
Object.assign(state.pagination, resPageable);
};
const updatedTotalParam = () => {
state.totalParam = {};
// 处理查询参数,可以给查询参数加自定义前缀操作
let nowSearchParam: { [key: string]: any } = {};
// 防止手动清空输入框携带参数(这里可以自定义查询参数前缀)
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.page = 1;
updatedTotalParam();
getTableData();
};
const reset = () => {
state.searchParam = {};
// 重置搜索表单的时,如果有默认搜索参数,则重置默认的搜索参数
Object.keys(state.searchInitParam).forEach(key => {
state.searchParam[key] = state.searchInitParam[key];
});
search()
};
onMounted(() => {
reset();
});
const handleSizeChange = (val: number) => {
state.pagination.page = 1;
state.pagination.pageSize = val;
getTableData();
};
const handleCurrentChange = (val: number) => {
state.pagination.page = val;
getTableData();
};
return {
...toRefs(state),
getTableData, handleCurrentChange, handleSizeChange, search, reset
}
}
4.使用
const { getTableData, handleCurrentChange, handleSizeChange, tableData, pagination } = useTable(getArticleList, { pageSize: 5 })
4.1分页
<el-pagination class="h-14 float-right clear-both mr-6" v-model:currentPage="pagination.page"
:page-size="pagination.pageSize" layout="total, prev, pager, next" :total="pagination.total"
@size-change="handleSizeChange" @current-change="handleCurrentChange">
</el-pagination>
4.2表格
<el-table :data="tableData" :show-header="false" class="px-6">
<el-table-column prop="date" label="Date">
<template #default="scope">
<div class="artcle" @click="artcleEvt(scope.row.id)">
<div class="text-lg text-black"> {{scope.row.title}}</div>
<div class="flex mt-2 text-gray-500">
<div class="ml-2">发布时间</div>
<div class="ml-2" v-show="scope.row.publishedTime"> {{scope.row.publishedTime.slice(0, 10)}}
</div>
<div class="ml-2 text-white px-2" v-show="scope.row.category"
style="backgroundColor:#0000ff;"> {{scope.row.category}}
</div>
</div>
</div>
</template>
</el-table-column>
</el-table>