基于ElementPlus的table组件封装

前言

我们在使用UI库编写页面的时候,特别是账务系统,需要用到表格的情况会比较多,如果我们每次都是复制一遍UI库中的demo然后进行调整,这样造成的结果是多次引入 Table 组件,而且从前端开发规范来讲,不符合组件化的初衷。因此我们将 Table 组件进行二次封装,无疑是最好的选择。二次封装 Table 组件就是为了增强组件的可复用性、可维护性和功能性。

二次封装的优势
  1. 统一风格和功能
    • 样式一致性:项目中可能有多个地方使用表格,二次封装可以确保所有表格在样式和功能上保持一致。
  2. 简化使用
    • 简化 API:通过封装,可以提供更简单、更直观的 API,减少开发者在使用时需要考虑的细节。例如,可以通过 props 传递配置选项,而不必每次都重复编写配置。
    • 封装复杂逻辑:将复杂的逻辑(例如数据处理、列渲染)封装到一个组件中,调用方只需使用这个封装的组件即可,降低了使用难度。
  3. 增强功能
    • 插槽:通过插槽,可以灵活地扩展表格的功能,例如自定义列内容、操作按钮等,提升组件的灵活性。
    • 事件处理:可以统一处理表格中的各种事件,如行点击、行选中等,提供更一致的事件响应方式。
  4. 提高可维护性
    • 集中管理:将表格的所有相关逻辑集中到一个地方,使得后续的维护和修改更加简单。如果需要更改某个功能,只需在封装的组件中进行修改,而不必在每个使用表格的地方重复修改。
    • 易于调试:封装后的组件可以更容易进行单元测试和调试,确保表格的各项功能在不同场景下都能正常工作。
  5. 提高开发效率
    • 复用性:封装后的组件可以在不同的项目中复用,节省开发时间。通过封装常用的表格功能,可以减少重复劳动。
    • 快速迭代:通过提供易于使用的组件,团队成员可以快速上手并使用,而不必深入了解底层实现。

我使用的技术栈是Vue3+Ts+Element Plus,所以此次table组件的二次封装也是基于Element Plus的,其中Element Plus版本选的是2.8.0

前提条件

默认大家已经能够正确地创建Vue3+Ts的前端工程,Element Plus的安装可以参考https://element-plus.org/zh-CN/guide/installation.html,还需要大家在提前注册Element Plus所有的图标。代码如下:

// main.tsimport * as ElementPlusIconsVue from '@element-plus/icons-vue';// 注册elementPlus所有的图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {app.component(key, component)
}
封装代码源码
// c-table/components/table-column-item/index.vue<template><el-table-columnv-if="!column.children && column.type !== 'selection' && column.type !== 'expand'":prop="column.prop":label="column.label":width="column.width":show-overflow-tooltip="column.showOverflowTooltip":fixed="column.fixed":min-width="column.minWidth":align="column.align":type="column.type":resizable="column.resizable":sortable="column.sortable":selectable="column.selectable":column-key="column.columnKey":filters="column.filters":filter-method="column.filterMethod":sort-method="column.sortMethod":sort-by="column.sortBy":sort-orders="column.sortOrders":header-align="column.headerAlign":class-name="column.className":label-class-name="column.labelClassName":reserve-selection="column.reserveSelection":filter-placement="column.filterPlacement":filter-class-name="column.filterClassName":filter-multiple="column.filterMultiple":filter-value="column.filterValue"><template #default="{ row, $index }"><template v-if="column.formatter"><template v-if="isStringFormatter(row, $index)">{{column.formatter(row, column, row[column.prop], $index)}}</template><template v-else><component:is="column.formatter(row,column,row[column.prop],$index)"/></template></template><template v-if="column.index && !column.formatter">{{ column.index($index) }}</template><component:is="column.slots.default":row="row":index="$index"v-if="column.slots && column.slots.default"/></template><template #header="scope" v-if="column.slots && column.slots.header"><component:is="column.slots.header":row="scope.row":index="scope.$index"/></template></el-table-column><!-- 多级表头 --><el-table-columnv-if="column.children && column.type !== 'expand'":prop="column.prop":label="column.label":align="column.align":type="column.type":resizable="column.resizable"><template v-for="child in column.children" :key="child.prop"><table-column-item :column="child" /></template></el-table-column><!-- 多选 --><el-table-columnv-if="column.type == 'selection'"type="selection":width="column.width":selectable="column.selectable"/><!-- 展开行 --><el-table-column v-if="column.type == 'expand'" type="expand"><template #default="{ row, $index }"><component:is="column.slots.default":row="row":index="$index"/></template></el-table-column>
</template><script lang="ts">
import { PropType } from "vue";
import { ColumnItem } from "../../../../type";
import { ElTable, ElTableColumn } from "element-plus";export default {name: "TableColumnItem",props: {column: {type: Object as PropType<ColumnItem>,default: {},},},components: {ElTableColumn,},setup(props, context) {const isStringFormatter = (row: any, index: number): boolean => {const formattedValue = props.column.formatter(row,props.column,row[props.column.prop],index);return typeof formattedValue === "string";};return {isStringFormatter,};},
};
</script>
// c-table/index.vue<template><el-table v-bind="$attrs" :data="tableData" ref="elTableRef"><TableColumnItemv-for="column in columns":key="column.prop":column="column"></TableColumnItem><!-- 自定义空数据时的内容 --><template v-slot:empty><slot name="empty"></slot></template></el-table>
</template><script lang="ts">
import {computed,ref,reactive,onMounted,onBeforeUnmount,watch,toRefs,PropType,
} from "vue";
import { TableData, ColumnItem } from "../../type";
import TableColumnItem from "./components/table-column-item/index.vue";
import { ElButton, ElIcon, ElTable } from "element-plus";export default {name: "CTable",props: {tableData: {type: Array as PropType<TableData[]>,default: [],},columns: {type: Array as PropType<ColumnItem[]>,default: [],},},components: {TableColumnItem,},setup(props, ctx) {const elTableRef = ref<InstanceType<typeof ElTable>>();return {elTableRef,};},
};
</script>
// type.tsimport { VNode } from 'vue';
import type { TableColumnCtx } from 'element-plus'export interface TableData {name?: string,date?: string,address?: string,result?: number,amount?: number,state?: string,city?: string,zip?: string,hasChildren?: boolean,propsData?: TableData[]
}export interface ColumnItem {prop?: string,label: string,width?: number | string,minWidth?: number,align?: string,type?: string,sortable?: boolean | string,resizable?: boolean,columnKey?: string,selectable?: () => boolean,index?: (index: number) => number,filterMethod?: (value: any, row: any, column: any) => void,formatter?: (row: any, column: any, cellValue: any, index: number) => VNode | string,sortMethod?: (a: TableData, b: TableData) => number,slots?: {default?: (a: SlotsItem) => VNode;header?: (a: SlotsItem) => VNode;};showOverflowTooltip?: boolean,fixed?: boolean | 'left' | 'right',children?: ColumnItem[],filters?: FilterItem[],sortBy?: (row: any, index: number) => string | string | string[],sortOrders?: ('ascending' | 'descending' | null)[],headerAlign?: 'left' | 'center' | 'right',filterPlacement?: 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end' | 'right' | 'right-start' | 'right-end',className?: string,labelClassName?: string,filterClassName?: string,reserveSelection?: boolean,filterMultiple?: boolean,filterValue?: string[],
}export interface SlotsItem {index: number,row: TableData,
}export interface FilterItem {text: string,value: string,
}export interface SummaryMethodProps<T = TableData> {columns: TableColumnCtx<T>[]data: T[]
}export interface SpanMethodProps {row: TableDatacolumn: TableColumnCtx<TableData>rowIndex: numbercolumnIndex: number
}
使用
<template><div class="table-list"><!-- lazy:load="load":tree-props="{ children: 'children', hasChildren: 'hasChildren' }" --><c-Table:span-method="arraySpanMethod"show-summary:summary-method="getSummaries":default-expand-all="false":row-class-name="tableRowClassName"ref="tableRef":data="tableData":columns="columns"borderstripestyle="width: 100%"height="500"row-key="name"highlight-current-row@current-change="handleCurrentChange"table-layout="auto"><template v-slot:empty><div style="text-align: center; padding: 20px; color: #999"><p>没有数据可显示</p><el-button type="primary">添加数据</el-button></div></template></c-Table><div style="margin-top: 20px"><el-button @click="setCurrent(tableData[1])">选择第二行</el-button><el-button @click="setCurrent()">清除选中行</el-button><el-button@click="toggleSelection([tableData[1], tableData[2]], false)">勾选目标行</el-button><el-button @click="toggleSelection()">清除勾选数据</el-button></div></div>
</template><script lang="ts">
import { ref, defineComponent, reactive, toRefs, h, VNode } from "vue";
import CTable from "./components/c-table/index.vue";
import {TableData,SlotsItem,ColumnItem,SummaryMethodProps,SpanMethodProps,
} from "./type";
import { ElButton, ElIcon, ElInput } from "element-plus";
import type { TableColumnCtx } from "element-plus";
import { Check, Close } from "@element-plus/icons-vue";export default defineComponent({name: "tableList",props: {},components: {CTable,},setup() {const search = ref("");const searchValueChange = (val: any) => {console.log("searchValueChange", val);};const handleInputChange = (val: any) => {console.log("handleInputChange", val);};const filterHandler = (value: string,row: TableData,column: TableColumnCtx<TableData>) => {const property = column.property as keyof TableData;return row[property] === value;};const subTableRef = ref();const tableInfo = reactive({tableData: [{date: "2016-05-03",name: "Alice",address: "No. 189, Grove St, Los Angeles1",state: "California",city: "Los Angeles",zip: "CA 90036",result: 0,amount: 12,// hasChildren: true,// propsData: [// 	{// 		name: "Jerry",// 		state: "California",// 		city: "San Francisco",// 		address: "3650 21st St, San Francisco",// 		zip: "CA 94114",// 	},// ],},{date: "2016-05-02",name: "Tom",address: "No. 189, Grove St, Los Angeles2",state: "California",city: "Los Angeles",zip: "CA 90036",result: 1,amount: 19,},{date: "2016-05-01",name: "Bob",address: "No. 189, Grove St, Los Angeles12",state: "California",city: "Los Angeles",zip: "CA 90038",result: 0,amount: 120,},] as TableData[],subColumn: [{prop: "name",label: "name",width: 120,},{prop: "state",label: "state",width: 120,},{prop: "city",label: "city",width: 120,},{prop: "address",label: "address",width: 120,},{prop: "zip",label: "zip",},] as ColumnItem[],columns: [{label: "索引",width: 60,fixed: true,type: "index",formatter: (row: any,column: any,cellValue: any,index: number): string => {return String(index * 2);},},{type: "selection",width: 100,selectable: (val: TableData) => {return true;},reserveSelection: true,},{type: "expand",slots: {default: ({ index, row }: SlotsItem): VNode => {return h(CTable, {ref: subTableRef,data: row.propsData,columns: tableInfo.subColumn,border: true,stripe: true,style: { width: "100%" },height: "auto","row-key": "name","highlight-current-row": true,"table-layout": "auto",});},},},{prop: "date",label: "时间",width: 140,sortable: true,columnKey: "date",filterMethod: filterHandler,filterValue: ["2016-05-01", "2016-05-02"],filters: [{ text: "2016-05-01", value: "2016-05-01" },{ text: "2016-05-02", value: "2016-05-02" },{ text: "2016-05-03", value: "2016-05-03" },{ text: "2016-05-04", value: "2016-05-04" },],},{prop: "name",label: "姓名",width: 240,formatter: (row: any,column: any,cellValue: any,index: number): VNode | string => {if (!cellValue) return h("span", "");return h("span",{ style: { color: "red" } },cellValue);},sortable: true,sortMethod: (a, b) => {return a.name.localeCompare(b.name);},sortOrders: ["ascending", "descending"],align: "center",className: "custom-column",labelClassName: "custom-labelClassName",},{prop: "address",label: "地址",width: 120,// renderHeader: (data) => {//     console.log('-----', data);//     return '1'// } -> 建议通过插槽的方式实现},{label: "第一级表头",align: "center",resizable: true,children: [{label: "第二级表头",align: "center",resizable: true,children: [{prop: "state",label: "州",width: 320,resizable: true,},{prop: "city",label: "城市",width: 400,resizable: true,},],},{prop: "zip",label: "Zip",resizable: true,width: 280,},],},{prop: "amount",label: "数量",width: 120,},{prop: "result",label: "校验结果",align: "center",width: 120,slots: {default: ({ row }: SlotsItem): VNode => {return h(ElIcon,{id: "result-i",size: 14,color: row.result == 1 ? "red" : "green",},{default: () =>row.result == 0 ? h(Check) : h(Close),});},},},{label: "操作",fixed: "right",minWidth: 180,slots: {default: (val: SlotsItem): VNode => {return h(ElButton,{type: "primary",onClick: (event) => {event.stopPropagation(); // 阻止事件冒泡checkF(val.row);},},() => "查看");},header: (val: SlotsItem): VNode => {return h(ElInput, {size: "small",placeholder: "请输入",modelValue: search.value,"onUpdate:modelValue": (value) => {search.value = value; // 更新 search},onChange: (value) => {searchValueChange(value); // 处理 change 事件,失去焦点才会触发},onInput: (value) => {handleInputChange(value); // 处理 input 事件},disabled: false,});},},},] as ColumnItem[],});const tableRef = ref();const currentRow = ref();const load = (row: TableData,treeNode: unknown,resolve: (data: TableData[]) => void) => {setTimeout(() => {resolve([{date: "2016-05-01",name: "wangxiaohu",address: "No. 189, Grove St, Los Angeles",},{date: "2016-05-01",name: "wangxiaohu",address: "No. 189, Grove St, Los Angeles",},]);}, 1000);};const getSummaries = (param: SummaryMethodProps) => {const { columns, data } = param;const sums: (string | VNode)[] = [];columns.forEach((column, index) => {if (index === 0) {sums[index] = h("div",{ style: { textDecoration: "underline" } },["Total Cost"]);return;}const values = data.map((item: any) =>Number(item[column.property]));if (!values.every((value) => Number.isNaN(value))) {sums[index] = `$ ${values.reduce((prev, curr) => {const value = Number(curr);if (!Number.isNaN(value)) {return prev + curr;} else {return prev;}}, 0)}`;} else {sums[index] = "N/A";}});return sums;};const tableRowClassName = ({rowIndex,}: {rowIndex: number;}): string => {if (rowIndex === 1) {return "warning-row";} else if (rowIndex === 3) {return "success-row";}return "";};const checkF = (val: TableData) => {console.log("checkF", val, tableRef.value);};const handleCurrentChange = (val: TableData | undefined) => {currentRow.value = val;};const setCurrent = (row?: TableData) => {tableRef.value.elTableRef!.setCurrentRow(row);};const toggleSelection = (rows?: TableData[],ignoreSelectable?: boolean) => {if (rows) {rows.forEach((row) => {tableRef.value.elTableRef!.toggleRowSelection(row,undefined,ignoreSelectable);});} else {tableRef.value.elTableRef!.clearSelection();}};const arraySpanMethod = ({row,column,rowIndex,columnIndex,}: SpanMethodProps) => {if (rowIndex % 2 === 0) {if (columnIndex === 0) {return [1, 2];} else if (columnIndex === 1) {return [0, 0];}}};return {...toRefs(tableInfo),tableRowClassName,checkF,tableRef,handleCurrentChange,currentRow,setCurrent,toggleSelection,getSummaries,arraySpanMethod,load,};},
});
</script><style lang="scss" scoped>
.table-list {height: 100%;width: 100%;padding: 12px;::v-deep .el-table {.warning-row .el-table__cell {background-color: #fdf6ec;}.success-row .el-table__cell {background-color: #f0f9eb;}.el-button:focus {outline: none;border: none;}}
}
</style>

[!CAUTION]

Table 暴露出来的函数,需要通过tableRef.value.elTableRef来访问

总结

此次封装是针对Table组件的二次封装,并没有将分页、筛选等功能放在一起,后续会继续更新,完善组件封装的完整性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/57253.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android14 SystemUI 启动流程(1)

1.从SystemServer启动 private void run() {......// Start services.try {t.traceBegin("StartServices");startBootstrapServices(t);startCoreServices(t);startOtherServices(t);//从这里走startApexServices(t);// Only update the timeout after starting all …

红队老子养成记3 - 学会反弹shell的多种姿势,拿shell拿手软!!(全网最多姿势!)

大家好&#xff0c;我是Dest1ny! 今天还是讲redteam里比较重要的反弹shell&#xff01; 不会反弹shell&#xff0c;那你如何拿控制权限&#xff01; 今天满满干货&#xff0c;大家加油学&#xff01; CLASS-1 正向连接与反向连接详解 1. 正向连接 正向连接是最常见的连接方…

单神经元建模:基于电导的模型[神经元结构、静息电位和等效电路]

文章目录 神经元结构、静息电位和等效电路神经元结构静息电位能斯特方程1. **描述浓度比的非线性关系**&#xff1a;2. **化学势与电势的关系**&#xff1a;3. **对称性**&#xff1a;4. **热力学与平衡**&#xff1a;总结&#xff1a; GHK方程Nernst方程和GHK方程的对比 等效电…

ChatGLM-6B大模型 + Bert预训练模型 + RAG实现知识库信息抽取(含完整代码)

‌ 目录 RAG技术 知识库准备 检索相关文本 生成问答 完整代码 本文将基于ChatGLM-6B大模型、Bert预训练模型和RAG完整金融知识库信息抽取任务。 RAG技术 RAG技术,即检索增强生成(Retrieval-Augmented Generation),是一种结合了检索和生成技术的模型。‌ 它通…

《仓库猎手模拟》风灵月影游戏辅助使用教程

《仓库猎手模拟》是一款休闲独立的模拟经营佳作&#xff0c;让玩家沉浸于经济管理的乐趣中&#xff0c;亲手利用工具探索仓库的每个角落&#xff0c;发掘并鉴定珍稀物品。借助修改器&#xff0c;玩家能更轻松地享受游戏过程&#xff0c;体验寻宝与经营的双重乐趣。 修改器安装&…

【C语言】文件操作(2)(文件缓冲区和随机读取函数)

文章目录 一、文件的随机读取函数1.fseek函数2.ftell函数3.rewind函数 二、文件读取结束的判断1.被错误使用的feof2.判断文件读取结束的方法3.判断文件结束的原因feofferror判断文件读取结束原因示例 三、文件缓冲区 一、文件的随机读取函数 在上一篇的文章中&#xff0c;我们讲…

Android10 recent键相关总结

目录 初始化流程 点击Recent键流程 RecentsActivity 显示流程 RecentsModel 获取数据管理类 RecentsActivity 布局 已处于Recent界面时 点击recent 空白区域 点击返回键 recent组件配置 Android10 Recent 功能由 System UI&#xff0c;Launcher共同实现。 初始化流程 …

如何克隆Git仓库的子目录:稀疏检出

一、环境 Git 2.34.1 二、前言 一般来说&#xff0c;我们在克隆git仓库的时候&#xff0c;都是一整个仓库都克隆出来的。如果假设现在有一个很大的仓库&#xff0c;仓库里有多个子项目&#xff0c;而我们只想克隆其中一个子项目的时候&#xff0c;应该怎么做呢&#xff1f; …

洛谷刷题 P1008 [NOIP1998 普及组] 三连击

题目链接&#xff1a;P1008 [NOIP1998 普及组] 三连击 思路 此题主要采取枚举的思想&#xff0c;但是直接暴力枚举会导致时间不够&#xff0c;因此我们先进行分析 因为要求三个三位数构成的比例是1&#xff1a;2&#xff1a;3&#xff0c;因此最小的那个数的百位只能在1、2、…

【Java后端】之 ThreadLocal 详解

想象一下&#xff0c;你有一个工具箱&#xff0c;里面放着各种工具。在多人共用这个工具箱的时候&#xff0c;很容易出现混乱&#xff0c;比如有人拿走了你的锤子&#xff0c;或者你找不到合适的螺丝刀。为了避免这种情况&#xff0c;最好的办法就是每个人都有自己独立的工具箱…

初识适配器模式

适配器模式 引入 生活中的例子&#xff1a;当我们使用手机充电时&#xff0c;充电器起到了转换器的作用&#xff0c;它将家用的220伏特电压转换成适合手机充电的5伏特电压。 适配器模式的三种类型 命名原则&#xff1a;适配器的命名应基于资源如何传递给适配器来进行。 类适配…

实验室信息化转型要注意哪些

在当今科技飞速发展的时代&#xff0c;实验室信息化转型已成为提升实验室效能和竞争力的关键举措。然而&#xff0c;这一转型过程并非一帆风顺&#xff0c;需要谨慎对待并注意以下几个重要方面。 一、明确需求与目标 在开启信息化转型之前&#xff0c;你必须深入了解实验室的…

第14篇:下一代网络与新兴技术

目录 引言 14.1 下一代网络&#xff08;NGN&#xff09;的定义与特点 14.2 IPv6协议的改进与未来应用 14.3 软件定义网络&#xff08;SDN&#xff09; 14.4 网络功能虚拟化&#xff08;NFV&#xff09; 14.5 量子通信网络 14.6 软件定义广域网&#xff08;SD-WAN&#x…

xlsx xlsx-style-vite 实现前端根据element 表格导出excel且定制化样式 背景 列宽等

前言 先看下最终效果图吧&#xff0c;需要的可以参考我的实现方式 这是最终导出的表格文件 类似这种的&#xff0c;特定单元格需要额外标注&#xff0c;表头也有月份然后细分的&#xff0c;表格组件是这样的 注意 别使用xlsx-style 这个库&#xff0c;太多问题了&#xff0c;…

【C语言刷力扣】1768.交替合并字符串

题目&#xff1a; 解题思路&#xff1a; 将 word1 和 word2 元素依次添加至 ans 的后面。 时间复杂度&#xff1a; &#xff0c; n是word1的长度 m是word2的长度 空间复杂度&#xff1a; char* mergeAlternately(char* word1, char* word2) {int len1 strlen(word1);in…

供电电压和逻辑电压

供电电压和逻辑电压是电子电路中两个重要的电压概念&#xff0c;它们有不同的定义和应用。以下是它们之间的主要区别&#xff1a; 1. 定义 供电电压&#xff08;Power Supply Voltage&#xff09;&#xff1a; 供电电压是用于给电路或设备提供电能的电压值。它是电源输出的电压…

【Linux】top命令查看CPU、内存使用率、解释

1. top 命令 top 是最常用的实时监控工具之一&#xff0c;可以显示 CPU 的总利用率以及各个进程的 CPU 使用情况。在Linux命令行直接输入top即可查看动态原始数据 top 在 top 命令的输出中&#xff0c;最上面的一行会显示 CPU 的使用情况&#xff1a; us&#xff08;User&a…

写了十几年程序,今天才第一天知道什么是屎山代码

可以说&#xff0c;我确实没在工作中用过Javascript&#xff0c;因为我从未见过如此“厚颜无耻”的代码 我曾经也是学过2~3年&#xff0c;还是JQuery的年代&#xff0c;但应该确实没在实战中用过&#xff0c;否则我怎么会不记得写过这些屎山代码的&#xff1f;&#xff1f;&…

图片怎么转文字?11种好用的方法!

如何快速将图片的文字提取出来&#xff0c;可以大量节省手打的时间&#xff0c;无论是截图&#xff0c;或者批量提取照片文字&#xff0c;都经常需要这个操作&#xff01; 作为一名社畜&#xff0c;俺也经常用到各种图片转文字工具&#xff0c;今天通过测评12个主流的图片转文…

React Native 项目中使用 Expo Application Services (EAS) 进行多渠道打包

在 React Native 项目中使用 Expo Application Services (EAS) 进行多渠道打包&#xff0c;你可以利用 EAS Build 来构建你的应用。以下是一些关键步骤和配置&#xff1a; 安装 EAS CLI&#xff1a; 首先&#xff0c;你需要安装 EAS CLI 工具&#xff0c;以便在本地使用 EAS 的…