Vue3.2 + Element-Plus 二次封装 el-table(Pro版)
创始人
2024-01-29 18:37:42
0

前言 📖

ProTable 组件目前已是 2.0版本🌈,在 1.0版本 中大家提出的问题与功能优化,目前已经得到优化和解决。

😀 欢迎大家在使用过程中发现任何问题或更好的想法,都可以在下方评论区留言,或者我的开源项目 issues 中提出。如果你觉得还不错,请帮我点个小小的 Star 🧡

一、在线预览 👀

Link:admin.spicyboy.cn

二、Git 仓库地址 (欢迎 Star⭐⭐⭐)

Gitee:gitee.com/laramie/Gee…

GitHub:github.com/HalseySpicy…

三、ProTable 功能 🚀🚀🚀

ProTable 组件目前使用属性透传进行重构,支持 el-table && el-table-column 所有属性、事件、方法的调用,不会有任何心智负担。

  • 表格内容自适应屏幕宽高,溢出内容表格内部滚动(flex 布局)
  • 表格搜索、重置、分页查询 Hooks 封装 (页面使用不会存在任何搜索、重置、分页查询逻辑)
  • 表格数据操作 Hooks 封装 (单条数据删除、批量删除、重置密码、状态切换等操作)
  • 表格数据多选 Hooks 封装 (支持现跨页勾选数据)
  • 表格数据导入组件、导出 Hooks 封装
  • 表格搜索区域使用 Grid 布局重构,支持自定义响应式配置
  • 表格分页组件封装(Pagination)
  • 表格数据刷新、列显隐、列排序、搜索区域显隐设置
  • 表格数据打印功能(可勾选数据、隐藏列打印)
  • 单元格内容格式化、tag 标签显示(有字典 enum 会根据字典 enum 自动格式化)
  • 支持多级表头、表头内容自定义渲染(支持作用域插槽、tsx 语法、h 函数)
  • 支持单元格内容自定义渲染(支持作用域插槽、tsx 语法、h 函数)
  • 配合 TreeFilter、SelectFilter 组件使用更佳(项目中有使用示例)

四、ProTable 功能需求分析 📑

首先我们来看效果图(总共可以分为五个模块):

  • 1、表格搜索区域
  • 2、表格数据操作按钮区域
  • 3、表格功能按钮区域
  • 4、表格主体内容展示区域
  • 5、表格分页区域

1、表格搜索区域需求分析:

可以看到搜索区域的字段都是存在于表格当中的,并且每个页面的搜索、重置方法都是一样的逻辑,只是不同的查询参数而已。我们完全可以在传表格配置项 columns 时,直接指定某个 column 的 search 配置,就能把该项变为搜索项,然后使用 el 字段可以指定搜索框的类型,最后把表格的搜索方法都封装成 Hooks 钩子函数。页面上完全就不会存在任何搜索、重置逻辑了。

在 1.0 版本中使用 v-if 判断太麻烦,为了更方便用户传递参数,搜索组件在 2.0 版本中通过 component :is 动态组件 && v-bind 属性透传实现,将用户传递的参数全部透传到组件上,所以大家可以直接根据 element 官方文档在 props 中传递参数了。以下代码还结合了自己逻辑上的一些处理:


复制代码

表格搜索组件在 2.0 版本中还支持了响应式配置,使用 Grid 方法进行整体重构 😋。

2、表格数据操作按钮区域需求分析:

表格数据操作按钮基本上每个页面都会不一样,所以我们直接使用 作用域插槽 来完成每个页面的数据操作按钮区域,作用域插槽 可以将表格多选数据信息从 ProTable 的 Hooks 多选钩子函数中传到页面上使用。

scope 数据中包含:selectedList(当前选择的数据)、selectedListIds(当前选择的数据id)、isSelected(当前是否选中的数据)




复制代码

3、表格功能按钮区域分析:

这块区域没什么特殊功能,只有四个按钮,其功能分别为:表格数据刷新(一直会携带当前查询和分页条件)、表格数据打印、表格列设置(列显隐、列排序)、表格搜索区域显隐(方便展示更多的数据信息)。 可通过 toolButton 属性控制这块区域的显隐。

表格打印功能基于 PrintJs 实现,因 PrintJs 不支持多级表头打印,所以当页面存在多级表头时,只会打印最后一级表头。表格打印功能可根据显示的列和勾选的数据动态打印,默认打印当前显示的所有数据。

4、表格主体内容展示区域分析:

🍉 该区域是最重要的数据展示区域,对于使用最多的功能就是表头和单元格内容可以自定义渲染,在第 1.0 版本中,自定义表头只支持传入renderHeader方法,自定义单元格内容只支持slot插槽。

