当前位置: 首页 > news >正文

用el-table实现的可编辑的动态表格组件

用el-table实现的可编辑的动态表格组件

  • 需求
  • 说明
  • 实现效果
  • 代码

需求

  1. 点击单元格可编辑内容
  2. 右键单元格可选择"向下合并"或"拆分"
  3. 点击"新增行"按钮添加新行
  4. 点击"删除"按钮删除行(不能删除被合并的行)

说明

  1. 仅选择了具有特殊属性的列做合并与拆分操作
  2. span-method没有生效,【暂不清楚】,当前是使用的操作dom的方式来改变合并状态
  3. 删除时,如果是合并项的第一项会自动拆分并删除,其他则提示不能删除

实现效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码

使用数据

cs_columns: any = [{ label: '项目', prop: 'proName' },{ label: '位置', prop: 'projectLocation' },{ label: '有无地铁', prop: 'subway' },{ label: '通勤时间', prop: 'time' },{ label: '结论', prop: 'checkResult' },]
cs_checkItemsList: any = [{proName: '土壤重金属检测',projectLocation: '农田北区',subway: '无',time: '30分钟',checkResult: '合格'},{proName: '水质农药残留',projectLocation: '灌溉水渠',subway: '有',time: '45分钟',checkResult: '不合格'},{proName: '空气污染物检测',projectLocation: '加工厂周边',subway: '无',time: '60分钟',checkResult: '合格'},{proName: '农产品营养成分',projectLocation: '果蔬大棚',subway: '有',time: '25分钟',checkResult: '合格'},{proName: '饲料添加剂检测',projectLocation: '养殖场',subway: '无',time: '40分钟',checkResult: '不合格'}]

使用EditTable组件

<edit-table ref="editTable" :columns="columns" :initial-data="checkItemsList"></edit-table>

EditTable组件具体实现

