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.xdnf.cn/news/9428.html

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

相关文章

zxing生成、解析二维码,条形码

1、maven依赖 <!--zxing依赖--><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.1.0</version></dependency><dependency><groupId>com.google.zxing</groupI…

JQuery设置Cookie操作,设置、获取、删除三种方法

//触发条件 当用户点击或者操作时需要设置cookie时 //方法里面定义了三个处理cookie的方法 $(document).ready(function(e) {$("#btnsetcookie").click(function() {setCookie("Demo", "我的示例Cookie数据", 2); //设置cookie});$("#btn…

bert-base-uncased使用

1.下载模型 https://github.com/google-research/bert?tabreadme-ov-file 2.下载config.json和pytorch_model.bin https://huggingface.co/google-bert/bert-base-uncased/tree/main 3.解压缩到同一文件夹 4.代码测试 from transformers import BertModel,BertTokenizerBER…

【人工智能】阿里云PAI平台DSW实例一键安装Python脚本

阿里云的DSW实例自带的镜像很少而且并不好用&#xff0c;所以我在这里写三个一键编译安装Python3.8&#xff0c;Python3.9&#xff0c;Python3.10的Shell脚本。 安装Python3.8 wget https://www.smallbamboo.cn/install_python38.sh && chmod x install_python38.sh …

每日科技资讯:2024年11月09日【龙】农历十月初九 ---文末送书

目录 1.史上最强游戏CPU&#xff01;9800X3D首发评测2.苹果喊话iPhone 13和14钉子户&#xff1a;16方方面面都升级了3.加拿大政府下令 TikTok 关闭该国业务&#xff0c;但应用仍可以继续访问4.OpenAI 刚刚花了超过 1000 万美元购买了Chat.com5.Max 加入打击密码共享行列6.微软可…

「实战应用」如何用图表控件LightningChart .NET在WPF中制作表格?(一)

LightningChart .NET完全由GPU加速&#xff0c;并且性能经过优化&#xff0c;可用于实时显示海量数据-超过10亿个数据点。 LightningChart包括广泛的2D&#xff0c;高级3D&#xff0c;Polar&#xff0c;Smith&#xff0c;3D饼/甜甜圈&#xff0c;地理地图和GIS图表以及适用于科…

大数据学习11之Hive优化篇

1.Hive压缩 1.1概述 当前的大数据环境下&#xff0c;机器性能好&#xff0c;节点更多&#xff0c;但并不代表我们无条件直接对数据进行处理&#xff0c;在某些情况下&#xff0c;我们依旧需要对数据进行压缩处理&#xff0c;压缩处理能有效减少存储系统的字节读取数&#xff0…

【Linux】【Vim】多文件编辑与分屏

多文件编辑 编辑另一个文件文件列表分屏vimdiff文件跳转 编辑另一个文件 除了为每一个要编辑的文件运行一次 Vim 之外&#xff0c;还可以在当前 Vim 中开始编辑另一个文件。 :edit foo.txtVim 会关闭当前正在编辑的文件打开指定的新文件进行编辑。如果当前文件还有未存盘的内容…

Fastify Swagger:自动化API文档生成与展示

在现代软件开发中&#xff0c;API文档的生成和维护是一个不可或缺的环节。Fastify Swagger 是一个专为 Fastify 框架设计的插件&#xff0c;它能够自动生成符合 Swagger&#xff08;OpenAPI v2 或 v3&#xff09;规范的文档&#xff0c;从而帮助开发者轻松创建和维护API文档。本…

SQL,力扣题目262,行程和用户

一、力扣链接 LeetCode_262 二、题目描述 表&#xff1a;Trips ----------------------- | Column Name | Type | ----------------------- | id | int | | client_id | int | | driver_id | int | | city_id | int | | status …

【复旦微FM33 MCU 开发指南】ADC

前言 本系列基于复旦微FM33LC0系列单片机的DataSheet编写&#xff0c;旨在提供手册解析和开发指南。 本文章及本系列其他文章将持续更新&#xff0c;本系列其它文章请跳转【复旦微FM33 MCU 外设开发指南】总集篇 本文章最后更新日期&#xff1a;2024/11/09 全文字数&#xff…

机器学习—是否有路通向AGI(通用人工智能)

AI包含两个非常不同的东西&#xff0c;一个是ANI&#xff0c;代表人工狭义智能&#xff0c;这是一个人工智能系统&#xff0c;只做一件事&#xff0c;狭隘的任务&#xff0c;可能非常有价值&#xff0c;比如智能音箱或者网络搜索或AI应用于特定应用。例如&#xff0c;过去几年的…

2.4w字 —TS入门教程

目录 1. 什么是TS 2. TS基本使用 3 TS基础语法 3.1 基础类型约束 3.11 string&#xff0c;number&#xff0c;boolean&#xff0c; null和undefined 3.12 any 3.13 unknown 3.14 void 3.15 数组 3.16 对象 3.2 函数的约束 3.21 普通写法 3.22 函数表达式 3.22 可选…

深度学习注意力机制类型总结pytorch实现代码

一、注意力机制的基本原理 在深度学习中&#xff0c;注意力机制&#xff08;Attention Mechanism&#xff09;已经成为一种重要的技术。意力机制通过动态调整模型的注意力权重&#xff0c;来突出重要信息&#xff0c;忽略不重要的信息&#xff0c;大大提高了模型的效果 注意力…

数据库SQLite的使用

SQLite是一个C语言库&#xff0c;实现了一个小型、快速、独立、高可靠性、功能齐全的SQL数据库引擎。SQLite文件格式稳定、跨平台且向后兼容。SQLite源代码属于公共领域(public-domain)&#xff0c;任何人都可以免费将其用于任何目的。源码地址&#xff1a;https://github.com/…

基于java宠物医院管理系统的设计与实现

一、环境信息 开发语言&#xff1a;JAVA JDK版本&#xff1a;JDK8及以上 数据库&#xff1a;MySql5.6及以上 Maven版本&#xff1a;任意版本 操作系统&#xff1a;Windows、macOS 开发工具&#xff1a;Idea、Eclipse、MyEclipse 开发框架&#xff1a;SpringbootHtmljQueryMysql…

米家护眼灯和孩视宝哪个好?书客、米家、孩视宝巨头测评大PK!

米家护眼灯和孩视宝哪个好&#xff1f;从护眼照明市场发展趋势可以知道&#xff0c;如今热度越来越高&#xff0c;品牌越来越丰富&#xff0c;增加了用户的选择难度。而且有些劣质产品由于生产过程中没有任何技术参数调校&#xff0c;选料和做工方面低劣&#xff0c;照明过程中…

L1G2000作业

1、MindSearch 2、书生浦语 3、书生万象

海思3559 网口 delay值配置

问题 我们研发了一个自动配置 phy delay的脚本&#xff0c;里面采用了shell 数组&#xff0c;以及ethtool等工具。 但是海思SDK默认的文件系统没有这些工具&#xff0c;并且sh 不支持shell 数组&#xff0c;只有bash支持。 因而我们需要编译相关工具&#xff0c;此处记录编译…

如何去除图片水印?快来试试这4种图片去水印方法!

去除图片水印是一项普遍存在的图像处理需求&#xff0c;它旨在消除水印对图片视觉效果的干扰&#xff0c;让我们能够更自由、更美观、更专业地使用图片资源。接下来&#xff0c;我们将介绍四种有效的去除图片水印的方法和工具&#xff0c;它们各自具有独特的优势和适用场景。 方…