💥 目前 2.0 版本中,表头支持headerRender方法(避免与 el-table-column 上的属性重名导致报错)、作用域插槽(column.prop + 'Header')两种方式自定义,单元格内容支持render方法和作用域插槽(column 上的 prop 属性)两种方式自定义。

  • 使用作用域插槽:



复制代码
  • 使用 tsx 语法:

复制代码

💢💢💢 最强大的功能:如果你想使用 el-table 的任何属性、事件,目前通过属性透传都能支持。

如果你还不了解属性透传,请阅读 vue 官方文档:cn.vuejs.org/guide/compo…

  • ProTable 组件上的绑定的所有属性和事件都会通过 v-bind="$attrs" 透传到 el-table 上。
  • ProTable 组件内部暴露了 el-table DOM,可通过 proTable.value.element.方法名 调用其方法。

复制代码

5、表格分页区域分析:

分页区域也没有什么特殊的功能,该支持的都支持了🤣(页面上使用 ProTable 组件完全不存在分页逻辑)


复制代码

五、ProTable 文档 📚

1、ProTable 属性(ProTableProps):

使用 v-bind="$atts" 通过属性透传将 ProTable 组件属性全部透传到 el-table 上,所以我们支持 el-table 的所有 Props 属性。在此基础上,还扩展了以下 Props:

属性名类型是否必传默认值属性描述
columnsColumnPropsProTable 组件会根据此字段渲染搜索表单与表格列,详情见 ColumnProps
requestApiFunction获取表格数据的请求 API
dataCallbackFunction返回数据的回调函数,可以对数据进行处理
titleString表格标题,目前只在打印的时候用到
paginationBooleantrue是否显示分页组件
initParamObject{}表格请求的初始化参数
toolButtonBooleantrue是否显示表格功能按钮
selectIdString'id'当表格数据多选时,所指定的 id

2、Column 配置(ColumnProps):

使用 v-bind="column" 通过属性透传将每一项 column 属性全部透传到 el-table-column 上,所以我们支持 el-table-column 的所有 Props 属性。在此基础上,还扩展了以下 Props:

属性名类型是否必传默认值属性描述
tagBooleanfalse当前单元格值是否为标签展示
isShowBooleantrue当前列是否显示在表格内
searchSearchProps搜索项配置,详情见 SearchProps
enumObject | Function字典,可格式化单元格内容,还可以作为搜索框的下拉选项(字典可以为请求函数,内部会自动执行)
isFilterEnumBooleantrue当前单元格值是否根据 enum 格式化,例如 enum 只作为搜索项数据,不参与内容格式化
fieldNamesObject指定字典 label && value 的 key 值
headerRenderFunction自定义表头内容渲染(tsx语法)
renderFunction自定义单元格内容渲染(tsx语法)
_childrenColumnProps多级表头

3、搜索项 配置(SearchProps):

使用 v-bind="column.search.props“ 通过属性透传将 search.props 属性全部透传到每一项搜索组件上,所以我们支持 input、select、tree-select、date-packer、time-picker、time-select、swicth 所有属性,并在其基础上还扩展了以下 Props:

属性名类型是否必传默认值属性描述
elString当前项搜索框的类型,支持:input、select、tree-select、date-packer、time-picker、time-select、swicth
propsObject根据 element plus 官方文档来传递,该属性所有值会透传到组件
defaultValueAny搜索项默认值
keyString当搜索项 key 不为 prop 属性时,可通过 key 指定
orderNumber搜索项排序(从大到小)
spanNumber1搜索项所占用的列数,默认为1列
offsetNumber搜索字段左侧偏移列数

4、ProTable 事件:

根据 ElementPlus Table 文档在 ProTable 组件上绑定事件即可,组件会通过 $attrs 透传给 el-table

el-table 事件文档链接

5、ProTable 方法:

ProTable 组件暴露了 el-table 实例和一些组件内部的参数和方法:

el-table 方法文档链接

方法名描述
elementel-table 实例,可以通过element.value.方法名来调用 el-table 的所有方法
tableData当前页面所展示的数据
searchParam所有的搜索参数
pageable当前表格的分页数据
getTableList获取、刷新表格数据的方法
clearSelection清空表格选择的数据

6、ProTable 插槽:

插槽名描述
默认插槽,支持直接写 el-table-column
tableHeader自定义表格头部左侧区域
column.prop单元格的插槽
column.prop + "Header"表头的插槽

六、代码实现 & 基础使用 💪(代码较多,详情请去项目里查看)

使用一段话总结下我的想法:📚📚

🤔 前提:首先我们在封装 ProTable 组件的时候,在不影响 el-table 原有的属性、事件、方法的前提下,然后在其基础上做二次封装,否则做得再好,也不太完美。