<template><div><el-button class="m-y-16" @click="addRow" type="primary" size="small">新增行</el-button><el-tableref="table":data="tableData"border:span-method="objectSpanMethod"style="width: 100%"><el-table-column type="index" label="序号" width="50px"></el-table-column><el-table-columnv-for="col in columns":key="col.prop":prop="col.prop":label="col.label":width="col.width"><template #default="scope"><div v-if="editingCell.rowIndex === scope.$index && editingCell.colKey === col.prop"><el-inputv-model="scope.row[col.prop]"@blur="saveEdit"size="small"autofocus /></div><divv-else:id="col.prop + '-' + scope.$index"@click="handleCellClick(scope.$index, col.prop)"@contextmenu="handleContextMenu($event, scope.$index, col.prop)">{{ scope.row[col.prop] || '--' }}</div></template></el-table-column><el-table-column label="操作" width="100"><template #default="scope"><el-buttonsize="small"@click="deleteRow(scope.$index)"type="danger">删除</el-button></template></el-table-column></el-table><!-- 右键菜单 --><div v-show="contextMenu.visible":style="{left: contextMenu.left+'px', top: contextMenu.top+'px'}"class="context-menu"><div class="menu-item" @click="mergeCells">向下合并</div><div class="menu-item" @click="splitCells">拆分</div></div></div>
</template><script>
export default {name: 'EditTable',props: {initialData: {type: Array,default: () => []},columns: {type: Array,default: () => []},},data() {return {tableData: this.initialData.length > 0 ? this.initialData : [],spanArr: [],editingCell: {rowIndex: -1,colKey: ''},contextMenu: {visible: false,left: 0,top: 0,rowIndex: -1},colIndex: -1,checkItemsList: []}},created() {this.colIndex = this.columns.findIndex(item => item.prop === 'checkResult');this.initSpanArr();// 点击其他地方关闭右键菜单document.addEventListener('click', () => {this.contextMenu.visible = false})},mounted() {// 初始调用保持不变this.$nextTick(() => {this.initEditTable();});},methods: {// 初始化合并规则initSpanArr() {this.spanArr = this.tableData.map(item => {return item.rowspan || 1})},initEditTable() {// 添加更多的检查条件确保元素已渲染if (this.$refs.table && this.tableData.length > 0 && this.spanArr.length > 0) {this.tableData.forEach((item, index) => {// 确保元素存在且 rowspan 大于 1if (item.rowspan > 1 && index < this.spanArr.length) {const elementById = document.getElementById('checkResult-' + index);if (elementById) {const parentNode = elementById.parentNode.parentNode;parentNode.rowSpan = item.rowspan;// 隐藏被合并的行for (let i = 1; i < item.rowspan && (index + i) < this.tableData.length; i++) {const nextElementById = document.getElementById('checkResult-' + (index + i));if (nextElementById) {const nextParentNode = nextElementById.parentNode.parentNode;nextParentNode.style.display = 'none';}}}}});}},// 合并单元格方法objectSpanMethod({ column, rowIndex }) {if (column.prop === 'checkResult') {if (this.spanArr[rowIndex]) {return {rowspan: this.spanArr[rowIndex],colspan: 1}}}},// 单元格点击编辑handleCellClick(rowIndex, colKey) {this.editingCell = { rowIndex, colKey }},// 保存编辑saveEdit() {this.editingCell = { rowIndex: -1, colKey: '' }},// 处理右键菜单handleContextMenu(event, rowIndex, colKey) {if (colKey === 'checkResult') {event.preventDefault();this.contextMenu = {visible: true,left: event.clientX,top: event.clientY,rowIndex};}},// 合并单元格mergeCells() {const { rowIndex } = this.contextMenu;if (rowIndex === -1) return;// 获取当前行在checkResult列的rowspanconst currentRowspan = this.spanArr[rowIndex];// 检查是否可以合并下一行(防止越界)console.log("当前行的index:",rowIndex,"当前行的rowSpan:", currentRowspan, "表数据行数", this.tableData.length);if (rowIndex + currentRowspan >= this.tableData.length) {this.$message.warning('无法向下合并,已到达表格底部');this.contextMenu.visible = false;return;}// 检查目标行是否已被其他单元格合并const targetRowIndex = rowIndex + currentRowspan;console.log("目标行的index", targetRowIndex);if (this.spanArr[targetRowIndex] === 0) {this.$message.warning('无法合并,目标行已被其他单元格合并');this.contextMenu.visible = false;return;}// 检查下一行的值是否相同console.log("当前行的值", this.tableData[rowIndex][this.columns[this.colIndex].prop]);console.log("下一行的值", this.tableData[targetRowIndex][this.columns[this.colIndex].prop]);if (this.tableData[targetRowIndex][this.columns[this.colIndex].prop] !== this.tableData[rowIndex][this.columns[this.colIndex].prop]) {this.$message.warning('无法合并,目标行单元格的值不相同');this.contextMenu.visible = false;return;}// 获取当前行的元素const elementById = document.getElementById('checkResult-' + rowIndex)// 获取当前行的父元素的父元素,设置它的rowspanconst parentNode = elementById.parentNode.parentNode// 获取目标行的rowspanconst targetRowspan = this.spanArr[targetRowIndex];console.log("目标行的rowspan", targetRowspan);// 更新spanArr数据:将当前单元格的rowspan增加目标单元格的rowspanthis.spanArr[rowIndex] += targetRowspan;// 获取当前行的父元素的父元素,设置它的rowspanparentNode.rowSpan = this.spanArr[rowIndex]// 将被合并的行标记为rowspan=0,表示被合并// 如果目标行还合并了其他行,需要将这些行也标记为被当前行合并for (let i = 0; i < targetRowspan; i++) {if (targetRowIndex + i < this.spanArr.length) {let nextElementById = document.getElementById('checkResult-' + (targetRowIndex + i))let nextParentNode = nextElementById.parentNode.parentNodenextParentNode.style.display = 'none'this.spanArr[targetRowIndex + i] = 0;}}console.log(this.spanArr);this.contextMenu.visible = false;},// 拆分单元格splitCells() {const { rowIndex } = this.contextMenu;if (rowIndex === -1) return;const currentRowspan = this.spanArr[rowIndex];console.log("当前行行数", rowIndex,"当前行的所占行数",currentRowspan);// 如果当前单元格没有合并其他行,则无需拆分if (currentRowspan <= 1) {this.$message.warning('当前单元格未合并其他行');this.contextMenu.visible = false;return;}// 获取当前行的元素并设置rowSpan为1const elementById = document.getElementById('checkResult-' + rowIndex);if (elementById) {const parentNode = elementById.parentNode.parentNode;parentNode.rowSpan = 1;}// 将当前行的rowspan重置为1this.spanArr[rowIndex] = 1;// 恢复被合并行的rowspan为1for (let i = 1; i < currentRowspan; i++) {const targetIndex = rowIndex + i;// 显示被隐藏的单元格const nextElementById = document.getElementById('checkResult-' + targetIndex);if (nextElementById) {const nextParentNode = nextElementById.parentNode.parentNode;nextParentNode.style.display = 'table-cell';nextParentNode.rowSpan = 1;}// 恢复被合并行的span值为1this.spanArr[targetIndex] = 1;this.tableData[targetIndex][this.columns[this.colIndex].prop] = this.tableData[rowIndex][this.columns[this.colIndex].prop];}console.log(this.spanArr);this.contextMenu.visible = false;},// 新增行addRow() {const newRow = {};this.columns.forEach(col => {newRow[col.prop] = '';});this.tableData.push(newRow);this.spanArr.push(1);},// 删除行deleteRow(index) {// 如果该行是被合并的行,则不允许删除if (this.spanArr[index] === 0) {this.$message.warning('不能删除被合并的行,请先拆分单元格');return;}// 如果是合并的起始行,需要先拆分if (this.spanArr[index] > 1) {// 设置当前行索引用于拆分this.contextMenu.rowIndex = index;// 执行拆分this.splitCells();}this.tableData.splice(index, 1)this.spanArr.splice(index, 1)}}
}
</script><style scoped>
.context-menu {position: fixed;z-index: 9999;background: #fff;border: 1px solid #ebeef5;border-radius: 4px;box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);padding: 10px;display: flex;flex-direction: column;
}.menu-item {padding: 8px 20px;cursor: pointer;color: #606266;
}.menu-item:hover {background: #f5f7fa;color: #409eff;
}
</style>
http://www.lryc.cn/news/606008.html

相关文章:

  • 树形DP-核心基础
  • DVD特工总部,DVD管理系统
  • 如何在 Ubuntu 24.04 或 22.04 LTS Linux 上安装 DaVinci Resolve
  • 【01】大恒相机SDK C++开发 —— 初始化相机,采集第一帧图像、回调采集、关闭相机
  • FastAPI的请求-响应周期为何需要后台任务分离?
  • Spire.XLS for .NET 中, 将 Excel 转换为 PDF 时, 如何设置纸张大小为A4纸,并将excel内容分页放置?
  • VBA代码解决方案第二十七讲:禁用EXCEL工作簿右上角的关闭按钮
  • 微信小程序性能优化与内存管理
  • 辐射源定位方法简述
  • 【25-cv-08807】David携Tyrone Acierto 雕塑版权发案
  • ros2--参数指令--rqt
  • sqli-labs:Less-16关卡详细解析
  • 揭秘动态测试:软件质量的实战防线
  • vue+elementui实现问卷调查配置可单选、多选、解答
  • 代码随想录day51图论2
  • Elasticsearch DSL 核心语法大全:match、bool、range、聚合查询实战解析
  • 软件项目中如何编写项目计划书?指南
  • SpringBoot3.x入门到精通系列:1.1 简介与新特性
  • 代码随想录刷题Day21
  • SELinux 核心概念与访问控制机制解析
  • 数据库学习------数据库事务的特性
  • 【计算机组成原理】第二章:数据的表示和运算(上)
  • Python爬虫06_Requests政府采购严重违法失信行为信息记录爬取
  • Android U 软件fota版本后APN更新逻辑
  • CSS入门指南:从选择器到样式布局
  • SQL 中 WHERE 与 HAVING 的用法详解:分组聚合场景下的混用指南
  • Spring AI 系列之二十八 - Spring AI Alibaba-基于Nacos的prompt模版
  • HCIP面试第一章内容总结
  • 【LeetCode 热题 100】4. 寻找两个正序数组的中位数——(解法一)线性扫描
  • 【ARM】PK51关于内存模式的解析与选择