vue3+ts+element-ui实现的可编辑table表格组件 插入单行多行 组件代码可直接使用

最近需求越来越离谱,加班越来越严重,干活的牛马也越来越卑微。写了一个可编辑表格,并已封装好组件,可直接使用。

基于这位大佬的 动态表格自由编辑 方法和思路,于是参考和重写了表格,在基础上增加和删除了一些功能点。

实现功能如下:

1、双击单元格可编辑格子内容,输入框和文本的高度自适应。
2、右击表格弹出菜单,可插入单行、多行和删除行。
3、可配置字段是否可以编辑。

效果图

在这里插入图片描述

组件参数截图

在这里插入图片描述

使用组件

在这里插入图片描述

使用组件代码
<!-- 可编辑表格 -->
<EditTableForm :list="state.questionChoiceVOlist" :headerList="columnList" :selectUserList="scorerUserList" :forbidPop="['itemScore','scorerUserIdList','expirationDay']" />
组件EditTableForm完整代码
<template><el-table:data="tableData" @cell-dblclick="cellDblclick" @row-contextmenu="cellRightClick":row-class-name="tableRowClassName" border><el-table-columntype="index"label="序号"align="center":resizable="false"width="70"/><el-table-column:resizable="false"align="left"v-for="(col, idx) in columnList":key="col.prop":prop="col.prop":label="col.label":index="idx"><template #default="scope"><el-input-numberv-if="col.type === 'input-number'"v-model.number="scope.row[col.prop]"style="width: 120px;":min="0":max="100":step="1"/><el-select v-if="col.type && col.type === 'select'" v-model="scope.row[col.prop]" multiple:multiple-limit="1"filterable clearablecollapse-tagscollapse-tags-tooltipplaceholder="请选择"><el-optionv-for="item in props.selectUserList":key="item.id":label="item.nickname":value="item.id"/></el-select><el-select v-if="col.type && col.type === 'select-day'" v-model="scope.row[col.prop]" clearablecollapse-tagsplaceholder="截止日期"><el-optionv-for="item in 15":key="item":label="item + '号'":value="item"/></el-select><divclass="cell-text"v-if="!scope.row[`${col.prop}_ifWrite`] && !isPop(scope.column)" v-html="filterHtml(scope.row[col.prop])"></div><el-input v-show="scope.row[`${col.prop}_ifWrite`]" :ref="setInputRef(scope.$index, col.prop)"type="textarea" autosize:maxRows="4"v-model="scope.row[col.prop]" @blur="scope.row[`${col.prop}_ifWrite`] = false" /></template></el-table-column></el-table><!-- 右键菜单框 --><div v-show="showMenu" id="contextmenu" @mouseleave="menuMouseleave"><el-button type="primary" @click="addRow(false)" v-show="!curTarget.isHead">上方插入一行</el-button><el-button @click="openAddMore(false)" v-show="!curTarget.isHead">上方插入多行</el-button><el-button type="primary" @click="addRow(true)" v-show="!curTarget.isHead">下方插入一行</el-button><el-button @click="openAddMore(true)" v-show="!curTarget.isHead">下方插入多行</el-button><el-button type="danger" @click="delRow" v-show="!curTarget.isHead" >删除当前行</el-button><el-dialogv-model="visible"title="请输入行数"width="200"align-centerdraggable><el-input-number style="width: 100%;" v-model="addMoreNumber" :min="1" :max="20" /><template #footer><div class="dialog-footer"><el-button @click="visible = false" style="width: 49%;">取消</el-button><el-button type="primary" style="width: 49%;" @click="addMoreRow(addMorelater,addMoreNumber)">确定</el-button></div></template></el-dialog></div>
</template><script lang="ts" setup>
defineOptions({ name: 'EditTableForm' })const props = defineProps({// 表格数据list: {type: Array as PropType<any[]>,default: () => [],},// 表格表头headerList: {type: Array as PropType<{ prop: string; label: string; type?: string }[]>,default: () => [],},// 禁止填写的字段forbidPop: {type: Array as PropType<string[]>,default: () => [],},// 评分人列表selectUserList: {type: Array as PropType<{ id: number; nickname: string }[]>,default: () => [],},
})// const itemBox = {
//   id: 1,
//   date: '',
//   name: '',
//   address: '',
// }
// tableData.forEach(item => {
//   columnList.forEach(col => {
//     item[col.prop + '_ifWrite'] = false;
//     item[col.prop + '_ref'] = null;
//   })
// })interface Target {rowIdx: number | null;colIdx: number | null;val: string | null;isHead: boolean | undefined;
}const state = reactive({tableData: [] as any[],columnList: props.headerList as any[],itemBox: getItemBox(props.list) as {},showMenu: false, // 显示右键菜单curTarget: {// 当前目标信息rowIdx: null, // 行下标colIdx: null, // 列下标val: null, // 单元格内容/列名isHead: undefined, // 当前目标是表头?} as Target,
});
const { tableData,columnList,showMenu,curTarget } = toRefs(state);
const inputRefs = ref<{ [key: string]: any }>({});
const visible = ref(false)
const addMoreNumber = ref(1);
const addMorelater= ref(false);/**监听表头的变化 */
watch(() => props.headerList,(val:any) => {if (!val) returnstate.columnList = val;},{deep: true,immediate: true}
)
/**监听表格数据的变化 */
watch(() => props.list,async (val: any) => {if (!val || val.length === 0) return;// 使用 nextTick 确保 DOM 更新后进行操作,防止 offsetHeight 报错await nextTick();// 在此处进行安全的表格数据更新// console.log('表格数据已更新:', val);state.tableData = val;},{ deep: true, immediate: true }
)/**设置添加的行元素 */
function getItemBox(data:Array<any>) {let obj:any = {};if(data.length > 0){let item = data[0];for(let key of Object.keys(item)){obj[key] = '';}for (const col of props.headerList) {obj[col.prop + '_ifWrite'] = false;obj[col.prop + '_ref'] = null;}}return obj;
}// 打开添加行弹窗
const openAddMore = (val:boolean) => {visible.value = true;addMorelater.value = val;
}// 鼠标移出菜单
const menuMouseleave = ()=>{showMenu.value = false;visible.value = false;
}// 这个方法动态为每个 el-input 实例设置 ref,并将其存储在 inputRefs 中,以便后续访问。
const setInputRef = (rowIdx: number, colProp: string) => (el: any) => {inputRefs.value[`${rowIdx}-${colProp}`] = el;
};// 添加表格行下标和控制每个row的显示隐藏字段
const tableRowClassName = ({ row, rowIndex }: { row: any; rowIndex: number }) => {row.row_index = rowIndex;
};// 控制某字段不能打开弹框
const isPop = (column: { index: null; property: string; label: string }) => {// return column.property === "itemScore" || column.property === "scorerUserIdList" || column.property === "expirationDay";return props.forbidPop.includes(column.property);
};// 双击左键输入框
const cellDblclick = (row: { [x: string]: any; row_index: any },column: any,cell: HTMLTableCellElement,event: MouseEvent
) => {// 如果禁填项,不执行后续代码if (isPop(column)) return;// 显示输入框的控制Object.keys(row).forEach(key => {// endsWith判断 以_ifWrite结束的if (key.endsWith('_ifWrite')) {row[key] = false;}})row[`${column.property}_ifWrite`] = true;// 获取input焦点nextTick(() => {const inputKey = `${row.row_index}-${column.property}`;const inputComponent = inputRefs.value[inputKey];if (inputComponent) {inputComponent.focus();}});
};// 单元格右击事件 - 打开菜单
const cellRightClick = (row: any, column: any, event: MouseEvent) => {// 阻止事件的默认行为(禁止浏览器右键菜单)event.preventDefault();showMenu.value = false;// 定位菜单/编辑框locateMenuOrEditInput('contextmenu', -500, event); // 右键输入框showMenu.value = true;// 获取当前单元格的值curTarget.value = {rowIdx: row ? row.row_index : null,colIdx: column.index,val: row ? row[column.property] : column.label,isHead: !row,};
};// 新增行
const addRow = (later: boolean) => {showMenu.value = false;const idx = later ? curTarget.value.rowIdx! + 1 : curTarget.value.rowIdx!;let obj: any = {...state.itemBox,id: Math.floor(Math.random() * 100000),};state.tableData.splice(idx, 0, obj);
};// 新增多行
const addMoreRow = (later: boolean,lineNum: number) => {showMenu.value = false;const idx = later ? curTarget.value.rowIdx! + 1 : curTarget.value.rowIdx!;// 创建一个包含要插入数据的新数组let newRows: any[] = [];for (let i = 0; i < lineNum; i++) {let obj: any = {...state.itemBox,id: Math.floor(Math.random() * 100000),};newRows.push(obj);}// 使用Vue的响应式更新方法来插入新数据// 这里假设tableData是通过reactive函数创建的响应式数据state.tableData.splice(idx, 0,...newRows);
};// 删除行
const delRow = () => {ElMessageBox.confirm(`此操作将永久删除该行, 是否继续 ?`, {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning',}).then(() => {showMenu.value = false;curTarget.value.rowIdx !== null && state.tableData.splice(curTarget.value.rowIdx!, 1);ElMessage.success('删除成功');}).catch(() => ElMessage.info('已取消删除'));
};// 定位菜单/编辑框
const locateMenuOrEditInput = (eleId: string, distance: number, event: MouseEvent) => {if (window.innerWidth < 1130 || window.innerWidth < 660)return ElMessage.warning('窗口太小,已经固定菜单位置,或请重新调整窗口大小');const ele = document.getElementById(eleId) as HTMLElement;const x = event.pageX;const y = event.clientY + 200; //右键菜单位置 Ylet left = x + distance + 200; //右键菜单位置 Xlet top;if (eleId == 'editInput') {// 左键top = y + distance;left = x + distance + 50;} else {// 右键top = y + distance + 180;left = x + distance + 270;}ele.style.left = `${left}px`;ele.style.top = `${top}px`;
};// 缓存每个分组的合并信息
const mergeCache = ref<Record<number, [number, number]>>({});// 合并单元格的方法
const objectSpanMethod = ({row,column,rowIndex,columnIndex,
}) => {if (columnIndex === 1) {// 检查缓存中是否已经存在该行的合并数据if (mergeCache.value[rowIndex]) {return mergeCache.value[rowIndex];}// 计算需要的合并行数let rowspan = 1;for (let i = rowIndex + 1; i < tableData.value.length; i++) {if (tableData.value[i].bigTargetClassify === row.bigTargetClassify) {rowspan++;} else {break;}}// 更新缓存mergeCache.value[rowIndex] = [rowspan, 1];// 对于合并行中的其他行,缓存 [0, 0] 表示隐藏for (let i = 1; i < rowspan; i++) {mergeCache.value[rowIndex + i] = [0, 0];}return [rowspan, 1];}
};// 当数据变化时清除缓存
watch(tableData, () => {mergeCache.value = {};
});// 字符串格式转换
function filterHtml(text){if(text) {let text1 = String(text);return text1.replace(/(\r\n|\n)/g, '<br/>')}else{return text}
}</script>
<style lang="scss" scoped>
:deep(.el-textarea__inner){padding: 0;
}
.cell-text{width: 100%;min-height: 100%;// min-height: 42px;
}
/* 右键 */
#contextmenu {position: absolute;display: flex;flex-direction: column;align-items: center;justify-content: center;z-index: 999;top: 0;left: 0;height: auto;width: 180px;border-radius: 3px;border: #e2e2e2 1px solid;box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);background-color: #fff;border-radius: 6px;padding: 15px 10px 14px 12px;button {display: block;margin: 0 0 5px;width: 100%;}
}
.dialog-footer{display: flex;justify-content: space-between;
}
</style>

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

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

相关文章

决策树(部分)

目录 信息熵 总结&#xff1a; 特征选择 信息增益&#xff1a;ID3算法 增益率&#xff1a;C4.5 基尼指数 剪枝处理 预剪枝 后剪枝 信息熵 信息熵 (entropy)是 用于度量样本集合“ 纯度 ” 最常用的一种指标&#xff0c;其中 “ 熵 ” 是事物的不确定性&#xff0c;假定…

webpack 执行流程 — 实现 myWebpack

前言 实现 myWebpack 主要是为了更好的理解&#xff0c;webpack 中的工作流程&#xff0c;一切都是最简单的实现&#xff0c;不包含细节内容和边界处理&#xff0c;涉及到 ast 抽象语法树和编译代码部分&#xff0c;最好可以打印出来观察一下&#xff0c;方便后续的理解。 re…

【python】Flask

文章目录 1、Flask 介绍2、Flask 实现网页版美颜效果3、参考 1、Flask 介绍 Flask 是一个用 Python 编写的轻量级 Web 应用框架。它设计简单且易于扩展&#xff0c;非常适合小型项目到大型应用的开发。 以下是一些 Flask 库中常用的函数和组件&#xff1a; 一、Flask 应用对…

AI大模型如何重塑软件开发流程?

《AI大模型对软件开发流程的重塑&#xff1a;变革、优势、挑战与展望》 一、传统软件开发流程与模式&#xff08;一&#xff09;传统软件开发流程&#xff08;二&#xff09;传统软件开发模式面临的问题&#xff08;一&#xff09;AI在软件开发中的应用场景&#xff08;二&…

OceanBase 应用实践:如何处理数据空洞,降低存储空间

问题描述 某保险行业客户的核心系统&#xff0c;从Oracle 迁移到OceanBase之后&#xff0c;发现数据存储空间出现膨胀问题&#xff0c;数据空间 datasize9857715.48M&#xff0c;实际存储占用空间17790702.00M。根据 required_mb - data_mb 值判断&#xff0c;数据空洞较为严重…

Zookeeper运维秘籍:四字命令基础、详解及业务应用全解析

文章目录 一、四字命令基础二、四字命令详解三、四字命令的开启与配置四、结合业务解读四字命令confconsenvi命令Stat命令MNTR命令ruok命令dump命令wchswchp ZooKeeper&#xff0c;作为一款分布式协调服务&#xff0c;提供了丰富的四字命令&#xff08;也称为四字短语&#xff…

MATLAB大数计算工具箱及其用法

1. MATLAB大数工具箱Variable Precision Integer Arithmetic介绍 Variable Precision Integer Arithmetic是John DErrico 开发的大数运算工具箱&#xff0c;可以用完全任意大小的整数进行算术运算。支持vpi定义的数组和向量。 2.MATLAB代码 完整代码见: https://download.cs…

【野生动物识别系统】Python+深度学习+人工智能+卷积神经网络算法+TensorFlow+ResNet+图像识别

一、介绍 动物识别系统&#xff0c;使用Python作为主要开发语言&#xff0c;基于深度学习TensorFlow框架&#xff0c;搭建卷积神经网络算法。并通过对18种动物数据集进行训练&#xff0c;最后得到一个识别精度较高的模型。并基于Django框架&#xff0c;开发网页端操作平台&…

数据库_SQLite3

下载 1、更新软件源&#xff1a; sudo apt-get update 2、下载SQLite3&#xff1a; sudo apt-get install sqlite3 3、验证&#xff1a; sqlite3启动数据库&#xff0c;出现以下界面代表运行正常。输入 .exit 可以退出数据库 4、安装sqlite3的库 sudo apt-get install l…

PyTorch核心概念:从梯度、计算图到连续性的全面解析(三)

文章目录 Contiguous vs Non-Contiguous TensorTensor and ViewStrides非连续数据结构&#xff1a;Transpose( )在 PyTorch 中检查Contiguous and Non-Contiguous将不连续张量&#xff08;或视图&#xff09;转换为连续张量view() 和 reshape() 之间的区别总结 参考文献 Contig…

如何解决导入aioredis报错TypeError: duplicate base class TimeoutError的问题(轻松解决,亲测有效)

下面是根据你的要求撰写的文章: 文章目录 📖 介绍 📖🏡 演示环境 🏡📒 aioredis导包报错 📒📝 解决方案📝 小贴士⚓️ 相关链接 ⚓️📖 介绍 📖 最近在使用Python异步redis模块aioredis的时候遇到了一个错误,导包报错提示 TypeError: duplicate base cla…

基于Springboot+Android的智慧社区互助平台 (含源码数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 这个系…

讨论一个mysql事务问题

最近在阅读一篇关于隔离级别的文章&#xff0c;文章中提到了一种场景&#xff0c;我们下面来分析一下。 文章目录 1、实验环境2、两个实验的语句执行顺序3、关于start transaction和start transaction with consistent snapshot4、实验结果解释4.1、实验14.2、实验24.3、调整实…

Kubernetes-编排工具篇-01-Kustomize与Helm对比

Kustomize与Helm对比 0、前言 K8s 是一个开源容器编排平台&#xff0c;可自动执行容器化应用程序的部署、扩展和管理。近年来&#xff0c;K8s 已成为采用云原生架构和容器化技术的组织的标准。 但是由于K8s的复杂性&#xff0c;所以很多公司以及开源组织都在开发相关的工具来…

确定图像的熵和各向异性 Halcon entropy_gray 解析

1、图像的熵 1.1 介绍 图像熵&#xff08;image entropy&#xff09;是图像“繁忙”程度的估计值&#xff0c;它表示为图像灰度级集合的比特平均数&#xff0c;单位比特/像素&#xff0c;也描述了图像信源的平均信息量。熵指的是体系的混乱程度&#xff0c;对于图像而言&#…

数字IC后端设计实现之Innovus自动修复Min Step DRC Violation方案

在实际IC后端项目中我们经常会遇到min step的DRC Violation&#xff0c;如下图所示。 在咱们IC后端训练营项目中也会遇到这类DRC Violation。这类DRC Violation的本质是出现Metal的Notch&#xff0c;即metal有凹槽。 如果是pg net的 Min Step问题&#xff0c;我们可以使用下面的…

进程相关内容

进程内容 进程类型守护进程进程的概念查看进程信息父子进程创建子进程进程结束 – exit/_exit进程回收 –wait waitpid 进程类型 交互进程 (Interactive Process) 交互进程是由用户通过终端或图形界面直接启动的进程&#xff0c;例如我们在命令行输入的命令。它通常需要等待用…

石墨舟氮气柜:半导体制造中的关键保护设备

石墨舟是由高纯度石墨材料制成的&#xff0c;主要用于承载硅片或其他基板材料通过高温处理过程&#xff0c;是制造半导体器件和太阳能电池片的关键设备之一。 石墨舟在空气中容易与氧气发生反应&#xff0c;尤其是在高温处理后&#xff0c;表面可能更为敏感&#xff1b;石墨舟具…

rabbitMq双节点高可用集群安装(亲测可用)

查询系统版本 cat /etc/redhat-release CentOS Linux release 7.7.1908 (Core) rabbitmq v3.9.13 &#xff08;centos7支持比较大的版本了&#xff0c;后面版本貌似都是centos8以上&#xff09; erlang erlang-23.3.4.11-1.el7.x86_64 &#xff08;需要和rabbitmq版本匹配&…

简单介绍一下mvvm mvc mvp以及区别、历史

MVC&#xff08;Model - View - Controller&#xff09; 因MVC架构的灵活性&#xff0c;架构图形式很多&#xff0c;仅供参考 历史&#xff1a; MVC 是最早出现的软件架构模式之一&#xff0c;其历史可以追溯到 20 世纪 70 年代&#xff0c;最初被用于 Smalltalk - 80 环境。…