🧐 思路:把一个表格页面所有重复的功能 (表格多选、查询、重置、刷新、分页、数据操作二次确认、文件下载、文件上传) 都封装成 Hooks 函数钩子或组件,然后在 ProTable 组件中使用这些函数钩子或组件。在页面中使用的时,只需传给 ProTable 当前表格数据的请求 API、表格配置项 columns 就行了,数据传输都使用 作用域插槽 或 tsx 语法从 ProTable 传递给父组件就能在页面上获取到了。

1、常用 Hooks 函数

  • useTable:
import { Table } from "./interface";
import { reactive, computed, onMounted, toRefs } from "vue";/*** @description table 页面操作方法封装* @param {Function} api 获取表格数据 api 方法(必传)* @param {Object} initParam 获取数据初始化参数(非必传,默认为{})* @param {Boolean} isPageable 是否有分页(非必传,默认为true)* @param {Function} dataCallBack 对后台返回的数据进行处理的方法(非必传)* */
export const useTable = (api: (params: any) => Promise,initParam: object = {},isPageable: boolean = true,dataCallBack?: (data: any) => any
) => {const state = reactive({// 表格数据tableData: [],// 分页数据pageable: {// 当前页数pageNum: 1,// 每页显示条数pageSize: 10,// 总条数total: 0,},// 查询参数(只包括查询)searchParam: {},// 初始化默认的查询参数searchInitParam: {},// 总参数(包含分页和查询参数)totalParam: {},});/*** @description 分页查询参数(只包括分页和表格字段排序,其他排序方式可自行配置)* */const pageParam = computed({get: () => {return {pageNum: state.pageable.pageNum,pageSize: state.pageable.pageSize,};},set: (newVal: any) => {console.log("我是分页更新之后的值", newVal);},});// 初始化的时候需要做的事情就是 设置表单查询默认值 && 获取表格数据(reset函数的作用刚好是这两个功能)onMounted(() => {reset();});/*** @description 获取表格数据* @return void* */const getTableList = async () => {try {// 先把初始化参数和分页参数放到总参数里面Object.assign(state.totalParam,initParam,isPageable ? pageParam.value : {});let { data } = await api(state.totalParam);dataCallBack && (data = dataCallBack(data));state.tableData = isPageable ? data.datalist : data;// 解构后台返回的分页数据 (如果有分页更新分页信息)const { pageNum, pageSize, total } = data;isPageable && updatePageable({ pageNum, pageSize, total });} catch (error) {console.log(error);}};/*** @description 更新查询参数* @return void* */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 : {});};/*** @description 更新分页信息* @param {Object} resPageable 后台返回的分页数据* @return void* */const updatePageable = (resPageable: Table.Pageable) => {Object.assign(state.pageable, resPageable);};/*** @description 表格数据查询* @return void* */const search = () => {state.pageable.pageNum = 1;updatedTotalParam();getTableList();};/*** @description 表格数据重置* @return void* */const reset = () => {state.pageable.pageNum = 1;state.searchParam = {};// 重置搜索表单的时,如果有默认搜索参数,则重置默认的搜索参数Object.keys(state.searchInitParam).forEach((key) => {state.searchParam[key] = state.searchInitParam[key];});updatedTotalParam();getTableList();};/*** @description 每页条数改变* @param {Number} val 当前条数* @return void* */const handleSizeChange = (val: number) => {state.pageable.pageNum = 1;state.pageable.pageSize = val;getTableList();};/*** @description 当前页改变* @param {Number} val 当前页* @return void* */const handleCurrentChange = (val: number) => {state.pageable.pageNum = val;getTableList();};return {...toRefs(state),getTableList,search,reset,handleSizeChange,handleCurrentChange,};
};
复制代码
  • useSelection:
import { ref, computed } from "vue";/*** @description 表格多选数据操作* @param {String} selectId 当表格可以多选时,所指定的 id* @param {Any} tableRef 当表格 ref* */
export const useSelection = (selectId: string = "id") => {// 是否选中数据const isSelected = ref(false);// 选中的数据列表const selectedList = ref([]);// 当前选中的所有ids(数组),可根据项目自行配置id字段const selectedListIds = computed((): string[] => {let ids: string[] = [];selectedList.value.forEach(item => {ids.push(item[selectId]);});return ids;});// 获取行数据的 Key,用来优化 Table 的渲染;在使用跨页多选时,该属性是必填的const getRowKeys = (row: any) => {return row[selectId];};/*** @description 多选操作* @param {Array} rowArr 当前选择的所有数据* @return void*/const selectionChange = (rowArr: any) => {rowArr.length === 0 ? (isSelected.value = false) : (isSelected.value = true);selectedList.value = rowArr;};return {isSelected,selectedList,selectedListIds,selectionChange,getRowKeys};
};复制代码
  • useDownload:
import { ElNotification } from "element-plus";/*** @description 接收数据流生成blob,创建链接,下载文件* @param {Function} api 导出表格的api方法(必传)* @param {String} tempName 导出的文件名(必传)* @param {Object} params 导出的参数(默认为空对象)* @param {Boolean} isNotify 是否有导出消息提示(默认为 true)* @param {String} fileType 导出的文件格式(默认为.xlsx)* @return void* */
export const useDownload = async (api: (param: any) => Promise,tempName: string,params: any = {},isNotify: boolean = true,fileType: string = ".xlsx"
) => {if (isNotify) {ElNotification({title: "温馨提示",message: "如果数据庞大会导致下载缓慢哦,请您耐心等待!",type: "info",duration: 3000});}try {const res = await api(params);const blob = new Blob([res]);// 兼容 edge 不支持 createObjectURL 方法if ("msSaveOrOpenBlob" in navigator) return window.navigator.msSaveOrOpenBlob(blob, tempName + fileType);const blobUrl = window.URL.createObjectURL(blob);const exportFile = document.createElement("a");exportFile.style.display = "none";exportFile.download = `${tempName}${fileType}`;exportFile.href = blobUrl;document.body.appendChild(exportFile);exportFile.click();// 去除下载对 url 的影响document.body.removeChild(exportFile);window.URL.revokeObjectURL(blobUrl);} catch (error) {console.log(error);}
};复制代码
  • useHandleData:
import { ElMessageBox, ElMessage } from "element-plus";
import { HandleData } from "./interface";/*** @description 操作单条数据信息(二次确认【删除、禁用、启用、重置密码】)* @param {Function} api 操作数据接口的api方法(必传)* @param {Object} params 携带的操作数据参数 {id,params}(必传)* @param {String} message 提示信息(必传)* @param {String} confirmType icon类型(不必传,默认为 warning)* @return Promise*/
export const useHandleData = 

(api: (params: P) => Promise,params: Parameters[0],message: string,confirmType: HandleData.MessageType = "warning" ) => {return new Promise((resolve, reject) => {ElMessageBox.confirm(`是否${message}?`, "温馨提示", {confirmButtonText: "确定",cancelButtonText: "取消",type: confirmType,draggable: true}).then(async () => {const res = await api(params);if (!res) return reject(false);ElMessage({type: "success",message: `${message}成功!`});resolve(true);});}); }; 复制代码

2、Protable 组件:

  • ProTable:
复制代码
  • TableColumn:
复制代码

3、页面使用 ProTable 组件:


复制代码

七、贡献者 👨‍👦‍👦

  • HalseySpicy
  • denganjia

相关内容

热门资讯

推荐几个小本创业开店项目 推荐... top2小本开店有什么好项目?必赚40项经验小本开店有什么好项目?不想有过多的投资,还想有好的收入,...
小本推荐几个小本创业开店项目创... 上海科镭的答复:1.摊贩型对于摊贩我们绝对不会陌生,这种方式出现在人群聚集的地方,如夜市、风景区、车...
小本创业开店的项目 小本创业开... 创业不仅有助于提高社会生产率,提供充分的就业机会,还对新理论观点的形成和建立起着决定性作用,而后者对...
不起眼的4个创业项目 虽然不起... 创业项目指创业者为了达到商业目的具体实施和操作的工作。创业项目分类很广,按照行业来分可以分为餐饮、服...
四个创业好项目 推荐四个 四个... 据了解:目前有个行业可以把手机里的照片做成书册,像我们平时看的杂志一样,这种书可以保存五十年以上,平...
2020年,四个简单小本创业好... 当下的人们生活中对个性化的一些物品越来越喜欢,尤其是生活中日常值得留念的照片存的太多了没处保存,时间...
有最新小本创业好项目没有靠谱的... 加盟连锁便利店小超市经营也不需要多大的资金和管理经验,而且是一个不错的创业项目,不少创业者都有这个想...
小本创业做什么项目好 小本创业... 7/9网络小礼品个性化小礼品这种风靡欧美的个人图片保存新模式,已经从欧美展开,于去年9月份,个性化小...
宝妈励志创业!2018适合女性... 女性创业项目推荐:3.特色茶吧特色茶吧主要销售鲜花茶,兼售水果茶,这在国内的一些大中城市已比较流行。...
推荐几个女孩创业低成本的小本项... 女孩子开什么店成本低?如今开店项目一抓一大把,满足了人们的各种消费需求。开店是很多人创业的首选,尤其...