vue3 + ts + element-plus 二次封装 el-table

一、实现效果:

(1)数据为空时:

(2)有数据时:存在数据合并;可自定义表头和列的内容

(3)新增行:

(4)删除行:

(5)双击编辑单元格内容:

二、组件代码:

(1)components / CustomTable / index.vue

<template><div class="custom-table-dialog"><el-table :data="paginatedData" border :span-method="spanMethod"highlight-current-row @row-click="rowClick" @cell-dblclick="cellDbClick"@selection-change="handleSelection" :header-row-style="props?.headerRowStyle":header-cell-style="props?.headerCellStyle":row-style="props?.rowStyle" :cell-style="props?.cellStyle" :empty-text="props.emptyText"><!-- 多选 --><el-table-column v-if="isSelection" type="selection" width="55" align="center"></el-table-column><template v-for="column in columnList" :key="column.label"><custom-table-column :column="column"><template v-slot:[`header-${column.prop}`]="scope"><slot :name="`header-${column.prop}`" v-bind="scope"></slot></template><template v-slot:[`default-${column.prop}`]="scope"><slot :name="`default-${column.prop}`" v-bind="scope"/></template></custom-table-column></template></el-table><el-paginationsize="small":hide-on-single-page="true"backgroundlayout="prev, pager, next":total="tableData.length":current-page="currentPage":page-size="pageSize"@current-change="handlePageChange"/></div></template><script setup lang="ts">import CustomTableColumn from "@/components/CustomTable/components/CustomTableColumn.vue";
import {computed, ref} from "vue";
import type {ColumnItem} from "@/types/table";
import EventBus from "@/plugins/event-bus";const props = defineProps<{tableData: any[] // 表格数据columnList: ColumnItem[] // 表头数据pageSize: number // 每页显示条数selection?: boolean // 是否多选merge?: boolean // 是否合并mergeColumns?: string[] // 哪些列中的单元格需要合并distinguishColumns?: string[] // 哪些列中单元格的值不一样,就不合并headerRowStyle?: any // 表头行样式headerCellStyle?: any // 表头单元格样式rowStyle?: any // 行样式cellStyle?: any // 单元格样式emptyText?: string // 空数据时显示的文本内容
}>()const currentPage = ref(1)
const pageSize = ref(props.pageSize)const paginatedData = computed(() => { // 分页数据const start = (currentPage.value - 1) * pageSize.valueconst end = start + pageSize.valuereturn props.tableData.slice(start, end)
})const isSelection = computed(() => { // 是否多选(勾选行)let selection = falseif (props.selection) {selection = props.selection}return selection
})const colFields = computed(() => { // 所有列的 propslet fields: string[] = []const properties = props.columnList.map((item: any) => {return item.prop})if (props.selection) {fields.push('')fields.push(...properties)} else {fields = properties}return fields
})
let spanArr: any[] = [] //存储合并单元格的开始位置const handlePageChange = (page: number) => {currentPage.value = page
}/* 行点击事件 */
const rowClick = (row: any, column: any) => {EventBus.emit('row-click', {row, column})
}/* 单元格双击事件 */
const cellDbClick = (row: any, column: any) => {EventBus.emit('cell-dbClick', {row, column})
}/* 行选择事件 */
const handleSelection = (data: any) => { // data 为所有处于勾选状态的行的数组EventBus.emit('row-selection', data)
}// 分析每一列,找出相同的
const getSpanArr = () => {let mergeColumns: string[] = []let distinguishColumns: string[] = []if (props.mergeColumns) {mergeColumns = props.mergeColumns}if (props.distinguishColumns) {distinguishColumns = props.distinguishColumns}for (let i = 0; i < paginatedData.value.length; i++) {let row = i;// let col = i % this.colCount;if (row === 0) {// i 表示行 j表示列for (let j = 0; j < colFields.value.length; j++) {spanArr[i * colFields.value.length + j] = {rowspan: 1,colspan: 1,}}} else {for (let j = 0; j < colFields.value.length; j++) {// 当前和上一次的一样//  合并所有列的相同数据单元格if (mergeColumns.includes(colFields.value[j])) { // Check if the column is in mergeColumnsif (distinguishColumns.some(col => paginatedData.value[row][col] !== paginatedData.value[row - 1][col])) {// If any distinguish column is different, do not mergespanArr[row * colFields.value.length + j] = {rowspan: 1,colspan: 1,}} else if (paginatedData.value[row][colFields.value[j]] === paginatedData.value[row - 1][colFields.value[j]]) {let beforeItem =spanArr[(row - 1) * colFields.value.length + j]spanArr[row * colFields.value.length + j] = {rowspan: 1 + beforeItem.rowspan, // Merge rowscolspan: 1, // Merge columns}beforeItem.rowspan = 0beforeItem.colspan = 0} else {// No mergespanArr[row * colFields.value.length + j] = {rowspan: 1,colspan: 1,}}}}}}// 对数据进行倒序let stack = []for (let i = 0; i < colFields.value.length; i++) {for (let j = 0; j < paginatedData.value.length; j++) {// console.log("i=" + i + " j=" + j);// i 表示列 j表示行if (j === 0) {if (spanArr[j * colFields.value.length + i].rowspan === 0) {stack.push(spanArr[j * colFields.value.length + i])}} else {if (spanArr[j * colFields.value.length + i]?.rowspan === 0) {stack.push(spanArr[j * colFields.value.length + i])} else {stack.push(spanArr[j * colFields.value.length + i])while (stack.length > 0) {let pop = stack.pop()let len = stack.lengthspanArr[(j - len) * colFields.value.length + i] = pop}}}}}
}const spanMethod = (data: { row: any, column: any, rowIndex: any, columnIndex: any }) => {if (props.merge) {getSpanArr()return spanArr[data.rowIndex * colFields.value.length + data.columnIndex]} else {return}
}/*// 合并固定列
const spanMethod = (data: { row: any, column: any, rowIndex: any, columnIndex: any }) => {if (data.column.property === 'column1') { // 指定列相邻单元格的值相等就合并const currentValue = data.row[data.column.property] // 当前单元格的值const preRow = props.tableData[data.rowIndex - 1] // 上一行const preValue = preRow ? preRow[data.column.property] : null // 上一行相同列的值if (currentValue === preValue) { // 将当前单元格隐藏return {rowspan: 0,colspan: 0}} else { // 合并 —— 计算当前单元格应该跨多少行let rowSpan = 1 // 初始跨 1 行for (let i = data.rowIndex + 1; i < props.tableData.length; i++) {const nextRow = props.tableData[i] // 下一行const nextValue = nextRow ? nextRow[data.column.property] : null // 下一行相同列的值if (nextValue === currentValue) {rowSpan++} else {break}}return {rowspan: rowSpan,colspan: 1}}}
}*/</script><style lang="scss">.custom-table-dialog {//height: 100%;height: fit-content;display: flex;flex-direction: column;align-items: center;justify-content: center;//border: 1px solid red;.el-table {--el-table-border-color: rgba(218, 226, 237); // 边框颜色--el-table-bg-color: transparent;--el-table-text-color: rgba(165, 176, 193); // 表格数据部分文字颜色--el-table-header-text-color: rgba(251, 251, 251); // 表头文字颜色--el-table-row-hover-bg-color: rgba(252, 250, 255); // 鼠标悬浮时表行的背景色--el-table-header-bg-color: rgba(201, 213, 229); // 表头背景色--el-table-tr-bg-color: rgba(255, 255, 255); // 表格数据部分表行背景色background-color: transparent;.el-table__body tr.current-row > td.el-table__cell { // 高亮行单元格的背景色background: rgba(252, 250, 255);}.el-table__empty-block { // 数据为空时 表格 body 的样式background-color: #FFFFFF;}}.el-checkbox__input.is-checked .el-checkbox__inner {background-color: rgba(192, 172, 233);border-color: rgba(192, 172, 233);}.el-checkbox__input.is-indeterminate .el-checkbox__inner {background-color: rgba(192, 172, 233);border-color: rgba(192, 172, 233);}.el-checkbox__inner:hover {border-color: rgba(192, 172, 233);}//:deep(.custom-table-dialog .el-pagination) {//  //margin-top: 100px;//  position: absolute;//  left: 50%;//  transform: translateX(-50%);//  bottom: 20px;//}.el-pagination { /* 如要固定分页器的位置在容器底部,在父组件中使用:deep(.custom-table-dialog .el-pagination) {}修改,示例如上 */margin-top: 10px;}.el-pagination.is-background .btn-next, .el-pagination.is-background .btn-prev, .el-pagination.is-background .el-pager li {background-color: rgba(201, 213, 229);color: rgba(251, 251, 251);}.el-pagination.is-background .btn-next.is-active, .el-pagination.is-background .btn-prev.is-active, .el-pagination.is-background .el-pager li.is-active {background: rgba(192, 172, 233);}}</style>

(2)components / CustomTable / components / CustomTableColumn.vue

<template><el-table-column v-if="column.children?.length" v-bind="getColumnProps(column)"><template #header="scope"><slot :name="`header-${column.prop}`" v-bind="scope"><span>{{ column.label }}</span></slot></template><template v-for="child in column.children" :key="child.label"><custom-table-column :column="child"/></template></el-table-column><el-table-column v-else v-bind="getColumnProps(column)"><template #header="scope"><slot :name="`header-${column.prop}`" v-bind="scope"><span>{{ column.label }}</span></slot></template><template #default="scope"><slot :name="`default-${column.prop}`" v-bind="scope"><span>{{ scope.row[column.prop] }}</span></slot></template></el-table-column></template><script setup lang="ts">import type {ColumnItem} from "@/types/table";defineProps<{column: ColumnItem
}>()const getColumnProps = (column: ColumnItem) => {const {children, ...props} = columnreturn props
}</script><style scoped lang="scss"></style>

(3)types / table.ts

/*
* 表列属性【 ?. 代表非必传,否则必传】
* */
export interface ColumnItem {label: string,prop: string,children?: ColumnItem[],align?: string, // 对齐方式width?: string | number, // 宽度sortable?: boolean | string, // 对应列是否可以排序,如果设置为 'custom',则代表用户希望远程排序,需要监听 Table 的 sort-change 事件/* 对象可以具有任意数量的键,这些键的类型为 string,对应的值的类型为 any */[key: string]: any
}export enum TableSize {Large = 'large',Default = 'default',Small = 'small',
}

(4) plugins / event-bus.ts

import mitt from 'mitt' // 首先 npm install mitt
const EventBus = mitt()
export default EventBus

三、使用代码:

MyTable.vue

<template><!-- 二次封装 el-table --><div class="custom-table-container"><div class="table-header"><div>title</div><div class="table-buttons"><div class="table-button" @click="addRow"><el-icon><Plus/></el-icon></div><div class="table-button" @click="deleteRows"><el-icon><Delete/></el-icon></div></div></div><custom-table :column-list="columnList" :page-size="15" :table-data="tableData" :selection="true" :merge="true":merge-columns="mergeColumns" :distinguish-columns="distinguishColumns" empty-text="Please set Data"><!-- 自定义表头单元格内容示例 --><template #header-id="{row, column}">{{ column.label }} Here</template><!-- 自定义列内容示例 --><template #default-column1="{row, column}"><el-input ref="inputRef" v-model="row.column1"v-if="editRow === row.id && editColumn === column.id" @blur="stopEdit"size="small"/><span v-else>{{ row.column1 }}</span></template><template #default-number1="{row, column}"><el-input ref="inputRef" v-model="row.number1"v-if="editRow === row.id && editColumn === column.id" @blur="stopEdit"size="small"/><span v-else>{{ row.number1 }}</span></template><template #default-select1="{row, column}"><el-select ref="inputRef" v-model="row.select1"v-if="editRow === row.id && editColumn === column.id" @blur="stopEdit"size="small"><el-optionv-for="item in options":key="item.value":label="item.label":value="item.value"/></el-select><span v-else>{{ row.select1 }}</span></template></custom-table></div></template><script setup lang="ts">import CustomTable from "@/components/CustomTable/index.vue";
import type {ColumnItem} from "@/types/table";
import {nextTick, onMounted, ref} from "vue";
import EventBus from "@/plugins/event-bus";
import {Delete, Plus} from "@element-plus/icons-vue";
import {ElMessage} from "element-plus";const columnList: ColumnItem[] = [{label: 'id',prop: 'id',align: 'center'},{label: 'column1',prop: 'column1',align: 'center',width: 100},{label: 'number1',prop: 'number1',align: 'center',width: 100},{label: 'select1',prop: 'select1',align: 'center'},{label: 'time1',prop: 'time1',align: 'center'},{label: 'time2',prop: 'time2',align: 'center'}
]const mergeColumns = ['column1', 'select1'] // Specify merge columns
const distinguishColumns = ['column1'] // Specify distinguish columnsconst headerCellStyle = (row: any) => { // 表头单元格样式示例return {backgroundColor: 'lightpink'}
}const rowStyle = (data: any) => { // 行样式示例if (data.row.column1 === 'row1') {return {backgroundColor: 'lightpink'}} else {return {backgroundColor: 'lightgreen'}}
}const tableData = ref<any[]>([ // 表格数据{id: 1,column1: 'row1',number1: 1,select1: 'option1',time1: 'test',time2: 'test'},{id: 2,column1: 'row1',number1: 2,select1: 'option1',time1: 'test',time2: 'test'},{id: 3,column1: 'row2',number1: 3,select1: 'option1',time1: 'test',time2: 'test'},{id: 4,column1: 'row2',number1: 4,select1: 'option1',time1: 'test',time2: 'test'},{id: 5,column1: 'row3',number1: 4,select1: 'option1',time1: 'test',time2: 'test'},{id: 6,column1: 'row4',number1: 5,select1: 'option1',time1: 'test',time2: 'test'}
])const options = [{value: 'option1',label: 'option1'},{value: 'option2',label: 'option2'}
]const editRow = ref<any>(null) // 正在编辑的行
const editColumn = ref<any>(null) // 正在编辑的列
const inputRef = ref<any>(null)const selectedRows = ref<any[]>([]) // 选中的行onMounted(() => {EventBus.on('cell-dbClick', (data: any) => { // 单元格双击事件editRow.value = nulleditColumn.value = nulleditRow.value = data.row.ideditColumn.value = data.column.idnextTick(() => {inputRef.value.focus() // 输入框自动聚焦})})
})const stopEdit = () => { // 停止编辑editRow.value = nulleditColumn.value = nullinputRef.value = null/* 每次编辑后根据 column1 的内容重新排序 */const groupedTableData = tableData.value.reduce((acc, curr) => {(acc[curr.column1] = acc[curr.column1] || []).push(curr)return acc}, {})tableData.value = []Object.keys(groupedTableData).forEach((key: any) => {tableData.value.push(...groupedTableData[key])})
}onMounted(() => {EventBus.on('row-selection', (data: any) => { // 行选择事件selectedRows.value = data})
})const addRow = async () => { // 新增行const rowData = {id: tableData.value.length + 1,column1: '',number1: '',select1: '',time1: '',time2: ''}tableData.value.push(rowData)
}const deleteRows = () => { // 删除行if (selectedRows.value.length > 0) {selectedRows.value.forEach((item: any) => {tableData.value = tableData.value.filter((data: any) => data.id !== item.id)})} else {ElMessage.error('Please select the row to delete')}
}</script><style scoped lang="scss">.custom-table-container {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);background: rgba(233, 237, 246);padding: 20px;.table-header {background: rgba(200, 180, 243);color: rgba(254, 255, 253);font-family: Consolas;font-size: 20px;font-weight: bolder;display: flex;justify-content: space-between;align-items: center;padding: 10px 15px;}.table-buttons {display: flex;.table-button {color: rgba(255, 254, 254);background: rgba(192, 172, 233);font-size: 20px;font-family: Consolas;width: fit-content;padding: 10px;margin-right: 10px;cursor: pointer;}.table-button:last-child { // 最右边的按钮 margin-right = 0margin-right: 0;}}}</style>

四、参考文章

el-table表格动态合并相同数据单元格(可指定列+自定义合并)_el-table 合并单元格动态-CSDN博客

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

6 张图带你深入了解 kube-scheduler

本文目录&#xff1a; kube-scheduler 用途Scheduler Pod 调度流程源码调用链路Scheduler Framework如何扩展 kube-scheduler 用途 顾名思义&#xff1a;负责将 Pod 调度到 Node 上。 Pod 创建流程&#xff1a; 说明&#xff1a; 所有的组件只与 Apiserver 做交互&#xff0…

数据结构(8.7_1)——外部排序

知识总览 外存、内存之间的数据交换 外部排序原理 构造初始“归并段” 第一趟归并 第二趟归并 输出缓冲区1空了用归并段1的下一块元素补上 ..... 顺序的两个归并段方法一样..... 第三趟归并 时间开销分析 对外部排序进行优化 由于文件总块数无法改变&#xff0c;所以…

“方块兽神仙猿点石成金”游戏搭建开发

“方块兽神仙猿点石成金”是一款结合了策略和运气的休闲游戏。玩家需在规定时间内向不同的山头投入矿石&#xff0c;等待神仙猿降临并随机选择一座山进行“点石成金”。根据神仙猿的选择&#xff0c;玩家将获得不同的奖励。 游戏核心机制 矿石投入&#xff1a;玩家在游戏开始…

【压力测试】如何确定系统最大并发用户数?

一、明确测试目的与了解需求 明确测试目的&#xff1a;首先需要明确测试的目的&#xff0c;即为什么要确定系统的最大并发用户数。这通常与业务需求、系统预期的最大用户负载以及系统的稳定性要求相关。 了解业务需求&#xff1a;深入了解系统的业务特性&#xff0c;包括用户行…

达梦数据库DM管理工具增删改不生效怎么办?如何设置事务自动提交?

前言 我在使用达梦数据库DM时&#xff0c;一开始使用的是达梦数据库自带的连接工具DM管理工具。自带的有它自己的好处&#xff0c;起码对于修改新增字段等是比较兼容的。后面我发现DBeaver也是支持连接达梦数据库的&#xff0c;所以后面用DBeaver也在连接达梦数据库。 我在一…

JVM出现OOM错误排查

前言 对应线程出现的OOM错误&#xff0c;其实分好几类&#xff1a;堆内存溢出、栈溢出、方法区溢出&#xff0c;下面我们要区分两个概念 内存泄漏&#xff1a; 内存泄漏是指GC垃圾回收的速度跟不上内存消耗的速度,造成OOM的情况 内存溢出: 内存溢出是指程序员在申请内存时&…

WinForms 中使用 MVVM 模式构建应用:实现登录页面、页面导航及 SQLite 数据库连接完整框架搭建过程

前言 在传统的 WinForms 应用程序开发中&#xff0c;很多开发者使用事件驱动的设计模式&#xff0c;直接将业务逻辑编写在界面代码中。然而&#xff0c;随着应用程序的复杂性增加&#xff0c;单一的界面文件变得臃肿&#xff0c;难以测试和维护。借鉴 WPF 中 MVVM&#xff08;…

windows系统编程 - 静态库和动态库

文章目录 前言一、使用obj保护源码生成obj文件导入并使用obj文件方式一 拖入解决方案方式二 附加依赖项适配C语言文件 二、静态库的概述三、静态库的创建与使用四、动态库的概述五、动态库的创建六、动态库的两种调用方式七、动态链接库的隐式加载__declspec(dllimport) 声明外…

数据结构 ——— 查找链式二叉树中值为X的节点

目录 链式二叉树示意图 手搓一个链式二叉树 查找链式二叉树中值为X的节点 链式二叉树示意图 手搓一个链式二叉树 代码演示&#xff1a; // 数据类型 typedef int BTDataType;// 二叉树节点的结构 typedef struct BinaryTreeNode {BTDataType data; //每个节点的数据struc…

基于SSM的BBS社区论坛系统源码

1.项目介绍 系统角色&#xff1a;管理员、业主&#xff08;普通用户&#xff09;功能模块&#xff1a;管理员&#xff08;用户管理、二手置换管理、报修管理、缴费管理、公告管理&#xff09;、普通用户&#xff08;登录注册、二手置换、生活缴费、信息采集、报事报修&#xf…

python的安装环境Miniconda(Conda 命令管理依赖配置)

这一段时间&#xff0c;对AI大模型 有了兴趣就想研究一下。 在研究之前肯定要先把需要的编程技能掌握了。经过我查阅资料&#xff0c;今天就先学一下 python的 环境安装。 Node.js 包管理工具&#xff1a;npm 依赖配置文件&#xff1a;package.json 环境管理&#xff1a;nvm&am…

出租房管理系统有哪些?

出租房管理系统在现代房产租赁市场中发挥着至关重要的作用&#xff0c;其供应商众多&#xff0c;各具特色。以下是对易收租、寓小二、全房通、水滴管家以及悟空租房管理系统等供应商的详细介绍。 一、深圳合众致达科技有限公司的易收租 深圳合众致达科技有限公司是一家专注于…

【在Linux世界中追寻伟大的One Piece】Socket编程TCP

目录 1 -> TCP socket API 2 -> V1 -Echo Server 2.1 -> 测试多个连接的情况 1 -> TCP socket API socket()&#xff1a; socket()打开一个网络通讯端口&#xff0c;如果成功的话&#xff0c;就像open()一样返回一个文件描述符。应用程序可以像读写文件一样用r…

【spring】IOC与DI

&#x1f490;个人主页&#xff1a;初晴~ &#x1f4da;相关专栏&#xff1a;程序猿的春天 一、IOC&#xff08;Inversion of Control&#xff09; 1、概念 IOC&#xff08;Inversion of Control&#xff0c;控制反转&#xff09;是一种设计原则&#xff0c;它将对象的控制权…

【英特尔IA-32架构软件开发者开发手册第3卷:系统编程指南】2001年版翻译,2-16

文件下载与邀请翻译者 学习英特尔开发手册&#xff0c;最好手里这个手册文件。原版是PDF文件。点击下方链接了解下载方法。 讲解下载英特尔开发手册的文章 翻译英特尔开发手册&#xff0c;会是一件耗时费力的工作。如果有愿意和我一起来做这件事的&#xff0c;那么&#xff…

​​​​​​​PHP类型比较

在php中符号分为两种&#xff0c;一种是&#xff0c;还是一种是 松散比较&#xff1a;使用两个等号 比较&#xff0c;只比较值&#xff0c;不比较类型。 严格比较&#xff1a;用三个等号 比较&#xff0c;除了比较值&#xff0c;也比较类型。 注意&#xff0c;当一个号时&…

Mysql、Dm8达梦数据库通过脚本导出指定库所有表的结构详情信息到

目录 前言二、Mysql三、达梦8 前言 在当今复杂多变的数据环境中&#xff0c;数据库作为信息存储与管理的核心&#xff0c;其重要性不言而喻。随着业务的不断拓展和深化&#xff0c;对于数据库表结构的理解与管理成为了确保数据一致性和准确性的关键。特别是在跨数据库系统的场…

yelp数据集上识别潜在的热门商家

yelp数据集是研究B2C业态的一个很好的数据集&#xff0c;要识别潜在的热门商家是一个多维度的分析过程&#xff0c;涉及用户行为、商家特征和社区结构等多个因素。从yelp数据集里我们可以挖掘到下面信息有助于识别热门商家 用户评分和评论分析 评分均值: 商家的平均评分是反映其…

文献阅读记录6-Toward computer-made artificial antibiotics

摘要 将合成生物学和计算生物学的概念结合起来&#xff0c;可能会产生比现有药物更不容易产生耐药性的抗生素&#xff0c;而且还能对抗耐药感染。事实上&#xff0c;计算机引导策略与大规模并行高通量实验方法相结合&#xff0c;代表了抗生素发现的新范式。耐多药微生物引起的…

【docker compose】docker compose的hello world

安装docker desktop后在终端使用以下命令&#xff0c;代表安装成功&#xff0c;并查看当前安装的版本 docker-compose --version示例docker-compose.yml文件 version: 3.8 # 指定 Docker Compose 文件的版本services:scau_jwc: # 定义一个名为 scau_jwc 的服务image: scau_…